diff options
Diffstat (limited to '')
280 files changed, 80133 insertions, 0 deletions
diff --git a/src/pulsecore/arpa-inet.c b/src/pulsecore/arpa-inet.c new file mode 100644 index 0000000..afea397 --- /dev/null +++ b/src/pulsecore/arpa-inet.c @@ -0,0 +1,106 @@ +/*** + This file is part of PulseAudio. + + 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#if !defined(HAVE_ARPA_INET_H) && defined(OS_IS_WIN32) + +#include <errno.h> + +#include <pulsecore/macro.h> +#include <pulsecore/socket.h> +#include <pulsecore/core-util.h> + +#include "arpa-inet.h" + +const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt) { + struct in_addr *in = (struct in_addr*)src; +#ifdef HAVE_IPV6 + struct in6_addr *in6 = (struct in6_addr*)src; +#endif + + pa_assert(src); + pa_assert(dst); + + switch (af) { + case AF_INET: + pa_snprintf(dst, cnt, "%d.%d.%d.%d", +#ifdef WORDS_BIGENDIAN + (int)(in->s_addr >> 24) & 0xff, + (int)(in->s_addr >> 16) & 0xff, + (int)(in->s_addr >> 8) & 0xff, + (int)(in->s_addr >> 0) & 0xff); +#else + (int)(in->s_addr >> 0) & 0xff, + (int)(in->s_addr >> 8) & 0xff, + (int)(in->s_addr >> 16) & 0xff, + (int)(in->s_addr >> 24) & 0xff); +#endif + break; +#ifdef HAVE_IPV6 + case AF_INET6: + pa_snprintf(dst, cnt, "%x:%x:%x:%x:%x:%x:%x:%x", + in6->s6_addr[ 0] << 8 | in6->s6_addr[ 1], + in6->s6_addr[ 2] << 8 | in6->s6_addr[ 3], + in6->s6_addr[ 4] << 8 | in6->s6_addr[ 5], + in6->s6_addr[ 6] << 8 | in6->s6_addr[ 7], + in6->s6_addr[ 8] << 8 | in6->s6_addr[ 9], + in6->s6_addr[10] << 8 | in6->s6_addr[11], + in6->s6_addr[12] << 8 | in6->s6_addr[13], + in6->s6_addr[14] << 8 | in6->s6_addr[15]); + break; +#endif + default: + errno = EAFNOSUPPORT; + return NULL; + } + + return dst; +} + +int inet_pton(int af, const char *src, void *dst) { + struct in_addr *in = (struct in_addr*)dst; +#ifdef HAVE_IPV6 + struct in6_addr *in6 = (struct in6_addr*)dst; +#endif + + pa_assert(src); + pa_assert(dst); + + switch (af) { + case AF_INET: + in->s_addr = inet_addr(src); + if (in->s_addr == INADDR_NONE) + return 0; + break; +#ifdef HAVE_IPV6 + case AF_INET6: + /* FIXME */ +#endif + default: + errno = EAFNOSUPPORT; + return -1; + } + + return 1; +} + +#endif diff --git a/src/pulsecore/arpa-inet.h b/src/pulsecore/arpa-inet.h new file mode 100644 index 0000000..d940f70 --- /dev/null +++ b/src/pulsecore/arpa-inet.h @@ -0,0 +1,21 @@ +#ifndef fooarpa_inethfoo +#define fooarpa_inethfoo + +#if defined(HAVE_ARPA_INET_H) + +#include <arpa/inet.h> + +#elif defined(OS_IS_WIN32) + +/* On Windows winsock2.h (here included via pulsecore/socket.h) provides most of the functionality of arpa/inet.h, except for + * the inet_ntop and inet_pton functions, which are implemented here. */ + +#include <pulsecore/socket.h> + +const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt); + +int inet_pton(int af, const char *src, void *dst); + +#endif + +#endif diff --git a/src/pulsecore/asyncmsgq.c b/src/pulsecore/asyncmsgq.c new file mode 100644 index 0000000..47371ae --- /dev/null +++ b/src/pulsecore/asyncmsgq.c @@ -0,0 +1,358 @@ +/*** + 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <unistd.h> +#include <errno.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/macro.h> +#include <pulsecore/log.h> +#include <pulsecore/semaphore.h> +#include <pulsecore/macro.h> +#include <pulsecore/mutex.h> +#include <pulsecore/flist.h> + +#include "asyncmsgq.h" + +PA_STATIC_FLIST_DECLARE(asyncmsgq, 0, pa_xfree); +PA_STATIC_FLIST_DECLARE(semaphores, 0, (void(*)(void*)) pa_semaphore_free); + +struct asyncmsgq_item { + int code; + pa_msgobject *object; + void *userdata; + pa_free_cb_t free_cb; + int64_t offset; + pa_memchunk memchunk; + pa_semaphore *semaphore; + int ret; +}; + +struct pa_asyncmsgq { + PA_REFCNT_DECLARE; + pa_asyncq *asyncq; + pa_mutex *mutex; /* only for the writer side */ + + struct asyncmsgq_item *current; +}; + +pa_asyncmsgq *pa_asyncmsgq_new(unsigned size) { + pa_asyncq *asyncq; + pa_asyncmsgq *a; + + asyncq = pa_asyncq_new(size); + if (!asyncq) + return NULL; + + a = pa_xnew(pa_asyncmsgq, 1); + + PA_REFCNT_INIT(a); + a->asyncq = asyncq; + pa_assert_se(a->mutex = pa_mutex_new(false, true)); + a->current = NULL; + + return a; +} + +static void asyncmsgq_free(pa_asyncmsgq *a) { + struct asyncmsgq_item *i; + pa_assert(a); + + while ((i = pa_asyncq_pop(a->asyncq, false))) { + + pa_assert(!i->semaphore); + + if (i->object) + pa_msgobject_unref(i->object); + + if (i->memchunk.memblock) + pa_memblock_unref(i->memchunk.memblock); + + if (i->free_cb) + i->free_cb(i->userdata); + + if (pa_flist_push(PA_STATIC_FLIST_GET(asyncmsgq), i) < 0) + pa_xfree(i); + } + + pa_asyncq_free(a->asyncq, NULL); + pa_mutex_free(a->mutex); + pa_xfree(a); +} + +pa_asyncmsgq* pa_asyncmsgq_ref(pa_asyncmsgq *q) { + pa_assert(PA_REFCNT_VALUE(q) > 0); + + PA_REFCNT_INC(q); + return q; +} + +void pa_asyncmsgq_unref(pa_asyncmsgq* q) { + pa_assert(PA_REFCNT_VALUE(q) > 0); + + if (PA_REFCNT_DEC(q) <= 0) + asyncmsgq_free(q); +} + +void pa_asyncmsgq_post(pa_asyncmsgq *a, pa_msgobject *object, int code, const void *userdata, int64_t offset, const pa_memchunk *chunk, pa_free_cb_t free_cb) { + struct asyncmsgq_item *i; + pa_assert(PA_REFCNT_VALUE(a) > 0); + + if (!(i = pa_flist_pop(PA_STATIC_FLIST_GET(asyncmsgq)))) + i = pa_xnew(struct asyncmsgq_item, 1); + + i->code = code; + i->object = object ? pa_msgobject_ref(object) : NULL; + i->userdata = (void*) userdata; + i->free_cb = free_cb; + i->offset = offset; + if (chunk) { + pa_assert(chunk->memblock); + i->memchunk = *chunk; + pa_memblock_ref(i->memchunk.memblock); + } else + pa_memchunk_reset(&i->memchunk); + i->semaphore = NULL; + + /* This mutex makes the queue multiple-writer safe. This lock is only used on the writing side */ + pa_mutex_lock(a->mutex); + pa_asyncq_post(a->asyncq, i); + pa_mutex_unlock(a->mutex); +} + +int pa_asyncmsgq_send(pa_asyncmsgq *a, pa_msgobject *object, int code, const void *userdata, int64_t offset, const pa_memchunk *chunk) { + struct asyncmsgq_item i; + pa_assert(PA_REFCNT_VALUE(a) > 0); + + i.code = code; + i.object = object; + i.userdata = (void*) userdata; + i.free_cb = NULL; + i.ret = -1; + i.offset = offset; + if (chunk) { + pa_assert(chunk->memblock); + i.memchunk = *chunk; + } else + pa_memchunk_reset(&i.memchunk); + + if (!(i.semaphore = pa_flist_pop(PA_STATIC_FLIST_GET(semaphores)))) + i.semaphore = pa_semaphore_new(0); + + /* This mutex makes the queue multiple-writer safe. This lock is only used on the writing side */ + pa_mutex_lock(a->mutex); + pa_assert_se(pa_asyncq_push(a->asyncq, &i, true) == 0); + pa_mutex_unlock(a->mutex); + + pa_semaphore_wait(i.semaphore); + + if (pa_flist_push(PA_STATIC_FLIST_GET(semaphores), i.semaphore) < 0) + pa_semaphore_free(i.semaphore); + + return i.ret; +} + +int pa_asyncmsgq_get(pa_asyncmsgq *a, pa_msgobject **object, int *code, void **userdata, int64_t *offset, pa_memchunk *chunk, bool wait_op) { + pa_assert(PA_REFCNT_VALUE(a) > 0); + pa_assert(!a->current); + + if (!(a->current = pa_asyncq_pop(a->asyncq, wait_op))) { +/* pa_log("failure"); */ + return -1; + } + +/* pa_log("success"); */ + + if (code) + *code = a->current->code; + if (userdata) + *userdata = a->current->userdata; + if (offset) + *offset = a->current->offset; + if (object) { + if ((*object = a->current->object)) + pa_msgobject_assert_ref(*object); + } + if (chunk) + *chunk = a->current->memchunk; + +/* pa_log_debug("Get q=%p object=%p (%s) code=%i data=%p chunk.length=%lu", */ +/* (void*) a, */ +/* (void*) a->current->object, */ +/* a->current->object ? a->current->object->parent.type_name : NULL, */ +/* a->current->code, */ +/* (void*) a->current->userdata, */ +/* (unsigned long) a->current->memchunk.length); */ + + return 0; +} + +void pa_asyncmsgq_done(pa_asyncmsgq *a, int ret) { + pa_assert(PA_REFCNT_VALUE(a) > 0); + pa_assert(a); + pa_assert(a->current); + + if (a->current->semaphore) { + a->current->ret = ret; + pa_semaphore_post(a->current->semaphore); + } else { + + if (a->current->free_cb) + a->current->free_cb(a->current->userdata); + + if (a->current->object) + pa_msgobject_unref(a->current->object); + + if (a->current->memchunk.memblock) + pa_memblock_unref(a->current->memchunk.memblock); + + if (pa_flist_push(PA_STATIC_FLIST_GET(asyncmsgq), a->current) < 0) + pa_xfree(a->current); + } + + a->current = NULL; +} + +int pa_asyncmsgq_wait_for(pa_asyncmsgq *a, int code) { + int c; + pa_assert(PA_REFCNT_VALUE(a) > 0); + + pa_asyncmsgq_ref(a); + + do { + pa_msgobject *o; + void *data; + int64_t offset; + pa_memchunk chunk; + int ret; + + if (pa_asyncmsgq_get(a, &o, &c, &data, &offset, &chunk, true) < 0) + return -1; + + ret = pa_asyncmsgq_dispatch(o, c, data, offset, &chunk); + pa_asyncmsgq_done(a, ret); + + } while (c != code); + + pa_asyncmsgq_unref(a); + + return 0; +} + +int pa_asyncmsgq_process_one(pa_asyncmsgq *a) { + pa_msgobject *object; + int code; + void *data; + pa_memchunk chunk; + int64_t offset; + int ret; + + pa_assert(PA_REFCNT_VALUE(a) > 0); + + if (pa_asyncmsgq_get(a, &object, &code, &data, &offset, &chunk, false) < 0) + return 0; + + pa_asyncmsgq_ref(a); + ret = pa_asyncmsgq_dispatch(object, code, data, offset, &chunk); + pa_asyncmsgq_done(a, ret); + pa_asyncmsgq_unref(a); + + return 1; +} + +int pa_asyncmsgq_read_fd(pa_asyncmsgq *a) { + pa_assert(PA_REFCNT_VALUE(a) > 0); + + return pa_asyncq_read_fd(a->asyncq); +} + +int pa_asyncmsgq_read_before_poll(pa_asyncmsgq *a) { + pa_assert(PA_REFCNT_VALUE(a) > 0); + + return pa_asyncq_read_before_poll(a->asyncq); +} + +void pa_asyncmsgq_read_after_poll(pa_asyncmsgq *a) { + pa_assert(PA_REFCNT_VALUE(a) > 0); + + pa_asyncq_read_after_poll(a->asyncq); +} + +int pa_asyncmsgq_write_fd(pa_asyncmsgq *a) { + pa_assert(PA_REFCNT_VALUE(a) > 0); + + return pa_asyncq_write_fd(a->asyncq); +} + +void pa_asyncmsgq_write_before_poll(pa_asyncmsgq *a) { + pa_assert(PA_REFCNT_VALUE(a) > 0); + + pa_asyncq_write_before_poll(a->asyncq); +} + +void pa_asyncmsgq_write_after_poll(pa_asyncmsgq *a) { + pa_assert(PA_REFCNT_VALUE(a) > 0); + + pa_asyncq_write_after_poll(a->asyncq); +} + +int pa_asyncmsgq_dispatch(pa_msgobject *object, int code, void *userdata, int64_t offset, pa_memchunk *memchunk) { + + if (object) + return object->process_msg(object, code, userdata, offset, pa_memchunk_isset(memchunk) ? memchunk : NULL); + + return 0; +} + +void pa_asyncmsgq_flush(pa_asyncmsgq *a, bool run) { + pa_assert(PA_REFCNT_VALUE(a) > 0); + + for (;;) { + pa_msgobject *object; + int code; + void *data; + int64_t offset; + pa_memchunk chunk; + int ret; + + if (pa_asyncmsgq_get(a, &object, &code, &data, &offset, &chunk, false) < 0) + return; + + if (!run) { + pa_asyncmsgq_done(a, -1); + continue; + } + + pa_asyncmsgq_ref(a); + ret = pa_asyncmsgq_dispatch(object, code, data, offset, &chunk); + pa_asyncmsgq_done(a, ret); + pa_asyncmsgq_unref(a); + } +} + +bool pa_asyncmsgq_dispatching(pa_asyncmsgq *a) { + pa_assert(PA_REFCNT_VALUE(a) > 0); + + return !!a->current; +} diff --git a/src/pulsecore/asyncmsgq.h b/src/pulsecore/asyncmsgq.h new file mode 100644 index 0000000..367ccac --- /dev/null +++ b/src/pulsecore/asyncmsgq.h @@ -0,0 +1,81 @@ +#ifndef foopulseasyncmsgqhfoo +#define foopulseasyncmsgqhfoo + +/*** + 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/types.h> + +#include <pulsecore/asyncq.h> +#include <pulsecore/memchunk.h> +#include <pulsecore/msgobject.h> + +/* A simple asynchronous message queue, based on pa_asyncq. In + * contrast to pa_asyncq this one is multiple-writer safe, though + * still not multiple-reader safe. This queue is intended to be used + * for controlling real-time threads from normal-priority + * threads. Multiple-writer-safety is accomplished by using a mutex on + * the writer side. This queue is thus not useful for communication + * between several real-time threads. + * + * The queue takes messages consisting of: + * "Object" for which this messages is intended (may be NULL) + * A numeric message code + * Arbitrary userdata pointer (may be NULL) + * A memchunk (may be NULL) + * + * There are two functions for submitting messages: _post and + * _send. The former just enqueues the message asynchronously, the + * latter waits for completion, synchronously. */ + +enum { + PA_MESSAGE_SHUTDOWN = -1/* A generic message to inform the handler of this queue to quit */ +}; + +typedef struct pa_asyncmsgq pa_asyncmsgq; + +pa_asyncmsgq* pa_asyncmsgq_new(unsigned size); +pa_asyncmsgq* pa_asyncmsgq_ref(pa_asyncmsgq *q); + +void pa_asyncmsgq_unref(pa_asyncmsgq* q); + +void pa_asyncmsgq_post(pa_asyncmsgq *q, pa_msgobject *object, int code, const void *userdata, int64_t offset, const pa_memchunk *memchunk, pa_free_cb_t userdata_free_cb); +int pa_asyncmsgq_send(pa_asyncmsgq *q, pa_msgobject *object, int code, const void *userdata, int64_t offset, const pa_memchunk *memchunk); + +int pa_asyncmsgq_get(pa_asyncmsgq *q, pa_msgobject **object, int *code, void **userdata, int64_t *offset, pa_memchunk *memchunk, bool wait); +int pa_asyncmsgq_dispatch(pa_msgobject *object, int code, void *userdata, int64_t offset, pa_memchunk *memchunk); +void pa_asyncmsgq_done(pa_asyncmsgq *q, int ret); +int pa_asyncmsgq_wait_for(pa_asyncmsgq *a, int code); +int pa_asyncmsgq_process_one(pa_asyncmsgq *a); + +void pa_asyncmsgq_flush(pa_asyncmsgq *a, bool run); + +/* For the reading side */ +int pa_asyncmsgq_read_fd(pa_asyncmsgq *q); +int pa_asyncmsgq_read_before_poll(pa_asyncmsgq *a); +void pa_asyncmsgq_read_after_poll(pa_asyncmsgq *a); + +/* For the write side */ +int pa_asyncmsgq_write_fd(pa_asyncmsgq *q); +void pa_asyncmsgq_write_before_poll(pa_asyncmsgq *a); +void pa_asyncmsgq_write_after_poll(pa_asyncmsgq *a); + +bool pa_asyncmsgq_dispatching(pa_asyncmsgq *a); + +#endif diff --git a/src/pulsecore/asyncq.c b/src/pulsecore/asyncq.c new file mode 100644 index 0000000..53bfe4f --- /dev/null +++ b/src/pulsecore/asyncq.c @@ -0,0 +1,320 @@ +/*** + This file is part of PulseAudio. + + Copyright 2006-2008 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <unistd.h> +#include <errno.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/atomic.h> +#include <pulsecore/log.h> +#include <pulsecore/thread.h> +#include <pulsecore/macro.h> +#include <pulsecore/core-util.h> +#include <pulsecore/llist.h> +#include <pulsecore/flist.h> +#include <pulsecore/fdsem.h> + +#include "asyncq.h" + +#define ASYNCQ_SIZE 256 + +/* For debugging purposes we can define _Y to put an extra thread + * yield between each operation. */ + +/* #define PROFILE */ + +#ifdef PROFILE +#define _Y pa_thread_yield() +#else +#define _Y do { } while(0) +#endif + +struct localq { + void *data; + PA_LLIST_FIELDS(struct localq); +}; + +struct pa_asyncq { + unsigned size; + unsigned read_idx; + unsigned write_idx; + pa_fdsem *read_fdsem, *write_fdsem; + + PA_LLIST_HEAD(struct localq, localq); + struct localq *last_localq; + bool waiting_for_post; +}; + +PA_STATIC_FLIST_DECLARE(localq, 0, pa_xfree); + +#define PA_ASYNCQ_CELLS(x) ((pa_atomic_ptr_t*) ((uint8_t*) (x) + PA_ALIGN(sizeof(struct pa_asyncq)))) + +static unsigned reduce(pa_asyncq *l, unsigned value) { + return value & (unsigned) (l->size - 1); +} + +pa_asyncq *pa_asyncq_new(unsigned size) { + pa_asyncq *l; + + if (!size) + size = ASYNCQ_SIZE; + + pa_assert(pa_is_power_of_two(size)); + + l = pa_xmalloc0(PA_ALIGN(sizeof(pa_asyncq)) + (sizeof(pa_atomic_ptr_t) * size)); + + l->size = size; + + PA_LLIST_HEAD_INIT(struct localq, l->localq); + l->last_localq = NULL; + l->waiting_for_post = false; + + if (!(l->read_fdsem = pa_fdsem_new())) { + pa_xfree(l); + return NULL; + } + + if (!(l->write_fdsem = pa_fdsem_new())) { + pa_fdsem_free(l->read_fdsem); + pa_xfree(l); + return NULL; + } + + return l; +} + +void pa_asyncq_free(pa_asyncq *l, pa_free_cb_t free_cb) { + struct localq *q; + pa_assert(l); + + if (free_cb) { + void *p; + + while ((p = pa_asyncq_pop(l, 0))) + free_cb(p); + } + + while ((q = l->localq)) { + if (free_cb) + free_cb(q->data); + + PA_LLIST_REMOVE(struct localq, l->localq, q); + + if (pa_flist_push(PA_STATIC_FLIST_GET(localq), q) < 0) + pa_xfree(q); + } + + pa_fdsem_free(l->read_fdsem); + pa_fdsem_free(l->write_fdsem); + pa_xfree(l); +} + +static int push(pa_asyncq*l, void *p, bool wait_op) { + unsigned idx; + pa_atomic_ptr_t *cells; + + pa_assert(l); + pa_assert(p); + + cells = PA_ASYNCQ_CELLS(l); + + _Y; + idx = reduce(l, l->write_idx); + + if (!pa_atomic_ptr_cmpxchg(&cells[idx], NULL, p)) { + + if (!wait_op) + return -1; + +/* pa_log("sleeping on push"); */ + + do { + pa_fdsem_wait(l->read_fdsem); + } while (!pa_atomic_ptr_cmpxchg(&cells[idx], NULL, p)); + } + + _Y; + l->write_idx++; + + pa_fdsem_post(l->write_fdsem); + + return 0; +} + +static bool flush_postq(pa_asyncq *l, bool wait_op) { + struct localq *q; + + pa_assert(l); + + while ((q = l->last_localq)) { + + if (push(l, q->data, wait_op) < 0) + return false; + + l->last_localq = q->prev; + + PA_LLIST_REMOVE(struct localq, l->localq, q); + + if (pa_flist_push(PA_STATIC_FLIST_GET(localq), q) < 0) + pa_xfree(q); + } + + return true; +} + +int pa_asyncq_push(pa_asyncq*l, void *p, bool wait_op) { + pa_assert(l); + + if (!flush_postq(l, wait_op)) + return -1; + + return push(l, p, wait_op); +} + +void pa_asyncq_post(pa_asyncq*l, void *p) { + struct localq *q; + + pa_assert(l); + pa_assert(p); + + if (flush_postq(l, false)) + if (pa_asyncq_push(l, p, false) >= 0) + return; + + /* OK, we couldn't push anything in the queue. So let's queue it + * locally and push it later */ + + if (pa_log_ratelimit(PA_LOG_WARN)) + pa_log_warn("q overrun, queuing locally"); + + if (!(q = pa_flist_pop(PA_STATIC_FLIST_GET(localq)))) + q = pa_xnew(struct localq, 1); + + q->data = p; + PA_LLIST_PREPEND(struct localq, l->localq, q); + + if (!l->last_localq) + l->last_localq = q; + + return; +} + +void* pa_asyncq_pop(pa_asyncq*l, bool wait_op) { + unsigned idx; + void *ret; + pa_atomic_ptr_t *cells; + + pa_assert(l); + + cells = PA_ASYNCQ_CELLS(l); + + _Y; + idx = reduce(l, l->read_idx); + + if (!(ret = pa_atomic_ptr_load(&cells[idx]))) { + + if (!wait_op) + return NULL; + +/* pa_log("sleeping on pop"); */ + + do { + pa_fdsem_wait(l->write_fdsem); + } while (!(ret = pa_atomic_ptr_load(&cells[idx]))); + } + + pa_assert(ret); + + /* Guaranteed to succeed if we only have a single reader */ + pa_assert_se(pa_atomic_ptr_cmpxchg(&cells[idx], ret, NULL)); + + _Y; + l->read_idx++; + + pa_fdsem_post(l->read_fdsem); + + return ret; +} + +int pa_asyncq_read_fd(pa_asyncq *q) { + pa_assert(q); + + return pa_fdsem_get(q->write_fdsem); +} + +int pa_asyncq_read_before_poll(pa_asyncq *l) { + unsigned idx; + pa_atomic_ptr_t *cells; + + pa_assert(l); + + cells = PA_ASYNCQ_CELLS(l); + + _Y; + idx = reduce(l, l->read_idx); + + for (;;) { + if (pa_atomic_ptr_load(&cells[idx])) + return -1; + + if (pa_fdsem_before_poll(l->write_fdsem) >= 0) + return 0; + } +} + +void pa_asyncq_read_after_poll(pa_asyncq *l) { + pa_assert(l); + + pa_fdsem_after_poll(l->write_fdsem); +} + +int pa_asyncq_write_fd(pa_asyncq *q) { + pa_assert(q); + + return pa_fdsem_get(q->read_fdsem); +} + +void pa_asyncq_write_before_poll(pa_asyncq *l) { + pa_assert(l); + + for (;;) { + + if (flush_postq(l, false)) + break; + + if (pa_fdsem_before_poll(l->read_fdsem) >= 0) { + l->waiting_for_post = true; + break; + } + } +} + +void pa_asyncq_write_after_poll(pa_asyncq *l) { + pa_assert(l); + + if (l->waiting_for_post) { + pa_fdsem_after_poll(l->read_fdsem); + l->waiting_for_post = false; + } +} diff --git a/src/pulsecore/asyncq.h b/src/pulsecore/asyncq.h new file mode 100644 index 0000000..8c86762 --- /dev/null +++ b/src/pulsecore/asyncq.h @@ -0,0 +1,64 @@ +#ifndef foopulseasyncqhfoo +#define foopulseasyncqhfoo + +/*** + 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/types.h> +#include <pulse/def.h> +#include <pulsecore/macro.h> + +/* A simple, asynchronous, lock-free (if requested also wait-free) + * queue. Not multiple-reader/multiple-writer safe. If that is + * required both sides can be protected by a mutex each. --- Which is + * not a bad thing in most cases, since this queue is intended for + * communication between a normal thread and a single real-time + * thread. Only the real-time side needs to be lock-free/wait-free. + * + * If the queue is full and another entry shall be pushed, or when the + * queue is empty and another entry shall be popped and the "wait" + * argument is non-zero, the queue will block on a UNIX FIFO object -- + * that will probably require locking on the kernel side -- which + * however is probably not problematic, because we do it only on + * starvation or overload in which case we have to block anyway. */ + +typedef struct pa_asyncq pa_asyncq; + +pa_asyncq* pa_asyncq_new(unsigned size); +void pa_asyncq_free(pa_asyncq* q, pa_free_cb_t free_cb); + +void* pa_asyncq_pop(pa_asyncq *q, bool wait); +int pa_asyncq_push(pa_asyncq *q, void *p, bool wait); + +/* Similar to pa_asyncq_push(), but if the queue is full, postpone the + * appending of the item locally and delay until + * pa_asyncq_before_poll_post() is called. */ +void pa_asyncq_post(pa_asyncq*l, void *p); + +/* For the reading side */ +int pa_asyncq_read_fd(pa_asyncq *q); +int pa_asyncq_read_before_poll(pa_asyncq *a); +void pa_asyncq_read_after_poll(pa_asyncq *a); + +/* For the writing side */ +int pa_asyncq_write_fd(pa_asyncq *q); +void pa_asyncq_write_before_poll(pa_asyncq *a); +void pa_asyncq_write_after_poll(pa_asyncq *a); + +#endif diff --git a/src/pulsecore/atomic.h b/src/pulsecore/atomic.h new file mode 100644 index 0000000..e5c1401 --- /dev/null +++ b/src/pulsecore/atomic.h @@ -0,0 +1,683 @@ +#ifndef foopulseatomichfoo +#define foopulseatomichfoo + +/*** + This file is part of PulseAudio. + + Copyright 2006-2008 Lennart Poettering + Copyright 2008 Nokia Corporation + + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulsecore/macro.h> + +/* + * atomic_ops guarantees us that sizeof(AO_t) == sizeof(void*). It is + * not guaranteed however, that sizeof(AO_t) == sizeof(size_t). + * however very likely. + * + * For now we do only full memory barriers. Eventually we might want + * to support more elaborate memory barriers, in which case we will add + * suffixes to the function names. + * + * On gcc >= 4.1 we use the builtin atomic functions. otherwise we use + * libatomic_ops + */ + +#ifndef PACKAGE +#error "Please include config.h before including this file!" +#endif + +#ifdef HAVE_ATOMIC_BUILTINS + +/* __sync based implementation */ + +typedef struct pa_atomic { + volatile int value; +} pa_atomic_t; + +#define PA_ATOMIC_INIT(v) { .value = (v) } + +#ifdef HAVE_ATOMIC_BUILTINS_MEMORY_MODEL + +/* __atomic based implementation */ + +static inline int pa_atomic_load(const pa_atomic_t *a) { + return __atomic_load_n(&a->value, __ATOMIC_SEQ_CST); +} + +static inline void pa_atomic_store(pa_atomic_t *a, int i) { + __atomic_store_n(&a->value, i, __ATOMIC_SEQ_CST); +} + +#else + +static inline int pa_atomic_load(const pa_atomic_t *a) { + __sync_synchronize(); + return a->value; +} + +static inline void pa_atomic_store(pa_atomic_t *a, int i) { + a->value = i; + __sync_synchronize(); +} + +#endif + + +/* Returns the previously set value */ +static inline int pa_atomic_add(pa_atomic_t *a, int i) { + return __sync_fetch_and_add(&a->value, i); +} + +/* Returns the previously set value */ +static inline int pa_atomic_sub(pa_atomic_t *a, int i) { + return __sync_fetch_and_sub(&a->value, i); +} + +/* Returns the previously set value */ +static inline int pa_atomic_inc(pa_atomic_t *a) { + return pa_atomic_add(a, 1); +} + +/* Returns the previously set value */ +static inline int pa_atomic_dec(pa_atomic_t *a) { + return pa_atomic_sub(a, 1); +} + +/* Returns true when the operation was successful. */ +static inline bool pa_atomic_cmpxchg(pa_atomic_t *a, int old_i, int new_i) { + return __sync_bool_compare_and_swap(&a->value, old_i, new_i); +} + +typedef struct pa_atomic_ptr { + volatile unsigned long value; +} pa_atomic_ptr_t; + +#define PA_ATOMIC_PTR_INIT(v) { .value = (long) (v) } + +#ifdef HAVE_ATOMIC_BUILTINS_MEMORY_MODEL + +/* __atomic based implementation */ + +static inline void* pa_atomic_ptr_load(const pa_atomic_ptr_t *a) { + return (void*) __atomic_load_n(&a->value, __ATOMIC_SEQ_CST); +} + +static inline void pa_atomic_ptr_store(pa_atomic_ptr_t *a, void* p) { + __atomic_store_n(&a->value, (unsigned long) p, __ATOMIC_SEQ_CST); +} + +#else + +static inline void* pa_atomic_ptr_load(const pa_atomic_ptr_t *a) { + __sync_synchronize(); + return (void*) a->value; +} + +static inline void pa_atomic_ptr_store(pa_atomic_ptr_t *a, void *p) { + a->value = (unsigned long) p; + __sync_synchronize(); +} + +#endif + +static inline bool pa_atomic_ptr_cmpxchg(pa_atomic_ptr_t *a, void *old_p, void* new_p) { + return __sync_bool_compare_and_swap(&a->value, (long) old_p, (long) new_p); +} + +#elif defined(__NetBSD__) && defined(HAVE_SYS_ATOMIC_H) + +/* NetBSD 5.0+ atomic_ops(3) implementation */ + +#include <sys/atomic.h> + +typedef struct pa_atomic { + volatile unsigned int value; +} pa_atomic_t; + +#define PA_ATOMIC_INIT(v) { .value = (unsigned int) (v) } + +static inline int pa_atomic_load(const pa_atomic_t *a) { + membar_sync(); + return (int) a->value; +} + +static inline void pa_atomic_store(pa_atomic_t *a, int i) { + a->value = (unsigned int) i; + membar_sync(); +} + +/* Returns the previously set value */ +static inline int pa_atomic_add(pa_atomic_t *a, int i) { + int nv = (int) atomic_add_int_nv(&a->value, i); + return nv - i; +} + +/* Returns the previously set value */ +static inline int pa_atomic_sub(pa_atomic_t *a, int i) { + int nv = (int) atomic_add_int_nv(&a->value, -i); + return nv + i; +} + +/* Returns the previously set value */ +static inline int pa_atomic_inc(pa_atomic_t *a) { + int nv = (int) atomic_inc_uint_nv(&a->value); + return nv - 1; +} + +/* Returns the previously set value */ +static inline int pa_atomic_dec(pa_atomic_t *a) { + int nv = (int) atomic_dec_uint_nv(&a->value); + return nv + 1; +} + +/* Returns true when the operation was successful. */ +static inline bool pa_atomic_cmpxchg(pa_atomic_t *a, int old_i, int new_i) { + unsigned int r = atomic_cas_uint(&a->value, (unsigned int) old_i, (unsigned int) new_i); + return (int) r == old_i; +} + +typedef struct pa_atomic_ptr { + volatile void *value; +} pa_atomic_ptr_t; + +#define PA_ATOMIC_PTR_INIT(v) { .value = (v) } + +static inline void* pa_atomic_ptr_load(const pa_atomic_ptr_t *a) { + membar_sync(); + return (void *) a->value; +} + +static inline void pa_atomic_ptr_store(pa_atomic_ptr_t *a, void *p) { + a->value = p; + membar_sync(); +} + +static inline bool pa_atomic_ptr_cmpxchg(pa_atomic_ptr_t *a, void *old_p, void* new_p) { + void *r = atomic_cas_ptr(&a->value, old_p, new_p); + return r == old_p; +} + +#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) + +#include <sys/cdefs.h> +#include <sys/types.h> +#include <sys/param.h> +#include <machine/atomic.h> + +#if __FreeBSD_version < 600000 +#if defined(__i386__) || defined(__amd64__) +#if defined(__amd64__) +#define atomic_load_acq_64 atomic_load_acq_long +#endif +static inline u_int atomic_fetchadd_int(volatile u_int *p, u_int v) { + __asm __volatile( + " " __XSTRING(MPLOCKED) " " + " xaddl %0, %1 ; " + "# atomic_fetchadd_int" + : "+r" (v), + "=m" (*p) + : "m" (*p)); + + return (v); +} +#elif defined(__sparc__) && defined(__arch64__) +#define atomic_load_acq_64 atomic_load_acq_long +#define atomic_fetchadd_int atomic_add_int +#elif defined(__ia64__) +#define atomic_load_acq_64 atomic_load_acq_long +static inline uint32_t +atomic_fetchadd_int(volatile uint32_t *p, uint32_t v) { + uint32_t value; + + do { + value = *p; + } while (!atomic_cmpset_32(p, value, value + v)); + return (value); +} +#endif +#endif + +typedef struct pa_atomic { + volatile unsigned long value; +} pa_atomic_t; + +#define PA_ATOMIC_INIT(v) { .value = (v) } + +static inline int pa_atomic_load(const pa_atomic_t *a) { + return (int) atomic_load_acq_int((unsigned int *) &a->value); +} + +static inline void pa_atomic_store(pa_atomic_t *a, int i) { + atomic_store_rel_int((unsigned int *) &a->value, i); +} + +static inline int pa_atomic_add(pa_atomic_t *a, int i) { + return atomic_fetchadd_int((unsigned int *) &a->value, i); +} + +static inline int pa_atomic_sub(pa_atomic_t *a, int i) { + return atomic_fetchadd_int((unsigned int *) &a->value, -(i)); +} + +static inline int pa_atomic_inc(pa_atomic_t *a) { + return atomic_fetchadd_int((unsigned int *) &a->value, 1); +} + +static inline int pa_atomic_dec(pa_atomic_t *a) { + return atomic_fetchadd_int((unsigned int *) &a->value, -1); +} + +static inline int pa_atomic_cmpxchg(pa_atomic_t *a, int old_i, int new_i) { + return atomic_cmpset_int((unsigned int *) &a->value, old_i, new_i); +} + +typedef struct pa_atomic_ptr { + volatile unsigned long value; +} pa_atomic_ptr_t; + +#define PA_ATOMIC_PTR_INIT(v) { .value = (unsigned long) (v) } + +static inline void* pa_atomic_ptr_load(const pa_atomic_ptr_t *a) { +#ifdef atomic_load_acq_64 + return (void*) atomic_load_acq_ptr((unsigned long *) &a->value); +#else + return (void*) atomic_load_acq_ptr((unsigned int *) &a->value); +#endif +} + +static inline void pa_atomic_ptr_store(pa_atomic_ptr_t *a, void *p) { +#ifdef atomic_load_acq_64 + atomic_store_rel_ptr(&a->value, (unsigned long) p); +#else + atomic_store_rel_ptr((unsigned int *) &a->value, (unsigned int) p); +#endif +} + +static inline int pa_atomic_ptr_cmpxchg(pa_atomic_ptr_t *a, void *old_p, void* new_p) { +#ifdef atomic_load_acq_64 + return atomic_cmpset_ptr(&a->value, (unsigned long) old_p, (unsigned long) new_p); +#else + return atomic_cmpset_ptr((unsigned int *) &a->value, (unsigned int) old_p, (unsigned int) new_p); +#endif +} + +#elif defined(__GNUC__) && (defined(__amd64__) || defined(__x86_64__)) + +#warn "The native atomic operations implementation for AMD64 has not been tested thoroughly. libatomic_ops is known to not work properly on AMD64 and your gcc version is too old for the gcc-builtin atomic ops support. You have three options now: test the native atomic operations implementation for AMD64, fix libatomic_ops, or upgrade your GCC." + +/* Adapted from glibc */ + +typedef struct pa_atomic { + volatile int value; +} pa_atomic_t; + +#define PA_ATOMIC_INIT(v) { .value = (v) } + +static inline int pa_atomic_load(const pa_atomic_t *a) { + return a->value; +} + +static inline void pa_atomic_store(pa_atomic_t *a, int i) { + a->value = i; +} + +static inline int pa_atomic_add(pa_atomic_t *a, int i) { + int result; + + __asm __volatile ("lock; xaddl %0, %1" + : "=r" (result), "=m" (a->value) + : "0" (i), "m" (a->value)); + + return result; +} + +static inline int pa_atomic_sub(pa_atomic_t *a, int i) { + return pa_atomic_add(a, -i); +} + +static inline int pa_atomic_inc(pa_atomic_t *a) { + return pa_atomic_add(a, 1); +} + +static inline int pa_atomic_dec(pa_atomic_t *a) { + return pa_atomic_sub(a, 1); +} + +static inline bool pa_atomic_cmpxchg(pa_atomic_t *a, int old_i, int new_i) { + int result; + + __asm__ __volatile__ ("lock; cmpxchgl %2, %1" + : "=a" (result), "=m" (a->value) + : "r" (new_i), "m" (a->value), "0" (old_i)); + + return result == old_i; +} + +typedef struct pa_atomic_ptr { + volatile unsigned long value; +} pa_atomic_ptr_t; + +#define PA_ATOMIC_PTR_INIT(v) { .value = (long) (v) } + +static inline void* pa_atomic_ptr_load(const pa_atomic_ptr_t *a) { + return (void*) a->value; +} + +static inline void pa_atomic_ptr_store(pa_atomic_ptr_t *a, void *p) { + a->value = (unsigned long) p; +} + +static inline bool pa_atomic_ptr_cmpxchg(pa_atomic_ptr_t *a, void *old_p, void* new_p) { + void *result; + + __asm__ __volatile__ ("lock; cmpxchgq %q2, %1" + : "=a" (result), "=m" (a->value) + : "r" (new_p), "m" (a->value), "0" (old_p)); + + return result == old_p; +} + +#elif defined(ATOMIC_ARM_INLINE_ASM) + +/* + These should only be enabled if we have ARMv6 or better. +*/ + +typedef struct pa_atomic { + volatile int value; +} pa_atomic_t; + +#define PA_ATOMIC_INIT(v) { .value = (v) } + +static inline void pa_memory_barrier(void) { +#ifdef ATOMIC_ARM_MEMORY_BARRIER_ENABLED + asm volatile ("mcr p15, 0, r0, c7, c10, 5 @ dmb"); +#endif +} + +static inline int pa_atomic_load(const pa_atomic_t *a) { + pa_memory_barrier(); + return a->value; +} + +static inline void pa_atomic_store(pa_atomic_t *a, int i) { + a->value = i; + pa_memory_barrier(); +} + +/* Returns the previously set value */ +static inline int pa_atomic_add(pa_atomic_t *a, int i) { + unsigned long not_exclusive; + int new_val, old_val; + + pa_memory_barrier(); + do { + asm volatile ("ldrex %0, [%3]\n" + "add %2, %0, %4\n" + "strex %1, %2, [%3]\n" + : "=&r" (old_val), "=&r" (not_exclusive), "=&r" (new_val) + : "r" (&a->value), "Ir" (i) + : "cc"); + } while(not_exclusive); + pa_memory_barrier(); + + return old_val; +} + +/* Returns the previously set value */ +static inline int pa_atomic_sub(pa_atomic_t *a, int i) { + unsigned long not_exclusive; + int new_val, old_val; + + pa_memory_barrier(); + do { + asm volatile ("ldrex %0, [%3]\n" + "sub %2, %0, %4\n" + "strex %1, %2, [%3]\n" + : "=&r" (old_val), "=&r" (not_exclusive), "=&r" (new_val) + : "r" (&a->value), "Ir" (i) + : "cc"); + } while(not_exclusive); + pa_memory_barrier(); + + return old_val; +} + +static inline int pa_atomic_inc(pa_atomic_t *a) { + return pa_atomic_add(a, 1); +} + +static inline int pa_atomic_dec(pa_atomic_t *a) { + return pa_atomic_sub(a, 1); +} + +static inline bool pa_atomic_cmpxchg(pa_atomic_t *a, int old_i, int new_i) { + unsigned long not_equal, not_exclusive; + + pa_memory_barrier(); + do { + asm volatile ("ldrex %0, [%2]\n" + "subs %0, %0, %3\n" + "mov %1, %0\n" + "strexeq %0, %4, [%2]\n" + : "=&r" (not_exclusive), "=&r" (not_equal) + : "r" (&a->value), "Ir" (old_i), "r" (new_i) + : "cc"); + } while(not_exclusive && !not_equal); + pa_memory_barrier(); + + return !not_equal; +} + +typedef struct pa_atomic_ptr { + volatile unsigned long value; +} pa_atomic_ptr_t; + +#define PA_ATOMIC_PTR_INIT(v) { .value = (long) (v) } + +static inline void* pa_atomic_ptr_load(const pa_atomic_ptr_t *a) { + pa_memory_barrier(); + return (void*) a->value; +} + +static inline void pa_atomic_ptr_store(pa_atomic_ptr_t *a, void *p) { + a->value = (unsigned long) p; + pa_memory_barrier(); +} + +static inline bool pa_atomic_ptr_cmpxchg(pa_atomic_ptr_t *a, void *old_p, void* new_p) { + unsigned long not_equal, not_exclusive; + + pa_memory_barrier(); + do { + asm volatile ("ldrex %0, [%2]\n" + "subs %0, %0, %3\n" + "mov %1, %0\n" + "strexeq %0, %4, [%2]\n" + : "=&r" (not_exclusive), "=&r" (not_equal) + : "r" (&a->value), "Ir" (old_p), "r" (new_p) + : "cc"); + } while(not_exclusive && !not_equal); + pa_memory_barrier(); + + return !not_equal; +} + +#elif defined(ATOMIC_ARM_LINUX_HELPERS) + +/* See file arch/arm/kernel/entry-armv.S in your kernel sources for more + information about these functions. The arm kernel helper functions first + appeared in 2.6.16. + Apply --disable-atomic-arm-linux-helpers flag to configure if you prefer + inline asm implementation or you have an obsolete Linux kernel. +*/ +/* Memory barrier */ +typedef void (__kernel_dmb_t)(void); +#define __kernel_dmb (*(__kernel_dmb_t *)0xffff0fa0) + +static inline void pa_memory_barrier(void) { +#ifndef ATOMIC_ARM_MEMORY_BARRIER_ENABLED + __kernel_dmb(); +#endif +} + +/* Atomic exchange (__kernel_cmpxchg_t contains memory barriers if needed) */ +typedef int (__kernel_cmpxchg_t)(int oldval, int newval, volatile int *ptr); +#define __kernel_cmpxchg (*(__kernel_cmpxchg_t *)0xffff0fc0) + +/* This is just to get rid of all warnings */ +typedef int (__kernel_cmpxchg_u_t)(unsigned long oldval, unsigned long newval, volatile unsigned long *ptr); +#define __kernel_cmpxchg_u (*(__kernel_cmpxchg_u_t *)0xffff0fc0) + +typedef struct pa_atomic { + volatile int value; +} pa_atomic_t; + +#define PA_ATOMIC_INIT(v) { .value = (v) } + +static inline int pa_atomic_load(const pa_atomic_t *a) { + pa_memory_barrier(); + return a->value; +} + +static inline void pa_atomic_store(pa_atomic_t *a, int i) { + a->value = i; + pa_memory_barrier(); +} + +/* Returns the previously set value */ +static inline int pa_atomic_add(pa_atomic_t *a, int i) { + int old_val; + do { + old_val = a->value; + } while(__kernel_cmpxchg(old_val, old_val + i, &a->value)); + return old_val; +} + +/* Returns the previously set value */ +static inline int pa_atomic_sub(pa_atomic_t *a, int i) { + int old_val; + do { + old_val = a->value; + } while(__kernel_cmpxchg(old_val, old_val - i, &a->value)); + return old_val; +} + +/* Returns the previously set value */ +static inline int pa_atomic_inc(pa_atomic_t *a) { + return pa_atomic_add(a, 1); +} + +/* Returns the previously set value */ +static inline int pa_atomic_dec(pa_atomic_t *a) { + return pa_atomic_sub(a, 1); +} + +/* Returns true when the operation was successful. */ +static inline bool pa_atomic_cmpxchg(pa_atomic_t *a, int old_i, int new_i) { + bool failed; + do { + failed = !!__kernel_cmpxchg(old_i, new_i, &a->value); + } while(failed && a->value == old_i); + return !failed; +} + +typedef struct pa_atomic_ptr { + volatile unsigned long value; +} pa_atomic_ptr_t; + +#define PA_ATOMIC_PTR_INIT(v) { .value = (unsigned long) (v) } + +static inline void* pa_atomic_ptr_load(const pa_atomic_ptr_t *a) { + pa_memory_barrier(); + return (void*) a->value; +} + +static inline void pa_atomic_ptr_store(pa_atomic_ptr_t *a, void *p) { + a->value = (unsigned long) p; + pa_memory_barrier(); +} + +static inline bool pa_atomic_ptr_cmpxchg(pa_atomic_ptr_t *a, void *old_p, void* new_p) { + bool failed; + do { + failed = !!__kernel_cmpxchg_u((unsigned long) old_p, (unsigned long) new_p, &a->value); + } while(failed && a->value == (unsigned long) old_p); + return !failed; +} + +#else + +/* libatomic_ops based implementation */ + +#include <atomic_ops.h> + +typedef struct pa_atomic { + volatile AO_t value; +} pa_atomic_t; + +#define PA_ATOMIC_INIT(v) { .value = (AO_t) (v) } + +static inline int pa_atomic_load(const pa_atomic_t *a) { + return (int) AO_load_full((AO_t*) &a->value); +} + +static inline void pa_atomic_store(pa_atomic_t *a, int i) { + AO_store_full(&a->value, (AO_t) i); +} + +static inline int pa_atomic_add(pa_atomic_t *a, int i) { + return (int) AO_fetch_and_add_full(&a->value, (AO_t) i); +} + +static inline int pa_atomic_sub(pa_atomic_t *a, int i) { + return (int) AO_fetch_and_add_full(&a->value, (AO_t) -i); +} + +static inline int pa_atomic_inc(pa_atomic_t *a) { + return (int) AO_fetch_and_add1_full(&a->value); +} + +static inline int pa_atomic_dec(pa_atomic_t *a) { + return (int) AO_fetch_and_sub1_full(&a->value); +} + +static inline bool pa_atomic_cmpxchg(pa_atomic_t *a, int old_i, int new_i) { + return AO_compare_and_swap_full(&a->value, (unsigned long) old_i, (unsigned long) new_i); +} + +typedef struct pa_atomic_ptr { + volatile AO_t value; +} pa_atomic_ptr_t; + +#define PA_ATOMIC_PTR_INIT(v) { .value = (AO_t) (v) } + +static inline void* pa_atomic_ptr_load(const pa_atomic_ptr_t *a) { + return (void*) AO_load_full((AO_t*) &a->value); +} + +static inline void pa_atomic_ptr_store(pa_atomic_ptr_t *a, void *p) { + AO_store_full(&a->value, (AO_t) p); +} + +static inline bool pa_atomic_ptr_cmpxchg(pa_atomic_ptr_t *a, void *old_p, void* new_p) { + return AO_compare_and_swap_full(&a->value, (AO_t) old_p, (AO_t) new_p); +} + +#endif + +#endif diff --git a/src/pulsecore/aupdate.c b/src/pulsecore/aupdate.c new file mode 100644 index 0000000..04d5a57 --- /dev/null +++ b/src/pulsecore/aupdate.c @@ -0,0 +1,135 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/xmalloc.h> + +#include <pulsecore/semaphore.h> +#include <pulsecore/macro.h> +#include <pulsecore/mutex.h> + +#include "aupdate.h" + +#define MSB (1U << (sizeof(unsigned)*8U-1)) +#define WHICH(n) (!!((n) & MSB)) +#define COUNTER(n) ((n) & ~MSB) + +struct pa_aupdate { + pa_atomic_t read_lock; + pa_mutex *write_lock; + pa_semaphore *semaphore; + bool swapped; +}; + +pa_aupdate *pa_aupdate_new(void) { + pa_aupdate *a; + + a = pa_xnew(pa_aupdate, 1); + pa_atomic_store(&a->read_lock, 0); + a->write_lock = pa_mutex_new(false, false); + a->semaphore = pa_semaphore_new(0); + + return a; +} + +void pa_aupdate_free(pa_aupdate *a) { + pa_assert(a); + + pa_mutex_free(a->write_lock); + pa_semaphore_free(a->semaphore); + + pa_xfree(a); +} + +unsigned pa_aupdate_read_begin(pa_aupdate *a) { + unsigned n; + + pa_assert(a); + + /* Increase the lock counter */ + n = (unsigned) pa_atomic_inc(&a->read_lock); + + /* When n is 0 we have about 2^31 threads running that all try to + * access the data at the same time, oh my! */ + pa_assert(COUNTER(n)+1 > 0); + + /* The uppermost bit tells us which data to look at */ + return WHICH(n); +} + +void pa_aupdate_read_end(pa_aupdate *a) { + unsigned PA_UNUSED n; + + pa_assert(a); + + /* Decrease the lock counter */ + n = (unsigned) pa_atomic_dec(&a->read_lock); + + /* Make sure the counter was valid */ + pa_assert(COUNTER(n) > 0); + + /* Post the semaphore */ + pa_semaphore_post(a->semaphore); +} + +unsigned pa_aupdate_write_begin(pa_aupdate *a) { + unsigned n; + + pa_assert(a); + + pa_mutex_lock(a->write_lock); + + n = (unsigned) pa_atomic_load(&a->read_lock); + + a->swapped = false; + + return !WHICH(n); +} + +unsigned pa_aupdate_write_swap(pa_aupdate *a) { + unsigned n; + + pa_assert(a); + + for (;;) { + n = (unsigned) pa_atomic_load(&a->read_lock); + + /* If the read counter is > 0 wait; if it is 0 try to swap the lists */ + if (COUNTER(n) > 0) + pa_semaphore_wait(a->semaphore); + else if (pa_atomic_cmpxchg(&a->read_lock, (int) n, (int) (n ^ MSB))) + break; + } + + a->swapped = true; + + return WHICH(n); +} + +void pa_aupdate_write_end(pa_aupdate *a) { + pa_assert(a); + + if (!a->swapped) + pa_aupdate_write_swap(a); + + pa_mutex_unlock(a->write_lock); +} diff --git a/src/pulsecore/aupdate.h b/src/pulsecore/aupdate.h new file mode 100644 index 0000000..347b853 --- /dev/null +++ b/src/pulsecore/aupdate.h @@ -0,0 +1,100 @@ +#ifndef foopulsecoreaupdatehfoo +#define foopulsecoreaupdatehfoo + +/*** + This file is part of PulseAudio. + + Copyright 2009 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +typedef struct pa_aupdate pa_aupdate; + +pa_aupdate *pa_aupdate_new(void); +void pa_aupdate_free(pa_aupdate *a); + +/* Will return 0, or 1, depending on which copy of the data the caller + * should look at */ +unsigned pa_aupdate_read_begin(pa_aupdate *a); +void pa_aupdate_read_end(pa_aupdate *a); + +/* Will return 0, or 1, depending which copy of the data the caller + * should modify */ +unsigned pa_aupdate_write_begin(pa_aupdate *a); +void pa_aupdate_write_end(pa_aupdate *a); + +/* Will return 0, or 1, depending which copy of the data the caller + * should modify. Each time called this will return the opposite of + * the previous pa_aupdate_write_begin() / pa_aupdate_write_swap() + * call. Should only be called between pa_aupdate_write_begin() and + * pa_aupdate_write_end() */ +unsigned pa_aupdate_write_swap(pa_aupdate *a); + +/* + * This infrastructure allows lock-free updates of arbitrary data + * structures in an rcu'ish way: two copies of the data structure + * should be existing. One side ('the reader') has read access to one + * of the two data structure at a time. It does not have to lock it, + * however it needs to signal that it is using it/stopped using + * it. The other side ('the writer') modifies the second data structure, + * and then atomically swaps the two data structures, followed by a + * modification of the other one. + * + * This is intended to be used for cases where the reader side needs + * to be fast while the writer side can be slow. + * + * The reader side is signal handler safe. + * + * The writer side lock is not recursive. The reader side is. + * + * There may be multiple readers and multiple writers at the same + * time. + * + * Usage is like this: + * + * static struct foo bar[2]; + * static pa_aupdate *a; + * + * reader() { + * unsigned j; + * + * j = pa_update_read_begin(a); + * + * ... read the data structure bar[j] ... + * + * pa_update_read_end(a); + * } + * + * writer() { + * unsigned j; + * + * j = pa_update_write_begin(a); + * + * ... update the data structure bar[j] ... + * + * j = pa_update_write_swap(a); + * + * ... update the data structure bar[j], the same way as above ... + * + * pa_update_write_end(a) + * } + * + * In some cases keeping both structures up-to-date might not be + * necessary, since they are fully rebuilt on each iteration + * anyway. In that case you may leave the _write_swap() call out, it + * will then be done implicitly in the _write_end() invocation. + */ + +#endif diff --git a/src/pulsecore/auth-cookie.c b/src/pulsecore/auth-cookie.c new file mode 100644 index 0000000..248d9bf --- /dev/null +++ b/src/pulsecore/auth-cookie.c @@ -0,0 +1,139 @@ +/*** + This file is part of PulseAudio. + + Copyright 2008 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <sys/types.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/refcnt.h> +#include <pulsecore/macro.h> +#include <pulsecore/core-util.h> +#include <pulsecore/shared.h> +#include <pulsecore/authkey.h> + +#include "auth-cookie.h" + +struct pa_auth_cookie { + PA_REFCNT_DECLARE; + pa_core *core; + char *name; + size_t size; +}; + +pa_auth_cookie* pa_auth_cookie_get(pa_core *core, const char *cn, bool create, size_t size) { + pa_auth_cookie *c; + char *t; + + pa_assert(core); + pa_assert(size > 0); + + t = pa_sprintf_malloc("auth-cookie%s%s", cn ? "@" : "", cn ? cn : ""); + + if ((c = pa_shared_get(core, t))) { + + pa_xfree(t); + + if (c->size != size) + return NULL; + + return pa_auth_cookie_ref(c); + } + + c = pa_xmalloc(PA_ALIGN(sizeof(pa_auth_cookie)) + size); + PA_REFCNT_INIT(c); + c->core = core; + c->name = t; + c->size = size; + + pa_assert_se(pa_shared_set(core, t, c) >= 0); + + if (pa_authkey_load(cn, create, (uint8_t*) c + PA_ALIGN(sizeof(pa_auth_cookie)), size) < 0) { + pa_auth_cookie_unref(c); + return NULL; + } + + return c; +} + +pa_auth_cookie *pa_auth_cookie_create(pa_core *core, const void *data, size_t size) { + pa_auth_cookie *c; + char *t; + + pa_assert(core); + pa_assert(data); + pa_assert(size > 0); + + t = pa_xstrdup("auth-cookie"); + + if ((c = pa_shared_get(core, t))) { + + pa_xfree(t); + + if (c->size != size) + return NULL; + + return pa_auth_cookie_ref(c); + } + + c = pa_xmalloc(PA_ALIGN(sizeof(pa_auth_cookie)) + size); + PA_REFCNT_INIT(c); + c->core = core; + c->name = t; + c->size = size; + + pa_assert_se(pa_shared_set(core, t, c) >= 0); + + memcpy((uint8_t *) c + PA_ALIGN(sizeof(pa_auth_cookie)), data, size); + + return c; +} + +pa_auth_cookie* pa_auth_cookie_ref(pa_auth_cookie *c) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_REFCNT_INC(c); + + return c; +} + +void pa_auth_cookie_unref(pa_auth_cookie *c) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + if (PA_REFCNT_DEC(c) > 0) + return; + + pa_assert_se(pa_shared_remove(c->core, c->name) >= 0); + + pa_xfree(c->name); + pa_xfree(c); +} + +const uint8_t* pa_auth_cookie_read(pa_auth_cookie *c, size_t size) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(c->size == size); + + return (const uint8_t*) c + PA_ALIGN(sizeof(pa_auth_cookie)); +} diff --git a/src/pulsecore/auth-cookie.h b/src/pulsecore/auth-cookie.h new file mode 100644 index 0000000..01f5244 --- /dev/null +++ b/src/pulsecore/auth-cookie.h @@ -0,0 +1,34 @@ +#ifndef fooauthcookiehfoo +#define fooauthcookiehfoo + +/*** + This file is part of PulseAudio. + + Copyright 2008 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulsecore/core.h> + +typedef struct pa_auth_cookie pa_auth_cookie; + +pa_auth_cookie* pa_auth_cookie_get(pa_core *c, const char *cn, bool create, size_t size); +pa_auth_cookie* pa_auth_cookie_create(pa_core *c, const void *data, size_t size); +pa_auth_cookie* pa_auth_cookie_ref(pa_auth_cookie *c); +void pa_auth_cookie_unref(pa_auth_cookie *c); + +const uint8_t* pa_auth_cookie_read(pa_auth_cookie *, size_t size); + +#endif diff --git a/src/pulsecore/authkey.c b/src/pulsecore/authkey.c new file mode 100644 index 0000000..71a9833 --- /dev/null +++ b/src/pulsecore/authkey.c @@ -0,0 +1,208 @@ +/*** + 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <unistd.h> +#include <fcntl.h> +#include <string.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> + +#include <pulse/util.h> +#include <pulse/xmalloc.h> +#include <pulsecore/core-error.h> +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> +#include <pulsecore/random.h> +#include <pulsecore/macro.h> + +#include "authkey.h" + +/* Generate a new authentication key, store it in file fd and return it in *data */ +static int generate(int fd, void *ret_data, size_t length) { + ssize_t r; + + pa_assert(fd >= 0); + pa_assert(ret_data); + pa_assert(length > 0); + + pa_random(ret_data, length); + + lseek(fd, (off_t) 0, SEEK_SET); + if (ftruncate(fd, (off_t) 0) < 0) { + pa_log("Failed to truncate cookie file: %s", pa_cstrerror(errno)); + return -1; + } + + if ((r = pa_loop_write(fd, ret_data, length, NULL)) < 0 || (size_t) r != length) { + pa_log("Failed to write cookie file: %s", pa_cstrerror(errno)); + return -1; + } + + return 0; +} + +#ifndef O_BINARY +#define O_BINARY 0 +#endif + +/* Load an authentication cookie from file fn and store it in data. If + * the cookie file doesn't exist, create it */ +static int load(const char *fn, bool create, void *data, size_t length) { + int fd = -1; + int writable = 1; + int unlock = 0, ret = -1; + ssize_t r; + + pa_assert(fn); + pa_assert(data); + pa_assert(length > 0); + + if (create) + pa_make_secure_parent_dir(fn, pa_in_system_mode() ? 0755U : 0700U, -1, -1, false); + + if ((fd = pa_open_cloexec(fn, (create ? O_RDWR|O_CREAT : O_RDONLY)|O_BINARY, S_IRUSR|S_IWUSR)) < 0) { + + if (!create || errno != EACCES || (fd = open(fn, O_RDONLY|O_BINARY)) < 0) { + pa_log_warn("Failed to open cookie file '%s': %s", fn, pa_cstrerror(errno)); + goto finish; + } else + writable = 0; + } + + unlock = pa_lock_fd(fd, 1) >= 0; + + if ((r = pa_loop_read(fd, data, length, NULL)) < 0) { + pa_log("Failed to read cookie file '%s': %s", fn, pa_cstrerror(errno)); + goto finish; + } + + if ((size_t) r != length) { + pa_log_debug("Got %d bytes from cookie file '%s', expected %d", (int) r, fn, (int) length); + + if (!writable) { + pa_log_warn("Unable to write cookie to read-only file"); + goto finish; + } + + if (generate(fd, data, length) < 0) + goto finish; + } + + ret = 0; + +finish: + + if (fd >= 0) { + + if (unlock) + pa_lock_fd(fd, 0); + + if (pa_close(fd) < 0) { + pa_log_warn("Failed to close cookie file: %s", pa_cstrerror(errno)); + ret = -1; + } + } + + return ret; +} + +/* If the specified file path starts with / return it, otherwise + * return path prepended with the config home directory. */ +static int normalize_path(const char *fn, char **_r) { + pa_assert(fn); + pa_assert(_r); + + if (!pa_is_path_absolute(fn)) + return pa_append_to_config_home_dir(fn, _r); + + *_r = pa_xstrdup(fn); + return 0; +} + +int pa_authkey_load(const char *fn, bool create, void *data, size_t length) { + char *p; + int ret; + + pa_assert(fn); + pa_assert(data); + pa_assert(length > 0); + + if ((ret = normalize_path(fn, &p)) < 0) + return ret; + + if ((ret = load(p, create, data, length)) < 0) + pa_log_warn("Failed to load authentication key '%s': %s", p, (ret < 0) ? pa_cstrerror(errno) : "File corrupt"); + + pa_xfree(p); + + return ret; +} + +/* Store the specified cookie in the specified cookie file */ +int pa_authkey_save(const char *fn, const void *data, size_t length) { + int fd = -1; + int unlock = 0, ret; + ssize_t r; + char *p; + + pa_assert(fn); + pa_assert(data); + pa_assert(length > 0); + + if ((ret = normalize_path(fn, &p)) < 0) + return ret; + + if ((fd = pa_open_cloexec(p, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR)) < 0) { + pa_log_warn("Failed to open cookie file '%s': %s", fn, pa_cstrerror(errno)); + ret = -1; + goto finish; + } + + unlock = pa_lock_fd(fd, 1) >= 0; + + if ((r = pa_loop_write(fd, data, length, NULL)) < 0 || (size_t) r != length) { + pa_log("Failed to read cookie file '%s': %s", fn, pa_cstrerror(errno)); + ret = -1; + goto finish; + } + +finish: + + if (fd >= 0) { + + if (unlock) + pa_lock_fd(fd, 0); + + if (pa_close(fd) < 0) { + pa_log_warn("Failed to close cookie file: %s", pa_cstrerror(errno)); + ret = -1; + } + } + + pa_xfree(p); + + return ret; +} diff --git a/src/pulsecore/authkey.h b/src/pulsecore/authkey.h new file mode 100644 index 0000000..7af9253 --- /dev/null +++ b/src/pulsecore/authkey.h @@ -0,0 +1,29 @@ +#ifndef fooauthkeyhfoo +#define fooauthkeyhfoo + +/*** + 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/types.h> + +int pa_authkey_load(const char *fn, bool create, void *data, size_t length); + +int pa_authkey_save(const char *path, const void *data, size_t length); + +#endif diff --git a/src/pulsecore/avahi-wrap.c b/src/pulsecore/avahi-wrap.c new file mode 100644 index 0000000..dc586cb --- /dev/null +++ b/src/pulsecore/avahi-wrap.c @@ -0,0 +1,193 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/timeval.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/macro.h> + +#include "avahi-wrap.h" + +typedef struct { + AvahiPoll api; + pa_mainloop_api *mainloop; +} pa_avahi_poll; + +struct AvahiWatch { + pa_io_event *io_event; + pa_avahi_poll *avahi_poll; + AvahiWatchEvent current_event; + AvahiWatchCallback callback; + void *userdata; +}; + +static AvahiWatchEvent translate_io_flags_back(pa_io_event_flags_t e) { + return + (e & PA_IO_EVENT_INPUT ? AVAHI_WATCH_IN : 0) | + (e & PA_IO_EVENT_OUTPUT ? AVAHI_WATCH_OUT : 0) | + (e & PA_IO_EVENT_ERROR ? AVAHI_WATCH_ERR : 0) | + (e & PA_IO_EVENT_HANGUP ? AVAHI_WATCH_HUP : 0); +} + +static pa_io_event_flags_t translate_io_flags(AvahiWatchEvent e) { + return + (e & AVAHI_WATCH_IN ? PA_IO_EVENT_INPUT : 0) | + (e & AVAHI_WATCH_OUT ? PA_IO_EVENT_OUTPUT : 0) | + (e & AVAHI_WATCH_ERR ? PA_IO_EVENT_ERROR : 0) | + (e & AVAHI_WATCH_HUP ? PA_IO_EVENT_HANGUP : 0); +} + +static void watch_callback(pa_mainloop_api*a, pa_io_event* e, int fd, pa_io_event_flags_t events, void *userdata) { + AvahiWatch *w = userdata; + + pa_assert(a); + pa_assert(e); + pa_assert(w); + + w->current_event = translate_io_flags_back(events); + w->callback(w, fd, w->current_event, w->userdata); + w->current_event = 0; +} + +static AvahiWatch* watch_new(const AvahiPoll *api, int fd, AvahiWatchEvent event, AvahiWatchCallback callback, void *userdata) { + pa_avahi_poll *p; + AvahiWatch *w; + + pa_assert(api); + pa_assert(fd >= 0); + pa_assert(callback); + pa_assert_se(p = api->userdata); + + w = pa_xnew(AvahiWatch, 1); + w->avahi_poll = p; + w->current_event = 0; + w->callback = callback; + w->userdata = userdata; + w->io_event = p->mainloop->io_new(p->mainloop, fd, translate_io_flags(event), watch_callback, w); + + return w; +} + +static void watch_update(AvahiWatch *w, AvahiWatchEvent event) { + pa_assert(w); + + w->avahi_poll->mainloop->io_enable(w->io_event, translate_io_flags(event)); +} + +static AvahiWatchEvent watch_get_events(AvahiWatch *w) { + pa_assert(w); + + return w->current_event; +} + +static void watch_free(AvahiWatch *w) { + pa_assert(w); + + w->avahi_poll->mainloop->io_free(w->io_event); + pa_xfree(w); +} + +struct AvahiTimeout { + pa_time_event *time_event; + pa_avahi_poll *avahi_poll; + AvahiTimeoutCallback callback; + void *userdata; +}; + +static void timeout_callback(pa_mainloop_api*a, pa_time_event* e, const struct timeval *t, void *userdata) { + AvahiTimeout *to = userdata; + + pa_assert(a); + pa_assert(e); + + to->callback(to, to->userdata); +} + +static AvahiTimeout* timeout_new(const AvahiPoll *api, const struct timeval *tv, AvahiTimeoutCallback callback, void *userdata) { + pa_avahi_poll *p; + AvahiTimeout *t; + + pa_assert(api); + pa_assert(callback); + pa_assert_se(p = api->userdata); + + t = pa_xnew(AvahiTimeout, 1); + t->avahi_poll = p; + t->callback = callback; + t->userdata = userdata; + + t->time_event = tv ? p->mainloop->time_new(p->mainloop, tv, timeout_callback, t) : NULL; + + return t; +} + +static void timeout_update(AvahiTimeout *t, const struct timeval *tv) { + + pa_assert(t); + + if (t->time_event && tv) + t->avahi_poll->mainloop->time_restart(t->time_event, tv); + else if (!t->time_event && tv) + t->time_event = t->avahi_poll->mainloop->time_new(t->avahi_poll->mainloop, tv, timeout_callback, t); + else if (t->time_event && !tv) { + t->avahi_poll->mainloop->time_free(t->time_event); + t->time_event = NULL; + } +} + +static void timeout_free(AvahiTimeout *t) { + pa_assert(t); + + if (t->time_event) + t->avahi_poll->mainloop->time_free(t->time_event); + pa_xfree(t); +} + +AvahiPoll* pa_avahi_poll_new(pa_mainloop_api *m) { + pa_avahi_poll *p; + + pa_assert(m); + + p = pa_xnew(pa_avahi_poll, 1); + + p->api.userdata = p; + p->api.watch_new = watch_new; + p->api.watch_update = watch_update; + p->api.watch_get_events = watch_get_events; + p->api.watch_free = watch_free; + p->api.timeout_new = timeout_new; + p->api.timeout_update = timeout_update; + p->api.timeout_free = timeout_free; + p->mainloop = m; + + return &p->api; +} + +void pa_avahi_poll_free(AvahiPoll *api) { + pa_avahi_poll *p; + pa_assert(api); + pa_assert_se(p = api->userdata); + + pa_xfree(p); +} + diff --git a/src/pulsecore/avahi-wrap.h b/src/pulsecore/avahi-wrap.h new file mode 100644 index 0000000..c7a9719 --- /dev/null +++ b/src/pulsecore/avahi-wrap.h @@ -0,0 +1,30 @@ +#ifndef fooavahiwrapperhfoo +#define fooavahiwrapperhfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <avahi-client/client.h> + +#include <pulse/mainloop-api.h> + +AvahiPoll* pa_avahi_poll_new(pa_mainloop_api *api); +void pa_avahi_poll_free(AvahiPoll *p); + +#endif diff --git a/src/pulsecore/bitset.c b/src/pulsecore/bitset.c new file mode 100644 index 0000000..5c146ca --- /dev/null +++ b/src/pulsecore/bitset.c @@ -0,0 +1,65 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> + +#include <pulse/xmalloc.h> + +#include "bitset.h" + +void pa_bitset_set(pa_bitset_t *b, unsigned k, bool v) { + pa_assert(b); + + if (v) + b[k >> 5] |= 1 << (k & 31); + else + b[k >> 5] &= ~((uint32_t) (1 << (k & 31))); +} + +bool pa_bitset_get(const pa_bitset_t *b, unsigned k) { + return !!(b[k >> 5] & (1 << (k & 31))); +} + +bool pa_bitset_equals(const pa_bitset_t *b, unsigned n, ...) { + va_list ap; + pa_bitset_t *a; + bool equal; + + a = pa_xnew0(pa_bitset_t, PA_BITSET_ELEMENTS(n)); + + va_start(ap, n); + for (;;) { + int j = va_arg(ap, int); + + if (j < 0) + break; + + pa_bitset_set(a, j, true); + } + va_end(ap); + + equal = memcmp(a, b, PA_BITSET_SIZE(n)) == 0; + pa_xfree(a); + + return equal; +} diff --git a/src/pulsecore/bitset.h b/src/pulsecore/bitset.h new file mode 100644 index 0000000..32fa82d --- /dev/null +++ b/src/pulsecore/bitset.h @@ -0,0 +1,35 @@ +#ifndef foopulsecorebitsethfoo +#define foopulsecorebitsethfoo + +/*** + This file is part of PulseAudio. + + Copyright 2009 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <inttypes.h> +#include <pulsecore/macro.h> + +#define PA_BITSET_ELEMENTS(n) (((n)+31)/32) +#define PA_BITSET_SIZE(n) (PA_BITSET_ELEMENTS(n)*4) + +typedef uint32_t pa_bitset_t; + +void pa_bitset_set(pa_bitset_t *b, unsigned k, bool v); +bool pa_bitset_get(const pa_bitset_t *b, unsigned k); +bool pa_bitset_equals(const pa_bitset_t *b, unsigned n, ...); + +#endif diff --git a/src/pulsecore/card.c b/src/pulsecore/card.c new file mode 100644 index 0000000..6989596 --- /dev/null +++ b/src/pulsecore/card.c @@ -0,0 +1,425 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <pulse/xmalloc.h> +#include <pulse/util.h> + +#include <pulsecore/log.h> +#include <pulsecore/macro.h> +#include <pulsecore/core-util.h> +#include <pulsecore/namereg.h> +#include <pulsecore/device-port.h> + +#include "card.h" + +const char *pa_available_to_string(pa_available_t available) { + switch (available) { + case PA_AVAILABLE_UNKNOWN: + return "unknown"; + case PA_AVAILABLE_NO: + return "no"; + case PA_AVAILABLE_YES: + return "yes"; + default: + pa_assert_not_reached(); + } +} + +pa_card_profile *pa_card_profile_new(const char *name, const char *description, size_t extra) { + pa_card_profile *c; + + pa_assert(name); + + c = pa_xmalloc0(PA_ALIGN(sizeof(pa_card_profile)) + extra); + c->name = pa_xstrdup(name); + c->description = pa_xstrdup(description); + c->available = PA_AVAILABLE_UNKNOWN; + + return c; +} + +void pa_card_profile_free(pa_card_profile *c) { + pa_assert(c); + + pa_xfree(c->input_name); + pa_xfree(c->output_name); + pa_xfree(c->name); + pa_xfree(c->description); + pa_xfree(c); +} + +void pa_card_profile_set_available(pa_card_profile *c, pa_available_t available) { + pa_core *core; + + pa_assert(c); + pa_assert(c->card); /* Modify member variable directly during creation instead of using this function */ + + if (c->available == available) + return; + + c->available = available; + pa_log_debug("Setting card %s profile %s to availability status %s", c->card->name, c->name, + pa_available_to_string(available)); + + /* Post subscriptions to the card which owns us */ + pa_assert_se(core = c->card->core); + pa_subscription_post(core, PA_SUBSCRIPTION_EVENT_CARD|PA_SUBSCRIPTION_EVENT_CHANGE, c->card->index); + + if (c->card->linked) + pa_hook_fire(&core->hooks[PA_CORE_HOOK_CARD_PROFILE_AVAILABLE_CHANGED], c); +} + +pa_card_new_data* pa_card_new_data_init(pa_card_new_data *data) { + pa_assert(data); + + memset(data, 0, sizeof(*data)); + data->proplist = pa_proplist_new(); + data->profiles = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, (pa_free_cb_t) pa_card_profile_free); + data->ports = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, (pa_free_cb_t) pa_device_port_unref); + return data; +} + +void pa_card_new_data_set_name(pa_card_new_data *data, const char *name) { + pa_assert(data); + + pa_xfree(data->name); + data->name = pa_xstrdup(name); +} + +void pa_card_new_data_set_preferred_port(pa_card_new_data *data, pa_direction_t direction, pa_device_port *port) { + pa_assert(data); + + if (direction == PA_DIRECTION_INPUT) + data->preferred_input_port = port; + else + data->preferred_output_port = port; +} + +void pa_card_new_data_done(pa_card_new_data *data) { + + pa_assert(data); + + pa_proplist_free(data->proplist); + + if (data->profiles) + pa_hashmap_free(data->profiles); + + if (data->ports) + pa_hashmap_free(data->ports); + + pa_xfree(data->name); +} + +pa_card *pa_card_new(pa_core *core, pa_card_new_data *data) { + pa_card *c; + const char *name; + void *state; + pa_card_profile *profile; + pa_device_port *port; + + pa_core_assert_ref(core); + pa_assert(data); + pa_assert(data->name); + pa_assert(data->profiles); + pa_assert(!pa_hashmap_isempty(data->profiles)); + + c = pa_xnew0(pa_card, 1); + + if (!(name = pa_namereg_register(core, data->name, PA_NAMEREG_CARD, c, data->namereg_fail))) { + pa_xfree(c); + return NULL; + } + + pa_card_new_data_set_name(data, name); + pa_hook_fire(&core->hooks[PA_CORE_HOOK_CARD_NEW], data); + + c->core = core; + c->name = pa_xstrdup(data->name); + c->proplist = pa_proplist_copy(data->proplist); + c->driver = pa_xstrdup(pa_path_get_filename(data->driver)); + c->module = data->module; + + c->sinks = pa_idxset_new(NULL, NULL); + c->sources = pa_idxset_new(NULL, NULL); + + /* As a minor optimization we just steal the list instead of + * copying it here */ + pa_assert_se(c->profiles = data->profiles); + data->profiles = NULL; + pa_assert_se(c->ports = data->ports); + data->ports = NULL; + + PA_HASHMAP_FOREACH(profile, c->profiles, state) + profile->card = c; + + PA_HASHMAP_FOREACH(port, c->ports, state) + port->card = c; + + c->preferred_input_port = data->preferred_input_port; + c->preferred_output_port = data->preferred_output_port; + + pa_device_init_description(c->proplist, c); + pa_device_init_icon(c->proplist, true); + pa_device_init_intended_roles(c->proplist); + + return c; +} + +void pa_card_choose_initial_profile(pa_card *card) { + pa_card_profile *profile; + void *state; + pa_card_profile *best = NULL; + + pa_assert(card); + + /* By default, pick the highest priority profile that is not unavailable, + * or if all profiles are unavailable, pick the profile with the highest + * priority regardless of its availability. */ + + pa_log_debug("Looking for initial profile for card %s", card->name); + PA_HASHMAP_FOREACH(profile, card->profiles, state) { + pa_log_debug("%s availability %s", profile->name, pa_available_to_string(profile->available)); + if (profile->available == PA_AVAILABLE_NO) + continue; + + if (!best || profile->priority > best->priority) + best = profile; + } + + if (!best) { + PA_HASHMAP_FOREACH(profile, card->profiles, state) { + if (!best || profile->priority > best->priority) + best = profile; + } + } + pa_assert(best); + + card->active_profile = best; + card->save_profile = false; + pa_log_info("%s: active_profile: %s", card->name, card->active_profile->name); + + /* Let policy modules override the default. */ + pa_hook_fire(&card->core->hooks[PA_CORE_HOOK_CARD_CHOOSE_INITIAL_PROFILE], card); +} + +void pa_card_put(pa_card *card) { + pa_assert(card); + + pa_assert_se(pa_idxset_put(card->core->cards, card, &card->index) >= 0); + card->linked = true; + + pa_log_info("Created %u \"%s\"", card->index, card->name); + pa_hook_fire(&card->core->hooks[PA_CORE_HOOK_CARD_PUT], card); + pa_subscription_post(card->core, PA_SUBSCRIPTION_EVENT_CARD|PA_SUBSCRIPTION_EVENT_NEW, card->index); +} + +void pa_card_free(pa_card *c) { + pa_core *core; + + pa_assert(c); + pa_assert(c->core); + + core = c->core; + + if (c->linked) { + pa_hook_fire(&core->hooks[PA_CORE_HOOK_CARD_UNLINK], c); + + pa_idxset_remove_by_data(c->core->cards, c, NULL); + pa_log_info("Freed %u \"%s\"", c->index, c->name); + pa_subscription_post(c->core, PA_SUBSCRIPTION_EVENT_CARD|PA_SUBSCRIPTION_EVENT_REMOVE, c->index); + } + + pa_namereg_unregister(core, c->name); + + pa_assert(pa_idxset_isempty(c->sinks)); + pa_idxset_free(c->sinks, NULL); + pa_assert(pa_idxset_isempty(c->sources)); + pa_idxset_free(c->sources, NULL); + + pa_hashmap_free(c->ports); + + if (c->profiles) + pa_hashmap_free(c->profiles); + + pa_proplist_free(c->proplist); + pa_xfree(c->driver); + pa_xfree(c->name); + pa_xfree(c); +} + +void pa_card_add_profile(pa_card *c, pa_card_profile *profile) { + pa_assert(c); + pa_assert(profile); + + /* take ownership of the profile */ + pa_assert_se(pa_hashmap_put(c->profiles, profile->name, profile) >= 0); + profile->card = c; + + pa_subscription_post(c->core, PA_SUBSCRIPTION_EVENT_CARD|PA_SUBSCRIPTION_EVENT_CHANGE, c->index); + + pa_hook_fire(&c->core->hooks[PA_CORE_HOOK_CARD_PROFILE_ADDED], profile); +} + +static const char* profile_name_for_dir(pa_card_profile *cp, pa_direction_t dir) { + if (dir == PA_DIRECTION_OUTPUT && cp->output_name) + return cp->output_name; + if (dir == PA_DIRECTION_INPUT && cp->input_name) + return cp->input_name; + return cp->name; +} + +static void update_port_preferred_profile(pa_card *c) { + pa_sink *sink; + pa_source *source; + uint32_t state; + + PA_IDXSET_FOREACH(sink, c->sinks, state) + if (sink->active_port) + pa_device_port_set_preferred_profile(sink->active_port, profile_name_for_dir(c->active_profile, PA_DIRECTION_OUTPUT)); + PA_IDXSET_FOREACH(source, c->sources, state) + if (source->active_port) + pa_device_port_set_preferred_profile(source->active_port, profile_name_for_dir(c->active_profile, PA_DIRECTION_INPUT)); +} + +int pa_card_set_profile(pa_card *c, pa_card_profile *profile, bool save) { + int r; + + pa_assert(c); + pa_assert(profile); + pa_assert(profile->card == c); + + if (!c->set_profile) { + pa_log_debug("set_profile() operation not implemented for card %u \"%s\"", c->index, c->name); + return -PA_ERR_NOTIMPLEMENTED; + } + + if (c->active_profile == profile) { + if (save && !c->save_profile) { + update_port_preferred_profile(c); + c->save_profile = true; + } + return 0; + } + + /* If we're setting the initial profile, we shouldn't call set_profile(), + * because the implementations don't expect that (for historical reasons). + * We should just set c->active_profile, and the implementations will + * properly set up that profile after pa_card_put() has returned. It would + * be probably good to change this so that also the initial profile can be + * set up in set_profile(), but if set_profile() fails, that would need + * some better handling than what we do here currently. */ + if (c->linked && (r = c->set_profile(c, profile)) < 0) + return r; + + pa_log_debug("%s: active_profile: %s -> %s", c->name, c->active_profile->name, profile->name); + c->active_profile = profile; + c->save_profile = save; + + if (save) + update_port_preferred_profile(c); + + if (c->linked) { + pa_hook_fire(&c->core->hooks[PA_CORE_HOOK_CARD_PROFILE_CHANGED], c); + pa_subscription_post(c->core, PA_SUBSCRIPTION_EVENT_CARD|PA_SUBSCRIPTION_EVENT_CHANGE, c->index); + } + + return 0; +} + +void pa_card_set_preferred_port(pa_card *c, pa_direction_t direction, pa_device_port *port) { + pa_device_port *old_port; + const char *old_port_str; + const char *new_port_str; + pa_card_preferred_port_changed_hook_data data; + + pa_assert(c); + + if (direction == PA_DIRECTION_INPUT) { + old_port = c->preferred_input_port; + old_port_str = c->preferred_input_port ? c->preferred_input_port->name : "(unset)"; + } else { + old_port = c->preferred_output_port; + old_port_str = c->preferred_output_port ? c->preferred_output_port->name : "(unset)"; + } + + if (port == old_port) + return; + + new_port_str = port ? port->name : "(unset)"; + + if (direction == PA_DIRECTION_INPUT) { + c->preferred_input_port = port; + pa_log_debug("%s: preferred_input_port: %s -> %s", c->name, old_port_str, new_port_str); + } else { + c->preferred_output_port = port; + pa_log_debug("%s: preferred_output_port: %s -> %s", c->name, old_port_str, new_port_str); + } + + data.card = c; + data.direction = direction; + pa_hook_fire(&c->core->hooks[PA_CORE_HOOK_CARD_PREFERRED_PORT_CHANGED], &data); +} + +int pa_card_suspend(pa_card *c, bool suspend, pa_suspend_cause_t cause) { + pa_sink *sink; + pa_source *source; + pa_suspend_cause_t suspend_cause; + uint32_t idx; + int ret = 0; + + pa_assert(c); + pa_assert(cause != 0); + + suspend_cause = c->suspend_cause; + + if (suspend) + suspend_cause |= cause; + else + suspend_cause &= ~cause; + + if (c->suspend_cause != suspend_cause) { + pa_log_debug("Card suspend causes/state changed"); + c->suspend_cause = suspend_cause; + pa_hook_fire(&c->core->hooks[PA_CORE_HOOK_CARD_SUSPEND_CHANGED], c); + } + + PA_IDXSET_FOREACH(sink, c->sinks, idx) { + int r; + + if ((r = pa_sink_suspend(sink, suspend, cause)) < 0) + ret = r; + } + + PA_IDXSET_FOREACH(source, c->sources, idx) { + int r; + + if ((r = pa_source_suspend(source, suspend, cause)) < 0) + ret = r; + } + + return ret; +} diff --git a/src/pulsecore/card.h b/src/pulsecore/card.h new file mode 100644 index 0000000..a11e33d --- /dev/null +++ b/src/pulsecore/card.h @@ -0,0 +1,147 @@ +#ifndef foopulsecardhfoo +#define foopulsecardhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2009 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulsecore/typedefs.h> +#include <pulse/proplist.h> +#include <pulsecore/core.h> +#include <pulsecore/module.h> +#include <pulsecore/idxset.h> + +/* This enum replaces pa_port_available_t (defined in pulse/def.h) for + * internal use, so make sure both enum types stay in sync. */ +typedef enum pa_available { + PA_AVAILABLE_UNKNOWN = 0, + PA_AVAILABLE_NO = 1, + PA_AVAILABLE_YES = 2, +} pa_available_t; + +struct pa_card_profile { + pa_card *card; + char *name; + char *description; + + /* Identifiers for the profile's input and output parts, i e, if two different profiles + have the same input_name string, they have the same source(s). + Same for output_name and sink(s). + Can be NULL (and in case of an input- or output- only profile, the other direction + will be NULL). */ + char *input_name; + char *output_name; + + unsigned priority; + pa_available_t available; /* PA_AVAILABLE_UNKNOWN, PA_AVAILABLE_NO or PA_AVAILABLE_YES */ + + /* We probably want to have different properties later on here */ + unsigned n_sinks; + unsigned n_sources; + + unsigned max_sink_channels; + unsigned max_source_channels; + + /* .. followed by some implementation specific data */ +}; + +#define PA_CARD_PROFILE_DATA(d) ((void*) ((uint8_t*) d + PA_ALIGN(sizeof(pa_card_profile)))) + +struct pa_card { + uint32_t index; + pa_core *core; + + char *name; + + pa_proplist *proplist; + pa_module *module; + char *driver; + + pa_idxset *sinks; + pa_idxset *sources; + + pa_hashmap *profiles; + pa_card_profile *active_profile; + + pa_hashmap *ports; + pa_device_port *preferred_input_port; + pa_device_port *preferred_output_port; + + bool save_profile:1; + + pa_suspend_cause_t suspend_cause; + + bool linked; + + void *userdata; + + int (*set_profile)(pa_card *c, pa_card_profile *profile); +}; + +typedef struct pa_card_new_data { + char *name; + pa_proplist *proplist; + + const char *driver; + pa_module *module; + + pa_hashmap *profiles; + pa_hashmap *ports; + pa_device_port *preferred_input_port; + pa_device_port *preferred_output_port; + + bool namereg_fail:1; +} pa_card_new_data; + +typedef struct { + pa_card *card; + pa_direction_t direction; +} pa_card_preferred_port_changed_hook_data; + +const char *pa_available_to_string(pa_available_t available); + +pa_card_profile *pa_card_profile_new(const char *name, const char *description, size_t extra); +void pa_card_profile_free(pa_card_profile *c); + +/* The profile's available status has changed */ +void pa_card_profile_set_available(pa_card_profile *c, pa_available_t available); + +pa_card_new_data *pa_card_new_data_init(pa_card_new_data *data); +void pa_card_new_data_set_name(pa_card_new_data *data, const char *name); +void pa_card_new_data_set_preferred_port(pa_card_new_data *data, pa_direction_t direction, pa_device_port *port); +void pa_card_new_data_done(pa_card_new_data *data); + +pa_card *pa_card_new(pa_core *c, pa_card_new_data *data); + +/* Select the initial card profile according to the configured policies. This + * must be called between pa_card_new() and pa_card_put(), after the port and + * profile availabilities have been initialized. */ +void pa_card_choose_initial_profile(pa_card *card); + +void pa_card_put(pa_card *c); +void pa_card_free(pa_card *c); + +void pa_card_add_profile(pa_card *c, pa_card_profile *profile); + +int pa_card_set_profile(pa_card *c, pa_card_profile *profile, bool save); + +void pa_card_set_preferred_port(pa_card *c, pa_direction_t direction, pa_device_port *port); + +int pa_card_suspend(pa_card *c, bool suspend, pa_suspend_cause_t cause); + +#endif diff --git a/src/pulsecore/cli-command.c b/src/pulsecore/cli-command.c new file mode 100644 index 0000000..58f3d1e --- /dev/null +++ b/src/pulsecore/cli-command.c @@ -0,0 +1,2249 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#include <unistd.h> +#include <ltdl.h> +#include <sys/stat.h> +#include <dirent.h> +#include <time.h> +#include <fcntl.h> +#include <ctype.h> + +#include <pulse/xmalloc.h> +#include <pulse/error.h> + +#include <pulsecore/module.h> +#include <pulsecore/sink.h> +#include <pulsecore/source.h> +#include <pulsecore/client.h> +#include <pulsecore/sink-input.h> +#include <pulsecore/source-output.h> +#include <pulsecore/tokenizer.h> +#include <pulsecore/strbuf.h> +#include <pulsecore/namereg.h> +#include <pulsecore/cli-text.h> +#include <pulsecore/core-scache.h> +#include <pulsecore/sound-file.h> +#include <pulsecore/play-memchunk.h> +#include <pulsecore/sound-file-stream.h> +#include <pulsecore/shared.h> +#include <pulsecore/core-util.h> +#include <pulsecore/core-error.h> +#include <pulsecore/modinfo.h> +#include <pulsecore/dynarray.h> + +#include "cli-command.h" + +struct command { + const char *name; + int (*proc) (pa_core *c, pa_tokenizer*t, pa_strbuf *buf, bool *fail); + const char *help; + unsigned args; +}; + +#define META_INCLUDE ".include" +#define META_FAIL ".fail" +#define META_NOFAIL ".nofail" +#define META_IFEXISTS ".ifexists" +#define META_ELSE ".else" +#define META_ENDIF ".endif" + +enum { + IFSTATE_NONE = -1, + IFSTATE_FALSE = 0, + IFSTATE_TRUE = 1, +}; + +/* Prototypes for all available commands */ +static int pa_cli_command_exit(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_help(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_modules(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_clients(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_cards(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_sinks(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_sources(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_sink_inputs(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_source_outputs(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_stat(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_info(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_load(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_unload(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_describe(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_sink_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_sink_input_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_source_output_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_source_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_sink_mute(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_source_mute(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_sink_input_mute(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_source_output_mute(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_sink_default(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_source_default(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_kill_client(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_kill_sink_input(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_kill_source_output(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_scache_play(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_scache_remove(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_scache_list(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_scache_load(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_scache_load_dir(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_play_file(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_dump(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_list_shared_props(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_move_sink_input(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_move_source_output(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_vacuum(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_suspend_sink(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_suspend_source(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_suspend(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_log_target(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_log_level(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_log_meta(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_log_time(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_log_backtrace(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_update_sink_proplist(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_update_source_proplist(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_update_sink_input_proplist(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_update_source_output_proplist(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_card_profile(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_sink_port(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_source_port(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_port_offset(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); +static int pa_cli_command_dump_volumes(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail); + +/* A method table for all available commands */ + +static const struct command commands[] = { + { "help", pa_cli_command_help, "Show this help", 1 }, + { "list-modules", pa_cli_command_modules, "List loaded modules", 1 }, + { "list-cards", pa_cli_command_cards, "List cards", 1 }, + { "list-sinks", pa_cli_command_sinks, "List loaded sinks", 1 }, + { "list-sources", pa_cli_command_sources, "List loaded sources", 1 }, + { "list-clients", pa_cli_command_clients, "List loaded clients", 1 }, + { "list-sink-inputs", pa_cli_command_sink_inputs, "List sink inputs", 1 }, + { "list-source-outputs", pa_cli_command_source_outputs, "List source outputs", 1 }, + { "stat", pa_cli_command_stat, "Show memory block statistics", 1 }, + { "info", pa_cli_command_info, "Show comprehensive status", 1 }, + { "ls", pa_cli_command_info, NULL, 1 }, + { "list", pa_cli_command_info, NULL, 1 }, + { "load-module", pa_cli_command_load, "Load a module (args: name, arguments)", 3}, + { "unload-module", pa_cli_command_unload, "Unload a module (args: index|name)", 2}, + { "describe-module", pa_cli_command_describe, "Describe a module (arg: name)", 2}, + { "set-sink-volume", pa_cli_command_sink_volume, "Set the volume of a sink (args: index|name, volume)", 3}, + { "set-source-volume", pa_cli_command_source_volume, "Set the volume of a source (args: index|name, volume)", 3}, + { "set-sink-mute", pa_cli_command_sink_mute, "Set the mute switch of a sink (args: index|name, bool)", 3}, + { "set-source-mute", pa_cli_command_source_mute, "Set the mute switch of a source (args: index|name, bool)", 3}, + { "set-sink-input-volume", pa_cli_command_sink_input_volume, "Set the volume of a sink input (args: index, volume)", 3}, + { "set-source-output-volume",pa_cli_command_source_output_volume,"Set the volume of a source output (args: index, volume)", 3}, + { "set-sink-input-mute", pa_cli_command_sink_input_mute, "Set the mute switch of a sink input (args: index, bool)", 3}, + { "set-source-output-mute", pa_cli_command_source_output_mute, "Set the mute switch of a source output (args: index, bool)", 3}, + { "set-default-sink", pa_cli_command_sink_default, "Set the default sink (args: index|name)", 2}, + { "set-default-source", pa_cli_command_source_default, "Set the default source (args: index|name)", 2}, + { "set-card-profile", pa_cli_command_card_profile, "Change the profile of a card (args: index|name, profile-name)", 3}, + { "set-sink-port", pa_cli_command_sink_port, "Change the port of a sink (args: index|name, port-name)", 3}, + { "set-source-port", pa_cli_command_source_port, "Change the port of a source (args: index|name, port-name)", 3}, + { "set-port-latency-offset", pa_cli_command_port_offset, "Change the latency of a port (args: card-index|card-name, port-name, latency-offset)", 4}, + { "suspend-sink", pa_cli_command_suspend_sink, "Suspend sink (args: index|name, bool)", 3}, + { "suspend-source", pa_cli_command_suspend_source, "Suspend source (args: index|name, bool)", 3}, + { "suspend", pa_cli_command_suspend, "Suspend all sinks and all sources (args: bool)", 2}, + { "move-sink-input", pa_cli_command_move_sink_input, "Move sink input to another sink (args: index, sink)", 3}, + { "move-source-output", pa_cli_command_move_source_output, "Move source output to another source (args: index, source)", 3}, + { "update-sink-proplist", pa_cli_command_update_sink_proplist, "Update the properties of a sink (args: index|name, properties)", 3}, + { "update-source-proplist", pa_cli_command_update_source_proplist, "Update the properties of a source (args: index|name, properties)", 3}, + { "update-sink-input-proplist", pa_cli_command_update_sink_input_proplist, "Update the properties of a sink input (args: index, properties)", 3}, + { "update-source-output-proplist", pa_cli_command_update_source_output_proplist, "Update the properties of a source output (args: index, properties)", 3}, + { "list-samples", pa_cli_command_scache_list, "List all entries in the sample cache", 1}, + { "play-sample", pa_cli_command_scache_play, "Play a sample from the sample cache (args: name, sink|index)", 3}, + { "remove-sample", pa_cli_command_scache_remove, "Remove a sample from the sample cache (args: name)", 2}, + { "load-sample", pa_cli_command_scache_load, "Load a sound file into the sample cache (args: name, filename)", 3}, + { "load-sample-lazy", pa_cli_command_scache_load, "Lazily load a sound file into the sample cache (args: name, filename)", 3}, + { "load-sample-dir-lazy", pa_cli_command_scache_load_dir, "Lazily load all files in a directory into the sample cache (args: pathname)", 2}, + { "kill-client", pa_cli_command_kill_client, "Kill a client (args: index)", 2}, + { "kill-sink-input", pa_cli_command_kill_sink_input, "Kill a sink input (args: index)", 2}, + { "kill-source-output", pa_cli_command_kill_source_output, "Kill a source output (args: index)", 2}, + { "set-log-target", pa_cli_command_log_target, "Change the log target (args: null|auto|syslog|stderr|file:PATH|newfile:PATH)", 2}, + { "set-log-level", pa_cli_command_log_level, "Change the log level (args: numeric level)", 2}, + { "set-log-meta", pa_cli_command_log_meta, "Show source code location in log messages (args: bool)", 2}, + { "set-log-time", pa_cli_command_log_time, "Show timestamps in log messages (args: bool)", 2}, + { "set-log-backtrace", pa_cli_command_log_backtrace, "Show backtrace in log messages (args: frames)", 2}, + { "play-file", pa_cli_command_play_file, "Play a sound file (args: filename, sink|index)", 3}, + { "dump", pa_cli_command_dump, "Dump daemon configuration", 1}, + { "dump-volumes", pa_cli_command_dump_volumes, "Debug: Show the state of all volumes", 1 }, + { "shared", pa_cli_command_list_shared_props, "Debug: Show shared properties", 1}, + { "exit", pa_cli_command_exit, "Terminate the daemon", 1 }, + { "vacuum", pa_cli_command_vacuum, NULL, 1}, + { NULL, NULL, NULL, 0 } +}; + +static const char whitespace[] = " \t\n\r"; +static const char linebreak[] = "\n\r"; + +static uint32_t parse_index(const char *n) { + uint32_t idx; + + if (pa_atou(n, &idx) < 0) + return (uint32_t) PA_IDXSET_INVALID; + + return idx; +} + +static int pa_cli_command_exit(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (pa_core_exit(c, false, 0) < 0) + pa_strbuf_puts(buf, "Not allowed to terminate daemon.\n"); + + return 0; +} + +static int pa_cli_command_help(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + const struct command*command; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + pa_strbuf_puts(buf, "Available commands:\n"); + + for (command = commands; command->name; command++) + if (command->help) + pa_strbuf_printf(buf, " %-25s %s\n", command->name, command->help); + return 0; +} + +static int pa_cli_command_modules(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + char *s; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + pa_assert_se(s = pa_module_list_to_string(c)); + pa_strbuf_puts(buf, s); + pa_xfree(s); + return 0; +} + +static int pa_cli_command_clients(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + char *s; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + pa_assert_se(s = pa_client_list_to_string(c)); + pa_strbuf_puts(buf, s); + pa_xfree(s); + return 0; +} + +static int pa_cli_command_cards(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + char *s; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + pa_assert_se(s = pa_card_list_to_string(c)); + pa_strbuf_puts(buf, s); + pa_xfree(s); + return 0; +} + +static int pa_cli_command_sinks(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + char *s; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + pa_assert_se(s = pa_sink_list_to_string(c)); + pa_strbuf_puts(buf, s); + pa_xfree(s); + return 0; +} + +static int pa_cli_command_sources(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + char *s; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + pa_assert_se(s = pa_source_list_to_string(c)); + pa_strbuf_puts(buf, s); + pa_xfree(s); + return 0; +} + +static int pa_cli_command_sink_inputs(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + char *s; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + pa_assert_se(s = pa_sink_input_list_to_string(c)); + pa_strbuf_puts(buf, s); + pa_xfree(s); + return 0; +} + +static int pa_cli_command_source_outputs(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + char *s; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + pa_assert_se(s = pa_source_output_list_to_string(c)); + pa_strbuf_puts(buf, s); + pa_xfree(s); + return 0; +} + +static int pa_cli_command_stat(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + char ss[PA_SAMPLE_SPEC_SNPRINT_MAX]; + char cm[PA_CHANNEL_MAP_SNPRINT_MAX]; + char bytes[PA_BYTES_SNPRINT_MAX]; + const pa_mempool_stat *mstat; + unsigned k; + + static const char* const type_table[PA_MEMBLOCK_TYPE_MAX] = { + [PA_MEMBLOCK_POOL] = "POOL", + [PA_MEMBLOCK_POOL_EXTERNAL] = "POOL_EXTERNAL", + [PA_MEMBLOCK_APPENDED] = "APPENDED", + [PA_MEMBLOCK_USER] = "USER", + [PA_MEMBLOCK_FIXED] = "FIXED", + [PA_MEMBLOCK_IMPORTED] = "IMPORTED", + }; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + mstat = pa_mempool_get_stat(c->mempool); + + pa_strbuf_printf(buf, "Memory blocks currently allocated: %u, size: %s.\n", + (unsigned) pa_atomic_load(&mstat->n_allocated), + pa_bytes_snprint(bytes, sizeof(bytes), (unsigned) pa_atomic_load(&mstat->allocated_size))); + + pa_strbuf_printf(buf, "Memory blocks allocated during the whole lifetime: %u, size: %s.\n", + (unsigned) pa_atomic_load(&mstat->n_accumulated), + pa_bytes_snprint(bytes, sizeof(bytes), (unsigned) pa_atomic_load(&mstat->accumulated_size))); + + pa_strbuf_printf(buf, "Memory blocks imported from other processes: %u, size: %s.\n", + (unsigned) pa_atomic_load(&mstat->n_imported), + pa_bytes_snprint(bytes, sizeof(bytes), (unsigned) pa_atomic_load(&mstat->imported_size))); + + pa_strbuf_printf(buf, "Memory blocks exported to other processes: %u, size: %s.\n", + (unsigned) pa_atomic_load(&mstat->n_exported), + pa_bytes_snprint(bytes, sizeof(bytes), (unsigned) pa_atomic_load(&mstat->exported_size))); + + pa_strbuf_printf(buf, "Total sample cache size: %s.\n", + pa_bytes_snprint(bytes, sizeof(bytes), (unsigned) pa_scache_total_size(c))); + + pa_strbuf_printf(buf, "Default sample spec: %s\n", + pa_sample_spec_snprint(ss, sizeof(ss), &c->default_sample_spec)); + + pa_strbuf_printf(buf, "Default channel map: %s\n", + pa_channel_map_snprint(cm, sizeof(cm), &c->default_channel_map)); + + pa_strbuf_printf(buf, "Default sink name: %s\n" + "Default source name: %s\n", + c->default_sink ? c->default_sink->name : "none", + c->default_source ? c->default_source->name : "none"); + + for (k = 0; k < PA_MEMBLOCK_TYPE_MAX; k++) + pa_strbuf_printf(buf, + "Memory blocks of type %s: %u allocated/%u accumulated.\n", + type_table[k], + (unsigned) pa_atomic_load(&mstat->n_allocated_by_type[k]), + (unsigned) pa_atomic_load(&mstat->n_accumulated_by_type[k])); + + return 0; +} + +static int pa_cli_command_info(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + pa_cli_command_stat(c, t, buf, fail); + pa_cli_command_modules(c, t, buf, fail); + pa_cli_command_sinks(c, t, buf, fail); + pa_cli_command_sources(c, t, buf, fail); + pa_cli_command_clients(c, t, buf, fail); + pa_cli_command_cards(c, t, buf, fail); + pa_cli_command_sink_inputs(c, t, buf, fail); + pa_cli_command_source_outputs(c, t, buf, fail); + pa_cli_command_scache_list(c, t, buf, fail); + return 0; +} + +static int pa_cli_command_load(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + const char *name; + pa_error_code_t err; + pa_module *m = NULL; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(name = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify the module name and optionally arguments.\n"); + return -1; + } + + if ((err = pa_module_load(&m, c, name, pa_tokenizer_get(t, 2))) < 0) { + if (err == PA_ERR_EXIST) { + pa_strbuf_puts(buf, "Module already loaded; ignoring.\n"); + } else { + pa_strbuf_puts(buf, "Module load failed.\n"); + return -1; + } + } + + return 0; +} + +static int pa_cli_command_unload(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + pa_module *m; + uint32_t idx; + const char *i; + bool unloaded = false; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(i = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify the module index or name.\n"); + return -1; + } + + if (pa_atou(i, &idx) >= 0) { + if (!(m = pa_idxset_get_by_index(c->modules, idx))) { + pa_strbuf_puts(buf, "Invalid module index.\n"); + return -1; + } + + pa_module_unload(m, false); + + } else { + PA_IDXSET_FOREACH(m, c->modules, idx) + if (pa_streq(i, m->name)) { + unloaded = true; + pa_module_unload(m, false); + } + + if (unloaded == false) { + pa_strbuf_printf(buf, "Module %s not loaded.\n", i); + return -1; + } + } + + return 0; +} + +static int pa_cli_command_describe(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + const char *name; + pa_modinfo *i; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(name = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify the module name.\n"); + return -1; + } + + if ((i = pa_modinfo_get_by_name(name))) { + + pa_strbuf_printf(buf, "Name: %s\n", name); + + if (!i->description && !i->version && !i->author && !i->usage) + pa_strbuf_printf(buf, "No module information available\n"); + else { + if (i->version) + pa_strbuf_printf(buf, "Version: %s\n", i->version); + if (i->description) + pa_strbuf_printf(buf, "Description: %s\n", i->description); + if (i->author) + pa_strbuf_printf(buf, "Author: %s\n", i->author); + if (i->usage) + pa_strbuf_printf(buf, "Usage: %s\n", i->usage); + pa_strbuf_printf(buf, "Load Once: %s\n", pa_yes_no(i->load_once)); + if (i->deprecated) + pa_strbuf_printf(buf, "Warning, deprecated: %s\n", i->deprecated); + } + + pa_modinfo_free(i); + } else + pa_strbuf_puts(buf, "Failed to open module.\n"); + + return 0; +} + +static int pa_cli_command_sink_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + const char *n, *v; + pa_sink *sink; + uint32_t volume; + pa_cvolume cvolume; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(n = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a sink either by its name or its index.\n"); + return -1; + } + + if (!(v = pa_tokenizer_get(t, 2))) { + pa_strbuf_puts(buf, "You need to specify a volume >= 0. (0 is muted, 0x10000 is normal volume)\n"); + return -1; + } + + if (pa_atou(v, &volume) < 0) { + pa_strbuf_puts(buf, "Failed to parse volume.\n"); + return -1; + } + + if (!PA_VOLUME_IS_VALID(volume)) { + pa_strbuf_puts(buf, "Volume outside permissible range.\n"); + return -1; + } + + if (!(sink = pa_namereg_get(c, n, PA_NAMEREG_SINK))) { + pa_strbuf_puts(buf, "No sink found by this name or index.\n"); + return -1; + } + + pa_cvolume_set(&cvolume, 1, volume); + pa_sink_set_volume(sink, &cvolume, true, true); + return 0; +} + +static int pa_cli_command_sink_input_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + const char *n, *v; + pa_sink_input *si; + pa_volume_t volume; + pa_cvolume cvolume; + uint32_t idx; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(n = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a sink input by its index.\n"); + return -1; + } + + if ((idx = parse_index(n)) == PA_IDXSET_INVALID) { + pa_strbuf_puts(buf, "Failed to parse index.\n"); + return -1; + } + + if (!(v = pa_tokenizer_get(t, 2))) { + pa_strbuf_puts(buf, "You need to specify a volume >= 0. (0 is muted, 0x10000 is normal volume)\n"); + return -1; + } + + if (pa_atou(v, &volume) < 0) { + pa_strbuf_puts(buf, "Failed to parse volume.\n"); + return -1; + } + + if (!PA_VOLUME_IS_VALID(volume)) { + pa_strbuf_puts(buf, "Volume outside permissible range.\n"); + return -1; + } + + if (!(si = pa_idxset_get_by_index(c->sink_inputs, idx))) { + pa_strbuf_puts(buf, "No sink input found with this index.\n"); + return -1; + } + + if (!si->volume_writable) { + pa_strbuf_puts(buf, "This sink input's volume can't be changed.\n"); + return -1; + } + + pa_cvolume_set(&cvolume, 1, volume); + pa_sink_input_set_volume(si, &cvolume, true, true); + return 0; +} + +static int pa_cli_command_source_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + const char *n, *v; + pa_source *source; + uint32_t volume; + pa_cvolume cvolume; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(n = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a source either by its name or its index.\n"); + return -1; + } + + if (!(v = pa_tokenizer_get(t, 2))) { + pa_strbuf_puts(buf, "You need to specify a volume >= 0. (0 is muted, 0x10000 is normal volume)\n"); + return -1; + } + + if (pa_atou(v, &volume) < 0) { + pa_strbuf_puts(buf, "Failed to parse volume.\n"); + return -1; + } + + if (!PA_VOLUME_IS_VALID(volume)) { + pa_strbuf_puts(buf, "Volume outside permissible range.\n"); + return -1; + } + + if (!(source = pa_namereg_get(c, n, PA_NAMEREG_SOURCE))) { + pa_strbuf_puts(buf, "No source found by this name or index.\n"); + return -1; + } + + pa_cvolume_set(&cvolume, 1, volume); + pa_source_set_volume(source, &cvolume, true, true); + return 0; +} + +static int pa_cli_command_source_output_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + const char *n, *v; + pa_source_output *so; + pa_volume_t volume; + pa_cvolume cvolume; + uint32_t idx; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(n = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a source output by its index.\n"); + return -1; + } + + if ((idx = parse_index(n)) == PA_IDXSET_INVALID) { + pa_strbuf_puts(buf, "Failed to parse index.\n"); + return -1; + } + + if (!(v = pa_tokenizer_get(t, 2))) { + pa_strbuf_puts(buf, "You need to specify a volume >= 0. (0 is muted, 0x10000 is normal volume)\n"); + return -1; + } + + if (pa_atou(v, &volume) < 0) { + pa_strbuf_puts(buf, "Failed to parse volume.\n"); + return -1; + } + + if (!PA_VOLUME_IS_VALID(volume)) { + pa_strbuf_puts(buf, "Volume outside permissible range.\n"); + return -1; + } + + if (!(so = pa_idxset_get_by_index(c->source_outputs, idx))) { + pa_strbuf_puts(buf, "No source output found with this index.\n"); + return -1; + } + + if (!so->volume_writable) { + pa_strbuf_puts(buf, "This source output's volume can't be changed.\n"); + return -1; + } + + pa_cvolume_set(&cvolume, 1, volume); + pa_source_output_set_volume(so, &cvolume, true, true); + return 0; +} + +static int pa_cli_command_sink_mute(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + const char *n, *m; + pa_sink *sink; + int mute; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(n = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a sink either by its name or its index.\n"); + return -1; + } + + if (!(m = pa_tokenizer_get(t, 2))) { + pa_strbuf_puts(buf, "You need to specify a mute switch setting (0/1).\n"); + return -1; + } + + if ((mute = pa_parse_boolean(m)) < 0) { + pa_strbuf_puts(buf, "Failed to parse mute switch.\n"); + return -1; + } + + if (!(sink = pa_namereg_get(c, n, PA_NAMEREG_SINK))) { + pa_strbuf_puts(buf, "No sink found by this name or index.\n"); + return -1; + } + + pa_sink_set_mute(sink, mute, true); + return 0; +} + +static int pa_cli_command_source_mute(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + const char *n, *m; + pa_source *source; + int mute; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(n = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a source either by its name or its index.\n"); + return -1; + } + + if (!(m = pa_tokenizer_get(t, 2))) { + pa_strbuf_puts(buf, "You need to specify a mute switch setting (0/1).\n"); + return -1; + } + + if ((mute = pa_parse_boolean(m)) < 0) { + pa_strbuf_puts(buf, "Failed to parse mute switch.\n"); + return -1; + } + + if (!(source = pa_namereg_get(c, n, PA_NAMEREG_SOURCE))) { + pa_strbuf_puts(buf, "No sink found by this name or index.\n"); + return -1; + } + + pa_source_set_mute(source, mute, true); + return 0; +} + +static int pa_cli_command_update_sink_proplist(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + const char *n, *s; + pa_sink *sink; + pa_proplist *p; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(n = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a sink either by its name or its index.\n"); + return -1; + } + + if (!(s = pa_tokenizer_get(t, 2))) { + pa_strbuf_puts(buf, "You need to specify a \"key=value\" argument.\n"); + return -1; + } + + if (!(sink = pa_namereg_get(c, n, PA_NAMEREG_SINK))) { + pa_strbuf_puts(buf, "No sink found by this name or index.\n"); + return -1; + } + + if (!(p = pa_proplist_from_string(s))) { + pa_strbuf_puts(buf, "Failed to parse proplist.\n"); + return -1; + } + + pa_sink_update_proplist(sink, PA_UPDATE_REPLACE, p); + + pa_proplist_free(p); + + return 0; +} + +static int pa_cli_command_update_source_proplist(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + const char *n, *s; + pa_source *source; + pa_proplist *p; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(n = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a source either by its name or its index.\n"); + return -1; + } + + if (!(s = pa_tokenizer_get(t, 2))) { + pa_strbuf_puts(buf, "You need to specify a \"key=value\" argument.\n"); + return -1; + } + + if (!(source = pa_namereg_get(c, n, PA_NAMEREG_SOURCE))) { + pa_strbuf_puts(buf, "No source found by this name or index.\n"); + return -1; + } + + if (!(p = pa_proplist_from_string(s))) { + pa_strbuf_puts(buf, "Failed to parse proplist.\n"); + return -1; + } + + pa_source_update_proplist(source, PA_UPDATE_REPLACE, p); + + pa_proplist_free(p); + + return 0; +} + +static int pa_cli_command_update_sink_input_proplist(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + const char *n, *s; + pa_sink_input *si; + uint32_t idx; + pa_proplist *p; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(n = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a sink input either by index.\n"); + return -1; + } + + if ((idx = parse_index(n)) == PA_IDXSET_INVALID) { + pa_strbuf_puts(buf, "Failed to parse index.\n"); + return -1; + } + + if (!(s = pa_tokenizer_get(t, 2))) { + pa_strbuf_puts(buf, "You need to specify a \"key=value\" argument.\n"); + return -1; + } + + if (!(si = pa_idxset_get_by_index(c->sink_inputs, (uint32_t) idx))) { + pa_strbuf_puts(buf, "No sink input found with this index.\n"); + return -1; + } + + if (!(p = pa_proplist_from_string(s))) { + pa_strbuf_puts(buf, "Failed to parse proplist.\n"); + return -1; + } + + pa_sink_input_update_proplist(si, PA_UPDATE_REPLACE, p); + + pa_proplist_free(p); + + return 0; +} + +static int pa_cli_command_update_source_output_proplist(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + const char *n, *s; + pa_source_output *so; + uint32_t idx; + pa_proplist *p; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(n = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a source output by its index.\n"); + return -1; + } + + if ((idx = parse_index(n)) == PA_IDXSET_INVALID) { + pa_strbuf_puts(buf, "Failed to parse index.\n"); + return -1; + } + + if (!(s = pa_tokenizer_get(t, 2))) { + pa_strbuf_puts(buf, "You need to specify a \"key=value\" argument.\n"); + return -1; + } + + if (!(so = pa_idxset_get_by_index(c->source_outputs, (uint32_t) idx))) { + pa_strbuf_puts(buf, "No source output found with this index.\n"); + return -1; + } + + if (!(p = pa_proplist_from_string(s))) { + pa_strbuf_puts(buf, "Failed to parse proplist.\n"); + return -1; + } + + pa_source_output_update_proplist(so, PA_UPDATE_REPLACE, p); + + pa_proplist_free(p); + + return 0; +} + +static int pa_cli_command_sink_input_mute(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + const char *n, *v; + pa_sink_input *si; + uint32_t idx; + int mute; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(n = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a sink input by its index.\n"); + return -1; + } + + if ((idx = parse_index(n)) == PA_IDXSET_INVALID) { + pa_strbuf_puts(buf, "Failed to parse index.\n"); + return -1; + } + + if (!(v = pa_tokenizer_get(t, 2))) { + pa_strbuf_puts(buf, "You need to specify a mute switch setting (0/1).\n"); + return -1; + } + + if ((mute = pa_parse_boolean(v)) < 0) { + pa_strbuf_puts(buf, "Failed to parse mute switch.\n"); + return -1; + } + + if (!(si = pa_idxset_get_by_index(c->sink_inputs, (uint32_t) idx))) { + pa_strbuf_puts(buf, "No sink input found with this index.\n"); + return -1; + } + + pa_sink_input_set_mute(si, mute, true); + return 0; +} + +static int pa_cli_command_source_output_mute(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + const char *n, *v; + pa_source_output *so; + uint32_t idx; + int mute; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(n = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a source output by its index.\n"); + return -1; + } + + if ((idx = parse_index(n)) == PA_IDXSET_INVALID) { + pa_strbuf_puts(buf, "Failed to parse index.\n"); + return -1; + } + + if (!(v = pa_tokenizer_get(t, 2))) { + pa_strbuf_puts(buf, "You need to specify a mute switch setting (0/1).\n"); + return -1; + } + + if ((mute = pa_parse_boolean(v)) < 0) { + pa_strbuf_puts(buf, "Failed to parse mute switch.\n"); + return -1; + } + + if (!(so = pa_idxset_get_by_index(c->source_outputs, (uint32_t) idx))) { + pa_strbuf_puts(buf, "No source output found with this index.\n"); + return -1; + } + + pa_source_output_set_mute(so, mute, true); + return 0; +} + +static int pa_cli_command_sink_default(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + const char *n; + pa_sink *s; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(n = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a sink either by its name or its index.\n"); + return -1; + } + + if ((s = pa_namereg_get(c, n, PA_NAMEREG_SINK))) + pa_core_set_configured_default_sink(c, s->name); + else + pa_strbuf_printf(buf, "Sink %s does not exist.\n", n); + + return 0; +} + +static int pa_cli_command_source_default(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + const char *n; + pa_source *s; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(n = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a source either by its name or its index.\n"); + return -1; + } + + if ((s = pa_namereg_get(c, n, PA_NAMEREG_SOURCE))) + pa_core_set_configured_default_source(c, s->name); + else + pa_strbuf_printf(buf, "Source %s does not exist.\n", n); + return 0; +} + +static int pa_cli_command_kill_client(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + const char *n; + pa_client *client; + uint32_t idx; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(n = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a client by its index.\n"); + return -1; + } + + if ((idx = parse_index(n)) == PA_IDXSET_INVALID) { + pa_strbuf_puts(buf, "Failed to parse index.\n"); + return -1; + } + + if (!(client = pa_idxset_get_by_index(c->clients, idx))) { + pa_strbuf_puts(buf, "No client found by this index.\n"); + return -1; + } + + pa_client_kill(client); + return 0; +} + +static int pa_cli_command_kill_sink_input(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + const char *n; + pa_sink_input *sink_input; + uint32_t idx; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(n = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a sink input by its index.\n"); + return -1; + } + + if ((idx = parse_index(n)) == PA_IDXSET_INVALID) { + pa_strbuf_puts(buf, "Failed to parse index.\n"); + return -1; + } + + if (!(sink_input = pa_idxset_get_by_index(c->sink_inputs, idx))) { + pa_strbuf_puts(buf, "No sink input found by this index.\n"); + return -1; + } + + pa_sink_input_kill(sink_input); + return 0; +} + +static int pa_cli_command_kill_source_output(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + const char *n; + pa_source_output *source_output; + uint32_t idx; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(n = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a source output by its index.\n"); + return -1; + } + + if ((idx = parse_index(n)) == PA_IDXSET_INVALID) { + pa_strbuf_puts(buf, "Failed to parse index.\n"); + return -1; + } + + if (!(source_output = pa_idxset_get_by_index(c->source_outputs, idx))) { + pa_strbuf_puts(buf, "No source output found by this index.\n"); + return -1; + } + + pa_source_output_kill(source_output); + return 0; +} + +static int pa_cli_command_scache_list(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + char *s; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + pa_assert_se(s = pa_scache_list_to_string(c)); + pa_strbuf_puts(buf, s); + pa_xfree(s); + + return 0; +} + +static int pa_cli_command_scache_play(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + const char *n, *sink_name; + pa_sink *sink; + uint32_t idx; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(n = pa_tokenizer_get(t, 1)) || !(sink_name = pa_tokenizer_get(t, 2))) { + pa_strbuf_puts(buf, "You need to specify a sample name and a sink name.\n"); + return -1; + } + + if (!(sink = pa_namereg_get(c, sink_name, PA_NAMEREG_SINK))) { + pa_strbuf_puts(buf, "No sink by that name.\n"); + return -1; + } + + if (pa_scache_play_item(c, n, sink, PA_VOLUME_NORM, NULL, &idx) < 0) { + pa_strbuf_puts(buf, "Failed to play sample.\n"); + return -1; + } + + pa_strbuf_printf(buf, "Playing on sink input #%i\n", idx); + + return 0; +} + +static int pa_cli_command_scache_remove(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + const char *n; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(n = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a sample name.\n"); + return -1; + } + + if (pa_scache_remove_item(c, n) < 0) { + pa_strbuf_puts(buf, "Failed to remove sample.\n"); + return -1; + } + + return 0; +} + +static int pa_cli_command_scache_load(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + const char *fname, *n; + int r; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(fname = pa_tokenizer_get(t, 2)) || !(n = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a file name and a sample name.\n"); + return -1; + } + + if (strstr(pa_tokenizer_get(t, 0), "lazy")) + r = pa_scache_add_file_lazy(c, n, fname, NULL); + else + r = pa_scache_add_file(c, n, fname, NULL); + + if (r < 0) + pa_strbuf_puts(buf, "Failed to load sound file.\n"); + + return 0; +} + +static int pa_cli_command_scache_load_dir(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + const char *pname; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(pname = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a path name.\n"); + return -1; + } + + if (pa_scache_add_directory_lazy(c, pname) < 0) { + pa_strbuf_puts(buf, "Failed to load directory.\n"); + return -1; + } + + return 0; +} + +static int pa_cli_command_play_file(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + const char *fname, *sink_name; + pa_sink *sink; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(fname = pa_tokenizer_get(t, 1)) || !(sink_name = pa_tokenizer_get(t, 2))) { + pa_strbuf_puts(buf, "You need to specify a file name and a sink name.\n"); + return -1; + } + + if (!(sink = pa_namereg_get(c, sink_name, PA_NAMEREG_SINK))) { + pa_strbuf_puts(buf, "No sink by that name.\n"); + return -1; + } + + if (pa_play_file(sink, fname, NULL) < 0) { + pa_strbuf_puts(buf, "Failed to play sound file.\n"); + return -1; + } + + return 0; +} + +static int pa_cli_command_list_shared_props(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + pa_shared_dump(c, buf); + return 0; +} + +static int pa_cli_command_vacuum(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + pa_mempool_vacuum(c->mempool); + + return 0; +} + +static int pa_cli_command_move_sink_input(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + const char *n, *k; + pa_sink_input *si; + pa_sink *sink; + uint32_t idx; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(n = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a sink input by its index.\n"); + return -1; + } + + if ((idx = parse_index(n)) == PA_IDXSET_INVALID) { + pa_strbuf_puts(buf, "Failed to parse index.\n"); + return -1; + } + + if (!(k = pa_tokenizer_get(t, 2))) { + pa_strbuf_puts(buf, "You need to specify a sink.\n"); + return -1; + } + + if (!(si = pa_idxset_get_by_index(c->sink_inputs, (uint32_t) idx))) { + pa_strbuf_puts(buf, "No sink input found with this index.\n"); + return -1; + } + + if (!(sink = pa_namereg_get(c, k, PA_NAMEREG_SINK))) { + pa_strbuf_puts(buf, "No sink found by this name or index.\n"); + return -1; + } + + if (pa_sink_input_move_to(si, sink, true) < 0) { + pa_strbuf_puts(buf, "Moved failed.\n"); + return -1; + } + return 0; +} + +static int pa_cli_command_move_source_output(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + const char *n, *k; + pa_source_output *so; + pa_source *source; + uint32_t idx; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(n = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a source output by its index.\n"); + return -1; + } + + if ((idx = parse_index(n)) == PA_IDXSET_INVALID) { + pa_strbuf_puts(buf, "Failed to parse index.\n"); + return -1; + } + + if (!(k = pa_tokenizer_get(t, 2))) { + pa_strbuf_puts(buf, "You need to specify a source.\n"); + return -1; + } + + if (!(so = pa_idxset_get_by_index(c->source_outputs, (uint32_t) idx))) { + pa_strbuf_puts(buf, "No source output found with this index.\n"); + return -1; + } + + if (!(source = pa_namereg_get(c, k, PA_NAMEREG_SOURCE))) { + pa_strbuf_puts(buf, "No source found by this name or index.\n"); + return -1; + } + + if (pa_source_output_move_to(so, source, true) < 0) { + pa_strbuf_puts(buf, "Moved failed.\n"); + return -1; + } + return 0; +} + +static int pa_cli_command_suspend_sink(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + const char *n, *m; + pa_sink *sink; + int suspend, r; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(n = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a sink either by its name or its index.\n"); + return -1; + } + + if (!(m = pa_tokenizer_get(t, 2))) { + pa_strbuf_puts(buf, "You need to specify a suspend switch setting (0/1).\n"); + return -1; + } + + if ((suspend = pa_parse_boolean(m)) < 0) { + pa_strbuf_puts(buf, "Failed to parse suspend switch.\n"); + return -1; + } + + if (!(sink = pa_namereg_get(c, n, PA_NAMEREG_SINK))) { + pa_strbuf_puts(buf, "No sink found by this name or index.\n"); + return -1; + } + + pa_log_debug("%s of sink %s requested via CLI.", suspend ? "Suspending" : "Resuming", sink->name); + + if ((r = pa_sink_suspend(sink, suspend, PA_SUSPEND_USER)) < 0) + pa_strbuf_printf(buf, "Failed to resume/suspend sink: %s\n", pa_strerror(r)); + + return 0; +} + +static int pa_cli_command_suspend_source(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + const char *n, *m; + pa_source *source; + int suspend, r; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(n = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a source either by its name or its index.\n"); + return -1; + } + + if (!(m = pa_tokenizer_get(t, 2))) { + pa_strbuf_puts(buf, "You need to specify a suspend switch setting (0/1).\n"); + return -1; + } + + if ((suspend = pa_parse_boolean(m)) < 0) { + pa_strbuf_puts(buf, "Failed to parse suspend switch.\n"); + return -1; + } + + if (!(source = pa_namereg_get(c, n, PA_NAMEREG_SOURCE))) { + pa_strbuf_puts(buf, "No source found by this name or index.\n"); + return -1; + } + + pa_log_debug("%s of source %s requested via CLI.", suspend ? "Suspending" : "Resuming", source->name); + + if ((r = pa_source_suspend(source, suspend, PA_SUSPEND_USER)) < 0) + pa_strbuf_printf(buf, "Failed to resume/suspend source: %s\n", pa_strerror(r)); + + return 0; +} + +static int pa_cli_command_suspend(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + const char *m; + int suspend, r; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(m = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a suspend switch setting (0/1).\n"); + return -1; + } + + if ((suspend = pa_parse_boolean(m)) < 0) { + pa_strbuf_puts(buf, "Failed to parse suspend switch.\n"); + return -1; + } + + pa_log_debug("%s of all sinks and sources requested via CLI.", suspend ? "Suspending" : "Resuming"); + + if ((r = pa_sink_suspend_all(c, suspend, PA_SUSPEND_USER)) < 0) + pa_strbuf_printf(buf, "Failed to resume/suspend all sinks: %s\n", pa_strerror(r)); + + if ((r = pa_source_suspend_all(c, suspend, PA_SUSPEND_USER)) < 0) + pa_strbuf_printf(buf, "Failed to resume/suspend all sources: %s\n", pa_strerror(r)); + + return 0; +} + +static int pa_cli_command_log_target(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + const char *m; + pa_log_target *log_target = NULL; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(m = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a log target (null|auto|syslog|stderr|file:PATH|newfile:PATH).\n"); + return -1; + } + + /* 'auto' is actually the effect with 'stderr' */ + if (pa_streq(m, "auto")) + log_target = pa_log_target_new(PA_LOG_STDERR, NULL); + else { + log_target = pa_log_parse_target(m); + + if (!log_target) { + pa_strbuf_puts(buf, "Invalid log target.\n"); + return -1; + } + } + + if (pa_log_set_target(log_target) < 0) { + pa_strbuf_puts(buf, "Failed to set log target.\n"); + pa_log_target_free(log_target); + return -1; + } + + pa_log_target_free(log_target); + + return 0; +} + +static int pa_cli_command_log_level(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + const char *m; + uint32_t level; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(m = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a log level (0..4).\n"); + return -1; + } + + if (pa_atou(m, &level) < 0 || level >= PA_LOG_LEVEL_MAX) { + pa_strbuf_puts(buf, "Failed to parse log level.\n"); + return -1; + } + + pa_log_set_level(level); + + return 0; +} + +static int pa_cli_command_log_meta(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + const char *m; + int b; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(m = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a boolean.\n"); + return -1; + } + + if ((b = pa_parse_boolean(m)) < 0) { + pa_strbuf_puts(buf, "Failed to parse log meta switch.\n"); + return -1; + } + + pa_log_set_flags(PA_LOG_PRINT_META, b ? PA_LOG_SET : PA_LOG_UNSET); + + return 0; +} + +static int pa_cli_command_log_time(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + const char *m; + int b; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(m = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a boolean.\n"); + return -1; + } + + if ((b = pa_parse_boolean(m)) < 0) { + pa_strbuf_puts(buf, "Failed to parse log meta switch.\n"); + return -1; + } + + pa_log_set_flags(PA_LOG_PRINT_TIME, b ? PA_LOG_SET : PA_LOG_UNSET); + + return 0; +} + +static int pa_cli_command_log_backtrace(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + const char *m; + uint32_t nframes; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(m = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a backtrace level.\n"); + return -1; + } + + if (pa_atou(m, &nframes) < 0 || nframes >= 1000) { + pa_strbuf_puts(buf, "Failed to parse backtrace level.\n"); + return -1; + } + + pa_log_set_show_backtrace(nframes); + + return 0; +} + +static int pa_cli_command_card_profile(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + const char *n, *p; + pa_card *card; + pa_card_profile *profile; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(n = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a card either by its name or its index.\n"); + return -1; + } + + if (!(p = pa_tokenizer_get(t, 2))) { + pa_strbuf_puts(buf, "You need to specify a profile by its name.\n"); + return -1; + } + + if (!(card = pa_namereg_get(c, n, PA_NAMEREG_CARD))) { + pa_strbuf_puts(buf, "No card found by this name or index.\n"); + return -1; + } + + if (!(profile = pa_hashmap_get(card->profiles, p))) { + pa_strbuf_printf(buf, "No such profile: %s\n", p); + return -1; + } + + if (pa_card_set_profile(card, profile, true) < 0) { + pa_strbuf_printf(buf, "Failed to set card profile to '%s'.\n", p); + return -1; + } + + return 0; +} + +static int pa_cli_command_sink_port(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + const char *n, *p; + pa_sink *sink; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(n = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a sink either by its name or its index.\n"); + return -1; + } + + if (!(p = pa_tokenizer_get(t, 2))) { + pa_strbuf_puts(buf, "You need to specify a profile by its name.\n"); + return -1; + } + + if (!(sink = pa_namereg_get(c, n, PA_NAMEREG_SINK))) { + pa_strbuf_puts(buf, "No sink found by this name or index.\n"); + return -1; + } + + if (pa_sink_set_port(sink, p, true) < 0) { + pa_strbuf_printf(buf, "Failed to set sink port to '%s'.\n", p); + return -1; + } + + return 0; +} + +static int pa_cli_command_source_port(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + const char *n, *p; + pa_source *source; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(n = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a source either by its name or its index.\n"); + return -1; + } + + if (!(p = pa_tokenizer_get(t, 2))) { + pa_strbuf_puts(buf, "You need to specify a profile by its name.\n"); + return -1; + } + + if (!(source = pa_namereg_get(c, n, PA_NAMEREG_SOURCE))) { + pa_strbuf_puts(buf, "No source found by this name or index.\n"); + return -1; + } + + if (pa_source_set_port(source, p, true) < 0) { + pa_strbuf_printf(buf, "Failed to set source port to '%s'.\n", p); + return -1; + } + + return 0; +} + +static int pa_cli_command_port_offset(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + const char *n, *p, *l; + pa_device_port *port; + pa_card *card; + int32_t offset; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(n = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a card either by its name or its index.\n"); + return -1; + } + + if (!(p = pa_tokenizer_get(t, 2))) { + pa_strbuf_puts(buf, "You need to specify a port by its name.\n"); + return -1; + } + + if (!(l = pa_tokenizer_get(t, 3))) { + pa_strbuf_puts(buf, "You need to specify a latency offset.\n"); + return -1; + } + + if (pa_atoi(l, &offset) < 0) { + pa_strbuf_puts(buf, "Failed to parse the latency offset.\n"); + return -1; + } + + if (!(card = pa_namereg_get(c, n, PA_NAMEREG_CARD))) { + pa_strbuf_puts(buf, "No card found by this name or index.\n"); + return -1; + } + + if (!(port = pa_hashmap_get(card->ports, p))) { + pa_strbuf_puts(buf, "No port found by this name.\n"); + return -1; + } + + pa_device_port_set_latency_offset(port, offset); + + return 0; +} + +static int pa_cli_command_dump(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + pa_module *m; + pa_sink *sink; + pa_source *source; + pa_card *card; + bool nl; + uint32_t idx; + time_t now; +#ifdef HAVE_CTIME_R + char txt[256]; +#endif + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + time(&now); + +#ifdef HAVE_CTIME_R + pa_strbuf_printf(buf, "### Configuration dump generated at %s\n", ctime_r(&now, txt)); +#else + pa_strbuf_printf(buf, "### Configuration dump generated at %s\n", ctime(&now)); +#endif + + PA_IDXSET_FOREACH(m, c->modules, idx) { + + pa_strbuf_printf(buf, "load-module %s", m->name); + + if (m->argument) + pa_strbuf_printf(buf, " %s", m->argument); + + pa_strbuf_puts(buf, "\n"); + } + + nl = false; + PA_IDXSET_FOREACH(sink, c->sinks, idx) { + + if (!nl) { + pa_strbuf_puts(buf, "\n"); + nl = true; + } + + pa_strbuf_printf(buf, "set-sink-volume %s 0x%03x\n", sink->name, pa_cvolume_max(pa_sink_get_volume(sink, false))); + pa_strbuf_printf(buf, "set-sink-mute %s %s\n", sink->name, pa_yes_no(pa_sink_get_mute(sink, false))); + pa_strbuf_printf(buf, "suspend-sink %s %s\n", sink->name, pa_yes_no(sink->state == PA_SINK_SUSPENDED)); + } + + nl = false; + PA_IDXSET_FOREACH(source, c->sources, idx) { + + if (!nl) { + pa_strbuf_puts(buf, "\n"); + nl = true; + } + + pa_strbuf_printf(buf, "set-source-volume %s 0x%03x\n", source->name, pa_cvolume_max(pa_source_get_volume(source, false))); + pa_strbuf_printf(buf, "set-source-mute %s %s\n", source->name, pa_yes_no(pa_source_get_mute(source, false))); + pa_strbuf_printf(buf, "suspend-source %s %s\n", source->name, pa_yes_no(source->state == PA_SOURCE_SUSPENDED)); + } + + nl = false; + PA_IDXSET_FOREACH(card, c->cards, idx) { + + if (!nl) { + pa_strbuf_puts(buf, "\n"); + nl = true; + } + + pa_strbuf_printf(buf, "set-card-profile %s %s\n", card->name, card->active_profile->name); + } + + nl = false; + if (c->default_sink) { + if (!nl) { + pa_strbuf_puts(buf, "\n"); + nl = true; + } + + pa_strbuf_printf(buf, "set-default-sink %s\n", c->default_sink->name); + } + + if (c->default_source) { + if (!nl) + pa_strbuf_puts(buf, "\n"); + + pa_strbuf_printf(buf, "set-default-source %s\n", c->default_source->name); + } + + pa_strbuf_puts(buf, "\n### EOF\n"); + + return 0; +} + +static int pa_cli_command_dump_volumes(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) { + pa_sink *s; + pa_source *so; + pa_sink_input *i; + pa_source_output *o; + uint32_t s_idx, i_idx; + char v_str[PA_CVOLUME_SNPRINT_VERBOSE_MAX]; + pa_channel_map *map; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + PA_IDXSET_FOREACH(s, c->sinks, s_idx) { + map = &s->channel_map; + pa_strbuf_printf(buf, "Sink %d: ", s_idx); + pa_strbuf_printf(buf, + "reference = %s, ", + pa_cvolume_snprint_verbose(v_str, + sizeof(v_str), + &s->reference_volume, + map, + s->flags & PA_SINK_DECIBEL_VOLUME)); + pa_strbuf_printf(buf, + "real = %s, ", + pa_cvolume_snprint_verbose(v_str, + sizeof(v_str), + &s->real_volume, + &s->channel_map, + s->flags & PA_SINK_DECIBEL_VOLUME)); + pa_strbuf_printf(buf, "soft = %s, ", pa_cvolume_snprint_verbose(v_str, sizeof(v_str), &s->soft_volume, map, true)); + pa_strbuf_printf(buf, + "current_hw = %s, ", + pa_cvolume_snprint_verbose(v_str, + sizeof(v_str), + &s->thread_info.current_hw_volume, + map, + s->flags & PA_SINK_DECIBEL_VOLUME)); + pa_strbuf_printf(buf, "save = %s\n", pa_yes_no(s->save_volume)); + + PA_IDXSET_FOREACH(i, s->inputs, i_idx) { + map = &i->channel_map; + pa_strbuf_printf(buf, "\tInput %d: ", i_idx); + pa_strbuf_printf(buf, "volume = %s, ", pa_cvolume_snprint_verbose(v_str, sizeof(v_str), &i->volume, map, true)); + pa_strbuf_printf(buf, + "reference_ratio = %s, ", + pa_cvolume_snprint_verbose(v_str, sizeof(v_str), &i->reference_ratio, map, true)); + pa_strbuf_printf(buf, + "real_ratio = %s, ", + pa_cvolume_snprint_verbose(v_str, sizeof(v_str), &i->real_ratio, map, true)); + pa_strbuf_printf(buf, "soft = %s, ", pa_cvolume_snprint_verbose(v_str, sizeof(v_str), &i->soft_volume, map, true)); + pa_strbuf_printf(buf, + "volume_factor = %s, ", + pa_cvolume_snprint_verbose(v_str, sizeof(v_str), &i->volume_factor, map, true)); + pa_strbuf_printf(buf, + "volume_factor_sink = %s, ", + pa_cvolume_snprint_verbose(v_str, + sizeof(v_str), + &i->volume_factor_sink, + &i->sink->channel_map, + true)); + pa_strbuf_printf(buf, "save = %s\n", pa_yes_no(i->save_volume)); + } + } + + PA_IDXSET_FOREACH(so, c->sources, s_idx) { + map = &so->channel_map; + pa_strbuf_printf(buf, "Source %d: ", s_idx); + pa_strbuf_printf(buf, + "reference = %s, ", + pa_cvolume_snprint_verbose(v_str, + sizeof(v_str), + &so->reference_volume, + map, + so->flags & PA_SOURCE_DECIBEL_VOLUME)); + pa_strbuf_printf(buf, + "real = %s, ", + pa_cvolume_snprint_verbose(v_str, + sizeof(v_str), + &so->real_volume, + map, + so->flags & PA_SOURCE_DECIBEL_VOLUME)); + pa_strbuf_printf(buf, "soft = %s, ", pa_cvolume_snprint_verbose(v_str, sizeof(v_str), &so->soft_volume, map, true)); + pa_strbuf_printf(buf, + "current_hw = %s, ", + pa_cvolume_snprint_verbose(v_str, + sizeof(v_str), + &so->thread_info.current_hw_volume, + map, + so->flags & PA_SOURCE_DECIBEL_VOLUME)); + pa_strbuf_printf(buf, "save = %s\n", pa_yes_no(so->save_volume)); + + PA_IDXSET_FOREACH(o, so->outputs, i_idx) { + map = &o->channel_map; + pa_strbuf_printf(buf, "\tOutput %d: ", i_idx); + pa_strbuf_printf(buf, "volume = %s, ", pa_cvolume_snprint_verbose(v_str, sizeof(v_str), &o->volume, map, true)); + pa_strbuf_printf(buf, + "reference_ratio = %s, ", + pa_cvolume_snprint_verbose(v_str, sizeof(v_str), &o->reference_ratio, map, true)); + pa_strbuf_printf(buf, + "real_ratio = %s, ", + pa_cvolume_snprint_verbose(v_str, sizeof(v_str), &o->real_ratio, map, true)); + pa_strbuf_printf(buf, "soft = %s, ", pa_cvolume_snprint_verbose(v_str, sizeof(v_str), &o->soft_volume, map, true)); + pa_strbuf_printf(buf, + "volume_factor = %s, ", + pa_cvolume_snprint_verbose(v_str, sizeof(v_str), &o->volume_factor, map, true)); + pa_strbuf_printf(buf, + "volume_factor_source = %s, ", + pa_cvolume_snprint_verbose(v_str, + sizeof(v_str), + &o->volume_factor_source, + &o->source->channel_map, + true)); + pa_strbuf_printf(buf, "save = %s\n", pa_yes_no(o->save_volume)); + } + } + + return 0; +} + +int pa_cli_command_execute_line_stateful(pa_core *c, const char *s, pa_strbuf *buf, bool *fail, int *ifstate) { + const char *cs; + + pa_assert(c); + pa_assert(s); + pa_assert(buf); + + cs = s+strspn(s, whitespace); + + if (*cs == '#' || !*cs) + return 0; + else if (*cs == '.') { + if (!strcmp(cs, META_ELSE)) { + if (!ifstate || *ifstate == IFSTATE_NONE) { + pa_strbuf_printf(buf, "Meta command %s is not valid in this context\n", cs); + return -1; + } else if (*ifstate == IFSTATE_TRUE) + *ifstate = IFSTATE_FALSE; + else + *ifstate = IFSTATE_TRUE; + return 0; + } else if (!strcmp(cs, META_ENDIF)) { + if (!ifstate || *ifstate == IFSTATE_NONE) { + pa_strbuf_printf(buf, "Meta command %s is not valid in this context\n", cs); + return -1; + } else + *ifstate = IFSTATE_NONE; + return 0; + } + if (ifstate && *ifstate == IFSTATE_FALSE) + return 0; + if (!strcmp(cs, META_FAIL)) + *fail = true; + else if (!strcmp(cs, META_NOFAIL)) + *fail = false; + else { + size_t l; + l = strcspn(cs, whitespace); + + if (l == sizeof(META_INCLUDE)-1 && !strncmp(cs, META_INCLUDE, l)) { + struct stat st; + const char *filename = cs+l+strspn(cs+l, whitespace); + + if (stat(filename, &st) < 0) { + pa_log_warn("stat('%s'): %s", filename, pa_cstrerror(errno)); + if (*fail) + return -1; + } else { + if (S_ISDIR(st.st_mode)) { + DIR *d; + + if (!(d = opendir(filename))) { + pa_log_warn("Failed to read '%s': %s", filename, pa_cstrerror(errno)); + if (*fail) + return -1; + } else { + unsigned i, count; + char **sorted_files; + struct dirent *de; + bool failed = false; + pa_dynarray *files = pa_dynarray_new(NULL); + + while ((de = readdir(d))) { + char *extn; + size_t flen = strlen(de->d_name); + + if (flen < 4) + continue; + + extn = &de->d_name[flen-3]; + if (strncmp(extn, ".pa", 3) == 0) + pa_dynarray_append(files, pa_sprintf_malloc("%s" PA_PATH_SEP "%s", filename, de->d_name)); + } + + closedir(d); + if ((count = pa_dynarray_size(files))) { + sorted_files = pa_xnew(char*, count); + for (i = 0; i < count; ++i) + sorted_files[i] = pa_dynarray_get(files, i); + pa_dynarray_free(files); + + for (i = 0; i < count; ++i) { + for (unsigned j = 0; j < count; ++j) { + if (strcmp(sorted_files[i], sorted_files[j]) < 0) { + char *tmp = sorted_files[i]; + sorted_files[i] = sorted_files[j]; + sorted_files[j] = tmp; + } + } + } + + for (i = 0; i < count; ++i) { + if (!failed) { + if (pa_cli_command_execute_file(c, sorted_files[i], buf, fail) < 0 && *fail) + failed = true; + } + + pa_xfree(sorted_files[i]); + } + pa_xfree(sorted_files); + if (failed) + return -1; + } + } + } else if (pa_cli_command_execute_file(c, filename, buf, fail) < 0 && *fail) { + return -1; + } + } + } else if (l == sizeof(META_IFEXISTS)-1 && !strncmp(cs, META_IFEXISTS, l)) { + if (!ifstate) { + pa_strbuf_printf(buf, "Meta command %s is not valid in this context\n", cs); + return -1; + } else if (*ifstate != IFSTATE_NONE) { + pa_strbuf_printf(buf, "Nested %s commands not supported\n", cs); + return -1; + } else { + const char *filename = cs+l+strspn(cs+l, whitespace); + *ifstate = pa_module_exists(filename) ? IFSTATE_TRUE : IFSTATE_FALSE; + } + } else { + pa_strbuf_printf(buf, "Invalid meta command: %s\n", cs); + if (*fail) return -1; + } + } + } else { + const struct command*command; + int unknown = 1; + size_t l; + + if (ifstate && *ifstate == IFSTATE_FALSE) + return 0; + + l = strcspn(cs, whitespace); + + for (command = commands; command->name; command++) + if (strlen(command->name) == l && !strncmp(cs, command->name, l)) { + int ret; + pa_tokenizer *t = pa_tokenizer_new(cs, command->args); + pa_assert(t); + ret = command->proc(c, t, buf, fail); + pa_tokenizer_free(t); + unknown = 0; + + if (ret < 0 && *fail) + return -1; + + break; + } + + if (unknown) { + pa_strbuf_printf(buf, "Unknown command: %s\n", cs); + if (*fail) + return -1; + } + } + + return 0; +} + +int pa_cli_command_execute_line(pa_core *c, const char *s, pa_strbuf *buf, bool *fail) { + return pa_cli_command_execute_line_stateful(c, s, buf, fail, NULL); +} + +int pa_cli_command_execute_file_stream(pa_core *c, FILE *f, pa_strbuf *buf, bool *fail) { + char line[2048]; + int ifstate = IFSTATE_NONE; + int ret = -1; + bool _fail = true; + + pa_assert(c); + pa_assert(f); + pa_assert(buf); + + if (!fail) + fail = &_fail; + + while (fgets(line, sizeof(line), f)) { + pa_strip_nl(line); + + if (pa_cli_command_execute_line_stateful(c, line, buf, fail, &ifstate) < 0 && *fail) + goto fail; + } + + ret = 0; + +fail: + + return ret; +} + +int pa_cli_command_execute_file(pa_core *c, const char *fn, pa_strbuf *buf, bool *fail) { + FILE *f = NULL; + int ret = -1; + bool _fail = true; + + pa_assert(c); + pa_assert(fn); + pa_assert(buf); + + if (!fail) + fail = &_fail; + + if (!(f = pa_fopen_cloexec(fn, "r"))) { + pa_strbuf_printf(buf, "open('%s') failed: %s\n", fn, pa_cstrerror(errno)); + if (!*fail) + ret = 0; + goto fail; + } + + pa_log_debug("Parsing script '%s'", fn); + ret = pa_cli_command_execute_file_stream(c, f, buf, fail); + +fail: + if (f) + fclose(f); + + return ret; +} + +int pa_cli_command_execute(pa_core *c, const char *s, pa_strbuf *buf, bool *fail) { + const char *p; + int ifstate = IFSTATE_NONE; + bool _fail = true; + + pa_assert(c); + pa_assert(s); + pa_assert(buf); + + if (!fail) + fail = &_fail; + + p = s; + while (*p) { + size_t l = strcspn(p, linebreak); + char *line = pa_xstrndup(p, l); + + if (pa_cli_command_execute_line_stateful(c, line, buf, fail, &ifstate) < 0 && *fail) { + pa_xfree(line); + return -1; + } + pa_xfree(line); + + p += l; + p += strspn(p, linebreak); + } + + return 0; +} diff --git a/src/pulsecore/cli-command.h b/src/pulsecore/cli-command.h new file mode 100644 index 0000000..3513341 --- /dev/null +++ b/src/pulsecore/cli-command.h @@ -0,0 +1,44 @@ +#ifndef fooclicommandhfoo +#define fooclicommandhfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulsecore/strbuf.h> +#include <pulsecore/core.h> + +/* Execute a single CLI command. Write the results to the string + * buffer *buf. If *fail is non-zero the function will return -1 when + * one or more of the executed commands failed. *fail + * may be modified by the function call. */ +int pa_cli_command_execute_line(pa_core *c, const char *s, pa_strbuf *buf, bool *fail); + +/* Execute a whole file of CLI commands */ +int pa_cli_command_execute_file(pa_core *c, const char *fn, pa_strbuf *buf, bool *fail); + +/* Execute a whole file of CLI commands */ +int pa_cli_command_execute_file_stream(pa_core *c, FILE *f, pa_strbuf *buf, bool *fail); + +/* Split the specified string into lines and run pa_cli_command_execute_line() for each. */ +int pa_cli_command_execute(pa_core *c, const char *s, pa_strbuf *buf, bool *fail); + +/* Same as pa_cli_command_execute_line() but also take ifstate var. */ +int pa_cli_command_execute_line_stateful(pa_core *c, const char *s, pa_strbuf *buf, bool *fail, int *ifstate); + +#endif diff --git a/src/pulsecore/cli-text.c b/src/pulsecore/cli-text.c new file mode 100644 index 0000000..8f35f36 --- /dev/null +++ b/src/pulsecore/cli-text.c @@ -0,0 +1,704 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/volume.h> +#include <pulse/xmalloc.h> +#include <pulse/timeval.h> + +#include <pulsecore/module.h> +#include <pulsecore/client.h> +#include <pulsecore/card.h> +#include <pulsecore/sink.h> +#include <pulsecore/source.h> +#include <pulsecore/sink-input.h> +#include <pulsecore/source-output.h> +#include <pulsecore/strbuf.h> +#include <pulsecore/core-scache.h> +#include <pulsecore/macro.h> +#include <pulsecore/core-util.h> +#include <pulsecore/namereg.h> + +#include "cli-text.h" + +char *pa_module_list_to_string(pa_core *c) { + pa_strbuf *s; + pa_module *m; + uint32_t idx = PA_IDXSET_INVALID; + pa_assert(c); + + s = pa_strbuf_new(); + + pa_strbuf_printf(s, "%u module(s) loaded.\n", pa_idxset_size(c->modules)); + + PA_IDXSET_FOREACH(m, c->modules, idx) { + char *t; + + pa_strbuf_printf(s, " index: %u\n" + "\tname: <%s>\n" + "\targument: <%s>\n" + "\tused: %i\n" + "\tload once: %s\n", + m->index, + m->name, + pa_strempty(m->argument), + pa_module_get_n_used(m), + pa_yes_no(m->load_once)); + + t = pa_proplist_to_string_sep(m->proplist, "\n\t\t"); + pa_strbuf_printf(s, "\tproperties:\n\t\t%s\n", t); + pa_xfree(t); + } + + return pa_strbuf_to_string_free(s); +} + +char *pa_client_list_to_string(pa_core *c) { + pa_strbuf *s; + pa_client *client; + uint32_t idx = PA_IDXSET_INVALID; + pa_assert(c); + + s = pa_strbuf_new(); + + pa_strbuf_printf(s, "%u client(s) logged in.\n", pa_idxset_size(c->clients)); + + PA_IDXSET_FOREACH(client, c->clients, idx) { + char *t; + pa_strbuf_printf( + s, + " index: %u\n" + "\tdriver: <%s>\n", + client->index, + client->driver); + + if (client->module) + pa_strbuf_printf(s, "\towner module: %u\n", client->module->index); + + t = pa_proplist_to_string_sep(client->proplist, "\n\t\t"); + pa_strbuf_printf(s, "\tproperties:\n\t\t%s\n", t); + pa_xfree(t); + } + + return pa_strbuf_to_string_free(s); +} + +static void append_port_list(pa_strbuf *s, pa_hashmap *ports) { + pa_device_port *p; + void *state; + + pa_assert(ports); + + if (pa_hashmap_isempty(ports)) + return; + + pa_strbuf_puts(s, "\tports:\n"); + PA_HASHMAP_FOREACH(p, ports, state) { + char *t = pa_proplist_to_string_sep(p->proplist, "\n\t\t\t\t"); + pa_strbuf_printf(s, "\t\t%s: %s (priority %u, latency offset %" PRId64 " usec, available: %s)\n", + p->name, p->description, p->priority, p->latency_offset, + pa_available_to_string(p->available)); + pa_strbuf_printf(s, "\t\t\tproperties:\n\t\t\t\t%s\n", t); + pa_xfree(t); + } +} + +char *pa_card_list_to_string(pa_core *c) { + pa_strbuf *s; + pa_card *card; + uint32_t idx = PA_IDXSET_INVALID; + pa_assert(c); + + s = pa_strbuf_new(); + + pa_strbuf_printf(s, "%u card(s) available.\n", pa_idxset_size(c->cards)); + + PA_IDXSET_FOREACH(card, c->cards, idx) { + char *t; + pa_sink *sink; + pa_source *source; + uint32_t sidx; + pa_card_profile *profile; + void *state; + + pa_strbuf_printf( + s, + " index: %u\n" + "\tname: <%s>\n" + "\tdriver: <%s>\n", + card->index, + card->name, + card->driver); + + if (card->module) + pa_strbuf_printf(s, "\towner module: %u\n", card->module->index); + + t = pa_proplist_to_string_sep(card->proplist, "\n\t\t"); + pa_strbuf_printf(s, "\tproperties:\n\t\t%s\n", t); + pa_xfree(t); + + pa_strbuf_puts(s, "\tprofiles:\n"); + PA_HASHMAP_FOREACH(profile, card->profiles, state) + pa_strbuf_printf(s, "\t\t%s: %s (priority %u, available: %s)\n", profile->name, profile->description, + profile->priority, pa_available_to_string(profile->available)); + + pa_strbuf_printf( + s, + "\tactive profile: <%s>\n", + card->active_profile->name); + + if (!pa_idxset_isempty(card->sinks)) { + pa_strbuf_puts(s, "\tsinks:\n"); + PA_IDXSET_FOREACH(sink, card->sinks, sidx) + pa_strbuf_printf(s, "\t\t%s/#%u: %s\n", sink->name, sink->index, pa_strna(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_DESCRIPTION))); + } + + if (!pa_idxset_isempty(card->sources)) { + pa_strbuf_puts(s, "\tsources:\n"); + PA_IDXSET_FOREACH(source, card->sources, sidx) + pa_strbuf_printf(s, "\t\t%s/#%u: %s\n", source->name, source->index, pa_strna(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_DESCRIPTION))); + } + + append_port_list(s, card->ports); + } + + return pa_strbuf_to_string_free(s); +} + +char *pa_sink_list_to_string(pa_core *c) { + pa_strbuf *s; + pa_sink *sink; + uint32_t idx = PA_IDXSET_INVALID; + pa_assert(c); + + s = pa_strbuf_new(); + + pa_strbuf_printf(s, "%u sink(s) available.\n", pa_idxset_size(c->sinks)); + + PA_IDXSET_FOREACH(sink, c->sinks, idx) { + char ss[PA_SAMPLE_SPEC_SNPRINT_MAX], + cv[PA_CVOLUME_SNPRINT_VERBOSE_MAX], + v[PA_VOLUME_SNPRINT_VERBOSE_MAX], + cm[PA_CHANNEL_MAP_SNPRINT_MAX], *t; + const char *cmn; + char suspend_cause_buf[PA_SUSPEND_CAUSE_TO_STRING_BUF_SIZE]; + + cmn = pa_channel_map_to_pretty_name(&sink->channel_map); + + pa_strbuf_printf( + s, + " %c index: %u\n" + "\tname: <%s>\n" + "\tdriver: <%s>\n" + "\tflags: %s%s%s%s%s%s%s%s\n" + "\tstate: %s\n" + "\tsuspend cause: %s\n" + "\tpriority: %u\n" + "\tvolume: %s\n" + "\t balance %0.2f\n" + "\tbase volume: %s\n" + "\tvolume steps: %u\n" + "\tmuted: %s\n" + "\tcurrent latency: %0.2f ms\n" + "\tmax request: %lu KiB\n" + "\tmax rewind: %lu KiB\n" + "\tmonitor source: %u\n" + "\tsample spec: %s\n" + "\tchannel map: %s%s%s\n" + "\tused by: %u\n" + "\tlinked by: %u\n", + sink == c->default_sink ? '*' : ' ', + sink->index, + sink->name, + sink->driver, + sink->flags & PA_SINK_HARDWARE ? "HARDWARE " : "", + sink->flags & PA_SINK_NETWORK ? "NETWORK " : "", + sink->flags & PA_SINK_HW_MUTE_CTRL ? "HW_MUTE_CTRL " : "", + sink->flags & PA_SINK_HW_VOLUME_CTRL ? "HW_VOLUME_CTRL " : "", + sink->flags & PA_SINK_DECIBEL_VOLUME ? "DECIBEL_VOLUME " : "", + sink->flags & PA_SINK_LATENCY ? "LATENCY " : "", + sink->flags & PA_SINK_FLAT_VOLUME ? "FLAT_VOLUME " : "", + sink->flags & PA_SINK_DYNAMIC_LATENCY ? "DYNAMIC_LATENCY" : "", + pa_sink_state_to_string(sink->state), + pa_suspend_cause_to_string(sink->suspend_cause, suspend_cause_buf), + sink->priority, + pa_cvolume_snprint_verbose(cv, + sizeof(cv), + pa_sink_get_volume(sink, false), + &sink->channel_map, + sink->flags & PA_SINK_DECIBEL_VOLUME), + pa_cvolume_get_balance(pa_sink_get_volume(sink, false), &sink->channel_map), + pa_volume_snprint_verbose(v, sizeof(v), sink->base_volume, sink->flags & PA_SINK_DECIBEL_VOLUME), + sink->n_volume_steps, + pa_yes_no(pa_sink_get_mute(sink, false)), + (double) pa_sink_get_latency(sink) / (double) PA_USEC_PER_MSEC, + (unsigned long) pa_sink_get_max_request(sink) / 1024, + (unsigned long) pa_sink_get_max_rewind(sink) / 1024, + sink->monitor_source ? sink->monitor_source->index : PA_INVALID_INDEX, + pa_sample_spec_snprint(ss, sizeof(ss), &sink->sample_spec), + pa_channel_map_snprint(cm, sizeof(cm), &sink->channel_map), + cmn ? "\n\t " : "", + cmn ? cmn : "", + pa_sink_used_by(sink), + pa_sink_linked_by(sink)); + + if (sink->flags & PA_SINK_DYNAMIC_LATENCY) { + pa_usec_t min_latency, max_latency; + pa_sink_get_latency_range(sink, &min_latency, &max_latency); + + pa_strbuf_printf( + s, + "\tconfigured latency: %0.2f ms; range is %0.2f .. %0.2f ms\n", + (double) pa_sink_get_requested_latency(sink) / (double) PA_USEC_PER_MSEC, + (double) min_latency / PA_USEC_PER_MSEC, + (double) max_latency / PA_USEC_PER_MSEC); + } else + pa_strbuf_printf( + s, + "\tfixed latency: %0.2f ms\n", + (double) pa_sink_get_fixed_latency(sink) / PA_USEC_PER_MSEC); + + if (sink->card) + pa_strbuf_printf(s, "\tcard: %u <%s>\n", sink->card->index, sink->card->name); + if (sink->module) + pa_strbuf_printf(s, "\tmodule: %u\n", sink->module->index); + + t = pa_proplist_to_string_sep(sink->proplist, "\n\t\t"); + pa_strbuf_printf(s, "\tproperties:\n\t\t%s\n", t); + pa_xfree(t); + + append_port_list(s, sink->ports); + + if (sink->active_port) + pa_strbuf_printf( + s, + "\tactive port: <%s>\n", + sink->active_port->name); + } + + return pa_strbuf_to_string_free(s); +} + +char *pa_source_list_to_string(pa_core *c) { + pa_strbuf *s; + pa_source *source; + uint32_t idx = PA_IDXSET_INVALID; + pa_assert(c); + + s = pa_strbuf_new(); + + pa_strbuf_printf(s, "%u source(s) available.\n", pa_idxset_size(c->sources)); + + PA_IDXSET_FOREACH(source, c->sources, idx) { + char ss[PA_SAMPLE_SPEC_SNPRINT_MAX], + cv[PA_CVOLUME_SNPRINT_VERBOSE_MAX], + v[PA_VOLUME_SNPRINT_VERBOSE_MAX], + cm[PA_CHANNEL_MAP_SNPRINT_MAX], *t; + const char *cmn; + char suspend_cause_buf[PA_SUSPEND_CAUSE_TO_STRING_BUF_SIZE]; + + cmn = pa_channel_map_to_pretty_name(&source->channel_map); + + pa_strbuf_printf( + s, + " %c index: %u\n" + "\tname: <%s>\n" + "\tdriver: <%s>\n" + "\tflags: %s%s%s%s%s%s%s\n" + "\tstate: %s\n" + "\tsuspend cause: %s\n" + "\tpriority: %u\n" + "\tvolume: %s\n" + "\t balance %0.2f\n" + "\tbase volume: %s\n" + "\tvolume steps: %u\n" + "\tmuted: %s\n" + "\tcurrent latency: %0.2f ms\n" + "\tmax rewind: %lu KiB\n" + "\tsample spec: %s\n" + "\tchannel map: %s%s%s\n" + "\tused by: %u\n" + "\tlinked by: %u\n", + source == c->default_source ? '*' : ' ', + source->index, + source->name, + source->driver, + source->flags & PA_SOURCE_HARDWARE ? "HARDWARE " : "", + source->flags & PA_SOURCE_NETWORK ? "NETWORK " : "", + source->flags & PA_SOURCE_HW_MUTE_CTRL ? "HW_MUTE_CTRL " : "", + source->flags & PA_SOURCE_HW_VOLUME_CTRL ? "HW_VOLUME_CTRL " : "", + source->flags & PA_SOURCE_DECIBEL_VOLUME ? "DECIBEL_VOLUME " : "", + source->flags & PA_SOURCE_LATENCY ? "LATENCY " : "", + source->flags & PA_SOURCE_DYNAMIC_LATENCY ? "DYNAMIC_LATENCY" : "", + pa_source_state_to_string(source->state), + pa_suspend_cause_to_string(source->suspend_cause, suspend_cause_buf), + source->priority, + pa_cvolume_snprint_verbose(cv, + sizeof(cv), + pa_source_get_volume(source, false), + &source->channel_map, + source->flags & PA_SOURCE_DECIBEL_VOLUME), + pa_cvolume_get_balance(pa_source_get_volume(source, false), &source->channel_map), + pa_volume_snprint_verbose(v, sizeof(v), source->base_volume, source->flags & PA_SOURCE_DECIBEL_VOLUME), + source->n_volume_steps, + pa_yes_no(pa_source_get_mute(source, false)), + (double) pa_source_get_latency(source) / PA_USEC_PER_MSEC, + (unsigned long) pa_source_get_max_rewind(source) / 1024, + pa_sample_spec_snprint(ss, sizeof(ss), &source->sample_spec), + pa_channel_map_snprint(cm, sizeof(cm), &source->channel_map), + cmn ? "\n\t " : "", + cmn ? cmn : "", + pa_source_used_by(source), + pa_source_linked_by(source)); + + if (source->flags & PA_SOURCE_DYNAMIC_LATENCY) { + pa_usec_t min_latency, max_latency; + pa_source_get_latency_range(source, &min_latency, &max_latency); + + pa_strbuf_printf( + s, + "\tconfigured latency: %0.2f ms; range is %0.2f .. %0.2f ms\n", + (double) pa_source_get_requested_latency(source) / PA_USEC_PER_MSEC, + (double) min_latency / PA_USEC_PER_MSEC, + (double) max_latency / PA_USEC_PER_MSEC); + } else + pa_strbuf_printf( + s, + "\tfixed latency: %0.2f ms\n", + (double) pa_source_get_fixed_latency(source) / PA_USEC_PER_MSEC); + + if (source->monitor_of) + pa_strbuf_printf(s, "\tmonitor_of: %u\n", source->monitor_of->index); + if (source->card) + pa_strbuf_printf(s, "\tcard: %u <%s>\n", source->card->index, source->card->name); + if (source->module) + pa_strbuf_printf(s, "\tmodule: %u\n", source->module->index); + + t = pa_proplist_to_string_sep(source->proplist, "\n\t\t"); + pa_strbuf_printf(s, "\tproperties:\n\t\t%s\n", t); + pa_xfree(t); + + append_port_list(s, source->ports); + + if (source->active_port) + pa_strbuf_printf( + s, + "\tactive port: <%s>\n", + source->active_port->name); + } + + return pa_strbuf_to_string_free(s); +} + +char *pa_source_output_list_to_string(pa_core *c) { + pa_strbuf *s; + pa_source_output *o; + uint32_t idx = PA_IDXSET_INVALID; + static const char* const state_table[] = { + [PA_SOURCE_OUTPUT_INIT] = "INIT", + [PA_SOURCE_OUTPUT_RUNNING] = "RUNNING", + [PA_SOURCE_OUTPUT_CORKED] = "CORKED", + [PA_SOURCE_OUTPUT_UNLINKED] = "UNLINKED" + }; + pa_assert(c); + + s = pa_strbuf_new(); + + pa_strbuf_printf(s, "%u source output(s) available.\n", pa_idxset_size(c->source_outputs)); + + PA_IDXSET_FOREACH(o, c->source_outputs, idx) { + char ss[PA_SAMPLE_SPEC_SNPRINT_MAX], cv[PA_CVOLUME_SNPRINT_VERBOSE_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX], *t, clt[28]; + pa_usec_t cl; + const char *cmn; + pa_cvolume v; + char *volume_str = NULL; + + cmn = pa_channel_map_to_pretty_name(&o->channel_map); + + if ((cl = pa_source_output_get_requested_latency(o)) == (pa_usec_t) -1) + pa_snprintf(clt, sizeof(clt), "n/a"); + else + pa_snprintf(clt, sizeof(clt), "%0.2f ms", (double) cl / PA_USEC_PER_MSEC); + + pa_assert(o->source); + + if (pa_source_output_is_volume_readable(o)) { + pa_source_output_get_volume(o, &v, true); + volume_str = pa_sprintf_malloc("%s\n\t balance %0.2f", + pa_cvolume_snprint_verbose(cv, sizeof(cv), &v, &o->channel_map, true), + pa_cvolume_get_balance(&v, &o->channel_map)); + } else + volume_str = pa_xstrdup("n/a"); + + pa_strbuf_printf( + s, + " index: %u\n" + "\tdriver: <%s>\n" + "\tflags: %s%s%s%s%s%s%s%s%s%s%s%s\n" + "\tstate: %s\n" + "\tsource: %u <%s>\n" + "\tvolume: %s\n" + "\tmuted: %s\n" + "\tcurrent latency: %0.2f ms\n" + "\trequested latency: %s\n" + "\tsample spec: %s\n" + "\tchannel map: %s%s%s\n" + "\tresample method: %s\n", + o->index, + o->driver, + o->flags & PA_SOURCE_OUTPUT_VARIABLE_RATE ? "VARIABLE_RATE " : "", + o->flags & PA_SOURCE_OUTPUT_DONT_MOVE ? "DONT_MOVE " : "", + o->flags & PA_SOURCE_OUTPUT_START_CORKED ? "START_CORKED " : "", + o->flags & PA_SOURCE_OUTPUT_NO_REMAP ? "NO_REMAP " : "", + o->flags & PA_SOURCE_OUTPUT_NO_REMIX ? "NO_REMIX " : "", + o->flags & PA_SOURCE_OUTPUT_FIX_FORMAT ? "FIX_FORMAT " : "", + o->flags & PA_SOURCE_OUTPUT_FIX_RATE ? "FIX_RATE " : "", + o->flags & PA_SOURCE_OUTPUT_FIX_CHANNELS ? "FIX_CHANNELS " : "", + o->flags & PA_SOURCE_OUTPUT_DONT_INHIBIT_AUTO_SUSPEND ? "DONT_INHIBIT_AUTO_SUSPEND " : "", + o->flags & PA_SOURCE_OUTPUT_NO_CREATE_ON_SUSPEND ? "NO_CREATE_ON_SUSPEND " : "", + o->flags & PA_SOURCE_OUTPUT_KILL_ON_SUSPEND ? "KILL_ON_SUSPEND " : "", + o->flags & PA_SOURCE_OUTPUT_PASSTHROUGH ? "PASSTHROUGH " : "", + state_table[o->state], + o->source->index, o->source->name, + volume_str, + pa_yes_no(o->muted), + (double) pa_source_output_get_latency(o, NULL) / PA_USEC_PER_MSEC, + clt, + pa_sample_spec_snprint(ss, sizeof(ss), &o->sample_spec), + pa_channel_map_snprint(cm, sizeof(cm), &o->channel_map), + cmn ? "\n\t " : "", + cmn ? cmn : "", + pa_resample_method_to_string(pa_source_output_get_resample_method(o))); + + pa_xfree(volume_str); + + if (o->module) + pa_strbuf_printf(s, "\towner module: %u\n", o->module->index); + if (o->client) + pa_strbuf_printf(s, "\tclient: %u <%s>\n", o->client->index, pa_strnull(pa_proplist_gets(o->client->proplist, PA_PROP_APPLICATION_NAME))); + if (o->direct_on_input) + pa_strbuf_printf(s, "\tdirect on input: %u\n", o->direct_on_input->index); + + t = pa_proplist_to_string_sep(o->proplist, "\n\t\t"); + pa_strbuf_printf(s, "\tproperties:\n\t\t%s\n", t); + pa_xfree(t); + } + + return pa_strbuf_to_string_free(s); +} + +char *pa_sink_input_list_to_string(pa_core *c) { + pa_strbuf *s; + pa_sink_input *i; + uint32_t idx = PA_IDXSET_INVALID; + static const char* const state_table[] = { + [PA_SINK_INPUT_INIT] = "INIT", + [PA_SINK_INPUT_RUNNING] = "RUNNING", + [PA_SINK_INPUT_CORKED] = "CORKED", + [PA_SINK_INPUT_UNLINKED] = "UNLINKED" + }; + + pa_assert(c); + s = pa_strbuf_new(); + + pa_strbuf_printf(s, "%u sink input(s) available.\n", pa_idxset_size(c->sink_inputs)); + + PA_IDXSET_FOREACH(i, c->sink_inputs, idx) { + char ss[PA_SAMPLE_SPEC_SNPRINT_MAX], cv[PA_CVOLUME_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX], *t, clt[28]; + pa_usec_t cl; + const char *cmn; + pa_cvolume v; + char *volume_str = NULL; + + cmn = pa_channel_map_to_pretty_name(&i->channel_map); + + if ((cl = pa_sink_input_get_requested_latency(i)) == (pa_usec_t) -1) + pa_snprintf(clt, sizeof(clt), "n/a"); + else + pa_snprintf(clt, sizeof(clt), "%0.2f ms", (double) cl / PA_USEC_PER_MSEC); + + pa_assert(i->sink); + + if (pa_sink_input_is_volume_readable(i)) { + pa_sink_input_get_volume(i, &v, true); + volume_str = pa_sprintf_malloc("%s\n\t balance %0.2f", + pa_cvolume_snprint_verbose(cv, sizeof(cv), &v, &i->channel_map, true), + pa_cvolume_get_balance(&v, &i->channel_map)); + } else + volume_str = pa_xstrdup("n/a"); + + pa_strbuf_printf( + s, + " index: %u\n" + "\tdriver: <%s>\n" + "\tflags: %s%s%s%s%s%s%s%s%s%s%s%s\n" + "\tstate: %s\n" + "\tsink: %u <%s>\n" + "\tvolume: %s\n" + "\tmuted: %s\n" + "\tcurrent latency: %0.2f ms\n" + "\trequested latency: %s\n" + "\tsample spec: %s\n" + "\tchannel map: %s%s%s\n" + "\tresample method: %s\n", + i->index, + i->driver, + i->flags & PA_SINK_INPUT_VARIABLE_RATE ? "VARIABLE_RATE " : "", + i->flags & PA_SINK_INPUT_DONT_MOVE ? "DONT_MOVE " : "", + i->flags & PA_SINK_INPUT_START_CORKED ? "START_CORKED " : "", + i->flags & PA_SINK_INPUT_NO_REMAP ? "NO_REMAP " : "", + i->flags & PA_SINK_INPUT_NO_REMIX ? "NO_REMIX " : "", + i->flags & PA_SINK_INPUT_FIX_FORMAT ? "FIX_FORMAT " : "", + i->flags & PA_SINK_INPUT_FIX_RATE ? "FIX_RATE " : "", + i->flags & PA_SINK_INPUT_FIX_CHANNELS ? "FIX_CHANNELS " : "", + i->flags & PA_SINK_INPUT_DONT_INHIBIT_AUTO_SUSPEND ? "DONT_INHIBIT_AUTO_SUSPEND " : "", + i->flags & PA_SINK_INPUT_NO_CREATE_ON_SUSPEND ? "NO_CREATE_SUSPEND " : "", + i->flags & PA_SINK_INPUT_KILL_ON_SUSPEND ? "KILL_ON_SUSPEND " : "", + i->flags & PA_SINK_INPUT_PASSTHROUGH ? "PASSTHROUGH " : "", + state_table[i->state], + i->sink->index, i->sink->name, + volume_str, + pa_yes_no(i->muted), + (double) pa_sink_input_get_latency(i, NULL) / PA_USEC_PER_MSEC, + clt, + pa_sample_spec_snprint(ss, sizeof(ss), &i->sample_spec), + pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map), + cmn ? "\n\t " : "", + cmn ? cmn : "", + pa_resample_method_to_string(pa_sink_input_get_resample_method(i))); + + pa_xfree(volume_str); + + if (i->module) + pa_strbuf_printf(s, "\tmodule: %u\n", i->module->index); + if (i->client) + pa_strbuf_printf(s, "\tclient: %u <%s>\n", i->client->index, pa_strnull(pa_proplist_gets(i->client->proplist, PA_PROP_APPLICATION_NAME))); + + t = pa_proplist_to_string_sep(i->proplist, "\n\t\t"); + pa_strbuf_printf(s, "\tproperties:\n\t\t%s\n", t); + pa_xfree(t); + } + + return pa_strbuf_to_string_free(s); +} + +char *pa_scache_list_to_string(pa_core *c) { + pa_strbuf *s; + pa_assert(c); + + s = pa_strbuf_new(); + + pa_strbuf_printf(s, "%u cache entrie(s) available.\n", c->scache ? pa_idxset_size(c->scache) : 0); + + if (c->scache) { + pa_scache_entry *e; + uint32_t idx = PA_IDXSET_INVALID; + + PA_IDXSET_FOREACH(e, c->scache, idx) { + double l = 0; + char ss[PA_SAMPLE_SPEC_SNPRINT_MAX] = "n/a", cv[PA_CVOLUME_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX] = "n/a", *t; + const char *cmn; + + cmn = pa_channel_map_to_pretty_name(&e->channel_map); + + if (e->memchunk.memblock) { + pa_sample_spec_snprint(ss, sizeof(ss), &e->sample_spec); + pa_channel_map_snprint(cm, sizeof(cm), &e->channel_map); + l = (double) e->memchunk.length / (double) pa_bytes_per_second(&e->sample_spec); + } + + pa_strbuf_printf( + s, + " name: <%s>\n" + "\tindex: %u\n" + "\tsample spec: %s\n" + "\tchannel map: %s%s%s\n" + "\tlength: %lu\n" + "\tduration: %0.1f s\n" + "\tvolume: %s\n" + "\t balance %0.2f\n" + "\tlazy: %s\n" + "\tfilename: <%s>\n", + e->name, + e->index, + ss, + cm, + cmn ? "\n\t " : "", + cmn ? cmn : "", + (long unsigned)(e->memchunk.memblock ? e->memchunk.length : 0), + l, + e->volume_is_set ? pa_cvolume_snprint_verbose(cv, sizeof(cv), &e->volume, &e->channel_map, true) : "n/a", + (e->memchunk.memblock && e->volume_is_set) ? pa_cvolume_get_balance(&e->volume, &e->channel_map) : 0.0f, + pa_yes_no(e->lazy), + e->filename ? e->filename : "n/a"); + + t = pa_proplist_to_string_sep(e->proplist, "\n\t\t"); + pa_strbuf_printf(s, "\tproperties:\n\t\t%s\n", t); + pa_xfree(t); + } + } + + return pa_strbuf_to_string_free(s); +} + +char *pa_full_status_string(pa_core *c) { + pa_strbuf *s; + int i; + + s = pa_strbuf_new(); + + for (i = 0; i < 8; i++) { + char *t = NULL; + + switch (i) { + case 0: + t = pa_sink_list_to_string(c); + break; + case 1: + t = pa_source_list_to_string(c); + break; + case 2: + t = pa_sink_input_list_to_string(c); + break; + case 3: + t = pa_source_output_list_to_string(c); + break; + case 4: + t = pa_client_list_to_string(c); + break; + case 5: + t = pa_card_list_to_string(c); + break; + case 6: + t = pa_module_list_to_string(c); + break; + case 7: + t = pa_scache_list_to_string(c); + break; + } + + pa_strbuf_puts(s, t); + pa_xfree(t); + } + + return pa_strbuf_to_string_free(s); +} diff --git a/src/pulsecore/cli-text.h b/src/pulsecore/cli-text.h new file mode 100644 index 0000000..b89be50 --- /dev/null +++ b/src/pulsecore/cli-text.h @@ -0,0 +1,39 @@ +#ifndef fooclitexthfoo +#define fooclitexthfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulsecore/core.h> + +/* Some functions to generate pretty formatted listings of + * entities. The returned strings have to be freed manually. */ + +char *pa_sink_input_list_to_string(pa_core *c); +char *pa_source_output_list_to_string(pa_core *c); +char *pa_sink_list_to_string(pa_core *core); +char *pa_source_list_to_string(pa_core *c); +char *pa_card_list_to_string(pa_core *c); +char *pa_client_list_to_string(pa_core *c); +char *pa_module_list_to_string(pa_core *c); +char *pa_scache_list_to_string(pa_core *c); + +char *pa_full_status_string(pa_core *c); + +#endif diff --git a/src/pulsecore/cli.c b/src/pulsecore/cli.c new file mode 100644 index 0000000..f942629 --- /dev/null +++ b/src/pulsecore/cli.c @@ -0,0 +1,176 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/ioline.h> +#include <pulsecore/module.h> +#include <pulsecore/client.h> +#include <pulsecore/tokenizer.h> +#include <pulsecore/strbuf.h> +#include <pulsecore/cli-text.h> +#include <pulsecore/cli-command.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> + +#include "cli.h" + +#define PROMPT ">>> " + +struct pa_cli { + pa_core *core; + pa_ioline *line; + + pa_cli_eof_cb_t eof_callback; + void *userdata; + + pa_client *client; + + bool fail, kill_requested; + int defer_kill; + + bool interactive; + char *last_line; +}; + +static void line_callback(pa_ioline *line, const char *s, void *userdata); +static void client_kill(pa_client *c); + +pa_cli* pa_cli_new(pa_core *core, pa_iochannel *io, pa_module *m) { + char cname[256]; + pa_cli *c; + pa_client_new_data data; + pa_client *client; + + pa_assert(io); + + pa_iochannel_socket_peer_to_string(io, cname, sizeof(cname)); + + pa_client_new_data_init(&data); + data.driver = __FILE__; + data.module = m; + pa_proplist_sets(data.proplist, PA_PROP_APPLICATION_NAME, cname); + client = pa_client_new(core, &data); + pa_client_new_data_done(&data); + + if (!client) + return NULL; + + c = pa_xnew0(pa_cli, 1); + c->core = core; + c->client = client; + pa_assert_se(c->line = pa_ioline_new(io)); + + c->client->kill = client_kill; + c->client->userdata = c; + + pa_ioline_set_callback(c->line, line_callback, c); + + return c; +} + +void pa_cli_free(pa_cli *c) { + pa_assert(c); + + pa_ioline_close(c->line); + pa_ioline_unref(c->line); + pa_client_free(c->client); + pa_xfree(c->last_line); + pa_xfree(c); +} + +static void client_kill(pa_client *client) { + pa_cli *c; + + pa_assert(client); + pa_assert_se(c = client->userdata); + + pa_log_debug("CLI client killed."); + + if (c->defer_kill) + c->kill_requested = true; + else if (c->eof_callback) + c->eof_callback(c, c->userdata); +} + +static void line_callback(pa_ioline *line, const char *s, void *userdata) { + pa_strbuf *buf; + pa_cli *c = userdata; + char *p; + + pa_assert(line); + pa_assert(c); + + if (!s) { + pa_log_debug("CLI got EOF from user."); + + if (c->eof_callback) + c->eof_callback(c, c->userdata); + + return; + } + + /* Magic command, like they had in AT Hayes Modems! Those were the good days! */ + if (pa_streq(s, "/")) + s = c->last_line; + else if (s[0]) { + pa_xfree(c->last_line); + c->last_line = pa_xstrdup(s); + } + + pa_assert_se(buf = pa_strbuf_new()); + c->defer_kill++; + if (pa_streq(s, "hello")) { + pa_strbuf_printf(buf, "Welcome to PulseAudio %s! " + "Use \"help\" for usage information.\n", PACKAGE_VERSION); + c->interactive = true; + } + else + pa_cli_command_execute_line(c->core, s, buf, &c->fail); + c->defer_kill--; + pa_ioline_puts(line, p = pa_strbuf_to_string_free(buf)); + pa_xfree(p); + + if (c->kill_requested) { + if (c->eof_callback) + c->eof_callback(c, c->userdata); + } else if (c->interactive) + pa_ioline_puts(line, PROMPT); +} + +void pa_cli_set_eof_callback(pa_cli *c, pa_cli_eof_cb_t cb, void *userdata) { + pa_assert(c); + + c->eof_callback = cb; + c->userdata = userdata; +} + +pa_module *pa_cli_get_module(pa_cli *c) { + pa_assert(c); + + return c->client->module; +} diff --git a/src/pulsecore/cli.h b/src/pulsecore/cli.h new file mode 100644 index 0000000..37d5ff9 --- /dev/null +++ b/src/pulsecore/cli.h @@ -0,0 +1,40 @@ +#ifndef fooclihfoo +#define fooclihfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulsecore/iochannel.h> +#include <pulsecore/core.h> +#include <pulsecore/module.h> + +typedef struct pa_cli pa_cli; + +typedef void (*pa_cli_eof_cb_t)(pa_cli *c, void *userdata); + +/* Create a new command line session on the specified io channel owned by the specified module */ +pa_cli* pa_cli_new(pa_core *core, pa_iochannel *io, pa_module *m); +void pa_cli_free(pa_cli *cli); + +/* Set a callback function that is called whenever the command line session is terminated */ +void pa_cli_set_eof_callback(pa_cli *cli, pa_cli_eof_cb_t cb, void *userdata); + +pa_module *pa_cli_get_module(pa_cli *c); + +#endif diff --git a/src/pulsecore/client.c b/src/pulsecore/client.c new file mode 100644 index 0000000..2e6af47 --- /dev/null +++ b/src/pulsecore/client.c @@ -0,0 +1,168 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <pulse/xmalloc.h> +#include <pulse/util.h> + +#include <pulsecore/core-subscribe.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> +#include <pulsecore/core-util.h> + +#include "client.h" + +pa_client_new_data* pa_client_new_data_init(pa_client_new_data *data) { + pa_assert(data); + + memset(data, 0, sizeof(*data)); + data->proplist = pa_proplist_new(); + + return data; +} + +void pa_client_new_data_done(pa_client_new_data *data) { + pa_assert(data); + + pa_proplist_free(data->proplist); +} + +pa_client *pa_client_new(pa_core *core, pa_client_new_data *data) { + pa_client *c; + + pa_core_assert_ref(core); + pa_assert(data); + + if (pa_hook_fire(&core->hooks[PA_CORE_HOOK_CLIENT_NEW], data) < 0) + return NULL; + + c = pa_xnew0(pa_client, 1); + c->core = core; + c->proplist = pa_proplist_copy(data->proplist); + c->driver = pa_xstrdup(pa_path_get_filename(data->driver)); + c->module = data->module; + + c->sink_inputs = pa_idxset_new(NULL, NULL); + c->source_outputs = pa_idxset_new(NULL, NULL); + + pa_assert_se(pa_idxset_put(core->clients, c, &c->index) >= 0); + + pa_log_info("Created %u \"%s\"", c->index, pa_strnull(pa_proplist_gets(c->proplist, PA_PROP_APPLICATION_NAME))); + pa_subscription_post(core, PA_SUBSCRIPTION_EVENT_CLIENT|PA_SUBSCRIPTION_EVENT_NEW, c->index); + + pa_hook_fire(&core->hooks[PA_CORE_HOOK_CLIENT_PUT], c); + + pa_core_check_idle(core); + + return c; +} + +void pa_client_free(pa_client *c) { + pa_core *core; + + pa_assert(c); + pa_assert(c->core); + + core = c->core; + + pa_hook_fire(&core->hooks[PA_CORE_HOOK_CLIENT_UNLINK], c); + + pa_idxset_remove_by_data(c->core->clients, c, NULL); + + pa_log_info("Freed %u \"%s\"", c->index, pa_strnull(pa_proplist_gets(c->proplist, PA_PROP_APPLICATION_NAME))); + pa_subscription_post(c->core, PA_SUBSCRIPTION_EVENT_CLIENT|PA_SUBSCRIPTION_EVENT_REMOVE, c->index); + + pa_assert(pa_idxset_isempty(c->sink_inputs)); + pa_idxset_free(c->sink_inputs, NULL); + pa_assert(pa_idxset_isempty(c->source_outputs)); + pa_idxset_free(c->source_outputs, NULL); + + pa_proplist_free(c->proplist); + pa_xfree(c->driver); + pa_xfree(c); + + pa_core_check_idle(core); +} + +void pa_client_kill(pa_client *c) { + pa_assert(c); + + if (!c->kill) { + pa_log_warn("kill() operation not implemented for client %u", c->index); + return; + } + + c->kill(c); +} + +void pa_client_set_name(pa_client *c, const char *name) { + pa_assert(c); + pa_assert(name); + + pa_log_info("Client %u changed name from \"%s\" to \"%s\"", c->index, pa_strnull(pa_proplist_gets(c->proplist, PA_PROP_APPLICATION_NAME)), name); + pa_proplist_sets(c->proplist, PA_PROP_APPLICATION_NAME, name); + + pa_client_update_proplist(c, 0, NULL); +} + +void pa_client_update_proplist(pa_client *c, pa_update_mode_t mode, pa_proplist *p) { + pa_assert(c); + + if (p) + pa_proplist_update(c->proplist, mode, p); + + pa_hook_fire(&c->core->hooks[PA_CORE_HOOK_CLIENT_PROPLIST_CHANGED], c); + pa_subscription_post(c->core, PA_SUBSCRIPTION_EVENT_CLIENT|PA_SUBSCRIPTION_EVENT_CHANGE, c->index); +} + +void pa_client_send_event(pa_client *c, const char *event, pa_proplist *data) { + pa_proplist *pl = NULL; + pa_client_send_event_hook_data hook_data; + + pa_assert(c); + pa_assert(event); + + if (!c->send_event) + return; + + if (!data) + data = pl = pa_proplist_new(); + + hook_data.client = c; + hook_data.data = data; + hook_data.event = event; + + if (pa_hook_fire(&c->core->hooks[PA_CORE_HOOK_CLIENT_SEND_EVENT], &hook_data) < 0) + goto finish; + + c->send_event(c, event, data); + +finish: + + if (pl) + pa_proplist_free(pl); +} diff --git a/src/pulsecore/client.h b/src/pulsecore/client.h new file mode 100644 index 0000000..c6952e3 --- /dev/null +++ b/src/pulsecore/client.h @@ -0,0 +1,83 @@ +#ifndef foopulseclienthfoo +#define foopulseclienthfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <inttypes.h> + +#include <pulsecore/typedefs.h> +#include <pulse/proplist.h> +#include <pulsecore/core.h> +#include <pulsecore/module.h> + +/* Every connection to the server should have a pa_client + * attached. That way the user may generate a listing of all connected + * clients easily and kill them if they want.*/ + +struct pa_client { + uint32_t index; + pa_core *core; + + pa_proplist *proplist; + pa_module *module; + char *driver; + + pa_idxset *sink_inputs; + pa_idxset *source_outputs; + + void *userdata; + + void (*kill)(pa_client *c); + + void (*send_event)(pa_client *c, const char *name, pa_proplist *data); +}; + +typedef struct pa_client_new_data { + pa_proplist *proplist; + const char *driver; + pa_module *module; +} pa_client_new_data; + +pa_client_new_data *pa_client_new_data_init(pa_client_new_data *data); +void pa_client_new_data_done(pa_client_new_data *data); + +pa_client *pa_client_new(pa_core *c, pa_client_new_data *data); + +/* This function should be called only by the code that created the client */ +void pa_client_free(pa_client *c); + +/* Code that didn't create the client should call this function to + * request destruction of the client */ +void pa_client_kill(pa_client *c); + +/* Rename the client */ +void pa_client_set_name(pa_client *c, const char *name); + +void pa_client_update_proplist(pa_client *c, pa_update_mode_t mode, pa_proplist *p); + +void pa_client_send_event(pa_client *c, const char *event, pa_proplist *data); + +typedef struct pa_client_send_event_hook_data { + pa_client *client; + const char *event; + pa_proplist *data; +} pa_client_send_event_hook_data; + +#endif diff --git a/src/pulsecore/conf-parser.c b/src/pulsecore/conf-parser.c new file mode 100644 index 0000000..73b7061 --- /dev/null +++ b/src/pulsecore/conf-parser.c @@ -0,0 +1,394 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <dirent.h> +#include <string.h> +#include <stdio.h> +#include <errno.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/log.h> +#include <pulsecore/core-util.h> +#include <pulsecore/macro.h> + +#include "conf-parser.h" + +#define WHITESPACE " \t\n" +#define COMMENTS "#;\n" + +/* Run the user supplied parser for an assignment */ +static int normal_assignment(pa_config_parser_state *state) { + const pa_config_item *item; + + pa_assert(state); + + for (item = state->item_table; item->parse; item++) { + + if (item->lvalue && !pa_streq(state->lvalue, item->lvalue)) + continue; + + if (item->section && !state->section) + continue; + + if (item->section && !pa_streq(state->section, item->section)) + continue; + + state->data = item->data; + + return item->parse(state); + } + + pa_log("[%s:%u] Unknown lvalue '%s' in section '%s'.", state->filename, state->lineno, state->lvalue, pa_strna(state->section)); + + return -1; +} + +/* Parse a proplist entry. */ +static int proplist_assignment(pa_config_parser_state *state) { + pa_assert(state); + pa_assert(state->proplist); + + if (pa_proplist_sets(state->proplist, state->lvalue, state->rvalue) < 0) { + pa_log("[%s:%u] Failed to parse a proplist entry: %s = %s", state->filename, state->lineno, state->lvalue, state->rvalue); + return -1; + } + + return 0; +} + +/* Parse a variable assignment line */ +static int parse_line(pa_config_parser_state *state) { + char *c; + + state->lvalue = state->buf + strspn(state->buf, WHITESPACE); + + if ((c = strpbrk(state->lvalue, COMMENTS))) + *c = 0; + + if (!*state->lvalue) + return 0; + + if (pa_startswith(state->lvalue, ".include ")) { + char *path = NULL, *fn; + int r; + + fn = pa_strip(state->lvalue + 9); + if (!pa_is_path_absolute(fn)) { + const char *k; + if ((k = strrchr(state->filename, '/'))) { + char *dir = pa_xstrndup(state->filename, k - state->filename); + fn = path = pa_sprintf_malloc("%s" PA_PATH_SEP "%s", dir, fn); + pa_xfree(dir); + } + } + + r = pa_config_parse(fn, NULL, state->item_table, state->proplist, false, state->userdata); + pa_xfree(path); + return r; + } + + if (*state->lvalue == '[') { + size_t k; + + k = strlen(state->lvalue); + pa_assert(k > 0); + + if (state->lvalue[k-1] != ']') { + pa_log("[%s:%u] Invalid section header.", state->filename, state->lineno); + return -1; + } + + pa_xfree(state->section); + state->section = pa_xstrndup(state->lvalue + 1, k-2); + + if (pa_streq(state->section, "Properties")) { + if (!state->proplist) { + pa_log("[%s:%u] \"Properties\" section is not allowed in this file.", state->filename, state->lineno); + return -1; + } + + state->in_proplist = true; + } else + state->in_proplist = false; + + return 0; + } + + if (!(state->rvalue = strchr(state->lvalue, '='))) { + pa_log("[%s:%u] Missing '='.", state->filename, state->lineno); + return -1; + } + + *state->rvalue = 0; + state->rvalue++; + + state->lvalue = pa_strip(state->lvalue); + state->rvalue = pa_strip(state->rvalue); + + if (state->in_proplist) + return proplist_assignment(state); + else + return normal_assignment(state); +} + +#ifndef OS_IS_WIN32 +static int conf_filter(const struct dirent *entry) { + return pa_endswith(entry->d_name, ".conf"); +} +#endif + +/* Go through the file and parse each line */ +int pa_config_parse(const char *filename, FILE *f, const pa_config_item *t, pa_proplist *proplist, bool use_dot_d, + void *userdata) { + int r = -1; + bool do_close = !f; + pa_config_parser_state state; + + pa_assert(filename); + pa_assert(t); + + pa_zero(state); + + if (!f && !(f = pa_fopen_cloexec(filename, "r"))) { + if (errno == ENOENT) { + pa_log_debug("Failed to open configuration file '%s': %s", filename, pa_cstrerror(errno)); + r = 0; + goto finish; + } + + pa_log_warn("Failed to open configuration file '%s': %s", filename, pa_cstrerror(errno)); + goto finish; + } + pa_log_debug("Parsing configuration file '%s'", filename); + + state.filename = filename; + state.item_table = t; + state.userdata = userdata; + + if (proplist) + state.proplist = pa_proplist_new(); + + while (!feof(f)) { + if (!fgets(state.buf, sizeof(state.buf), f)) { + if (feof(f)) + break; + + pa_log_warn("Failed to read configuration file '%s': %s", filename, pa_cstrerror(errno)); + goto finish; + } + + state.lineno++; + + if (parse_line(&state) < 0) + goto finish; + } + + if (proplist) + pa_proplist_update(proplist, PA_UPDATE_REPLACE, state.proplist); + + r = 0; + +finish: + if (state.proplist) + pa_proplist_free(state.proplist); + + pa_xfree(state.section); + + if (do_close && f) + fclose(f); + + if (use_dot_d) { +#ifdef OS_IS_WIN32 + char *dir_name = pa_sprintf_malloc("%s.d", filename); + char *pattern = pa_sprintf_malloc("%s\\*.conf", dir_name); + HANDLE fh; + WIN32_FIND_DATA wfd; + + fh = FindFirstFile(pattern, &wfd); + if (fh != INVALID_HANDLE_VALUE) { + do { + if (!(wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { + char *filename2 = pa_sprintf_malloc("%s\\%s", dir_name, wfd.cFileName); + pa_config_parse(filename2, NULL, t, proplist, false, userdata); + pa_xfree(filename2); + } + } while (FindNextFile(fh, &wfd)); + FindClose(fh); + } else { + DWORD err = GetLastError(); + + if (err == ERROR_PATH_NOT_FOUND) { + pa_log_debug("Pattern %s did not match any files, ignoring.", pattern); + } else { + LPVOID msgbuf; + DWORD fret = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&msgbuf, 0, NULL); + + if (fret != 0) { + pa_log_warn("FindFirstFile(%s) failed with error %ld (%s), ignoring.", pattern, err, (char*)msgbuf); + LocalFree(msgbuf); + } else { + pa_log_warn("FindFirstFile(%s) failed with error %ld, ignoring.", pattern, err); + pa_log_warn("FormatMessage failed with error %ld", GetLastError()); + } + } + } + + pa_xfree(pattern); + pa_xfree(dir_name); +#else + char *dir_name; + int n; + struct dirent **entries = NULL; + + dir_name = pa_sprintf_malloc("%s.d", filename); + + n = scandir(dir_name, &entries, conf_filter, alphasort); + if (n >= 0) { + int i; + + for (i = 0; i < n; i++) { + char *filename2; + + filename2 = pa_sprintf_malloc("%s" PA_PATH_SEP "%s", dir_name, entries[i]->d_name); + pa_config_parse(filename2, NULL, t, proplist, false, userdata); + pa_xfree(filename2); + + free(entries[i]); + } + + free(entries); + } else { + if (errno == ENOENT) + pa_log_debug("%s does not exist, ignoring.", dir_name); + else + pa_log_warn("scandir(\"%s\") failed: %s", dir_name, pa_cstrerror(errno)); + } + + pa_xfree(dir_name); +#endif + } + + return r; +} + +int pa_config_parse_int(pa_config_parser_state *state) { + int *i; + int32_t k; + + pa_assert(state); + + i = state->data; + + if (pa_atoi(state->rvalue, &k) < 0) { + pa_log("[%s:%u] Failed to parse numeric value: %s", state->filename, state->lineno, state->rvalue); + return -1; + } + + *i = (int) k; + return 0; +} + +int pa_config_parse_unsigned(pa_config_parser_state *state) { + unsigned *u; + uint32_t k; + + pa_assert(state); + + u = state->data; + + if (pa_atou(state->rvalue, &k) < 0) { + pa_log("[%s:%u] Failed to parse numeric value: %s", state->filename, state->lineno, state->rvalue); + return -1; + } + + *u = (unsigned) k; + return 0; +} + +int pa_config_parse_size(pa_config_parser_state *state) { + size_t *i; + uint32_t k; + + pa_assert(state); + + i = state->data; + + if (pa_atou(state->rvalue, &k) < 0) { + pa_log("[%s:%u] Failed to parse numeric value: %s", state->filename, state->lineno, state->rvalue); + return -1; + } + + *i = (size_t) k; + return 0; +} + +int pa_config_parse_bool(pa_config_parser_state *state) { + int k; + bool *b; + + pa_assert(state); + + b = state->data; + + if ((k = pa_parse_boolean(state->rvalue)) < 0) { + pa_log("[%s:%u] Failed to parse boolean value: %s", state->filename, state->lineno, state->rvalue); + return -1; + } + + *b = !!k; + + return 0; +} + +int pa_config_parse_not_bool(pa_config_parser_state *state) { + int k; + bool *b; + + pa_assert(state); + + b = state->data; + + if ((k = pa_parse_boolean(state->rvalue)) < 0) { + pa_log("[%s:%u] Failed to parse boolean value: %s", state->filename, state->lineno, state->rvalue); + return -1; + } + + *b = !k; + + return 0; +} + +int pa_config_parse_string(pa_config_parser_state *state) { + char **s; + + pa_assert(state); + + s = state->data; + + pa_xfree(*s); + *s = *state->rvalue ? pa_xstrdup(state->rvalue) : NULL; + return 0; +} diff --git a/src/pulsecore/conf-parser.h b/src/pulsecore/conf-parser.h new file mode 100644 index 0000000..7dc0ff9 --- /dev/null +++ b/src/pulsecore/conf-parser.h @@ -0,0 +1,87 @@ +#ifndef fooconfparserhfoo +#define fooconfparserhfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <stdio.h> + +#include <pulse/proplist.h> + +/* An abstract parser for simple, line based, shallow configuration + * files consisting of variable assignments only. */ + +typedef struct pa_config_parser_state pa_config_parser_state; + +typedef int (*pa_config_parser_cb_t)(pa_config_parser_state *state); + +/* Wraps info for parsing a specific configuration variable */ +typedef struct pa_config_item { + const char *lvalue; /* name of the variable */ + pa_config_parser_cb_t parse; /* Function that is called to parse the variable's value */ + void *data; /* Where to store the variable's data */ + const char *section; +} pa_config_item; + +struct pa_config_parser_state { + const char *filename; + unsigned lineno; + char *section; + char *lvalue; + char *rvalue; + void *data; /* The data pointer of the current pa_config_item. */ + void *userdata; /* The pointer that was given to pa_config_parse(). */ + + /* Private data to be used only by conf-parser.c. */ + const pa_config_item *item_table; + char buf[4096]; + pa_proplist *proplist; + bool in_proplist; +}; + +/* The configuration file parsing routine. Expects a table of + * pa_config_items in *t that is terminated by an item where lvalue is + * NULL. + * + * If use_dot_d is true, then after parsing the file named by the filename + * argument, the function will parse all files ending with ".conf" in + * alphabetical order from a directory whose name is filename + ".d", if such + * directory exists. + * + * Some configuration files may contain a Properties section, which + * is a bit special. Normally all accepted lvalues must be predefined + * in the pa_config_item table, but in the Properties section the + * pa_config_item table is ignored, and all lvalues are accepted (as + * long as they are valid proplist keys). If the proplist pointer is + * non-NULL, the parser will parse any section named "Properties" as + * properties, and those properties will be merged into the given + * proplist. If proplist is NULL, then sections named "Properties" + * are not allowed at all in the configuration file. */ +int pa_config_parse(const char *filename, FILE *f, const pa_config_item *t, pa_proplist *proplist, bool use_dot_d, + void *userdata); + +/* Generic parsers for integers, size_t, booleans and strings */ +int pa_config_parse_int(pa_config_parser_state *state); +int pa_config_parse_unsigned(pa_config_parser_state *state); +int pa_config_parse_size(pa_config_parser_state *state); +int pa_config_parse_bool(pa_config_parser_state *state); +int pa_config_parse_not_bool(pa_config_parser_state *state); +int pa_config_parse_string(pa_config_parser_state *state); + +#endif diff --git a/src/pulsecore/core-error.c b/src/pulsecore/core-error.c new file mode 100644 index 0000000..446de6c --- /dev/null +++ b/src/pulsecore/core-error.c @@ -0,0 +1,78 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <pulse/utf8.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/thread.h> +#include <pulsecore/macro.h> +#include <pulsecore/log.h> + +#include "core-error.h" + +PA_STATIC_TLS_DECLARE(cstrerror, pa_xfree); + +const char* pa_cstrerror(int errnum) { + const char *original = NULL; + char *translated, *t; + char errbuf[128]; + + if (errnum < 0) + errnum = -errnum; + + if ((t = PA_STATIC_TLS_GET(cstrerror))) + pa_xfree(t); + +#if defined(HAVE_STRERROR_R) && defined(__GLIBC__) + original = strerror_r(errnum, errbuf, sizeof(errbuf)); +#elif defined(HAVE_STRERROR_R) + if (strerror_r(errnum, errbuf, sizeof(errbuf)) == 0) { + errbuf[sizeof(errbuf) - 1] = 0; + original = errbuf; + } +#else + /* This might not be thread safe, but we hope for the best */ + original = strerror(errnum); +#endif + + /* The second condition is a Windows-ism */ + if (!original || !strcasecmp(original, "Unknown error")) { + pa_snprintf(errbuf, sizeof(errbuf), "Unknown error %d", errnum); + original = errbuf; + } + + if (!(translated = pa_locale_to_utf8(original))) { + pa_log_warn("Unable to convert error string to locale, filtering."); + translated = pa_utf8_filter(original); + } + + PA_STATIC_TLS_SET(cstrerror, translated); + + return translated; +} diff --git a/src/pulsecore/core-error.h b/src/pulsecore/core-error.h new file mode 100644 index 0000000..f4af5d5 --- /dev/null +++ b/src/pulsecore/core-error.h @@ -0,0 +1,39 @@ +#ifndef foocoreerrorhfoo +#define foocoreerrorhfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulse/cdecl.h> + +/** \file + * Error management */ + +PA_C_DECL_BEGIN + +/** A wrapper around the standard strerror() function that converts the + * string to UTF-8. The function is thread safe but the returned string is + * only guaranteed to exist until the thread exits or pa_cstrerror() is + * called again from the same thread. */ +const char* pa_cstrerror(int errnum); + +PA_C_DECL_END + +#endif diff --git a/src/pulsecore/core-format.c b/src/pulsecore/core-format.c new file mode 100644 index 0000000..1a21864 --- /dev/null +++ b/src/pulsecore/core-format.c @@ -0,0 +1,164 @@ +/*** + This file is part of PulseAudio. + + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "core-format.h" + +#include <pulse/def.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/macro.h> + +pa_format_info *pa_format_info_from_sample_spec2(const pa_sample_spec *ss, const pa_channel_map *map, bool set_format, + bool set_rate, bool set_channels) { + pa_format_info *format = NULL; + + pa_assert(ss); + + format = pa_format_info_new(); + format->encoding = PA_ENCODING_PCM; + + if (set_format) + pa_format_info_set_sample_format(format, ss->format); + + if (set_rate) + pa_format_info_set_rate(format, ss->rate); + + if (set_channels) { + pa_format_info_set_channels(format, ss->channels); + + if (map) { + if (map->channels != ss->channels) { + pa_log_debug("Channel map is incompatible with the sample spec."); + goto fail; + } + + pa_format_info_set_channel_map(format, map); + } + } + + return format; + +fail: + if (format) + pa_format_info_free(format); + + return NULL; +} + +int pa_format_info_to_sample_spec2(const pa_format_info *f, pa_sample_spec *ss, pa_channel_map *map, + const pa_sample_spec *fallback_ss, const pa_channel_map *fallback_map) { + int r, r2; + pa_sample_spec ss_local; + pa_channel_map map_local; + + pa_assert(f); + pa_assert(ss); + pa_assert(map); + pa_assert(fallback_ss); + pa_assert(fallback_map); + + if (!pa_format_info_is_pcm(f)) + return pa_format_info_to_sample_spec_fake(f, ss, map); + + r = pa_format_info_get_sample_format(f, &ss_local.format); + if (r == -PA_ERR_NOENTITY) + ss_local.format = fallback_ss->format; + else if (r < 0) + return r; + + pa_assert(pa_sample_format_valid(ss_local.format)); + + r = pa_format_info_get_rate(f, &ss_local.rate); + if (r == -PA_ERR_NOENTITY) + ss_local.rate = fallback_ss->rate; + else if (r < 0) + return r; + + pa_assert(pa_sample_rate_valid(ss_local.rate)); + + r = pa_format_info_get_channels(f, &ss_local.channels); + r2 = pa_format_info_get_channel_map(f, &map_local); + if (r == -PA_ERR_NOENTITY && r2 >= 0) + ss_local.channels = map_local.channels; + else if (r == -PA_ERR_NOENTITY) + ss_local.channels = fallback_ss->channels; + else if (r < 0) + return r; + + pa_assert(pa_channels_valid(ss_local.channels)); + + if (r2 >= 0 && map_local.channels != ss_local.channels) { + pa_log_debug("Channel map is not compatible with the sample spec."); + return -PA_ERR_INVALID; + } + + if (r2 == -PA_ERR_NOENTITY) { + if (fallback_map->channels == ss_local.channels) + map_local = *fallback_map; + else + pa_channel_map_init_extend(&map_local, ss_local.channels, PA_CHANNEL_MAP_DEFAULT); + } else if (r2 < 0) + return r2; + + pa_assert(pa_channel_map_valid(&map_local)); + pa_assert(ss_local.channels == map_local.channels); + + *ss = ss_local; + *map = map_local; + + return 0; +} + +int pa_format_info_to_sample_spec_fake(const pa_format_info *f, pa_sample_spec *ss, pa_channel_map *map) { + int rate; + + pa_assert(f); + pa_assert(ss); + + /* Note: When we add support for non-IEC61937 encapsulated compressed + * formats, this function should return a non-zero values for these. */ + + ss->format = PA_SAMPLE_S16LE; + if ((f->encoding == PA_ENCODING_TRUEHD_IEC61937) || + (f->encoding == PA_ENCODING_DTSHD_IEC61937)) { + ss->channels = 8; + if (map) { + /* We use the ALSA mapping, because most likely we will be using an + * ALSA sink. This doesn't really matter anyway, though, because + * the channel map doesn't affect anything with passthrough + * streams. The channel map just needs to be consistent with the + * sample spec's channel count. */ + pa_channel_map_init_auto(map, 8, PA_CHANNEL_MAP_ALSA); + } + } else { + ss->channels = 2; + if (map) + pa_channel_map_init_stereo(map); + } + + pa_return_val_if_fail(pa_format_info_get_prop_int(f, PA_PROP_FORMAT_RATE, &rate) == 0, -PA_ERR_INVALID); + ss->rate = (uint32_t) rate; + + if (f->encoding == PA_ENCODING_EAC3_IEC61937) + ss->rate *= 4; + + return 0; +} diff --git a/src/pulsecore/core-format.h b/src/pulsecore/core-format.h new file mode 100644 index 0000000..e2e02fe --- /dev/null +++ b/src/pulsecore/core-format.h @@ -0,0 +1,59 @@ +#ifndef foocoreformathfoo +#define foocoreformathfoo + +/*** + This file is part of PulseAudio. + + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulse/format.h> + +#include <stdbool.h> + +/* Convert a sample spec and an optional channel map to a new PCM format info + * object (remember to free it). If map is NULL, then the channel map will be + * left unspecified. If some fields of the sample spec should be ignored, pass + * false for set_format, set_rate and set_channels as appropriate, then those + * fields will be left unspecified. This function returns NULL if the input is + * invalid (for example, setting the sample rate was requested, but the rate + * in ss is invalid). + * + * pa_format_info_from_sample_spec() exists too. This "version 2" was created, + * because the original function doesn't provide the possibility of ignoring + * some of the sample spec fields. That functionality can't be added to the + * original function, because the function is a part of the public API and + * adding parameters to it would break the API. */ +pa_format_info *pa_format_info_from_sample_spec2(const pa_sample_spec *ss, const pa_channel_map *map, bool set_format, + bool set_rate, bool set_channels); + +/* Convert the format info into a sample spec and a channel map. If the format + * info doesn't contain some information, the fallback sample spec and channel + * map are used to populate the output. + * + * pa_format_info_to_sample_spec() exists too. This "version 2" was created, + * because the original function doesn't provide the possibility of specifying + * a fallback sample spec and channel map. That functionality can't be added to + * the original function, because the function is part of the public API and + * adding parameters to it would break the API. */ +int pa_format_info_to_sample_spec2(const pa_format_info *f, pa_sample_spec *ss, pa_channel_map *map, + const pa_sample_spec *fallback_ss, const pa_channel_map *fallback_map); + +/* For compressed formats. Converts the format info into a sample spec and a + * channel map that an ALSA device can use as its configuration parameters when + * playing back the compressed data. That is, the returned sample spec doesn't + * describe the audio content, but the device parameters. */ +int pa_format_info_to_sample_spec_fake(const pa_format_info *f, pa_sample_spec *ss, pa_channel_map *map); + +#endif diff --git a/src/pulsecore/core-rtclock.c b/src/pulsecore/core-rtclock.c new file mode 100644 index 0000000..2c2e286 --- /dev/null +++ b/src/pulsecore/core-rtclock.c @@ -0,0 +1,267 @@ +/*** + 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#ifdef OS_IS_DARWIN +#define _POSIX_C_SOURCE 1 +#endif + +#include <stddef.h> +#include <time.h> +#include <sys/time.h> +#include <errno.h> + +#ifdef HAVE_SYS_PRCTL_H +#include <sys/prctl.h> +#endif + +#ifdef OS_IS_DARWIN +#include <CoreServices/CoreServices.h> +#include <mach/mach.h> +#include <mach/mach_time.h> +#include <unistd.h> +#endif + +#ifdef HAVE_WINDOWS_H +#include <windows.h> +#endif + +#include <pulse/timeval.h> +#include <pulsecore/macro.h> +#include <pulsecore/core-error.h> + +#include "core-rtclock.h" + +#ifdef OS_IS_WIN32 +static int64_t counter_freq = 0; +#endif + +pa_usec_t pa_rtclock_age(const struct timeval *tv) { + struct timeval now; + pa_assert(tv); + + return pa_timeval_diff(pa_rtclock_get(&now), tv); +} + +struct timeval *pa_rtclock_get(struct timeval *tv) { + +#if defined(OS_IS_DARWIN) + uint64_t val, abs_time = mach_absolute_time(); + Nanoseconds nanos; + + nanos = AbsoluteToNanoseconds(*(AbsoluteTime *) &abs_time); + val = *(uint64_t *) &nanos; + + tv->tv_sec = val / PA_NSEC_PER_SEC; + tv->tv_usec = (val % PA_NSEC_PER_SEC) / PA_NSEC_PER_USEC; + + return tv; + +#elif defined(HAVE_CLOCK_GETTIME) + struct timespec ts; + +#ifdef CLOCK_MONOTONIC + /* No locking or atomic ops for no_monotonic here */ + static bool no_monotonic = false; + + if (!no_monotonic) + if (clock_gettime(CLOCK_MONOTONIC, &ts) < 0) + no_monotonic = true; + + if (no_monotonic) +#endif /* CLOCK_MONOTONIC */ + pa_assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0); + + pa_assert(tv); + + tv->tv_sec = ts.tv_sec; + tv->tv_usec = ts.tv_nsec / PA_NSEC_PER_USEC; + + return tv; +#elif defined(OS_IS_WIN32) + if (counter_freq > 0) { + LARGE_INTEGER count; + + pa_assert_se(QueryPerformanceCounter(&count)); + + tv->tv_sec = count.QuadPart / counter_freq; + tv->tv_usec = (count.QuadPart % counter_freq) * PA_USEC_PER_SEC / counter_freq; + + return tv; + } +#endif /* HAVE_CLOCK_GETTIME */ + + return pa_gettimeofday(tv); +} + +bool pa_rtclock_hrtimer(void) { + +#if defined (OS_IS_DARWIN) + mach_timebase_info_data_t tbi; + uint64_t time_nsec; + + mach_timebase_info(&tbi); + + /* nsec = nticks * (N/D) - we want 1 tick == resolution !? */ + time_nsec = tbi.numer / tbi.denom; + return time_nsec <= (long) (PA_HRTIMER_THRESHOLD_USEC*PA_NSEC_PER_USEC); + +#elif defined(HAVE_CLOCK_GETTIME) + struct timespec ts; + +#ifdef CLOCK_MONOTONIC + + if (clock_getres(CLOCK_MONOTONIC, &ts) >= 0) + return ts.tv_sec == 0 && ts.tv_nsec <= (long) (PA_HRTIMER_THRESHOLD_USEC*PA_NSEC_PER_USEC); + +#endif /* CLOCK_MONOTONIC */ + + pa_assert_se(clock_getres(CLOCK_REALTIME, &ts) == 0); + return ts.tv_sec == 0 && ts.tv_nsec <= (long) (PA_HRTIMER_THRESHOLD_USEC*PA_NSEC_PER_USEC); + +#elif defined(OS_IS_WIN32) + + if (counter_freq > 0) + return counter_freq >= (int64_t) (PA_USEC_PER_SEC/PA_HRTIMER_THRESHOLD_USEC); + +#endif /* HAVE_CLOCK_GETTIME */ + + return false; +} + +#define TIMER_SLACK_NS (int) ((500 * PA_NSEC_PER_USEC)) + +void pa_rtclock_hrtimer_enable(void) { + +#ifdef PR_SET_TIMERSLACK + int slack_ns; + + if ((slack_ns = prctl(PR_GET_TIMERSLACK, 0, 0, 0, 0)) < 0) { + pa_log_info("PR_GET_TIMERSLACK/PR_SET_TIMERSLACK not supported."); + return; + } + + pa_log_debug("Timer slack is set to %i us.", (int) (slack_ns/PA_NSEC_PER_USEC)); + + if (slack_ns > TIMER_SLACK_NS) { + slack_ns = TIMER_SLACK_NS; + + pa_log_debug("Setting timer slack to %i us.", (int) (slack_ns/PA_NSEC_PER_USEC)); + + if (prctl(PR_SET_TIMERSLACK, slack_ns, 0, 0, 0) < 0) { + pa_log_warn("PR_SET_TIMERSLACK failed: %s", pa_cstrerror(errno)); + return; + } + } + +#elif defined(OS_IS_WIN32) + LARGE_INTEGER freq; + + pa_assert_se(QueryPerformanceFrequency(&freq)); + counter_freq = freq.QuadPart; + +#endif +} + +struct timeval* pa_rtclock_from_wallclock(struct timeval *tv) { + struct timeval wc_now, rt_now; + + pa_assert(tv); + + pa_gettimeofday(&wc_now); + pa_rtclock_get(&rt_now); + + /* pa_timeval_sub() saturates on underflow! */ + + if (pa_timeval_cmp(&wc_now, tv) < 0) + pa_timeval_add(&rt_now, pa_timeval_diff(tv, &wc_now)); + else + pa_timeval_sub(&rt_now, pa_timeval_diff(&wc_now, tv)); + + *tv = rt_now; + + return tv; +} + +#ifdef HAVE_CLOCK_GETTIME +pa_usec_t pa_timespec_load(const struct timespec *ts) { + + if (PA_UNLIKELY(!ts)) + return PA_USEC_INVALID; + + return + (pa_usec_t) ts->tv_sec * PA_USEC_PER_SEC + + (pa_usec_t) ts->tv_nsec / PA_NSEC_PER_USEC; +} + +struct timespec* pa_timespec_store(struct timespec *ts, pa_usec_t v) { + pa_assert(ts); + + if (PA_UNLIKELY(v == PA_USEC_INVALID)) { + ts->tv_sec = PA_INT_TYPE_MAX(time_t); + ts->tv_nsec = (long) (PA_NSEC_PER_SEC-1); + return NULL; + } + + ts->tv_sec = (time_t) (v / PA_USEC_PER_SEC); + ts->tv_nsec = (long) ((v % PA_USEC_PER_SEC) * PA_NSEC_PER_USEC); + + return ts; +} +#endif + +static struct timeval* wallclock_from_rtclock(struct timeval *tv) { + struct timeval wc_now, rt_now; + + pa_assert(tv); + + pa_gettimeofday(&wc_now); + pa_rtclock_get(&rt_now); + + /* pa_timeval_sub() saturates on underflow! */ + + if (pa_timeval_cmp(&rt_now, tv) < 0) + pa_timeval_add(&wc_now, pa_timeval_diff(tv, &rt_now)); + else + pa_timeval_sub(&wc_now, pa_timeval_diff(&rt_now, tv)); + + *tv = wc_now; + + return tv; +} + +struct timeval* pa_timeval_rtstore(struct timeval *tv, pa_usec_t v, bool rtclock) { + pa_assert(tv); + + if (v == PA_USEC_INVALID) + return NULL; + + pa_timeval_store(tv, v); + + if (rtclock) + tv->tv_usec |= PA_TIMEVAL_RTCLOCK; + else + wallclock_from_rtclock(tv); + + return tv; +} diff --git a/src/pulsecore/core-rtclock.h b/src/pulsecore/core-rtclock.h new file mode 100644 index 0000000..89ad237 --- /dev/null +++ b/src/pulsecore/core-rtclock.h @@ -0,0 +1,53 @@ +#ifndef foopulsertclockhfoo +#define foopulsertclockhfoo + +/*** + 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulsecore/macro.h> +#include <pulse/sample.h> + +struct timeval; + +/* Something like pulse/timeval.h but based on CLOCK_MONOTONIC */ + +struct timeval *pa_rtclock_get(struct timeval *ts); + +pa_usec_t pa_rtclock_age(const struct timeval *tv); +bool pa_rtclock_hrtimer(void); +void pa_rtclock_hrtimer_enable(void); + +/* timer with a resolution better than this are considered high-resolution */ +#define PA_HRTIMER_THRESHOLD_USEC 10 + +/* bit to set in tv.tv_usec to mark that the timeval is in monotonic time */ +#define PA_TIMEVAL_RTCLOCK ((time_t) (1LU << 30)) + +struct timeval* pa_rtclock_from_wallclock(struct timeval *tv); + +#ifdef HAVE_CLOCK_GETTIME +struct timespec; + +pa_usec_t pa_timespec_load(const struct timespec *ts); +struct timespec* pa_timespec_store(struct timespec *ts, pa_usec_t v); +#endif + +struct timeval* pa_timeval_rtstore(struct timeval *tv, pa_usec_t v, bool rtclock); + +#endif diff --git a/src/pulsecore/core-scache.c b/src/pulsecore/core-scache.c new file mode 100644 index 0000000..026eeca --- /dev/null +++ b/src/pulsecore/core-scache.c @@ -0,0 +1,521 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2008 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <stdio.h> +#include <sys/types.h> +#include <dirent.h> +#include <sys/stat.h> +#include <errno.h> +#include <limits.h> +#include <time.h> + +#ifdef HAVE_GLOB_H +#include <glob.h> +#endif + +#ifdef HAVE_WINDOWS_H +#include <windows.h> +#endif + +#include <pulse/mainloop.h> +#include <pulse/channelmap.h> +#include <pulse/timeval.h> +#include <pulse/util.h> +#include <pulse/volume.h> +#include <pulse/xmalloc.h> +#include <pulse/rtclock.h> + +#include <pulsecore/sink-input.h> +#include <pulsecore/play-memchunk.h> +#include <pulsecore/core-subscribe.h> +#include <pulsecore/namereg.h> +#include <pulsecore/sound-file.h> +#include <pulsecore/core-rtclock.h> +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> +#include <pulsecore/core-error.h> +#include <pulsecore/macro.h> + +#include "core-scache.h" + +#define UNLOAD_POLL_TIME (60 * PA_USEC_PER_SEC) + +static void timeout_callback(pa_mainloop_api *m, pa_time_event *e, const struct timeval *t, void *userdata) { + pa_core *c = userdata; + + pa_assert(c); + pa_assert(c->mainloop == m); + pa_assert(c->scache_auto_unload_event == e); + + pa_scache_unload_unused(c); + + pa_core_rttime_restart(c, e, pa_rtclock_now() + UNLOAD_POLL_TIME); +} + +static void free_entry(pa_scache_entry *e) { + pa_assert(e); + + pa_namereg_unregister(e->core, e->name); + pa_subscription_post(e->core, PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE|PA_SUBSCRIPTION_EVENT_REMOVE, e->index); + pa_hook_fire(&e->core->hooks[PA_CORE_HOOK_SAMPLE_CACHE_UNLINK], e); + pa_xfree(e->name); + pa_xfree(e->filename); + if (e->memchunk.memblock) + pa_memblock_unref(e->memchunk.memblock); + if (e->proplist) + pa_proplist_free(e->proplist); + pa_xfree(e); +} + +static pa_scache_entry* scache_add_item(pa_core *c, const char *name, bool *new_sample) { + pa_scache_entry *e; + + pa_assert(c); + pa_assert(name); + pa_assert(new_sample); + + if ((e = pa_namereg_get(c, name, PA_NAMEREG_SAMPLE))) { + if (e->memchunk.memblock) + pa_memblock_unref(e->memchunk.memblock); + + pa_xfree(e->filename); + pa_proplist_clear(e->proplist); + + pa_assert(e->core == c); + + pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE|PA_SUBSCRIPTION_EVENT_CHANGE, e->index); + *new_sample = false; + } else { + e = pa_xnew(pa_scache_entry, 1); + + if (!pa_namereg_register(c, name, PA_NAMEREG_SAMPLE, e, true)) { + pa_xfree(e); + return NULL; + } + + e->name = pa_xstrdup(name); + e->core = c; + e->proplist = pa_proplist_new(); + + pa_idxset_put(c->scache, e, &e->index); + + pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE|PA_SUBSCRIPTION_EVENT_NEW, e->index); + *new_sample = true; + } + + e->last_used_time = 0; + pa_memchunk_reset(&e->memchunk); + e->filename = NULL; + e->lazy = false; + e->last_used_time = 0; + + pa_sample_spec_init(&e->sample_spec); + pa_channel_map_init(&e->channel_map); + pa_cvolume_init(&e->volume); + e->volume_is_set = false; + + pa_proplist_sets(e->proplist, PA_PROP_MEDIA_ROLE, "event"); + + return e; +} + +int pa_scache_add_item( + pa_core *c, + const char *name, + const pa_sample_spec *ss, + const pa_channel_map *map, + const pa_memchunk *chunk, + pa_proplist *p, + uint32_t *idx) { + + pa_scache_entry *e; + char st[PA_SAMPLE_SPEC_SNPRINT_MAX]; + pa_channel_map tmap; + bool new_sample; + + pa_assert(c); + pa_assert(name); + pa_assert(!ss || pa_sample_spec_valid(ss)); + pa_assert(!map || (pa_channel_map_valid(map) && ss && pa_channel_map_compatible(map, ss))); + + if (ss && !map) { + pa_channel_map_init_extend(&tmap, ss->channels, PA_CHANNEL_MAP_DEFAULT); + map = &tmap; + } + + if (chunk && chunk->length > PA_SCACHE_ENTRY_SIZE_MAX) + return -1; + + if (!(e = scache_add_item(c, name, &new_sample))) + return -1; + + pa_sample_spec_init(&e->sample_spec); + pa_channel_map_init(&e->channel_map); + pa_cvolume_init(&e->volume); + e->volume_is_set = false; + + if (ss) { + e->sample_spec = *ss; + pa_cvolume_reset(&e->volume, ss->channels); + } + + if (map) + e->channel_map = *map; + + if (chunk) { + e->memchunk = *chunk; + pa_memblock_ref(e->memchunk.memblock); + } + + if (p) + pa_proplist_update(e->proplist, PA_UPDATE_REPLACE, p); + + if (idx) + *idx = e->index; + + pa_log_debug("Created sample \"%s\" (#%d), %lu bytes with sample spec %s", + name, e->index, (unsigned long) e->memchunk.length, + pa_sample_spec_snprint(st, sizeof(st), &e->sample_spec)); + + pa_hook_fire(&e->core->hooks[new_sample ? PA_CORE_HOOK_SAMPLE_CACHE_NEW : PA_CORE_HOOK_SAMPLE_CACHE_CHANGED], e); + + return 0; +} + +int pa_scache_add_file(pa_core *c, const char *name, const char *filename, uint32_t *idx) { + pa_sample_spec ss; + pa_channel_map map; + pa_memchunk chunk; + int r; + pa_proplist *p; + +#ifdef OS_IS_WIN32 + char buf[MAX_PATH]; + + if (ExpandEnvironmentStrings(filename, buf, MAX_PATH)) + filename = buf; +#endif + + pa_assert(c); + pa_assert(name); + pa_assert(filename); + + p = pa_proplist_new(); + pa_proplist_sets(p, PA_PROP_MEDIA_FILENAME, filename); + + if (pa_sound_file_load(c->mempool, filename, &ss, &map, &chunk, p) < 0) { + pa_proplist_free(p); + return -1; + } + + r = pa_scache_add_item(c, name, &ss, &map, &chunk, p, idx); + pa_memblock_unref(chunk.memblock); + pa_proplist_free(p); + + return r; +} + +int pa_scache_add_file_lazy(pa_core *c, const char *name, const char *filename, uint32_t *idx) { + pa_scache_entry *e; + bool new_sample; + +#ifdef OS_IS_WIN32 + char buf[MAX_PATH]; + + if (ExpandEnvironmentStrings(filename, buf, MAX_PATH)) + filename = buf; +#endif + + pa_assert(c); + pa_assert(name); + pa_assert(filename); + + if (!(e = scache_add_item(c, name, &new_sample))) + return -1; + + e->lazy = true; + e->filename = pa_xstrdup(filename); + + pa_proplist_sets(e->proplist, PA_PROP_MEDIA_FILENAME, filename); + + if (!c->scache_auto_unload_event) + c->scache_auto_unload_event = pa_core_rttime_new(c, pa_rtclock_now() + UNLOAD_POLL_TIME, timeout_callback, c); + + if (idx) + *idx = e->index; + + pa_hook_fire(&e->core->hooks[new_sample ? PA_CORE_HOOK_SAMPLE_CACHE_NEW : PA_CORE_HOOK_SAMPLE_CACHE_CHANGED], e); + + return 0; +} + +int pa_scache_remove_item(pa_core *c, const char *name) { + pa_scache_entry *e; + + pa_assert(c); + pa_assert(name); + + if (!(e = pa_namereg_get(c, name, PA_NAMEREG_SAMPLE))) + return -1; + + pa_assert_se(pa_idxset_remove_by_data(c->scache, e, NULL) == e); + + pa_log_debug("Removed sample \"%s\"", name); + + free_entry(e); + + return 0; +} + +void pa_scache_free_all(pa_core *c) { + pa_assert(c); + + pa_idxset_remove_all(c->scache, (pa_free_cb_t) free_entry); + + if (c->scache_auto_unload_event) { + c->mainloop->time_free(c->scache_auto_unload_event); + c->scache_auto_unload_event = NULL; + } +} + +int pa_scache_play_item(pa_core *c, const char *name, pa_sink *sink, pa_volume_t volume, pa_proplist *p, uint32_t *sink_input_idx) { + pa_scache_entry *e; + pa_cvolume r; + pa_proplist *merged; + bool pass_volume; + + pa_assert(c); + pa_assert(name); + pa_assert(sink); + + if (!(e = pa_namereg_get(c, name, PA_NAMEREG_SAMPLE))) + return -1; + + merged = pa_proplist_new(); + pa_proplist_sets(merged, PA_PROP_MEDIA_NAME, name); + pa_proplist_sets(merged, PA_PROP_EVENT_ID, name); + + if (e->lazy && !e->memchunk.memblock) { + pa_channel_map old_channel_map = e->channel_map; + + if (pa_sound_file_load(c->mempool, e->filename, &e->sample_spec, &e->channel_map, &e->memchunk, merged) < 0) + goto fail; + + pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE|PA_SUBSCRIPTION_EVENT_CHANGE, e->index); + + if (e->volume_is_set) { + if (pa_cvolume_valid(&e->volume)) + pa_cvolume_remap(&e->volume, &old_channel_map, &e->channel_map); + else + pa_cvolume_reset(&e->volume, e->sample_spec.channels); + } + } + + if (!e->memchunk.memblock) + goto fail; + + pa_log_debug("Playing sample \"%s\" on \"%s\"", name, sink->name); + + pass_volume = true; + + if (e->volume_is_set && PA_VOLUME_IS_VALID(volume)) { + pa_cvolume_set(&r, e->sample_spec.channels, volume); + pa_sw_cvolume_multiply(&r, &r, &e->volume); + } else if (e->volume_is_set) + r = e->volume; + else if (PA_VOLUME_IS_VALID(volume)) + pa_cvolume_set(&r, e->sample_spec.channels, volume); + else + pass_volume = false; + + pa_proplist_update(merged, PA_UPDATE_REPLACE, e->proplist); + + if (p) + pa_proplist_update(merged, PA_UPDATE_REPLACE, p); + + if (pa_play_memchunk(sink, + &e->sample_spec, &e->channel_map, + &e->memchunk, + pass_volume ? &r : NULL, + merged, + PA_SINK_INPUT_NO_CREATE_ON_SUSPEND|PA_SINK_INPUT_KILL_ON_SUSPEND, sink_input_idx) < 0) + goto fail; + + pa_proplist_free(merged); + + if (e->lazy) + time(&e->last_used_time); + + return 0; + +fail: + pa_proplist_free(merged); + return -1; +} + +int pa_scache_play_item_by_name(pa_core *c, const char *name, const char*sink_name, pa_volume_t volume, pa_proplist *p, uint32_t *sink_input_idx) { + pa_sink *sink; + + pa_assert(c); + pa_assert(name); + + if (!(sink = pa_namereg_get(c, sink_name, PA_NAMEREG_SINK))) + return -1; + + return pa_scache_play_item(c, name, sink, volume, p, sink_input_idx); +} + +const char *pa_scache_get_name_by_id(pa_core *c, uint32_t id) { + pa_scache_entry *e; + + pa_assert(c); + pa_assert(id != PA_IDXSET_INVALID); + + if (!c->scache || !(e = pa_idxset_get_by_index(c->scache, id))) + return NULL; + + return e->name; +} + +uint32_t pa_scache_get_id_by_name(pa_core *c, const char *name) { + pa_scache_entry *e; + + pa_assert(c); + pa_assert(name); + + if (!(e = pa_namereg_get(c, name, PA_NAMEREG_SAMPLE))) + return PA_IDXSET_INVALID; + + return e->index; +} + +size_t pa_scache_total_size(pa_core *c) { + pa_scache_entry *e; + uint32_t idx; + size_t sum = 0; + + pa_assert(c); + + if (!c->scache || !pa_idxset_size(c->scache)) + return 0; + + PA_IDXSET_FOREACH(e, c->scache, idx) + if (e->memchunk.memblock) + sum += e->memchunk.length; + + return sum; +} + +void pa_scache_unload_unused(pa_core *c) { + pa_scache_entry *e; + time_t now; + uint32_t idx; + + pa_assert(c); + + if (!c->scache || !pa_idxset_size(c->scache)) + return; + + time(&now); + + PA_IDXSET_FOREACH(e, c->scache, idx) { + + if (!e->lazy || !e->memchunk.memblock) + continue; + + if (e->last_used_time + c->scache_idle_time > now) + continue; + + pa_memblock_unref(e->memchunk.memblock); + pa_memchunk_reset(&e->memchunk); + + pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE|PA_SUBSCRIPTION_EVENT_CHANGE, e->index); + } +} + +static void add_file(pa_core *c, const char *pathname) { + struct stat st; + const char *e; + + pa_core_assert_ref(c); + pa_assert(pathname); + + e = pa_path_get_filename(pathname); + + if (stat(pathname, &st) < 0) { + pa_log("stat('%s'): %s", pathname, pa_cstrerror(errno)); + return; + } + +#if defined(S_ISREG) && defined(S_ISLNK) + if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) +#endif + pa_scache_add_file_lazy(c, e, pathname, NULL); +} + +int pa_scache_add_directory_lazy(pa_core *c, const char *pathname) { + DIR *dir; + + pa_core_assert_ref(c); + pa_assert(pathname); + + /* First try to open this as directory */ + if (!(dir = opendir(pathname))) { +#ifdef HAVE_GLOB_H + glob_t p; + unsigned int i; + /* If that fails, try to open it as shell glob */ + + if (glob(pathname, GLOB_ERR|GLOB_NOSORT, NULL, &p) < 0) { + pa_log("failed to open directory '%s': %s", pathname, pa_cstrerror(errno)); + return -1; + } + + for (i = 0; i < p.gl_pathc; i++) + add_file(c, p.gl_pathv[i]); + + globfree(&p); +#else + return -1; +#endif + } else { + struct dirent *e; + + while ((e = readdir(dir))) { + char *p; + + if (e->d_name[0] == '.') + continue; + + p = pa_sprintf_malloc("%s" PA_PATH_SEP "%s", pathname, e->d_name); + add_file(c, p); + pa_xfree(p); + } + + closedir(dir); + } + + return 0; +} diff --git a/src/pulsecore/core-scache.h b/src/pulsecore/core-scache.h new file mode 100644 index 0000000..f0eacaa --- /dev/null +++ b/src/pulsecore/core-scache.h @@ -0,0 +1,68 @@ +#ifndef foocorescachehfoo +#define foocorescachehfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulsecore/core.h> +#include <pulsecore/memchunk.h> +#include <pulsecore/sink.h> + +#define PA_SCACHE_ENTRY_SIZE_MAX (1024*1024*16) + +typedef struct pa_scache_entry { + uint32_t index; + pa_core *core; + + char *name; + + pa_cvolume volume; + bool volume_is_set; + pa_sample_spec sample_spec; + pa_channel_map channel_map; + pa_memchunk memchunk; + + char *filename; + + bool lazy; + time_t last_used_time; + + pa_proplist *proplist; +} pa_scache_entry; + +int pa_scache_add_item(pa_core *c, const char *name, const pa_sample_spec *ss, const pa_channel_map *map, const pa_memchunk *chunk, pa_proplist *p, uint32_t *idx); +int pa_scache_add_file(pa_core *c, const char *name, const char *filename, uint32_t *idx); +int pa_scache_add_file_lazy(pa_core *c, const char *name, const char *filename, uint32_t *idx); + +int pa_scache_add_directory_lazy(pa_core *c, const char *pathname); + +int pa_scache_remove_item(pa_core *c, const char *name); +int pa_scache_play_item(pa_core *c, const char *name, pa_sink *sink, pa_volume_t volume, pa_proplist *p, uint32_t *sink_input_idx); +int pa_scache_play_item_by_name(pa_core *c, const char *name, const char*sink_name, pa_volume_t volume, pa_proplist *p, uint32_t *sink_input_idx); +void pa_scache_free_all(pa_core *c); + +const char *pa_scache_get_name_by_id(pa_core *c, uint32_t id); +uint32_t pa_scache_get_id_by_name(pa_core *c, const char *name); + +size_t pa_scache_total_size(pa_core *c); + +void pa_scache_unload_unused(pa_core *c); + +#endif diff --git a/src/pulsecore/core-subscribe.c b/src/pulsecore/core-subscribe.c new file mode 100644 index 0000000..8c91059 --- /dev/null +++ b/src/pulsecore/core-subscribe.c @@ -0,0 +1,262 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/log.h> +#include <pulsecore/macro.h> + +#include "core-subscribe.h" + +/* The subscription subsystem may be used to be notified whenever an + * entity (sink, source, ...) is created or deleted. Modules may + * register a callback function that is called whenever an event + * matching a subscription mask happens. The execution of the callback + * function is postponed to the next main loop iteration, i.e. is not + * called from within the stack frame the entity was created in. */ + +struct pa_subscription { + pa_core *core; + bool dead; + + pa_subscription_cb_t callback; + void *userdata; + pa_subscription_mask_t mask; + + PA_LLIST_FIELDS(pa_subscription); +}; + +struct pa_subscription_event { + pa_core *core; + + pa_subscription_event_type_t type; + uint32_t index; + + PA_LLIST_FIELDS(pa_subscription_event); +}; + +static void sched_event(pa_core *c); + +/* Allocate a new subscription object for the given subscription mask. Use the specified callback function and user data */ +pa_subscription* pa_subscription_new(pa_core *c, pa_subscription_mask_t m, pa_subscription_cb_t callback, void *userdata) { + pa_subscription *s; + + pa_assert(c); + pa_assert(m); + pa_assert(callback); + + s = pa_xnew(pa_subscription, 1); + s->core = c; + s->dead = false; + s->callback = callback; + s->userdata = userdata; + s->mask = m; + + PA_LLIST_PREPEND(pa_subscription, c->subscriptions, s); + return s; +} + +/* Free a subscription object, effectively marking it for deletion */ +void pa_subscription_free(pa_subscription*s) { + pa_assert(s); + pa_assert(!s->dead); + + s->dead = true; + sched_event(s->core); +} + +static void free_subscription(pa_subscription *s) { + pa_assert(s); + pa_assert(s->core); + + PA_LLIST_REMOVE(pa_subscription, s->core->subscriptions, s); + pa_xfree(s); +} + +static void free_event(pa_subscription_event *s) { + pa_assert(s); + pa_assert(s->core); + + if (!s->next) + s->core->subscription_event_last = s->prev; + + PA_LLIST_REMOVE(pa_subscription_event, s->core->subscription_event_queue, s); + pa_xfree(s); +} + +/* Free all subscription objects */ +void pa_subscription_free_all(pa_core *c) { + pa_assert(c); + + while (c->subscriptions) + free_subscription(c->subscriptions); + + while (c->subscription_event_queue) + free_event(c->subscription_event_queue); + + if (c->subscription_defer_event) { + c->mainloop->defer_free(c->subscription_defer_event); + c->subscription_defer_event = NULL; + } +} + +#ifdef DEBUG +static void dump_event(const char * prefix, pa_subscription_event*e) { + const char * const fac_table[] = { + [PA_SUBSCRIPTION_EVENT_SINK] = "SINK", + [PA_SUBSCRIPTION_EVENT_SOURCE] = "SOURCE", + [PA_SUBSCRIPTION_EVENT_SINK_INPUT] = "SINK_INPUT", + [PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT] = "SOURCE_OUTPUT", + [PA_SUBSCRIPTION_EVENT_MODULE] = "MODULE", + [PA_SUBSCRIPTION_EVENT_CLIENT] = "CLIENT", + [PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE] = "SAMPLE_CACHE", + [PA_SUBSCRIPTION_EVENT_SERVER] = "SERVER", + [PA_SUBSCRIPTION_EVENT_AUTOLOAD] = "AUTOLOAD", + [PA_SUBSCRIPTION_EVENT_CARD] = "CARD" + }; + + const char * const type_table[] = { + [PA_SUBSCRIPTION_EVENT_NEW] = "NEW", + [PA_SUBSCRIPTION_EVENT_CHANGE] = "CHANGE", + [PA_SUBSCRIPTION_EVENT_REMOVE] = "REMOVE" + }; + + pa_log_debug("%s event (%s|%s|%u)", + prefix, + fac_table[e->type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK], + type_table[e->type & PA_SUBSCRIPTION_EVENT_TYPE_MASK], + e->index); +} +#endif + +/* Deferred callback for dispatching subscription events */ +static void defer_cb(pa_mainloop_api *m, pa_defer_event *de, void *userdata) { + pa_core *c = userdata; + pa_subscription *s; + + pa_assert(c->mainloop == m); + pa_assert(c); + pa_assert(c->subscription_defer_event == de); + + c->mainloop->defer_enable(c->subscription_defer_event, 0); + + /* Dispatch queued events */ + + while (c->subscription_event_queue) { + pa_subscription_event *e = c->subscription_event_queue; + + for (s = c->subscriptions; s; s = s->next) { + + if (!s->dead && pa_subscription_match_flags(s->mask, e->type)) + s->callback(c, e->type, e->index, s->userdata); + } + +#ifdef DEBUG + dump_event("Dispatched", e); +#endif + free_event(e); + } + + /* Remove dead subscriptions */ + + s = c->subscriptions; + while (s) { + pa_subscription *n = s->next; + if (s->dead) + free_subscription(s); + s = n; + } +} + +/* Schedule an mainloop event so that a pending subscription event is dispatched */ +static void sched_event(pa_core *c) { + pa_assert(c); + + if (!c->subscription_defer_event) { + c->subscription_defer_event = c->mainloop->defer_new(c->mainloop, defer_cb, c); + pa_assert(c->subscription_defer_event); + } + + c->mainloop->defer_enable(c->subscription_defer_event, 1); +} + +/* Append a new subscription event to the subscription event queue and schedule a main loop event */ +void pa_subscription_post(pa_core *c, pa_subscription_event_type_t t, uint32_t idx) { + pa_subscription_event *e; + pa_assert(c); + + /* No need for queuing subscriptions of no one is listening */ + if (!c->subscriptions) + return; + + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_NEW) { + pa_subscription_event *i, *n; + + /* Check for duplicates */ + for (i = c->subscription_event_last; i; i = n) { + n = i->prev; + + /* not the same object type */ + if (((t ^ i->type) & PA_SUBSCRIPTION_EVENT_FACILITY_MASK)) + continue; + + /* not the same object */ + if (i->index != idx) + continue; + + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + /* This object is being removed, hence there is no + * point in keeping the old events regarding this + * entry in the queue. */ + + free_event(i); + pa_log_debug("Dropped redundant event due to remove event."); + continue; + } + + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_CHANGE) { + /* This object has changed. If a "new" or "change" event for + * this object is still in the queue we can exit. */ + + pa_log_debug("Dropped redundant event due to change event."); + return; + } + } + } + + e = pa_xnew(pa_subscription_event, 1); + e->core = c; + e->type = t; + e->index = idx; + + PA_LLIST_INSERT_AFTER(pa_subscription_event, c->subscription_event_queue, c->subscription_event_last, e); + c->subscription_event_last = e; + +#ifdef DEBUG + dump_event("Queued", e); +#endif + + sched_event(c); +} diff --git a/src/pulsecore/core-subscribe.h b/src/pulsecore/core-subscribe.h new file mode 100644 index 0000000..6032dc3 --- /dev/null +++ b/src/pulsecore/core-subscribe.h @@ -0,0 +1,37 @@ +#ifndef foocoresubscribehfoo +#define foocoresubscribehfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +typedef struct pa_subscription pa_subscription; +typedef struct pa_subscription_event pa_subscription_event; + +#include <pulsecore/core.h> +#include <pulsecore/native-common.h> + +typedef void (*pa_subscription_cb_t)(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata); + +pa_subscription* pa_subscription_new(pa_core *c, pa_subscription_mask_t m, pa_subscription_cb_t cb, void *userdata); +void pa_subscription_free(pa_subscription*s); +void pa_subscription_free_all(pa_core *c); + +void pa_subscription_post(pa_core *c, pa_subscription_event_type_t t, uint32_t idx); + +#endif diff --git a/src/pulsecore/core-util.c b/src/pulsecore/core-util.c new file mode 100644 index 0000000..601b1d1 --- /dev/null +++ b/src/pulsecore/core-util.c @@ -0,0 +1,3637 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2004 Joe Marcus Clarke + 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 by the Free Software Foundation; either version 2.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <math.h> +#include <stdarg.h> +#include <stdlib.h> +#include <signal.h> +#include <errno.h> +#include <string.h> +#include <stdio.h> +#include <fcntl.h> +#include <unistd.h> +#include <limits.h> +#include <ctype.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> + +#ifdef HAVE_LANGINFO_H +#include <langinfo.h> +#endif + +#ifdef HAVE_UNAME +#include <sys/utsname.h> +#endif + +#if defined(HAVE_REGEX_H) +#include <regex.h> +#elif defined(HAVE_PCREPOSIX_H) +#include <pcreposix.h> +#endif + +#ifdef HAVE_STRTOD_L +#ifdef HAVE_LOCALE_H +#include <locale.h> +#endif +#ifdef HAVE_XLOCALE_H +#include <xlocale.h> +#endif +#endif + +#ifdef HAVE_SYS_RESOURCE_H +#include <sys/resource.h> +#endif + +#ifdef HAVE_SYS_CAPABILITY_H +#include <sys/capability.h> +#endif + +#ifdef HAVE_SYS_MMAN_H +#include <sys/mman.h> +#endif + +#ifdef HAVE_PTHREAD +#include <pthread.h> +#endif + +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif + +#ifdef HAVE_WINDOWS_H +#include <windows.h> +#endif + +#ifndef ENOTSUP +#define ENOTSUP 135 +#endif + +#ifdef HAVE_PWD_H +#include <pwd.h> +#endif + +#ifdef HAVE_GRP_H +#include <grp.h> +#endif + +#ifdef HAVE_LIBSAMPLERATE +#include <samplerate.h> +#endif + +#ifdef HAVE_DBUS +#include <pulsecore/rtkit.h> +#endif + +#if defined(__linux__) && !defined(__ANDROID__) +#include <sys/personality.h> +#endif + +#ifdef HAVE_CPUID_H +#include <cpuid.h> +#endif + +#include <pulse/xmalloc.h> +#include <pulse/util.h> +#include <pulse/utf8.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/socket.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> +#include <pulsecore/thread.h> +#include <pulsecore/strbuf.h> +#include <pulsecore/usergroup.h> +#include <pulsecore/strlist.h> +#include <pulsecore/pipe.h> +#include <pulsecore/once.h> + +#include "core-util.h" + +/* Not all platforms have this */ +#ifndef MSG_NOSIGNAL +#define MSG_NOSIGNAL 0 +#endif + +#define NEWLINE "\r\n" +#define WHITESPACE "\n\r \t" + +static pa_strlist *recorded_env = NULL; + +#ifdef OS_IS_WIN32 +static fd_set nonblocking_fds; +#endif + +#ifdef OS_IS_WIN32 + +/* Returns the directory of the current DLL, with '/bin/' removed if it is the last component */ +char *pa_win32_get_toplevel(HANDLE handle) { + static char *toplevel = NULL; + + if (!toplevel) { + char library_path[MAX_PATH]; + char *p; + + if (!GetModuleFileName(handle, library_path, MAX_PATH)) + return NULL; + + toplevel = pa_xstrdup(library_path); + + p = strrchr(toplevel, PA_PATH_SEP_CHAR); + if (p) + *p = '\0'; + + p = strrchr(toplevel, PA_PATH_SEP_CHAR); + if (p && pa_streq(p + 1, "bin")) + *p = '\0'; + } + + return toplevel; +} + +#endif + +static void set_nonblock(int fd, bool nonblock) { + +#ifdef O_NONBLOCK + int v, nv; + pa_assert(fd >= 0); + + pa_assert_se((v = fcntl(fd, F_GETFL)) >= 0); + + if (nonblock) + nv = v | O_NONBLOCK; + else + nv = v & ~O_NONBLOCK; + + if (v != nv) + pa_assert_se(fcntl(fd, F_SETFL, nv) >= 0); + +#elif defined(OS_IS_WIN32) + u_long arg; + + if (nonblock) + arg = 1; + else + arg = 0; + + if (ioctlsocket(fd, FIONBIO, &arg) < 0) { + pa_assert_se(WSAGetLastError() == WSAENOTSOCK); + pa_log_warn("Only sockets can be made non-blocking!"); + return; + } + + /* There is no method to query status, so we remember all fds */ + if (nonblock) + FD_SET(fd, &nonblocking_fds); + else + FD_CLR(fd, &nonblocking_fds); +#else + pa_log_warn("Non-blocking I/O not supported.!"); +#endif + +} + +/** Make a file descriptor nonblock. Doesn't do any error checking */ +void pa_make_fd_nonblock(int fd) { + set_nonblock(fd, true); +} + +/** Make a file descriptor blocking. Doesn't do any error checking */ +void pa_make_fd_block(int fd) { + set_nonblock(fd, false); +} + +/** Query if a file descriptor is non-blocking */ +bool pa_is_fd_nonblock(int fd) { + +#ifdef O_NONBLOCK + int v; + pa_assert(fd >= 0); + + pa_assert_se((v = fcntl(fd, F_GETFL)) >= 0); + + return !!(v & O_NONBLOCK); + +#elif defined(OS_IS_WIN32) + return !!FD_ISSET(fd, &nonblocking_fds); +#else + return false; +#endif + +} + +/* Set the FD_CLOEXEC flag for a fd */ +void pa_make_fd_cloexec(int fd) { + +#ifdef FD_CLOEXEC + int v; + pa_assert(fd >= 0); + + pa_assert_se((v = fcntl(fd, F_GETFD, 0)) >= 0); + + if (!(v & FD_CLOEXEC)) + pa_assert_se(fcntl(fd, F_SETFD, v|FD_CLOEXEC) >= 0); +#endif + +} + +/** Creates a directory securely. Will create parent directories recursively if + * required. This will not update permissions on parent directories if they + * already exist, however. */ +int pa_make_secure_dir(const char* dir, mode_t m, uid_t uid, gid_t gid, bool update_perms) { + struct stat st; + int r, saved_errno; + bool retry = true; + + pa_assert(dir); + +again: +#ifdef OS_IS_WIN32 + r = mkdir(dir); +#else +{ + mode_t u; + u = umask((~m) & 0777); + r = mkdir(dir, m); + umask(u); +} +#endif + + if (r < 0 && errno == ENOENT && retry) { + /* If a parent directory in the path doesn't exist, try to create that + * first, then try again. */ + pa_make_secure_parent_dir(dir, m, uid, gid, false); + retry = false; + goto again; + } + + if (r < 0 && errno != EEXIST) + return -1; + +#if defined(HAVE_FSTAT) && !defined(OS_IS_WIN32) +{ + int fd; + if ((fd = open(dir, +#ifdef O_CLOEXEC + O_CLOEXEC| +#endif +#ifdef O_NOCTTY + O_NOCTTY| +#endif +#ifdef O_NOFOLLOW + O_NOFOLLOW| +#endif + O_RDONLY)) < 0) + goto fail; + + if (fstat(fd, &st) < 0) { + pa_assert_se(pa_close(fd) >= 0); + goto fail; + } + + if (!S_ISDIR(st.st_mode)) { + pa_assert_se(pa_close(fd) >= 0); + errno = EEXIST; + goto fail; + } + + if (!update_perms) { + pa_assert_se(pa_close(fd) >= 0); + return 0; + } + +#ifdef HAVE_FCHOWN + if (uid == (uid_t) -1) + uid = getuid(); + if (gid == (gid_t) -1) + gid = getgid(); + if (((st.st_uid != uid) || (st.st_gid != gid)) && fchown(fd, uid, gid) < 0) { + pa_assert_se(pa_close(fd) >= 0); + goto fail; + } +#endif + +#ifdef HAVE_FCHMOD + if ((st.st_mode & 07777) != m && fchmod(fd, m) < 0) { + pa_assert_se(pa_close(fd) >= 0); + goto fail; + }; +#endif + + pa_assert_se(pa_close(fd) >= 0); +} +#else + pa_log_warn("Secure directory creation not supported on this platform."); +#endif + + return 0; + +fail: + saved_errno = errno; + rmdir(dir); + errno = saved_errno; + + return -1; +} + +/* Return a newly allocated sting containing the parent directory of the specified file */ +char *pa_parent_dir(const char *fn) { + char *slash, *dir = pa_xstrdup(fn); + + if ((slash = (char*) pa_path_get_filename(dir)) == dir) { + pa_xfree(dir); + errno = ENOENT; + return NULL; + } + + *(slash-1) = 0; + return dir; +} + +/* Creates a the parent directory of the specified path securely */ +int pa_make_secure_parent_dir(const char *fn, mode_t m, uid_t uid, gid_t gid, bool update_perms) { + int ret = -1; + char *dir; + + if (!(dir = pa_parent_dir(fn))) + goto finish; + + if (pa_make_secure_dir(dir, m, uid, gid, update_perms) < 0) + goto finish; + + ret = 0; + +finish: + pa_xfree(dir); + return ret; +} + +/** Platform independent read function. Necessary since not all + * systems treat all file descriptors equal. If type is + * non-NULL it is used to cache the type of the fd. This is + * useful for making sure that only a single syscall is executed per + * function call. The variable pointed to should be initialized to 0 + * by the caller. */ +ssize_t pa_read(int fd, void *buf, size_t count, int *type) { + +#ifdef OS_IS_WIN32 + + if (!type || *type == 0) { + ssize_t r; + + if ((r = recv(fd, buf, count, 0)) >= 0) + return r; + + if (WSAGetLastError() != WSAENOTSOCK) { + errno = WSAGetLastError(); + return r; + } + + if (type) + *type = 1; + } + +#endif + + for (;;) { + ssize_t r; + + if ((r = read(fd, buf, count)) < 0) + if (errno == EINTR) + continue; + + return r; + } +} + +/** Similar to pa_read(), but handles writes */ +ssize_t pa_write(int fd, const void *buf, size_t count, int *type) { + + if (!type || *type == 0) { + ssize_t r; + + for (;;) { + if ((r = send(fd, buf, count, MSG_NOSIGNAL)) < 0) { + + if (errno == EINTR) + continue; + + break; + } + + return r; + } + +#ifdef OS_IS_WIN32 + if (WSAGetLastError() != WSAENOTSOCK) { + errno = WSAGetLastError(); + return r; + } +#else + if (errno != ENOTSOCK) + return r; +#endif + + if (type) + *type = 1; + } + + for (;;) { + ssize_t r; + + if ((r = write(fd, buf, count)) < 0) + if (errno == EINTR) + continue; + + return r; + } +} + +/** Calls read() in a loop. Makes sure that as much as 'size' bytes, + * unless EOF is reached or an error occurred */ +ssize_t pa_loop_read(int fd, void*data, size_t size, int *type) { + ssize_t ret = 0; + int _type; + + pa_assert(fd >= 0); + pa_assert(data); + pa_assert(size); + + if (!type) { + _type = 0; + type = &_type; + } + + while (size > 0) { + ssize_t r; + + if ((r = pa_read(fd, data, size, type)) < 0) + return r; + + if (r == 0) + break; + + ret += r; + data = (uint8_t*) data + r; + size -= (size_t) r; + } + + return ret; +} + +/** Similar to pa_loop_read(), but wraps write() */ +ssize_t pa_loop_write(int fd, const void*data, size_t size, int *type) { + ssize_t ret = 0; + int _type; + + pa_assert(fd >= 0); + pa_assert(data); + pa_assert(size); + + if (!type) { + _type = 0; + type = &_type; + } + + while (size > 0) { + ssize_t r; + + if ((r = pa_write(fd, data, size, type)) < 0) + return r; + + if (r == 0) + break; + + ret += r; + data = (const uint8_t*) data + r; + size -= (size_t) r; + } + + return ret; +} + +/** Platform independent close function. Necessary since not all + * systems treat all file descriptors equal. */ +int pa_close(int fd) { + +#ifdef OS_IS_WIN32 + int ret; + + FD_CLR(fd, &nonblocking_fds); + + if ((ret = closesocket(fd)) == 0) + return 0; + + if (WSAGetLastError() != WSAENOTSOCK) { + errno = WSAGetLastError(); + return ret; + } +#endif + + for (;;) { + int r; + + if ((r = close(fd)) < 0) + if (errno == EINTR) + continue; + + return r; + } +} + +/* Print a warning messages in case that the given signal is not + * blocked or trapped */ +void pa_check_signal_is_blocked(int sig) { +#ifdef HAVE_SIGACTION + struct sigaction sa; + sigset_t set; + + /* If POSIX threads are supported use thread-aware + * pthread_sigmask() function, to check if the signal is + * blocked. Otherwise fall back to sigprocmask() */ + +#ifdef HAVE_PTHREAD + if (pthread_sigmask(SIG_SETMASK, NULL, &set) < 0) { +#endif + if (sigprocmask(SIG_SETMASK, NULL, &set) < 0) { + pa_log("sigprocmask(): %s", pa_cstrerror(errno)); + return; + } +#ifdef HAVE_PTHREAD + } +#endif + + if (sigismember(&set, sig)) + return; + + /* Check whether the signal is trapped */ + + if (sigaction(sig, NULL, &sa) < 0) { + pa_log("sigaction(): %s", pa_cstrerror(errno)); + return; + } + + if (sa.sa_handler != SIG_DFL) + return; + + pa_log_warn("%s is not trapped. This might cause malfunction!", pa_sig2str(sig)); +#else /* HAVE_SIGACTION */ + pa_log_warn("%s might not be trapped. This might cause malfunction!", pa_sig2str(sig)); +#endif +} + +/* The following function is based on an example from the GNU libc + * documentation. This function is similar to GNU's asprintf(). */ +char *pa_sprintf_malloc(const char *format, ...) { + size_t size = 100; + char *c = NULL; + + pa_assert(format); + + for(;;) { + int r; + va_list ap; + + c = pa_xrealloc(c, size); + + va_start(ap, format); + r = vsnprintf(c, size, format, ap); + va_end(ap); + + c[size-1] = 0; + + if (r > -1 && (size_t) r < size) + return c; + + if (r > -1) /* glibc 2.1 */ + size = (size_t) r+1; + else /* glibc 2.0 */ + size *= 2; + } +} + +/* Same as the previous function, but use a va_list instead of an + * ellipsis */ +char *pa_vsprintf_malloc(const char *format, va_list ap) { + size_t size = 100; + char *c = NULL; + + pa_assert(format); + + for(;;) { + int r; + va_list aq; + + c = pa_xrealloc(c, size); + + va_copy(aq, ap); + r = vsnprintf(c, size, format, aq); + va_end(aq); + + c[size-1] = 0; + + if (r > -1 && (size_t) r < size) + return c; + + if (r > -1) /* glibc 2.1 */ + size = (size_t) r+1; + else /* glibc 2.0 */ + size *= 2; + } +} + +/* Similar to OpenBSD's strlcpy() function */ +char *pa_strlcpy(char *b, const char *s, size_t l) { + size_t k; + + pa_assert(b); + pa_assert(s); + pa_assert(l > 0); + + k = strlen(s); + + if (k > l-1) + k = l-1; + + memcpy(b, s, k); + b[k] = 0; + + return b; +} + +#ifdef HAVE_SYS_RESOURCE_H +static int set_nice(int nice_level) { +#ifdef HAVE_DBUS + DBusError error; + DBusConnection *bus; + int r; + + dbus_error_init(&error); +#endif + +#ifdef HAVE_SYS_RESOURCE_H + if (setpriority(PRIO_PROCESS, 0, nice_level) >= 0) { + pa_log_debug("setpriority() worked."); + return 0; + } +#endif + +#ifdef HAVE_DBUS + /* Try to talk to RealtimeKit */ + + if (!(bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error))) { + pa_log("Failed to connect to system bus: %s", error.message); + dbus_error_free(&error); + errno = -EIO; + return -1; + } + + /* We need to disable exit on disconnect because otherwise + * dbus_shutdown will kill us. See + * https://bugs.freedesktop.org/show_bug.cgi?id=16924 */ + dbus_connection_set_exit_on_disconnect(bus, FALSE); + + r = rtkit_make_high_priority(bus, 0, nice_level); + dbus_connection_close(bus); + dbus_connection_unref(bus); + + if (r >= 0) { + pa_log_debug("RealtimeKit worked."); + return 0; + } + + errno = -r; +#endif + + return -1; +} +#endif + +/* Raise the priority of the current process as much as possible that + * is <= the specified nice level..*/ +int pa_raise_priority(int nice_level) { + +#ifdef HAVE_SYS_RESOURCE_H + int n; + + if (set_nice(nice_level) >= 0) { + pa_log_info("Successfully gained nice level %i.", nice_level); + return 0; + } + + for (n = nice_level+1; n < 0; n++) + if (set_nice(n) >= 0) { + pa_log_info("Successfully acquired nice level %i, which is lower than the requested %i.", n, nice_level); + return 0; + } + + pa_log_info("Failed to acquire high-priority scheduling: %s", pa_cstrerror(errno)); + return -1; +#endif + +#ifdef OS_IS_WIN32 + if (nice_level < 0) { + if (!SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS)) { + pa_log_warn("SetPriorityClass() failed: 0x%08X", GetLastError()); + errno = EPERM; + return -1; + } + + pa_log_info("Successfully gained high priority class."); + } +#endif + + return 0; +} + +/* Reset the priority to normal, inverting the changes made by + * pa_raise_priority() and pa_thread_make_realtime()*/ +void pa_reset_priority(void) { +#ifdef HAVE_SYS_RESOURCE_H + struct sched_param sp; + + setpriority(PRIO_PROCESS, 0, 0); + + pa_zero(sp); + pthread_setschedparam(pthread_self(), SCHED_OTHER, &sp); +#endif + +#ifdef OS_IS_WIN32 + SetPriorityClass(GetCurrentProcess(), NORMAL_PRIORITY_CLASS); +#endif +} + +/* Check whenever any substring in v matches the provided regex. */ +int pa_match(const char *expr, const char *v) { +#if defined(HAVE_REGEX_H) || defined(HAVE_PCREPOSIX_H) + int k; + regex_t re; + int r; + + pa_assert(expr); + pa_assert(v); + + if (regcomp(&re, expr, REG_NOSUB|REG_EXTENDED) != 0) { + errno = EINVAL; + return -1; + } + + if ((k = regexec(&re, v, 0, NULL, 0)) == 0) + r = 1; + else if (k == REG_NOMATCH) + r = 0; + else + r = -1; + + regfree(&re); + + if (r < 0) + errno = EINVAL; + + return r; +#else + errno = ENOSYS; + return -1; +#endif +} + +/* Check whenever the provided regex pattern is valid. */ +bool pa_is_regex_valid(const char *expr) { +#if defined(HAVE_REGEX_H) || defined(HAVE_PCREPOSIX_H) + regex_t re; + + if (expr == NULL || regcomp(&re, expr, REG_NOSUB|REG_EXTENDED) != 0) { + return false; + } + + regfree(&re); + return true; +#else + return false; +#endif +} + +/* Try to parse a boolean string value.*/ +int pa_parse_boolean(const char *v) { + pa_assert(v); + + /* First we check language independent */ + if (pa_streq(v, "1") || !strcasecmp(v, "y") || !strcasecmp(v, "t") + || !strcasecmp(v, "yes") || !strcasecmp(v, "true") || !strcasecmp(v, "on")) + return 1; + else if (pa_streq(v, "0") || !strcasecmp(v, "n") || !strcasecmp(v, "f") + || !strcasecmp(v, "no") || !strcasecmp(v, "false") || !strcasecmp(v, "off")) + return 0; + +#ifdef HAVE_LANGINFO_H +{ + const char *expr; + /* And then we check language dependent */ + if ((expr = nl_langinfo(YESEXPR))) + if (expr[0]) + if (pa_match(expr, v) > 0) + return 1; + + if ((expr = nl_langinfo(NOEXPR))) + if (expr[0]) + if (pa_match(expr, v) > 0) + return 0; +} +#endif + + errno = EINVAL; + return -1; +} + +/* Try to parse a volume string to pa_volume_t. The allowed formats are: + * db, % and unsigned integer */ +int pa_parse_volume(const char *v, pa_volume_t *volume) { + int len; + uint32_t i; + double d; + char str[64]; + + pa_assert(v); + pa_assert(volume); + + len = strlen(v); + + if (len <= 0 || len >= 64) + return -1; + + memcpy(str, v, len + 1); + + if (str[len - 1] == '%') { + str[len - 1] = '\0'; + if (pa_atod(str, &d) < 0) + return -1; + + d = d / 100 * PA_VOLUME_NORM; + + if (d < 0 || d > PA_VOLUME_MAX) + return -1; + + *volume = d; + return 0; + } + + if (len > 2 && (str[len - 1] == 'b' || str[len - 1] == 'B') && + (str[len - 2] == 'd' || str[len - 2] == 'D')) { + str[len - 2] = '\0'; + if (pa_atod(str, &d) < 0) + return -1; + + if (d > pa_sw_volume_to_dB(PA_VOLUME_MAX)) + return -1; + + *volume = pa_sw_volume_from_dB(d); + return 0; + } + + if (pa_atou(v, &i) < 0 || !PA_VOLUME_IS_VALID(i)) + return -1; + + *volume = i; + return 0; +} + +/* Split the specified string wherever one of the characters in delimiter + * occurs. Each time it is called returns a newly allocated string + * with pa_xmalloc(). The variable state points to, should be + * initialized to NULL before the first call. */ +char *pa_split(const char *c, const char *delimiter, const char**state) { + const char *current = *state ? *state : c; + size_t l; + + if (!*current) + return NULL; + + l = strcspn(current, delimiter); + *state = current+l; + + if (**state) + (*state)++; + + return pa_xstrndup(current, l); +} + +/* Split the specified string wherever one of the characters in delimiter + * occurs. Each time it is called returns a pointer to the substring within the + * string and the length in 'n'. Note that the resultant string cannot be used + * as-is without the length parameter, since it is merely pointing to a point + * within the original string. The variable state points to, should be + * initialized to NULL before the first call. */ +const char *pa_split_in_place(const char *c, const char *delimiter, size_t *n, const char**state) { + const char *current = *state ? *state : c; + size_t l; + + if (!*current) + return NULL; + + l = strcspn(current, delimiter); + *state = current+l; + + if (**state) + (*state)++; + + *n = l; + return current; +} + +/* Split a string into words. Otherwise similar to pa_split(). */ +char *pa_split_spaces(const char *c, const char **state) { + const char *current = *state ? *state : c; + size_t l; + + if (!*current || *c == 0) + return NULL; + + current += strspn(current, WHITESPACE); + l = strcspn(current, WHITESPACE); + + *state = current+l; + + return pa_xstrndup(current, l); +} + +/* Similar to pa_split_spaces, except this returns a string in-place. + Returned string is generally not NULL-terminated. + See pa_split_in_place(). */ +const char *pa_split_spaces_in_place(const char *c, size_t *n, const char **state) { + const char *current = *state ? *state : c; + size_t l; + + if (!*current || *c == 0) + return NULL; + + current += strspn(current, WHITESPACE); + l = strcspn(current, WHITESPACE); + + *state = current+l; + + *n = l; + return current; +} + +PA_STATIC_TLS_DECLARE(signame, pa_xfree); + +/* Return the name of an UNIX signal. Similar to Solaris sig2str() */ +const char *pa_sig2str(int sig) { + char *t; + + if (sig <= 0) + goto fail; + +#ifdef NSIG + if (sig >= NSIG) + goto fail; +#endif + +#ifdef HAVE_SIG2STR + { + char buf[SIG2STR_MAX]; + + if (sig2str(sig, buf) == 0) { + pa_xfree(PA_STATIC_TLS_GET(signame)); + t = pa_sprintf_malloc("SIG%s", buf); + PA_STATIC_TLS_SET(signame, t); + return t; + } + } +#else + + switch (sig) { +#ifdef SIGHUP + case SIGHUP: return "SIGHUP"; +#endif + case SIGINT: return "SIGINT"; +#ifdef SIGQUIT + case SIGQUIT: return "SIGQUIT"; +#endif + case SIGILL: return "SIGULL"; +#ifdef SIGTRAP + case SIGTRAP: return "SIGTRAP"; +#endif + case SIGABRT: return "SIGABRT"; +#ifdef SIGBUS + case SIGBUS: return "SIGBUS"; +#endif + case SIGFPE: return "SIGFPE"; +#ifdef SIGKILL + case SIGKILL: return "SIGKILL"; +#endif +#ifdef SIGUSR1 + case SIGUSR1: return "SIGUSR1"; +#endif + case SIGSEGV: return "SIGSEGV"; +#ifdef SIGUSR2 + case SIGUSR2: return "SIGUSR2"; +#endif +#ifdef SIGPIPE + case SIGPIPE: return "SIGPIPE"; +#endif +#ifdef SIGALRM + case SIGALRM: return "SIGALRM"; +#endif + case SIGTERM: return "SIGTERM"; +#ifdef SIGSTKFLT + case SIGSTKFLT: return "SIGSTKFLT"; +#endif +#ifdef SIGCHLD + case SIGCHLD: return "SIGCHLD"; +#endif +#ifdef SIGCONT + case SIGCONT: return "SIGCONT"; +#endif +#ifdef SIGSTOP + case SIGSTOP: return "SIGSTOP"; +#endif +#ifdef SIGTSTP + case SIGTSTP: return "SIGTSTP"; +#endif +#ifdef SIGTTIN + case SIGTTIN: return "SIGTTIN"; +#endif +#ifdef SIGTTOU + case SIGTTOU: return "SIGTTOU"; +#endif +#ifdef SIGURG + case SIGURG: return "SIGURG"; +#endif +#ifdef SIGXCPU + case SIGXCPU: return "SIGXCPU"; +#endif +#ifdef SIGXFSZ + case SIGXFSZ: return "SIGXFSZ"; +#endif +#ifdef SIGVTALRM + case SIGVTALRM: return "SIGVTALRM"; +#endif +#ifdef SIGPROF + case SIGPROF: return "SIGPROF"; +#endif +#ifdef SIGWINCH + case SIGWINCH: return "SIGWINCH"; +#endif +#ifdef SIGIO + case SIGIO: return "SIGIO"; +#endif +#ifdef SIGPWR + case SIGPWR: return "SIGPWR"; +#endif +#ifdef SIGSYS + case SIGSYS: return "SIGSYS"; +#endif + } + +#ifdef SIGRTMIN + if (sig >= SIGRTMIN && sig <= SIGRTMAX) { + pa_xfree(PA_STATIC_TLS_GET(signame)); + t = pa_sprintf_malloc("SIGRTMIN+%i", sig - SIGRTMIN); + PA_STATIC_TLS_SET(signame, t); + return t; + } +#endif + +#endif + +fail: + + pa_xfree(PA_STATIC_TLS_GET(signame)); + t = pa_sprintf_malloc("SIG%i", sig); + PA_STATIC_TLS_SET(signame, t); + return t; +} + +#ifdef HAVE_GRP_H + +/* Check whether the specified GID and the group name match */ +static int is_group(gid_t gid, const char *name) { + struct group *group = NULL; + int r = -1; + + errno = 0; + if (!(group = pa_getgrgid_malloc(gid))) { + if (!errno) + errno = ENOENT; + + pa_log("pa_getgrgid_malloc(%u): %s", gid, pa_cstrerror(errno)); + + goto finish; + } + + r = pa_streq(name, group->gr_name); + +finish: + pa_getgrgid_free(group); + + return r; +} + +/* Check the current user is member of the specified group */ +int pa_own_uid_in_group(const char *name, gid_t *gid) { + GETGROUPS_T *gids, tgid; + long n = sysconf(_SC_NGROUPS_MAX); + int r = -1, i, k; + + pa_assert(n > 0); + + gids = pa_xmalloc(sizeof(GETGROUPS_T) * (size_t) n); + + if ((n = getgroups((int) n, gids)) < 0) { + pa_log("getgroups(): %s", pa_cstrerror(errno)); + goto finish; + } + + for (i = 0; i < n; i++) { + + if ((k = is_group(gids[i], name)) < 0) + goto finish; + else if (k > 0) { + *gid = gids[i]; + r = 1; + goto finish; + } + } + + if ((k = is_group(tgid = getgid(), name)) < 0) + goto finish; + else if (k > 0) { + *gid = tgid; + r = 1; + goto finish; + } + + r = 0; + +finish: + + pa_xfree(gids); + return r; +} + +/* Check whether the specific user id is a member of the specified group */ +int pa_uid_in_group(uid_t uid, const char *name) { + struct group *group = NULL; + char **i; + int r = -1; + + errno = 0; + if (!(group = pa_getgrnam_malloc(name))) { + if (!errno) + errno = ENOENT; + goto finish; + } + + r = 0; + for (i = group->gr_mem; *i; i++) { + struct passwd *pw = NULL; + + errno = 0; + if (!(pw = pa_getpwnam_malloc(*i))) + continue; + + if (pw->pw_uid == uid) + r = 1; + + pa_getpwnam_free(pw); + + if (r == 1) + break; + } + +finish: + pa_getgrnam_free(group); + + return r; +} + +/* Get the GID of a given group, return (gid_t) -1 on failure. */ +gid_t pa_get_gid_of_group(const char *name) { + gid_t ret = (gid_t) -1; + struct group *gr = NULL; + + errno = 0; + if (!(gr = pa_getgrnam_malloc(name))) { + if (!errno) + errno = ENOENT; + goto finish; + } + + ret = gr->gr_gid; + +finish: + pa_getgrnam_free(gr); + return ret; +} + +int pa_check_in_group(gid_t g) { + gid_t gids[NGROUPS_MAX]; + int r; + + if ((r = getgroups(NGROUPS_MAX, gids)) < 0) + return -1; + + for (; r > 0; r--) + if (gids[r-1] == g) + return 1; + + return 0; +} + +#else /* HAVE_GRP_H */ + +int pa_own_uid_in_group(const char *name, gid_t *gid) { + errno = ENOTSUP; + return -1; + +} + +int pa_uid_in_group(uid_t uid, const char *name) { + errno = ENOTSUP; + return -1; +} + +gid_t pa_get_gid_of_group(const char *name) { + errno = ENOTSUP; + return (gid_t) -1; +} + +int pa_check_in_group(gid_t g) { + errno = ENOTSUP; + return -1; +} + +#endif + +/* Lock or unlock a file entirely. + (advisory on UNIX, mandatory on Windows) */ +int pa_lock_fd(int fd, int b) { +#ifdef F_SETLKW + struct flock f_lock; + + /* Try a R/W lock first */ + + f_lock.l_type = (short) (b ? F_WRLCK : F_UNLCK); + f_lock.l_whence = SEEK_SET; + f_lock.l_start = 0; + f_lock.l_len = 0; + + if (fcntl(fd, F_SETLKW, &f_lock) >= 0) + return 0; + + /* Perhaps the file descriptor was opened for read only, than try again with a read lock. */ + if (b && errno == EBADF) { + f_lock.l_type = F_RDLCK; + if (fcntl(fd, F_SETLKW, &f_lock) >= 0) + return 0; + } + + pa_log("%slock: %s", !b ? "un" : "", pa_cstrerror(errno)); +#endif + +#ifdef OS_IS_WIN32 + HANDLE h = (HANDLE) _get_osfhandle(fd); + + if (b && LockFile(h, 0, 0, 0xFFFFFFFF, 0xFFFFFFFF)) + return 0; + if (!b && UnlockFile(h, 0, 0, 0xFFFFFFFF, 0xFFFFFFFF)) + return 0; + + pa_log("%slock failed: 0x%08X", !b ? "un" : "", GetLastError()); + + /* FIXME: Needs to set errno! */ +#endif + + return -1; +} + +/* Remove trailing newlines from a string */ +char* pa_strip_nl(char *s) { + pa_assert(s); + + s[strcspn(s, NEWLINE)] = 0; + return s; +} + +char *pa_strip(char *s) { + char *e, *l = NULL; + + /* Drops trailing whitespace. Modifies the string in + * place. Returns pointer to first non-space character */ + + s += strspn(s, WHITESPACE); + + for (e = s; *e; e++) + if (!strchr(WHITESPACE, *e)) + l = e; + + if (l) + *(l+1) = 0; + else + *s = 0; + + return s; +} + +/* Create a temporary lock file and lock it. */ +int pa_lock_lockfile(const char *fn) { + int fd; + pa_assert(fn); + + for (;;) { + struct stat st; + + if ((fd = pa_open_cloexec(fn, O_CREAT|O_RDWR +#ifdef O_NOFOLLOW + |O_NOFOLLOW +#endif + , S_IRUSR|S_IWUSR)) < 0) { + pa_log_warn("Failed to create lock file '%s': %s", fn, pa_cstrerror(errno)); + goto fail; + } + + if (pa_lock_fd(fd, 1) < 0) { + pa_log_warn("Failed to lock file '%s'.", fn); + goto fail; + } + + if (fstat(fd, &st) < 0) { + pa_log_warn("Failed to fstat() file '%s': %s", fn, pa_cstrerror(errno)); + goto fail; + } + + /* Check whether the file has been removed meanwhile. When yes, + * restart this loop, otherwise, we're done */ + if (st.st_nlink >= 1) + break; + + if (pa_lock_fd(fd, 0) < 0) { + pa_log_warn("Failed to unlock file '%s'.", fn); + goto fail; + } + + if (pa_close(fd) < 0) { + pa_log_warn("Failed to close file '%s': %s", fn, pa_cstrerror(errno)); + fd = -1; + goto fail; + } + } + + return fd; + +fail: + + if (fd >= 0) { + int saved_errno = errno; + pa_close(fd); + errno = saved_errno; + } + + return -1; +} + +/* Unlock a temporary lock file */ +int pa_unlock_lockfile(const char *fn, int fd) { + int r = 0; + pa_assert(fd >= 0); + + if (fn) { + if (unlink(fn) < 0) { + pa_log_warn("Unable to remove lock file '%s': %s", fn, pa_cstrerror(errno)); + r = -1; + } + } + + if (pa_lock_fd(fd, 0) < 0) { + pa_log_warn("Failed to unlock file '%s'.", fn); + r = -1; + } + + if (pa_close(fd) < 0) { + pa_log_warn("Failed to close '%s': %s", fn, pa_cstrerror(errno)); + r = -1; + } + + return r; +} + +static int check_ours(const char *p) { + struct stat st; + + pa_assert(p); + + if (stat(p, &st) < 0) + return -errno; + +#ifdef HAVE_GETUID + if (st.st_uid != getuid() && st.st_uid != 0) + return -EACCES; +#endif + + return 0; +} + +static char *get_pulse_home(void) { + char *h, *ret; + int t; + + h = pa_get_home_dir_malloc(); + if (!h) { + pa_log_error("Failed to get home directory."); + return NULL; + } + + t = check_ours(h); + if (t < 0 && t != -ENOENT) { + pa_log_error("Home directory not accessible: %s", pa_cstrerror(-t)); + pa_xfree(h); + return NULL; + } + + /* If the old directory exists, use it. */ + ret = pa_sprintf_malloc("%s" PA_PATH_SEP ".pulse", h); + pa_xfree(h); + if (access(ret, F_OK) >= 0) + return ret; + free(ret); + + /* Otherwise go for the XDG compliant directory. */ + if (pa_get_config_home_dir(&ret) < 0) + return NULL; + + return ret; +} + +char *pa_get_state_dir(void) { + char *d; + + /* The state directory shall contain dynamic data that should be + * kept across reboots, and is private to this user */ + + if (!(d = pa_xstrdup(getenv("PULSE_STATE_PATH")))) + if (!(d = get_pulse_home())) + return NULL; + + /* If PULSE_STATE_PATH and PULSE_RUNTIME_PATH point to the same + * dir then this will break. */ + + if (pa_make_secure_dir(d, 0700U, (uid_t) -1, (gid_t) -1, true) < 0) { + pa_log_error("Failed to create secure directory (%s): %s", d, pa_cstrerror(errno)); + pa_xfree(d); + return NULL; + } + + return d; +} + +char *pa_get_home_dir_malloc(void) { + char *homedir; + size_t allocated = 128; + + for (;;) { + homedir = pa_xmalloc(allocated); + + if (!pa_get_home_dir(homedir, allocated)) { + pa_xfree(homedir); + return NULL; + } + + if (strlen(homedir) < allocated - 1) + break; + + pa_xfree(homedir); + allocated *= 2; + } + + return homedir; +} + +int pa_append_to_home_dir(const char *path, char **_r) { + char *home_dir; + + pa_assert(path); + pa_assert(_r); + + home_dir = pa_get_home_dir_malloc(); + if (!home_dir) { + pa_log("Failed to get home directory."); + return -PA_ERR_NOENTITY; + } + + *_r = pa_sprintf_malloc("%s" PA_PATH_SEP "%s", home_dir, path); + pa_xfree(home_dir); + return 0; +} + +int pa_get_config_home_dir(char **_r) { + const char *e; + char *home_dir; + + pa_assert(_r); + + e = getenv("XDG_CONFIG_HOME"); + if (e && *e) { + *_r = pa_sprintf_malloc("%s" PA_PATH_SEP "pulse", e); + return 0; + } + + home_dir = pa_get_home_dir_malloc(); + if (!home_dir) + return -PA_ERR_NOENTITY; + + *_r = pa_sprintf_malloc("%s" PA_PATH_SEP ".config" PA_PATH_SEP "pulse", home_dir); + pa_xfree(home_dir); + return 0; +} + +int pa_append_to_config_home_dir(const char *path, char **_r) { + int r; + char *config_home_dir; + + pa_assert(path); + pa_assert(_r); + + r = pa_get_config_home_dir(&config_home_dir); + if (r < 0) + return r; + + *_r = pa_sprintf_malloc("%s" PA_PATH_SEP "%s", config_home_dir, path); + pa_xfree(config_home_dir); + return 0; +} + +char *pa_get_binary_name_malloc(void) { + char *t; + size_t allocated = 128; + + for (;;) { + t = pa_xmalloc(allocated); + + if (!pa_get_binary_name(t, allocated)) { + pa_xfree(t); + return NULL; + } + + if (strlen(t) < allocated - 1) + break; + + pa_xfree(t); + allocated *= 2; + } + + return t; +} + +static char* make_random_dir(mode_t m) { + static const char table[] = + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789"; + + char *fn; + size_t pathlen; + + fn = pa_sprintf_malloc("%s" PA_PATH_SEP "pulse-XXXXXXXXXXXX", pa_get_temp_dir()); + pathlen = strlen(fn); + + for (;;) { + size_t i; + int r; + mode_t u; + int saved_errno; + + for (i = pathlen - 12; i < pathlen; i++) + fn[i] = table[rand() % (sizeof(table)-1)]; + + u = umask((~m) & 0777); +#ifndef OS_IS_WIN32 + r = mkdir(fn, m); +#else + r = mkdir(fn); +#endif + + saved_errno = errno; + umask(u); + errno = saved_errno; + + if (r >= 0) + return fn; + + if (errno != EEXIST) { + pa_log_error("Failed to create random directory %s: %s", fn, pa_cstrerror(errno)); + pa_xfree(fn); + return NULL; + } + } +} + +static int make_random_dir_and_link(mode_t m, const char *k) { + char *p; + + if (!(p = make_random_dir(m))) + return -1; + +#ifdef HAVE_SYMLINK + if (symlink(p, k) < 0) { + int saved_errno = errno; + + if (errno != EEXIST) + pa_log_error("Failed to symlink %s to %s: %s", k, p, pa_cstrerror(errno)); + + rmdir(p); + pa_xfree(p); + + errno = saved_errno; + return -1; + } +#else + pa_xfree(p); + return -1; +#endif + + pa_xfree(p); + return 0; +} + +char *pa_get_runtime_dir(void) { + char *d, *k = NULL, *p = NULL, *t = NULL, *mid; + mode_t m; + + /* The runtime directory shall contain dynamic data that needs NOT + * to be kept across reboots and is usually private to the user, + * except in system mode, where it might be accessible by other + * users, too. Since we need POSIX locking and UNIX sockets in + * this directory, we try XDG_RUNTIME_DIR first, and if that isn't + * set create a directory in $HOME and link it to a random subdir + * in /tmp, if it was not explicitly configured. */ + + m = pa_in_system_mode() ? 0755U : 0700U; + + /* Use the explicitly configured value if it is set */ + d = getenv("PULSE_RUNTIME_PATH"); + if (d) { + + if (pa_make_secure_dir(d, m, (uid_t) -1, (gid_t) -1, true) < 0) { + pa_log_error("Failed to create secure directory (%s): %s", d, pa_cstrerror(errno)); + goto fail; + } + + return pa_xstrdup(d); + } + + /* Use the XDG standard for the runtime directory. */ + d = getenv("XDG_RUNTIME_DIR"); + if (d) { +#ifdef HAVE_GETUID + struct stat st; + if (stat(d, &st) == 0 && st.st_uid != getuid()) { + pa_log(_("XDG_RUNTIME_DIR (%s) is not owned by us (uid %d), but by uid %d! " + "(This could e.g. happen if you try to connect to a non-root PulseAudio as a root user, over the native protocol. Don't do that.)"), + d, getuid(), st.st_uid); + goto fail; + } +#endif + + k = pa_sprintf_malloc("%s" PA_PATH_SEP "pulse", d); + + if (pa_make_secure_dir(k, m, (uid_t) -1, (gid_t) -1, true) < 0) { + pa_log_error("Failed to create secure directory (%s): %s", k, pa_cstrerror(errno)); + goto fail; + } + + return k; + } + + /* XDG_RUNTIME_DIR wasn't set, use the old legacy fallback */ + d = get_pulse_home(); + if (!d) + goto fail; + + if (pa_make_secure_dir(d, m, (uid_t) -1, (gid_t) -1, true) < 0) { + pa_log_error("Failed to create secure directory (%s): %s", d, pa_cstrerror(errno)); + pa_xfree(d); + goto fail; + } + + mid = pa_machine_id(); + if (!mid) { + pa_xfree(d); + goto fail; + } + + k = pa_sprintf_malloc("%s" PA_PATH_SEP "%s-runtime", d, mid); + pa_xfree(d); + pa_xfree(mid); + + for (;;) { + /* OK, first let's check if the "runtime" symlink already exists */ + + p = pa_readlink(k); + if (!p) { + + if (errno != ENOENT) { + pa_log_error("Failed to stat runtime directory %s: %s", k, pa_cstrerror(errno)); + goto fail; + } + +#ifdef HAVE_SYMLINK + /* Hmm, so the runtime directory didn't exist yet, so let's + * create one in /tmp and symlink that to it */ + + if (make_random_dir_and_link(0700, k) < 0) { + + /* Mhmm, maybe another process was quicker than us, + * let's check if that was valid */ + if (errno == EEXIST) + continue; + + goto fail; + } +#else + /* No symlink possible, so let's just create the runtime directly + * Do not check again if it exists since it cannot be a symlink */ + if (mkdir(k) < 0 && errno != EEXIST) + goto fail; +#endif + + return k; + } + + /* Make sure that this actually makes sense */ + if (!pa_is_path_absolute(p)) { + pa_log_error("Path %s in link %s is not absolute.", p, k); + errno = ENOENT; + goto fail; + } + + /* Hmm, so this symlink is still around, make sure nobody fools us */ +#ifdef HAVE_LSTAT +{ + struct stat st; + if (lstat(p, &st) < 0) { + + if (errno != ENOENT) { + pa_log_error("Failed to stat runtime directory %s: %s", p, pa_cstrerror(errno)); + goto fail; + } + + } else { + + if (S_ISDIR(st.st_mode) && + (st.st_uid == getuid()) && + ((st.st_mode & 0777) == 0700)) { + + pa_xfree(p); + return k; + } + + pa_log_info("Hmm, runtime path exists, but points to an invalid directory. Changing runtime directory."); + } +} +#endif + + pa_xfree(p); + p = NULL; + + /* Hmm, so the link points to some nonexisting or invalid + * dir. Let's replace it by a new link. We first create a + * temporary link and then rename that to allow concurrent + * execution of this function. */ + + t = pa_sprintf_malloc("%s.tmp", k); + + if (make_random_dir_and_link(0700, t) < 0) { + + if (errno != EEXIST) { + pa_log_error("Failed to symlink %s: %s", t, pa_cstrerror(errno)); + goto fail; + } + + pa_xfree(t); + t = NULL; + + /* Hmm, someone else was quicker then us. Let's give + * him some time to finish, and retry. */ + pa_msleep(10); + continue; + } + + /* OK, we succeeded in creating the temporary symlink, so + * let's rename it */ + if (rename(t, k) < 0) { + pa_log_error("Failed to rename %s to %s: %s", t, k, pa_cstrerror(errno)); + goto fail; + } + + pa_xfree(t); + return k; + } + +fail: + pa_xfree(p); + pa_xfree(k); + pa_xfree(t); + + return NULL; +} + +/* Try to open a configuration file. If "env" is specified, open the + * value of the specified environment variable. Otherwise look for a + * file "local" in the home directory or a file "global" in global + * file system. If "result" is non-NULL, a pointer to a newly + * allocated buffer containing the used configuration file is + * stored there.*/ +FILE *pa_open_config_file(const char *global, const char *local, const char *env, char **result) { + const char *fn; + FILE *f; + + if (env && (fn = getenv(env))) { + if ((f = pa_fopen_cloexec(fn, "r"))) { + if (result) + *result = pa_xstrdup(fn); + + return f; + } + + pa_log_warn("Failed to open configuration file '%s': %s", fn, pa_cstrerror(errno)); + return NULL; + } + + if (local) { + const char *e; + char *lfn; + char *h; + + if ((e = getenv("PULSE_CONFIG_PATH"))) { + fn = lfn = pa_sprintf_malloc("%s" PA_PATH_SEP "%s", e, local); + f = pa_fopen_cloexec(fn, "r"); + } else if ((h = pa_get_home_dir_malloc())) { + fn = lfn = pa_sprintf_malloc("%s" PA_PATH_SEP ".pulse" PA_PATH_SEP "%s", h, local); + f = pa_fopen_cloexec(fn, "r"); + if (!f) { + free(lfn); + fn = lfn = pa_sprintf_malloc("%s" PA_PATH_SEP ".config/pulse" PA_PATH_SEP "%s", h, local); + f = pa_fopen_cloexec(fn, "r"); + } + pa_xfree(h); + } else + return NULL; + + if (f) { + if (result) + *result = pa_xstrdup(fn); + + pa_xfree(lfn); + return f; + } + + if (errno != ENOENT) { + pa_log_warn("Failed to open configuration file '%s': %s", fn, pa_cstrerror(errno)); + pa_xfree(lfn); + return NULL; + } + + pa_xfree(lfn); + } + + if (global) { + char *gfn; + +#ifdef OS_IS_WIN32 + if (strncmp(global, PA_DEFAULT_CONFIG_DIR, strlen(PA_DEFAULT_CONFIG_DIR)) == 0) + gfn = pa_sprintf_malloc("%s" PA_PATH_SEP "etc" PA_PATH_SEP "pulse%s", + pa_win32_get_toplevel(NULL), + global + strlen(PA_DEFAULT_CONFIG_DIR)); + else +#endif + gfn = pa_xstrdup(global); + + if ((f = pa_fopen_cloexec(gfn, "r"))) { + if (result) + *result = gfn; + else + pa_xfree(gfn); + + return f; + } + pa_xfree(gfn); + } + + errno = ENOENT; + return NULL; +} + +char *pa_find_config_file(const char *global, const char *local, const char *env) { + const char *fn; + + if (env && (fn = getenv(env))) { + if (access(fn, R_OK) == 0) + return pa_xstrdup(fn); + + pa_log_warn("Failed to access configuration file '%s': %s", fn, pa_cstrerror(errno)); + return NULL; + } + + if (local) { + const char *e; + char *lfn; + char *h; + + if ((e = getenv("PULSE_CONFIG_PATH"))) + fn = lfn = pa_sprintf_malloc("%s" PA_PATH_SEP "%s", e, local); + else if ((h = pa_get_home_dir_malloc())) { + fn = lfn = pa_sprintf_malloc("%s" PA_PATH_SEP ".pulse" PA_PATH_SEP "%s", h, local); + pa_xfree(h); + } else + return NULL; + + if (access(fn, R_OK) == 0) { + char *r = pa_xstrdup(fn); + pa_xfree(lfn); + return r; + } + + if (errno != ENOENT) { + pa_log_warn("Failed to access configuration file '%s': %s", fn, pa_cstrerror(errno)); + pa_xfree(lfn); + return NULL; + } + + pa_xfree(lfn); + } + + if (global) { + char *gfn; + +#ifdef OS_IS_WIN32 + if (strncmp(global, PA_DEFAULT_CONFIG_DIR, strlen(PA_DEFAULT_CONFIG_DIR)) == 0) + gfn = pa_sprintf_malloc("%s" PA_PATH_SEP "etc" PA_PATH_SEP "pulse%s", + pa_win32_get_toplevel(NULL), + global + strlen(PA_DEFAULT_CONFIG_DIR)); + else +#endif + gfn = pa_xstrdup(global); + + if (access(gfn, R_OK) == 0) + return gfn; + pa_xfree(gfn); + } + + errno = ENOENT; + + return NULL; +} + +/* Format the specified data as a hexademical string */ +char *pa_hexstr(const uint8_t* d, size_t dlength, char *s, size_t slength) { + size_t i = 0, j = 0; + const char hex[] = "0123456789abcdef"; + + pa_assert(d); + pa_assert(s); + pa_assert(slength > 0); + + while (j+2 < slength && i < dlength) { + s[j++] = hex[*d >> 4]; + s[j++] = hex[*d & 0xF]; + + d++; + i++; + } + + s[j < slength ? j : slength] = 0; + return s; +} + +/* Convert a hexadecimal digit to a number or -1 if invalid */ +static int hexc(char c) { + if (c >= '0' && c <= '9') + return c - '0'; + + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + + errno = EINVAL; + return -1; +} + +/* Parse a hexadecimal string as created by pa_hexstr() to a BLOB */ +size_t pa_parsehex(const char *p, uint8_t *d, size_t dlength) { + size_t j = 0; + + pa_assert(p); + pa_assert(d); + + while (j < dlength && *p) { + int b; + + if ((b = hexc(*(p++))) < 0) + return (size_t) -1; + + d[j] = (uint8_t) (b << 4); + + if (!*p) + return (size_t) -1; + + if ((b = hexc(*(p++))) < 0) + return (size_t) -1; + + d[j] |= (uint8_t) b; + j++; + } + + return j; +} + +/* Returns nonzero when *s starts with *pfx */ +bool pa_startswith(const char *s, const char *pfx) { + size_t l; + + pa_assert(s); + pa_assert(pfx); + + l = strlen(pfx); + + return strlen(s) >= l && strncmp(s, pfx, l) == 0; +} + +/* Returns nonzero when *s ends with *sfx */ +bool pa_endswith(const char *s, const char *sfx) { + size_t l1, l2; + + pa_assert(s); + pa_assert(sfx); + + l1 = strlen(s); + l2 = strlen(sfx); + + return l1 >= l2 && pa_streq(s + l1 - l2, sfx); +} + +bool pa_is_path_absolute(const char *fn) { + pa_assert(fn); + +#ifndef OS_IS_WIN32 + return *fn == '/'; +#else + return strlen(fn) >= 3 && isalpha(fn[0]) && fn[1] == ':' && fn[2] == '\\'; +#endif +} + +char *pa_make_path_absolute(const char *p) { + char *r; + char *cwd; + + pa_assert(p); + + if (pa_is_path_absolute(p)) + return pa_xstrdup(p); + + if (!(cwd = pa_getcwd())) + return pa_xstrdup(p); + + r = pa_sprintf_malloc("%s" PA_PATH_SEP "%s", cwd, p); + pa_xfree(cwd); + return r; +} + +/* If fn is NULL, return the PulseAudio runtime or state dir (depending on the + * rt parameter). If fn is non-NULL and starts with /, return fn. Otherwise, + * append fn to the runtime/state dir and return it. */ +static char *get_path(const char *fn, bool prependmid, bool rt) { + char *rtp; + + rtp = rt ? pa_get_runtime_dir() : pa_get_state_dir(); + + if (fn) { + char *r, *canonical_rtp; + + if (pa_is_path_absolute(fn)) { + pa_xfree(rtp); + return pa_xstrdup(fn); + } + + if (!rtp) + return NULL; + + /* Hopefully make the path smaller to avoid 108 char limit (fdo#44680) */ + if ((canonical_rtp = pa_realpath(rtp))) { + if (strlen(rtp) >= strlen(canonical_rtp)) + pa_xfree(rtp); + else { + pa_xfree(canonical_rtp); + canonical_rtp = rtp; + } + } else + canonical_rtp = rtp; + + if (prependmid) { + char *mid; + + if (!(mid = pa_machine_id())) { + pa_xfree(canonical_rtp); + return NULL; + } + + r = pa_sprintf_malloc("%s" PA_PATH_SEP "%s-%s", canonical_rtp, mid, fn); + pa_xfree(mid); + } else + r = pa_sprintf_malloc("%s" PA_PATH_SEP "%s", canonical_rtp, fn); + + pa_xfree(canonical_rtp); + return r; + } else + return rtp; +} + +char *pa_runtime_path(const char *fn) { + return get_path(fn, false, true); +} + +char *pa_state_path(const char *fn, bool appendmid) { + return get_path(fn, appendmid, false); +} + +/* Convert the string s to a signed integer in *ret_i */ +int pa_atoi(const char *s, int32_t *ret_i) { + long l; + + pa_assert(s); + pa_assert(ret_i); + + if (pa_atol(s, &l) < 0) + return -1; + + if ((int32_t) l != l) { + errno = ERANGE; + return -1; + } + + *ret_i = (int32_t) l; + + return 0; +} + +/* Convert the string s to an unsigned integer in *ret_u */ +int pa_atou(const char *s, uint32_t *ret_u) { + char *x = NULL; + unsigned long l; + + pa_assert(s); + pa_assert(ret_u); + + /* strtoul() ignores leading spaces. We don't. */ + if (isspace((unsigned char)*s)) { + errno = EINVAL; + return -1; + } + + /* strtoul() accepts strings that start with a minus sign. In that case the + * original negative number gets negated, and strtoul() returns the negated + * result. We don't want that kind of behaviour. strtoul() also allows a + * leading plus sign, which is also a thing that we don't want. */ + if (*s == '-' || *s == '+') { + errno = EINVAL; + return -1; + } + + errno = 0; + l = strtoul(s, &x, 0); + + /* If x doesn't point to the end of s, there was some trailing garbage in + * the string. If x points to s, no conversion was done (empty string). */ + if (!x || *x || x == s || errno) { + if (!errno) + errno = EINVAL; + return -1; + } + + if ((uint32_t) l != l) { + errno = ERANGE; + return -1; + } + + *ret_u = (uint32_t) l; + + return 0; +} + +/* Convert the string s to a signed long integer in *ret_l. */ +int pa_atol(const char *s, long *ret_l) { + char *x = NULL; + long l; + + pa_assert(s); + pa_assert(ret_l); + + /* strtol() ignores leading spaces. We don't. */ + if (isspace((unsigned char)*s)) { + errno = EINVAL; + return -1; + } + + /* strtol() accepts leading plus signs, but that's ugly, so we don't allow + * that. */ + if (*s == '+') { + errno = EINVAL; + return -1; + } + + errno = 0; + l = strtol(s, &x, 0); + + /* If x doesn't point to the end of s, there was some trailing garbage in + * the string. If x points to s, no conversion was done (at least an empty + * string can trigger this). */ + if (!x || *x || x == s || errno) { + if (!errno) + errno = EINVAL; + return -1; + } + + *ret_l = l; + + return 0; +} + +#ifdef HAVE_STRTOD_L +static locale_t c_locale = NULL; + +static void c_locale_destroy(void) { + freelocale(c_locale); +} +#endif + +int pa_atod(const char *s, double *ret_d) { + char *x = NULL; + double f; + + pa_assert(s); + pa_assert(ret_d); + + /* strtod() ignores leading spaces. We don't. */ + if (isspace((unsigned char)*s)) { + errno = EINVAL; + return -1; + } + + /* strtod() accepts leading plus signs, but that's ugly, so we don't allow + * that. */ + if (*s == '+') { + errno = EINVAL; + return -1; + } + + /* This should be locale independent */ + +#ifdef HAVE_STRTOD_L + + PA_ONCE_BEGIN { + + if ((c_locale = newlocale(LC_ALL_MASK, "C", NULL))) + atexit(c_locale_destroy); + + } PA_ONCE_END; + + if (c_locale) { + errno = 0; + f = strtod_l(s, &x, c_locale); + } else +#endif + { + errno = 0; + f = strtod(s, &x); + } + + /* If x doesn't point to the end of s, there was some trailing garbage in + * the string. If x points to s, no conversion was done (at least an empty + * string can trigger this). */ + if (!x || *x || x == s || errno) { + if (!errno) + errno = EINVAL; + return -1; + } + + if (isnan(f)) { + errno = EINVAL; + return -1; + } + + *ret_d = f; + + return 0; +} + +/* Same as snprintf, but guarantees NUL-termination on every platform */ +size_t pa_snprintf(char *str, size_t size, const char *format, ...) { + size_t ret; + va_list ap; + + pa_assert(str); + pa_assert(size > 0); + pa_assert(format); + + va_start(ap, format); + ret = pa_vsnprintf(str, size, format, ap); + va_end(ap); + + return ret; +} + +/* Same as vsnprintf, but guarantees NUL-termination on every platform */ +size_t pa_vsnprintf(char *str, size_t size, const char *format, va_list ap) { + int ret; + + pa_assert(str); + pa_assert(size > 0); + pa_assert(format); + + ret = vsnprintf(str, size, format, ap); + + str[size-1] = 0; + + if (ret < 0) + return strlen(str); + + if ((size_t) ret > size-1) + return size-1; + + return (size_t) ret; +} + +/* Truncate the specified string, but guarantee that the string + * returned still validates as UTF8 */ +char *pa_truncate_utf8(char *c, size_t l) { + pa_assert(c); + pa_assert(pa_utf8_valid(c)); + + if (strlen(c) <= l) + return c; + + c[l] = 0; + + while (l > 0 && !pa_utf8_valid(c)) + c[--l] = 0; + + return c; +} + +char *pa_getcwd(void) { + size_t l = 128; + + for (;;) { + char *p = pa_xmalloc(l); + if (getcwd(p, l)) + return p; + + if (errno != ERANGE) { + pa_xfree(p); + return NULL; + } + + pa_xfree(p); + l *= 2; + } +} + +void *pa_will_need(const void *p, size_t l) { +#ifdef RLIMIT_MEMLOCK + struct rlimit rlim; +#endif + const void *a; + size_t size; + int r = ENOTSUP; + size_t bs; + const size_t page_size = pa_page_size(); + + pa_assert(p); + pa_assert(l > 0); + + a = PA_PAGE_ALIGN_PTR(p); + size = (size_t) ((const uint8_t*) p + l - (const uint8_t*) a); + +#ifdef HAVE_POSIX_MADVISE + if ((r = posix_madvise((void*) a, size, POSIX_MADV_WILLNEED)) == 0) { + pa_log_debug("posix_madvise() worked fine!"); + return (void*) p; + } +#endif + + /* Most likely the memory was not mmap()ed from a file and thus + * madvise() didn't work, so let's misuse mlock() do page this + * stuff back into RAM. Yeah, let's fuck with the MM! It's so + * inviting, the man page of mlock() tells us: "All pages that + * contain a part of the specified address range are guaranteed to + * be resident in RAM when the call returns successfully." */ + +#ifdef RLIMIT_MEMLOCK + pa_assert_se(getrlimit(RLIMIT_MEMLOCK, &rlim) == 0); + + if (rlim.rlim_cur < page_size) { + pa_log_debug("posix_madvise() failed (or doesn't exist), resource limits don't allow mlock(), can't page in data: %s", pa_cstrerror(r)); + errno = EPERM; + return (void*) p; + } + + bs = PA_PAGE_ALIGN((size_t) rlim.rlim_cur); +#else + bs = page_size*4; +#endif + + pa_log_debug("posix_madvise() failed (or doesn't exist), trying mlock(): %s", pa_cstrerror(r)); + +#ifdef HAVE_MLOCK + while (size > 0 && bs > 0) { + + if (bs > size) + bs = size; + + if (mlock(a, bs) < 0) { + bs = PA_PAGE_ALIGN(bs / 2); + continue; + } + + pa_assert_se(munlock(a, bs) == 0); + + a = (const uint8_t*) a + bs; + size -= bs; + } +#endif + + if (bs <= 0) + pa_log_debug("mlock() failed too (or doesn't exist), giving up: %s", pa_cstrerror(errno)); + else + pa_log_debug("mlock() worked fine!"); + + return (void*) p; +} + +void pa_close_pipe(int fds[2]) { + pa_assert(fds); + + if (fds[0] >= 0) + pa_assert_se(pa_close(fds[0]) == 0); + + if (fds[1] >= 0) + pa_assert_se(pa_close(fds[1]) == 0); + + fds[0] = fds[1] = -1; +} + +char *pa_readlink(const char *p) { +#ifdef HAVE_READLINK + size_t l = 100; + + for (;;) { + char *c; + ssize_t n; + + c = pa_xmalloc(l); + + if ((n = readlink(p, c, l-1)) < 0) { + pa_xfree(c); + return NULL; + } + + if ((size_t) n < l-1) { + c[n] = 0; + return c; + } + + pa_xfree(c); + l *= 2; + } +#else + return NULL; +#endif +} + +int pa_close_all(int except_fd, ...) { + va_list ap; + unsigned n = 0, i; + int r, *p; + + va_start(ap, except_fd); + + if (except_fd >= 0) + for (n = 1; va_arg(ap, int) >= 0; n++) + ; + + va_end(ap); + + p = pa_xnew(int, n+1); + + va_start(ap, except_fd); + + i = 0; + if (except_fd >= 0) { + int fd; + p[i++] = except_fd; + + while ((fd = va_arg(ap, int)) >= 0) + p[i++] = fd; + } + p[i] = -1; + + va_end(ap); + + r = pa_close_allv(p); + pa_xfree(p); + + return r; +} + +int pa_close_allv(const int except_fds[]) { +#ifndef OS_IS_WIN32 + struct rlimit rl; + int maxfd, fd; + +#if defined(__linux__) || defined(__sun) + int saved_errno; + DIR *d; + + if ((d = opendir("/proc/self/fd"))) { + + struct dirent *de; + + while ((de = readdir(d))) { + bool found; + long l; + char *e = NULL; + int i; + + if (de->d_name[0] == '.') + continue; + + errno = 0; + l = strtol(de->d_name, &e, 10); + if (errno != 0 || !e || *e) { + closedir(d); + errno = EINVAL; + return -1; + } + + fd = (int) l; + + if ((long) fd != l) { + closedir(d); + errno = EINVAL; + return -1; + } + + if (fd < 3) + continue; + + if (fd == dirfd(d)) + continue; + + found = false; + for (i = 0; except_fds[i] >= 0; i++) + if (except_fds[i] == fd) { + found = true; + break; + } + + if (found) + continue; + + if (pa_close(fd) < 0) { + saved_errno = errno; + closedir(d); + errno = saved_errno; + + return -1; + } + } + + closedir(d); + return 0; + } + +#endif + + if (getrlimit(RLIMIT_NOFILE, &rl) >= 0) + maxfd = (int) rl.rlim_max; + else + maxfd = sysconf(_SC_OPEN_MAX); + + for (fd = 3; fd < maxfd; fd++) { + int i; + bool found; + + found = false; + for (i = 0; except_fds[i] >= 0; i++) + if (except_fds[i] == fd) { + found = true; + break; + } + + if (found) + continue; + + if (pa_close(fd) < 0 && errno != EBADF) + return -1; + } +#endif /* !OS_IS_WIN32 */ + + return 0; +} + +int pa_unblock_sigs(int except, ...) { + va_list ap; + unsigned n = 0, i; + int r, *p; + + va_start(ap, except); + + if (except >= 1) + for (n = 1; va_arg(ap, int) >= 0; n++) + ; + + va_end(ap); + + p = pa_xnew(int, n+1); + + va_start(ap, except); + + i = 0; + if (except >= 1) { + int sig; + p[i++] = except; + + while ((sig = va_arg(ap, int)) >= 0) + p[i++] = sig; + } + p[i] = -1; + + va_end(ap); + + r = pa_unblock_sigsv(p); + pa_xfree(p); + + return r; +} + +int pa_unblock_sigsv(const int except[]) { +#ifndef OS_IS_WIN32 + int i; + sigset_t ss; + + if (sigemptyset(&ss) < 0) + return -1; + + for (i = 0; except[i] > 0; i++) + if (sigaddset(&ss, except[i]) < 0) + return -1; + + return sigprocmask(SIG_SETMASK, &ss, NULL); +#else + return 0; +#endif +} + +int pa_reset_sigs(int except, ...) { + va_list ap; + unsigned n = 0, i; + int *p, r; + + va_start(ap, except); + + if (except >= 1) + for (n = 1; va_arg(ap, int) >= 0; n++) + ; + + va_end(ap); + + p = pa_xnew(int, n+1); + + va_start(ap, except); + + i = 0; + if (except >= 1) { + int sig; + p[i++] = except; + + while ((sig = va_arg(ap, int)) >= 0) + p[i++] = sig; + } + p[i] = -1; + + va_end(ap); + + r = pa_reset_sigsv(p); + pa_xfree(p); + + return r; +} + +int pa_reset_sigsv(const int except[]) { +#ifndef OS_IS_WIN32 + int sig; + + for (sig = 1; sig < NSIG; sig++) { + bool reset = true; + + switch (sig) { + case SIGKILL: + case SIGSTOP: + reset = false; + break; + + default: { + int i; + + for (i = 0; except[i] > 0; i++) { + if (sig == except[i]) { + reset = false; + break; + } + } + } + } + + if (reset) { + struct sigaction sa; + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = SIG_DFL; + + /* On Linux the first two RT signals are reserved by + * glibc, and sigaction() will return EINVAL for them. */ + if ((sigaction(sig, &sa, NULL) < 0)) + if (errno != EINVAL) + return -1; + } + } +#endif + + return 0; +} + +void pa_set_env(const char *key, const char *value) { + pa_assert(key); + pa_assert(value); + + /* This is not thread-safe */ + +#ifdef OS_IS_WIN32 + SetEnvironmentVariable(key, value); +#else + setenv(key, value, 1); +#endif +} + +void pa_unset_env(const char *key) { + pa_assert(key); + + /* This is not thread-safe */ + +#ifdef OS_IS_WIN32 + SetEnvironmentVariable(key, NULL); +#else + unsetenv(key); +#endif +} + +void pa_set_env_and_record(const char *key, const char *value) { + pa_assert(key); + pa_assert(value); + + /* This is not thread-safe */ + + pa_set_env(key, value); + recorded_env = pa_strlist_prepend(recorded_env, key); +} + +void pa_unset_env_recorded(void) { + + /* This is not thread-safe */ + + for (;;) { + char *s; + + recorded_env = pa_strlist_pop(recorded_env, &s); + + if (!s) + break; + + pa_unset_env(s); + pa_xfree(s); + } +} + +bool pa_in_system_mode(void) { + const char *e; + + if (!(e = getenv("PULSE_SYSTEM"))) + return false; + + return !!atoi(e); +} + +/* Checks a delimiters-separated list of words in haystack for needle */ +bool pa_str_in_list(const char *haystack, const char *delimiters, const char *needle) { + char *s; + const char *state = NULL; + + if (!haystack || !needle) + return false; + + while ((s = pa_split(haystack, delimiters, &state))) { + if (pa_streq(needle, s)) { + pa_xfree(s); + return true; + } + + pa_xfree(s); + } + + return false; +} + +/* Checks a whitespace-separated list of words in haystack for needle */ +bool pa_str_in_list_spaces(const char *haystack, const char *needle) { + const char *s; + size_t n; + const char *state = NULL; + + if (!haystack || !needle) + return false; + + while ((s = pa_split_spaces_in_place(haystack, &n, &state))) { + if (pa_strneq(needle, s, n)) + return true; + } + + return false; +} + +char* pa_str_strip_suffix(const char *str, const char *suffix) { + size_t str_l, suf_l, prefix; + char *ret; + + pa_assert(str); + pa_assert(suffix); + + str_l = strlen(str); + suf_l = strlen(suffix); + + if (str_l < suf_l) + return NULL; + + prefix = str_l - suf_l; + + if (!pa_streq(&str[prefix], suffix)) + return NULL; + + ret = pa_xmalloc(prefix + 1); + + strncpy(ret, str, prefix); + ret[prefix] = '\0'; + + return ret; +} + +char *pa_get_user_name_malloc(void) { + ssize_t k; + char *u; + +#ifdef _SC_LOGIN_NAME_MAX + k = (ssize_t) sysconf(_SC_LOGIN_NAME_MAX); + + if (k <= 0) +#endif + k = 32; + + u = pa_xnew(char, k+1); + + if (!(pa_get_user_name(u, k))) { + pa_xfree(u); + return NULL; + } + + return u; +} + +char *pa_get_host_name_malloc(void) { + size_t l; + + l = 100; + for (;;) { + char *c; + + c = pa_xmalloc(l); + + if (!pa_get_host_name(c, l)) { + + if (errno != EINVAL && errno != ENAMETOOLONG) + break; + + } else if (strlen(c) < l-1) { + char *u; + + if (*c == 0) { + pa_xfree(c); + break; + } + + u = pa_utf8_filter(c); + pa_xfree(c); + return u; + } + + /* Hmm, the hostname is as long the space we offered the + * function, we cannot know if it fully fit in, so let's play + * safe and retry. */ + + pa_xfree(c); + l *= 2; + } + + return NULL; +} + +char *pa_machine_id(void) { + FILE *f; + char *h; + + /* The returned value is supposed be some kind of ascii identifier + * that is unique and stable across reboots. First we try if the machine-id + * file is available. If it's available, that's great, since it provides an + * identifier that suits our needs perfectly. If it's not, we fall back to + * the hostname, which is not as good, since it can change over time. */ + + /* We search for the machine-id file from four locations. The first two are + * relative to the configured installation prefix, but if we're installed + * under /usr/local, for example, it's likely that the machine-id won't be + * found there, so we also try the hardcoded paths. + * + * PA_MACHINE_ID or PA_MACHINE_ID_FALLBACK might exist on a Windows system, + * but the last two hardcoded paths certainly don't, hence we don't try + * them on Windows. */ + if ((f = pa_fopen_cloexec(PA_MACHINE_ID, "r")) || + (f = pa_fopen_cloexec(PA_MACHINE_ID_FALLBACK, "r")) || +#if !defined(OS_IS_WIN32) + (f = pa_fopen_cloexec("/etc/machine-id", "r")) || + (f = pa_fopen_cloexec("/var/lib/dbus/machine-id", "r")) +#else + false +#endif + ) { + char ln[34] = "", *r; + + r = fgets(ln, sizeof(ln)-1, f); + fclose(f); + + pa_strip_nl(ln); + + if (r && ln[0]) + return pa_utf8_filter(ln); + } + + if ((h = pa_get_host_name_malloc())) + return h; + +#if !defined(OS_IS_WIN32) && !defined(__ANDROID__) + /* If no hostname was set we use the POSIX hostid. It's usually + * the IPv4 address. Might not be that stable. */ + return pa_sprintf_malloc("%08lx", (unsigned long) gethostid()); +#else + return NULL; +#endif +} + +char *pa_session_id(void) { + const char *e; + + e = getenv("XDG_SESSION_ID"); + if (!e) + return NULL; + + return pa_utf8_filter(e); +} + +char *pa_uname_string(void) { +#ifdef HAVE_UNAME + struct utsname u; + + pa_assert_se(uname(&u) >= 0); + + return pa_sprintf_malloc("%s %s %s %s", u.sysname, u.machine, u.release, u.version); +#endif +#ifdef OS_IS_WIN32 + OSVERSIONINFO i; + + pa_zero(i); + i.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + pa_assert_se(GetVersionEx(&i)); + + return pa_sprintf_malloc("Windows %d.%d (%d) %s", i.dwMajorVersion, i.dwMinorVersion, i.dwBuildNumber, i.szCSDVersion); +#endif +} + +#ifdef HAVE_VALGRIND_MEMCHECK_H +bool pa_in_valgrind(void) { + static int b = 0; + + /* To make heisenbugs a bit simpler to find we check for $VALGRIND + * here instead of really checking whether we run in valgrind or + * not. */ + + if (b < 1) + b = getenv("VALGRIND") ? 2 : 1; + + return b > 1; +} +#endif + +unsigned pa_gcd(unsigned a, unsigned b) { + + while (b > 0) { + unsigned t = b; + b = a % b; + a = t; + } + + return a; +} + +void pa_reduce(unsigned *num, unsigned *den) { + + unsigned gcd = pa_gcd(*num, *den); + + if (gcd <= 0) + return; + + *num /= gcd; + *den /= gcd; + + pa_assert(pa_gcd(*num, *den) == 1); +} + +unsigned pa_ncpus(void) { + long ncpus; + +#ifdef _SC_NPROCESSORS_ONLN + ncpus = sysconf(_SC_NPROCESSORS_ONLN); +#else + ncpus = 1; +#endif + + return ncpus <= 0 ? 1 : (unsigned) ncpus; +} + +char *pa_replace(const char*s, const char*a, const char *b) { + pa_strbuf *sb; + size_t an; + + pa_assert(s); + pa_assert(a); + pa_assert(*a); + pa_assert(b); + + an = strlen(a); + sb = pa_strbuf_new(); + + for (;;) { + const char *p; + + if (!(p = strstr(s, a))) + break; + + pa_strbuf_putsn(sb, s, p-s); + pa_strbuf_puts(sb, b); + s = p + an; + } + + pa_strbuf_puts(sb, s); + + return pa_strbuf_to_string_free(sb); +} + +char *pa_escape(const char *p, const char *chars) { + const char *s; + const char *c; + char *out_string, *output; + int char_count = strlen(p); + + /* Maximum number of characters in output string + * including trailing 0. */ + char_count = 2 * char_count + 1; + + /* allocate output string */ + out_string = pa_xmalloc(char_count); + output = out_string; + + /* write output string */ + for (s = p; *s; ++s) { + if (*s == '\\') + *output++ = '\\'; + else if (chars) { + for (c = chars; *c; ++c) { + if (*s == *c) { + *output++ = '\\'; + break; + } + } + } + *output++ = *s; + } + + *output = 0; + + /* Remove trailing garbage */ + output = pa_xstrdup(out_string); + + pa_xfree(out_string); + return output; +} + +char *pa_unescape(char *p) { + char *s, *d; + bool escaped = false; + + for (s = p, d = p; *s; s++) { + if (!escaped && *s == '\\') { + escaped = true; + continue; + } + + *(d++) = *s; + escaped = false; + } + + *d = 0; + + return p; +} + +char *pa_realpath(const char *path) { + char *t; + pa_assert(path); + + /* We want only absolute paths */ + if (path[0] != '/') { + errno = EINVAL; + return NULL; + } + +#if defined(__GLIBC__) + { + char *r; + + if (!(r = realpath(path, NULL))) + return NULL; + + /* We copy this here in case our pa_xmalloc() is not + * implemented on top of libc malloc() */ + t = pa_xstrdup(r); + pa_xfree(r); + } +#elif defined(PATH_MAX) + { + char *path_buf; + path_buf = pa_xmalloc(PATH_MAX); + +#if defined(OS_IS_WIN32) + if (!(t = _fullpath(path_buf, path, _MAX_PATH))) { + pa_xfree(path_buf); + return NULL; + } +#else + if (!(t = realpath(path, path_buf))) { + pa_xfree(path_buf); + return NULL; + } +#endif + } +#else +#error "It's not clear whether this system supports realpath(..., NULL) like GNU libc does. If it doesn't we need a private version of realpath() here." +#endif + + return t; +} + +void pa_disable_sigpipe(void) { + +#ifdef SIGPIPE + struct sigaction sa; + + pa_zero(sa); + + if (sigaction(SIGPIPE, NULL, &sa) < 0) { + pa_log("sigaction(): %s", pa_cstrerror(errno)); + return; + } + + sa.sa_handler = SIG_IGN; + + if (sigaction(SIGPIPE, &sa, NULL) < 0) { + pa_log("sigaction(): %s", pa_cstrerror(errno)); + return; + } +#endif +} + +void pa_xfreev(void**a) { + void **p; + + if (!a) + return; + + for (p = a; *p; p++) + pa_xfree(*p); + + pa_xfree(a); +} + +char **pa_split_spaces_strv(const char *s) { + char **t, *e; + unsigned i = 0, n = 8; + const char *state = NULL; + + t = pa_xnew(char*, n); + while ((e = pa_split_spaces(s, &state))) { + t[i++] = e; + + if (i >= n) { + n *= 2; + t = pa_xrenew(char*, t, n); + } + } + + if (i <= 0) { + pa_xfree(t); + return NULL; + } + + t[i] = NULL; + return t; +} + +char* pa_maybe_prefix_path(const char *path, const char *prefix) { + pa_assert(path); + + if (pa_is_path_absolute(path)) + return pa_xstrdup(path); + + return pa_sprintf_malloc("%s" PA_PATH_SEP "%s", prefix, path); +} + +size_t pa_pipe_buf(int fd) { + +#ifdef _PC_PIPE_BUF + long n; + + if ((n = fpathconf(fd, _PC_PIPE_BUF)) >= 0) + return (size_t) n; +#endif + +#ifdef PIPE_BUF + return PIPE_BUF; +#else + return 4096; +#endif +} + +void pa_reset_personality(void) { + +#if defined(__linux__) && !defined(__ANDROID__) + if (personality(PER_LINUX) < 0) + pa_log_warn("Uh, personality() failed: %s", pa_cstrerror(errno)); +#endif + +} + +bool pa_run_from_build_tree(void) { + static bool b = false; + +#ifdef HAVE_RUNNING_FROM_BUILD_TREE + char *rp; + PA_ONCE_BEGIN { + if ((rp = pa_readlink("/proc/self/exe"))) { + b = pa_startswith(rp, PA_BUILDDIR); + pa_xfree(rp); + } + } PA_ONCE_END; +#endif + + return b; +} + +const char *pa_get_temp_dir(void) { + const char *t; + + if ((t = getenv("TMPDIR")) && + pa_is_path_absolute(t)) + return t; + + if ((t = getenv("TMP")) && + pa_is_path_absolute(t)) + return t; + + if ((t = getenv("TEMP")) && + pa_is_path_absolute(t)) + return t; + + if ((t = getenv("TEMPDIR")) && + pa_is_path_absolute(t)) + return t; + + return "/tmp"; +} + +int pa_open_cloexec(const char *fn, int flags, mode_t mode) { + int fd; + +#ifdef O_NOCTTY + flags |= O_NOCTTY; +#endif + +#ifdef O_CLOEXEC + if ((fd = open(fn, flags|O_CLOEXEC, mode)) >= 0) + goto finish; + + if (errno != EINVAL) + return fd; +#endif + + if ((fd = open(fn, flags, mode)) >= 0) + goto finish; + + /* return error */ + return fd; + +finish: + /* Some implementations might simply ignore O_CLOEXEC if it is not + * understood, make sure FD_CLOEXEC is enabled anyway */ + + pa_make_fd_cloexec(fd); + return fd; +} + +int pa_socket_cloexec(int domain, int type, int protocol) { + int fd; + +#ifdef SOCK_CLOEXEC + if ((fd = socket(domain, type | SOCK_CLOEXEC, protocol)) >= 0) + goto finish; + + if (errno != EINVAL) + return fd; +#endif + + if ((fd = socket(domain, type, protocol)) >= 0) + goto finish; + + /* return error */ + return fd; + +finish: + /* Some implementations might simply ignore SOCK_CLOEXEC if it is + * not understood, make sure FD_CLOEXEC is enabled anyway */ + + pa_make_fd_cloexec(fd); + return fd; +} + +int pa_pipe_cloexec(int pipefd[2]) { + int r; + +#ifdef HAVE_PIPE2 + if ((r = pipe2(pipefd, O_CLOEXEC)) >= 0) + goto finish; + + if (errno == EMFILE) { + pa_log_error("The per-process limit on the number of open file descriptors has been reached."); + return r; + } + + if (errno == ENFILE) { + pa_log_error("The system-wide limit on the total number of open files has been reached."); + return r; + } + + if (errno != EINVAL && errno != ENOSYS) + return r; + +#endif + + if ((r = pipe(pipefd)) >= 0) + goto finish; + + if (errno == EMFILE) { + pa_log_error("The per-process limit on the number of open file descriptors has been reached."); + return r; + } + + if (errno == ENFILE) { + pa_log_error("The system-wide limit on the total number of open files has been reached."); + return r; + } + + /* return error */ + return r; + +finish: + pa_make_fd_cloexec(pipefd[0]); + pa_make_fd_cloexec(pipefd[1]); + + return 0; +} + +int pa_accept_cloexec(int sockfd, struct sockaddr *addr, socklen_t *addrlen) { + int fd; + + errno = 0; + +#ifdef HAVE_ACCEPT4 + if ((fd = accept4(sockfd, addr, addrlen, SOCK_CLOEXEC)) >= 0) + goto finish; + + if (errno != EINVAL && errno != ENOSYS) + return fd; + +#endif + +#ifdef HAVE_PACCEPT + if ((fd = paccept(sockfd, addr, addrlen, NULL, SOCK_CLOEXEC)) >= 0) + goto finish; +#endif + + if ((fd = accept(sockfd, addr, addrlen)) >= 0) + goto finish; + + /* return error */ + return fd; + +finish: + pa_make_fd_cloexec(fd); + return fd; +} + +FILE* pa_fopen_cloexec(const char *path, const char *mode) { + FILE *f; + char *m; + + m = pa_sprintf_malloc("%se", mode); + + errno = 0; + if ((f = fopen(path, m))) { + pa_xfree(m); + goto finish; + } + + pa_xfree(m); + + if (errno != EINVAL) + return NULL; + + if (!(f = fopen(path, mode))) + return NULL; + +finish: + pa_make_fd_cloexec(fileno(f)); + return f; +} + +void pa_nullify_stdfds(void) { + +#ifndef OS_IS_WIN32 + pa_close(STDIN_FILENO); + pa_close(STDOUT_FILENO); + pa_close(STDERR_FILENO); + + pa_assert_se(open("/dev/null", O_RDONLY) == STDIN_FILENO); + pa_assert_se(open("/dev/null", O_WRONLY) == STDOUT_FILENO); + pa_assert_se(open("/dev/null", O_WRONLY) == STDERR_FILENO); +#else + FreeConsole(); +#endif + +} + +char *pa_read_line_from_file(const char *fn) { + FILE *f; + char ln[256] = "", *r; + + if (!(f = pa_fopen_cloexec(fn, "r"))) + return NULL; + + r = fgets(ln, sizeof(ln)-1, f); + fclose(f); + + if (!r) { + errno = EIO; + return NULL; + } + + pa_strip_nl(ln); + return pa_xstrdup(ln); +} + +bool pa_running_in_vm(void) { + +#if defined(__i386__) || defined(__x86_64__) + + /* Both CPUID and DMI are x86 specific interfaces... */ + +#ifdef HAVE_CPUID_H + unsigned int eax, ebx, ecx, edx; +#endif + +#ifdef __linux__ + const char *const dmi_vendors[] = { + "/sys/class/dmi/id/sys_vendor", + "/sys/class/dmi/id/board_vendor", + "/sys/class/dmi/id/bios_vendor" + }; + + unsigned i; + + for (i = 0; i < PA_ELEMENTSOF(dmi_vendors); i++) { + char *s; + + if ((s = pa_read_line_from_file(dmi_vendors[i]))) { + + if (pa_startswith(s, "QEMU") || + /* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */ + pa_startswith(s, "VMware") || + pa_startswith(s, "VMW") || + pa_startswith(s, "Microsoft Corporation") || + pa_startswith(s, "innotek GmbH") || + pa_startswith(s, "Xen")) { + + pa_xfree(s); + return true; + } + + pa_xfree(s); + } + } + +#endif + +#ifdef HAVE_CPUID_H + + /* Hypervisors provide presence on 0x1 cpuid leaf. + * http://lwn.net/Articles/301888/ */ + if (__get_cpuid(1, &eax, &ebx, &ecx, &edx) == 0) + return false; + + if (ecx & 0x80000000) + return true; + +#endif /* HAVE_CPUID_H */ + +#endif /* defined(__i386__) || defined(__x86_64__) */ + + return false; +} + +size_t pa_page_size(void) { +#if defined(PAGE_SIZE) + return PAGE_SIZE; +#elif defined(PAGESIZE) + return PAGESIZE; +#elif defined(HAVE_SYSCONF) + static size_t page_size = 4096; /* Let's hope it's like x86. */ + + PA_ONCE_BEGIN { + long ret = sysconf(_SC_PAGE_SIZE); + if (ret > 0) + page_size = ret; + } PA_ONCE_END; + + return page_size; +#else + return 4096; +#endif +} diff --git a/src/pulsecore/core-util.h b/src/pulsecore/core-util.h new file mode 100644 index 0000000..9440af9 --- /dev/null +++ b/src/pulsecore/core-util.h @@ -0,0 +1,323 @@ +#ifndef foocoreutilhfoo +#define foocoreutilhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + 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 by the Free Software Foundation; either version 2.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/types.h> +#include <inttypes.h> +#include <stdarg.h> +#include <stdio.h> +#include <string.h> + +#ifdef HAVE_SYS_RESOURCE_H +#include <sys/resource.h> +#endif + +#include <pulse/gccmacro.h> +#include <pulse/volume.h> + +#include <pulsecore/i18n.h> +#include <pulsecore/macro.h> +#include <pulsecore/socket.h> + +#ifndef PACKAGE +#error "Please include config.h before including this file!" +#endif + +struct timeval; + +/* These resource limits are pretty new on Linux, let's define them + * here manually, in case the kernel is newer than the glibc */ +#if !defined(RLIMIT_NICE) && defined(__linux__) +#define RLIMIT_NICE 13 +#endif +#if !defined(RLIMIT_RTPRIO) && defined(__linux__) +#define RLIMIT_RTPRIO 14 +#endif +#if !defined(RLIMIT_RTTIME) && defined(__linux__) +#define RLIMIT_RTTIME 15 +#endif + +void pa_make_fd_nonblock(int fd); +void pa_make_fd_block(int fd); +bool pa_is_fd_nonblock(int fd); + +void pa_make_fd_cloexec(int fd); + +int pa_make_secure_dir(const char* dir, mode_t m, uid_t uid, gid_t gid, bool update_perms); +int pa_make_secure_parent_dir(const char *fn, mode_t, uid_t uid, gid_t gid, bool update_perms); + +ssize_t pa_read(int fd, void *buf, size_t count, int *type); +ssize_t pa_write(int fd, const void *buf, size_t count, int *type); +ssize_t pa_loop_read(int fd, void*data, size_t size, int *type); +ssize_t pa_loop_write(int fd, const void*data, size_t size, int *type); + +int pa_close(int fd); + +void pa_check_signal_is_blocked(int sig); + +char *pa_sprintf_malloc(const char *format, ...) PA_GCC_PRINTF_ATTR(1,2); +char *pa_vsprintf_malloc(const char *format, va_list ap); + +char *pa_strlcpy(char *b, const char *s, size_t l); + +char *pa_parent_dir(const char *fn); + +int pa_raise_priority(int nice_level); +void pa_reset_priority(void); + +int pa_parse_boolean(const char *s) PA_GCC_PURE; + +int pa_parse_volume(const char *s, pa_volume_t *volume); + +static inline const char *pa_yes_no(bool b) { + return b ? "yes" : "no"; +} + +static inline const char *pa_yes_no_localised(bool b) { + return b ? _("yes") : _("no"); +} + +static inline const char *pa_strnull(const char *x) { + return x ? x : "(null)"; +} + +static inline const char *pa_strempty(const char *x) { + return x ? x : ""; +} + +static inline const char *pa_strna(const char *x) { + return x ? x : "n/a"; +} + +char *pa_split(const char *c, const char *delimiters, const char **state); +const char *pa_split_in_place(const char *c, const char *delimiters, size_t *n, const char **state); +char *pa_split_spaces(const char *c, const char **state); +const char *pa_split_spaces_in_place(const char *c, size_t *n, const char **state); + +char *pa_strip_nl(char *s); +char *pa_strip(char *s); + +const char *pa_sig2str(int sig) PA_GCC_PURE; + +int pa_own_uid_in_group(const char *name, gid_t *gid); +int pa_uid_in_group(uid_t uid, const char *name); +gid_t pa_get_gid_of_group(const char *name); +int pa_check_in_group(gid_t g); + +int pa_lock_fd(int fd, int b); + +int pa_lock_lockfile(const char *fn); +int pa_unlock_lockfile(const char *fn, int fd); + +char *pa_hexstr(const uint8_t* d, size_t dlength, char *s, size_t slength); +size_t pa_parsehex(const char *p, uint8_t *d, size_t dlength); + +bool pa_startswith(const char *s, const char *pfx) PA_GCC_PURE; +bool pa_endswith(const char *s, const char *sfx) PA_GCC_PURE; + +FILE *pa_open_config_file(const char *global, const char *local, const char *env, char **result); +char* pa_find_config_file(const char *global, const char *local, const char *env); + +char *pa_get_runtime_dir(void); +char *pa_get_state_dir(void); +char *pa_get_home_dir_malloc(void); +int pa_append_to_home_dir(const char *path, char **_r); +int pa_get_config_home_dir(char **_r); +int pa_append_to_config_home_dir(const char *path, char **_r); +char *pa_get_binary_name_malloc(void); +char *pa_runtime_path(const char *fn); +char *pa_state_path(const char *fn, bool prepend_machine_id); + +int pa_atoi(const char *s, int32_t *ret_i); +int pa_atou(const char *s, uint32_t *ret_u); +int pa_atol(const char *s, long *ret_l); +int pa_atod(const char *s, double *ret_d); + +size_t pa_snprintf(char *str, size_t size, const char *format, ...); +size_t pa_vsnprintf(char *str, size_t size, const char *format, va_list ap); + +char *pa_truncate_utf8(char *c, size_t l); + +int pa_match(const char *expr, const char *v); +bool pa_is_regex_valid(const char *expr); + +char *pa_getcwd(void); +char *pa_make_path_absolute(const char *p); +bool pa_is_path_absolute(const char *p); + +void *pa_will_need(const void *p, size_t l); + +static inline int pa_is_power_of_two(unsigned n) { + return !(n & (n - 1)); +} + +static inline unsigned pa_ulog2(unsigned n) { + + if (n <= 1) + return 0; + +#if __GNUC__ >= 4 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4) + return 8U * (unsigned) sizeof(unsigned) - (unsigned) __builtin_clz(n) - 1; +#else +{ + unsigned r = 0; + + for (;;) { + n = n >> 1; + + if (!n) + return r; + + r++; + } +} +#endif +} + +static inline unsigned pa_make_power_of_two(unsigned n) { + + if (pa_is_power_of_two(n)) + return n; + + return 1U << (pa_ulog2(n) + 1); +} + +void pa_close_pipe(int fds[2]); + +char *pa_readlink(const char *p); + +int pa_close_all(int except_fd, ...); +int pa_close_allv(const int except_fds[]); +int pa_unblock_sigs(int except, ...); +int pa_unblock_sigsv(const int except[]); +int pa_reset_sigs(int except, ...); +int pa_reset_sigsv(const int except[]); + +void pa_set_env(const char *key, const char *value); +void pa_unset_env(const char *key); +void pa_set_env_and_record(const char *key, const char *value); +void pa_unset_env_recorded(void); + +bool pa_in_system_mode(void); + +#define pa_streq(a,b) (!strcmp((a),(b))) +#define pa_strneq(a,b,n) (!strncmp((a),(b),(n))) + +/* Like pa_streq, but does not blow up on NULL pointers. */ +static inline bool pa_safe_streq(const char *a, const char *b) { + if (a == NULL || b == NULL) + return a == b; + return pa_streq(a, b); +} + +bool pa_str_in_list_spaces(const char *needle, const char *haystack); +bool pa_str_in_list(const char *haystack, const char *delimiters, const char *needle); + +char* pa_str_strip_suffix(const char *str, const char *suffix); + +char *pa_get_host_name_malloc(void); +char *pa_get_user_name_malloc(void); + +char *pa_machine_id(void); +char *pa_session_id(void); +char *pa_uname_string(void); + +#ifdef HAVE_VALGRIND_MEMCHECK_H +bool pa_in_valgrind(void); +#else +static inline bool pa_in_valgrind(void) { + return false; +} +#endif + +unsigned pa_gcd(unsigned a, unsigned b); +void pa_reduce(unsigned *num, unsigned *den); + +unsigned pa_ncpus(void); + +/* Replaces all occurrences of `a' in `s' with `b'. The caller has to free the + * returned string. All parameters must be non-NULL and additionally `a' must + * not be a zero-length string. + */ +char *pa_replace(const char*s, const char*a, const char *b); + +/* Escapes p by inserting backslashes in front of backslashes. chars is a + * regular (i.e. NULL-terminated) string containing additional characters that + * should be escaped. chars can be NULL. The caller has to free the returned + * string. */ +char *pa_escape(const char *p, const char *chars); + +/* Does regular backslash unescaping. Returns the argument p. */ +char *pa_unescape(char *p); + +char *pa_realpath(const char *path); + +void pa_disable_sigpipe(void); + +void pa_xfreev(void**a); + +static inline void pa_xstrfreev(char **a) { + pa_xfreev((void**) a); +} + +char **pa_split_spaces_strv(const char *s); + +char* pa_maybe_prefix_path(const char *path, const char *prefix); + +/* Returns size of the specified pipe or 4096 on failure */ +size_t pa_pipe_buf(int fd); + +void pa_reset_personality(void); + +bool pa_run_from_build_tree(void) PA_GCC_CONST; + +const char *pa_get_temp_dir(void); + +int pa_open_cloexec(const char *fn, int flags, mode_t mode); +int pa_socket_cloexec(int domain, int type, int protocol); +int pa_pipe_cloexec(int pipefd[2]); +int pa_accept_cloexec(int sockfd, struct sockaddr *addr, socklen_t *addrlen); +FILE* pa_fopen_cloexec(const char *path, const char *mode); + +void pa_nullify_stdfds(void); + +char *pa_read_line_from_file(const char *fn); +bool pa_running_in_vm(void); + +#ifdef OS_IS_WIN32 +char *pa_win32_get_toplevel(HANDLE handle); +#endif + +size_t pa_page_size(void); + +/* Rounds down */ +static inline void* PA_PAGE_ALIGN_PTR(const void *p) { + return (void*) (((size_t) p) & ~(pa_page_size() - 1)); +} + +/* Rounds up */ +static inline size_t PA_PAGE_ALIGN(size_t l) { + size_t page_size = pa_page_size(); + return (l + page_size - 1) & ~(page_size - 1); +} + +#endif diff --git a/src/pulsecore/core.c b/src/pulsecore/core.c new file mode 100644 index 0000000..c28c531 --- /dev/null +++ b/src/pulsecore/core.c @@ -0,0 +1,632 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <stdio.h> +#include <signal.h> + +#include <pulse/rtclock.h> +#include <pulse/timeval.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/module.h> +#include <pulsecore/core-rtclock.h> +#include <pulsecore/core-util.h> +#include <pulsecore/core-scache.h> +#include <pulsecore/core-subscribe.h> +#include <pulsecore/random.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> + +#include "core.h" + +PA_DEFINE_PUBLIC_CLASS(pa_core, pa_msgobject); + +static int core_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk) { + pa_core *c = PA_CORE(o); + + pa_core_assert_ref(c); + + switch (code) { + + case PA_CORE_MESSAGE_UNLOAD_MODULE: + pa_module_unload(userdata, true); + return 0; + + default: + return -1; + } +} + +static void core_free(pa_object *o); + +pa_core* pa_core_new(pa_mainloop_api *m, bool shared, bool enable_memfd, size_t shm_size) { + pa_core* c; + pa_mempool *pool; + pa_mem_type_t type; + int j; + + pa_assert(m); + + if (shared) { + type = (enable_memfd) ? PA_MEM_TYPE_SHARED_MEMFD : PA_MEM_TYPE_SHARED_POSIX; + if (!(pool = pa_mempool_new(type, shm_size, false))) { + pa_log_warn("Failed to allocate %s memory pool. Falling back to a normal memory pool.", + pa_mem_type_to_string(type)); + shared = false; + } + } + + if (!shared) { + if (!(pool = pa_mempool_new(PA_MEM_TYPE_PRIVATE, shm_size, false))) { + pa_log("pa_mempool_new() failed."); + return NULL; + } + } + + c = pa_msgobject_new(pa_core); + c->parent.parent.free = core_free; + c->parent.process_msg = core_process_msg; + + c->state = PA_CORE_STARTUP; + c->mainloop = m; + + c->clients = pa_idxset_new(NULL, NULL); + c->cards = pa_idxset_new(NULL, NULL); + c->sinks = pa_idxset_new(NULL, NULL); + c->sources = pa_idxset_new(NULL, NULL); + c->sink_inputs = pa_idxset_new(NULL, NULL); + c->source_outputs = pa_idxset_new(NULL, NULL); + c->modules = pa_idxset_new(NULL, NULL); + c->scache = pa_idxset_new(NULL, NULL); + + c->namereg = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + c->shared = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + c->message_handlers = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + + c->default_source = NULL; + c->default_sink = NULL; + + c->default_sample_spec.format = PA_SAMPLE_S16NE; + c->default_sample_spec.rate = 44100; + c->default_sample_spec.channels = 2; + pa_channel_map_init_extend(&c->default_channel_map, c->default_sample_spec.channels, PA_CHANNEL_MAP_DEFAULT); + c->default_n_fragments = 4; + c->default_fragment_size_msec = 25; + + c->deferred_volume_safety_margin_usec = 8000; + c->deferred_volume_extra_delay_usec = 0; + + c->module_defer_unload_event = NULL; + c->modules_pending_unload = pa_hashmap_new(NULL, NULL); + + c->subscription_defer_event = NULL; + PA_LLIST_HEAD_INIT(pa_subscription, c->subscriptions); + PA_LLIST_HEAD_INIT(pa_subscription_event, c->subscription_event_queue); + c->subscription_event_last = NULL; + + c->mempool = pool; + c->shm_size = shm_size; + pa_silence_cache_init(&c->silence_cache); + + c->exit_event = NULL; + c->scache_auto_unload_event = NULL; + + c->exit_idle_time = -1; + c->scache_idle_time = 20; + + c->flat_volumes = true; + c->disallow_module_loading = false; + c->disallow_exit = false; + c->running_as_daemon = false; + c->realtime_scheduling = false; + c->realtime_priority = 5; + c->disable_remixing = false; + c->remixing_use_all_sink_channels = true; + c->remixing_produce_lfe = false; + c->remixing_consume_lfe = false; + c->lfe_crossover_freq = 0; + c->deferred_volume = true; + c->resample_method = PA_RESAMPLER_SPEEX_FLOAT_BASE + 1; + + for (j = 0; j < PA_CORE_HOOK_MAX; j++) + pa_hook_init(&c->hooks[j], c); + + pa_random(&c->cookie, sizeof(c->cookie)); + +#ifdef SIGPIPE + pa_check_signal_is_blocked(SIGPIPE); +#endif + + return c; +} + +static void core_free(pa_object *o) { + pa_core *c = PA_CORE(o); + int j; + pa_assert(c); + + c->state = PA_CORE_SHUTDOWN; + + /* Note: All modules and samples in the cache should be unloaded before + * we get here */ + + pa_assert(pa_idxset_isempty(c->scache)); + pa_idxset_free(c->scache, NULL); + + pa_assert(pa_idxset_isempty(c->modules)); + pa_idxset_free(c->modules, NULL); + + pa_assert(pa_idxset_isempty(c->clients)); + pa_idxset_free(c->clients, NULL); + + pa_assert(pa_idxset_isempty(c->cards)); + pa_idxset_free(c->cards, NULL); + + pa_assert(pa_idxset_isempty(c->sinks)); + pa_idxset_free(c->sinks, NULL); + + pa_assert(pa_idxset_isempty(c->sources)); + pa_idxset_free(c->sources, NULL); + + pa_assert(pa_idxset_isempty(c->source_outputs)); + pa_idxset_free(c->source_outputs, NULL); + + pa_assert(pa_idxset_isempty(c->sink_inputs)); + pa_idxset_free(c->sink_inputs, NULL); + + pa_assert(pa_hashmap_isempty(c->namereg)); + pa_hashmap_free(c->namereg); + + pa_assert(pa_hashmap_isempty(c->shared)); + pa_hashmap_free(c->shared); + + pa_assert(pa_hashmap_isempty(c->message_handlers)); + pa_hashmap_free(c->message_handlers); + + pa_assert(pa_hashmap_isempty(c->modules_pending_unload)); + pa_hashmap_free(c->modules_pending_unload); + + pa_subscription_free_all(c); + + if (c->exit_event) + c->mainloop->time_free(c->exit_event); + + pa_assert(!c->default_source); + pa_assert(!c->default_sink); + pa_xfree(c->configured_default_source); + pa_xfree(c->configured_default_sink); + + pa_silence_cache_done(&c->silence_cache); + pa_mempool_unref(c->mempool); + + for (j = 0; j < PA_CORE_HOOK_MAX; j++) + pa_hook_done(&c->hooks[j]); + + pa_xfree(c); +} + +void pa_core_set_configured_default_sink(pa_core *core, const char *sink) { + char *old_sink; + + pa_assert(core); + + old_sink = pa_xstrdup(core->configured_default_sink); + + if (pa_safe_streq(sink, old_sink)) + goto finish; + + pa_xfree(core->configured_default_sink); + core->configured_default_sink = pa_xstrdup(sink); + pa_log_info("configured_default_sink: %s -> %s", + old_sink ? old_sink : "(unset)", sink ? sink : "(unset)"); + pa_subscription_post(core, PA_SUBSCRIPTION_EVENT_SERVER | PA_SUBSCRIPTION_EVENT_CHANGE, PA_INVALID_INDEX); + + pa_core_update_default_sink(core); + +finish: + pa_xfree(old_sink); +} + +void pa_core_set_configured_default_source(pa_core *core, const char *source) { + char *old_source; + + pa_assert(core); + + old_source = pa_xstrdup(core->configured_default_source); + + if (pa_safe_streq(source, old_source)) + goto finish; + + pa_xfree(core->configured_default_source); + core->configured_default_source = pa_xstrdup(source); + pa_log_info("configured_default_source: %s -> %s", + old_source ? old_source : "(unset)", source ? source : "(unset)"); + pa_subscription_post(core, PA_SUBSCRIPTION_EVENT_SERVER | PA_SUBSCRIPTION_EVENT_CHANGE, PA_INVALID_INDEX); + + pa_core_update_default_source(core); + +finish: + pa_xfree(old_source); +} + +/* a < b -> return -1 + * a == b -> return 0 + * a > b -> return 1 */ +static int compare_sinks(pa_sink *a, pa_sink *b) { + pa_core *core; + + core = a->core; + + /* Available sinks always beat unavailable sinks. */ + if (a->active_port && a->active_port->available == PA_AVAILABLE_NO + && (!b->active_port || b->active_port->available != PA_AVAILABLE_NO)) + return -1; + if (b->active_port && b->active_port->available == PA_AVAILABLE_NO + && (!a->active_port || a->active_port->available != PA_AVAILABLE_NO)) + return 1; + + /* The configured default sink is preferred over any other sink. */ + if (pa_safe_streq(b->name, core->configured_default_sink)) + return -1; + if (pa_safe_streq(a->name, core->configured_default_sink)) + return 1; + + if (a->priority < b->priority) + return -1; + if (a->priority > b->priority) + return 1; + + /* It's hard to find any difference between these sinks, but maybe one of + * them is already the default sink? If so, it's best to keep it as the + * default to avoid changing the routing for no good reason. */ + if (b == core->default_sink) + return -1; + if (a == core->default_sink) + return 1; + + return 0; +} + +void pa_core_update_default_sink(pa_core *core) { + pa_sink *best = NULL; + pa_sink *sink; + uint32_t idx; + pa_sink *old_default_sink; + + pa_assert(core); + + PA_IDXSET_FOREACH(sink, core->sinks, idx) { + if (!PA_SINK_IS_LINKED(sink->state)) + continue; + + if (!best) { + best = sink; + continue; + } + + if (compare_sinks(sink, best) > 0) + best = sink; + } + + old_default_sink = core->default_sink; + + if (best == old_default_sink) + return; + + core->default_sink = best; + pa_log_info("default_sink: %s -> %s", + old_default_sink ? old_default_sink->name : "(unset)", best ? best->name : "(unset)"); + + /* If the default sink changed, it may be that the default source has to be + * changed too, because monitor sources are prioritized partly based on the + * priorities of the monitored sinks. */ + pa_core_update_default_source(core); + + pa_subscription_post(core, PA_SUBSCRIPTION_EVENT_SERVER | PA_SUBSCRIPTION_EVENT_CHANGE, PA_INVALID_INDEX); + pa_hook_fire(&core->hooks[PA_CORE_HOOK_DEFAULT_SINK_CHANGED], core->default_sink); + + /* try to move the streams from old_default_sink to the new default_sink conditionally */ + if (old_default_sink) + pa_sink_move_streams_to_default_sink(core, old_default_sink, true); +} + +/* a < b -> return -1 + * a == b -> return 0 + * a > b -> return 1 */ +static int compare_sources(pa_source *a, pa_source *b) { + pa_core *core; + + core = a->core; + + /* Available sources always beat unavailable sources. */ + if (a->active_port && a->active_port->available == PA_AVAILABLE_NO + && (!b->active_port || b->active_port->available != PA_AVAILABLE_NO)) + return -1; + if (b->active_port && b->active_port->available == PA_AVAILABLE_NO + && (!a->active_port || a->active_port->available != PA_AVAILABLE_NO)) + return 1; + + /* The configured default source is preferred over any other source. */ + if (pa_safe_streq(b->name, core->configured_default_source)) + return -1; + if (pa_safe_streq(a->name, core->configured_default_source)) + return 1; + + /* Monitor sources lose to non-monitor sources. */ + if (a->monitor_of && !b->monitor_of) + return -1; + if (!a->monitor_of && b->monitor_of) + return 1; + + if (a->priority < b->priority) + return -1; + if (a->priority > b->priority) + return 1; + + /* If the sources are monitors, we can compare the monitored sinks. */ + if (a->monitor_of) + return compare_sinks(a->monitor_of, b->monitor_of); + + /* It's hard to find any difference between these sources, but maybe one of + * them is already the default source? If so, it's best to keep it as the + * default to avoid changing the routing for no good reason. */ + if (b == core->default_source) + return -1; + if (a == core->default_source) + return 1; + + return 0; +} + +void pa_core_update_default_source(pa_core *core) { + pa_source *best = NULL; + pa_source *source; + uint32_t idx; + pa_source *old_default_source; + + pa_assert(core); + + PA_IDXSET_FOREACH(source, core->sources, idx) { + if (!PA_SOURCE_IS_LINKED(source->state)) + continue; + + if (!best) { + best = source; + continue; + } + + if (compare_sources(source, best) > 0) + best = source; + } + + old_default_source = core->default_source; + + if (best == old_default_source) + return; + + core->default_source = best; + pa_log_info("default_source: %s -> %s", + old_default_source ? old_default_source->name : "(unset)", best ? best->name : "(unset)"); + pa_subscription_post(core, PA_SUBSCRIPTION_EVENT_SERVER | PA_SUBSCRIPTION_EVENT_CHANGE, PA_INVALID_INDEX); + pa_hook_fire(&core->hooks[PA_CORE_HOOK_DEFAULT_SOURCE_CHANGED], core->default_source); + + /* try to move the streams from old_default_source to the new default_source conditionally */ + if (old_default_source) + pa_source_move_streams_to_default_source(core, old_default_source, true); +} + +void pa_core_set_exit_idle_time(pa_core *core, int time) { + pa_assert(core); + + if (time == core->exit_idle_time) + return; + + pa_log_info("exit_idle_time: %i -> %i", core->exit_idle_time, time); + core->exit_idle_time = time; +} + +static void exit_callback(pa_mainloop_api *m, pa_time_event *e, const struct timeval *t, void *userdata) { + pa_core *c = userdata; + pa_assert(c->exit_event == e); + + pa_log_info("We are idle, quitting..."); + pa_core_exit(c, true, 0); +} + +void pa_core_check_idle(pa_core *c) { + pa_assert(c); + + if (!c->exit_event && + c->exit_idle_time >= 0 && + pa_idxset_size(c->clients) == 0) { + + c->exit_event = pa_core_rttime_new(c, pa_rtclock_now() + c->exit_idle_time * PA_USEC_PER_SEC, exit_callback, c); + + } else if (c->exit_event && pa_idxset_size(c->clients) > 0) { + c->mainloop->time_free(c->exit_event); + c->exit_event = NULL; + } +} + +int pa_core_exit(pa_core *c, bool force, int retval) { + pa_assert(c); + + if (c->disallow_exit && !force) + return -1; + + c->mainloop->quit(c->mainloop, retval); + return 0; +} + +void pa_core_maybe_vacuum(pa_core *c) { + pa_assert(c); + + if (pa_idxset_isempty(c->sink_inputs) && pa_idxset_isempty(c->source_outputs)) { + pa_log_debug("Hmm, no streams around, trying to vacuum."); + } else { + pa_sink *si; + pa_source *so; + uint32_t idx; + + idx = 0; + PA_IDXSET_FOREACH(si, c->sinks, idx) + if (si->state != PA_SINK_SUSPENDED) + return; + + idx = 0; + PA_IDXSET_FOREACH(so, c->sources, idx) + if (so->state != PA_SOURCE_SUSPENDED) + return; + + pa_log_info("All sinks and sources are suspended, vacuuming memory"); + } + + pa_mempool_vacuum(c->mempool); +} + +pa_time_event* pa_core_rttime_new(pa_core *c, pa_usec_t usec, pa_time_event_cb_t cb, void *userdata) { + struct timeval tv; + + pa_assert(c); + pa_assert(c->mainloop); + + return c->mainloop->time_new(c->mainloop, pa_timeval_rtstore(&tv, usec, true), cb, userdata); +} + +void pa_core_rttime_restart(pa_core *c, pa_time_event *e, pa_usec_t usec) { + struct timeval tv; + + pa_assert(c); + pa_assert(c->mainloop); + + c->mainloop->time_restart(e, pa_timeval_rtstore(&tv, usec, true)); +} + +void pa_core_move_streams_to_newly_available_preferred_sink(pa_core *c, pa_sink *s) { + pa_sink_input *si; + uint32_t idx; + + pa_assert(c); + pa_assert(s); + + PA_IDXSET_FOREACH(si, c->sink_inputs, idx) { + if (si->sink == s) + continue; + + if (!si->sink) + continue; + + /* Skip this sink input if it is connecting a filter sink to + * the master */ + if (si->origin_sink) + continue; + + /* It might happen that a stream and a sink are set up at the + same time, in which case we want to make sure we don't + interfere with that */ + if (!PA_SINK_INPUT_IS_LINKED(si->state)) + continue; + + if (pa_safe_streq(si->preferred_sink, s->name)) + pa_sink_input_move_to(si, s, false); + } + +} + +void pa_core_move_streams_to_newly_available_preferred_source(pa_core *c, pa_source *s) { + pa_source_output *so; + uint32_t idx; + + pa_assert(c); + pa_assert(s); + + PA_IDXSET_FOREACH(so, c->source_outputs, idx) { + if (so->source == s) + continue; + + if (so->direct_on_input) + continue; + + if (!so->source) + continue; + + /* Skip this source output if it is connecting a filter source to + * the master */ + if (so->destination_source) + continue; + + /* It might happen that a stream and a source are set up at the + same time, in which case we want to make sure we don't + interfere with that */ + if (!PA_SOURCE_OUTPUT_IS_LINKED(so->state)) + continue; + + if (pa_safe_streq(so->preferred_source, s->name)) + pa_source_output_move_to(so, s, false); + } + +} + + +/* Helper macro to reduce repetition in pa_suspend_cause_to_string(). + * Parameters: + * char *p: the current position in the write buffer + * bool first: is cause_to_check the first cause to be written? + * pa_suspend_cause_t cause_bitfield: the causes given to pa_suspend_cause_to_string() + * pa_suspend_cause_t cause_to_check: the cause whose presence in cause_bitfield is to be checked + */ +#define CHECK_CAUSE(p, first, cause_bitfield, cause_to_check) \ + if (cause_bitfield & PA_SUSPEND_##cause_to_check) { \ + size_t len = sizeof(#cause_to_check) - 1; \ + if (!first) { \ + *p = '|'; \ + p++; \ + } \ + first = false; \ + memcpy(p, #cause_to_check, len); \ + p += len; \ + } + +const char *pa_suspend_cause_to_string(pa_suspend_cause_t cause_bitfield, char buf[PA_SUSPEND_CAUSE_TO_STRING_BUF_SIZE]) { + char *p = buf; + bool first = true; + + CHECK_CAUSE(p, first, cause_bitfield, USER); + CHECK_CAUSE(p, first, cause_bitfield, APPLICATION); + CHECK_CAUSE(p, first, cause_bitfield, IDLE); + CHECK_CAUSE(p, first, cause_bitfield, SESSION); + CHECK_CAUSE(p, first, cause_bitfield, PASSTHROUGH); + CHECK_CAUSE(p, first, cause_bitfield, INTERNAL); + CHECK_CAUSE(p, first, cause_bitfield, UNAVAILABLE); + + if (p == buf) { + memcpy(p, "(none)", 6); + p += 6; + } + + *p = 0; + + return buf; +} diff --git a/src/pulsecore/core.h b/src/pulsecore/core.h new file mode 100644 index 0000000..57924b9 --- /dev/null +++ b/src/pulsecore/core.h @@ -0,0 +1,287 @@ +#ifndef foocorehfoo +#define foocorehfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulsecore/typedefs.h> +#include <pulse/mainloop-api.h> +#include <pulse/sample.h> +#include <pulsecore/cpu.h> + +/* This is a bitmask that encodes the cause why a sink/source is + * suspended. + * + * When adding new causes, remember to update pa_suspend_cause_to_string() and + * PA_SUSPEND_CAUSE_TO_STRING_BUF_SIZE! */ +typedef enum pa_suspend_cause { + PA_SUSPEND_USER = 1, /* Exposed to the user via some protocol */ + PA_SUSPEND_APPLICATION = 2, /* Used by the device reservation logic */ + PA_SUSPEND_IDLE = 4, /* Used by module-suspend-on-idle */ + PA_SUSPEND_SESSION = 8, /* Used by module-hal for mark inactive sessions */ + PA_SUSPEND_PASSTHROUGH = 16, /* Used to suspend monitor sources when the sink is in passthrough mode */ + PA_SUSPEND_INTERNAL = 32, /* This is used for short period server-internal suspends, such as for sample rate updates */ + PA_SUSPEND_UNAVAILABLE = 64, /* Used by device implementations that have to suspend when the device is unavailable */ + PA_SUSPEND_ALL = 0xFFFF /* Magic cause that can be used to resume forcibly */ +} pa_suspend_cause_t; + +#include <pulsecore/idxset.h> +#include <pulsecore/hashmap.h> +#include <pulsecore/memblock.h> +#include <pulsecore/resampler.h> +#include <pulsecore/llist.h> +#include <pulsecore/hook-list.h> +#include <pulsecore/asyncmsgq.h> +#include <pulsecore/sample-util.h> +#include <pulsecore/sink.h> +#include <pulsecore/source.h> +#include <pulsecore/core-subscribe.h> +#include <pulsecore/msgobject.h> + +typedef enum pa_server_type { + PA_SERVER_TYPE_UNSET, + PA_SERVER_TYPE_USER, + PA_SERVER_TYPE_SYSTEM, + PA_SERVER_TYPE_NONE +} pa_server_type_t; + +typedef enum pa_core_state { + PA_CORE_STARTUP, + PA_CORE_RUNNING, + PA_CORE_SHUTDOWN +} pa_core_state_t; + +typedef enum pa_core_hook { + PA_CORE_HOOK_SINK_NEW, + PA_CORE_HOOK_SINK_FIXATE, + PA_CORE_HOOK_SINK_PUT, + PA_CORE_HOOK_SINK_UNLINK, + PA_CORE_HOOK_SINK_UNLINK_POST, + PA_CORE_HOOK_SINK_STATE_CHANGED, + PA_CORE_HOOK_SINK_PROPLIST_CHANGED, + PA_CORE_HOOK_SINK_PORT_CHANGED, + PA_CORE_HOOK_SINK_FLAGS_CHANGED, + PA_CORE_HOOK_SINK_VOLUME_CHANGED, + PA_CORE_HOOK_SINK_MUTE_CHANGED, + PA_CORE_HOOK_SINK_PORT_LATENCY_OFFSET_CHANGED, + PA_CORE_HOOK_SOURCE_NEW, + PA_CORE_HOOK_SOURCE_FIXATE, + PA_CORE_HOOK_SOURCE_PUT, + PA_CORE_HOOK_SOURCE_UNLINK, + PA_CORE_HOOK_SOURCE_UNLINK_POST, + PA_CORE_HOOK_SOURCE_STATE_CHANGED, + PA_CORE_HOOK_SOURCE_PROPLIST_CHANGED, + PA_CORE_HOOK_SOURCE_PORT_CHANGED, + PA_CORE_HOOK_SOURCE_FLAGS_CHANGED, + PA_CORE_HOOK_SOURCE_VOLUME_CHANGED, + PA_CORE_HOOK_SOURCE_MUTE_CHANGED, + PA_CORE_HOOK_SOURCE_PORT_LATENCY_OFFSET_CHANGED, + PA_CORE_HOOK_SINK_INPUT_NEW, + PA_CORE_HOOK_SINK_INPUT_FIXATE, + PA_CORE_HOOK_SINK_INPUT_PUT, + PA_CORE_HOOK_SINK_INPUT_UNLINK, + PA_CORE_HOOK_SINK_INPUT_UNLINK_POST, + PA_CORE_HOOK_SINK_INPUT_MOVE_START, + PA_CORE_HOOK_SINK_INPUT_MOVE_FINISH, + PA_CORE_HOOK_SINK_INPUT_MOVE_FAIL, + PA_CORE_HOOK_SINK_INPUT_STATE_CHANGED, + PA_CORE_HOOK_SINK_INPUT_PROPLIST_CHANGED, + PA_CORE_HOOK_SINK_INPUT_VOLUME_CHANGED, + PA_CORE_HOOK_SINK_INPUT_MUTE_CHANGED, + PA_CORE_HOOK_SINK_INPUT_SEND_EVENT, + PA_CORE_HOOK_SOURCE_OUTPUT_NEW, + PA_CORE_HOOK_SOURCE_OUTPUT_FIXATE, + PA_CORE_HOOK_SOURCE_OUTPUT_PUT, + PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK, + PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK_POST, + PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_START, + PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_FINISH, + PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_FAIL, + PA_CORE_HOOK_SOURCE_OUTPUT_STATE_CHANGED, + PA_CORE_HOOK_SOURCE_OUTPUT_PROPLIST_CHANGED, + PA_CORE_HOOK_SOURCE_OUTPUT_VOLUME_CHANGED, + PA_CORE_HOOK_SOURCE_OUTPUT_MUTE_CHANGED, + PA_CORE_HOOK_SOURCE_OUTPUT_SEND_EVENT, + PA_CORE_HOOK_CLIENT_NEW, + PA_CORE_HOOK_CLIENT_PUT, + PA_CORE_HOOK_CLIENT_UNLINK, + PA_CORE_HOOK_CLIENT_PROPLIST_CHANGED, + PA_CORE_HOOK_CLIENT_SEND_EVENT, + PA_CORE_HOOK_CARD_NEW, + PA_CORE_HOOK_CARD_CHOOSE_INITIAL_PROFILE, + PA_CORE_HOOK_CARD_PUT, + PA_CORE_HOOK_CARD_UNLINK, + PA_CORE_HOOK_CARD_PREFERRED_PORT_CHANGED, + PA_CORE_HOOK_CARD_PROFILE_CHANGED, + PA_CORE_HOOK_CARD_PROFILE_ADDED, + PA_CORE_HOOK_CARD_PROFILE_AVAILABLE_CHANGED, + PA_CORE_HOOK_CARD_SUSPEND_CHANGED, + PA_CORE_HOOK_PORT_AVAILABLE_CHANGED, + PA_CORE_HOOK_PORT_LATENCY_OFFSET_CHANGED, + PA_CORE_HOOK_DEFAULT_SINK_CHANGED, + PA_CORE_HOOK_DEFAULT_SOURCE_CHANGED, + PA_CORE_HOOK_MODULE_NEW, + PA_CORE_HOOK_MODULE_PROPLIST_CHANGED, + PA_CORE_HOOK_MODULE_UNLINK, + PA_CORE_HOOK_SAMPLE_CACHE_NEW, + PA_CORE_HOOK_SAMPLE_CACHE_CHANGED, + PA_CORE_HOOK_SAMPLE_CACHE_UNLINK, + PA_CORE_HOOK_MAX +} pa_core_hook_t; + +/* The core structure of PulseAudio. Every PulseAudio daemon contains + * exactly one of these. It is used for storing kind of global + * variables for the daemon. */ + +struct pa_core { + pa_msgobject parent; + + pa_core_state_t state; + + /* A random value which may be used to identify this instance of + * PulseAudio. Not cryptographically secure in any way. */ + uint32_t cookie; + + pa_mainloop_api *mainloop; + + /* idxset of all kinds of entities */ + pa_idxset *clients, *cards, *sinks, *sources, *sink_inputs, *source_outputs, *modules, *scache; + + /* Some hashmaps for all sorts of entities */ + pa_hashmap *namereg, *shared, *message_handlers; + + /* The default sink/source as configured by the user. If the user hasn't + * explicitly configured anything, these are set to NULL. These are strings + * instead of sink/source pointers, because that allows us to reference + * devices that don't currently exist. That's useful for remembering that + * a hotplugged USB sink was previously set as the default sink. */ + char *configured_default_sink; + char *configured_default_source; + + /* The effective default sink/source. If no sink or source is explicitly + * configured as the default, we pick the device that ranks highest + * according to the compare_sinks() and compare_sources() functions in + * core.c. pa_core_update_default_sink/source() has to be called whenever + * anything changes that might change the comparison results. */ + pa_sink *default_sink; + pa_source *default_source; + + pa_channel_map default_channel_map; + pa_sample_spec default_sample_spec; + uint32_t alternate_sample_rate; + unsigned default_n_fragments, default_fragment_size_msec; + unsigned deferred_volume_safety_margin_usec; + int deferred_volume_extra_delay_usec; + unsigned lfe_crossover_freq; + + pa_defer_event *module_defer_unload_event; + pa_hashmap *modules_pending_unload; /* pa_module -> pa_module (hashmap-as-a-set) */ + + pa_defer_event *subscription_defer_event; + PA_LLIST_HEAD(pa_subscription, subscriptions); + PA_LLIST_HEAD(pa_subscription_event, subscription_event_queue); + pa_subscription_event *subscription_event_last; + + /* The mempool is used for data we write to, it's readonly for the client. */ + pa_mempool *mempool; + + /* Shared memory size, as specified either by daemon configuration + * or PA daemon defaults (~ 64 MiB). */ + size_t shm_size; + + pa_silence_cache silence_cache; + + pa_time_event *exit_event; + pa_time_event *scache_auto_unload_event; + + int exit_idle_time, scache_idle_time; + + bool flat_volumes:1; + bool rescue_streams:1; + bool disallow_module_loading:1; + bool disallow_exit:1; + bool running_as_daemon:1; + bool realtime_scheduling:1; + bool avoid_resampling:1; + bool disable_remixing:1; + bool remixing_use_all_sink_channels:1; + bool remixing_produce_lfe:1; + bool remixing_consume_lfe:1; + bool deferred_volume:1; + + pa_resample_method_t resample_method; + int realtime_priority; + + pa_server_type_t server_type; + pa_cpu_info cpu_info; + + /* hooks */ + pa_hook hooks[PA_CORE_HOOK_MAX]; +}; + +PA_DECLARE_PUBLIC_CLASS(pa_core); +#define PA_CORE(o) pa_core_cast(o) + +enum { + PA_CORE_MESSAGE_UNLOAD_MODULE, + PA_CORE_MESSAGE_MAX +}; + +pa_core* pa_core_new(pa_mainloop_api *m, bool shared, bool enable_memfd, size_t shm_size); + +void pa_core_set_configured_default_sink(pa_core *core, const char *sink); +void pa_core_set_configured_default_source(pa_core *core, const char *source); + +/* These should be called whenever something changes that may affect the + * default sink or source choice. + * + * If the default source choice happens between two monitor sources, the + * monitored sinks are compared, so if the default sink changes, the default + * source may change too. However, pa_core_update_default_sink() calls + * pa_core_update_default_source() internally, so it's sufficient to only call + * pa_core_update_default_sink() when something happens that affects the sink + * ordering. */ +void pa_core_update_default_sink(pa_core *core); +void pa_core_update_default_source(pa_core *core); + +void pa_core_set_exit_idle_time(pa_core *core, int time); + +/* Check whether no one is connected to this core */ +void pa_core_check_idle(pa_core *c); + +int pa_core_exit(pa_core *c, bool force, int retval); + +void pa_core_maybe_vacuum(pa_core *c); + +/* wrapper for c->mainloop->time_*() RT time events */ +pa_time_event* pa_core_rttime_new(pa_core *c, pa_usec_t usec, pa_time_event_cb_t cb, void *userdata); +void pa_core_rttime_restart(pa_core *c, pa_time_event *e, pa_usec_t usec); + +static const size_t PA_SUSPEND_CAUSE_TO_STRING_BUF_SIZE = + sizeof("USER|APPLICATION|IDLE|SESSION|PASSTHROUGH|INTERNAL|UNAVAILABLE"); + +/* Converts the given suspend cause to a string. The string is written to the + * provided buffer. The same buffer is the return value of this function. */ +const char *pa_suspend_cause_to_string(pa_suspend_cause_t cause, char buf[PA_SUSPEND_CAUSE_TO_STRING_BUF_SIZE]); + +void pa_core_move_streams_to_newly_available_preferred_sink(pa_core *c, pa_sink *s); + +void pa_core_move_streams_to_newly_available_preferred_source(pa_core *c, pa_source *s); + +#endif diff --git a/src/pulsecore/cpu-arm.c b/src/pulsecore/cpu-arm.c new file mode 100644 index 0000000..122a72b --- /dev/null +++ b/src/pulsecore/cpu-arm.c @@ -0,0 +1,170 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2009 Wim Taymans <wim.taymans@collabora.co.uk> + + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdint.h> +#include <sys/types.h> +#include <fcntl.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> + +#include "cpu-arm.h" + +#if defined (__arm__) && defined (__linux__) + +#define MAX_BUFFER 4096 +static char * +get_cpuinfo_line(char *cpuinfo, const char *tag) { + char *line, *end, *colon; + + if (!(line = strstr(cpuinfo, tag))) + return NULL; + + if (!(end = strchr(line, '\n'))) + return NULL; + + if (!(colon = strchr(line, ':'))) + return NULL; + + if (++colon >= end) + return NULL; + + return pa_xstrndup(colon, end - colon); +} + +static char *get_cpuinfo(void) { + char *cpuinfo; + int n, fd; + + cpuinfo = pa_xmalloc(MAX_BUFFER); + + if ((fd = pa_open_cloexec("/proc/cpuinfo", O_RDONLY, 0)) < 0) { + pa_xfree(cpuinfo); + return NULL; + } + + if ((n = pa_read(fd, cpuinfo, MAX_BUFFER-1, NULL)) < 0) { + pa_xfree(cpuinfo); + pa_close(fd); + return NULL; + } + cpuinfo[n] = 0; + pa_close(fd); + + return cpuinfo; +} +#endif /* defined (__arm__) && defined (__linux__) */ + +void pa_cpu_get_arm_flags(pa_cpu_arm_flag_t *flags) { +#if defined (__arm__) && defined (__linux__) + char *cpuinfo, *line; + int arch, part; + + /* We need to read the CPU flags from /proc/cpuinfo because there is no user + * space support to get the CPU features. This only works on linux AFAIK. */ + if (!(cpuinfo = get_cpuinfo())) { + pa_log("Can't read cpuinfo"); + return; + } + + *flags = 0; + + /* get the CPU architecture */ + if ((line = get_cpuinfo_line(cpuinfo, "CPU architecture"))) { + arch = strtoul(line, NULL, 0); + if (arch >= 6) + *flags |= PA_CPU_ARM_V6; + if (arch >= 7) + *flags |= PA_CPU_ARM_V7; + + pa_xfree(line); + } + + /* get the CPU features */ + if ((line = get_cpuinfo_line(cpuinfo, "Features"))) { + const char *state = NULL; + char *current; + + while ((current = pa_split_spaces(line, &state))) { + if (pa_streq(current, "vfp")) + *flags |= PA_CPU_ARM_VFP; + else if (pa_streq(current, "edsp")) + *flags |= PA_CPU_ARM_EDSP; + else if (pa_streq(current, "neon")) + *flags |= PA_CPU_ARM_NEON; + else if (pa_streq(current, "vfpv3")) + *flags |= PA_CPU_ARM_VFPV3; + + pa_xfree(current); + } + pa_xfree(line); + } + + /* get the CPU part number */ + if ((line = get_cpuinfo_line(cpuinfo, "CPU part"))) { + part = strtoul(line, NULL, 0); + if (part == 0xc08) + *flags |= PA_CPU_ARM_CORTEX_A8; + pa_xfree(line); + } + pa_xfree(cpuinfo); + + pa_log_info("CPU flags: %s%s%s%s%s%s%s", + (*flags & PA_CPU_ARM_V6) ? "V6 " : "", + (*flags & PA_CPU_ARM_V7) ? "V7 " : "", + (*flags & PA_CPU_ARM_VFP) ? "VFP " : "", + (*flags & PA_CPU_ARM_EDSP) ? "EDSP " : "", + (*flags & PA_CPU_ARM_NEON) ? "NEON " : "", + (*flags & PA_CPU_ARM_VFPV3) ? "VFPV3 " : "", + (*flags & PA_CPU_ARM_CORTEX_A8) ? "Cortex-A8 " : ""); +#endif +} + +bool pa_cpu_init_arm(pa_cpu_arm_flag_t *flags) { +#if defined (__arm__) +#if defined (__linux__) + pa_cpu_get_arm_flags(flags); + + if (*flags & PA_CPU_ARM_V6) + pa_volume_func_init_arm(*flags); + +#ifdef HAVE_NEON + if (*flags & PA_CPU_ARM_NEON) { + pa_convert_func_init_neon(*flags); + pa_mix_func_init_neon(*flags); + pa_remap_func_init_neon(*flags); + } +#endif + + return true; + +#else /* defined (__linux__) */ + pa_log("Reading ARM CPU features not yet supported on this OS"); +#endif /* defined (__linux__) */ + +#else /* defined (__arm__) */ + return false; +#endif /* defined (__arm__) */ +} diff --git a/src/pulsecore/cpu-arm.h b/src/pulsecore/cpu-arm.h new file mode 100644 index 0000000..dfdda32 --- /dev/null +++ b/src/pulsecore/cpu-arm.h @@ -0,0 +1,53 @@ +#ifndef foocpuarmhfoo +#define foocpuarmhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2009 Wim Taymans <wim.taymans@collabora.co.uk> + + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <stdint.h> +#include <pulsecore/macro.h> + +#ifndef PACKAGE +#error "Please include config.h before including this file!" +#endif + +typedef enum pa_cpu_arm_flag { + PA_CPU_ARM_V6 = (1 << 0), + PA_CPU_ARM_V7 = (1 << 1), + PA_CPU_ARM_VFP = (1 << 2), + PA_CPU_ARM_EDSP = (1 << 3), + PA_CPU_ARM_NEON = (1 << 4), + PA_CPU_ARM_VFPV3 = (1 << 5), + PA_CPU_ARM_CORTEX_A8 = (1 << 6), +} pa_cpu_arm_flag_t; + +void pa_cpu_get_arm_flags(pa_cpu_arm_flag_t *flags); +bool pa_cpu_init_arm(pa_cpu_arm_flag_t *flags); + +/* some optimized functions */ +void pa_volume_func_init_arm(pa_cpu_arm_flag_t flags); + +#ifdef HAVE_NEON +void pa_convert_func_init_neon(pa_cpu_arm_flag_t flags); +void pa_mix_func_init_neon(pa_cpu_arm_flag_t flags); +void pa_remap_func_init_neon(pa_cpu_arm_flag_t flags); +#endif + +#endif /* foocpuarmhfoo */ diff --git a/src/pulsecore/cpu-orc.c b/src/pulsecore/cpu-orc.c new file mode 100644 index 0000000..5caf6b6 --- /dev/null +++ b/src/pulsecore/cpu-orc.c @@ -0,0 +1,39 @@ +/*** + This file is part of PulseAudio. + + Copyright 2010 Arun Raghavan <arun.raghavan@collabora.co.uk> + + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "cpu-orc.h" + +bool pa_cpu_init_orc(pa_cpu_info cpu_info) { +#ifndef DISABLE_ORC + /* Update these as we test on more architectures */ + pa_cpu_x86_flag_t x86_want_flags = PA_CPU_X86_MMX | PA_CPU_X86_SSE | PA_CPU_X86_SSE2 | PA_CPU_X86_SSE3 | PA_CPU_X86_SSSE3 | PA_CPU_X86_SSE4_1 | PA_CPU_X86_SSE4_2; + + /* Enable Orc svolume optimizations */ + if ((cpu_info.cpu_type == PA_CPU_X86) && (cpu_info.flags.x86 & x86_want_flags)) { + pa_volume_func_init_orc(); + return true; + } +#endif + + return false; +} diff --git a/src/pulsecore/cpu-orc.h b/src/pulsecore/cpu-orc.h new file mode 100644 index 0000000..edfacea --- /dev/null +++ b/src/pulsecore/cpu-orc.h @@ -0,0 +1,31 @@ +#ifndef foocpuorchfoo +#define foocpuorchfoo + +/*** + This file is part of PulseAudio. + + Copyright 2010 Arun Raghavan <arun.raghavan@collabora.co.uk> + + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulsecore/cpu.h> + +/* Orc-optimised bits */ + +bool pa_cpu_init_orc(pa_cpu_info cpu_info); + +void pa_volume_func_init_orc(void); + +#endif /* foocpuorchfoo */ diff --git a/src/pulsecore/cpu-x86.c b/src/pulsecore/cpu-x86.c new file mode 100644 index 0000000..4e59e14 --- /dev/null +++ b/src/pulsecore/cpu-x86.c @@ -0,0 +1,132 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2009 Wim Taymans <wim.taymans@collabora.co.uk> + + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdint.h> + +#ifdef HAVE_CPUID_H +#include <cpuid.h> +#endif + +#include <pulsecore/log.h> + +#include "cpu-x86.h" + +void pa_cpu_get_x86_flags(pa_cpu_x86_flag_t *flags) { +#if (defined(__i386__) || defined(__amd64__)) && defined(HAVE_CPUID_H) + uint32_t eax, ebx, ecx, edx; + uint32_t level; + + *flags = 0; + + /* get standard level */ + if (__get_cpuid(0x00000000, &level, &ebx, &ecx, &edx) == 0) + goto finish; + + if (level >= 1) { + if (__get_cpuid(0x00000001, &eax, &ebx, &ecx, &edx) == 0) + goto finish; + + if (edx & (1<<15)) + *flags |= PA_CPU_X86_CMOV; + + if (edx & (1<<23)) + *flags |= PA_CPU_X86_MMX; + + if (edx & (1<<25)) + *flags |= PA_CPU_X86_SSE; + + if (edx & (1<<26)) + *flags |= PA_CPU_X86_SSE2; + + if (ecx & (1<<0)) + *flags |= PA_CPU_X86_SSE3; + + if (ecx & (1<<9)) + *flags |= PA_CPU_X86_SSSE3; + + if (ecx & (1<<19)) + *flags |= PA_CPU_X86_SSE4_1; + + if (ecx & (1<<20)) + *flags |= PA_CPU_X86_SSE4_2; + } + + /* get extended level */ + if (__get_cpuid(0x80000000, &level, &ebx, &ecx, &edx) == 0) + goto finish; + + if (level >= 0x80000001) { + if (__get_cpuid(0x80000001, &eax, &ebx, &ecx, &edx) == 0) + goto finish; + + if (edx & (1<<22)) + *flags |= PA_CPU_X86_MMXEXT; + + if (edx & (1<<23)) + *flags |= PA_CPU_X86_MMX; + + if (edx & (1<<30)) + *flags |= PA_CPU_X86_3DNOWEXT; + + if (edx & (1<<31)) + *flags |= PA_CPU_X86_3DNOW; + } + +finish: + pa_log_info("CPU flags: %s%s%s%s%s%s%s%s%s%s%s", + (*flags & PA_CPU_X86_CMOV) ? "CMOV " : "", + (*flags & PA_CPU_X86_MMX) ? "MMX " : "", + (*flags & PA_CPU_X86_SSE) ? "SSE " : "", + (*flags & PA_CPU_X86_SSE2) ? "SSE2 " : "", + (*flags & PA_CPU_X86_SSE3) ? "SSE3 " : "", + (*flags & PA_CPU_X86_SSSE3) ? "SSSE3 " : "", + (*flags & PA_CPU_X86_SSE4_1) ? "SSE4_1 " : "", + (*flags & PA_CPU_X86_SSE4_2) ? "SSE4_2 " : "", + (*flags & PA_CPU_X86_MMXEXT) ? "MMXEXT " : "", + (*flags & PA_CPU_X86_3DNOW) ? "3DNOW " : "", + (*flags & PA_CPU_X86_3DNOWEXT) ? "3DNOWEXT " : ""); +#endif /* (defined(__i386__) || defined(__amd64__)) && defined(HAVE_CPUID_H) */ +} + +bool pa_cpu_init_x86(pa_cpu_x86_flag_t *flags) { +#if defined (__i386__) || defined (__amd64__) + pa_cpu_get_x86_flags(flags); + + /* activate various optimisations */ + if (*flags & PA_CPU_X86_MMX) { + pa_volume_func_init_mmx(*flags); + pa_remap_func_init_mmx(*flags); + } + + if (*flags & (PA_CPU_X86_SSE | PA_CPU_X86_SSE2)) { + pa_volume_func_init_sse(*flags); + pa_remap_func_init_sse(*flags); + pa_convert_func_init_sse(*flags); + } + + return true; +#else /* defined (__i386__) || defined (__amd64__) */ + return false; +#endif /* defined (__i386__) || defined (__amd64__) */ +} diff --git a/src/pulsecore/cpu-x86.h b/src/pulsecore/cpu-x86.h new file mode 100644 index 0000000..eff2014 --- /dev/null +++ b/src/pulsecore/cpu-x86.h @@ -0,0 +1,59 @@ +#ifndef foocpux86hfoo +#define foocpux86hfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2009 Wim Taymans <wim.taymans@collabora.co.uk> + + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <stdint.h> +#include <pulsecore/macro.h> + +typedef enum pa_cpu_x86_flag { + PA_CPU_X86_MMX = (1 << 0), + PA_CPU_X86_MMXEXT = (1 << 1), + PA_CPU_X86_SSE = (1 << 2), + PA_CPU_X86_SSE2 = (1 << 3), + PA_CPU_X86_SSE3 = (1 << 4), + PA_CPU_X86_SSSE3 = (1 << 5), + PA_CPU_X86_SSE4_1 = (1 << 6), + PA_CPU_X86_SSE4_2 = (1 << 7), + PA_CPU_X86_3DNOW = (1 << 8), + PA_CPU_X86_3DNOWEXT = (1 << 9), + PA_CPU_X86_CMOV = (1 << 10) +} pa_cpu_x86_flag_t; + +void pa_cpu_get_x86_flags(pa_cpu_x86_flag_t *flags); +bool pa_cpu_init_x86 (pa_cpu_x86_flag_t *flags); + +#if defined (__i386__) +typedef int32_t pa_reg_x86; +#elif defined (__amd64__) +typedef int64_t pa_reg_x86; +#endif + +/* some optimized functions */ +void pa_volume_func_init_mmx(pa_cpu_x86_flag_t flags); +void pa_volume_func_init_sse(pa_cpu_x86_flag_t flags); + +void pa_remap_func_init_mmx(pa_cpu_x86_flag_t flags); +void pa_remap_func_init_sse(pa_cpu_x86_flag_t flags); + +void pa_convert_func_init_sse (pa_cpu_x86_flag_t flags); + +#endif /* foocpux86hfoo */ diff --git a/src/pulsecore/cpu.c b/src/pulsecore/cpu.c new file mode 100644 index 0000000..e0c110e --- /dev/null +++ b/src/pulsecore/cpu.c @@ -0,0 +1,38 @@ +/*** + This file is part of PulseAudio. + + Copyright 2014 Peter Meerwald <pmeerw@pmeerw.net> + + 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.1 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. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "cpu.h" +#include "cpu-orc.h" + +void pa_cpu_init(pa_cpu_info *cpu_info) { + cpu_info->cpu_type = PA_CPU_UNDEFINED; + /* don't force generic code, used for testing only */ + cpu_info->force_generic_code = false; + if (!getenv("PULSE_NO_SIMD")) { + if (pa_cpu_init_x86(&cpu_info->flags.x86)) + cpu_info->cpu_type = PA_CPU_X86; + else if (pa_cpu_init_arm(&cpu_info->flags.arm)) + cpu_info->cpu_type = PA_CPU_ARM; + pa_cpu_init_orc(*cpu_info); + } + + pa_remap_func_init(cpu_info); + pa_mix_func_init(cpu_info); +} diff --git a/src/pulsecore/cpu.h b/src/pulsecore/cpu.h new file mode 100644 index 0000000..e65c4fb --- /dev/null +++ b/src/pulsecore/cpu.h @@ -0,0 +1,49 @@ +#ifndef foocpuhfoo +#define foocpuhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2010 Arun Raghavan + + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulsecore/cpu-x86.h> +#include <pulsecore/cpu-arm.h> + +typedef enum { + PA_CPU_UNDEFINED = 0, + PA_CPU_X86, + PA_CPU_ARM, +} pa_cpu_type_t; + +typedef struct pa_cpu_info pa_cpu_info; + +struct pa_cpu_info { + pa_cpu_type_t cpu_type; + + union { + pa_cpu_x86_flag_t x86; + pa_cpu_arm_flag_t arm; + } flags; + bool force_generic_code; +}; + +void pa_cpu_init(pa_cpu_info *cpu_info); + +void pa_remap_func_init(const pa_cpu_info *cpu_info); +void pa_mix_func_init(const pa_cpu_info *cpu_info); + +#endif /* foocpuhfoo */ diff --git a/src/pulsecore/creds.h b/src/pulsecore/creds.h new file mode 100644 index 0000000..9fdbb4f --- /dev/null +++ b/src/pulsecore/creds.h @@ -0,0 +1,64 @@ +#ifndef foocredshfoo +#define foocredshfoo + +/*** + 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/types.h> + +#ifndef PACKAGE +#error "Please include config.h before including this file!" +#endif + +#include <pulsecore/socket.h> +#include <stdbool.h> + +#define MAX_ANCIL_DATA_FDS 2 + +typedef struct pa_creds pa_creds; +typedef struct pa_cmsg_ancil_data pa_cmsg_ancil_data; + +#if defined(SCM_CREDENTIALS) + +#define HAVE_CREDS 1 + +struct pa_creds { + gid_t gid; + uid_t uid; +}; + +/* Struct for handling ancillary data, i e, extra data that can be sent together with a message + * over unix pipes. Supports sending and receiving credentials and file descriptors. */ +struct pa_cmsg_ancil_data { + pa_creds creds; + bool creds_valid; + int nfd; + + /* Don't close these fds by your own. Check pa_cmsg_ancil_data_close_fds() */ + int fds[MAX_ANCIL_DATA_FDS]; + bool close_fds_on_cleanup; +}; + +void pa_cmsg_ancil_data_close_fds(struct pa_cmsg_ancil_data *ancil); + +#else +#undef HAVE_CREDS +#endif + +#endif diff --git a/src/pulsecore/database-gdbm.c b/src/pulsecore/database-gdbm.c new file mode 100644 index 0000000..b39f7de --- /dev/null +++ b/src/pulsecore/database-gdbm.c @@ -0,0 +1,244 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <gdbm.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> + +#include "database.h" + +#define MAKE_GDBM_FILE(x) ((GDBM_FILE) (x)) + +static inline datum* datum_to_gdbm(datum *to, const pa_datum *from) { + pa_assert(from); + pa_assert(to); + + to->dptr = from->data; + to->dsize = from->size; + + return to; +} + +static inline pa_datum* datum_from_gdbm(pa_datum *to, const datum *from) { + pa_assert(from); + pa_assert(to); + + to->data = from->dptr; + to->size = from->dsize; + + return to; +} + +void pa_datum_free(pa_datum *d) { + pa_assert(d); + + free(d->data); /* gdbm uses raw malloc/free hence we should do that here, too */ + pa_zero(d); +} + +const char* pa_database_get_filename_suffix(void) { + return ".gdbm"; +} + +pa_database* pa_database_open_internal(const char *path, bool for_write) { + GDBM_FILE f; + int gdbm_cache_size; + + pa_assert(path); + + errno = 0; + + /* We need to set the block size explicitly here, since otherwise + * gdbm takes the native block size of the underlying file system + * which might be incredibly large. */ + f = gdbm_open((char*) path, 1024, GDBM_NOLOCK | (for_write ? GDBM_WRCREAT : GDBM_READER), 0644, NULL); + + if (f) + pa_log_debug("Opened GDBM database '%s'", path); + + if (!f) { + if (errno == 0) + errno = EIO; + return NULL; + } + + /* By default the cache of gdbm is rather large, let's reduce it a bit to save memory */ + gdbm_cache_size = 10; + gdbm_setopt(f, GDBM_CACHESIZE, &gdbm_cache_size, sizeof(gdbm_cache_size)); + + return (pa_database*) f; +} + +void pa_database_close(pa_database *db) { + pa_assert(db); + + gdbm_close(MAKE_GDBM_FILE(db)); +} + +pa_datum* pa_database_get(pa_database *db, const pa_datum *key, pa_datum* data) { + datum gdbm_key, gdbm_data; + + pa_assert(db); + pa_assert(key); + pa_assert(data); + + gdbm_data = gdbm_fetch(MAKE_GDBM_FILE(db), *datum_to_gdbm(&gdbm_key, key)); + + return gdbm_data.dptr ? + datum_from_gdbm(data, &gdbm_data) : + NULL; +} + +int pa_database_set(pa_database *db, const pa_datum *key, const pa_datum* data, bool overwrite) { + datum gdbm_key, gdbm_data; + + pa_assert(db); + pa_assert(key); + pa_assert(data); + + return gdbm_store(MAKE_GDBM_FILE(db), + *datum_to_gdbm(&gdbm_key, key), + *datum_to_gdbm(&gdbm_data, data), + overwrite ? GDBM_REPLACE : GDBM_INSERT) != 0 ? -1 : 0; +} + +int pa_database_unset(pa_database *db, const pa_datum *key) { + datum gdbm_key; + + pa_assert(db); + pa_assert(key); + + return gdbm_delete(MAKE_GDBM_FILE(db), *datum_to_gdbm(&gdbm_key, key)) != 0 ? -1 : 0; +} + +int pa_database_clear(pa_database *db) { + datum gdbm_key; + + pa_assert(db); + + gdbm_key = gdbm_firstkey(MAKE_GDBM_FILE(db)); + + while (gdbm_key.dptr) { + datum next; + + next = gdbm_nextkey(MAKE_GDBM_FILE(db), gdbm_key); + + gdbm_delete(MAKE_GDBM_FILE(db), gdbm_key); + + free(gdbm_key.dptr); + gdbm_key = next; + } + + return gdbm_reorganize(MAKE_GDBM_FILE(db)) == 0 ? 0 : -1; +} + +signed pa_database_size(pa_database *db) { + datum gdbm_key; + unsigned n = 0; + + pa_assert(db); + + /* This sucks */ + + gdbm_key = gdbm_firstkey(MAKE_GDBM_FILE(db)); + + while (gdbm_key.dptr) { + datum next; + + n++; + + next = gdbm_nextkey(MAKE_GDBM_FILE(db), gdbm_key); + free(gdbm_key.dptr); + gdbm_key = next; + } + + return (signed) n; +} + +pa_datum* pa_database_first(pa_database *db, pa_datum *key, pa_datum *data) { + datum gdbm_key, gdbm_data; + + pa_assert(db); + pa_assert(key); + + gdbm_key = gdbm_firstkey(MAKE_GDBM_FILE(db)); + + if (!gdbm_key.dptr) + return NULL; + + if (data) { + gdbm_data = gdbm_fetch(MAKE_GDBM_FILE(db), gdbm_key); + + if (!gdbm_data.dptr) { + free(gdbm_key.dptr); + return NULL; + } + + datum_from_gdbm(data, &gdbm_data); + } + + datum_from_gdbm(key, &gdbm_key); + + return key; +} + +pa_datum* pa_database_next(pa_database *db, const pa_datum *key, pa_datum *next, pa_datum *data) { + datum gdbm_key, gdbm_data; + + pa_assert(db); + pa_assert(key); + pa_assert(next); + + if (!key) + return pa_database_first(db, next, data); + + gdbm_key = gdbm_nextkey(MAKE_GDBM_FILE(db), *datum_to_gdbm(&gdbm_key, key)); + + if (!gdbm_key.dptr) + return NULL; + + if (data) { + gdbm_data = gdbm_fetch(MAKE_GDBM_FILE(db), gdbm_key); + + if (!gdbm_data.dptr) { + free(gdbm_key.dptr); + return NULL; + } + + datum_from_gdbm(data, &gdbm_data); + } + + datum_from_gdbm(next, &gdbm_key); + + return next; +} + +int pa_database_sync(pa_database *db) { + pa_assert(db); + + gdbm_sync(MAKE_GDBM_FILE(db)); + return 0; +} diff --git a/src/pulsecore/database-simple.c b/src/pulsecore/database-simple.c new file mode 100644 index 0000000..96af8e0 --- /dev/null +++ b/src/pulsecore/database-simple.c @@ -0,0 +1,495 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Nokia Corporation + Contact: Maemo Multimedia <multimedia@maemo.org> + + 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <sys/types.h> +#include <unistd.h> +#include <stdio.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> +#include <pulsecore/core-error.h> +#include <pulsecore/hashmap.h> + +#include "database.h" + +typedef struct simple_data { + char *filename; + char *tmp_filename; + pa_hashmap *map; + bool read_only; +} simple_data; + +typedef struct entry { + pa_datum key; + pa_datum data; +} entry; + +void pa_datum_free(pa_datum *d) { + pa_assert(d); + + pa_xfree(d->data); + d->data = NULL; + d->size = 0; +} + +static int compare_func(const void *a, const void *b) { + const pa_datum *aa, *bb; + + aa = (const pa_datum*)a; + bb = (const pa_datum*)b; + + if (aa->size != bb->size) + return aa->size > bb->size ? 1 : -1; + + return memcmp(aa->data, bb->data, aa->size); +} + +/* pa_idxset_string_hash_func modified for our use */ +static unsigned hash_func(const void *p) { + const pa_datum *d; + unsigned hash = 0; + const char *c; + unsigned i; + + d = (const pa_datum*)p; + c = d->data; + + for (i = 0; i < d->size; i++) { + hash = 31 * hash + (unsigned) *c; + c++; + } + + return hash; +} + +static entry* new_entry(const pa_datum *key, const pa_datum *data) { + entry *e; + + pa_assert(key); + pa_assert(data); + + e = pa_xnew0(entry, 1); + e->key.data = key->size > 0 ? pa_xmemdup(key->data, key->size) : NULL; + e->key.size = key->size; + e->data.data = data->size > 0 ? pa_xmemdup(data->data, data->size) : NULL; + e->data.size = data->size; + return e; +} + +static void free_entry(entry *e) { + if (e) { + if (e->key.data) + pa_xfree(e->key.data); + if (e->data.data) + pa_xfree(e->data.data); + pa_xfree(e); + } +} + +static int read_uint(FILE *f, uint32_t *res) { + size_t items = 0; + uint8_t values[4]; + uint32_t tmp; + int i; + + items = fread(&values, sizeof(values), sizeof(uint8_t), f); + + if (feof(f)) /* EOF */ + return 0; + + if (ferror(f)) + return -1; + + for (i = 0; i < 4; ++i) { + tmp = values[i]; + *res += (tmp << (i*8)); + } + + return items; +} + +static int read_data(FILE *f, void **data, ssize_t *length) { + size_t items = 0; + uint32_t data_len = 0; + + pa_assert(f); + + *data = NULL; + *length = 0; + + if ((items = read_uint(f, &data_len)) <= 0) + return -1; + + if (data_len > 0) { + *data = pa_xmalloc0(data_len); + items = fread(*data, data_len, 1, f); + + if (feof(f)) /* EOF */ + goto reset; + + if (ferror(f)) + goto reset; + + *length = data_len; + + } else { /* no data? */ + return -1; + } + + return 0; + +reset: + pa_xfree(*data); + *data = NULL; + *length = 0; + return -1; +} + +static int fill_data(simple_data *db, FILE *f) { + pa_datum key; + pa_datum data; + void *d = NULL; + ssize_t l = 0; + bool append = false; + enum { FIELD_KEY = 0, FIELD_DATA } field = FIELD_KEY; + + pa_assert(db); + pa_assert(db->map); + + errno = 0; + + key.size = 0; + key.data = NULL; + + while (!read_data(f, &d, &l)) { + + switch (field) { + case FIELD_KEY: + key.data = d; + key.size = l; + field = FIELD_DATA; + break; + case FIELD_DATA: + data.data = d; + data.size = l; + append = true; + break; + } + + if (append) { + entry *e = pa_xnew0(entry, 1); + e->key.data = key.data; + e->key.size = key.size; + e->data.data = data.data; + e->data.size = data.size; + pa_hashmap_put(db->map, &e->key, e); + append = false; + field = FIELD_KEY; + } + } + + if (ferror(f)) { + pa_log_warn("read error. %s", pa_cstrerror(errno)); + pa_database_clear((pa_database*)db); + } + + if (field == FIELD_DATA && d) + pa_xfree(d); + + return pa_hashmap_size(db->map); +} + +const char* pa_database_get_filename_suffix(void) { + return ".simple"; +} + +pa_database* pa_database_open_internal(const char *path, bool for_write) { + FILE *f; + simple_data *db; + + pa_assert(path); + + errno = 0; + + f = pa_fopen_cloexec(path, "r"); + + if (f || errno == ENOENT) { /* file not found is ok */ + db = pa_xnew0(simple_data, 1); + db->map = pa_hashmap_new_full(hash_func, compare_func, NULL, (pa_free_cb_t) free_entry); + db->filename = pa_xstrdup(path); + db->tmp_filename = pa_sprintf_malloc(".%s.tmp", db->filename); + db->read_only = !for_write; + + if (f) { + fill_data(db, f); + fclose(f); + } + } else { + if (errno == 0) + errno = EIO; + db = NULL; + } + + return (pa_database*) db; +} + +void pa_database_close(pa_database *database) { + simple_data *db = (simple_data*)database; + pa_assert(db); + + pa_database_sync(database); + pa_xfree(db->filename); + pa_xfree(db->tmp_filename); + pa_hashmap_free(db->map); + pa_xfree(db); +} + +pa_datum* pa_database_get(pa_database *database, const pa_datum *key, pa_datum* data) { + simple_data *db = (simple_data*)database; + entry *e; + + pa_assert(db); + pa_assert(key); + pa_assert(data); + + e = pa_hashmap_get(db->map, key); + + if (!e) + return NULL; + + data->data = e->data.size > 0 ? pa_xmemdup(e->data.data, e->data.size) : NULL; + data->size = e->data.size; + + return data; +} + +int pa_database_set(pa_database *database, const pa_datum *key, const pa_datum* data, bool overwrite) { + simple_data *db = (simple_data*)database; + entry *e; + int ret = 0; + + pa_assert(db); + pa_assert(key); + pa_assert(data); + + if (db->read_only) + return -1; + + e = new_entry(key, data); + + if (pa_hashmap_put(db->map, &e->key, e) < 0) { + /* entry with same key exists in hashmap */ + entry *r; + if (overwrite) { + r = pa_hashmap_remove(db->map, key); + pa_hashmap_put(db->map, &e->key, e); + } else { + /* won't overwrite, so clean new entry */ + r = e; + ret = -1; + } + + free_entry(r); + } + + return ret; +} + +int pa_database_unset(pa_database *database, const pa_datum *key) { + simple_data *db = (simple_data*)database; + + pa_assert(db); + pa_assert(key); + + return pa_hashmap_remove_and_free(db->map, key); +} + +int pa_database_clear(pa_database *database) { + simple_data *db = (simple_data*)database; + + pa_assert(db); + + pa_hashmap_remove_all(db->map); + + return 0; +} + +signed pa_database_size(pa_database *database) { + simple_data *db = (simple_data*)database; + pa_assert(db); + + return (signed) pa_hashmap_size(db->map); +} + +pa_datum* pa_database_first(pa_database *database, pa_datum *key, pa_datum *data) { + simple_data *db = (simple_data*)database; + entry *e; + + pa_assert(db); + pa_assert(key); + + e = pa_hashmap_first(db->map); + + if (!e) + return NULL; + + key->data = e->key.size > 0 ? pa_xmemdup(e->key.data, e->key.size) : NULL; + key->size = e->key.size; + + if (data) { + data->data = e->data.size > 0 ? pa_xmemdup(e->data.data, e->data.size) : NULL; + data->size = e->data.size; + } + + return key; +} + +pa_datum* pa_database_next(pa_database *database, const pa_datum *key, pa_datum *next, pa_datum *data) { + simple_data *db = (simple_data*)database; + entry *e; + entry *search; + void *state; + bool pick_now; + + pa_assert(db); + pa_assert(next); + + if (!key) + return pa_database_first(database, next, data); + + search = pa_hashmap_get(db->map, key); + + state = NULL; + pick_now = false; + + while ((e = pa_hashmap_iterate(db->map, &state, NULL))) { + if (pick_now) + break; + + if (search == e) + pick_now = true; + } + + if (!pick_now || !e) + return NULL; + + next->data = e->key.size > 0 ? pa_xmemdup(e->key.data, e->key.size) : NULL; + next->size = e->key.size; + + if (data) { + data->data = e->data.size > 0 ? pa_xmemdup(e->data.data, e->data.size) : NULL; + data->size = e->data.size; + } + + return next; +} + +static int write_uint(FILE *f, const uint32_t num) { + size_t items; + uint8_t values[4]; + int i; + errno = 0; + + for (i = 0; i < 4; i++) + values[i] = (num >> (i*8)) & 0xFF; + + items = fwrite(&values, sizeof(values), sizeof(uint8_t), f); + + if (ferror(f)) + return -1; + + return items; +} + +static int write_data(FILE *f, void *data, const size_t length) { + size_t items; + uint32_t len; + + len = length; + if ((items = write_uint(f, len)) <= 0) + return -1; + + items = fwrite(data, length, 1, f); + + if (ferror(f) || items != 1) + return -1; + + return 0; +} + +static int write_entry(FILE *f, const entry *e) { + pa_assert(f); + pa_assert(e); + + if (write_data(f, e->key.data, e->key.size) < 0) + return -1; + if (write_data(f, e->data.data, e->data.size) < 0) + return -1; + + return 0; +} + +int pa_database_sync(pa_database *database) { + simple_data *db = (simple_data*)database; + FILE *f; + void *state; + entry *e; + + pa_assert(db); + + if (db->read_only) + return 0; + + errno = 0; + + f = pa_fopen_cloexec(db->tmp_filename, "w"); + + if (!f) + goto fail; + + state = NULL; + while((e = pa_hashmap_iterate(db->map, &state, NULL))) { + if (write_entry(f, e) < 0) { + pa_log_warn("error while writing to file. %s", pa_cstrerror(errno)); + goto fail; + } + } + + fclose(f); + f = NULL; + + if (rename(db->tmp_filename, db->filename) < 0) { + pa_log_warn("error while renaming file. %s", pa_cstrerror(errno)); + goto fail; + } + + return 0; + +fail: + if (f) + fclose(f); + return -1; +} diff --git a/src/pulsecore/database-tdb.c b/src/pulsecore/database-tdb.c new file mode 100644 index 0000000..6605ed8 --- /dev/null +++ b/src/pulsecore/database-tdb.c @@ -0,0 +1,250 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> + +/* Some versions of tdb lack inclusion of signal.h in the header files but use sigatomic_t */ +#include <signal.h> +#include <tdb.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> + +#include "database.h" + +#define MAKE_TDB_CONTEXT(x) ((struct tdb_context*) (x)) + +static inline TDB_DATA* datum_to_tdb(TDB_DATA *to, const pa_datum *from) { + pa_assert(from); + pa_assert(to); + + to->dptr = from->data; + to->dsize = from->size; + + return to; +} + +static inline pa_datum* datum_from_tdb(pa_datum *to, const TDB_DATA *from) { + pa_assert(from); + pa_assert(to); + + to->data = from->dptr; + to->size = from->dsize; + + return to; +} + +void pa_datum_free(pa_datum *d) { + pa_assert(d); + + free(d->data); /* tdb uses raw malloc/free hence we should do that here, too */ + pa_zero(d); +} + +static struct tdb_context *tdb_open_cloexec( + const char *name, + int hash_size, + int tdb_flags, + int open_flags, + mode_t mode) { + + /* Mimics pa_open_cloexec() */ + + struct tdb_context *c; + +#ifdef O_NOCTTY + open_flags |= O_NOCTTY; +#endif + +#ifdef O_CLOEXEC + errno = 0; + if ((c = tdb_open(name, hash_size, tdb_flags, open_flags | O_CLOEXEC, mode))) + goto finish; + + if (errno != EINVAL) + return NULL; +#endif + + errno = 0; + if (!(c = tdb_open(name, hash_size, tdb_flags, open_flags, mode))) + return NULL; + +finish: + pa_make_fd_cloexec(tdb_fd(c)); + return c; +} + +const char* pa_database_get_filename_suffix(void) { + return ".tdb"; +} + +pa_database* pa_database_open_internal(const char *path, bool for_write) { + struct tdb_context *c; + + pa_assert(path); + + if ((c = tdb_open_cloexec(path, 0, TDB_NOSYNC|TDB_NOLOCK, (for_write ? O_RDWR|O_CREAT : O_RDONLY), 0644))) + pa_log_debug("Opened TDB database '%s'", path); + + if (!c) { + if (errno == 0) + errno = EIO; + return NULL; + } + + return (pa_database*) c; +} + +void pa_database_close(pa_database *db) { + pa_assert(db); + + tdb_close(MAKE_TDB_CONTEXT(db)); +} + +pa_datum* pa_database_get(pa_database *db, const pa_datum *key, pa_datum* data) { + TDB_DATA tdb_key, tdb_data; + + pa_assert(db); + pa_assert(key); + pa_assert(data); + + tdb_data = tdb_fetch(MAKE_TDB_CONTEXT(db), *datum_to_tdb(&tdb_key, key)); + + return tdb_data.dptr ? + datum_from_tdb(data, &tdb_data) : + NULL; +} + +int pa_database_set(pa_database *db, const pa_datum *key, const pa_datum* data, bool overwrite) { + TDB_DATA tdb_key, tdb_data; + + pa_assert(db); + pa_assert(key); + pa_assert(data); + + return tdb_store(MAKE_TDB_CONTEXT(db), + *datum_to_tdb(&tdb_key, key), + *datum_to_tdb(&tdb_data, data), + overwrite ? TDB_REPLACE : TDB_INSERT) != 0 ? -1 : 0; +} + +int pa_database_unset(pa_database *db, const pa_datum *key) { + TDB_DATA tdb_key; + + pa_assert(db); + pa_assert(key); + + return tdb_delete(MAKE_TDB_CONTEXT(db), *datum_to_tdb(&tdb_key, key)) != 0 ? -1 : 0; +} + +int pa_database_clear(pa_database *db) { + pa_assert(db); + + return tdb_wipe_all(MAKE_TDB_CONTEXT(db)) != 0 ? -1 : 0; +} + +signed pa_database_size(pa_database *db) { + TDB_DATA tdb_key; + unsigned n = 0; + + pa_assert(db); + + /* This sucks */ + + tdb_key = tdb_firstkey(MAKE_TDB_CONTEXT(db)); + + while (tdb_key.dptr) { + TDB_DATA next; + + n++; + + next = tdb_nextkey(MAKE_TDB_CONTEXT(db), tdb_key); + free(tdb_key.dptr); + tdb_key = next; + } + + return (signed) n; +} + +pa_datum* pa_database_first(pa_database *db, pa_datum *key, pa_datum *data) { + TDB_DATA tdb_key, tdb_data; + + pa_assert(db); + pa_assert(key); + + tdb_key = tdb_firstkey(MAKE_TDB_CONTEXT(db)); + + if (!tdb_key.dptr) + return NULL; + + if (data) { + tdb_data = tdb_fetch(MAKE_TDB_CONTEXT(db), tdb_key); + + if (!tdb_data.dptr) { + free(tdb_key.dptr); + return NULL; + } + + datum_from_tdb(data, &tdb_data); + } + + datum_from_tdb(key, &tdb_key); + + return key; +} + +pa_datum* pa_database_next(pa_database *db, const pa_datum *key, pa_datum *next, pa_datum *data) { + TDB_DATA tdb_key, tdb_data; + + pa_assert(db); + pa_assert(key); + + tdb_key = tdb_nextkey(MAKE_TDB_CONTEXT(db), *datum_to_tdb(&tdb_key, key)); + + if (!tdb_key.dptr) + return NULL; + + if (data) { + tdb_data = tdb_fetch(MAKE_TDB_CONTEXT(db), tdb_key); + + if (!tdb_data.dptr) { + free(tdb_key.dptr); + return NULL; + } + + datum_from_tdb(data, &tdb_data); + } + + datum_from_tdb(next, &tdb_key); + + return next; +} + +int pa_database_sync(pa_database *db) { + pa_assert(db); + + return 0; +} diff --git a/src/pulsecore/database.c b/src/pulsecore/database.c new file mode 100644 index 0000000..8b3b89c --- /dev/null +++ b/src/pulsecore/database.c @@ -0,0 +1,107 @@ +/*** + This file is part of PulseAudio. + + Copyright 2020 Igor V. Kovalenko <igor.v.kovalenko@gmail.com> + + 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <dirent.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> + +#include "database.h" +#include "core-error.h" + +pa_database* pa_database_open(const char *path, const char *fn, bool prependmid, bool for_write) { + + const char *filename_suffix = pa_database_get_filename_suffix(); + + char *machine_id = NULL, *filename_prefix, *full_path; + + DIR *database_dir = NULL; + struct dirent *de; + + pa_database *f; + + pa_assert(filename_suffix && filename_suffix[0]); + + if (prependmid && !(machine_id = pa_machine_id())) { + return NULL; + } + + /* Database file name starts with ${machine_id}-${fn} */ + if (machine_id) + filename_prefix = pa_sprintf_malloc("%s-%s", machine_id, fn); + else + filename_prefix = pa_xstrdup(fn); + + /* Search for existing database directory entry name matching architecture suffix and filename suffix. */ + database_dir = opendir(path); + + if (database_dir) { + for (;;) { + errno = 0; + de = readdir(database_dir); + if (!de) { + if (errno) { + pa_log_warn("Unable to search for existing database file, readdir() failed: %s", pa_cstrerror(errno)); + /* can continue as if there is no matching database file candidate */ + } + + break; + } + + if (pa_startswith(de->d_name, filename_prefix) + && de->d_name[strlen(filename_prefix)] == '.' + && pa_endswith(de->d_name + strlen(filename_prefix) + 1, filename_suffix)) { + + /* candidate filename found, replace filename_prefix with this one */ + + pa_log_debug("Found existing database file '%s/%s', using it", path, de->d_name); + pa_xfree(filename_prefix); + filename_prefix = pa_xstrndup(de->d_name, strlen(de->d_name) - strlen(filename_suffix)); + break; + } + } + + closedir(database_dir); + } else { + pa_log_warn("Unable to search for existing database file, failed to open directory %s: %s", path, pa_cstrerror(errno)); + } + + full_path = pa_sprintf_malloc("%s" PA_PATH_SEP "%s%s", path, filename_prefix, filename_suffix); + + f = pa_database_open_internal(full_path, for_write); + + if (f) + pa_log_info("Successfully opened '%s' database file '%s'.", fn, full_path); + else + pa_log("Failed to open '%s' database file '%s': %s", fn, full_path, pa_cstrerror(errno)); + + pa_xfree(full_path); + pa_xfree(filename_prefix); + + /* deallocate machine_id if it was used to construct file name */ + pa_xfree(machine_id); + + return f; +} diff --git a/src/pulsecore/database.h b/src/pulsecore/database.h new file mode 100644 index 0000000..7fa489c --- /dev/null +++ b/src/pulsecore/database.h @@ -0,0 +1,80 @@ +#ifndef foopulsecoredatabasehfoo +#define foopulsecoredatabasehfoo + +/*** + This file is part of PulseAudio. + + Copyright 2009 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/types.h> + +#include <pulsecore/macro.h> + +/* A little abstraction over simple databases, such as gdbm, tdb, and + * so on. We only make minimal assumptions about the supported + * backend: it does not need to support locking, it does not have to + * be arch independent. */ + +typedef struct pa_database pa_database; + +typedef struct pa_datum { + void *data; + size_t size; +} pa_datum; + +void pa_datum_free(pa_datum *d); + +/* Database implementation; returns non-empty database filename extension string */ +const char* pa_database_get_filename_suffix(void); + +/* Opens a database file. The file is loaded from the directory indicated by + * path. The file name is constructed by using fn as the base and then adding + * several parts: + * 1) If prependmid is true, the machine id is prepended to the file name. + * 2) The database implementation specific suffix is added. + * 3) Older versions of PulseAudio in some cases added the CPU architecture + * to the file name, which was later deemed unnecessary, but for + * compatibility reasons we still need to look for those files, so we scan + * the directory for files that match the prefix (possible machine id plus + * fn) and the suffix, and if any matches are found, we use the first one. + * + * When no existing file is found, we create a new file for the database + * (without the CPU architecture part in the name). + * + * For a read-only database, set for_write to false. */ + +pa_database* pa_database_open(const char *path, const char *fn, bool prependmid, bool for_write); + +/* Database implementation; opens specified database file using provided path. */ +pa_database* pa_database_open_internal(const char *path, bool for_write); +void pa_database_close(pa_database *db); + +pa_datum* pa_database_get(pa_database *db, const pa_datum *key, pa_datum* data); + +int pa_database_set(pa_database *db, const pa_datum *key, const pa_datum* data, bool overwrite); +int pa_database_unset(pa_database *db, const pa_datum *key); + +int pa_database_clear(pa_database *db); + +signed pa_database_size(pa_database *db); + +pa_datum* pa_database_first(pa_database *db, pa_datum *key, pa_datum *data /* may be NULL */); +pa_datum* pa_database_next(pa_database *db, const pa_datum *key, pa_datum *next, pa_datum *data /* may be NULL */); + +int pa_database_sync(pa_database *db); + +#endif diff --git a/src/pulsecore/dbus-shared.c b/src/pulsecore/dbus-shared.c new file mode 100644 index 0000000..3422c29 --- /dev/null +++ b/src/pulsecore/dbus-shared.c @@ -0,0 +1,102 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006, 2009 Lennart Poettering + Copyright 2006 Shams E. King + + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/xmalloc.h> + +#include <pulsecore/shared.h> + +#include "dbus-shared.h" + +struct pa_dbus_connection { + PA_REFCNT_DECLARE; + + pa_dbus_wrap_connection *connection; + pa_core *core; + const char *property_name; +}; + +static pa_dbus_connection* dbus_connection_new(pa_core *c, pa_dbus_wrap_connection *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; + + pa_assert_se(pa_shared_set(c, name, pconn) >= 0); + + return pconn; +} + +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" + }; + pa_dbus_wrap_connection *conn; + pa_dbus_connection *pconn; + + pa_assert(type == DBUS_BUS_SYSTEM || type == DBUS_BUS_SESSION || type == DBUS_BUS_STARTER); + + if ((pconn = pa_shared_get(c, prop_name[type]))) + return pa_dbus_connection_ref(pconn); + + if (!(conn = pa_dbus_wrap_connection_new(c->mainloop, true, type, error))) + return NULL; + + return dbus_connection_new(c, conn, prop_name[type]); +} + +DBusConnection* pa_dbus_connection_get(pa_dbus_connection *c) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) > 0); + pa_assert(c->connection); + + return pa_dbus_wrap_connection_get(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; + + pa_dbus_wrap_connection_free(c->connection); + + pa_assert_se(pa_shared_remove(c->core, c->property_name) >= 0); + pa_xfree(c); +} + +pa_dbus_connection* pa_dbus_connection_ref(pa_dbus_connection *c) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) > 0); + + PA_REFCNT_INC(c); + + return c; +} diff --git a/src/pulsecore/dbus-shared.h b/src/pulsecore/dbus-shared.h new file mode 100644 index 0000000..3d552e9 --- /dev/null +++ b/src/pulsecore/dbus-shared.h @@ -0,0 +1,40 @@ +#ifndef foodbussharedhfoo +#define foodbussharedhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006, 2009 Lennart Poettering + Copyright 2006 Shams E. King + + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <dbus/dbus.h> + +#include <pulsecore/core.h> +#include <pulsecore/dbus-util.h> + +typedef struct pa_dbus_connection pa_dbus_connection; + +/* return a pa_dbus_connection of the specified type for the given core, + * like dbus_bus_get(), but integrates the connection with the pa_core */ +pa_dbus_connection* pa_dbus_bus_get(pa_core *c, DBusBusType type, DBusError *error); + +DBusConnection* pa_dbus_connection_get(pa_dbus_connection *conn); + +pa_dbus_connection* pa_dbus_connection_ref(pa_dbus_connection *conn); +void pa_dbus_connection_unref(pa_dbus_connection *conn); + +#endif diff --git a/src/pulsecore/dbus-util.c b/src/pulsecore/dbus-util.c new file mode 100644 index 0000000..7d55020 --- /dev/null +++ b/src/pulsecore/dbus-util.c @@ -0,0 +1,778 @@ +/*** + This file is part of PulseAudio. + + Copyright 2006 Lennart Poettering + Copyright 2006 Shams E. King + + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdarg.h> + +#include <pulse/rtclock.h> +#include <pulse/timeval.h> +#include <pulse/utf8.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/core-rtclock.h> +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> + +#include "dbus-util.h" + +struct pa_dbus_wrap_connection { + pa_mainloop_api *mainloop; + DBusConnection *connection; + pa_defer_event* dispatch_event; + bool use_rtclock:1; +}; + +struct timeout_data { + pa_dbus_wrap_connection *connection; + DBusTimeout *timeout; +}; + +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); +} + +/* DBusDispatchStatusFunction callback for the pa mainloop */ +static void dispatch_status(DBusConnection *conn, DBusDispatchStatus status, void *userdata) { + pa_dbus_wrap_connection *c = userdata; + + pa_assert(c); + + switch(status) { + + case DBUS_DISPATCH_COMPLETE: + c->mainloop->defer_enable(c->dispatch_event, 0); + break; + + case DBUS_DISPATCH_DATA_REMAINS: + case DBUS_DISPATCH_NEED_MEMORY: + default: + c->mainloop->defer_enable(c->dispatch_event, 1); + break; + } +} + +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)) + return PA_IO_EVENT_NULL; + + if (flags & DBUS_WATCH_READABLE) + events |= PA_IO_EVENT_INPUT; + if (flags & DBUS_WATCH_WRITABLE) + events |= PA_IO_EVENT_OUTPUT; + + return events | PA_IO_EVENT_HANGUP | PA_IO_EVENT_ERROR; +} + +/* pa_io_event_cb_t IO event handler */ +static void handle_io_event(pa_mainloop_api *ea, pa_io_event *e, int fd, pa_io_event_flags_t events, void *userdata) { + unsigned int flags = 0; + DBusWatch *watch = userdata; + + pa_assert(fd == dbus_watch_get_unix_fd(watch)); + + if (!dbus_watch_get_enabled(watch)) { + pa_log_warn("Asked to handle disabled watch: %p %i", (void*) watch, fd); + return; + } + + if (events & PA_IO_EVENT_INPUT) + flags |= DBUS_WATCH_READABLE; + if (events & PA_IO_EVENT_OUTPUT) + flags |= DBUS_WATCH_WRITABLE; + if (events & PA_IO_EVENT_HANGUP) + flags |= DBUS_WATCH_HANGUP; + if (events & PA_IO_EVENT_ERROR) + flags |= DBUS_WATCH_ERROR; + + dbus_watch_handle(watch, flags); +} + +/* pa_time_event_cb_t timer event handler */ +static void handle_time_event(pa_mainloop_api *ea, pa_time_event* e, const struct timeval *t, void *userdata) { + struct timeval tv; + struct timeout_data *d = userdata; + + pa_assert(d); + pa_assert(d->connection); + + if (dbus_timeout_get_enabled(d->timeout)) { + /* Restart it for the next scheduled time. We do this before + * calling dbus_timeout_handle() to make sure that the time + * event is still around. */ + ea->time_restart(e, pa_timeval_rtstore(&tv, + pa_timeval_load(t) + dbus_timeout_get_interval(d->timeout) * PA_USEC_PER_MSEC, + d->connection->use_rtclock)); + + dbus_timeout_handle(d->timeout); + } +} + +/* DBusAddWatchFunction callback for pa mainloop */ +static dbus_bool_t add_watch(DBusWatch *watch, void *data) { + pa_dbus_wrap_connection *c = data; + pa_io_event *ev; + + pa_assert(watch); + pa_assert(c); + + ev = c->mainloop->io_new( + c->mainloop, + dbus_watch_get_unix_fd(watch), + 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_dbus_wrap_connection *c = data; + pa_io_event *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_dbus_wrap_connection *c = data; + pa_io_event *ev; + + pa_assert(watch); + pa_assert(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)); +} + +static void time_event_destroy_cb(pa_mainloop_api *a, pa_time_event *e, void *userdata) { + pa_xfree(userdata); +} + +/* DBusAddTimeoutFunction callback for pa mainloop */ +static dbus_bool_t add_timeout(DBusTimeout *timeout, void *data) { + pa_dbus_wrap_connection *c = data; + pa_time_event *ev; + struct timeval tv; + struct timeout_data *d; + + pa_assert(timeout); + pa_assert(c); + + if (!dbus_timeout_get_enabled(timeout)) + return FALSE; + + d = pa_xnew(struct timeout_data, 1); + d->connection = c; + d->timeout = timeout; + ev = c->mainloop->time_new(c->mainloop, pa_timeval_rtstore(&tv, pa_rtclock_now() + dbus_timeout_get_interval(timeout) * PA_USEC_PER_MSEC, c->use_rtclock), handle_time_event, d); + c->mainloop->time_set_destroy(ev, time_event_destroy_cb); + + dbus_timeout_set_data(timeout, ev, NULL); + + return TRUE; +} + +/* DBusRemoveTimeoutFunction callback for pa mainloop */ +static void remove_timeout(DBusTimeout *timeout, void *data) { + pa_dbus_wrap_connection *c = data; + pa_time_event *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 timeout_data *d = data; + pa_time_event *ev; + struct timeval tv; + + pa_assert(d); + pa_assert(d->connection); + pa_assert(timeout); + + pa_assert_se(ev = dbus_timeout_get_data(timeout)); + + if (dbus_timeout_get_enabled(timeout)) + d->connection->mainloop->time_restart(ev, pa_timeval_rtstore(&tv, pa_rtclock_now() + dbus_timeout_get_interval(timeout) * PA_USEC_PER_MSEC, d->connection->use_rtclock)); + else + d->connection->mainloop->time_restart(ev, pa_timeval_rtstore(&tv, PA_USEC_INVALID, d->connection->use_rtclock)); +} + +static void wakeup_main(void *userdata) { + pa_dbus_wrap_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->mainloop->defer_enable(c->dispatch_event, 1); +} + +pa_dbus_wrap_connection* pa_dbus_wrap_connection_new(pa_mainloop_api *m, bool use_rtclock, DBusBusType type, DBusError *error) { + DBusConnection *conn; + pa_dbus_wrap_connection *pconn; + char *id; + + pa_assert(type == DBUS_BUS_SYSTEM || type == DBUS_BUS_SESSION || type == DBUS_BUS_STARTER); + + if (!(conn = dbus_bus_get_private(type, error))) + return NULL; + + pconn = pa_xnew(pa_dbus_wrap_connection, 1); + pconn->mainloop = m; + pconn->connection = conn; + pconn->use_rtclock = use_rtclock; + + dbus_connection_set_exit_on_disconnect(conn, FALSE); + dbus_connection_set_dispatch_status_function(conn, dispatch_status, pconn, NULL); + dbus_connection_set_watch_functions(conn, add_watch, remove_watch, toggle_watch, pconn, NULL); + dbus_connection_set_timeout_functions(conn, add_timeout, remove_timeout, toggle_timeout, pconn, NULL); + dbus_connection_set_wakeup_main_function(conn, wakeup_main, pconn, NULL); + + pconn->dispatch_event = pconn->mainloop->defer_new(pconn->mainloop, dispatch_cb, conn); + + pa_log_debug("Successfully connected to D-Bus %s bus %s as %s", + type == DBUS_BUS_SYSTEM ? "system" : (type == DBUS_BUS_SESSION ? "session" : "starter"), + pa_strnull((id = dbus_connection_get_server_id(conn))), + pa_strnull(dbus_bus_get_unique_name(conn))); + + dbus_free(id); + + return pconn; +} + +pa_dbus_wrap_connection* pa_dbus_wrap_connection_new_from_existing( + pa_mainloop_api *m, + bool use_rtclock, + DBusConnection *conn) { + pa_dbus_wrap_connection *pconn; + + pa_assert(m); + pa_assert(conn); + + pconn = pa_xnew(pa_dbus_wrap_connection, 1); + pconn->mainloop = m; + pconn->connection = dbus_connection_ref(conn); + pconn->use_rtclock = use_rtclock; + + dbus_connection_set_exit_on_disconnect(conn, FALSE); + dbus_connection_set_dispatch_status_function(conn, dispatch_status, pconn, NULL); + dbus_connection_set_watch_functions(conn, add_watch, remove_watch, toggle_watch, pconn, NULL); + dbus_connection_set_timeout_functions(conn, add_timeout, remove_timeout, toggle_timeout, pconn, NULL); + dbus_connection_set_wakeup_main_function(conn, wakeup_main, pconn, NULL); + + pconn->dispatch_event = pconn->mainloop->defer_new(pconn->mainloop, dispatch_cb, conn); + + return pconn; +} + +void pa_dbus_wrap_connection_free(pa_dbus_wrap_connection* c) { + pa_assert(c); + + 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)) + ; + } + + c->mainloop->defer_free(c->dispatch_event); + dbus_connection_unref(c->connection); + pa_xfree(c); +} + +DBusConnection* pa_dbus_wrap_connection_get(pa_dbus_wrap_connection *c) { + pa_assert(c); + pa_assert(c->connection); + + return c->connection; +} + +int pa_dbus_add_matches(DBusConnection *c, DBusError *error, ...) { + const char *t; + va_list ap; + unsigned k = 0; + + pa_assert(c); + pa_assert(error); + + va_start(ap, error); + while ((t = va_arg(ap, const char*))) { + dbus_bus_add_match(c, t, error); + + if (dbus_error_is_set(error)) + goto fail; + + k++; + } + va_end(ap); + return 0; + +fail: + + va_end(ap); + va_start(ap, error); + for (; k > 0; k--) { + pa_assert_se(t = va_arg(ap, const char*)); + dbus_bus_remove_match(c, t, NULL); + } + va_end(ap); + + return -1; +} + +void pa_dbus_remove_matches(DBusConnection *c, ...) { + const char *t; + va_list ap; + + pa_assert(c); + + va_start(ap, c); + while ((t = va_arg(ap, const char*))) + dbus_bus_remove_match(c, t, NULL); + va_end(ap); +} + +pa_dbus_pending *pa_dbus_pending_new( + DBusConnection *c, + DBusMessage *m, + DBusPendingCall *pending, + void *context_data, + void *call_data) { + + pa_dbus_pending *p; + + pa_assert(pending); + + p = pa_xnew(pa_dbus_pending, 1); + p->connection = c; + p->message = m; + p->pending = pending; + p->context_data = context_data; + p->call_data = call_data; + + PA_LLIST_INIT(pa_dbus_pending, p); + + return p; +} + +void pa_dbus_pending_free(pa_dbus_pending *p) { + pa_assert(p); + + if (p->pending) { + dbus_pending_call_cancel(p->pending); + dbus_pending_call_unref(p->pending); + } + + if (p->message) + dbus_message_unref(p->message); + + pa_xfree(p); +} + +void pa_dbus_sync_pending_list(pa_dbus_pending **p) { + pa_assert(p); + + while (*p && dbus_connection_read_write_dispatch((*p)->connection, -1)) + ; +} + +void pa_dbus_free_pending_list(pa_dbus_pending **p) { + pa_dbus_pending *i; + + pa_assert(p); + + while ((i = *p)) { + PA_LLIST_REMOVE(pa_dbus_pending, *p, i); + pa_dbus_pending_free(i); + } +} + +const char *pa_dbus_get_error_message(DBusMessage *m) { + const char *message; + + pa_assert(m); + pa_assert(dbus_message_get_type(m) == DBUS_MESSAGE_TYPE_ERROR); + + if (dbus_message_get_signature(m)[0] != 's') + return "<no explanation>"; + + pa_assert_se(dbus_message_get_args(m, NULL, DBUS_TYPE_STRING, &message, DBUS_TYPE_INVALID)); + + return message; +} + +void pa_dbus_send_error(DBusConnection *c, DBusMessage *in_reply_to, const char *name, const char *format, ...) { + va_list ap; + char *message; + DBusMessage *reply = NULL; + + pa_assert(c); + pa_assert(in_reply_to); + pa_assert(name); + pa_assert(format); + + va_start(ap, format); + message = pa_vsprintf_malloc(format, ap); + va_end(ap); + pa_assert_se((reply = dbus_message_new_error(in_reply_to, name, message))); + pa_assert_se(dbus_connection_send(c, reply, NULL)); + + dbus_message_unref(reply); + + pa_xfree(message); +} + +void pa_dbus_send_empty_reply(DBusConnection *c, DBusMessage *in_reply_to) { + DBusMessage *reply = NULL; + + pa_assert(c); + pa_assert(in_reply_to); + + pa_assert_se((reply = dbus_message_new_method_return(in_reply_to))); + pa_assert_se(dbus_connection_send(c, reply, NULL)); + dbus_message_unref(reply); +} + +void pa_dbus_send_basic_value_reply(DBusConnection *c, DBusMessage *in_reply_to, int type, void *data) { + DBusMessage *reply = NULL; + + pa_assert(c); + pa_assert(in_reply_to); + pa_assert(dbus_type_is_basic(type)); + pa_assert(data); + + pa_assert_se((reply = dbus_message_new_method_return(in_reply_to))); + pa_assert_se(dbus_message_append_args(reply, type, data, DBUS_TYPE_INVALID)); + pa_assert_se(dbus_connection_send(c, reply, NULL)); + dbus_message_unref(reply); +} + +static const char *signature_from_basic_type(int type) { + switch (type) { + case DBUS_TYPE_BOOLEAN: return DBUS_TYPE_BOOLEAN_AS_STRING; + case DBUS_TYPE_BYTE: return DBUS_TYPE_BYTE_AS_STRING; + case DBUS_TYPE_INT16: return DBUS_TYPE_INT16_AS_STRING; + case DBUS_TYPE_UINT16: return DBUS_TYPE_UINT16_AS_STRING; + case DBUS_TYPE_INT32: return DBUS_TYPE_INT32_AS_STRING; + case DBUS_TYPE_UINT32: return DBUS_TYPE_UINT32_AS_STRING; + case DBUS_TYPE_INT64: return DBUS_TYPE_INT64_AS_STRING; + case DBUS_TYPE_UINT64: return DBUS_TYPE_UINT64_AS_STRING; + case DBUS_TYPE_DOUBLE: return DBUS_TYPE_DOUBLE_AS_STRING; + case DBUS_TYPE_STRING: return DBUS_TYPE_STRING_AS_STRING; + case DBUS_TYPE_OBJECT_PATH: return DBUS_TYPE_OBJECT_PATH_AS_STRING; + case DBUS_TYPE_SIGNATURE: return DBUS_TYPE_SIGNATURE_AS_STRING; + default: pa_assert_not_reached(); + } +} + +void pa_dbus_send_basic_variant_reply(DBusConnection *c, DBusMessage *in_reply_to, int type, void *data) { + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + DBusMessageIter variant_iter; + + pa_assert(c); + pa_assert(in_reply_to); + pa_assert(dbus_type_is_basic(type)); + pa_assert(data); + + pa_assert_se((reply = dbus_message_new_method_return(in_reply_to))); + dbus_message_iter_init_append(reply, &msg_iter); + pa_assert_se(dbus_message_iter_open_container(&msg_iter, + DBUS_TYPE_VARIANT, + signature_from_basic_type(type), + &variant_iter)); + pa_assert_se(dbus_message_iter_append_basic(&variant_iter, type, data)); + pa_assert_se(dbus_message_iter_close_container(&msg_iter, &variant_iter)); + pa_assert_se(dbus_connection_send(c, reply, NULL)); + dbus_message_unref(reply); +} + +/* Note: returns sizeof(char*) for strings, object paths and signatures. */ +static unsigned basic_type_size(int type) { + switch (type) { + case DBUS_TYPE_BOOLEAN: return sizeof(dbus_bool_t); + case DBUS_TYPE_BYTE: return 1; + case DBUS_TYPE_INT16: return sizeof(dbus_int16_t); + case DBUS_TYPE_UINT16: return sizeof(dbus_uint16_t); + case DBUS_TYPE_INT32: return sizeof(dbus_int32_t); + case DBUS_TYPE_UINT32: return sizeof(dbus_uint32_t); + case DBUS_TYPE_INT64: return sizeof(dbus_int64_t); + case DBUS_TYPE_UINT64: return sizeof(dbus_uint64_t); + case DBUS_TYPE_DOUBLE: return sizeof(double); + case DBUS_TYPE_STRING: + case DBUS_TYPE_OBJECT_PATH: + case DBUS_TYPE_SIGNATURE: return sizeof(char*); + default: pa_assert_not_reached(); + } +} + +void pa_dbus_send_basic_array_variant_reply( + DBusConnection *c, + DBusMessage *in_reply_to, + int item_type, + void *array, + unsigned n) { + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + + pa_assert(c); + pa_assert(in_reply_to); + pa_assert(dbus_type_is_basic(item_type)); + pa_assert(array || n == 0); + + pa_assert_se((reply = dbus_message_new_method_return(in_reply_to))); + dbus_message_iter_init_append(reply, &msg_iter); + pa_dbus_append_basic_array_variant(&msg_iter, item_type, array, n); + pa_assert_se(dbus_connection_send(c, reply, NULL)); + dbus_message_unref(reply); +} + +void pa_dbus_send_proplist_variant_reply(DBusConnection *c, DBusMessage *in_reply_to, pa_proplist *proplist) { + DBusMessage *reply = NULL; + DBusMessageIter msg_iter; + + pa_assert(c); + pa_assert(in_reply_to); + pa_assert(proplist); + + pa_assert_se((reply = dbus_message_new_method_return(in_reply_to))); + dbus_message_iter_init_append(reply, &msg_iter); + pa_dbus_append_proplist_variant(&msg_iter, proplist); + pa_assert_se(dbus_connection_send(c, reply, NULL)); + dbus_message_unref(reply); +} + +void pa_dbus_append_basic_array(DBusMessageIter *iter, int item_type, const void *array, unsigned n) { + DBusMessageIter array_iter; + unsigned i; + unsigned item_size; + + pa_assert(iter); + pa_assert(dbus_type_is_basic(item_type)); + pa_assert(array || n == 0); + + item_size = basic_type_size(item_type); + + pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, signature_from_basic_type(item_type), &array_iter)); + + for (i = 0; i < n; ++i) + pa_assert_se(dbus_message_iter_append_basic(&array_iter, item_type, &((uint8_t*) array)[i * item_size])); + + pa_assert_se(dbus_message_iter_close_container(iter, &array_iter)); +} + +void pa_dbus_append_basic_variant(DBusMessageIter *iter, int type, void *data) { + DBusMessageIter variant_iter; + + pa_assert(iter); + pa_assert(dbus_type_is_basic(type)); + pa_assert(data); + + pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, signature_from_basic_type(type), &variant_iter)); + pa_assert_se(dbus_message_iter_append_basic(&variant_iter, type, data)); + pa_assert_se(dbus_message_iter_close_container(iter, &variant_iter)); +} + +void pa_dbus_append_basic_array_variant(DBusMessageIter *iter, int item_type, const void *array, unsigned n) { + DBusMessageIter variant_iter; + char *array_signature; + + pa_assert(iter); + pa_assert(dbus_type_is_basic(item_type)); + pa_assert(array || n == 0); + + array_signature = pa_sprintf_malloc("a%c", *signature_from_basic_type(item_type)); + + pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, array_signature, &variant_iter)); + pa_dbus_append_basic_array(&variant_iter, item_type, array, n); + pa_assert_se(dbus_message_iter_close_container(iter, &variant_iter)); + + pa_xfree(array_signature); +} + +void pa_dbus_append_basic_variant_dict_entry(DBusMessageIter *dict_iter, const char *key, int type, void *data) { + DBusMessageIter dict_entry_iter; + + pa_assert(dict_iter); + pa_assert(key); + pa_assert(dbus_type_is_basic(type)); + pa_assert(data); + + pa_assert_se(dbus_message_iter_open_container(dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_iter)); + pa_assert_se(dbus_message_iter_append_basic(&dict_entry_iter, DBUS_TYPE_STRING, &key)); + pa_dbus_append_basic_variant(&dict_entry_iter, type, data); + pa_assert_se(dbus_message_iter_close_container(dict_iter, &dict_entry_iter)); +} + +void pa_dbus_append_basic_array_variant_dict_entry( + DBusMessageIter *dict_iter, + const char *key, + int item_type, + const void *array, + unsigned n) { + DBusMessageIter dict_entry_iter; + + pa_assert(dict_iter); + pa_assert(key); + pa_assert(dbus_type_is_basic(item_type)); + pa_assert(array || n == 0); + + pa_assert_se(dbus_message_iter_open_container(dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_iter)); + pa_assert_se(dbus_message_iter_append_basic(&dict_entry_iter, DBUS_TYPE_STRING, &key)); + pa_dbus_append_basic_array_variant(&dict_entry_iter, item_type, array, n); + pa_assert_se(dbus_message_iter_close_container(dict_iter, &dict_entry_iter)); +} + +void pa_dbus_append_proplist(DBusMessageIter *iter, pa_proplist *proplist) { + DBusMessageIter dict_iter; + DBusMessageIter dict_entry_iter; + DBusMessageIter array_iter; + void *state = NULL; + const char *key; + + pa_assert(iter); + pa_assert(proplist); + + pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "{say}", &dict_iter)); + + while ((key = pa_proplist_iterate(proplist, &state))) { + const void *value = NULL; + size_t nbytes; + + pa_assert_se(pa_proplist_get(proplist, key, &value, &nbytes) >= 0); + + pa_assert_se(dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_iter)); + + pa_assert_se(dbus_message_iter_append_basic(&dict_entry_iter, DBUS_TYPE_STRING, &key)); + + pa_assert_se(dbus_message_iter_open_container(&dict_entry_iter, DBUS_TYPE_ARRAY, "y", &array_iter)); + pa_assert_se(dbus_message_iter_append_fixed_array(&array_iter, DBUS_TYPE_BYTE, &value, nbytes)); + pa_assert_se(dbus_message_iter_close_container(&dict_entry_iter, &array_iter)); + + pa_assert_se(dbus_message_iter_close_container(&dict_iter, &dict_entry_iter)); + } + + pa_assert_se(dbus_message_iter_close_container(iter, &dict_iter)); +} + +void pa_dbus_append_proplist_variant(DBusMessageIter *iter, pa_proplist *proplist) { + DBusMessageIter variant_iter; + + pa_assert(iter); + pa_assert(proplist); + + pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "a{say}", &variant_iter)); + pa_dbus_append_proplist(&variant_iter, proplist); + pa_assert_se(dbus_message_iter_close_container(iter, &variant_iter)); +} + +void pa_dbus_append_proplist_variant_dict_entry(DBusMessageIter *dict_iter, const char *key, pa_proplist *proplist) { + DBusMessageIter dict_entry_iter; + + pa_assert(dict_iter); + pa_assert(key); + pa_assert(proplist); + + pa_assert_se(dbus_message_iter_open_container(dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_iter)); + pa_assert_se(dbus_message_iter_append_basic(&dict_entry_iter, DBUS_TYPE_STRING, &key)); + pa_dbus_append_proplist_variant(&dict_entry_iter, proplist); + pa_assert_se(dbus_message_iter_close_container(dict_iter, &dict_entry_iter)); +} + +pa_proplist *pa_dbus_get_proplist_arg(DBusConnection *c, DBusMessage *msg, DBusMessageIter *iter) { + DBusMessageIter dict_iter; + DBusMessageIter dict_entry_iter; + char *signature; + pa_proplist *proplist = NULL; + const char *key = NULL; + const uint8_t *value = NULL; + int value_length = 0; + + pa_assert(c); + pa_assert(msg); + pa_assert(iter); + + pa_assert(signature = dbus_message_iter_get_signature(iter)); + pa_assert_se(pa_streq(signature, "a{say}")); + + dbus_free(signature); + + proplist = pa_proplist_new(); + + dbus_message_iter_recurse(iter, &dict_iter); + + while (dbus_message_iter_get_arg_type(&dict_iter) != DBUS_TYPE_INVALID) { + dbus_message_iter_recurse(&dict_iter, &dict_entry_iter); + + dbus_message_iter_get_basic(&dict_entry_iter, &key); + dbus_message_iter_next(&dict_entry_iter); + + if (strlen(key) <= 0 || !pa_ascii_valid(key)) { + pa_dbus_send_error(c, msg, DBUS_ERROR_INVALID_ARGS, "Invalid property list key: '%s'.", key); + goto fail; + } + + dbus_message_iter_get_fixed_array(&dict_entry_iter, &value, &value_length); + + pa_assert(value_length >= 0); + + pa_assert_se(pa_proplist_set(proplist, key, value, value_length) >= 0); + + dbus_message_iter_next(&dict_iter); + } + + dbus_message_iter_next(iter); + + return proplist; + +fail: + if (proplist) + pa_proplist_free(proplist); + + return NULL; +} diff --git a/src/pulsecore/dbus-util.h b/src/pulsecore/dbus-util.h new file mode 100644 index 0000000..cc3abda --- /dev/null +++ b/src/pulsecore/dbus-util.h @@ -0,0 +1,114 @@ +#ifndef foodbusutilhfoo +#define foodbusutilhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2006 Shams E. King + + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <dbus/dbus.h> + +#include <pulse/gccmacro.h> +#include <pulse/mainloop-api.h> +#include <pulse/proplist.h> + +#include <pulsecore/llist.h> + +/* A wrap connection is not shared or refcounted, it is available in client side */ +typedef struct pa_dbus_wrap_connection pa_dbus_wrap_connection; + +pa_dbus_wrap_connection* pa_dbus_wrap_connection_new(pa_mainloop_api *mainloop, bool use_rtclock, DBusBusType type, DBusError *error); +pa_dbus_wrap_connection* pa_dbus_wrap_connection_new_from_existing( + pa_mainloop_api *mainloop, + bool use_rtclock, + DBusConnection *conn); +void pa_dbus_wrap_connection_free(pa_dbus_wrap_connection* conn); + +DBusConnection* pa_dbus_wrap_connection_get(pa_dbus_wrap_connection *conn); + +int pa_dbus_add_matches(DBusConnection *c, DBusError *error, ...) PA_GCC_SENTINEL; +void pa_dbus_remove_matches(DBusConnection *c, ...) PA_GCC_SENTINEL; + +typedef struct pa_dbus_pending pa_dbus_pending; + +struct pa_dbus_pending { + DBusConnection *connection; + DBusMessage *message; + DBusPendingCall *pending; + + void *context_data; + void *call_data; + + PA_LLIST_FIELDS(pa_dbus_pending); +}; + +pa_dbus_pending *pa_dbus_pending_new(DBusConnection *c, DBusMessage *m, DBusPendingCall *pending, void *context_data, void *call_data); +void pa_dbus_pending_free(pa_dbus_pending *p); + +/* Sync up a list of pa_dbus_pending_call objects */ +void pa_dbus_sync_pending_list(pa_dbus_pending **p); + +/* Free up a list of pa_dbus_pending_call objects */ +void pa_dbus_free_pending_list(pa_dbus_pending **p); + +/* When receiving a DBusMessage with type DBUS_MESSAGE_TYPE_ERROR, the + * DBusMessage may or may not contain an error message (a human-readable + * explanation of what went wrong). Extracting the error message from the + * DBusMessage object is a bit tedious, so here's a helper function that does + * that. If the DBusMessage doesn't contain any error message, + * "<no explanation>" is returned. */ +const char *pa_dbus_get_error_message(DBusMessage *m); + +/* Sends an error message as the reply to the given message. */ +void pa_dbus_send_error( + DBusConnection *c, + DBusMessage *in_reply_to, + const char *name, + const char *format, ...) PA_GCC_PRINTF_ATTR(4, 5); + +void pa_dbus_send_empty_reply(DBusConnection *c, DBusMessage *in_reply_to); +void pa_dbus_send_basic_value_reply(DBusConnection *c, DBusMessage *in_reply_to, int type, void *data); +void pa_dbus_send_basic_variant_reply(DBusConnection *c, DBusMessage *in_reply_to, int type, void *data); +void pa_dbus_send_basic_array_variant_reply( + DBusConnection *c, + DBusMessage *in_reply_to, + int item_type, + void *array, + unsigned n); +void pa_dbus_send_proplist_variant_reply(DBusConnection *c, DBusMessage *in_reply_to, pa_proplist *proplist); + +void pa_dbus_append_basic_array(DBusMessageIter *iter, int item_type, const void *array, unsigned n); +void pa_dbus_append_basic_array_variant(DBusMessageIter *iter, int item_type, const void *array, unsigned n); +void pa_dbus_append_basic_variant(DBusMessageIter *iter, int type, void *data); +void pa_dbus_append_basic_variant_dict_entry(DBusMessageIter *dict_iter, const char *key, int type, void *data); +void pa_dbus_append_basic_array_variant_dict_entry( + DBusMessageIter *dict_iter, + const char *key, + int item_type, + const void *array, + unsigned n); +void pa_dbus_append_proplist(DBusMessageIter *iter, pa_proplist *proplist); +void pa_dbus_append_proplist_variant(DBusMessageIter *iter, pa_proplist *proplist); +void pa_dbus_append_proplist_variant_dict_entry(DBusMessageIter *dict_iter, const char *key, pa_proplist *proplist); + +/* Returns a new proplist that the caller has to free. If the proplist contains + * invalid keys, an error reply is sent and NULL is returned. The iterator must + * point to "a{say}" element. This function calls dbus_message_iter_next(iter) + * before returning. */ +pa_proplist *pa_dbus_get_proplist_arg(DBusConnection *c, DBusMessage *msg, DBusMessageIter *iter); + +#endif diff --git a/src/pulsecore/device-port.c b/src/pulsecore/device-port.c new file mode 100644 index 0000000..4f9235e --- /dev/null +++ b/src/pulsecore/device-port.c @@ -0,0 +1,303 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + Copyright 2011 David Henningsson, Canonical Ltd. + + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include "device-port.h" +#include <pulsecore/card.h> +#include <pulsecore/core-util.h> + +PA_DEFINE_PUBLIC_CLASS(pa_device_port, pa_object); + +pa_device_port_new_data *pa_device_port_new_data_init(pa_device_port_new_data *data) { + pa_assert(data); + + pa_zero(*data); + data->type = PA_DEVICE_PORT_TYPE_UNKNOWN; + data->available = PA_AVAILABLE_UNKNOWN; + return data; +} + +void pa_device_port_new_data_set_name(pa_device_port_new_data *data, const char *name) { + pa_assert(data); + + pa_xfree(data->name); + data->name = pa_xstrdup(name); +} + +void pa_device_port_new_data_set_description(pa_device_port_new_data *data, const char *description) { + pa_assert(data); + + pa_xfree(data->description); + data->description = pa_xstrdup(description); +} + +void pa_device_port_new_data_set_available(pa_device_port_new_data *data, pa_available_t available) { + pa_assert(data); + + data->available = available; +} + +void pa_device_port_new_data_set_availability_group(pa_device_port_new_data *data, const char *group) { + pa_assert(data); + + pa_xfree(data->availability_group); + data->availability_group = pa_xstrdup(group); +} + +void pa_device_port_new_data_set_direction(pa_device_port_new_data *data, pa_direction_t direction) { + pa_assert(data); + + data->direction = direction; +} + +void pa_device_port_new_data_set_type(pa_device_port_new_data *data, pa_device_port_type_t type) { + pa_assert(data); + + data->type = type; +} + +void pa_device_port_new_data_done(pa_device_port_new_data *data) { + pa_assert(data); + + pa_xfree(data->name); + pa_xfree(data->description); + pa_xfree(data->availability_group); +} + +void pa_device_port_set_preferred_profile(pa_device_port *p, const char *new_pp) { + pa_assert(p); + + if (!pa_safe_streq(p->preferred_profile, new_pp)) { + pa_xfree(p->preferred_profile); + p->preferred_profile = pa_xstrdup(new_pp); + } +} + +void pa_device_port_set_available(pa_device_port *p, pa_available_t status) { + pa_assert(p); + + if (p->available == status) + return; + +/* pa_assert(status != PA_AVAILABLE_UNKNOWN); */ + + p->available = status; + pa_log_debug("Setting port %s to status %s", p->name, pa_available_to_string(status)); + + /* Post subscriptions to the card which owns us */ + /* XXX: We need to check p->card, because this function may be called + * before the card object has been created. The card object should probably + * be created before port objects, and then p->card could be non-NULL for + * the whole lifecycle of pa_device_port. */ + if (p->card && p->card->linked) { + pa_sink *sink; + pa_source *source; + + pa_subscription_post(p->core, PA_SUBSCRIPTION_EVENT_CARD|PA_SUBSCRIPTION_EVENT_CHANGE, p->card->index); + + sink = pa_device_port_get_sink(p); + source = pa_device_port_get_source(p); + if (sink) + pa_subscription_post(p->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, sink->index); + if (source) + pa_subscription_post(p->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, source->index); + + /* A sink or source whose active port is unavailable can't be the + * default sink/source, so port availability changes may affect the + * default sink/source choice. */ + if (p->direction == PA_DIRECTION_OUTPUT) + pa_core_update_default_sink(p->core); + else + pa_core_update_default_source(p->core); + + if (p->direction == PA_DIRECTION_OUTPUT) { + if (sink && p == sink->active_port) { + if (sink->active_port->available == PA_AVAILABLE_NO) { + if (p->core->rescue_streams) + pa_sink_move_streams_to_default_sink(p->core, sink, false); + } else + pa_core_move_streams_to_newly_available_preferred_sink(p->core, sink); + } + } else { + if (source && p == source->active_port) { + if (source->active_port->available == PA_AVAILABLE_NO) { + if (p->core->rescue_streams) + pa_source_move_streams_to_default_source(p->core, source, false); + } else + pa_core_move_streams_to_newly_available_preferred_source(p->core, source); + } + } + + /* This may cause the sink and source pointers to become invalid, if + * the availability change causes the card profile to get switched. If + * you add code after this line, remember to take that into account. */ + pa_hook_fire(&p->core->hooks[PA_CORE_HOOK_PORT_AVAILABLE_CHANGED], p); + } +} + +static void device_port_free(pa_object *o) { + pa_device_port *p = PA_DEVICE_PORT(o); + + pa_assert(p); + pa_assert(pa_device_port_refcnt(p) == 0); + + if (p->impl_free) + p->impl_free(p); + + if (p->proplist) + pa_proplist_free(p->proplist); + + if (p->profiles) + pa_hashmap_free(p->profiles); + + pa_xfree(p->availability_group); + pa_xfree(p->preferred_profile); + pa_xfree(p->name); + pa_xfree(p->description); + pa_xfree(p); +} + +pa_device_port *pa_device_port_new(pa_core *c, pa_device_port_new_data *data, size_t extra) { + pa_device_port *p; + + pa_assert(data); + pa_assert(data->name); + pa_assert(data->description); + pa_assert(data->direction == PA_DIRECTION_OUTPUT || data->direction == PA_DIRECTION_INPUT); + + p = PA_DEVICE_PORT(pa_object_new_internal(PA_ALIGN(sizeof(pa_device_port)) + extra, pa_device_port_type_id, pa_device_port_check_type)); + p->parent.free = device_port_free; + + p->name = data->name; + data->name = NULL; + p->description = data->description; + data->description = NULL; + p->core = c; + p->card = NULL; + p->priority = 0; + p->available = data->available; + p->availability_group = data->availability_group; + data->availability_group = NULL; + p->profiles = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + p->direction = data->direction; + p->type = data->type; + + p->latency_offset = 0; + p->proplist = pa_proplist_new(); + + return p; +} + +void pa_device_port_set_latency_offset(pa_device_port *p, int64_t offset) { + uint32_t state; + pa_core *core; + + pa_assert(p); + + if (offset == p->latency_offset) + return; + + p->latency_offset = offset; + + switch (p->direction) { + case PA_DIRECTION_OUTPUT: { + pa_sink *sink; + + PA_IDXSET_FOREACH(sink, p->core->sinks, state) { + if (sink->active_port == p) { + pa_sink_set_port_latency_offset(sink, p->latency_offset); + break; + } + } + + break; + } + + case PA_DIRECTION_INPUT: { + pa_source *source; + + PA_IDXSET_FOREACH(source, p->core->sources, state) { + if (source->active_port == p) { + pa_source_set_port_latency_offset(source, p->latency_offset); + break; + } + } + + break; + } + } + + pa_assert_se(core = p->core); + pa_subscription_post(core, PA_SUBSCRIPTION_EVENT_CARD|PA_SUBSCRIPTION_EVENT_CHANGE, p->card->index); + pa_hook_fire(&core->hooks[PA_CORE_HOOK_PORT_LATENCY_OFFSET_CHANGED], p); +} + +pa_device_port *pa_device_port_find_best(pa_hashmap *ports) +{ + void *state; + pa_device_port *p, *best = NULL; + + if (!ports) + return NULL; + + /* First run: skip unavailable ports */ + PA_HASHMAP_FOREACH(p, ports, state) { + if (p->available == PA_AVAILABLE_NO) + continue; + + if (!best || p->priority > best->priority) + best = p; + } + + /* Second run: if only unavailable ports exist, still suggest a port */ + if (!best) { + PA_HASHMAP_FOREACH(p, ports, state) + if (!best || p->priority > best->priority) + best = p; + } + + return best; +} + +pa_sink *pa_device_port_get_sink(pa_device_port *p) { + pa_sink *rs = NULL; + pa_sink *sink; + uint32_t state; + + PA_IDXSET_FOREACH(sink, p->card->sinks, state) + if (p == pa_hashmap_get(sink->ports, p->name)) { + rs = sink; + break; + } + return rs; +} + +pa_source *pa_device_port_get_source(pa_device_port *p) { + pa_source *rs = NULL; + pa_source *source; + uint32_t state; + + PA_IDXSET_FOREACH(source, p->card->sources, state) + if (p == pa_hashmap_get(source->ports, p->name)) { + rs = source; + break; + } + return rs; +} diff --git a/src/pulsecore/device-port.h b/src/pulsecore/device-port.h new file mode 100644 index 0000000..7178ff2 --- /dev/null +++ b/src/pulsecore/device-port.h @@ -0,0 +1,100 @@ +#ifndef foopulsedeviceporthfoo +#define foopulsedeviceporthfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + Copyright 2011 David Henningsson, Canonical Ltd. + + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <inttypes.h> + +#include <pulsecore/typedefs.h> +#include <pulse/def.h> +#include <pulsecore/object.h> +#include <pulsecore/hashmap.h> +#include <pulsecore/core.h> +#include <pulsecore/card.h> + +struct pa_device_port { + pa_object parent; /* Needed for reference counting */ + pa_core *core; + pa_card *card; + + char *name; + char *description; + char *preferred_profile; + pa_device_port_type_t type; + + unsigned priority; + pa_available_t available; /* PA_AVAILABLE_UNKNOWN, PA_AVAILABLE_NO or PA_AVAILABLE_YES */ + char *availability_group; /* a string indentifier which determine the group of devices handling the available state simulteneously */ + + pa_proplist *proplist; + pa_hashmap *profiles; /* Does not own the profiles */ + pa_direction_t direction; + int64_t latency_offset; + + /* Free the extra implementation specific data. Called before other members are freed. */ + void (*impl_free)(pa_device_port *port); + + /* .. followed by some implementation specific data */ +}; + +PA_DECLARE_PUBLIC_CLASS(pa_device_port); +#define PA_DEVICE_PORT(s) (pa_device_port_cast(s)) + +#define PA_DEVICE_PORT_DATA(d) ((void*) ((uint8_t*) d + PA_ALIGN(sizeof(pa_device_port)))) + +typedef struct pa_device_port_new_data { + char *name; + char *description; + pa_available_t available; + char *availability_group; + pa_direction_t direction; + pa_device_port_type_t type; +} pa_device_port_new_data; + +pa_device_port_new_data *pa_device_port_new_data_init(pa_device_port_new_data *data); +void pa_device_port_new_data_set_name(pa_device_port_new_data *data, const char *name); +void pa_device_port_new_data_set_description(pa_device_port_new_data *data, const char *description); +void pa_device_port_new_data_set_available(pa_device_port_new_data *data, pa_available_t available); +void pa_device_port_new_data_set_availability_group(pa_device_port_new_data *data, const char *group); +void pa_device_port_new_data_set_direction(pa_device_port_new_data *data, pa_direction_t direction); +void pa_device_port_new_data_set_type(pa_device_port_new_data *data, pa_device_port_type_t type); +void pa_device_port_new_data_done(pa_device_port_new_data *data); + +pa_device_port *pa_device_port_new(pa_core *c, pa_device_port_new_data *data, size_t extra); + +/* The port's available status has changed */ +void pa_device_port_set_available(pa_device_port *p, pa_available_t available); + +void pa_device_port_set_latency_offset(pa_device_port *p, int64_t offset); +void pa_device_port_set_preferred_profile(pa_device_port *p, const char *new_pp); + +pa_device_port *pa_device_port_find_best(pa_hashmap *ports); + +pa_sink *pa_device_port_get_sink(pa_device_port *p); + +pa_source *pa_device_port_get_source(pa_device_port *p); + +#endif diff --git a/src/pulsecore/dllmain.c b/src/pulsecore/dllmain.c new file mode 100644 index 0000000..e594ae5 --- /dev/null +++ b/src/pulsecore/dllmain.c @@ -0,0 +1,53 @@ +/*** + This file is part of PulseAudio. + + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#ifdef OS_IS_WIN32 + +#include <stdlib.h> +#include <stdio.h> + +#include <windows.h> +#include <winsock2.h> + +extern char *pa_win32_get_toplevel(HANDLE handle); + +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { + WSADATA data; + + switch (fdwReason) { + + case DLL_PROCESS_ATTACH: + if (!pa_win32_get_toplevel(hinstDLL)) + return FALSE; + WSAStartup(MAKEWORD(2, 0), &data); + break; + + case DLL_PROCESS_DETACH: + WSACleanup(); + break; + + } + return TRUE; +} + +#endif /* OS_IS_WIN32 */ diff --git a/src/pulsecore/dynarray.c b/src/pulsecore/dynarray.c new file mode 100644 index 0000000..a948f26 --- /dev/null +++ b/src/pulsecore/dynarray.c @@ -0,0 +1,165 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2008 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <stdlib.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/macro.h> + +#include "dynarray.h" + +struct pa_dynarray { + void **data; + unsigned n_allocated, n_entries; + pa_free_cb_t free_cb; +}; + +pa_dynarray* pa_dynarray_new(pa_free_cb_t free_cb) { + pa_dynarray *array; + + array = pa_xnew0(pa_dynarray, 1); + array->free_cb = free_cb; + + return array; +} + +void pa_dynarray_free(pa_dynarray *array) { + unsigned i; + pa_assert(array); + + if (array->free_cb) + for (i = 0; i < array->n_entries; i++) + array->free_cb(array->data[i]); + + pa_xfree(array->data); + pa_xfree(array); +} + +void pa_dynarray_append(pa_dynarray *array, void *p) { + pa_assert(array); + pa_assert(p); + + if (array->n_entries == array->n_allocated) { + unsigned n = PA_MAX(array->n_allocated * 2, 25U); + + array->data = pa_xrealloc(array->data, sizeof(void *) * n); + array->n_allocated = n; + } + + array->data[array->n_entries++] = p; +} + +void *pa_dynarray_get(pa_dynarray *array, unsigned i) { + pa_assert(array); + + if (i >= array->n_entries) + return NULL; + + return array->data[i]; +} + +void *pa_dynarray_last(pa_dynarray *array) { + pa_assert(array); + + if (array->n_entries == 0) + return NULL; + + return array->data[array->n_entries - 1]; +} + +int pa_dynarray_remove_by_index(pa_dynarray *array, unsigned i) { + void *entry; + + pa_assert(array); + + if (i >= array->n_entries) + return -PA_ERR_NOENTITY; + + entry = array->data[i]; + array->data[i] = array->data[array->n_entries - 1]; + array->n_entries--; + + if (array->free_cb) + array->free_cb(entry); + + return 0; +} + +int pa_dynarray_remove_by_data(pa_dynarray *array, void *p) { + unsigned i; + + pa_assert(array); + pa_assert(p); + + /* Iterate backwards, with the assumption that recently appended entries + * are likely to be removed first. */ + i = array->n_entries; + while (i > 0) { + i--; + if (array->data[i] == p) { + pa_dynarray_remove_by_index(array, i); + return 0; + } + } + + return -PA_ERR_NOENTITY; +} + +void *pa_dynarray_steal_last(pa_dynarray *array) { + pa_assert(array); + + if (array->n_entries > 0) + return array->data[--array->n_entries]; + else + return NULL; +} + +unsigned pa_dynarray_size(pa_dynarray *array) { + pa_assert(array); + + return array->n_entries; +} + +int pa_dynarray_insert_by_index(pa_dynarray *array, void *p, unsigned i) { + void *entry; + unsigned j; + + pa_assert(array); + + if (i > array->n_entries) + return -PA_ERR_NOENTITY; + + if (i == array->n_entries) + pa_dynarray_append(array, p); + else { + entry = pa_dynarray_last(array); + pa_dynarray_append(array, entry); + j = array->n_entries - 2; + for (;j > i; j--) + array->data[j] = array->data[j-1]; + array->data[i] = p; + } + + return 0; +} diff --git a/src/pulsecore/dynarray.h b/src/pulsecore/dynarray.h new file mode 100644 index 0000000..4c0925e --- /dev/null +++ b/src/pulsecore/dynarray.h @@ -0,0 +1,73 @@ +#ifndef foopulsecoredynarrayhfoo +#define foopulsecoredynarrayhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2008 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulse/def.h> + +typedef struct pa_dynarray pa_dynarray; + +/* Implementation of a simple dynamically sized array for storing pointers. + * + * When the array is created, a free callback can be provided, which will be + * then used when removing items from the array and when freeing the array. If + * the free callback is not provided, the memory management of the stored items + * is the responsibility of the array user. If there is need to remove items + * from the array without freeing them, while also having the free callback + * set, the functions with "steal" in their name can be used. + * + * Removing items from the middle of the array causes the last item to be + * moved to the place of the removed item. That is, array ordering is not + * preserved. + * + * The array doesn't support storing NULL pointers. */ + +pa_dynarray* pa_dynarray_new(pa_free_cb_t free_cb); +void pa_dynarray_free(pa_dynarray *array); + +void pa_dynarray_append(pa_dynarray *array, void *p); + +/* Returns the element at index i, or NULL if i is out of bounds. */ +void *pa_dynarray_get(pa_dynarray *array, unsigned i); + +/* Returns the last element, or NULL if the array is empty. */ +void *pa_dynarray_last(pa_dynarray *array); + +/* Returns -PA_ERR_NOENTITY if i is out of bounds, and zero otherwise. */ +int pa_dynarray_remove_by_index(pa_dynarray *array, unsigned i); + +/* Returns -PA_ERR_NOENTITY if p is not found in the array, and zero + * otherwise. If the array contains multiple occurrences of p, only one of + * them is removed (and it's unspecified which one). */ +int pa_dynarray_remove_by_data(pa_dynarray *array, void *p); + +/* Returns the removed item, or NULL if the array is empty. */ +void *pa_dynarray_steal_last(pa_dynarray *array); + +unsigned pa_dynarray_size(pa_dynarray *array); + +/* Returns -PA_ERR_NOENTITY if i is out of bounds, and zero otherwise. + * Here i is the location index in the array like 0, ..., array->entries */ +int pa_dynarray_insert_by_index(pa_dynarray *array, void *p, unsigned i); + +#define PA_DYNARRAY_FOREACH(elem, array, idx) \ + for ((idx) = 0; ((elem) = pa_dynarray_get(array, idx)); (idx)++) + +#endif diff --git a/src/pulsecore/endianmacros.h b/src/pulsecore/endianmacros.h new file mode 100644 index 0000000..c4cb8a2 --- /dev/null +++ b/src/pulsecore/endianmacros.h @@ -0,0 +1,160 @@ +#ifndef fooendianmacroshfoo +#define fooendianmacroshfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <inttypes.h> + +#ifndef PACKAGE +#error "Please include config.h before including this file!" +#endif + +#ifdef HAVE_BYTESWAP_H +#include <byteswap.h> +#endif + +#ifdef HAVE_BYTESWAP_H +#define PA_INT16_SWAP(x) ((int16_t) bswap_16((uint16_t) x)) +#define PA_UINT16_SWAP(x) ((uint16_t) bswap_16((uint16_t) x)) +#define PA_INT32_SWAP(x) ((int32_t) bswap_32((uint32_t) x)) +#define PA_UINT32_SWAP(x) ((uint32_t) bswap_32((uint32_t) x)) +#else +#define PA_INT16_SWAP(x) ( (int16_t) ( ((uint16_t) (x) >> 8) | ((uint16_t) (x) << 8) ) ) +#define PA_UINT16_SWAP(x) ( (uint16_t) ( ((uint16_t) (x) >> 8) | ((uint16_t) (x) << 8) ) ) +#define PA_INT32_SWAP(x) ( (int32_t) ( ((uint32_t) (x) >> 24) | ((uint32_t) (x) << 24) | (((uint32_t) (x) & 0xFF00) << 8) | ((((uint32_t) (x)) >> 8) & 0xFF00) ) ) +#define PA_UINT32_SWAP(x) ( (uint32_t) ( ((uint32_t) (x) >> 24) | ((uint32_t) (x) << 24) | (((uint32_t) (x) & 0xFF00) << 8) | ((((uint32_t) (x)) >> 8) & 0xFF00) ) ) +#endif + +static inline uint32_t PA_READ24BE(const uint8_t *p) { + return + ((uint32_t) p[0] << 16) | + ((uint32_t) p[1] << 8) | + ((uint32_t) p[2]); +} + +static inline uint32_t PA_READ24LE(const uint8_t *p) { + return + ((uint32_t) p[2] << 16) | + ((uint32_t) p[1] << 8) | + ((uint32_t) p[0]); +} + +static inline void PA_WRITE24BE(uint8_t *p, uint32_t u) { + p[0] = (uint8_t) (u >> 16); + p[1] = (uint8_t) (u >> 8); + p[2] = (uint8_t) u; +} + +static inline void PA_WRITE24LE(uint8_t *p, uint32_t u) { + p[2] = (uint8_t) (u >> 16); + p[1] = (uint8_t) (u >> 8); + p[0] = (uint8_t) u; +} + +static inline float PA_READ_FLOAT32RE(const void *p) { + union { + float f; + uint32_t u; + } t; + + t.u = PA_UINT32_SWAP(*(uint32_t *) p); + return t.f; +} + +static inline void PA_WRITE_FLOAT32RE(void *p, float x) { + union { + float f; + uint32_t u; + } t; + + t.f = x; + *(uint32_t *) p = PA_UINT32_SWAP(t.u); +} + +#define PA_MAYBE_INT16_SWAP(c,x) ((c) ? PA_INT16_SWAP(x) : (x)) +#define PA_MAYBE_UINT16_SWAP(c,x) ((c) ? PA_UINT16_SWAP(x) : (x)) + +#define PA_MAYBE_INT32_SWAP(c,x) ((c) ? PA_INT32_SWAP(x) : (x)) +#define PA_MAYBE_UINT32_SWAP(c,x) ((c) ? PA_UINT32_SWAP(x) : (x)) + +#ifdef WORDS_BIGENDIAN + #define PA_INT16_FROM_LE(x) PA_INT16_SWAP(x) + #define PA_INT16_FROM_BE(x) ((int16_t)(x)) + + #define PA_INT16_TO_LE(x) PA_INT16_SWAP(x) + #define PA_INT16_TO_BE(x) ((int16_t)(x)) + + #define PA_UINT16_FROM_LE(x) PA_UINT16_SWAP(x) + #define PA_UINT16_FROM_BE(x) ((uint16_t)(x)) + + #define PA_UINT16_TO_LE(x) PA_UINT16_SWAP(x) + #define PA_UINT16_TO_BE(x) ((uint16_t)(x)) + + #define PA_INT32_FROM_LE(x) PA_INT32_SWAP(x) + #define PA_INT32_FROM_BE(x) ((int32_t)(x)) + + #define PA_INT32_TO_LE(x) PA_INT32_SWAP(x) + #define PA_INT32_TO_BE(x) ((int32_t)(x)) + + #define PA_UINT32_FROM_LE(x) PA_UINT32_SWAP(x) + #define PA_UINT32_FROM_BE(x) ((uint32_t)(x)) + + #define PA_UINT32_TO_LE(x) PA_UINT32_SWAP(x) + #define PA_UINT32_TO_BE(x) ((uint32_t)(x)) + + #define PA_READ24NE(x) PA_READ24BE(x) + #define PA_WRITE24NE(x,y) PA_WRITE24BE((x),(y)) + + #define PA_READ24RE(x) PA_READ24LE(x) + #define PA_WRITE24RE(x,y) PA_WRITE24LE((x),(y)) +#else + #define PA_INT16_FROM_LE(x) ((int16_t)(x)) + #define PA_INT16_FROM_BE(x) PA_INT16_SWAP(x) + + #define PA_INT16_TO_LE(x) ((int16_t)(x)) + #define PA_INT16_TO_BE(x) PA_INT16_SWAP(x) + + #define PA_UINT16_FROM_LE(x) ((uint16_t)(x)) + #define PA_UINT16_FROM_BE(x) PA_UINT16_SWAP(x) + + #define PA_UINT16_TO_LE(x) ((uint16_t)(x)) + #define PA_UINT16_TO_BE(x) PA_UINT16_SWAP(x) + + #define PA_INT32_FROM_LE(x) ((int32_t)(x)) + #define PA_INT32_FROM_BE(x) PA_INT32_SWAP(x) + + #define PA_INT32_TO_LE(x) ((int32_t)(x)) + #define PA_INT32_TO_BE(x) PA_INT32_SWAP(x) + + #define PA_UINT32_FROM_LE(x) ((uint32_t)(x)) + #define PA_UINT32_FROM_BE(x) PA_UINT32_SWAP(x) + + #define PA_UINT32_TO_LE(x) ((uint32_t)(x)) + #define PA_UINT32_TO_BE(x) PA_UINT32_SWAP(x) + + #define PA_READ24NE(x) PA_READ24LE(x) + #define PA_WRITE24NE(x,y) PA_WRITE24LE((x),(y)) + + #define PA_READ24RE(x) PA_READ24BE(x) + #define PA_WRITE24RE(x,y) PA_WRITE24BE((x),(y)) +#endif + +#endif diff --git a/src/pulsecore/esound.h b/src/pulsecore/esound.h new file mode 100644 index 0000000..4ca0e04 --- /dev/null +++ b/src/pulsecore/esound.h @@ -0,0 +1,204 @@ +#ifndef fooesoundhfoo +#define fooesoundhfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +/* Most of the following is blatantly stolen from esound. */ + +/* path and name of the default EsounD domain socket */ +#define ESD_UNIX_SOCKET_DIR "/tmp/.esd" +#define ESD_UNIX_SOCKET_NAME "/tmp/.esd/socket" + +/* length of the audio buffer size */ +#define ESD_BUF_SIZE (4 * 1024) +/* maximum size we can write(). Otherwise we might overflow */ +#define ESD_MAX_WRITE_SIZE (21 * 4096) + +/* length of the authentication key, octets */ +#define ESD_KEY_LEN (16) + +/* default port for the EsounD server */ +#define ESD_DEFAULT_PORT (16001) + +/* default sample rate for the EsounD server */ +#define ESD_DEFAULT_RATE (44100) + +/* maximum length of a stream/sample name */ +#define ESD_NAME_MAX (128) + +/* a magic number to identify the relative endianness of a client */ +#define ESD_ENDIAN_KEY ((uint32_t) (('E' << 24) + ('N' << 16) + ('D' << 8) + ('N'))) + +#define ESD_VOLUME_BASE (256) + +/*************************************/ +/* what can we do to/with the EsounD */ +enum esd_proto { + ESD_PROTO_CONNECT, /* implied on initial client connection */ + + /* pseudo "security" functionality */ + ESD_PROTO_LOCK, /* disable "foreign" client connections */ + ESD_PROTO_UNLOCK, /* enable "foreign" client connections */ + + /* stream functionality: play, record, monitor */ + ESD_PROTO_STREAM_PLAY, /* play all following data as a stream */ + ESD_PROTO_STREAM_REC, /* record data from card as a stream */ + ESD_PROTO_STREAM_MON, /* send mixed buffer output as a stream */ + + /* sample functionality: cache, free, play, loop, EOL, kill */ + ESD_PROTO_SAMPLE_CACHE, /* cache a sample in the server */ + ESD_PROTO_SAMPLE_FREE, /* release a sample in the server */ + ESD_PROTO_SAMPLE_PLAY, /* play a cached sample */ + ESD_PROTO_SAMPLE_LOOP, /* loop a cached sample, til eoloop */ + ESD_PROTO_SAMPLE_STOP, /* stop a looping sample when done */ + ESD_PROTO_SAMPLE_KILL, /* stop the looping sample immediately */ + + /* free and reclaim /dev/dsp functionality */ + ESD_PROTO_STANDBY, /* release /dev/dsp and ignore all data */ + ESD_PROTO_RESUME, /* reclaim /dev/dsp and play sounds again */ + + /* TODO: move these to a more logical place. NOTE: will break the protocol */ + ESD_PROTO_SAMPLE_GETID, /* get the ID for an already-cached sample */ + ESD_PROTO_STREAM_FILT, /* filter mixed buffer output as a stream */ + + /* esd remote management */ + ESD_PROTO_SERVER_INFO, /* get server info (ver, sample rate, format) */ + ESD_PROTO_ALL_INFO, /* get all info (server info, players, samples) */ + ESD_PROTO_SUBSCRIBE, /* track new and removed players and samples */ + ESD_PROTO_UNSUBSCRIBE, /* stop tracking updates */ + + /* esd remote control */ + ESD_PROTO_STREAM_PAN, /* set stream panning */ + ESD_PROTO_SAMPLE_PAN, /* set default sample panning */ + + /* esd status */ + ESD_PROTO_STANDBY_MODE, /* see if server is in standby, autostandby, etc */ + + /* esd latency */ + ESD_PROTO_LATENCY, /* retrieve latency between write()'s and output */ + + ESD_PROTO_MAX /* for bounds checking */ +}; + +/******************/ +/* The EsounD api */ + +/* the properties of a sound buffer are logically or'd */ + +/* bits of stream/sample data */ +#define ESD_MASK_BITS ( 0x000F ) +#define ESD_BITS8 ( 0x0000 ) +#define ESD_BITS16 ( 0x0001 ) + +/* how many interleaved channels of data */ +#define ESD_MASK_CHAN ( 0x00F0 ) +#define ESD_MONO ( 0x0010 ) +#define ESD_STEREO ( 0x0020 ) + +/* whether it's a stream or a sample */ +#define ESD_MASK_MODE ( 0x0F00 ) +#define ESD_STREAM ( 0x0000 ) +#define ESD_SAMPLE ( 0x0100 ) +#define ESD_ADPCM ( 0x0200 ) /* TODO: anyone up for this? =P */ + +/* the function of the stream/sample, and common functions */ +#define ESD_MASK_FUNC ( 0xF000 ) +#define ESD_PLAY ( 0x1000 ) +/* functions for streams only */ +#define ESD_MONITOR ( 0x0000 ) +#define ESD_RECORD ( 0x2000 ) +/* functions for samples only */ +#define ESD_STOP ( 0x0000 ) +#define ESD_LOOP ( 0x2000 ) + +typedef int esd_format_t; +typedef int esd_proto_t; + +/*******************************************************************/ +/* esdmgr.c - functions to implement a "sound manager" for esd */ + +/* structures to retrieve information about streams/samples from the server */ +typedef struct esd_server_info { + + int version; /* server version encoded as an int */ + esd_format_t format; /* magic int with the format info */ + int rate; /* sample rate */ + +} esd_server_info_t; + +typedef struct esd_player_info { + + struct esd_player_info *next; /* point to next entry in list */ + esd_server_info_t *server; /* the server that contains this stream */ + + int source_id; /* either a stream fd or sample id */ + char name[ ESD_NAME_MAX ]; /* name of stream for remote control */ + int rate; /* sample rate */ + int left_vol_scale; /* volume scaling */ + int right_vol_scale; + + esd_format_t format; /* magic int with the format info */ + +} esd_player_info_t; + +typedef struct esd_sample_info { + + struct esd_sample_info *next; /* point to next entry in list */ + esd_server_info_t *server; /* the server that contains this sample */ + + int sample_id; /* either a stream fd or sample id */ + char name[ ESD_NAME_MAX ]; /* name of stream for remote control */ + int rate; /* sample rate */ + int left_vol_scale; /* volume scaling */ + int right_vol_scale; + + esd_format_t format; /* magic int with the format info */ + int length; /* total buffer length */ + +} esd_sample_info_t; + +typedef struct esd_info { + + esd_server_info_t *server; + esd_player_info_t *player_list; + esd_sample_info_t *sample_list; + +} esd_info_t; + +enum esd_standby_mode { + ESM_ERROR, ESM_ON_STANDBY, ESM_ON_AUTOSTANDBY, ESM_RUNNING +}; +typedef int esd_standby_mode_t; + +enum esd_client_state { + ESD_STREAMING_DATA, /* data from here on is streamed data */ + ESD_CACHING_SAMPLE, /* midway through caching a sample */ + ESD_NEEDS_REQDATA, /* more data needed to complete request */ + ESD_NEXT_REQUEST, /* proceed to next request */ + ESD_CLIENT_STATE_MAX /* place holder */ +}; +typedef int esd_client_state_t; + +/* the endian key is transferred in binary, if it's read into int, */ +/* and matches ESD_ENDIAN_KEY (ENDN), then the endianness of the */ +/* server and the client match; if it's SWAP_ENDIAN_KEY, swap data */ +#define ESD_SWAP_ENDIAN_KEY (PA_UINT32_SWAP(ESD_ENDIAN_KEY)) + +#endif diff --git a/src/pulsecore/fdsem.c b/src/pulsecore/fdsem.c new file mode 100644 index 0000000..a7fbf95 --- /dev/null +++ b/src/pulsecore/fdsem.c @@ -0,0 +1,320 @@ +/*** + 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#ifdef HAVE_SYS_SYSCALL_H +#include <sys/syscall.h> +#endif + +#include <unistd.h> +#include <errno.h> + +#include <pulsecore/atomic.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> +#include <pulsecore/core-util.h> +#include <pulsecore/core-error.h> +#include <pulse/xmalloc.h> + +#ifndef HAVE_PIPE +#include <pulsecore/pipe.h> +#endif + +#ifdef HAVE_SYS_EVENTFD_H +#include <sys/eventfd.h> +#endif + +#include "fdsem.h" + +struct pa_fdsem { + int fds[2]; +#ifdef HAVE_SYS_EVENTFD_H + int efd; +#endif + int write_type; + pa_fdsem_data *data; +}; + +pa_fdsem *pa_fdsem_new(void) { + pa_fdsem *f; + + f = pa_xmalloc0(PA_ALIGN(sizeof(pa_fdsem)) + PA_ALIGN(sizeof(pa_fdsem_data))); + +#ifdef HAVE_SYS_EVENTFD_H + if ((f->efd = eventfd(0, EFD_CLOEXEC)) >= 0) + f->fds[0] = f->fds[1] = -1; + else +#endif + { + if (pa_pipe_cloexec(f->fds) < 0) { + pa_xfree(f); + return NULL; + } + } + + f->data = (pa_fdsem_data*) ((uint8_t*) f + PA_ALIGN(sizeof(pa_fdsem))); + + pa_atomic_store(&f->data->waiting, 0); + pa_atomic_store(&f->data->signalled, 0); + pa_atomic_store(&f->data->in_pipe, 0); + + return f; +} + +pa_fdsem *pa_fdsem_open_shm(pa_fdsem_data *data, int event_fd) { + pa_fdsem *f = NULL; + + pa_assert(data); + pa_assert(event_fd >= 0); + +#ifdef HAVE_SYS_EVENTFD_H + f = pa_xnew0(pa_fdsem, 1); + + f->efd = event_fd; + pa_make_fd_cloexec(f->efd); + f->fds[0] = f->fds[1] = -1; + f->data = data; +#endif + + return f; +} + +pa_fdsem *pa_fdsem_new_shm(pa_fdsem_data *data) { + pa_fdsem *f = NULL; + + pa_assert(data); + +#ifdef HAVE_SYS_EVENTFD_H + + f = pa_xnew0(pa_fdsem, 1); + + if ((f->efd = eventfd(0, EFD_CLOEXEC)) < 0) { + pa_xfree(f); + return NULL; + } + + f->fds[0] = f->fds[1] = -1; + f->data = data; + + pa_atomic_store(&f->data->waiting, 0); + pa_atomic_store(&f->data->signalled, 0); + pa_atomic_store(&f->data->in_pipe, 0); + +#endif + + return f; +} + +void pa_fdsem_free(pa_fdsem *f) { + pa_assert(f); + +#ifdef HAVE_SYS_EVENTFD_H + if (f->efd >= 0) + pa_close(f->efd); +#endif + pa_close_pipe(f->fds); + + pa_xfree(f); +} + +static void flush(pa_fdsem *f) { + ssize_t r; + pa_assert(f); + + if (pa_atomic_load(&f->data->in_pipe) <= 0) + return; + + do { + char x[10]; + +#ifdef HAVE_SYS_EVENTFD_H + if (f->efd >= 0) { + uint64_t u; + + if ((r = pa_read(f->efd, &u, sizeof(u), NULL)) != sizeof(u)) { + + if (r >= 0 || errno != EINTR) { + pa_log_error("Invalid read from eventfd: %s", r < 0 ? pa_cstrerror(errno) : "EOF"); + pa_assert_not_reached(); + } + + continue; + } + r = (ssize_t) u; + } else +#endif + + if ((r = pa_read(f->fds[0], &x, sizeof(x), NULL)) <= 0) { + + if (r >= 0 || errno != EINTR) { + pa_log_error("Invalid read from pipe: %s", r < 0 ? pa_cstrerror(errno) : "EOF"); + pa_assert_not_reached(); + } + + continue; + } + + } while (pa_atomic_sub(&f->data->in_pipe, (int) r) > (int) r); +} + +void pa_fdsem_post(pa_fdsem *f) { + pa_assert(f); + + if (pa_atomic_cmpxchg(&f->data->signalled, 0, 1)) { + + if (pa_atomic_load(&f->data->waiting)) { + ssize_t r; + char x = 'x'; + + pa_atomic_inc(&f->data->in_pipe); + + for (;;) { + +#ifdef HAVE_SYS_EVENTFD_H + if (f->efd >= 0) { + uint64_t u = 1; + + if ((r = pa_write(f->efd, &u, sizeof(u), &f->write_type)) != sizeof(u)) { + if (r >= 0 || errno != EINTR) { + pa_log_error("Invalid write to eventfd: %s", r < 0 ? pa_cstrerror(errno) : "EOF"); + pa_assert_not_reached(); + } + + continue; + } + } else +#endif + + if ((r = pa_write(f->fds[1], &x, 1, &f->write_type)) != 1) { + if (r >= 0 || errno != EINTR) { + pa_log_error("Invalid write to pipe: %s", r < 0 ? pa_cstrerror(errno) : "EOF"); + pa_assert_not_reached(); + } + + continue; + } + + break; + } + } + } +} + +void pa_fdsem_wait(pa_fdsem *f) { + pa_assert(f); + + flush(f); + + if (pa_atomic_cmpxchg(&f->data->signalled, 1, 0)) + return; + + pa_atomic_inc(&f->data->waiting); + + while (!pa_atomic_cmpxchg(&f->data->signalled, 1, 0)) { + char x[10]; + ssize_t r; + +#ifdef HAVE_SYS_EVENTFD_H + if (f->efd >= 0) { + uint64_t u; + + if ((r = pa_read(f->efd, &u, sizeof(u), NULL)) != sizeof(u)) { + + if (r >= 0 || errno != EINTR) { + pa_log_error("Invalid read from eventfd: %s", r < 0 ? pa_cstrerror(errno) : "EOF"); + pa_assert_not_reached(); + } + + continue; + } + + r = (ssize_t) u; + } else +#endif + + if ((r = pa_read(f->fds[0], &x, sizeof(x), NULL)) <= 0) { + + if (r >= 0 || errno != EINTR) { + pa_log_error("Invalid read from pipe: %s", r < 0 ? pa_cstrerror(errno) : "EOF"); + pa_assert_not_reached(); + } + + continue; + } + + pa_atomic_sub(&f->data->in_pipe, (int) r); + } + + pa_assert_se(pa_atomic_dec(&f->data->waiting) >= 1); +} + +int pa_fdsem_try(pa_fdsem *f) { + pa_assert(f); + + flush(f); + + if (pa_atomic_cmpxchg(&f->data->signalled, 1, 0)) + return 1; + + return 0; +} + +int pa_fdsem_get(pa_fdsem *f) { + pa_assert(f); + +#ifdef HAVE_SYS_EVENTFD_H + if (f->efd >= 0) + return f->efd; +#endif + + return f->fds[0]; +} + +int pa_fdsem_before_poll(pa_fdsem *f) { + pa_assert(f); + + flush(f); + + if (pa_atomic_cmpxchg(&f->data->signalled, 1, 0)) + return -1; + + pa_atomic_inc(&f->data->waiting); + + if (pa_atomic_cmpxchg(&f->data->signalled, 1, 0)) { + pa_assert_se(pa_atomic_dec(&f->data->waiting) >= 1); + return -1; + } + return 0; +} + +int pa_fdsem_after_poll(pa_fdsem *f) { + pa_assert(f); + + pa_assert_se(pa_atomic_dec(&f->data->waiting) >= 1); + + flush(f); + + if (pa_atomic_cmpxchg(&f->data->signalled, 1, 0)) + return 1; + + return 0; +} diff --git a/src/pulsecore/fdsem.h b/src/pulsecore/fdsem.h new file mode 100644 index 0000000..ae1bbe9 --- /dev/null +++ b/src/pulsecore/fdsem.h @@ -0,0 +1,53 @@ +#ifndef foopulsefdsemhfoo +#define foopulsefdsemhfoo + +/*** + 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/types.h> + +/* A simple, asynchronous semaphore which uses fds for sleeping. In + * the best case all functions are lock-free unless sleeping is + * required. */ + +#include <pulsecore/atomic.h> + +typedef struct pa_fdsem pa_fdsem; + +typedef struct pa_fdsem_data { + pa_atomic_t waiting; + pa_atomic_t signalled; + pa_atomic_t in_pipe; +} pa_fdsem_data; + +pa_fdsem *pa_fdsem_new(void); +pa_fdsem *pa_fdsem_open_shm(pa_fdsem_data *data, int event_fd); +pa_fdsem *pa_fdsem_new_shm(pa_fdsem_data *data); +void pa_fdsem_free(pa_fdsem *f); + +void pa_fdsem_post(pa_fdsem *f); +void pa_fdsem_wait(pa_fdsem *f); +int pa_fdsem_try(pa_fdsem *f); + +int pa_fdsem_get(pa_fdsem *f); + +int pa_fdsem_before_poll(pa_fdsem *f); +int pa_fdsem_after_poll(pa_fdsem *f); + +#endif diff --git a/src/pulsecore/ffmpeg/avcodec.h b/src/pulsecore/ffmpeg/avcodec.h new file mode 100644 index 0000000..079c252 --- /dev/null +++ b/src/pulsecore/ffmpeg/avcodec.h @@ -0,0 +1,81 @@ +/* + * copyright (c) 2001 Fabrice Bellard + * + * This file is part of FFmpeg. + * + * FFmpeg 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. + * + * FFmpeg 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 FFmpeg; if not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef AVCODEC_H +#define AVCODEC_H + +/* Just a heavily bastardized version of the original file from + * ffmpeg, just enough to get resample2.c to compile without + * modification -- Lennart */ + +#if !defined(PACKAGE) && defined(HAVE_CONFIG_H) +#include <config.h> +#endif + +#include <sys/types.h> +#include <inttypes.h> +#include <math.h> +#include <string.h> +#include <stdlib.h> +#include <assert.h> + +#define av_mallocz(l) calloc(1, (l)) +#define av_malloc(l) malloc(l) +#define av_realloc(p,l) realloc((p),(l)) +#define av_free(p) free(p) + +static inline void av_freep(void *k) { + void **p = k; + + if (p) { + free(*p); + *p = NULL; + } +} + +static inline int av_clip(int a, int amin, int amax) +{ + if (a < amin) return amin; + else if (a > amax) return amax; + else return a; +} + +#define av_log(a,b,c) + +#define FFABS(a) ((a) >= 0 ? (a) : (-(a))) +#define FFSIGN(a) ((a) > 0 ? 1 : -1) + +#define FFMAX(a,b) ((a) > (b) ? (a) : (b)) +#define FFMIN(a,b) ((a) > (b) ? (b) : (a)) + +struct AVResampleContext; +struct AVResampleContext *av_resample_init(int out_rate, int in_rate, int filter_length, int log2_phase_count, int linear, double cutoff); +int av_resample(struct AVResampleContext *c, short *dst, short *src, int *consumed, int src_size, int dst_size, int update_ctx); +void av_resample_compensate(struct AVResampleContext *c, int sample_delta, int compensation_distance); +void av_resample_close(struct AVResampleContext *c); +void av_build_filter(int16_t *filter, double factor, int tap_count, int phase_count, int scale, int type); + +/* + * crude lrintf for non-C99 systems. + */ +#ifndef HAVE_LRINTF +#define lrintf(x) ((long int)(x)) +#endif + +#endif /* AVCODEC_H */ diff --git a/src/pulsecore/ffmpeg/dsputil.h b/src/pulsecore/ffmpeg/dsputil.h new file mode 100644 index 0000000..8da742d --- /dev/null +++ b/src/pulsecore/ffmpeg/dsputil.h @@ -0,0 +1 @@ +/* empty file, just here to allow us to compile an unmodified resampler2.c */ diff --git a/src/pulsecore/ffmpeg/resample2.c b/src/pulsecore/ffmpeg/resample2.c new file mode 100644 index 0000000..a18f96e --- /dev/null +++ b/src/pulsecore/ffmpeg/resample2.c @@ -0,0 +1,298 @@ +/* + * audio resampling + * Copyright (c) 2004 Michael Niedermayer <michaelni@gmx.at> + * + * This file is part of FFmpeg. + * + * FFmpeg 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. + * + * FFmpeg 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 FFmpeg; if not, see <http://www.gnu.org/licenses/>. + */ + +/** + * @file libavcodec/resample2.c + * audio resampling + * @author Michael Niedermayer <michaelni@gmx.at> + */ + +#include "avcodec.h" +#include "dsputil.h" + +#ifndef CONFIG_RESAMPLE_HP +#define FILTER_SHIFT 15 + +#define FELEM int16_t +#define FELEM2 int32_t +#define FELEML int64_t +#define FELEM_MAX INT16_MAX +#define FELEM_MIN INT16_MIN +#define WINDOW_TYPE 9 +#elif !defined(CONFIG_RESAMPLE_AUDIOPHILE_KIDDY_MODE) +#define FILTER_SHIFT 30 + +#define FELEM int32_t +#define FELEM2 int64_t +#define FELEML int64_t +#define FELEM_MAX INT32_MAX +#define FELEM_MIN INT32_MIN +#define WINDOW_TYPE 12 +#else +#define FILTER_SHIFT 0 + +#define FELEM double +#define FELEM2 double +#define FELEML double +#define WINDOW_TYPE 24 +#endif + + +typedef struct AVResampleContext{ + FELEM *filter_bank; + int filter_length; + int ideal_dst_incr; + int dst_incr; + int index; + int frac; + int src_incr; + int compensation_distance; + int phase_shift; + int phase_mask; + int linear; +}AVResampleContext; + +/** + * 0th order modified bessel function of the first kind. + */ +static double bessel(double x){ + double v=1; + double t=1; + int i; + + x= x*x/4; + for(i=1; i<50; i++){ + t *= x/(i*i); + v += t; + } + return v; +} + +/** + * builds a polyphase filterbank. + * @param factor resampling factor + * @param scale wanted sum of coefficients for each filter + * @param type 0->cubic, 1->blackman nuttall windowed sinc, 2..16->kaiser windowed sinc beta=2..16 + */ +void av_build_filter(FELEM *filter, double factor, int tap_count, int phase_count, int scale, int type){ + int ph, i; + double x, y, w, tab[tap_count]; + const int center= (tap_count-1)/2; + + /* if upsampling, only need to interpolate, no filter */ + if (factor > 1.0) + factor = 1.0; + + for(ph=0;ph<phase_count;ph++) { + double norm = 0; + for(i=0;i<tap_count;i++) { + x = M_PI * ((double)(i - center) - (double)ph / phase_count) * factor; + if (x == 0) y = 1.0; + else y = sin(x) / x; + switch(type){ + case 0:{ + const float d= -0.5; //first order derivative = -0.5 + x = fabs(((double)(i - center) - (double)ph / phase_count) * factor); + if(x<1.0) y= 1 - 3*x*x + 2*x*x*x + d*( -x*x + x*x*x); + else y= d*(-4 + 8*x - 5*x*x + x*x*x); + break;} + case 1: + w = 2.0*x / (factor*tap_count) + M_PI; + y *= 0.3635819 - 0.4891775 * cos(w) + 0.1365995 * cos(2*w) - 0.0106411 * cos(3*w); + break; + default: + w = 2.0*x / (factor*tap_count*M_PI); + y *= bessel(type*sqrt(FFMAX(1-w*w, 0))); + break; + } + + tab[i] = y; + norm += y; + } + + /* normalize so that an uniform color remains the same */ + for(i=0;i<tap_count;i++) { +#ifdef CONFIG_RESAMPLE_AUDIOPHILE_KIDDY_MODE + filter[ph * tap_count + i] = tab[i] / norm; +#else + filter[ph * tap_count + i] = av_clip(lrintf(tab[i] * scale / norm), FELEM_MIN, FELEM_MAX); +#endif + } + } +#if 0 + { +#define LEN 1024 + int j,k; + double sine[LEN + tap_count]; + double filtered[LEN]; + double maxff=-2, minff=2, maxsf=-2, minsf=2; + for(i=0; i<LEN; i++){ + double ss=0, sf=0, ff=0; + for(j=0; j<LEN+tap_count; j++) + sine[j]= cos(i*j*M_PI/LEN); + for(j=0; j<LEN; j++){ + double sum=0; + ph=0; + for(k=0; k<tap_count; k++) + sum += filter[ph * tap_count + k] * sine[k+j]; + filtered[j]= sum / (1<<FILTER_SHIFT); + ss+= sine[j + center] * sine[j + center]; + ff+= filtered[j] * filtered[j]; + sf+= sine[j + center] * filtered[j]; + } + ss= sqrt(2*ss/LEN); + ff= sqrt(2*ff/LEN); + sf= 2*sf/LEN; + maxff= FFMAX(maxff, ff); + minff= FFMIN(minff, ff); + maxsf= FFMAX(maxsf, sf); + minsf= FFMIN(minsf, sf); + if(i%11==0){ + av_log(NULL, AV_LOG_ERROR, "i:%4d ss:%f ff:%13.6e-%13.6e sf:%13.6e-%13.6e\n", i, ss, maxff, minff, maxsf, minsf); + minff=minsf= 2; + maxff=maxsf= -2; + } + } + } +#endif +} + +AVResampleContext *av_resample_init(int out_rate, int in_rate, int filter_size, int phase_shift, int linear, double cutoff){ + AVResampleContext *c= av_mallocz(sizeof(AVResampleContext)); + double factor= FFMIN(out_rate * cutoff / in_rate, 1.0); + int phase_count= 1<<phase_shift; + + c->phase_shift= phase_shift; + c->phase_mask= phase_count-1; + c->linear= linear; + + c->filter_length= FFMAX((int)ceil(filter_size/factor), 1); + c->filter_bank= av_mallocz(c->filter_length*(phase_count+1)*sizeof(FELEM)); + av_build_filter(c->filter_bank, factor, c->filter_length, phase_count, 1<<FILTER_SHIFT, WINDOW_TYPE); + memcpy(&c->filter_bank[c->filter_length*phase_count+1], c->filter_bank, (c->filter_length-1)*sizeof(FELEM)); + c->filter_bank[c->filter_length*phase_count]= c->filter_bank[c->filter_length - 1]; + + c->src_incr= out_rate; + c->ideal_dst_incr= c->dst_incr= in_rate * phase_count; + c->index= -phase_count*((c->filter_length-1)/2); + + return c; +} + +void av_resample_close(AVResampleContext *c){ + av_freep(&c->filter_bank); + av_freep(&c); +} + +void av_resample_compensate(AVResampleContext *c, int sample_delta, int compensation_distance){ +// sample_delta += (c->ideal_dst_incr - c->dst_incr)*(int64_t)c->compensation_distance / c->ideal_dst_incr; + c->compensation_distance= compensation_distance; + c->dst_incr = c->ideal_dst_incr - c->ideal_dst_incr * (int64_t)sample_delta / compensation_distance; +} + +int av_resample(AVResampleContext *c, short *dst, short *src, int *consumed, int src_size, int dst_size, int update_ctx){ + int dst_index, i; + int index= c->index; + int frac= c->frac; + int dst_incr_frac= c->dst_incr % c->src_incr; + int dst_incr= c->dst_incr / c->src_incr; + int compensation_distance= c->compensation_distance; + + if(compensation_distance == 0 && c->filter_length == 1 && c->phase_shift==0){ + int64_t index2= ((int64_t)index)<<32; + int64_t incr= (1LL<<32) * c->dst_incr / c->src_incr; + dst_size= FFMIN(dst_size, (src_size-1-index) * (int64_t)c->src_incr / c->dst_incr); + + for(dst_index=0; dst_index < dst_size; dst_index++){ + dst[dst_index] = src[index2>>32]; + index2 += incr; + } + frac += dst_index * dst_incr_frac; + index += dst_index * dst_incr; + index += frac / c->src_incr; + frac %= c->src_incr; + }else{ + for(dst_index=0; dst_index < dst_size; dst_index++){ + FELEM *filter= c->filter_bank + c->filter_length*(index & c->phase_mask); + int sample_index= index >> c->phase_shift; + FELEM2 val=0; + + if(sample_index < 0){ + for(i=0; i<c->filter_length; i++) + val += src[FFABS(sample_index + i) % src_size] * filter[i]; + }else if(sample_index + c->filter_length > src_size){ + break; + }else if(c->linear){ + FELEM2 v2=0; + for(i=0; i<c->filter_length; i++){ + val += src[sample_index + i] * (FELEM2)filter[i]; + v2 += src[sample_index + i] * (FELEM2)filter[i + c->filter_length]; + } + val+=(v2-val)*(FELEML)frac / c->src_incr; + }else{ + for(i=0; i<c->filter_length; i++){ + val += src[sample_index + i] * (FELEM2)filter[i]; + } + } + +#ifdef CONFIG_RESAMPLE_AUDIOPHILE_KIDDY_MODE + dst[dst_index] = av_clip_int16(lrintf(val)); +#else + val = (val + (1<<(FILTER_SHIFT-1)))>>FILTER_SHIFT; + dst[dst_index] = (unsigned)(val + 32768) > 65535 ? (val>>31) ^ 32767 : val; +#endif + + frac += dst_incr_frac; + index += dst_incr; + if(frac >= c->src_incr){ + frac -= c->src_incr; + index++; + } + + if(dst_index + 1 == compensation_distance){ + compensation_distance= 0; + dst_incr_frac= c->ideal_dst_incr % c->src_incr; + dst_incr= c->ideal_dst_incr / c->src_incr; + } + } + } + *consumed= FFMAX(index, 0) >> c->phase_shift; + if(index>=0) index &= c->phase_mask; + + if(compensation_distance){ + compensation_distance -= dst_index; + assert(compensation_distance > 0); + } + if(update_ctx){ + c->frac= frac; + c->index= index; + c->dst_incr= dst_incr_frac + c->src_incr*dst_incr; + c->compensation_distance= compensation_distance; + } +#if 0 + if(update_ctx && !c->compensation_distance){ +#undef rand + av_resample_compensate(c, rand() % (8000*2) - 8000, 8000*2); +av_log(NULL, AV_LOG_DEBUG, "%d %d %d\n", c->dst_incr, c->ideal_dst_incr, c->compensation_distance); + } +#endif + + return dst_index; +} diff --git a/src/pulsecore/filter/LICENSE.WEBKIT b/src/pulsecore/filter/LICENSE.WEBKIT new file mode 100644 index 0000000..2f69d9f --- /dev/null +++ b/src/pulsecore/filter/LICENSE.WEBKIT @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ diff --git a/src/pulsecore/filter/biquad.c b/src/pulsecore/filter/biquad.c new file mode 100644 index 0000000..3205e7c --- /dev/null +++ b/src/pulsecore/filter/biquad.c @@ -0,0 +1,117 @@ +/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* Copyright (C) 2010 Google Inc. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE.WEBKIT file. + */ + + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulsecore/macro.h> + +#include <math.h> +#include "biquad.h" + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +#ifndef M_SQRT2 +#define M_SQRT2 1.41421356237309504880 +#endif + +static void set_coefficient(struct biquad *bq, double b0, double b1, double b2, + double a0, double a1, double a2) +{ + double a0_inv = 1 / a0; + bq->b0 = b0 * a0_inv; + bq->b1 = b1 * a0_inv; + bq->b2 = b2 * a0_inv; + bq->a1 = a1 * a0_inv; + bq->a2 = a2 * a0_inv; +} + +static void biquad_lowpass(struct biquad *bq, double cutoff) +{ + /* Limit cutoff to 0 to 1. */ + cutoff = PA_MIN(cutoff, 1.0); + cutoff = PA_MAX(0.0, cutoff); + + if (cutoff >= 1.0) { + /* When cutoff is 1, the z-transform is 1. */ + set_coefficient(bq, 1, 0, 0, 1, 0, 0); + } else if (cutoff > 0) { + /* Compute biquad coefficients for lowpass filter */ + double theta = M_PI * cutoff; + double sn = 0.5 * M_SQRT2 * sin(theta); + double beta = 0.5 * (1 - sn) / (1 + sn); + double gamma_coeff = (0.5 + beta) * cos(theta); + double alpha = 0.25 * (0.5 + beta - gamma_coeff); + + double b0 = 2 * alpha; + double b1 = 2 * 2 * alpha; + double b2 = 2 * alpha; + double a1 = 2 * -gamma_coeff; + double a2 = 2 * beta; + + set_coefficient(bq, b0, b1, b2, 1, a1, a2); + } else { + /* When cutoff is zero, nothing gets through the filter, so set + * coefficients up correctly. + */ + set_coefficient(bq, 0, 0, 0, 1, 0, 0); + } +} + +static void biquad_highpass(struct biquad *bq, double cutoff) +{ + /* Limit cutoff to 0 to 1. */ + cutoff = PA_MIN(cutoff, 1.0); + cutoff = PA_MAX(0.0, cutoff); + + if (cutoff >= 1.0) { + /* The z-transform is 0. */ + set_coefficient(bq, 0, 0, 0, 1, 0, 0); + } else if (cutoff > 0) { + /* Compute biquad coefficients for highpass filter */ + double theta = M_PI * cutoff; + double sn = 0.5 * M_SQRT2 * sin(theta); + double beta = 0.5 * (1 - sn) / (1 + sn); + double gamma_coeff = (0.5 + beta) * cos(theta); + double alpha = 0.25 * (0.5 + beta + gamma_coeff); + + double b0 = 2 * alpha; + double b1 = 2 * -2 * alpha; + double b2 = 2 * alpha; + double a1 = 2 * -gamma_coeff; + double a2 = 2 * beta; + + set_coefficient(bq, b0, b1, b2, 1, a1, a2); + } else { + /* When cutoff is zero, we need to be careful because the above + * gives a quadratic divided by the same quadratic, with poles + * and zeros on the unit circle in the same place. When cutoff + * is zero, the z-transform is 1. + */ + set_coefficient(bq, 1, 0, 0, 1, 0, 0); + } +} + +void biquad_set(struct biquad *bq, enum biquad_type type, double freq) +{ + + switch (type) { + case BQ_LOWPASS: + biquad_lowpass(bq, freq); + break; + case BQ_HIGHPASS: + biquad_highpass(bq, freq); + break; + } +} diff --git a/src/pulsecore/filter/biquad.h b/src/pulsecore/filter/biquad.h new file mode 100644 index 0000000..bb8f2fb --- /dev/null +++ b/src/pulsecore/filter/biquad.h @@ -0,0 +1,45 @@ +/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef BIQUAD_H_ +#define BIQUAD_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* The biquad filter parameters. The transfer function H(z) is (b0 + b1 * z^(-1) + * + b2 * z^(-2)) / (1 + a1 * z^(-1) + a2 * z^(-2)). The previous two inputs + * are stored in x1 and x2, and the previous two outputs are stored in y1 and + * y2. + * + * We use double during the coefficients calculation for better accurary, but + * float is used during the actual filtering for faster computation. + */ +struct biquad { + float b0, b1, b2; + float a1, a2; +}; + +/* The type of the biquad filters */ +enum biquad_type { + BQ_LOWPASS, + BQ_HIGHPASS, +}; + +/* Initialize a biquad filter parameters from its type and parameters. + * Args: + * bq - The biquad filter we want to set. + * type - The type of the biquad filter. + * frequency - The value should be in the range [0, 1]. It is relative to + * half of the sampling rate. + */ +void biquad_set(struct biquad *bq, enum biquad_type type, double freq); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* BIQUAD_H_ */ diff --git a/src/pulsecore/filter/crossover.c b/src/pulsecore/filter/crossover.c new file mode 100644 index 0000000..dab34af --- /dev/null +++ b/src/pulsecore/filter/crossover.c @@ -0,0 +1,97 @@ +/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulsecore/macro.h> + +#include "crossover.h" + +void lr4_set(struct lr4 *lr4, enum biquad_type type, float freq) +{ + biquad_set(&lr4->bq, type, freq); + lr4->x1 = 0; + lr4->x2 = 0; + lr4->y1 = 0; + lr4->y2 = 0; + lr4->z1 = 0; + lr4->z2 = 0; +} + +void lr4_process_float32(struct lr4 *lr4, int samples, int channels, float *src, float *dest) +{ + float lx1 = lr4->x1; + float lx2 = lr4->x2; + float ly1 = lr4->y1; + float ly2 = lr4->y2; + float lz1 = lr4->z1; + float lz2 = lr4->z2; + float lb0 = lr4->bq.b0; + float lb1 = lr4->bq.b1; + float lb2 = lr4->bq.b2; + float la1 = lr4->bq.a1; + float la2 = lr4->bq.a2; + + int i; + for (i = 0; i < samples * channels; i += channels) { + float x, y, z; + x = src[i]; + y = lb0*x + lb1*lx1 + lb2*lx2 - la1*ly1 - la2*ly2; + z = lb0*y + lb1*ly1 + lb2*ly2 - la1*lz1 - la2*lz2; + lx2 = lx1; + lx1 = x; + ly2 = ly1; + ly1 = y; + lz2 = lz1; + lz1 = z; + dest[i] = z; + } + + lr4->x1 = lx1; + lr4->x2 = lx2; + lr4->y1 = ly1; + lr4->y2 = ly2; + lr4->z1 = lz1; + lr4->z2 = lz2; +} + +void lr4_process_s16(struct lr4 *lr4, int samples, int channels, short *src, short *dest) +{ + float lx1 = lr4->x1; + float lx2 = lr4->x2; + float ly1 = lr4->y1; + float ly2 = lr4->y2; + float lz1 = lr4->z1; + float lz2 = lr4->z2; + float lb0 = lr4->bq.b0; + float lb1 = lr4->bq.b1; + float lb2 = lr4->bq.b2; + float la1 = lr4->bq.a1; + float la2 = lr4->bq.a2; + + int i; + for (i = 0; i < samples * channels; i += channels) { + float x, y, z; + x = src[i]; + y = lb0*x + lb1*lx1 + lb2*lx2 - la1*ly1 - la2*ly2; + z = lb0*y + lb1*ly1 + lb2*ly2 - la1*lz1 - la2*lz2; + lx2 = lx1; + lx1 = x; + ly2 = ly1; + ly1 = y; + lz2 = lz1; + lz1 = z; + dest[i] = PA_CLAMP_UNLIKELY((int) z, -0x8000, 0x7fff); + } + + lr4->x1 = lx1; + lr4->x2 = lx2; + lr4->y1 = ly1; + lr4->y2 = ly2; + lr4->z1 = lz1; + lr4->z2 = lz2; +} diff --git a/src/pulsecore/filter/crossover.h b/src/pulsecore/filter/crossover.h new file mode 100644 index 0000000..c5c9765 --- /dev/null +++ b/src/pulsecore/filter/crossover.h @@ -0,0 +1,29 @@ +/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef CROSSOVER_H_ +#define CROSSOVER_H_ + +#include "biquad.h" +/* An LR4 filter is two biquads with the same parameters connected in series: + * + * x -- [BIQUAD] -- y -- [BIQUAD] -- z + * + * Both biquad filter has the same parameter b[012] and a[12], + * The variable [xyz][12] keep the history values. + */ +struct lr4 { + struct biquad bq; + float x1, x2; + float y1, y2; + float z1, z2; +}; + +void lr4_set(struct lr4 *lr4, enum biquad_type type, float freq); + +void lr4_process_float32(struct lr4 *lr4, int samples, int channels, float *src, float *dest); +void lr4_process_s16(struct lr4 *lr4, int samples, int channels, short *src, short *dest); + +#endif /* CROSSOVER_H_ */ diff --git a/src/pulsecore/filter/lfe-filter.c b/src/pulsecore/filter/lfe-filter.c new file mode 100644 index 0000000..c0b1eb0 --- /dev/null +++ b/src/pulsecore/filter/lfe-filter.c @@ -0,0 +1,201 @@ +/*** + This file is part of PulseAudio. + + Copyright 2014 David Henningsson, Canonical Ltd. + + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "lfe-filter.h" +#include <pulse/xmalloc.h> +#include <pulsecore/flist.h> +#include <pulsecore/llist.h> +#include <pulsecore/filter/biquad.h> +#include <pulsecore/filter/crossover.h> + +struct saved_state { + PA_LLIST_FIELDS(struct saved_state); + pa_memchunk chunk; + int64_t index; + struct lr4 lr4[PA_CHANNELS_MAX]; +}; + +PA_STATIC_FLIST_DECLARE(lfe_state, 0, pa_xfree); + +/* An LR4 filter, implemented as a chain of two Butterworth filters. + + Currently the channel map is fixed so that a highpass filter is applied to all + channels except for the LFE channel, where a lowpass filter is applied. + This works well for e g stereo to 2.1/5.1/7.1 scenarios, where the remap engine + has calculated the LFE channel to be the average of all source channels. +*/ + +struct pa_lfe_filter { + int64_t index; + PA_LLIST_HEAD(struct saved_state, saved); + float crossover; + pa_channel_map cm; + pa_sample_spec ss; + size_t maxrewind; + bool active; + struct lr4 lr4[PA_CHANNELS_MAX]; +}; + +static void remove_state(pa_lfe_filter_t *f, struct saved_state *s) { + PA_LLIST_REMOVE(struct saved_state, f->saved, s); + pa_memblock_unref(s->chunk.memblock); + pa_xfree(s); +} + +pa_lfe_filter_t * pa_lfe_filter_new(const pa_sample_spec* ss, const pa_channel_map* cm, float crossover_freq, size_t maxrewind) { + + pa_lfe_filter_t *f = pa_xnew0(struct pa_lfe_filter, 1); + f->crossover = crossover_freq; + f->cm = *cm; + f->ss = *ss; + f->maxrewind = maxrewind; + pa_lfe_filter_update_rate(f, ss->rate); + return f; +} + +void pa_lfe_filter_free(pa_lfe_filter_t *f) { + while (f->saved) + remove_state(f, f->saved); + + pa_xfree(f); +} + +void pa_lfe_filter_reset(pa_lfe_filter_t *f) { + pa_lfe_filter_update_rate(f, f->ss.rate); +} + +static void process_block(pa_lfe_filter_t *f, pa_memchunk *buf, bool store_result) { + int samples = buf->length / pa_frame_size(&f->ss); + + void *garbage = store_result ? NULL : pa_xmalloc(buf->length); + + if (f->ss.format == PA_SAMPLE_FLOAT32NE) { + int i; + float *data = pa_memblock_acquire_chunk(buf); + for (i = 0; i < f->cm.channels; i++) + lr4_process_float32(&f->lr4[i], samples, f->cm.channels, &data[i], garbage ? garbage : &data[i]); + pa_memblock_release(buf->memblock); + } + else if (f->ss.format == PA_SAMPLE_S16NE) { + int i; + short *data = pa_memblock_acquire_chunk(buf); + for (i = 0; i < f->cm.channels; i++) + lr4_process_s16(&f->lr4[i], samples, f->cm.channels, &data[i], garbage ? garbage : &data[i]); + pa_memblock_release(buf->memblock); + } + else pa_assert_not_reached(); + + pa_xfree(garbage); + f->index += samples; +} + +pa_memchunk * pa_lfe_filter_process(pa_lfe_filter_t *f, pa_memchunk *buf) { + pa_mempool *pool; + struct saved_state *s, *s2; + void *data; + + if (!f->active || !buf->length) + return buf; + + /* Remove old states (FIXME: we could do better than searching the entire array here?) */ + PA_LLIST_FOREACH_SAFE(s, s2, f->saved) + if (s->index + (int64_t) (s->chunk.length / pa_frame_size(&f->ss) + f->maxrewind) < f->index) + remove_state(f, s); + + /* Insert our existing state into the flist */ + if ((s = pa_flist_pop(PA_STATIC_FLIST_GET(lfe_state))) == NULL) + s = pa_xnew(struct saved_state, 1); + PA_LLIST_INIT(struct saved_state, s); + + /* TODO: This actually memcpys the entire chunk into a new allocation, because we need to retain the original + in case of rewinding. Investigate whether this can be avoided. */ + data = pa_memblock_acquire_chunk(buf); + pool = pa_memblock_get_pool(buf->memblock); + s->chunk.memblock = pa_memblock_new_malloced(pool, pa_xmemdup(data, buf->length), buf->length); + s->chunk.length = buf->length; + s->chunk.index = 0; + pa_memblock_release(buf->memblock); + pa_mempool_unref(pool), pool = NULL; + + s->index = f->index; + memcpy(s->lr4, f->lr4, sizeof(struct lr4) * f->cm.channels); + PA_LLIST_PREPEND(struct saved_state, f->saved, s); + + process_block(f, buf, true); + return buf; +} + +void pa_lfe_filter_update_rate(pa_lfe_filter_t *f, uint32_t new_rate) { + int i; + float biquad_freq = f->crossover / (new_rate / 2); + + while (f->saved) + remove_state(f, f->saved); + + f->index = 0; + f->ss.rate = new_rate; + if (biquad_freq <= 0 || biquad_freq >= 1) { + pa_log_warn("Crossover frequency (%f) outside range for sample rate %d", f->crossover, new_rate); + f->active = false; + return; + } + + for (i = 0; i < f->cm.channels; i++) + lr4_set(&f->lr4[i], f->cm.map[i] == PA_CHANNEL_POSITION_LFE ? BQ_LOWPASS : BQ_HIGHPASS, biquad_freq); + + f->active = true; +} + +void pa_lfe_filter_rewind(pa_lfe_filter_t *f, size_t amount) { + struct saved_state *i, *s = NULL; + size_t samples = amount / pa_frame_size(&f->ss); + f->index -= samples; + + /* Find the closest saved position */ + PA_LLIST_FOREACH(i, f->saved) { + if (i->index > f->index) + continue; + if (s == NULL || i->index > s->index) + s = i; + } + if (s == NULL) { + pa_log_debug("Rewinding LFE filter %zu samples to position %lli. No saved state found", samples, (long long) f->index); + pa_lfe_filter_update_rate(f, f->ss.rate); + return; + } + pa_log_debug("Rewinding LFE filter %zu samples to position %lli. Found saved state at position %lli", + samples, (long long) f->index, (long long) s->index); + memcpy(f->lr4, s->lr4, sizeof(struct lr4) * f->cm.channels); + + /* now fast forward to the actual position */ + if (f->index > s->index) { + pa_memchunk x = s->chunk; + x.length = (f->index - s->index) * pa_frame_size(&f->ss); + if (x.length > s->chunk.length) { + pa_log_error("Hole in stream, cannot fast forward LFE filter"); + return; + } + f->index = s->index; + process_block(f, &x, false); + } +} diff --git a/src/pulsecore/filter/lfe-filter.h b/src/pulsecore/filter/lfe-filter.h new file mode 100644 index 0000000..54d695b --- /dev/null +++ b/src/pulsecore/filter/lfe-filter.h @@ -0,0 +1,39 @@ +#ifndef foolfefilterhfoo +#define foolfefilterhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2014 David Henningsson, Canonical Ltd. + + 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.1 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. +***/ + +#include <pulse/sample.h> +#include <pulse/channelmap.h> +#include <pulsecore/memchunk.h> +#include <pulsecore/memblockq.h> + +typedef struct pa_lfe_filter pa_lfe_filter_t; + +pa_lfe_filter_t * pa_lfe_filter_new(const pa_sample_spec* ss, const pa_channel_map* cm, float crossover_freq, size_t maxrewind); +void pa_lfe_filter_free(pa_lfe_filter_t *); +void pa_lfe_filter_reset(pa_lfe_filter_t *); +void pa_lfe_filter_rewind(pa_lfe_filter_t *, size_t amount); +pa_memchunk * pa_lfe_filter_process(pa_lfe_filter_t *filter, pa_memchunk *buf); +void pa_lfe_filter_update_rate(pa_lfe_filter_t *, uint32_t new_rate); + +#endif diff --git a/src/pulsecore/flist.c b/src/pulsecore/flist.c new file mode 100644 index 0000000..8d2e643 --- /dev/null +++ b/src/pulsecore/flist.c @@ -0,0 +1,177 @@ +/*** + This file is part of PulseAudio. + + Copyright 2006-2008 Lennart Poettering + Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + Copyright (C) 2012 Canonical Ltd. + + Contact: Jyri Sarha <Jyri.Sarha@nokia.com> + + 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/xmalloc.h> + +#include <pulsecore/atomic.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> +#include <pulsecore/core-util.h> +#include <pulsecore/macro.h> + +#include "flist.h" + +#define FLIST_SIZE 256 + +/* Atomic table indices contain + sign bit = if set, indicates empty/NULL value + tag bits (to avoid the ABA problem) + actual index bits +*/ + +/* Lock free single linked list element. */ +struct pa_flist_elem { + pa_atomic_t next; + pa_atomic_ptr_t ptr; +}; + +typedef struct pa_flist_elem pa_flist_elem; + +struct pa_flist { + char *name; + unsigned size; + + pa_atomic_t current_tag; + int index_mask; + int tag_shift; + int tag_mask; + + /* Stack that contains pointers stored into free list */ + pa_atomic_t stored; + /* Stack that contains empty list elements */ + pa_atomic_t empty; + pa_flist_elem table[]; +}; + +/* Lock free pop from linked list stack */ +static pa_flist_elem *stack_pop(pa_flist *flist, pa_atomic_t *list) { + pa_flist_elem *popped; + int idx; + pa_assert(list); + + do { + idx = pa_atomic_load(list); + if (idx < 0) + return NULL; + popped = &flist->table[idx & flist->index_mask]; + } while (!pa_atomic_cmpxchg(list, idx, pa_atomic_load(&popped->next))); + + return popped; +} + +/* Lock free push to linked list stack */ +static void stack_push(pa_flist *flist, pa_atomic_t *list, pa_flist_elem *new_elem) { + int tag, newindex, next; + pa_assert(list); + + tag = pa_atomic_inc(&flist->current_tag); + newindex = new_elem - flist->table; + pa_assert(newindex >= 0 && newindex < (int) flist->size); + newindex |= (tag << flist->tag_shift) & flist->tag_mask; + + do { + next = pa_atomic_load(list); + pa_atomic_store(&new_elem->next, next); + } while (!pa_atomic_cmpxchg(list, next, newindex)); +} + +pa_flist *pa_flist_new_with_name(unsigned size, const char *name) { + pa_flist *l; + unsigned i; + pa_assert(name); + + if (!size) + size = FLIST_SIZE; + + l = pa_xmalloc0(sizeof(pa_flist) + sizeof(pa_flist_elem) * size); + + l->name = pa_xstrdup(name); + l->size = size; + + while (1 << l->tag_shift < (int) size) + l->tag_shift++; + l->index_mask = (1 << l->tag_shift) - 1; + l->tag_mask = INT_MAX - l->index_mask; + + pa_atomic_store(&l->stored, -1); + pa_atomic_store(&l->empty, -1); + for (i=0; i < size; i++) { + stack_push(l, &l->empty, &l->table[i]); + } + return l; +} + +pa_flist *pa_flist_new(unsigned size) { + return pa_flist_new_with_name(size, "unknown"); +} + +void pa_flist_free(pa_flist *l, pa_free_cb_t free_cb) { + pa_assert(l); + pa_assert(l->name); + + if (free_cb) { + pa_flist_elem *elem; + while((elem = stack_pop(l, &l->stored))) + free_cb(pa_atomic_ptr_load(&elem->ptr)); + } + + pa_xfree(l->name); + pa_xfree(l); +} + +int pa_flist_push(pa_flist *l, void *p) { + pa_flist_elem *elem; + pa_assert(l); + pa_assert(p); + + elem = stack_pop(l, &l->empty); + if (elem == NULL) { + if (pa_log_ratelimit(PA_LOG_DEBUG)) + pa_log_debug("%s flist is full (don't worry)", l->name); + return -1; + } + pa_atomic_ptr_store(&elem->ptr, p); + stack_push(l, &l->stored, elem); + + return 0; +} + +void* pa_flist_pop(pa_flist *l) { + pa_flist_elem *elem; + void *ptr; + pa_assert(l); + + elem = stack_pop(l, &l->stored); + if (elem == NULL) + return NULL; + + ptr = pa_atomic_ptr_load(&elem->ptr); + + stack_push(l, &l->empty, elem); + + return ptr; +} diff --git a/src/pulsecore/flist.h b/src/pulsecore/flist.h new file mode 100644 index 0000000..0341208 --- /dev/null +++ b/src/pulsecore/flist.h @@ -0,0 +1,70 @@ +#ifndef foopulseflisthfoo +#define foopulseflisthfoo + +/*** + This file is part of PulseAudio. + + Copyright 2006-2008 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulse/def.h> +#include <pulse/gccmacro.h> + +#include <pulsecore/once.h> +#include <pulsecore/core-util.h> + +/* A multiple-reader multipler-write lock-free free list implementation */ + +typedef struct pa_flist pa_flist; + +pa_flist * pa_flist_new(unsigned size); +/* Name string is copied and added to flist structure. The original is + * responsibility of the caller. The name is only used for debug printing. */ +pa_flist * pa_flist_new_with_name(unsigned size, const char *name); +void pa_flist_free(pa_flist *l, pa_free_cb_t free_cb); + +/* Please note that this routine might fail! */ +int pa_flist_push(pa_flist*l, void *p); +void* pa_flist_pop(pa_flist*l); + +/* Please note that the destructor stuff is not really necessary, we do + * this just to make valgrind output more useful. */ + +#define PA_STATIC_FLIST_DECLARE(name, size, free_cb) \ + static struct { \ + pa_flist *volatile flist; \ + pa_once once; \ + } name##_flist = { NULL, PA_ONCE_INIT }; \ + static void name##_flist_init(void) { \ + name##_flist.flist = \ + pa_flist_new_with_name(size, __FILE__ ": " #name); \ + } \ + static inline pa_flist* name##_flist_get(void) { \ + pa_run_once(&name##_flist.once, name##_flist_init); \ + return name##_flist.flist; \ + } \ + static void name##_flist_destructor(void) PA_GCC_DESTRUCTOR; \ + static void name##_flist_destructor(void) { \ + if (!pa_in_valgrind()) \ + return; \ + if (name##_flist.flist) \ + pa_flist_free(name##_flist.flist, (free_cb)); \ + } \ + struct __stupid_useless_struct_to_allow_trailing_semicolon + +#define PA_STATIC_FLIST_GET(name) (name##_flist_get()) + +#endif diff --git a/src/pulsecore/g711.c b/src/pulsecore/g711.c new file mode 100644 index 0000000..aa2d703 --- /dev/null +++ b/src/pulsecore/g711.c @@ -0,0 +1,2531 @@ +/* + * This source code is a product of Sun Microsystems, Inc. and is provided + * for unrestricted use. Users may copy or modify this source code without + * charge. + * + * SUN SOURCE CODE IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING + * THE WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR + * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE. + * + * Sun source code is provided with no support and without any obligation on + * the part of Sun Microsystems, Inc. to assist in its use, correction, + * modification or enhancement. + * + * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE + * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY THIS SOFTWARE + * OR ANY PART THEREOF. + * + * In no event will Sun Microsystems, Inc. be liable for any lost revenue + * or profits or other special, indirect and consequential damages, even if + * Sun has been advised of the possibility of such damages. + * + * Sun Microsystems, Inc. + * 2550 Garcia Avenue + * Mountain View, California 94043 + */ + +/* + * g711.c + * + * u-law, A-law and linear PCM conversions. + */ + +/* + * December 30, 1994: + * Functions linear2alaw, linear2ulaw have been updated to correctly + * convert unquantized 16 bit values. + * Tables for direct u- to A-law and A- to u-law conversions have been + * corrected. + * Borge Lindberg, Center for PersonKommunikation, Aalborg University. + * bli@cpk.auc.dk + * + */ + +#include "g711.h" + +#define SIGN_BIT (0x80) /* Sign bit for a A-law byte. */ +#define QUANT_MASK (0xf) /* Quantization field mask. */ +#define NSEGS (8) /* Number of A-law segments. */ +#define SEG_SHIFT (4) /* Left shift for segment number. */ +#define SEG_MASK (0x70) /* Segment field mask. */ + +#if !defined(FAST_ALAW_CONVERSION) || !defined(FAST_ULAW_CONVERSION) +static int16_t seg_aend[8] = {0x1F, 0x3F, 0x7F, 0xFF, + 0x1FF, 0x3FF, 0x7FF, 0xFFF}; +static int16_t seg_uend[8] = {0x3F, 0x7F, 0xFF, 0x1FF, + 0x3FF, 0x7FF, 0xFFF, 0x1FFF}; + +static int16_t search( + int16_t val, + int16_t *table, + int size) +{ + int i; + + for (i = 0; i < size; i++) { + if (val <= *table++) + return (i); + } + return (size); +} +#endif /* !FAST_*_CONVERSION */ + +#ifndef FAST_ALAW_CONVERSION +/* + * linear2alaw() accepts an 13-bit signed integer and encodes it as A-law data + * stored in a unsigned char. This function should only be called with + * the data shifted such that it only contains information in the lower + * 13-bits. + * + * Linear Input Code Compressed Code + * ------------------------ --------------- + * 0000000wxyza 000wxyz + * 0000001wxyza 001wxyz + * 000001wxyzab 010wxyz + * 00001wxyzabc 011wxyz + * 0001wxyzabcd 100wxyz + * 001wxyzabcde 101wxyz + * 01wxyzabcdef 110wxyz + * 1wxyzabcdefg 111wxyz + * + * For further information see John C. Bellamy's Digital Telephony, 1982, + * John Wiley & Sons, pps 98-111 and 472-476. + */ +unsigned char st_13linear2alaw( + int16_t pcm_val) /* 2's complement (13-bit range) */ +{ + int16_t mask; + short seg; + unsigned char aval; + + /* Have calling software do it since its already doing a shift + * from 32-bits down to 16-bits. + */ + /* pcm_val = pcm_val >> 3; */ + + /* A-law using even bit inversion */ + if (pcm_val >= 0) { + mask = 0xD5; /* sign (7th) bit = 1 */ + } else { + mask = 0x55; /* sign bit = 0 */ + pcm_val = -pcm_val - 1; + } + + /* Convert the scaled magnitude to segment number. */ + seg = search(pcm_val, seg_aend, 8); + + /* Combine the sign, segment, and quantization bits. */ + + if (seg >= 8) /* out of range, return maximum value. */ + return (unsigned char) (0x7F ^ mask); + else { + aval = (unsigned char) seg << SEG_SHIFT; + if (seg < 2) + aval |= (pcm_val >> 1) & QUANT_MASK; + else + aval |= (pcm_val >> seg) & QUANT_MASK; + return (aval ^ mask); + } +} + +/* + * alaw2linear() - Convert an A-law value to 16-bit signed linear PCM + * + */ +int16_t st_alaw2linear16( + unsigned char a_val) +{ + int16_t t; + int16_t seg; + + a_val ^= 0x55; + + t = (a_val & QUANT_MASK) << 4; + seg = ((unsigned)a_val & SEG_MASK) >> SEG_SHIFT; + switch (seg) { + case 0: + t += 8; + break; + case 1: + t += 0x108; + break; + default: + t += 0x108; + t <<= seg - 1; + } + return ((a_val & SIGN_BIT) ? t : -t); +} +#endif /* !FAST_ALAW_CONVERSION */ + +#define BIAS (0x84) /* Bias for linear code. */ +#define CLIP 8159 + +#ifndef FAST_ULAW_CONVERSION +/* + * linear2ulaw() accepts a 14-bit signed integer and encodes it as u-law data + * stored in a unsigned char. This function should only be called with + * the data shifted such that it only contains information in the lower + * 14-bits. + * + * In order to simplify the encoding process, the original linear magnitude + * is biased by adding 33 which shifts the encoding range from (0 - 8158) to + * (33 - 8191). The result can be seen in the following encoding table: + * + * Biased Linear Input Code Compressed Code + * ------------------------ --------------- + * 00000001wxyza 000wxyz + * 0000001wxyzab 001wxyz + * 000001wxyzabc 010wxyz + * 00001wxyzabcd 011wxyz + * 0001wxyzabcde 100wxyz + * 001wxyzabcdef 101wxyz + * 01wxyzabcdefg 110wxyz + * 1wxyzabcdefgh 111wxyz + * + * Each biased linear code has a leading 1 which identifies the segment + * number. The value of the segment number is equal to 7 minus the number + * of leading 0's. The quantization interval is directly available as the + * four bits wxyz. * The trailing bits (a - h) are ignored. + * + * Ordinarily the complement of the resulting code word is used for + * transmission, and so the code word is complemented before it is returned. + * + * For further information see John C. Bellamy's Digital Telephony, 1982, + * John Wiley & Sons, pps 98-111 and 472-476. + */ +unsigned char st_14linear2ulaw( + int16_t pcm_val) /* 2's complement (14-bit range) */ +{ + int16_t mask; + int16_t seg; + unsigned char uval; + + /* Have calling software do it since its already doing a shift + * from 32-bits down to 16-bits. + */ + /* pcm_val = pcm_val >> 2; */ + + /* u-law inverts all bits */ + /* Get the sign and the magnitude of the value. */ + if (pcm_val < 0) { + pcm_val = -pcm_val; + mask = 0x7F; + } else { + mask = 0xFF; + } + if ( pcm_val > CLIP ) pcm_val = CLIP; /* clip the magnitude */ + pcm_val += (BIAS >> 2); + + /* Convert the scaled magnitude to segment number. */ + seg = search(pcm_val, seg_uend, 8); + + /* + * Combine the sign, segment, quantization bits; + * and complement the code word. + */ + if (seg >= 8) /* out of range, return maximum value. */ + return (unsigned char) (0x7F ^ mask); + else { + uval = (unsigned char) (seg << 4) | ((pcm_val >> (seg + 1)) & 0xF); + return (uval ^ mask); + } + +} + +/* + * ulaw2linear() - Convert a u-law value to 16-bit linear PCM + * + * First, a biased linear code is derived from the code word. An unbiased + * output can then be obtained by subtracting 33 from the biased code. + * + * Note that this function expects to be passed the complement of the + * original code word. This is in keeping with ISDN conventions. + */ +int16_t st_ulaw2linear16( + unsigned char u_val) +{ + int16_t t; + + /* Complement to obtain normal u-law value. */ + u_val = ~u_val; + + /* + * Extract and bias the quantization bits. Then + * shift up by the segment number and subtract out the bias. + */ + t = ((u_val & QUANT_MASK) << 3) + BIAS; + t <<= ((unsigned)u_val & SEG_MASK) >> SEG_SHIFT; + + return ((u_val & SIGN_BIT) ? (BIAS - t) : (t - BIAS)); +} +#endif /* !FAST_ULAW_CONVERSION */ + +#ifdef FAST_ALAW_CONVERSION + +int16_t _st_alaw2linear16[256] = { + -5504, -5248, -6016, -5760, -4480, -4224, -4992, + -4736, -7552, -7296, -8064, -7808, -6528, -6272, + -7040, -6784, -2752, -2624, -3008, -2880, -2240, + -2112, -2496, -2368, -3776, -3648, -4032, -3904, + -3264, -3136, -3520, -3392, -22016, -20992, -24064, + -23040, -17920, -16896, -19968, -18944, -30208, -29184, + -32256, -31232, -26112, -25088, -28160, -27136, -11008, + -10496, -12032, -11520, -8960, -8448, -9984, -9472, + -15104, -14592, -16128, -15616, -13056, -12544, -14080, + -13568, -344, -328, -376, -360, -280, -264, + -312, -296, -472, -456, -504, -488, -408, + -392, -440, -424, -88, -72, -120, -104, + -24, -8, -56, -40, -216, -200, -248, + -232, -152, -136, -184, -168, -1376, -1312, + -1504, -1440, -1120, -1056, -1248, -1184, -1888, + -1824, -2016, -1952, -1632, -1568, -1760, -1696, + -688, -656, -752, -720, -560, -528, -624, + -592, -944, -912, -1008, -976, -816, -784, + -880, -848, 5504, 5248, 6016, 5760, 4480, + 4224, 4992, 4736, 7552, 7296, 8064, 7808, + 6528, 6272, 7040, 6784, 2752, 2624, 3008, + 2880, 2240, 2112, 2496, 2368, 3776, 3648, + 4032, 3904, 3264, 3136, 3520, 3392, 22016, + 20992, 24064, 23040, 17920, 16896, 19968, 18944, + 30208, 29184, 32256, 31232, 26112, 25088, 28160, + 27136, 11008, 10496, 12032, 11520, 8960, 8448, + 9984, 9472, 15104, 14592, 16128, 15616, 13056, + 12544, 14080, 13568, 344, 328, 376, 360, + 280, 264, 312, 296, 472, 456, 504, + 488, 408, 392, 440, 424, 88, 72, + 120, 104, 24, 8, 56, 40, 216, + 200, 248, 232, 152, 136, 184, 168, + 1376, 1312, 1504, 1440, 1120, 1056, 1248, + 1184, 1888, 1824, 2016, 1952, 1632, 1568, + 1760, 1696, 688, 656, 752, 720, 560, + 528, 624, 592, 944, 912, 1008, 976, + 816, 784, 880, 848 +}; + +uint8_t _st_13linear2alaw[0x2000] = { + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x3a, 0x3a, 0x3a, 0x3a, + 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, + 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, + 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, + 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, + 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, + 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, + 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, + 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, + 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, + 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, + 0x3b, 0x3b, 0x3b, 0x3b, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, + 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, + 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, + 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, + 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, + 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x39, 0x39, 0x39, 0x39, + 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, + 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, + 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, + 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, + 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, + 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, + 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, + 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, + 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, + 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, + 0x3e, 0x3e, 0x3e, 0x3e, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, + 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, + 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, + 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, + 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, + 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3c, 0x3c, 0x3c, 0x3c, + 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, + 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, + 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, + 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, + 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, + 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, + 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, + 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, + 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, + 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, + 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, + 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, + 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, + 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, + 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, + 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, + 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, + 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, + 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, + 0x37, 0x37, 0x37, 0x37, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x35, 0x35, 0x35, 0x35, + 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, + 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, + 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, + 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, + 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, + 0x1b, 0x1b, 0x1b, 0x1b, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x19, 0x19, 0x19, 0x19, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x16, 0x16, 0x16, 0x16, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6b, 0x6b, 0x6b, 0x6b, + 0x6b, 0x6b, 0x6b, 0x6b, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x6e, 0x6e, 0x6e, 0x6e, + 0x6e, 0x6e, 0x6e, 0x6e, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, + 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6d, 0x6d, 0x6d, 0x6d, + 0x6d, 0x6d, 0x6d, 0x6d, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x60, 0x60, 0x60, 0x60, + 0x60, 0x60, 0x60, 0x60, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x67, 0x67, 0x67, 0x67, + 0x67, 0x67, 0x67, 0x67, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, + 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x7a, 0x7a, 0x7a, 0x7a, + 0x7b, 0x7b, 0x7b, 0x7b, 0x78, 0x78, 0x78, 0x78, 0x79, 0x79, 0x79, 0x79, + 0x7e, 0x7e, 0x7e, 0x7e, 0x7f, 0x7f, 0x7f, 0x7f, 0x7c, 0x7c, 0x7c, 0x7c, + 0x7d, 0x7d, 0x7d, 0x7d, 0x72, 0x72, 0x72, 0x72, 0x73, 0x73, 0x73, 0x73, + 0x70, 0x70, 0x70, 0x70, 0x71, 0x71, 0x71, 0x71, 0x76, 0x76, 0x76, 0x76, + 0x77, 0x77, 0x77, 0x77, 0x74, 0x74, 0x74, 0x74, 0x75, 0x75, 0x75, 0x75, + 0x4a, 0x4a, 0x4b, 0x4b, 0x48, 0x48, 0x49, 0x49, 0x4e, 0x4e, 0x4f, 0x4f, + 0x4c, 0x4c, 0x4d, 0x4d, 0x42, 0x42, 0x43, 0x43, 0x40, 0x40, 0x41, 0x41, + 0x46, 0x46, 0x47, 0x47, 0x44, 0x44, 0x45, 0x45, 0x5a, 0x5a, 0x5b, 0x5b, + 0x58, 0x58, 0x59, 0x59, 0x5e, 0x5e, 0x5f, 0x5f, 0x5c, 0x5c, 0x5d, 0x5d, + 0x52, 0x52, 0x53, 0x53, 0x50, 0x50, 0x51, 0x51, 0x56, 0x56, 0x57, 0x57, + 0x54, 0x54, 0x55, 0x55, 0xd5, 0xd5, 0xd4, 0xd4, 0xd7, 0xd7, 0xd6, 0xd6, + 0xd1, 0xd1, 0xd0, 0xd0, 0xd3, 0xd3, 0xd2, 0xd2, 0xdd, 0xdd, 0xdc, 0xdc, + 0xdf, 0xdf, 0xde, 0xde, 0xd9, 0xd9, 0xd8, 0xd8, 0xdb, 0xdb, 0xda, 0xda, + 0xc5, 0xc5, 0xc4, 0xc4, 0xc7, 0xc7, 0xc6, 0xc6, 0xc1, 0xc1, 0xc0, 0xc0, + 0xc3, 0xc3, 0xc2, 0xc2, 0xcd, 0xcd, 0xcc, 0xcc, 0xcf, 0xcf, 0xce, 0xce, + 0xc9, 0xc9, 0xc8, 0xc8, 0xcb, 0xcb, 0xca, 0xca, 0xf5, 0xf5, 0xf5, 0xf5, + 0xf4, 0xf4, 0xf4, 0xf4, 0xf7, 0xf7, 0xf7, 0xf7, 0xf6, 0xf6, 0xf6, 0xf6, + 0xf1, 0xf1, 0xf1, 0xf1, 0xf0, 0xf0, 0xf0, 0xf0, 0xf3, 0xf3, 0xf3, 0xf3, + 0xf2, 0xf2, 0xf2, 0xf2, 0xfd, 0xfd, 0xfd, 0xfd, 0xfc, 0xfc, 0xfc, 0xfc, + 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xfe, 0xf9, 0xf9, 0xf9, 0xf9, + 0xf8, 0xf8, 0xf8, 0xf8, 0xfb, 0xfb, 0xfb, 0xfb, 0xfa, 0xfa, 0xfa, 0xfa, + 0xe5, 0xe5, 0xe5, 0xe5, 0xe5, 0xe5, 0xe5, 0xe5, 0xe4, 0xe4, 0xe4, 0xe4, + 0xe4, 0xe4, 0xe4, 0xe4, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7, + 0xe6, 0xe6, 0xe6, 0xe6, 0xe6, 0xe6, 0xe6, 0xe6, 0xe1, 0xe1, 0xe1, 0xe1, + 0xe1, 0xe1, 0xe1, 0xe1, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, + 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe2, 0xe2, 0xe2, 0xe2, + 0xe2, 0xe2, 0xe2, 0xe2, 0xed, 0xed, 0xed, 0xed, 0xed, 0xed, 0xed, 0xed, + 0xec, 0xec, 0xec, 0xec, 0xec, 0xec, 0xec, 0xec, 0xef, 0xef, 0xef, 0xef, + 0xef, 0xef, 0xef, 0xef, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, + 0xe9, 0xe9, 0xe9, 0xe9, 0xe9, 0xe9, 0xe9, 0xe9, 0xe8, 0xe8, 0xe8, 0xe8, + 0xe8, 0xe8, 0xe8, 0xe8, 0xeb, 0xeb, 0xeb, 0xeb, 0xeb, 0xeb, 0xeb, 0xeb, + 0xea, 0xea, 0xea, 0xea, 0xea, 0xea, 0xea, 0xea, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0xb5, 0xb5, 0xb5, 0xb5, + 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, + 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, + 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, + 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, + 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, + 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, + 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, + 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, + 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, + 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, + 0xb4, 0xb4, 0xb4, 0xb4, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, + 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, + 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, + 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, + 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, + 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb6, 0xb6, 0xb6, 0xb6, + 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, + 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, + 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, + 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, + 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, + 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, + 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, + 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, + 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, + 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, + 0xb1, 0xb1, 0xb1, 0xb1, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, + 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, + 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, + 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, + 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, + 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb3, 0xb3, 0xb3, 0xb3, + 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, + 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, + 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, + 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, + 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, + 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, + 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, + 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, + 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, + 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, + 0xb2, 0xb2, 0xb2, 0xb2, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, + 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, + 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, + 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, + 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, + 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbc, 0xbc, 0xbc, 0xbc, + 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, + 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, + 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, + 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, + 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, + 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, + 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, + 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, + 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, + 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xb9, 0xb9, 0xb9, 0xb9, + 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, + 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, + 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, + 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, + 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, + 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, + 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, + 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, + 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, + 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, + 0xb8, 0xb8, 0xb8, 0xb8, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, + 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, + 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, + 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, + 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, + 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xba, 0xba, 0xba, 0xba, + 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, + 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, + 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, + 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, + 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa +}; + +#endif /* FAST_ALAW_CONVERSION */ + +#ifdef FAST_ULAW_CONVERSION + +int16_t _st_ulaw2linear16[256] = { + -32124, -31100, -30076, -29052, -28028, -27004, -25980, + -24956, -23932, -22908, -21884, -20860, -19836, -18812, + -17788, -16764, -15996, -15484, -14972, -14460, -13948, + -13436, -12924, -12412, -11900, -11388, -10876, -10364, + -9852, -9340, -8828, -8316, -7932, -7676, -7420, + -7164, -6908, -6652, -6396, -6140, -5884, -5628, + -5372, -5116, -4860, -4604, -4348, -4092, -3900, + -3772, -3644, -3516, -3388, -3260, -3132, -3004, + -2876, -2748, -2620, -2492, -2364, -2236, -2108, + -1980, -1884, -1820, -1756, -1692, -1628, -1564, + -1500, -1436, -1372, -1308, -1244, -1180, -1116, + -1052, -988, -924, -876, -844, -812, -780, + -748, -716, -684, -652, -620, -588, -556, + -524, -492, -460, -428, -396, -372, -356, + -340, -324, -308, -292, -276, -260, -244, + -228, -212, -196, -180, -164, -148, -132, + -120, -112, -104, -96, -88, -80, -72, + -64, -56, -48, -40, -32, -24, -16, + -8, 0, 32124, 31100, 30076, 29052, 28028, + 27004, 25980, 24956, 23932, 22908, 21884, 20860, + 19836, 18812, 17788, 16764, 15996, 15484, 14972, + 14460, 13948, 13436, 12924, 12412, 11900, 11388, + 10876, 10364, 9852, 9340, 8828, 8316, 7932, + 7676, 7420, 7164, 6908, 6652, 6396, 6140, + 5884, 5628, 5372, 5116, 4860, 4604, 4348, + 4092, 3900, 3772, 3644, 3516, 3388, 3260, + 3132, 3004, 2876, 2748, 2620, 2492, 2364, + 2236, 2108, 1980, 1884, 1820, 1756, 1692, + 1628, 1564, 1500, 1436, 1372, 1308, 1244, + 1180, 1116, 1052, 988, 924, 876, 844, + 812, 780, 748, 716, 684, 652, 620, + 588, 556, 524, 492, 460, 428, 396, + 372, 356, 340, 324, 308, 292, 276, + 260, 244, 228, 212, 196, 180, 164, + 148, 132, 120, 112, 104, 96, 88, + 80, 72, 64, 56, 48, 40, 32, + 24, 16, 8, 0 +}; + +uint8_t _st_14linear2ulaw[0x4000] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, + 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, + 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, + 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, + 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, + 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, + 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, + 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, + 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, + 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, + 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, + 0x1b, 0x1b, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x31, + 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, + 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, + 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, + 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, + 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, + 0x32, 0x32, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x34, 0x34, + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, + 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, + 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, + 0x35, 0x35, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x37, 0x37, + 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, + 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, + 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, + 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, + 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, + 0x38, 0x38, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, + 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, + 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x3a, 0x3a, + 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, + 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, + 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, + 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, + 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, + 0x3b, 0x3b, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, + 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, + 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, + 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, + 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, + 0x3e, 0x3e, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, + 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, + 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x40, 0x40, + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, + 0x40, 0x40, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, + 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, + 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x43, 0x43, + 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, + 0x43, 0x43, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, + 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, + 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x46, 0x46, + 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, + 0x46, 0x46, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, + 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, + 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x49, 0x49, + 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, + 0x49, 0x49, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, + 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, + 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4c, 0x4c, + 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, + 0x4c, 0x4c, 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, + 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, + 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4f, 0x4f, + 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, + 0x4f, 0x4f, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x51, 0x51, + 0x51, 0x51, 0x51, 0x51, 0x51, 0x51, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, + 0x52, 0x52, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x54, 0x54, + 0x54, 0x54, 0x54, 0x54, 0x54, 0x54, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, + 0x55, 0x55, 0x56, 0x56, 0x56, 0x56, 0x56, 0x56, 0x56, 0x56, 0x57, 0x57, + 0x57, 0x57, 0x57, 0x57, 0x57, 0x57, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, + 0x58, 0x58, 0x59, 0x59, 0x59, 0x59, 0x59, 0x59, 0x59, 0x59, 0x5a, 0x5a, + 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5b, 0x5b, 0x5b, 0x5b, 0x5b, 0x5b, + 0x5b, 0x5b, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5d, 0x5d, + 0x5d, 0x5d, 0x5d, 0x5d, 0x5d, 0x5d, 0x5e, 0x5e, 0x5e, 0x5e, 0x5e, 0x5e, + 0x5e, 0x5e, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x60, 0x60, + 0x60, 0x60, 0x61, 0x61, 0x61, 0x61, 0x62, 0x62, 0x62, 0x62, 0x63, 0x63, + 0x63, 0x63, 0x64, 0x64, 0x64, 0x64, 0x65, 0x65, 0x65, 0x65, 0x66, 0x66, + 0x66, 0x66, 0x67, 0x67, 0x67, 0x67, 0x68, 0x68, 0x68, 0x68, 0x69, 0x69, + 0x69, 0x69, 0x6a, 0x6a, 0x6a, 0x6a, 0x6b, 0x6b, 0x6b, 0x6b, 0x6c, 0x6c, + 0x6c, 0x6c, 0x6d, 0x6d, 0x6d, 0x6d, 0x6e, 0x6e, 0x6e, 0x6e, 0x6f, 0x6f, + 0x6f, 0x6f, 0x70, 0x70, 0x71, 0x71, 0x72, 0x72, 0x73, 0x73, 0x74, 0x74, + 0x75, 0x75, 0x76, 0x76, 0x77, 0x77, 0x78, 0x78, 0x79, 0x79, 0x7a, 0x7a, + 0x7b, 0x7b, 0x7c, 0x7c, 0x7d, 0x7d, 0x7e, 0x7e, 0xff, 0xfe, 0xfe, 0xfd, + 0xfd, 0xfc, 0xfc, 0xfb, 0xfb, 0xfa, 0xfa, 0xf9, 0xf9, 0xf8, 0xf8, 0xf7, + 0xf7, 0xf6, 0xf6, 0xf5, 0xf5, 0xf4, 0xf4, 0xf3, 0xf3, 0xf2, 0xf2, 0xf1, + 0xf1, 0xf0, 0xf0, 0xef, 0xef, 0xef, 0xef, 0xee, 0xee, 0xee, 0xee, 0xed, + 0xed, 0xed, 0xed, 0xec, 0xec, 0xec, 0xec, 0xeb, 0xeb, 0xeb, 0xeb, 0xea, + 0xea, 0xea, 0xea, 0xe9, 0xe9, 0xe9, 0xe9, 0xe8, 0xe8, 0xe8, 0xe8, 0xe7, + 0xe7, 0xe7, 0xe7, 0xe6, 0xe6, 0xe6, 0xe6, 0xe5, 0xe5, 0xe5, 0xe5, 0xe4, + 0xe4, 0xe4, 0xe4, 0xe3, 0xe3, 0xe3, 0xe3, 0xe2, 0xe2, 0xe2, 0xe2, 0xe1, + 0xe1, 0xe1, 0xe1, 0xe0, 0xe0, 0xe0, 0xe0, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, + 0xdf, 0xdf, 0xdf, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xdd, + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdc, 0xdc, 0xdc, 0xdc, 0xdc, + 0xdc, 0xdc, 0xdc, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xda, + 0xda, 0xda, 0xda, 0xda, 0xda, 0xda, 0xda, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, + 0xd9, 0xd9, 0xd9, 0xd8, 0xd8, 0xd8, 0xd8, 0xd8, 0xd8, 0xd8, 0xd8, 0xd7, + 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, + 0xd6, 0xd6, 0xd6, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd4, + 0xd4, 0xd4, 0xd4, 0xd4, 0xd4, 0xd4, 0xd4, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, + 0xd3, 0xd3, 0xd3, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd1, + 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, + 0xd0, 0xd0, 0xd0, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, + 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xce, 0xce, 0xce, 0xce, 0xce, + 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, + 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, + 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xca, + 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, + 0xca, 0xca, 0xca, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, + 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, + 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc7, + 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, + 0xc7, 0xc7, 0xc7, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, + 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, + 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc4, + 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, + 0xc4, 0xc4, 0xc4, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, + 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, + 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc1, + 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, + 0xc1, 0xc1, 0xc1, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, + 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, + 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbd, + 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, + 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, + 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, + 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, + 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, + 0xbc, 0xbc, 0xbc, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, + 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, + 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xba, + 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, + 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, + 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, + 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, + 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, + 0xb9, 0xb9, 0xb9, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, + 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, + 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb7, + 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, + 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, + 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, + 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, + 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, + 0xb6, 0xb6, 0xb6, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, + 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, + 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb4, + 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, + 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, + 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, + 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, + 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, + 0xb3, 0xb3, 0xb3, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, + 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, + 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb1, + 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, + 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, + 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, + 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, + 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, + 0xb0, 0xb0, 0xb0, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80 +}; + +#endif /* FAST_ULAW_CONVERSION */ + +/* The following code was used to generate the lookup tables */ +#if 0 +int main() +{ + int x, y, find2a = 0; + + y = 0; + printf("int16_t _st_alaw2linear16[256] = {\n "); + for (x = 0; x < 256; x++) + { + printf("%8d,", st_alaw2linear16(x)); + y++; + if (y == 7) + { + y = 0; + printf("\n "); + } + } + + printf("\n};\n\nuint8_t _st_13linear2alaw[0x2000] = {\n "); + y = 0; + for (x = 0; x < 0x2000; x++) + { + printf(" 0x%02x,", st_13linear2alaw((-0x1000)+x)); + y++; + if (y == 12) + { + y = 0; + printf("\n "); + } + } + + printf("\n};\n\nint16_t _st_ulaw2linear16[256] = {\n "); + y = 0; + for (x = 0; x < 256; x++) + { + printf("%8d,", st_ulaw2linear16(x)); + y++; + if (y == 7) + { + y = 0; + printf("\n "); + } + } + + printf("\n};\n\nuint8_t _st_14linear2ulaw[0x4000] = {\n "); + y = 0; + for (x = 0; x < 0x4000; x++) + { + printf(" 0x%02x,", st_14linear2ulaw((-0x2000)+x)); + y++; + if (y == 12) + { + y = 0; + printf("\n "); + } + } + printf("\n};\n"); + +} +#endif + +/* The following is not used by SoX but kept for reference */ +#if 0 +/* copy from CCITT G.711 specifications */ +unsigned char _u2a[128] = { /* u- to A-law conversions */ + 1, 1, 2, 2, 3, 3, 4, 4, + 5, 5, 6, 6, 7, 7, 8, 8, + 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, + 25, 27, 29, 31, 33, 34, 35, 36, + 37, 38, 39, 40, 41, 42, 43, 44, + 46, 48, 49, 50, 51, 52, 53, 54, + 55, 56, 57, 58, 59, 60, 61, 62, + 64, 65, 66, 67, 68, 69, 70, 71, + 72, 73, 74, 75, 76, 77, 78, 79, +/* corrected: + 81, 82, 83, 84, 85, 86, 87, 88, + should be: */ + 80, 82, 83, 84, 85, 86, 87, 88, + 89, 90, 91, 92, 93, 94, 95, 96, + 97, 98, 99, 100, 101, 102, 103, 104, + 105, 106, 107, 108, 109, 110, 111, 112, + 113, 114, 115, 116, 117, 118, 119, 120, + 121, 122, 123, 124, 125, 126, 127, 128}; + +unsigned char _a2u[128] = { /* A- to u-law conversions */ + 1, 3, 5, 7, 9, 11, 13, 15, + 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, + 32, 32, 33, 33, 34, 34, 35, 35, + 36, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 47, 48, 48, 49, 49, + 50, 51, 52, 53, 54, 55, 56, 57, + 58, 59, 60, 61, 62, 63, 64, 64, + 65, 66, 67, 68, 69, 70, 71, 72, +/* corrected: + 73, 74, 75, 76, 77, 78, 79, 79, + should be: */ + 73, 74, 75, 76, 77, 78, 79, 80, + + 80, 81, 82, 83, 84, 85, 86, 87, + 88, 89, 90, 91, 92, 93, 94, 95, + 96, 97, 98, 99, 100, 101, 102, 103, + 104, 105, 106, 107, 108, 109, 110, 111, + 112, 113, 114, 115, 116, 117, 118, 119, + 120, 121, 122, 123, 124, 125, 126, 127}; + +/* A-law to u-law conversion */ +unsigned char st_alaw2ulaw( + unsigned char aval) +{ + aval &= 0xff; + return (unsigned char) ((aval & 0x80) ? (0xFF ^ _a2u[aval ^ 0xD5]) : + (0x7F ^ _a2u[aval ^ 0x55])); +} + +/* u-law to A-law conversion */ +unsigned char st_ulaw2alaw( + unsigned char uval) +{ + uval &= 0xff; + return (unsigned char) ((uval & 0x80) ? (0xD5 ^ (_u2a[0xFF ^ uval] - 1)) : + (unsigned char) (0x55 ^ (_u2a[0x7F ^ uval] - 1))); +} +#endif diff --git a/src/pulsecore/g711.h b/src/pulsecore/g711.h new file mode 100644 index 0000000..37ebcf7 --- /dev/null +++ b/src/pulsecore/g711.h @@ -0,0 +1,40 @@ +#ifndef foog711hfoo +#define foog711hfoo + +/* g711.h - include for G711 u-law and a-law conversion routines +** +** Copyright (C) 2001 Chris Bagwell +** +** Permission to use, copy, modify, and distribute this software and its +** documentation for any purpose and without fee is hereby granted, provided +** that the above copyright notice appear in all copies and that both that +** copyright notice and this permission notice appear in supporting +** documentation. This software is provided "as is" without express or +** implied warranty. +*/ + +/** Copied from sox -- Lennart Poettering */ + +#include <inttypes.h> + +#ifdef FAST_ALAW_CONVERSION +extern uint8_t _st_13linear2alaw[0x2000]; +extern int16_t _st_alaw2linear16[256]; +#define st_13linear2alaw(sw) (_st_13linear2alaw[(sw + 0x1000)]) +#define st_alaw2linear16(uc) (_st_alaw2linear16[uc]) +#else +unsigned char st_13linear2alaw(int16_t pcm_val); +int16_t st_alaw2linear16(unsigned char); +#endif + +#ifdef FAST_ULAW_CONVERSION +extern uint8_t _st_14linear2ulaw[0x4000]; +extern int16_t _st_ulaw2linear16[256]; +#define st_14linear2ulaw(sw) (_st_14linear2ulaw[(sw + 0x2000)]) +#define st_ulaw2linear16(uc) (_st_ulaw2linear16[uc]) +#else +unsigned char st_14linear2ulaw(int16_t pcm_val); +int16_t st_ulaw2linear16(unsigned char); +#endif + +#endif diff --git a/src/pulsecore/hashmap.c b/src/pulsecore/hashmap.c new file mode 100644 index 0000000..c2fc3f5 --- /dev/null +++ b/src/pulsecore/hashmap.c @@ -0,0 +1,342 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2008 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/idxset.h> +#include <pulsecore/flist.h> +#include <pulsecore/macro.h> + +#include "hashmap.h" + +#define NBUCKETS 127 + +struct hashmap_entry { + void *key; + void *value; + + struct hashmap_entry *bucket_next, *bucket_previous; + struct hashmap_entry *iterate_next, *iterate_previous; +}; + +struct pa_hashmap { + pa_hash_func_t hash_func; + pa_compare_func_t compare_func; + + pa_free_cb_t key_free_func; + pa_free_cb_t value_free_func; + + struct hashmap_entry *iterate_list_head, *iterate_list_tail; + unsigned n_entries; +}; + +#define BY_HASH(h) ((struct hashmap_entry**) ((uint8_t*) (h) + PA_ALIGN(sizeof(pa_hashmap)))) + +PA_STATIC_FLIST_DECLARE(entries, 0, pa_xfree); + +pa_hashmap *pa_hashmap_new_full(pa_hash_func_t hash_func, pa_compare_func_t compare_func, pa_free_cb_t key_free_func, pa_free_cb_t value_free_func) { + pa_hashmap *h; + + h = pa_xmalloc0(PA_ALIGN(sizeof(pa_hashmap)) + NBUCKETS*sizeof(struct hashmap_entry*)); + + h->hash_func = hash_func ? hash_func : pa_idxset_trivial_hash_func; + h->compare_func = compare_func ? compare_func : pa_idxset_trivial_compare_func; + + h->key_free_func = key_free_func; + h->value_free_func = value_free_func; + + h->n_entries = 0; + h->iterate_list_head = h->iterate_list_tail = NULL; + + return h; +} + +pa_hashmap *pa_hashmap_new(pa_hash_func_t hash_func, pa_compare_func_t compare_func) { + return pa_hashmap_new_full(hash_func, compare_func, NULL, NULL); +} + +static void remove_entry(pa_hashmap *h, struct hashmap_entry *e) { + pa_assert(h); + pa_assert(e); + + /* Remove from iteration list */ + if (e->iterate_next) + e->iterate_next->iterate_previous = e->iterate_previous; + else + h->iterate_list_tail = e->iterate_previous; + + if (e->iterate_previous) + e->iterate_previous->iterate_next = e->iterate_next; + else + h->iterate_list_head = e->iterate_next; + + /* Remove from hash table bucket list */ + if (e->bucket_next) + e->bucket_next->bucket_previous = e->bucket_previous; + + if (e->bucket_previous) + e->bucket_previous->bucket_next = e->bucket_next; + else { + unsigned hash = h->hash_func(e->key) % NBUCKETS; + BY_HASH(h)[hash] = e->bucket_next; + } + + if (h->key_free_func) + h->key_free_func(e->key); + + if (pa_flist_push(PA_STATIC_FLIST_GET(entries), e) < 0) + pa_xfree(e); + + pa_assert(h->n_entries >= 1); + h->n_entries--; +} + +void pa_hashmap_free(pa_hashmap *h) { + pa_assert(h); + + pa_hashmap_remove_all(h); + pa_xfree(h); +} + +static struct hashmap_entry *hash_scan(const pa_hashmap *h, unsigned hash, const void *key) { + struct hashmap_entry *e; + pa_assert(h); + pa_assert(hash < NBUCKETS); + + for (e = BY_HASH(h)[hash]; e; e = e->bucket_next) + if (h->compare_func(e->key, key) == 0) + return e; + + return NULL; +} + +int pa_hashmap_put(pa_hashmap *h, void *key, void *value) { + struct hashmap_entry *e; + unsigned hash; + + pa_assert(h); + + hash = h->hash_func(key) % NBUCKETS; + + if (hash_scan(h, hash, key)) + return -1; + + if (!(e = pa_flist_pop(PA_STATIC_FLIST_GET(entries)))) + e = pa_xnew(struct hashmap_entry, 1); + + e->key = key; + e->value = value; + + /* Insert into hash table */ + e->bucket_next = BY_HASH(h)[hash]; + e->bucket_previous = NULL; + if (BY_HASH(h)[hash]) + BY_HASH(h)[hash]->bucket_previous = e; + BY_HASH(h)[hash] = e; + + /* Insert into iteration list */ + e->iterate_previous = h->iterate_list_tail; + e->iterate_next = NULL; + if (h->iterate_list_tail) { + pa_assert(h->iterate_list_head); + h->iterate_list_tail->iterate_next = e; + } else { + pa_assert(!h->iterate_list_head); + h->iterate_list_head = e; + } + h->iterate_list_tail = e; + + h->n_entries++; + pa_assert(h->n_entries >= 1); + + return 0; +} + +void* pa_hashmap_get(const pa_hashmap *h, const void *key) { + unsigned hash; + struct hashmap_entry *e; + + pa_assert(h); + + hash = h->hash_func(key) % NBUCKETS; + + if (!(e = hash_scan(h, hash, key))) + return NULL; + + return e->value; +} + +void* pa_hashmap_remove(pa_hashmap *h, const void *key) { + struct hashmap_entry *e; + unsigned hash; + void *data; + + pa_assert(h); + + hash = h->hash_func(key) % NBUCKETS; + + if (!(e = hash_scan(h, hash, key))) + return NULL; + + data = e->value; + remove_entry(h, e); + + return data; +} + +int pa_hashmap_remove_and_free(pa_hashmap *h, const void *key) { + void *data; + + pa_assert(h); + + data = pa_hashmap_remove(h, key); + + if (data && h->value_free_func) + h->value_free_func(data); + + return data ? 0 : -1; +} + +void pa_hashmap_remove_all(pa_hashmap *h) { + pa_assert(h); + + while (h->iterate_list_head) { + void *data; + data = h->iterate_list_head->value; + remove_entry(h, h->iterate_list_head); + + if (h->value_free_func) + h->value_free_func(data); + } +} + +void *pa_hashmap_iterate(const pa_hashmap *h, void **state, const void **key) { + struct hashmap_entry *e; + + pa_assert(h); + pa_assert(state); + + if (*state == (void*) -1) + goto at_end; + + if (!*state && !h->iterate_list_head) + goto at_end; + + e = *state ? *state : h->iterate_list_head; + + if (e->iterate_next) + *state = e->iterate_next; + else + *state = (void*) -1; + + if (key) + *key = e->key; + + return e->value; + +at_end: + *state = (void *) -1; + + if (key) + *key = NULL; + + return NULL; +} + +void *pa_hashmap_iterate_backwards(const pa_hashmap *h, void **state, const void **key) { + struct hashmap_entry *e; + + pa_assert(h); + pa_assert(state); + + if (*state == (void*) -1) + goto at_beginning; + + if (!*state && !h->iterate_list_tail) + goto at_beginning; + + e = *state ? *state : h->iterate_list_tail; + + if (e->iterate_previous) + *state = e->iterate_previous; + else + *state = (void*) -1; + + if (key) + *key = e->key; + + return e->value; + +at_beginning: + *state = (void *) -1; + + if (key) + *key = NULL; + + return NULL; +} + +void* pa_hashmap_first(const pa_hashmap *h) { + pa_assert(h); + + if (!h->iterate_list_head) + return NULL; + + return h->iterate_list_head->value; +} + +void* pa_hashmap_last(const pa_hashmap *h) { + pa_assert(h); + + if (!h->iterate_list_tail) + return NULL; + + return h->iterate_list_tail->value; +} + +void* pa_hashmap_steal_first(pa_hashmap *h) { + void *data; + + pa_assert(h); + + if (!h->iterate_list_head) + return NULL; + + data = h->iterate_list_head->value; + remove_entry(h, h->iterate_list_head); + + return data; +} + +unsigned pa_hashmap_size(const pa_hashmap *h) { + pa_assert(h); + + return h->n_entries; +} + +bool pa_hashmap_isempty(const pa_hashmap *h) { + pa_assert(h); + + return h->n_entries == 0; +} diff --git a/src/pulsecore/hashmap.h b/src/pulsecore/hashmap.h new file mode 100644 index 0000000..c18a564 --- /dev/null +++ b/src/pulsecore/hashmap.h @@ -0,0 +1,101 @@ +#ifndef foopulsecorehashmaphfoo +#define foopulsecorehashmaphfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2008 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulse/def.h> + +#include <pulsecore/idxset.h> + +/* Simple Implementation of a hash table. Memory management is the + * user's job. It's a good idea to have the key pointer point to a + * string in the value data. The insertion order is preserved when + * iterating. */ + +typedef struct pa_hashmap pa_hashmap; + +/* Create a new hashmap. Use the specified functions for hashing and comparing objects in the map */ +pa_hashmap *pa_hashmap_new(pa_hash_func_t hash_func, pa_compare_func_t compare_func); + +/* Create a new hashmap. Use the specified functions for hashing and comparing objects in the map, and functions to free the key + * and value (either or both can be NULL). */ +pa_hashmap *pa_hashmap_new_full(pa_hash_func_t hash_func, pa_compare_func_t compare_func, pa_free_cb_t key_free_func, pa_free_cb_t value_free_func); + +/* Free the hash table. */ +void pa_hashmap_free(pa_hashmap*); + +/* Add an entry to the hashmap. Returns non-zero when the entry already exists */ +int pa_hashmap_put(pa_hashmap *h, void *key, void *value); + +/* Return an entry from the hashmap */ +void* pa_hashmap_get(const pa_hashmap *h, const void *key); + +/* Returns the data of the entry while removing */ +void* pa_hashmap_remove(pa_hashmap *h, const void *key); + +/* Removes the entry and frees the entry data. Returns a negative value if the + * entry is not found. FIXME: This function shouldn't be needed. + * pa_hashmap_remove() should free the entry data, and the current semantics of + * pa_hashmap_remove() should be implemented by a function called + * pa_hashmap_steal(). */ +int pa_hashmap_remove_and_free(pa_hashmap *h, const void *key); + +/* Remove all entries but don't free the hashmap */ +void pa_hashmap_remove_all(pa_hashmap *h); + +/* Return the current number of entries of the hashmap */ +unsigned pa_hashmap_size(const pa_hashmap *h); + +/* Return true if the hashmap is empty */ +bool pa_hashmap_isempty(const pa_hashmap *h); + +/* May be used to iterate through the hashmap. Initially the opaque + pointer *state has to be set to NULL. The hashmap may not be + modified during iteration -- except for deleting the current entry + via pa_hashmap_remove(). The key of the entry is returned in *key, + if key is non-NULL. After the last entry in the hashmap NULL is + returned. */ +void *pa_hashmap_iterate(const pa_hashmap *h, void **state, const void**key); + +/* Same as pa_hashmap_iterate() but goes backwards */ +void *pa_hashmap_iterate_backwards(const pa_hashmap *h, void **state, const void**key); + +/* Remove the oldest entry in the hashmap and return it */ +void *pa_hashmap_steal_first(pa_hashmap *h); + +/* Return the oldest entry in the hashmap */ +void* pa_hashmap_first(const pa_hashmap *h); + +/* Return the newest entry in the hashmap */ +void* pa_hashmap_last(const pa_hashmap *h); + +/* A macro to ease iteration through all entries */ +#define PA_HASHMAP_FOREACH(e, h, state) \ + for ((state) = NULL, (e) = pa_hashmap_iterate((h), &(state), NULL); (e); (e) = pa_hashmap_iterate((h), &(state), NULL)) + +/* A macro to ease itration through all key, value pairs */ +#define PA_HASHMAP_FOREACH_KV(k, e, h, state) \ + for ((state) = NULL, (e) = pa_hashmap_iterate((h), &(state), (const void **) &(k)); (e); (e) = pa_hashmap_iterate((h), &(state), (const void **) &(k))) + +/* A macro to ease iteration through all entries, backwards */ +#define PA_HASHMAP_FOREACH_BACKWARDS(e, h, state) \ + for ((state) = NULL, (e) = pa_hashmap_iterate_backwards((h), &(state), NULL); (e); (e) = pa_hashmap_iterate_backwards((h), &(state), NULL)) + +#endif diff --git a/src/pulsecore/hook-list.c b/src/pulsecore/hook-list.c new file mode 100644 index 0000000..e59dcba --- /dev/null +++ b/src/pulsecore/hook-list.c @@ -0,0 +1,129 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/xmalloc.h> + +#include <pulsecore/macro.h> + +#include "hook-list.h" + +void pa_hook_init(pa_hook *hook, void *data) { + pa_assert(hook); + + PA_LLIST_HEAD_INIT(pa_hook_slot, hook->slots); + hook->n_dead = hook->n_firing = 0; + hook->data = data; +} + +static void slot_free(pa_hook *hook, pa_hook_slot *slot) { + pa_assert(hook); + pa_assert(slot); + + PA_LLIST_REMOVE(pa_hook_slot, hook->slots, slot); + + pa_xfree(slot); +} + +void pa_hook_done(pa_hook *hook) { + pa_assert(hook); + pa_assert(hook->n_firing == 0); + + while (hook->slots) + slot_free(hook, hook->slots); + + pa_hook_init(hook, NULL); +} + +pa_hook_slot* pa_hook_connect(pa_hook *hook, pa_hook_priority_t prio, pa_hook_cb_t cb, void *data) { + pa_hook_slot *slot, *where, *prev; + + pa_assert(cb); + + slot = pa_xnew(pa_hook_slot, 1); + slot->hook = hook; + slot->dead = false; + slot->callback = cb; + slot->data = data; + slot->priority = prio; + + prev = NULL; + for (where = hook->slots; where; where = where->next) { + if (prio < where->priority) + break; + prev = where; + } + + PA_LLIST_INSERT_AFTER(pa_hook_slot, hook->slots, prev, slot); + + return slot; +} + +void pa_hook_slot_free(pa_hook_slot *slot) { + pa_assert(slot); + pa_assert(!slot->dead); + + if (slot->hook->n_firing > 0) { + slot->dead = true; + slot->hook->n_dead++; + } else + slot_free(slot->hook, slot); +} + +pa_hook_result_t pa_hook_fire(pa_hook *hook, void *data) { + pa_hook_slot *slot, *next; + pa_hook_result_t result = PA_HOOK_OK; + + pa_assert(hook); + + hook->n_firing ++; + + PA_LLIST_FOREACH(slot, hook->slots) { + if (slot->dead) + continue; + + if ((result = slot->callback(hook->data, data, slot->data)) != PA_HOOK_OK) + break; + } + + hook->n_firing --; + pa_assert(hook->n_firing >= 0); + + for (slot = hook->slots; hook->n_dead > 0 && slot; slot = next) { + next = slot->next; + + if (slot->dead) { + slot_free(hook, slot); + hook->n_dead--; + } + } + + pa_assert(hook->n_dead == 0); + + return result; +} + +bool pa_hook_is_firing(pa_hook *hook) { + pa_assert(hook); + + return hook->n_firing > 0; +} diff --git a/src/pulsecore/hook-list.h b/src/pulsecore/hook-list.h new file mode 100644 index 0000000..63037e7 --- /dev/null +++ b/src/pulsecore/hook-list.h @@ -0,0 +1,71 @@ +#ifndef foohooklistfoo +#define foohooklistfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulsecore/llist.h> + +typedef struct pa_hook_slot pa_hook_slot; +typedef struct pa_hook pa_hook; + +typedef enum pa_hook_result { + PA_HOOK_OK = 0, + PA_HOOK_STOP = 1, + PA_HOOK_CANCEL = -1 +} pa_hook_result_t; + +typedef enum pa_hook_priority { + PA_HOOK_EARLY = -100, + PA_HOOK_NORMAL = 0, + PA_HOOK_LATE = 100 +} pa_hook_priority_t; + +typedef pa_hook_result_t (*pa_hook_cb_t)( + void *hook_data, + void *call_data, + void *slot_data); + +struct pa_hook_slot { + bool dead; + pa_hook *hook; + pa_hook_priority_t priority; + pa_hook_cb_t callback; + void *data; + PA_LLIST_FIELDS(pa_hook_slot); +}; + +struct pa_hook { + PA_LLIST_HEAD(pa_hook_slot, slots); + int n_firing, n_dead; + + void *data; +}; + +void pa_hook_init(pa_hook *hook, void *data); +void pa_hook_done(pa_hook *hook); + +pa_hook_slot* pa_hook_connect(pa_hook *hook, pa_hook_priority_t prio, pa_hook_cb_t cb, void *data); +void pa_hook_slot_free(pa_hook_slot *slot); + +pa_hook_result_t pa_hook_fire(pa_hook *hook, void *data); + +bool pa_hook_is_firing(pa_hook *hook); + +#endif diff --git a/src/pulsecore/i18n.c b/src/pulsecore/i18n.c new file mode 100644 index 0000000..1cd74c0 --- /dev/null +++ b/src/pulsecore/i18n.c @@ -0,0 +1,37 @@ +/*** + This file is part of PulseAudio. + + Copyright 2008 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulsecore/once.h> + +#include "i18n.h" + +void pa_init_i18n(void) { +#ifdef ENABLE_NLS + PA_ONCE_BEGIN { + + bindtextdomain(GETTEXT_PACKAGE, PULSE_LOCALEDIR); + bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); + + } PA_ONCE_END; +#endif +} diff --git a/src/pulsecore/i18n.h b/src/pulsecore/i18n.h new file mode 100644 index 0000000..edf894f --- /dev/null +++ b/src/pulsecore/i18n.h @@ -0,0 +1,62 @@ +#ifndef foopulsei18nhfoo +#define foopulsei18nhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2008 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulse/cdecl.h> + +PA_C_DECL_BEGIN + +#ifdef ENABLE_NLS + +#if !defined(GETTEXT_PACKAGE) +#error "Something is very wrong here, config.h needs to be included first" +#endif + +#include <libintl.h> + +#define _(String) dgettext(GETTEXT_PACKAGE, String) +#ifdef gettext_noop +#define N_(String) gettext_noop(String) +#else +#define N_(String) (String) +#endif + +#else /* NLS is disabled */ + +#define _(String) (String) +#define N_(String) (String) +#define textdomain(String) (String) +#define gettext(String) (String) +#define dgettext(Domain,String) (String) +#define dcgettext(Domain,String,Type) (String) +#define ngettext(SingularString,PluralString,N) (PluralString) +#define dngettext(Domain,SingularString,PluralString,N) (PluralString) +#define dcngettext(Domain,SingularString,PluralString,N,Type) (PluralString) +#define bindtextdomain(Domain,Directory) (Domain) +#define bind_textdomain_codeset(Domain,Codeset) (Codeset) + +#endif /* ENABLE_NLS */ + +void pa_init_i18n(void); + +PA_C_DECL_END + +#endif diff --git a/src/pulsecore/idxset.c b/src/pulsecore/idxset.c new file mode 100644 index 0000000..5175ca2 --- /dev/null +++ b/src/pulsecore/idxset.c @@ -0,0 +1,471 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2008 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/flist.h> +#include <pulsecore/macro.h> + +#include "idxset.h" + +#define NBUCKETS 127 + +struct idxset_entry { + uint32_t idx; + void *data; + + struct idxset_entry *data_next, *data_previous; + struct idxset_entry *index_next, *index_previous; + struct idxset_entry *iterate_next, *iterate_previous; +}; + +struct pa_idxset { + pa_hash_func_t hash_func; + pa_compare_func_t compare_func; + + uint32_t current_index; + + struct idxset_entry *iterate_list_head, *iterate_list_tail; + unsigned n_entries; +}; + +#define BY_DATA(i) ((struct idxset_entry**) ((uint8_t*) (i) + PA_ALIGN(sizeof(pa_idxset)))) +#define BY_INDEX(i) (BY_DATA(i) + NBUCKETS) + +PA_STATIC_FLIST_DECLARE(entries, 0, pa_xfree); + +unsigned pa_idxset_string_hash_func(const void *p) { + unsigned hash = 0; + const char *c; + + for (c = p; *c; c++) + hash = 31 * hash + (unsigned) *c; + + return hash; +} + +int pa_idxset_string_compare_func(const void *a, const void *b) { + return strcmp(a, b); +} + +unsigned pa_idxset_trivial_hash_func(const void *p) { + return PA_PTR_TO_UINT(p); +} + +int pa_idxset_trivial_compare_func(const void *a, const void *b) { + return a < b ? -1 : (a > b ? 1 : 0); +} + +pa_idxset* pa_idxset_new(pa_hash_func_t hash_func, pa_compare_func_t compare_func) { + pa_idxset *s; + + s = pa_xmalloc0(PA_ALIGN(sizeof(pa_idxset)) + NBUCKETS*2*sizeof(struct idxset_entry*)); + + s->hash_func = hash_func ? hash_func : pa_idxset_trivial_hash_func; + s->compare_func = compare_func ? compare_func : pa_idxset_trivial_compare_func; + + s->current_index = 0; + s->n_entries = 0; + s->iterate_list_head = s->iterate_list_tail = NULL; + + return s; +} + +static void remove_entry(pa_idxset *s, struct idxset_entry *e) { + pa_assert(s); + pa_assert(e); + + /* Remove from iteration linked list */ + if (e->iterate_next) + e->iterate_next->iterate_previous = e->iterate_previous; + else + s->iterate_list_tail = e->iterate_previous; + + if (e->iterate_previous) + e->iterate_previous->iterate_next = e->iterate_next; + else + s->iterate_list_head = e->iterate_next; + + /* Remove from data hash table */ + if (e->data_next) + e->data_next->data_previous = e->data_previous; + + if (e->data_previous) + e->data_previous->data_next = e->data_next; + else { + unsigned hash = s->hash_func(e->data) % NBUCKETS; + BY_DATA(s)[hash] = e->data_next; + } + + /* Remove from index hash table */ + if (e->index_next) + e->index_next->index_previous = e->index_previous; + + if (e->index_previous) + e->index_previous->index_next = e->index_next; + else + BY_INDEX(s)[e->idx % NBUCKETS] = e->index_next; + + if (pa_flist_push(PA_STATIC_FLIST_GET(entries), e) < 0) + pa_xfree(e); + + pa_assert(s->n_entries >= 1); + s->n_entries--; +} + +void pa_idxset_free(pa_idxset *s, pa_free_cb_t free_cb) { + pa_assert(s); + + pa_idxset_remove_all(s, free_cb); + pa_xfree(s); +} + +static struct idxset_entry* data_scan(pa_idxset *s, unsigned hash, const void *p) { + struct idxset_entry *e; + pa_assert(s); + pa_assert(hash < NBUCKETS); + pa_assert(p); + + for (e = BY_DATA(s)[hash]; e; e = e->data_next) + if (s->compare_func(e->data, p) == 0) + return e; + + return NULL; +} + +static struct idxset_entry* index_scan(pa_idxset *s, unsigned hash, uint32_t idx) { + struct idxset_entry *e; + pa_assert(s); + pa_assert(hash < NBUCKETS); + + for (e = BY_INDEX(s)[hash]; e; e = e->index_next) + if (e->idx == idx) + return e; + + return NULL; +} + +int pa_idxset_put(pa_idxset*s, void *p, uint32_t *idx) { + unsigned hash; + struct idxset_entry *e; + + pa_assert(s); + + hash = s->hash_func(p) % NBUCKETS; + + if ((e = data_scan(s, hash, p))) { + if (idx) + *idx = e->idx; + + return -1; + } + + if (!(e = pa_flist_pop(PA_STATIC_FLIST_GET(entries)))) + e = pa_xnew(struct idxset_entry, 1); + + e->data = p; + e->idx = s->current_index++; + + /* Insert into data hash table */ + e->data_next = BY_DATA(s)[hash]; + e->data_previous = NULL; + if (BY_DATA(s)[hash]) + BY_DATA(s)[hash]->data_previous = e; + BY_DATA(s)[hash] = e; + + hash = e->idx % NBUCKETS; + + /* Insert into index hash table */ + e->index_next = BY_INDEX(s)[hash]; + e->index_previous = NULL; + if (BY_INDEX(s)[hash]) + BY_INDEX(s)[hash]->index_previous = e; + BY_INDEX(s)[hash] = e; + + /* Insert into iteration list */ + e->iterate_previous = s->iterate_list_tail; + e->iterate_next = NULL; + if (s->iterate_list_tail) { + pa_assert(s->iterate_list_head); + s->iterate_list_tail->iterate_next = e; + } else { + pa_assert(!s->iterate_list_head); + s->iterate_list_head = e; + } + s->iterate_list_tail = e; + + s->n_entries++; + pa_assert(s->n_entries >= 1); + + if (idx) + *idx = e->idx; + + return 0; +} + +void* pa_idxset_get_by_index(pa_idxset*s, uint32_t idx) { + unsigned hash; + struct idxset_entry *e; + + pa_assert(s); + + hash = idx % NBUCKETS; + + if (!(e = index_scan(s, hash, idx))) + return NULL; + + return e->data; +} + +void* pa_idxset_get_by_data(pa_idxset*s, const void *p, uint32_t *idx) { + unsigned hash; + struct idxset_entry *e; + + pa_assert(s); + + hash = s->hash_func(p) % NBUCKETS; + + if (!(e = data_scan(s, hash, p))) + return NULL; + + if (idx) + *idx = e->idx; + + return e->data; +} + +void* pa_idxset_remove_by_index(pa_idxset*s, uint32_t idx) { + struct idxset_entry *e; + unsigned hash; + void *data; + + pa_assert(s); + + hash = idx % NBUCKETS; + + if (!(e = index_scan(s, hash, idx))) + return NULL; + + data = e->data; + remove_entry(s, e); + + return data; +} + +void* pa_idxset_remove_by_data(pa_idxset*s, const void *data, uint32_t *idx) { + struct idxset_entry *e; + unsigned hash; + void *r; + + pa_assert(s); + + hash = s->hash_func(data) % NBUCKETS; + + if (!(e = data_scan(s, hash, data))) + return NULL; + + r = e->data; + + if (idx) + *idx = e->idx; + + remove_entry(s, e); + + return r; +} + +void pa_idxset_remove_all(pa_idxset *s, pa_free_cb_t free_cb) { + pa_assert(s); + + while (s->iterate_list_head) { + void *data = s->iterate_list_head->data; + + remove_entry(s, s->iterate_list_head); + + if (free_cb) + free_cb(data); + } +} + +void* pa_idxset_rrobin(pa_idxset *s, uint32_t *idx) { + unsigned hash; + struct idxset_entry *e; + + pa_assert(s); + pa_assert(idx); + + hash = *idx % NBUCKETS; + + e = index_scan(s, hash, *idx); + + if (e && e->iterate_next) + e = e->iterate_next; + else + e = s->iterate_list_head; + + if (!e) + return NULL; + + *idx = e->idx; + return e->data; +} + +void *pa_idxset_iterate(pa_idxset *s, void **state, uint32_t *idx) { + struct idxset_entry *e; + + pa_assert(s); + pa_assert(state); + + if (*state == (void*) -1) + goto at_end; + + if ((!*state && !s->iterate_list_head)) + goto at_end; + + e = *state ? *state : s->iterate_list_head; + + if (e->iterate_next) + *state = e->iterate_next; + else + *state = (void*) -1; + + if (idx) + *idx = e->idx; + + return e->data; + +at_end: + *state = (void *) -1; + + if (idx) + *idx = PA_IDXSET_INVALID; + + return NULL; +} + +void* pa_idxset_steal_first(pa_idxset *s, uint32_t *idx) { + void *data; + + pa_assert(s); + + if (!s->iterate_list_head) + return NULL; + + data = s->iterate_list_head->data; + + if (idx) + *idx = s->iterate_list_head->idx; + + remove_entry(s, s->iterate_list_head); + + return data; +} + +void* pa_idxset_first(pa_idxset *s, uint32_t *idx) { + pa_assert(s); + + if (!s->iterate_list_head) { + if (idx) + *idx = PA_IDXSET_INVALID; + return NULL; + } + + if (idx) + *idx = s->iterate_list_head->idx; + + return s->iterate_list_head->data; +} + +void *pa_idxset_next(pa_idxset *s, uint32_t *idx) { + struct idxset_entry *e; + unsigned hash; + + pa_assert(s); + pa_assert(idx); + + if (*idx == PA_IDXSET_INVALID) + return NULL; + + hash = *idx % NBUCKETS; + + if ((e = index_scan(s, hash, *idx))) { + + e = e->iterate_next; + + if (e) { + *idx = e->idx; + return e->data; + } else { + *idx = PA_IDXSET_INVALID; + return NULL; + } + + } else { + + /* If the entry passed doesn't exist anymore we try to find + * the next following */ + + for ((*idx)++; *idx < s->current_index; (*idx)++) { + + hash = *idx % NBUCKETS; + + if ((e = index_scan(s, hash, *idx))) { + *idx = e->idx; + return e->data; + } + } + + *idx = PA_IDXSET_INVALID; + return NULL; + } +} + +unsigned pa_idxset_size(pa_idxset*s) { + pa_assert(s); + + return s->n_entries; +} + +bool pa_idxset_isempty(pa_idxset *s) { + pa_assert(s); + + return s->n_entries == 0; +} + +pa_idxset *pa_idxset_copy(pa_idxset *s, pa_copy_func_t copy_func) { + pa_idxset *copy; + struct idxset_entry *i; + + pa_assert(s); + + copy = pa_idxset_new(s->hash_func, s->compare_func); + + for (i = s->iterate_list_head; i; i = i->iterate_next) + pa_idxset_put(copy, copy_func ? copy_func(i->data) : i->data, NULL); + + return copy; +} diff --git a/src/pulsecore/idxset.h b/src/pulsecore/idxset.h new file mode 100644 index 0000000..7acb202 --- /dev/null +++ b/src/pulsecore/idxset.h @@ -0,0 +1,116 @@ +#ifndef foopulsecoreidxsethfoo +#define foopulsecoreidxsethfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2008 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <inttypes.h> + +#include <pulse/def.h> + +#include <pulsecore/macro.h> + +/* A combination of a set and a dynamic array. Entries are indexable + * both through an automatically generated numeric index and the + * entry's data pointer. As usual, memory management is the user's + * job. */ + +/* A special index value denoting the invalid index. */ +#define PA_IDXSET_INVALID ((uint32_t) -1) + +/* Generic implementations for hash and comparison functions. Just + * compares the pointer or calculates the hash value directly from the + * pointer value. */ +unsigned pa_idxset_trivial_hash_func(const void *p); +int pa_idxset_trivial_compare_func(const void *a, const void *b); + +/* Generic implementations for hash and comparison functions for strings. */ +unsigned pa_idxset_string_hash_func(const void *p); +int pa_idxset_string_compare_func(const void *a, const void *b); + +typedef unsigned (*pa_hash_func_t)(const void *p); +typedef int (*pa_compare_func_t)(const void *a, const void *b); +typedef void *(*pa_copy_func_t)(const void *p); + +typedef struct pa_idxset pa_idxset; + +/* Instantiate a new idxset with the specified hash and comparison functions */ +pa_idxset* pa_idxset_new(pa_hash_func_t hash_func, pa_compare_func_t compare_func); + +/* Free the idxset. When the idxset is not empty the specified function is called for every entry contained */ +void pa_idxset_free(pa_idxset *s, pa_free_cb_t free_cb); + +/* Store a new item in the idxset. The index of the item is returned in *idx */ +int pa_idxset_put(pa_idxset*s, void *p, uint32_t *idx); + +/* Get the entry by its idx */ +void* pa_idxset_get_by_index(pa_idxset*s, uint32_t idx); + +/* Get the entry by its data. The index is returned in *idx */ +void* pa_idxset_get_by_data(pa_idxset*s, const void *p, uint32_t *idx); + +/* Similar to pa_idxset_get_by_index(), but removes the entry from the idxset. */ +void* pa_idxset_remove_by_index(pa_idxset*s, uint32_t idx); + +/* Similar to pa_idxset_get_by_data(), but removes the entry from the idxset */ +void* pa_idxset_remove_by_data(pa_idxset*s, const void *p, uint32_t *idx); + +/* If free_cb is not NULL, it's called for each entry. */ +void pa_idxset_remove_all(pa_idxset *s, pa_free_cb_t free_cb); + +/* This may be used to iterate through all entries. When called with + an invalid index value it returns the first entry, otherwise the + next following. The function is best called with *idx = + PA_IDXSET_VALID first. It is safe to manipulate the idxset between + the calls. It is not guaranteed that all entries have already been + returned before the an entry is returned the second time.*/ +void* pa_idxset_rrobin(pa_idxset *s, uint32_t *idx); + +/* Iterate through the idxset. At first iteration state should be NULL */ +void *pa_idxset_iterate(pa_idxset *s, void **state, uint32_t *idx); + +/* Return the oldest entry in the idxset and remove it. If idx is not NULL fill in its index in *idx */ +void* pa_idxset_steal_first(pa_idxset *s, uint32_t *idx); + +/* Return the oldest entry in the idxset. Fill in its index in *idx. */ +void* pa_idxset_first(pa_idxset *s, uint32_t *idx); + +/* Return the entry following the entry indexed by *idx. After the + * call *index contains the index of the returned + * object. pa_idxset_first() and pa_idxset_next() may be used to + * iterate through the set.*/ +void *pa_idxset_next(pa_idxset *s, uint32_t *idx); + +/* Return the current number of entries in the idxset */ +unsigned pa_idxset_size(pa_idxset*s); + +/* Return true of the idxset is empty */ +bool pa_idxset_isempty(pa_idxset *s); + +/* Duplicate the idxset. This will not copy the actual indexes. If copy_func is + * set, each entry is copied using the provided function, otherwise a shallow + * copy will be made. */ +pa_idxset *pa_idxset_copy(pa_idxset *s, pa_copy_func_t copy_func); + +/* A macro to ease iteration through all entries */ +#define PA_IDXSET_FOREACH(e, s, idx) \ + for ((e) = pa_idxset_first((s), &(idx)); (e); (e) = pa_idxset_next((s), &(idx))) + +#endif diff --git a/src/pulsecore/iochannel.c b/src/pulsecore/iochannel.c new file mode 100644 index 0000000..e25824b --- /dev/null +++ b/src/pulsecore/iochannel.c @@ -0,0 +1,541 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + 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 by the Free Software Foundation; either version 2.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> + +#ifdef HAVE_SYS_UN_H +#include <sys/un.h> +#endif + +#include <pulse/xmalloc.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/core-util.h> +#include <pulsecore/socket.h> +#include <pulsecore/socket-util.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> + +#include "iochannel.h" + +struct pa_iochannel { + int ifd, ofd; + int ifd_type, ofd_type; + pa_mainloop_api* mainloop; + + pa_iochannel_cb_t callback; + void*userdata; + + bool readable:1; + bool writable:1; + bool hungup:1; + bool no_close:1; + + pa_io_event* input_event, *output_event; +}; + +static void callback(pa_mainloop_api* m, pa_io_event *e, int fd, pa_io_event_flags_t f, void *userdata); + +static void delete_events(pa_iochannel *io) { + pa_assert(io); + + if (io->input_event) + io->mainloop->io_free(io->input_event); + + if (io->output_event && io->output_event != io->input_event) + io->mainloop->io_free(io->output_event); + + io->input_event = io->output_event = NULL; +} + +static void enable_events(pa_iochannel *io) { + pa_assert(io); + + if (io->hungup) { + delete_events(io); + return; + } + + if (io->ifd == io->ofd && io->ifd >= 0) { + pa_io_event_flags_t f = PA_IO_EVENT_NULL; + + if (!io->readable) + f |= PA_IO_EVENT_INPUT; + if (!io->writable) + f |= PA_IO_EVENT_OUTPUT; + + pa_assert(io->input_event == io->output_event); + + if (f != PA_IO_EVENT_NULL) { + if (io->input_event) + io->mainloop->io_enable(io->input_event, f); + else + io->input_event = io->output_event = io->mainloop->io_new(io->mainloop, io->ifd, f, callback, io); + } else + delete_events(io); + + } else { + + if (io->ifd >= 0) { + if (!io->readable) { + if (io->input_event) + io->mainloop->io_enable(io->input_event, PA_IO_EVENT_INPUT); + else + io->input_event = io->mainloop->io_new(io->mainloop, io->ifd, PA_IO_EVENT_INPUT, callback, io); + } else if (io->input_event) { + io->mainloop->io_free(io->input_event); + io->input_event = NULL; + } + } + + if (io->ofd >= 0) { + if (!io->writable) { + if (io->output_event) + io->mainloop->io_enable(io->output_event, PA_IO_EVENT_OUTPUT); + else + io->output_event = io->mainloop->io_new(io->mainloop, io->ofd, PA_IO_EVENT_OUTPUT, callback, io); + } else if (io->output_event) { + io->mainloop->io_free(io->output_event); + io->output_event = NULL; + } + } + } +} + +static void callback(pa_mainloop_api* m, pa_io_event *e, int fd, pa_io_event_flags_t f, void *userdata) { + pa_iochannel *io = userdata; + bool changed = false; + + pa_assert(m); + pa_assert(e); + pa_assert(fd >= 0); + pa_assert(userdata); + + if ((f & (PA_IO_EVENT_HANGUP|PA_IO_EVENT_ERROR)) && !io->hungup) { + io->hungup = true; + changed = true; + } + + if ((f & PA_IO_EVENT_INPUT) && !io->readable) { + io->readable = true; + changed = true; + pa_assert(e == io->input_event); + } + + if ((f & PA_IO_EVENT_OUTPUT) && !io->writable) { + io->writable = true; + changed = true; + pa_assert(e == io->output_event); + } + + if (changed) { + enable_events(io); + + if (io->callback) + io->callback(io, io->userdata); + } +} + +pa_iochannel* pa_iochannel_new(pa_mainloop_api*m, int ifd, int ofd) { + pa_iochannel *io; + + pa_assert(m); + pa_assert(ifd >= 0 || ofd >= 0); + + io = pa_xnew0(pa_iochannel, 1); + io->ifd = ifd; + io->ofd = ofd; + io->mainloop = m; + + if (io->ifd >= 0) + pa_make_fd_nonblock(io->ifd); + + if (io->ofd >= 0 && io->ofd != io->ifd) + pa_make_fd_nonblock(io->ofd); + + enable_events(io); + return io; +} + +void pa_iochannel_free(pa_iochannel*io) { + pa_assert(io); + + delete_events(io); + + if (!io->no_close) { + if (io->ifd >= 0) + pa_close(io->ifd); + if (io->ofd >= 0 && io->ofd != io->ifd) + pa_close(io->ofd); + } + + pa_xfree(io); +} + +bool pa_iochannel_is_readable(pa_iochannel*io) { + pa_assert(io); + + return io->readable || io->hungup; +} + +bool pa_iochannel_is_writable(pa_iochannel*io) { + pa_assert(io); + + return io->writable && !io->hungup; +} + +bool pa_iochannel_is_hungup(pa_iochannel*io) { + pa_assert(io); + + return io->hungup; +} + +ssize_t pa_iochannel_write(pa_iochannel*io, const void*data, size_t l) { + ssize_t r; + + pa_assert(io); + pa_assert(data); + pa_assert(l); + pa_assert(io->ofd >= 0); + + r = pa_write(io->ofd, data, l, &io->ofd_type); + + if ((size_t) r == l) + return r; /* Fast path - we almost always successfully write everything */ + + if (r < 0) { + if (errno == EINTR || errno == EAGAIN) + r = 0; + else + return r; + } + + /* Partial write - let's get a notification when we can write more */ + io->writable = io->hungup = false; + enable_events(io); + + return r; +} + +ssize_t pa_iochannel_read(pa_iochannel*io, void*data, size_t l) { + ssize_t r; + + pa_assert(io); + pa_assert(data); + pa_assert(io->ifd >= 0); + + if ((r = pa_read(io->ifd, data, l, &io->ifd_type)) >= 0) { + + /* We also reset the hangup flag here to ensure that another + * IO callback is triggered so that we will again call into + * user code */ + io->readable = io->hungup = false; + enable_events(io); + } + + return r; +} + +#ifdef HAVE_CREDS + +bool pa_iochannel_creds_supported(pa_iochannel *io) { + struct { + struct sockaddr sa; +#ifdef HAVE_SYS_UN_H + struct sockaddr_un un; +#endif + struct sockaddr_storage storage; + } sa; + + socklen_t l; + + pa_assert(io); + pa_assert(io->ifd >= 0); + pa_assert(io->ofd == io->ifd); + + l = sizeof(sa); + if (getsockname(io->ifd, &sa.sa, &l) < 0) + return false; + + return sa.sa.sa_family == AF_UNIX; +} + +int pa_iochannel_creds_enable(pa_iochannel *io) { + int t = 1; + + pa_assert(io); + pa_assert(io->ifd >= 0); + + if (setsockopt(io->ifd, SOL_SOCKET, SO_PASSCRED, &t, sizeof(t)) < 0) { + pa_log_error("setsockopt(SOL_SOCKET, SO_PASSCRED): %s", pa_cstrerror(errno)); + return -1; + } + + return 0; +} + +ssize_t pa_iochannel_write_with_creds(pa_iochannel*io, const void*data, size_t l, const pa_creds *ucred) { + ssize_t r; + struct msghdr mh; + struct iovec iov; + union { + struct cmsghdr hdr; + uint8_t data[CMSG_SPACE(sizeof(struct ucred))]; + } cmsg; + struct ucred *u; + + pa_assert(io); + pa_assert(data); + pa_assert(l); + pa_assert(io->ofd >= 0); + + pa_zero(iov); + iov.iov_base = (void*) data; + iov.iov_len = l; + + pa_zero(cmsg); + cmsg.hdr.cmsg_len = CMSG_LEN(sizeof(struct ucred)); + cmsg.hdr.cmsg_level = SOL_SOCKET; + cmsg.hdr.cmsg_type = SCM_CREDENTIALS; + + u = (struct ucred*) CMSG_DATA(&cmsg.hdr); + + u->pid = getpid(); + if (ucred) { + u->uid = ucred->uid; + u->gid = ucred->gid; + } else { + u->uid = getuid(); + u->gid = getgid(); + } + + pa_zero(mh); + mh.msg_iov = &iov; + mh.msg_iovlen = 1; + mh.msg_control = &cmsg; + mh.msg_controllen = sizeof(cmsg); + + if ((r = sendmsg(io->ofd, &mh, MSG_NOSIGNAL)) >= 0) { + io->writable = io->hungup = false; + enable_events(io); + } + + return r; +} + +/* For more details on FD passing, check the cmsg(3) manpage + * and IETF RFC #2292: "Advanced Sockets API for IPv6" */ +ssize_t pa_iochannel_write_with_fds(pa_iochannel*io, const void*data, size_t l, int nfd, const int *fds) { + ssize_t r; + int *msgdata; + struct msghdr mh; + struct iovec iov; + union { + struct cmsghdr hdr; + uint8_t data[CMSG_SPACE(sizeof(int) * MAX_ANCIL_DATA_FDS)]; + } cmsg; + + pa_assert(io); + pa_assert(data); + pa_assert(l); + pa_assert(io->ofd >= 0); + pa_assert(fds); + pa_assert(nfd > 0); + pa_assert(nfd <= MAX_ANCIL_DATA_FDS); + + pa_zero(iov); + iov.iov_base = (void*) data; + iov.iov_len = l; + + pa_zero(cmsg); + cmsg.hdr.cmsg_level = SOL_SOCKET; + cmsg.hdr.cmsg_type = SCM_RIGHTS; + + msgdata = (int*) CMSG_DATA(&cmsg.hdr); + memcpy(msgdata, fds, nfd * sizeof(int)); + cmsg.hdr.cmsg_len = CMSG_LEN(sizeof(int) * nfd); + + pa_zero(mh); + mh.msg_iov = &iov; + mh.msg_iovlen = 1; + mh.msg_control = &cmsg; + + /* If we followed the example on the cmsg man page, we'd use + * sizeof(cmsg.data) here, but if nfd < MAX_ANCIL_DATA_FDS, then the data + * buffer is larger than needed, and the kernel doesn't like it if we set + * msg_controllen to a larger than necessary value. The commit message for + * commit 451d1d6762 contains a longer explanation. */ + mh.msg_controllen = CMSG_SPACE(sizeof(int) * nfd); + + if ((r = sendmsg(io->ofd, &mh, MSG_NOSIGNAL)) >= 0) { + io->writable = io->hungup = false; + enable_events(io); + } + return r; +} + +ssize_t pa_iochannel_read_with_ancil_data(pa_iochannel*io, void*data, size_t l, pa_cmsg_ancil_data *ancil_data) { + ssize_t r; + struct msghdr mh; + struct iovec iov; + union { + struct cmsghdr hdr; + uint8_t data[CMSG_SPACE(sizeof(struct ucred)) + CMSG_SPACE(sizeof(int) * MAX_ANCIL_DATA_FDS)]; + } cmsg; + + pa_assert(io); + pa_assert(data); + pa_assert(l); + pa_assert(io->ifd >= 0); + pa_assert(ancil_data); + + if (io->ifd_type > 0) { + ancil_data->creds_valid = false; + ancil_data->nfd = 0; + return pa_iochannel_read(io, data, l); + } + + iov.iov_base = data; + iov.iov_len = l; + + pa_zero(mh); + mh.msg_iov = &iov; + mh.msg_iovlen = 1; + mh.msg_control = &cmsg; + mh.msg_controllen = sizeof(cmsg); + + if ((r = recvmsg(io->ifd, &mh, 0)) >= 0) { + struct cmsghdr *cmh; + + ancil_data->creds_valid = false; + ancil_data->nfd = 0; + + for (cmh = CMSG_FIRSTHDR(&mh); cmh; cmh = CMSG_NXTHDR(&mh, cmh)) { + + if (cmh->cmsg_level != SOL_SOCKET) + continue; + + if (cmh->cmsg_type == SCM_CREDENTIALS) { + struct ucred u; + pa_assert(cmh->cmsg_len == CMSG_LEN(sizeof(struct ucred))); + memcpy(&u, CMSG_DATA(cmh), sizeof(struct ucred)); + + ancil_data->creds.gid = u.gid; + ancil_data->creds.uid = u.uid; + ancil_data->creds_valid = true; + } + else if (cmh->cmsg_type == SCM_RIGHTS) { + int nfd = (cmh->cmsg_len - CMSG_LEN(0)) / sizeof(int); + if (nfd > MAX_ANCIL_DATA_FDS) { + int i; + pa_log("Trying to receive too many file descriptors!"); + for (i = 0; i < nfd; i++) + pa_close(((int*) CMSG_DATA(cmh))[i]); + continue; + } + memcpy(ancil_data->fds, CMSG_DATA(cmh), nfd * sizeof(int)); + ancil_data->nfd = nfd; + ancil_data->close_fds_on_cleanup = true; + } + } + + io->readable = io->hungup = false; + enable_events(io); + } + + if (r == -1 && errno == ENOTSOCK) { + io->ifd_type = 1; + return pa_iochannel_read_with_ancil_data(io, data, l, ancil_data); + } + + return r; +} + +#endif /* HAVE_CREDS */ + +void pa_iochannel_set_callback(pa_iochannel*io, pa_iochannel_cb_t _callback, void *userdata) { + pa_assert(io); + + io->callback = _callback; + io->userdata = userdata; +} + +void pa_iochannel_set_noclose(pa_iochannel*io, bool b) { + pa_assert(io); + + io->no_close = b; +} + +void pa_iochannel_socket_peer_to_string(pa_iochannel*io, char*s, size_t l) { + pa_assert(io); + pa_assert(s); + pa_assert(l); + + pa_socket_peer_to_string(io->ifd, s, l); +} + +int pa_iochannel_socket_set_rcvbuf(pa_iochannel *io, size_t l) { + pa_assert(io); + + return pa_socket_set_rcvbuf(io->ifd, l); +} + +int pa_iochannel_socket_set_sndbuf(pa_iochannel *io, size_t l) { + pa_assert(io); + + return pa_socket_set_sndbuf(io->ofd, l); +} + +pa_mainloop_api* pa_iochannel_get_mainloop_api(pa_iochannel *io) { + pa_assert(io); + + return io->mainloop; +} + +int pa_iochannel_get_recv_fd(pa_iochannel *io) { + pa_assert(io); + + return io->ifd; +} + +int pa_iochannel_get_send_fd(pa_iochannel *io) { + pa_assert(io); + + return io->ofd; +} + +bool pa_iochannel_socket_is_local(pa_iochannel *io) { + pa_assert(io); + + if (pa_socket_is_local(io->ifd)) + return true; + + if (io->ifd != io->ofd) + if (pa_socket_is_local(io->ofd)) + return true; + + return false; +} diff --git a/src/pulsecore/iochannel.h b/src/pulsecore/iochannel.h new file mode 100644 index 0000000..c81c5e3 --- /dev/null +++ b/src/pulsecore/iochannel.h @@ -0,0 +1,89 @@ +#ifndef fooiochannelhfoo +#define fooiochannelhfoo + +/*** + 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifndef PACKAGE +#error "Please include config.h before including this file!" +#endif + +#include <sys/types.h> + +#include <pulse/mainloop-api.h> +#include <pulsecore/creds.h> +#include <pulsecore/macro.h> + +/* A wrapper around UNIX file descriptors for attaching them to the a + main event loop. Every time new data may be read or be written to + the channel a callback function is called. It is safe to destroy + the calling iochannel object from the callback */ + +typedef struct pa_iochannel pa_iochannel; + +/* Create a new IO channel for the specified file descriptors for +input resp. output. It is safe to pass the same file descriptor for +both parameters (in case of full-duplex channels). For a simplex +channel specify -1 for the other direction. */ + +pa_iochannel* pa_iochannel_new(pa_mainloop_api*m, int ifd, int ofd); +void pa_iochannel_free(pa_iochannel*io); + +/* Returns: length written on success, 0 if a retry is needed, negative value + * on error. */ +ssize_t pa_iochannel_write(pa_iochannel*io, const void*data, size_t l); +ssize_t pa_iochannel_read(pa_iochannel*io, void*data, size_t l); + +#ifdef HAVE_CREDS +bool pa_iochannel_creds_supported(pa_iochannel *io); +int pa_iochannel_creds_enable(pa_iochannel *io); + +ssize_t pa_iochannel_write_with_fds(pa_iochannel*io, const void*data, size_t l, int nfd, const int *fds); +ssize_t pa_iochannel_write_with_creds(pa_iochannel*io, const void*data, size_t l, const pa_creds *ucred); +ssize_t pa_iochannel_read_with_ancil_data(pa_iochannel*io, void*data, size_t l, pa_cmsg_ancil_data *ancil_data); +#endif + +bool pa_iochannel_is_readable(pa_iochannel*io); +bool pa_iochannel_is_writable(pa_iochannel*io); +bool pa_iochannel_is_hungup(pa_iochannel*io); + +/* Don't close the file descriptors when the io channel is freed. By + * default the file descriptors are closed. */ +void pa_iochannel_set_noclose(pa_iochannel*io, bool b); + +/* Set the callback function that is called whenever data becomes available for read or write */ +typedef void (*pa_iochannel_cb_t)(pa_iochannel*io, void *userdata); +void pa_iochannel_set_callback(pa_iochannel*io, pa_iochannel_cb_t callback, void *userdata); + +/* In case the file descriptor is a socket, return a pretty-printed string in *s which describes the peer connected */ +void pa_iochannel_socket_peer_to_string(pa_iochannel*io, char*s, size_t l); + +/* Use setsockopt() to tune the receive and send buffers of TCP sockets */ +int pa_iochannel_socket_set_rcvbuf(pa_iochannel*io, size_t l); +int pa_iochannel_socket_set_sndbuf(pa_iochannel*io, size_t l); + +bool pa_iochannel_socket_is_local(pa_iochannel *io); + +pa_mainloop_api* pa_iochannel_get_mainloop_api(pa_iochannel *io); + +int pa_iochannel_get_recv_fd(pa_iochannel *io); +int pa_iochannel_get_send_fd(pa_iochannel *io); + +#endif diff --git a/src/pulsecore/ioline.c b/src/pulsecore/ioline.c new file mode 100644 index 0000000..dfc5a73 --- /dev/null +++ b/src/pulsecore/ioline.c @@ -0,0 +1,463 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/socket.h> +#include <pulsecore/core-error.h> +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> +#include <pulsecore/refcnt.h> + +#include "ioline.h" + +#define BUFFER_LIMIT (64*1024) +#define READ_SIZE (1024) + +struct pa_ioline { + PA_REFCNT_DECLARE; + + pa_iochannel *io; + pa_defer_event *defer_event; + pa_mainloop_api *mainloop; + + char *wbuf; + size_t wbuf_length, wbuf_index, wbuf_valid_length; + + char *rbuf; + size_t rbuf_length, rbuf_index, rbuf_valid_length; + + pa_ioline_cb_t callback; + void *userdata; + + pa_ioline_drain_cb_t drain_callback; + void *drain_userdata; + + bool dead:1; + bool defer_close:1; +}; + +static void io_callback(pa_iochannel*io, void *userdata); +static void defer_callback(pa_mainloop_api*m, pa_defer_event*e, void *userdata); + +pa_ioline* pa_ioline_new(pa_iochannel *io) { + pa_ioline *l; + pa_assert(io); + + l = pa_xnew(pa_ioline, 1); + PA_REFCNT_INIT(l); + l->io = io; + + l->wbuf = NULL; + l->wbuf_length = l->wbuf_index = l->wbuf_valid_length = 0; + + l->rbuf = NULL; + l->rbuf_length = l->rbuf_index = l->rbuf_valid_length = 0; + + l->callback = NULL; + l->userdata = NULL; + + l->drain_callback = NULL; + l->drain_userdata = NULL; + + l->mainloop = pa_iochannel_get_mainloop_api(io); + + l->defer_event = l->mainloop->defer_new(l->mainloop, defer_callback, l); + l->mainloop->defer_enable(l->defer_event, 0); + + l->dead = false; + l->defer_close = false; + + pa_iochannel_set_callback(io, io_callback, l); + + return l; +} + +static void ioline_free(pa_ioline *l) { + pa_assert(l); + + if (l->io) + pa_iochannel_free(l->io); + + if (l->defer_event) + l->mainloop->defer_free(l->defer_event); + + pa_xfree(l->wbuf); + pa_xfree(l->rbuf); + pa_xfree(l); +} + +void pa_ioline_unref(pa_ioline *l) { + pa_assert(l); + pa_assert(PA_REFCNT_VALUE(l) >= 1); + + if (PA_REFCNT_DEC(l) <= 0) + ioline_free(l); +} + +pa_ioline* pa_ioline_ref(pa_ioline *l) { + pa_assert(l); + pa_assert(PA_REFCNT_VALUE(l) >= 1); + + PA_REFCNT_INC(l); + return l; +} + +void pa_ioline_close(pa_ioline *l) { + pa_assert(l); + pa_assert(PA_REFCNT_VALUE(l) >= 1); + + l->dead = true; + + if (l->io) { + pa_iochannel_free(l->io); + l->io = NULL; + } + + if (l->defer_event) { + l->mainloop->defer_free(l->defer_event); + l->defer_event = NULL; + } + + if (l->callback) + l->callback = NULL; +} + +void pa_ioline_puts(pa_ioline *l, const char *c) { + size_t len; + + pa_assert(l); + pa_assert(PA_REFCNT_VALUE(l) >= 1); + pa_assert(c); + + if (l->dead) + return; + + len = strlen(c); + if (len > BUFFER_LIMIT - l->wbuf_valid_length) + len = BUFFER_LIMIT - l->wbuf_valid_length; + + if (len) { + pa_assert(l->wbuf_length >= l->wbuf_valid_length); + + /* In case the allocated buffer is too small, enlarge it. */ + if (l->wbuf_valid_length + len > l->wbuf_length) { + size_t n = l->wbuf_valid_length+len; + char *new = pa_xnew(char, (unsigned) n); + + if (l->wbuf) { + memcpy(new, l->wbuf+l->wbuf_index, l->wbuf_valid_length); + pa_xfree(l->wbuf); + } + + l->wbuf = new; + l->wbuf_length = n; + l->wbuf_index = 0; + } else if (l->wbuf_index + l->wbuf_valid_length + len > l->wbuf_length) { + + /* In case the allocated buffer fits, but the current index is too far from the start, move it to the front. */ + memmove(l->wbuf, l->wbuf+l->wbuf_index, l->wbuf_valid_length); + l->wbuf_index = 0; + } + + pa_assert(l->wbuf_index + l->wbuf_valid_length + len <= l->wbuf_length); + + /* Append the new string */ + memcpy(l->wbuf + l->wbuf_index + l->wbuf_valid_length, c, len); + l->wbuf_valid_length += len; + + l->mainloop->defer_enable(l->defer_event, 1); + } +} + +void pa_ioline_set_callback(pa_ioline*l, pa_ioline_cb_t callback, void *userdata) { + pa_assert(l); + pa_assert(PA_REFCNT_VALUE(l) >= 1); + + if (l->dead) + return; + + l->callback = callback; + l->userdata = userdata; +} + +void pa_ioline_set_drain_callback(pa_ioline*l, pa_ioline_drain_cb_t callback, void *userdata) { + pa_assert(l); + pa_assert(PA_REFCNT_VALUE(l) >= 1); + + if (l->dead) + return; + + l->drain_callback = callback; + l->drain_userdata = userdata; +} + +static void failure(pa_ioline *l, bool process_leftover) { + pa_assert(l); + pa_assert(PA_REFCNT_VALUE(l) >= 1); + pa_assert(!l->dead); + + if (process_leftover && l->rbuf_valid_length > 0) { + /* Pass the last missing bit to the client */ + + if (l->callback) { + char *p = pa_xstrndup(l->rbuf+l->rbuf_index, l->rbuf_valid_length); + l->callback(l, p, l->userdata); + pa_xfree(p); + } + } + + if (l->callback) { + l->callback(l, NULL, l->userdata); + l->callback = NULL; + } + + pa_ioline_close(l); +} + +static void scan_for_lines(pa_ioline *l, size_t skip) { + pa_assert(l); + pa_assert(PA_REFCNT_VALUE(l) >= 1); + pa_assert(skip < l->rbuf_valid_length); + + while (!l->dead && l->rbuf_valid_length > skip) { + char *e, *p; + size_t m; + + if (!(e = memchr(l->rbuf + l->rbuf_index + skip, '\n', l->rbuf_valid_length - skip))) + break; + + *e = 0; + + p = l->rbuf + l->rbuf_index; + m = strlen(p); + + l->rbuf_index += m+1; + l->rbuf_valid_length -= m+1; + + /* A shortcut for the next time */ + if (l->rbuf_valid_length == 0) + l->rbuf_index = 0; + + if (l->callback) + l->callback(l, pa_strip_nl(p), l->userdata); + + skip = 0; + } + + /* If the buffer became too large and still no newline was found, drop it. */ + if (l->rbuf_valid_length >= BUFFER_LIMIT) + l->rbuf_index = l->rbuf_valid_length = 0; +} + +static int do_write(pa_ioline *l); + +static int do_read(pa_ioline *l) { + pa_assert(l); + pa_assert(PA_REFCNT_VALUE(l) >= 1); + + while (l->io && !l->dead && pa_iochannel_is_readable(l->io)) { + ssize_t r; + size_t len; + + len = l->rbuf_length - l->rbuf_index - l->rbuf_valid_length; + + /* Check if we have to enlarge the read buffer */ + if (len < READ_SIZE) { + size_t n = l->rbuf_valid_length+READ_SIZE; + + if (n >= BUFFER_LIMIT) + n = BUFFER_LIMIT; + + if (l->rbuf_length >= n) { + /* The current buffer is large enough, let's just move the data to the front */ + if (l->rbuf_valid_length) + memmove(l->rbuf, l->rbuf+l->rbuf_index, l->rbuf_valid_length); + } else { + /* Enlarge the buffer */ + char *new = pa_xnew(char, (unsigned) n); + if (l->rbuf_valid_length) + memcpy(new, l->rbuf+l->rbuf_index, l->rbuf_valid_length); + pa_xfree(l->rbuf); + l->rbuf = new; + l->rbuf_length = n; + } + + l->rbuf_index = 0; + } + + len = l->rbuf_length - l->rbuf_index - l->rbuf_valid_length; + + pa_assert(len >= READ_SIZE); + + /* Read some data */ + if ((r = pa_iochannel_read(l->io, l->rbuf+l->rbuf_index+l->rbuf_valid_length, len)) <= 0) { + + if (r < 0 && errno == EAGAIN) + return 0; + + if (r < 0 && errno != ECONNRESET) { + pa_log("read(): %s", pa_cstrerror(errno)); + failure(l, false); + } else + failure(l, true); + + return -1; + } + + l->rbuf_valid_length += (size_t) r; + + /* Look if a line has been terminated in the newly read data */ + scan_for_lines(l, l->rbuf_valid_length - (size_t) r); + } + + return 0; +} + +/* Try to flush the buffer */ +static int do_write(pa_ioline *l) { + ssize_t r; + + pa_assert(l); + pa_assert(PA_REFCNT_VALUE(l) >= 1); + + while (l->io && !l->dead && pa_iochannel_is_writable(l->io) && l->wbuf_valid_length > 0) { + + if ((r = pa_iochannel_write(l->io, l->wbuf+l->wbuf_index, l->wbuf_valid_length)) < 0) { + + if (errno != EPIPE) + pa_log("write(): %s", pa_cstrerror(errno)); + + failure(l, false); + + return -1; + } + + l->wbuf_index += (size_t) r; + l->wbuf_valid_length -= (size_t) r; + + /* A shortcut for the next time */ + if (l->wbuf_valid_length == 0) + l->wbuf_index = 0; + } + + if (l->wbuf_valid_length <= 0 && l->drain_callback) + l->drain_callback(l, l->drain_userdata); + + return 0; +} + +/* Try to flush read/write data */ +static void do_work(pa_ioline *l) { + pa_assert(l); + pa_assert(PA_REFCNT_VALUE(l) >= 1); + + pa_ioline_ref(l); + + l->mainloop->defer_enable(l->defer_event, 0); + + if (!l->dead) + do_read(l); + + if (!l->dead) + do_write(l); + + if (l->defer_close && !l->wbuf_valid_length) + failure(l, true); + + pa_ioline_unref(l); +} + +static void io_callback(pa_iochannel*io, void *userdata) { + pa_ioline *l = userdata; + + pa_assert(io); + pa_assert(l); + pa_assert(PA_REFCNT_VALUE(l) >= 1); + + do_work(l); +} + +static void defer_callback(pa_mainloop_api*m, pa_defer_event*e, void *userdata) { + pa_ioline *l = userdata; + + pa_assert(l); + pa_assert(PA_REFCNT_VALUE(l) >= 1); + pa_assert(l->mainloop == m); + pa_assert(l->defer_event == e); + + do_work(l); +} + +void pa_ioline_defer_close(pa_ioline *l) { + pa_assert(l); + pa_assert(PA_REFCNT_VALUE(l) >= 1); + + l->defer_close = true; + + if (!l->wbuf_valid_length) + l->mainloop->defer_enable(l->defer_event, 1); +} + +void pa_ioline_printf(pa_ioline *l, const char *format, ...) { + char *t; + va_list ap; + + pa_assert(l); + pa_assert(PA_REFCNT_VALUE(l) >= 1); + + va_start(ap, format); + t = pa_vsprintf_malloc(format, ap); + va_end(ap); + + pa_ioline_puts(l, t); + pa_xfree(t); +} + +pa_iochannel* pa_ioline_detach_iochannel(pa_ioline *l) { + pa_iochannel *r; + + pa_assert(l); + + if (!l->io) + return NULL; + + r = l->io; + l->io = NULL; + + pa_iochannel_set_callback(r, NULL, NULL); + + return r; +} + +bool pa_ioline_is_drained(pa_ioline *l) { + pa_assert(l); + + return l->wbuf_valid_length <= 0; +} diff --git a/src/pulsecore/ioline.h b/src/pulsecore/ioline.h new file mode 100644 index 0000000..7b6dff3 --- /dev/null +++ b/src/pulsecore/ioline.h @@ -0,0 +1,63 @@ +#ifndef fooiolinehfoo +#define fooiolinehfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulse/gccmacro.h> + +#include <pulsecore/iochannel.h> + +/* An ioline wraps an iochannel for line based communication. A + * callback function is called whenever a new line has been received + * from the client */ + +typedef struct pa_ioline pa_ioline; + +typedef void (*pa_ioline_cb_t)(pa_ioline*io, const char *s, void *userdata); +typedef void (*pa_ioline_drain_cb_t)(pa_ioline *io, void *userdata); + +pa_ioline* pa_ioline_new(pa_iochannel *io); +void pa_ioline_unref(pa_ioline *l); +pa_ioline* pa_ioline_ref(pa_ioline *l); +void pa_ioline_close(pa_ioline *l); + +/* Write a string to the channel */ +void pa_ioline_puts(pa_ioline *s, const char *c); + +/* Write a string to the channel */ +void pa_ioline_printf(pa_ioline *s, const char *format, ...) PA_GCC_PRINTF_ATTR(2,3); + +/* Set the callback function that is called for every received line */ +void pa_ioline_set_callback(pa_ioline*io, pa_ioline_cb_t callback, void *userdata); + +/* Set the callback function that is called when everything has been written */ +void pa_ioline_set_drain_callback(pa_ioline*io, pa_ioline_drain_cb_t callback, void *userdata); + +/* Make sure to close the ioline object as soon as the send buffer is emptied */ +void pa_ioline_defer_close(pa_ioline *io); + +/* Returns true when everything was written */ +bool pa_ioline_is_drained(pa_ioline *io); + +/* Detaches from the iochannel and returns it. Data that has already + * been read will not be available in the detached iochannel */ +pa_iochannel* pa_ioline_detach_iochannel(pa_ioline *l); + +#endif diff --git a/src/pulsecore/ipacl.c b/src/pulsecore/ipacl.c new file mode 100644 index 0000000..e83d8a0 --- /dev/null +++ b/src/pulsecore/ipacl.c @@ -0,0 +1,238 @@ +/*** + 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <sys/types.h> +#include <sys/types.h> +#include <string.h> + +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#ifdef HAVE_NETINET_IN_SYSTM_H +#include <netinet/in_systm.h> +#endif +#ifdef HAVE_NETINET_IP_H +#include <netinet/ip.h> +#endif + +#include <pulse/xmalloc.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/llist.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> +#include <pulsecore/socket.h> +#include <pulsecore/arpa-inet.h> + +#include "ipacl.h" + +struct acl_entry { + PA_LLIST_FIELDS(struct acl_entry); + int family; + struct in_addr address_ipv4; +#ifdef HAVE_IPV6 + struct in6_addr address_ipv6; +#endif + int bits; +}; + +struct pa_ip_acl { + PA_LLIST_HEAD(struct acl_entry, entries); +}; + +pa_ip_acl* pa_ip_acl_new(const char *s) { + const char *state = NULL; + char *a; + pa_ip_acl *acl; + + pa_assert(s); + + acl = pa_xnew(pa_ip_acl, 1); + PA_LLIST_HEAD_INIT(struct acl_entry, acl->entries); + + while ((a = pa_split(s, ";", &state))) { + char *slash; + struct acl_entry e, *n; + uint32_t bits; + + if ((slash = strchr(a, '/'))) { + *slash = 0; + slash++; + if (pa_atou(slash, &bits) < 0) { + pa_log_warn("Failed to parse number of bits: %s", slash); + goto fail; + } + } else + bits = (uint32_t) -1; + + if (inet_pton(AF_INET, a, &e.address_ipv4) > 0) { + + e.bits = bits == (uint32_t) -1 ? 32 : (int) bits; + + if (e.bits > 32) { + pa_log_warn("Number of bits out of range: %i", e.bits); + goto fail; + } + + e.family = AF_INET; + + if (e.bits < 32 && (uint32_t) (ntohl(e.address_ipv4.s_addr) << e.bits) != 0) + pa_log_warn("Host part of ACL entry '%s/%u' is not zero!", a, e.bits); + +#ifdef HAVE_IPV6 + } else if (inet_pton(AF_INET6, a, &e.address_ipv6) > 0) { + + e.bits = bits == (uint32_t) -1 ? 128 : (int) bits; + + if (e.bits > 128) { + pa_log_warn("Number of bits out of range: %i", e.bits); + goto fail; + } + e.family = AF_INET6; + + if (e.bits < 128) { + int t = 0, i; + + for (i = 0, bits = (uint32_t) e.bits; i < 16; i++) { + + if (bits >= 8) + bits -= 8; + else { + if ((uint8_t) ((e.address_ipv6.s6_addr[i]) << bits) != 0) { + t = 1; + break; + } + bits = 0; + } + } + + if (t) + pa_log_warn("Host part of ACL entry '%s/%u' is not zero!", a, e.bits); + } +#endif + + } else { + pa_log_warn("Failed to parse address: %s", a); + goto fail; + } + + n = pa_xmemdup(&e, sizeof(struct acl_entry)); + PA_LLIST_PREPEND(struct acl_entry, acl->entries, n); + + pa_xfree(a); + } + + return acl; + +fail: + pa_xfree(a); + pa_ip_acl_free(acl); + + return NULL; +} + +void pa_ip_acl_free(pa_ip_acl *acl) { + pa_assert(acl); + + while (acl->entries) { + struct acl_entry *e = acl->entries; + PA_LLIST_REMOVE(struct acl_entry, acl->entries, e); + pa_xfree(e); + } + + pa_xfree(acl); +} + +int pa_ip_acl_check(pa_ip_acl *acl, int fd) { + struct sockaddr_storage sa; + struct acl_entry *e; + socklen_t salen; + + pa_assert(acl); + pa_assert(fd >= 0); + + salen = sizeof(sa); + if (getpeername(fd, (struct sockaddr*) &sa, &salen) < 0) + return -1; + +#ifdef HAVE_IPV6 + if (sa.ss_family != AF_INET && sa.ss_family != AF_INET6) +#else + if (sa.ss_family != AF_INET) +#endif + return -1; + + if (sa.ss_family == AF_INET && salen != sizeof(struct sockaddr_in)) + return -1; + +#ifdef HAVE_IPV6 + if (sa.ss_family == AF_INET6 && salen != sizeof(struct sockaddr_in6)) + return -1; +#endif + + for (e = acl->entries; e; e = e->next) { + + if (e->family != sa.ss_family) + continue; + + if (e->family == AF_INET) { + struct sockaddr_in *sai = (struct sockaddr_in*) &sa; + + if (e->bits == 0 || /* this needs special handling because >> takes the right-hand side modulo 32 */ + (ntohl(sai->sin_addr.s_addr ^ e->address_ipv4.s_addr) >> (32 - e->bits)) == 0) + return 1; +#ifdef HAVE_IPV6 + } else if (e->family == AF_INET6) { + int i, bits; + struct sockaddr_in6 *sai = (struct sockaddr_in6*) &sa; + + if (e->bits == 128) + return memcmp(&sai->sin6_addr, &e->address_ipv6, 16) == 0; + + if (e->bits == 0) + return 1; + + for (i = 0, bits = e->bits; i < 16; i++) { + + if (bits >= 8) { + if (sai->sin6_addr.s6_addr[i] != e->address_ipv6.s6_addr[i]) + break; + + bits -= 8; + } else { + if ((sai->sin6_addr.s6_addr[i] ^ e->address_ipv6.s6_addr[i]) >> (8 - bits) != 0) + break; + + bits = 0; + } + + if (bits == 0) + return 1; + } +#endif + } + } + + return 0; +} diff --git a/src/pulsecore/ipacl.h b/src/pulsecore/ipacl.h new file mode 100644 index 0000000..034248a --- /dev/null +++ b/src/pulsecore/ipacl.h @@ -0,0 +1,30 @@ +#ifndef foopulsecoreipaclhfoo +#define foopulsecoreipaclhfoo + +/*** + 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +typedef struct pa_ip_acl pa_ip_acl; + +pa_ip_acl* pa_ip_acl_new(const char *s); +void pa_ip_acl_free(pa_ip_acl *acl); +int pa_ip_acl_check(pa_ip_acl *acl, int fd); + +#endif diff --git a/src/pulsecore/llist.h b/src/pulsecore/llist.h new file mode 100644 index 0000000..a9412fa --- /dev/null +++ b/src/pulsecore/llist.h @@ -0,0 +1,111 @@ +#ifndef foollistfoo +#define foollistfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulsecore/macro.h> + +/* Some macros for maintaining doubly linked lists */ + +/* The head of the linked list. Use this in the structure that shall + * contain the head of the linked list */ +#define PA_LLIST_HEAD(t,name) \ + t *name + +/* The pointers in the linked list's items. Use this in the item structure */ +#define PA_LLIST_FIELDS(t) \ + t *next, *prev + +/* Initialize the list's head */ +#define PA_LLIST_HEAD_INIT(t,item) \ + do { \ + (item) = (t*) NULL; } \ + while(0) + +/* Initialize a list item */ +#define PA_LLIST_INIT(t,item) \ + do { \ + t *_item = (item); \ + pa_assert(_item); \ + _item->prev = _item->next = NULL; \ + } while(0) + +/* Prepend an item to the list */ +#define PA_LLIST_PREPEND(t,head,item) \ + do { \ + t **_head = &(head), *_item = (item); \ + pa_assert(_item); \ + if ((_item->next = *_head)) \ + _item->next->prev = _item; \ + _item->prev = NULL; \ + *_head = _item; \ + } while (0) + +/* Remove an item from the list */ +#define PA_LLIST_REMOVE(t,head,item) \ + do { \ + t **_head = &(head), *_item = (item); \ + pa_assert(_item); \ + if (_item->next) \ + _item->next->prev = _item->prev; \ + if (_item->prev) \ + _item->prev->next = _item->next; \ + else { \ + pa_assert(*_head == _item); \ + *_head = _item->next; \ + } \ + _item->next = _item->prev = NULL; \ + } while(0) + +/* Find the head of the list */ +#define PA_LLIST_FIND_HEAD(t,item,head) \ + do { \ + t **_head = (head), *_item = (item); \ + *_head = _item; \ + pa_assert(_head); \ + while ((*_head)->prev) \ + *_head = (*_head)->prev; \ + } while (0) + +/* Insert an item after another one (a = where, b = what) */ +#define PA_LLIST_INSERT_AFTER(t,head,a,b) \ + do { \ + t **_head = &(head), *_a = (a), *_b = (b); \ + pa_assert(_b); \ + if (!_a) { \ + if ((_b->next = *_head)) \ + _b->next->prev = _b; \ + _b->prev = NULL; \ + *_head = _b; \ + } else { \ + if ((_b->next = _a->next)) \ + _b->next->prev = _b; \ + _b->prev = _a; \ + _a->next = _b; \ + } \ + } while (0) + +#define PA_LLIST_FOREACH(i,head) \ + for (i = (head); i; i = i->next) + +#define PA_LLIST_FOREACH_SAFE(i,n,head) \ + for (i = (head); i && ((n = i->next), 1); i = n) + +#endif diff --git a/src/pulsecore/lock-autospawn.c b/src/pulsecore/lock-autospawn.c new file mode 100644 index 0000000..955fd23 --- /dev/null +++ b/src/pulsecore/lock-autospawn.c @@ -0,0 +1,362 @@ +/*** + This file is part of PulseAudio. + + Copyright 2008 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <string.h> +#include <signal.h> + +#ifdef HAVE_PTHREAD +#include <pthread.h> +#endif + +#include <pulse/gccmacro.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/i18n.h> +#include <pulsecore/poll.h> +#include <pulsecore/mutex.h> +#include <pulsecore/thread.h> +#include <pulsecore/core-util.h> + +#include "lock-autospawn.h" + +/* So, why do we have this complex code here with threads and pipes + * and stuff? For two reasons: POSIX file locks are per-process, not + * per-file descriptor. That means that two contexts within the same + * process that try to create the autospawn lock might end up assuming + * they both managed to lock the file. And then, POSIX locking + * operations are synchronous. If two contexts run from the same event + * loop it must be made sure that they do not block each other, but + * that the locking operation can happen asynchronously. */ + +#define AUTOSPAWN_LOCK "autospawn.lock" + +static pa_mutex *mutex; + +static unsigned n_ref = 0; +static int lock_fd = -1; +static pa_mutex *lock_fd_mutex = NULL; +static pa_thread *thread = NULL; +static int pipe_fd[2] = { -1, -1 }; + +static enum { + STATE_IDLE, + STATE_OWNING, + STATE_TAKEN, + STATE_FAILED +} state = STATE_IDLE; + +static void destroy_mutex(void) PA_GCC_DESTRUCTOR; + +static int ref(void) { + + if (n_ref > 0) { + + pa_assert(pipe_fd[0] >= 0); + pa_assert(pipe_fd[1] >= 0); + pa_assert(lock_fd_mutex); + + n_ref++; + + return 0; + } + + pa_assert(!lock_fd_mutex); + pa_assert(state == STATE_IDLE); + pa_assert(lock_fd < 0); + pa_assert(!thread); + pa_assert(pipe_fd[0] < 0); + pa_assert(pipe_fd[1] < 0); + + if (pa_pipe_cloexec(pipe_fd) < 0) + return -1; + + pa_make_fd_nonblock(pipe_fd[1]); + pa_make_fd_nonblock(pipe_fd[0]); + + lock_fd_mutex = pa_mutex_new(false, false); + + n_ref = 1; + return 0; +} + +static void unref(bool after_fork) { + + pa_assert(n_ref > 0); + pa_assert(pipe_fd[0] >= 0); + pa_assert(pipe_fd[1] >= 0); + pa_assert(lock_fd_mutex); + + n_ref--; + + if (n_ref > 0) + return; + + /* Join threads only in the process the new thread was created in + * to avoid undefined behaviour. + * POSIX.1-2008 XSH 2.9.2 Thread IDs: "applications should only assume + * that thread IDs are usable and unique within a single process." */ + if (thread) { + if (after_fork) + pa_thread_free_nojoin(thread); + else + pa_thread_free(thread); + thread = NULL; + } + + pa_mutex_lock(lock_fd_mutex); + + pa_assert(state != STATE_TAKEN); + + if (state == STATE_OWNING) { + + pa_assert(lock_fd >= 0); + + if (after_fork) + pa_close(lock_fd); + else { + char *lf; + + if (!(lf = pa_runtime_path(AUTOSPAWN_LOCK))) + pa_log_warn(_("Cannot access autospawn lock.")); + + pa_unlock_lockfile(lf, lock_fd); + pa_xfree(lf); + } + } + + lock_fd = -1; + state = STATE_IDLE; + + pa_mutex_unlock(lock_fd_mutex); + + pa_mutex_free(lock_fd_mutex); + lock_fd_mutex = NULL; + + pa_close(pipe_fd[0]); + pa_close(pipe_fd[1]); + pipe_fd[0] = pipe_fd[1] = -1; +} + +static void ping(void) { + ssize_t s; + + pa_assert(pipe_fd[1] >= 0); + + for (;;) { + char x = 'x'; + + if ((s = pa_write(pipe_fd[1], &x, 1, NULL)) == 1) + break; + + pa_assert(s < 0); + + if (errno == EAGAIN) + break; + + pa_assert(errno == EINTR); + } +} + +static void wait_for_ping(void) { + ssize_t s; + char x; + struct pollfd pfd; + int k; + + pa_assert(pipe_fd[0] >= 0); + + memset(&pfd, 0, sizeof(pfd)); + pfd.fd = pipe_fd[0]; + pfd.events = POLLIN; + + if ((k = pa_poll(&pfd, 1, -1)) != 1) { + pa_assert(k < 0); + pa_assert(errno == EINTR); + } else if ((s = pa_read(pipe_fd[0], &x, 1, NULL)) != 1) { + pa_assert(s < 0); + pa_assert(errno == EAGAIN); + } +} + +static void empty_pipe(void) { + char x[16]; + ssize_t s; + + pa_assert(pipe_fd[0] >= 0); + + if ((s = pa_read(pipe_fd[0], &x, sizeof(x), NULL)) < 1) { + pa_assert(s < 0); + pa_assert(errno == EAGAIN); + } +} + +static void thread_func(void *u) { + int fd; + char *lf; + +#ifdef HAVE_PTHREAD + sigset_t fullset; + + /* No signals in this thread please */ + sigfillset(&fullset); + pthread_sigmask(SIG_BLOCK, &fullset, NULL); +#endif + + if (!(lf = pa_runtime_path(AUTOSPAWN_LOCK))) { + pa_log_warn(_("Cannot access autospawn lock.")); + goto fail; + } + + if ((fd = pa_lock_lockfile(lf)) < 0) + goto fail; + + pa_mutex_lock(lock_fd_mutex); + pa_assert(state == STATE_IDLE); + lock_fd = fd; + state = STATE_OWNING; + pa_mutex_unlock(lock_fd_mutex); + + goto finish; + +fail: + pa_mutex_lock(lock_fd_mutex); + pa_assert(state == STATE_IDLE); + state = STATE_FAILED; + pa_mutex_unlock(lock_fd_mutex); + +finish: + pa_xfree(lf); + + ping(); +} + +static int start_thread(void) { + + if (!thread) + if (!(thread = pa_thread_new("autospawn", thread_func, NULL))) + return -1; + + return 0; +} + +static void create_mutex(void) { + PA_ONCE_BEGIN { + mutex = pa_mutex_new(false, false); + } PA_ONCE_END; +} + +static void destroy_mutex(void) { + if (mutex) + pa_mutex_free(mutex); +} + +int pa_autospawn_lock_init(void) { + int ret = -1; + + create_mutex(); + pa_mutex_lock(mutex); + + if (ref() < 0) + ret = -1; + else + ret = pipe_fd[0]; + + pa_mutex_unlock(mutex); + + return ret; +} + +int pa_autospawn_lock_acquire(bool block) { + int ret = -1; + + create_mutex(); + pa_mutex_lock(mutex); + pa_assert(n_ref >= 1); + + pa_mutex_lock(lock_fd_mutex); + + for (;;) { + + empty_pipe(); + + if (state == STATE_OWNING) { + state = STATE_TAKEN; + ret = 1; + break; + } + + if (state == STATE_FAILED) { + ret = -1; + break; + } + + if (state == STATE_IDLE) + if (start_thread() < 0) + break; + + if (!block) { + ret = 0; + break; + } + + pa_mutex_unlock(lock_fd_mutex); + pa_mutex_unlock(mutex); + + wait_for_ping(); + + pa_mutex_lock(mutex); + pa_mutex_lock(lock_fd_mutex); + } + + pa_mutex_unlock(lock_fd_mutex); + + pa_mutex_unlock(mutex); + + return ret; +} + +void pa_autospawn_lock_release(void) { + + create_mutex(); + pa_mutex_lock(mutex); + pa_assert(n_ref >= 1); + + pa_assert(state == STATE_TAKEN); + state = STATE_OWNING; + + ping(); + + pa_mutex_unlock(mutex); +} + +void pa_autospawn_lock_done(bool after_fork) { + + create_mutex(); + pa_mutex_lock(mutex); + pa_assert(n_ref >= 1); + + unref(after_fork); + + pa_mutex_unlock(mutex); +} diff --git a/src/pulsecore/lock-autospawn.h b/src/pulsecore/lock-autospawn.h new file mode 100644 index 0000000..9fc0ab1 --- /dev/null +++ b/src/pulsecore/lock-autospawn.h @@ -0,0 +1,30 @@ +#ifndef foopulselockautospawnhfoo +#define foopulselockautospawnhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2008 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulsecore/macro.h> + +int pa_autospawn_lock_init(void); +int pa_autospawn_lock_acquire(bool block); +void pa_autospawn_lock_release(void); +void pa_autospawn_lock_done(bool after_fork); + +#endif diff --git a/src/pulsecore/log.c b/src/pulsecore/log.c new file mode 100644 index 0000000..2ea8862 --- /dev/null +++ b/src/pulsecore/log.c @@ -0,0 +1,685 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdarg.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <sys/stat.h> + +#ifdef HAVE_EXECINFO_H +#include <execinfo.h> +#endif + +#ifdef HAVE_SYSLOG_H +#include <syslog.h> +#endif + +#ifdef HAVE_SYSTEMD_JOURNAL + +/* sd_journal_send() implicitly add fields for the source file, + * function name and code line from where it's invoked. As the + * correct code location fields CODE_FILE, CODE_LINE and + * CODE_FUNC are already handled by this module, we do not want + * the automatic values supplied by the systemd journal API. + * + * Without suppressing these, both the actual log event source + * and the call to sd_journal_send() will be logged. */ +#define SD_JOURNAL_SUPPRESS_LOCATION + +#include <systemd/sd-journal.h> +#endif + +#include <pulse/gccmacro.h> +#include <pulse/rtclock.h> +#include <pulse/utf8.h> +#include <pulse/xmalloc.h> +#include <pulse/util.h> +#include <pulse/timeval.h> + +#include <pulsecore/macro.h> +#include <pulsecore/core-util.h> +#include <pulsecore/core-error.h> +#include <pulsecore/once.h> +#include <pulsecore/ratelimit.h> +#include <pulsecore/thread.h> +#include <pulsecore/i18n.h> + +#include "log.h" + +#define ENV_LOG_SYSLOG "PULSE_LOG_SYSLOG" +#define ENV_LOG_JOURNAL "PULSE_LOG_JOURNAL" +#define ENV_LOG_LEVEL "PULSE_LOG" +#define ENV_LOG_COLORS "PULSE_LOG_COLORS" +#define ENV_LOG_PRINT_TIME "PULSE_LOG_TIME" +#define ENV_LOG_PRINT_FILE "PULSE_LOG_FILE" +#define ENV_LOG_PRINT_META "PULSE_LOG_META" +#define ENV_LOG_PRINT_LEVEL "PULSE_LOG_LEVEL" +#define ENV_LOG_BACKTRACE "PULSE_LOG_BACKTRACE" +#define ENV_LOG_BACKTRACE_SKIP "PULSE_LOG_BACKTRACE_SKIP" +#define ENV_LOG_NO_RATELIMIT "PULSE_LOG_NO_RATE_LIMIT" +#define LOG_MAX_SUFFIX_NUMBER 99 + +static char *ident = NULL; /* in local charset format */ +static pa_log_target target = { PA_LOG_STDERR, NULL }; +static pa_log_target_type_t target_override; +static bool target_override_set = false; +static pa_log_level_t maximum_level = PA_LOG_ERROR, maximum_level_override = PA_LOG_ERROR; +static unsigned show_backtrace = 0, show_backtrace_override = 0, skip_backtrace = 0; +static pa_log_flags_t flags = 0, flags_override = 0; +static bool no_rate_limit = false; +static int log_fd = -1; +static int write_type = 0; + +#ifdef HAVE_SYSLOG_H +static const int level_to_syslog[] = { + [PA_LOG_ERROR] = LOG_ERR, + [PA_LOG_WARN] = LOG_WARNING, + [PA_LOG_NOTICE] = LOG_NOTICE, + [PA_LOG_INFO] = LOG_INFO, + [PA_LOG_DEBUG] = LOG_DEBUG +}; +#endif + +/* These are actually equivalent to the syslog ones + * but we don't want to depend on syslog.h */ +#ifdef HAVE_SYSTEMD_JOURNAL +static const int level_to_journal[] = { + [PA_LOG_ERROR] = 3, + [PA_LOG_WARN] = 4, + [PA_LOG_NOTICE] = 5, + [PA_LOG_INFO] = 6, + [PA_LOG_DEBUG] = 7 +}; +#endif + +static const char level_to_char[] = { + [PA_LOG_ERROR] = 'E', + [PA_LOG_WARN] = 'W', + [PA_LOG_NOTICE] = 'N', + [PA_LOG_INFO] = 'I', + [PA_LOG_DEBUG] = 'D' +}; + +void pa_log_set_ident(const char *p) { + pa_xfree(ident); + + if (!(ident = pa_utf8_to_locale(p))) + ident = pa_ascii_filter(p); +} + +/* To make valgrind shut up. */ +static void ident_destructor(void) PA_GCC_DESTRUCTOR; +static void ident_destructor(void) { + if (!pa_in_valgrind()) + return; + + pa_xfree(ident); +} + +void pa_log_set_level(pa_log_level_t l) { + pa_assert(l < PA_LOG_LEVEL_MAX); + + maximum_level = l; +} + +int pa_log_set_target(pa_log_target *t) { + int fd = -1; + int old_fd; + + pa_assert(t); + + switch (t->type) { + case PA_LOG_STDERR: + case PA_LOG_SYSLOG: +#ifdef HAVE_SYSTEMD_JOURNAL + case PA_LOG_JOURNAL: +#endif + case PA_LOG_NULL: + break; + case PA_LOG_FILE: + if ((fd = pa_open_cloexec(t->file, O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR)) < 0) { + pa_log(_("Failed to open target file '%s'."), t->file); + return -1; + } + break; + case PA_LOG_NEWFILE: { + char *file_path; + char *p; + unsigned version; + + file_path = pa_sprintf_malloc("%s.xx", t->file); + p = file_path + strlen(t->file); + + for (version = 0; version <= LOG_MAX_SUFFIX_NUMBER; version++) { + memset(p, 0, 3); /* Overwrite the ".xx" part in file_path with zero bytes. */ + + if (version > 0) + pa_snprintf(p, 4, ".%u", version); /* Why 4? ".xx" + termitating zero byte. */ + + if ((fd = pa_open_cloexec(file_path, O_WRONLY | O_TRUNC | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR)) >= 0) + break; + } + + if (version > LOG_MAX_SUFFIX_NUMBER) { + pa_log(_("Tried to open target file '%s', '%s.1', '%s.2' ... '%s.%d', but all failed."), + t->file, t->file, t->file, t->file, LOG_MAX_SUFFIX_NUMBER); + pa_xfree(file_path); + return -1; + } else + pa_log_debug("Opened target file %s\n", file_path); + + pa_xfree(file_path); + break; + } + } + + target.type = t->type; + pa_xfree(target.file); + target.file = pa_xstrdup(t->file); + + old_fd = log_fd; + log_fd = fd; + + if (old_fd >= 0) + pa_close(old_fd); + + return 0; +} + +void pa_log_set_flags(pa_log_flags_t _flags, pa_log_merge_t merge) { + pa_assert(!(_flags & ~(PA_LOG_COLORS|PA_LOG_PRINT_TIME|PA_LOG_PRINT_FILE|PA_LOG_PRINT_META|PA_LOG_PRINT_LEVEL))); + + if (merge == PA_LOG_SET) + flags |= _flags; + else if (merge == PA_LOG_UNSET) + flags &= ~_flags; + else + flags = _flags; +} + +void pa_log_set_show_backtrace(unsigned nlevels) { + show_backtrace = nlevels; +} + +void pa_log_set_skip_backtrace(unsigned nlevels) { + skip_backtrace = nlevels; +} + +#ifdef HAVE_EXECINFO_H + +static char* get_backtrace(unsigned show_nframes) { + void* trace[32]; + int n_frames; + char **symbols, *e, *r; + unsigned j, n, s; + size_t a; + + pa_assert(show_nframes > 0); + + n_frames = backtrace(trace, PA_ELEMENTSOF(trace)); + + if (n_frames <= 0) + return NULL; + + symbols = backtrace_symbols(trace, n_frames); + + if (!symbols) + return NULL; + + s = skip_backtrace; + n = PA_MIN((unsigned) n_frames, s + show_nframes); + + a = 4; + + for (j = s; j < n; j++) { + if (j > s) + a += 2; + a += strlen(pa_path_get_filename(symbols[j])); + } + + r = pa_xnew(char, a); + + strcpy(r, " ("); + e = r + 2; + + for (j = s; j < n; j++) { + const char *sym; + + if (j > s) { + strcpy(e, "<<"); + e += 2; + } + + sym = pa_path_get_filename(symbols[j]); + + strcpy(e, sym); + e += strlen(sym); + } + + strcpy(e, ")"); + + free(symbols); + + return r; +} + +#endif + +static void init_defaults(void) { + PA_ONCE_BEGIN { + + const char *e; + + if (!ident) { + char binary[256]; + if (pa_get_binary_name(binary, sizeof(binary))) + pa_log_set_ident(binary); + } + + if (getenv(ENV_LOG_SYSLOG)) { + target_override = PA_LOG_SYSLOG; + target_override_set = true; + } + +#ifdef HAVE_SYSTEMD_JOURNAL + if (getenv(ENV_LOG_JOURNAL)) { + target_override = PA_LOG_JOURNAL; + target_override_set = true; + } +#endif + + if ((e = getenv(ENV_LOG_LEVEL))) { + maximum_level_override = (pa_log_level_t) atoi(e); + + if (maximum_level_override >= PA_LOG_LEVEL_MAX) + maximum_level_override = PA_LOG_LEVEL_MAX-1; + } + + if (getenv(ENV_LOG_COLORS)) + flags_override |= PA_LOG_COLORS; + + if (getenv(ENV_LOG_PRINT_TIME)) + flags_override |= PA_LOG_PRINT_TIME; + + if (getenv(ENV_LOG_PRINT_FILE)) + flags_override |= PA_LOG_PRINT_FILE; + + if (getenv(ENV_LOG_PRINT_META)) + flags_override |= PA_LOG_PRINT_META; + + if (getenv(ENV_LOG_PRINT_LEVEL)) + flags_override |= PA_LOG_PRINT_LEVEL; + + if ((e = getenv(ENV_LOG_BACKTRACE))) { + show_backtrace_override = (unsigned) atoi(e); + + if (show_backtrace_override <= 0) + show_backtrace_override = 0; + } + + if ((e = getenv(ENV_LOG_BACKTRACE_SKIP))) { + skip_backtrace = (unsigned) atoi(e); + + if (skip_backtrace <= 0) + skip_backtrace = 0; + } + + if (getenv(ENV_LOG_NO_RATELIMIT)) + no_rate_limit = true; + + } PA_ONCE_END; +} + +#ifdef HAVE_SYSLOG_H +static void log_syslog(pa_log_level_t level, char *t, char *timestamp, char *location, char *bt) { + char *local_t; + + openlog(ident, LOG_PID, LOG_USER); + + if ((local_t = pa_utf8_to_locale(t))) + t = local_t; + + syslog(level_to_syslog[level], "%s%s%s%s", timestamp, location, t, pa_strempty(bt)); + pa_xfree(local_t); +} +#endif + +void pa_log_levelv_meta( + pa_log_level_t level, + const char*file, + int line, + const char *func, + const char *format, + va_list ap) { + + char *t, *n; + int saved_errno = errno; + char *bt = NULL; + pa_log_target_type_t _target; + pa_log_level_t _maximum_level; + unsigned _show_backtrace; + pa_log_flags_t _flags; + + /* We don't use dynamic memory allocation here to minimize the hit + * in RT threads */ + char text[16*1024], location[128], timestamp[32]; + + pa_assert(level < PA_LOG_LEVEL_MAX); + pa_assert(format); + + init_defaults(); + + _target = target_override_set ? target_override : target.type; + _maximum_level = PA_MAX(maximum_level, maximum_level_override); + _show_backtrace = PA_MAX(show_backtrace, show_backtrace_override); + _flags = flags | flags_override; + + if (PA_LIKELY(level > _maximum_level)) { + errno = saved_errno; + return; + } + + pa_vsnprintf(text, sizeof(text), format, ap); + + if ((_flags & PA_LOG_PRINT_META) && file && line > 0 && func) + pa_snprintf(location, sizeof(location), "[%s][%s:%i %s()] ", + pa_strnull(pa_thread_get_name(pa_thread_self())), file, line, func); + else if ((_flags & (PA_LOG_PRINT_META|PA_LOG_PRINT_FILE)) && file) + pa_snprintf(location, sizeof(location), "[%s] %s: ", + pa_strnull(pa_thread_get_name(pa_thread_self())), pa_path_get_filename(file)); + else + location[0] = 0; + + if (_flags & PA_LOG_PRINT_TIME) { + static pa_usec_t start, last; + pa_usec_t u, a, r; + + u = pa_rtclock_now(); + + PA_ONCE_BEGIN { + start = u; + last = u; + } PA_ONCE_END; + + r = u - last; + a = u - start; + + /* This is not thread safe, but this is a debugging tool only + * anyway. */ + last = u; + + pa_snprintf(timestamp, sizeof(timestamp), "(%4llu.%03llu|%4llu.%03llu) ", + (unsigned long long) (a / PA_USEC_PER_SEC), + (unsigned long long) (((a / PA_USEC_PER_MSEC)) % 1000), + (unsigned long long) (r / PA_USEC_PER_SEC), + (unsigned long long) (((r / PA_USEC_PER_MSEC)) % 1000)); + + } else + timestamp[0] = 0; + +#ifdef HAVE_EXECINFO_H + if (_show_backtrace > 0) + bt = get_backtrace(_show_backtrace); +#endif + + if (!pa_utf8_valid(text)) + pa_logl(level, "Invalid UTF-8 string following below:"); + + for (t = text; t; t = n) { + if ((n = strchr(t, '\n'))) { + *n = 0; + n++; + } + + /* We ignore strings only made out of whitespace */ + if (t[strspn(t, "\t ")] == 0) + continue; + + switch (_target) { + + case PA_LOG_STDERR: { + const char *prefix = "", *suffix = "", *grey = ""; + char *local_t; + +#ifndef OS_IS_WIN32 + /* Yes indeed. Useless, but fun! */ + if ((_flags & PA_LOG_COLORS) && isatty(STDERR_FILENO)) { + if (level <= PA_LOG_ERROR) + prefix = "\x1B[1;31m"; + else if (level <= PA_LOG_WARN) + prefix = "\x1B[1m"; + + if (bt) + grey = "\x1B[2m"; + + if (grey[0] || prefix[0]) + suffix = "\x1B[0m"; + } +#endif + + /* We shouldn't be using dynamic allocation here to + * minimize the hit in RT threads */ + if ((local_t = pa_utf8_to_locale(t))) + t = local_t; + + if (_flags & PA_LOG_PRINT_LEVEL) + fprintf(stderr, "%s%c: %s%s%s%s%s%s\n", timestamp, level_to_char[level], location, prefix, t, grey, pa_strempty(bt), suffix); + else + fprintf(stderr, "%s%s%s%s%s%s%s\n", timestamp, location, prefix, t, grey, pa_strempty(bt), suffix); +#ifdef OS_IS_WIN32 + fflush(stderr); +#endif + + pa_xfree(local_t); + + break; + } + +#ifdef HAVE_SYSLOG_H + case PA_LOG_SYSLOG: + log_syslog(level, t, timestamp, location, bt); + break; +#endif + +#ifdef HAVE_SYSTEMD_JOURNAL + case PA_LOG_JOURNAL: + if (sd_journal_send("MESSAGE=%s", t, + "PRIORITY=%i", level_to_journal[level], + "CODE_FILE=%s", file, + "CODE_FUNC=%s", func, + "CODE_LINE=%d", line, + "PULSE_BACKTRACE=%s", pa_strempty(bt), + NULL) < 0) { +#ifdef HAVE_SYSLOG_H + pa_log_target new_target = { .type = PA_LOG_SYSLOG, .file = NULL }; + + syslog(level_to_syslog[PA_LOG_ERROR], "%s%s%s", timestamp, __FILE__, + "Error writing logs to the journal. Redirect log messages to syslog."); + log_syslog(level, t, timestamp, location, bt); +#else + pa_log_target new_target = { .type = PA_LOG_STDERR, .file = NULL }; + + saved_errno = errno; + fprintf(stderr, "%s\n", "Error writing logs to the journal. Redirect log messages to console."); + fprintf(stderr, "%s\n", t); +#endif + pa_log_set_target(&new_target); + } + break; +#endif + + case PA_LOG_FILE: + case PA_LOG_NEWFILE: { + char *local_t; + + if ((local_t = pa_utf8_to_locale(t))) + t = local_t; + + if (log_fd >= 0) { + char metadata[256]; + + if (_flags & PA_LOG_PRINT_LEVEL) + pa_snprintf(metadata, sizeof(metadata), "%s%c: %s", timestamp, level_to_char[level], location); + else + pa_snprintf(metadata, sizeof(metadata), "%s%s", timestamp, location); + + if ((pa_write(log_fd, metadata, strlen(metadata), &write_type) < 0) + || (pa_write(log_fd, t, strlen(t), &write_type) < 0) + || (bt && pa_write(log_fd, bt, strlen(bt), &write_type) < 0) + || (pa_write(log_fd, "\n", 1, &write_type) < 0)) { + pa_log_target new_target = { .type = PA_LOG_STDERR, .file = NULL }; + saved_errno = errno; + fprintf(stderr, "%s\n", "Error writing logs to a file descriptor. Redirect log messages to console."); + fprintf(stderr, "%s %s\n", metadata, t); + pa_log_set_target(&new_target); + } + } + + pa_xfree(local_t); + + break; + } + case PA_LOG_NULL: + default: + break; + } + } + + pa_xfree(bt); + errno = saved_errno; +} + +void pa_log_level_meta( + pa_log_level_t level, + const char*file, + int line, + const char *func, + const char *format, ...) { + + va_list ap; + va_start(ap, format); + pa_log_levelv_meta(level, file, line, func, format, ap); + va_end(ap); +} + +void pa_log_levelv(pa_log_level_t level, const char *format, va_list ap) { + pa_log_levelv_meta(level, NULL, 0, NULL, format, ap); +} + +void pa_log_level(pa_log_level_t level, const char *format, ...) { + va_list ap; + + va_start(ap, format); + pa_log_levelv_meta(level, NULL, 0, NULL, format, ap); + va_end(ap); +} + +bool pa_log_ratelimit(pa_log_level_t level) { + /* Not more than 10 messages every 5s */ + static PA_DEFINE_RATELIMIT(ratelimit, 5 * PA_USEC_PER_SEC, 10); + + init_defaults(); + + if (no_rate_limit) + return true; + + return pa_ratelimit_test(&ratelimit, level); +} + +pa_log_target *pa_log_target_new(pa_log_target_type_t type, const char *file) { + pa_log_target *t = NULL; + + t = pa_xnew(pa_log_target, 1); + + t->type = type; + t->file = pa_xstrdup(file); + + return t; +} + +void pa_log_target_free(pa_log_target *t) { + pa_assert(t); + + pa_xfree(t->file); + pa_xfree(t); +} + +pa_log_target *pa_log_parse_target(const char *string) { + pa_log_target *t = NULL; + + pa_assert(string); + + if (pa_streq(string, "stderr")) + t = pa_log_target_new(PA_LOG_STDERR, NULL); + else if (pa_streq(string, "syslog")) + t = pa_log_target_new(PA_LOG_SYSLOG, NULL); +#ifdef HAVE_SYSTEMD_JOURNAL + else if (pa_streq(string, "journal")) + t = pa_log_target_new(PA_LOG_JOURNAL, NULL); +#endif + else if (pa_streq(string, "null")) + t = pa_log_target_new(PA_LOG_NULL, NULL); + else if (pa_startswith(string, "file:")) + t = pa_log_target_new(PA_LOG_FILE, string + 5); + else if (pa_startswith(string, "newfile:")) + t = pa_log_target_new(PA_LOG_NEWFILE, string + 8); + else + pa_log(_("Invalid log target.")); + + return t; +} + +char *pa_log_target_to_string(const pa_log_target *t) { + char *string = NULL; + + pa_assert(t); + + switch (t->type) { + case PA_LOG_STDERR: + string = pa_xstrdup("stderr"); + break; + case PA_LOG_SYSLOG: + string = pa_xstrdup("syslog"); + break; +#ifdef HAVE_SYSTEMD_JOURNAL + case PA_LOG_JOURNAL: + string = pa_xstrdup("journal"); + break; +#endif + case PA_LOG_NULL: + string = pa_xstrdup("null"); + break; + case PA_LOG_FILE: + string = pa_sprintf_malloc("file:%s", t->file); + break; + case PA_LOG_NEWFILE: + string = pa_sprintf_malloc("newfile:%s", t->file); + break; + } + + return string; +} diff --git a/src/pulsecore/log.h b/src/pulsecore/log.h new file mode 100644 index 0000000..803ed5a --- /dev/null +++ b/src/pulsecore/log.h @@ -0,0 +1,155 @@ +#ifndef foologhfoo +#define foologhfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <stdarg.h> +#include <stdlib.h> + +#include <pulsecore/macro.h> +#include <pulse/gccmacro.h> + +/* A simple logging subsystem */ + +/* Where to log to */ +typedef enum pa_log_target_type { + PA_LOG_STDERR, /* default */ + PA_LOG_SYSLOG, +#ifdef HAVE_SYSTEMD_JOURNAL + PA_LOG_JOURNAL, /* systemd journal */ +#endif + PA_LOG_NULL, /* to /dev/null */ + PA_LOG_FILE, /* to a user specified file */ + PA_LOG_NEWFILE, /* with an automatic suffix to avoid overwriting anything */ +} pa_log_target_type_t; + +typedef enum pa_log_level { + PA_LOG_ERROR = 0, /* Error messages */ + PA_LOG_WARN = 1, /* Warning messages */ + PA_LOG_NOTICE = 2, /* Notice messages */ + PA_LOG_INFO = 3, /* Info messages */ + PA_LOG_DEBUG = 4, /* Debug messages */ + PA_LOG_LEVEL_MAX +} pa_log_level_t; + +typedef enum pa_log_flags { + PA_LOG_COLORS = 0x01, /* Show colorful output */ + PA_LOG_PRINT_TIME = 0x02, /* Show time */ + PA_LOG_PRINT_FILE = 0x04, /* Show source file */ + PA_LOG_PRINT_META = 0x08, /* Show extended location information */ + PA_LOG_PRINT_LEVEL = 0x10, /* Show log level prefix */ +} pa_log_flags_t; + +typedef enum pa_log_merge { + PA_LOG_SET, + PA_LOG_UNSET, + PA_LOG_RESET +} pa_log_merge_t; + +typedef struct { + pa_log_target_type_t type; + char *file; +} pa_log_target; + +/* Set an identification for the current daemon. Used when logging to syslog. */ +void pa_log_set_ident(const char *p); + +/* Set a log target. */ +int pa_log_set_target(pa_log_target *t); + +/* Maximal log level */ +void pa_log_set_level(pa_log_level_t l); + +/* Set flags */ +void pa_log_set_flags(pa_log_flags_t flags, pa_log_merge_t merge); + +/* Enable backtrace */ +void pa_log_set_show_backtrace(unsigned nlevels); + +/* Skip the first backtrace frames */ +void pa_log_set_skip_backtrace(unsigned nlevels); + +void pa_log_level_meta( + pa_log_level_t level, + const char*file, + int line, + const char *func, + const char *format, ...) PA_GCC_PRINTF_ATTR(5,6); + +void pa_log_levelv_meta( + pa_log_level_t level, + const char*file, + int line, + const char *func, + const char *format, + va_list ap); + +void pa_log_level( + pa_log_level_t level, + const char *format, ...) PA_GCC_PRINTF_ATTR(2,3); + +void pa_log_levelv( + pa_log_level_t level, + const char *format, + va_list ap); + +pa_log_target *pa_log_target_new(pa_log_target_type_t type, const char *file); + +void pa_log_target_free(pa_log_target *t); + +pa_log_target *pa_log_parse_target(const char *string); + +char *pa_log_target_to_string(const pa_log_target *t); + +#if __STDC_VERSION__ >= 199901L + +/* ISO varargs available */ + +#define pa_log_debug(...) pa_log_level_meta(PA_LOG_DEBUG, __FILE__, __LINE__, __func__, __VA_ARGS__) +#define pa_log_info(...) pa_log_level_meta(PA_LOG_INFO, __FILE__, __LINE__, __func__, __VA_ARGS__) +#define pa_log_notice(...) pa_log_level_meta(PA_LOG_NOTICE, __FILE__, __LINE__, __func__, __VA_ARGS__) +#define pa_log_warn(...) pa_log_level_meta(PA_LOG_WARN, __FILE__, __LINE__, __func__, __VA_ARGS__) +#define pa_log_error(...) pa_log_level_meta(PA_LOG_ERROR, __FILE__, __LINE__, __func__, __VA_ARGS__) +#define pa_logl(level, ...) pa_log_level_meta(level, __FILE__, __LINE__, __func__, __VA_ARGS__) + +#else + +#define LOG_FUNC(suffix, level) \ +PA_GCC_UNUSED static void pa_log_##suffix(const char *format, ...) { \ + va_list ap; \ + va_start(ap, format); \ + pa_log_levelv_meta(level, NULL, 0, NULL, format, ap); \ + va_end(ap); \ +} + +LOG_FUNC(debug, PA_LOG_DEBUG) +LOG_FUNC(info, PA_LOG_INFO) +LOG_FUNC(notice, PA_LOG_NOTICE) +LOG_FUNC(warn, PA_LOG_WARN) +LOG_FUNC(error, PA_LOG_ERROR) + +#endif + +#define pa_log pa_log_error + +bool pa_log_ratelimit(pa_log_level_t level); + +#endif diff --git a/src/pulsecore/ltdl-helper.c b/src/pulsecore/ltdl-helper.c new file mode 100644 index 0000000..cfdde26 --- /dev/null +++ b/src/pulsecore/ltdl-helper.c @@ -0,0 +1,63 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <ctype.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/macro.h> + +#include "ltdl-helper.h" + +pa_void_func_t pa_load_sym(lt_dlhandle handle, const char *module, const char *symbol) { + char *sn, *c; + pa_void_func_t f; + + pa_assert(handle); + pa_assert(symbol); + + f = (pa_void_func_t) lt_dlsym(handle, symbol); + + if (f) + return f; + + if (!module) + return NULL; + + /* As the .la files might have been cleansed from the system, we should + * try with the ltdl prefix as well. */ + + sn = pa_sprintf_malloc("%s_LTX_%s", module, symbol); + + for (c = sn; *c; c++) + if (!isalnum((unsigned char)*c)) + *c = '_'; + + f = (pa_void_func_t) lt_dlsym(handle, sn); + pa_xfree(sn); + + return f; +} diff --git a/src/pulsecore/ltdl-helper.h b/src/pulsecore/ltdl-helper.h new file mode 100644 index 0000000..d3219ba --- /dev/null +++ b/src/pulsecore/ltdl-helper.h @@ -0,0 +1,29 @@ +#ifndef foopulsecoreltdlhelperhfoo +#define foopulsecoreltdlhelperhfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <ltdl.h> + +typedef void (*pa_void_func_t)(void); + +pa_void_func_t pa_load_sym(lt_dlhandle handle, const char*module, const char *symbol); + +#endif diff --git a/src/pulsecore/macro.h b/src/pulsecore/macro.h new file mode 100644 index 0000000..ec8d31c --- /dev/null +++ b/src/pulsecore/macro.h @@ -0,0 +1,272 @@ +#ifndef foopulsemacrohfoo +#define foopulsemacrohfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/types.h> +#include <unistd.h> +#include <assert.h> +#include <limits.h> +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdbool.h> + +#ifndef PACKAGE +#error "Please include config.h before including this file!" +#endif + +/* Rounds down */ +static inline void* PA_ALIGN_PTR(const void *p) { + return (void*) (((size_t) p) & ~(sizeof(void*) - 1)); +} + +/* Rounds up */ +static inline size_t PA_ALIGN(size_t l) { + return ((l + sizeof(void*) - 1) & ~(sizeof(void*) - 1)); +} + +#if defined(__GNUC__) + #define PA_UNUSED __attribute__ ((unused)) +#else + #define PA_UNUSED +#endif + +#define PA_ELEMENTSOF(x) (sizeof(x)/sizeof((x)[0])) + +#if defined(__GNUC__) + #define PA_DECLARE_ALIGNED(n,t,v) t v __attribute__ ((aligned (n))) +#else + #define PA_DECLARE_ALIGNED(n,t,v) t v +#endif + +#ifdef __GNUC__ +#define typeof __typeof__ +#endif + +/* The users of PA_MIN and PA_MAX, PA_CLAMP, PA_ROUND_UP should be + * aware that these macros on non-GCC executed code with side effects + * twice. It is thus considered misuse to use code with side effects + * as arguments to MIN and MAX. */ + +#ifdef __GNUC__ +#define PA_MAX(a,b) \ + __extension__ ({ \ + typeof(a) _a = (a); \ + typeof(b) _b = (b); \ + _a > _b ? _a : _b; \ + }) +#else +#define PA_MAX(a, b) ((a) > (b) ? (a) : (b)) +#endif + +#ifdef __GNUC__ +#define PA_MIN(a,b) \ + __extension__ ({ \ + typeof(a) _a = (a); \ + typeof(b) _b = (b); \ + _a < _b ? _a : _b; \ + }) +#else +#define PA_MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif + +#ifdef __GNUC__ +#define PA_ROUND_UP(a, b) \ + __extension__ ({ \ + typeof(a) _a = (a); \ + typeof(b) _b = (b); \ + ((_a + _b - 1) / _b) * _b; \ + }) +#else +#define PA_ROUND_UP(a, b) ((((a) + (b) - 1) / (b)) * (b)) +#endif + +#ifdef __GNUC__ +#define PA_ROUND_DOWN(a, b) \ + __extension__ ({ \ + typeof(a) _a = (a); \ + typeof(b) _b = (b); \ + (_a / _b) * _b; \ + }) +#else +#define PA_ROUND_DOWN(a, b) (((a) / (b)) * (b)) +#endif + +#ifdef __GNUC__ +#define PA_CLIP_SUB(a, b) \ + __extension__ ({ \ + typeof(a) _a = (a); \ + typeof(b) _b = (b); \ + _a > _b ? _a - _b : 0; \ + }) +#else +#define PA_CLIP_SUB(a, b) ((a) > (b) ? (a) - (b) : 0) +#endif + +#ifdef __GNUC__ +#define PA_PRETTY_FUNCTION __PRETTY_FUNCTION__ +#else +#define PA_PRETTY_FUNCTION "" +#endif + +#define pa_return_if_fail(expr) \ + do { \ + if (PA_UNLIKELY(!(expr))) { \ + pa_log_debug("Assertion '%s' failed at %s:%u, function %s.", #expr , __FILE__, __LINE__, PA_PRETTY_FUNCTION); \ + return; \ + } \ + } while(false) + +#define pa_return_val_if_fail(expr, val) \ + do { \ + if (PA_UNLIKELY(!(expr))) { \ + pa_log_debug("Assertion '%s' failed at %s:%u, function %s.", #expr , __FILE__, __LINE__, PA_PRETTY_FUNCTION); \ + return (val); \ + } \ + } while(false) + +#define pa_return_null_if_fail(expr) pa_return_val_if_fail(expr, NULL) + +/* pa_assert_se() is an assert which guarantees side effects of x, + * i.e. is never optimized away, regardless of NDEBUG or FASTPATH. */ +#ifndef __COVERITY__ +#define pa_assert_se(expr) \ + do { \ + if (PA_UNLIKELY(!(expr))) { \ + pa_log_error("Assertion '%s' failed at %s:%u, function %s(). Aborting.", #expr , __FILE__, __LINE__, PA_PRETTY_FUNCTION); \ + abort(); \ + } \ + } while (false) +#else +#define pa_assert_se(expr) \ + do { \ + int _unique_var = (expr); \ + if (!_unique_var) \ + abort(); \ + } while (false) +#endif + +/* Does exactly nothing */ +#define pa_nop() do {} while (false) + +/* pa_assert() is an assert that may be optimized away by defining + * NDEBUG. pa_assert_fp() is an assert that may be optimized away by + * defining FASTPATH. It is supposed to be used in inner loops. It's + * there for extra paranoia checking and should probably be removed in + * production builds. */ +#ifdef NDEBUG +#define pa_assert(expr) pa_nop() +#define pa_assert_fp(expr) pa_nop() +#elif defined (FASTPATH) +#define pa_assert(expr) pa_assert_se(expr) +#define pa_assert_fp(expr) pa_nop() +#else +#define pa_assert(expr) pa_assert_se(expr) +#define pa_assert_fp(expr) pa_assert_se(expr) +#endif + +#ifdef NDEBUG +#define pa_assert_not_reached() abort() +#else +#define pa_assert_not_reached() \ + do { \ + pa_log_error("Code should not be reached at %s:%u, function %s(). Aborting.", __FILE__, __LINE__, PA_PRETTY_FUNCTION); \ + abort(); \ + } while (false) +#endif + +/* A compile time assertion */ +#define pa_assert_cc(expr) \ + do { \ + switch (0) { \ + case 0: \ + case !!(expr): \ + ; \ + } \ + } while (false) + +#define PA_PTR_TO_UINT(p) ((unsigned int) ((uintptr_t) (p))) +#define PA_UINT_TO_PTR(u) ((void*) ((uintptr_t) (u))) + +#define PA_PTR_TO_UINT32(p) ((uint32_t) ((uintptr_t) (p))) +#define PA_UINT32_TO_PTR(u) ((void*) ((uintptr_t) (u))) + +#define PA_PTR_TO_INT(p) ((int) ((intptr_t) (p))) +#define PA_INT_TO_PTR(u) ((void*) ((intptr_t) (u))) + +#define PA_PTR_TO_INT32(p) ((int32_t) ((intptr_t) (p))) +#define PA_INT32_TO_PTR(u) ((void*) ((intptr_t) (u))) + +#ifdef OS_IS_WIN32 +#define PA_PATH_SEP "\\" +#define PA_PATH_SEP_CHAR '\\' +#else +#define PA_PATH_SEP "/" +#define PA_PATH_SEP_CHAR '/' +#endif + +#if defined(__GNUC__) && defined(__ELF__) + +#define PA_WARN_REFERENCE(sym, msg) \ + __asm__(".section .gnu.warning." #sym); \ + __asm__(".asciz \"" msg "\""); \ + __asm__(".previous") + +#else + +#define PA_WARN_REFERENCE(sym, msg) + +#endif + +#if defined(__i386__) || defined(__x86_64__) +#define PA_DEBUG_TRAP __asm__("int $3") +#else +#define PA_DEBUG_TRAP raise(SIGTRAP) +#endif + +#define pa_memzero(x,l) (memset((x), 0, (l))) +#define pa_zero(x) (pa_memzero(&(x), sizeof(x))) + +#define PA_INT_TYPE_SIGNED(type) (!!((type) 0 > (type) -1)) + +#define PA_INT_TYPE_HALF(type) ((type) 1 << (sizeof(type)*8 - 2)) + +#define PA_INT_TYPE_MAX(type) \ + ((type) (PA_INT_TYPE_SIGNED(type) \ + ? (PA_INT_TYPE_HALF(type) - 1 + PA_INT_TYPE_HALF(type)) \ + : (type) -1)) + +#define PA_INT_TYPE_MIN(type) \ + ((type) (PA_INT_TYPE_SIGNED(type) \ + ? (-1 - PA_INT_TYPE_MAX(type)) \ + : (type) 0)) + +/* The '#' preprocessor operator doesn't expand any macros that are in the + * parameter, which is why we need a separate macro for those cases where the + * parameter contains a macro that needs expanding. */ +#define PA_STRINGIZE(x) #x +#define PA_EXPAND_AND_STRINGIZE(x) PA_STRINGIZE(x) + +/* We include this at the very last place */ +#include <pulsecore/log.h> + +#endif diff --git a/src/pulsecore/mcalign.c b/src/pulsecore/mcalign.c new file mode 100644 index 0000000..df885c2 --- /dev/null +++ b/src/pulsecore/mcalign.c @@ -0,0 +1,216 @@ +/*** + 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/macro.h> + +#include "mcalign.h" + +struct pa_mcalign { + size_t base; + pa_memchunk leftover, current; +}; + +pa_mcalign *pa_mcalign_new(size_t base) { + pa_mcalign *m; + pa_assert(base); + + m = pa_xnew(pa_mcalign, 1); + + m->base = base; + pa_memchunk_reset(&m->leftover); + pa_memchunk_reset(&m->current); + + return m; +} + +void pa_mcalign_free(pa_mcalign *m) { + pa_assert(m); + + if (m->leftover.memblock) + pa_memblock_unref(m->leftover.memblock); + + if (m->current.memblock) + pa_memblock_unref(m->current.memblock); + + pa_xfree(m); +} + +void pa_mcalign_push(pa_mcalign *m, const pa_memchunk *c) { + pa_assert(m); + pa_assert(c); + + pa_assert(c->memblock); + pa_assert(c->length > 0); + + pa_assert(!m->current.memblock); + + /* Append to the leftover memory block */ + if (m->leftover.memblock) { + + /* Try to merge */ + if (m->leftover.memblock == c->memblock && + m->leftover.index + m->leftover.length == c->index) { + + /* Merge */ + m->leftover.length += c->length; + + /* If the new chunk is larger than m->base, move it to current */ + if (m->leftover.length >= m->base) { + m->current = m->leftover; + pa_memchunk_reset(&m->leftover); + } + + } else { + size_t l; + void *lo_data, *m_data; + + /* We have to copy */ + pa_assert(m->leftover.length < m->base); + l = m->base - m->leftover.length; + + if (l > c->length) + l = c->length; + + /* Can we use the current block? */ + pa_memchunk_make_writable(&m->leftover, m->base); + + lo_data = pa_memblock_acquire(m->leftover.memblock); + m_data = pa_memblock_acquire(c->memblock); + memcpy((uint8_t*) lo_data + m->leftover.index + m->leftover.length, (uint8_t*) m_data + c->index, l); + pa_memblock_release(m->leftover.memblock); + pa_memblock_release(c->memblock); + m->leftover.length += l; + + pa_assert(m->leftover.length <= m->base); + pa_assert(m->leftover.length <= pa_memblock_get_length(m->leftover.memblock)); + + if (c->length > l) { + /* Save the remainder of the memory block */ + m->current = *c; + m->current.index += l; + m->current.length -= l; + pa_memblock_ref(m->current.memblock); + } + } + } else { + /* Nothing to merge or copy, just store it */ + + if (c->length >= m->base) + m->current = *c; + else + m->leftover = *c; + + pa_memblock_ref(c->memblock); + } +} + +int pa_mcalign_pop(pa_mcalign *m, pa_memchunk *c) { + pa_assert(m); + pa_assert(c); + + /* First test if there's a leftover memory block available */ + if (m->leftover.memblock) { + pa_assert(m->leftover.length > 0); + pa_assert(m->leftover.length <= m->base); + + /* The leftover memory block is not yet complete */ + if (m->leftover.length < m->base) + return -1; + + /* Return the leftover memory block */ + *c = m->leftover; + pa_memchunk_reset(&m->leftover); + + /* If the current memblock is too small move it the leftover */ + if (m->current.memblock && m->current.length < m->base) { + m->leftover = m->current; + pa_memchunk_reset(&m->current); + } + + return 0; + } + + /* Now let's see if there is other data available */ + if (m->current.memblock) { + size_t l; + pa_assert(m->current.length >= m->base); + + /* The length of the returned memory block */ + l = m->current.length; + l /= m->base; + l *= m->base; + pa_assert(l > 0); + + /* Prepare the returned block */ + *c = m->current; + pa_memblock_ref(c->memblock); + c->length = l; + + /* Drop that from the current memory block */ + pa_assert(l <= m->current.length); + m->current.index += l; + m->current.length -= l; + + /* In case the whole block was dropped ... */ + if (m->current.length == 0) + pa_memblock_unref(m->current.memblock); + else { + /* Move the remainder to leftover */ + pa_assert(m->current.length < m->base && !m->leftover.memblock); + + m->leftover = m->current; + } + + pa_memchunk_reset(&m->current); + + return 0; + } + + /* There's simply nothing */ + return -1; +} + +size_t pa_mcalign_csize(pa_mcalign *m, size_t l) { + pa_assert(m); + pa_assert(l > 0); + + pa_assert(!m->current.memblock); + + if (m->leftover.memblock) + l += m->leftover.length; + + return (l/m->base)*m->base; +} + +void pa_mcalign_flush(pa_mcalign *m) { + pa_memchunk chunk; + pa_assert(m); + + while (pa_mcalign_pop(m, &chunk) >= 0) + pa_memblock_unref(chunk.memblock); +} diff --git a/src/pulsecore/mcalign.h b/src/pulsecore/mcalign.h new file mode 100644 index 0000000..353e66a --- /dev/null +++ b/src/pulsecore/mcalign.h @@ -0,0 +1,81 @@ +#ifndef foomcalignhfoo +#define foomcalignhfoo + +/*** + 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulsecore/memblock.h> +#include <pulsecore/memchunk.h> + +/* An alignment object, used for aligning memchunks to multiples of + * the frame size. */ + +/* Method of operation: the user creates a new mcalign object by + * calling pa_mcalign_new() with the appropriate aligning + * granularity. After that they may call pa_mcalign_push() for an input + * memchunk. After exactly one memchunk the user has to call + * pa_mcalign_pop() until it returns -1. If pa_mcalign_pop() returns + * 0, the memchunk *c is valid and aligned to the granularity. Some + * pseudocode illustrating this: + * + * pa_mcalign *a = pa_mcalign_new(4, NULL); + * + * for (;;) { + * pa_memchunk input; + * + * ... fill input ... + * + * pa_mcalign_push(m, &input); + * pa_memblock_unref(input.memblock); + * + * for (;;) { + * pa_memchunk output; + * + * if (pa_mcalign_pop(m, &output) < 0) + * break; + * + * ... consume output ... + * + * pa_memblock_unref(output.memblock); + * } + * } + * + * pa_memchunk_free(a); + * */ + +typedef struct pa_mcalign pa_mcalign; + +pa_mcalign *pa_mcalign_new(size_t base); +void pa_mcalign_free(pa_mcalign *m); + +/* Push a new memchunk into the aligner. The caller of this routine + * has to free the memchunk by himself. */ +void pa_mcalign_push(pa_mcalign *m, const pa_memchunk *c); + +/* Pop a new memchunk from the aligner. Returns 0 when successful, + * nonzero otherwise. */ +int pa_mcalign_pop(pa_mcalign *m, pa_memchunk *c); + +/* If we pass l bytes in now, how many bytes would we get out? */ +size_t pa_mcalign_csize(pa_mcalign *m, size_t l); + +/* Flush what's still stored in the aligner */ +void pa_mcalign_flush(pa_mcalign *m); + +#endif diff --git a/src/pulsecore/mem.h b/src/pulsecore/mem.h new file mode 100644 index 0000000..cba1410 --- /dev/null +++ b/src/pulsecore/mem.h @@ -0,0 +1,60 @@ +#ifndef foopulsememhfoo +#define foopulsememhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2016 Ahmed S. Darwish <darwish.07@gmail.com> + + 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <stdbool.h> + +#include <pulsecore/creds.h> +#include <pulsecore/macro.h> + +typedef enum pa_mem_type { + PA_MEM_TYPE_SHARED_POSIX, /* Data is shared and created using POSIX shm_open() */ + PA_MEM_TYPE_SHARED_MEMFD, /* Data is shared and created using Linux memfd_create() */ + PA_MEM_TYPE_PRIVATE, /* Data is private and created using classic memory allocation + (posix_memalign(), malloc() or anonymous mmap()) */ +} pa_mem_type_t; + +static inline const char *pa_mem_type_to_string(pa_mem_type_t type) { + switch (type) { + case PA_MEM_TYPE_SHARED_POSIX: + return "shared posix-shm"; + case PA_MEM_TYPE_SHARED_MEMFD: + return "shared memfd"; + case PA_MEM_TYPE_PRIVATE: + return "private"; + } + + pa_assert_not_reached(); +} + +static inline bool pa_mem_type_is_shared(pa_mem_type_t t) { + return (t == PA_MEM_TYPE_SHARED_POSIX) || (t == PA_MEM_TYPE_SHARED_MEMFD); +} + +static inline bool pa_memfd_is_locally_supported() { +#if defined(HAVE_CREDS) && defined(HAVE_MEMFD) + return true; +#else + return false; +#endif +} + +#endif diff --git a/src/pulsecore/memblock.c b/src/pulsecore/memblock.c new file mode 100644 index 0000000..fb83dac --- /dev/null +++ b/src/pulsecore/memblock.c @@ -0,0 +1,1518 @@ +/*** + 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.1 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 + Lesser General Public License for more details + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <signal.h> +#include <errno.h> + +#ifdef HAVE_VALGRIND_MEMCHECK_H +#include <valgrind/memcheck.h> +#endif + +#include <pulse/xmalloc.h> +#include <pulse/def.h> + +#include <pulsecore/shm.h> +#include <pulsecore/log.h> +#include <pulsecore/hashmap.h> +#include <pulsecore/semaphore.h> +#include <pulsecore/mutex.h> +#include <pulsecore/macro.h> +#include <pulsecore/refcnt.h> +#include <pulsecore/llist.h> +#include <pulsecore/flist.h> +#include <pulsecore/core-util.h> +#include <pulsecore/memtrap.h> + +#include "memblock.h" + +/* We can allocate 64*1024*1024 bytes at maximum. That's 64MB. Please + * note that the footprint is usually much smaller, since the data is + * stored in SHM and our OS does not commit the memory before we use + * it for the first time. */ +#define PA_MEMPOOL_SLOTS_MAX 1024 +#define PA_MEMPOOL_SLOT_SIZE (64*1024) + +#define PA_MEMEXPORT_SLOTS_MAX 128 + +#define PA_MEMIMPORT_SLOTS_MAX 160 +#define PA_MEMIMPORT_SEGMENTS_MAX 16 + +struct pa_memblock { + PA_REFCNT_DECLARE; /* the reference counter */ + pa_mempool *pool; + + pa_memblock_type_t type; + + bool read_only:1; + bool is_silence:1; + + pa_atomic_ptr_t data; + size_t length; + + pa_atomic_t n_acquired; + pa_atomic_t please_signal; + + union { + struct { + /* If type == PA_MEMBLOCK_USER this points to a function for freeing this memory block */ + pa_free_cb_t free_cb; + /* If type == PA_MEMBLOCK_USER this is passed as free_cb argument */ + void *free_cb_data; + } user; + + struct { + uint32_t id; + pa_memimport_segment *segment; + } imported; + } per_type; +}; + +struct pa_memimport_segment { + pa_memimport *import; + pa_shm memory; + pa_memtrap *trap; + unsigned n_blocks; + bool writable; +}; + +/* + * If true, this segment's lifetime will not be limited by the + * number of active blocks (seg->n_blocks) using its shared memory. + * Rather, it will exist for the full lifetime of the memimport it + * is attached to. + * + * This is done to support memfd blocks transport. + * + * To transfer memfd-backed blocks without passing their fd every + * time, thus minimizing overhead and avoiding fd leaks, a command + * is sent with the memfd fd as ancil data very early on. + * + * This command has an ID that identifies the memfd region. Further + * block references are then exclusively done using this ID. On the + * receiving end, such logic is enabled by the memimport's segment + * hash and 'permanent' segments below. + */ +static bool segment_is_permanent(pa_memimport_segment *seg) { + pa_assert(seg); + return seg->memory.type == PA_MEM_TYPE_SHARED_MEMFD; +} + +/* A collection of multiple segments */ +struct pa_memimport { + pa_mutex *mutex; + + pa_mempool *pool; + pa_hashmap *segments; + pa_hashmap *blocks; + + /* Called whenever an imported memory block is no longer + * needed. */ + pa_memimport_release_cb_t release_cb; + void *userdata; + + PA_LLIST_FIELDS(pa_memimport); +}; + +struct memexport_slot { + PA_LLIST_FIELDS(struct memexport_slot); + pa_memblock *block; +}; + +struct pa_memexport { + pa_mutex *mutex; + pa_mempool *pool; + + struct memexport_slot slots[PA_MEMEXPORT_SLOTS_MAX]; + + PA_LLIST_HEAD(struct memexport_slot, free_slots); + PA_LLIST_HEAD(struct memexport_slot, used_slots); + unsigned n_init; + unsigned baseidx; + + /* Called whenever a client from which we imported a memory block + which we in turn exported to another client dies and we need to + revoke the memory block accordingly */ + pa_memexport_revoke_cb_t revoke_cb; + void *userdata; + + PA_LLIST_FIELDS(pa_memexport); +}; + +struct pa_mempool { + /* Reference count the mempool + * + * Any block allocation from the pool itself, or even just imported from + * another process through SHM and attached to it (PA_MEMBLOCK_IMPORTED), + * shall increase the refcount. + * + * This is done for per-client mempools: global references to blocks in + * the pool, or just to attached ones, can still be lingering around when + * the client connection dies and all per-client objects are to be freed. + * That is, current PulseAudio design does not guarantee that the client + * mempool blocks are referenced only by client-specific objects. + * + * For further details, please check: + * https://lists.freedesktop.org/archives/pulseaudio-discuss/2016-February/025587.html + */ + PA_REFCNT_DECLARE; + + pa_semaphore *semaphore; + pa_mutex *mutex; + + pa_shm memory; + + bool global; + + size_t block_size; + unsigned n_blocks; + bool is_remote_writable; + + pa_atomic_t n_init; + + PA_LLIST_HEAD(pa_memimport, imports); + PA_LLIST_HEAD(pa_memexport, exports); + + /* A list of free slots that may be reused */ + pa_flist *free_slots; + + pa_mempool_stat stat; +}; + +static void segment_detach(pa_memimport_segment *seg); + +PA_STATIC_FLIST_DECLARE(unused_memblocks, 0, pa_xfree); + +/* No lock necessary */ +static void stat_add(pa_memblock*b) { + pa_assert(b); + pa_assert(b->pool); + + pa_atomic_inc(&b->pool->stat.n_allocated); + pa_atomic_add(&b->pool->stat.allocated_size, (int) b->length); + + pa_atomic_inc(&b->pool->stat.n_accumulated); + pa_atomic_add(&b->pool->stat.accumulated_size, (int) b->length); + + if (b->type == PA_MEMBLOCK_IMPORTED) { + pa_atomic_inc(&b->pool->stat.n_imported); + pa_atomic_add(&b->pool->stat.imported_size, (int) b->length); + } + + pa_atomic_inc(&b->pool->stat.n_allocated_by_type[b->type]); + pa_atomic_inc(&b->pool->stat.n_accumulated_by_type[b->type]); +} + +/* No lock necessary */ +static void stat_remove(pa_memblock *b) { + pa_assert(b); + pa_assert(b->pool); + + pa_assert(pa_atomic_load(&b->pool->stat.n_allocated) > 0); + pa_assert(pa_atomic_load(&b->pool->stat.allocated_size) >= (int) b->length); + + pa_atomic_dec(&b->pool->stat.n_allocated); + pa_atomic_sub(&b->pool->stat.allocated_size, (int) b->length); + + if (b->type == PA_MEMBLOCK_IMPORTED) { + pa_assert(pa_atomic_load(&b->pool->stat.n_imported) > 0); + pa_assert(pa_atomic_load(&b->pool->stat.imported_size) >= (int) b->length); + + pa_atomic_dec(&b->pool->stat.n_imported); + pa_atomic_sub(&b->pool->stat.imported_size, (int) b->length); + } + + pa_atomic_dec(&b->pool->stat.n_allocated_by_type[b->type]); +} + +static pa_memblock *memblock_new_appended(pa_mempool *p, size_t length); + +/* No lock necessary */ +pa_memblock *pa_memblock_new(pa_mempool *p, size_t length) { + pa_memblock *b; + + pa_assert(p); + pa_assert(length); + + if (!(b = pa_memblock_new_pool(p, length))) + b = memblock_new_appended(p, length); + + return b; +} + +/* No lock necessary */ +static pa_memblock *memblock_new_appended(pa_mempool *p, size_t length) { + pa_memblock *b; + + pa_assert(p); + pa_assert(length); + + /* If -1 is passed as length we choose the size for the caller. */ + + if (length == (size_t) -1) + length = pa_mempool_block_size_max(p); + + b = pa_xmalloc(PA_ALIGN(sizeof(pa_memblock)) + length); + PA_REFCNT_INIT(b); + b->pool = p; + pa_mempool_ref(b->pool); + b->type = PA_MEMBLOCK_APPENDED; + b->read_only = b->is_silence = false; + pa_atomic_ptr_store(&b->data, (uint8_t*) b + PA_ALIGN(sizeof(pa_memblock))); + b->length = length; + pa_atomic_store(&b->n_acquired, 0); + pa_atomic_store(&b->please_signal, 0); + + stat_add(b); + return b; +} + +/* No lock necessary */ +static struct mempool_slot* mempool_allocate_slot(pa_mempool *p) { + struct mempool_slot *slot; + pa_assert(p); + + if (!(slot = pa_flist_pop(p->free_slots))) { + int idx; + + /* The free list was empty, we have to allocate a new entry */ + + if ((unsigned) (idx = pa_atomic_inc(&p->n_init)) >= p->n_blocks) + pa_atomic_dec(&p->n_init); + else + slot = (struct mempool_slot*) ((uint8_t*) p->memory.ptr + (p->block_size * (size_t) idx)); + + if (!slot) { + if (pa_log_ratelimit(PA_LOG_DEBUG)) + pa_log_debug("Pool full"); + pa_atomic_inc(&p->stat.n_pool_full); + return NULL; + } + } + +/* #ifdef HAVE_VALGRIND_MEMCHECK_H */ +/* if (PA_UNLIKELY(pa_in_valgrind())) { */ +/* VALGRIND_MALLOCLIKE_BLOCK(slot, p->block_size, 0, 0); */ +/* } */ +/* #endif */ + + return slot; +} + +/* No lock necessary, totally redundant anyway */ +static inline void* mempool_slot_data(struct mempool_slot *slot) { + return slot; +} + +/* No lock necessary */ +static unsigned mempool_slot_idx(pa_mempool *p, void *ptr) { + pa_assert(p); + + pa_assert((uint8_t*) ptr >= (uint8_t*) p->memory.ptr); + pa_assert((uint8_t*) ptr < (uint8_t*) p->memory.ptr + p->memory.size); + + return (unsigned) ((size_t) ((uint8_t*) ptr - (uint8_t*) p->memory.ptr) / p->block_size); +} + +/* No lock necessary */ +static struct mempool_slot* mempool_slot_by_ptr(pa_mempool *p, void *ptr) { + unsigned idx; + + if ((idx = mempool_slot_idx(p, ptr)) == (unsigned) -1) + return NULL; + + return (struct mempool_slot*) ((uint8_t*) p->memory.ptr + (idx * p->block_size)); +} + +/* No lock necessary */ +bool pa_mempool_is_remote_writable(pa_mempool *p) { + pa_assert(p); + return p->is_remote_writable; +} + +/* No lock necessary */ +void pa_mempool_set_is_remote_writable(pa_mempool *p, bool writable) { + pa_assert(p); + pa_assert(!writable || pa_mempool_is_shared(p)); + p->is_remote_writable = writable; +} + +/* No lock necessary */ +pa_memblock *pa_memblock_new_pool(pa_mempool *p, size_t length) { + pa_memblock *b = NULL; + struct mempool_slot *slot; + static int mempool_disable = 0; + + pa_assert(p); + pa_assert(length); + + if (mempool_disable == 0) + mempool_disable = getenv("PULSE_MEMPOOL_DISABLE") ? 1 : -1; + + if (mempool_disable > 0) + return NULL; + + /* If -1 is passed as length we choose the size for the caller: we + * take the largest size that fits in one of our slots. */ + + if (length == (size_t) -1) + length = pa_mempool_block_size_max(p); + + if (p->block_size >= PA_ALIGN(sizeof(pa_memblock)) + length) { + + if (!(slot = mempool_allocate_slot(p))) + return NULL; + + b = mempool_slot_data(slot); + b->type = PA_MEMBLOCK_POOL; + pa_atomic_ptr_store(&b->data, (uint8_t*) b + PA_ALIGN(sizeof(pa_memblock))); + + } else if (p->block_size >= length) { + + if (!(slot = mempool_allocate_slot(p))) + return NULL; + + if (!(b = pa_flist_pop(PA_STATIC_FLIST_GET(unused_memblocks)))) + b = pa_xnew(pa_memblock, 1); + + b->type = PA_MEMBLOCK_POOL_EXTERNAL; + pa_atomic_ptr_store(&b->data, mempool_slot_data(slot)); + + } else { + pa_log_debug("Memory block too large for pool: %lu > %lu", (unsigned long) length, (unsigned long) p->block_size); + pa_atomic_inc(&p->stat.n_too_large_for_pool); + return NULL; + } + + PA_REFCNT_INIT(b); + b->pool = p; + pa_mempool_ref(b->pool); + b->read_only = b->is_silence = false; + b->length = length; + pa_atomic_store(&b->n_acquired, 0); + pa_atomic_store(&b->please_signal, 0); + + stat_add(b); + return b; +} + +/* No lock necessary */ +pa_memblock *pa_memblock_new_fixed(pa_mempool *p, void *d, size_t length, bool read_only) { + pa_memblock *b; + + pa_assert(p); + pa_assert(d); + pa_assert(length != (size_t) -1); + pa_assert(length); + + if (!(b = pa_flist_pop(PA_STATIC_FLIST_GET(unused_memblocks)))) + b = pa_xnew(pa_memblock, 1); + + PA_REFCNT_INIT(b); + b->pool = p; + pa_mempool_ref(b->pool); + b->type = PA_MEMBLOCK_FIXED; + b->read_only = read_only; + b->is_silence = false; + pa_atomic_ptr_store(&b->data, d); + b->length = length; + pa_atomic_store(&b->n_acquired, 0); + pa_atomic_store(&b->please_signal, 0); + + stat_add(b); + return b; +} + +/* No lock necessary */ +pa_memblock *pa_memblock_new_user( + pa_mempool *p, + void *d, + size_t length, + pa_free_cb_t free_cb, + void *free_cb_data, + bool read_only) { + pa_memblock *b; + + pa_assert(p); + pa_assert(d); + pa_assert(length); + pa_assert(length != (size_t) -1); + pa_assert(free_cb); + + if (!(b = pa_flist_pop(PA_STATIC_FLIST_GET(unused_memblocks)))) + b = pa_xnew(pa_memblock, 1); + + PA_REFCNT_INIT(b); + b->pool = p; + pa_mempool_ref(b->pool); + b->type = PA_MEMBLOCK_USER; + b->read_only = read_only; + b->is_silence = false; + pa_atomic_ptr_store(&b->data, d); + b->length = length; + pa_atomic_store(&b->n_acquired, 0); + pa_atomic_store(&b->please_signal, 0); + + b->per_type.user.free_cb = free_cb; + b->per_type.user.free_cb_data = free_cb_data; + + stat_add(b); + return b; +} + +/* No lock necessary */ +bool pa_memblock_is_ours(pa_memblock *b) { + pa_assert(b); + pa_assert(PA_REFCNT_VALUE(b) > 0); + + return b->type != PA_MEMBLOCK_IMPORTED; +} + +/* No lock necessary */ +bool pa_memblock_is_read_only(pa_memblock *b) { + pa_assert(b); + pa_assert(PA_REFCNT_VALUE(b) > 0); + + return b->read_only || PA_REFCNT_VALUE(b) > 1; +} + +/* No lock necessary */ +bool pa_memblock_is_silence(pa_memblock *b) { + pa_assert(b); + pa_assert(PA_REFCNT_VALUE(b) > 0); + + return b->is_silence; +} + +/* No lock necessary */ +void pa_memblock_set_is_silence(pa_memblock *b, bool v) { + pa_assert(b); + pa_assert(PA_REFCNT_VALUE(b) > 0); + + b->is_silence = v; +} + +/* No lock necessary */ +bool pa_memblock_ref_is_one(pa_memblock *b) { + int r; + pa_assert(b); + + pa_assert_se((r = PA_REFCNT_VALUE(b)) > 0); + + return r == 1; +} + +/* No lock necessary */ +void* pa_memblock_acquire(pa_memblock *b) { + pa_assert(b); + pa_assert(PA_REFCNT_VALUE(b) > 0); + + pa_atomic_inc(&b->n_acquired); + + return pa_atomic_ptr_load(&b->data); +} + +/* No lock necessary */ +void *pa_memblock_acquire_chunk(const pa_memchunk *c) { + pa_assert(c); + + return (uint8_t *) pa_memblock_acquire(c->memblock) + c->index; +} + +/* No lock necessary, in corner cases locks by its own */ +void pa_memblock_release(pa_memblock *b) { + int r; + pa_assert(b); + pa_assert(PA_REFCNT_VALUE(b) > 0); + + r = pa_atomic_dec(&b->n_acquired); + pa_assert(r >= 1); + + /* Signal a waiting thread that this memblock is no longer used */ + if (r == 1 && pa_atomic_load(&b->please_signal)) + pa_semaphore_post(b->pool->semaphore); +} + +size_t pa_memblock_get_length(pa_memblock *b) { + pa_assert(b); + pa_assert(PA_REFCNT_VALUE(b) > 0); + + return b->length; +} + +/* Note! Always unref the returned pool after use */ +pa_mempool* pa_memblock_get_pool(pa_memblock *b) { + pa_assert(b); + pa_assert(PA_REFCNT_VALUE(b) > 0); + pa_assert(b->pool); + + pa_mempool_ref(b->pool); + return b->pool; +} + +/* No lock necessary */ +pa_memblock* pa_memblock_ref(pa_memblock*b) { + pa_assert(b); + pa_assert(PA_REFCNT_VALUE(b) > 0); + + PA_REFCNT_INC(b); + return b; +} + +static void memblock_free(pa_memblock *b) { + pa_mempool *pool; + + pa_assert(b); + pa_assert(b->pool); + pa_assert(pa_atomic_load(&b->n_acquired) == 0); + + pool = b->pool; + stat_remove(b); + + switch (b->type) { + case PA_MEMBLOCK_USER : + pa_assert(b->per_type.user.free_cb); + b->per_type.user.free_cb(b->per_type.user.free_cb_data); + + /* Fall through */ + + case PA_MEMBLOCK_FIXED: + if (pa_flist_push(PA_STATIC_FLIST_GET(unused_memblocks), b) < 0) + pa_xfree(b); + + break; + + case PA_MEMBLOCK_APPENDED: + + /* We could attach it to unused_memblocks, but that would + * probably waste some considerable amount of memory */ + pa_xfree(b); + break; + + case PA_MEMBLOCK_IMPORTED: { + pa_memimport_segment *segment; + pa_memimport *import; + + /* FIXME! This should be implemented lock-free */ + + pa_assert_se(segment = b->per_type.imported.segment); + pa_assert_se(import = segment->import); + + pa_mutex_lock(import->mutex); + + pa_assert_se(pa_hashmap_remove(import->blocks, PA_UINT32_TO_PTR(b->per_type.imported.id))); + + pa_assert(segment->n_blocks >= 1); + if (-- segment->n_blocks <= 0) + segment_detach(segment); + + pa_mutex_unlock(import->mutex); + + import->release_cb(import, b->per_type.imported.id, import->userdata); + + if (pa_flist_push(PA_STATIC_FLIST_GET(unused_memblocks), b) < 0) + pa_xfree(b); + + break; + } + + case PA_MEMBLOCK_POOL_EXTERNAL: + case PA_MEMBLOCK_POOL: { + struct mempool_slot *slot; + bool call_free; + + pa_assert_se(slot = mempool_slot_by_ptr(b->pool, pa_atomic_ptr_load(&b->data))); + + call_free = b->type == PA_MEMBLOCK_POOL_EXTERNAL; + +/* #ifdef HAVE_VALGRIND_MEMCHECK_H */ +/* if (PA_UNLIKELY(pa_in_valgrind())) { */ +/* VALGRIND_FREELIKE_BLOCK(slot, b->pool->block_size); */ +/* } */ +/* #endif */ + + /* The free list dimensions should easily allow all slots + * to fit in, hence try harder if pushing this slot into + * the free list fails */ + while (pa_flist_push(b->pool->free_slots, slot) < 0) + ; + + if (call_free) + if (pa_flist_push(PA_STATIC_FLIST_GET(unused_memblocks), b) < 0) + pa_xfree(b); + + break; + } + + case PA_MEMBLOCK_TYPE_MAX: + default: + pa_assert_not_reached(); + } + + pa_mempool_unref(pool); +} + +/* No lock necessary */ +void pa_memblock_unref(pa_memblock*b) { + pa_assert(b); + pa_assert(PA_REFCNT_VALUE(b) > 0); + + if (PA_REFCNT_DEC(b) > 0) + return; + + memblock_free(b); +} + +/* Self locked */ +static void memblock_wait(pa_memblock *b) { + pa_assert(b); + + if (pa_atomic_load(&b->n_acquired) > 0) { + /* We need to wait until all threads gave up access to the + * memory block before we can go on. Unfortunately this means + * that we have to lock and wait here. Sniff! */ + + pa_atomic_inc(&b->please_signal); + + while (pa_atomic_load(&b->n_acquired) > 0) + pa_semaphore_wait(b->pool->semaphore); + + pa_atomic_dec(&b->please_signal); + } +} + +/* No lock necessary. This function is not multiple caller safe! */ +static void memblock_make_local(pa_memblock *b) { + pa_assert(b); + + pa_atomic_dec(&b->pool->stat.n_allocated_by_type[b->type]); + + if (b->length <= b->pool->block_size) { + struct mempool_slot *slot; + + if ((slot = mempool_allocate_slot(b->pool))) { + void *new_data; + /* We can move it into a local pool, perfect! */ + + new_data = mempool_slot_data(slot); + memcpy(new_data, pa_atomic_ptr_load(&b->data), b->length); + pa_atomic_ptr_store(&b->data, new_data); + + b->type = PA_MEMBLOCK_POOL_EXTERNAL; + b->read_only = false; + + goto finish; + } + } + + /* Humm, not enough space in the pool, so lets allocate the memory with malloc() */ + b->per_type.user.free_cb = pa_xfree; + pa_atomic_ptr_store(&b->data, pa_xmemdup(pa_atomic_ptr_load(&b->data), b->length)); + b->per_type.user.free_cb_data = pa_atomic_ptr_load(&b->data); + + b->type = PA_MEMBLOCK_USER; + b->read_only = false; + +finish: + pa_atomic_inc(&b->pool->stat.n_allocated_by_type[b->type]); + pa_atomic_inc(&b->pool->stat.n_accumulated_by_type[b->type]); + memblock_wait(b); +} + +/* No lock necessary. This function is not multiple caller safe */ +void pa_memblock_unref_fixed(pa_memblock *b) { + pa_assert(b); + pa_assert(PA_REFCNT_VALUE(b) > 0); + pa_assert(b->type == PA_MEMBLOCK_FIXED); + + if (PA_REFCNT_VALUE(b) > 1) + memblock_make_local(b); + + pa_memblock_unref(b); +} + +/* No lock necessary. */ +pa_memblock *pa_memblock_will_need(pa_memblock *b) { + void *p; + + pa_assert(b); + pa_assert(PA_REFCNT_VALUE(b) > 0); + + p = pa_memblock_acquire(b); + pa_will_need(p, b->length); + pa_memblock_release(b); + + return b; +} + +/* Self-locked. This function is not multiple-caller safe */ +static void memblock_replace_import(pa_memblock *b) { + pa_memimport_segment *segment; + pa_memimport *import; + + pa_assert(b); + pa_assert(b->type == PA_MEMBLOCK_IMPORTED); + + pa_assert(pa_atomic_load(&b->pool->stat.n_imported) > 0); + pa_assert(pa_atomic_load(&b->pool->stat.imported_size) >= (int) b->length); + pa_atomic_dec(&b->pool->stat.n_imported); + pa_atomic_sub(&b->pool->stat.imported_size, (int) b->length); + + pa_assert_se(segment = b->per_type.imported.segment); + pa_assert_se(import = segment->import); + + pa_mutex_lock(import->mutex); + + pa_assert_se(pa_hashmap_remove(import->blocks, PA_UINT32_TO_PTR(b->per_type.imported.id))); + + memblock_make_local(b); + + pa_assert(segment->n_blocks >= 1); + if (-- segment->n_blocks <= 0) + segment_detach(segment); + + pa_mutex_unlock(import->mutex); +} + +/*@per_client: This is a security measure. By default this should + * be set to true where the created mempool is never shared with more + * than one client in the system. Set this to false if a global + * mempool, shared with all existing and future clients, is required. + * + * NOTE-1: Do not create any further global mempools! They allow data + * leaks between clients and thus conflict with the xdg-app containers + * model. They also complicate the handling of memfd-based pools. + * + * NOTE-2: Almost all mempools are now created on a per client basis. + * The only exception is the pa_core's mempool which is still shared + * between all clients of the system. + * + * Beside security issues, special marking for global mempools is + * required for memfd communication. To avoid fd leaks, memfd pools + * are registered with the connection pstream to create an ID<->memfd + * mapping on both PA endpoints. Such memory regions are then always + * referenced by their IDs and never by their fds and thus their fds + * can be quickly closed later. + * + * Unfortunately this scheme cannot work with global pools since the + * ID registration mechanism needs to happen for each newly connected + * client, and thus the need for a more special handling. That is, + * for the pool's fd to be always open :-( + * + * TODO-1: Transform the global core mempool to a per-client one + * TODO-2: Remove global mempools support */ +pa_mempool *pa_mempool_new(pa_mem_type_t type, size_t size, bool per_client) { + pa_mempool *p; + char t1[PA_BYTES_SNPRINT_MAX], t2[PA_BYTES_SNPRINT_MAX]; + const size_t page_size = pa_page_size(); + + p = pa_xnew0(pa_mempool, 1); + PA_REFCNT_INIT(p); + + p->block_size = PA_PAGE_ALIGN(PA_MEMPOOL_SLOT_SIZE); + if (p->block_size < page_size) + p->block_size = page_size; + + if (size <= 0) + p->n_blocks = PA_MEMPOOL_SLOTS_MAX; + else { + p->n_blocks = (unsigned) (size / p->block_size); + + if (p->n_blocks < 2) + p->n_blocks = 2; + } + + if (pa_shm_create_rw(&p->memory, type, p->n_blocks * p->block_size, 0700) < 0) { + pa_xfree(p); + return NULL; + } + + pa_log_debug("Using %s memory pool with %u slots of size %s each, total size is %s, maximum usable slot size is %lu", + pa_mem_type_to_string(type), + p->n_blocks, + pa_bytes_snprint(t1, sizeof(t1), (unsigned) p->block_size), + pa_bytes_snprint(t2, sizeof(t2), (unsigned) (p->n_blocks * p->block_size)), + (unsigned long) pa_mempool_block_size_max(p)); + + p->global = !per_client; + + pa_atomic_store(&p->n_init, 0); + + PA_LLIST_HEAD_INIT(pa_memimport, p->imports); + PA_LLIST_HEAD_INIT(pa_memexport, p->exports); + + p->mutex = pa_mutex_new(true, true); + p->semaphore = pa_semaphore_new(0); + + p->free_slots = pa_flist_new(p->n_blocks); + + return p; +} + +static void mempool_free(pa_mempool *p) { + pa_assert(p); + + pa_mutex_lock(p->mutex); + + while (p->imports) + pa_memimport_free(p->imports); + + while (p->exports) + pa_memexport_free(p->exports); + + pa_mutex_unlock(p->mutex); + + pa_flist_free(p->free_slots, NULL); + + if (pa_atomic_load(&p->stat.n_allocated) > 0) { + + /* Ouch, somebody is retaining a memory block reference! */ + +#ifdef DEBUG_REF + unsigned i; + pa_flist *list; + + /* Let's try to find at least one of those leaked memory blocks */ + + list = pa_flist_new(p->n_blocks); + + for (i = 0; i < (unsigned) pa_atomic_load(&p->n_init); i++) { + struct mempool_slot *slot; + pa_memblock *b, *k; + + slot = (struct mempool_slot*) ((uint8_t*) p->memory.ptr + (p->block_size * (size_t) i)); + b = mempool_slot_data(slot); + + while ((k = pa_flist_pop(p->free_slots))) { + while (pa_flist_push(list, k) < 0) + ; + + if (b == k) + break; + } + + if (!k) + pa_log("REF: Leaked memory block %p", b); + + while ((k = pa_flist_pop(list))) + while (pa_flist_push(p->free_slots, k) < 0) + ; + } + + pa_flist_free(list, NULL); + +#endif + + pa_log_error("Memory pool destroyed but not all memory blocks freed! %u remain.", pa_atomic_load(&p->stat.n_allocated)); + +/* PA_DEBUG_TRAP; */ + } + + pa_shm_free(&p->memory); + + pa_mutex_free(p->mutex); + pa_semaphore_free(p->semaphore); + + pa_xfree(p); +} + +/* No lock necessary */ +const pa_mempool_stat* pa_mempool_get_stat(pa_mempool *p) { + pa_assert(p); + + return &p->stat; +} + +/* No lock necessary */ +size_t pa_mempool_block_size_max(pa_mempool *p) { + pa_assert(p); + + return p->block_size - PA_ALIGN(sizeof(pa_memblock)); +} + +/* No lock necessary */ +void pa_mempool_vacuum(pa_mempool *p) { + struct mempool_slot *slot; + pa_flist *list; + + pa_assert(p); + + list = pa_flist_new(p->n_blocks); + + while ((slot = pa_flist_pop(p->free_slots))) + while (pa_flist_push(list, slot) < 0) + ; + + while ((slot = pa_flist_pop(list))) { + pa_shm_punch(&p->memory, (size_t) ((uint8_t*) slot - (uint8_t*) p->memory.ptr), p->block_size); + + while (pa_flist_push(p->free_slots, slot)) + ; + } + + pa_flist_free(list, NULL); +} + +/* No lock necessary */ +bool pa_mempool_is_shared(pa_mempool *p) { + pa_assert(p); + + return pa_mem_type_is_shared(p->memory.type); +} + +/* No lock necessary */ +bool pa_mempool_is_memfd_backed(const pa_mempool *p) { + pa_assert(p); + + return (p->memory.type == PA_MEM_TYPE_SHARED_MEMFD); +} + +/* No lock necessary */ +int pa_mempool_get_shm_id(pa_mempool *p, uint32_t *id) { + pa_assert(p); + + if (!pa_mempool_is_shared(p)) + return -1; + + *id = p->memory.id; + + return 0; +} + +pa_mempool* pa_mempool_ref(pa_mempool *p) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + + PA_REFCNT_INC(p); + return p; +} + +void pa_mempool_unref(pa_mempool *p) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + + if (PA_REFCNT_DEC(p) <= 0) + mempool_free(p); +} + +/* No lock necessary + * Check pa_mempool_new() for per-client vs. global mempools */ +bool pa_mempool_is_global(pa_mempool *p) { + pa_assert(p); + + return p->global; +} + +/* No lock necessary + * Check pa_mempool_new() for per-client vs. global mempools */ +bool pa_mempool_is_per_client(pa_mempool *p) { + return !pa_mempool_is_global(p); +} + +/* Self-locked + * + * This is only for per-client mempools! + * + * After this method's return, the caller owns the file descriptor + * and is responsible for closing it in the appropriate time. This + * should only be called once during during a mempool's lifetime. + * + * Check pa_shm->fd and pa_mempool_new() for further context. */ +int pa_mempool_take_memfd_fd(pa_mempool *p) { + int memfd_fd; + + pa_assert(p); + pa_assert(pa_mempool_is_shared(p)); + pa_assert(pa_mempool_is_memfd_backed(p)); + pa_assert(pa_mempool_is_per_client(p)); + + pa_mutex_lock(p->mutex); + + memfd_fd = p->memory.fd; + p->memory.fd = -1; + + pa_mutex_unlock(p->mutex); + + pa_assert(memfd_fd != -1); + return memfd_fd; +} + +/* No lock necessary + * + * This is only for global mempools! + * + * Global mempools have their memfd descriptor always open. DO NOT + * close the returned descriptor by your own. + * + * Check pa_mempool_new() for further context. */ +int pa_mempool_get_memfd_fd(pa_mempool *p) { + int memfd_fd; + + pa_assert(p); + pa_assert(pa_mempool_is_shared(p)); + pa_assert(pa_mempool_is_memfd_backed(p)); + pa_assert(pa_mempool_is_global(p)); + + memfd_fd = p->memory.fd; + pa_assert(memfd_fd != -1); + + return memfd_fd; +} + +/* For receiving blocks from other nodes */ +pa_memimport* pa_memimport_new(pa_mempool *p, pa_memimport_release_cb_t cb, void *userdata) { + pa_memimport *i; + + pa_assert(p); + pa_assert(cb); + + i = pa_xnew(pa_memimport, 1); + i->mutex = pa_mutex_new(true, true); + i->pool = p; + pa_mempool_ref(i->pool); + i->segments = pa_hashmap_new(NULL, NULL); + i->blocks = pa_hashmap_new(NULL, NULL); + i->release_cb = cb; + i->userdata = userdata; + + pa_mutex_lock(p->mutex); + PA_LLIST_PREPEND(pa_memimport, p->imports, i); + pa_mutex_unlock(p->mutex); + + return i; +} + +static void memexport_revoke_blocks(pa_memexport *e, pa_memimport *i); + +/* Should be called locked + * Caller owns passed @memfd_fd and must close it down when appropriate. */ +static pa_memimport_segment* segment_attach(pa_memimport *i, pa_mem_type_t type, uint32_t shm_id, + int memfd_fd, bool writable) { + pa_memimport_segment* seg; + pa_assert(pa_mem_type_is_shared(type)); + + if (pa_hashmap_size(i->segments) >= PA_MEMIMPORT_SEGMENTS_MAX) + return NULL; + + seg = pa_xnew0(pa_memimport_segment, 1); + + if (pa_shm_attach(&seg->memory, type, shm_id, memfd_fd, writable) < 0) { + pa_xfree(seg); + return NULL; + } + + seg->writable = writable; + seg->import = i; + seg->trap = pa_memtrap_add(seg->memory.ptr, seg->memory.size); + + pa_hashmap_put(i->segments, PA_UINT32_TO_PTR(seg->memory.id), seg); + return seg; +} + +/* Should be called locked */ +static void segment_detach(pa_memimport_segment *seg) { + pa_assert(seg); + pa_assert(seg->n_blocks == (segment_is_permanent(seg) ? 1u : 0u)); + + pa_hashmap_remove(seg->import->segments, PA_UINT32_TO_PTR(seg->memory.id)); + pa_shm_free(&seg->memory); + + if (seg->trap) + pa_memtrap_remove(seg->trap); + + pa_xfree(seg); +} + +/* Self-locked. Not multiple-caller safe */ +void pa_memimport_free(pa_memimport *i) { + pa_memexport *e; + pa_memblock *b; + pa_memimport_segment *seg; + void *state = NULL; + + pa_assert(i); + + pa_mutex_lock(i->mutex); + + while ((b = pa_hashmap_first(i->blocks))) + memblock_replace_import(b); + + /* Permanent segments exist for the lifetime of the memimport. Now + * that we're freeing the memimport itself, clear them all up. + * + * Careful! segment_detach() internally removes itself from the + * memimport's hash; the same hash we're now using for iteration. */ + PA_HASHMAP_FOREACH(seg, i->segments, state) { + if (segment_is_permanent(seg)) + segment_detach(seg); + } + pa_assert(pa_hashmap_size(i->segments) == 0); + + pa_mutex_unlock(i->mutex); + + pa_mutex_lock(i->pool->mutex); + + /* If we've exported this block further we need to revoke that export */ + for (e = i->pool->exports; e; e = e->next) + memexport_revoke_blocks(e, i); + + PA_LLIST_REMOVE(pa_memimport, i->pool->imports, i); + + pa_mutex_unlock(i->pool->mutex); + + pa_mempool_unref(i->pool); + pa_hashmap_free(i->blocks); + pa_hashmap_free(i->segments); + + pa_mutex_free(i->mutex); + + pa_xfree(i); +} + +/* Create a new memimport's memfd segment entry, with passed SHM ID + * as key and the newly-created segment (with its mmap()-ed memfd + * memory region) as its value. + * + * Note! check comments at 'pa_shm->fd', 'segment_is_permanent()', + * and 'pa_pstream_register_memfd_mempool()' for further details. + * + * Caller owns passed @memfd_fd and must close it down when appropriate. */ +int pa_memimport_attach_memfd(pa_memimport *i, uint32_t shm_id, int memfd_fd, bool writable) { + pa_memimport_segment *seg; + int ret = -1; + + pa_assert(i); + pa_assert(memfd_fd != -1); + + pa_mutex_lock(i->mutex); + + if (!(seg = segment_attach(i, PA_MEM_TYPE_SHARED_MEMFD, shm_id, memfd_fd, writable))) + goto finish; + + /* n_blocks acts as a segment reference count. To avoid the segment + * being deleted when receiving silent memchunks, etc., mark our + * permanent presence by incrementing that refcount. */ + seg->n_blocks++; + + pa_assert(segment_is_permanent(seg)); + ret = 0; + +finish: + pa_mutex_unlock(i->mutex); + return ret; +} + +/* Self-locked */ +pa_memblock* pa_memimport_get(pa_memimport *i, pa_mem_type_t type, uint32_t block_id, uint32_t shm_id, + size_t offset, size_t size, bool writable) { + pa_memblock *b = NULL; + pa_memimport_segment *seg; + + pa_assert(i); + pa_assert(pa_mem_type_is_shared(type)); + + pa_mutex_lock(i->mutex); + + if ((b = pa_hashmap_get(i->blocks, PA_UINT32_TO_PTR(block_id)))) { + pa_memblock_ref(b); + goto finish; + } + + if (pa_hashmap_size(i->blocks) >= PA_MEMIMPORT_SLOTS_MAX) + goto finish; + + if (!(seg = pa_hashmap_get(i->segments, PA_UINT32_TO_PTR(shm_id)))) { + if (type == PA_MEM_TYPE_SHARED_MEMFD) { + pa_log("Bailing out! No cached memimport segment for memfd ID %u", shm_id); + pa_log("Did the other PA endpoint forget registering its memfd pool?"); + goto finish; + } + + pa_assert(type == PA_MEM_TYPE_SHARED_POSIX); + if (!(seg = segment_attach(i, type, shm_id, -1, writable))) + goto finish; + } + + if (writable && !seg->writable) { + pa_log("Cannot import cached segment in write mode - previously mapped as read-only"); + goto finish; + } + + if (offset+size > seg->memory.size) + goto finish; + + if (!(b = pa_flist_pop(PA_STATIC_FLIST_GET(unused_memblocks)))) + b = pa_xnew(pa_memblock, 1); + + PA_REFCNT_INIT(b); + b->pool = i->pool; + pa_mempool_ref(b->pool); + b->type = PA_MEMBLOCK_IMPORTED; + b->read_only = !writable; + b->is_silence = false; + pa_atomic_ptr_store(&b->data, (uint8_t*) seg->memory.ptr + offset); + b->length = size; + pa_atomic_store(&b->n_acquired, 0); + pa_atomic_store(&b->please_signal, 0); + b->per_type.imported.id = block_id; + b->per_type.imported.segment = seg; + + pa_hashmap_put(i->blocks, PA_UINT32_TO_PTR(block_id), b); + + seg->n_blocks++; + + stat_add(b); + +finish: + pa_mutex_unlock(i->mutex); + + return b; +} + +int pa_memimport_process_revoke(pa_memimport *i, uint32_t id) { + pa_memblock *b; + int ret = 0; + pa_assert(i); + + pa_mutex_lock(i->mutex); + + if (!(b = pa_hashmap_get(i->blocks, PA_UINT32_TO_PTR(id)))) { + ret = -1; + goto finish; + } + + memblock_replace_import(b); + +finish: + pa_mutex_unlock(i->mutex); + + return ret; +} + +/* For sending blocks to other nodes */ +pa_memexport* pa_memexport_new(pa_mempool *p, pa_memexport_revoke_cb_t cb, void *userdata) { + pa_memexport *e; + + static pa_atomic_t export_baseidx = PA_ATOMIC_INIT(0); + + pa_assert(p); + pa_assert(cb); + + if (!pa_mempool_is_shared(p)) + return NULL; + + e = pa_xnew(pa_memexport, 1); + e->mutex = pa_mutex_new(true, true); + e->pool = p; + pa_mempool_ref(e->pool); + PA_LLIST_HEAD_INIT(struct memexport_slot, e->free_slots); + PA_LLIST_HEAD_INIT(struct memexport_slot, e->used_slots); + e->n_init = 0; + e->revoke_cb = cb; + e->userdata = userdata; + + pa_mutex_lock(p->mutex); + + PA_LLIST_PREPEND(pa_memexport, p->exports, e); + e->baseidx = (uint32_t) pa_atomic_add(&export_baseidx, PA_MEMEXPORT_SLOTS_MAX); + + pa_mutex_unlock(p->mutex); + return e; +} + +void pa_memexport_free(pa_memexport *e) { + pa_assert(e); + + pa_mutex_lock(e->mutex); + while (e->used_slots) + pa_memexport_process_release(e, (uint32_t) (e->used_slots - e->slots + e->baseidx)); + pa_mutex_unlock(e->mutex); + + pa_mutex_lock(e->pool->mutex); + PA_LLIST_REMOVE(pa_memexport, e->pool->exports, e); + pa_mutex_unlock(e->pool->mutex); + + pa_mempool_unref(e->pool); + pa_mutex_free(e->mutex); + pa_xfree(e); +} + +/* Self-locked */ +int pa_memexport_process_release(pa_memexport *e, uint32_t id) { + pa_memblock *b; + + pa_assert(e); + + pa_mutex_lock(e->mutex); + + if (id < e->baseidx) + goto fail; + id -= e->baseidx; + + if (id >= e->n_init) + goto fail; + + if (!e->slots[id].block) + goto fail; + + b = e->slots[id].block; + e->slots[id].block = NULL; + + PA_LLIST_REMOVE(struct memexport_slot, e->used_slots, &e->slots[id]); + PA_LLIST_PREPEND(struct memexport_slot, e->free_slots, &e->slots[id]); + + pa_mutex_unlock(e->mutex); + +/* pa_log("Processing release for %u", id); */ + + pa_assert(pa_atomic_load(&e->pool->stat.n_exported) > 0); + pa_assert(pa_atomic_load(&e->pool->stat.exported_size) >= (int) b->length); + + pa_atomic_dec(&e->pool->stat.n_exported); + pa_atomic_sub(&e->pool->stat.exported_size, (int) b->length); + + pa_memblock_unref(b); + + return 0; + +fail: + pa_mutex_unlock(e->mutex); + + return -1; +} + +/* Self-locked */ +static void memexport_revoke_blocks(pa_memexport *e, pa_memimport *i) { + struct memexport_slot *slot, *next; + pa_assert(e); + pa_assert(i); + + pa_mutex_lock(e->mutex); + + for (slot = e->used_slots; slot; slot = next) { + uint32_t idx; + next = slot->next; + + if (slot->block->type != PA_MEMBLOCK_IMPORTED || + slot->block->per_type.imported.segment->import != i) + continue; + + idx = (uint32_t) (slot - e->slots + e->baseidx); + e->revoke_cb(e, idx, e->userdata); + pa_memexport_process_release(e, idx); + } + + pa_mutex_unlock(e->mutex); +} + +/* No lock necessary */ +static pa_memblock *memblock_shared_copy(pa_mempool *p, pa_memblock *b) { + pa_memblock *n; + + pa_assert(p); + pa_assert(b); + + if (b->type == PA_MEMBLOCK_IMPORTED || + b->type == PA_MEMBLOCK_POOL || + b->type == PA_MEMBLOCK_POOL_EXTERNAL) { + pa_assert(b->pool == p); + return pa_memblock_ref(b); + } + + if (!(n = pa_memblock_new_pool(p, b->length))) + return NULL; + + memcpy(pa_atomic_ptr_load(&n->data), pa_atomic_ptr_load(&b->data), b->length); + return n; +} + +/* Self-locked */ +int pa_memexport_put(pa_memexport *e, pa_memblock *b, pa_mem_type_t *type, uint32_t *block_id, + uint32_t *shm_id, size_t *offset, size_t * size) { + pa_shm *memory; + struct memexport_slot *slot; + void *data; + + pa_assert(e); + pa_assert(b); + pa_assert(type); + pa_assert(block_id); + pa_assert(shm_id); + pa_assert(offset); + pa_assert(size); + pa_assert(b->pool == e->pool); + + if (!(b = memblock_shared_copy(e->pool, b))) + return -1; + + pa_mutex_lock(e->mutex); + + if (e->free_slots) { + slot = e->free_slots; + PA_LLIST_REMOVE(struct memexport_slot, e->free_slots, slot); + } else if (e->n_init < PA_MEMEXPORT_SLOTS_MAX) + slot = &e->slots[e->n_init++]; + else { + pa_mutex_unlock(e->mutex); + pa_memblock_unref(b); + return -1; + } + + PA_LLIST_PREPEND(struct memexport_slot, e->used_slots, slot); + slot->block = b; + *block_id = (uint32_t) (slot - e->slots + e->baseidx); + + pa_mutex_unlock(e->mutex); +/* pa_log("Got block id %u", *block_id); */ + + data = pa_memblock_acquire(b); + + if (b->type == PA_MEMBLOCK_IMPORTED) { + pa_assert(b->per_type.imported.segment); + memory = &b->per_type.imported.segment->memory; + } else { + pa_assert(b->type == PA_MEMBLOCK_POOL || b->type == PA_MEMBLOCK_POOL_EXTERNAL); + pa_assert(b->pool); + pa_assert(pa_mempool_is_shared(b->pool)); + memory = &b->pool->memory; + } + + pa_assert(data >= memory->ptr); + pa_assert((uint8_t*) data + b->length <= (uint8_t*) memory->ptr + memory->size); + + *type = memory->type; + *shm_id = memory->id; + *offset = (size_t) ((uint8_t*) data - (uint8_t*) memory->ptr); + *size = b->length; + + pa_memblock_release(b); + + pa_atomic_inc(&e->pool->stat.n_exported); + pa_atomic_add(&e->pool->stat.exported_size, (int) b->length); + + return 0; +} diff --git a/src/pulsecore/memblock.h b/src/pulsecore/memblock.h new file mode 100644 index 0000000..4273c09 --- /dev/null +++ b/src/pulsecore/memblock.h @@ -0,0 +1,159 @@ +#ifndef foopulsememblockhfoo +#define foopulsememblockhfoo + +/*** + 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +typedef struct pa_memblock pa_memblock; + +#include <sys/types.h> +#include <inttypes.h> + +#include <pulse/def.h> +#include <pulse/xmalloc.h> +#include <pulsecore/atomic.h> +#include <pulsecore/memchunk.h> +#include <pulsecore/mem.h> + +/* A pa_memblock is a reference counted memory block. PulseAudio + * passes references to pa_memblocks around instead of copying + * data. See pa_memchunk for a structure that describes parts of + * memory blocks. */ + +/* The type of memory this block points to */ +typedef enum pa_memblock_type { + PA_MEMBLOCK_POOL, /* Memory is part of the memory pool */ + PA_MEMBLOCK_POOL_EXTERNAL, /* Data memory is part of the memory pool but the pa_memblock structure itself is not */ + PA_MEMBLOCK_APPENDED, /* The data is appended to the memory block */ + PA_MEMBLOCK_USER, /* User supplied memory, to be freed with free_cb */ + PA_MEMBLOCK_FIXED, /* Data is a pointer to fixed memory that needs not to be freed */ + PA_MEMBLOCK_IMPORTED, /* Memory is imported from another process via shm */ + PA_MEMBLOCK_TYPE_MAX +} pa_memblock_type_t; + +typedef struct pa_mempool pa_mempool; +typedef struct pa_mempool_stat pa_mempool_stat; +typedef struct pa_memimport_segment pa_memimport_segment; +typedef struct pa_memimport pa_memimport; +typedef struct pa_memexport pa_memexport; + +typedef void (*pa_memimport_release_cb_t)(pa_memimport *i, uint32_t block_id, void *userdata); +typedef void (*pa_memexport_revoke_cb_t)(pa_memexport *e, uint32_t block_id, void *userdata); + +/* Please note that updates to this structure are not locked, + * i.e. n_allocated might be updated at a point in time where + * n_accumulated is not yet. Take these values with a grain of salt, + * they are here for purely statistical reasons.*/ +struct pa_mempool_stat { + pa_atomic_t n_allocated; + pa_atomic_t n_accumulated; + pa_atomic_t n_imported; + pa_atomic_t n_exported; + pa_atomic_t allocated_size; + pa_atomic_t accumulated_size; + pa_atomic_t imported_size; + pa_atomic_t exported_size; + + pa_atomic_t n_too_large_for_pool; + pa_atomic_t n_pool_full; + + pa_atomic_t n_allocated_by_type[PA_MEMBLOCK_TYPE_MAX]; + pa_atomic_t n_accumulated_by_type[PA_MEMBLOCK_TYPE_MAX]; +}; + +/* Allocate a new memory block of type PA_MEMBLOCK_MEMPOOL or PA_MEMBLOCK_APPENDED, depending on the size */ +pa_memblock *pa_memblock_new(pa_mempool *, size_t length); + +/* Allocate a new memory block of type PA_MEMBLOCK_MEMPOOL. If the requested size is too large, return NULL */ +pa_memblock *pa_memblock_new_pool(pa_mempool *, size_t length); + +/* Allocate a new memory block of type PA_MEMBLOCK_USER */ +pa_memblock *pa_memblock_new_user(pa_mempool *, void *data, size_t length, pa_free_cb_t free_cb, void *free_cb_data, bool read_only); + +/* A special case of pa_memblock_new_user: take a memory buffer previously allocated with pa_xmalloc() */ +static inline pa_memblock *pa_memblock_new_malloced(pa_mempool *p, void *data, size_t length) { + return pa_memblock_new_user(p, data, length, pa_xfree, data, 0); +} + +/* Allocate a new memory block of type PA_MEMBLOCK_FIXED */ +pa_memblock *pa_memblock_new_fixed(pa_mempool *, void *data, size_t length, bool read_only); + +void pa_memblock_unref(pa_memblock*b); +pa_memblock* pa_memblock_ref(pa_memblock*b); + +/* This special unref function has to be called by the owner of the +memory of a static memory block when they want to release all +references to the memory. This causes the memory to be copied and +converted into a pool of malloc'ed memory block. Please note that this +function is not multiple caller safe, i.e. needs to be locked +manually if called from more than one thread at the same time. */ +void pa_memblock_unref_fixed(pa_memblock*b); + +bool pa_memblock_is_ours(pa_memblock *b); +bool pa_memblock_is_read_only(pa_memblock *b); +bool pa_memblock_is_silence(pa_memblock *b); +bool pa_memblock_ref_is_one(pa_memblock *b); +void pa_memblock_set_is_silence(pa_memblock *b, bool v); + +void* pa_memblock_acquire(pa_memblock *b); +void *pa_memblock_acquire_chunk(const pa_memchunk *c); +void pa_memblock_release(pa_memblock *b); + +size_t pa_memblock_get_length(pa_memblock *b); + +/* Note! Always unref the returned pool after use */ +pa_mempool * pa_memblock_get_pool(pa_memblock *b); + +pa_memblock *pa_memblock_will_need(pa_memblock *b); + +/* The memory block manager */ +pa_mempool *pa_mempool_new(pa_mem_type_t type, size_t size, bool per_client); +void pa_mempool_unref(pa_mempool *p); +pa_mempool* pa_mempool_ref(pa_mempool *p); +const pa_mempool_stat* pa_mempool_get_stat(pa_mempool *p); +void pa_mempool_vacuum(pa_mempool *p); +int pa_mempool_get_shm_id(pa_mempool *p, uint32_t *id); +bool pa_mempool_is_shared(pa_mempool *p); +bool pa_mempool_is_memfd_backed(const pa_mempool *p); +bool pa_mempool_is_global(pa_mempool *p); +bool pa_mempool_is_per_client(pa_mempool *p); +bool pa_mempool_is_remote_writable(pa_mempool *p); +void pa_mempool_set_is_remote_writable(pa_mempool *p, bool writable); +size_t pa_mempool_block_size_max(pa_mempool *p); + +int pa_mempool_take_memfd_fd(pa_mempool *p); +int pa_mempool_get_memfd_fd(pa_mempool *p); + +/* For receiving blocks from other nodes */ +pa_memimport* pa_memimport_new(pa_mempool *p, pa_memimport_release_cb_t cb, void *userdata); +void pa_memimport_free(pa_memimport *i); +int pa_memimport_attach_memfd(pa_memimport *i, uint32_t shm_id, int memfd_fd, bool writable); +pa_memblock* pa_memimport_get(pa_memimport *i, pa_mem_type_t type, uint32_t block_id, + uint32_t shm_id, size_t offset, size_t size, bool writable); +int pa_memimport_process_revoke(pa_memimport *i, uint32_t block_id); + +/* For sending blocks to other nodes */ +pa_memexport* pa_memexport_new(pa_mempool *p, pa_memexport_revoke_cb_t cb, void *userdata); +void pa_memexport_free(pa_memexport *e); +int pa_memexport_put(pa_memexport *e, pa_memblock *b, pa_mem_type_t *type, uint32_t *block_id, + uint32_t *shm_id, size_t *offset, size_t * size); +int pa_memexport_process_release(pa_memexport *e, uint32_t id); + +#endif diff --git a/src/pulsecore/memblockq.c b/src/pulsecore/memblockq.c new file mode 100644 index 0000000..b132dd3 --- /dev/null +++ b/src/pulsecore/memblockq.c @@ -0,0 +1,1019 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/log.h> +#include <pulsecore/mcalign.h> +#include <pulsecore/macro.h> +#include <pulsecore/flist.h> + +#include "memblockq.h" + +/* #define MEMBLOCKQ_DEBUG */ + +struct list_item { + struct list_item *next, *prev; + int64_t index; + pa_memchunk chunk; +}; + +PA_STATIC_FLIST_DECLARE(list_items, 0, pa_xfree); + +struct pa_memblockq { + struct list_item *blocks, *blocks_tail; + struct list_item *current_read, *current_write; + unsigned n_blocks; + size_t maxlength, tlength, base, prebuf, minreq, maxrewind; + int64_t read_index, write_index; + bool in_prebuf; + pa_memchunk silence; + pa_mcalign *mcalign; + int64_t missing, requested; + char *name; + pa_sample_spec sample_spec; +}; + +pa_memblockq* pa_memblockq_new( + const char *name, + int64_t idx, + size_t maxlength, + size_t tlength, + const pa_sample_spec *sample_spec, + size_t prebuf, + size_t minreq, + size_t maxrewind, + pa_memchunk *silence) { + + pa_memblockq* bq; + + pa_assert(sample_spec); + pa_assert(name); + + bq = pa_xnew0(pa_memblockq, 1); + bq->name = pa_xstrdup(name); + + bq->sample_spec = *sample_spec; + bq->base = pa_frame_size(sample_spec); + bq->read_index = bq->write_index = idx; + + pa_log_debug("memblockq requested: maxlength=%lu, tlength=%lu, base=%lu, prebuf=%lu, minreq=%lu maxrewind=%lu", + (unsigned long) maxlength, (unsigned long) tlength, (unsigned long) bq->base, (unsigned long) prebuf, (unsigned long) minreq, (unsigned long) maxrewind); + + bq->in_prebuf = true; + + pa_memblockq_set_maxlength(bq, maxlength); + pa_memblockq_set_tlength(bq, tlength); + pa_memblockq_set_minreq(bq, minreq); + pa_memblockq_set_prebuf(bq, prebuf); + pa_memblockq_set_maxrewind(bq, maxrewind); + + pa_log_debug("memblockq sanitized: maxlength=%lu, tlength=%lu, base=%lu, prebuf=%lu, minreq=%lu maxrewind=%lu", + (unsigned long) bq->maxlength, (unsigned long) bq->tlength, (unsigned long) bq->base, (unsigned long) bq->prebuf, (unsigned long) bq->minreq, (unsigned long) bq->maxrewind); + + if (silence) { + bq->silence = *silence; + pa_memblock_ref(bq->silence.memblock); + } + + bq->mcalign = pa_mcalign_new(bq->base); + + return bq; +} + +void pa_memblockq_free(pa_memblockq* bq) { + pa_assert(bq); + + pa_memblockq_silence(bq); + + if (bq->silence.memblock) + pa_memblock_unref(bq->silence.memblock); + + if (bq->mcalign) + pa_mcalign_free(bq->mcalign); + + pa_xfree(bq->name); + pa_xfree(bq); +} + +static void fix_current_read(pa_memblockq *bq) { + pa_assert(bq); + + if (PA_UNLIKELY(!bq->blocks)) { + bq->current_read = NULL; + return; + } + + if (PA_UNLIKELY(!bq->current_read)) + bq->current_read = bq->blocks; + + /* Scan left */ + while (PA_UNLIKELY(bq->current_read->index > bq->read_index)) + + if (bq->current_read->prev) + bq->current_read = bq->current_read->prev; + else + break; + + /* Scan right */ + while (PA_LIKELY(bq->current_read != NULL) && PA_UNLIKELY(bq->current_read->index + (int64_t) bq->current_read->chunk.length <= bq->read_index)) + bq->current_read = bq->current_read->next; + + /* At this point current_read will either point at or left of the + next block to play. It may be NULL in case everything in + the queue was already played */ +} + +static void fix_current_write(pa_memblockq *bq) { + pa_assert(bq); + + if (PA_UNLIKELY(!bq->blocks)) { + bq->current_write = NULL; + return; + } + + if (PA_UNLIKELY(!bq->current_write)) + bq->current_write = bq->blocks_tail; + + /* Scan right */ + while (PA_UNLIKELY(bq->current_write->index + (int64_t) bq->current_write->chunk.length <= bq->write_index)) + + if (bq->current_write->next) + bq->current_write = bq->current_write->next; + else + break; + + /* Scan left */ + while (PA_LIKELY(bq->current_write != NULL) && PA_UNLIKELY(bq->current_write->index > bq->write_index)) + bq->current_write = bq->current_write->prev; + + /* At this point current_write will either point at or right of + the next block to write data to. It may be NULL in case + everything in the queue is still to be played */ +} + +static void drop_block(pa_memblockq *bq, struct list_item *q) { + pa_assert(bq); + pa_assert(q); + + pa_assert(bq->n_blocks >= 1); + + if (q->prev) + q->prev->next = q->next; + else { + pa_assert(bq->blocks == q); + bq->blocks = q->next; + } + + if (q->next) + q->next->prev = q->prev; + else { + pa_assert(bq->blocks_tail == q); + bq->blocks_tail = q->prev; + } + + if (bq->current_write == q) + bq->current_write = q->prev; + + if (bq->current_read == q) + bq->current_read = q->next; + + pa_memblock_unref(q->chunk.memblock); + + if (pa_flist_push(PA_STATIC_FLIST_GET(list_items), q) < 0) + pa_xfree(q); + + bq->n_blocks--; +} + +static void drop_backlog(pa_memblockq *bq) { + int64_t boundary; + pa_assert(bq); + + boundary = bq->read_index - (int64_t) bq->maxrewind; + + while (bq->blocks && (bq->blocks->index + (int64_t) bq->blocks->chunk.length <= boundary)) + drop_block(bq, bq->blocks); +} + +static bool can_push(pa_memblockq *bq, size_t l) { + int64_t end; + + pa_assert(bq); + + if (bq->read_index > bq->write_index) { + int64_t d = bq->read_index - bq->write_index; + + if ((int64_t) l > d) + l -= (size_t) d; + else + return true; + } + + end = bq->blocks_tail ? bq->blocks_tail->index + (int64_t) bq->blocks_tail->chunk.length : bq->write_index; + + /* Make sure that the list doesn't get too long */ + if (bq->write_index + (int64_t) l > end) + if (bq->write_index + (int64_t) l - bq->read_index > (int64_t) bq->maxlength) + return false; + + return true; +} + +static void write_index_changed(pa_memblockq *bq, int64_t old_write_index, bool account) { + int64_t delta; + + pa_assert(bq); + + delta = bq->write_index - old_write_index; + + if (account) + bq->requested -= delta; + else + bq->missing -= delta; + +#ifdef MEMBLOCKQ_DEBUG + pa_log_debug("[%s] pushed/seeked %lli: requested counter at %lli, account=%i", bq->name, (long long) delta, (long long) bq->requested, account); +#endif +} + +static void read_index_changed(pa_memblockq *bq, int64_t old_read_index) { + int64_t delta; + + pa_assert(bq); + + delta = bq->read_index - old_read_index; + bq->missing += delta; + +#ifdef MEMBLOCKQ_DEBUG + pa_log_debug("[%s] popped %lli: missing counter at %lli", bq->name, (long long) delta, (long long) bq->missing); +#endif +} + +int pa_memblockq_push(pa_memblockq* bq, const pa_memchunk *uchunk) { + struct list_item *q, *n; + pa_memchunk chunk; + int64_t old; + + pa_assert(bq); + pa_assert(uchunk); + pa_assert(uchunk->memblock); + pa_assert(uchunk->length > 0); + pa_assert(uchunk->index + uchunk->length <= pa_memblock_get_length(uchunk->memblock)); + + pa_assert(uchunk->length % bq->base == 0); + pa_assert(uchunk->index % bq->base == 0); + + if (!can_push(bq, uchunk->length)) + return -1; + + old = bq->write_index; + chunk = *uchunk; + + fix_current_write(bq); + q = bq->current_write; + + /* First we advance the q pointer right of where we want to + * write to */ + + if (q) { + while (bq->write_index + (int64_t) chunk.length > q->index) + if (q->next) + q = q->next; + else + break; + } + + if (!q) + q = bq->blocks_tail; + + /* We go from back to front to look for the right place to add + * this new entry. Drop data we will overwrite on the way */ + + while (q) { + + if (bq->write_index >= q->index + (int64_t) q->chunk.length) + /* We found the entry where we need to place the new entry immediately after */ + break; + else if (bq->write_index + (int64_t) chunk.length <= q->index) { + /* This entry isn't touched at all, let's skip it */ + q = q->prev; + } else if (bq->write_index <= q->index && + bq->write_index + (int64_t) chunk.length >= q->index + (int64_t) q->chunk.length) { + + /* This entry is fully replaced by the new entry, so let's drop it */ + + struct list_item *p; + p = q; + q = q->prev; + drop_block(bq, p); + } else if (bq->write_index >= q->index) { + /* The write index points into this memblock, so let's + * truncate or split it */ + + if (bq->write_index + (int64_t) chunk.length < q->index + (int64_t) q->chunk.length) { + + /* We need to save the end of this memchunk */ + struct list_item *p; + size_t d; + + /* Create a new list entry for the end of the memchunk */ + if (!(p = pa_flist_pop(PA_STATIC_FLIST_GET(list_items)))) + p = pa_xnew(struct list_item, 1); + + p->chunk = q->chunk; + pa_memblock_ref(p->chunk.memblock); + + /* Calculate offset */ + d = (size_t) (bq->write_index + (int64_t) chunk.length - q->index); + pa_assert(d > 0); + + /* Drop it from the new entry */ + p->index = q->index + (int64_t) d; + p->chunk.length -= d; + + /* Add it to the list */ + p->prev = q; + if ((p->next = q->next)) + q->next->prev = p; + else + bq->blocks_tail = p; + q->next = p; + + bq->n_blocks++; + } + + /* Truncate the chunk */ + if (!(q->chunk.length = (size_t) (bq->write_index - q->index))) { + struct list_item *p; + p = q; + q = q->prev; + drop_block(bq, p); + } + + /* We had to truncate this block, hence we're now at the right position */ + break; + } else { + size_t d; + + pa_assert(bq->write_index + (int64_t)chunk.length > q->index && + bq->write_index + (int64_t)chunk.length < q->index + (int64_t)q->chunk.length && + bq->write_index < q->index); + + /* The job overwrites the current entry at the end, so let's drop the beginning of this entry */ + + d = (size_t) (bq->write_index + (int64_t) chunk.length - q->index); + q->index += (int64_t) d; + q->chunk.index += d; + q->chunk.length -= d; + + q = q->prev; + } + } + + if (q) { + pa_assert(bq->write_index >= q->index + (int64_t)q->chunk.length); + pa_assert(!q->next || (bq->write_index + (int64_t)chunk.length <= q->next->index)); + + /* Try to merge memory blocks */ + + if (q->chunk.memblock == chunk.memblock && + q->chunk.index + q->chunk.length == chunk.index && + bq->write_index == q->index + (int64_t) q->chunk.length) { + + q->chunk.length += chunk.length; + bq->write_index += (int64_t) chunk.length; + goto finish; + } + } else + pa_assert(!bq->blocks || (bq->write_index + (int64_t)chunk.length <= bq->blocks->index)); + + if (!(n = pa_flist_pop(PA_STATIC_FLIST_GET(list_items)))) + n = pa_xnew(struct list_item, 1); + + n->chunk = chunk; + pa_memblock_ref(n->chunk.memblock); + n->index = bq->write_index; + bq->write_index += (int64_t) n->chunk.length; + + n->next = q ? q->next : bq->blocks; + n->prev = q; + + if (n->next) + n->next->prev = n; + else + bq->blocks_tail = n; + + if (n->prev) + n->prev->next = n; + else + bq->blocks = n; + + bq->n_blocks++; + +finish: + + write_index_changed(bq, old, true); + return 0; +} + +bool pa_memblockq_prebuf_active(pa_memblockq *bq) { + pa_assert(bq); + + if (bq->in_prebuf) + return pa_memblockq_get_length(bq) < bq->prebuf; + else + return bq->prebuf > 0 && bq->read_index >= bq->write_index; +} + +static bool update_prebuf(pa_memblockq *bq) { + pa_assert(bq); + + if (bq->in_prebuf) { + + if (pa_memblockq_get_length(bq) < bq->prebuf) + return true; + + bq->in_prebuf = false; + return false; + } else { + + if (bq->prebuf > 0 && bq->read_index >= bq->write_index) { + bq->in_prebuf = true; + return true; + } + + return false; + } +} + +int pa_memblockq_peek(pa_memblockq* bq, pa_memchunk *chunk) { + int64_t d; + pa_assert(bq); + pa_assert(chunk); + + /* We need to pre-buffer */ + if (update_prebuf(bq)) + return -1; + + fix_current_read(bq); + + /* Do we need to spit out silence? */ + if (!bq->current_read || bq->current_read->index > bq->read_index) { + size_t length; + + /* How much silence shall we return? */ + if (bq->current_read) + length = (size_t) (bq->current_read->index - bq->read_index); + else if (bq->write_index > bq->read_index) + length = (size_t) (bq->write_index - bq->read_index); + else + length = 0; + + /* We need to return silence, since no data is yet available */ + if (bq->silence.memblock) { + *chunk = bq->silence; + pa_memblock_ref(chunk->memblock); + + if (length > 0 && length < chunk->length) + chunk->length = length; + + } else { + + /* If the memblockq is empty, return -1, otherwise return + * the time to sleep */ + if (length <= 0) + return -1; + + chunk->memblock = NULL; + chunk->length = length; + } + + chunk->index = 0; + return 0; + } + + /* Ok, let's pass real data to the caller */ + *chunk = bq->current_read->chunk; + pa_memblock_ref(chunk->memblock); + + pa_assert(bq->read_index >= bq->current_read->index); + d = bq->read_index - bq->current_read->index; + chunk->index += (size_t) d; + chunk->length -= (size_t) d; + + return 0; +} + +int pa_memblockq_peek_fixed_size(pa_memblockq *bq, size_t block_size, pa_memchunk *chunk) { + pa_mempool *pool; + pa_memchunk tchunk, rchunk; + int64_t ri; + struct list_item *item; + + pa_assert(bq); + pa_assert(block_size > 0); + pa_assert(chunk); + pa_assert(bq->silence.memblock); + + if (pa_memblockq_peek(bq, &tchunk) < 0) + return -1; + + if (tchunk.length >= block_size) { + *chunk = tchunk; + chunk->length = block_size; + return 0; + } + + pool = pa_memblock_get_pool(tchunk.memblock); + rchunk.memblock = pa_memblock_new(pool, block_size); + rchunk.index = 0; + rchunk.length = tchunk.length; + pa_mempool_unref(pool), pool = NULL; + + pa_memchunk_memcpy(&rchunk, &tchunk); + pa_memblock_unref(tchunk.memblock); + + rchunk.index += tchunk.length; + + /* We don't need to call fix_current_read() here, since + * pa_memblock_peek() already did that */ + item = bq->current_read; + ri = bq->read_index + tchunk.length; + + while (rchunk.index < block_size) { + + if (!item || item->index > ri) { + /* Do we need to append silence? */ + tchunk = bq->silence; + + if (item) + tchunk.length = PA_MIN(tchunk.length, (size_t) (item->index - ri)); + + } else { + int64_t d; + + /* We can append real data! */ + tchunk = item->chunk; + + d = ri - item->index; + tchunk.index += (size_t) d; + tchunk.length -= (size_t) d; + + /* Go to next item for the next iteration */ + item = item->next; + } + + rchunk.length = tchunk.length = PA_MIN(tchunk.length, block_size - rchunk.index); + pa_memchunk_memcpy(&rchunk, &tchunk); + + rchunk.index += rchunk.length; + ri += rchunk.length; + } + + rchunk.index = 0; + rchunk.length = block_size; + + *chunk = rchunk; + return 0; +} + +void pa_memblockq_drop(pa_memblockq *bq, size_t length) { + int64_t old; + pa_assert(bq); + pa_assert(length % bq->base == 0); + + old = bq->read_index; + + while (length > 0) { + + /* Do not drop any data when we are in prebuffering mode */ + if (update_prebuf(bq)) + break; + + fix_current_read(bq); + + if (bq->current_read) { + int64_t p, d; + + /* We go through this piece by piece to make sure we don't + * drop more than allowed by prebuf */ + + p = bq->current_read->index + (int64_t) bq->current_read->chunk.length; + pa_assert(p >= bq->read_index); + d = p - bq->read_index; + + if (d > (int64_t) length) + d = (int64_t) length; + + bq->read_index += d; + length -= (size_t) d; + + } else { + + /* The list is empty, there's nothing we could drop */ + bq->read_index += (int64_t) length; + break; + } + } + + drop_backlog(bq); + read_index_changed(bq, old); +} + +void pa_memblockq_rewind(pa_memblockq *bq, size_t length) { + int64_t old; + pa_assert(bq); + pa_assert(length % bq->base == 0); + + old = bq->read_index; + + /* This is kind of the inverse of pa_memblockq_drop() */ + + bq->read_index -= (int64_t) length; + + read_index_changed(bq, old); +} + +bool pa_memblockq_is_readable(pa_memblockq *bq) { + pa_assert(bq); + + if (pa_memblockq_prebuf_active(bq)) + return false; + + if (pa_memblockq_get_length(bq) <= 0) + return false; + + return true; +} + +size_t pa_memblockq_get_length(pa_memblockq *bq) { + pa_assert(bq); + + if (bq->write_index <= bq->read_index) + return 0; + + return (size_t) (bq->write_index - bq->read_index); +} + +void pa_memblockq_seek(pa_memblockq *bq, int64_t offset, pa_seek_mode_t seek, bool account) { + int64_t old; + pa_assert(bq); + + old = bq->write_index; + + switch (seek) { + case PA_SEEK_RELATIVE: + bq->write_index += offset; + break; + case PA_SEEK_ABSOLUTE: + bq->write_index = offset; + break; + case PA_SEEK_RELATIVE_ON_READ: + bq->write_index = bq->read_index + offset; + break; + case PA_SEEK_RELATIVE_END: + bq->write_index = (bq->blocks_tail ? bq->blocks_tail->index + (int64_t) bq->blocks_tail->chunk.length : bq->read_index) + offset; + break; + default: + pa_assert_not_reached(); + } + + drop_backlog(bq); + write_index_changed(bq, old, account); +} + +void pa_memblockq_flush_write(pa_memblockq *bq, bool account) { + int64_t old; + pa_assert(bq); + + pa_memblockq_silence(bq); + + old = bq->write_index; + bq->write_index = bq->read_index; + + pa_memblockq_prebuf_force(bq); + write_index_changed(bq, old, account); +} + +void pa_memblockq_flush_read(pa_memblockq *bq) { + int64_t old; + pa_assert(bq); + + pa_memblockq_silence(bq); + + old = bq->read_index; + bq->read_index = bq->write_index; + + pa_memblockq_prebuf_force(bq); + read_index_changed(bq, old); +} + +size_t pa_memblockq_get_tlength(pa_memblockq *bq) { + pa_assert(bq); + + return bq->tlength; +} + +size_t pa_memblockq_get_minreq(pa_memblockq *bq) { + pa_assert(bq); + + return bq->minreq; +} + +size_t pa_memblockq_get_maxrewind(pa_memblockq *bq) { + pa_assert(bq); + + return bq->maxrewind; +} + +int64_t pa_memblockq_get_read_index(pa_memblockq *bq) { + pa_assert(bq); + + return bq->read_index; +} + +int64_t pa_memblockq_get_write_index(pa_memblockq *bq) { + pa_assert(bq); + + return bq->write_index; +} + +int pa_memblockq_push_align(pa_memblockq* bq, const pa_memchunk *chunk) { + pa_memchunk rchunk; + + pa_assert(bq); + pa_assert(chunk); + + if (bq->base == 1) + return pa_memblockq_push(bq, chunk); + + if (!can_push(bq, pa_mcalign_csize(bq->mcalign, chunk->length))) + return -1; + + pa_mcalign_push(bq->mcalign, chunk); + + while (pa_mcalign_pop(bq->mcalign, &rchunk) >= 0) { + int r; + r = pa_memblockq_push(bq, &rchunk); + pa_memblock_unref(rchunk.memblock); + + if (r < 0) { + pa_mcalign_flush(bq->mcalign); + return -1; + } + } + + return 0; +} + +void pa_memblockq_prebuf_disable(pa_memblockq *bq) { + pa_assert(bq); + + bq->in_prebuf = false; +} + +void pa_memblockq_prebuf_force(pa_memblockq *bq) { + pa_assert(bq); + + if (bq->prebuf > 0) + bq->in_prebuf = true; +} + +size_t pa_memblockq_get_maxlength(pa_memblockq *bq) { + pa_assert(bq); + + return bq->maxlength; +} + +size_t pa_memblockq_get_prebuf(pa_memblockq *bq) { + pa_assert(bq); + + return bq->prebuf; +} + +size_t pa_memblockq_pop_missing(pa_memblockq *bq) { + size_t l; + + pa_assert(bq); + +#ifdef MEMBLOCKQ_DEBUG + pa_log_debug("[%s] pop: %lli", bq->name, (long long) bq->missing); +#endif + + if (bq->missing <= 0) + return 0; + + if (((size_t) bq->missing < bq->minreq) && + !pa_memblockq_prebuf_active(bq)) + return 0; + + l = (size_t) bq->missing; + + bq->requested += bq->missing; + bq->missing = 0; + +#ifdef MEMBLOCKQ_DEBUG + pa_log_debug("[%s] sent %lli: request counter is at %lli", bq->name, (long long) l, (long long) bq->requested); +#endif + + return l; +} + +void pa_memblockq_set_maxlength(pa_memblockq *bq, size_t maxlength) { + pa_assert(bq); + + bq->maxlength = ((maxlength+bq->base-1)/bq->base)*bq->base; + + if (bq->maxlength < bq->base) + bq->maxlength = bq->base; + + if (bq->tlength > bq->maxlength) + pa_memblockq_set_tlength(bq, bq->maxlength); +} + +void pa_memblockq_set_tlength(pa_memblockq *bq, size_t tlength) { + size_t old_tlength; + pa_assert(bq); + + if (tlength <= 0 || tlength == (size_t) -1) + tlength = bq->maxlength; + + old_tlength = bq->tlength; + bq->tlength = ((tlength+bq->base-1)/bq->base)*bq->base; + + if (bq->tlength > bq->maxlength) + bq->tlength = bq->maxlength; + + if (bq->minreq > bq->tlength) + pa_memblockq_set_minreq(bq, bq->tlength); + + if (bq->prebuf > bq->tlength+bq->base-bq->minreq) + pa_memblockq_set_prebuf(bq, bq->tlength+bq->base-bq->minreq); + + bq->missing += (int64_t) bq->tlength - (int64_t) old_tlength; +} + +void pa_memblockq_set_minreq(pa_memblockq *bq, size_t minreq) { + pa_assert(bq); + + bq->minreq = (minreq/bq->base)*bq->base; + + if (bq->minreq > bq->tlength) + bq->minreq = bq->tlength; + + if (bq->minreq < bq->base) + bq->minreq = bq->base; + + if (bq->prebuf > bq->tlength+bq->base-bq->minreq) + pa_memblockq_set_prebuf(bq, bq->tlength+bq->base-bq->minreq); +} + +void pa_memblockq_set_prebuf(pa_memblockq *bq, size_t prebuf) { + pa_assert(bq); + + if (prebuf == (size_t) -1) + prebuf = bq->tlength+bq->base-bq->minreq; + + bq->prebuf = ((prebuf+bq->base-1)/bq->base)*bq->base; + + if (prebuf > 0 && bq->prebuf < bq->base) + bq->prebuf = bq->base; + + if (bq->prebuf > bq->tlength+bq->base-bq->minreq) + bq->prebuf = bq->tlength+bq->base-bq->minreq; + + if (bq->prebuf <= 0 || pa_memblockq_get_length(bq) >= bq->prebuf) + bq->in_prebuf = false; +} + +void pa_memblockq_set_maxrewind(pa_memblockq *bq, size_t maxrewind) { + pa_assert(bq); + + bq->maxrewind = (maxrewind/bq->base)*bq->base; +} + +void pa_memblockq_apply_attr(pa_memblockq *bq, const pa_buffer_attr *a) { + pa_assert(bq); + pa_assert(a); + + pa_memblockq_set_maxlength(bq, a->maxlength); + pa_memblockq_set_tlength(bq, a->tlength); + pa_memblockq_set_minreq(bq, a->minreq); + pa_memblockq_set_prebuf(bq, a->prebuf); +} + +void pa_memblockq_get_attr(pa_memblockq *bq, pa_buffer_attr *a) { + pa_assert(bq); + pa_assert(a); + + a->maxlength = (uint32_t) pa_memblockq_get_maxlength(bq); + a->tlength = (uint32_t) pa_memblockq_get_tlength(bq); + a->prebuf = (uint32_t) pa_memblockq_get_prebuf(bq); + a->minreq = (uint32_t) pa_memblockq_get_minreq(bq); +} + +int pa_memblockq_splice(pa_memblockq *bq, pa_memblockq *source) { + + pa_assert(bq); + pa_assert(source); + + pa_memblockq_prebuf_disable(bq); + + for (;;) { + pa_memchunk chunk; + + if (pa_memblockq_peek(source, &chunk) < 0) + return 0; + + pa_assert(chunk.length > 0); + + if (chunk.memblock) { + + if (pa_memblockq_push_align(bq, &chunk) < 0) { + pa_memblock_unref(chunk.memblock); + return -1; + } + + pa_memblock_unref(chunk.memblock); + } else + pa_memblockq_seek(bq, (int64_t) chunk.length, PA_SEEK_RELATIVE, true); + + pa_memblockq_drop(bq, chunk.length); + } +} + +void pa_memblockq_willneed(pa_memblockq *bq) { + struct list_item *q; + + pa_assert(bq); + + fix_current_read(bq); + + for (q = bq->current_read; q; q = q->next) + pa_memchunk_will_need(&q->chunk); +} + +void pa_memblockq_set_silence(pa_memblockq *bq, pa_memchunk *silence) { + pa_assert(bq); + + if (bq->silence.memblock) + pa_memblock_unref(bq->silence.memblock); + + if (silence) { + bq->silence = *silence; + pa_memblock_ref(bq->silence.memblock); + } else + pa_memchunk_reset(&bq->silence); +} + +bool pa_memblockq_is_empty(pa_memblockq *bq) { + pa_assert(bq); + + return !bq->blocks; +} + +void pa_memblockq_silence(pa_memblockq *bq) { + pa_assert(bq); + + while (bq->blocks) + drop_block(bq, bq->blocks); + + pa_assert(bq->n_blocks == 0); +} + +unsigned pa_memblockq_get_nblocks(pa_memblockq *bq) { + pa_assert(bq); + + return bq->n_blocks; +} + +size_t pa_memblockq_get_base(pa_memblockq *bq) { + pa_assert(bq); + + return bq->base; +} diff --git a/src/pulsecore/memblockq.h b/src/pulsecore/memblockq.h new file mode 100644 index 0000000..96e41cc --- /dev/null +++ b/src/pulsecore/memblockq.h @@ -0,0 +1,187 @@ +#ifndef foomemblockqhfoo +#define foomemblockqhfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/types.h> +#include <inttypes.h> + +#include <pulsecore/memblock.h> +#include <pulsecore/memchunk.h> +#include <pulse/def.h> + +/* A memblockq is a queue of pa_memchunks (yep, the name is not + * perfect). It is similar to the ring buffers used by most other + * audio software. In contrast to a ring buffer this memblockq data + * type doesn't need to copy any data around, it just maintains + * references to reference counted memory blocks. */ + +typedef struct pa_memblockq pa_memblockq; + +/* Parameters: + + - name: name for debugging purposes + + - idx: start value for both read and write index + + - maxlength: maximum length of queue. If more data is pushed into + the queue, the operation will fail. Must not be 0. + + - tlength: the target length of the queue. Pass 0 for the default. + + - ss: Sample spec describing the queue contents. Only multiples + of the frame size as implied by the sample spec are + allowed into the queue or can be popped from it. + + - prebuf: If the queue runs empty wait until this many bytes are in + queue again before passing the first byte out. If set + to 0 pa_memblockq_pop() will return a silence memblock + if no data is in the queue and will never fail. Pass + (size_t) -1 for the default. + + - minreq: pa_memblockq_pop_missing() will only return values greater + than this value. Pass 0 for the default. + + - maxrewind: how many bytes of history to keep in the queue + + - silence: return this memchunk when reading uninitialized data +*/ +pa_memblockq* pa_memblockq_new( + const char *name, + int64_t idx, + size_t maxlength, + size_t tlength, + const pa_sample_spec *sample_spec, + size_t prebuf, + size_t minreq, + size_t maxrewind, + pa_memchunk *silence); + +void pa_memblockq_free(pa_memblockq*bq); + +/* Push a new memory chunk into the queue. */ +int pa_memblockq_push(pa_memblockq* bq, const pa_memchunk *chunk); + +/* Push a new memory chunk into the queue, but filter it through a + * pa_mcalign object. Don't mix this with pa_memblockq_seek() unless + * you know what you do. */ +int pa_memblockq_push_align(pa_memblockq* bq, const pa_memchunk *chunk); + +/* Manipulate the write pointer */ +void pa_memblockq_seek(pa_memblockq *bq, int64_t offset, pa_seek_mode_t seek, bool account); + +/* Return a copy of the next memory chunk in the queue. It is not + * removed from the queue. There are two reasons this function might + * fail: 1. prebuffering is active, 2. queue is empty and no silence + * memblock was passed at initialization. If the queue is not empty, + * but we're currently at a hole in the queue and no silence memblock + * was passed we return the length of the hole in chunk->length. */ +int pa_memblockq_peek(pa_memblockq* bq, pa_memchunk *chunk); + +/* Much like pa_memblockq_peek, but guarantees that the returned chunk + * will have a length of the block size passed. You must configure a + * silence memchunk for this memblockq if you use this call. */ +int pa_memblockq_peek_fixed_size(pa_memblockq *bq, size_t block_size, pa_memchunk *chunk); + +/* Drop the specified bytes from the queue. */ +void pa_memblockq_drop(pa_memblockq *bq, size_t length); + +/* Rewind the read index. If the history is shorter than the specified length we'll point to silence afterwards. */ +void pa_memblockq_rewind(pa_memblockq *bq, size_t length); + +/* Test if the pa_memblockq is currently readable, that is, more data than base */ +bool pa_memblockq_is_readable(pa_memblockq *bq); + +/* Return the length of the queue in bytes */ +size_t pa_memblockq_get_length(pa_memblockq *bq); + +/* Return the number of bytes that are missing since the last call to + * this function, reset the internal counter to 0. */ +size_t pa_memblockq_pop_missing(pa_memblockq *bq); + +/* Directly moves the data from the source memblockq into bq */ +int pa_memblockq_splice(pa_memblockq *bq, pa_memblockq *source); + +/* Set the queue to silence, set write index to read index */ +void pa_memblockq_flush_write(pa_memblockq *bq, bool account); + +/* Set the queue to silence, set write read index to write index*/ +void pa_memblockq_flush_read(pa_memblockq *bq); + +/* Ignore prebuf for now */ +void pa_memblockq_prebuf_disable(pa_memblockq *bq); + +/* Force prebuf */ +void pa_memblockq_prebuf_force(pa_memblockq *bq); + +/* Return the maximum length of the queue in bytes */ +size_t pa_memblockq_get_maxlength(pa_memblockq *bq); + +/* Get Target length */ +size_t pa_memblockq_get_tlength(pa_memblockq *bq); + +/* Return the prebuffer length in bytes */ +size_t pa_memblockq_get_prebuf(pa_memblockq *bq); + +/* Returns the minimal request value */ +size_t pa_memblockq_get_minreq(pa_memblockq *bq); + +/* Returns the maximal rewind value */ +size_t pa_memblockq_get_maxrewind(pa_memblockq *bq); + +/* Return the base unit in bytes */ +size_t pa_memblockq_get_base(pa_memblockq *bq); + +/* Return the current read index */ +int64_t pa_memblockq_get_read_index(pa_memblockq *bq); + +/* Return the current write index */ +int64_t pa_memblockq_get_write_index(pa_memblockq *bq); + +/* Change metrics. Always call in order. */ +void pa_memblockq_set_maxlength(pa_memblockq *memblockq, size_t maxlength); /* might modify tlength, prebuf, minreq too */ +void pa_memblockq_set_tlength(pa_memblockq *memblockq, size_t tlength); /* might modify minreq, too */ +void pa_memblockq_set_minreq(pa_memblockq *memblockq, size_t minreq); /* might modify prebuf, too */ +void pa_memblockq_set_prebuf(pa_memblockq *memblockq, size_t prebuf); +void pa_memblockq_set_maxrewind(pa_memblockq *memblockq, size_t maxrewind); /* Set the maximum history size */ +void pa_memblockq_set_silence(pa_memblockq *memblockq, pa_memchunk *silence); + +/* Apply the data from pa_buffer_attr */ +void pa_memblockq_apply_attr(pa_memblockq *memblockq, const pa_buffer_attr *a); +void pa_memblockq_get_attr(pa_memblockq *bq, pa_buffer_attr *a); + +/* Call pa_memchunk_will_need() for every chunk in the queue from the current read pointer to the end */ +void pa_memblockq_willneed(pa_memblockq *bq); + +/* Check whether the memblockq is completely empty, i.e. no data + * neither left nor right of the read pointer, and hence no buffered + * data for the future nor data in the backlog. */ +bool pa_memblockq_is_empty(pa_memblockq *bq); + +/* Drop everything in the queue, but don't modify the indexes */ +void pa_memblockq_silence(pa_memblockq *bq); + +/* Check whether we currently are in prebuf state */ +bool pa_memblockq_prebuf_active(pa_memblockq *bq); + +/* Return how many items are currently stored in the queue */ +unsigned pa_memblockq_get_nblocks(pa_memblockq *bq); + +#endif diff --git a/src/pulsecore/memchunk.c b/src/pulsecore/memchunk.c new file mode 100644 index 0000000..8822134 --- /dev/null +++ b/src/pulsecore/memchunk.c @@ -0,0 +1,121 @@ +/*** + 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +#include <pulsecore/macro.h> +#include <pulsecore/core-util.h> + +#include "memchunk.h" + +pa_memchunk* pa_memchunk_make_writable(pa_memchunk *c, size_t min) { + pa_mempool *pool; + pa_memblock *n; + size_t l; + void *tdata, *sdata; + + pa_assert(c); + pa_assert(c->memblock); + + if (pa_memblock_ref_is_one(c->memblock) && + !pa_memblock_is_read_only(c->memblock) && + pa_memblock_get_length(c->memblock) >= c->index+min) + return c; + + l = PA_MAX(c->length, min); + + pool = pa_memblock_get_pool(c->memblock); + n = pa_memblock_new(pool, l); + pa_mempool_unref(pool), pool = NULL; + + sdata = pa_memblock_acquire(c->memblock); + tdata = pa_memblock_acquire(n); + + memcpy(tdata, (uint8_t*) sdata + c->index, c->length); + + pa_memblock_release(c->memblock); + pa_memblock_release(n); + + pa_memblock_unref(c->memblock); + + c->memblock = n; + c->index = 0; + + return c; +} + +pa_memchunk* pa_memchunk_reset(pa_memchunk *c) { + pa_assert(c); + + memset(c, 0, sizeof(*c)); + + return c; +} + +pa_memchunk *pa_memchunk_will_need(const pa_memchunk *c) { + void *p; + + pa_assert(c); + pa_assert(c->memblock); + + /* A version of pa_memblock_will_need() that works on memchunks + * instead of memblocks */ + + p = pa_memblock_acquire_chunk(c); + pa_will_need(p, c->length); + pa_memblock_release(c->memblock); + + return (pa_memchunk*) c; +} + +pa_memchunk* pa_memchunk_memcpy(pa_memchunk *dst, pa_memchunk *src) { + void *p, *q; + + pa_assert(dst); + pa_assert(src); + pa_assert(dst->length == src->length); + + p = pa_memblock_acquire(dst->memblock); + q = pa_memblock_acquire(src->memblock); + + memmove((uint8_t*) p + dst->index, + (uint8_t*) q + src->index, + dst->length); + + pa_memblock_release(dst->memblock); + pa_memblock_release(src->memblock); + + return dst; +} + +bool pa_memchunk_isset(pa_memchunk *chunk) { + pa_assert(chunk); + + return + chunk->memblock || + chunk->index > 0 || + chunk->length > 0; +} diff --git a/src/pulsecore/memchunk.h b/src/pulsecore/memchunk.h new file mode 100644 index 0000000..2b19712 --- /dev/null +++ b/src/pulsecore/memchunk.h @@ -0,0 +1,56 @@ +#ifndef foomemchunkhfoo +#define foomemchunkhfoo + +/*** + 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +typedef struct pa_memchunk pa_memchunk; + +#include <pulsecore/memblock.h> + +/* A memchunk describes a part of a memblock. In contrast to the memblock, a + * memchunk is not allocated dynamically or reference counted, instead + * it is usually stored on the stack and copied around */ + +struct pa_memchunk { + pa_memblock *memblock; + size_t index, length; +}; + +/* Make a memchunk writable, i.e. make sure that the caller may have + * exclusive access to the memblock and it is not read-only. If needed + * the memblock in the structure is replaced by a copy. If min is not + * 0 it is made sure that the returned memblock is at least of the + * specified size, i.e. is enlarged if necessary. */ +pa_memchunk* pa_memchunk_make_writable(pa_memchunk *c, size_t min); + +/* Invalidate a memchunk. This does not free the containing memblock, + * but sets all members to zero. */ +pa_memchunk* pa_memchunk_reset(pa_memchunk *c); + +/* Map a memory chunk back into memory if it was swapped out */ +pa_memchunk *pa_memchunk_will_need(const pa_memchunk *c); + +/* Copy the data in the src memchunk to the dst memchunk */ +pa_memchunk* pa_memchunk_memcpy(pa_memchunk *dst, pa_memchunk *src); + +/* Return true if any field is set != 0 */ +bool pa_memchunk_isset(pa_memchunk *c); + +#endif diff --git a/src/pulsecore/memfd-wrappers.h b/src/pulsecore/memfd-wrappers.h new file mode 100644 index 0000000..c7aadfd --- /dev/null +++ b/src/pulsecore/memfd-wrappers.h @@ -0,0 +1,69 @@ +#ifndef foopulsememfdwrappershfoo +#define foopulsememfdwrappershfoo + +/*** + This file is part of PulseAudio. + + Copyright 2016 Ahmed S. Darwish <darwish.07@gmail.com> + + 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#if defined(HAVE_MEMFD) && !defined(HAVE_MEMFD_CREATE) + +#include <sys/syscall.h> +#include <fcntl.h> + +/* + * Before glibc version 2.27 there was no wrapper for memfd_create(2), + * so we have to provide our own. + * + * Also define memfd fcntl sealing macros. While they are already + * defined in the kernel header file <linux/fcntl.h>, that file as + * a whole conflicts with the original glibc header <fnctl.h>. + */ + +static inline int memfd_create(const char *name, unsigned int flags) { + return syscall(SYS_memfd_create, name, flags); +} + +/* memfd_create(2) flags */ + +#ifndef MFD_CLOEXEC +#define MFD_CLOEXEC 0x0001U +#endif + +#ifndef MFD_ALLOW_SEALING +#define MFD_ALLOW_SEALING 0x0002U +#endif + +/* fcntl() seals-related flags */ + +#ifndef F_LINUX_SPECIFIC_BASE +#define F_LINUX_SPECIFIC_BASE 1024 +#endif + +#ifndef F_ADD_SEALS +#define F_ADD_SEALS (F_LINUX_SPECIFIC_BASE + 9) +#define F_GET_SEALS (F_LINUX_SPECIFIC_BASE + 10) + +#define F_SEAL_SEAL 0x0001 /* prevent further seals from being set */ +#define F_SEAL_SHRINK 0x0002 /* prevent file from shrinking */ +#define F_SEAL_GROW 0x0004 /* prevent file from growing */ +#define F_SEAL_WRITE 0x0008 /* prevent writes */ +#endif + +#endif /* HAVE_MEMFD && !HAVE_MEMFD_CREATE */ + +#endif diff --git a/src/pulsecore/memtrap.c b/src/pulsecore/memtrap.c new file mode 100644 index 0000000..e7b511c --- /dev/null +++ b/src/pulsecore/memtrap.c @@ -0,0 +1,242 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <signal.h> + +#ifdef HAVE_SYS_MMAN_H +#include <sys/mman.h> +#endif + +/* This is deprecated on glibc but is still used by FreeBSD */ +#if !defined(MAP_ANONYMOUS) && defined(MAP_ANON) +# define MAP_ANONYMOUS MAP_ANON +#endif + +#include <pulse/xmalloc.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/aupdate.h> +#include <pulsecore/atomic.h> +#include <pulsecore/once.h> +#include <pulsecore/mutex.h> + +#include "memtrap.h" + +struct pa_memtrap { + void *start; + size_t size; + pa_atomic_t bad; + pa_memtrap *next[2], *prev[2]; +}; + +static pa_memtrap *memtraps[2] = { NULL, NULL }; +static pa_aupdate *aupdate; +static pa_static_mutex mutex = PA_STATIC_MUTEX_INIT; /* only required to serialize access to the write side */ + +static void allocate_aupdate(void) { + PA_ONCE_BEGIN { + aupdate = pa_aupdate_new(); + } PA_ONCE_END; +} + +bool pa_memtrap_is_good(pa_memtrap *m) { + pa_assert(m); + + return !pa_atomic_load(&m->bad); +} + +#ifdef HAVE_SIGACTION +static void sigsafe_error(const char *s) { + size_t ret PA_GCC_UNUSED; + ret = write(STDERR_FILENO, s, strlen(s)); +} + +static void signal_handler(int sig, siginfo_t* si, void *data) { + unsigned j; + pa_memtrap *m; + void *r; + + j = pa_aupdate_read_begin(aupdate); + + for (m = memtraps[j]; m; m = m->next[j]) + if (si->si_addr >= m->start && + (uint8_t*) si->si_addr < (uint8_t*) m->start + m->size) + break; + + if (!m) + goto fail; + + pa_atomic_store(&m->bad, 1); + + /* Remap anonymous memory into the bad segment */ + if ((r = mmap(m->start, m->size, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_FIXED|MAP_PRIVATE, -1, 0)) == MAP_FAILED) { + sigsafe_error("mmap() failed.\n"); + goto fail; + } + + pa_assert(r == m->start); + + pa_aupdate_read_end(aupdate); + return; + +fail: + pa_aupdate_read_end(aupdate); + + sigsafe_error("Failed to handle SIGBUS.\n"); + abort(); +} +#endif + +static void memtrap_link(pa_memtrap *m, unsigned j) { + pa_assert(m); + + m->prev[j] = NULL; + + if ((m->next[j] = memtraps[j])) + m->next[j]->prev[j] = m; + + memtraps[j] = m; +} + +static void memtrap_unlink(pa_memtrap *m, unsigned j) { + pa_assert(m); + + if (m->next[j]) + m->next[j]->prev[j] = m->prev[j]; + + if (m->prev[j]) + m->prev[j]->next[j] = m->next[j]; + else + memtraps[j] = m->next[j]; +} + +pa_memtrap* pa_memtrap_add(const void *start, size_t size) { + pa_memtrap *m = NULL; + unsigned j; + pa_mutex *mx; + + pa_assert(start); + pa_assert(size > 0); + + start = PA_PAGE_ALIGN_PTR(start); + size = PA_PAGE_ALIGN(size); + + m = pa_xnew(pa_memtrap, 1); + m->start = (void*) start; + m->size = size; + pa_atomic_store(&m->bad, 0); + + allocate_aupdate(); + + mx = pa_static_mutex_get(&mutex, false, true); + pa_mutex_lock(mx); + + j = pa_aupdate_write_begin(aupdate); + memtrap_link(m, j); + j = pa_aupdate_write_swap(aupdate); + memtrap_link(m, j); + pa_aupdate_write_end(aupdate); + + pa_mutex_unlock(mx); + + return m; +} + +void pa_memtrap_remove(pa_memtrap *m) { + unsigned j; + pa_mutex *mx; + + pa_assert(m); + + allocate_aupdate(); + + mx = pa_static_mutex_get(&mutex, false, true); + pa_mutex_lock(mx); + + j = pa_aupdate_write_begin(aupdate); + memtrap_unlink(m, j); + j = pa_aupdate_write_swap(aupdate); + memtrap_unlink(m, j); + pa_aupdate_write_end(aupdate); + + pa_mutex_unlock(mx); + + pa_xfree(m); +} + +pa_memtrap *pa_memtrap_update(pa_memtrap *m, const void *start, size_t size) { + unsigned j; + pa_mutex *mx; + + pa_assert(m); + + pa_assert(start); + pa_assert(size > 0); + + start = PA_PAGE_ALIGN_PTR(start); + size = PA_PAGE_ALIGN(size); + + allocate_aupdate(); + + mx = pa_static_mutex_get(&mutex, false, true); + pa_mutex_lock(mx); + + j = pa_aupdate_write_begin(aupdate); + + if (m->start == start && m->size == size) + goto unlock; + + memtrap_unlink(m, j); + pa_aupdate_write_swap(aupdate); + + m->start = (void*) start; + m->size = size; + pa_atomic_store(&m->bad, 0); + + pa_assert_se(pa_aupdate_write_swap(aupdate) == j); + memtrap_link(m, j); + +unlock: + pa_aupdate_write_end(aupdate); + + pa_mutex_unlock(mx); + + return m; +} + +void pa_memtrap_install(void) { +#ifdef HAVE_SIGACTION + struct sigaction sa; + + allocate_aupdate(); + + memset(&sa, 0, sizeof(sa)); + sa.sa_sigaction = signal_handler; + sa.sa_flags = SA_RESTART|SA_SIGINFO; + + pa_assert_se(sigaction(SIGBUS, &sa, NULL) == 0); +#ifdef __FreeBSD_kernel__ + pa_assert_se(sigaction(SIGSEGV, &sa, NULL) == 0); +#endif +#endif +} diff --git a/src/pulsecore/memtrap.h b/src/pulsecore/memtrap.h new file mode 100644 index 0000000..cf3e99e --- /dev/null +++ b/src/pulsecore/memtrap.h @@ -0,0 +1,49 @@ +#ifndef foopulsecorememtraphfoo +#define foopulsecorememtraphfoo + +/*** + This file is part of PulseAudio. + + Copyright 2009 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/types.h> + +#include <pulsecore/macro.h> + +/* This subsystem will trap SIGBUS on specific memory regions. The + * regions will be remapped to anonymous memory (i.e. writable NUL + * bytes) on SIGBUS, so that execution of the main program can + * continue though with memory having changed beneath its hands. With + * pa_memtrap_is_good() it is possible to query if a memory region is + * still 'good' i.e. no SIGBUS has happened yet for it. + * + * Intended usage is to handle memory mapped in which is controlled by + * other processes that might execute ftruncate() or when mapping inb + * hardware resources that might get invalidated when unplugged. */ + +typedef struct pa_memtrap pa_memtrap; + +pa_memtrap* pa_memtrap_add(const void *start, size_t size); +pa_memtrap *pa_memtrap_update(pa_memtrap *m, const void *start, size_t size); + +void pa_memtrap_remove(pa_memtrap *m); + +bool pa_memtrap_is_good(pa_memtrap *m); + +void pa_memtrap_install(void); + +#endif diff --git a/src/pulsecore/meson.build b/src/pulsecore/meson.build new file mode 100644 index 0000000..99a702e --- /dev/null +++ b/src/pulsecore/meson.build @@ -0,0 +1,286 @@ +libpulsecore_sources = [ + 'asyncmsgq.c', + 'asyncq.c', + 'auth-cookie.c', + 'card.c', + 'cli-command.c', + 'cli-text.c', + 'client.c', + 'core-scache.c', + 'core-subscribe.c', + 'core.c', + 'cpu.c', + 'cpu-arm.c', + 'cpu-orc.c', + 'cpu-x86.c', + 'device-port.c', + 'database.c', + 'ffmpeg/resample2.c', + 'filter/biquad.c', + 'filter/crossover.c', + 'filter/lfe-filter.c', + 'hook-list.c', + 'ltdl-helper.c', + 'message-handler.c', + 'mix.c', + 'modargs.c', + 'modinfo.c', + 'module.c', + 'msgobject.c', + 'namereg.c', + 'object.c', + 'play-memblockq.c', + 'play-memchunk.c', + 'remap.c', + 'resampler.c', + 'resampler/ffmpeg.c', + 'resampler/peaks.c', + 'resampler/trivial.c', + 'rtpoll.c', + 'sconv-s16be.c', + 'sconv-s16le.c', + 'sconv.c', + 'shared.c', + 'sink.c', + 'sink-input.c', + 'sioman.c', + 'sound-file-stream.c', + 'sound-file.c', + 'source.c', + 'source-output.c', + 'start-child.c', + 'stream-util.c', + 'svolume_arm.c', + 'svolume_c.c', + 'svolume_mmx.c', + 'svolume_sse.c', + 'thread-mq.c', +] + +libpulsecore_headers = [ + 'asyncmsgq.h', + 'asyncq.h', + 'auth-cookie.h', + 'card.h', + 'cli-command.h', + 'cli-text.h', + 'client.h', + 'core.h', + 'core-scache.h', + 'core-subscribe.h', + 'cpu.h', + 'cpu-arm.h', + 'cpu-orc.h', + 'cpu-x86.h', + 'database.h', + 'device-port.h', + 'ffmpeg/avcodec.h', + 'ffmpeg/dsputil.h', + 'filter/biquad.h', + 'filter/crossover.h', + 'filter/lfe-filter.h', + 'hook-list.h', + 'ltdl-helper.h', + 'message-handler.h', + 'mix.h', + 'modargs.h', + 'modinfo.h', + 'module.h', + 'msgobject.h', + 'namereg.h', + 'object.h', + 'play-memblockq.h', + 'play-memchunk.h', + 'remap.h', + 'resampler.h', + 'rtpoll.h', + 'sconv.h', + 'sconv-s16be.h', + 'sconv-s16le.h', + 'shared.h', + 'sink-input.h', + 'sink.h', + 'sioman.h', + 'sound-file-stream.h', + 'sound-file.h', + 'source-output.h', + 'source.h', + 'start-child.h', + 'stream-util.h', + 'thread-mq.h', + 'typedefs.h', +] + +if get_option('database') == 'tdb' + libpulsecore_sources += 'database-tdb.c' + database_c_args = '-DHAVE_TDB' +elif get_option('database') == 'gdbm' + libpulsecore_sources += 'database-gdbm.c' + database_c_args = '-DHAVE_GDBM' +else + libpulsecore_sources += 'database-simple.c' + database_c_args = '-DHAVE_SIMPLEDB' +endif + +if dbus_dep.found() + libpulsecore_sources += [ + 'dbus-shared.c', + 'protocol-dbus.c', + ] + libpulsecore_headers += [ + 'dbus-shared.h', + 'protocol-dbus.h', + ] +endif + +if samplerate_dep.found() + libpulsecore_sources += ['resampler/libsamplerate.c'] +endif + +if soxr_dep.found() + libpulsecore_sources += ['resampler/soxr.c'] +endif + +if speex_dep.found() + libpulsecore_sources += ['resampler/speex.c'] +endif + +if x11_dep.found() + libpulsecore_sources += ['x11wrap.c'] + libpulsecore_headers += ['x11wrap.h'] +endif + +orc_sources = [] +orc_headers = [] +if have_orcc + orcsrc = 'svolume' + orc_h = custom_target(orcsrc + '-orc-gen.h', + input : orcsrc + '.orc', + output : orcsrc + '-orc-gen.h', + command : orcc_args + ['--header', '-o', '@OUTPUT@', '@INPUT@'] + ) + orc_c = custom_target(orcsrc + '-orc-gen.c', + input : orcsrc + '.orc', + output : orcsrc + '-orc-gen.c', + command : orcc_args + ['--implementation', '-o', '@OUTPUT@', '@INPUT@'] + ) + orc_sources = [orc_c, 'svolume_orc.c'] + orc_headers = [orc_h] +endif + +# FIXME: walk through dependencies and add files + +# FIXME: SIMD support (ORC) +simd = import('unstable-simd') +libpulsecore_simd = simd.check('libpulsecore_simd', + mmx : ['remap_mmx.c', 'svolume_mmx.c'], + sse : ['remap_sse.c', 'sconv_sse.c', 'svolume_sse.c'], + neon : ['remap_neon.c', 'sconv_neon.c', 'mix_neon.c'], + c_args : [pa_c_args], + include_directories : [configinc, topinc], + implicit_include_directories : false, + compiler : cc) +libpulsecore_simd_lib = libpulsecore_simd[0] +cdata.merge_from(libpulsecore_simd[1]) + +# FIXME: Implement Windows support +#'mutex-win32.c', +#'poll-win32.c', +#'semaphore-win32.c', +#'thread-win32.c', + +libpulsecore = shared_library('pulsecore-' + pa_version_major_minor, + libpulsecore_sources, libpulsecore_headers, + orc_sources, orc_headers, + include_directories : [configinc, topinc], + c_args : [pa_c_args, server_c_args], + link_args : [nodelete_link_args], + install : true, + install_rpath : privlibdir, + install_dir : privlibdir, + link_with : libpulsecore_simd_lib, + dependencies : [libm_dep, libpulsecommon_dep, ltdl_dep, shm_dep, sndfile_dep, database_dep, dbus_dep, libatomic_ops_dep, orc_dep, samplerate_dep, soxr_dep, speex_dep, x11_dep, libintl_dep], + implicit_include_directories : false) + +libpulsecore_dep = declare_dependency(link_with: libpulsecore) + +# Internal libraries for modules +# TODO: understand 'c_args' and 'dependencies' better, maybe we can remove some + +libavahi_wrap = shared_library('avahi-wrap', + 'avahi-wrap.c', + 'avahi-wrap.h', + c_args : [pa_c_args, server_c_args, database_c_args], + link_args : [nodelete_link_args], + include_directories : [configinc, topinc], + dependencies : [libpulse_dep, libpulsecommon_dep, libpulsecore_dep, avahi_dep], + implicit_include_directories : false, # pulsecore/poll.h <vs> /usr/include/poll.h + install : true, + install_rpath : privlibdir, + install_dir : modlibexecdir, +) + +libcli = shared_library('cli', + 'cli.c', + 'cli.h', + c_args : [pa_c_args, server_c_args, database_c_args], + link_args : [nodelete_link_args], + include_directories : [configinc, topinc], + dependencies : [libpulse_dep, libpulsecommon_dep, libpulsecore_dep], + install : true, + install_rpath : privlibdir, + install_dir : modlibexecdir, +) + +libcli_dep = declare_dependency(link_with: libcli) + +# FIXME: meson doesn't support multiple RPATH arguments currently +rpath_dirs = join_paths(privlibdir) + ':' + join_paths(modlibexecdir) + +libprotocol_cli = shared_library('protocol-cli', + 'protocol-cli.c', + 'protocol-cli.h', + c_args : [pa_c_args, server_c_args, database_c_args], + link_args : [nodelete_link_args], + include_directories : [configinc, topinc], + dependencies : [libpulse_dep, libpulsecommon_dep, libpulsecore_dep, libcli_dep], + install : true, + install_rpath : rpath_dirs, + install_dir : modlibexecdir, +) + +libprotocol_http = shared_library('protocol-http', + ['protocol-http.c', 'mime-type.c'], + ['protocol-http.h', 'mime-type.h'], + c_args : [pa_c_args, server_c_args, database_c_args], + link_args : [nodelete_link_args], + include_directories : [configinc, topinc], + dependencies : [libpulse_dep, libpulsecommon_dep, libpulsecore_dep], + install : true, + install_rpath : privlibdir, + install_dir : modlibexecdir, +) + +libprotocol_native = shared_library('protocol-native', + 'protocol-native.c', + ['protocol-native.h', 'native-common.h'], + c_args : [pa_c_args, server_c_args, database_c_args], + link_args : [nodelete_link_args], + include_directories : [configinc, topinc], + dependencies : [libpulse_dep, libpulsecommon_dep, libpulsecore_dep, dbus_dep], + install : true, + install_rpath : privlibdir, + install_dir : modlibexecdir, +) + +libprotocol_simple = shared_library('protocol-simple', + 'protocol-simple.c', + 'protocol-simple.h', + c_args : [pa_c_args, server_c_args, database_c_args], + link_args : [nodelete_link_args], + include_directories : [configinc, topinc], + dependencies : [libpulse_dep, libpulsecommon_dep, libpulsecore_dep], + install : true, + install_rpath : privlibdir, + install_dir : modlibexecdir, +) diff --git a/src/pulsecore/message-handler.c b/src/pulsecore/message-handler.c new file mode 100644 index 0000000..7555a18 --- /dev/null +++ b/src/pulsecore/message-handler.c @@ -0,0 +1,104 @@ +/*** + This file is part of PulseAudio. + + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <stdio.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/core.h> +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> + +#include "message-handler.h" + +/* Message handler functions */ + +/* Register message handler for the specified object. object_path must be a unique name starting with "/". */ +void pa_message_handler_register(pa_core *c, const char *object_path, const char *description, pa_message_handler_cb_t cb, void *userdata) { + struct pa_message_handler *handler; + + pa_assert(c); + pa_assert(object_path); + pa_assert(cb); + pa_assert(userdata); + + /* Ensure that the object path is not empty and starts with "/". */ + pa_assert(object_path[0] == '/'); + + handler = pa_xnew0(struct pa_message_handler, 1); + handler->userdata = userdata; + handler->callback = cb; + handler->object_path = pa_xstrdup(object_path); + handler->description = pa_xstrdup(description); + + pa_assert_se(pa_hashmap_put(c->message_handlers, handler->object_path, handler) == 0); +} + +/* Unregister a message handler */ +void pa_message_handler_unregister(pa_core *c, const char *object_path) { + struct pa_message_handler *handler; + + pa_assert(c); + pa_assert(object_path); + + pa_assert_se(handler = pa_hashmap_remove(c->message_handlers, object_path)); + + pa_xfree(handler->object_path); + pa_xfree(handler->description); + pa_xfree(handler); +} + +/* Send a message to an object identified by object_path */ +int pa_message_handler_send_message(pa_core *c, const char *object_path, const char *message, const char *message_parameters, char **response) { + struct pa_message_handler *handler; + + pa_assert(c); + pa_assert(object_path); + pa_assert(message); + pa_assert(response); + + *response = NULL; + + if (!(handler = pa_hashmap_get(c->message_handlers, object_path))) + return -PA_ERR_NOENTITY; + + /* The handler is expected to return an error code and may also + return an error string in response */ + return handler->callback(handler->object_path, message, message_parameters, response, handler->userdata); +} + +/* Set handler description */ +int pa_message_handler_set_description(pa_core *c, const char *object_path, const char *description) { + struct pa_message_handler *handler; + + pa_assert(c); + pa_assert(object_path); + + if (!(handler = pa_hashmap_get(c->message_handlers, object_path))) + return -PA_ERR_NOENTITY; + + pa_xfree(handler->description); + handler->description = pa_xstrdup(description); + + return PA_OK; +} diff --git a/src/pulsecore/message-handler.h b/src/pulsecore/message-handler.h new file mode 100644 index 0000000..be94510 --- /dev/null +++ b/src/pulsecore/message-handler.h @@ -0,0 +1,50 @@ +#ifndef foocoremessageshfoo +#define foocoremessageshfoo + +/*** + This file is part of PulseAudio. + + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulsecore/core.h> + +/* Message handler types and functions */ + +/* Prototype for message callback */ +typedef int (*pa_message_handler_cb_t)( + const char *object_path, + const char *message, + const char *message_parameters, + char **response, + void *userdata); + +/* Message handler object */ +struct pa_message_handler { + char *object_path; + char *description; + pa_message_handler_cb_t callback; + void *userdata; +}; + +/* Handler registration */ +void pa_message_handler_register(pa_core *c, const char *object_path, const char *description, pa_message_handler_cb_t cb, void *userdata); +void pa_message_handler_unregister(pa_core *c, const char *object_path); + +/* Send message to the specified object path */ +int pa_message_handler_send_message(pa_core *c, const char *object_path, const char *message, const char *message_parameters, char **response); + +/* Set handler description */ +int pa_message_handler_set_description(pa_core *c, const char *object_path, const char *description); +#endif diff --git a/src/pulsecore/mime-type.c b/src/pulsecore/mime-type.c new file mode 100644 index 0000000..b7157b4 --- /dev/null +++ b/src/pulsecore/mime-type.c @@ -0,0 +1,179 @@ +/*** + This file is part of PulseAudio. + + Copyright 2005-2009 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/xmalloc.h> +#include <pulsecore/core-util.h> + +#include "mime-type.h" + +bool pa_sample_spec_is_mime(const pa_sample_spec *ss, const pa_channel_map *cm) { + + pa_assert(pa_channel_map_compatible(cm, ss)); + + switch (ss->format) { + case PA_SAMPLE_S16BE: + case PA_SAMPLE_S24BE: + case PA_SAMPLE_U8: + + if (ss->rate != 8000 && + ss->rate != 11025 && + ss->rate != 16000 && + ss->rate != 22050 && + ss->rate != 24000 && + ss->rate != 32000 && + ss->rate != 44100 && + ss->rate != 48000) + return false; + + if (ss->channels != 1 && + ss->channels != 2) + return false; + + if ((cm->channels == 1 && cm->map[0] != PA_CHANNEL_POSITION_MONO) || + (cm->channels == 2 && (cm->map[0] != PA_CHANNEL_POSITION_LEFT || cm->map[1] != PA_CHANNEL_POSITION_RIGHT))) + return false; + + return true; + + case PA_SAMPLE_ULAW: + + if (ss->rate != 8000) + return false; + + if (ss->channels != 1) + return false; + + if (cm->map[0] != PA_CHANNEL_POSITION_MONO) + return false; + + return true; + + default: + return false; + } +} + +void pa_sample_spec_mimefy(pa_sample_spec *ss, pa_channel_map *cm) { + + pa_assert(pa_channel_map_compatible(cm, ss)); + + /* Turns the sample type passed in into the next 'better' one that + * can be encoded for HTTP. If there is no 'better' one we pick + * the 'best' one that is 'worse'. */ + + if (ss->channels > 2) + ss->channels = 2; + + if (ss->rate > 44100) + ss->rate = 48000; + else if (ss->rate > 32000) + ss->rate = 44100; + else if (ss->rate > 24000) + ss->rate = 32000; + else if (ss->rate > 22050) + ss->rate = 24000; + else if (ss->rate > 16000) + ss->rate = 22050; + else if (ss->rate > 11025) + ss->rate = 16000; + else if (ss->rate > 8000) + ss->rate = 11025; + else + ss->rate = 8000; + + switch (ss->format) { + case PA_SAMPLE_S24BE: + case PA_SAMPLE_S24LE: + case PA_SAMPLE_S24_32LE: + case PA_SAMPLE_S24_32BE: + case PA_SAMPLE_S32LE: + case PA_SAMPLE_S32BE: + case PA_SAMPLE_FLOAT32LE: + case PA_SAMPLE_FLOAT32BE: + ss->format = PA_SAMPLE_S24BE; + break; + + case PA_SAMPLE_S16BE: + case PA_SAMPLE_S16LE: + ss->format = PA_SAMPLE_S16BE; + break; + + case PA_SAMPLE_ULAW: + case PA_SAMPLE_ALAW: + + if (ss->rate == 8000 && ss->channels == 1) + ss->format = PA_SAMPLE_ULAW; + else + ss->format = PA_SAMPLE_S16BE; + break; + + case PA_SAMPLE_U8: + ss->format = PA_SAMPLE_U8; + break; + + case PA_SAMPLE_MAX: + case PA_SAMPLE_INVALID: + pa_assert_not_reached(); + } + + pa_channel_map_init_auto(cm, ss->channels, PA_CHANNEL_MAP_DEFAULT); + + pa_assert(pa_sample_spec_is_mime(ss, cm)); +} + +char *pa_sample_spec_to_mime_type(const pa_sample_spec *ss, const pa_channel_map *cm) { + pa_assert(pa_channel_map_compatible(cm, ss)); + pa_assert(pa_sample_spec_valid(ss)); + + if (!pa_sample_spec_is_mime(ss, cm)) + return NULL; + + switch (ss->format) { + + case PA_SAMPLE_S16BE: + case PA_SAMPLE_S24BE: + case PA_SAMPLE_U8: + /* Stupid UPnP implementations (PS3...) choke on spaces in + * the mime type, that's why we write only ';' here, + * instead of '; '. */ + return pa_sprintf_malloc("audio/%s;rate=%u;channels=%u", + ss->format == PA_SAMPLE_S16BE ? "L16" : + (ss->format == PA_SAMPLE_S24BE ? "L24" : "L8"), + ss->rate, ss->channels); + + case PA_SAMPLE_ULAW: + return pa_xstrdup("audio/basic"); + + default: + pa_assert_not_reached(); + } +} + +char *pa_sample_spec_to_mime_type_mimefy(const pa_sample_spec *_ss, const pa_channel_map *_cm) { + pa_sample_spec ss = *_ss; + pa_channel_map cm = *_cm; + + pa_sample_spec_mimefy(&ss, &cm); + + return pa_sample_spec_to_mime_type(&ss, &cm); +} diff --git a/src/pulsecore/mime-type.h b/src/pulsecore/mime-type.h new file mode 100644 index 0000000..f07f455 --- /dev/null +++ b/src/pulsecore/mime-type.h @@ -0,0 +1,31 @@ +#ifndef foopulsecoremimetypehfoo +#define foopulsecoremimetypehfoo +/*** + This file is part of PulseAudio. + + Copyright 2009 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulsecore/macro.h> +#include <pulse/sample.h> +#include <pulse/channelmap.h> + +bool pa_sample_spec_is_mime(const pa_sample_spec *ss, const pa_channel_map *cm); +void pa_sample_spec_mimefy(pa_sample_spec *ss, pa_channel_map *cm); +char *pa_sample_spec_to_mime_type(const pa_sample_spec *ss, const pa_channel_map *cm); +char *pa_sample_spec_to_mime_type_mimefy(const pa_sample_spec *_ss, const pa_channel_map *_cm); + +#endif diff --git a/src/pulsecore/mix.c b/src/pulsecore/mix.c new file mode 100644 index 0000000..59622d7 --- /dev/null +++ b/src/pulsecore/mix.c @@ -0,0 +1,726 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + Copyright 2013 Peter Meerwald <pmeerw@pmeerw.net> + + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <math.h> + +#include <pulsecore/sample-util.h> +#include <pulsecore/macro.h> +#include <pulsecore/g711.h> +#include <pulsecore/endianmacros.h> + +#include "cpu.h" +#include "mix.h" + +#define VOLUME_PADDING 32 + +static void calc_linear_integer_volume(int32_t linear[], const pa_cvolume *volume) { + unsigned channel, nchannels, padding; + + pa_assert(linear); + pa_assert(volume); + + nchannels = volume->channels; + + for (channel = 0; channel < nchannels; channel++) + linear[channel] = (int32_t) lrint(pa_sw_volume_to_linear(volume->values[channel]) * 0x10000); + + for (padding = 0; padding < VOLUME_PADDING; padding++, channel++) + linear[channel] = linear[padding]; +} + +static void calc_linear_float_volume(float linear[], const pa_cvolume *volume) { + unsigned channel, nchannels, padding; + + pa_assert(linear); + pa_assert(volume); + + nchannels = volume->channels; + + for (channel = 0; channel < nchannels; channel++) + linear[channel] = (float) pa_sw_volume_to_linear(volume->values[channel]); + + for (padding = 0; padding < VOLUME_PADDING; padding++, channel++) + linear[channel] = linear[padding]; +} + +static void calc_linear_integer_stream_volumes(pa_mix_info streams[], unsigned nstreams, const pa_cvolume *volume, const pa_sample_spec *spec) { + unsigned k, channel; + float linear[PA_CHANNELS_MAX + VOLUME_PADDING]; + + pa_assert(streams); + pa_assert(spec); + pa_assert(volume); + + calc_linear_float_volume(linear, volume); + + for (k = 0; k < nstreams; k++) { + + for (channel = 0; channel < spec->channels; channel++) { + pa_mix_info *m = streams + k; + m->linear[channel].i = (int32_t) lrint(pa_sw_volume_to_linear(m->volume.values[channel]) * linear[channel] * 0x10000); + } + } +} + +static void calc_linear_float_stream_volumes(pa_mix_info streams[], unsigned nstreams, const pa_cvolume *volume, const pa_sample_spec *spec) { + unsigned k, channel; + float linear[PA_CHANNELS_MAX + VOLUME_PADDING]; + + pa_assert(streams); + pa_assert(spec); + pa_assert(volume); + + calc_linear_float_volume(linear, volume); + + for (k = 0; k < nstreams; k++) { + + for (channel = 0; channel < spec->channels; channel++) { + pa_mix_info *m = streams + k; + m->linear[channel].f = (float) (pa_sw_volume_to_linear(m->volume.values[channel]) * linear[channel]); + } + } +} + +typedef void (*pa_calc_stream_volumes_func_t) (pa_mix_info streams[], unsigned nstreams, const pa_cvolume *volume, const pa_sample_spec *spec); + +static const pa_calc_stream_volumes_func_t calc_stream_volumes_table[] = { + [PA_SAMPLE_U8] = (pa_calc_stream_volumes_func_t) calc_linear_integer_stream_volumes, + [PA_SAMPLE_ALAW] = (pa_calc_stream_volumes_func_t) calc_linear_integer_stream_volumes, + [PA_SAMPLE_ULAW] = (pa_calc_stream_volumes_func_t) calc_linear_integer_stream_volumes, + [PA_SAMPLE_S16LE] = (pa_calc_stream_volumes_func_t) calc_linear_integer_stream_volumes, + [PA_SAMPLE_S16BE] = (pa_calc_stream_volumes_func_t) calc_linear_integer_stream_volumes, + [PA_SAMPLE_FLOAT32LE] = (pa_calc_stream_volumes_func_t) calc_linear_float_stream_volumes, + [PA_SAMPLE_FLOAT32BE] = (pa_calc_stream_volumes_func_t) calc_linear_float_stream_volumes, + [PA_SAMPLE_S32LE] = (pa_calc_stream_volumes_func_t) calc_linear_integer_stream_volumes, + [PA_SAMPLE_S32BE] = (pa_calc_stream_volumes_func_t) calc_linear_integer_stream_volumes, + [PA_SAMPLE_S24LE] = (pa_calc_stream_volumes_func_t) calc_linear_integer_stream_volumes, + [PA_SAMPLE_S24BE] = (pa_calc_stream_volumes_func_t) calc_linear_integer_stream_volumes, + [PA_SAMPLE_S24_32LE] = (pa_calc_stream_volumes_func_t) calc_linear_integer_stream_volumes, + [PA_SAMPLE_S24_32BE] = (pa_calc_stream_volumes_func_t) calc_linear_integer_stream_volumes +}; + +/* special case: mix 2 s16ne streams, 1 channel each */ +static void pa_mix2_ch1_s16ne(pa_mix_info streams[], int16_t *data, unsigned length) { + const int16_t *ptr0 = streams[0].ptr; + const int16_t *ptr1 = streams[1].ptr; + + const int32_t cv0 = streams[0].linear[0].i; + const int32_t cv1 = streams[1].linear[0].i; + + length /= sizeof(int16_t); + + for (; length > 0; length--) { + int32_t sum; + + sum = pa_mult_s16_volume(*ptr0++, cv0); + sum += pa_mult_s16_volume(*ptr1++, cv1); + + sum = PA_CLAMP_UNLIKELY(sum, -0x8000, 0x7FFF); + *data++ = sum; + } +} + +/* special case: mix 2 s16ne streams, 2 channels each */ +static void pa_mix2_ch2_s16ne(pa_mix_info streams[], int16_t *data, unsigned length) { + const int16_t *ptr0 = streams[0].ptr; + const int16_t *ptr1 = streams[1].ptr; + + length /= sizeof(int16_t) * 2; + + for (; length > 0; length--) { + int32_t sum; + + sum = pa_mult_s16_volume(*ptr0++, streams[0].linear[0].i); + sum += pa_mult_s16_volume(*ptr1++, streams[1].linear[0].i); + + sum = PA_CLAMP_UNLIKELY(sum, -0x8000, 0x7FFF); + *data++ = sum; + + sum = pa_mult_s16_volume(*ptr0++, streams[0].linear[1].i); + sum += pa_mult_s16_volume(*ptr1++, streams[1].linear[1].i); + + sum = PA_CLAMP_UNLIKELY(sum, -0x8000, 0x7FFF); + *data++ = sum; + } +} + +/* special case: mix 2 s16ne streams */ +static void pa_mix2_s16ne(pa_mix_info streams[], unsigned channels, int16_t *data, unsigned length) { + const int16_t *ptr0 = streams[0].ptr; + const int16_t *ptr1 = streams[1].ptr; + unsigned channel = 0; + + length /= sizeof(int16_t); + + for (; length > 0; length--) { + int32_t sum; + + sum = pa_mult_s16_volume(*ptr0++, streams[0].linear[channel].i); + sum += pa_mult_s16_volume(*ptr1++, streams[1].linear[channel].i); + + sum = PA_CLAMP_UNLIKELY(sum, -0x8000, 0x7FFF); + *data++ = sum; + + if (PA_UNLIKELY(++channel >= channels)) + channel = 0; + } +} + +/* special case: mix s16ne streams, 2 channels each */ +static void pa_mix_ch2_s16ne(pa_mix_info streams[], unsigned nstreams, int16_t *data, unsigned length) { + + length /= sizeof(int16_t) * 2; + + for (; length > 0; length--) { + int32_t sum0 = 0, sum1 = 0; + unsigned i; + + for (i = 0; i < nstreams; i++) { + pa_mix_info *m = streams + i; + int32_t cv0 = m->linear[0].i; + int32_t cv1 = m->linear[1].i; + + sum0 += pa_mult_s16_volume(*((int16_t*) m->ptr), cv0); + m->ptr = (uint8_t*) m->ptr + sizeof(int16_t); + + sum1 += pa_mult_s16_volume(*((int16_t*) m->ptr), cv1); + m->ptr = (uint8_t*) m->ptr + sizeof(int16_t); + } + + *data++ = PA_CLAMP_UNLIKELY(sum0, -0x8000, 0x7FFF); + *data++ = PA_CLAMP_UNLIKELY(sum1, -0x8000, 0x7FFF); + } +} + +static void pa_mix_generic_s16ne(pa_mix_info streams[], unsigned nstreams, unsigned channels, int16_t *data, unsigned length) { + unsigned channel = 0; + + length /= sizeof(int16_t); + + for (; length > 0; length--) { + int32_t sum = 0; + unsigned i; + + for (i = 0; i < nstreams; i++) { + pa_mix_info *m = streams + i; + int32_t cv = m->linear[channel].i; + + if (PA_LIKELY(cv > 0)) + sum += pa_mult_s16_volume(*((int16_t*) m->ptr), cv); + m->ptr = (uint8_t*) m->ptr + sizeof(int16_t); + } + + sum = PA_CLAMP_UNLIKELY(sum, -0x8000, 0x7FFF); + *data++ = sum; + + if (PA_UNLIKELY(++channel >= channels)) + channel = 0; + } +} + +static void pa_mix_s16ne_c(pa_mix_info streams[], unsigned nstreams, unsigned channels, int16_t *data, unsigned length) { + if (nstreams == 2 && channels == 1) + pa_mix2_ch1_s16ne(streams, data, length); + else if (nstreams == 2 && channels == 2) + pa_mix2_ch2_s16ne(streams, data, length); + else if (nstreams == 2) + pa_mix2_s16ne(streams, channels, data, length); + else if (channels == 2) + pa_mix_ch2_s16ne(streams, nstreams, data, length); + else + pa_mix_generic_s16ne(streams, nstreams, channels, data, length); +} + +static void pa_mix_s16re_c(pa_mix_info streams[], unsigned nstreams, unsigned channels, int16_t *data, unsigned length) { + unsigned channel = 0; + + length /= sizeof(int16_t); + + for (; length > 0; length--, data++) { + int32_t sum = 0; + unsigned i; + + for (i = 0; i < nstreams; i++) { + pa_mix_info *m = streams + i; + int32_t cv = m->linear[channel].i; + + if (PA_LIKELY(cv > 0)) + sum += pa_mult_s16_volume(PA_INT16_SWAP(*((int16_t*) m->ptr)), cv); + m->ptr = (uint8_t*) m->ptr + sizeof(int16_t); + } + + sum = PA_CLAMP_UNLIKELY(sum, -0x8000, 0x7FFF); + *data = PA_INT16_SWAP((int16_t) sum); + + if (PA_UNLIKELY(++channel >= channels)) + channel = 0; + } +} + +static void pa_mix_s32ne_c(pa_mix_info streams[], unsigned nstreams, unsigned channels, int32_t *data, unsigned length) { + unsigned channel = 0; + + length /= sizeof(int32_t); + + for (; length > 0; length--, data++) { + int64_t sum = 0; + unsigned i; + + for (i = 0; i < nstreams; i++) { + pa_mix_info *m = streams + i; + int32_t cv = m->linear[channel].i; + int64_t v; + + if (PA_LIKELY(cv > 0)) { + v = *((int32_t*) m->ptr); + v = (v * cv) >> 16; + sum += v; + } + m->ptr = (uint8_t*) m->ptr + sizeof(int32_t); + } + + sum = PA_CLAMP_UNLIKELY(sum, -0x80000000LL, 0x7FFFFFFFLL); + *data = (int32_t) sum; + + if (PA_UNLIKELY(++channel >= channels)) + channel = 0; + } +} + +static void pa_mix_s32re_c(pa_mix_info streams[], unsigned nstreams, unsigned channels, int32_t *data, unsigned length) { + unsigned channel = 0; + + length /= sizeof(int32_t); + + for (; length > 0; length--, data++) { + int64_t sum = 0; + unsigned i; + + for (i = 0; i < nstreams; i++) { + pa_mix_info *m = streams + i; + int32_t cv = m->linear[channel].i; + int64_t v; + + if (PA_LIKELY(cv > 0)) { + v = PA_INT32_SWAP(*((int32_t*) m->ptr)); + v = (v * cv) >> 16; + sum += v; + } + m->ptr = (uint8_t*) m->ptr + sizeof(int32_t); + } + + sum = PA_CLAMP_UNLIKELY(sum, -0x80000000LL, 0x7FFFFFFFLL); + *data = PA_INT32_SWAP((int32_t) sum); + + if (PA_UNLIKELY(++channel >= channels)) + channel = 0; + } +} + +static void pa_mix_s24ne_c(pa_mix_info streams[], unsigned nstreams, unsigned channels, uint8_t *data, unsigned length) { + unsigned channel = 0; + + for (; length > 0; length -= 3, data += 3) { + int64_t sum = 0; + unsigned i; + + for (i = 0; i < nstreams; i++) { + pa_mix_info *m = streams + i; + int32_t cv = m->linear[channel].i; + int64_t v; + + if (PA_LIKELY(cv > 0)) { + v = (int32_t) (PA_READ24NE(m->ptr) << 8); + v = (v * cv) >> 16; + sum += v; + } + m->ptr = (uint8_t*) m->ptr + 3; + } + + sum = PA_CLAMP_UNLIKELY(sum, -0x80000000LL, 0x7FFFFFFFLL); + PA_WRITE24NE(data, ((uint32_t) sum) >> 8); + + if (PA_UNLIKELY(++channel >= channels)) + channel = 0; + } +} + +static void pa_mix_s24re_c(pa_mix_info streams[], unsigned nstreams, unsigned channels, uint8_t *data, unsigned length) { + unsigned channel = 0; + + for (; length > 0; length -= 3, data += 3) { + int64_t sum = 0; + unsigned i; + + for (i = 0; i < nstreams; i++) { + pa_mix_info *m = streams + i; + int32_t cv = m->linear[channel].i; + int64_t v; + + if (PA_LIKELY(cv > 0)) { + v = (int32_t) (PA_READ24RE(m->ptr) << 8); + v = (v * cv) >> 16; + sum += v; + } + m->ptr = (uint8_t*) m->ptr + 3; + } + + sum = PA_CLAMP_UNLIKELY(sum, -0x80000000LL, 0x7FFFFFFFLL); + PA_WRITE24RE(data, ((uint32_t) sum) >> 8); + + if (PA_UNLIKELY(++channel >= channels)) + channel = 0; + } +} + +static void pa_mix_s24_32ne_c(pa_mix_info streams[], unsigned nstreams, unsigned channels, uint32_t *data, unsigned length) { + unsigned channel = 0; + + length /= sizeof(uint32_t); + + for (; length > 0; length--, data++) { + int64_t sum = 0; + unsigned i; + + for (i = 0; i < nstreams; i++) { + pa_mix_info *m = streams + i; + int32_t cv = m->linear[channel].i; + int64_t v; + + if (PA_LIKELY(cv > 0)) { + v = (int32_t) (*((uint32_t*)m->ptr) << 8); + v = (v * cv) >> 16; + sum += v; + } + m->ptr = (uint8_t*) m->ptr + sizeof(int32_t); + } + + sum = PA_CLAMP_UNLIKELY(sum, -0x80000000LL, 0x7FFFFFFFLL); + *data = ((uint32_t) (int32_t) sum) >> 8; + + if (PA_UNLIKELY(++channel >= channels)) + channel = 0; + } +} + +static void pa_mix_s24_32re_c(pa_mix_info streams[], unsigned nstreams, unsigned channels, uint32_t *data, unsigned length) { + unsigned channel = 0; + + length /= sizeof(uint32_t); + + for (; length > 0; length--, data++) { + int64_t sum = 0; + unsigned i; + + for (i = 0; i < nstreams; i++) { + pa_mix_info *m = streams + i; + int32_t cv = m->linear[channel].i; + int64_t v; + + if (PA_LIKELY(cv > 0)) { + v = (int32_t) (PA_UINT32_SWAP(*((uint32_t*) m->ptr)) << 8); + v = (v * cv) >> 16; + sum += v; + } + m->ptr = (uint8_t*) m->ptr + sizeof(int32_t); + } + + sum = PA_CLAMP_UNLIKELY(sum, -0x80000000LL, 0x7FFFFFFFLL); + *data = PA_INT32_SWAP(((uint32_t) (int32_t) sum) >> 8); + + if (PA_UNLIKELY(++channel >= channels)) + channel = 0; + } +} + +static void pa_mix_u8_c(pa_mix_info streams[], unsigned nstreams, unsigned channels, uint8_t *data, unsigned length) { + unsigned channel = 0; + + length /= sizeof(uint8_t); + + for (; length > 0; length--, data++) { + int32_t sum = 0; + unsigned i; + + for (i = 0; i < nstreams; i++) { + pa_mix_info *m = streams + i; + int32_t v, cv = m->linear[channel].i; + + if (PA_LIKELY(cv > 0)) { + v = (int32_t) *((uint8_t*) m->ptr) - 0x80; + v = (v * cv) >> 16; + sum += v; + } + m->ptr = (uint8_t*) m->ptr + 1; + } + + sum = PA_CLAMP_UNLIKELY(sum, -0x80, 0x7F); + *data = (uint8_t) (sum + 0x80); + + if (PA_UNLIKELY(++channel >= channels)) + channel = 0; + } +} + +static void pa_mix_ulaw_c(pa_mix_info streams[], unsigned nstreams, unsigned channels, uint8_t *data, unsigned length) { + unsigned channel = 0; + + length /= sizeof(uint8_t); + + for (; length > 0; length--, data++) { + int32_t sum = 0; + unsigned i; + + for (i = 0; i < nstreams; i++) { + pa_mix_info *m = streams + i; + int32_t cv = m->linear[channel].i; + + if (PA_LIKELY(cv > 0)) + sum += pa_mult_s16_volume(st_ulaw2linear16(*((uint8_t*) m->ptr)), cv); + m->ptr = (uint8_t*) m->ptr + 1; + } + + sum = PA_CLAMP_UNLIKELY(sum, -0x8000, 0x7FFF); + *data = (uint8_t) st_14linear2ulaw((int16_t) sum >> 2); + + if (PA_UNLIKELY(++channel >= channels)) + channel = 0; + } +} + +static void pa_mix_alaw_c(pa_mix_info streams[], unsigned nstreams, unsigned channels, uint8_t *data, unsigned length) { + unsigned channel = 0; + + length /= sizeof(uint8_t); + + for (; length > 0; length--, data++) { + int32_t sum = 0; + unsigned i; + + for (i = 0; i < nstreams; i++) { + pa_mix_info *m = streams + i; + int32_t cv = m->linear[channel].i; + + if (PA_LIKELY(cv > 0)) + sum += pa_mult_s16_volume(st_alaw2linear16(*((uint8_t*) m->ptr)), cv); + m->ptr = (uint8_t*) m->ptr + 1; + } + + sum = PA_CLAMP_UNLIKELY(sum, -0x8000, 0x7FFF); + *data = (uint8_t) st_13linear2alaw((int16_t) sum >> 3); + + if (PA_UNLIKELY(++channel >= channels)) + channel = 0; + } +} + +static void pa_mix_float32ne_c(pa_mix_info streams[], unsigned nstreams, unsigned channels, float *data, unsigned length) { + unsigned channel = 0; + + length /= sizeof(float); + + for (; length > 0; length--, data++) { + float sum = 0; + unsigned i; + + for (i = 0; i < nstreams; i++) { + pa_mix_info *m = streams + i; + float v, cv = m->linear[channel].f; + + if (PA_LIKELY(cv > 0)) { + v = *((float*) m->ptr); + v *= cv; + sum += v; + } + m->ptr = (uint8_t*) m->ptr + sizeof(float); + } + + *data = sum; + + if (PA_UNLIKELY(++channel >= channels)) + channel = 0; + } +} + +static void pa_mix_float32re_c(pa_mix_info streams[], unsigned nstreams, unsigned channels, float *data, unsigned length) { + unsigned channel = 0; + + length /= sizeof(float); + + for (; length > 0; length--, data++) { + float sum = 0; + unsigned i; + + for (i = 0; i < nstreams; i++) { + pa_mix_info *m = streams + i; + float cv = m->linear[channel].f; + + if (PA_LIKELY(cv > 0)) + sum += PA_READ_FLOAT32RE(m->ptr) * cv; + m->ptr = (uint8_t*) m->ptr + sizeof(float); + } + + PA_WRITE_FLOAT32RE(data, sum); + + if (PA_UNLIKELY(++channel >= channels)) + channel = 0; + } +} + +static pa_do_mix_func_t do_mix_table[] = { + [PA_SAMPLE_U8] = (pa_do_mix_func_t) pa_mix_u8_c, + [PA_SAMPLE_ALAW] = (pa_do_mix_func_t) pa_mix_alaw_c, + [PA_SAMPLE_ULAW] = (pa_do_mix_func_t) pa_mix_ulaw_c, + [PA_SAMPLE_S16NE] = (pa_do_mix_func_t) pa_mix_s16ne_c, + [PA_SAMPLE_S16RE] = (pa_do_mix_func_t) pa_mix_s16re_c, + [PA_SAMPLE_FLOAT32NE] = (pa_do_mix_func_t) pa_mix_float32ne_c, + [PA_SAMPLE_FLOAT32RE] = (pa_do_mix_func_t) pa_mix_float32re_c, + [PA_SAMPLE_S32NE] = (pa_do_mix_func_t) pa_mix_s32ne_c, + [PA_SAMPLE_S32RE] = (pa_do_mix_func_t) pa_mix_s32re_c, + [PA_SAMPLE_S24NE] = (pa_do_mix_func_t) pa_mix_s24ne_c, + [PA_SAMPLE_S24RE] = (pa_do_mix_func_t) pa_mix_s24re_c, + [PA_SAMPLE_S24_32NE] = (pa_do_mix_func_t) pa_mix_s24_32ne_c, + [PA_SAMPLE_S24_32RE] = (pa_do_mix_func_t) pa_mix_s24_32re_c +}; + +void pa_mix_func_init(const pa_cpu_info *cpu_info) { + if (cpu_info->force_generic_code) + do_mix_table[PA_SAMPLE_S16NE] = (pa_do_mix_func_t) pa_mix_generic_s16ne; + else + do_mix_table[PA_SAMPLE_S16NE] = (pa_do_mix_func_t) pa_mix_s16ne_c; +} + +size_t pa_mix( + pa_mix_info streams[], + unsigned nstreams, + void *data, + size_t length, + const pa_sample_spec *spec, + const pa_cvolume *volume, + bool mute) { + + pa_cvolume full_volume; + unsigned k; + + pa_assert(streams); + pa_assert(data); + pa_assert(length); + pa_assert(spec); + pa_assert(nstreams > 1); + + if (!volume) + volume = pa_cvolume_reset(&full_volume, spec->channels); + + if (mute || pa_cvolume_is_muted(volume)) { + pa_silence_memory(data, length, spec); + return length; + } + + for (k = 0; k < nstreams; k++) { + pa_assert(length <= streams[k].chunk.length); + streams[k].ptr = pa_memblock_acquire_chunk(&streams[k].chunk); + } + + calc_stream_volumes_table[spec->format](streams, nstreams, volume, spec); + do_mix_table[spec->format](streams, nstreams, spec->channels, data, length); + + for (k = 0; k < nstreams; k++) + pa_memblock_release(streams[k].chunk.memblock); + + return length; +} + +pa_do_mix_func_t pa_get_mix_func(pa_sample_format_t f) { + pa_assert(pa_sample_format_valid(f)); + + return do_mix_table[f]; +} + +void pa_set_mix_func(pa_sample_format_t f, pa_do_mix_func_t func) { + pa_assert(pa_sample_format_valid(f)); + + do_mix_table[f] = func; +} + +typedef union { + float f; + uint32_t i; +} volume_val; + +typedef void (*pa_calc_volume_func_t) (void *volumes, const pa_cvolume *volume); + +static const pa_calc_volume_func_t calc_volume_table[] = { + [PA_SAMPLE_U8] = (pa_calc_volume_func_t) calc_linear_integer_volume, + [PA_SAMPLE_ALAW] = (pa_calc_volume_func_t) calc_linear_integer_volume, + [PA_SAMPLE_ULAW] = (pa_calc_volume_func_t) calc_linear_integer_volume, + [PA_SAMPLE_S16LE] = (pa_calc_volume_func_t) calc_linear_integer_volume, + [PA_SAMPLE_S16BE] = (pa_calc_volume_func_t) calc_linear_integer_volume, + [PA_SAMPLE_FLOAT32LE] = (pa_calc_volume_func_t) calc_linear_float_volume, + [PA_SAMPLE_FLOAT32BE] = (pa_calc_volume_func_t) calc_linear_float_volume, + [PA_SAMPLE_S32LE] = (pa_calc_volume_func_t) calc_linear_integer_volume, + [PA_SAMPLE_S32BE] = (pa_calc_volume_func_t) calc_linear_integer_volume, + [PA_SAMPLE_S24LE] = (pa_calc_volume_func_t) calc_linear_integer_volume, + [PA_SAMPLE_S24BE] = (pa_calc_volume_func_t) calc_linear_integer_volume, + [PA_SAMPLE_S24_32LE] = (pa_calc_volume_func_t) calc_linear_integer_volume, + [PA_SAMPLE_S24_32BE] = (pa_calc_volume_func_t) calc_linear_integer_volume +}; + +void pa_volume_memchunk( + pa_memchunk*c, + const pa_sample_spec *spec, + const pa_cvolume *volume) { + + void *ptr; + volume_val linear[PA_CHANNELS_MAX + VOLUME_PADDING]; + pa_do_volume_func_t do_volume; + + pa_assert(c); + pa_assert(spec); + pa_assert(pa_sample_spec_valid(spec)); + pa_assert(pa_frame_aligned(c->length, spec)); + pa_assert(volume); + + if (pa_memblock_is_silence(c->memblock)) + return; + + if (pa_cvolume_is_norm(volume)) + return; + + if (pa_cvolume_is_muted(volume)) { + pa_silence_memchunk(c, spec); + return; + } + + do_volume = pa_get_volume_func(spec->format); + pa_assert(do_volume); + + calc_volume_table[spec->format] ((void *)linear, volume); + + ptr = pa_memblock_acquire_chunk(c); + + do_volume(ptr, (void *)linear, spec->channels, c->length); + + pa_memblock_release(c->memblock); +} diff --git a/src/pulsecore/mix.h b/src/pulsecore/mix.h new file mode 100644 index 0000000..8102bcd --- /dev/null +++ b/src/pulsecore/mix.h @@ -0,0 +1,62 @@ +#ifndef foomixhfoo +#define foomixhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + Copyright 2013 Peter Meerwald <pmeerw@pmeerw.net> + + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulse/sample.h> +#include <pulse/volume.h> +#include <pulsecore/memchunk.h> + +typedef struct pa_mix_info { + pa_memchunk chunk; + pa_cvolume volume; + void *userdata; + + /* The following fields are used internally by pa_mix(), should + * not be initialised by the caller of pa_mix(). */ + void *ptr; + union { + int32_t i; + float f; + } linear[PA_CHANNELS_MAX]; +} pa_mix_info; + +size_t pa_mix( + pa_mix_info channels[], + unsigned nchannels, + void *data, + size_t length, + const pa_sample_spec *spec, + const pa_cvolume *volume, + bool mute); + +typedef void (*pa_do_mix_func_t) (pa_mix_info streams[], unsigned nstreams, unsigned channels, void *data, unsigned length); + +pa_do_mix_func_t pa_get_mix_func(pa_sample_format_t f); +void pa_set_mix_func(pa_sample_format_t f, pa_do_mix_func_t func); + +void pa_volume_memchunk( + pa_memchunk*c, + const pa_sample_spec *spec, + const pa_cvolume *volume); + +#endif diff --git a/src/pulsecore/mix_neon.c b/src/pulsecore/mix_neon.c new file mode 100644 index 0000000..eb02d81 --- /dev/null +++ b/src/pulsecore/mix_neon.c @@ -0,0 +1,223 @@ +/*** + This file is part of PulseAudio. + + Copyright 2013 Peter Meerwald <pmeerw@pmeerw.net> + + 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.1 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. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulsecore/macro.h> +#include <pulsecore/endianmacros.h> +#include <pulsecore/sample-util.h> + +#include "cpu-arm.h" +#include "mix.h" + +#include <arm_neon.h> + +static pa_do_mix_func_t fallback; + +/* special case: mix s16ne streams, 2 channels each */ +static void pa_mix_ch2_s16ne_neon(pa_mix_info streams[], unsigned nstreams, uint8_t *data, unsigned length) { + const unsigned mask = sizeof(int16_t) * 8 - 1; + const uint8_t *end = data + (length & ~mask); + + while (data < end) { + int32x4_t sum0, sum1; + unsigned i; + + __asm__ __volatile__ ( + "veor.s32 %q[sum0], %q[sum0] \n\t" + "veor.s32 %q[sum1], %q[sum1] \n\t" + : [sum0] "=w" (sum0), [sum1] "=w" (sum1) + : + : "cc" /* clobber list */ + ); + + for (i = 0; i < nstreams; i++) { + pa_mix_info *m = streams + i; + int32_t cv0 = m->linear[0].i; + int32_t cv1 = m->linear[1].i; + + __asm__ __volatile__ ( + "vld2.s16 {d0,d2}, [%[ptr]]! \n\t" + "vmov.s32 d4[0], %[cv0] \n\t" + "vmov.s32 d4[1], %[cv1] \n\t" + "vshll.s16 q0, d0, #15 \n\t" + "vshll.s16 q1, d2, #15 \n\t" + "vqdmulh.s32 q0, q0, d4[0] \n\t" + "vqdmulh.s32 q1, q1, d4[1] \n\t" + "vqadd.s32 %q[sum0], %q[sum0], q0 \n\t" + "vqadd.s32 %q[sum1], %q[sum1], q1 \n\t" + : [ptr] "+r" (m->ptr), [sum0] "+w" (sum0), [sum1] "+w" (sum1) + : [cv0] "r" (cv0), [cv1] "r" (cv1) + : "memory", "cc", "q0", "q1", "d4" /* clobber list */ + ); + } + + __asm__ __volatile__ ( + "vqmovn.s32 d0, %q[sum0] \n\t" + "vqmovn.s32 d1, %q[sum1] \n\t" + "vst2.s16 {d0,d1}, [%[data]]! \n\t" + : [data] "+r" (data) + : [sum0] "w" (sum0), [sum1] "w" (sum1) + : "memory", "cc", "q0" /* clobber list */ + ); + } + + fallback(streams, nstreams, 2, data, length & mask); +} + +/* special case: mix 2 s16ne streams, 1 channel each */ +static void pa_mix2_ch1_s16ne_neon(pa_mix_info streams[], int16_t *data, unsigned length) { + const int16_t *ptr0 = streams[0].ptr; + const int16_t *ptr1 = streams[1].ptr; + + int32x4_t sv0, sv1; + __asm__ __volatile__ ( + "vdup.s32 %q[sv0], %[lin0] \n\t" + "vdup.s32 %q[sv1], %[lin1] \n\t" + : [sv0] "=w" (sv0), [sv1] "=w" (sv1) + : [lin0] "r" (streams[0].linear[0]), [lin1] "r" (streams[1].linear[0]) + : /* clobber list */ + ); + + length /= sizeof(int16_t); + for (; length >= 4; length -= 4) { + __asm__ __volatile__ ( + "vld1.s16 d0, [%[ptr0]]! \n\t" + "vld1.s16 d2, [%[ptr1]]! \n\t" + "vshll.s16 q0, d0, #15 \n\t" + "vshll.s16 q1, d2, #15 \n\t" + "vqdmulh.s32 q0, q0, %q[sv0] \n\t" + "vqdmulh.s32 q1, q1, %q[sv1] \n\t" + "vqadd.s32 q0, q0, q1 \n\t" + "vqmovn.s32 d0, q0 \n\t" + "vst1.s16 d0, [%[data]]! \n\t" + : [ptr0] "+r" (ptr0), [ptr1] "+r" (ptr1), [data] "+r" (data) + : [sv0] "w" (sv0), [sv1] "w" (sv1) + : "memory", "cc", "q0", "q1" /* clobber list */ + ); + } + + for (; length > 0; length--) { + int32_t sum = pa_mult_s16_volume(*ptr0++, streams[0].linear[0].i); + sum += pa_mult_s16_volume(*ptr1++, streams[1].linear[0].i); + *data++ = PA_CLAMP_UNLIKELY(sum, -0x8000, 0x7FFF); + } +} + +/* special case: mix 2 s16ne streams, 2 channel each */ +static void pa_mix2_ch2_s16ne_neon(pa_mix_info streams[], int16_t *data, unsigned length) { + const int16_t *ptr0 = streams[0].ptr; + const int16_t *ptr1 = streams[1].ptr; + + int32x4_t sv0, sv1; + __asm__ __volatile__ ( + "vld1.s32 d0, [%[lin0]] \n\t" + "vmov.s32 d1, d0 \n\t" + "vmov.s32 %q[sv0], q0 \n\t" + "vld1.s32 d0, [%[lin1]] \n\t" + "vmov.s32 d1, d0 \n\t" + "vmov.s32 %q[sv1], q0 \n\t" + : [sv0] "=w" (sv0), [sv1] "=w" (sv1) + : [lin0] "r" (streams[0].linear), [lin1] "r" (streams[1].linear) + : "q0" /* clobber list */ + ); + + length /= sizeof(int16_t); + for (; length >= 4; length -= 4) { + __asm__ __volatile__ ( + "vld1.s16 d0, [%[ptr0]]! \n\t" + "vld1.s16 d2, [%[ptr1]]! \n\t" + "vshll.s16 q0, d0, #15 \n\t" + "vshll.s16 q1, d2, #15 \n\t" + "vqdmulh.s32 q0, q0, %q[sv0] \n\t" + "vqdmulh.s32 q1, q1, %q[sv1] \n\t" + "vqadd.s32 q0, q0, q1 \n\t" + "vqmovn.s32 d0, q0 \n\t" + "vst1.s16 d0, [%[data]]! \n\t" + : [ptr0] "+r" (ptr0), [ptr1] "+r" (ptr1), [data] "+r" (data) + : [sv0] "w" (sv0), [sv1] "w" (sv1) + : "memory", "cc", "q0", "q1" /* clobber list */ + ); + } + + if (length > 0) { + int32_t sum; + + sum = pa_mult_s16_volume(*ptr0++, streams[0].linear[0].i); + sum += pa_mult_s16_volume(*ptr1++, streams[1].linear[0].i); + *data++ = PA_CLAMP_UNLIKELY(sum, -0x8000, 0x7FFF); + + sum = pa_mult_s16_volume(*ptr0++, streams[0].linear[1].i); + sum += pa_mult_s16_volume(*ptr1++, streams[1].linear[1].i); + *data++ = PA_CLAMP_UNLIKELY(sum, -0x8000, 0x7FFF); + } +} + +/* special case: mix 2 s16ne streams, 4 channels each */ +static void pa_mix2_ch4_s16ne_neon(pa_mix_info streams[], int16_t *data, unsigned length) { + const int16_t *ptr0 = streams[0].ptr; + const int16_t *ptr1 = streams[1].ptr; + + int32x4_t sv0, sv1; + + __asm__ __volatile__ ( + "vld1.s32 %h[sv0], [%[lin0]] \n\t" + "vld1.s32 %h[sv1], [%[lin1]] \n\t" + : [sv0] "=w" (sv0), [sv1] "=w" (sv1) + : [lin0] "r" (streams[0].linear), [lin1] "r" (streams[1].linear) + : /* clobber list */ + ); + + length /= sizeof(int16_t); + for (; length >= 4; length -= 4) { + __asm__ __volatile__ ( + "vld1.s16 d0, [%[ptr0]]! \n\t" + "vld1.s16 d2, [%[ptr1]]! \n\t" + "vshll.s16 q0, d0, #15 \n\t" + "vshll.s16 q1, d2, #15 \n\t" + "vqdmulh.s32 q0, q0, %q[sv0] \n\t" + "vqdmulh.s32 q1, q1, %q[sv1] \n\t" + "vqadd.s32 q0, q0, q1 \n\t" + "vqmovn.s32 d0, q0 \n\t" + "vst1.s16 d0, [%[data]]! \n\t" + : [ptr0] "+r" (ptr0), [ptr1] "+r" (ptr1), [data] "+r" (data) + : [sv0] "w" (sv0), [sv1] "w" (sv1) + : "memory", "cc", "q0", "q1" /* clobber list */ + ); + } +} + +static void pa_mix_s16ne_neon(pa_mix_info streams[], unsigned nstreams, unsigned nchannels, void *data, unsigned length) { + if (nstreams == 2 && nchannels == 2) + pa_mix2_ch2_s16ne_neon(streams, data, length); + else if (nstreams == 2 && nchannels == 4) + pa_mix2_ch4_s16ne_neon(streams, data, length); + else if (nstreams == 2 && nchannels == 1) + pa_mix2_ch1_s16ne_neon(streams, data, length); + else if (nchannels == 2) + pa_mix_ch2_s16ne_neon(streams, nstreams, data, length); + else + fallback(streams, nstreams, nchannels, data, length); +} + +void pa_mix_func_init_neon(pa_cpu_arm_flag_t flags) { + pa_log_info("Initialising ARM NEON optimized mixing functions."); + + fallback = pa_get_mix_func(PA_SAMPLE_S16NE); + pa_set_mix_func(PA_SAMPLE_S16NE, (pa_do_mix_func_t) pa_mix_s16ne_neon); +} diff --git a/src/pulsecore/modargs.c b/src/pulsecore/modargs.c new file mode 100644 index 0000000..bce5891 --- /dev/null +++ b/src/pulsecore/modargs.c @@ -0,0 +1,546 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <ctype.h> +#include <stdlib.h> +#include <string.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/hashmap.h> +#include <pulsecore/idxset.h> +#include <pulsecore/core-util.h> +#include <pulsecore/macro.h> + +#include "modargs.h" + +struct pa_modargs { + pa_hashmap *raw; + pa_hashmap *unescaped; +}; + +struct entry { + char *key, *value; +}; + +static int add_key_value(pa_modargs *ma, char *key, char *value, const char* const valid_keys[], bool ignore_dupes) { + struct entry *e; + char *raw; + + pa_assert(ma); + pa_assert(ma->raw); + pa_assert(ma->unescaped); + pa_assert(key); + pa_assert(value); + + if (pa_hashmap_get(ma->unescaped, key)) { + pa_xfree(key); + pa_xfree(value); + + if (ignore_dupes) + return 0; + else + return -1; + } + + if (valid_keys) { + const char*const* v; + for (v = valid_keys; *v; v++) + if (pa_streq(*v, key)) + break; + + if (!*v) { + pa_xfree(key); + pa_xfree(value); + return -1; + } + } + + raw = pa_xstrdup(value); + + e = pa_xnew(struct entry, 1); + e->key = key; + e->value = pa_unescape(value); + pa_hashmap_put(ma->unescaped, key, e); + + if (pa_streq(raw, value)) + pa_xfree(raw); + else { + e = pa_xnew(struct entry, 1); + e->key = pa_xstrdup(key); + e->value = raw; + pa_hashmap_put(ma->raw, key, e); + } + + return 0; +} + +static void free_func(void *p) { + struct entry *e = p; + pa_assert(e); + + pa_xfree(e->key); + pa_xfree(e->value); + pa_xfree(e); +} + +static int parse(pa_modargs *ma, const char *args, const char* const* valid_keys, bool ignore_dupes) { + enum { + WHITESPACE, + KEY, + VALUE_START, + VALUE_SIMPLE, + VALUE_SIMPLE_ESCAPED, + VALUE_DOUBLE_QUOTES, + VALUE_DOUBLE_QUOTES_ESCAPED, + VALUE_TICKS, + VALUE_TICKS_ESCAPED + } state; + + const char *p, *key = NULL, *value = NULL; + size_t key_len = 0, value_len = 0; + + state = WHITESPACE; + + for (p = args; *p; p++) { + switch (state) { + + case WHITESPACE: + if (*p == '=') + goto fail; + else if (!isspace((unsigned char)*p)) { + key = p; + state = KEY; + key_len = 1; + } + break; + + case KEY: + if (*p == '=') + state = VALUE_START; + else if (isspace((unsigned char)*p)) + goto fail; + else + key_len++; + break; + + case VALUE_START: + if (*p == '\'') { + state = VALUE_TICKS; + value = p+1; + value_len = 0; + } else if (*p == '"') { + state = VALUE_DOUBLE_QUOTES; + value = p+1; + value_len = 0; + } else if (isspace((unsigned char)*p)) { + if (add_key_value(ma, + pa_xstrndup(key, key_len), + pa_xstrdup(""), + valid_keys, + ignore_dupes) < 0) + goto fail; + state = WHITESPACE; + } else if (*p == '\\') { + state = VALUE_SIMPLE_ESCAPED; + value = p; + value_len = 1; + } else { + state = VALUE_SIMPLE; + value = p; + value_len = 1; + } + break; + + case VALUE_SIMPLE: + if (isspace((unsigned char)*p)) { + if (add_key_value(ma, + pa_xstrndup(key, key_len), + pa_xstrndup(value, value_len), + valid_keys, + ignore_dupes) < 0) + goto fail; + state = WHITESPACE; + } else if (*p == '\\') { + state = VALUE_SIMPLE_ESCAPED; + value_len++; + } else + value_len++; + break; + + case VALUE_SIMPLE_ESCAPED: + state = VALUE_SIMPLE; + value_len++; + break; + + case VALUE_DOUBLE_QUOTES: + if (*p == '"') { + if (add_key_value(ma, + pa_xstrndup(key, key_len), + pa_xstrndup(value, value_len), + valid_keys, + ignore_dupes) < 0) + goto fail; + state = WHITESPACE; + } else if (*p == '\\') { + state = VALUE_DOUBLE_QUOTES_ESCAPED; + value_len++; + } else + value_len++; + break; + + case VALUE_DOUBLE_QUOTES_ESCAPED: + state = VALUE_DOUBLE_QUOTES; + value_len++; + break; + + case VALUE_TICKS: + if (*p == '\'') { + if (add_key_value(ma, + pa_xstrndup(key, key_len), + pa_xstrndup(value, value_len), + valid_keys, + ignore_dupes) < 0) + goto fail; + state = WHITESPACE; + } else if (*p == '\\') { + state = VALUE_TICKS_ESCAPED; + value_len++; + } else + value_len++; + break; + + case VALUE_TICKS_ESCAPED: + state = VALUE_TICKS; + value_len++; + break; + } + } + + if (state == VALUE_START) { + if (add_key_value(ma, pa_xstrndup(key, key_len), pa_xstrdup(""), valid_keys, ignore_dupes) < 0) + goto fail; + } else if (state == VALUE_SIMPLE) { + if (add_key_value(ma, pa_xstrndup(key, key_len), pa_xstrdup(value), valid_keys, ignore_dupes) < 0) + goto fail; + } else if (state != WHITESPACE) + goto fail; + + return 0; + +fail: + return -1; +} + +pa_modargs *pa_modargs_new(const char *args, const char* const* valid_keys) { + pa_modargs *ma = pa_xnew(pa_modargs, 1); + + ma->raw = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, free_func); + ma->unescaped = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, free_func); + + if (args && parse(ma, args, valid_keys, false) < 0) + goto fail; + + return ma; + +fail: + pa_modargs_free(ma); + return NULL; +} + +int pa_modargs_append(pa_modargs *ma, const char *args, const char* const* valid_keys) { + return parse(ma, args, valid_keys, true); +} + +void pa_modargs_free(pa_modargs*ma) { + pa_assert(ma); + + pa_hashmap_free(ma->raw); + pa_hashmap_free(ma->unescaped); + pa_xfree(ma); +} + +const char *pa_modargs_get_value(pa_modargs *ma, const char *key, const char *def) { + struct entry*e; + + pa_assert(ma); + pa_assert(key); + + if (!(e = pa_hashmap_get(ma->unescaped, key))) + return def; + + return e->value; +} + +static const char *modargs_get_value_raw(pa_modargs *ma, const char *key, const char *def) { + struct entry*e; + + pa_assert(ma); + pa_assert(key); + + if (!(e = pa_hashmap_get(ma->raw, key))) + if (!(e = pa_hashmap_get(ma->unescaped, key))) + return def; + + return e->value; +} + +int pa_modargs_get_value_u32(pa_modargs *ma, const char *key, uint32_t *value) { + const char *v; + + pa_assert(value); + + if (!(v = pa_modargs_get_value(ma, key, NULL))) + return 0; + + if (pa_atou(v, value) < 0) + return -1; + + return 0; +} + +int pa_modargs_get_value_s32(pa_modargs *ma, const char *key, int32_t *value) { + const char *v; + + pa_assert(value); + + if (!(v = pa_modargs_get_value(ma, key, NULL))) + return 0; + + if (pa_atoi(v, value) < 0) + return -1; + + return 0; +} + +int pa_modargs_get_value_boolean(pa_modargs *ma, const char *key, bool *value) { + const char *v; + int r; + + pa_assert(value); + + if (!(v = pa_modargs_get_value(ma, key, NULL))) + return 0; + + if (!*v) + return -1; + + if ((r = pa_parse_boolean(v)) < 0) + return -1; + + *value = r; + return 0; +} + +int pa_modargs_get_value_double(pa_modargs *ma, const char *key, double *value) { + const char *v; + + pa_assert(value); + + if (!(v = pa_modargs_get_value(ma, key, NULL))) + return 0; + + if (pa_atod(v, value) < 0) + return -1; + + return 0; +} + +int pa_modargs_get_value_volume(pa_modargs *ma, const char *key, pa_volume_t *value) { + const char *v; + + pa_assert(value); + + if (!(v = pa_modargs_get_value(ma, key, NULL))) + return 0; + + if (pa_parse_volume(v, value) < 0) + return -1; + + return 0; +} + +int pa_modargs_get_sample_rate(pa_modargs *ma, uint32_t *rate) { + uint32_t rate_local; + + pa_assert(rate); + + rate_local = *rate; + if ((pa_modargs_get_value_u32(ma, "rate", &rate_local)) < 0 || + !pa_sample_rate_valid(rate_local)) + return -1; + + *rate = rate_local; + + return 0; +} + +int pa_modargs_get_sample_spec(pa_modargs *ma, pa_sample_spec *rss) { + const char *format; + uint32_t channels; + pa_sample_spec ss; + + pa_assert(rss); + + ss = *rss; + if ((pa_modargs_get_sample_rate(ma, &ss.rate)) < 0) + return -1; + + channels = ss.channels; + if ((pa_modargs_get_value_u32(ma, "channels", &channels)) < 0 || + !pa_channels_valid(channels)) + return -1; + ss.channels = (uint8_t) channels; + + if ((format = pa_modargs_get_value(ma, "format", NULL))) + if ((ss.format = pa_parse_sample_format(format)) < 0) + return -1; + + if (!pa_sample_spec_valid(&ss)) + return -1; + + *rss = ss; + + return 0; +} + +int pa_modargs_get_alternate_sample_rate(pa_modargs *ma, uint32_t *alternate_rate) { + uint32_t rate_local; + + pa_assert(alternate_rate); + + rate_local = *alternate_rate; + if ((pa_modargs_get_value_u32(ma, "alternate_rate", &rate_local)) < 0 || + !pa_sample_rate_valid(*alternate_rate)) + return -1; + + *alternate_rate = rate_local; + + return 0; +} + +int pa_modargs_get_channel_map(pa_modargs *ma, const char *name, pa_channel_map *rmap) { + pa_channel_map map; + const char *cm; + + pa_assert(rmap); + + map = *rmap; + + if ((cm = pa_modargs_get_value(ma, name ? name : "channel_map", NULL))) + if (!pa_channel_map_parse(&map, cm)) + return -1; + + if (!pa_channel_map_valid(&map)) + return -1; + + *rmap = map; + return 0; +} + +int pa_modargs_get_resample_method(pa_modargs *ma, pa_resample_method_t *rmethod) { + const char *m; + + pa_assert(ma); + pa_assert(rmethod); + + if ((m = pa_modargs_get_value(ma, "resample_method", NULL))) { + pa_resample_method_t method = pa_parse_resample_method(m); + + if (method == PA_RESAMPLER_INVALID) + return -1; + + *rmethod = method; + } + + return 0; +} + +int pa_modargs_get_sample_spec_and_channel_map( + pa_modargs *ma, + pa_sample_spec *rss, + pa_channel_map *rmap, + pa_channel_map_def_t def) { + + pa_sample_spec ss; + pa_channel_map map; + + pa_assert(rss); + pa_assert(rmap); + + ss = *rss; + + if (pa_modargs_get_sample_spec(ma, &ss) < 0) + return -1; + + map = *rmap; + + if (ss.channels != map.channels) + pa_channel_map_init_extend(&map, ss.channels, def); + + if (pa_modargs_get_channel_map(ma, NULL, &map) < 0) + return -1; + + if (map.channels != ss.channels) { + if (!pa_modargs_get_value(ma, "channels", NULL)) + ss.channels = map.channels; + else + return -1; + } + + *rmap = map; + *rss = ss; + + return 0; +} + +int pa_modargs_get_proplist(pa_modargs *ma, const char *name, pa_proplist *p, pa_update_mode_t m) { + const char *v; + pa_proplist *n; + + pa_assert(ma); + pa_assert(name); + pa_assert(p); + + if (!(v = modargs_get_value_raw(ma, name, NULL))) + return 0; + + if (!(n = pa_proplist_from_string(v))) + return -1; + + pa_proplist_update(p, m, n); + pa_proplist_free(n); + + return 0; +} + +const char *pa_modargs_iterate(pa_modargs *ma, void **state) { + struct entry *e; + + pa_assert(ma); + + if (!(e = pa_hashmap_iterate(ma->unescaped, state, NULL))) + return NULL; + + return e->key; +} diff --git a/src/pulsecore/modargs.h b/src/pulsecore/modargs.h new file mode 100644 index 0000000..96132a3 --- /dev/null +++ b/src/pulsecore/modargs.h @@ -0,0 +1,98 @@ +#ifndef foomodargshfoo +#define foomodargshfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <inttypes.h> +#include <pulse/sample.h> +#include <pulse/channelmap.h> +#include <pulse/proplist.h> +#include <pulse/volume.h> +#include <pulsecore/macro.h> +#include <pulsecore/resampler.h> + +typedef struct pa_modargs pa_modargs; + +/* A generic parser for module arguments */ + +/* Parse the string args. The NULL-terminated array keys contains all valid arguments. */ +pa_modargs *pa_modargs_new(const char *args, const char* const keys[]); +/* Parse the string args, and add any keys that are not already present. */ +int pa_modargs_append(pa_modargs *ma, const char *args, const char* const* valid_keys); +void pa_modargs_free(pa_modargs*ma); + +/* Return the module argument for the specified name as a string. If + * the argument was not specified, return def instead.*/ +const char *pa_modargs_get_value(pa_modargs *ma, const char *key, const char *def); + +/* Return a module argument as unsigned 32bit value in *value. If the argument + * was not specified, *value remains unchanged. */ +int pa_modargs_get_value_u32(pa_modargs *ma, const char *key, uint32_t *value); +int pa_modargs_get_value_s32(pa_modargs *ma, const char *key, int32_t *value); +int pa_modargs_get_value_boolean(pa_modargs *ma, const char *key, bool *value); + +/* Return a module argument as double value in *value. If the argument was not + * specified, *value remains unchanged. */ +int pa_modargs_get_value_double(pa_modargs *ma, const char *key, double *value); + +/* Return a module argument as pa_volume_t value in *value. If the argument + * was not specified, *value remains unchanged. */ +int pa_modargs_get_value_volume(pa_modargs *ma, const char *key, pa_volume_t *value); + +/* Return sample rate from the "rate" argument. If the argument was not + * specified, *rate remains unchanged. */ +int pa_modargs_get_sample_rate(pa_modargs *ma, uint32_t *rate); + +/* Return sample spec data from the three arguments "rate", "format" and + * "channels". If the argument was not specified, *ss remains unchanged. */ +int pa_modargs_get_sample_spec(pa_modargs *ma, pa_sample_spec *ss); + +/* Return channel map data from the argument "channel_map" if name is NULL, + * otherwise read from the specified argument. If the argument was not + * specified, *map remains unchanged. */ +int pa_modargs_get_channel_map(pa_modargs *ma, const char *name, pa_channel_map *map); + +/* Return resample method from the argument "resample_method". If the argument + * was not specified, *method remains unchanged. */ +int pa_modargs_get_resample_method(pa_modargs *ma, pa_resample_method_t *method); + +/* Combination of pa_modargs_get_sample_spec() and +pa_modargs_get_channel_map(). Not always suitable, since this routine +initializes the map parameter based on the channels field of the ss +structure if no channel_map is found, using pa_channel_map_init_auto() */ + +int pa_modargs_get_sample_spec_and_channel_map(pa_modargs *ma, pa_sample_spec *ss, pa_channel_map *map, pa_channel_map_def_t def); + +/* Return alternate sample rate from "alternate_sample_rate" parameter. If the + * argument was not specified, *alternate_rate remains unchanged. */ +int pa_modargs_get_alternate_sample_rate(pa_modargs *ma, uint32_t *alternate_rate); + +int pa_modargs_get_proplist(pa_modargs *ma, const char *name, pa_proplist *p, pa_update_mode_t m); + +/* Iterate through the module argument list. The user should allocate a + * state variable of type void* and initialize it with NULL. A pointer + * to this variable should then be passed to pa_modargs_iterate() + * which should be called in a loop until it returns NULL which + * signifies EOL. On each invocation this function will return the + * key string for the next entry. The keys in the argument list do not + * have any particular order. */ +const char *pa_modargs_iterate(pa_modargs *ma, void **state); + +#endif diff --git a/src/pulsecore/modinfo.c b/src/pulsecore/modinfo.c new file mode 100644 index 0000000..e1a814f --- /dev/null +++ b/src/pulsecore/modinfo.c @@ -0,0 +1,97 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <ltdl.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/log.h> +#include <pulsecore/macro.h> +#include <pulsecore/ltdl-helper.h> + +#include "modinfo.h" + +#define PA_SYMBOL_AUTHOR "pa__get_author" +#define PA_SYMBOL_DESCRIPTION "pa__get_description" +#define PA_SYMBOL_USAGE "pa__get_usage" +#define PA_SYMBOL_VERSION "pa__get_version" +#define PA_SYMBOL_DEPRECATED "pa__get_deprecated" +#define PA_SYMBOL_LOAD_ONCE "pa__load_once" + +pa_modinfo *pa_modinfo_get_by_handle(lt_dlhandle dl, const char *module_name) { + pa_modinfo *i; + const char* (*func)(void); + bool (*func2) (void); + + pa_assert(dl); + + i = pa_xnew0(pa_modinfo, 1); + + if ((func = (const char* (*)(void)) pa_load_sym(dl, module_name, PA_SYMBOL_AUTHOR))) + i->author = pa_xstrdup(func()); + + if ((func = (const char* (*)(void)) pa_load_sym(dl, module_name, PA_SYMBOL_DESCRIPTION))) + i->description = pa_xstrdup(func()); + + if ((func = (const char* (*)(void)) pa_load_sym(dl, module_name, PA_SYMBOL_USAGE))) + i->usage = pa_xstrdup(func()); + + if ((func = (const char* (*)(void)) pa_load_sym(dl, module_name, PA_SYMBOL_VERSION))) + i->version = pa_xstrdup(func()); + + if ((func = (const char* (*)(void)) pa_load_sym(dl, module_name, PA_SYMBOL_DEPRECATED))) + i->deprecated = pa_xstrdup(func()); + + if ((func2 = (bool (*)(void)) pa_load_sym(dl, module_name, PA_SYMBOL_LOAD_ONCE))) + i->load_once = func2(); + + return i; +} + +pa_modinfo *pa_modinfo_get_by_name(const char *name) { + lt_dlhandle dl; + pa_modinfo *i; + + pa_assert(name); + + if (!(dl = lt_dlopenext(name))) { + pa_log("Failed to open module \"%s\": %s", name, lt_dlerror()); + return NULL; + } + + i = pa_modinfo_get_by_handle(dl, name); + lt_dlclose(dl); + + return i; +} + +void pa_modinfo_free(pa_modinfo *i) { + pa_assert(i); + + pa_xfree(i->author); + pa_xfree(i->description); + pa_xfree(i->usage); + pa_xfree(i->version); + pa_xfree(i->deprecated); + pa_xfree(i); +} diff --git a/src/pulsecore/modinfo.h b/src/pulsecore/modinfo.h new file mode 100644 index 0000000..5b99b77 --- /dev/null +++ b/src/pulsecore/modinfo.h @@ -0,0 +1,44 @@ +#ifndef foomodinfohfoo +#define foomodinfohfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +/* Some functions for reading module meta data from PulseAudio modules */ +#include <pulsecore/macro.h> + +typedef struct pa_modinfo { + char *author; + char *description; + char *usage; + char *version; + char *deprecated; + bool load_once; +} pa_modinfo; + +/* Read meta data from an libtool handle */ +pa_modinfo *pa_modinfo_get_by_handle(lt_dlhandle dl, const char *module_name); + +/* Read meta data from a module file */ +pa_modinfo *pa_modinfo_get_by_name(const char *name); + +/* Free meta data */ +void pa_modinfo_free(pa_modinfo *i); + +#endif diff --git a/src/pulsecore/module.c b/src/pulsecore/module.c new file mode 100644 index 0000000..15a54b6 --- /dev/null +++ b/src/pulsecore/module.c @@ -0,0 +1,415 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <ltdl.h> + +#include <pulse/xmalloc.h> +#include <pulse/proplist.h> + +#include <pulsecore/core-subscribe.h> +#include <pulsecore/log.h> +#include <pulsecore/core-util.h> +#include <pulsecore/macro.h> +#include <pulsecore/ltdl-helper.h> +#include <pulsecore/modinfo.h> + +#include "module.h" + +#define PA_SYMBOL_INIT "pa__init" +#define PA_SYMBOL_DONE "pa__done" +#define PA_SYMBOL_LOAD_ONCE "pa__load_once" +#define PA_SYMBOL_GET_N_USED "pa__get_n_used" +#define PA_SYMBOL_GET_DEPRECATE "pa__get_deprecated" + +bool pa_module_exists(const char *name) { + const char *paths, *state = NULL; + char *n, *p, *pathname; + bool result; + + pa_assert(name); + + if (name[0] == PA_PATH_SEP_CHAR) { + result = access(name, F_OK) == 0 ? true : false; + pa_log_debug("Checking for existence of '%s': %s", name, result ? "success" : "failure"); + if (result) + return true; + } + + if (!(paths = lt_dlgetsearchpath())) + return false; + + /* strip .so from the end of name, if present */ + n = pa_xstrdup(name); + p = strrchr(n, '.'); + if (p && pa_streq(p, PA_SOEXT)) + p[0] = 0; + + while ((p = pa_split(paths, ":", &state))) { + pathname = pa_sprintf_malloc("%s" PA_PATH_SEP "%s" PA_SOEXT, p, n); + result = access(pathname, F_OK) == 0 ? true : false; + pa_log_debug("Checking for existence of '%s': %s", pathname, result ? "success" : "failure"); + pa_xfree(pathname); + pa_xfree(p); + if (result) { + pa_xfree(n); + return true; + } + } + + state = NULL; + if (PA_UNLIKELY(pa_run_from_build_tree())) { + while ((p = pa_split(paths, ":", &state))) { +#ifdef MESON_BUILD + pathname = pa_sprintf_malloc("%s" PA_PATH_SEP "src" PA_PATH_SEP "modules" PA_PATH_SEP "%s" PA_SOEXT, p, n); +#else + pathname = pa_sprintf_malloc("%s" PA_PATH_SEP ".libs" PA_PATH_SEP "%s" PA_SOEXT, p, n); +#endif + result = access(pathname, F_OK) == 0 ? true : false; + pa_log_debug("Checking for existence of '%s': %s", pathname, result ? "success" : "failure"); + pa_xfree(pathname); + pa_xfree(p); + if (result) { + pa_xfree(n); + return true; + } + } + } + + pa_xfree(n); + return false; +} + +void pa_module_hook_connect(pa_module *m, pa_hook *hook, pa_hook_priority_t prio, pa_hook_cb_t cb, void *data) { + pa_assert(m); + pa_assert(hook); + pa_assert(m->hooks); + pa_dynarray_append(m->hooks, pa_hook_connect(hook, prio, cb, data)); +} + +int pa_module_load(pa_module** module, pa_core *c, const char *name, const char *argument) { + pa_module *m = NULL; + bool (*load_once)(void); + const char* (*get_deprecated)(void); + pa_modinfo *mi; + int errcode, rval; + + pa_assert(module); + pa_assert(c); + pa_assert(name); + + if (c->disallow_module_loading) { + errcode = -PA_ERR_ACCESS; + goto fail; + } + + m = pa_xnew(pa_module, 1); + m->name = pa_xstrdup(name); + m->argument = pa_xstrdup(argument); + m->load_once = false; + m->proplist = pa_proplist_new(); + m->hooks = pa_dynarray_new((pa_free_cb_t) pa_hook_slot_free); + m->index = PA_IDXSET_INVALID; + + if (!(m->dl = lt_dlopenext(name))) { + /* We used to print the error that is returned by lt_dlerror(), but + * lt_dlerror() is useless. It returns pretty much always "file not + * found". That's because if there are any problems with loading the + * module with normal loaders, libltdl falls back to the "preload" + * loader, which never finds anything, and therefore says "file not + * found". */ + pa_log("Failed to open module \"%s\".", name); + errcode = -PA_ERR_IO; + goto fail; + } + + if ((load_once = (bool (*)(void)) pa_load_sym(m->dl, name, PA_SYMBOL_LOAD_ONCE))) { + + m->load_once = load_once(); + + if (m->load_once) { + pa_module *i; + uint32_t idx; + /* OK, the module only wants to be loaded once, let's make sure it is */ + + PA_IDXSET_FOREACH(i, c->modules, idx) { + if (pa_streq(name, i->name)) { + pa_log("Module \"%s\" should be loaded once at most. Refusing to load.", name); + errcode = -PA_ERR_EXIST; + goto fail; + } + } + } + } + + if ((get_deprecated = (const char* (*) (void)) pa_load_sym(m->dl, name, PA_SYMBOL_GET_DEPRECATE))) { + const char *t; + + if ((t = get_deprecated())) + pa_log_warn("%s is deprecated: %s", name, t); + } + + if (!(m->init = (int (*)(pa_module*_m)) pa_load_sym(m->dl, name, PA_SYMBOL_INIT))) { + pa_log("Failed to load module \"%s\": symbol \""PA_SYMBOL_INIT"\" not found.", name); + errcode = -PA_ERR_IO; + goto fail; + } + + m->done = (void (*)(pa_module*_m)) pa_load_sym(m->dl, name, PA_SYMBOL_DONE); + m->get_n_used = (int (*)(pa_module*_m)) pa_load_sym(m->dl, name, PA_SYMBOL_GET_N_USED); + m->userdata = NULL; + m->core = c; + m->unload_requested = false; + + pa_assert_se(pa_idxset_put(c->modules, m, &m->index) >= 0); + pa_assert(m->index != PA_IDXSET_INVALID); + + if ((rval = m->init(m)) < 0) { + if (rval == -PA_MODULE_ERR_SKIP) { + errcode = -PA_ERR_NOENTITY; + goto fail; + } + pa_log_error("Failed to load module \"%s\" (argument: \"%s\"): initialization failed.", name, argument ? argument : ""); + errcode = -PA_ERR_IO; + goto fail; + } + + pa_log_info("Loaded \"%s\" (index: #%u; argument: \"%s\").", m->name, m->index, m->argument ? m->argument : ""); + + pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_MODULE|PA_SUBSCRIPTION_EVENT_NEW, m->index); + + if ((mi = pa_modinfo_get_by_handle(m->dl, name))) { + + if (mi->author && !pa_proplist_contains(m->proplist, PA_PROP_MODULE_AUTHOR)) + pa_proplist_sets(m->proplist, PA_PROP_MODULE_AUTHOR, mi->author); + + if (mi->description && !pa_proplist_contains(m->proplist, PA_PROP_MODULE_DESCRIPTION)) + pa_proplist_sets(m->proplist, PA_PROP_MODULE_DESCRIPTION, mi->description); + + if (mi->version && !pa_proplist_contains(m->proplist, PA_PROP_MODULE_VERSION)) + pa_proplist_sets(m->proplist, PA_PROP_MODULE_VERSION, mi->version); + + pa_modinfo_free(mi); + } + + pa_hook_fire(&m->core->hooks[PA_CORE_HOOK_MODULE_NEW], m); + + *module = m; + + return 0; + +fail: + + if (m) { + if (m->index != PA_IDXSET_INVALID) + pa_idxset_remove_by_index(c->modules, m->index); + + if (m->hooks) + pa_dynarray_free(m->hooks); + + if (m->proplist) + pa_proplist_free(m->proplist); + + pa_xfree(m->argument); + pa_xfree(m->name); + + if (m->dl) + lt_dlclose(m->dl); + + pa_xfree(m); + } + + *module = NULL; + + return errcode; +} + +static void postponed_dlclose(pa_mainloop_api *api, void *userdata) { + lt_dlhandle dl = userdata; + + lt_dlclose(dl); +} + +static void pa_module_free(pa_module *m) { + pa_assert(m); + pa_assert(m->core); + + pa_log_info("Unloading \"%s\" (index: #%u).", m->name, m->index); + pa_hook_fire(&m->core->hooks[PA_CORE_HOOK_MODULE_UNLINK], m); + + if (m->hooks) { + pa_dynarray_free(m->hooks); + m->hooks = NULL; + } + + if (m->done) + m->done(m); + + if (m->proplist) + pa_proplist_free(m->proplist); + + /* If a module unloads itself with pa_module_unload(), we can't call + * lt_dlclose() here, because otherwise pa_module_unload() may return to a + * code location that has been removed from memory. Therefore, let's + * postpone the lt_dlclose() call a bit. + * + * Apparently lt_dlclose() doesn't always remove the module from memory, + * but it can happen, as can be seen here: + * https://bugs.freedesktop.org/show_bug.cgi?id=96831 */ + pa_mainloop_api_once(m->core->mainloop, postponed_dlclose, m->dl); + + pa_hashmap_remove(m->core->modules_pending_unload, m); + + pa_log_info("Unloaded \"%s\" (index: #%u).", m->name, m->index); + + pa_subscription_post(m->core, PA_SUBSCRIPTION_EVENT_MODULE|PA_SUBSCRIPTION_EVENT_REMOVE, m->index); + + pa_xfree(m->name); + pa_xfree(m->argument); + pa_xfree(m); +} + +void pa_module_unload(pa_module *m, bool force) { + pa_assert(m); + + if (m->core->disallow_module_loading && !force) + return; + + if (!(m = pa_idxset_remove_by_data(m->core->modules, m, NULL))) + return; + + pa_module_free(m); +} + +void pa_module_unload_by_index(pa_core *c, uint32_t idx, bool force) { + pa_module *m; + pa_assert(c); + pa_assert(idx != PA_IDXSET_INVALID); + + if (c->disallow_module_loading && !force) + return; + + if (!(m = pa_idxset_remove_by_index(c->modules, idx))) + return; + + pa_module_free(m); +} + +void pa_module_unload_all(pa_core *c) { + pa_module *m; + uint32_t *indices; + uint32_t state; + int i; + + pa_assert(c); + pa_assert(c->modules); + + if (pa_idxset_isempty(c->modules)) + return; + + /* Unload modules in reverse order by default */ + indices = pa_xnew(uint32_t, pa_idxset_size(c->modules)); + i = 0; + PA_IDXSET_FOREACH(m, c->modules, state) + indices[i++] = state; + pa_assert(i == (int) pa_idxset_size(c->modules)); + i--; + for (; i >= 0; i--) { + m = pa_idxset_remove_by_index(c->modules, indices[i]); + if (m) + pa_module_free(m); + } + pa_xfree(indices); + + /* Just in case module unloading caused more modules to load */ + PA_IDXSET_FOREACH(m, c->modules, state) + pa_log_warn("After module unload, module '%s' was still loaded!", m->name); + c->disallow_module_loading = 1; + pa_idxset_remove_all(c->modules, (pa_free_cb_t) pa_module_free); + pa_assert(pa_idxset_isempty(c->modules)); + + if (c->module_defer_unload_event) { + c->mainloop->defer_free(c->module_defer_unload_event); + c->module_defer_unload_event = NULL; + } + pa_assert(pa_hashmap_isempty(c->modules_pending_unload)); +} + +static void defer_cb(pa_mainloop_api*api, pa_defer_event *e, void *userdata) { + pa_core *c = PA_CORE(userdata); + pa_module *m; + + pa_core_assert_ref(c); + api->defer_enable(e, 0); + + while ((m = pa_hashmap_first(c->modules_pending_unload))) + pa_module_unload(m, true); +} + +void pa_module_unload_request(pa_module *m, bool force) { + pa_assert(m); + + if (m->core->disallow_module_loading && !force) + return; + + m->unload_requested = true; + pa_hashmap_put(m->core->modules_pending_unload, m, m); + + if (!m->core->module_defer_unload_event) + m->core->module_defer_unload_event = m->core->mainloop->defer_new(m->core->mainloop, defer_cb, m->core); + + m->core->mainloop->defer_enable(m->core->module_defer_unload_event, 1); +} + +void pa_module_unload_request_by_index(pa_core *c, uint32_t idx, bool force) { + pa_module *m; + pa_assert(c); + + if (!(m = pa_idxset_get_by_index(c->modules, idx))) + return; + + pa_module_unload_request(m, force); +} + +int pa_module_get_n_used(pa_module*m) { + pa_assert(m); + + if (!m->get_n_used) + return -1; + + return m->get_n_used(m); +} + +void pa_module_update_proplist(pa_module *m, pa_update_mode_t mode, pa_proplist *p) { + pa_assert(m); + + if (p) + pa_proplist_update(m->proplist, mode, p); + + pa_subscription_post(m->core, PA_SUBSCRIPTION_EVENT_MODULE|PA_SUBSCRIPTION_EVENT_CHANGE, m->index); + pa_hook_fire(&m->core->hooks[PA_CORE_HOOK_MODULE_PROPLIST_CHANGED], m); +} diff --git a/src/pulsecore/module.h b/src/pulsecore/module.h new file mode 100644 index 0000000..f918ea6 --- /dev/null +++ b/src/pulsecore/module.h @@ -0,0 +1,129 @@ +#ifndef foomodulehfoo +#define foomodulehfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <inttypes.h> +#include <ltdl.h> + +typedef struct pa_module pa_module; + +#include <pulse/proplist.h> +#include <pulsecore/dynarray.h> + +#include <pulsecore/core.h> + +enum { + PA_MODULE_ERR_UNSPECIFIED = 1, + PA_MODULE_ERR_SKIP = 2 +}; + +struct pa_module { + pa_core *core; + char *name, *argument; + uint32_t index; + + lt_dlhandle dl; + + int (*init)(pa_module*m); + void (*done)(pa_module*m); + int (*get_n_used)(pa_module *m); + + void *userdata; + + bool load_once:1; + bool unload_requested:1; + + pa_proplist *proplist; + pa_dynarray *hooks; +}; + +bool pa_module_exists(const char *name); + +int pa_module_load(pa_module** m, pa_core *c, const char *name, const char *argument); + +void pa_module_unload(pa_module *m, bool force); +void pa_module_unload_by_index(pa_core *c, uint32_t idx, bool force); + +void pa_module_unload_request(pa_module *m, bool force); +void pa_module_unload_request_by_index(pa_core *c, uint32_t idx, bool force); + +void pa_module_unload_all(pa_core *c); + +int pa_module_get_n_used(pa_module*m); + +void pa_module_update_proplist(pa_module *m, pa_update_mode_t mode, pa_proplist *p); + +void pa_module_hook_connect(pa_module *m, pa_hook *hook, pa_hook_priority_t prio, pa_hook_cb_t cb, void *data); + +#define PA_MODULE_AUTHOR(s) \ + const char *pa__get_author(void) { return s; } \ + struct __stupid_useless_struct_to_allow_trailing_semicolon + +#define PA_MODULE_DESCRIPTION(s) \ + const char *pa__get_description(void) { return s; } \ + struct __stupid_useless_struct_to_allow_trailing_semicolon + +#define PA_MODULE_USAGE(s) \ + const char *pa__get_usage(void) { return s; } \ + struct __stupid_useless_struct_to_allow_trailing_semicolon + +#define PA_MODULE_VERSION(s) \ + const char * pa__get_version(void) { return s; } \ + struct __stupid_useless_struct_to_allow_trailing_semicolon + +#define PA_MODULE_DEPRECATED(s) \ + const char * pa__get_deprecated(void) { return s; } \ + struct __stupid_useless_struct_to_allow_trailing_semicolon + +#define PA_MODULE_LOAD_ONCE(b) \ + bool pa__load_once(void) { return b; } \ + struct __stupid_useless_struct_to_allow_trailing_semicolon + +/* Check if we're defining a module (usually defined via compiler flags) */ +#ifdef PA_MODULE_NAME + +/* Jump through some double-indirection hoops to get PA_MODULE_NAME substituted before the concatenation */ +#define _MACRO_CONCAT1(a, b) a ## b +#define _MACRO_CONCAT(a, b) _MACRO_CONCAT1(a, b) + +#define pa__init _MACRO_CONCAT(PA_MODULE_NAME, _LTX_pa__init) +#define pa__done _MACRO_CONCAT(PA_MODULE_NAME, _LTX_pa__done) +#define pa__get_author _MACRO_CONCAT(PA_MODULE_NAME, _LTX_pa__get_author) +#define pa__get_description _MACRO_CONCAT(PA_MODULE_NAME, _LTX_pa__get_description) +#define pa__get_usage _MACRO_CONCAT(PA_MODULE_NAME, _LTX_pa__get_usage) +#define pa__get_version _MACRO_CONCAT(PA_MODULE_NAME, _LTX_pa__get_version) +#define pa__get_deprecated _MACRO_CONCAT(PA_MODULE_NAME, _LTX_pa__get_deprecated) +#define pa__load_once _MACRO_CONCAT(PA_MODULE_NAME, _LTX_pa__load_once) +#define pa__get_n_used _MACRO_CONCAT(PA_MODULE_NAME, _LTX_pa__get_n_used) + +int pa__init(pa_module*m); +void pa__done(pa_module*m); +int pa__get_n_used(pa_module*m); + +const char* pa__get_author(void); +const char* pa__get_description(void); +const char* pa__get_usage(void); +const char* pa__get_version(void); +const char* pa__get_deprecated(void); +bool pa__load_once(void); +#endif /* PA_MODULE_NAME */ + +#endif diff --git a/src/pulsecore/msgobject.c b/src/pulsecore/msgobject.c new file mode 100644 index 0000000..e1d0fff --- /dev/null +++ b/src/pulsecore/msgobject.c @@ -0,0 +1,45 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "msgobject.h" + +PA_DEFINE_PUBLIC_CLASS(pa_msgobject, pa_object); + +pa_msgobject *pa_msgobject_new_internal(size_t size, const char *type_id, bool (*check_type)(const char *type_name)) { + pa_msgobject *o; + + pa_assert(size >= sizeof(pa_msgobject)); + pa_assert(type_id); + + if (!check_type) + check_type = pa_msgobject_check_type; + + pa_assert(check_type(type_id)); + pa_assert(check_type(pa_object_type_id)); + pa_assert(check_type(pa_msgobject_type_id)); + + o = PA_MSGOBJECT(pa_object_new_internal(size, type_id, check_type)); + o->process_msg = NULL; + return o; +} diff --git a/src/pulsecore/msgobject.h b/src/pulsecore/msgobject.h new file mode 100644 index 0000000..dfb519d --- /dev/null +++ b/src/pulsecore/msgobject.h @@ -0,0 +1,46 @@ +#ifndef foopulsemsgobjecthfoo +#define foopulsemsgobjecthfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/types.h> + +#include <pulsecore/macro.h> +#include <pulsecore/object.h> +#include <pulsecore/memchunk.h> + +typedef struct pa_msgobject pa_msgobject; + +struct pa_msgobject { + pa_object parent; + int (*process_msg)(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk); +}; + +pa_msgobject *pa_msgobject_new_internal(size_t size, const char *type_id, bool (*check_type)(const char *type_name)); + +#define pa_msgobject_new(type) ((type*) pa_msgobject_new_internal(sizeof(type), type##_type_id, type##_check_type)) +#define pa_msgobject_free ((void (*) (pa_msgobject* o)) pa_object_free) + +#define PA_MSGOBJECT(o) pa_msgobject_cast(o) + +PA_DECLARE_PUBLIC_CLASS(pa_msgobject); + +#endif diff --git a/src/pulsecore/mutex-posix.c b/src/pulsecore/mutex-posix.c new file mode 100644 index 0000000..a835be1 --- /dev/null +++ b/src/pulsecore/mutex-posix.c @@ -0,0 +1,162 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pthread.h> +#include <errno.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/macro.h> + +#include "mutex.h" + +struct pa_mutex { + pthread_mutex_t mutex; +}; + +struct pa_cond { + pthread_cond_t cond; +}; + +pa_mutex* pa_mutex_new(bool recursive, bool inherit_priority) { + pa_mutex *m; + pthread_mutexattr_t attr; +#ifdef HAVE_PTHREAD_PRIO_INHERIT + int r; +#endif + + pa_assert_se(pthread_mutexattr_init(&attr) == 0); + + if (recursive) + pa_assert_se(pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) == 0); + +#ifdef HAVE_PTHREAD_PRIO_INHERIT + if (inherit_priority) { + r = pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT); + pa_assert(r == 0 || r == ENOTSUP); + } +#endif + + m = pa_xnew(pa_mutex, 1); + +#ifndef HAVE_PTHREAD_PRIO_INHERIT + pa_assert_se(pthread_mutex_init(&m->mutex, &attr) == 0); + +#else + if ((r = pthread_mutex_init(&m->mutex, &attr))) { + + /* If this failed, then this was probably due to non-available + * priority inheritance. In which case we fall back to normal + * mutexes. */ + pa_assert(r == ENOTSUP && inherit_priority); + + pa_assert_se(pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_NONE) == 0); + pa_assert_se(pthread_mutex_init(&m->mutex, &attr) == 0); + } +#endif + + return m; +} + +void pa_mutex_free(pa_mutex *m) { + pa_assert(m); + + pa_assert_se(pthread_mutex_destroy(&m->mutex) == 0); + pa_xfree(m); +} + +void pa_mutex_lock(pa_mutex *m) { + pa_assert(m); + + pa_assert_se(pthread_mutex_lock(&m->mutex) == 0); +} + +bool pa_mutex_try_lock(pa_mutex *m) { + int r; + pa_assert(m); + + if ((r = pthread_mutex_trylock(&m->mutex)) != 0) { + pa_assert(r == EBUSY); + return false; + } + + return true; +} + +void pa_mutex_unlock(pa_mutex *m) { + pa_assert(m); + + pa_assert_se(pthread_mutex_unlock(&m->mutex) == 0); +} + +pa_cond *pa_cond_new(void) { + pa_cond *c; + + c = pa_xnew(pa_cond, 1); + pa_assert_se(pthread_cond_init(&c->cond, NULL) == 0); + return c; +} + +void pa_cond_free(pa_cond *c) { + pa_assert(c); + + pa_assert_se(pthread_cond_destroy(&c->cond) == 0); + pa_xfree(c); +} + +void pa_cond_signal(pa_cond *c, int broadcast) { + pa_assert(c); + + if (broadcast) + pa_assert_se(pthread_cond_broadcast(&c->cond) == 0); + else + pa_assert_se(pthread_cond_signal(&c->cond) == 0); +} + +int pa_cond_wait(pa_cond *c, pa_mutex *m) { + pa_assert(c); + pa_assert(m); + + return pthread_cond_wait(&c->cond, &m->mutex); +} + +pa_mutex* pa_static_mutex_get(pa_static_mutex *s, bool recursive, bool inherit_priority) { + pa_mutex *m; + + pa_assert(s); + + /* First, check if already initialized and short cut */ + if ((m = pa_atomic_ptr_load(&s->ptr))) + return m; + + /* OK, not initialized, so let's allocate, and fill in */ + m = pa_mutex_new(recursive, inherit_priority); + if ((pa_atomic_ptr_cmpxchg(&s->ptr, NULL, m))) + return m; + + pa_mutex_free(m); + + /* Him, filling in failed, so someone else must have filled in + * already */ + pa_assert_se(m = pa_atomic_ptr_load(&s->ptr)); + return m; +} diff --git a/src/pulsecore/mutex-win32.c b/src/pulsecore/mutex-win32.c new file mode 100644 index 0000000..370f443 --- /dev/null +++ b/src/pulsecore/mutex-win32.c @@ -0,0 +1,154 @@ +/*** + This file is part of PulseAudio. + + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <windows.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/hashmap.h> + +#include "mutex.h" + +struct pa_mutex { + CRITICAL_SECTION mutex; +}; + +struct pa_cond { + pa_hashmap *wait_events; +}; + +pa_mutex* pa_mutex_new(bool recursive, bool inherit_priority) { + pa_mutex *m; + + m = pa_xnew(pa_mutex, 1); + + InitializeCriticalSection(&m->mutex); + + return m; +} + +void pa_mutex_free(pa_mutex *m) { + assert(m); + + DeleteCriticalSection(&m->mutex); + pa_xfree(m); +} + +void pa_mutex_lock(pa_mutex *m) { + assert(m); + + EnterCriticalSection(&m->mutex); +} + +void pa_mutex_unlock(pa_mutex *m) { + assert(m); + + LeaveCriticalSection(&m->mutex); +} + +pa_cond *pa_cond_new(void) { + pa_cond *c; + + c = pa_xnew(pa_cond, 1); + c->wait_events = pa_hashmap_new(NULL, NULL); + assert(c->wait_events); + + return c; +} + +void pa_cond_free(pa_cond *c) { + assert(c); + + pa_hashmap_free(c->wait_events); + pa_xfree(c); +} + +void pa_cond_signal(pa_cond *c, int broadcast) { + assert(c); + + if (pa_hashmap_size(c->wait_events) == 0) + return; + + if (broadcast) + SetEvent(pa_hashmap_first(c->wait_events)); + else { + void *iter; + const void *key; + HANDLE event; + + iter = NULL; + while (1) { + pa_hashmap_iterate(c->wait_events, &iter, &key); + if (key == NULL) + break; + event = (HANDLE)pa_hashmap_get(c->wait_events, key); + SetEvent(event); + } + } +} + +int pa_cond_wait(pa_cond *c, pa_mutex *m) { + HANDLE event; + + assert(c); + assert(m); + + event = CreateEvent(NULL, FALSE, FALSE, NULL); + assert(event); + + pa_hashmap_put(c->wait_events, event, event); + + pa_mutex_unlock(m); + + WaitForSingleObject(event, INFINITE); + + pa_mutex_lock(m); + + pa_hashmap_remove(c->wait_events, event); + + CloseHandle(event); + + return 0; +} + +/* This is a copy of the function in mutex-posix.c */ +pa_mutex* pa_static_mutex_get(pa_static_mutex *s, bool recursive, bool inherit_priority) { + pa_mutex *m; + + pa_assert(s); + + /* First, check if already initialized and short cut */ + if ((m = pa_atomic_ptr_load(&s->ptr))) + return m; + + /* OK, not initialized, so let's allocate, and fill in */ + m = pa_mutex_new(recursive, inherit_priority); + if ((pa_atomic_ptr_cmpxchg(&s->ptr, NULL, m))) + return m; + + pa_mutex_free(m); + + /* Him, filling in failed, so someone else must have filled in + * already */ + pa_assert_se(m = pa_atomic_ptr_load(&s->ptr)); + return m; +} diff --git a/src/pulsecore/mutex.h b/src/pulsecore/mutex.h new file mode 100644 index 0000000..9cdd857 --- /dev/null +++ b/src/pulsecore/mutex.h @@ -0,0 +1,57 @@ +#ifndef foopulsemutexhfoo +#define foopulsemutexhfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulsecore/macro.h> +#include <pulsecore/atomic.h> + +typedef struct pa_mutex pa_mutex; + +/* Please think twice before enabling priority inheritance. This is no + * magic wand! Use it only when the potentially prioritized threads are + * good candidates for it. Don't use this blindly! Also, note that + * only very few operating systems actually implement this, hence this + * is merely a hint. */ +pa_mutex* pa_mutex_new(bool recursive, bool inherit_priority); + +void pa_mutex_free(pa_mutex *m); +void pa_mutex_lock(pa_mutex *m); +bool pa_mutex_try_lock(pa_mutex *m); +void pa_mutex_unlock(pa_mutex *m); + +typedef struct pa_cond pa_cond; + +pa_cond *pa_cond_new(void); +void pa_cond_free(pa_cond *c); +void pa_cond_signal(pa_cond *c, int broadcast); +int pa_cond_wait(pa_cond *c, pa_mutex *m); + +/* Static mutexes are basically just atomically updated pointers to pa_mutex objects */ + +typedef struct pa_static_mutex { + pa_atomic_ptr_t ptr; +} pa_static_mutex; + +#define PA_STATIC_MUTEX_INIT { PA_ATOMIC_PTR_INIT(NULL) } + +pa_mutex* pa_static_mutex_get(pa_static_mutex *m, bool recursive, bool inherit_priority); + +#endif diff --git a/src/pulsecore/namereg.c b/src/pulsecore/namereg.c new file mode 100644 index 0000000..0b73885 --- /dev/null +++ b/src/pulsecore/namereg.c @@ -0,0 +1,227 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <string.h> +#include <string.h> +#include <stdio.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/source.h> +#include <pulsecore/sink.h> +#include <pulsecore/core-subscribe.h> +#include <pulsecore/core-util.h> +#include <pulsecore/macro.h> + +#include "namereg.h" + +struct namereg_entry { + pa_namereg_type_t type; + char *name; + void *data; +}; + +static bool is_valid_char(char c) { + return + (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || + c == '.' || + c == '-' || + c == '_'; +} + +bool pa_namereg_is_valid_name(const char *name) { + const char *c; + + pa_assert(name); + + if (*name == 0) + return false; + + for (c = name; *c && (c-name < PA_NAME_MAX); c++) + if (!is_valid_char(*c)) + return false; + + if (*c) + return false; + + return true; +} + +bool pa_namereg_is_valid_name_or_wildcard(const char *name, pa_namereg_type_t type) { + + pa_assert(name); + + if (pa_namereg_is_valid_name(name)) + return true; + + if (type == PA_NAMEREG_SINK && + pa_streq(name, "@DEFAULT_SINK@")) + return true; + + if (type == PA_NAMEREG_SOURCE && + (pa_streq(name, "@DEFAULT_SOURCE@") || + pa_streq(name, "@DEFAULT_MONITOR@"))) + return true; + + return false; +} + +char* pa_namereg_make_valid_name(const char *name) { + const char *a; + char *b, *n; + + if (*name == 0) + return NULL; + + n = pa_xnew(char, strlen(name)+1); + + for (a = name, b = n; *a && (a-name < PA_NAME_MAX); a++, b++) + *b = (char) (is_valid_char(*a) ? *a : '_'); + + *b = 0; + + return n; +} + +const char *pa_namereg_register(pa_core *c, const char *name, pa_namereg_type_t type, void *data, bool fail) { + struct namereg_entry *e; + char *n = NULL; + + pa_assert(c); + pa_assert(name); + pa_assert(data); + + if (!*name) + return NULL; + + if ((type == PA_NAMEREG_SINK || type == PA_NAMEREG_SOURCE || type == PA_NAMEREG_CARD) && + !pa_namereg_is_valid_name(name)) { + + if (fail) + return NULL; + + if (!(name = n = pa_namereg_make_valid_name(name))) + return NULL; + } + + if ((e = pa_hashmap_get(c->namereg, name)) && fail) { + pa_xfree(n); + return NULL; + } + + if (e) { + unsigned i; + size_t l = strlen(name); + char *k; + + if (l+4 > PA_NAME_MAX) { + pa_xfree(n); + return NULL; + } + + k = pa_xmalloc(l+4); + + for (i = 2; i <= 99; i++) { + pa_snprintf(k, l+4, "%s.%u", name, i); + + if (!(e = pa_hashmap_get(c->namereg, k))) + break; + } + + if (e) { + pa_xfree(n); + pa_xfree(k); + return NULL; + } + + pa_xfree(n); + n = k; + } + + e = pa_xnew(struct namereg_entry, 1); + e->type = type; + e->name = n ? n : pa_xstrdup(name); + e->data = data; + + pa_assert_se(pa_hashmap_put(c->namereg, e->name, e) >= 0); + + return e->name; +} + +void pa_namereg_unregister(pa_core *c, const char *name) { + struct namereg_entry *e; + + pa_assert(c); + pa_assert(name); + + pa_assert_se(e = pa_hashmap_remove(c->namereg, name)); + pa_xfree(e->name); + pa_xfree(e); +} + +void* pa_namereg_get(pa_core *c, const char *name, pa_namereg_type_t type) { + struct namereg_entry *e; + uint32_t idx; + pa_assert(c); + + if (type == PA_NAMEREG_SOURCE && (!name || pa_streq(name, "@DEFAULT_SOURCE@"))) { + return c->default_source; + + } else if (type == PA_NAMEREG_SINK && (!name || pa_streq(name, "@DEFAULT_SINK@"))) { + return c->default_sink; + + } else if (type == PA_NAMEREG_SOURCE && name && pa_streq(name, "@DEFAULT_MONITOR@")) { + if (c->default_sink) + return c->default_sink->monitor_source; + else + return NULL; + } + + if (!name) + return NULL; + + if ((type == PA_NAMEREG_SINK || type == PA_NAMEREG_SOURCE || type == PA_NAMEREG_CARD) && + !pa_namereg_is_valid_name(name)) + return NULL; + + if ((e = pa_hashmap_get(c->namereg, name))) + if (e->type == type) + return e->data; + + if (pa_atou(name, &idx) < 0) + return NULL; + + if (type == PA_NAMEREG_SINK) + return pa_idxset_get_by_index(c->sinks, idx); + else if (type == PA_NAMEREG_SOURCE) + return pa_idxset_get_by_index(c->sources, idx); + else if (type == PA_NAMEREG_SAMPLE && c->scache) + return pa_idxset_get_by_index(c->scache, idx); + else if (type == PA_NAMEREG_CARD) + return pa_idxset_get_by_index(c->cards, idx); + + return NULL; +} diff --git a/src/pulsecore/namereg.h b/src/pulsecore/namereg.h new file mode 100644 index 0000000..eb3475a --- /dev/null +++ b/src/pulsecore/namereg.h @@ -0,0 +1,43 @@ +#ifndef foonamereghfoo +#define foonamereghfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulsecore/core.h> +#include <pulsecore/macro.h> + +#define PA_NAME_MAX 128 + +typedef enum pa_namereg_type { + PA_NAMEREG_SINK, + PA_NAMEREG_SOURCE, + PA_NAMEREG_SAMPLE, + PA_NAMEREG_CARD +} pa_namereg_type_t; + +const char *pa_namereg_register(pa_core *c, const char *name, pa_namereg_type_t type, void *data, bool fail); +void pa_namereg_unregister(pa_core *c, const char *name); +void* pa_namereg_get(pa_core *c, const char *name, pa_namereg_type_t type); + +bool pa_namereg_is_valid_name(const char *name); +bool pa_namereg_is_valid_name_or_wildcard(const char *name, pa_namereg_type_t type); +char* pa_namereg_make_valid_name(const char *name); + +#endif diff --git a/src/pulsecore/native-common.c b/src/pulsecore/native-common.c new file mode 100644 index 0000000..282a4ed --- /dev/null +++ b/src/pulsecore/native-common.c @@ -0,0 +1,78 @@ +/*** + This file is part of PulseAudio. + + Copyright 2016 Ahmed S. Darwish <darwish.07@gmail.com> + + 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulsecore/core-util.h> +#include <pulsecore/creds.h> +#include <pulsecore/macro.h> +#include <pulsecore/pdispatch.h> +#include <pulsecore/pstream.h> +#include <pulsecore/tagstruct.h> + +#include "native-common.h" + +/* + * Command handlers shared between client and server + */ + +/* Check pa_pstream_register_memfd_mempool() for further details */ +int pa_common_command_register_memfd_shmid(pa_pstream *p, pa_pdispatch *pd, uint32_t version, + uint32_t command, pa_tagstruct *t) { +#if defined(HAVE_CREDS) && defined(HAVE_MEMFD) + pa_cmsg_ancil_data *ancil = NULL; + unsigned shm_id; + int ret = -1; + + pa_assert(pd); + pa_assert(command == PA_COMMAND_REGISTER_MEMFD_SHMID); + pa_assert(t); + + ancil = pa_pdispatch_take_ancil_data(pd); + if (!ancil) + goto finish; + + /* Upon fd leaks and reaching our open fd limit, recvmsg(2) + * just strips all passed fds from the ancillary data */ + if (ancil->nfd == 0) { + pa_log("Expected 1 memfd fd to be received over pipe; got 0"); + pa_log("Did we reach our open file descriptors limit?"); + goto finish; + } + + if (ancil->nfd != 1 || ancil->fds[0] == -1) + goto finish; + + if (version < 31 || pa_tagstruct_getu32(t, &shm_id) < 0 || !pa_tagstruct_eof(t)) + goto finish; + + pa_pstream_attach_memfd_shmid(p, shm_id, ancil->fds[0]); + + ret = 0; +finish: + if (ancil) + pa_cmsg_ancil_data_close_fds(ancil); + + return ret; +#else + return -1; +#endif +} diff --git a/src/pulsecore/native-common.h b/src/pulsecore/native-common.h new file mode 100644 index 0000000..70338b9 --- /dev/null +++ b/src/pulsecore/native-common.h @@ -0,0 +1,209 @@ +#ifndef foonativecommonhfoo +#define foonativecommonhfoo + +/*** + 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulse/cdecl.h> +#include <pulse/def.h> + +#include <pulsecore/pdispatch.h> +#include <pulsecore/pstream.h> +#include <pulsecore/tagstruct.h> + +PA_C_DECL_BEGIN + +enum { + /* Generic commands */ + PA_COMMAND_ERROR, + PA_COMMAND_TIMEOUT, /* pseudo command */ + PA_COMMAND_REPLY, + + /* CLIENT->SERVER */ + PA_COMMAND_CREATE_PLAYBACK_STREAM, /* Payload changed in v9, v12 (0.9.0, 0.9.8) */ + PA_COMMAND_DELETE_PLAYBACK_STREAM, + PA_COMMAND_CREATE_RECORD_STREAM, /* Payload changed in v9, v12 (0.9.0, 0.9.8) */ + PA_COMMAND_DELETE_RECORD_STREAM, + PA_COMMAND_EXIT, + PA_COMMAND_AUTH, + PA_COMMAND_SET_CLIENT_NAME, + PA_COMMAND_LOOKUP_SINK, + PA_COMMAND_LOOKUP_SOURCE, + PA_COMMAND_DRAIN_PLAYBACK_STREAM, + PA_COMMAND_STAT, + PA_COMMAND_GET_PLAYBACK_LATENCY, + PA_COMMAND_CREATE_UPLOAD_STREAM, + PA_COMMAND_DELETE_UPLOAD_STREAM, + PA_COMMAND_FINISH_UPLOAD_STREAM, + PA_COMMAND_PLAY_SAMPLE, + PA_COMMAND_REMOVE_SAMPLE, + + PA_COMMAND_GET_SERVER_INFO, + PA_COMMAND_GET_SINK_INFO, + PA_COMMAND_GET_SINK_INFO_LIST, + PA_COMMAND_GET_SOURCE_INFO, + PA_COMMAND_GET_SOURCE_INFO_LIST, + PA_COMMAND_GET_MODULE_INFO, + PA_COMMAND_GET_MODULE_INFO_LIST, + PA_COMMAND_GET_CLIENT_INFO, + PA_COMMAND_GET_CLIENT_INFO_LIST, + PA_COMMAND_GET_SINK_INPUT_INFO, /* Payload changed in v11 (0.9.7) */ + PA_COMMAND_GET_SINK_INPUT_INFO_LIST, /* Payload changed in v11 (0.9.7) */ + PA_COMMAND_GET_SOURCE_OUTPUT_INFO, + PA_COMMAND_GET_SOURCE_OUTPUT_INFO_LIST, + PA_COMMAND_GET_SAMPLE_INFO, + PA_COMMAND_GET_SAMPLE_INFO_LIST, + PA_COMMAND_SUBSCRIBE, + + PA_COMMAND_SET_SINK_VOLUME, + PA_COMMAND_SET_SINK_INPUT_VOLUME, + PA_COMMAND_SET_SOURCE_VOLUME, + + PA_COMMAND_SET_SINK_MUTE, + PA_COMMAND_SET_SOURCE_MUTE, + + PA_COMMAND_CORK_PLAYBACK_STREAM, + PA_COMMAND_FLUSH_PLAYBACK_STREAM, + PA_COMMAND_TRIGGER_PLAYBACK_STREAM, + + PA_COMMAND_SET_DEFAULT_SINK, + PA_COMMAND_SET_DEFAULT_SOURCE, + + PA_COMMAND_SET_PLAYBACK_STREAM_NAME, + PA_COMMAND_SET_RECORD_STREAM_NAME, + + PA_COMMAND_KILL_CLIENT, + PA_COMMAND_KILL_SINK_INPUT, + PA_COMMAND_KILL_SOURCE_OUTPUT, + + PA_COMMAND_LOAD_MODULE, + PA_COMMAND_UNLOAD_MODULE, + + /* Obsolete */ + PA_COMMAND_ADD_AUTOLOAD___OBSOLETE, + PA_COMMAND_REMOVE_AUTOLOAD___OBSOLETE, + PA_COMMAND_GET_AUTOLOAD_INFO___OBSOLETE, + PA_COMMAND_GET_AUTOLOAD_INFO_LIST___OBSOLETE, + + PA_COMMAND_GET_RECORD_LATENCY, + PA_COMMAND_CORK_RECORD_STREAM, + PA_COMMAND_FLUSH_RECORD_STREAM, + PA_COMMAND_PREBUF_PLAYBACK_STREAM, + + /* SERVER->CLIENT */ + PA_COMMAND_REQUEST, + PA_COMMAND_OVERFLOW, + PA_COMMAND_UNDERFLOW, + PA_COMMAND_PLAYBACK_STREAM_KILLED, + PA_COMMAND_RECORD_STREAM_KILLED, + PA_COMMAND_SUBSCRIBE_EVENT, + + /* A few more client->server commands */ + + /* Supported since protocol v10 (0.9.5) */ + PA_COMMAND_MOVE_SINK_INPUT, + PA_COMMAND_MOVE_SOURCE_OUTPUT, + + /* Supported since protocol v11 (0.9.7) */ + PA_COMMAND_SET_SINK_INPUT_MUTE, + + PA_COMMAND_SUSPEND_SINK, + PA_COMMAND_SUSPEND_SOURCE, + + /* Supported since protocol v12 (0.9.8) */ + PA_COMMAND_SET_PLAYBACK_STREAM_BUFFER_ATTR, + PA_COMMAND_SET_RECORD_STREAM_BUFFER_ATTR, + + PA_COMMAND_UPDATE_PLAYBACK_STREAM_SAMPLE_RATE, + PA_COMMAND_UPDATE_RECORD_STREAM_SAMPLE_RATE, + + /* SERVER->CLIENT */ + PA_COMMAND_PLAYBACK_STREAM_SUSPENDED, + PA_COMMAND_RECORD_STREAM_SUSPENDED, + PA_COMMAND_PLAYBACK_STREAM_MOVED, + PA_COMMAND_RECORD_STREAM_MOVED, + + /* Supported since protocol v13 (0.9.11) */ + PA_COMMAND_UPDATE_RECORD_STREAM_PROPLIST, + PA_COMMAND_UPDATE_PLAYBACK_STREAM_PROPLIST, + PA_COMMAND_UPDATE_CLIENT_PROPLIST, + PA_COMMAND_REMOVE_RECORD_STREAM_PROPLIST, + PA_COMMAND_REMOVE_PLAYBACK_STREAM_PROPLIST, + PA_COMMAND_REMOVE_CLIENT_PROPLIST, + + /* SERVER->CLIENT */ + PA_COMMAND_STARTED, + + /* Supported since protocol v14 (0.9.12) */ + PA_COMMAND_EXTENSION, + + /* Supported since protocol v15 (0.9.15) */ + PA_COMMAND_GET_CARD_INFO, + PA_COMMAND_GET_CARD_INFO_LIST, + PA_COMMAND_SET_CARD_PROFILE, + + PA_COMMAND_CLIENT_EVENT, + PA_COMMAND_PLAYBACK_STREAM_EVENT, + PA_COMMAND_RECORD_STREAM_EVENT, + + /* SERVER->CLIENT */ + PA_COMMAND_PLAYBACK_BUFFER_ATTR_CHANGED, + PA_COMMAND_RECORD_BUFFER_ATTR_CHANGED, + + /* Supported since protocol v16 (0.9.16) */ + PA_COMMAND_SET_SINK_PORT, + PA_COMMAND_SET_SOURCE_PORT, + + /* Supported since protocol v22 (1.0) */ + PA_COMMAND_SET_SOURCE_OUTPUT_VOLUME, + PA_COMMAND_SET_SOURCE_OUTPUT_MUTE, + + /* Supported since protocol v27 (3.0) */ + PA_COMMAND_SET_PORT_LATENCY_OFFSET, + + /* Supported since protocol v30 (6.0) */ + /* BOTH DIRECTIONS */ + PA_COMMAND_ENABLE_SRBCHANNEL, + PA_COMMAND_DISABLE_SRBCHANNEL, + + /* Supported since protocol v31 (9.0) + * BOTH DIRECTIONS */ + PA_COMMAND_REGISTER_MEMFD_SHMID, + + PA_COMMAND_MAX +}; + +#define PA_NATIVE_COOKIE_LENGTH 256 +#define PA_NATIVE_COOKIE_FILE "cookie" +#define PA_NATIVE_COOKIE_FILE_FALLBACK ".pulse-cookie" + +#define PA_NATIVE_DEFAULT_PORT 4713 + +#define PA_NATIVE_COOKIE_PROPERTY_NAME "protocol-native-cookie" +#define PA_NATIVE_SERVER_PROPERTY_NAME "protocol-native-server" + +#define PA_NATIVE_DEFAULT_UNIX_SOCKET "native" + +int pa_common_command_register_memfd_shmid(pa_pstream *p, pa_pdispatch *pd, uint32_t version, + uint32_t command, pa_tagstruct *t); + +PA_C_DECL_END + +#endif diff --git a/src/pulsecore/object.c b/src/pulsecore/object.c new file mode 100644 index 0000000..cb0328f --- /dev/null +++ b/src/pulsecore/object.c @@ -0,0 +1,70 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "object.h" + +const char pa_object_type_id[] = "pa_object"; + +pa_object *pa_object_new_internal(size_t size, const char *type_id, bool (*check_type)(const char *type_id)) { + pa_object *o; + + pa_assert(size > sizeof(pa_object)); + pa_assert(type_id); + + if (!check_type) + check_type = pa_object_check_type; + + pa_assert(check_type(type_id)); + pa_assert(check_type(pa_object_type_id)); + + o = pa_xmalloc0(size); + PA_REFCNT_INIT(o); + o->type_id = type_id; + o->free = pa_object_free; + o->check_type = check_type; + + return o; +} + +pa_object *pa_object_ref(pa_object *o) { + pa_object_assert_ref(o); + + PA_REFCNT_INC(o); + return o; +} + +void pa_object_unref(pa_object *o) { + pa_object_assert_ref(o); + + if (PA_REFCNT_DEC(o) <= 0) { + pa_assert(o->free); + o->free(o); + } +} + +bool pa_object_check_type(const char *type_id) { + pa_assert(type_id); + + return type_id == pa_object_type_id; +} diff --git a/src/pulsecore/object.h b/src/pulsecore/object.h new file mode 100644 index 0000000..15e8365 --- /dev/null +++ b/src/pulsecore/object.h @@ -0,0 +1,117 @@ +#ifndef foopulseobjecthfoo +#define foopulseobjecthfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/types.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/refcnt.h> +#include <pulsecore/macro.h> + +typedef struct pa_object pa_object; + +struct pa_object { + PA_REFCNT_DECLARE; + const char *type_id; + void (*free)(pa_object *o); + bool (*check_type)(const char *type_name); +}; + +pa_object *pa_object_new_internal(size_t size, const char *type_id, bool (*check_type)(const char *type_id)); +#define pa_object_new(type) ((type*) pa_object_new_internal(sizeof(type), type##_type_id, type##_check_type)) + +#define pa_object_free ((void (*) (pa_object* _obj)) pa_xfree) + +bool pa_object_check_type(const char *type_id); + +extern const char pa_object_type_id[]; + +static inline bool pa_object_isinstance(void *o) { + pa_object *obj = (pa_object*) o; + return obj ? obj->check_type(pa_object_type_id) : true; +} + +pa_object *pa_object_ref(pa_object *o); +void pa_object_unref(pa_object *o); + +static inline int pa_object_refcnt(pa_object *o) { + return o ? PA_REFCNT_VALUE(o) : 0; +} + +static inline pa_object* pa_object_cast(void *o) { + pa_object *obj = (pa_object*) o; + pa_assert(!obj || obj->check_type(pa_object_type_id)); + return obj; +} + +#define pa_object_assert_ref(o) pa_assert(pa_object_refcnt(o) > 0) + +#define PA_OBJECT(o) pa_object_cast(o) + +#define PA_DECLARE_CLASS_COMMON(c) \ + static inline bool c##_isinstance(void *o) { \ + pa_object *obj = (pa_object*) o; \ + return obj ? obj->check_type(c##_type_id) : true; \ + } \ + static inline c* c##_cast(void *o) { \ + pa_assert(c##_isinstance(o)); \ + return (c*) o; \ + } \ + static inline c* c##_ref(c *o) { \ + return (c *) ((void *) pa_object_ref(PA_OBJECT(o))); \ + } \ + static inline void c##_unref(c* o) { \ + pa_object_unref(PA_OBJECT(o)); \ + } \ + static inline int c##_refcnt(c* o) { \ + return pa_object_refcnt(PA_OBJECT(o)); \ + } \ + static inline void c##_assert_ref(c *o) { \ + pa_object_assert_ref(PA_OBJECT(o)); \ + } \ + struct __stupid_useless_struct_to_allow_trailing_semicolon + +#define PA_DECLARE_PUBLIC_CLASS(c) \ + extern const char c##_type_id[]; \ + PA_DECLARE_CLASS_COMMON(c); \ + bool c##_check_type(const char *type_id) + +#define PA_DEFINE_PUBLIC_CLASS(c, parent) \ + const char c##_type_id[] = #c; \ + bool c##_check_type(const char *type_id) { \ + if (type_id == c##_type_id) \ + return true; \ + return parent##_check_type(type_id); \ + } \ + struct __stupid_useless_struct_to_allow_trailing_semicolon + +#define PA_DEFINE_PRIVATE_CLASS(c, parent) \ + static const char c##_type_id[] = #c; \ + PA_DECLARE_CLASS_COMMON(c); \ + static bool c##_check_type(const char *type_id) { \ + if (type_id == c##_type_id) \ + return true; \ + return parent##_check_type(type_id); \ + } \ + struct __stupid_useless_struct_to_allow_trailing_semicolon + +#endif diff --git a/src/pulsecore/once.c b/src/pulsecore/once.c new file mode 100644 index 0000000..6e52801 --- /dev/null +++ b/src/pulsecore/once.c @@ -0,0 +1,75 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulsecore/macro.h> + +#include "once.h" + +/* See http://www.hpl.hp.com/research/linux/atomic_ops/example.php4 for the + * reference algorithm used here. */ + +bool pa_once_begin(pa_once *control) { + pa_mutex *m; + + pa_assert(control); + + if (pa_atomic_load(&control->done)) + return false; + + /* Caveat: We have to make sure that the once func has completed + * before returning, even if the once func is not actually + * executed by us. Hence the awkward locking. */ + + m = pa_static_mutex_get(&control->mutex, false, false); + pa_mutex_lock(m); + + if (pa_atomic_load(&control->done)) { + pa_mutex_unlock(m); + return false; + } + + return true; +} + +void pa_once_end(pa_once *control) { + pa_mutex *m; + + pa_assert(control); + + pa_assert(!pa_atomic_load(&control->done)); + pa_atomic_store(&control->done, 1); + + m = pa_static_mutex_get(&control->mutex, false, false); + pa_mutex_unlock(m); +} + +/* Not reentrant -- how could it be? */ +void pa_run_once(pa_once *control, pa_once_func_t func) { + pa_assert(control); + pa_assert(func); + + if (pa_once_begin(control)) { + func(); + pa_once_end(control); + } +} diff --git a/src/pulsecore/once.h b/src/pulsecore/once.h new file mode 100644 index 0000000..8cd7894 --- /dev/null +++ b/src/pulsecore/once.h @@ -0,0 +1,71 @@ +#ifndef foopulseoncehfoo +#define foopulseoncehfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulsecore/atomic.h> +#include <pulsecore/mutex.h> + +typedef struct pa_once { + pa_static_mutex mutex; + pa_atomic_t done; +} pa_once; + +#define PA_ONCE_INIT \ + { \ + .mutex = PA_STATIC_MUTEX_INIT, \ + .done = PA_ATOMIC_INIT(0) \ + } + +/* Not to be called directly, use the macros defined below instead */ +bool pa_once_begin(pa_once *o); +void pa_once_end(pa_once *o); + +#define PA_ONCE_BEGIN \ + do { \ + static pa_once _once = PA_ONCE_INIT; \ + if (pa_once_begin(&_once)) {{ + +#define PA_ONCE_END \ + } \ + pa_once_end(&_once); \ + } \ + } while(0) + +/* + + Usage of these macros is like this: + + void foo() { + + PA_ONCE_BEGIN { + + ... stuff to be called just once ... + + } PA_ONCE_END; + } + +*/ + +/* Same API but calls a function */ +typedef void (*pa_once_func_t) (void); +void pa_run_once(pa_once *o, pa_once_func_t f); + +#endif diff --git a/src/pulsecore/packet.c b/src/pulsecore/packet.c new file mode 100644 index 0000000..2a61d58 --- /dev/null +++ b/src/pulsecore/packet.c @@ -0,0 +1,122 @@ +/*** + 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/macro.h> +#include <pulsecore/refcnt.h> +#include <pulsecore/flist.h> + +#include "packet.h" + +#define MAX_APPENDED_SIZE 128 + +struct pa_packet { + PA_REFCNT_DECLARE; + enum { PA_PACKET_APPENDED, PA_PACKET_DYNAMIC } type; + size_t length; + uint8_t *data; + union { + uint8_t appended[MAX_APPENDED_SIZE]; + } per_type; +}; + +PA_STATIC_FLIST_DECLARE(packets, 0, pa_xfree); + +pa_packet* pa_packet_new(size_t length) { + pa_packet *p; + + pa_assert(length > 0); + + if (!(p = pa_flist_pop(PA_STATIC_FLIST_GET(packets)))) + p = pa_xnew(pa_packet, 1); + PA_REFCNT_INIT(p); + p->length = length; + if (length > MAX_APPENDED_SIZE) { + p->data = pa_xmalloc(length); + p->type = PA_PACKET_DYNAMIC; + } else { + p->data = p->per_type.appended; + p->type = PA_PACKET_APPENDED; + } + + return p; +} + +pa_packet* pa_packet_new_data(const void* data, size_t length) { + pa_packet *p = pa_packet_new(length); + + pa_assert(data); + pa_assert(length > 0); + + memcpy(p->data, data, length); + + return p; +} + +pa_packet* pa_packet_new_dynamic(void* data, size_t length) { + pa_packet *p; + + pa_assert(data); + pa_assert(length > 0); + + if (!(p = pa_flist_pop(PA_STATIC_FLIST_GET(packets)))) + p = pa_xnew(pa_packet, 1); + PA_REFCNT_INIT(p); + p->length = length; + p->data = data; + p->type = PA_PACKET_DYNAMIC; + + return p; +} + +const void* pa_packet_data(pa_packet *p, size_t *l) { + pa_assert(PA_REFCNT_VALUE(p) >= 1); + pa_assert(p->data); + pa_assert(l); + + *l = p->length; + + return p->data; +} + +pa_packet* pa_packet_ref(pa_packet *p) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) >= 1); + + PA_REFCNT_INC(p); + return p; +} + +void pa_packet_unref(pa_packet *p) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) >= 1); + + if (PA_REFCNT_DEC(p) <= 0) { + if (p->type == PA_PACKET_DYNAMIC) + pa_xfree(p->data); + if (pa_flist_push(PA_STATIC_FLIST_GET(packets), p) < 0) + pa_xfree(p); + } +} diff --git a/src/pulsecore/packet.h b/src/pulsecore/packet.h new file mode 100644 index 0000000..2060987 --- /dev/null +++ b/src/pulsecore/packet.h @@ -0,0 +1,45 @@ +#ifndef foopackethfoo +#define foopackethfoo + +/*** + 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/types.h> +#include <inttypes.h> + +typedef struct pa_packet pa_packet; + +/* create empty packet (either of type appended or dynamic depending + * on length) */ +pa_packet* pa_packet_new(size_t length); + +/* create packet (either of type appended or dynamic depending on length) + * and copy data */ +pa_packet* pa_packet_new_data(const void* data, size_t length); + +/* data must have been malloc()ed; the packet takes ownership of the memory, + * i.e. memory is free()d with the packet */ +pa_packet* pa_packet_new_dynamic(void* data, size_t length); + +const void* pa_packet_data(pa_packet *p, size_t *l); + +pa_packet* pa_packet_ref(pa_packet *p); +void pa_packet_unref(pa_packet *p); + +#endif diff --git a/src/pulsecore/parseaddr.c b/src/pulsecore/parseaddr.c new file mode 100644 index 0000000..b909f52 --- /dev/null +++ b/src/pulsecore/parseaddr.c @@ -0,0 +1,156 @@ +/*** + 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <stdlib.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/macro.h> +#include <pulsecore/arpa-inet.h> + +#include "parseaddr.h" + +/* Parse addresses in one of the following forms: + * HOSTNAME + * HOSTNAME:PORT + * [HOSTNAME] + * [HOSTNAME]:PORT + * + * Return a newly allocated string of the hostname and fill in *ret_port if specified */ + +static char *parse_host(const char *s, uint16_t *ret_port) { + pa_assert(s); + pa_assert(ret_port); + + if (*s == '[') { + char *e; + if (!(e = strchr(s+1, ']'))) + return NULL; + + if (e[1] == ':') { + uint32_t p; + + if (pa_atou(e+2, &p) < 0) + return NULL; + + *ret_port = (uint16_t) p; + } else if (e[1] != 0) + return NULL; + + return pa_xstrndup(s+1, (size_t) (e-s-1)); + } else { + char *e; + uint32_t p; + + if (!(e = strrchr(s, ':'))) + return pa_xstrdup(s); + + if (pa_atou(e+1, &p) < 0) + return NULL; + + *ret_port = (uint16_t) p; + return pa_xstrndup(s, (size_t) (e-s)); + } +} + +int pa_parse_address(const char *name, pa_parsed_address *ret_p) { + const char *p; + + pa_assert(name); + pa_assert(ret_p); + + memset(ret_p, 0, sizeof(pa_parsed_address)); + ret_p->type = PA_PARSED_ADDRESS_TCP_AUTO; + + if (*name == '{') { + char *id, *pfx; + + /* The URL starts with a host id for detecting local connections */ + if (!(id = pa_machine_id())) + return -1; + + pfx = pa_sprintf_malloc("{%s}", id); + pa_xfree(id); + + if (!pa_startswith(name, pfx)) { + pa_xfree(pfx); + /* Not local */ + return -1; + } + + p = name + strlen(pfx); + pa_xfree(pfx); + } else + p = name; + + if (*p == '/') + ret_p->type = PA_PARSED_ADDRESS_UNIX; + else if (pa_startswith(p, "unix:")) { + ret_p->type = PA_PARSED_ADDRESS_UNIX; + p += sizeof("unix:")-1; + } else if (pa_startswith(p, "tcp:")) { + ret_p->type = PA_PARSED_ADDRESS_TCP4; + p += sizeof("tcp:")-1; + } else if (pa_startswith(p, "tcp4:")) { + ret_p->type = PA_PARSED_ADDRESS_TCP4; + p += sizeof("tcp4:")-1; + } else if (pa_startswith(p, "tcp6:")) { + ret_p->type = PA_PARSED_ADDRESS_TCP6; + p += sizeof("tcp6:")-1; + } + + if (ret_p->type == PA_PARSED_ADDRESS_UNIX) + ret_p->path_or_host = pa_xstrdup(p); + else + if (!(ret_p->path_or_host = parse_host(p, &ret_p->port))) + return -1; + + return 0; +} + +bool pa_is_ip_address(const char *a) { + char buf[INET6_ADDRSTRLEN]; + + pa_assert(a); + + if (inet_pton(AF_INET6, a, buf) >= 1) + return true; + + if (inet_pton(AF_INET, a, buf) >= 1) + return true; + + return false; +} + +bool pa_is_ip6_address(const char *a) { + char buf[INET6_ADDRSTRLEN]; + + pa_assert(a); + + if (inet_pton(AF_INET6, a, buf) >= 1) + return true; + + return false; +} diff --git a/src/pulsecore/parseaddr.h b/src/pulsecore/parseaddr.h new file mode 100644 index 0000000..6bb4d85 --- /dev/null +++ b/src/pulsecore/parseaddr.h @@ -0,0 +1,46 @@ +#ifndef foopulsecoreparseaddrhfoo +#define foopulsecoreparseaddrhfoo + +/*** + 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <inttypes.h> + +#include <pulsecore/macro.h> + +typedef enum pa_parsed_address_type { + PA_PARSED_ADDRESS_UNIX, + PA_PARSED_ADDRESS_TCP4, + PA_PARSED_ADDRESS_TCP6, + PA_PARSED_ADDRESS_TCP_AUTO +} pa_parsed_address_type_t; + +typedef struct pa_parsed_address { + pa_parsed_address_type_t type; + char *path_or_host; + uint16_t port; +} pa_parsed_address; + +int pa_parse_address(const char *a, pa_parsed_address *ret_p); + +bool pa_is_ip_address(const char *a); + +bool pa_is_ip6_address(const char *a); + +#endif diff --git a/src/pulsecore/pdispatch.c b/src/pulsecore/pdispatch.c new file mode 100644 index 0000000..ab632a5 --- /dev/null +++ b/src/pulsecore/pdispatch.c @@ -0,0 +1,475 @@ +/*** + 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> + +#include <pulse/rtclock.h> +#include <pulse/timeval.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/native-common.h> +#include <pulsecore/llist.h> +#include <pulsecore/log.h> +#include <pulsecore/core-util.h> +#include <pulsecore/macro.h> +#include <pulsecore/refcnt.h> +#include <pulsecore/flist.h> +#include <pulsecore/core-rtclock.h> + +#include "pdispatch.h" + +/* #define DEBUG_OPCODES */ + +#ifdef DEBUG_OPCODES + +static const char *command_names[PA_COMMAND_MAX] = { + /* Generic commands */ + [PA_COMMAND_ERROR] = "ERROR", + [PA_COMMAND_TIMEOUT] = "TIMEOUT", + [PA_COMMAND_REPLY] = "REPLY", + + /* CLIENT->SERVER */ + [PA_COMMAND_CREATE_PLAYBACK_STREAM] = "CREATE_PLAYBACK_STREAM", + [PA_COMMAND_DELETE_PLAYBACK_STREAM] = "DELETE_PLAYBACK_STREAM", + [PA_COMMAND_CREATE_RECORD_STREAM] = "CREATE_RECORD_STREAM", + [PA_COMMAND_DELETE_RECORD_STREAM] = "DELETE_RECORD_STREAM", + [PA_COMMAND_AUTH] = "AUTH", + [PA_COMMAND_EXIT] = "EXIT", + [PA_COMMAND_SET_CLIENT_NAME] = "SET_CLIENT_NAME", + [PA_COMMAND_LOOKUP_SINK] = "LOOKUP_SINK", + [PA_COMMAND_LOOKUP_SOURCE] = "LOOKUP_SOURCE", + [PA_COMMAND_DRAIN_PLAYBACK_STREAM] = "DRAIN_PLAYBACK_STREAM", + [PA_COMMAND_STAT] = "STAT", + [PA_COMMAND_GET_PLAYBACK_LATENCY] = "GET_PLAYBACK_LATENCY", + [PA_COMMAND_CREATE_UPLOAD_STREAM] = "CREATE_UPLOAD_STREAM", + [PA_COMMAND_DELETE_UPLOAD_STREAM] = "DELETE_UPLOAD_STREAM", + [PA_COMMAND_FINISH_UPLOAD_STREAM] = "FINISH_UPLOAD_STREAM", + [PA_COMMAND_PLAY_SAMPLE] = "PLAY_SAMPLE", + [PA_COMMAND_REMOVE_SAMPLE] = "REMOVE_SAMPLE", + + [PA_COMMAND_GET_SERVER_INFO] = "GET_SERVER_INFO", + [PA_COMMAND_GET_SINK_INFO] = "GET_SINK_INFO", + [PA_COMMAND_GET_SINK_INFO_LIST] = "GET_SINK_INFO_LIST", + [PA_COMMAND_GET_SOURCE_INFO] = "GET_SOURCE_INFO", + [PA_COMMAND_GET_SOURCE_INFO_LIST] = "GET_SOURCE_INFO_LIST", + [PA_COMMAND_GET_MODULE_INFO] = "GET_MODULE_INFO", + [PA_COMMAND_GET_MODULE_INFO_LIST] = "GET_MODULE_INFO_LIST", + [PA_COMMAND_GET_CLIENT_INFO] = "GET_CLIENT_INFO", + [PA_COMMAND_GET_CLIENT_INFO_LIST] = "GET_CLIENT_INFO_LIST", + [PA_COMMAND_GET_SAMPLE_INFO] = "GET_SAMPLE_INFO", + [PA_COMMAND_GET_SAMPLE_INFO_LIST] = "GET_SAMPLE_INFO_LIST", + [PA_COMMAND_GET_SINK_INPUT_INFO] = "GET_SINK_INPUT_INFO", + [PA_COMMAND_GET_SINK_INPUT_INFO_LIST] = "GET_SINK_INPUT_INFO_LIST", + [PA_COMMAND_GET_SOURCE_OUTPUT_INFO] = "GET_SOURCE_OUTPUT_INFO", + [PA_COMMAND_GET_SOURCE_OUTPUT_INFO_LIST] = "GET_SOURCE_OUTPUT_INFO_LIST", + [PA_COMMAND_SUBSCRIBE] = "SUBSCRIBE", + + [PA_COMMAND_SET_SINK_VOLUME] = "SET_SINK_VOLUME", + [PA_COMMAND_SET_SINK_INPUT_VOLUME] = "SET_SINK_INPUT_VOLUME", + [PA_COMMAND_SET_SOURCE_VOLUME] = "SET_SOURCE_VOLUME", + + [PA_COMMAND_SET_SINK_MUTE] = "SET_SINK_MUTE", + [PA_COMMAND_SET_SOURCE_MUTE] = "SET_SOURCE_MUTE", + + [PA_COMMAND_TRIGGER_PLAYBACK_STREAM] = "TRIGGER_PLAYBACK_STREAM", + [PA_COMMAND_FLUSH_PLAYBACK_STREAM] = "FLUSH_PLAYBACK_STREAM", + [PA_COMMAND_CORK_PLAYBACK_STREAM] = "CORK_PLAYBACK_STREAM", + + [PA_COMMAND_SET_DEFAULT_SINK] = "SET_DEFAULT_SINK", + [PA_COMMAND_SET_DEFAULT_SOURCE] = "SET_DEFAULT_SOURCE", + + [PA_COMMAND_SET_PLAYBACK_STREAM_NAME] = "SET_PLAYBACK_STREAM_NAME", + [PA_COMMAND_SET_RECORD_STREAM_NAME] = "SET_RECORD_STREAM_NAME", + + [PA_COMMAND_KILL_CLIENT] = "KILL_CLIENT", + [PA_COMMAND_KILL_SINK_INPUT] = "KILL_SINK_INPUT", + [PA_COMMAND_KILL_SOURCE_OUTPUT] = "KILL_SOURCE_OUTPUT", + + [PA_COMMAND_LOAD_MODULE] = "LOAD_MODULE", + [PA_COMMAND_UNLOAD_MODULE] = "UNLOAD_MODULE", + + [PA_COMMAND_ADD_AUTOLOAD___OBSOLETE] = "ADD_AUTOLOAD (obsolete)", + [PA_COMMAND_REMOVE_AUTOLOAD___OBSOLETE] = "REMOVE_AUTOLOAD (obsolete)", + [PA_COMMAND_GET_AUTOLOAD_INFO___OBSOLETE] = "GET_AUTOLOAD_INFO (obsolete)", + [PA_COMMAND_GET_AUTOLOAD_INFO_LIST___OBSOLETE] = "GET_AUTOLOAD_INFO_LIST (obsolete)", + + [PA_COMMAND_GET_RECORD_LATENCY] = "GET_RECORD_LATENCY", + [PA_COMMAND_CORK_RECORD_STREAM] = "CORK_RECORD_STREAM", + [PA_COMMAND_FLUSH_RECORD_STREAM] = "FLUSH_RECORD_STREAM", + [PA_COMMAND_PREBUF_PLAYBACK_STREAM] = "PREBUF_PLAYBACK_STREAM", + + /* SERVER->CLIENT */ + [PA_COMMAND_REQUEST] = "REQUEST", + [PA_COMMAND_OVERFLOW] = "OVERFLOW", + [PA_COMMAND_UNDERFLOW] = "UNDERFLOW", + [PA_COMMAND_PLAYBACK_STREAM_KILLED] = "PLAYBACK_STREAM_KILLED", + [PA_COMMAND_RECORD_STREAM_KILLED] = "RECORD_STREAM_KILLED", + [PA_COMMAND_SUBSCRIBE_EVENT] = "SUBSCRIBE_EVENT", + + /* A few more client->server commands */ + + /* Supported since protocol v10 (0.9.5) */ + [PA_COMMAND_MOVE_SINK_INPUT] = "MOVE_SINK_INPUT", + [PA_COMMAND_MOVE_SOURCE_OUTPUT] = "MOVE_SOURCE_OUTPUT", + + /* Supported since protocol v11 (0.9.7) */ + [PA_COMMAND_SET_SINK_INPUT_MUTE] = "SET_SINK_INPUT_MUTE", + + [PA_COMMAND_SUSPEND_SINK] = "SUSPEND_SINK", + [PA_COMMAND_SUSPEND_SOURCE] = "SUSPEND_SOURCE", + + /* Supported since protocol v12 (0.9.8) */ + [PA_COMMAND_SET_PLAYBACK_STREAM_BUFFER_ATTR] = "SET_PLAYBACK_STREAM_BUFFER_ATTR", + [PA_COMMAND_SET_RECORD_STREAM_BUFFER_ATTR] = "SET_RECORD_STREAM_BUFFER_ATTR", + + [PA_COMMAND_UPDATE_PLAYBACK_STREAM_SAMPLE_RATE] = "UPDATE_PLAYBACK_STREAM_SAMPLE_RATE", + [PA_COMMAND_UPDATE_RECORD_STREAM_SAMPLE_RATE] = "UPDATE_RECORD_STREAM_SAMPLE_RATE", + + /* SERVER->CLIENT */ + [PA_COMMAND_PLAYBACK_STREAM_SUSPENDED] = "PLAYBACK_STREAM_SUSPENDED", + [PA_COMMAND_RECORD_STREAM_SUSPENDED] = "RECORD_STREAM_SUSPENDED", + [PA_COMMAND_PLAYBACK_STREAM_MOVED] = "PLAYBACK_STREAM_MOVED", + [PA_COMMAND_RECORD_STREAM_MOVED] = "RECORD_STREAM_MOVED", + + /* Supported since protocol v13 (0.9.11) */ + [PA_COMMAND_UPDATE_RECORD_STREAM_PROPLIST] = "UPDATE_RECORD_STREAM_PROPLIST", + [PA_COMMAND_UPDATE_PLAYBACK_STREAM_PROPLIST] = "UPDATE_PLAYBACK_STREAM_PROPLIST", + [PA_COMMAND_UPDATE_CLIENT_PROPLIST] = "UPDATE_CLIENT_PROPLIST", + [PA_COMMAND_REMOVE_RECORD_STREAM_PROPLIST] = "REMOVE_RECORD_STREAM_PROPLIST", + [PA_COMMAND_REMOVE_PLAYBACK_STREAM_PROPLIST] = "REMOVE_PLAYBACK_STREAM_PROPLIST", + [PA_COMMAND_REMOVE_CLIENT_PROPLIST] = "REMOVE_CLIENT_PROPLIST", + + /* SERVER->CLIENT */ + [PA_COMMAND_STARTED] = "STARTED", + + /* Supported since protocol v14 (0.9.12) */ + [PA_COMMAND_EXTENSION] = "EXTENSION", + + /* Supported since protocol v15 (0.9.15) */ + [PA_COMMAND_GET_CARD_INFO] = "GET_CARD_INFO", + [PA_COMMAND_GET_CARD_INFO_LIST] = "GET_CARD_INFO_LIST", + [PA_COMMAND_SET_CARD_PROFILE] = "SET_CARD_PROFILE", + + [PA_COMMAND_CLIENT_EVENT] = "CLIENT_EVENT", + [PA_COMMAND_PLAYBACK_STREAM_EVENT] = "PLAYBACK_STREAM_EVENT", + [PA_COMMAND_RECORD_STREAM_EVENT] = "RECORD_STREAM_EVENT", + + /* SERVER->CLIENT */ + [PA_COMMAND_PLAYBACK_BUFFER_ATTR_CHANGED] = "PLAYBACK_BUFFER_ATTR_CHANGED", + [PA_COMMAND_RECORD_BUFFER_ATTR_CHANGED] = "RECORD_BUFFER_ATTR_CHANGED", + + /* Supported since protocol v16 (0.9.16) */ + [PA_COMMAND_SET_SINK_PORT] = "SET_SINK_PORT", + [PA_COMMAND_SET_SOURCE_PORT] = "SET_SOURCE_PORT", + + /* Supported since protocol v22 (1.0) */ + [PA_COMMAND_SET_SOURCE_OUTPUT_VOLUME] = "SET_SOURCE_OUTPUT_VOLUME", + [PA_COMMAND_SET_SOURCE_OUTPUT_MUTE] = "SET_SOURCE_OUTPUT_MUTE", + + /* Supported since protocol v27 (3.0) */ + [PA_COMMAND_SET_PORT_LATENCY_OFFSET] = "SET_PORT_LATENCY_OFFSET", + + /* Supported since protocol v30 (6.0) */ + /* BOTH DIRECTIONS */ + [PA_COMMAND_ENABLE_SRBCHANNEL] = "ENABLE_SRBCHANNEL", + [PA_COMMAND_DISABLE_SRBCHANNEL] = "DISABLE_SRBCHANNEL", + + /* Supported since protocol v31 (9.0) */ + /* BOTH DIRECTIONS */ + [PA_COMMAND_REGISTER_MEMFD_SHMID] = "REGISTER_MEMFD_SHMID", +}; + +#endif + +PA_STATIC_FLIST_DECLARE(reply_infos, 0, pa_xfree); + +struct reply_info { + pa_pdispatch *pdispatch; + PA_LLIST_FIELDS(struct reply_info); + pa_pdispatch_cb_t callback; + void *userdata; + pa_free_cb_t free_cb; + uint32_t tag; + pa_time_event *time_event; +}; + +struct pa_pdispatch { + PA_REFCNT_DECLARE; + pa_mainloop_api *mainloop; + const pa_pdispatch_cb_t *callback_table; + unsigned n_commands; + PA_LLIST_HEAD(struct reply_info, replies); + pa_pdispatch_drain_cb_t drain_callback; + void *drain_userdata; + pa_cmsg_ancil_data *ancil_data; + bool use_rtclock; +}; + +static void reply_info_free(struct reply_info *r) { + pa_assert(r); + pa_assert(r->pdispatch); + pa_assert(r->pdispatch->mainloop); + + if (r->time_event) + r->pdispatch->mainloop->time_free(r->time_event); + + PA_LLIST_REMOVE(struct reply_info, r->pdispatch->replies, r); + + if (pa_flist_push(PA_STATIC_FLIST_GET(reply_infos), r) < 0) + pa_xfree(r); +} + +pa_pdispatch* pa_pdispatch_new(pa_mainloop_api *mainloop, bool use_rtclock, const pa_pdispatch_cb_t *table, unsigned entries) { + pa_pdispatch *pd; + + pa_assert(mainloop); + pa_assert((entries && table) || (!entries && !table)); + + pd = pa_xnew0(pa_pdispatch, 1); + PA_REFCNT_INIT(pd); + pd->mainloop = mainloop; + pd->callback_table = table; + pd->n_commands = entries; + PA_LLIST_HEAD_INIT(struct reply_info, pd->replies); + pd->use_rtclock = use_rtclock; + + return pd; +} + +static void pdispatch_free(pa_pdispatch *pd) { + pa_assert(pd); + + while (pd->replies) { + if (pd->replies->free_cb) + pd->replies->free_cb(pd->replies->userdata); + + reply_info_free(pd->replies); + } + + pa_xfree(pd); +} + +static void run_action(pa_pdispatch *pd, struct reply_info *r, uint32_t command, pa_tagstruct *ts) { + pa_pdispatch_cb_t callback; + void *userdata; + uint32_t tag; + pa_assert(r); + + pa_pdispatch_ref(pd); + + callback = r->callback; + userdata = r->userdata; + tag = r->tag; + + reply_info_free(r); + + callback(pd, command, tag, ts, userdata); + + if (pd->drain_callback && !pa_pdispatch_is_pending(pd)) + pd->drain_callback(pd, pd->drain_userdata); + + pa_pdispatch_unref(pd); +} + +int pa_pdispatch_run(pa_pdispatch *pd, pa_packet *packet, pa_cmsg_ancil_data *ancil_data, void *userdata) { + uint32_t tag, command; + pa_tagstruct *ts = NULL; + int ret = -1; + const void *pdata; + size_t plen; + + pa_assert(pd); + pa_assert(PA_REFCNT_VALUE(pd) >= 1); + pa_assert(packet); + + pa_pdispatch_ref(pd); + + pdata = pa_packet_data(packet, &plen); + if (plen <= 8) + goto finish; + + ts = pa_tagstruct_new_fixed(pdata, plen); + + if (pa_tagstruct_getu32(ts, &command) < 0 || + pa_tagstruct_getu32(ts, &tag) < 0) + goto finish; + +#ifdef DEBUG_OPCODES +{ + char t[256]; + char const *p = NULL; + + if (command >= PA_COMMAND_MAX || !(p = command_names[command])) + pa_snprintf((char*) (p = t), sizeof(t), "%u", command); + + pa_log("[%p] Received opcode <%s>", pd, p); +} +#endif + + pd->ancil_data = ancil_data; + + if (command == PA_COMMAND_ERROR || command == PA_COMMAND_REPLY) { + struct reply_info *r; + + PA_LLIST_FOREACH(r, pd->replies) + if (r->tag == tag) + break; + + if (r) + run_action(pd, r, command, ts); + + } else if (pd->callback_table && (command < pd->n_commands) && pd->callback_table[command]) { + const pa_pdispatch_cb_t *cb = pd->callback_table+command; + + (*cb)(pd, command, tag, ts, userdata); + } else { + pa_log("Received unsupported command %u", command); + goto finish; + } + + ret = 0; + +finish: + pd->ancil_data = NULL; + + if (ts) + pa_tagstruct_free(ts); + + pa_pdispatch_unref(pd); + + return ret; +} + +static void timeout_callback(pa_mainloop_api*m, pa_time_event*e, const struct timeval *t, void *userdata) { + struct reply_info*r = userdata; + + pa_assert(r); + pa_assert(r->time_event == e); + pa_assert(r->pdispatch); + pa_assert(r->pdispatch->mainloop == m); + pa_assert(r->callback); + + run_action(r->pdispatch, r, PA_COMMAND_TIMEOUT, NULL); +} + +void pa_pdispatch_register_reply(pa_pdispatch *pd, uint32_t tag, int timeout, pa_pdispatch_cb_t cb, void *userdata, pa_free_cb_t free_cb) { + struct reply_info *r; + struct timeval tv; + + pa_assert(pd); + pa_assert(PA_REFCNT_VALUE(pd) >= 1); + pa_assert(cb); + + if (!(r = pa_flist_pop(PA_STATIC_FLIST_GET(reply_infos)))) + r = pa_xnew(struct reply_info, 1); + + r->pdispatch = pd; + r->callback = cb; + r->userdata = userdata; + r->free_cb = free_cb; + r->tag = tag; + + pa_assert_se(r->time_event = pd->mainloop->time_new(pd->mainloop, + pa_timeval_rtstore(&tv, pa_rtclock_now() + timeout * PA_USEC_PER_SEC, pd->use_rtclock), + timeout_callback, r)); + + PA_LLIST_PREPEND(struct reply_info, pd->replies, r); +} + +int pa_pdispatch_is_pending(pa_pdispatch *pd) { + pa_assert(pd); + pa_assert(PA_REFCNT_VALUE(pd) >= 1); + + return !!pd->replies; +} + +void pa_pdispatch_set_drain_callback(pa_pdispatch *pd, pa_pdispatch_drain_cb_t cb, void *userdata) { + pa_assert(pd); + pa_assert(PA_REFCNT_VALUE(pd) >= 1); + pa_assert(!cb || pa_pdispatch_is_pending(pd)); + + pd->drain_callback = cb; + pd->drain_userdata = userdata; +} + +void pa_pdispatch_unregister_reply(pa_pdispatch *pd, void *userdata) { + struct reply_info *r, *n; + + pa_assert(pd); + pa_assert(PA_REFCNT_VALUE(pd) >= 1); + + PA_LLIST_FOREACH_SAFE(r, n, pd->replies) + if (r->userdata == userdata) + reply_info_free(r); +} + +void pa_pdispatch_unref(pa_pdispatch *pd) { + pa_assert(pd); + pa_assert(PA_REFCNT_VALUE(pd) >= 1); + + if (PA_REFCNT_DEC(pd) <= 0) + pdispatch_free(pd); +} + +pa_pdispatch* pa_pdispatch_ref(pa_pdispatch *pd) { + pa_assert(pd); + pa_assert(PA_REFCNT_VALUE(pd) >= 1); + + PA_REFCNT_INC(pd); + return pd; +} + +#ifdef HAVE_CREDS + +const pa_creds * pa_pdispatch_creds(pa_pdispatch *pd) { + pa_assert(pd); + pa_assert(PA_REFCNT_VALUE(pd) >= 1); + + if (pd->ancil_data && pd->ancil_data->creds_valid) + return &pd->ancil_data->creds; + return NULL; +} + +/* Should be called only once during the dispatcher lifetime + * + * If the returned ancillary data contains any fds, caller maintains sole + * responsibility of closing them down using pa_cmsg_ancil_data_close_fds() */ +pa_cmsg_ancil_data *pa_pdispatch_take_ancil_data(pa_pdispatch *pd) { + pa_cmsg_ancil_data *ancil; + + pa_assert(pd); + pa_assert(PA_REFCNT_VALUE(pd) >= 1); + + ancil = pd->ancil_data; + + /* iochannel guarantees us that nfd will always be capped */ + if (ancil) + pa_assert(ancil->nfd <= MAX_ANCIL_DATA_FDS); + + pd->ancil_data = NULL; + return ancil; +} + +#endif diff --git a/src/pulsecore/pdispatch.h b/src/pulsecore/pdispatch.h new file mode 100644 index 0000000..af76981 --- /dev/null +++ b/src/pulsecore/pdispatch.h @@ -0,0 +1,56 @@ +#ifndef foopdispatchhfoo +#define foopdispatchhfoo + +/*** + 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <inttypes.h> + +#include <pulse/mainloop-api.h> +#include <pulse/def.h> + +#include <pulsecore/tagstruct.h> +#include <pulsecore/packet.h> +#include <pulsecore/creds.h> + +typedef struct pa_pdispatch pa_pdispatch; + +typedef void (*pa_pdispatch_cb_t)(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +typedef void (*pa_pdispatch_drain_cb_t)(pa_pdispatch *pd, void *userdata); + +pa_pdispatch* pa_pdispatch_new(pa_mainloop_api *m, bool use_rtclock, const pa_pdispatch_cb_t *table, unsigned entries); +void pa_pdispatch_unref(pa_pdispatch *pd); +pa_pdispatch* pa_pdispatch_ref(pa_pdispatch *pd); + +int pa_pdispatch_run(pa_pdispatch *pd, pa_packet *p, pa_cmsg_ancil_data *ancil_data, void *userdata); + +void pa_pdispatch_register_reply(pa_pdispatch *pd, uint32_t tag, int timeout, pa_pdispatch_cb_t callback, void *userdata, pa_free_cb_t free_cb); + +int pa_pdispatch_is_pending(pa_pdispatch *pd); + +void pa_pdispatch_set_drain_callback(pa_pdispatch *pd, pa_pdispatch_drain_cb_t callback, void *userdata); + +/* Remove all reply slots with the give userdata parameter */ +void pa_pdispatch_unregister_reply(pa_pdispatch *pd, void *userdata); + +const pa_creds * pa_pdispatch_creds(pa_pdispatch *pd); +pa_cmsg_ancil_data *pa_pdispatch_take_ancil_data(pa_pdispatch *pd); + +#endif diff --git a/src/pulsecore/pid.c b/src/pulsecore/pid.c new file mode 100644 index 0000000..8749b42 --- /dev/null +++ b/src/pulsecore/pid.c @@ -0,0 +1,398 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <limits.h> +#include <signal.h> + +#ifdef HAVE_WINDOWS_H +#include <windows.h> +#endif + +#include <pulse/xmalloc.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> + +#include "pid.h" + +/* Read the PID data from the file descriptor fd, and return it. If no + * pid could be read, return 0, on failure (pid_t) -1 */ +static pid_t read_pid(const char *fn, int fd) { + ssize_t r; + char t[20], *e; + uint32_t pid; + + pa_assert(fn); + pa_assert(fd >= 0); + + if ((r = pa_loop_read(fd, t, sizeof(t)-1, NULL)) < 0) { + pa_log_warn("Failed to read PID file '%s': %s", fn, pa_cstrerror(errno)); + return (pid_t) -1; + } + + if (r == 0) + return (pid_t) 0; + + t[r] = 0; + if ((e = strchr(t, '\n'))) + *e = 0; + + if (pa_atou(t, &pid) < 0) { + pa_log_warn("Failed to parse PID file '%s'", fn); + errno = EINVAL; + return (pid_t) -1; + } + + return (pid_t) pid; +} + +static int open_pid_file(const char *fn, int mode) { + int fd; + + pa_assert(fn); + + for (;;) { + struct stat st; + + if ((fd = pa_open_cloexec(fn, mode +#ifdef O_NOFOLLOW + |O_NOFOLLOW +#endif + , S_IRUSR|S_IWUSR + )) < 0) { + if (mode != O_RDONLY || errno != ENOENT) + pa_log_warn("Failed to open PID file '%s': %s", fn, pa_cstrerror(errno)); + goto fail; + } + + /* Try to lock the file. If that fails, go without */ + if (pa_lock_fd(fd, 1) < 0) + goto fail; + + if (fstat(fd, &st) < 0) { + pa_log_warn("Failed to fstat() PID file '%s': %s", fn, pa_cstrerror(errno)); + goto fail; + } + + /* Does the file still exist in the file system? When yes, we're done, otherwise restart */ + if (st.st_nlink >= 1) + break; + + if (pa_lock_fd(fd, 0) < 0) + goto fail; + + if (pa_close(fd) < 0) { + pa_log_warn("Failed to close file '%s': %s", fn, pa_cstrerror(errno)); + fd = -1; + goto fail; + } + } + + return fd; + +fail: + + if (fd >= 0) { + int saved_errno = errno; + pa_lock_fd(fd, 0); + pa_close(fd); + errno = saved_errno; + } + + return -1; +} + +static int proc_name_ours(pid_t pid, const char *procname) { +#ifdef __linux__ + char bn[PATH_MAX]; + FILE *f; + + pa_snprintf(bn, sizeof(bn), "/proc/%lu/stat", (unsigned long) pid); + + if (!(f = pa_fopen_cloexec(bn, "r"))) { + pa_log_info("Failed to open %s: %s", bn, pa_cstrerror(errno)); + return -1; + } else { + char *expected; + bool good; + char stored[64]; + + if (!(fgets(stored, sizeof(stored), f))) { + int saved_errno = feof(f) ? EINVAL : errno; + pa_log_info("Failed to read from %s: %s", bn, feof(f) ? "EOF" : pa_cstrerror(errno)); + fclose(f); + + errno = saved_errno; + return -1; + } + + fclose(f); + + expected = pa_sprintf_malloc("%lu (%s)", (unsigned long) pid, procname); + good = pa_startswith(stored, expected); + pa_xfree(expected); + +/*#if !defined(__OPTIMIZE__)*/ + if (!good) { + /* libtool likes to rename our binary names ... */ + expected = pa_sprintf_malloc("%lu (lt-%s)", (unsigned long) pid, procname); + good = pa_startswith(stored, expected); + pa_xfree(expected); + } +/*#endif*/ + + return good; + } +#else + + return 1; +#endif + +} + +/* Create a new PID file for the current process. */ +int pa_pid_file_create(const char *procname) { + int fd = -1; + int ret = -1; + char t[20]; + pid_t pid; + size_t l; + char *fn; + +#ifdef OS_IS_WIN32 + HANDLE process; +#endif + + if (!(fn = pa_runtime_path("pid"))) + goto fail; + + if ((fd = open_pid_file(fn, O_CREAT|O_RDWR)) < 0) + goto fail; + + if ((pid = read_pid(fn, fd)) == (pid_t) -1) + pa_log_warn("Corrupt PID file, overwriting."); + else if (pid > 0) { + int ours = 1; + +#ifdef OS_IS_WIN32 + if ((process = OpenProcess(PROCESS_QUERY_INFORMATION, false, pid)) != NULL) { + CloseHandle(process); +#else + if (kill(pid, 0) >= 0 || errno != ESRCH) { +#endif + + if (procname) + if ((ours = proc_name_ours(pid, procname)) < 0) { + pa_log_warn("Could not check to see if pid %lu is a pulseaudio process. " + "Assuming it is and the daemon is already running.", (unsigned long) pid); + goto fail; + } + + if (ours) { + pa_log("Daemon already running."); + ret = 1; + goto fail; + } + } + + pa_log_warn("Stale PID file, overwriting."); + } + + /* Overwrite the current PID file */ + if (lseek(fd, (off_t) 0, SEEK_SET) == (off_t) -1 || ftruncate(fd, (off_t) 0) < 0) { + pa_log("Failed to truncate PID file '%s': %s", fn, pa_cstrerror(errno)); + goto fail; + } + + pa_snprintf(t, sizeof(t), "%lu\n", (unsigned long) getpid()); + l = strlen(t); + + if (pa_loop_write(fd, t, l, NULL) != (ssize_t) l) { + pa_log("Failed to write PID file."); + goto fail; + } + + ret = 0; + +fail: + if (fd >= 0) { + pa_lock_fd(fd, 0); + + if (pa_close(fd) < 0) { + pa_log("Failed to close PID file '%s': %s", fn, pa_cstrerror(errno)); + ret = -1; + } + } + + pa_xfree(fn); + + return ret; +} + +/* Remove the PID file, if it is ours */ +int pa_pid_file_remove(void) { + int fd = -1; + char *fn; + int ret = -1; + pid_t pid; + + if (!(fn = pa_runtime_path("pid"))) + goto fail; + + if ((fd = open_pid_file(fn, O_RDWR)) < 0) { + pa_log_warn("Failed to open PID file '%s': %s", fn, pa_cstrerror(errno)); + goto fail; + } + + if ((pid = read_pid(fn, fd)) == (pid_t) -1) + goto fail; + + if (pid != getpid()) { + pa_log("PID file '%s' not mine!", fn); + goto fail; + } + + if (ftruncate(fd, (off_t) 0) < 0) { + pa_log_warn("Failed to truncate PID file '%s': %s", fn, pa_cstrerror(errno)); + goto fail; + } + +#ifdef OS_IS_WIN32 + pa_lock_fd(fd, 0); + pa_close(fd); + fd = -1; +#endif + + if (unlink(fn) < 0) { + pa_log_warn("Failed to remove PID file '%s': %s", fn, pa_cstrerror(errno)); + goto fail; + } + + ret = 0; + +fail: + + if (fd >= 0) { + pa_lock_fd(fd, 0); + + if (pa_close(fd) < 0) { + pa_log_warn("Failed to close PID file '%s': %s", fn, pa_cstrerror(errno)); + ret = -1; + } + } + + pa_xfree(fn); + + return ret; +} + +/* Check whether the daemon is currently running, i.e. if a PID file + * exists and the PID therein too. Returns 0 on success, -1 + * otherwise. If pid is non-NULL and a running daemon was found, + * return its PID therein */ +int pa_pid_file_check_running(pid_t *pid, const char *procname) { + return pa_pid_file_kill(0, pid, procname); +} + +#ifndef OS_IS_WIN32 + +/* Kill a current running daemon. Return non-zero on success, -1 + * otherwise. If successful *pid contains the PID of the daemon + * process. */ +int pa_pid_file_kill(int sig, pid_t *pid, const char *procname) { + int fd = -1; + char *fn; + int ret = -1; + pid_t _pid; +#ifdef __linux__ + char *e = NULL; +#endif + + if (!pid) + pid = &_pid; + + if (!(fn = pa_runtime_path("pid"))) + goto fail; + + if ((fd = open_pid_file(fn, O_RDONLY)) < 0) { + + if (errno == ENOENT) + errno = ESRCH; + + goto fail; + } + + if ((*pid = read_pid(fn, fd)) == (pid_t) -1) + goto fail; + + if (procname) { + int ours; + + if ((ours = proc_name_ours(*pid, procname)) < 0) + goto fail; + + if (!ours) { + errno = ESRCH; + goto fail; + } + } + + ret = kill(*pid, sig); + +fail: + + if (fd >= 0) { + int saved_errno = errno; + pa_lock_fd(fd, 0); + pa_close(fd); + errno = saved_errno; + } + +#ifdef __linux__ + pa_xfree(e); +#endif + + pa_xfree(fn); + + return ret; + +} + +#else /* OS_IS_WIN32 */ + +int pa_pid_file_kill(int sig, pid_t *pid, const char *exe_name) { + return -1; +} + +#endif diff --git a/src/pulsecore/pid.h b/src/pulsecore/pid.h new file mode 100644 index 0000000..ff0356b --- /dev/null +++ b/src/pulsecore/pid.h @@ -0,0 +1,28 @@ +#ifndef foopidhfoo +#define foopidhfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +int pa_pid_file_create(const char *procname); +int pa_pid_file_remove(void); +int pa_pid_file_check_running(pid_t *pid, const char *procname); +int pa_pid_file_kill(int sig, pid_t *pid, const char *procname); + +#endif diff --git a/src/pulsecore/pipe.c b/src/pulsecore/pipe.c new file mode 100644 index 0000000..9f86a48 --- /dev/null +++ b/src/pulsecore/pipe.c @@ -0,0 +1,155 @@ +/*** + This file is part of PulseAudio. + + 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 + by the Free Software Foundation; either version 2.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <unistd.h> +#include <fcntl.h> + +#include <sys/types.h> + +#include <pulsecore/socket.h> +#include <pulsecore/core-util.h> + +#include "pipe.h" + +#ifndef HAVE_PIPE + +static int set_block(int fd, int blocking) { +#ifdef O_NONBLOCK + + int v; + + pa_assert(fd >= 0); + + if ((v = fcntl(fd, F_GETFL)) < 0) + return -1; + + if (blocking) + v &= ~O_NONBLOCK; + else + v |= O_NONBLOCK; + + if (fcntl(fd, F_SETFL, v) < 0) + return -1; + + return 0; + +#elif defined(OS_IS_WIN32) + + u_long arg; + + arg = !blocking; + + if (ioctlsocket(fd, FIONBIO, &arg) < 0) + return -1; + + return 0; + +#else + + return -1; + +#endif +} + +int pipe(int filedes[2]) { + int listener; + struct sockaddr_in addr, peer; + socklen_t len; + + listener = -1; + filedes[0] = -1; + filedes[1] = -1; + + listener = socket(PF_INET, SOCK_STREAM, 0); + if (listener < 0) + goto error; + + filedes[0] = socket(PF_INET, SOCK_STREAM, 0); + if (filedes[0] < 0) + goto error; + + filedes[1] = socket(PF_INET, SOCK_STREAM, 0); + if (filedes[1] < 0) + goto error; + + /* Make non-blocking so that connect() won't block */ + if (set_block(filedes[0], 0) < 0) + goto error; + + addr.sin_family = AF_INET; + addr.sin_port = 0; + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + if (bind(listener, (struct sockaddr*)&addr, sizeof(addr)) < 0) + goto error; + + if (listen(listener, 1) < 0) + goto error; + + len = sizeof(addr); + if (getsockname(listener, (struct sockaddr*)&addr, &len) < 0) + goto error; + + if (connect(filedes[0], (struct sockaddr*)&addr, sizeof(addr)) < 0) { +#ifdef OS_IS_WIN32 + if (WSAGetLastError() != EWOULDBLOCK) +#else + if (errno != EINPROGRESS) +#endif + goto error; + } + + len = sizeof(peer); + filedes[1] = accept(listener, (struct sockaddr*)&peer, &len); + if (filedes[1] < 0) + goto error; + + /* Restore blocking */ + if (set_block(filedes[0], 1) < 0) + goto error; + + len = sizeof(addr); + if (getsockname(filedes[0], (struct sockaddr*)&addr, &len) < 0) + goto error; + + /* Check that someone else didn't steal the connection */ + if ((addr.sin_port != peer.sin_port) || (addr.sin_addr.s_addr != peer.sin_addr.s_addr)) + goto error; + + pa_close(listener); + + return 0; + +error: + if (listener >= 0) + pa_close(listener); + if (filedes[0] >= 0) + pa_close(filedes[0]); + if (filedes[1] >= 0) + pa_close(filedes[1]); + + return -1; +} + +#endif /* HAVE_PIPE */ diff --git a/src/pulsecore/pipe.h b/src/pulsecore/pipe.h new file mode 100644 index 0000000..54e9819 --- /dev/null +++ b/src/pulsecore/pipe.h @@ -0,0 +1,29 @@ +#ifndef foopipehfoo +#define foopipehfoo + +/*** + This file is part of PulseAudio. + + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifndef HAVE_PIPE + +int pipe(int filedes[2]); + +#endif + +#endif diff --git a/src/pulsecore/play-memblockq.c b/src/pulsecore/play-memblockq.c new file mode 100644 index 0000000..da586a8 --- /dev/null +++ b/src/pulsecore/play-memblockq.c @@ -0,0 +1,283 @@ +/*** + This file is part of PulseAudio. + + Copyright 2006-2008 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <stdio.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/sink-input.h> +#include <pulsecore/thread-mq.h> + +#include "play-memblockq.h" + +typedef struct memblockq_stream { + pa_msgobject parent; + pa_core *core; + pa_sink_input *sink_input; + pa_memblockq *memblockq; +} memblockq_stream; + +enum { + MEMBLOCKQ_STREAM_MESSAGE_UNLINK, +}; + +PA_DEFINE_PRIVATE_CLASS(memblockq_stream, pa_msgobject); +#define MEMBLOCKQ_STREAM(o) (memblockq_stream_cast(o)) + +static void memblockq_stream_unlink(memblockq_stream *u) { + pa_assert(u); + + if (!u->sink_input) + return; + + pa_sink_input_unlink(u->sink_input); + pa_sink_input_unref(u->sink_input); + u->sink_input = NULL; + + memblockq_stream_unref(u); +} + +static void memblockq_stream_free(pa_object *o) { + memblockq_stream *u = MEMBLOCKQ_STREAM(o); + pa_assert(u); + + if (u->memblockq) + pa_memblockq_free(u->memblockq); + + pa_xfree(u); +} + +static int memblockq_stream_process_msg(pa_msgobject *o, int code, void*userdata, int64_t offset, pa_memchunk *chunk) { + memblockq_stream *u = MEMBLOCKQ_STREAM(o); + memblockq_stream_assert_ref(u); + + switch (code) { + case MEMBLOCKQ_STREAM_MESSAGE_UNLINK: + memblockq_stream_unlink(u); + break; + } + + return 0; +} + +static void sink_input_kill_cb(pa_sink_input *i) { + memblockq_stream *u; + + pa_sink_input_assert_ref(i); + u = MEMBLOCKQ_STREAM(i->userdata); + memblockq_stream_assert_ref(u); + + memblockq_stream_unlink(u); +} + +/* Called from IO thread context */ +static void sink_input_state_change_cb(pa_sink_input *i, pa_sink_input_state_t state) { + memblockq_stream *u; + + pa_sink_input_assert_ref(i); + u = MEMBLOCKQ_STREAM(i->userdata); + memblockq_stream_assert_ref(u); + + /* If we are added for the first time, ask for a rewinding so that + * we are heard right-away. */ + if (PA_SINK_INPUT_IS_LINKED(state) && + i->thread_info.state == PA_SINK_INPUT_INIT && i->sink) + pa_sink_input_request_rewind(i, 0, false, true, true); +} + +static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk) { + memblockq_stream *u; + + pa_sink_input_assert_ref(i); + pa_assert(chunk); + u = MEMBLOCKQ_STREAM(i->userdata); + memblockq_stream_assert_ref(u); + + if (!u->memblockq) + return -1; + + if (pa_memblockq_peek(u->memblockq, chunk) < 0) { + + if (pa_sink_input_safe_to_remove(i)) { + + pa_memblockq_free(u->memblockq); + u->memblockq = NULL; + + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(u), MEMBLOCKQ_STREAM_MESSAGE_UNLINK, NULL, 0, NULL, NULL); + } + + return -1; + } + + /* If there's no memblock, there's going to be data in the memblockq after + * a gap with length chunk->length. Drop the gap and peek the actual + * data. There should always be some data coming - hence the assert. The + * gap will occur if the memblockq is rewound beyond index 0.*/ + if (!chunk->memblock) { + pa_memblockq_drop(u->memblockq, chunk->length); + pa_assert_se(pa_memblockq_peek(u->memblockq, chunk) >= 0); + } + + chunk->length = PA_MIN(chunk->length, nbytes); + pa_memblockq_drop(u->memblockq, chunk->length); + + return 0; +} + +static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) { + memblockq_stream *u; + + pa_sink_input_assert_ref(i); + u = MEMBLOCKQ_STREAM(i->userdata); + memblockq_stream_assert_ref(u); + + if (!u->memblockq) + return; + + pa_memblockq_rewind(u->memblockq, nbytes); +} + +static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) { + memblockq_stream *u; + + pa_sink_input_assert_ref(i); + u = MEMBLOCKQ_STREAM(i->userdata); + memblockq_stream_assert_ref(u); + + if (!u->memblockq) + return; + + pa_memblockq_set_maxrewind(u->memblockq, nbytes); +} + +pa_sink_input* pa_memblockq_sink_input_new( + pa_sink *sink, + const pa_sample_spec *ss, + const pa_channel_map *map, + pa_memblockq *q, + pa_cvolume *volume, + pa_proplist *p, + pa_sink_input_flags_t flags) { + + memblockq_stream *u = NULL; + pa_sink_input_new_data data; + + pa_assert(sink); + pa_assert(ss); + + /* We allow creating this stream with no q set, so that it can be + * filled in later */ + + u = pa_msgobject_new(memblockq_stream); + u->parent.parent.free = memblockq_stream_free; + u->parent.process_msg = memblockq_stream_process_msg; + u->core = sink->core; + u->sink_input = NULL; + u->memblockq = NULL; + + pa_sink_input_new_data_init(&data); + pa_sink_input_new_data_set_sink(&data, sink, false, true); + data.driver = __FILE__; + pa_sink_input_new_data_set_sample_spec(&data, ss); + pa_sink_input_new_data_set_channel_map(&data, map); + pa_sink_input_new_data_set_volume(&data, volume); + pa_proplist_update(data.proplist, PA_UPDATE_REPLACE, p); + data.flags |= flags; + + pa_sink_input_new(&u->sink_input, sink->core, &data); + pa_sink_input_new_data_done(&data); + + if (!u->sink_input) + goto fail; + + u->sink_input->pop = sink_input_pop_cb; + u->sink_input->process_rewind = sink_input_process_rewind_cb; + u->sink_input->update_max_rewind = sink_input_update_max_rewind_cb; + u->sink_input->kill = sink_input_kill_cb; + u->sink_input->state_change = sink_input_state_change_cb; + u->sink_input->userdata = u; + + if (q) + pa_memblockq_sink_input_set_queue(u->sink_input, q); + + /* The reference to u is dangling here, because we want + * to keep this stream around until it is fully played. */ + + /* This sink input is not "put" yet, i.e. pa_sink_input_put() has + * not been called! */ + + return pa_sink_input_ref(u->sink_input); + +fail: + if (u) + memblockq_stream_unref(u); + + return NULL; +} + +int pa_play_memblockq( + pa_sink *sink, + const pa_sample_spec *ss, + const pa_channel_map *map, + pa_memblockq *q, + pa_cvolume *volume, + pa_proplist *p, + pa_sink_input_flags_t flags, + uint32_t *sink_input_index) { + + pa_sink_input *i; + + pa_assert(sink); + pa_assert(ss); + pa_assert(q); + + if (!(i = pa_memblockq_sink_input_new(sink, ss, map, q, volume, p, flags))) + return -1; + + pa_sink_input_put(i); + + if (sink_input_index) + *sink_input_index = i->index; + + pa_sink_input_unref(i); + + return 0; +} + +void pa_memblockq_sink_input_set_queue(pa_sink_input *i, pa_memblockq *q) { + memblockq_stream *u; + + pa_sink_input_assert_ref(i); + u = MEMBLOCKQ_STREAM(i->userdata); + memblockq_stream_assert_ref(u); + + if (u->memblockq) + pa_memblockq_free(u->memblockq); + + if ((u->memblockq = q)) { + pa_memblockq_set_prebuf(q, 0); + pa_memblockq_set_silence(q, NULL); + pa_memblockq_willneed(q); + } +} diff --git a/src/pulsecore/play-memblockq.h b/src/pulsecore/play-memblockq.h new file mode 100644 index 0000000..c13aced --- /dev/null +++ b/src/pulsecore/play-memblockq.h @@ -0,0 +1,47 @@ +#ifndef fooplaymemblockqhfoo +#define fooplaymemblockqhfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulsecore/sink.h> +#include <pulsecore/memblockq.h> + +pa_sink_input* pa_memblockq_sink_input_new( + pa_sink *sink, + const pa_sample_spec *ss, + const pa_channel_map *map, + pa_memblockq *q, + pa_cvolume *volume, + pa_proplist *p, + pa_sink_input_flags_t flags); + +void pa_memblockq_sink_input_set_queue(pa_sink_input *i, pa_memblockq *q); + +int pa_play_memblockq( + pa_sink *sink, + const pa_sample_spec *ss, + const pa_channel_map *map, + pa_memblockq *q, + pa_cvolume *cvolume, + pa_proplist *p, + pa_sink_input_flags_t flags, + uint32_t *sink_input_index); + +#endif diff --git a/src/pulsecore/play-memchunk.c b/src/pulsecore/play-memchunk.c new file mode 100644 index 0000000..cc0bc16 --- /dev/null +++ b/src/pulsecore/play-memchunk.c @@ -0,0 +1,62 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2008 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <stdio.h> + +#include <pulsecore/sink-input.h> +#include <pulsecore/play-memblockq.h> + +#include "play-memchunk.h" + +int pa_play_memchunk( + pa_sink *sink, + const pa_sample_spec *ss, + const pa_channel_map *map, + const pa_memchunk *chunk, + pa_cvolume *volume, + pa_proplist *p, + pa_sink_input_flags_t flags, + uint32_t *sink_input_index) { + + pa_memblockq *q; + int r; + pa_memchunk silence; + + pa_assert(sink); + pa_assert(ss); + pa_assert(chunk); + + pa_silence_memchunk_get(&sink->core->silence_cache, sink->core->mempool, &silence, ss, 0); + q = pa_memblockq_new("pa_play_memchunk() q", 0, chunk->length, 0, ss, 1, 1, 0, &silence); + pa_memblock_unref(silence.memblock); + + pa_assert_se(pa_memblockq_push(q, chunk) >= 0); + + if ((r = pa_play_memblockq(sink, ss, map, q, volume, p, flags, sink_input_index)) < 0) { + pa_memblockq_free(q); + return r; + } + + return 0; +} diff --git a/src/pulsecore/play-memchunk.h b/src/pulsecore/play-memchunk.h new file mode 100644 index 0000000..7c56fe3 --- /dev/null +++ b/src/pulsecore/play-memchunk.h @@ -0,0 +1,36 @@ +#ifndef fooplaychunkhfoo +#define fooplaychunkhfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulsecore/sink.h> +#include <pulsecore/memchunk.h> + +int pa_play_memchunk( + pa_sink *sink, + const pa_sample_spec *ss, + const pa_channel_map *map, + const pa_memchunk *chunk, + pa_cvolume *cvolume, + pa_proplist *p, + pa_sink_input_flags_t flags, + uint32_t *sink_input_index); + +#endif diff --git a/src/pulsecore/poll-posix.c b/src/pulsecore/poll-posix.c new file mode 100644 index 0000000..e232295 --- /dev/null +++ b/src/pulsecore/poll-posix.c @@ -0,0 +1,235 @@ + +/*** + This file is part of PulseAudio. + + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +/*** + Based on work for the GNU C Library. + Copyright (C) 1994, 1996, 1997 Free Software Foundation, Inc. +***/ + +/* Poll the file descriptors described by the NFDS structures starting at + FDS. If TIMEOUT is nonzero and not -1, allow TIMEOUT milliseconds for + an event to occur; if TIMEOUT is -1, block until an event occurs. + Returns the number of file descriptors with events, zero if timed out, + or -1 for errors. */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#ifdef HAVE_SYS_IOCTL_H +#include <sys/ioctl.h> +#endif + +#include <errno.h> +#include <fcntl.h> + +#ifdef HAVE_SYS_SELECT_H +#include <sys/select.h> +#endif + +#include <pulsecore/socket.h> +#include <pulsecore/core-util.h> +#include <pulse/util.h> + +#include "poll.h" + +/* Mac OSX fails to implement poll() in a working way since 10.4. IOW, for + * several years. We need to enable a dirty workaround and emulate that call + * with select(), just like for Windows. sic! */ + +#if !defined(HAVE_POLL_H) || defined(OS_IS_DARWIN) + +int pa_poll (struct pollfd *fds, unsigned long int nfds, int timeout) { + struct timeval tv; + fd_set rset, wset, xset; + struct pollfd *f; + int ready; + int maxfd = 0; +#ifdef OS_IS_WIN32 + char data[64]; +#endif + + FD_ZERO (&rset); + FD_ZERO (&wset); + FD_ZERO (&xset); + + if (nfds == 0) { + if (timeout >= 0) { + pa_msleep(timeout); + return 0; + } + +#ifdef OS_IS_WIN32 + /* + * Windows does not support signals properly so waiting for them would + * mean a deadlock. + */ + pa_msleep(100); + return 0; +#else + return select(0, NULL, NULL, NULL, NULL); +#endif + } + + for (f = fds; f < &fds[nfds]; ++f) { + if (f->fd != -1) { + if (f->events & POLLIN) + FD_SET (f->fd, &rset); + if (f->events & POLLOUT) + FD_SET (f->fd, &wset); + if (f->events & POLLPRI) + FD_SET (f->fd, &xset); + if (f->fd > maxfd && (f->events & (POLLIN|POLLOUT|POLLPRI))) + maxfd = f->fd; + } + } + + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + + ready = select(maxfd + 1, &rset, &wset, &xset, (timeout == -1 ? NULL : &tv)); + + if ((ready == -1) && (errno == EBADF)) { + ready = 0; + maxfd = -1; + +#ifdef OS_IS_WIN32 + /* + * Windows has no fcntl(), so we have to trick around with more + * select() calls to find out what went wrong + */ + + FD_ZERO (&rset); + FD_ZERO (&wset); + FD_ZERO (&xset); + + for (f = fds; f < &fds[nfds]; ++f) { + if (f->fd != -1) { + fd_set sngl_rset, sngl_wset, sngl_xset; + + FD_ZERO (&sngl_rset); + FD_ZERO (&sngl_wset); + FD_ZERO (&sngl_xset); + + if (f->events & POLLIN) + FD_SET (f->fd, &sngl_rset); + if (f->events & POLLOUT) + FD_SET (f->fd, &sngl_wset); + if (f->events & POLLPRI) + FD_SET (f->fd, &sngl_xset); + if (f->events & (POLLIN|POLLOUT|POLLPRI)) { + struct timeval singl_tv; + + singl_tv.tv_sec = 0; + singl_tv.tv_usec = 0; + + if (select(f->fd, &rset, &wset, &xset, &singl_tv) != -1) { + if (f->events & POLLIN) + FD_SET (f->fd, &rset); + if (f->events & POLLOUT) + FD_SET (f->fd, &wset); + if (f->events & POLLPRI) + FD_SET (f->fd, &xset); + if (f->fd > maxfd && (f->events & (POLLIN|POLLOUT|POLLPRI))) + maxfd = f->fd; + ++ready; + } else if (errno == EBADF) + f->revents |= POLLNVAL; + } + } + } + +#else /* !OS_IS_WIN32 */ + + for (f = fds; f < &fds[nfds]; f++) + if (f->fd != -1) { + /* use fcntl() to find out whether the descriptor is valid */ + if (fcntl(f->fd, F_GETFL) != -1) { + if (f->fd > maxfd && (f->events & (POLLIN|POLLOUT|POLLPRI))) { + maxfd = f->fd; + ready++; + } + } else { + FD_CLR(f->fd, &rset); + FD_CLR(f->fd, &wset); + FD_CLR(f->fd, &xset); + } + } + +#endif + + if (ready) { + /* Linux alters the tv struct... but it shouldn't matter here ... + * as we're going to be a little bit out anyway as we've just eaten + * more than a couple of cpu cycles above */ + ready = select(maxfd + 1, &rset, &wset, &xset, (timeout == -1 ? NULL : &tv)); + } + } + +#ifdef OS_IS_WIN32 + errno = WSAGetLastError(); +#endif + + if (ready > 0) { + ready = 0; + for (f = fds; f < &fds[nfds]; ++f) { + f->revents = 0; + if (f->fd != -1) { + if (FD_ISSET (f->fd, &rset)) { + /* support for POLLHUP. An hung up descriptor does not + increase the return value! */ +#ifdef OS_IS_DARWIN + /* There is a bug in Mac OS X that causes it to ignore MSG_PEEK + * for some kinds of descriptors. Detect if this descriptor is a + * connected socket, a server socket, or something else using a + * 0-byte recv, and use ioctl(2) to detect POLLHUP. */ + int r = recv(f->fd, NULL, 0, MSG_PEEK); + if (r == 0 || (r < 0 && errno == ENOTSOCK)) + ioctl(f->fd, FIONREAD, &r); + + if (r == 0) + f->revents |= POLLHUP; +#else /* !OS_IS_DARWIN */ + if (recv (f->fd, data, 64, MSG_PEEK) == -1) { + if (errno == ESHUTDOWN || errno == ECONNRESET || + errno == ECONNABORTED || errno == ENETRESET) { + fprintf(stderr, "Hangup\n"); + f->revents |= POLLHUP; + } + } +#endif + + if (f->revents == 0) + f->revents |= POLLIN; + } + if (FD_ISSET (f->fd, &wset)) + f->revents |= POLLOUT; + if (FD_ISSET (f->fd, &xset)) + f->revents |= POLLPRI; + } + if (f->revents) + ready++; + } + } + + return ready; +} + +#endif /* HAVE_SYS_POLL_H */ diff --git a/src/pulsecore/poll-win32.c b/src/pulsecore/poll-win32.c new file mode 100644 index 0000000..c927e53 --- /dev/null +++ b/src/pulsecore/poll-win32.c @@ -0,0 +1,646 @@ + +/* Emulation for poll(2) + Contributed by Paolo Bonzini. + + Copyright 2001-2003, 2006-2012 Free Software Foundation, Inc. + + This file is part of gnulib. + + This program 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, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License along + with this program; if not, see <http://www.gnu.org/licenses/>. */ + +/* Tell gcc not to warn about the (nfd < 0) tests, below. */ +#if (__GNUC__ == 4 && 3 <= __GNUC_MINOR__) || 4 < __GNUC__ +# pragma GCC diagnostic ignored "-Wtype-limits" +#endif + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif +#include <malloc.h> + +#include <sys/types.h> + +/* Specification. */ +#include "poll.h" +typedef unsigned long nfds_t; + +#include <errno.h> +#include <limits.h> +#include <assert.h> + +#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__ +# define WINDOWS_NATIVE +# include <winsock2.h> +# include <windows.h> +# include <io.h> +# include <stdio.h> +# include <conio.h> +# include <signal.h> +# if 0 +# include "msvc-nothrow.h" +# endif +#else +# include <sys/time.h> +# include <sys/socket.h> +# include <sys/select.h> +# include <unistd.h> +#endif + +#ifdef HAVE_SYS_IOCTL_H +# include <sys/ioctl.h> +#endif +#ifdef HAVE_SYS_FILIO_H +# include <sys/filio.h> +#endif + +#include <time.h> + +#include <pulsecore/core-util.h> + +#ifndef INFTIM +# define INFTIM (-1) +#endif + +/* BeOS does not have MSG_PEEK. */ +#ifndef MSG_PEEK +# define MSG_PEEK 0 +#endif + +#ifndef POLLRDNORM +# define POLLRDNORM 0 +# define POLLRDBAND 0 +# define POLLWRNORM 0 +# define POLLWRBAND 0 +#endif + +#ifdef WINDOWS_NATIVE + +/* Optimized test whether a HANDLE refers to a console. + See <http://lists.gnu.org/archive/html/bug-gnulib/2009-08/msg00065.html>. */ +#define IsConsoleHandle(h) (((intptr_t) (h) & 3) == 3) + +static BOOL +IsSocketHandle (HANDLE h) +{ + WSANETWORKEVENTS ev; + + if (IsConsoleHandle (h)) + return FALSE; + + /* Under Wine, it seems that getsockopt returns 0 for pipes too. + WSAEnumNetworkEvents instead distinguishes the two correctly. */ + ev.lNetworkEvents = 0xDEADBEEFl; + WSAEnumNetworkEvents ((SOCKET) h, NULL, &ev); + return ev.lNetworkEvents != 0xDEADBEEFl; +} + +static HANDLE +HandleFromFd (int fd) +{ + /* since socket() returns a HANDLE already, try that first */ + if (IsSocketHandle((HANDLE) fd)) + return ((HANDLE) fd); + + return ((HANDLE) _get_osfhandle(fd)); +} + +/* Declare data structures for ntdll functions. */ +typedef struct _FILE_PIPE_LOCAL_INFORMATION { + ULONG NamedPipeType; + ULONG NamedPipeConfiguration; + ULONG MaximumInstances; + ULONG CurrentInstances; + ULONG InboundQuota; + ULONG ReadDataAvailable; + ULONG OutboundQuota; + ULONG WriteQuotaAvailable; + ULONG NamedPipeState; + ULONG NamedPipeEnd; +} FILE_PIPE_LOCAL_INFORMATION, *PFILE_PIPE_LOCAL_INFORMATION; + +typedef struct _IO_STATUS_BLOCK +{ + union { + DWORD Status; + PVOID Pointer; + } u; + ULONG_PTR Information; +} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK; + +typedef enum _FILE_INFORMATION_CLASS { + FilePipeLocalInformation = 24 +} FILE_INFORMATION_CLASS, *PFILE_INFORMATION_CLASS; + +typedef DWORD (WINAPI *PNtQueryInformationFile) + (HANDLE, IO_STATUS_BLOCK *, VOID *, ULONG, FILE_INFORMATION_CLASS); + +# ifndef PIPE_BUF +# define PIPE_BUF 512 +# endif + +/* Compute revents values for file handle H. If some events cannot happen + for the handle, eliminate them from *P_SOUGHT. */ + +static int +windows_compute_revents (HANDLE h, int *p_sought) +{ + int i, ret, happened; + INPUT_RECORD *irbuffer; + DWORD avail, nbuffer; + BOOL bRet; + IO_STATUS_BLOCK iosb; + FILE_PIPE_LOCAL_INFORMATION fpli; + static PNtQueryInformationFile NtQueryInformationFile; + static BOOL once_only; + + switch (GetFileType (h)) + { + case FILE_TYPE_PIPE: + if (!once_only) + { + NtQueryInformationFile = (PNtQueryInformationFile) + GetProcAddress (GetModuleHandle ("ntdll.dll"), + "NtQueryInformationFile"); + once_only = TRUE; + } + + happened = 0; + if (PeekNamedPipe (h, NULL, 0, NULL, &avail, NULL) != 0) + { + if (avail) + happened |= *p_sought & (POLLIN | POLLRDNORM); + } + else if (GetLastError () == ERROR_BROKEN_PIPE) + happened |= POLLHUP; + + else + { + /* It was the write-end of the pipe. Check if it is writable. + If NtQueryInformationFile fails, optimistically assume the pipe is + writable. This could happen on Windows 9x, where + NtQueryInformationFile is not available, or if we inherit a pipe + that doesn't permit FILE_READ_ATTRIBUTES access on the write end + (I think this should not happen since Windows XP SP2; WINE seems + fine too). Otherwise, ensure that enough space is available for + atomic writes. */ + memset (&iosb, 0, sizeof (iosb)); + memset (&fpli, 0, sizeof (fpli)); + + if (!NtQueryInformationFile + || NtQueryInformationFile (h, &iosb, &fpli, sizeof (fpli), + FilePipeLocalInformation) + || fpli.WriteQuotaAvailable >= PIPE_BUF + || (fpli.OutboundQuota < PIPE_BUF && + fpli.WriteQuotaAvailable == fpli.OutboundQuota)) + happened |= *p_sought & (POLLOUT | POLLWRNORM | POLLWRBAND); + } + return happened; + + case FILE_TYPE_CHAR: + ret = WaitForSingleObject (h, 0); + if (!IsConsoleHandle (h)) + return ret == WAIT_OBJECT_0 ? *p_sought & ~(POLLPRI | POLLRDBAND) : 0; + + nbuffer = avail = 0; + bRet = GetNumberOfConsoleInputEvents (h, &nbuffer); + if (bRet) + { + /* Input buffer. */ + *p_sought &= POLLIN | POLLRDNORM; + if (nbuffer == 0) + return POLLHUP; + if (!*p_sought) + return 0; + + irbuffer = (INPUT_RECORD *) alloca (nbuffer * sizeof (INPUT_RECORD)); + bRet = PeekConsoleInput (h, irbuffer, nbuffer, &avail); + if (!bRet || avail == 0) + return POLLHUP; + + for (i = 0; i < avail; i++) + if (irbuffer[i].EventType == KEY_EVENT) + return *p_sought; + return 0; + } + else + { + /* Screen buffer. */ + *p_sought &= POLLOUT | POLLWRNORM | POLLWRBAND; + return *p_sought; + } + + default: + ret = WaitForSingleObject (h, 0); + if (ret == WAIT_OBJECT_0) + return *p_sought & ~(POLLPRI | POLLRDBAND); + + return *p_sought & (POLLOUT | POLLWRNORM | POLLWRBAND); + } +} + +/* Convert fd_sets returned by select into revents values. */ + +static int +windows_compute_revents_socket (SOCKET h, int sought, long lNetworkEvents) +{ + int happened = 0; + + if ((lNetworkEvents & (FD_READ | FD_ACCEPT | FD_CLOSE)) == FD_ACCEPT) + happened |= (POLLIN | POLLRDNORM) & sought; + + else if (lNetworkEvents & (FD_READ | FD_ACCEPT | FD_CLOSE)) + { + int r, error; + + char data[64]; + WSASetLastError (0); + r = recv (h, data, sizeof (data), MSG_PEEK); + error = WSAGetLastError (); + WSASetLastError (0); + + if (r > 0 || error == WSAENOTCONN) + happened |= (POLLIN | POLLRDNORM) & sought; + + /* Distinguish hung-up sockets from other errors. */ + else if (r == 0 || error == WSAESHUTDOWN || error == WSAECONNRESET + || error == WSAECONNABORTED || error == WSAENETRESET) + happened |= POLLHUP; + + else + happened |= POLLERR; + } + + if (lNetworkEvents & (FD_WRITE | FD_CONNECT)) + happened |= (POLLOUT | POLLWRNORM | POLLWRBAND) & sought; + + if (lNetworkEvents & FD_OOB) + happened |= (POLLPRI | POLLRDBAND) & sought; + + return happened; +} + +#else /* !MinGW */ + +/* Convert select(2) returned fd_sets into poll(2) revents values. */ +static int +compute_revents (int fd, int sought, fd_set *rfds, fd_set *wfds, fd_set *efds) +{ + int happened = 0; + if (FD_ISSET (fd, rfds)) + { + int r; + int socket_errno; + +# if defined __MACH__ && defined __APPLE__ + /* There is a bug in Mac OS X that causes it to ignore MSG_PEEK + for some kinds of descriptors. Detect if this descriptor is a + connected socket, a server socket, or something else using a + 0-byte recv, and use ioctl(2) to detect POLLHUP. */ + r = recv (fd, NULL, 0, MSG_PEEK); + socket_errno = (r < 0) ? errno : 0; + if (r == 0 || socket_errno == ENOTSOCK) + ioctl (fd, FIONREAD, &r); +# else + char data[64]; + r = recv (fd, data, sizeof (data), MSG_PEEK); + socket_errno = (r < 0) ? errno : 0; +# endif + if (r == 0) + happened |= POLLHUP; + + /* If the event happened on an unconnected server socket, + that's fine. */ + else if (r > 0 || ( /* (r == -1) && */ socket_errno == ENOTCONN)) + happened |= (POLLIN | POLLRDNORM) & sought; + + /* Distinguish hung-up sockets from other errors. */ + else if (socket_errno == ESHUTDOWN || socket_errno == ECONNRESET + || socket_errno == ECONNABORTED || socket_errno == ENETRESET) + happened |= POLLHUP; + + /* some systems can't use recv() on non-socket, including HP NonStop */ + else if (socket_errno == ENOTSOCK) + happened |= (POLLIN | POLLRDNORM) & sought; + + else + happened |= POLLERR; + } + + if (FD_ISSET (fd, wfds)) + happened |= (POLLOUT | POLLWRNORM | POLLWRBAND) & sought; + + if (FD_ISSET (fd, efds)) + happened |= (POLLPRI | POLLRDBAND) & sought; + + return happened; +} +#endif /* !MinGW */ + +int +pa_poll (struct pollfd *pfd, nfds_t nfd, int timeout) +{ + struct timeval tv; + +#ifndef WINDOWS_NATIVE + struct timeval *ptv; + fd_set rfds, wfds, efds; + int maxfd, rc; + nfds_t i; + +# ifdef _SC_OPEN_MAX + static int sc_open_max = -1; + + if (nfd < 0 + || (nfd > sc_open_max + && (sc_open_max != -1 + || nfd > (sc_open_max = sysconf (_SC_OPEN_MAX))))) + { + errno = EINVAL; + return -1; + } +# else /* !_SC_OPEN_MAX */ +# ifdef OPEN_MAX + if (nfd < 0 || nfd > OPEN_MAX) + { + errno = EINVAL; + return -1; + } +# endif /* OPEN_MAX -- else, no check is needed */ +# endif /* !_SC_OPEN_MAX */ + + /* EFAULT is not necessary to implement, but let's do it in the + simplest case. */ + if (!pfd && nfd) + { + errno = EFAULT; + return -1; + } + + /* convert timeout number into a timeval structure */ + if (timeout == 0) + { + ptv = &tv; + ptv->tv_sec = 0; + ptv->tv_usec = 0; + } + else if (timeout > 0) + { + ptv = &tv; + ptv->tv_sec = timeout / 1000; + ptv->tv_usec = (timeout % 1000) * 1000; + } + else if (timeout == INFTIM) + /* wait forever */ + ptv = NULL; + else + { + errno = EINVAL; + return -1; + } + + /* create fd sets and determine max fd */ + maxfd = -1; + FD_ZERO (&rfds); + FD_ZERO (&wfds); + FD_ZERO (&efds); + for (i = 0; i < nfd; i++) + { + if (pfd[i].fd < 0) + continue; + + if (pfd[i].events & (POLLIN | POLLRDNORM)) + FD_SET (pfd[i].fd, &rfds); + + /* see select(2): "the only exceptional condition detectable + is out-of-band data received on a socket", hence we push + POLLWRBAND events onto wfds instead of efds. */ + if (pfd[i].events & (POLLOUT | POLLWRNORM | POLLWRBAND)) + FD_SET (pfd[i].fd, &wfds); + if (pfd[i].events & (POLLPRI | POLLRDBAND)) + FD_SET (pfd[i].fd, &efds); + if (pfd[i].fd >= maxfd + && (pfd[i].events & (POLLIN | POLLOUT | POLLPRI + | POLLRDNORM | POLLRDBAND + | POLLWRNORM | POLLWRBAND))) + { + maxfd = pfd[i].fd; + if (maxfd > FD_SETSIZE) + { + errno = EOVERFLOW; + return -1; + } + } + } + + /* examine fd sets */ + rc = select (maxfd + 1, &rfds, &wfds, &efds, ptv); + if (rc < 0) + return rc; + + /* establish results */ + rc = 0; + for (i = 0; i < nfd; i++) + if (pfd[i].fd < 0) + pfd[i].revents = 0; + else + { + int happened = compute_revents (pfd[i].fd, pfd[i].events, + &rfds, &wfds, &efds); + if (happened) + { + pfd[i].revents = happened; + rc++; + } + } + + return rc; +#else /* WINDOWS_NATIVE*/ + HANDLE hEvent; + WSANETWORKEVENTS ev; + HANDLE h, handle_array[FD_SETSIZE + 2]; + DWORD ret, wait_timeout, nhandles; + fd_set rfds, wfds, xfds; + BOOL poll_again; + MSG msg; + int rc = 0; + nfds_t i; + + hEvent = CreateEvent (NULL, FALSE, FALSE, NULL); + +restart: + handle_array[0] = hEvent; + nhandles = 1; + FD_ZERO (&rfds); + FD_ZERO (&wfds); + FD_ZERO (&xfds); + + /* Classify socket handles and create fd sets. */ + for (i = 0; i < nfd; i++) + { + int sought = pfd[i].events; + pfd[i].revents = 0; + if (pfd[i].fd < 0) + continue; + if (!(sought & (POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM | POLLWRBAND + | POLLPRI | POLLRDBAND))) + continue; + + h = HandleFromFd (pfd[i].fd); + assert (h != NULL && h != INVALID_HANDLE_VALUE); + if (IsSocketHandle (h)) + { + int requested = FD_CLOSE; + + /* see above; socket handles are mapped onto select. */ + if (sought & (POLLIN | POLLRDNORM)) + { + requested |= FD_READ | FD_ACCEPT; + FD_SET ((SOCKET) h, &rfds); + } + if (sought & (POLLOUT | POLLWRNORM | POLLWRBAND)) + { + requested |= FD_WRITE | FD_CONNECT; + FD_SET ((SOCKET) h, &wfds); + } + if (sought & (POLLPRI | POLLRDBAND)) + { + requested |= FD_OOB; + FD_SET ((SOCKET) h, &xfds); + } + + if (requested) + WSAEventSelect ((SOCKET) h, hEvent, requested); + } + else + { + /* Poll now. If we get an event, do not poll again. Also, + screen buffer handles are waitable, and they'll block until + a character is available. windows_compute_revents eliminates + bits for the "wrong" direction. */ + pfd[i].revents = windows_compute_revents (h, &sought); + if (sought) + handle_array[nhandles++] = h; + if (pfd[i].revents) + timeout = 0; + } + } + + /* We poll current status using select(). It cannot be used to check + anything but sockets, so we still have to wait in + MsgWaitForMultipleObjects(). But that in turn cannot check existing + state, so we can't remove this select(). */ + /* FIXME: MSDN states that we cannot give empty fd_set:s. */ + tv.tv_sec = tv.tv_usec = 0; + if (select (0, &rfds, &wfds, &xfds, &tv) > 0) + { + /* Do MsgWaitForMultipleObjects anyway to dispatch messages, but + no need to call select again. */ + poll_again = FALSE; + wait_timeout = 0; + } + else + { + poll_again = TRUE; + if (timeout == INFTIM) + wait_timeout = INFINITE; + else + wait_timeout = timeout; + } + + for (;;) + { + ret = MsgWaitForMultipleObjects (nhandles, handle_array, FALSE, + wait_timeout, QS_ALLINPUT); + + if (ret == WAIT_OBJECT_0 + nhandles) + { + /* new input of some other kind */ + BOOL bRet; + while ((bRet = PeekMessage (&msg, NULL, 0, 0, PM_REMOVE)) != 0) + { + if (msg.message == WM_QUIT) + raise(SIGTERM); + else + { + TranslateMessage (&msg); + DispatchMessage (&msg); + } + } + } + else + break; + } + + if (poll_again) + select (0, &rfds, &wfds, &xfds, &tv); + + /* Place a sentinel at the end of the array. */ + handle_array[nhandles] = NULL; + nhandles = 1; + for (i = 0; i < nfd; i++) + { + int happened; + + if (pfd[i].fd < 0) + continue; + if (!(pfd[i].events & (POLLIN | POLLRDNORM | + POLLOUT | POLLWRNORM | POLLWRBAND))) + continue; + + h = (HANDLE) HandleFromFd (pfd[i].fd); + if (h != handle_array[nhandles]) + { + /* It's a socket. */ + WSAEnumNetworkEvents ((SOCKET) h, NULL, &ev); + WSAEventSelect ((SOCKET) h, 0, 0); + /* Have to restore blocking as WSAEventSelect() clears it */ + if (!pa_is_fd_nonblock(pfd[i].fd)) + pa_make_fd_block(pfd[i].fd); + + /* If we're lucky, WSAEnumNetworkEvents already provided a way + to distinguish FD_READ and FD_ACCEPT; this saves a recv later. */ + if (FD_ISSET ((SOCKET) h, &rfds) + && !(ev.lNetworkEvents & (FD_READ | FD_ACCEPT))) + ev.lNetworkEvents |= FD_READ | FD_ACCEPT; + if (FD_ISSET ((SOCKET) h, &wfds)) + ev.lNetworkEvents |= FD_WRITE | FD_CONNECT; + if (FD_ISSET ((SOCKET) h, &xfds)) + ev.lNetworkEvents |= FD_OOB; + + happened = windows_compute_revents_socket ((SOCKET) h, pfd[i].events, + ev.lNetworkEvents); + } + else + { + /* Not a socket. */ + int sought = pfd[i].events; + happened = windows_compute_revents (h, &sought); + nhandles++; + } + + if ((pfd[i].revents |= happened) != 0) + rc++; + } + + if (!rc && timeout == INFTIM) + { + SleepEx (1, TRUE); + goto restart; + } + + CloseHandle(hEvent); + + return rc; +#endif +} diff --git a/src/pulsecore/poll.h b/src/pulsecore/poll.h new file mode 100644 index 0000000..4af1b99 --- /dev/null +++ b/src/pulsecore/poll.h @@ -0,0 +1,62 @@ +/*** + This file is part of PulseAudio. + + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +/*** + Based on work for the GNU C Library. + Copyright (C) 1994,96,97,98,99,2000,2001,2004 Free Software Foundation, Inc. +***/ + +#if defined(HAVE_POLL_H) +#include <poll.h> +#else + +/* Event types that can be polled for. These bits may be set in `events' + to indicate the interesting event types; they will appear in `revents' + to indicate the status of the file descriptor. */ +#define POLLIN 0x001 /* There is data to read. */ +#define POLLPRI 0x002 /* There is urgent data to read. */ +#define POLLOUT 0x004 /* Writing now will not block. */ + +/* Event types always implicitly polled for. These bits need not be set in + `events', but they will appear in `revents' to indicate the status of + the file descriptor. */ +#define POLLERR 0x008 /* Error condition. */ +#define POLLHUP 0x010 /* Hung up. */ +#define POLLNVAL 0x020 /* Invalid polling request. */ + +/* Data structure describing a polling request. */ +struct pollfd { + int fd; /* File descriptor to poll. */ + short int events; /* Types of events poller cares about. */ + short int revents; /* Types of events that actually occurred. */ +}; + +/* Poll the file descriptors described by the NFDS structures starting at + FDS. If TIMEOUT is nonzero and not -1, allow TIMEOUT milliseconds for + an event to occur; if TIMEOUT is -1, block until an event occurs. + Returns the number of file descriptors with events, zero if timed out, + or -1 for errors. */ + +#endif /* HAVE_POLL_H */ + +#if defined(HAVE_POLL_H) && !defined(OS_IS_DARWIN) +#define pa_poll(fds,nfds,timeout) poll((fds),(nfds),(timeout)) +#else +int pa_poll(struct pollfd *fds, unsigned long nfds, int timeout); +#endif diff --git a/src/pulsecore/proplist-util.c b/src/pulsecore/proplist-util.c new file mode 100644 index 0000000..f966579 --- /dev/null +++ b/src/pulsecore/proplist-util.c @@ -0,0 +1,276 @@ +/*** + This file is part of PulseAudio. + + Copyright 2008 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <locale.h> + +#ifdef ENABLE_NLS +#include <libintl.h> +#endif + +#ifdef __APPLE__ +#include <crt_externs.h> +#define environ (*_NSGetEnviron()) +#elif !HAVE_DECL_ENVIRON +extern char **environ; +#endif + +#include <pulse/gccmacro.h> +#include <pulse/proplist.h> +#include <pulse/utf8.h> +#include <pulse/xmalloc.h> +#include <pulse/util.h> + +#include <pulsecore/core-util.h> + +#if defined(HAVE_GLIB) && defined(PA_GCC_WEAKREF) +#include <glib.h> +static G_CONST_RETURN gchar* _g_get_application_name(void) PA_GCC_WEAKREF(g_get_application_name); +#endif + +#if defined(HAVE_GTK) && defined(PA_GCC_WEAKREF) +#pragma GCC diagnostic ignored "-Wstrict-prototypes" +#include <gtk/gtk.h> +#include <gdk/gdkx.h> +static G_CONST_RETURN gchar* _gtk_window_get_default_icon_name(void) PA_GCC_WEAKREF(gtk_window_get_default_icon_name); +static Display *_gdk_display PA_GCC_WEAKREF(gdk_display); +#endif + +#include "proplist-util.h" + +static void add_glib_properties(pa_proplist *p) { + +#if defined(HAVE_GLIB) && defined(PA_GCC_WEAKREF) + + if (!pa_proplist_contains(p, PA_PROP_APPLICATION_NAME)) + if (_g_get_application_name) { + const gchar *t; + + /* We ignore the tiny race condition here. */ + + if ((t = _g_get_application_name())) + pa_proplist_sets(p, PA_PROP_APPLICATION_NAME, t); + } + +#endif +} + +static void add_gtk_properties(pa_proplist *p) { + +#if defined(HAVE_GTK) && defined(PA_GCC_WEAKREF) + + if (!pa_proplist_contains(p, PA_PROP_APPLICATION_ICON_NAME)) + if (_gtk_window_get_default_icon_name) { + const gchar *t; + + /* We ignore the tiny race condition here. */ + + if ((t = _gtk_window_get_default_icon_name())) + pa_proplist_sets(p, PA_PROP_APPLICATION_ICON_NAME, t); + } + + if (!pa_proplist_contains(p, PA_PROP_WINDOW_X11_DISPLAY)) + if (&_gdk_display && _gdk_display) { + const char *t; + + /* We ignore the tiny race condition here. */ + + if ((t = DisplayString(_gdk_display))) + pa_proplist_sets(p, PA_PROP_WINDOW_X11_DISPLAY, t); + } + +#endif +} + +void pa_init_proplist(pa_proplist *p) { + char **e; + const char *pp; + + pa_assert(p); + + if (environ) { + + /* Some applications seem to reset environ to NULL for various + * reasons, hence we need to check for this explicitly. See + * rhbz #473080 */ + + for (e = environ; *e; e++) { + + if (pa_startswith(*e, "PULSE_PROP_")) { + size_t kl, skip; + char *k; + bool override; + + if (pa_startswith(*e, "PULSE_PROP_OVERRIDE_")) { + skip = 20; + override = true; + } else { + skip = 11; + override = false; + } + + kl = strcspn(*e+skip, "="); + + if ((*e)[skip+kl] != '=') + continue; + + k = pa_xstrndup(*e+skip, kl); + + if (!pa_streq(k, "OVERRIDE")) + if (override || !pa_proplist_contains(p, k)) + pa_proplist_sets(p, k, *e+skip+kl+1); + pa_xfree(k); + } + } + } + + if ((pp = getenv("PULSE_PROP"))) { + pa_proplist *t; + + if ((t = pa_proplist_from_string(pp))) { + pa_proplist_update(p, PA_UPDATE_MERGE, t); + pa_proplist_free(t); + } + } + + if ((pp = getenv("PULSE_PROP_OVERRIDE"))) { + pa_proplist *t; + + if ((t = pa_proplist_from_string(pp))) { + pa_proplist_update(p, PA_UPDATE_REPLACE, t); + pa_proplist_free(t); + } + } + + if (!pa_proplist_contains(p, PA_PROP_APPLICATION_PROCESS_ID)) { + char t[32]; + pa_snprintf(t, sizeof(t), "%lu", (unsigned long) getpid()); + pa_proplist_sets(p, PA_PROP_APPLICATION_PROCESS_ID, t); + } + + if (!pa_proplist_contains(p, PA_PROP_APPLICATION_PROCESS_USER)) { + char *u; + + if ((u = pa_get_user_name_malloc())) { + pa_proplist_sets(p, PA_PROP_APPLICATION_PROCESS_USER, u); + pa_xfree(u); + } + } + + if (!pa_proplist_contains(p, PA_PROP_APPLICATION_PROCESS_HOST)) { + char *h; + + if ((h = pa_get_host_name_malloc())) { + pa_proplist_sets(p, PA_PROP_APPLICATION_PROCESS_HOST, h); + pa_xfree(h); + } + } + + if (!pa_proplist_contains(p, PA_PROP_APPLICATION_PROCESS_BINARY)) { + char *t; + + if ((t = pa_get_binary_name_malloc())) { + char *c = pa_utf8_filter(t); + pa_proplist_sets(p, PA_PROP_APPLICATION_PROCESS_BINARY, c); + pa_xfree(t); + pa_xfree(c); + } + } + + add_glib_properties(p); + add_gtk_properties(p); + + if (!pa_proplist_contains(p, PA_PROP_APPLICATION_NAME)) { + const char *t; + + if ((t = pa_proplist_gets(p, PA_PROP_APPLICATION_PROCESS_BINARY))) + pa_proplist_sets(p, PA_PROP_APPLICATION_NAME, t); + } + +#ifdef ENABLE_NLS + if (!pa_proplist_contains(p, PA_PROP_APPLICATION_LANGUAGE)) { + const char *l; + + if ((l = setlocale(LC_MESSAGES, NULL))) + pa_proplist_sets(p, PA_PROP_APPLICATION_LANGUAGE, l); + } +#endif + + if (!pa_proplist_contains(p, PA_PROP_WINDOW_X11_DISPLAY)) { + const char *t; + + if ((t = getenv("DISPLAY"))) { + char *c = pa_utf8_filter(t); + pa_proplist_sets(p, PA_PROP_WINDOW_X11_DISPLAY, c); + pa_xfree(c); + } + } + + if (!pa_proplist_contains(p, PA_PROP_APPLICATION_PROCESS_MACHINE_ID)) { + char *m; + + if ((m = pa_machine_id())) { + pa_proplist_sets(p, PA_PROP_APPLICATION_PROCESS_MACHINE_ID, m); + pa_xfree(m); + } + } + + if (!pa_proplist_contains(p, PA_PROP_APPLICATION_PROCESS_SESSION_ID)) { + char *s; + + if ((s = pa_session_id())) { + pa_proplist_sets(p, PA_PROP_APPLICATION_PROCESS_SESSION_ID, s); + pa_xfree(s); + } + } +} + +char *pa_proplist_get_stream_group(pa_proplist *p, const char *prefix, const char *cache) { + const char *r; + char *t; + + if (!p) + return NULL; + + if (cache && (r = pa_proplist_gets(p, cache))) + return pa_xstrdup(r); + + if (!prefix) + prefix = "stream"; + + if ((r = pa_proplist_gets(p, PA_PROP_MEDIA_ROLE))) + t = pa_sprintf_malloc("%s-by-media-role:%s", prefix, r); + else if ((r = pa_proplist_gets(p, PA_PROP_APPLICATION_ID))) + t = pa_sprintf_malloc("%s-by-application-id:%s", prefix, r); + else if ((r = pa_proplist_gets(p, PA_PROP_APPLICATION_NAME))) + t = pa_sprintf_malloc("%s-by-application-name:%s", prefix, r); + else if ((r = pa_proplist_gets(p, PA_PROP_MEDIA_NAME))) + t = pa_sprintf_malloc("%s-by-media-name:%s", prefix, r); + else + t = pa_sprintf_malloc("%s-fallback:%s", prefix, r); + + if (cache) + pa_proplist_sets(p, cache, t); + + return t; +} diff --git a/src/pulsecore/proplist-util.h b/src/pulsecore/proplist-util.h new file mode 100644 index 0000000..17f231f --- /dev/null +++ b/src/pulsecore/proplist-util.h @@ -0,0 +1,28 @@ +#ifndef fooproplistutilutilhfoo +#define fooproplistutilutilhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2008 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulse/proplist.h> + +void pa_init_proplist(pa_proplist *p); +char *pa_proplist_get_stream_group(pa_proplist *pl, const char *prefix, const char *cache); + +#endif diff --git a/src/pulsecore/protocol-cli.c b/src/pulsecore/protocol-cli.c new file mode 100644 index 0000000..522a829 --- /dev/null +++ b/src/pulsecore/protocol-cli.c @@ -0,0 +1,140 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/cli.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> +#include <pulsecore/shared.h> + +#include "protocol-cli.h" + +/* Don't allow more than this many concurrent connections */ +#define MAX_CONNECTIONS 25 + +struct pa_cli_protocol { + PA_REFCNT_DECLARE; + + pa_core *core; + pa_idxset *connections; +}; + +static void cli_unlink(pa_cli_protocol *p, pa_cli *c) { + pa_assert(p); + pa_assert(c); + + pa_idxset_remove_by_data(p->connections, c, NULL); + pa_cli_free(c); +} + +static void cli_eof_cb(pa_cli*c, void*userdata) { + pa_cli_protocol *p = userdata; + pa_assert(p); + + cli_unlink(p, c); +} + +void pa_cli_protocol_connect(pa_cli_protocol *p, pa_iochannel *io, pa_module *m) { + pa_cli *c; + + pa_assert(p); + pa_assert(io); + pa_assert(m); + + if (pa_idxset_size(p->connections)+1 > MAX_CONNECTIONS) { + pa_log("Warning! Too many connections (%u), dropping incoming connection.", MAX_CONNECTIONS); + pa_iochannel_free(io); + return; + } + + c = pa_cli_new(p->core, io, m); + pa_cli_set_eof_callback(c, cli_eof_cb, p); + + pa_idxset_put(p->connections, c, NULL); +} + +void pa_cli_protocol_disconnect(pa_cli_protocol *p, pa_module *m) { + pa_cli *c; + void *state = NULL; + + pa_assert(p); + pa_assert(m); + + while ((c = pa_idxset_iterate(p->connections, &state, NULL))) + if (pa_cli_get_module(c) == m) + cli_unlink(p, c); +} + +static pa_cli_protocol* cli_protocol_new(pa_core *c) { + pa_cli_protocol *p; + + pa_assert(c); + + p = pa_xnew(pa_cli_protocol, 1); + PA_REFCNT_INIT(p); + p->core = c; + p->connections = pa_idxset_new(NULL, NULL); + + pa_assert_se(pa_shared_set(c, "cli-protocol", p) >= 0); + + return p; +} + +pa_cli_protocol* pa_cli_protocol_get(pa_core *c) { + pa_cli_protocol *p; + + if ((p = pa_shared_get(c, "cli-protocol"))) + return pa_cli_protocol_ref(p); + + return cli_protocol_new(c); +} + +pa_cli_protocol* pa_cli_protocol_ref(pa_cli_protocol *p) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) >= 1); + + PA_REFCNT_INC(p); + + return p; +} + +void pa_cli_protocol_unref(pa_cli_protocol *p) { + pa_cli *c; + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) >= 1); + + if (PA_REFCNT_DEC(p) > 0) + return; + + while ((c = pa_idxset_first(p->connections, NULL))) + cli_unlink(p, c); + + pa_idxset_free(p->connections, NULL); + + pa_assert_se(pa_shared_remove(p->core, "cli-protocol") >= 0); + + pa_xfree(p); +} diff --git a/src/pulsecore/protocol-cli.h b/src/pulsecore/protocol-cli.h new file mode 100644 index 0000000..fde6e38 --- /dev/null +++ b/src/pulsecore/protocol-cli.h @@ -0,0 +1,36 @@ +#ifndef fooprotocolclihfoo +#define fooprotocolclihfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulsecore/core.h> +#include <pulsecore/socket-server.h> +#include <pulsecore/module.h> +#include <pulsecore/modargs.h> + +typedef struct pa_cli_protocol pa_cli_protocol; + +pa_cli_protocol* pa_cli_protocol_get(pa_core *core); +pa_cli_protocol* pa_cli_protocol_ref(pa_cli_protocol *p); +void pa_cli_protocol_unref(pa_cli_protocol *p); +void pa_cli_protocol_connect(pa_cli_protocol *p, pa_iochannel *io, pa_module *m); +void pa_cli_protocol_disconnect(pa_cli_protocol *o, pa_module *m); + +#endif diff --git a/src/pulsecore/protocol-dbus.c b/src/pulsecore/protocol-dbus.c new file mode 100644 index 0000000..31a48d1 --- /dev/null +++ b/src/pulsecore/protocol-dbus.c @@ -0,0 +1,1140 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <dbus/dbus.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/dbus-util.h> +#include <pulsecore/hashmap.h> +#include <pulsecore/idxset.h> +#include <pulsecore/shared.h> +#include <pulsecore/strbuf.h> + +#include "protocol-dbus.h" + +struct pa_dbus_protocol { + PA_REFCNT_DECLARE; + + pa_core *core; + pa_hashmap *objects; /* Object path -> struct object_entry */ + pa_hashmap *connections; /* DBusConnection -> struct connection_entry */ + pa_idxset *extensions; /* Strings */ + + pa_hook hooks[PA_DBUS_PROTOCOL_HOOK_MAX]; +}; + +struct object_entry { + char *path; + pa_hashmap *interfaces; /* Interface name -> struct interface_entry */ + char *introspection; +}; + +struct connection_entry { + DBusConnection *connection; + pa_client *client; + + bool listening_for_all_signals; + + /* Contains object paths. If this is empty, then signals from all objects + * are accepted. Only used when listening_for_all_signals == true. */ + pa_idxset *all_signals_objects; + + /* Signal name -> signal paths entry. The entries contain object paths. If + * a path set is empty, then that signal is accepted from all objects. This + * variable is only used when listening_for_all_signals == false. */ + pa_hashmap *listening_signals; +}; + +/* Only used in connection entries' listening_signals hashmap. */ +struct signal_paths_entry { + char *signal; + pa_idxset *paths; +}; + +struct interface_entry { + char *name; + pa_hashmap *method_handlers; + pa_hashmap *method_signatures; /* Derived from method_handlers. Contains only "in" arguments. */ + pa_hashmap *property_handlers; + pa_dbus_receive_cb_t get_all_properties_cb; + pa_dbus_signal_info *signals; + unsigned n_signals; + void *userdata; +}; + +char *pa_get_dbus_address_from_server_type(pa_server_type_t server_type) { + char *address = NULL; + char *runtime_path = NULL; + char *escaped_path = NULL; + + switch (server_type) { + case PA_SERVER_TYPE_USER: + pa_assert_se((runtime_path = pa_runtime_path(PA_DBUS_SOCKET_NAME))); + pa_assert_se((escaped_path = dbus_address_escape_value(runtime_path))); + address = pa_sprintf_malloc("unix:path=%s", escaped_path); + break; + + case PA_SERVER_TYPE_SYSTEM: + pa_assert_se((escaped_path = dbus_address_escape_value(PA_DBUS_SYSTEM_SOCKET_PATH))); + address = pa_sprintf_malloc("unix:path=%s", escaped_path); + break; + + case PA_SERVER_TYPE_NONE: + address = pa_xnew0(char, 1); + break; + + default: + pa_assert_not_reached(); + } + + pa_xfree(runtime_path); + dbus_free(escaped_path); + + return address; +} + +static pa_dbus_protocol *dbus_protocol_new(pa_core *c) { + pa_dbus_protocol *p; + unsigned i; + + pa_assert(c); + + p = pa_xnew(pa_dbus_protocol, 1); + PA_REFCNT_INIT(p); + p->core = c; + p->objects = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + p->connections = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + p->extensions = pa_idxset_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + + for (i = 0; i < PA_DBUS_PROTOCOL_HOOK_MAX; ++i) + pa_hook_init(&p->hooks[i], p); + + pa_assert_se(pa_shared_set(c, "dbus-protocol", p) >= 0); + + return p; +} + +pa_dbus_protocol* pa_dbus_protocol_get(pa_core *c) { + pa_dbus_protocol *p; + + if ((p = pa_shared_get(c, "dbus-protocol"))) + return pa_dbus_protocol_ref(p); + + return dbus_protocol_new(c); +} + +pa_dbus_protocol* pa_dbus_protocol_ref(pa_dbus_protocol *p) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) >= 1); + + PA_REFCNT_INC(p); + + return p; +} + +void pa_dbus_protocol_unref(pa_dbus_protocol *p) { + unsigned i; + + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) >= 1); + + if (PA_REFCNT_DEC(p) > 0) + return; + + pa_assert(pa_hashmap_isempty(p->objects)); + pa_assert(pa_hashmap_isempty(p->connections)); + pa_assert(pa_idxset_isempty(p->extensions)); + + pa_hashmap_free(p->objects); + pa_hashmap_free(p->connections); + pa_idxset_free(p->extensions, NULL); + + for (i = 0; i < PA_DBUS_PROTOCOL_HOOK_MAX; ++i) + pa_hook_done(&p->hooks[i]); + + pa_assert_se(pa_shared_remove(p->core, "dbus-protocol") >= 0); + + pa_xfree(p); +} + +static void update_introspection(struct object_entry *oe) { + pa_strbuf *buf; + void *interfaces_state = NULL; + struct interface_entry *iface_entry = NULL; + + pa_assert(oe); + + buf = pa_strbuf_new(); + pa_strbuf_puts(buf, DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE); + pa_strbuf_puts(buf, "<node>\n"); + + PA_HASHMAP_FOREACH(iface_entry, oe->interfaces, interfaces_state) { + pa_dbus_method_handler *method_handler; + pa_dbus_property_handler *property_handler; + void *handlers_state = NULL; + unsigned i; + unsigned j; + + pa_strbuf_printf(buf, " <interface name=\"%s\">\n", iface_entry->name); + + PA_HASHMAP_FOREACH(method_handler, iface_entry->method_handlers, handlers_state) { + pa_strbuf_printf(buf, " <method name=\"%s\">\n", method_handler->method_name); + + for (i = 0; i < method_handler->n_arguments; ++i) + pa_strbuf_printf(buf, " <arg name=\"%s\" type=\"%s\" direction=\"%s\"/>\n", + method_handler->arguments[i].name, + method_handler->arguments[i].type, + method_handler->arguments[i].direction); + + pa_strbuf_puts(buf, " </method>\n"); + } + + handlers_state = NULL; + + PA_HASHMAP_FOREACH(property_handler, iface_entry->property_handlers, handlers_state) + pa_strbuf_printf(buf, " <property name=\"%s\" type=\"%s\" access=\"%s\"/>\n", + property_handler->property_name, + property_handler->type, + property_handler->get_cb ? (property_handler->set_cb ? "readwrite" : "read") : "write"); + + for (i = 0; i < iface_entry->n_signals; ++i) { + pa_strbuf_printf(buf, " <signal name=\"%s\">\n", iface_entry->signals[i].name); + + for (j = 0; j < iface_entry->signals[i].n_arguments; ++j) + pa_strbuf_printf(buf, " <arg name=\"%s\" type=\"%s\"/>\n", iface_entry->signals[i].arguments[j].name, + iface_entry->signals[i].arguments[j].type); + + pa_strbuf_puts(buf, " </signal>\n"); + } + + pa_strbuf_puts(buf, " </interface>\n"); + } + + pa_strbuf_puts(buf, " <interface name=\"" DBUS_INTERFACE_INTROSPECTABLE "\">\n" + " <method name=\"Introspect\">\n" + " <arg name=\"data\" type=\"s\" direction=\"out\"/>\n" + " </method>\n" + " </interface>\n" + " <interface name=\"" DBUS_INTERFACE_PROPERTIES "\">\n" + " <method name=\"Get\">\n" + " <arg name=\"interface_name\" type=\"s\" direction=\"in\"/>\n" + " <arg name=\"property_name\" type=\"s\" direction=\"in\"/>\n" + " <arg name=\"value\" type=\"v\" direction=\"out\"/>\n" + " </method>\n" + " <method name=\"Set\">\n" + " <arg name=\"interface_name\" type=\"s\" direction=\"in\"/>\n" + " <arg name=\"property_name\" type=\"s\" direction=\"in\"/>\n" + " <arg name=\"value\" type=\"v\" direction=\"in\"/>\n" + " </method>\n" + " <method name=\"GetAll\">\n" + " <arg name=\"interface_name\" type=\"s\" direction=\"in\"/>\n" + " <arg name=\"props\" type=\"a{sv}\" direction=\"out\"/>\n" + " </method>\n" + " </interface>\n"); + + pa_strbuf_puts(buf, "</node>\n"); + + pa_xfree(oe->introspection); + oe->introspection = pa_strbuf_to_string_free(buf); +} + +/* Return value of find_handler() and its subfunctions. */ +enum find_result_t { + /* The received message is a valid .Get call. */ + FOUND_GET_PROPERTY, + + /* The received message is a valid .Set call. */ + FOUND_SET_PROPERTY, + + /* The received message is a valid .GetAll call. */ + FOUND_GET_ALL, + + /* The received message is a valid method call. */ + FOUND_METHOD, + + /* The interface of the received message hasn't been registered for the + * destination object. */ + NO_SUCH_INTERFACE, + + /* No property handler was found for the received .Get or .Set call. */ + NO_SUCH_PROPERTY, + + /* The interface argument of a property call didn't match any registered + * interface. */ + NO_SUCH_PROPERTY_INTERFACE, + + /* The received message called .Get or .Set for a property whose access + * mode doesn't match the call. */ + PROPERTY_ACCESS_DENIED, + + /* The new value signature of a .Set call didn't match the expected + * signature. */ + INVALID_PROPERTY_SIG, + + /* No method handler was found for the received message. */ + NO_SUCH_METHOD, + + /* The signature of the received message didn't match the expected + * signature. Despite the name, this can also be returned for a property + * call if its message signature is invalid. */ + INVALID_METHOD_SIG +}; + +/* Data for resolving the correct reaction to a received message. */ +struct call_info { + DBusMessage *message; /* The received message. */ + struct object_entry *obj_entry; + const char *interface; /* Destination interface name (extracted from the message). */ + struct interface_entry *iface_entry; + + const char *property; /* Property name (extracted from the message). */ + const char *property_interface; /* The interface argument of a property call is stored here. */ + pa_dbus_property_handler *property_handler; + const char *expected_property_sig; /* Property signature from the introspection data. */ + char *property_sig; /* The signature of the new value in the received .Set message. */ + DBusMessageIter variant_iter; /* Iterator pointing to the beginning of the new value variant of a .Set call. */ + + const char *method; /* Method name (extracted from the message). */ + pa_dbus_method_handler *method_handler; + const char *expected_method_sig; /* Method signature from the introspection data. */ + const char *method_sig; /* The signature of the received message. */ +}; + +/* Called when call_info->property has been set and the property interface has + * not been given. In case of a Set call, call_info->property_sig is also set, + * which is checked against the expected value in this function. */ +static enum find_result_t find_handler_by_property(struct call_info *call_info) { + void *state = NULL; + + pa_assert(call_info); + + PA_HASHMAP_FOREACH(call_info->iface_entry, call_info->obj_entry->interfaces, state) { + if ((call_info->property_handler = pa_hashmap_get(call_info->iface_entry->property_handlers, call_info->property))) { + if (pa_streq(call_info->method, "Get")) + return call_info->property_handler->get_cb ? FOUND_GET_PROPERTY : PROPERTY_ACCESS_DENIED; + + else if (pa_streq(call_info->method, "Set")) { + call_info->expected_property_sig = call_info->property_handler->type; + + if (pa_streq(call_info->property_sig, call_info->expected_property_sig)) + return call_info->property_handler->set_cb ? FOUND_SET_PROPERTY : PROPERTY_ACCESS_DENIED; + else + return INVALID_PROPERTY_SIG; + + } else + pa_assert_not_reached(); + } + } + + return NO_SUCH_PROPERTY; +} + +static enum find_result_t find_handler_by_method(struct call_info *call_info) { + void *state = NULL; + + pa_assert(call_info); + + PA_HASHMAP_FOREACH(call_info->iface_entry, call_info->obj_entry->interfaces, state) { + if ((call_info->method_handler = pa_hashmap_get(call_info->iface_entry->method_handlers, call_info->method))) { + pa_assert_se(call_info->expected_method_sig = pa_hashmap_get(call_info->iface_entry->method_signatures, call_info->method)); + + if (pa_streq(call_info->method_sig, call_info->expected_method_sig)) + return FOUND_METHOD; + else + return INVALID_METHOD_SIG; + } + } + + return NO_SUCH_METHOD; +} + +static enum find_result_t find_handler_from_properties_call(struct call_info *call_info) { + pa_assert(call_info); + + if (pa_streq(call_info->method, "GetAll")) { + call_info->expected_method_sig = "s"; + if (!pa_streq(call_info->method_sig, call_info->expected_method_sig)) + return INVALID_METHOD_SIG; + + pa_assert_se(dbus_message_get_args(call_info->message, NULL, + DBUS_TYPE_STRING, &call_info->property_interface, + DBUS_TYPE_INVALID)); + + if (*call_info->property_interface) { + if ((call_info->iface_entry = pa_hashmap_get(call_info->obj_entry->interfaces, call_info->property_interface))) + return FOUND_GET_ALL; + else + return NO_SUCH_PROPERTY_INTERFACE; + + } else { + pa_assert_se(call_info->iface_entry = pa_hashmap_first(call_info->obj_entry->interfaces)); + return FOUND_GET_ALL; + } + + } else if (pa_streq(call_info->method, "Get")) { + call_info->expected_method_sig = "ss"; + if (!pa_streq(call_info->method_sig, call_info->expected_method_sig)) + return INVALID_METHOD_SIG; + + pa_assert_se(dbus_message_get_args(call_info->message, NULL, + DBUS_TYPE_STRING, &call_info->property_interface, + DBUS_TYPE_STRING, &call_info->property, + DBUS_TYPE_INVALID)); + + if (*call_info->property_interface) { + if (!(call_info->iface_entry = pa_hashmap_get(call_info->obj_entry->interfaces, call_info->property_interface))) + return NO_SUCH_PROPERTY_INTERFACE; + else if ((call_info->property_handler = + pa_hashmap_get(call_info->iface_entry->property_handlers, call_info->property))) + return call_info->property_handler->get_cb ? FOUND_GET_PROPERTY : PROPERTY_ACCESS_DENIED; + else + return NO_SUCH_PROPERTY; + + } else + return find_handler_by_property(call_info); + + } else if (pa_streq(call_info->method, "Set")) { + DBusMessageIter msg_iter; + + call_info->expected_method_sig = "ssv"; + if (!pa_streq(call_info->method_sig, call_info->expected_method_sig)) + return INVALID_METHOD_SIG; + + pa_assert_se(dbus_message_iter_init(call_info->message, &msg_iter)); + + dbus_message_iter_get_basic(&msg_iter, &call_info->property_interface); + pa_assert_se(dbus_message_iter_next(&msg_iter)); + dbus_message_iter_get_basic(&msg_iter, &call_info->property); + pa_assert_se(dbus_message_iter_next(&msg_iter)); + + dbus_message_iter_recurse(&msg_iter, &call_info->variant_iter); + + pa_assert_se(call_info->property_sig = dbus_message_iter_get_signature(&call_info->variant_iter)); + + if (*call_info->property_interface) { + if (!(call_info->iface_entry = pa_hashmap_get(call_info->obj_entry->interfaces, call_info->property_interface))) + return NO_SUCH_PROPERTY_INTERFACE; + + else if ((call_info->property_handler = + pa_hashmap_get(call_info->iface_entry->property_handlers, call_info->property))) { + call_info->expected_property_sig = call_info->property_handler->type; + + if (pa_streq(call_info->property_sig, call_info->expected_property_sig)) + return call_info->property_handler->set_cb ? FOUND_SET_PROPERTY : PROPERTY_ACCESS_DENIED; + else + return INVALID_PROPERTY_SIG; + + } else + return NO_SUCH_PROPERTY; + + } else + return find_handler_by_property(call_info); + + } else + pa_assert_not_reached(); +} + +static enum find_result_t find_handler(struct call_info *call_info) { + pa_assert(call_info); + + if (call_info->interface) { + if (pa_streq(call_info->interface, DBUS_INTERFACE_PROPERTIES)) + return find_handler_from_properties_call(call_info); + + else if (!(call_info->iface_entry = pa_hashmap_get(call_info->obj_entry->interfaces, call_info->interface))) + return NO_SUCH_INTERFACE; + + else if ((call_info->method_handler = pa_hashmap_get(call_info->iface_entry->method_handlers, call_info->method))) { + pa_assert_se(call_info->expected_method_sig = pa_hashmap_get(call_info->iface_entry->method_signatures, call_info->method)); + + if (!pa_streq(call_info->method_sig, call_info->expected_method_sig)) + return INVALID_METHOD_SIG; + + return FOUND_METHOD; + + } else + return NO_SUCH_METHOD; + + } else { /* The method call doesn't contain an interface. */ + if (pa_streq(call_info->method, "Get") || pa_streq(call_info->method, "Set") || pa_streq(call_info->method, "GetAll")) { + if (find_handler_by_method(call_info) == FOUND_METHOD) + /* The object has a method named Get, Set or GetAll in some other interface than .Properties. */ + return FOUND_METHOD; + else + /* Assume this is a .Properties call. */ + return find_handler_from_properties_call(call_info); + + } else /* This is not a .Properties call. */ + return find_handler_by_method(call_info); + } +} + +static DBusHandlerResult handle_message_cb(DBusConnection *connection, DBusMessage *message, void *user_data) { + pa_dbus_protocol *p = user_data; + struct call_info call_info; + call_info.property_sig = NULL; + + pa_assert(connection); + pa_assert(message); + pa_assert(p); + pa_assert(p->objects); + + if (dbus_message_get_type(message) != DBUS_MESSAGE_TYPE_METHOD_CALL) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + pa_log_debug("Received message: destination = %s, interface = %s, member = %s", + dbus_message_get_path(message), + dbus_message_get_interface(message), + dbus_message_get_member(message)); + + call_info.message = message; + pa_assert_se(call_info.obj_entry = pa_hashmap_get(p->objects, dbus_message_get_path(message))); + call_info.interface = dbus_message_get_interface(message); + pa_assert_se(call_info.method = dbus_message_get_member(message)); + pa_assert_se(call_info.method_sig = dbus_message_get_signature(message)); + + if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Introspectable", "Introspect") || + (!dbus_message_get_interface(message) && dbus_message_has_member(message, "Introspect"))) { + pa_dbus_send_basic_value_reply(connection, message, DBUS_TYPE_STRING, &call_info.obj_entry->introspection); + goto finish; + } + + switch (find_handler(&call_info)) { + case FOUND_GET_PROPERTY: + call_info.property_handler->get_cb(connection, message, call_info.iface_entry->userdata); + break; + + case FOUND_SET_PROPERTY: + call_info.property_handler->set_cb(connection, message, &call_info.variant_iter, call_info.iface_entry->userdata); + break; + + case FOUND_METHOD: + call_info.method_handler->receive_cb(connection, message, call_info.iface_entry->userdata); + break; + + case FOUND_GET_ALL: + if (call_info.iface_entry->get_all_properties_cb) + call_info.iface_entry->get_all_properties_cb(connection, message, call_info.iface_entry->userdata); + else { + DBusMessage *dummy_reply = NULL; + DBusMessageIter msg_iter; + DBusMessageIter dict_iter; + + pa_assert_se(dummy_reply = dbus_message_new_method_return(message)); + dbus_message_iter_init_append(dummy_reply, &msg_iter); + pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter)); + pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter)); + pa_assert_se(dbus_connection_send(connection, dummy_reply, NULL)); + dbus_message_unref(dummy_reply); + } + break; + + case PROPERTY_ACCESS_DENIED: + pa_dbus_send_error(connection, message, DBUS_ERROR_ACCESS_DENIED, + "%s access denied for property %s", call_info.method, call_info.property); + break; + + case NO_SUCH_METHOD: + pa_dbus_send_error(connection, message, DBUS_ERROR_UNKNOWN_METHOD, "No such method: %s", call_info.method); + break; + + case NO_SUCH_INTERFACE: + pa_dbus_send_error(connection, message, PA_DBUS_ERROR_NO_SUCH_INTERFACE, "No such interface: %s", call_info.interface); + break; + + case NO_SUCH_PROPERTY: + pa_dbus_send_error(connection, message, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "No such property: %s", call_info.property); + break; + + case NO_SUCH_PROPERTY_INTERFACE: + pa_dbus_send_error(connection, message, PA_DBUS_ERROR_NO_SUCH_INTERFACE, "No such property interface: %s", call_info.property_interface); + break; + + case INVALID_METHOD_SIG: + pa_dbus_send_error(connection, message, DBUS_ERROR_INVALID_ARGS, + "Invalid signature for method %s: '%s'. Expected '%s'.", + call_info.method, call_info.method_sig, call_info.expected_method_sig); + break; + + case INVALID_PROPERTY_SIG: + pa_dbus_send_error(connection, message, DBUS_ERROR_INVALID_ARGS, + "Invalid signature for property %s: '%s'. Expected '%s'.", + call_info.property, call_info.property_sig, call_info.expected_property_sig); + break; + + default: + pa_assert_not_reached(); + } + +finish: + if (call_info.property_sig) + dbus_free(call_info.property_sig); + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusObjectPathVTable vtable = { + .unregister_function = NULL, + .message_function = handle_message_cb, + .dbus_internal_pad1 = NULL, + .dbus_internal_pad2 = NULL, + .dbus_internal_pad3 = NULL, + .dbus_internal_pad4 = NULL +}; + +static void register_object(pa_dbus_protocol *p, struct object_entry *obj_entry) { + struct connection_entry *conn_entry; + void *state = NULL; + + pa_assert(p); + pa_assert(obj_entry); + + PA_HASHMAP_FOREACH(conn_entry, p->connections, state) + pa_assert_se(dbus_connection_register_object_path(conn_entry->connection, obj_entry->path, &vtable, p)); +} + +static pa_dbus_arg_info *copy_args(const pa_dbus_arg_info *src, unsigned n) { + pa_dbus_arg_info *dst; + unsigned i; + + if (n == 0) + return NULL; + + pa_assert(src); + + dst = pa_xnew0(pa_dbus_arg_info, n); + + for (i = 0; i < n; ++i) { + dst[i].name = pa_xstrdup(src[i].name); + dst[i].type = pa_xstrdup(src[i].type); + dst[i].direction = pa_xstrdup(src[i].direction); + } + + return dst; +} + +static void method_handler_free(pa_dbus_method_handler *h) { + unsigned i; + + pa_assert(h); + + pa_xfree((char *) h->method_name); + + for (i = 0; i < h->n_arguments; ++i) { + pa_xfree((char *) h->arguments[i].name); + pa_xfree((char *) h->arguments[i].type); + pa_xfree((char *) h->arguments[i].direction); + } + + pa_xfree((pa_dbus_arg_info *) h->arguments); + pa_xfree(h); +} + +static pa_hashmap *create_method_handlers(const pa_dbus_interface_info *info) { + pa_hashmap *handlers; + unsigned i; + + pa_assert(info); + pa_assert(info->method_handlers || info->n_method_handlers == 0); + + handlers = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, (pa_free_cb_t) method_handler_free); + + for (i = 0; i < info->n_method_handlers; ++i) { + pa_dbus_method_handler *h = pa_xnew(pa_dbus_method_handler, 1); + h->method_name = pa_xstrdup(info->method_handlers[i].method_name); + h->arguments = copy_args(info->method_handlers[i].arguments, info->method_handlers[i].n_arguments); + h->n_arguments = info->method_handlers[i].n_arguments; + h->receive_cb = info->method_handlers[i].receive_cb; + + pa_hashmap_put(handlers, (char *) h->method_name, h); + } + + return handlers; +} + +static pa_hashmap *extract_method_signatures(pa_hashmap *method_handlers) { + pa_hashmap *signatures = NULL; + pa_dbus_method_handler *handler = NULL; + void *state = NULL; + pa_strbuf *sig_buf = NULL; + unsigned i = 0; + + pa_assert(method_handlers); + + signatures = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, pa_xfree); + + PA_HASHMAP_FOREACH(handler, method_handlers, state) { + sig_buf = pa_strbuf_new(); + + for (i = 0; i < handler->n_arguments; ++i) { + if (pa_streq(handler->arguments[i].direction, "in")) + pa_strbuf_puts(sig_buf, handler->arguments[i].type); + } + + pa_hashmap_put(signatures, (char *) handler->method_name, pa_strbuf_to_string_free(sig_buf)); + } + + return signatures; +} + +static void property_handler_free(pa_dbus_property_handler *h) { + pa_assert(h); + + pa_xfree((char *) h->property_name); + pa_xfree((char *) h->type); + + pa_xfree(h); +} + +static pa_hashmap *create_property_handlers(const pa_dbus_interface_info *info) { + pa_hashmap *handlers; + unsigned i = 0; + + pa_assert(info); + pa_assert(info->property_handlers || info->n_property_handlers == 0); + + handlers = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, (pa_free_cb_t) property_handler_free); + + for (i = 0; i < info->n_property_handlers; ++i) { + pa_dbus_property_handler *h = pa_xnew(pa_dbus_property_handler, 1); + h->property_name = pa_xstrdup(info->property_handlers[i].property_name); + h->type = pa_xstrdup(info->property_handlers[i].type); + h->get_cb = info->property_handlers[i].get_cb; + h->set_cb = info->property_handlers[i].set_cb; + + pa_hashmap_put(handlers, (char *) h->property_name, h); + } + + return handlers; +} + +static pa_dbus_signal_info *copy_signals(const pa_dbus_interface_info *info) { + pa_dbus_signal_info *dst; + unsigned i; + + pa_assert(info); + + if (info->n_signals == 0) + return NULL; + + pa_assert(info->signals); + + dst = pa_xnew(pa_dbus_signal_info, info->n_signals); + + for (i = 0; i < info->n_signals; ++i) { + dst[i].name = pa_xstrdup(info->signals[i].name); + dst[i].arguments = copy_args(info->signals[i].arguments, info->signals[i].n_arguments); + dst[i].n_arguments = info->signals[i].n_arguments; + } + + return dst; +} + +int pa_dbus_protocol_add_interface(pa_dbus_protocol *p, + const char *path, + const pa_dbus_interface_info *info, + void *userdata) { + struct object_entry *obj_entry; + struct interface_entry *iface_entry; + bool obj_entry_created = false; + + pa_assert(p); + pa_assert(path); + pa_assert(info); + pa_assert(info->name); + pa_assert(info->method_handlers || info->n_method_handlers == 0); + pa_assert(info->property_handlers || info->n_property_handlers == 0); + pa_assert(info->get_all_properties_cb || info->n_property_handlers == 0); + pa_assert(info->signals || info->n_signals == 0); + + if (!(obj_entry = pa_hashmap_get(p->objects, path))) { + obj_entry = pa_xnew(struct object_entry, 1); + obj_entry->path = pa_xstrdup(path); + obj_entry->interfaces = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + obj_entry->introspection = NULL; + + pa_hashmap_put(p->objects, obj_entry->path, obj_entry); + obj_entry_created = true; + } + + if (pa_hashmap_get(obj_entry->interfaces, info->name) != NULL) + goto fail; /* The interface was already registered. */ + + iface_entry = pa_xnew(struct interface_entry, 1); + iface_entry->name = pa_xstrdup(info->name); + iface_entry->method_handlers = create_method_handlers(info); + iface_entry->method_signatures = extract_method_signatures(iface_entry->method_handlers); + iface_entry->property_handlers = create_property_handlers(info); + iface_entry->get_all_properties_cb = info->get_all_properties_cb; + iface_entry->signals = copy_signals(info); + iface_entry->n_signals = info->n_signals; + iface_entry->userdata = userdata; + pa_hashmap_put(obj_entry->interfaces, iface_entry->name, iface_entry); + + update_introspection(obj_entry); + + if (obj_entry_created) + register_object(p, obj_entry); + + pa_log_debug("Interface %s added for object %s", iface_entry->name, obj_entry->path); + + return 0; + +fail: + return -1; +} + +static void unregister_object(pa_dbus_protocol *p, struct object_entry *obj_entry) { + struct connection_entry *conn_entry; + void *state = NULL; + + pa_assert(p); + pa_assert(obj_entry); + + PA_HASHMAP_FOREACH(conn_entry, p->connections, state) + pa_assert_se(dbus_connection_unregister_object_path(conn_entry->connection, obj_entry->path)); +} + +int pa_dbus_protocol_remove_interface(pa_dbus_protocol *p, const char* path, const char* interface) { + struct object_entry *obj_entry; + struct interface_entry *iface_entry; + unsigned i; + + pa_assert(p); + pa_assert(path); + pa_assert(interface); + + if (!(obj_entry = pa_hashmap_get(p->objects, path))) + return -1; + + if (!(iface_entry = pa_hashmap_remove(obj_entry->interfaces, interface))) + return -1; + + update_introspection(obj_entry); + + pa_log_debug("Interface %s removed from object %s", iface_entry->name, obj_entry->path); + + pa_xfree(iface_entry->name); + pa_hashmap_free(iface_entry->method_signatures); + pa_hashmap_free(iface_entry->method_handlers); + pa_hashmap_free(iface_entry->property_handlers); + + for (i = 0; i < iface_entry->n_signals; ++i) { + unsigned j; + + pa_xfree((char *) iface_entry->signals[i].name); + + for (j = 0; j < iface_entry->signals[i].n_arguments; ++j) { + pa_xfree((char *) iface_entry->signals[i].arguments[j].name); + pa_xfree((char *) iface_entry->signals[i].arguments[j].type); + pa_assert(iface_entry->signals[i].arguments[j].direction == NULL); + } + + pa_xfree((pa_dbus_arg_info *) iface_entry->signals[i].arguments); + } + + pa_xfree(iface_entry->signals); + pa_xfree(iface_entry); + + if (pa_hashmap_isempty(obj_entry->interfaces)) { + unregister_object(p, obj_entry); + + pa_hashmap_remove(p->objects, path); + pa_xfree(obj_entry->path); + pa_hashmap_free(obj_entry->interfaces); + pa_xfree(obj_entry->introspection); + pa_xfree(obj_entry); + } + + return 0; +} + +static void register_all_objects(pa_dbus_protocol *p, DBusConnection *conn) { + struct object_entry *obj_entry; + void *state = NULL; + + pa_assert(p); + pa_assert(conn); + + PA_HASHMAP_FOREACH(obj_entry, p->objects, state) + pa_assert_se(dbus_connection_register_object_path(conn, obj_entry->path, &vtable, p)); +} + +static void signal_paths_entry_free(struct signal_paths_entry *e) { + pa_assert(e); + + pa_xfree(e->signal); + pa_idxset_free(e->paths, pa_xfree); + pa_xfree(e); +} + +int pa_dbus_protocol_register_connection(pa_dbus_protocol *p, DBusConnection *conn, pa_client *client) { + struct connection_entry *conn_entry; + + pa_assert(p); + pa_assert(conn); + pa_assert(client); + + if (pa_hashmap_get(p->connections, conn)) + return -1; /* The connection was already registered. */ + + register_all_objects(p, conn); + + conn_entry = pa_xnew(struct connection_entry, 1); + conn_entry->connection = dbus_connection_ref(conn); + conn_entry->client = client; + conn_entry->listening_for_all_signals = false; + conn_entry->all_signals_objects = pa_idxset_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + conn_entry->listening_signals = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, + (pa_free_cb_t) signal_paths_entry_free); + + pa_hashmap_put(p->connections, conn, conn_entry); + + return 0; +} + +static void unregister_all_objects(pa_dbus_protocol *p, DBusConnection *conn) { + struct object_entry *obj_entry; + void *state = NULL; + + pa_assert(p); + pa_assert(conn); + + PA_HASHMAP_FOREACH(obj_entry, p->objects, state) + pa_assert_se(dbus_connection_unregister_object_path(conn, obj_entry->path)); +} + +static struct signal_paths_entry *signal_paths_entry_new(const char *signal_name) { + struct signal_paths_entry *e = NULL; + + pa_assert(signal_name); + + e = pa_xnew0(struct signal_paths_entry, 1); + e->signal = pa_xstrdup(signal_name); + e->paths = pa_idxset_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + + return e; +} + +int pa_dbus_protocol_unregister_connection(pa_dbus_protocol *p, DBusConnection *conn) { + struct connection_entry *conn_entry = NULL; + + pa_assert(p); + pa_assert(conn); + + if (!(conn_entry = pa_hashmap_remove(p->connections, conn))) + return -1; + + unregister_all_objects(p, conn); + + dbus_connection_unref(conn_entry->connection); + pa_idxset_free(conn_entry->all_signals_objects, pa_xfree); + pa_hashmap_free(conn_entry->listening_signals); + pa_xfree(conn_entry); + + return 0; +} + +pa_client *pa_dbus_protocol_get_client(pa_dbus_protocol *p, DBusConnection *conn) { + struct connection_entry *conn_entry; + + pa_assert(p); + pa_assert(conn); + + if (!(conn_entry = pa_hashmap_get(p->connections, conn))) + return NULL; + + return conn_entry->client; +} + +void pa_dbus_protocol_add_signal_listener( + pa_dbus_protocol *p, + DBusConnection *conn, + const char *signal_name, + char **objects, + unsigned n_objects) { + struct connection_entry *conn_entry = NULL; + struct signal_paths_entry *signal_paths_entry = NULL; + unsigned i = 0; + + pa_assert(p); + pa_assert(conn); + pa_assert(objects || n_objects == 0); + + pa_assert_se((conn_entry = pa_hashmap_get(p->connections, conn))); + + /* all_signals_objects will either be emptied or replaced with new objects, + * so we empty it here unconditionally. If listening_for_all_signals is + * currently false, the idxset is empty already so this does nothing. */ + pa_idxset_remove_all(conn_entry->all_signals_objects, pa_xfree); + + if (signal_name) { + conn_entry->listening_for_all_signals = false; + + /* Replace the old signal paths entry for this signal with a new + * one. */ + pa_hashmap_remove_and_free(conn_entry->listening_signals, signal_name); + signal_paths_entry = signal_paths_entry_new(signal_name); + + for (i = 0; i < n_objects; ++i) + pa_idxset_put(signal_paths_entry->paths, pa_xstrdup(objects[i]), NULL); + + pa_hashmap_put(conn_entry->listening_signals, signal_paths_entry->signal, signal_paths_entry); + + } else { + conn_entry->listening_for_all_signals = true; + + /* We're not interested in individual signals anymore, so let's empty + * listening_signals. */ + pa_hashmap_remove_all(conn_entry->listening_signals); + + for (i = 0; i < n_objects; ++i) + pa_idxset_put(conn_entry->all_signals_objects, pa_xstrdup(objects[i]), NULL); + } +} + +void pa_dbus_protocol_remove_signal_listener(pa_dbus_protocol *p, DBusConnection *conn, const char *signal_name) { + struct connection_entry *conn_entry = NULL; + struct signal_paths_entry *signal_paths_entry = NULL; + + pa_assert(p); + pa_assert(conn); + + pa_assert_se((conn_entry = pa_hashmap_get(p->connections, conn))); + + if (signal_name) { + if ((signal_paths_entry = pa_hashmap_remove(conn_entry->listening_signals, signal_name))) + signal_paths_entry_free(signal_paths_entry); + + } else { + conn_entry->listening_for_all_signals = false; + pa_idxset_remove_all(conn_entry->all_signals_objects, pa_xfree); + pa_hashmap_remove_all(conn_entry->listening_signals); + } +} + +void pa_dbus_protocol_send_signal(pa_dbus_protocol *p, DBusMessage *signal_msg) { + struct connection_entry *conn_entry; + struct signal_paths_entry *signal_paths_entry; + void *state = NULL; + DBusMessage *signal_copy; + char *signal_string; + + pa_assert(p); + pa_assert(signal_msg); + pa_assert(dbus_message_get_type(signal_msg) == DBUS_MESSAGE_TYPE_SIGNAL); + pa_assert(dbus_message_get_path(signal_msg)); + pa_assert(dbus_message_get_interface(signal_msg)); + pa_assert(dbus_message_get_member(signal_msg)); + + signal_string = pa_sprintf_malloc("%s.%s", dbus_message_get_interface(signal_msg), dbus_message_get_member(signal_msg)); + + PA_HASHMAP_FOREACH(conn_entry, p->connections, state) { + if ((conn_entry->listening_for_all_signals /* Case 1: listening for all signals */ + && (pa_idxset_get_by_data(conn_entry->all_signals_objects, dbus_message_get_path(signal_msg), NULL) + || pa_idxset_isempty(conn_entry->all_signals_objects))) + + || (!conn_entry->listening_for_all_signals /* Case 2: not listening for all signals */ + && (signal_paths_entry = pa_hashmap_get(conn_entry->listening_signals, signal_string)) + && (pa_idxset_get_by_data(signal_paths_entry->paths, dbus_message_get_path(signal_msg), NULL) + || pa_idxset_isempty(signal_paths_entry->paths)))) { + + pa_assert_se(signal_copy = dbus_message_copy(signal_msg)); + pa_assert_se(dbus_connection_send(conn_entry->connection, signal_copy, NULL)); + dbus_message_unref(signal_copy); + } + } + + pa_xfree(signal_string); +} + +const char **pa_dbus_protocol_get_extensions(pa_dbus_protocol *p, unsigned *n) { + const char **extensions; + const char *ext_name; + void *state = NULL; + unsigned i = 0; + + pa_assert(p); + pa_assert(n); + + *n = pa_idxset_size(p->extensions); + + if (*n <= 0) + return NULL; + + extensions = pa_xnew(const char *, *n); + + while ((ext_name = pa_idxset_iterate(p->extensions, &state, NULL))) + extensions[i++] = ext_name; + + return extensions; +} + +int pa_dbus_protocol_register_extension(pa_dbus_protocol *p, const char *name) { + char *internal_name; + + pa_assert(p); + pa_assert(name); + + internal_name = pa_xstrdup(name); + + if (pa_idxset_put(p->extensions, internal_name, NULL) < 0) { + pa_xfree(internal_name); + return -1; + } + + pa_hook_fire(&p->hooks[PA_DBUS_PROTOCOL_HOOK_EXTENSION_REGISTERED], internal_name); + + return 0; +} + +int pa_dbus_protocol_unregister_extension(pa_dbus_protocol *p, const char *name) { + char *internal_name; + + pa_assert(p); + pa_assert(name); + + if (!(internal_name = pa_idxset_remove_by_data(p->extensions, name, NULL))) + return -1; + + pa_hook_fire(&p->hooks[PA_DBUS_PROTOCOL_HOOK_EXTENSION_UNREGISTERED], internal_name); + + pa_xfree(internal_name); + + return 0; +} + +pa_hook_slot *pa_dbus_protocol_hook_connect( + pa_dbus_protocol *p, + pa_dbus_protocol_hook_t hook, + pa_hook_priority_t prio, + pa_hook_cb_t cb, + void *data) { + pa_assert(p); + pa_assert(hook < PA_DBUS_PROTOCOL_HOOK_MAX); + pa_assert(cb); + + return pa_hook_connect(&p->hooks[hook], prio, cb, data); +} diff --git a/src/pulsecore/protocol-dbus.h b/src/pulsecore/protocol-dbus.h new file mode 100644 index 0000000..fdc1ae9 --- /dev/null +++ b/src/pulsecore/protocol-dbus.h @@ -0,0 +1,215 @@ +#ifndef fooprotocoldbushfoo +#define fooprotocoldbushfoo + +/*** + This file is part of PulseAudio. + + Copyright 2009 Tanu Kaskinen + + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <dbus/dbus.h> + +#include <pulsecore/core.h> +#include <pulsecore/macro.h> + +#define PA_DBUS_DEFAULT_PORT 24883 +#define PA_DBUS_SOCKET_NAME "dbus-socket" + +#define PA_DBUS_SYSTEM_SOCKET_PATH PA_SYSTEM_RUNTIME_PATH PA_PATH_SEP PA_DBUS_SOCKET_NAME + +#define PA_DBUS_CORE_INTERFACE "org.PulseAudio.Core1" +#define PA_DBUS_CORE_OBJECT_PATH "/org/pulseaudio/core1" + +#define PA_DBUS_ERROR_NO_SUCH_INTERFACE PA_DBUS_CORE_INTERFACE ".NoSuchInterfaceError" +#define PA_DBUS_ERROR_NO_SUCH_PROPERTY PA_DBUS_CORE_INTERFACE ".NoSuchPropertyError" +#define PA_DBUS_ERROR_NOT_FOUND PA_DBUS_CORE_INTERFACE ".NotFoundError" + +/* Returns the default address of the server type in the escaped form. For + * PA_SERVER_TYPE_NONE an empty string is returned. The caller frees the + * string. */ +char *pa_get_dbus_address_from_server_type(pa_server_type_t server_type); + +typedef struct pa_dbus_protocol pa_dbus_protocol; + +/* This function either creates a new pa_dbus_protocol object, or if one + * already exists, increases the reference count. */ +pa_dbus_protocol* pa_dbus_protocol_get(pa_core *c); + +pa_dbus_protocol* pa_dbus_protocol_ref(pa_dbus_protocol *p); +void pa_dbus_protocol_unref(pa_dbus_protocol *p); + +/* Called when a received message needs handling. Completely ignoring the + * message isn't a good idea; if you can't handle the message, reply with an + * error. + * + * The message signature is already checked against the introspection data, so + * you don't have to do that yourself. + * + * All messages are method calls. */ +typedef void (*pa_dbus_receive_cb_t)(DBusConnection *conn, DBusMessage *msg, void *userdata); + +/* A specialized version of pa_dbus_receive_cb_t: the additional iterator + * argument points to the element inside the new value variant. + * + * The new value signature is checked against the introspection data, so you + * don't have to do that yourself. */ +typedef void (*pa_dbus_set_property_cb_t)(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata); + +typedef struct pa_dbus_arg_info { + const char *name; + const char *type; + const char *direction; /* NULL for signal arguments. */ +} pa_dbus_arg_info; + +typedef struct pa_dbus_signal_info { + const char *name; + const pa_dbus_arg_info *arguments; /* NULL, if the signal has no args. */ + unsigned n_arguments; +} pa_dbus_signal_info; + +typedef struct pa_dbus_method_handler { + const char *method_name; + const pa_dbus_arg_info *arguments; /* NULL, if the method has no args. */ + unsigned n_arguments; + pa_dbus_receive_cb_t receive_cb; +} pa_dbus_method_handler; + +typedef struct pa_dbus_property_handler { + const char *property_name; + const char *type; + + /* The access mode for the property is determined by checking whether + * get_cb or set_cb is NULL. */ + pa_dbus_receive_cb_t get_cb; + pa_dbus_set_property_cb_t set_cb; +} pa_dbus_property_handler; + +typedef struct pa_dbus_interface_info { + const char* name; + const pa_dbus_method_handler *method_handlers; /* NULL, if the interface has no methods. */ + unsigned n_method_handlers; + const pa_dbus_property_handler *property_handlers; /* NULL, if the interface has no properties. */ + unsigned n_property_handlers; + const pa_dbus_receive_cb_t get_all_properties_cb; /* May be NULL, in which case GetAll returns an error. */ + const pa_dbus_signal_info *signals; /* NULL, if the interface has no signals. */ + unsigned n_signals; +} pa_dbus_interface_info; + +/* The following functions may only be called from the main thread. */ + +/* Registers the given interface to the given object path. It doesn't matter + * whether or not the object has already been registered; if it is, then its + * interface set is extended. + * + * Introspection requests are handled automatically. + * + * Userdata is passed to all the callbacks. + * + * Fails and returns a negative number if the object already has the interface + * registered. */ +int pa_dbus_protocol_add_interface(pa_dbus_protocol *p, const char *path, const pa_dbus_interface_info *info, void *userdata); + +/* Returns a negative number if the given object doesn't have the given + * interface registered. */ +int pa_dbus_protocol_remove_interface(pa_dbus_protocol *p, const char* path, const char* interface); + +/* Fails and returns a negative number if the connection is already + * registered. */ +int pa_dbus_protocol_register_connection(pa_dbus_protocol *p, DBusConnection *conn, pa_client *client); + +/* Returns a negative number if the connection isn't registered. */ +int pa_dbus_protocol_unregister_connection(pa_dbus_protocol *p, DBusConnection *conn); + +/* Returns NULL if the connection isn't registered. */ +pa_client *pa_dbus_protocol_get_client(pa_dbus_protocol *p, DBusConnection *conn); + +/* Enables signal receiving for the given connection. The connection must have + * been registered earlier. The signal string must contain both the signal + * interface and the signal name, concatenated using a period as the separator. + * + * If the signal argument is NULL, all signals will be sent to the connection, + * otherwise calling this function only adds the given signal to the list of + * signals that will be delivered to the connection. + * + * The objects argument is a list of object paths. If the list is not empty, + * only signals from the given objects are delivered. If this function is + * called multiple time for the same connection and signal, the latest call + * always replaces the previous object list. */ +void pa_dbus_protocol_add_signal_listener( + pa_dbus_protocol *p, + DBusConnection *conn, + const char *signal, + char **objects, + unsigned n_objects); + +/* Disables the delivery of the signal for the given connection. The connection + * must have been registered. If signal is NULL, all signals are disabled. If + * signal is non-NULL and _add_signal_listener() was previously called with + * NULL signal (causing all signals to be enabled), this function doesn't do + * anything. Also, if the signal wasn't enabled before, this function doesn't + * do anything in that case either. */ +void pa_dbus_protocol_remove_signal_listener(pa_dbus_protocol *p, DBusConnection *conn, const char *signal); + +/* Sends the given signal to all interested clients. By default no signals are + * sent - clients have to explicitly to request signals by calling + * .Core1.ListenForSignal. That method's handler then calls + * pa_dbus_protocol_add_signal_listener(). */ +void pa_dbus_protocol_send_signal(pa_dbus_protocol *p, DBusMessage *signal); + +/* Returns an array of extension identifier strings. The strings pointers point + * to the internal copies, so don't free the strings. The caller must free the + * array, however. Also, do not save the returned pointer or any of the string + * pointers, because the contained strings may be freed at any time. If you + * need to save the array, copy it. */ +const char **pa_dbus_protocol_get_extensions(pa_dbus_protocol *p, unsigned *n); + +/* Modules that want to provide a D-Bus interface for clients should register + * an identifier that the clients can use to check whether the additional + * functionality is available. + * + * This function registers the extension with the given name. It is recommended + * that the name follows the D-Bus interface naming convention, so that the + * names remain unique in case there will be at some point in the future + * extensions that aren't included with the main PulseAudio source tree. For + * in-tree extensions the convention is to use the org.PulseAudio.Ext + * namespace. + * + * It is suggested that the name contains a version number, and whenever the + * extension interface is modified in non-backwards compatible way, the version + * number is incremented. + * + * Fails and returns a negative number if the extension is already registered. + */ +int pa_dbus_protocol_register_extension(pa_dbus_protocol *p, const char *name); + +/* Returns a negative number if the extension isn't registered. */ +int pa_dbus_protocol_unregister_extension(pa_dbus_protocol *p, const char *name); + +/* All hooks have the pa_dbus_protocol object as hook data. */ +typedef enum pa_dbus_protocol_hook { + PA_DBUS_PROTOCOL_HOOK_EXTENSION_REGISTERED, /* Extension name as call data. */ + PA_DBUS_PROTOCOL_HOOK_EXTENSION_UNREGISTERED, /* Extension name as call data. */ + PA_DBUS_PROTOCOL_HOOK_MAX +} pa_dbus_protocol_hook_t; + +pa_hook_slot *pa_dbus_protocol_hook_connect( + pa_dbus_protocol *p, + pa_dbus_protocol_hook_t hook, + pa_hook_priority_t prio, + pa_hook_cb_t cb, + void *data); + +#endif diff --git a/src/pulsecore/protocol-esound.c b/src/pulsecore/protocol-esound.c new file mode 100644 index 0000000..d54c7f8 --- /dev/null +++ b/src/pulsecore/protocol-esound.c @@ -0,0 +1,1734 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> + +#include <pulse/rtclock.h> +#include <pulse/sample.h> +#include <pulse/timeval.h> +#include <pulse/utf8.h> +#include <pulse/xmalloc.h> +#include <pulse/proplist.h> + +#include <pulsecore/esound.h> +#include <pulsecore/memblock.h> +#include <pulsecore/client.h> +#include <pulsecore/sink-input.h> +#include <pulsecore/sink.h> +#include <pulsecore/source-output.h> +#include <pulsecore/source.h> +#include <pulsecore/core-scache.h> +#include <pulsecore/sample-util.h> +#include <pulsecore/namereg.h> +#include <pulsecore/log.h> +#include <pulsecore/core-util.h> +#include <pulsecore/core-error.h> +#include <pulsecore/ipacl.h> +#include <pulsecore/macro.h> +#include <pulsecore/thread-mq.h> +#include <pulsecore/shared.h> +#include <pulsecore/endianmacros.h> + +#include "protocol-esound.h" + +/* Don't accept more connection than this */ +#define MAX_CONNECTIONS 64 + +/* Kick a client if it doesn't authenticate within this time */ +#define AUTH_TIMEOUT (5*PA_USEC_PER_SEC) + +#define DEFAULT_COOKIE_FILE ".esd_auth" + +#define PLAYBACK_BUFFER_SECONDS (.25) +#define PLAYBACK_BUFFER_FRAGMENTS (10) +#define RECORD_BUFFER_SECONDS (5) + +#define MAX_CACHE_SAMPLE_SIZE (2048000) + +#define DEFAULT_SINK_LATENCY (150*PA_USEC_PER_MSEC) +#define DEFAULT_SOURCE_LATENCY (150*PA_USEC_PER_MSEC) + +#define SCACHE_PREFIX "esound." + +/* This is heavily based on esound's code */ + +typedef struct connection { + pa_msgobject parent; + + uint32_t index; + bool dead; + pa_esound_protocol *protocol; + pa_esound_options *options; + pa_iochannel *io; + pa_client *client; + bool authorized, swap_byte_order; + void *write_data; + size_t write_data_alloc, write_data_index, write_data_length; + void *read_data; + size_t read_data_alloc, read_data_length; + esd_proto_t request; + esd_client_state_t state; + pa_sink_input *sink_input; + pa_source_output *source_output; + pa_memblockq *input_memblockq, *output_memblockq; + pa_defer_event *defer_event; + + char *original_name; + + struct { + pa_memblock *current_memblock; + size_t memblock_index; + pa_atomic_t missing; + bool underrun; + } playback; + + struct { + pa_memchunk memchunk; + char *name; + pa_sample_spec sample_spec; + } scache; + + pa_time_event *auth_timeout_event; +} connection; + +PA_DEFINE_PRIVATE_CLASS(connection, pa_msgobject); +#define CONNECTION(o) (connection_cast(o)) + +struct pa_esound_protocol { + PA_REFCNT_DECLARE; + + pa_core *core; + pa_idxset *connections; + unsigned n_player; +}; + +enum { + SINK_INPUT_MESSAGE_POST_DATA = PA_SINK_INPUT_MESSAGE_MAX, /* data from main loop to sink input */ + SINK_INPUT_MESSAGE_DISABLE_PREBUF +}; + +enum { + CONNECTION_MESSAGE_REQUEST_DATA, + CONNECTION_MESSAGE_POST_DATA, + CONNECTION_MESSAGE_UNLINK_CONNECTION +}; + +typedef struct proto_handler { + size_t data_length; + int (*proc)(connection *c, esd_proto_t request, const void *data, size_t length); + const char *description; +} esd_proto_handler_info_t; + +static int sink_input_pop_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk); +static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes); +static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes); +static void sink_input_kill_cb(pa_sink_input *i); +static int sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk); +static pa_usec_t source_output_get_latency_cb(pa_source_output *o); + +static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk); +static void source_output_kill_cb(pa_source_output *o); + +static int esd_proto_connect(connection *c, esd_proto_t request, const void *data, size_t length); +static int esd_proto_stream_play(connection *c, esd_proto_t request, const void *data, size_t length); +static int esd_proto_stream_record(connection *c, esd_proto_t request, const void *data, size_t length); +static int esd_proto_get_latency(connection *c, esd_proto_t request, const void *data, size_t length); +static int esd_proto_server_info(connection *c, esd_proto_t request, const void *data, size_t length); +static int esd_proto_all_info(connection *c, esd_proto_t request, const void *data, size_t length); +static int esd_proto_stream_pan(connection *c, esd_proto_t request, const void *data, size_t length); +static int esd_proto_sample_pan(connection *c, esd_proto_t request, const void *data, size_t length); +static int esd_proto_sample_cache(connection *c, esd_proto_t request, const void *data, size_t length); +static int esd_proto_sample_free_or_play(connection *c, esd_proto_t request, const void *data, size_t length); +static int esd_proto_sample_get_id(connection *c, esd_proto_t request, const void *data, size_t length); +static int esd_proto_standby_or_resume(connection *c, esd_proto_t request, const void *data, size_t length); +static int esd_proto_standby_mode(connection *c, esd_proto_t request, const void *data, size_t length); + +/* the big map of protocol handler info */ +static struct proto_handler proto_map[ESD_PROTO_MAX] = { + { ESD_KEY_LEN + sizeof(int), esd_proto_connect, "connect" }, + { ESD_KEY_LEN + sizeof(int), NULL, "lock" }, + { ESD_KEY_LEN + sizeof(int), NULL, "unlock" }, + + { ESD_NAME_MAX + 2 * sizeof(int), esd_proto_stream_play, "stream play" }, + { ESD_NAME_MAX + 2 * sizeof(int), esd_proto_stream_record, "stream rec" }, + { ESD_NAME_MAX + 2 * sizeof(int), esd_proto_stream_record, "stream mon" }, + + { ESD_NAME_MAX + 3 * sizeof(int), esd_proto_sample_cache, "sample cache" }, /* 6 */ + { sizeof(int), esd_proto_sample_free_or_play, "sample free" }, + { sizeof(int), esd_proto_sample_free_or_play, "sample play" }, /* 8 */ + { sizeof(int), NULL, "sample loop" }, + { sizeof(int), NULL, "sample stop" }, + { (size_t) -1, NULL, "TODO: sample kill" }, + + { ESD_KEY_LEN + sizeof(int), esd_proto_standby_or_resume, "standby" }, + { ESD_KEY_LEN + sizeof(int), esd_proto_standby_or_resume, "resume" }, /* 13 */ + + { ESD_NAME_MAX, esd_proto_sample_get_id, "sample getid" }, /* 14 */ + { ESD_NAME_MAX + 2 * sizeof(int), NULL, "stream filter" }, + + { sizeof(int), esd_proto_server_info, "server info" }, + { sizeof(int), esd_proto_all_info, "all info" }, + { (size_t) -1, NULL, "TODO: subscribe" }, + { (size_t) -1, NULL, "TODO: unsubscribe" }, + + { 3 * sizeof(int), esd_proto_stream_pan, "stream pan"}, + { 3 * sizeof(int), esd_proto_sample_pan, "sample pan" }, + + { sizeof(int), esd_proto_standby_mode, "standby mode" }, + { 0, esd_proto_get_latency, "get latency" } +}; + +static void connection_unlink(connection *c) { + pa_assert(c); + + if (!c->protocol) + return; + + if (c->options) { + pa_esound_options_unref(c->options); + c->options = NULL; + } + + if (c->sink_input) { + pa_sink_input_unlink(c->sink_input); + pa_sink_input_unref(c->sink_input); + c->sink_input = NULL; + } + + if (c->source_output) { + pa_source_output_unlink(c->source_output); + pa_source_output_unref(c->source_output); + c->source_output = NULL; + } + + if (c->client) { + pa_client_free(c->client); + c->client = NULL; + } + + if (c->state == ESD_STREAMING_DATA) + c->protocol->n_player--; + + if (c->io) { + pa_iochannel_free(c->io); + c->io = NULL; + } + + if (c->defer_event) { + c->protocol->core->mainloop->defer_free(c->defer_event); + c->defer_event = NULL; + } + + if (c->auth_timeout_event) { + c->protocol->core->mainloop->time_free(c->auth_timeout_event); + c->auth_timeout_event = NULL; + } + + pa_assert_se(pa_idxset_remove_by_data(c->protocol->connections, c, NULL) == c); + c->protocol = NULL; + connection_unref(c); +} + +static void connection_free(pa_object *obj) { + connection *c = CONNECTION(obj); + pa_assert(c); + + if (c->input_memblockq) + pa_memblockq_free(c->input_memblockq); + if (c->output_memblockq) + pa_memblockq_free(c->output_memblockq); + + if (c->playback.current_memblock) + pa_memblock_unref(c->playback.current_memblock); + + pa_xfree(c->read_data); + pa_xfree(c->write_data); + + if (c->scache.memchunk.memblock) + pa_memblock_unref(c->scache.memchunk.memblock); + pa_xfree(c->scache.name); + + pa_xfree(c->original_name); + pa_xfree(c); +} + +static void connection_write_prepare(connection *c, size_t length) { + size_t t; + pa_assert(c); + + t = c->write_data_length+length; + + if (c->write_data_alloc < t) + c->write_data = pa_xrealloc(c->write_data, c->write_data_alloc = t); + + pa_assert(c->write_data); +} + +static void connection_write(connection *c, const void *data, size_t length) { + size_t i; + pa_assert(c); + + c->protocol->core->mainloop->defer_enable(c->defer_event, 1); + + connection_write_prepare(c, length); + + pa_assert(c->write_data); + + i = c->write_data_length; + c->write_data_length += length; + + memcpy((uint8_t*) c->write_data + i, data, length); +} + +static void format_esd2native(int format, bool swap_bytes, pa_sample_spec *ss) { + pa_assert(ss); + + ss->channels = (uint8_t) (((format & ESD_MASK_CHAN) == ESD_STEREO) ? 2 : 1); + if ((format & ESD_MASK_BITS) == ESD_BITS16) + ss->format = swap_bytes ? PA_SAMPLE_S16RE : PA_SAMPLE_S16NE; + else + ss->format = PA_SAMPLE_U8; +} + +static int format_native2esd(pa_sample_spec *ss) { + int format = 0; + + format = (ss->format == PA_SAMPLE_U8) ? ESD_BITS8 : ESD_BITS16; + format |= (ss->channels >= 2) ? ESD_STEREO : ESD_MONO; + + return format; +} + +#define CHECK_VALIDITY(expression, ...) do { \ + if (PA_UNLIKELY(!(expression))) { \ + pa_log_warn(__FILE__ ": " __VA_ARGS__); \ + return -1; \ + } \ + } while(0); + +/*** esound commands ***/ + +static int esd_proto_connect(connection *c, esd_proto_t request, const void *data, size_t length) { + uint32_t ekey; + int ok; + + connection_assert_ref(c); + pa_assert(data); + pa_assert(length == (ESD_KEY_LEN + sizeof(uint32_t))); + + if (!c->authorized && c->options->auth_cookie) { + const uint8_t*key; + + if ((key = pa_auth_cookie_read(c->options->auth_cookie, ESD_KEY_LEN))) + if (memcmp(data, key, ESD_KEY_LEN) == 0) + c->authorized = true; + } + + if (!c->authorized) { + pa_log("Kicked client with invalid authentication key."); + return -1; + } + + if (c->auth_timeout_event) { + c->protocol->core->mainloop->time_free(c->auth_timeout_event); + c->auth_timeout_event = NULL; + } + + data = (const char*)data + ESD_KEY_LEN; + + memcpy(&ekey, data, sizeof(uint32_t)); + if (ekey == ESD_ENDIAN_KEY) + c->swap_byte_order = false; + else if (ekey == ESD_SWAP_ENDIAN_KEY) + c->swap_byte_order = true; + else { + pa_log_warn("Client sent invalid endian key"); + return -1; + } + + pa_proplist_sets(c->client->proplist, "esound.byte_order", c->swap_byte_order ? "reverse" : "native"); + + ok = 1; + connection_write(c, &ok, sizeof(int)); + return 0; +} + +static int esd_proto_stream_play(connection *c, esd_proto_t request, const void *data, size_t length) { + char name[ESD_NAME_MAX], *utf8_name; + int32_t format, rate; + pa_sample_spec ss; + size_t l; + pa_sink *sink = NULL; + pa_sink_input_new_data sdata; + pa_memchunk silence; + + connection_assert_ref(c); + pa_assert(data); + pa_assert(length == (sizeof(int32_t)*2+ESD_NAME_MAX)); + + memcpy(&format, data, sizeof(int32_t)); + format = PA_MAYBE_INT32_SWAP(c->swap_byte_order, format); + data = (const char*) data + sizeof(int32_t); + + memcpy(&rate, data, sizeof(int32_t)); + rate = PA_MAYBE_INT32_SWAP(c->swap_byte_order, rate); + data = (const char*) data + sizeof(int32_t); + + ss.rate = (uint32_t) rate; + format_esd2native(format, c->swap_byte_order, &ss); + + CHECK_VALIDITY(pa_sample_spec_valid(&ss), "Invalid sample specification"); + + if (c->options->default_sink) { + sink = pa_namereg_get(c->protocol->core, c->options->default_sink, PA_NAMEREG_SINK); + CHECK_VALIDITY(sink, "No such sink: %s", pa_strnull(c->options->default_sink)); + } + + pa_strlcpy(name, data, sizeof(name)); + + utf8_name = pa_utf8_filter(name); + pa_client_set_name(c->client, utf8_name); + pa_xfree(utf8_name); + + c->original_name = pa_xstrdup(name); + + pa_assert(!c->sink_input && !c->input_memblockq); + + pa_sink_input_new_data_init(&sdata); + sdata.driver = __FILE__; + sdata.module = c->options->module; + sdata.client = c->client; + if (sink) + pa_sink_input_new_data_set_sink(&sdata, sink, false, true); + pa_sink_input_new_data_set_sample_spec(&sdata, &ss); + + pa_sink_input_new(&c->sink_input, c->protocol->core, &sdata); + pa_sink_input_new_data_done(&sdata); + + CHECK_VALIDITY(c->sink_input, "Failed to create sink input."); + + l = (size_t) ((double) pa_bytes_per_second(&ss)*PLAYBACK_BUFFER_SECONDS); + pa_sink_input_get_silence(c->sink_input, &silence); + c->input_memblockq = pa_memblockq_new( + "esound protocol connection input_memblockq", + 0, + l, + l, + &ss, + (size_t) -1, + l/PLAYBACK_BUFFER_FRAGMENTS, + 0, + &silence); + pa_memblock_unref(silence.memblock); + pa_iochannel_socket_set_rcvbuf(c->io, l); + + c->sink_input->parent.process_msg = sink_input_process_msg; + c->sink_input->pop = sink_input_pop_cb; + c->sink_input->process_rewind = sink_input_process_rewind_cb; + c->sink_input->update_max_rewind = sink_input_update_max_rewind_cb; + c->sink_input->kill = sink_input_kill_cb; + c->sink_input->userdata = c; + + pa_sink_input_set_requested_latency(c->sink_input, DEFAULT_SINK_LATENCY); + + c->state = ESD_STREAMING_DATA; + + c->protocol->n_player++; + + pa_atomic_store(&c->playback.missing, (int) pa_memblockq_pop_missing(c->input_memblockq)); + + pa_sink_input_put(c->sink_input); + + return 0; +} + +static int esd_proto_stream_record(connection *c, esd_proto_t request, const void *data, size_t length) { + char name[ESD_NAME_MAX], *utf8_name; + int32_t format, rate; + pa_source *source = NULL; + pa_sample_spec ss; + size_t l; + pa_source_output_new_data sdata; + + connection_assert_ref(c); + pa_assert(data); + pa_assert(length == (sizeof(int32_t)*2+ESD_NAME_MAX)); + + memcpy(&format, data, sizeof(int32_t)); + format = PA_MAYBE_INT32_SWAP(c->swap_byte_order, format); + data = (const char*) data + sizeof(int32_t); + + memcpy(&rate, data, sizeof(int32_t)); + rate = PA_MAYBE_INT32_SWAP(c->swap_byte_order, rate); + data = (const char*) data + sizeof(int32_t); + + ss.rate = (uint32_t) rate; + format_esd2native(format, c->swap_byte_order, &ss); + + CHECK_VALIDITY(pa_sample_spec_valid(&ss), "Invalid sample specification."); + + if (request == ESD_PROTO_STREAM_MON) { + pa_sink* sink; + + sink = pa_namereg_get(c->protocol->core, c->options->default_sink, PA_NAMEREG_SINK); + CHECK_VALIDITY(sink, "No such sink: %s", pa_strnull(c->options->default_sink)); + + source = sink->monitor_source; + CHECK_VALIDITY(source, "No such source."); + } else { + pa_assert(request == ESD_PROTO_STREAM_REC); + + if (c->options->default_source) { + source = pa_namereg_get(c->protocol->core, c->options->default_source, PA_NAMEREG_SOURCE); + CHECK_VALIDITY(source, "No such source: %s", pa_strnull(c->options->default_source)); + } + } + + pa_strlcpy(name, data, sizeof(name)); + + utf8_name = pa_utf8_filter(name); + pa_client_set_name(c->client, utf8_name); + pa_xfree(utf8_name); + + c->original_name = pa_xstrdup(name); + + pa_assert(!c->output_memblockq && !c->source_output); + + pa_source_output_new_data_init(&sdata); + sdata.driver = __FILE__; + sdata.module = c->options->module; + sdata.client = c->client; + if (source) + pa_source_output_new_data_set_source(&sdata, source, false, true); + pa_source_output_new_data_set_sample_spec(&sdata, &ss); + + pa_source_output_new(&c->source_output, c->protocol->core, &sdata); + pa_source_output_new_data_done(&sdata); + + CHECK_VALIDITY(c->source_output, "Failed to create source output."); + + l = (size_t) (pa_bytes_per_second(&ss)*RECORD_BUFFER_SECONDS); + c->output_memblockq = pa_memblockq_new( + "esound protocol connection output_memblockq", + 0, + l, + l, + &ss, + 1, + 0, + 0, + NULL); + pa_iochannel_socket_set_sndbuf(c->io, l); + + c->source_output->push = source_output_push_cb; + c->source_output->kill = source_output_kill_cb; + c->source_output->get_latency = source_output_get_latency_cb; + c->source_output->userdata = c; + + pa_source_output_set_requested_latency(c->source_output, DEFAULT_SOURCE_LATENCY); + + c->state = ESD_STREAMING_DATA; + + c->protocol->n_player++; + + pa_source_output_put(c->source_output); + + return 0; +} + +static int esd_proto_get_latency(connection *c, esd_proto_t request, const void *data, size_t length) { + pa_sink *sink; + int32_t latency; + + connection_assert_ref(c); + pa_assert(!data); + pa_assert(length == 0); + + if (!(sink = pa_namereg_get(c->protocol->core, c->options->default_sink, PA_NAMEREG_SINK))) + latency = 0; + else { + double usec = (double) pa_sink_get_requested_latency(sink); + latency = (int) ((usec*44100)/1000000); + } + + latency = PA_MAYBE_INT32_SWAP(c->swap_byte_order, latency); + connection_write(c, &latency, sizeof(int32_t)); + + return 0; +} + +static int esd_proto_server_info(connection *c, esd_proto_t request, const void *data, size_t length) { + int32_t rate = 44100, format = ESD_STEREO|ESD_BITS16; + int32_t response; + pa_sink *sink; + + connection_assert_ref(c); + pa_assert(data); + pa_assert(length == sizeof(int32_t)); + + if ((sink = pa_namereg_get(c->protocol->core, c->options->default_sink, PA_NAMEREG_SINK))) { + rate = (int32_t) sink->sample_spec.rate; + format = format_native2esd(&sink->sample_spec); + } + + connection_write_prepare(c, sizeof(int32_t) * 3); + + response = 0; + connection_write(c, &response, sizeof(int32_t)); + rate = PA_MAYBE_INT32_SWAP(c->swap_byte_order, rate); + connection_write(c, &rate, sizeof(int32_t)); + format = PA_MAYBE_INT32_SWAP(c->swap_byte_order, format); + connection_write(c, &format, sizeof(int32_t)); + + return 0; +} + +static int esd_proto_all_info(connection *c, esd_proto_t request, const void *data, size_t length) { + size_t t, k, s; + connection *conn; + uint32_t idx = PA_IDXSET_INVALID; + unsigned nsamples; + char terminator[sizeof(int32_t)*6+ESD_NAME_MAX]; + + connection_assert_ref(c); + pa_assert(data); + pa_assert(length == sizeof(int32_t)); + + if (esd_proto_server_info(c, request, data, length) < 0) + return -1; + + k = sizeof(int32_t)*5+ESD_NAME_MAX; + s = sizeof(int32_t)*6+ESD_NAME_MAX; + nsamples = pa_idxset_size(c->protocol->core->scache); + t = s*(nsamples+1) + k*(c->protocol->n_player+1); + + connection_write_prepare(c, t); + + memset(terminator, 0, sizeof(terminator)); + + PA_IDXSET_FOREACH(conn, c->protocol->connections, idx) { + int32_t id, format = ESD_BITS16 | ESD_STEREO, rate = 44100, lvolume = ESD_VOLUME_BASE, rvolume = ESD_VOLUME_BASE; + char name[ESD_NAME_MAX]; + + if (conn->state != ESD_STREAMING_DATA) + continue; + + pa_assert(t >= k*2+s); + + if (conn->sink_input) { + pa_cvolume volume; + pa_sink_input_get_volume(conn->sink_input, &volume, true); + rate = (int32_t) conn->sink_input->sample_spec.rate; + lvolume = (int32_t) ((volume.values[0]*ESD_VOLUME_BASE)/PA_VOLUME_NORM); + rvolume = (int32_t) ((volume.values[volume.channels == 2 ? 1 : 0]*ESD_VOLUME_BASE)/PA_VOLUME_NORM); + format = format_native2esd(&conn->sink_input->sample_spec); + } + + /* id */ + id = PA_MAYBE_INT32_SWAP(c->swap_byte_order, (int32_t) (conn->index+1)); + connection_write(c, &id, sizeof(int32_t)); + + /* name */ + memset(name, 0, ESD_NAME_MAX); /* don't leak old data */ + if (conn->original_name) + strncpy(name, conn->original_name, ESD_NAME_MAX); + else if (conn->client && pa_proplist_gets(conn->client->proplist, PA_PROP_APPLICATION_NAME)) + strncpy(name, pa_proplist_gets(conn->client->proplist, PA_PROP_APPLICATION_NAME), ESD_NAME_MAX); + connection_write(c, name, ESD_NAME_MAX); + + /* rate */ + rate = PA_MAYBE_INT32_SWAP(c->swap_byte_order, rate); + connection_write(c, &rate, sizeof(int32_t)); + + /* left */ + lvolume = PA_MAYBE_INT32_SWAP(c->swap_byte_order, lvolume); + connection_write(c, &lvolume, sizeof(int32_t)); + + /*right*/ + rvolume = PA_MAYBE_INT32_SWAP(c->swap_byte_order, rvolume); + connection_write(c, &rvolume, sizeof(int32_t)); + + /*format*/ + format = PA_MAYBE_INT32_SWAP(c->swap_byte_order, format); + connection_write(c, &format, sizeof(int32_t)); + + t -= k; + } + + pa_assert(t == s*(nsamples+1)+k); + t -= k; + + connection_write(c, terminator, k); + + if (nsamples) { + pa_scache_entry *ce; + + idx = PA_IDXSET_INVALID; + + PA_IDXSET_FOREACH(ce, c->protocol->core->scache, idx) { + int32_t id, rate, lvolume, rvolume, format, len; + char name[ESD_NAME_MAX]; + pa_channel_map stereo = { .channels = 2, .map = { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT } }; + pa_cvolume volume; + pa_sample_spec ss; + + pa_assert(t >= s*2); + + if (ce->volume_is_set) { + volume = ce->volume; + pa_cvolume_remap(&volume, &ce->channel_map, &stereo); + } else + pa_cvolume_reset(&volume, 2); + + if (ce->memchunk.memblock) + ss = ce->sample_spec; + else { + ss.format = PA_SAMPLE_S16NE; + ss.rate = 44100; + ss.channels = 2; + } + + /* id */ + id = PA_MAYBE_INT32_SWAP(c->swap_byte_order, (int) (ce->index+1)); + connection_write(c, &id, sizeof(int32_t)); + + /* name */ + memset(name, 0, ESD_NAME_MAX); /* don't leak old data */ + if (strncmp(ce->name, SCACHE_PREFIX, sizeof(SCACHE_PREFIX)-1) == 0) + strncpy(name, ce->name+sizeof(SCACHE_PREFIX)-1, ESD_NAME_MAX); + else + pa_snprintf(name, ESD_NAME_MAX, "native.%s", ce->name); + connection_write(c, name, ESD_NAME_MAX); + + /* rate */ + rate = PA_MAYBE_INT32_SWAP(c->swap_byte_order, (int32_t) ss.rate); + connection_write(c, &rate, sizeof(int32_t)); + + /* left */ + lvolume = PA_MAYBE_INT32_SWAP(c->swap_byte_order, (int32_t) ((volume.values[0]*ESD_VOLUME_BASE)/PA_VOLUME_NORM)); + connection_write(c, &lvolume, sizeof(int32_t)); + + /*right*/ + rvolume = PA_MAYBE_INT32_SWAP(c->swap_byte_order, (int32_t) ((volume.values[1]*ESD_VOLUME_BASE)/PA_VOLUME_NORM)); + connection_write(c, &rvolume, sizeof(int32_t)); + + /*format*/ + format = PA_MAYBE_INT32_SWAP(c->swap_byte_order, format_native2esd(&ss)); + connection_write(c, &format, sizeof(int32_t)); + + /*length*/ + len = PA_MAYBE_INT32_SWAP(c->swap_byte_order, (int) ce->memchunk.length); + connection_write(c, &len, sizeof(int32_t)); + + t -= s; + } + } + + pa_assert(t == s); + + connection_write(c, terminator, s); + + return 0; +} + +static int esd_proto_stream_pan(connection *c, esd_proto_t request, const void *data, size_t length) { + int32_t ok; + uint32_t idx, lvolume, rvolume; + connection *conn; + + connection_assert_ref(c); + pa_assert(data); + pa_assert(length == sizeof(int32_t)*3); + + memcpy(&idx, data, sizeof(uint32_t)); + idx = PA_MAYBE_UINT32_SWAP(c->swap_byte_order, idx) - 1; + data = (const char*)data + sizeof(uint32_t); + + memcpy(&lvolume, data, sizeof(uint32_t)); + lvolume = PA_MAYBE_UINT32_SWAP(c->swap_byte_order, lvolume); + data = (const char*)data + sizeof(uint32_t); + + memcpy(&rvolume, data, sizeof(uint32_t)); + rvolume = PA_MAYBE_UINT32_SWAP(c->swap_byte_order, rvolume); + + if ((conn = pa_idxset_get_by_index(c->protocol->connections, idx)) && conn->sink_input) { + pa_cvolume volume; + volume.values[0] = (lvolume*PA_VOLUME_NORM)/ESD_VOLUME_BASE; + volume.values[1] = (rvolume*PA_VOLUME_NORM)/ESD_VOLUME_BASE; + volume.channels = conn->sink_input->sample_spec.channels; + + pa_sink_input_set_volume(conn->sink_input, &volume, true, true); + ok = 1; + } else + ok = 0; + + connection_write(c, &ok, sizeof(int32_t)); + + return 0; +} + +static int esd_proto_sample_pan(connection *c, esd_proto_t request, const void *data, size_t length) { + int32_t ok = 0; + uint32_t idx, lvolume, rvolume; + pa_cvolume volume; + pa_scache_entry *ce; + + connection_assert_ref(c); + pa_assert(data); + pa_assert(length == sizeof(int32_t)*3); + + memcpy(&idx, data, sizeof(uint32_t)); + idx = PA_MAYBE_UINT32_SWAP(c->swap_byte_order, idx) - 1; + data = (const char*)data + sizeof(uint32_t); + + memcpy(&lvolume, data, sizeof(uint32_t)); + lvolume = PA_MAYBE_UINT32_SWAP(c->swap_byte_order, lvolume); + data = (const char*)data + sizeof(uint32_t); + + memcpy(&rvolume, data, sizeof(uint32_t)); + rvolume = PA_MAYBE_UINT32_SWAP(c->swap_byte_order, rvolume); + + volume.values[0] = (lvolume*PA_VOLUME_NORM)/ESD_VOLUME_BASE; + volume.values[1] = (rvolume*PA_VOLUME_NORM)/ESD_VOLUME_BASE; + volume.channels = 2; + + if ((ce = pa_idxset_get_by_index(c->protocol->core->scache, idx))) { + pa_channel_map stereo = { .channels = 2, .map = { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT } }; + + pa_cvolume_remap(&volume, &stereo, &ce->channel_map); + ce->volume = volume; + ce->volume_is_set = true; + ok = 1; + } + + connection_write(c, &ok, sizeof(int32_t)); + + return 0; +} + +static int esd_proto_sample_cache(connection *c, esd_proto_t request, const void *data, size_t length) { + pa_sample_spec ss; + int32_t format, rate, sc_length; + uint32_t idx; + char name[ESD_NAME_MAX+sizeof(SCACHE_PREFIX)-1]; + + connection_assert_ref(c); + pa_assert(data); + pa_assert(length == (ESD_NAME_MAX+3*sizeof(int32_t))); + + memcpy(&format, data, sizeof(int32_t)); + format = PA_MAYBE_INT32_SWAP(c->swap_byte_order, format); + data = (const char*)data + sizeof(int32_t); + + memcpy(&rate, data, sizeof(int32_t)); + rate = PA_MAYBE_INT32_SWAP(c->swap_byte_order, rate); + data = (const char*)data + sizeof(int32_t); + + ss.rate = (uint32_t) rate; + format_esd2native(format, c->swap_byte_order, &ss); + + CHECK_VALIDITY(pa_sample_spec_valid(&ss), "Invalid sample specification."); + + memcpy(&sc_length, data, sizeof(int32_t)); + sc_length = PA_MAYBE_INT32_SWAP(c->swap_byte_order, sc_length); + data = (const char*)data + sizeof(int32_t); + + CHECK_VALIDITY(sc_length <= MAX_CACHE_SAMPLE_SIZE, "Sample too large (%d bytes).", (int)sc_length); + + strcpy(name, SCACHE_PREFIX); + pa_strlcpy(name+sizeof(SCACHE_PREFIX)-1, data, ESD_NAME_MAX); + + CHECK_VALIDITY(pa_utf8_valid(name), "Invalid UTF8 in sample name."); + + pa_assert(!c->scache.memchunk.memblock); + c->scache.memchunk.memblock = pa_memblock_new(c->protocol->core->mempool, (size_t) sc_length); + c->scache.memchunk.index = 0; + c->scache.memchunk.length = (size_t) sc_length; + c->scache.sample_spec = ss; + pa_assert(!c->scache.name); + c->scache.name = pa_xstrdup(name); + + c->state = ESD_CACHING_SAMPLE; + + pa_scache_add_item(c->protocol->core, c->scache.name, NULL, NULL, NULL, c->client->proplist, &idx); + + idx += 1; + connection_write(c, &idx, sizeof(uint32_t)); + + return 0; +} + +static int esd_proto_sample_get_id(connection *c, esd_proto_t request, const void *data, size_t length) { + int32_t ok; + uint32_t idx; + char name[ESD_NAME_MAX+sizeof(SCACHE_PREFIX)-1]; + + connection_assert_ref(c); + pa_assert(data); + pa_assert(length == ESD_NAME_MAX); + + strcpy(name, SCACHE_PREFIX); + pa_strlcpy(name+sizeof(SCACHE_PREFIX)-1, data, ESD_NAME_MAX); + + CHECK_VALIDITY(pa_utf8_valid(name), "Invalid UTF8 in sample name."); + + ok = -1; + if ((idx = pa_scache_get_id_by_name(c->protocol->core, name)) != PA_IDXSET_INVALID) + ok = (int32_t) idx + 1; + + connection_write(c, &ok, sizeof(int32_t)); + + return 0; +} + +static int esd_proto_sample_free_or_play(connection *c, esd_proto_t request, const void *data, size_t length) { + int32_t ok; + const char *name; + uint32_t idx; + + connection_assert_ref(c); + pa_assert(data); + pa_assert(length == sizeof(int32_t)); + + memcpy(&idx, data, sizeof(uint32_t)); + idx = PA_MAYBE_UINT32_SWAP(c->swap_byte_order, idx) - 1; + + ok = 0; + + if ((name = pa_scache_get_name_by_id(c->protocol->core, idx))) { + if (request == ESD_PROTO_SAMPLE_PLAY) { + pa_sink *sink; + + if ((sink = pa_namereg_get(c->protocol->core, c->options->default_sink, PA_NAMEREG_SINK))) + if (pa_scache_play_item(c->protocol->core, name, sink, PA_VOLUME_NORM, c->client->proplist, NULL) >= 0) + ok = (int32_t) idx + 1; + } else { + pa_assert(request == ESD_PROTO_SAMPLE_FREE); + + if (pa_scache_remove_item(c->protocol->core, name) >= 0) + ok = (int32_t) idx + 1; + } + } + + connection_write(c, &ok, sizeof(int32_t)); + + return 0; +} + +static int esd_proto_standby_or_resume(connection *c, esd_proto_t request, const void *data, size_t length) { + int32_t ok = 1; + + connection_assert_ref(c); + + connection_write_prepare(c, sizeof(int32_t) * 2); + connection_write(c, &ok, sizeof(int32_t)); + + pa_log_debug("%s of all sinks and sources requested by client %" PRIu32 ".", + request == ESD_PROTO_STANDBY ? "Suspending" : "Resuming", c->client->index); + + if (request == ESD_PROTO_STANDBY) { + ok = pa_sink_suspend_all(c->protocol->core, true, PA_SUSPEND_USER) >= 0; + ok &= pa_source_suspend_all(c->protocol->core, true, PA_SUSPEND_USER) >= 0; + } else { + pa_assert(request == ESD_PROTO_RESUME); + ok = pa_sink_suspend_all(c->protocol->core, false, PA_SUSPEND_USER) >= 0; + ok &= pa_source_suspend_all(c->protocol->core, false, PA_SUSPEND_USER) >= 0; + } + + connection_write(c, &ok, sizeof(int32_t)); + + return 0; +} + +static int esd_proto_standby_mode(connection *c, esd_proto_t request, const void *data, size_t length) { + int32_t mode; + pa_sink *sink; + pa_source *source; + + connection_assert_ref(c); + + mode = ESM_RUNNING; + + if ((sink = pa_namereg_get(c->protocol->core, c->options->default_sink, PA_NAMEREG_SINK))) + if (sink->state == PA_SINK_SUSPENDED) + mode = ESM_ON_STANDBY; + + if ((source = pa_namereg_get(c->protocol->core, c->options->default_source, PA_NAMEREG_SOURCE))) + if (source->state == PA_SOURCE_SUSPENDED) + mode = ESM_ON_STANDBY; + + mode = PA_MAYBE_INT32_SWAP(c->swap_byte_order, mode); + + connection_write(c, &mode, sizeof(mode)); + return 0; +} + +/*** client callbacks ***/ + +static void client_kill_cb(pa_client *c) { + pa_assert(c); + + connection_unlink(CONNECTION(c->userdata)); +} + +/*** pa_iochannel callbacks ***/ + +static int do_read(connection *c) { + connection_assert_ref(c); + +/* pa_log("READ"); */ + + if (c->state == ESD_NEXT_REQUEST) { + ssize_t r; + pa_assert(c->read_data_length < sizeof(c->request)); + + if ((r = pa_iochannel_read(c->io, + ((uint8_t*) &c->request) + c->read_data_length, + sizeof(c->request) - c->read_data_length)) <= 0) { + + if (r < 0 && (errno == EINTR || errno == EAGAIN)) + return 0; + + pa_log_debug("read(): %s", r < 0 ? pa_cstrerror(errno) : "EOF"); + return -1; + } + + c->read_data_length += (size_t) r; + + if (c->read_data_length >= sizeof(c->request)) { + struct proto_handler *handler; + + c->request = PA_MAYBE_INT32_SWAP(c->swap_byte_order, c->request); + + if (c->request < ESD_PROTO_CONNECT || c->request >= ESD_PROTO_MAX) { + pa_log("received invalid request."); + return -1; + } + + handler = proto_map+c->request; + +/* pa_log("executing request #%u", c->request); */ + + if (!handler->proc) { + pa_log("received unimplemented request #%u.", c->request); + return -1; + } + + if (handler->data_length == 0) { + c->read_data_length = 0; + + if (handler->proc(c, c->request, NULL, 0) < 0) + return -1; + + } else { + if (c->read_data_alloc < handler->data_length) + c->read_data = pa_xrealloc(c->read_data, c->read_data_alloc = handler->data_length); + pa_assert(c->read_data); + + c->state = ESD_NEEDS_REQDATA; + c->read_data_length = 0; + } + } + + } else if (c->state == ESD_NEEDS_REQDATA) { + ssize_t r; + struct proto_handler *handler = proto_map+c->request; + + pa_assert(handler->proc); + + pa_assert(c->read_data && c->read_data_length < handler->data_length); + + if ((r = pa_iochannel_read(c->io, + (uint8_t*) c->read_data + c->read_data_length, + handler->data_length - c->read_data_length)) <= 0) { + + if (r < 0 && (errno == EINTR || errno == EAGAIN)) + return 0; + + pa_log_debug("read(): %s", r < 0 ? pa_cstrerror(errno) : "EOF"); + return -1; + } + + c->read_data_length += (size_t) r; + if (c->read_data_length >= handler->data_length) { + size_t l = c->read_data_length; + pa_assert(handler->proc); + + c->state = ESD_NEXT_REQUEST; + c->read_data_length = 0; + + if (handler->proc(c, c->request, c->read_data, l) < 0) + return -1; + } + } else if (c->state == ESD_CACHING_SAMPLE) { + ssize_t r; + void *p; + + pa_assert(c->scache.memchunk.memblock); + pa_assert(c->scache.name); + pa_assert(c->scache.memchunk.index < c->scache.memchunk.length); + + p = pa_memblock_acquire(c->scache.memchunk.memblock); + r = pa_iochannel_read(c->io, (uint8_t*) p+c->scache.memchunk.index, c->scache.memchunk.length-c->scache.memchunk.index); + pa_memblock_release(c->scache.memchunk.memblock); + + if (r <= 0) { + if (r < 0 && (errno == EINTR || errno == EAGAIN)) + return 0; + + pa_log_debug("read(): %s", r < 0 ? pa_cstrerror(errno) : "EOF"); + return -1; + } + + c->scache.memchunk.index += (size_t) r; + pa_assert(c->scache.memchunk.index <= c->scache.memchunk.length); + + if (c->scache.memchunk.index == c->scache.memchunk.length) { + uint32_t idx; + + c->scache.memchunk.index = 0; + pa_scache_add_item(c->protocol->core, c->scache.name, &c->scache.sample_spec, NULL, &c->scache.memchunk, c->client->proplist, &idx); + + pa_memblock_unref(c->scache.memchunk.memblock); + pa_memchunk_reset(&c->scache.memchunk); + + pa_xfree(c->scache.name); + c->scache.name = NULL; + + c->state = ESD_NEXT_REQUEST; + + idx += 1; + connection_write(c, &idx, sizeof(uint32_t)); + } + + } else if (c->state == ESD_STREAMING_DATA && c->sink_input) { + pa_memchunk chunk; + ssize_t r; + size_t l; + void *p; + size_t space = 0; + + pa_assert(c->input_memblockq); + +/* pa_log("STREAMING_DATA"); */ + + if ((l = (size_t) pa_atomic_load(&c->playback.missing)) <= 0) + return 0; + + if (c->playback.current_memblock) { + + space = pa_memblock_get_length(c->playback.current_memblock) - c->playback.memblock_index; + + if (space <= 0) { + pa_memblock_unref(c->playback.current_memblock); + c->playback.current_memblock = NULL; + } + } + + if (!c->playback.current_memblock) { + pa_assert_se(c->playback.current_memblock = pa_memblock_new(c->protocol->core->mempool, (size_t) -1)); + c->playback.memblock_index = 0; + + space = pa_memblock_get_length(c->playback.current_memblock); + } + + if (l > space) + l = space; + + p = pa_memblock_acquire(c->playback.current_memblock); + r = pa_iochannel_read(c->io, (uint8_t*) p+c->playback.memblock_index, l); + pa_memblock_release(c->playback.current_memblock); + + if (r <= 0) { + + if (r < 0 && (errno == EINTR || errno == EAGAIN)) + return 0; + + pa_log_debug("read(): %s", r < 0 ? pa_cstrerror(errno) : "EOF"); + return -1; + } + + chunk.memblock = c->playback.current_memblock; + chunk.index = c->playback.memblock_index; + chunk.length = (size_t) r; + + c->playback.memblock_index += (size_t) r; + + pa_atomic_sub(&c->playback.missing, (int) r); + pa_asyncmsgq_post(c->sink_input->sink->asyncmsgq, PA_MSGOBJECT(c->sink_input), SINK_INPUT_MESSAGE_POST_DATA, NULL, 0, &chunk, NULL); + } + + return 0; +} + +static int do_write(connection *c) { + connection_assert_ref(c); + +/* pa_log("WRITE"); */ + + if (c->write_data_length) { + ssize_t r; + + pa_assert(c->write_data_index < c->write_data_length); + if ((r = pa_iochannel_write(c->io, (uint8_t*) c->write_data+c->write_data_index, c->write_data_length-c->write_data_index)) < 0) { + pa_log("write(): %s", pa_cstrerror(errno)); + return -1; + } + + c->write_data_index += (size_t) r; + if (c->write_data_index >= c->write_data_length) + c->write_data_length = c->write_data_index = 0; + + return 1; + + } else if (c->state == ESD_STREAMING_DATA && c->source_output) { + pa_memchunk chunk; + ssize_t r; + void *p; + + if (pa_memblockq_peek(c->output_memblockq, &chunk) < 0) + return 0; + + pa_assert(chunk.memblock); + pa_assert(chunk.length); + + p = pa_memblock_acquire(chunk.memblock); + r = pa_iochannel_write(c->io, (uint8_t*) p+chunk.index, chunk.length); + pa_memblock_release(chunk.memblock); + + pa_memblock_unref(chunk.memblock); + + if (r < 0) { + pa_log("write(): %s", pa_cstrerror(errno)); + return -1; + } + + pa_memblockq_drop(c->output_memblockq, (size_t) r); + return 1; + } + + return 0; +} + +static void do_work(connection *c) { + connection_assert_ref(c); + + c->protocol->core->mainloop->defer_enable(c->defer_event, 0); + + if (c->dead) + return; + + if (pa_iochannel_is_readable(c->io)) + if (do_read(c) < 0) + goto fail; + + if (c->state == ESD_STREAMING_DATA && !c->sink_input && pa_iochannel_is_hungup(c->io)) + /* In case we are in capture mode we will never call read() + * on the socket, hence we need to detect the hangup manually + * here, instead of simply waiting for read() to return 0. */ + goto fail; + + while (pa_iochannel_is_writable(c->io)) { + int r = do_write(c); + if (r < 0) + goto fail; + if (r == 0) + break; + } + + return; + +fail: + + if (c->state == ESD_STREAMING_DATA && c->sink_input) { + c->dead = true; + + pa_iochannel_free(c->io); + c->io = NULL; + + pa_asyncmsgq_post(c->sink_input->sink->asyncmsgq, PA_MSGOBJECT(c->sink_input), SINK_INPUT_MESSAGE_DISABLE_PREBUF, NULL, 0, NULL, NULL); + } else + connection_unlink(c); +} + +static void io_callback(pa_iochannel*io, void *userdata) { + connection *c = CONNECTION(userdata); + + connection_assert_ref(c); + pa_assert(io); + + do_work(c); +} + +static void defer_callback(pa_mainloop_api*a, pa_defer_event *e, void *userdata) { + connection *c = CONNECTION(userdata); + + connection_assert_ref(c); + pa_assert(e); + + do_work(c); +} + +static int connection_process_msg(pa_msgobject *o, int code, void*userdata, int64_t offset, pa_memchunk *chunk) { + connection *c = CONNECTION(o); + connection_assert_ref(c); + + if (!c->protocol) + return -1; + + switch (code) { + case CONNECTION_MESSAGE_REQUEST_DATA: + do_work(c); + break; + + case CONNECTION_MESSAGE_POST_DATA: +/* pa_log("got data %u", chunk->length); */ + pa_memblockq_push_align(c->output_memblockq, chunk); + do_work(c); + break; + + case CONNECTION_MESSAGE_UNLINK_CONNECTION: + connection_unlink(c); + break; + } + + return 0; +} + +/*** sink_input callbacks ***/ + +/* Called from thread context */ +static int sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk) { + pa_sink_input *i = PA_SINK_INPUT(o); + connection*c; + + pa_sink_input_assert_ref(i); + c = CONNECTION(i->userdata); + connection_assert_ref(c); + + switch (code) { + + case SINK_INPUT_MESSAGE_POST_DATA: { + pa_assert(chunk); + + /* New data from the main loop */ + pa_memblockq_push_align(c->input_memblockq, chunk); + + if (pa_memblockq_is_readable(c->input_memblockq) && c->playback.underrun) { + pa_log_debug("Requesting rewind due to end of underrun."); + pa_sink_input_request_rewind(c->sink_input, 0, false, true, false); + } + +/* pa_log("got data, %u", pa_memblockq_get_length(c->input_memblockq)); */ + + return 0; + } + + case SINK_INPUT_MESSAGE_DISABLE_PREBUF: + pa_memblockq_prebuf_disable(c->input_memblockq); + return 0; + + case PA_SINK_INPUT_MESSAGE_GET_LATENCY: { + pa_usec_t *r = userdata; + + /* The default handler will add in the extra latency added by the resampler. */ + *r = pa_bytes_to_usec(pa_memblockq_get_length(c->input_memblockq), &c->sink_input->sample_spec); + } + /* Fall through. */ + + default: + return pa_sink_input_process_msg(o, code, userdata, offset, chunk); + } +} + +/* Called from thread context */ +static int sink_input_pop_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) { + connection*c; + + pa_sink_input_assert_ref(i); + c = CONNECTION(i->userdata); + connection_assert_ref(c); + pa_assert(chunk); + + if (pa_memblockq_peek(c->input_memblockq, chunk) < 0) { + + c->playback.underrun = true; + + if (c->dead && pa_sink_input_safe_to_remove(i)) + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(c), CONNECTION_MESSAGE_UNLINK_CONNECTION, NULL, 0, NULL, NULL); + + return -1; + } else { + size_t m; + + c->playback.underrun = false; + + chunk->length = PA_MIN(length, chunk->length); + pa_memblockq_drop(c->input_memblockq, chunk->length); + m = pa_memblockq_pop_missing(c->input_memblockq); + + if (m > 0) + if (pa_atomic_add(&c->playback.missing, (int) m) <= 0) + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(c), CONNECTION_MESSAGE_REQUEST_DATA, NULL, 0, NULL, NULL); + + return 0; + } +} + +/* Called from thread context */ +static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) { + connection *c; + + pa_sink_input_assert_ref(i); + c = CONNECTION(i->userdata); + connection_assert_ref(c); + + /* If we are in an underrun, then we don't rewind */ + if (i->thread_info.underrun_for > 0) + return; + + pa_memblockq_rewind(c->input_memblockq, nbytes); +} + +/* Called from thread context */ +static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) { + connection *c; + + pa_sink_input_assert_ref(i); + c = CONNECTION(i->userdata); + connection_assert_ref(c); + + pa_memblockq_set_maxrewind(c->input_memblockq, nbytes); +} + +static void sink_input_kill_cb(pa_sink_input *i) { + pa_sink_input_assert_ref(i); + + connection_unlink(CONNECTION(i->userdata)); +} + +/*** source_output callbacks ***/ + +/* Called from thread context */ +static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk) { + connection *c; + + pa_source_output_assert_ref(o); + c = CONNECTION(o->userdata); + pa_assert(c); + pa_assert(chunk); + + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(c), CONNECTION_MESSAGE_POST_DATA, NULL, 0, chunk, NULL); +} + +static void source_output_kill_cb(pa_source_output *o) { + pa_source_output_assert_ref(o); + + connection_unlink(CONNECTION(o->userdata)); +} + +static pa_usec_t source_output_get_latency_cb(pa_source_output *o) { + connection*c; + + pa_source_output_assert_ref(o); + c = CONNECTION(o->userdata); + pa_assert(c); + + return pa_bytes_to_usec(pa_memblockq_get_length(c->output_memblockq), &c->source_output->sample_spec); +} + +/*** entry points ***/ + +static void auth_timeout(pa_mainloop_api *m, pa_time_event *e, const struct timeval *t, void *userdata) { + connection *c = CONNECTION(userdata); + + pa_assert(m); + connection_assert_ref(c); + pa_assert(c->auth_timeout_event == e); + + if (!c->authorized) + connection_unlink(c); +} + +void pa_esound_protocol_connect(pa_esound_protocol *p, pa_iochannel *io, pa_esound_options *o) { + connection *c; + char pname[128]; + pa_client_new_data data; + pa_client *client; + + pa_assert(p); + pa_assert(io); + pa_assert(o); + + if (pa_idxset_size(p->connections)+1 > MAX_CONNECTIONS) { + pa_log("Warning! Too many connections (%u), dropping incoming connection.", MAX_CONNECTIONS); + pa_iochannel_free(io); + return; + } + + pa_client_new_data_init(&data); + data.module = o->module; + data.driver = __FILE__; + pa_iochannel_socket_peer_to_string(io, pname, sizeof(pname)); + pa_proplist_setf(data.proplist, PA_PROP_APPLICATION_NAME, "EsounD client (%s)", pname); + pa_proplist_sets(data.proplist, "esound-protocol.peer", pname); + client = pa_client_new(p->core, &data); + pa_client_new_data_done(&data); + + if (!client) + return; + + c = pa_msgobject_new(connection); + c->parent.parent.free = connection_free; + c->parent.process_msg = connection_process_msg; + c->protocol = p; + c->io = io; + pa_iochannel_set_callback(c->io, io_callback, c); + + c->client = client; + c->client->kill = client_kill_cb; + c->client->userdata = c; + + c->options = pa_esound_options_ref(o); + c->authorized = false; + c->swap_byte_order = false; + c->dead = false; + + c->read_data_length = 0; + c->read_data = pa_xmalloc(c->read_data_alloc = proto_map[ESD_PROTO_CONNECT].data_length); + + c->write_data_length = c->write_data_index = c->write_data_alloc = 0; + c->write_data = NULL; + + c->state = ESD_NEEDS_REQDATA; + c->request = ESD_PROTO_CONNECT; + + c->sink_input = NULL; + c->input_memblockq = NULL; + + c->source_output = NULL; + c->output_memblockq = NULL; + + c->playback.current_memblock = NULL; + c->playback.memblock_index = 0; + c->playback.underrun = true; + pa_atomic_store(&c->playback.missing, 0); + + pa_memchunk_reset(&c->scache.memchunk); + c->scache.name = NULL; + + c->original_name = NULL; + + if (o->auth_anonymous) { + pa_log_info("Client authenticated anonymously."); + c->authorized = true; + } + + if (!c->authorized && + o->auth_ip_acl && + pa_ip_acl_check(o->auth_ip_acl, pa_iochannel_get_recv_fd(io)) > 0) { + + pa_log_info("Client authenticated by IP ACL."); + c->authorized = true; + } + + if (!c->authorized) + c->auth_timeout_event = pa_core_rttime_new(p->core, pa_rtclock_now() + AUTH_TIMEOUT, auth_timeout, c); + else + c->auth_timeout_event = NULL; + + c->defer_event = p->core->mainloop->defer_new(p->core->mainloop, defer_callback, c); + p->core->mainloop->defer_enable(c->defer_event, 0); + + pa_idxset_put(p->connections, c, &c->index); +} + +void pa_esound_protocol_disconnect(pa_esound_protocol *p, pa_module *m) { + connection *c; + void *state = NULL; + + pa_assert(p); + pa_assert(m); + + while ((c = pa_idxset_iterate(p->connections, &state, NULL))) + if (c->options->module == m) + connection_unlink(c); +} + +static pa_esound_protocol* esound_protocol_new(pa_core *c) { + pa_esound_protocol *p; + + pa_assert(c); + + p = pa_xnew(pa_esound_protocol, 1); + PA_REFCNT_INIT(p); + p->core = c; + p->connections = pa_idxset_new(NULL, NULL); + p->n_player = 0; + + pa_assert_se(pa_shared_set(c, "esound-protocol", p) >= 0); + + return p; +} + +pa_esound_protocol* pa_esound_protocol_get(pa_core *c) { + pa_esound_protocol *p; + + if ((p = pa_shared_get(c, "esound-protocol"))) + return pa_esound_protocol_ref(p); + + return esound_protocol_new(c); +} + +pa_esound_protocol* pa_esound_protocol_ref(pa_esound_protocol *p) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) >= 1); + + PA_REFCNT_INC(p); + + return p; +} + +void pa_esound_protocol_unref(pa_esound_protocol *p) { + connection *c; + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) >= 1); + + if (PA_REFCNT_DEC(p) > 0) + return; + + while ((c = pa_idxset_first(p->connections, NULL))) + connection_unlink(c); + + pa_idxset_free(p->connections, NULL); + + pa_assert_se(pa_shared_remove(p->core, "esound-protocol") >= 0); + + pa_xfree(p); +} + +pa_esound_options* pa_esound_options_new(void) { + pa_esound_options *o; + + o = pa_xnew0(pa_esound_options, 1); + PA_REFCNT_INIT(o); + + return o; +} + +pa_esound_options* pa_esound_options_ref(pa_esound_options *o) { + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + PA_REFCNT_INC(o); + + return o; +} + +void pa_esound_options_unref(pa_esound_options *o) { + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (PA_REFCNT_DEC(o) > 0) + return; + + if (o->auth_ip_acl) + pa_ip_acl_free(o->auth_ip_acl); + + if (o->auth_cookie) + pa_auth_cookie_unref(o->auth_cookie); + + pa_xfree(o->default_sink); + pa_xfree(o->default_source); + + pa_xfree(o); +} + +int pa_esound_options_parse(pa_esound_options *o, pa_core *c, pa_modargs *ma) { + bool enabled; + const char *acl; + + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + pa_assert(ma); + + if (pa_modargs_get_value_boolean(ma, "auth-anonymous", &o->auth_anonymous) < 0) { + pa_log("auth-anonymous= expects a boolean argument."); + return -1; + } + + if ((acl = pa_modargs_get_value(ma, "auth-ip-acl", NULL))) { + pa_ip_acl *ipa; + + if (!(ipa = pa_ip_acl_new(acl))) { + pa_log("Failed to parse IP ACL '%s'", acl); + return -1; + } + + if (o->auth_ip_acl) + pa_ip_acl_free(o->auth_ip_acl); + + o->auth_ip_acl = ipa; + } + + enabled = true; + if (pa_modargs_get_value_boolean(ma, "auth-cookie-enabled", &enabled) < 0) { + pa_log("auth-cookie-enabled= expects a boolean argument."); + return -1; + } + + if (o->auth_cookie) + pa_auth_cookie_unref(o->auth_cookie); + + if (enabled) { + char *cn; + + /* The new name for this is 'auth-cookie', for compat reasons + * we check the old name too */ + if (!(cn = pa_xstrdup(pa_modargs_get_value(ma, "auth-cookie", NULL)))) { + if (!(cn = pa_xstrdup(pa_modargs_get_value(ma, "cookie", NULL)))) { + if (pa_append_to_home_dir(DEFAULT_COOKIE_FILE, &cn) < 0) + return -1; + } + } + + o->auth_cookie = pa_auth_cookie_get(c, cn, true, ESD_KEY_LEN); + pa_xfree(cn); + if (!o->auth_cookie) + return -1; + + } else + o->auth_cookie = NULL; + + pa_xfree(o->default_sink); + o->default_sink = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL)); + + pa_xfree(o->default_source); + o->default_source = pa_xstrdup(pa_modargs_get_value(ma, "source", NULL)); + + return 0; +} diff --git a/src/pulsecore/protocol-esound.h b/src/pulsecore/protocol-esound.h new file mode 100644 index 0000000..6208640 --- /dev/null +++ b/src/pulsecore/protocol-esound.h @@ -0,0 +1,56 @@ +#ifndef fooprotocolesoundhfoo +#define fooprotocolesoundhfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulsecore/core.h> +#include <pulsecore/ipacl.h> +#include <pulsecore/auth-cookie.h> +#include <pulsecore/iochannel.h> +#include <pulsecore/module.h> +#include <pulsecore/modargs.h> + +typedef struct pa_esound_protocol pa_esound_protocol; + +typedef struct pa_esound_options { + PA_REFCNT_DECLARE; + + pa_module *module; + + bool auth_anonymous; + pa_ip_acl *auth_ip_acl; + pa_auth_cookie *auth_cookie; + + char *default_sink, *default_source; +} pa_esound_options; + +pa_esound_protocol* pa_esound_protocol_get(pa_core*core); +pa_esound_protocol* pa_esound_protocol_ref(pa_esound_protocol *p); +void pa_esound_protocol_unref(pa_esound_protocol *p); +void pa_esound_protocol_connect(pa_esound_protocol *p, pa_iochannel *io, pa_esound_options *o); +void pa_esound_protocol_disconnect(pa_esound_protocol *p, pa_module *m); + +pa_esound_options* pa_esound_options_new(void); +pa_esound_options* pa_esound_options_ref(pa_esound_options *o); +void pa_esound_options_unref(pa_esound_options *o); +int pa_esound_options_parse(pa_esound_options *o, pa_core *c, pa_modargs *ma); + +#endif diff --git a/src/pulsecore/protocol-http.c b/src/pulsecore/protocol-http.c new file mode 100644 index 0000000..e8d22ed --- /dev/null +++ b/src/pulsecore/protocol-http.c @@ -0,0 +1,817 @@ +/*** + This file is part of PulseAudio. + + Copyright 2005-2009 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> + +#include <pulse/util.h> +#include <pulse/xmalloc.h> +#include <pulse/timeval.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/ioline.h> +#include <pulsecore/thread-mq.h> +#include <pulsecore/macro.h> +#include <pulsecore/log.h> +#include <pulsecore/namereg.h> +#include <pulsecore/cli-text.h> +#include <pulsecore/shared.h> +#include <pulsecore/core-error.h> +#include <pulsecore/mime-type.h> + +#include "protocol-http.h" + +/* Don't allow more than this many concurrent connections */ +#define MAX_CONNECTIONS 10 + +#define URL_ROOT "/" +#define URL_CSS "/style" +#define URL_STATUS "/status" +#define URL_LISTEN "/listen" +#define URL_LISTEN_SOURCE "/listen/source/" + +#define MIME_HTML "text/html; charset=utf-8" +#define MIME_TEXT "text/plain; charset=utf-8" +#define MIME_CSS "text/css" + +#define HTML_HEADER(t) \ + "<?xml version=\"1.0\"?>\n" \ + "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n" \ + "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n" \ + " <head>\n" \ + " <title>"t"</title>\n" \ + " <link rel=\"stylesheet\" type=\"text/css\" href=\"style\"/>\n" \ + " </head>\n" \ + " <body>\n" + +#define HTML_FOOTER \ + " </body>\n" \ + "</html>\n" + +#define RECORD_BUFFER_SECONDS (5) +#define DEFAULT_SOURCE_LATENCY (300*PA_USEC_PER_MSEC) + +enum state { + STATE_REQUEST_LINE, + STATE_MIME_HEADER, + STATE_DATA +}; + +enum method { + METHOD_GET, + METHOD_HEAD +}; + +struct connection { + pa_http_protocol *protocol; + pa_iochannel *io; + pa_ioline *line; + pa_memblockq *output_memblockq; + pa_source_output *source_output; + pa_client *client; + enum state state; + char *url; + enum method method; + pa_module *module; +}; + +struct pa_http_protocol { + PA_REFCNT_DECLARE; + + pa_core *core; + pa_idxset *connections; + + pa_strlist *servers; +}; + +enum { + SOURCE_OUTPUT_MESSAGE_POST_DATA = PA_SOURCE_OUTPUT_MESSAGE_MAX +}; + +/* Called from main context */ +static void connection_unlink(struct connection *c) { + pa_assert(c); + + if (c->source_output) { + pa_source_output_unlink(c->source_output); + c->source_output->userdata = NULL; + pa_source_output_unref(c->source_output); + } + + if (c->client) + pa_client_free(c->client); + + pa_xfree(c->url); + + if (c->line) + pa_ioline_unref(c->line); + + if (c->io) + pa_iochannel_free(c->io); + + if (c->output_memblockq) + pa_memblockq_free(c->output_memblockq); + + pa_idxset_remove_by_data(c->protocol->connections, c, NULL); + + pa_xfree(c); +} + +/* Called from main context */ +static int do_write(struct connection *c) { + pa_memchunk chunk; + ssize_t r; + void *p; + + pa_assert(c); + + if (pa_memblockq_peek(c->output_memblockq, &chunk) < 0) + return 0; + + pa_assert(chunk.memblock); + pa_assert(chunk.length > 0); + + p = pa_memblock_acquire(chunk.memblock); + r = pa_iochannel_write(c->io, (uint8_t*) p+chunk.index, chunk.length); + pa_memblock_release(chunk.memblock); + + pa_memblock_unref(chunk.memblock); + + if (r < 0) { + pa_log("write(): %s", pa_cstrerror(errno)); + return -1; + } + + pa_memblockq_drop(c->output_memblockq, (size_t) r); + + return 1; +} + +/* Called from main context */ +static void do_work(struct connection *c) { + pa_assert(c); + + if (pa_iochannel_is_hungup(c->io)) + goto fail; + + while (pa_iochannel_is_writable(c->io)) { + int r = do_write(c); + if (r < 0) + goto fail; + if (r == 0) + break; + } + + return; + +fail: + connection_unlink(c); +} + +/* Called from thread context, except when it is not */ +static int source_output_process_msg(pa_msgobject *m, int code, void *userdata, int64_t offset, pa_memchunk *chunk) { + pa_source_output *o = PA_SOURCE_OUTPUT(m); + struct connection *c; + + pa_source_output_assert_ref(o); + + if (!(c = o->userdata)) + return -1; + + switch (code) { + + case SOURCE_OUTPUT_MESSAGE_POST_DATA: + /* While this function is usually called from IO thread + * context, this specific command is not! */ + pa_memblockq_push_align(c->output_memblockq, chunk); + do_work(c); + break; + + default: + return pa_source_output_process_msg(m, code, userdata, offset, chunk); + } + + return 0; +} + +/* Called from thread context */ +static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk) { + struct connection *c; + + pa_source_output_assert_ref(o); + pa_assert_se(c = o->userdata); + pa_assert(chunk); + + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(o), SOURCE_OUTPUT_MESSAGE_POST_DATA, NULL, 0, chunk, NULL); +} + +/* Called from main context */ +static void source_output_kill_cb(pa_source_output *o) { + struct connection*c; + + pa_source_output_assert_ref(o); + pa_assert_se(c = o->userdata); + + connection_unlink(c); +} + +/* Called from main context */ +static pa_usec_t source_output_get_latency_cb(pa_source_output *o) { + struct connection*c; + + pa_source_output_assert_ref(o); + pa_assert_se(c = o->userdata); + + return pa_bytes_to_usec(pa_memblockq_get_length(c->output_memblockq), &c->source_output->sample_spec); +} + +/*** client callbacks ***/ +static void client_kill_cb(pa_client *client) { + struct connection*c; + + pa_assert(client); + pa_assert_se(c = client->userdata); + + connection_unlink(c); +} + +/*** pa_iochannel callbacks ***/ +static void io_callback(pa_iochannel*io, void *userdata) { + struct connection *c = userdata; + + pa_assert(c); + pa_assert(io); + + do_work(c); +} + +static char *escape_html(const char *t) { + pa_strbuf *sb; + const char *p, *e; + + sb = pa_strbuf_new(); + + for (e = p = t; *p; p++) { + + if (*p == '>' || *p == '<' || *p == '&') { + + if (p > e) { + pa_strbuf_putsn(sb, e, p-e); + e = p + 1; + } + + if (*p == '>') + pa_strbuf_puts(sb, ">"); + else if (*p == '<') + pa_strbuf_puts(sb, "<"); + else + pa_strbuf_puts(sb, "&"); + } + } + + if (p > e) + pa_strbuf_putsn(sb, e, p-e); + + return pa_strbuf_to_string_free(sb); +} + +static void http_response( + struct connection *c, + int code, + const char *msg, + const char *mime) { + + char *s; + + pa_assert(c); + pa_assert(msg); + pa_assert(mime); + + s = pa_sprintf_malloc( + "HTTP/1.0 %i %s\n" + "Connection: close\n" + "Content-Type: %s\n" + "Cache-Control: no-cache\n" + "Expires: 0\n" + "Server: "PACKAGE_NAME"/"PACKAGE_VERSION"\n" + "\n", code, msg, mime); + pa_ioline_puts(c->line, s); + pa_xfree(s); +} + +static void html_response( + struct connection *c, + int code, + const char *msg, + const char *text) { + + char *s; + pa_assert(c); + + http_response(c, code, msg, MIME_HTML); + + if (c->method == METHOD_HEAD) { + pa_ioline_defer_close(c->line); + return; + } + + if (!text) + text = msg; + + s = pa_sprintf_malloc( + HTML_HEADER("%s") + "%s" + HTML_FOOTER, + text, text); + + pa_ioline_puts(c->line, s); + pa_xfree(s); + + pa_ioline_defer_close(c->line); +} + +static void html_print_field(pa_ioline *line, const char *left, const char *right) { + char *eleft, *eright; + + eleft = escape_html(left); + eright = escape_html(right); + + pa_ioline_printf(line, + "<tr><td><b>%s</b></td>" + "<td>%s</td></tr>\n", eleft, eright); + + pa_xfree(eleft); + pa_xfree(eright); +} + +static void handle_root(struct connection *c) { + char *t; + + pa_assert(c); + + http_response(c, 200, "OK", MIME_HTML); + + if (c->method == METHOD_HEAD) { + pa_ioline_defer_close(c->line); + return; + } + + pa_ioline_puts(c->line, + HTML_HEADER(PACKAGE_NAME" "PACKAGE_VERSION) + "<h1>"PACKAGE_NAME" "PACKAGE_VERSION"</h1>\n" + "<table>\n"); + + t = pa_get_user_name_malloc(); + html_print_field(c->line, "User Name:", t); + pa_xfree(t); + + t = pa_get_host_name_malloc(); + html_print_field(c->line, "Host name:", t); + pa_xfree(t); + + t = pa_machine_id(); + html_print_field(c->line, "Machine ID:", t); + pa_xfree(t); + + t = pa_uname_string(); + html_print_field(c->line, "System:", t); + pa_xfree(t); + + t = pa_sprintf_malloc("%lu", (unsigned long) getpid()); + html_print_field(c->line, "Process ID:", t); + pa_xfree(t); + + pa_ioline_puts(c->line, + "</table>\n" + "<p><a href=\"" URL_STATUS "\">Show an extensive server status report</a></p>\n" + "<p><a href=\"" URL_LISTEN "\">Monitor sinks and sources</a></p>\n" + HTML_FOOTER); + + pa_ioline_defer_close(c->line); +} + +static void handle_css(struct connection *c) { + pa_assert(c); + + http_response(c, 200, "OK", MIME_CSS); + + if (c->method == METHOD_HEAD) { + pa_ioline_defer_close(c->line); + return; + } + + pa_ioline_puts(c->line, + "body { color: black; background-color: white; }\n" + "a:link, a:visited { color: #900000; }\n" + "div.news-date { font-size: 80%; font-style: italic; }\n" + "pre { background-color: #f0f0f0; padding: 0.4cm; }\n" + ".grey { color: #8f8f8f; font-size: 80%; }" + "table { margin-left: 1cm; border:1px solid lightgrey; padding: 0.2cm; }\n" + "td { padding-left:10px; padding-right:10px; }\n"); + + pa_ioline_defer_close(c->line); +} + +static void handle_status(struct connection *c) { + char *r; + + pa_assert(c); + + http_response(c, 200, "OK", MIME_TEXT); + + if (c->method == METHOD_HEAD) { + pa_ioline_defer_close(c->line); + return; + } + + r = pa_full_status_string(c->protocol->core); + pa_ioline_puts(c->line, r); + pa_xfree(r); + + pa_ioline_defer_close(c->line); +} + +static void handle_listen(struct connection *c) { + pa_source *source; + pa_sink *sink; + uint32_t idx; + + http_response(c, 200, "OK", MIME_HTML); + + pa_ioline_puts(c->line, + HTML_HEADER("Listen") + "<h2>Sinks</h2>\n" + "<p>\n"); + + if (c->method == METHOD_HEAD) { + pa_ioline_defer_close(c->line); + return; + } + + PA_IDXSET_FOREACH(sink, c->protocol->core->sinks, idx) { + char *t, *m; + + t = escape_html(pa_strna(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_DESCRIPTION))); + m = pa_sample_spec_to_mime_type_mimefy(&sink->sample_spec, &sink->channel_map); + + pa_ioline_printf(c->line, + "<a href=\"" URL_LISTEN_SOURCE "%s\" title=\"%s\">%s</a><br/>\n", + sink->monitor_source->name, m, t); + + pa_xfree(t); + pa_xfree(m); + } + + pa_ioline_puts(c->line, + "</p>\n" + "<h2>Sources</h2>\n" + "<p>\n"); + + PA_IDXSET_FOREACH(source, c->protocol->core->sources, idx) { + char *t, *m; + + if (source->monitor_of) + continue; + + t = escape_html(pa_strna(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_DESCRIPTION))); + m = pa_sample_spec_to_mime_type_mimefy(&source->sample_spec, &source->channel_map); + + pa_ioline_printf(c->line, + "<a href=\"" URL_LISTEN_SOURCE "%s\" title=\"%s\">%s</a><br/>\n", + source->name, m, t); + + pa_xfree(m); + pa_xfree(t); + + } + + pa_ioline_puts(c->line, + "</p>\n" + HTML_FOOTER); + + pa_ioline_defer_close(c->line); +} + +static void line_drain_callback(pa_ioline *l, void *userdata) { + struct connection *c; + + pa_assert(l); + pa_assert_se(c = userdata); + + /* We don't need the line reader anymore, instead we need a real + * binary io channel */ + pa_assert_se(c->io = pa_ioline_detach_iochannel(c->line)); + pa_iochannel_set_callback(c->io, io_callback, c); + + pa_iochannel_socket_set_sndbuf(c->io, pa_memblockq_get_length(c->output_memblockq)); + + pa_ioline_unref(c->line); + c->line = NULL; +} + +static void handle_listen_prefix(struct connection *c, const char *source_name) { + pa_source *source; + pa_source_output_new_data data; + pa_sample_spec ss; + pa_channel_map cm; + char *t; + size_t l; + + pa_assert(c); + pa_assert(source_name); + + pa_assert(c->line); + pa_assert(!c->io); + + if (!(source = pa_namereg_get(c->protocol->core, source_name, PA_NAMEREG_SOURCE))) { + html_response(c, 404, "Source not found", NULL); + return; + } + + ss = source->sample_spec; + cm = source->channel_map; + + pa_sample_spec_mimefy(&ss, &cm); + + pa_source_output_new_data_init(&data); + data.driver = __FILE__; + data.module = c->module; + data.client = c->client; + pa_source_output_new_data_set_source(&data, source, false, true); + pa_proplist_update(data.proplist, PA_UPDATE_MERGE, c->client->proplist); + pa_source_output_new_data_set_sample_spec(&data, &ss); + pa_source_output_new_data_set_channel_map(&data, &cm); + + pa_source_output_new(&c->source_output, c->protocol->core, &data); + pa_source_output_new_data_done(&data); + + if (!c->source_output) { + html_response(c, 403, "Cannot create source output", NULL); + return; + } + + c->source_output->parent.process_msg = source_output_process_msg; + c->source_output->push = source_output_push_cb; + c->source_output->kill = source_output_kill_cb; + c->source_output->get_latency = source_output_get_latency_cb; + c->source_output->userdata = c; + + pa_source_output_set_requested_latency(c->source_output, DEFAULT_SOURCE_LATENCY); + + l = (size_t) (pa_bytes_per_second(&ss)*RECORD_BUFFER_SECONDS); + c->output_memblockq = pa_memblockq_new( + "http protocol connection output_memblockq", + 0, + l, + 0, + &ss, + 1, + 0, + 0, + NULL); + + pa_source_output_put(c->source_output); + + t = pa_sample_spec_to_mime_type(&ss, &cm); + http_response(c, 200, "OK", t); + pa_xfree(t); + + if (c->method == METHOD_HEAD) { + connection_unlink(c); + return; + } + pa_ioline_set_callback(c->line, NULL, NULL); + + if (pa_ioline_is_drained(c->line)) + line_drain_callback(c->line, c); + else + pa_ioline_set_drain_callback(c->line, line_drain_callback, c); +} + +static void handle_url(struct connection *c) { + pa_assert(c); + + pa_log_debug("Request for %s", c->url); + + if (pa_streq(c->url, URL_ROOT)) + handle_root(c); + else if (pa_streq(c->url, URL_CSS)) + handle_css(c); + else if (pa_streq(c->url, URL_STATUS)) + handle_status(c); + else if (pa_streq(c->url, URL_LISTEN)) + handle_listen(c); + else if (pa_startswith(c->url, URL_LISTEN_SOURCE)) + handle_listen_prefix(c, c->url + sizeof(URL_LISTEN_SOURCE)-1); + else + html_response(c, 404, "Not Found", NULL); +} + +static void line_callback(pa_ioline *line, const char *s, void *userdata) { + struct connection *c = userdata; + pa_assert(line); + pa_assert(c); + + if (!s) { + /* EOF */ + connection_unlink(c); + return; + } + + switch (c->state) { + case STATE_REQUEST_LINE: { + if (pa_startswith(s, "GET ")) { + c->method = METHOD_GET; + s +=4; + } else if (pa_startswith(s, "HEAD ")) { + c->method = METHOD_HEAD; + s +=5; + } else { + goto fail; + } + + c->url = pa_xstrndup(s, strcspn(s, " \r\n\t?")); + c->state = STATE_MIME_HEADER; + break; + } + + case STATE_MIME_HEADER: { + + /* Ignore MIME headers */ + if (strcspn(s, " \r\n") != 0) + break; + + /* We're done */ + c->state = STATE_DATA; + + handle_url(c); + break; + } + + default: + ; + } + + return; + +fail: + html_response(c, 500, "Internal Server Error", NULL); +} + +void pa_http_protocol_connect(pa_http_protocol *p, pa_iochannel *io, pa_module *m) { + struct connection *c; + pa_client_new_data client_data; + char pname[128]; + + pa_assert(p); + pa_assert(io); + pa_assert(m); + + if (pa_idxset_size(p->connections)+1 > MAX_CONNECTIONS) { + pa_log("Warning! Too many connections (%u), dropping incoming connection.", MAX_CONNECTIONS); + pa_iochannel_free(io); + return; + } + + c = pa_xnew0(struct connection, 1); + c->protocol = p; + c->state = STATE_REQUEST_LINE; + c->module = m; + + c->line = pa_ioline_new(io); + pa_ioline_set_callback(c->line, line_callback, c); + + pa_client_new_data_init(&client_data); + client_data.module = c->module; + client_data.driver = __FILE__; + pa_iochannel_socket_peer_to_string(io, pname, sizeof(pname)); + pa_proplist_setf(client_data.proplist, PA_PROP_APPLICATION_NAME, "HTTP client (%s)", pname); + pa_proplist_sets(client_data.proplist, "http-protocol.peer", pname); + c->client = pa_client_new(p->core, &client_data); + pa_client_new_data_done(&client_data); + + if (!c->client) + goto fail; + + c->client->kill = client_kill_cb; + c->client->userdata = c; + + pa_idxset_put(p->connections, c, NULL); + + return; + +fail: + if (c) + connection_unlink(c); +} + +void pa_http_protocol_disconnect(pa_http_protocol *p, pa_module *m) { + struct connection *c; + uint32_t idx; + + pa_assert(p); + pa_assert(m); + + PA_IDXSET_FOREACH(c, p->connections, idx) + if (c->module == m) + connection_unlink(c); +} + +static pa_http_protocol* http_protocol_new(pa_core *c) { + pa_http_protocol *p; + + pa_assert(c); + + p = pa_xnew0(pa_http_protocol, 1); + PA_REFCNT_INIT(p); + p->core = c; + p->connections = pa_idxset_new(NULL, NULL); + + pa_assert_se(pa_shared_set(c, "http-protocol", p) >= 0); + + return p; +} + +pa_http_protocol* pa_http_protocol_get(pa_core *c) { + pa_http_protocol *p; + + if ((p = pa_shared_get(c, "http-protocol"))) + return pa_http_protocol_ref(p); + + return http_protocol_new(c); +} + +pa_http_protocol* pa_http_protocol_ref(pa_http_protocol *p) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) >= 1); + + PA_REFCNT_INC(p); + + return p; +} + +void pa_http_protocol_unref(pa_http_protocol *p) { + struct connection *c; + + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) >= 1); + + if (PA_REFCNT_DEC(p) > 0) + return; + + while ((c = pa_idxset_first(p->connections, NULL))) + connection_unlink(c); + + pa_idxset_free(p->connections, NULL); + + pa_strlist_free(p->servers); + + pa_assert_se(pa_shared_remove(p->core, "http-protocol") >= 0); + + pa_xfree(p); +} + +void pa_http_protocol_add_server_string(pa_http_protocol *p, const char *name) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) >= 1); + pa_assert(name); + + p->servers = pa_strlist_prepend(p->servers, name); +} + +void pa_http_protocol_remove_server_string(pa_http_protocol *p, const char *name) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) >= 1); + pa_assert(name); + + p->servers = pa_strlist_remove(p->servers, name); +} + +pa_strlist *pa_http_protocol_servers(pa_http_protocol *p) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) >= 1); + + return p->servers; +} diff --git a/src/pulsecore/protocol-http.h b/src/pulsecore/protocol-http.h new file mode 100644 index 0000000..89a6518 --- /dev/null +++ b/src/pulsecore/protocol-http.h @@ -0,0 +1,41 @@ +#ifndef fooprotocolhttphfoo +#define fooprotocolhttphfoo + +/*** + This file is part of PulseAudio. + + Copyright 2005-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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulsecore/core.h> +#include <pulsecore/module.h> +#include <pulsecore/modargs.h> +#include <pulsecore/iochannel.h> +#include <pulsecore/strlist.h> + +typedef struct pa_http_protocol pa_http_protocol; + +pa_http_protocol* pa_http_protocol_get(pa_core *core); +pa_http_protocol* pa_http_protocol_ref(pa_http_protocol *p); +void pa_http_protocol_unref(pa_http_protocol *p); +void pa_http_protocol_connect(pa_http_protocol *p, pa_iochannel *io, pa_module *m); +void pa_http_protocol_disconnect(pa_http_protocol *p, pa_module *m); + +void pa_http_protocol_add_server_string(pa_http_protocol *p, const char *name); +void pa_http_protocol_remove_server_string(pa_http_protocol *p, const char *name); +pa_strlist *pa_http_protocol_servers(pa_http_protocol *p); + +#endif diff --git a/src/pulsecore/protocol-native.c b/src/pulsecore/protocol-native.c new file mode 100644 index 0000000..e8559b2 --- /dev/null +++ b/src/pulsecore/protocol-native.c @@ -0,0 +1,5514 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include <pulse/rtclock.h> +#include <pulse/timeval.h> +#include <pulse/version.h> +#include <pulse/utf8.h> +#include <pulse/util.h> +#include <pulse/xmalloc.h> +#include <pulse/internal.h> + +#include <pulsecore/native-common.h> +#include <pulsecore/packet.h> +#include <pulsecore/client.h> +#include <pulsecore/source-output.h> +#include <pulsecore/sink-input.h> +#include <pulsecore/pstream.h> +#include <pulsecore/tagstruct.h> +#include <pulsecore/pdispatch.h> +#include <pulsecore/pstream-util.h> +#include <pulsecore/namereg.h> +#include <pulsecore/core-scache.h> +#include <pulsecore/core-subscribe.h> +#include <pulsecore/log.h> +#include <pulsecore/mem.h> +#include <pulsecore/strlist.h> +#include <pulsecore/shared.h> +#include <pulsecore/sample-util.h> +#include <pulsecore/creds.h> +#include <pulsecore/core-util.h> +#include <pulsecore/ipacl.h> +#include <pulsecore/thread-mq.h> +#include <pulsecore/mem.h> + +#include "protocol-native.h" + +/* #define PROTOCOL_NATIVE_DEBUG */ + +/* Kick a client if it doesn't authenticate within this time */ +#define AUTH_TIMEOUT (60 * PA_USEC_PER_SEC) + +/* Don't accept more connection than this */ +#define MAX_CONNECTIONS 64 + +#define MAX_MEMBLOCKQ_LENGTH (4*1024*1024) /* 4MB */ +#define DEFAULT_TLENGTH_MSEC 2000 /* 2s */ +#define DEFAULT_PROCESS_MSEC 20 /* 20ms */ +#define DEFAULT_FRAGSIZE_MSEC DEFAULT_TLENGTH_MSEC + +struct pa_native_protocol; + +typedef struct record_stream { + pa_msgobject parent; + + pa_native_connection *connection; + uint32_t index; + + pa_source_output *source_output; + pa_memblockq *memblockq; + + bool adjust_latency:1; + bool early_requests:1; + + /* Requested buffer attributes */ + pa_buffer_attr buffer_attr_req; + /* Fixed-up and adjusted buffer attributes */ + pa_buffer_attr buffer_attr; + + pa_atomic_t on_the_fly; + pa_usec_t configured_source_latency; + size_t drop_initial; + + /* Only updated after SOURCE_OUTPUT_MESSAGE_UPDATE_LATENCY */ + size_t on_the_fly_snapshot; + pa_usec_t current_monitor_latency; + pa_usec_t current_source_latency; +} record_stream; + +#define RECORD_STREAM(o) (record_stream_cast(o)) +PA_DEFINE_PRIVATE_CLASS(record_stream, pa_msgobject); + +typedef struct output_stream { + pa_msgobject parent; +} output_stream; + +#define OUTPUT_STREAM(o) (output_stream_cast(o)) +PA_DEFINE_PRIVATE_CLASS(output_stream, pa_msgobject); + +typedef struct playback_stream { + output_stream parent; + + pa_native_connection *connection; + uint32_t index; + + pa_sink_input *sink_input; + pa_memblockq *memblockq; + + bool adjust_latency:1; + bool early_requests:1; + + bool is_underrun:1; + bool drain_request:1; + uint32_t drain_tag; + uint32_t syncid; + + /* Optimization to avoid too many rewinds with a lot of small blocks */ + pa_atomic_t seek_or_post_in_queue; + int64_t seek_windex; + + pa_atomic_t missing; + pa_usec_t configured_sink_latency; + /* Requested buffer attributes */ + pa_buffer_attr buffer_attr_req; + /* Fixed-up and adjusted buffer attributes */ + pa_buffer_attr buffer_attr; + + /* Only updated after SINK_INPUT_MESSAGE_UPDATE_LATENCY */ + int64_t read_index, write_index; + size_t render_memblockq_length; + pa_usec_t current_sink_latency; + uint64_t playing_for, underrun_for; +} playback_stream; + +#define PLAYBACK_STREAM(o) (playback_stream_cast(o)) +PA_DEFINE_PRIVATE_CLASS(playback_stream, output_stream); + +typedef struct upload_stream { + output_stream parent; + + pa_native_connection *connection; + uint32_t index; + + pa_memchunk memchunk; + size_t length; + char *name; + pa_sample_spec sample_spec; + pa_channel_map channel_map; + pa_proplist *proplist; +} upload_stream; + +#define UPLOAD_STREAM(o) (upload_stream_cast(o)) +PA_DEFINE_PRIVATE_CLASS(upload_stream, output_stream); + +struct pa_native_connection { + pa_msgobject parent; + pa_native_protocol *protocol; + pa_native_options *options; + bool authorized:1; + bool is_local:1; + uint32_t version; + pa_client *client; + /* R/W mempool, one per client connection, for srbchannel transport. + * Both server and client can write to this shm area. + * + * Note: This will be NULL if our connection with the client does + * not support srbchannels */ + pa_mempool *rw_mempool; + pa_pstream *pstream; + pa_pdispatch *pdispatch; + pa_idxset *record_streams, *output_streams; + uint32_t rrobin_index; + pa_subscription *subscription; + pa_time_event *auth_timeout_event; + pa_srbchannel *srbpending; +}; + +#define PA_NATIVE_CONNECTION(o) (pa_native_connection_cast(o)) +PA_DEFINE_PRIVATE_CLASS(pa_native_connection, pa_msgobject); + +struct pa_native_protocol { + PA_REFCNT_DECLARE; + + pa_core *core; + pa_idxset *connections; + + pa_strlist *servers; + pa_hook hooks[PA_NATIVE_HOOK_MAX]; + + pa_hashmap *extensions; +}; + +enum { + SOURCE_OUTPUT_MESSAGE_UPDATE_LATENCY = PA_SOURCE_OUTPUT_MESSAGE_MAX +}; + +enum { + SINK_INPUT_MESSAGE_POST_DATA = PA_SINK_INPUT_MESSAGE_MAX, /* data from main loop to sink input */ + SINK_INPUT_MESSAGE_DRAIN, /* disabled prebuf, get playback started. */ + SINK_INPUT_MESSAGE_FLUSH, + SINK_INPUT_MESSAGE_TRIGGER, + SINK_INPUT_MESSAGE_SEEK, + SINK_INPUT_MESSAGE_PREBUF_FORCE, + SINK_INPUT_MESSAGE_UPDATE_LATENCY, + SINK_INPUT_MESSAGE_UPDATE_BUFFER_ATTR +}; + +enum { + PLAYBACK_STREAM_MESSAGE_REQUEST_DATA, /* data requested from sink input from the main loop */ + PLAYBACK_STREAM_MESSAGE_UNDERFLOW, + PLAYBACK_STREAM_MESSAGE_OVERFLOW, + PLAYBACK_STREAM_MESSAGE_DRAIN_ACK, + PLAYBACK_STREAM_MESSAGE_STARTED, + PLAYBACK_STREAM_MESSAGE_UPDATE_TLENGTH +}; + +enum { + RECORD_STREAM_MESSAGE_POST_DATA /* data from source output to main loop */ +}; + +enum { + CONNECTION_MESSAGE_RELEASE, + CONNECTION_MESSAGE_REVOKE +}; + +static bool sink_input_process_underrun_cb(pa_sink_input *i); +static int sink_input_pop_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk); +static void sink_input_kill_cb(pa_sink_input *i); +static void sink_input_suspend_cb(pa_sink_input *i, pa_sink_state_t old_state, pa_suspend_cause_t old_suspend_cause); +static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest); +static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes); +static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes); +static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes); +static void sink_input_send_event_cb(pa_sink_input *i, const char *event, pa_proplist *pl); + +static void native_connection_send_memblock(pa_native_connection *c); +static void playback_stream_request_bytes(struct playback_stream*s); + +static void source_output_kill_cb(pa_source_output *o); +static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk); +static void source_output_suspend_cb(pa_source_output *o, pa_source_state_t old_state, pa_suspend_cause_t old_suspend_cause); +static void source_output_moving_cb(pa_source_output *o, pa_source *dest); +static pa_usec_t source_output_get_latency_cb(pa_source_output *o); +static void source_output_send_event_cb(pa_source_output *o, const char *event, pa_proplist *pl); + +static int sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk); +static int source_output_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk); + +/* structure management */ + +/* Called from main context */ +static void upload_stream_unlink(upload_stream *s) { + pa_assert(s); + + if (!s->connection) + return; + + pa_assert_se(pa_idxset_remove_by_data(s->connection->output_streams, s, NULL) == s); + s->connection = NULL; + upload_stream_unref(s); +} + +/* Called from main context */ +static void upload_stream_free(pa_object *o) { + upload_stream *s = UPLOAD_STREAM(o); + pa_assert(s); + + upload_stream_unlink(s); + + pa_xfree(s->name); + + if (s->proplist) + pa_proplist_free(s->proplist); + + if (s->memchunk.memblock) + pa_memblock_unref(s->memchunk.memblock); + + pa_xfree(s); +} + +/* Called from main context */ +static upload_stream* upload_stream_new( + pa_native_connection *c, + const pa_sample_spec *ss, + const pa_channel_map *map, + const char *name, + size_t length, + pa_proplist *p) { + + upload_stream *s; + + pa_assert(c); + pa_assert(ss); + pa_assert(name); + pa_assert(length > 0); + pa_assert(p); + + s = pa_msgobject_new(upload_stream); + s->parent.parent.parent.free = upload_stream_free; + s->connection = c; + s->sample_spec = *ss; + s->channel_map = *map; + s->name = pa_xstrdup(name); + pa_memchunk_reset(&s->memchunk); + s->length = length; + s->proplist = pa_proplist_copy(p); + pa_proplist_update(s->proplist, PA_UPDATE_MERGE, c->client->proplist); + + pa_idxset_put(c->output_streams, s, &s->index); + + return s; +} + +/* Called from main context */ +static void record_stream_unlink(record_stream *s) { + pa_assert(s); + + if (!s->connection) + return; + + if (s->source_output) { + pa_source_output_unlink(s->source_output); + pa_source_output_unref(s->source_output); + s->source_output = NULL; + } + + pa_assert_se(pa_idxset_remove_by_data(s->connection->record_streams, s, NULL) == s); + s->connection = NULL; + record_stream_unref(s); +} + +/* Called from main context */ +static void record_stream_free(pa_object *o) { + record_stream *s = RECORD_STREAM(o); + pa_assert(s); + + record_stream_unlink(s); + + pa_memblockq_free(s->memblockq); + pa_xfree(s); +} + +/* Called from main context */ +static int record_stream_process_msg(pa_msgobject *o, int code, void*userdata, int64_t offset, pa_memchunk *chunk) { + record_stream *s = RECORD_STREAM(o); + record_stream_assert_ref(s); + + if (!s->connection) + return -1; + + switch (code) { + + case RECORD_STREAM_MESSAGE_POST_DATA: + + /* We try to keep up to date with how many bytes are + * currently on the fly */ + pa_atomic_sub(&s->on_the_fly, chunk->length); + + if (pa_memblockq_push_align(s->memblockq, chunk) < 0) { +/* pa_log_warn("Failed to push data into output queue."); */ + return -1; + } + + if (!pa_pstream_is_pending(s->connection->pstream)) + native_connection_send_memblock(s->connection); + + break; + } + + return 0; +} + +/* Called from main context */ +static void fix_record_buffer_attr_pre(record_stream *s) { + + size_t frame_size; + pa_usec_t orig_fragsize_usec, fragsize_usec, source_usec; + + pa_assert(s); + + /* This function will be called from the main thread, before as + * well as after the source output has been activated using + * pa_source_output_put()! That means it may not touch any + * ->thread_info data! */ + + frame_size = pa_frame_size(&s->source_output->sample_spec); + s->buffer_attr = s->buffer_attr_req; + + if (s->buffer_attr.maxlength == (uint32_t) -1 || s->buffer_attr.maxlength > MAX_MEMBLOCKQ_LENGTH) + s->buffer_attr.maxlength = MAX_MEMBLOCKQ_LENGTH; + if (s->buffer_attr.maxlength <= 0) + s->buffer_attr.maxlength = (uint32_t) frame_size; + + if (s->buffer_attr.fragsize == (uint32_t) -1) + s->buffer_attr.fragsize = (uint32_t) pa_usec_to_bytes(DEFAULT_FRAGSIZE_MSEC*PA_USEC_PER_MSEC, &s->source_output->sample_spec); + if (s->buffer_attr.fragsize <= 0) + s->buffer_attr.fragsize = (uint32_t) frame_size; + + orig_fragsize_usec = fragsize_usec = pa_bytes_to_usec(s->buffer_attr.fragsize, &s->source_output->sample_spec); + + if (s->early_requests) { + + /* In early request mode we need to emulate the classic + * fragment-based playback model. Unfortunately we have no + * mechanism to tell the source how often we want it to send us + * data. The next best thing we can do is to set the source's + * total buffer (i.e. its latency) to the fragment size. That + * way it will have to send data at least that often. */ + + source_usec = fragsize_usec; + + } else if (s->adjust_latency) { + + /* So, the user asked us to adjust the latency according to + * what the source can provide. We set the source to whatever + * latency it can provide that is closest to what we want, and + * let the client buffer be equally large. This does NOT mean + * that we are doing (2 * fragsize) bytes of buffering, since + * the client-side buffer is only data that is on the way to + * the client. */ + + source_usec = fragsize_usec; + + } else { + + /* Ok, the user didn't ask us to adjust the latency, hence we + * don't */ + + source_usec = (pa_usec_t) -1; + } + + if (source_usec != (pa_usec_t) -1) + s->configured_source_latency = pa_source_output_set_requested_latency(s->source_output, source_usec); + else + s->configured_source_latency = 0; + + if (s->early_requests) { + + /* Ok, we didn't necessarily get what we were asking for. We + * might still get the proper fragment interval, we just can't + * guarantee it. */ + + if (fragsize_usec != s->configured_source_latency) + pa_log_debug("Could not configure a sufficiently low latency. Early requests might not be satisfied."); + + } else if (s->adjust_latency) { + + /* We keep the client buffer large enough to transfer one + * hardware-buffer-sized chunk at a time to the client. */ + + fragsize_usec = s->configured_source_latency; + } + + if (pa_usec_to_bytes(orig_fragsize_usec, &s->source_output->sample_spec) != + pa_usec_to_bytes(fragsize_usec, &s->source_output->sample_spec)) + + s->buffer_attr.fragsize = (uint32_t) pa_usec_to_bytes(fragsize_usec, &s->source_output->sample_spec); + + if (s->buffer_attr.fragsize <= 0) + s->buffer_attr.fragsize = (uint32_t) frame_size; +} + +/* Called from main context */ +static void fix_record_buffer_attr_post(record_stream *s) { + size_t base; + + pa_assert(s); + + /* This function will be called from the main thread, before as + * well as after the source output has been activated using + * pa_source_output_put()! That means it may not touch and + * ->thread_info data! */ + + base = pa_frame_size(&s->source_output->sample_spec); + + s->buffer_attr.fragsize = (s->buffer_attr.fragsize/base)*base; + if (s->buffer_attr.fragsize <= 0) + s->buffer_attr.fragsize = base; + + if (s->buffer_attr.fragsize > s->buffer_attr.maxlength) + s->buffer_attr.fragsize = s->buffer_attr.maxlength; +} + +/* Called from main context */ +static record_stream* record_stream_new( + pa_native_connection *c, + pa_source *source, + pa_sample_spec *ss, + pa_channel_map *map, + pa_idxset *formats, + pa_buffer_attr *attr, + pa_cvolume *volume, + bool muted, + bool muted_set, + pa_source_output_flags_t flags, + pa_proplist *p, + bool adjust_latency, + bool early_requests, + bool relative_volume, + bool peak_detect, + pa_sink_input *direct_on_input, + int *ret) { + + /* Note: This function takes ownership of the 'formats' param, so we need + * to take extra care to not leak it */ + + record_stream *s; + pa_source_output *source_output = NULL; + pa_source_output_new_data data; + char *memblockq_name; + + pa_assert(c); + pa_assert(ss); + pa_assert(p); + pa_assert(ret); + + pa_source_output_new_data_init(&data); + + pa_proplist_update(data.proplist, PA_UPDATE_REPLACE, p); + data.driver = __FILE__; + data.module = c->options->module; + data.client = c->client; + if (source) + pa_source_output_new_data_set_source(&data, source, false, true); + if (pa_sample_spec_valid(ss)) + pa_source_output_new_data_set_sample_spec(&data, ss); + if (pa_channel_map_valid(map)) + pa_source_output_new_data_set_channel_map(&data, map); + if (formats) + pa_source_output_new_data_set_formats(&data, formats); + data.direct_on_input = direct_on_input; + if (volume) { + pa_source_output_new_data_set_volume(&data, volume); + data.volume_is_absolute = !relative_volume; + data.save_volume = false; + } + if (muted_set) { + pa_source_output_new_data_set_muted(&data, muted); + data.save_muted = false; + } + if (peak_detect) + data.resample_method = PA_RESAMPLER_PEAKS; + data.flags = flags; + + *ret = -pa_source_output_new(&source_output, c->protocol->core, &data); + + pa_source_output_new_data_done(&data); + + if (!source_output) + return NULL; + + s = pa_msgobject_new(record_stream); + s->parent.parent.free = record_stream_free; + s->parent.process_msg = record_stream_process_msg; + s->connection = c; + s->source_output = source_output; + s->buffer_attr_req = *attr; + s->adjust_latency = adjust_latency; + s->early_requests = early_requests; + pa_atomic_store(&s->on_the_fly, 0); + + s->source_output->parent.process_msg = source_output_process_msg; + s->source_output->push = source_output_push_cb; + s->source_output->kill = source_output_kill_cb; + s->source_output->get_latency = source_output_get_latency_cb; + s->source_output->moving = source_output_moving_cb; + s->source_output->suspend = source_output_suspend_cb; + s->source_output->send_event = source_output_send_event_cb; + s->source_output->userdata = s; + + fix_record_buffer_attr_pre(s); + + memblockq_name = pa_sprintf_malloc("native protocol record stream memblockq [%u]", s->source_output->index); + s->memblockq = pa_memblockq_new( + memblockq_name, + 0, + s->buffer_attr.maxlength, + 0, + &source_output->sample_spec, + 1, + 0, + 0, + NULL); + pa_xfree(memblockq_name); + + pa_memblockq_get_attr(s->memblockq, &s->buffer_attr); + fix_record_buffer_attr_post(s); + + *ss = s->source_output->sample_spec; + *map = s->source_output->channel_map; + + pa_idxset_put(c->record_streams, s, &s->index); + + pa_log_info("Final latency %0.2f ms = %0.2f ms + %0.2f ms", + ((double) pa_bytes_to_usec(s->buffer_attr.fragsize, &source_output->sample_spec) + (double) s->configured_source_latency) / PA_USEC_PER_MSEC, + (double) pa_bytes_to_usec(s->buffer_attr.fragsize, &source_output->sample_spec) / PA_USEC_PER_MSEC, + (double) s->configured_source_latency / PA_USEC_PER_MSEC); + + pa_source_output_put(s->source_output); + return s; +} + +/* Called from main context */ +static void record_stream_send_killed(record_stream *r) { + pa_tagstruct *t; + record_stream_assert_ref(r); + + t = pa_tagstruct_new(); + pa_tagstruct_putu32(t, PA_COMMAND_RECORD_STREAM_KILLED); + pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */ + pa_tagstruct_putu32(t, r->index); + pa_pstream_send_tagstruct(r->connection->pstream, t); +} + +/* Called from main context */ +static void playback_stream_unlink(playback_stream *s) { + pa_assert(s); + + if (!s->connection) + return; + + if (s->sink_input) { + pa_sink_input_unlink(s->sink_input); + pa_sink_input_unref(s->sink_input); + s->sink_input = NULL; + } + + if (s->drain_request) + pa_pstream_send_error(s->connection->pstream, s->drain_tag, PA_ERR_NOENTITY); + + pa_assert_se(pa_idxset_remove_by_data(s->connection->output_streams, s, NULL) == s); + s->connection = NULL; + playback_stream_unref(s); +} + +/* Called from main context */ +static void playback_stream_free(pa_object* o) { + playback_stream *s = PLAYBACK_STREAM(o); + pa_assert(s); + + playback_stream_unlink(s); + + pa_memblockq_free(s->memblockq); + pa_xfree(s); +} + +/* Called from main context */ +static int playback_stream_process_msg(pa_msgobject *o, int code, void*userdata, int64_t offset, pa_memchunk *chunk) { + playback_stream *s = PLAYBACK_STREAM(o); + playback_stream_assert_ref(s); + + if (!s->connection) + return -1; + + switch (code) { + + case PLAYBACK_STREAM_MESSAGE_REQUEST_DATA: { + pa_tagstruct *t; + int l = 0; + + for (;;) { + if ((l = pa_atomic_load(&s->missing)) <= 0) + return 0; + + if (pa_atomic_cmpxchg(&s->missing, l, 0)) + break; + } + + t = pa_tagstruct_new(); + pa_tagstruct_putu32(t, PA_COMMAND_REQUEST); + pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */ + pa_tagstruct_putu32(t, s->index); + pa_tagstruct_putu32(t, (uint32_t) l); + pa_pstream_send_tagstruct(s->connection->pstream, t); + +#ifdef PROTOCOL_NATIVE_DEBUG + pa_log("Requesting %lu bytes", (unsigned long) l); +#endif + break; + } + + case PLAYBACK_STREAM_MESSAGE_UNDERFLOW: { + pa_tagstruct *t; + +#ifdef PROTOCOL_NATIVE_DEBUG + pa_log("signalling underflow"); +#endif + + /* Report that we're empty */ + t = pa_tagstruct_new(); + pa_tagstruct_putu32(t, PA_COMMAND_UNDERFLOW); + pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */ + pa_tagstruct_putu32(t, s->index); + if (s->connection->version >= 23) + pa_tagstruct_puts64(t, offset); + pa_pstream_send_tagstruct(s->connection->pstream, t); + break; + } + + case PLAYBACK_STREAM_MESSAGE_OVERFLOW: { + pa_tagstruct *t; + + /* Notify the user we're overflowed*/ + t = pa_tagstruct_new(); + pa_tagstruct_putu32(t, PA_COMMAND_OVERFLOW); + pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */ + pa_tagstruct_putu32(t, s->index); + pa_pstream_send_tagstruct(s->connection->pstream, t); + break; + } + + case PLAYBACK_STREAM_MESSAGE_STARTED: + + if (s->connection->version >= 13) { + pa_tagstruct *t; + + /* Notify the user we started playback */ + t = pa_tagstruct_new(); + pa_tagstruct_putu32(t, PA_COMMAND_STARTED); + pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */ + pa_tagstruct_putu32(t, s->index); + pa_pstream_send_tagstruct(s->connection->pstream, t); + } + + break; + + case PLAYBACK_STREAM_MESSAGE_DRAIN_ACK: + pa_pstream_send_simple_ack(s->connection->pstream, PA_PTR_TO_UINT(userdata)); + break; + + case PLAYBACK_STREAM_MESSAGE_UPDATE_TLENGTH: + + s->buffer_attr.tlength = (uint32_t) offset; + + if (s->connection->version >= 15) { + pa_tagstruct *t; + + t = pa_tagstruct_new(); + pa_tagstruct_putu32(t, PA_COMMAND_PLAYBACK_BUFFER_ATTR_CHANGED); + pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */ + pa_tagstruct_putu32(t, s->index); + pa_tagstruct_putu32(t, s->buffer_attr.maxlength); + pa_tagstruct_putu32(t, s->buffer_attr.tlength); + pa_tagstruct_putu32(t, s->buffer_attr.prebuf); + pa_tagstruct_putu32(t, s->buffer_attr.minreq); + pa_tagstruct_put_usec(t, s->configured_sink_latency); + pa_pstream_send_tagstruct(s->connection->pstream, t); + } + + break; + } + + return 0; +} + +/* Called from main context */ +static void fix_playback_buffer_attr(playback_stream *s) { + size_t frame_size, max_prebuf; + pa_usec_t orig_tlength_usec, tlength_usec, orig_minreq_usec, minreq_usec, sink_usec; + + pa_assert(s); + +#ifdef PROTOCOL_NATIVE_DEBUG + pa_log("Client requested: maxlength=%li bytes tlength=%li bytes minreq=%li bytes prebuf=%li bytes", + (long) s->buffer_attr_req.maxlength, + (long) s->buffer_attr_req.tlength, + (long) s->buffer_attr_req.minreq, + (long) s->buffer_attr_req.prebuf); + + pa_log("Client requested: maxlength=%lu ms tlength=%lu ms minreq=%lu ms prebuf=%lu ms", + (unsigned long) (pa_bytes_to_usec(s->buffer_attr_req.maxlength, &s->sink_input->sample_spec) / PA_USEC_PER_MSEC), + (unsigned long) (pa_bytes_to_usec(s->buffer_attr_req.tlength, &s->sink_input->sample_spec) / PA_USEC_PER_MSEC), + (unsigned long) (pa_bytes_to_usec(s->buffer_attr_req.minreq, &s->sink_input->sample_spec) / PA_USEC_PER_MSEC), + (unsigned long) (pa_bytes_to_usec(s->buffer_attr_req.prebuf, &s->sink_input->sample_spec) / PA_USEC_PER_MSEC)); +#endif + + /* This function will be called from the main thread, before as + * well as after the sink input has been activated using + * pa_sink_input_put()! That means it may not touch any + * ->thread_info data, such as the memblockq! */ + + frame_size = pa_frame_size(&s->sink_input->sample_spec); + s->buffer_attr = s->buffer_attr_req; + + if (s->buffer_attr.maxlength == (uint32_t) -1 || s->buffer_attr.maxlength > MAX_MEMBLOCKQ_LENGTH) + s->buffer_attr.maxlength = MAX_MEMBLOCKQ_LENGTH; + if (s->buffer_attr.maxlength <= 0) + s->buffer_attr.maxlength = (uint32_t) frame_size; + + if (s->buffer_attr.tlength == (uint32_t) -1) + s->buffer_attr.tlength = (uint32_t) pa_usec_to_bytes_round_up(DEFAULT_TLENGTH_MSEC*PA_USEC_PER_MSEC, &s->sink_input->sample_spec); + if (s->buffer_attr.tlength <= 0) + s->buffer_attr.tlength = (uint32_t) frame_size; + if (s->buffer_attr.tlength > s->buffer_attr.maxlength) + s->buffer_attr.tlength = s->buffer_attr.maxlength; + + if (s->buffer_attr.minreq == (uint32_t) -1) { + uint32_t process = (uint32_t) pa_usec_to_bytes_round_up(DEFAULT_PROCESS_MSEC*PA_USEC_PER_MSEC, &s->sink_input->sample_spec); + /* With low-latency, tlength/4 gives a decent default in all of traditional, adjust latency and early request modes. */ + uint32_t m = s->buffer_attr.tlength / 4; + if (frame_size) + m -= m % frame_size; + s->buffer_attr.minreq = PA_MIN(process, m); + } + if (s->buffer_attr.minreq <= 0) + s->buffer_attr.minreq = (uint32_t) frame_size; + + if (s->buffer_attr.tlength < s->buffer_attr.minreq+frame_size) + s->buffer_attr.tlength = s->buffer_attr.minreq+(uint32_t) frame_size; + + orig_tlength_usec = tlength_usec = pa_bytes_to_usec(s->buffer_attr.tlength, &s->sink_input->sample_spec); + orig_minreq_usec = minreq_usec = pa_bytes_to_usec(s->buffer_attr.minreq, &s->sink_input->sample_spec); + + pa_log_info("Requested tlength=%0.2f ms, minreq=%0.2f ms", + (double) tlength_usec / PA_USEC_PER_MSEC, + (double) minreq_usec / PA_USEC_PER_MSEC); + + if (s->early_requests) { + + /* In early request mode we need to emulate the classic + * fragment-based playback model. Unfortunately we have no + * mechanism to tell the sink how often we want to be queried + * for data. The next best thing we can do is to set the sink's + * total buffer (i.e. its latency) to the fragment size. That + * way it will have to query us at least that often. */ + + sink_usec = minreq_usec; + pa_log_debug("Early requests mode enabled, configuring sink latency to minreq."); + + } else if (s->adjust_latency) { + + /* So, the user asked us to adjust the latency of the stream + * buffer according to the what the sink can provide. The + * tlength passed in shall be the overall latency. Roughly + * half the latency will be spent on the hw buffer, the other + * half of it in the async buffer queue we maintain for each + * client. In between we'll have a safety space of size + * 2*minreq. Why the 2*minreq? When the hw buffer is completely + * empty and needs to be filled, then our buffer must have + * enough data to fulfill this request immediately and thus + * have at least the same tlength as the size of the hw + * buffer. It additionally needs space for 2 times minreq + * because if the buffer ran empty and a partial fillup + * happens immediately on the next iteration we need to be + * able to fulfill it and give the application also minreq + * time to fill it up again for the next request Makes 2 times + * minreq in plus.. */ + + if (tlength_usec > minreq_usec*2) + sink_usec = (tlength_usec - minreq_usec*2)/2; + else + sink_usec = 0; + + pa_log_debug("Adjust latency mode enabled, configuring sink latency to half of overall latency."); + + } else { + + /* Ok, the user didn't ask us to adjust the latency, but we + * still need to make sure that the parameters from the user + * do make sense. */ + + if (tlength_usec > minreq_usec*2) + sink_usec = (tlength_usec - minreq_usec*2); + else + sink_usec = 0; + + pa_log_debug("Traditional mode enabled, modifying sink usec only for compat with minreq."); + } + + s->configured_sink_latency = pa_sink_input_set_requested_latency(s->sink_input, sink_usec); + + if (s->early_requests) { + + /* Ok, we didn't necessarily get what we were asking for. We + * might still get the proper fragment interval, we just can't + * guarantee it. */ + + if (minreq_usec != s->configured_sink_latency) + pa_log_debug("Could not configure a sufficiently low latency. Early requests might not be satisfied."); + + } else if (s->adjust_latency) { + + /* Ok, we didn't necessarily get what we were asking for, so + * let's subtract from what we asked for for the remaining + * buffer space */ + + if (tlength_usec >= s->configured_sink_latency) + tlength_usec -= s->configured_sink_latency; + } + + pa_log_debug("Requested latency=%0.2f ms, Received latency=%0.2f ms", + (double) sink_usec / PA_USEC_PER_MSEC, + (double) s->configured_sink_latency / PA_USEC_PER_MSEC); + + /* FIXME: This is actually larger than necessary, since not all of + * the sink latency is actually rewritable. */ + if (tlength_usec < s->configured_sink_latency + 2*minreq_usec) + tlength_usec = s->configured_sink_latency + 2*minreq_usec; + + if (pa_usec_to_bytes_round_up(orig_tlength_usec, &s->sink_input->sample_spec) != + pa_usec_to_bytes_round_up(tlength_usec, &s->sink_input->sample_spec)) + s->buffer_attr.tlength = (uint32_t) pa_usec_to_bytes_round_up(tlength_usec, &s->sink_input->sample_spec); + + if (pa_usec_to_bytes(orig_minreq_usec, &s->sink_input->sample_spec) != + pa_usec_to_bytes(minreq_usec, &s->sink_input->sample_spec)) + s->buffer_attr.minreq = (uint32_t) pa_usec_to_bytes(minreq_usec, &s->sink_input->sample_spec); + + if (s->buffer_attr.minreq <= 0) { + s->buffer_attr.minreq = (uint32_t) frame_size; + s->buffer_attr.tlength += (uint32_t) frame_size*2; + } + + if (s->buffer_attr.tlength <= s->buffer_attr.minreq) + s->buffer_attr.tlength = s->buffer_attr.minreq*2 + (uint32_t) frame_size; + + max_prebuf = s->buffer_attr.tlength + (uint32_t)frame_size - s->buffer_attr.minreq; + + if (s->buffer_attr.prebuf == (uint32_t) -1 || + s->buffer_attr.prebuf > max_prebuf) + s->buffer_attr.prebuf = max_prebuf; + +#ifdef PROTOCOL_NATIVE_DEBUG + pa_log("Client accepted: maxlength=%lu ms tlength=%lu ms minreq=%lu ms prebuf=%lu ms", + (unsigned long) (pa_bytes_to_usec(s->buffer_attr.maxlength, &s->sink_input->sample_spec) / PA_USEC_PER_MSEC), + (unsigned long) (pa_bytes_to_usec(s->buffer_attr.tlength, &s->sink_input->sample_spec) / PA_USEC_PER_MSEC), + (unsigned long) (pa_bytes_to_usec(s->buffer_attr.minreq, &s->sink_input->sample_spec) / PA_USEC_PER_MSEC), + (unsigned long) (pa_bytes_to_usec(s->buffer_attr.prebuf, &s->sink_input->sample_spec) / PA_USEC_PER_MSEC)); +#endif +} + +/* Called from main context */ +static playback_stream* playback_stream_new( + pa_native_connection *c, + pa_sink *sink, + pa_sample_spec *ss, + pa_channel_map *map, + pa_idxset *formats, + pa_buffer_attr *a, + pa_cvolume *volume, + bool muted, + bool muted_set, + pa_sink_input_flags_t flags, + pa_proplist *p, + bool adjust_latency, + bool early_requests, + bool relative_volume, + uint32_t syncid, + uint32_t *missing, + int *ret) { + + /* Note: This function takes ownership of the 'formats' param, so we need + * to take extra care to not leak it */ + + playback_stream *ssync; + playback_stream *s = NULL; + pa_sink_input *sink_input = NULL; + pa_memchunk silence; + uint32_t idx; + int64_t start_index; + pa_sink_input_new_data data; + char *memblockq_name; + + pa_assert(c); + pa_assert(ss); + pa_assert(missing); + pa_assert(p); + pa_assert(ret); + + /* Find syncid group */ + PA_IDXSET_FOREACH(ssync, c->output_streams, idx) { + + if (!playback_stream_isinstance(ssync)) + continue; + + if (ssync->syncid == syncid) + break; + } + + /* Synced streams must connect to the same sink */ + if (ssync) { + + if (!sink) + sink = ssync->sink_input->sink; + else if (sink != ssync->sink_input->sink) { + *ret = PA_ERR_INVALID; + goto out; + } + } + + pa_sink_input_new_data_init(&data); + + pa_proplist_update(data.proplist, PA_UPDATE_REPLACE, p); + data.driver = __FILE__; + data.module = c->options->module; + data.client = c->client; + if (sink) + pa_sink_input_new_data_set_sink(&data, sink, false, true); + if (pa_sample_spec_valid(ss)) + pa_sink_input_new_data_set_sample_spec(&data, ss); + if (pa_channel_map_valid(map)) + pa_sink_input_new_data_set_channel_map(&data, map); + if (formats) { + pa_sink_input_new_data_set_formats(&data, formats); + /* Ownership transferred to new_data, so we don't free it ourselves */ + formats = NULL; + } + if (volume) { + pa_sink_input_new_data_set_volume(&data, volume); + data.volume_is_absolute = !relative_volume; + data.save_volume = false; + } + if (muted_set) { + pa_sink_input_new_data_set_muted(&data, muted); + data.save_muted = false; + } + data.sync_base = ssync ? ssync->sink_input : NULL; + data.flags = flags; + + *ret = -pa_sink_input_new(&sink_input, c->protocol->core, &data); + + pa_sink_input_new_data_done(&data); + + if (!sink_input) + goto out; + + s = pa_msgobject_new(playback_stream); + s->parent.parent.parent.free = playback_stream_free; + s->parent.parent.process_msg = playback_stream_process_msg; + s->connection = c; + s->syncid = syncid; + s->sink_input = sink_input; + s->is_underrun = true; + s->drain_request = false; + pa_atomic_store(&s->missing, 0); + s->buffer_attr_req = *a; + s->adjust_latency = adjust_latency; + s->early_requests = early_requests; + pa_atomic_store(&s->seek_or_post_in_queue, 0); + s->seek_windex = -1; + + s->sink_input->parent.process_msg = sink_input_process_msg; + s->sink_input->pop = sink_input_pop_cb; + s->sink_input->process_underrun = sink_input_process_underrun_cb; + s->sink_input->process_rewind = sink_input_process_rewind_cb; + s->sink_input->update_max_rewind = sink_input_update_max_rewind_cb; + s->sink_input->update_max_request = sink_input_update_max_request_cb; + s->sink_input->kill = sink_input_kill_cb; + s->sink_input->moving = sink_input_moving_cb; + s->sink_input->suspend = sink_input_suspend_cb; + s->sink_input->send_event = sink_input_send_event_cb; + s->sink_input->userdata = s; + + start_index = ssync ? pa_memblockq_get_read_index(ssync->memblockq) : 0; + + fix_playback_buffer_attr(s); + + pa_sink_input_get_silence(sink_input, &silence); + memblockq_name = pa_sprintf_malloc("native protocol playback stream memblockq [%u]", s->sink_input->index); + s->memblockq = pa_memblockq_new( + memblockq_name, + start_index, + s->buffer_attr.maxlength, + s->buffer_attr.tlength, + &sink_input->sample_spec, + s->buffer_attr.prebuf, + s->buffer_attr.minreq, + 0, + &silence); + pa_xfree(memblockq_name); + pa_memblock_unref(silence.memblock); + + pa_memblockq_get_attr(s->memblockq, &s->buffer_attr); + + *missing = (uint32_t) pa_memblockq_pop_missing(s->memblockq); + +#ifdef PROTOCOL_NATIVE_DEBUG + pa_log("missing original: %li", (long int) *missing); +#endif + + *ss = s->sink_input->sample_spec; + *map = s->sink_input->channel_map; + + pa_idxset_put(c->output_streams, s, &s->index); + + pa_log_info("Final latency %0.2f ms = %0.2f ms + 2*%0.2f ms + %0.2f ms", + ((double) pa_bytes_to_usec(s->buffer_attr.tlength, &sink_input->sample_spec) + (double) s->configured_sink_latency) / PA_USEC_PER_MSEC, + (double) pa_bytes_to_usec(s->buffer_attr.tlength-s->buffer_attr.minreq*2, &sink_input->sample_spec) / PA_USEC_PER_MSEC, + (double) pa_bytes_to_usec(s->buffer_attr.minreq, &sink_input->sample_spec) / PA_USEC_PER_MSEC, + (double) s->configured_sink_latency / PA_USEC_PER_MSEC); + + pa_sink_input_put(s->sink_input); + +out: + if (formats) + pa_idxset_free(formats, (pa_free_cb_t) pa_format_info_free); + + return s; +} + +/* Called from IO context */ +static void playback_stream_request_bytes(playback_stream *s) { + size_t m; + + playback_stream_assert_ref(s); + + m = pa_memblockq_pop_missing(s->memblockq); + + /* pa_log("request_bytes(%lu) (tlength=%lu minreq=%lu length=%lu really missing=%lli)", */ + /* (unsigned long) m, */ + /* pa_memblockq_get_tlength(s->memblockq), */ + /* pa_memblockq_get_minreq(s->memblockq), */ + /* pa_memblockq_get_length(s->memblockq), */ + /* (long long) pa_memblockq_get_tlength(s->memblockq) - (long long) pa_memblockq_get_length(s->memblockq)); */ + + if (m <= 0) + return; + +#ifdef PROTOCOL_NATIVE_DEBUG + pa_log("request_bytes(%lu)", (unsigned long) m); +#endif + + if (pa_atomic_add(&s->missing, (int) m) <= 0) + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PLAYBACK_STREAM_MESSAGE_REQUEST_DATA, NULL, 0, NULL, NULL); +} + +/* Called from main context */ +static void playback_stream_send_killed(playback_stream *p) { + pa_tagstruct *t; + playback_stream_assert_ref(p); + + t = pa_tagstruct_new(); + pa_tagstruct_putu32(t, PA_COMMAND_PLAYBACK_STREAM_KILLED); + pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */ + pa_tagstruct_putu32(t, p->index); + pa_pstream_send_tagstruct(p->connection->pstream, t); +} + +/* Called from main context */ +static int native_connection_process_msg(pa_msgobject *o, int code, void*userdata, int64_t offset, pa_memchunk *chunk) { + pa_native_connection *c = PA_NATIVE_CONNECTION(o); + pa_native_connection_assert_ref(c); + + if (!c->protocol) + return -1; + + switch (code) { + + case CONNECTION_MESSAGE_REVOKE: + pa_pstream_send_revoke(c->pstream, PA_PTR_TO_UINT(userdata)); + break; + + case CONNECTION_MESSAGE_RELEASE: + pa_pstream_send_release(c->pstream, PA_PTR_TO_UINT(userdata)); + break; + } + + return 0; +} + +/* Called from main context */ +static void native_connection_unlink(pa_native_connection *c) { + record_stream *r; + output_stream *o; + + pa_assert(c); + + if (!c->protocol) + return; + + pa_hook_fire(&c->protocol->hooks[PA_NATIVE_HOOK_CONNECTION_UNLINK], c); + + if (c->options) + pa_native_options_unref(c->options); + + if (c->srbpending) + pa_srbchannel_free(c->srbpending); + + while ((r = pa_idxset_first(c->record_streams, NULL))) + record_stream_unlink(r); + + while ((o = pa_idxset_first(c->output_streams, NULL))) + if (playback_stream_isinstance(o)) + playback_stream_unlink(PLAYBACK_STREAM(o)); + else + upload_stream_unlink(UPLOAD_STREAM(o)); + + if (c->subscription) + pa_subscription_free(c->subscription); + + if (c->pstream) + pa_pstream_unlink(c->pstream); + + if (c->auth_timeout_event) { + c->protocol->core->mainloop->time_free(c->auth_timeout_event); + c->auth_timeout_event = NULL; + } + + pa_assert_se(pa_idxset_remove_by_data(c->protocol->connections, c, NULL) == c); + c->protocol = NULL; + pa_native_connection_unref(c); +} + +/* Called from main context */ +static void native_connection_free(pa_object *o) { + pa_native_connection *c = PA_NATIVE_CONNECTION(o); + + pa_assert(c); + + native_connection_unlink(c); + + pa_idxset_free(c->record_streams, NULL); + pa_idxset_free(c->output_streams, NULL); + + pa_pdispatch_unref(c->pdispatch); + pa_pstream_unref(c->pstream); + if (c->rw_mempool) + pa_mempool_unref(c->rw_mempool); + + pa_client_free(c->client); + + pa_xfree(c); +} + +/* Called from main context */ +static void native_connection_send_memblock(pa_native_connection *c) { + uint32_t start; + record_stream *r; + + start = PA_IDXSET_INVALID; + for (;;) { + pa_memchunk chunk; + + if (!(r = RECORD_STREAM(pa_idxset_rrobin(c->record_streams, &c->rrobin_index)))) + return; + + if (start == PA_IDXSET_INVALID) + start = c->rrobin_index; + else if (start == c->rrobin_index) + return; + + if (pa_memblockq_peek(r->memblockq, &chunk) >= 0) { + pa_memchunk schunk = chunk; + + if (schunk.length > r->buffer_attr.fragsize) + schunk.length = r->buffer_attr.fragsize; + + pa_pstream_send_memblock(c->pstream, r->index, 0, PA_SEEK_RELATIVE, &schunk); + + pa_memblockq_drop(r->memblockq, schunk.length); + pa_memblock_unref(schunk.memblock); + + return; + } + } +} + +/*** sink input callbacks ***/ + +/* Called from thread context */ +static void handle_seek(playback_stream *s, int64_t indexw) { + playback_stream_assert_ref(s); + +/* pa_log("handle_seek: %llu -- %i", (unsigned long long) s->sink_input->thread_info.underrun_for, pa_memblockq_is_readable(s->memblockq)); */ + + if (s->sink_input->thread_info.underrun_for > 0) { + +/* pa_log("%lu vs. %lu", (unsigned long) pa_memblockq_get_length(s->memblockq), (unsigned long) pa_memblockq_get_prebuf(s->memblockq)); */ + + if (pa_memblockq_is_readable(s->memblockq)) { + + /* We just ended an underrun, let's ask the sink + * for a complete rewind rewrite */ + + pa_log_debug("Requesting rewind due to end of underrun."); + pa_sink_input_request_rewind(s->sink_input, + (size_t) (s->sink_input->thread_info.underrun_for == (uint64_t) -1 ? 0 : + s->sink_input->thread_info.underrun_for), + false, true, false); + } + + } else { + int64_t indexr; + + indexr = pa_memblockq_get_read_index(s->memblockq); + + if (indexw < indexr) { + /* OK, the sink already asked for this data, so + * let's have it ask us again */ + + pa_log_debug("Requesting rewind due to rewrite."); + pa_sink_input_request_rewind(s->sink_input, (size_t) (indexr - indexw), true, false, false); + } + } + + playback_stream_request_bytes(s); +} + +static void flush_write_no_account(pa_memblockq *q) { + pa_memblockq_flush_write(q, false); +} + +/* Called from thread context */ +static int sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk) { + pa_sink_input *i = PA_SINK_INPUT(o); + playback_stream *s; + + pa_sink_input_assert_ref(i); + s = PLAYBACK_STREAM(i->userdata); + playback_stream_assert_ref(s); + + switch (code) { + + case SINK_INPUT_MESSAGE_SEEK: + case SINK_INPUT_MESSAGE_POST_DATA: { + int64_t windex = pa_memblockq_get_write_index(s->memblockq); + + if (code == SINK_INPUT_MESSAGE_SEEK) { + /* The client side is incapable of accounting correctly + * for seeks of a type != PA_SEEK_RELATIVE. We need to be + * able to deal with that. */ + + pa_memblockq_seek(s->memblockq, offset, PA_PTR_TO_UINT(userdata), PA_PTR_TO_UINT(userdata) == PA_SEEK_RELATIVE); + windex = PA_MIN(windex, pa_memblockq_get_write_index(s->memblockq)); + } + + if (chunk && pa_memblockq_push_align(s->memblockq, chunk) < 0) { + if (pa_log_ratelimit(PA_LOG_WARN)) + pa_log_warn("Failed to push data into queue"); + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PLAYBACK_STREAM_MESSAGE_OVERFLOW, NULL, 0, NULL, NULL); + pa_memblockq_seek(s->memblockq, (int64_t) chunk->length, PA_SEEK_RELATIVE, true); + } + + /* If more data is in queue, we rewind later instead. */ + if (s->seek_windex != -1) + windex = PA_MIN(windex, s->seek_windex); + if (pa_atomic_dec(&s->seek_or_post_in_queue) > 1) + s->seek_windex = windex; + else { + s->seek_windex = -1; + handle_seek(s, windex); + } + return 0; + } + + case SINK_INPUT_MESSAGE_DRAIN: + case SINK_INPUT_MESSAGE_FLUSH: + case SINK_INPUT_MESSAGE_PREBUF_FORCE: + case SINK_INPUT_MESSAGE_TRIGGER: { + + int64_t windex; + pa_sink_input *isync; + void (*func)(pa_memblockq *bq); + + switch (code) { + case SINK_INPUT_MESSAGE_FLUSH: + func = flush_write_no_account; + break; + + case SINK_INPUT_MESSAGE_PREBUF_FORCE: + func = pa_memblockq_prebuf_force; + break; + + case SINK_INPUT_MESSAGE_DRAIN: + case SINK_INPUT_MESSAGE_TRIGGER: + func = pa_memblockq_prebuf_disable; + break; + + default: + pa_assert_not_reached(); + } + + windex = pa_memblockq_get_write_index(s->memblockq); + func(s->memblockq); + handle_seek(s, windex); + + /* Do the same for all other members in the sync group */ + for (isync = i->sync_prev; isync; isync = isync->sync_prev) { + playback_stream *ssync = PLAYBACK_STREAM(isync->userdata); + windex = pa_memblockq_get_write_index(ssync->memblockq); + func(ssync->memblockq); + handle_seek(ssync, windex); + } + + for (isync = i->sync_next; isync; isync = isync->sync_next) { + playback_stream *ssync = PLAYBACK_STREAM(isync->userdata); + windex = pa_memblockq_get_write_index(ssync->memblockq); + func(ssync->memblockq); + handle_seek(ssync, windex); + } + + if (code == SINK_INPUT_MESSAGE_DRAIN) { + if (!pa_memblockq_is_readable(s->memblockq)) + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PLAYBACK_STREAM_MESSAGE_DRAIN_ACK, userdata, 0, NULL, NULL); + else { + s->drain_tag = PA_PTR_TO_UINT(userdata); + s->drain_request = true; + } + } + + return 0; + } + + case SINK_INPUT_MESSAGE_UPDATE_LATENCY: + /* Atomically get a snapshot of all timing parameters... */ + s->read_index = pa_memblockq_get_read_index(s->memblockq); + s->write_index = pa_memblockq_get_write_index(s->memblockq); + s->render_memblockq_length = pa_memblockq_get_length(s->sink_input->thread_info.render_memblockq); + s->current_sink_latency = pa_sink_get_latency_within_thread(s->sink_input->sink, false); + s->underrun_for = s->sink_input->thread_info.underrun_for; + s->playing_for = s->sink_input->thread_info.playing_for; + + return 0; + + case PA_SINK_INPUT_MESSAGE_SET_STATE: { + int64_t windex; + + windex = pa_memblockq_get_write_index(s->memblockq); + + /* We enable prebuffering so that after CORKED -> RUNNING + * transitions we don't have trouble with underruns in case the + * buffer has too little data. This must not be done when draining + * has been requested, however, otherwise the buffered audio would + * never play. */ + if (!s->drain_request) + pa_memblockq_prebuf_force(s->memblockq); + + handle_seek(s, windex); + + /* Fall through to the default handler */ + break; + } + + case PA_SINK_INPUT_MESSAGE_GET_LATENCY: { + pa_usec_t *r = userdata; + + *r = pa_bytes_to_usec(pa_memblockq_get_length(s->memblockq), &i->sample_spec); + + /* Fall through, the default handler will add in the extra + * latency added by the resampler */ + break; + } + + case SINK_INPUT_MESSAGE_UPDATE_BUFFER_ATTR: { + pa_memblockq_apply_attr(s->memblockq, &s->buffer_attr); + pa_memblockq_get_attr(s->memblockq, &s->buffer_attr); + return 0; + } + } + + return pa_sink_input_process_msg(o, code, userdata, offset, chunk); +} + +static bool handle_input_underrun(playback_stream *s, bool force) { + bool send_drain; + + if (pa_memblockq_is_readable(s->memblockq)) + return false; + + if (!s->is_underrun) + pa_log_debug("%s %s of '%s'", force ? "Actual" : "Implicit", + s->drain_request ? "drain" : "underrun", pa_strnull(pa_proplist_gets(s->sink_input->proplist, PA_PROP_MEDIA_NAME))); + + send_drain = s->drain_request && (force || pa_sink_input_safe_to_remove(s->sink_input)); + + if (send_drain) { + s->drain_request = false; + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PLAYBACK_STREAM_MESSAGE_DRAIN_ACK, PA_UINT_TO_PTR(s->drain_tag), 0, NULL, NULL); + pa_log_debug("Drain acknowledged of '%s'", pa_strnull(pa_proplist_gets(s->sink_input->proplist, PA_PROP_MEDIA_NAME))); + } else if (!s->is_underrun) { + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PLAYBACK_STREAM_MESSAGE_UNDERFLOW, NULL, pa_memblockq_get_read_index(s->memblockq), NULL, NULL); + } + s->is_underrun = true; + playback_stream_request_bytes(s); + return true; +} + +/* Called from thread context */ +static bool sink_input_process_underrun_cb(pa_sink_input *i) { + playback_stream *s; + + pa_sink_input_assert_ref(i); + s = PLAYBACK_STREAM(i->userdata); + playback_stream_assert_ref(s); + + return handle_input_underrun(s, true); +} + +/* Called from thread context */ +static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk) { + playback_stream *s; + + pa_sink_input_assert_ref(i); + s = PLAYBACK_STREAM(i->userdata); + playback_stream_assert_ref(s); + pa_assert(chunk); + +#ifdef PROTOCOL_NATIVE_DEBUG + pa_log("%s, pop(): %lu", pa_proplist_gets(i->proplist, PA_PROP_MEDIA_NAME), (unsigned long) pa_memblockq_get_length(s->memblockq)); +#endif + + if (!handle_input_underrun(s, false)) + s->is_underrun = false; + + /* This call will not fail with prebuf=0, hence we check for + underrun explicitly in handle_input_underrun */ + if (pa_memblockq_peek(s->memblockq, chunk) < 0) + return -1; + + chunk->length = PA_MIN(nbytes, chunk->length); + + if (i->thread_info.underrun_for > 0) + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PLAYBACK_STREAM_MESSAGE_STARTED, NULL, 0, NULL, NULL); + + pa_memblockq_drop(s->memblockq, chunk->length); + playback_stream_request_bytes(s); + + return 0; +} + +/* Called from thread context */ +static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) { + playback_stream *s; + + pa_sink_input_assert_ref(i); + s = PLAYBACK_STREAM(i->userdata); + playback_stream_assert_ref(s); + + /* If we are in an underrun, then we don't rewind */ + if (i->thread_info.underrun_for > 0) + return; + + pa_memblockq_rewind(s->memblockq, nbytes); +} + +/* Called from thread context */ +static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) { + playback_stream *s; + + pa_sink_input_assert_ref(i); + s = PLAYBACK_STREAM(i->userdata); + playback_stream_assert_ref(s); + + pa_memblockq_set_maxrewind(s->memblockq, nbytes); +} + +/* Called from thread context */ +static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) { + playback_stream *s; + size_t new_tlength, old_tlength; + + pa_sink_input_assert_ref(i); + s = PLAYBACK_STREAM(i->userdata); + playback_stream_assert_ref(s); + + old_tlength = pa_memblockq_get_tlength(s->memblockq); + new_tlength = nbytes+2*pa_memblockq_get_minreq(s->memblockq); + + if (old_tlength < new_tlength) { + pa_log_debug("max_request changed, trying to update from %zu to %zu.", old_tlength, new_tlength); + pa_memblockq_set_tlength(s->memblockq, new_tlength); + new_tlength = pa_memblockq_get_tlength(s->memblockq); + + if (new_tlength == old_tlength) + pa_log_debug("Failed to increase tlength"); + else { + pa_log_debug("Notifying client about increased tlength"); + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PLAYBACK_STREAM_MESSAGE_UPDATE_TLENGTH, NULL, pa_memblockq_get_tlength(s->memblockq), NULL, NULL); + } + } +} + +/* Called from main context */ +static void sink_input_kill_cb(pa_sink_input *i) { + playback_stream *s; + + pa_sink_input_assert_ref(i); + s = PLAYBACK_STREAM(i->userdata); + playback_stream_assert_ref(s); + + playback_stream_send_killed(s); + playback_stream_unlink(s); +} + +/* Called from main context */ +static void sink_input_send_event_cb(pa_sink_input *i, const char *event, pa_proplist *pl) { + playback_stream *s; + pa_tagstruct *t; + + pa_sink_input_assert_ref(i); + s = PLAYBACK_STREAM(i->userdata); + playback_stream_assert_ref(s); + + if (s->connection->version < 15) + return; + + t = pa_tagstruct_new(); + pa_tagstruct_putu32(t, PA_COMMAND_PLAYBACK_STREAM_EVENT); + pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */ + pa_tagstruct_putu32(t, s->index); + pa_tagstruct_puts(t, event); + pa_tagstruct_put_proplist(t, pl); + pa_pstream_send_tagstruct(s->connection->pstream, t); +} + +/* Called from main context */ +static void sink_input_suspend_cb(pa_sink_input *i, pa_sink_state_t old_state, pa_suspend_cause_t old_suspend_cause) { + playback_stream *s; + pa_tagstruct *t; + bool suspend; + + pa_sink_input_assert_ref(i); + + /* State has not changed, nothing to do */ + if (old_state == i->sink->state) + return; + + suspend = (i->sink->state == PA_SINK_SUSPENDED); + + s = PLAYBACK_STREAM(i->userdata); + playback_stream_assert_ref(s); + + if (s->connection->version < 12) + return; + + t = pa_tagstruct_new(); + pa_tagstruct_putu32(t, PA_COMMAND_PLAYBACK_STREAM_SUSPENDED); + pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */ + pa_tagstruct_putu32(t, s->index); + pa_tagstruct_put_boolean(t, suspend); + pa_pstream_send_tagstruct(s->connection->pstream, t); +} + +/* Called from main context */ +static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest) { + playback_stream *s; + pa_tagstruct *t; + + pa_sink_input_assert_ref(i); + s = PLAYBACK_STREAM(i->userdata); + playback_stream_assert_ref(s); + + if (!dest) + return; + + fix_playback_buffer_attr(s); + pa_memblockq_apply_attr(s->memblockq, &s->buffer_attr); + pa_memblockq_get_attr(s->memblockq, &s->buffer_attr); + + if (s->connection->version < 12) + return; + + t = pa_tagstruct_new(); + pa_tagstruct_putu32(t, PA_COMMAND_PLAYBACK_STREAM_MOVED); + pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */ + pa_tagstruct_putu32(t, s->index); + pa_tagstruct_putu32(t, dest->index); + pa_tagstruct_puts(t, dest->name); + pa_tagstruct_put_boolean(t, dest->state == PA_SINK_SUSPENDED); + + if (s->connection->version >= 13) { + pa_tagstruct_putu32(t, s->buffer_attr.maxlength); + pa_tagstruct_putu32(t, s->buffer_attr.tlength); + pa_tagstruct_putu32(t, s->buffer_attr.prebuf); + pa_tagstruct_putu32(t, s->buffer_attr.minreq); + pa_tagstruct_put_usec(t, s->configured_sink_latency); + } + + pa_pstream_send_tagstruct(s->connection->pstream, t); +} + +/*** source_output callbacks ***/ + +/* Called from thread context */ +static int source_output_process_msg(pa_msgobject *_o, int code, void *userdata, int64_t offset, pa_memchunk *chunk) { + pa_source_output *o = PA_SOURCE_OUTPUT(_o); + record_stream *s; + + pa_source_output_assert_ref(o); + s = RECORD_STREAM(o->userdata); + record_stream_assert_ref(s); + + switch (code) { + case SOURCE_OUTPUT_MESSAGE_UPDATE_LATENCY: + /* Atomically get a snapshot of all timing parameters... */ + s->current_monitor_latency = o->source->monitor_of ? pa_sink_get_latency_within_thread(o->source->monitor_of, false) : 0; + s->current_source_latency = pa_source_get_latency_within_thread(o->source, false); + s->on_the_fly_snapshot = pa_atomic_load(&s->on_the_fly); + return 0; + } + + return pa_source_output_process_msg(_o, code, userdata, offset, chunk); +} + +/* Called from thread context */ +static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk) { + record_stream *s; + + pa_source_output_assert_ref(o); + s = RECORD_STREAM(o->userdata); + record_stream_assert_ref(s); + pa_assert(chunk); + + pa_atomic_add(&s->on_the_fly, chunk->length); + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), RECORD_STREAM_MESSAGE_POST_DATA, NULL, 0, chunk, NULL); +} + +static void source_output_kill_cb(pa_source_output *o) { + record_stream *s; + + pa_source_output_assert_ref(o); + s = RECORD_STREAM(o->userdata); + record_stream_assert_ref(s); + + record_stream_send_killed(s); + record_stream_unlink(s); +} + +static pa_usec_t source_output_get_latency_cb(pa_source_output *o) { + record_stream *s; + + pa_source_output_assert_ref(o); + s = RECORD_STREAM(o->userdata); + record_stream_assert_ref(s); + + /*pa_log("get_latency: %u", pa_memblockq_get_length(s->memblockq));*/ + + return pa_bytes_to_usec(pa_memblockq_get_length(s->memblockq), &o->sample_spec); +} + +/* Called from main context */ +static void source_output_send_event_cb(pa_source_output *o, const char *event, pa_proplist *pl) { + record_stream *s; + pa_tagstruct *t; + + pa_source_output_assert_ref(o); + s = RECORD_STREAM(o->userdata); + record_stream_assert_ref(s); + + if (s->connection->version < 15) + return; + + t = pa_tagstruct_new(); + pa_tagstruct_putu32(t, PA_COMMAND_RECORD_STREAM_EVENT); + pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */ + pa_tagstruct_putu32(t, s->index); + pa_tagstruct_puts(t, event); + pa_tagstruct_put_proplist(t, pl); + pa_pstream_send_tagstruct(s->connection->pstream, t); +} + +/* Called from main context */ +static void source_output_suspend_cb(pa_source_output *o, pa_source_state_t old_state, pa_suspend_cause_t old_suspend_cause) { + record_stream *s; + pa_tagstruct *t; + bool suspend; + + pa_source_output_assert_ref(o); + + /* State has not changed, nothing to do */ + if (old_state == o->source->state) + return; + + suspend = (o->source->state == PA_SOURCE_SUSPENDED); + + s = RECORD_STREAM(o->userdata); + record_stream_assert_ref(s); + + if (s->connection->version < 12) + return; + + t = pa_tagstruct_new(); + pa_tagstruct_putu32(t, PA_COMMAND_RECORD_STREAM_SUSPENDED); + pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */ + pa_tagstruct_putu32(t, s->index); + pa_tagstruct_put_boolean(t, suspend); + pa_pstream_send_tagstruct(s->connection->pstream, t); +} + +/* Called from main context */ +static void source_output_moving_cb(pa_source_output *o, pa_source *dest) { + record_stream *s; + pa_tagstruct *t; + + pa_source_output_assert_ref(o); + s = RECORD_STREAM(o->userdata); + record_stream_assert_ref(s); + + if (!dest) + return; + + fix_record_buffer_attr_pre(s); + pa_memblockq_set_maxlength(s->memblockq, s->buffer_attr.maxlength); + pa_memblockq_get_attr(s->memblockq, &s->buffer_attr); + fix_record_buffer_attr_post(s); + + if (s->connection->version < 12) + return; + + t = pa_tagstruct_new(); + pa_tagstruct_putu32(t, PA_COMMAND_RECORD_STREAM_MOVED); + pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */ + pa_tagstruct_putu32(t, s->index); + pa_tagstruct_putu32(t, dest->index); + pa_tagstruct_puts(t, dest->name); + pa_tagstruct_put_boolean(t, dest->state == PA_SOURCE_SUSPENDED); + + if (s->connection->version >= 13) { + pa_tagstruct_putu32(t, s->buffer_attr.maxlength); + pa_tagstruct_putu32(t, s->buffer_attr.fragsize); + pa_tagstruct_put_usec(t, s->configured_source_latency); + } + + pa_pstream_send_tagstruct(s->connection->pstream, t); +} + +/*** pdispatch callbacks ***/ + +static void protocol_error(pa_native_connection *c) { + pa_log("protocol error, kicking client"); + native_connection_unlink(c); +} + +#define CHECK_VALIDITY(pstream, expression, tag, error) do { \ +if (!(expression)) { \ + pa_pstream_send_error((pstream), (tag), (error)); \ + return; \ +} \ +} while(0); + +#define CHECK_VALIDITY_GOTO(pstream, expression, tag, error, label) do { \ +if (!(expression)) { \ + pa_pstream_send_error((pstream), (tag), (error)); \ + goto label; \ +} \ +} while(0); + +static pa_tagstruct *reply_new(uint32_t tag) { + pa_tagstruct *reply; + + reply = pa_tagstruct_new(); + pa_tagstruct_putu32(reply, PA_COMMAND_REPLY); + pa_tagstruct_putu32(reply, tag); + return reply; +} + +static void command_create_playback_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + playback_stream *s; + uint32_t sink_index, syncid, missing = 0; + pa_buffer_attr attr; + const char *name = NULL, *sink_name; + pa_sample_spec ss; + pa_channel_map map; + pa_tagstruct *reply; + pa_sink *sink = NULL; + pa_cvolume volume; + bool + corked = false, + no_remap = false, + no_remix = false, + fix_format = false, + fix_rate = false, + fix_channels = false, + no_move = false, + variable_rate = false, + muted = false, + adjust_latency = false, + early_requests = false, + dont_inhibit_auto_suspend = false, + volume_set = true, + muted_set = false, + fail_on_suspend = false, + relative_volume = false, + passthrough = false; + + pa_sink_input_flags_t flags = 0; + pa_proplist *p = NULL; + int ret = PA_ERR_INVALID; + uint8_t n_formats = 0; + pa_format_info *format; + pa_idxset *formats = NULL; + uint32_t i; + + pa_native_connection_assert_ref(c); + pa_assert(t); + memset(&attr, 0, sizeof(attr)); + + if ((c->version < 13 && (pa_tagstruct_gets(t, &name) < 0 || !name)) || + pa_tagstruct_get( + t, + PA_TAG_SAMPLE_SPEC, &ss, + PA_TAG_CHANNEL_MAP, &map, + PA_TAG_U32, &sink_index, + PA_TAG_STRING, &sink_name, + PA_TAG_U32, &attr.maxlength, + PA_TAG_BOOLEAN, &corked, + PA_TAG_U32, &attr.tlength, + PA_TAG_U32, &attr.prebuf, + PA_TAG_U32, &attr.minreq, + PA_TAG_U32, &syncid, + PA_TAG_CVOLUME, &volume, + PA_TAG_INVALID) < 0) { + + protocol_error(c); + goto finish; + } + + CHECK_VALIDITY_GOTO(c->pstream, c->authorized, tag, PA_ERR_ACCESS, finish); + CHECK_VALIDITY_GOTO(c->pstream, !sink_name || pa_namereg_is_valid_name_or_wildcard(sink_name, PA_NAMEREG_SINK), tag, PA_ERR_INVALID, finish); + CHECK_VALIDITY_GOTO(c->pstream, sink_index == PA_INVALID_INDEX || !sink_name, tag, PA_ERR_INVALID, finish); + CHECK_VALIDITY_GOTO(c->pstream, !sink_name || sink_index == PA_INVALID_INDEX, tag, PA_ERR_INVALID, finish); + CHECK_VALIDITY_GOTO(c->pstream, pa_cvolume_valid(&volume), tag, PA_ERR_INVALID, finish); + + p = pa_proplist_new(); + + if (name) + pa_proplist_sets(p, PA_PROP_MEDIA_NAME, name); + + if (c->version >= 12) { + /* Since 0.9.8 the user can ask for a couple of additional flags */ + + if (pa_tagstruct_get_boolean(t, &no_remap) < 0 || + pa_tagstruct_get_boolean(t, &no_remix) < 0 || + pa_tagstruct_get_boolean(t, &fix_format) < 0 || + pa_tagstruct_get_boolean(t, &fix_rate) < 0 || + pa_tagstruct_get_boolean(t, &fix_channels) < 0 || + pa_tagstruct_get_boolean(t, &no_move) < 0 || + pa_tagstruct_get_boolean(t, &variable_rate) < 0) { + + protocol_error(c); + goto finish; + } + } + + if (c->version >= 13) { + + if (pa_tagstruct_get_boolean(t, &muted) < 0 || + pa_tagstruct_get_boolean(t, &adjust_latency) < 0 || + pa_tagstruct_get_proplist(t, p) < 0) { + + protocol_error(c); + goto finish; + } + } + + if (c->version >= 14) { + + if (pa_tagstruct_get_boolean(t, &volume_set) < 0 || + pa_tagstruct_get_boolean(t, &early_requests) < 0) { + + protocol_error(c); + goto finish; + } + } + + if (c->version >= 15) { + + if (pa_tagstruct_get_boolean(t, &muted_set) < 0 || + pa_tagstruct_get_boolean(t, &dont_inhibit_auto_suspend) < 0 || + pa_tagstruct_get_boolean(t, &fail_on_suspend) < 0) { + + protocol_error(c); + goto finish; + } + } + + if (c->version >= 17) { + + if (pa_tagstruct_get_boolean(t, &relative_volume) < 0) { + + protocol_error(c); + goto finish; + } + } + + if (c->version >= 18) { + + if (pa_tagstruct_get_boolean(t, &passthrough) < 0 ) { + protocol_error(c); + goto finish; + } + } + + if (c->version >= 21) { + + if (pa_tagstruct_getu8(t, &n_formats) < 0) { + protocol_error(c); + goto finish; + } + + if (n_formats) + formats = pa_idxset_new(NULL, NULL); + + for (i = 0; i < n_formats; i++) { + format = pa_format_info_new(); + if (pa_tagstruct_get_format_info(t, format) < 0) { + protocol_error(c); + goto finish; + } + pa_idxset_put(formats, format, NULL); + } + } + + if (n_formats == 0) { + CHECK_VALIDITY_GOTO(c->pstream, pa_sample_spec_valid(&ss), tag, PA_ERR_INVALID, finish); + CHECK_VALIDITY_GOTO(c->pstream, map.channels == ss.channels && volume.channels == ss.channels, tag, PA_ERR_INVALID, finish); + CHECK_VALIDITY_GOTO(c->pstream, pa_channel_map_valid(&map), tag, PA_ERR_INVALID, finish); + } else { + PA_IDXSET_FOREACH(format, formats, i) { + CHECK_VALIDITY_GOTO(c->pstream, pa_format_info_valid(format), tag, PA_ERR_INVALID, finish); + } + } + + if (!pa_tagstruct_eof(t)) { + protocol_error(c); + goto finish; + } + + if (sink_index != PA_INVALID_INDEX) { + + if (!(sink = pa_idxset_get_by_index(c->protocol->core->sinks, sink_index))) { + pa_pstream_send_error(c->pstream, tag, PA_ERR_NOENTITY); + goto finish; + } + + } else if (sink_name) { + + if (!(sink = pa_namereg_get(c->protocol->core, sink_name, PA_NAMEREG_SINK))) { + pa_pstream_send_error(c->pstream, tag, PA_ERR_NOENTITY); + goto finish; + } + } + + flags = + (corked ? PA_SINK_INPUT_START_CORKED : 0) | + (no_remap ? PA_SINK_INPUT_NO_REMAP : 0) | + (no_remix ? PA_SINK_INPUT_NO_REMIX : 0) | + (fix_format ? PA_SINK_INPUT_FIX_FORMAT : 0) | + (fix_rate ? PA_SINK_INPUT_FIX_RATE : 0) | + (fix_channels ? PA_SINK_INPUT_FIX_CHANNELS : 0) | + (no_move ? PA_SINK_INPUT_DONT_MOVE : 0) | + (variable_rate ? PA_SINK_INPUT_VARIABLE_RATE : 0) | + (dont_inhibit_auto_suspend ? PA_SINK_INPUT_DONT_INHIBIT_AUTO_SUSPEND : 0) | + (fail_on_suspend ? PA_SINK_INPUT_NO_CREATE_ON_SUSPEND|PA_SINK_INPUT_KILL_ON_SUSPEND : 0) | + (passthrough ? PA_SINK_INPUT_PASSTHROUGH : 0); + + /* Only since protocol version 15 there's a separate muted_set + * flag. For older versions we synthesize it here */ + muted_set = muted_set || muted; + + s = playback_stream_new(c, sink, &ss, &map, formats, &attr, volume_set ? &volume : NULL, muted, muted_set, flags, p, adjust_latency, early_requests, relative_volume, syncid, &missing, &ret); + /* We no longer own the formats idxset */ + formats = NULL; + + CHECK_VALIDITY_GOTO(c->pstream, s, tag, ret, finish); + + reply = reply_new(tag); + pa_tagstruct_putu32(reply, s->index); + pa_assert(s->sink_input); + pa_tagstruct_putu32(reply, s->sink_input->index); + pa_tagstruct_putu32(reply, missing); + +#ifdef PROTOCOL_NATIVE_DEBUG + pa_log("initial request is %u", missing); +#endif + + if (c->version >= 9) { + /* Since 0.9.0 we support sending the buffer metrics back to the client */ + + pa_tagstruct_putu32(reply, (uint32_t) s->buffer_attr.maxlength); + pa_tagstruct_putu32(reply, (uint32_t) s->buffer_attr.tlength); + pa_tagstruct_putu32(reply, (uint32_t) s->buffer_attr.prebuf); + pa_tagstruct_putu32(reply, (uint32_t) s->buffer_attr.minreq); + } + + if (c->version >= 12) { + /* Since 0.9.8 we support sending the chosen sample + * spec/channel map/device/suspend status back to the + * client */ + + pa_tagstruct_put_sample_spec(reply, &ss); + pa_tagstruct_put_channel_map(reply, &map); + + pa_tagstruct_putu32(reply, s->sink_input->sink->index); + pa_tagstruct_puts(reply, s->sink_input->sink->name); + + pa_tagstruct_put_boolean(reply, s->sink_input->sink->state == PA_SINK_SUSPENDED); + } + + if (c->version >= 13) + pa_tagstruct_put_usec(reply, s->configured_sink_latency); + + if (c->version >= 21) { + /* Send back the format we negotiated */ + if (s->sink_input->format) + pa_tagstruct_put_format_info(reply, s->sink_input->format); + else { + pa_format_info *f = pa_format_info_new(); + pa_tagstruct_put_format_info(reply, f); + pa_format_info_free(f); + } + } + + pa_pstream_send_tagstruct(c->pstream, reply); + +finish: + if (p) + pa_proplist_free(p); + if (formats) + pa_idxset_free(formats, (pa_free_cb_t) pa_format_info_free); +} + +static void command_delete_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + uint32_t channel; + + pa_native_connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &channel) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + + switch (command) { + + case PA_COMMAND_DELETE_PLAYBACK_STREAM: { + playback_stream *s; + if (!(s = pa_idxset_get_by_index(c->output_streams, channel)) || !playback_stream_isinstance(s)) { + pa_pstream_send_error(c->pstream, tag, PA_ERR_NOENTITY); + return; + } + + playback_stream_unlink(s); + break; + } + + case PA_COMMAND_DELETE_RECORD_STREAM: { + record_stream *s; + if (!(s = pa_idxset_get_by_index(c->record_streams, channel))) { + pa_pstream_send_error(c->pstream, tag, PA_ERR_NOENTITY); + return; + } + + record_stream_unlink(s); + break; + } + + case PA_COMMAND_DELETE_UPLOAD_STREAM: { + upload_stream *s; + + if (!(s = pa_idxset_get_by_index(c->output_streams, channel)) || !upload_stream_isinstance(s)) { + pa_pstream_send_error(c->pstream, tag, PA_ERR_NOENTITY); + return; + } + + upload_stream_unlink(s); + break; + } + + default: + pa_assert_not_reached(); + } + + pa_pstream_send_simple_ack(c->pstream, tag); +} + +static void command_create_record_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + record_stream *s; + pa_buffer_attr attr; + uint32_t source_index; + const char *name = NULL, *source_name; + pa_sample_spec ss; + pa_channel_map map; + pa_tagstruct *reply; + pa_source *source = NULL; + pa_cvolume volume; + bool + corked = false, + no_remap = false, + no_remix = false, + fix_format = false, + fix_rate = false, + fix_channels = false, + no_move = false, + variable_rate = false, + muted = false, + adjust_latency = false, + peak_detect = false, + early_requests = false, + dont_inhibit_auto_suspend = false, + volume_set = false, + muted_set = false, + fail_on_suspend = false, + relative_volume = false, + passthrough = false; + + pa_source_output_flags_t flags = 0; + pa_proplist *p = NULL; + uint32_t direct_on_input_idx = PA_INVALID_INDEX; + pa_sink_input *direct_on_input = NULL; + int ret = PA_ERR_INVALID; + uint8_t n_formats = 0; + pa_format_info *format; + pa_idxset *formats = NULL; + uint32_t i; + + pa_native_connection_assert_ref(c); + pa_assert(t); + + memset(&attr, 0, sizeof(attr)); + + if ((c->version < 13 && (pa_tagstruct_gets(t, &name) < 0 || !name)) || + pa_tagstruct_get_sample_spec(t, &ss) < 0 || + pa_tagstruct_get_channel_map(t, &map) < 0 || + pa_tagstruct_getu32(t, &source_index) < 0 || + pa_tagstruct_gets(t, &source_name) < 0 || + pa_tagstruct_getu32(t, &attr.maxlength) < 0 || + pa_tagstruct_get_boolean(t, &corked) < 0 || + pa_tagstruct_getu32(t, &attr.fragsize) < 0) { + + protocol_error(c); + goto finish; + } + + CHECK_VALIDITY_GOTO(c->pstream, c->authorized, tag, PA_ERR_ACCESS, finish); + CHECK_VALIDITY_GOTO(c->pstream, !source_name || pa_namereg_is_valid_name_or_wildcard(source_name, PA_NAMEREG_SOURCE), tag, PA_ERR_INVALID, finish); + CHECK_VALIDITY_GOTO(c->pstream, source_index == PA_INVALID_INDEX || !source_name, tag, PA_ERR_INVALID, finish); + CHECK_VALIDITY_GOTO(c->pstream, !source_name || source_index == PA_INVALID_INDEX, tag, PA_ERR_INVALID, finish); + + p = pa_proplist_new(); + + if (name) + pa_proplist_sets(p, PA_PROP_MEDIA_NAME, name); + + if (c->version >= 12) { + /* Since 0.9.8 the user can ask for a couple of additional flags */ + + if (pa_tagstruct_get_boolean(t, &no_remap) < 0 || + pa_tagstruct_get_boolean(t, &no_remix) < 0 || + pa_tagstruct_get_boolean(t, &fix_format) < 0 || + pa_tagstruct_get_boolean(t, &fix_rate) < 0 || + pa_tagstruct_get_boolean(t, &fix_channels) < 0 || + pa_tagstruct_get_boolean(t, &no_move) < 0 || + pa_tagstruct_get_boolean(t, &variable_rate) < 0) { + + protocol_error(c); + goto finish; + } + } + + if (c->version >= 13) { + + if (pa_tagstruct_get_boolean(t, &peak_detect) < 0 || + pa_tagstruct_get_boolean(t, &adjust_latency) < 0 || + pa_tagstruct_get_proplist(t, p) < 0 || + pa_tagstruct_getu32(t, &direct_on_input_idx) < 0) { + + protocol_error(c); + goto finish; + } + } + + if (c->version >= 14) { + + if (pa_tagstruct_get_boolean(t, &early_requests) < 0) { + protocol_error(c); + goto finish; + } + } + + if (c->version >= 15) { + + if (pa_tagstruct_get_boolean(t, &dont_inhibit_auto_suspend) < 0 || + pa_tagstruct_get_boolean(t, &fail_on_suspend) < 0) { + + protocol_error(c); + goto finish; + } + } + + if (c->version >= 22) { + /* For newer client versions (with per-source-output volumes), we try + * to make the behaviour for playback and record streams the same. */ + volume_set = true; + + if (pa_tagstruct_getu8(t, &n_formats) < 0) { + protocol_error(c); + goto finish; + } + + if (n_formats) + formats = pa_idxset_new(NULL, NULL); + + for (i = 0; i < n_formats; i++) { + format = pa_format_info_new(); + if (pa_tagstruct_get_format_info(t, format) < 0) { + protocol_error(c); + goto finish; + } + pa_idxset_put(formats, format, NULL); + } + + if (pa_tagstruct_get_cvolume(t, &volume) < 0 || + pa_tagstruct_get_boolean(t, &muted) < 0 || + pa_tagstruct_get_boolean(t, &volume_set) < 0 || + pa_tagstruct_get_boolean(t, &muted_set) < 0 || + pa_tagstruct_get_boolean(t, &relative_volume) < 0 || + pa_tagstruct_get_boolean(t, &passthrough) < 0) { + + protocol_error(c); + goto finish; + } + + CHECK_VALIDITY_GOTO(c->pstream, pa_cvolume_valid(&volume), tag, PA_ERR_INVALID, finish); + } + + if (n_formats == 0) { + CHECK_VALIDITY_GOTO(c->pstream, pa_sample_spec_valid(&ss), tag, PA_ERR_INVALID, finish); + CHECK_VALIDITY_GOTO(c->pstream, map.channels == ss.channels, tag, PA_ERR_INVALID, finish); + CHECK_VALIDITY_GOTO(c->pstream, c->version < 22 || (volume.channels == ss.channels), tag, PA_ERR_INVALID, finish); + CHECK_VALIDITY_GOTO(c->pstream, pa_channel_map_valid(&map), tag, PA_ERR_INVALID, finish); + } else { + PA_IDXSET_FOREACH(format, formats, i) { + CHECK_VALIDITY_GOTO(c->pstream, pa_format_info_valid(format), tag, PA_ERR_INVALID, finish); + } + } + + if (!pa_tagstruct_eof(t)) { + protocol_error(c); + goto finish; + } + + if (source_index != PA_INVALID_INDEX) { + + if (!(source = pa_idxset_get_by_index(c->protocol->core->sources, source_index))) { + pa_pstream_send_error(c->pstream, tag, PA_ERR_NOENTITY); + goto finish; + } + + } else if (source_name) { + + if (!(source = pa_namereg_get(c->protocol->core, source_name, PA_NAMEREG_SOURCE))) { + pa_pstream_send_error(c->pstream, tag, PA_ERR_NOENTITY); + goto finish; + } + } + + if (direct_on_input_idx != PA_INVALID_INDEX) { + + if (!(direct_on_input = pa_idxset_get_by_index(c->protocol->core->sink_inputs, direct_on_input_idx))) { + pa_pstream_send_error(c->pstream, tag, PA_ERR_NOENTITY); + goto finish; + } + } + + flags = + (corked ? PA_SOURCE_OUTPUT_START_CORKED : 0) | + (no_remap ? PA_SOURCE_OUTPUT_NO_REMAP : 0) | + (no_remix ? PA_SOURCE_OUTPUT_NO_REMIX : 0) | + (fix_format ? PA_SOURCE_OUTPUT_FIX_FORMAT : 0) | + (fix_rate ? PA_SOURCE_OUTPUT_FIX_RATE : 0) | + (fix_channels ? PA_SOURCE_OUTPUT_FIX_CHANNELS : 0) | + (no_move ? PA_SOURCE_OUTPUT_DONT_MOVE : 0) | + (variable_rate ? PA_SOURCE_OUTPUT_VARIABLE_RATE : 0) | + (dont_inhibit_auto_suspend ? PA_SOURCE_OUTPUT_DONT_INHIBIT_AUTO_SUSPEND : 0) | + (fail_on_suspend ? PA_SOURCE_OUTPUT_NO_CREATE_ON_SUSPEND|PA_SOURCE_OUTPUT_KILL_ON_SUSPEND : 0) | + (passthrough ? PA_SOURCE_OUTPUT_PASSTHROUGH : 0); + + s = record_stream_new(c, source, &ss, &map, formats, &attr, volume_set ? &volume : NULL, muted, muted_set, flags, p, adjust_latency, early_requests, relative_volume, peak_detect, direct_on_input, &ret); + /* We no longer own the formats idxset */ + formats = NULL; + + CHECK_VALIDITY_GOTO(c->pstream, s, tag, ret, finish); + + reply = reply_new(tag); + pa_tagstruct_putu32(reply, s->index); + pa_assert(s->source_output); + pa_tagstruct_putu32(reply, s->source_output->index); + + if (c->version >= 9) { + /* Since 0.9 we support sending the buffer metrics back to the client */ + + pa_tagstruct_putu32(reply, (uint32_t) s->buffer_attr.maxlength); + pa_tagstruct_putu32(reply, (uint32_t) s->buffer_attr.fragsize); + } + + if (c->version >= 12) { + /* Since 0.9.8 we support sending the chosen sample + * spec/channel map/device/suspend status back to the + * client */ + + pa_tagstruct_put_sample_spec(reply, &ss); + pa_tagstruct_put_channel_map(reply, &map); + + pa_tagstruct_putu32(reply, s->source_output->source->index); + pa_tagstruct_puts(reply, s->source_output->source->name); + + pa_tagstruct_put_boolean(reply, s->source_output->source->state == PA_SOURCE_SUSPENDED); + } + + if (c->version >= 13) + pa_tagstruct_put_usec(reply, s->configured_source_latency); + + if (c->version >= 22) { + /* Send back the format we negotiated */ + if (s->source_output->format) + pa_tagstruct_put_format_info(reply, s->source_output->format); + else { + pa_format_info *f = pa_format_info_new(); + pa_tagstruct_put_format_info(reply, f); + pa_format_info_free(f); + } + } + + pa_pstream_send_tagstruct(c->pstream, reply); + +finish: + if (p) + pa_proplist_free(p); + if (formats) + pa_idxset_free(formats, (pa_free_cb_t) pa_format_info_free); +} + +static void command_exit(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + int ret; + + pa_native_connection_assert_ref(c); + pa_assert(t); + + if (!pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + ret = pa_core_exit(c->protocol->core, false, 0); + CHECK_VALIDITY(c->pstream, ret >= 0, tag, PA_ERR_ACCESS); + + pa_log_debug("Client %s asks us to terminate.", pa_strnull(pa_proplist_gets(c->client->proplist, PA_PROP_APPLICATION_PROCESS_BINARY))); + + pa_pstream_send_simple_ack(c->pstream, tag); /* nonsense */ +} + +static void setup_srbchannel(pa_native_connection *c, pa_mem_type_t shm_type) { + pa_srbchannel_template srbt; + pa_srbchannel *srb; + pa_memchunk mc; + pa_tagstruct *t; + int fdlist[2]; + +#ifndef HAVE_CREDS + pa_log_debug("Disabling srbchannel, reason: No fd passing support"); + return; +#endif + + if (!c->options->srbchannel) { + pa_log_debug("Disabling srbchannel, reason: Must be enabled by module parameter"); + return; + } + + if (c->version < 30) { + pa_log_debug("Disabling srbchannel, reason: Protocol too old"); + return; + } + + if (!pa_pstream_get_shm(c->pstream)) { + pa_log_debug("Disabling srbchannel, reason: No SHM support"); + return; + } + + if (c->rw_mempool) { + pa_log_debug("Ignoring srbchannel setup, reason: received COMMAND_AUTH " + "more than once"); + return; + } + + if (!(c->rw_mempool = pa_mempool_new(shm_type, c->protocol->core->shm_size, true))) { + pa_log_warn("Disabling srbchannel, reason: Failed to allocate shared " + "writable memory pool."); + return; + } + + if (shm_type == PA_MEM_TYPE_SHARED_MEMFD) { + const char *reason; + if (pa_pstream_register_memfd_mempool(c->pstream, c->rw_mempool, &reason)) { + pa_log_warn("Disabling srbchannel, reason: Failed to register memfd mempool: %s", reason); + goto fail; + } + } + pa_mempool_set_is_remote_writable(c->rw_mempool, true); + + srb = pa_srbchannel_new(c->protocol->core->mainloop, c->rw_mempool); + if (!srb) { + pa_log_debug("Failed to create srbchannel"); + goto fail; + } + pa_log_debug("Enabling srbchannel..."); + pa_srbchannel_export(srb, &srbt); + + /* Send enable command to client */ + t = pa_tagstruct_new(); + pa_tagstruct_putu32(t, PA_COMMAND_ENABLE_SRBCHANNEL); + pa_tagstruct_putu32(t, (size_t) srb); /* tag */ + fdlist[0] = srbt.readfd; + fdlist[1] = srbt.writefd; + pa_pstream_send_tagstruct_with_fds(c->pstream, t, 2, fdlist, false); + + /* Send ringbuffer memblock to client */ + mc.memblock = srbt.memblock; + mc.index = 0; + mc.length = pa_memblock_get_length(srbt.memblock); + pa_pstream_send_memblock(c->pstream, 0, 0, 0, &mc); + + c->srbpending = srb; + return; + +fail: + if (c->rw_mempool) { + pa_mempool_unref(c->rw_mempool); + c->rw_mempool = NULL; + } +} + +static void command_enable_srbchannel(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + + if (tag != (uint32_t) (size_t) c->srbpending) { + protocol_error(c); + return; + } + + pa_log_debug("Client enabled srbchannel."); + pa_pstream_set_srbchannel(c->pstream, c->srbpending); + c->srbpending = NULL; +} + +static void command_auth(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + const void*cookie; + bool memfd_on_remote = false, do_memfd = false; + pa_tagstruct *reply; + pa_mem_type_t shm_type; + bool shm_on_remote = false, do_shm; + + pa_native_connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &c->version) < 0 || + pa_tagstruct_get_arbitrary(t, &cookie, PA_NATIVE_COOKIE_LENGTH) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + /* Minimum supported version */ + if (c->version < 8) { + pa_pstream_send_error(c->pstream, tag, PA_ERR_VERSION); + return; + } + + /* Starting with protocol version 13 the MSB of the version tag + reflects if shm is available for this pa_native_connection or + not. */ + if ((c->version & PA_PROTOCOL_VERSION_MASK) >= 13) { + shm_on_remote = !!(c->version & PA_PROTOCOL_FLAG_SHM); + + /* Starting with protocol version 31, the second MSB of the version + * tag reflects whether memfd is supported on the other PA end. */ + if ((c->version & PA_PROTOCOL_VERSION_MASK) >= 31) + memfd_on_remote = !!(c->version & PA_PROTOCOL_FLAG_MEMFD); + + /* Reserve the two most-significant _bytes_ of the version tag + * for flags. */ + c->version &= PA_PROTOCOL_VERSION_MASK; + } + + pa_log_debug("Protocol version: remote %u, local %u", c->version, PA_PROTOCOL_VERSION); + + pa_proplist_setf(c->client->proplist, "native-protocol.version", "%u", c->version); + + if (!c->authorized) { + bool success = false; + +#ifdef HAVE_CREDS + const pa_creds *creds; + + if ((creds = pa_pdispatch_creds(pd))) { + if (creds->uid == getuid()) + success = true; + else if (c->options->auth_group) { + int r; + gid_t gid; + + if ((gid = pa_get_gid_of_group(c->options->auth_group)) == (gid_t) -1) + pa_log_warn("Failed to get GID of group '%s'", c->options->auth_group); + else if (gid == creds->gid) + success = true; + + if (!success) { + if ((r = pa_uid_in_group(creds->uid, c->options->auth_group)) < 0) + pa_log_warn("Failed to check group membership."); + else if (r > 0) + success = true; + } + } + + pa_log_info("Got credentials: uid=%lu gid=%lu success=%i", + (unsigned long) creds->uid, + (unsigned long) creds->gid, + (int) success); + } +#endif + + if (!success && c->options->auth_cookie) { + const uint8_t *ac; + + if ((ac = pa_auth_cookie_read(c->options->auth_cookie, PA_NATIVE_COOKIE_LENGTH))) + if (memcmp(ac, cookie, PA_NATIVE_COOKIE_LENGTH) == 0) + success = true; + } + + if (!success) { + pa_log_warn("Denied access to client with invalid authentication data."); + pa_pstream_send_error(c->pstream, tag, PA_ERR_ACCESS); + return; + } + + c->authorized = true; + if (c->auth_timeout_event) { + c->protocol->core->mainloop->time_free(c->auth_timeout_event); + c->auth_timeout_event = NULL; + } + } + + /* Enable shared memory and memfd support if possible */ + do_shm = + pa_mempool_is_shared(c->protocol->core->mempool) && + c->is_local; + + pa_log_debug("SHM possible: %s", pa_yes_no(do_shm)); + + if (do_shm) + if (c->version < 10 || (c->version >= 13 && !shm_on_remote)) + do_shm = false; + +#ifdef HAVE_CREDS + if (do_shm) { + /* Only enable SHM if both sides are owned by the same + * user. This is a security measure because otherwise data + * private to the user might leak. */ + + const pa_creds *creds; + if (!(creds = pa_pdispatch_creds(pd)) || getuid() != creds->uid) + do_shm = false; + } +#endif + + pa_log_debug("Negotiated SHM: %s", pa_yes_no(do_shm)); + pa_pstream_enable_shm(c->pstream, do_shm); + + /* Do not declare memfd support for 9.0 client libraries (protocol v31). + * + * Although they support memfd transport, such 9.0 clients has an iochannel + * bug that would break memfd audio if they're run in 32-bit mode over a + * 64-bit kernel. Thus influence them to use the POSIX shared memory model + * instead. Check commit 451d1d676237c81 for further details. */ + do_memfd = + c->version >= 32 && do_shm && pa_mempool_is_memfd_backed(c->protocol->core->mempool); + + shm_type = PA_MEM_TYPE_PRIVATE; + if (do_shm) { + if (do_memfd && memfd_on_remote) { + pa_pstream_enable_memfd(c->pstream); + shm_type = PA_MEM_TYPE_SHARED_MEMFD; + } else + shm_type = PA_MEM_TYPE_SHARED_POSIX; + + pa_log_debug("Memfd possible: %s", pa_yes_no(pa_memfd_is_locally_supported())); + pa_log_debug("Negotiated SHM type: %s", pa_mem_type_to_string(shm_type)); + } + + reply = reply_new(tag); + pa_tagstruct_putu32(reply, PA_PROTOCOL_VERSION | (do_shm ? 0x80000000 : 0) | + (do_memfd ? 0x40000000 : 0)); + +#ifdef HAVE_CREDS +{ + /* SHM support is only enabled after both sides made sure they are the same user. */ + + pa_creds ucred; + + ucred.uid = getuid(); + ucred.gid = getgid(); + + pa_pstream_send_tagstruct_with_creds(c->pstream, reply, &ucred); +} +#else + pa_pstream_send_tagstruct(c->pstream, reply); +#endif + + /* The client enables memfd transport on its pstream only after + * inspecting our version flags to see if we support memfds too. + * + * Thus register any pools after sending the server's version + * flags and _never_ before it. */ + if (shm_type == PA_MEM_TYPE_SHARED_MEMFD) { + const char *reason; + + if (pa_pstream_register_memfd_mempool(c->pstream, c->protocol->core->mempool, &reason)) + pa_log("Failed to register memfd mempool. Reason: %s", reason); + } + + setup_srbchannel(c, shm_type); +} + +static void command_register_memfd_shmid(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + + pa_native_connection_assert_ref(c); + pa_assert(t); + + if (pa_common_command_register_memfd_shmid(c->pstream, pd, c->version, command, t)) + protocol_error(c); +} + +static void command_set_client_name(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + const char *name = NULL; + pa_proplist *p; + pa_tagstruct *reply; + + pa_native_connection_assert_ref(c); + pa_assert(t); + + p = pa_proplist_new(); + + if ((c->version < 13 && pa_tagstruct_gets(t, &name) < 0) || + (c->version >= 13 && pa_tagstruct_get_proplist(t, p) < 0) || + !pa_tagstruct_eof(t)) { + + protocol_error(c); + pa_proplist_free(p); + return; + } + + if (name) + if (pa_proplist_sets(p, PA_PROP_APPLICATION_NAME, name) < 0) { + pa_pstream_send_error(c->pstream, tag, PA_ERR_INVALID); + pa_proplist_free(p); + return; + } + + pa_client_update_proplist(c->client, PA_UPDATE_REPLACE, p); + pa_proplist_free(p); + + reply = reply_new(tag); + + if (c->version >= 13) + pa_tagstruct_putu32(reply, c->client->index); + + pa_pstream_send_tagstruct(c->pstream, reply); +} + +static void command_lookup(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + const char *name; + uint32_t idx = PA_IDXSET_INVALID; + + pa_native_connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_gets(t, &name) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + CHECK_VALIDITY(c->pstream, name && pa_namereg_is_valid_name_or_wildcard(name, command == PA_COMMAND_LOOKUP_SINK ? PA_NAMEREG_SINK : PA_NAMEREG_SOURCE), tag, PA_ERR_INVALID); + + if (command == PA_COMMAND_LOOKUP_SINK) { + pa_sink *sink; + if ((sink = pa_namereg_get(c->protocol->core, name, PA_NAMEREG_SINK))) + idx = sink->index; + } else { + pa_source *source; + pa_assert(command == PA_COMMAND_LOOKUP_SOURCE); + if ((source = pa_namereg_get(c->protocol->core, name, PA_NAMEREG_SOURCE))) + idx = source->index; + } + + if (idx == PA_IDXSET_INVALID) + pa_pstream_send_error(c->pstream, tag, PA_ERR_NOENTITY); + else { + pa_tagstruct *reply; + reply = reply_new(tag); + pa_tagstruct_putu32(reply, idx); + pa_pstream_send_tagstruct(c->pstream, reply); + } +} + +static void command_drain_playback_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + uint32_t idx; + playback_stream *s; + + pa_native_connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &idx) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + s = pa_idxset_get_by_index(c->output_streams, idx); + CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY); + CHECK_VALIDITY(c->pstream, playback_stream_isinstance(s), tag, PA_ERR_NOENTITY); + + pa_asyncmsgq_post(s->sink_input->sink->asyncmsgq, PA_MSGOBJECT(s->sink_input), SINK_INPUT_MESSAGE_DRAIN, PA_UINT_TO_PTR(tag), 0, NULL, NULL); +} + +static void command_stat(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + pa_tagstruct *reply; + const pa_mempool_stat *stat; + + pa_native_connection_assert_ref(c); + pa_assert(t); + + if (!pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + + stat = pa_mempool_get_stat(c->protocol->core->mempool); + + reply = reply_new(tag); + pa_tagstruct_putu32(reply, (uint32_t) pa_atomic_load(&stat->n_allocated)); + pa_tagstruct_putu32(reply, (uint32_t) pa_atomic_load(&stat->allocated_size)); + pa_tagstruct_putu32(reply, (uint32_t) pa_atomic_load(&stat->n_accumulated)); + pa_tagstruct_putu32(reply, (uint32_t) pa_atomic_load(&stat->accumulated_size)); + pa_tagstruct_putu32(reply, (uint32_t) pa_scache_total_size(c->protocol->core)); + pa_pstream_send_tagstruct(c->pstream, reply); +} + +static void command_get_playback_latency(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + pa_tagstruct *reply; + playback_stream *s; + struct timeval tv, now; + uint32_t idx; + + pa_native_connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &idx) < 0 || + pa_tagstruct_get_timeval(t, &tv) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + s = pa_idxset_get_by_index(c->output_streams, idx); + CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY); + CHECK_VALIDITY(c->pstream, playback_stream_isinstance(s), tag, PA_ERR_NOENTITY); + + /* Get an atomic snapshot of all timing parameters */ + pa_assert_se(pa_asyncmsgq_send(s->sink_input->sink->asyncmsgq, PA_MSGOBJECT(s->sink_input), SINK_INPUT_MESSAGE_UPDATE_LATENCY, s, 0, NULL) == 0); + + reply = reply_new(tag); + pa_tagstruct_put_usec(reply, + s->current_sink_latency + + pa_bytes_to_usec(s->render_memblockq_length, &s->sink_input->sink->sample_spec)); + pa_tagstruct_put_usec(reply, 0); + pa_tagstruct_put_boolean(reply, + s->playing_for > 0 && + s->sink_input->sink->state == PA_SINK_RUNNING && + s->sink_input->state == PA_SINK_INPUT_RUNNING); + pa_tagstruct_put_timeval(reply, &tv); + pa_tagstruct_put_timeval(reply, pa_gettimeofday(&now)); + pa_tagstruct_puts64(reply, s->write_index); + pa_tagstruct_puts64(reply, s->read_index); + + if (c->version >= 13) { + pa_tagstruct_putu64(reply, s->underrun_for); + pa_tagstruct_putu64(reply, s->playing_for); + } + + pa_pstream_send_tagstruct(c->pstream, reply); +} + +static void command_get_record_latency(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + pa_tagstruct *reply; + record_stream *s; + struct timeval tv, now; + uint32_t idx; + + pa_native_connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &idx) < 0 || + pa_tagstruct_get_timeval(t, &tv) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + s = pa_idxset_get_by_index(c->record_streams, idx); + CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY); + + /* Get an atomic snapshot of all timing parameters */ + pa_assert_se(pa_asyncmsgq_send(s->source_output->source->asyncmsgq, PA_MSGOBJECT(s->source_output), SOURCE_OUTPUT_MESSAGE_UPDATE_LATENCY, s, 0, NULL) == 0); + + reply = reply_new(tag); + pa_tagstruct_put_usec(reply, s->current_monitor_latency); + pa_tagstruct_put_usec(reply, + s->current_source_latency + + pa_bytes_to_usec(s->on_the_fly_snapshot, &s->source_output->sample_spec)); + pa_tagstruct_put_boolean(reply, + s->source_output->source->state == PA_SOURCE_RUNNING && + s->source_output->state == PA_SOURCE_OUTPUT_RUNNING); + pa_tagstruct_put_timeval(reply, &tv); + pa_tagstruct_put_timeval(reply, pa_gettimeofday(&now)); + pa_tagstruct_puts64(reply, pa_memblockq_get_write_index(s->memblockq)); + pa_tagstruct_puts64(reply, pa_memblockq_get_read_index(s->memblockq)); + pa_pstream_send_tagstruct(c->pstream, reply); +} + +static void command_create_upload_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + upload_stream *s; + uint32_t length; + const char *name = NULL; + pa_sample_spec ss; + pa_channel_map map; + pa_tagstruct *reply; + pa_proplist *p; + + pa_native_connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_gets(t, &name) < 0 || + pa_tagstruct_get_sample_spec(t, &ss) < 0 || + pa_tagstruct_get_channel_map(t, &map) < 0 || + pa_tagstruct_getu32(t, &length) < 0) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + CHECK_VALIDITY(c->pstream, pa_sample_spec_valid(&ss), tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, pa_channel_map_valid(&map), tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, map.channels == ss.channels, tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, (length % pa_frame_size(&ss)) == 0 && length > 0, tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, length <= PA_SCACHE_ENTRY_SIZE_MAX, tag, PA_ERR_TOOLARGE); + + p = pa_proplist_new(); + + if ((c->version >= 13 && pa_tagstruct_get_proplist(t, p) < 0) || + !pa_tagstruct_eof(t)) { + + protocol_error(c); + pa_proplist_free(p); + return; + } + + if (c->version < 13) + pa_proplist_sets(p, PA_PROP_MEDIA_NAME, name); + else if (!name) + if (!(name = pa_proplist_gets(p, PA_PROP_EVENT_ID))) + name = pa_proplist_gets(p, PA_PROP_MEDIA_NAME); + + if (!name || !pa_namereg_is_valid_name(name)) { + pa_proplist_free(p); + CHECK_VALIDITY(c->pstream, false, tag, PA_ERR_INVALID); + } + + s = upload_stream_new(c, &ss, &map, name, length, p); + pa_proplist_free(p); + + CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_INVALID); + + reply = reply_new(tag); + pa_tagstruct_putu32(reply, s->index); + pa_tagstruct_putu32(reply, length); + pa_pstream_send_tagstruct(c->pstream, reply); +} + +static void command_finish_upload_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + uint32_t channel; + upload_stream *s; + uint32_t idx; + + pa_native_connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &channel) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + + s = pa_idxset_get_by_index(c->output_streams, channel); + CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY); + CHECK_VALIDITY(c->pstream, upload_stream_isinstance(s), tag, PA_ERR_NOENTITY); + + if (!s->memchunk.memblock) + pa_pstream_send_error(c->pstream, tag, PA_ERR_TOOLARGE); + else if (pa_scache_add_item(c->protocol->core, s->name, &s->sample_spec, &s->channel_map, &s->memchunk, s->proplist, &idx) < 0) + pa_pstream_send_error(c->pstream, tag, PA_ERR_INTERNAL); + else + pa_pstream_send_simple_ack(c->pstream, tag); + + upload_stream_unlink(s); +} + +static void command_play_sample(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + uint32_t sink_index; + pa_volume_t volume; + pa_sink *sink; + const char *name, *sink_name; + uint32_t idx; + pa_proplist *p; + pa_tagstruct *reply; + + pa_native_connection_assert_ref(c); + pa_assert(t); + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + + if (pa_tagstruct_getu32(t, &sink_index) < 0 || + pa_tagstruct_gets(t, &sink_name) < 0 || + pa_tagstruct_getu32(t, &volume) < 0 || + pa_tagstruct_gets(t, &name) < 0) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, !sink_name || pa_namereg_is_valid_name_or_wildcard(sink_name, PA_NAMEREG_SINK), tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, sink_index == PA_INVALID_INDEX || !sink_name, tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, !sink_name || sink_index == PA_INVALID_INDEX, tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, name && pa_namereg_is_valid_name(name), tag, PA_ERR_INVALID); + + if (sink_index != PA_INVALID_INDEX) + sink = pa_idxset_get_by_index(c->protocol->core->sinks, sink_index); + else + sink = pa_namereg_get(c->protocol->core, sink_name, PA_NAMEREG_SINK); + + CHECK_VALIDITY(c->pstream, sink, tag, PA_ERR_NOENTITY); + + p = pa_proplist_new(); + + if ((c->version >= 13 && pa_tagstruct_get_proplist(t, p) < 0) || + !pa_tagstruct_eof(t)) { + protocol_error(c); + pa_proplist_free(p); + return; + } + + pa_proplist_update(p, PA_UPDATE_MERGE, c->client->proplist); + + if (pa_scache_play_item(c->protocol->core, name, sink, volume, p, &idx) < 0) { + pa_pstream_send_error(c->pstream, tag, PA_ERR_NOENTITY); + pa_proplist_free(p); + return; + } + + pa_proplist_free(p); + + reply = reply_new(tag); + + if (c->version >= 13) + pa_tagstruct_putu32(reply, idx); + + pa_pstream_send_tagstruct(c->pstream, reply); +} + +static void command_remove_sample(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + const char *name; + + pa_native_connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_gets(t, &name) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + CHECK_VALIDITY(c->pstream, name && pa_namereg_is_valid_name(name), tag, PA_ERR_INVALID); + + if (pa_scache_remove_item(c->protocol->core, name) < 0) { + pa_pstream_send_error(c->pstream, tag, PA_ERR_NOENTITY); + return; + } + + pa_pstream_send_simple_ack(c->pstream, tag); +} + +static void fixup_sample_spec(pa_native_connection *c, pa_sample_spec *fixed, const pa_sample_spec *original) { + pa_assert(c); + pa_assert(fixed); + pa_assert(original); + + *fixed = *original; + + if (c->version < 12) { + /* Before protocol version 12 we didn't support S32 samples, + * so we need to lie about this to the client */ + + if (fixed->format == PA_SAMPLE_S32LE) + fixed->format = PA_SAMPLE_FLOAT32LE; + if (fixed->format == PA_SAMPLE_S32BE) + fixed->format = PA_SAMPLE_FLOAT32BE; + } + + if (c->version < 15) { + if (fixed->format == PA_SAMPLE_S24LE || fixed->format == PA_SAMPLE_S24_32LE) + fixed->format = PA_SAMPLE_FLOAT32LE; + if (fixed->format == PA_SAMPLE_S24BE || fixed->format == PA_SAMPLE_S24_32BE) + fixed->format = PA_SAMPLE_FLOAT32BE; + } +} + +static void sink_fill_tagstruct(pa_native_connection *c, pa_tagstruct *t, pa_sink *sink) { + pa_sample_spec fixed_ss; + + pa_assert(t); + pa_sink_assert_ref(sink); + + fixup_sample_spec(c, &fixed_ss, &sink->sample_spec); + + pa_tagstruct_put( + t, + PA_TAG_U32, sink->index, + PA_TAG_STRING, sink->name, + PA_TAG_STRING, pa_strnull(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_DESCRIPTION)), + PA_TAG_SAMPLE_SPEC, &fixed_ss, + PA_TAG_CHANNEL_MAP, &sink->channel_map, + PA_TAG_U32, sink->module ? sink->module->index : PA_INVALID_INDEX, + PA_TAG_CVOLUME, pa_sink_get_volume(sink, false), + PA_TAG_BOOLEAN, pa_sink_get_mute(sink, false), + PA_TAG_U32, sink->monitor_source ? sink->monitor_source->index : PA_INVALID_INDEX, + PA_TAG_STRING, sink->monitor_source ? sink->monitor_source->name : NULL, + PA_TAG_USEC, pa_sink_get_latency(sink), + PA_TAG_STRING, sink->driver, + PA_TAG_U32, sink->flags & PA_SINK_CLIENT_FLAGS_MASK, + PA_TAG_INVALID); + + if (c->version >= 13) { + pa_tagstruct_put_proplist(t, sink->proplist); + pa_tagstruct_put_usec(t, pa_sink_get_requested_latency(sink)); + } + + if (c->version >= 15) { + pa_tagstruct_put_volume(t, sink->base_volume); + if (PA_UNLIKELY(sink->state == PA_SINK_INVALID_STATE)) + pa_log_error("Internal sink state is invalid."); + pa_tagstruct_putu32(t, sink->state); + pa_tagstruct_putu32(t, sink->n_volume_steps); + pa_tagstruct_putu32(t, sink->card ? sink->card->index : PA_INVALID_INDEX); + } + + if (c->version >= 16) { + void *state; + pa_device_port *p; + + pa_tagstruct_putu32(t, pa_hashmap_size(sink->ports)); + + PA_HASHMAP_FOREACH(p, sink->ports, state) { + pa_tagstruct_puts(t, p->name); + pa_tagstruct_puts(t, p->description); + pa_tagstruct_putu32(t, p->priority); + if (c->version >= 24) { + pa_tagstruct_putu32(t, p->available); + if (c->version >= 34) { + pa_tagstruct_puts(t, p->availability_group); + pa_tagstruct_putu32(t, p->type); + } + } + } + + pa_tagstruct_puts(t, sink->active_port ? sink->active_port->name : NULL); + } + + if (c->version >= 21) { + uint32_t i; + pa_format_info *f; + pa_idxset *formats = pa_sink_get_formats(sink); + + pa_tagstruct_putu8(t, (uint8_t) pa_idxset_size(formats)); + PA_IDXSET_FOREACH(f, formats, i) { + pa_tagstruct_put_format_info(t, f); + } + + pa_idxset_free(formats, (pa_free_cb_t) pa_format_info_free); + } +} + +static void source_fill_tagstruct(pa_native_connection *c, pa_tagstruct *t, pa_source *source) { + pa_sample_spec fixed_ss; + + pa_assert(t); + pa_source_assert_ref(source); + + fixup_sample_spec(c, &fixed_ss, &source->sample_spec); + + pa_tagstruct_put( + t, + PA_TAG_U32, source->index, + PA_TAG_STRING, source->name, + PA_TAG_STRING, pa_strnull(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_DESCRIPTION)), + PA_TAG_SAMPLE_SPEC, &fixed_ss, + PA_TAG_CHANNEL_MAP, &source->channel_map, + PA_TAG_U32, source->module ? source->module->index : PA_INVALID_INDEX, + PA_TAG_CVOLUME, pa_source_get_volume(source, false), + PA_TAG_BOOLEAN, pa_source_get_mute(source, false), + PA_TAG_U32, source->monitor_of ? source->monitor_of->index : PA_INVALID_INDEX, + PA_TAG_STRING, source->monitor_of ? source->monitor_of->name : NULL, + PA_TAG_USEC, pa_source_get_latency(source), + PA_TAG_STRING, source->driver, + PA_TAG_U32, source->flags & PA_SOURCE_CLIENT_FLAGS_MASK, + PA_TAG_INVALID); + + if (c->version >= 13) { + pa_tagstruct_put_proplist(t, source->proplist); + pa_tagstruct_put_usec(t, pa_source_get_requested_latency(source)); + } + + if (c->version >= 15) { + pa_tagstruct_put_volume(t, source->base_volume); + if (PA_UNLIKELY(source->state == PA_SOURCE_INVALID_STATE)) + pa_log_error("Internal source state is invalid."); + pa_tagstruct_putu32(t, source->state); + pa_tagstruct_putu32(t, source->n_volume_steps); + pa_tagstruct_putu32(t, source->card ? source->card->index : PA_INVALID_INDEX); + } + + if (c->version >= 16) { + void *state; + pa_device_port *p; + + pa_tagstruct_putu32(t, pa_hashmap_size(source->ports)); + + PA_HASHMAP_FOREACH(p, source->ports, state) { + pa_tagstruct_puts(t, p->name); + pa_tagstruct_puts(t, p->description); + pa_tagstruct_putu32(t, p->priority); + if (c->version >= 24) { + pa_tagstruct_putu32(t, p->available); + if (c->version >= 34) { + pa_tagstruct_puts(t, p->availability_group); + pa_tagstruct_putu32(t, p->type); + } + } + } + + pa_tagstruct_puts(t, source->active_port ? source->active_port->name : NULL); + } + + if (c->version >= 22) { + uint32_t i; + pa_format_info *f; + pa_idxset *formats = pa_source_get_formats(source); + + pa_tagstruct_putu8(t, (uint8_t) pa_idxset_size(formats)); + PA_IDXSET_FOREACH(f, formats, i) { + pa_tagstruct_put_format_info(t, f); + } + + pa_idxset_free(formats, (pa_free_cb_t) pa_format_info_free); + } +} + +static void client_fill_tagstruct(pa_native_connection *c, pa_tagstruct *t, pa_client *client) { + pa_assert(t); + pa_assert(client); + + pa_tagstruct_putu32(t, client->index); + pa_tagstruct_puts(t, pa_strnull(pa_proplist_gets(client->proplist, PA_PROP_APPLICATION_NAME))); + pa_tagstruct_putu32(t, client->module ? client->module->index : PA_INVALID_INDEX); + pa_tagstruct_puts(t, client->driver); + + if (c->version >= 13) + pa_tagstruct_put_proplist(t, client->proplist); +} + +static void card_fill_tagstruct(pa_native_connection *c, pa_tagstruct *t, pa_card *card) { + void *state = NULL; + pa_card_profile *p; + pa_device_port *port; + + pa_assert(t); + pa_assert(card); + + pa_tagstruct_putu32(t, card->index); + pa_tagstruct_puts(t, card->name); + pa_tagstruct_putu32(t, card->module ? card->module->index : PA_INVALID_INDEX); + pa_tagstruct_puts(t, card->driver); + + pa_tagstruct_putu32(t, pa_hashmap_size(card->profiles)); + + PA_HASHMAP_FOREACH(p, card->profiles, state) { + pa_tagstruct_puts(t, p->name); + pa_tagstruct_puts(t, p->description); + pa_tagstruct_putu32(t, p->n_sinks); + pa_tagstruct_putu32(t, p->n_sources); + pa_tagstruct_putu32(t, p->priority); + + if (c->version >= 29) + pa_tagstruct_putu32(t, (p->available != PA_AVAILABLE_NO)); + } + + pa_tagstruct_puts(t, card->active_profile->name); + pa_tagstruct_put_proplist(t, card->proplist); + + if (c->version < 26) + return; + + pa_tagstruct_putu32(t, pa_hashmap_size(card->ports)); + + PA_HASHMAP_FOREACH(port, card->ports, state) { + void *state2; + + pa_tagstruct_puts(t, port->name); + pa_tagstruct_puts(t, port->description); + pa_tagstruct_putu32(t, port->priority); + pa_tagstruct_putu32(t, port->available); + pa_tagstruct_putu8(t, port->direction); + pa_tagstruct_put_proplist(t, port->proplist); + + pa_tagstruct_putu32(t, pa_hashmap_size(port->profiles)); + + PA_HASHMAP_FOREACH(p, port->profiles, state2) + pa_tagstruct_puts(t, p->name); + + if (c->version >= 27) { + pa_tagstruct_puts64(t, port->latency_offset); + if (c->version >= 34) { + pa_tagstruct_puts(t, port->availability_group); + pa_tagstruct_putu32(t, port->type); + } + } + } +} + +static void module_fill_tagstruct(pa_native_connection *c, pa_tagstruct *t, pa_module *module) { + pa_assert(t); + pa_assert(module); + + pa_tagstruct_putu32(t, module->index); + pa_tagstruct_puts(t, module->name); + pa_tagstruct_puts(t, module->argument); + pa_tagstruct_putu32(t, (uint32_t) pa_module_get_n_used(module)); + + if (c->version < 15) + pa_tagstruct_put_boolean(t, false); /* autoload is obsolete */ + + if (c->version >= 15) + pa_tagstruct_put_proplist(t, module->proplist); +} + +static void sink_input_fill_tagstruct(pa_native_connection *c, pa_tagstruct *t, pa_sink_input *s) { + pa_sample_spec fixed_ss; + pa_usec_t sink_latency; + pa_cvolume v; + bool has_volume = false; + + pa_assert(t); + pa_sink_input_assert_ref(s); + + fixup_sample_spec(c, &fixed_ss, &s->sample_spec); + + has_volume = pa_sink_input_is_volume_readable(s); + if (has_volume) + pa_sink_input_get_volume(s, &v, true); + else + pa_cvolume_reset(&v, fixed_ss.channels); + + pa_tagstruct_putu32(t, s->index); + pa_tagstruct_puts(t, pa_strnull(pa_proplist_gets(s->proplist, PA_PROP_MEDIA_NAME))); + pa_tagstruct_putu32(t, s->module ? s->module->index : PA_INVALID_INDEX); + pa_tagstruct_putu32(t, s->client ? s->client->index : PA_INVALID_INDEX); + pa_tagstruct_putu32(t, s->sink->index); + pa_tagstruct_put_sample_spec(t, &fixed_ss); + pa_tagstruct_put_channel_map(t, &s->channel_map); + pa_tagstruct_put_cvolume(t, &v); + pa_tagstruct_put_usec(t, pa_sink_input_get_latency(s, &sink_latency)); + pa_tagstruct_put_usec(t, sink_latency); + pa_tagstruct_puts(t, pa_resample_method_to_string(pa_sink_input_get_resample_method(s))); + pa_tagstruct_puts(t, s->driver); + if (c->version >= 11) + pa_tagstruct_put_boolean(t, s->muted); + if (c->version >= 13) + pa_tagstruct_put_proplist(t, s->proplist); + if (c->version >= 19) + pa_tagstruct_put_boolean(t, s->state == PA_SINK_INPUT_CORKED); + if (c->version >= 20) { + pa_tagstruct_put_boolean(t, has_volume); + pa_tagstruct_put_boolean(t, s->volume_writable); + } + if (c->version >= 21) + pa_tagstruct_put_format_info(t, s->format); +} + +static void source_output_fill_tagstruct(pa_native_connection *c, pa_tagstruct *t, pa_source_output *s) { + pa_sample_spec fixed_ss; + pa_usec_t source_latency; + pa_cvolume v; + bool has_volume = false; + + pa_assert(t); + pa_source_output_assert_ref(s); + + fixup_sample_spec(c, &fixed_ss, &s->sample_spec); + + has_volume = pa_source_output_is_volume_readable(s); + if (has_volume) + pa_source_output_get_volume(s, &v, true); + else + pa_cvolume_reset(&v, fixed_ss.channels); + + pa_tagstruct_putu32(t, s->index); + pa_tagstruct_puts(t, pa_strnull(pa_proplist_gets(s->proplist, PA_PROP_MEDIA_NAME))); + pa_tagstruct_putu32(t, s->module ? s->module->index : PA_INVALID_INDEX); + pa_tagstruct_putu32(t, s->client ? s->client->index : PA_INVALID_INDEX); + pa_tagstruct_putu32(t, s->source->index); + pa_tagstruct_put_sample_spec(t, &fixed_ss); + pa_tagstruct_put_channel_map(t, &s->channel_map); + pa_tagstruct_put_usec(t, pa_source_output_get_latency(s, &source_latency)); + pa_tagstruct_put_usec(t, source_latency); + pa_tagstruct_puts(t, pa_resample_method_to_string(pa_source_output_get_resample_method(s))); + pa_tagstruct_puts(t, s->driver); + if (c->version >= 13) + pa_tagstruct_put_proplist(t, s->proplist); + if (c->version >= 19) + pa_tagstruct_put_boolean(t, s->state == PA_SOURCE_OUTPUT_CORKED); + if (c->version >= 22) { + pa_tagstruct_put_cvolume(t, &v); + pa_tagstruct_put_boolean(t, s->muted); + pa_tagstruct_put_boolean(t, has_volume); + pa_tagstruct_put_boolean(t, s->volume_writable); + pa_tagstruct_put_format_info(t, s->format); + } +} + +static void scache_fill_tagstruct(pa_native_connection *c, pa_tagstruct *t, pa_scache_entry *e) { + pa_sample_spec fixed_ss; + pa_cvolume v; + + pa_assert(t); + pa_assert(e); + + if (e->memchunk.memblock) + fixup_sample_spec(c, &fixed_ss, &e->sample_spec); + else + memset(&fixed_ss, 0, sizeof(fixed_ss)); + + pa_tagstruct_putu32(t, e->index); + pa_tagstruct_puts(t, e->name); + + if (e->volume_is_set) + v = e->volume; + else + pa_cvolume_init(&v); + + pa_tagstruct_put_cvolume(t, &v); + pa_tagstruct_put_usec(t, e->memchunk.memblock ? pa_bytes_to_usec(e->memchunk.length, &e->sample_spec) : 0); + pa_tagstruct_put_sample_spec(t, &fixed_ss); + pa_tagstruct_put_channel_map(t, &e->channel_map); + pa_tagstruct_putu32(t, (uint32_t) e->memchunk.length); + pa_tagstruct_put_boolean(t, e->lazy); + pa_tagstruct_puts(t, e->filename); + + if (c->version >= 13) + pa_tagstruct_put_proplist(t, e->proplist); +} + +static void command_get_info(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + uint32_t idx; + pa_sink *sink = NULL; + pa_source *source = NULL; + pa_client *client = NULL; + pa_card *card = NULL; + pa_module *module = NULL; + pa_sink_input *si = NULL; + pa_source_output *so = NULL; + pa_scache_entry *sce = NULL; + const char *name = NULL; + pa_tagstruct *reply; + + pa_native_connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &idx) < 0 || + (command != PA_COMMAND_GET_CLIENT_INFO && + command != PA_COMMAND_GET_MODULE_INFO && + command != PA_COMMAND_GET_SINK_INPUT_INFO && + command != PA_COMMAND_GET_SOURCE_OUTPUT_INFO && + pa_tagstruct_gets(t, &name) < 0) || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + CHECK_VALIDITY(c->pstream, !name || + (command == PA_COMMAND_GET_SINK_INFO && + pa_namereg_is_valid_name_or_wildcard(name, PA_NAMEREG_SINK)) || + (command == PA_COMMAND_GET_SOURCE_INFO && + pa_namereg_is_valid_name_or_wildcard(name, PA_NAMEREG_SOURCE)) || + pa_namereg_is_valid_name(name), tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, command == PA_COMMAND_GET_SINK_INFO || + command == PA_COMMAND_GET_SOURCE_INFO || + (idx != PA_INVALID_INDEX || name), tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, idx == PA_INVALID_INDEX || !name, tag, PA_ERR_INVALID); + + if (command == PA_COMMAND_GET_SINK_INFO) { + if (idx != PA_INVALID_INDEX) + sink = pa_idxset_get_by_index(c->protocol->core->sinks, idx); + else + sink = pa_namereg_get(c->protocol->core, name, PA_NAMEREG_SINK); + } else if (command == PA_COMMAND_GET_SOURCE_INFO) { + if (idx != PA_INVALID_INDEX) + source = pa_idxset_get_by_index(c->protocol->core->sources, idx); + else + source = pa_namereg_get(c->protocol->core, name, PA_NAMEREG_SOURCE); + } else if (command == PA_COMMAND_GET_CARD_INFO) { + if (idx != PA_INVALID_INDEX) + card = pa_idxset_get_by_index(c->protocol->core->cards, idx); + else + card = pa_namereg_get(c->protocol->core, name, PA_NAMEREG_CARD); + } else if (command == PA_COMMAND_GET_CLIENT_INFO) + client = pa_idxset_get_by_index(c->protocol->core->clients, idx); + else if (command == PA_COMMAND_GET_MODULE_INFO) + module = pa_idxset_get_by_index(c->protocol->core->modules, idx); + else if (command == PA_COMMAND_GET_SINK_INPUT_INFO) + si = pa_idxset_get_by_index(c->protocol->core->sink_inputs, idx); + else if (command == PA_COMMAND_GET_SOURCE_OUTPUT_INFO) + so = pa_idxset_get_by_index(c->protocol->core->source_outputs, idx); + else { + pa_assert(command == PA_COMMAND_GET_SAMPLE_INFO); + if (idx != PA_INVALID_INDEX) + sce = pa_idxset_get_by_index(c->protocol->core->scache, idx); + else + sce = pa_namereg_get(c->protocol->core, name, PA_NAMEREG_SAMPLE); + } + + if (!sink && !source && !client && !card && !module && !si && !so && !sce) { + pa_pstream_send_error(c->pstream, tag, PA_ERR_NOENTITY); + return; + } + + reply = reply_new(tag); + if (sink) + sink_fill_tagstruct(c, reply, sink); + else if (source) + source_fill_tagstruct(c, reply, source); + else if (client) + client_fill_tagstruct(c, reply, client); + else if (card) + card_fill_tagstruct(c, reply, card); + else if (module) + module_fill_tagstruct(c, reply, module); + else if (si) + sink_input_fill_tagstruct(c, reply, si); + else if (so) + source_output_fill_tagstruct(c, reply, so); + else + scache_fill_tagstruct(c, reply, sce); + pa_pstream_send_tagstruct(c->pstream, reply); +} + +static void command_get_info_list(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + pa_idxset *i; + uint32_t idx; + void *p; + pa_tagstruct *reply; + + pa_native_connection_assert_ref(c); + pa_assert(t); + + if (!pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + + reply = reply_new(tag); + + if (command == PA_COMMAND_GET_SINK_INFO_LIST) + i = c->protocol->core->sinks; + else if (command == PA_COMMAND_GET_SOURCE_INFO_LIST) + i = c->protocol->core->sources; + else if (command == PA_COMMAND_GET_CLIENT_INFO_LIST) + i = c->protocol->core->clients; + else if (command == PA_COMMAND_GET_CARD_INFO_LIST) + i = c->protocol->core->cards; + else if (command == PA_COMMAND_GET_MODULE_INFO_LIST) + i = c->protocol->core->modules; + else if (command == PA_COMMAND_GET_SINK_INPUT_INFO_LIST) + i = c->protocol->core->sink_inputs; + else if (command == PA_COMMAND_GET_SOURCE_OUTPUT_INFO_LIST) + i = c->protocol->core->source_outputs; + else { + pa_assert(command == PA_COMMAND_GET_SAMPLE_INFO_LIST); + i = c->protocol->core->scache; + } + + if (i) { + PA_IDXSET_FOREACH(p, i, idx) { + if (command == PA_COMMAND_GET_SINK_INFO_LIST) + sink_fill_tagstruct(c, reply, p); + else if (command == PA_COMMAND_GET_SOURCE_INFO_LIST) + source_fill_tagstruct(c, reply, p); + else if (command == PA_COMMAND_GET_CLIENT_INFO_LIST) + client_fill_tagstruct(c, reply, p); + else if (command == PA_COMMAND_GET_CARD_INFO_LIST) + card_fill_tagstruct(c, reply, p); + else if (command == PA_COMMAND_GET_MODULE_INFO_LIST) + module_fill_tagstruct(c, reply, p); + else if (command == PA_COMMAND_GET_SINK_INPUT_INFO_LIST) + sink_input_fill_tagstruct(c, reply, p); + else if (command == PA_COMMAND_GET_SOURCE_OUTPUT_INFO_LIST) + source_output_fill_tagstruct(c, reply, p); + else { + pa_assert(command == PA_COMMAND_GET_SAMPLE_INFO_LIST); + scache_fill_tagstruct(c, reply, p); + } + } + } + + pa_pstream_send_tagstruct(c->pstream, reply); +} + +static void command_get_server_info(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + pa_tagstruct *reply; + pa_sample_spec fixed_ss; + char *h, *u; + pa_core *core; + + pa_native_connection_assert_ref(c); + pa_assert(t); + + if (!pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + + reply = reply_new(tag); + pa_tagstruct_puts(reply, PACKAGE_NAME); + pa_tagstruct_puts(reply, PACKAGE_VERSION); + + u = pa_get_user_name_malloc(); + pa_tagstruct_puts(reply, u); + pa_xfree(u); + + h = pa_get_host_name_malloc(); + pa_tagstruct_puts(reply, h); + pa_xfree(h); + + core = c->protocol->core; + + fixup_sample_spec(c, &fixed_ss, &core->default_sample_spec); + pa_tagstruct_put_sample_spec(reply, &fixed_ss); + + pa_tagstruct_puts(reply, core->default_sink ? core->default_sink->name : NULL); + pa_tagstruct_puts(reply, core->default_source ? core->default_source->name : NULL); + + pa_tagstruct_putu32(reply, c->protocol->core->cookie); + + if (c->version >= 15) + pa_tagstruct_put_channel_map(reply, &core->default_channel_map); + + pa_pstream_send_tagstruct(c->pstream, reply); +} + +static void subscription_cb(pa_core *core, pa_subscription_event_type_t e, uint32_t idx, void *userdata) { + pa_tagstruct *t; + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + + pa_native_connection_assert_ref(c); + + t = pa_tagstruct_new(); + pa_tagstruct_putu32(t, PA_COMMAND_SUBSCRIBE_EVENT); + pa_tagstruct_putu32(t, (uint32_t) -1); + pa_tagstruct_putu32(t, e); + pa_tagstruct_putu32(t, idx); + pa_pstream_send_tagstruct(c->pstream, t); +} + +static void command_subscribe(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + pa_subscription_mask_t m; + + pa_native_connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &m) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + CHECK_VALIDITY(c->pstream, (m & ~PA_SUBSCRIPTION_MASK_ALL) == 0, tag, PA_ERR_INVALID); + + if (c->subscription) + pa_subscription_free(c->subscription); + + if (m != 0) { + c->subscription = pa_subscription_new(c->protocol->core, m, subscription_cb, c); + pa_assert(c->subscription); + } else + c->subscription = NULL; + + pa_pstream_send_simple_ack(c->pstream, tag); +} + +static void command_set_volume( + pa_pdispatch *pd, + uint32_t command, + uint32_t tag, + pa_tagstruct *t, + void *userdata) { + + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + uint32_t idx; + pa_cvolume volume; + pa_sink *sink = NULL; + pa_source *source = NULL; + pa_sink_input *si = NULL; + pa_source_output *so = NULL; + const char *name = NULL; + const char *client_name; + + pa_native_connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &idx) < 0 || + (command == PA_COMMAND_SET_SINK_VOLUME && pa_tagstruct_gets(t, &name) < 0) || + (command == PA_COMMAND_SET_SOURCE_VOLUME && pa_tagstruct_gets(t, &name) < 0) || + pa_tagstruct_get_cvolume(t, &volume) || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + CHECK_VALIDITY(c->pstream, !name || pa_namereg_is_valid_name_or_wildcard(name, command == PA_COMMAND_SET_SINK_VOLUME ? PA_NAMEREG_SINK : PA_NAMEREG_SOURCE), tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, (idx != PA_INVALID_INDEX) ^ (name != NULL), tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, pa_cvolume_valid(&volume), tag, PA_ERR_INVALID); + + switch (command) { + + case PA_COMMAND_SET_SINK_VOLUME: + if (idx != PA_INVALID_INDEX) + sink = pa_idxset_get_by_index(c->protocol->core->sinks, idx); + else + sink = pa_namereg_get(c->protocol->core, name, PA_NAMEREG_SINK); + break; + + case PA_COMMAND_SET_SOURCE_VOLUME: + if (idx != PA_INVALID_INDEX) + source = pa_idxset_get_by_index(c->protocol->core->sources, idx); + else + source = pa_namereg_get(c->protocol->core, name, PA_NAMEREG_SOURCE); + break; + + case PA_COMMAND_SET_SINK_INPUT_VOLUME: + si = pa_idxset_get_by_index(c->protocol->core->sink_inputs, idx); + break; + + case PA_COMMAND_SET_SOURCE_OUTPUT_VOLUME: + so = pa_idxset_get_by_index(c->protocol->core->source_outputs, idx); + break; + + default: + pa_assert_not_reached(); + } + + CHECK_VALIDITY(c->pstream, si || so || sink || source, tag, PA_ERR_NOENTITY); + + client_name = pa_strnull(pa_proplist_gets(c->client->proplist, PA_PROP_APPLICATION_PROCESS_BINARY)); + + if (sink) { + CHECK_VALIDITY(c->pstream, volume.channels == 1 || pa_cvolume_compatible(&volume, &sink->sample_spec), tag, PA_ERR_INVALID); + + pa_log_debug("Client %s changes volume of sink %s.", client_name, sink->name); + pa_sink_set_volume(sink, &volume, true, true); + } else if (source) { + CHECK_VALIDITY(c->pstream, volume.channels == 1 || pa_cvolume_compatible(&volume, &source->sample_spec), tag, PA_ERR_INVALID); + + pa_log_debug("Client %s changes volume of source %s.", client_name, source->name); + pa_source_set_volume(source, &volume, true, true); + } else if (si) { + CHECK_VALIDITY(c->pstream, si->volume_writable, tag, PA_ERR_BADSTATE); + CHECK_VALIDITY(c->pstream, volume.channels == 1 || pa_cvolume_compatible(&volume, &si->sample_spec), tag, PA_ERR_INVALID); + + pa_log_debug("Client %s changes volume of sink input %s.", + client_name, + pa_strnull(pa_proplist_gets(si->proplist, PA_PROP_MEDIA_NAME))); + pa_sink_input_set_volume(si, &volume, true, true); + } else if (so) { + CHECK_VALIDITY(c->pstream, so->volume_writable, tag, PA_ERR_BADSTATE); + CHECK_VALIDITY(c->pstream, volume.channels == 1 || pa_cvolume_compatible(&volume, &so->sample_spec), tag, PA_ERR_INVALID); + + pa_log_debug("Client %s changes volume of source output %s.", + client_name, + pa_strnull(pa_proplist_gets(so->proplist, PA_PROP_MEDIA_NAME))); + pa_source_output_set_volume(so, &volume, true, true); + } + + pa_pstream_send_simple_ack(c->pstream, tag); +} + +static void command_set_mute( + pa_pdispatch *pd, + uint32_t command, + uint32_t tag, + pa_tagstruct *t, + void *userdata) { + + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + uint32_t idx; + bool mute; + pa_sink *sink = NULL; + pa_source *source = NULL; + pa_sink_input *si = NULL; + pa_source_output *so = NULL; + const char *name = NULL, *client_name; + + pa_native_connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &idx) < 0 || + (command == PA_COMMAND_SET_SINK_MUTE && pa_tagstruct_gets(t, &name) < 0) || + (command == PA_COMMAND_SET_SOURCE_MUTE && pa_tagstruct_gets(t, &name) < 0) || + pa_tagstruct_get_boolean(t, &mute) || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + CHECK_VALIDITY(c->pstream, !name || pa_namereg_is_valid_name_or_wildcard(name, command == PA_COMMAND_SET_SINK_MUTE ? PA_NAMEREG_SINK : PA_NAMEREG_SOURCE), tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, (idx != PA_INVALID_INDEX) ^ (name != NULL), tag, PA_ERR_INVALID); + + switch (command) { + + case PA_COMMAND_SET_SINK_MUTE: + if (idx != PA_INVALID_INDEX) + sink = pa_idxset_get_by_index(c->protocol->core->sinks, idx); + else + sink = pa_namereg_get(c->protocol->core, name, PA_NAMEREG_SINK); + + break; + + case PA_COMMAND_SET_SOURCE_MUTE: + if (idx != PA_INVALID_INDEX) + source = pa_idxset_get_by_index(c->protocol->core->sources, idx); + else + source = pa_namereg_get(c->protocol->core, name, PA_NAMEREG_SOURCE); + + break; + + case PA_COMMAND_SET_SINK_INPUT_MUTE: + si = pa_idxset_get_by_index(c->protocol->core->sink_inputs, idx); + break; + + case PA_COMMAND_SET_SOURCE_OUTPUT_MUTE: + so = pa_idxset_get_by_index(c->protocol->core->source_outputs, idx); + break; + + default: + pa_assert_not_reached(); + } + + CHECK_VALIDITY(c->pstream, si || so || sink || source, tag, PA_ERR_NOENTITY); + + client_name = pa_strnull(pa_proplist_gets(c->client->proplist, PA_PROP_APPLICATION_PROCESS_BINARY)); + + if (sink) { + pa_log_debug("Client %s changes mute of sink %s.", client_name, sink->name); + pa_sink_set_mute(sink, mute, true); + } else if (source) { + pa_log_debug("Client %s changes mute of source %s.", client_name, source->name); + pa_source_set_mute(source, mute, true); + } else if (si) { + pa_log_debug("Client %s changes mute of sink input %s.", + client_name, + pa_strnull(pa_proplist_gets(si->proplist, PA_PROP_MEDIA_NAME))); + pa_sink_input_set_mute(si, mute, true); + } else if (so) { + pa_log_debug("Client %s changes mute of source output %s.", + client_name, + pa_strnull(pa_proplist_gets(so->proplist, PA_PROP_MEDIA_NAME))); + pa_source_output_set_mute(so, mute, true); + } + + pa_pstream_send_simple_ack(c->pstream, tag); +} + +static void command_cork_playback_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + uint32_t idx; + bool b; + playback_stream *s; + + pa_native_connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &idx) < 0 || + pa_tagstruct_get_boolean(t, &b) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + CHECK_VALIDITY(c->pstream, idx != PA_INVALID_INDEX, tag, PA_ERR_INVALID); + s = pa_idxset_get_by_index(c->output_streams, idx); + CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY); + CHECK_VALIDITY(c->pstream, playback_stream_isinstance(s), tag, PA_ERR_NOENTITY); + + pa_sink_input_cork(s->sink_input, b); + + if (b) + s->is_underrun = true; + + pa_pstream_send_simple_ack(c->pstream, tag); +} + +static void command_trigger_or_flush_or_prebuf_playback_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + uint32_t idx; + playback_stream *s; + + pa_native_connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &idx) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + CHECK_VALIDITY(c->pstream, idx != PA_INVALID_INDEX, tag, PA_ERR_INVALID); + s = pa_idxset_get_by_index(c->output_streams, idx); + CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY); + CHECK_VALIDITY(c->pstream, playback_stream_isinstance(s), tag, PA_ERR_NOENTITY); + + switch (command) { + case PA_COMMAND_FLUSH_PLAYBACK_STREAM: + pa_asyncmsgq_send(s->sink_input->sink->asyncmsgq, PA_MSGOBJECT(s->sink_input), SINK_INPUT_MESSAGE_FLUSH, NULL, 0, NULL); + break; + + case PA_COMMAND_PREBUF_PLAYBACK_STREAM: + pa_asyncmsgq_send(s->sink_input->sink->asyncmsgq, PA_MSGOBJECT(s->sink_input), SINK_INPUT_MESSAGE_PREBUF_FORCE, NULL, 0, NULL); + break; + + case PA_COMMAND_TRIGGER_PLAYBACK_STREAM: + pa_asyncmsgq_send(s->sink_input->sink->asyncmsgq, PA_MSGOBJECT(s->sink_input), SINK_INPUT_MESSAGE_TRIGGER, NULL, 0, NULL); + break; + + default: + pa_assert_not_reached(); + } + + pa_pstream_send_simple_ack(c->pstream, tag); +} + +static void command_cork_record_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + uint32_t idx; + record_stream *s; + bool b; + + pa_native_connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &idx) < 0 || + pa_tagstruct_get_boolean(t, &b) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + s = pa_idxset_get_by_index(c->record_streams, idx); + CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY); + + pa_source_output_cork(s->source_output, b); + pa_memblockq_prebuf_force(s->memblockq); + pa_pstream_send_simple_ack(c->pstream, tag); +} + +static void command_flush_record_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + uint32_t idx; + record_stream *s; + + pa_native_connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &idx) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + s = pa_idxset_get_by_index(c->record_streams, idx); + CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY); + + pa_memblockq_flush_read(s->memblockq); + pa_pstream_send_simple_ack(c->pstream, tag); +} + +static void command_set_stream_buffer_attr(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + uint32_t idx; + pa_buffer_attr a; + pa_tagstruct *reply; + + pa_native_connection_assert_ref(c); + pa_assert(t); + + memset(&a, 0, sizeof(a)); + + if (pa_tagstruct_getu32(t, &idx) < 0) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + + if (command == PA_COMMAND_SET_PLAYBACK_STREAM_BUFFER_ATTR) { + playback_stream *s; + bool adjust_latency = false, early_requests = false; + + s = pa_idxset_get_by_index(c->output_streams, idx); + CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY); + CHECK_VALIDITY(c->pstream, playback_stream_isinstance(s), tag, PA_ERR_NOENTITY); + + if (pa_tagstruct_get( + t, + PA_TAG_U32, &a.maxlength, + PA_TAG_U32, &a.tlength, + PA_TAG_U32, &a.prebuf, + PA_TAG_U32, &a.minreq, + PA_TAG_INVALID) < 0 || + (c->version >= 13 && pa_tagstruct_get_boolean(t, &adjust_latency) < 0) || + (c->version >= 14 && pa_tagstruct_get_boolean(t, &early_requests) < 0) || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + s->adjust_latency = adjust_latency; + s->early_requests = early_requests; + s->buffer_attr_req = a; + + fix_playback_buffer_attr(s); + pa_assert_se(pa_asyncmsgq_send(s->sink_input->sink->asyncmsgq, PA_MSGOBJECT(s->sink_input), SINK_INPUT_MESSAGE_UPDATE_BUFFER_ATTR, NULL, 0, NULL) == 0); + + reply = reply_new(tag); + pa_tagstruct_putu32(reply, s->buffer_attr.maxlength); + pa_tagstruct_putu32(reply, s->buffer_attr.tlength); + pa_tagstruct_putu32(reply, s->buffer_attr.prebuf); + pa_tagstruct_putu32(reply, s->buffer_attr.minreq); + + if (c->version >= 13) + pa_tagstruct_put_usec(reply, s->configured_sink_latency); + + } else { + record_stream *s; + bool adjust_latency = false, early_requests = false; + pa_assert(command == PA_COMMAND_SET_RECORD_STREAM_BUFFER_ATTR); + + s = pa_idxset_get_by_index(c->record_streams, idx); + CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY); + + if (pa_tagstruct_get( + t, + PA_TAG_U32, &a.maxlength, + PA_TAG_U32, &a.fragsize, + PA_TAG_INVALID) < 0 || + (c->version >= 13 && pa_tagstruct_get_boolean(t, &adjust_latency) < 0) || + (c->version >= 14 && pa_tagstruct_get_boolean(t, &early_requests) < 0) || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + s->adjust_latency = adjust_latency; + s->early_requests = early_requests; + s->buffer_attr_req = a; + + fix_record_buffer_attr_pre(s); + pa_memblockq_set_maxlength(s->memblockq, s->buffer_attr.maxlength); + pa_memblockq_get_attr(s->memblockq, &s->buffer_attr); + fix_record_buffer_attr_post(s); + + reply = reply_new(tag); + pa_tagstruct_putu32(reply, s->buffer_attr.maxlength); + pa_tagstruct_putu32(reply, s->buffer_attr.fragsize); + + if (c->version >= 13) + pa_tagstruct_put_usec(reply, s->configured_source_latency); + } + + pa_pstream_send_tagstruct(c->pstream, reply); +} + +static void command_update_stream_sample_rate(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + uint32_t idx; + uint32_t rate; + + pa_native_connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &idx) < 0 || + pa_tagstruct_getu32(t, &rate) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + CHECK_VALIDITY(c->pstream, pa_sample_rate_valid(rate), tag, PA_ERR_INVALID); + + if (command == PA_COMMAND_UPDATE_PLAYBACK_STREAM_SAMPLE_RATE) { + playback_stream *s; + + s = pa_idxset_get_by_index(c->output_streams, idx); + CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY); + CHECK_VALIDITY(c->pstream, playback_stream_isinstance(s), tag, PA_ERR_NOENTITY); + + pa_sink_input_set_rate(s->sink_input, rate); + + } else { + record_stream *s; + pa_assert(command == PA_COMMAND_UPDATE_RECORD_STREAM_SAMPLE_RATE); + + s = pa_idxset_get_by_index(c->record_streams, idx); + CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY); + + pa_source_output_set_rate(s->source_output, rate); + } + + pa_pstream_send_simple_ack(c->pstream, tag); +} + +static void command_update_proplist(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + uint32_t idx; + uint32_t mode; + pa_proplist *p; + + pa_native_connection_assert_ref(c); + pa_assert(t); + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + + p = pa_proplist_new(); + + if (command == PA_COMMAND_UPDATE_CLIENT_PROPLIST) { + + if (pa_tagstruct_getu32(t, &mode) < 0 || + pa_tagstruct_get_proplist(t, p) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + pa_proplist_free(p); + return; + } + + } else { + + if (pa_tagstruct_getu32(t, &idx) < 0 || + pa_tagstruct_getu32(t, &mode) < 0 || + pa_tagstruct_get_proplist(t, p) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + pa_proplist_free(p); + return; + } + } + + if (!(mode == PA_UPDATE_SET || mode == PA_UPDATE_MERGE || mode == PA_UPDATE_REPLACE)) { + pa_proplist_free(p); + CHECK_VALIDITY(c->pstream, false, tag, PA_ERR_INVALID); + } + + if (command == PA_COMMAND_UPDATE_PLAYBACK_STREAM_PROPLIST) { + playback_stream *s; + + s = pa_idxset_get_by_index(c->output_streams, idx); + if (!s || !playback_stream_isinstance(s)) { + pa_proplist_free(p); + CHECK_VALIDITY(c->pstream, false, tag, PA_ERR_NOENTITY); + } + pa_sink_input_update_proplist(s->sink_input, mode, p); + + } else if (command == PA_COMMAND_UPDATE_RECORD_STREAM_PROPLIST) { + record_stream *s; + + if (!(s = pa_idxset_get_by_index(c->record_streams, idx))) { + pa_proplist_free(p); + CHECK_VALIDITY(c->pstream, false, tag, PA_ERR_NOENTITY); + } + pa_source_output_update_proplist(s->source_output, mode, p); + + } else { + pa_assert(command == PA_COMMAND_UPDATE_CLIENT_PROPLIST); + + pa_client_update_proplist(c->client, mode, p); + } + + pa_pstream_send_simple_ack(c->pstream, tag); + pa_proplist_free(p); +} + +static void command_remove_proplist(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + uint32_t idx; + unsigned changed = 0; + pa_proplist *p; + pa_strlist *l = NULL; + + pa_native_connection_assert_ref(c); + pa_assert(t); + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + + if (command != PA_COMMAND_REMOVE_CLIENT_PROPLIST) { + + if (pa_tagstruct_getu32(t, &idx) < 0) { + protocol_error(c); + return; + } + } + + if (command == PA_COMMAND_REMOVE_PLAYBACK_STREAM_PROPLIST) { + playback_stream *s; + + s = pa_idxset_get_by_index(c->output_streams, idx); + CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY); + CHECK_VALIDITY(c->pstream, playback_stream_isinstance(s), tag, PA_ERR_NOENTITY); + + p = s->sink_input->proplist; + + } else if (command == PA_COMMAND_REMOVE_RECORD_STREAM_PROPLIST) { + record_stream *s; + + s = pa_idxset_get_by_index(c->record_streams, idx); + CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY); + + p = s->source_output->proplist; + } else { + pa_assert(command == PA_COMMAND_REMOVE_CLIENT_PROPLIST); + + p = c->client->proplist; + } + + for (;;) { + const char *k; + + if (pa_tagstruct_gets(t, &k) < 0) { + protocol_error(c); + pa_strlist_free(l); + return; + } + + if (!k) + break; + + l = pa_strlist_prepend(l, k); + } + + if (!pa_tagstruct_eof(t)) { + protocol_error(c); + pa_strlist_free(l); + return; + } + + for (;;) { + char *z; + + l = pa_strlist_pop(l, &z); + + if (!z) + break; + + changed += (unsigned) (pa_proplist_unset(p, z) >= 0); + pa_xfree(z); + } + + pa_pstream_send_simple_ack(c->pstream, tag); + + if (changed) { + if (command == PA_COMMAND_REMOVE_PLAYBACK_STREAM_PROPLIST) { + playback_stream *s; + + s = pa_idxset_get_by_index(c->output_streams, idx); + pa_subscription_post(c->protocol->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, s->sink_input->index); + + } else if (command == PA_COMMAND_REMOVE_RECORD_STREAM_PROPLIST) { + record_stream *s; + + s = pa_idxset_get_by_index(c->record_streams, idx); + pa_subscription_post(c->protocol->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, s->source_output->index); + + } else { + pa_assert(command == PA_COMMAND_REMOVE_CLIENT_PROPLIST); + pa_subscription_post(c->protocol->core, PA_SUBSCRIPTION_EVENT_CLIENT|PA_SUBSCRIPTION_EVENT_CHANGE, c->client->index); + } + } +} + +static void command_set_default_sink_or_source(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + const char *s; + + pa_native_connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_gets(t, &s) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + CHECK_VALIDITY(c->pstream, !s || pa_namereg_is_valid_name(s), tag, PA_ERR_INVALID); + + if (command == PA_COMMAND_SET_DEFAULT_SOURCE) { + pa_source *source; + + source = pa_namereg_get(c->protocol->core, s, PA_NAMEREG_SOURCE); + CHECK_VALIDITY(c->pstream, source, tag, PA_ERR_NOENTITY); + + pa_core_set_configured_default_source(c->protocol->core, source->name); + } else { + pa_sink *sink; + pa_assert(command == PA_COMMAND_SET_DEFAULT_SINK); + + sink = pa_namereg_get(c->protocol->core, s, PA_NAMEREG_SINK); + CHECK_VALIDITY(c->pstream, sink, tag, PA_ERR_NOENTITY); + + pa_core_set_configured_default_sink(c->protocol->core, sink->name); + } + + pa_pstream_send_simple_ack(c->pstream, tag); +} + +static void command_set_stream_name(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + uint32_t idx; + const char *name; + + pa_native_connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &idx) < 0 || + pa_tagstruct_gets(t, &name) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + CHECK_VALIDITY(c->pstream, name && pa_utf8_valid(name), tag, PA_ERR_INVALID); + + if (command == PA_COMMAND_SET_PLAYBACK_STREAM_NAME) { + playback_stream *s; + + s = pa_idxset_get_by_index(c->output_streams, idx); + CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY); + CHECK_VALIDITY(c->pstream, playback_stream_isinstance(s), tag, PA_ERR_NOENTITY); + + pa_sink_input_set_property(s->sink_input, PA_PROP_MEDIA_NAME, name); + + } else { + record_stream *s; + pa_assert(command == PA_COMMAND_SET_RECORD_STREAM_NAME); + + s = pa_idxset_get_by_index(c->record_streams, idx); + CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY); + + pa_source_output_set_property(s->source_output, PA_PROP_MEDIA_NAME, name); + } + + pa_pstream_send_simple_ack(c->pstream, tag); +} + +static void command_kill(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + uint32_t idx; + + pa_native_connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &idx) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + + if (command == PA_COMMAND_KILL_CLIENT) { + pa_client *client; + + client = pa_idxset_get_by_index(c->protocol->core->clients, idx); + CHECK_VALIDITY(c->pstream, client, tag, PA_ERR_NOENTITY); + + pa_native_connection_ref(c); + pa_client_kill(client); + + } else if (command == PA_COMMAND_KILL_SINK_INPUT) { + pa_sink_input *s; + + s = pa_idxset_get_by_index(c->protocol->core->sink_inputs, idx); + CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY); + + pa_native_connection_ref(c); + pa_sink_input_kill(s); + } else { + pa_source_output *s; + + pa_assert(command == PA_COMMAND_KILL_SOURCE_OUTPUT); + + s = pa_idxset_get_by_index(c->protocol->core->source_outputs, idx); + CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY); + + pa_native_connection_ref(c); + pa_source_output_kill(s); + } + + pa_pstream_send_simple_ack(c->pstream, tag); + pa_native_connection_unref(c); +} + +static void command_load_module(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + pa_module *m; + const char *name, *argument; + pa_tagstruct *reply; + + pa_native_connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_gets(t, &name) < 0 || + pa_tagstruct_gets(t, &argument) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + CHECK_VALIDITY(c->pstream, name && *name && pa_utf8_valid(name) && !strchr(name, '/'), tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, !argument || pa_utf8_valid(argument), tag, PA_ERR_INVALID); + + if (pa_module_load(&m, c->protocol->core, name, argument) < 0) { + pa_pstream_send_error(c->pstream, tag, PA_ERR_MODINITFAILED); + return; + } + + reply = reply_new(tag); + pa_tagstruct_putu32(reply, m->index); + pa_pstream_send_tagstruct(c->pstream, reply); +} + +static void command_unload_module(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + uint32_t idx; + pa_module *m; + + pa_native_connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &idx) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + m = pa_idxset_get_by_index(c->protocol->core->modules, idx); + CHECK_VALIDITY(c->pstream, m, tag, PA_ERR_NOENTITY); + + pa_module_unload_request(m, false); + pa_pstream_send_simple_ack(c->pstream, tag); +} + +static void command_move_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + uint32_t idx = PA_INVALID_INDEX, idx_device = PA_INVALID_INDEX; + const char *name_device = NULL; + + pa_native_connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &idx) < 0 || + pa_tagstruct_getu32(t, &idx_device) < 0 || + pa_tagstruct_gets(t, &name_device) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + CHECK_VALIDITY(c->pstream, idx != PA_INVALID_INDEX, tag, PA_ERR_INVALID); + + CHECK_VALIDITY(c->pstream, !name_device || pa_namereg_is_valid_name_or_wildcard(name_device, command == PA_COMMAND_MOVE_SINK_INPUT ? PA_NAMEREG_SINK : PA_NAMEREG_SOURCE), tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, (idx_device != PA_INVALID_INDEX) ^ (name_device != NULL), tag, PA_ERR_INVALID); + + if (command == PA_COMMAND_MOVE_SINK_INPUT) { + pa_sink_input *si = NULL; + pa_sink *sink = NULL; + + si = pa_idxset_get_by_index(c->protocol->core->sink_inputs, idx); + + if (idx_device != PA_INVALID_INDEX) + sink = pa_idxset_get_by_index(c->protocol->core->sinks, idx_device); + else + sink = pa_namereg_get(c->protocol->core, name_device, PA_NAMEREG_SINK); + + CHECK_VALIDITY(c->pstream, si && sink, tag, PA_ERR_NOENTITY); + + if (pa_sink_input_move_to(si, sink, true) < 0) { + pa_pstream_send_error(c->pstream, tag, PA_ERR_INVALID); + return; + } + } else { + pa_source_output *so = NULL; + pa_source *source; + + pa_assert(command == PA_COMMAND_MOVE_SOURCE_OUTPUT); + + so = pa_idxset_get_by_index(c->protocol->core->source_outputs, idx); + + if (idx_device != PA_INVALID_INDEX) + source = pa_idxset_get_by_index(c->protocol->core->sources, idx_device); + else + source = pa_namereg_get(c->protocol->core, name_device, PA_NAMEREG_SOURCE); + + CHECK_VALIDITY(c->pstream, so && source, tag, PA_ERR_NOENTITY); + + if (pa_source_output_move_to(so, source, true) < 0) { + pa_pstream_send_error(c->pstream, tag, PA_ERR_INVALID); + return; + } + } + + pa_pstream_send_simple_ack(c->pstream, tag); +} + +static void command_suspend(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + uint32_t idx = PA_INVALID_INDEX; + const char *name = NULL; + bool b; + + pa_native_connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &idx) < 0 || + pa_tagstruct_gets(t, &name) < 0 || + pa_tagstruct_get_boolean(t, &b) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + CHECK_VALIDITY(c->pstream, !name || pa_namereg_is_valid_name_or_wildcard(name, command == PA_COMMAND_SUSPEND_SINK ? PA_NAMEREG_SINK : PA_NAMEREG_SOURCE) || *name == 0, tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, (idx != PA_INVALID_INDEX) ^ (name != NULL), tag, PA_ERR_INVALID); + + if (command == PA_COMMAND_SUSPEND_SINK) { + + if (idx == PA_INVALID_INDEX && name && !*name) { + + pa_log_debug("%s all sinks", b ? "Suspending" : "Resuming"); + + if (pa_sink_suspend_all(c->protocol->core, b, PA_SUSPEND_USER) < 0) { + pa_pstream_send_error(c->pstream, tag, PA_ERR_INVALID); + return; + } + } else { + pa_sink *sink = NULL; + + if (idx != PA_INVALID_INDEX) + sink = pa_idxset_get_by_index(c->protocol->core->sinks, idx); + else + sink = pa_namereg_get(c->protocol->core, name, PA_NAMEREG_SINK); + + CHECK_VALIDITY(c->pstream, sink, tag, PA_ERR_NOENTITY); + + pa_log_debug("%s of sink %s requested by client %" PRIu32 ".", + b ? "Suspending" : "Resuming", sink->name, c->client->index); + + if (pa_sink_suspend(sink, b, PA_SUSPEND_USER) < 0) { + pa_pstream_send_error(c->pstream, tag, PA_ERR_INVALID); + return; + } + } + } else { + + pa_assert(command == PA_COMMAND_SUSPEND_SOURCE); + + if (idx == PA_INVALID_INDEX && name && !*name) { + + pa_log_debug("%s all sources", b ? "Suspending" : "Resuming"); + + if (pa_source_suspend_all(c->protocol->core, b, PA_SUSPEND_USER) < 0) { + pa_pstream_send_error(c->pstream, tag, PA_ERR_INVALID); + return; + } + + } else { + pa_source *source; + + if (idx != PA_INVALID_INDEX) + source = pa_idxset_get_by_index(c->protocol->core->sources, idx); + else + source = pa_namereg_get(c->protocol->core, name, PA_NAMEREG_SOURCE); + + CHECK_VALIDITY(c->pstream, source, tag, PA_ERR_NOENTITY); + + pa_log_debug("%s of source %s requested by client %" PRIu32 ".", + b ? "Suspending" : "Resuming", source->name, c->client->index); + + if (pa_source_suspend(source, b, PA_SUSPEND_USER) < 0) { + pa_pstream_send_error(c->pstream, tag, PA_ERR_INVALID); + return; + } + } + } + + pa_pstream_send_simple_ack(c->pstream, tag); +} + +static void command_extension(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + uint32_t idx = PA_INVALID_INDEX; + const char *name = NULL; + pa_module *m; + pa_native_protocol_ext_cb_t cb; + + pa_native_connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &idx) < 0 || + pa_tagstruct_gets(t, &name) < 0) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + CHECK_VALIDITY(c->pstream, !name || pa_utf8_valid(name), tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, (idx != PA_INVALID_INDEX) ^ (name != NULL), tag, PA_ERR_INVALID); + + if (idx != PA_INVALID_INDEX) + m = pa_idxset_get_by_index(c->protocol->core->modules, idx); + else + PA_IDXSET_FOREACH(m, c->protocol->core->modules, idx) + if (pa_streq(name, m->name)) + break; + + CHECK_VALIDITY(c->pstream, m, tag, PA_ERR_NOEXTENSION); + CHECK_VALIDITY(c->pstream, m->load_once || idx != PA_INVALID_INDEX, tag, PA_ERR_INVALID); + + cb = (pa_native_protocol_ext_cb_t) (unsigned long) pa_hashmap_get(c->protocol->extensions, m); + CHECK_VALIDITY(c->pstream, cb, tag, PA_ERR_NOEXTENSION); + + if (cb(c->protocol, m, c, tag, t) < 0) + protocol_error(c); +} + +static void command_set_card_profile(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + uint32_t idx = PA_INVALID_INDEX; + const char *name = NULL, *profile_name = NULL; + pa_card *card = NULL; + pa_card_profile *profile; + int ret; + + pa_native_connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &idx) < 0 || + pa_tagstruct_gets(t, &name) < 0 || + pa_tagstruct_gets(t, &profile_name) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + CHECK_VALIDITY(c->pstream, !name || pa_namereg_is_valid_name(name), tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, (idx != PA_INVALID_INDEX) ^ (name != NULL), tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, profile_name, tag, PA_ERR_INVALID); + + if (idx != PA_INVALID_INDEX) + card = pa_idxset_get_by_index(c->protocol->core->cards, idx); + else + card = pa_namereg_get(c->protocol->core, name, PA_NAMEREG_CARD); + + CHECK_VALIDITY(c->pstream, card, tag, PA_ERR_NOENTITY); + + profile = pa_hashmap_get(card->profiles, profile_name); + + CHECK_VALIDITY(c->pstream, profile, tag, PA_ERR_NOENTITY); + + pa_log_info("Application \"%s\" requests card profile change. card = %s, profile = %s", + pa_strnull(pa_proplist_gets(c->client->proplist, PA_PROP_APPLICATION_NAME)), + card->name, + profile->name); + + if ((ret = pa_card_set_profile(card, profile, true)) < 0) { + pa_pstream_send_error(c->pstream, tag, -ret); + return; + } + + pa_pstream_send_simple_ack(c->pstream, tag); +} + +static void command_set_sink_or_source_port(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + uint32_t idx = PA_INVALID_INDEX; + const char *name = NULL, *port = NULL; + int ret; + + pa_native_connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &idx) < 0 || + pa_tagstruct_gets(t, &name) < 0 || + pa_tagstruct_gets(t, &port) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + CHECK_VALIDITY(c->pstream, !name || pa_namereg_is_valid_name_or_wildcard(name, command == PA_COMMAND_SET_SINK_PORT ? PA_NAMEREG_SINK : PA_NAMEREG_SOURCE), tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, (idx != PA_INVALID_INDEX) ^ (name != NULL), tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, port, tag, PA_ERR_INVALID); + + if (command == PA_COMMAND_SET_SINK_PORT) { + pa_sink *sink; + + if (idx != PA_INVALID_INDEX) + sink = pa_idxset_get_by_index(c->protocol->core->sinks, idx); + else + sink = pa_namereg_get(c->protocol->core, name, PA_NAMEREG_SINK); + + CHECK_VALIDITY(c->pstream, sink, tag, PA_ERR_NOENTITY); + + if ((ret = pa_sink_set_port(sink, port, true)) < 0) { + pa_pstream_send_error(c->pstream, tag, -ret); + return; + } + } else { + pa_source *source; + + pa_assert(command == PA_COMMAND_SET_SOURCE_PORT); + + if (idx != PA_INVALID_INDEX) + source = pa_idxset_get_by_index(c->protocol->core->sources, idx); + else + source = pa_namereg_get(c->protocol->core, name, PA_NAMEREG_SOURCE); + + CHECK_VALIDITY(c->pstream, source, tag, PA_ERR_NOENTITY); + + if ((ret = pa_source_set_port(source, port, true)) < 0) { + pa_pstream_send_error(c->pstream, tag, -ret); + return; + } + } + + pa_pstream_send_simple_ack(c->pstream, tag); +} + +static void command_set_port_latency_offset(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + const char *port_name, *card_name; + uint32_t idx = PA_INVALID_INDEX; + int64_t offset; + pa_card *card = NULL; + pa_device_port *port = NULL; + + pa_native_connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &idx) < 0 || + pa_tagstruct_gets(t, &card_name) < 0 || + pa_tagstruct_gets(t, &port_name) < 0 || + pa_tagstruct_gets64(t, &offset) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + CHECK_VALIDITY(c->pstream, !card_name || pa_namereg_is_valid_name(card_name), tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, (idx != PA_INVALID_INDEX) ^ (card_name != NULL), tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, port_name, tag, PA_ERR_INVALID); + + if (idx != PA_INVALID_INDEX) + card = pa_idxset_get_by_index(c->protocol->core->cards, idx); + else + card = pa_namereg_get(c->protocol->core, card_name, PA_NAMEREG_CARD); + + CHECK_VALIDITY(c->pstream, card, tag, PA_ERR_NOENTITY); + + port = pa_hashmap_get(card->ports, port_name); + CHECK_VALIDITY(c->pstream, port, tag, PA_ERR_NOENTITY); + + pa_device_port_set_latency_offset(port, offset); + + pa_pstream_send_simple_ack(c->pstream, tag); +} + +static const pa_pdispatch_cb_t command_table[PA_COMMAND_MAX] = { + [PA_COMMAND_ERROR] = NULL, + [PA_COMMAND_TIMEOUT] = NULL, + [PA_COMMAND_REPLY] = NULL, + [PA_COMMAND_CREATE_PLAYBACK_STREAM] = command_create_playback_stream, + [PA_COMMAND_DELETE_PLAYBACK_STREAM] = command_delete_stream, + [PA_COMMAND_DRAIN_PLAYBACK_STREAM] = command_drain_playback_stream, + [PA_COMMAND_CREATE_RECORD_STREAM] = command_create_record_stream, + [PA_COMMAND_DELETE_RECORD_STREAM] = command_delete_stream, + [PA_COMMAND_AUTH] = command_auth, + [PA_COMMAND_REQUEST] = NULL, + [PA_COMMAND_EXIT] = command_exit, + [PA_COMMAND_SET_CLIENT_NAME] = command_set_client_name, + [PA_COMMAND_LOOKUP_SINK] = command_lookup, + [PA_COMMAND_LOOKUP_SOURCE] = command_lookup, + [PA_COMMAND_STAT] = command_stat, + [PA_COMMAND_GET_PLAYBACK_LATENCY] = command_get_playback_latency, + [PA_COMMAND_GET_RECORD_LATENCY] = command_get_record_latency, + [PA_COMMAND_CREATE_UPLOAD_STREAM] = command_create_upload_stream, + [PA_COMMAND_DELETE_UPLOAD_STREAM] = command_delete_stream, + [PA_COMMAND_FINISH_UPLOAD_STREAM] = command_finish_upload_stream, + [PA_COMMAND_PLAY_SAMPLE] = command_play_sample, + [PA_COMMAND_REMOVE_SAMPLE] = command_remove_sample, + [PA_COMMAND_GET_SINK_INFO] = command_get_info, + [PA_COMMAND_GET_SOURCE_INFO] = command_get_info, + [PA_COMMAND_GET_CLIENT_INFO] = command_get_info, + [PA_COMMAND_GET_CARD_INFO] = command_get_info, + [PA_COMMAND_GET_MODULE_INFO] = command_get_info, + [PA_COMMAND_GET_SINK_INPUT_INFO] = command_get_info, + [PA_COMMAND_GET_SOURCE_OUTPUT_INFO] = command_get_info, + [PA_COMMAND_GET_SAMPLE_INFO] = command_get_info, + [PA_COMMAND_GET_SINK_INFO_LIST] = command_get_info_list, + [PA_COMMAND_GET_SOURCE_INFO_LIST] = command_get_info_list, + [PA_COMMAND_GET_MODULE_INFO_LIST] = command_get_info_list, + [PA_COMMAND_GET_CLIENT_INFO_LIST] = command_get_info_list, + [PA_COMMAND_GET_CARD_INFO_LIST] = command_get_info_list, + [PA_COMMAND_GET_SINK_INPUT_INFO_LIST] = command_get_info_list, + [PA_COMMAND_GET_SOURCE_OUTPUT_INFO_LIST] = command_get_info_list, + [PA_COMMAND_GET_SAMPLE_INFO_LIST] = command_get_info_list, + [PA_COMMAND_GET_SERVER_INFO] = command_get_server_info, + [PA_COMMAND_SUBSCRIBE] = command_subscribe, + + [PA_COMMAND_SET_SINK_VOLUME] = command_set_volume, + [PA_COMMAND_SET_SINK_INPUT_VOLUME] = command_set_volume, + [PA_COMMAND_SET_SOURCE_VOLUME] = command_set_volume, + [PA_COMMAND_SET_SOURCE_OUTPUT_VOLUME] = command_set_volume, + + [PA_COMMAND_SET_SINK_MUTE] = command_set_mute, + [PA_COMMAND_SET_SINK_INPUT_MUTE] = command_set_mute, + [PA_COMMAND_SET_SOURCE_MUTE] = command_set_mute, + [PA_COMMAND_SET_SOURCE_OUTPUT_MUTE] = command_set_mute, + + [PA_COMMAND_SUSPEND_SINK] = command_suspend, + [PA_COMMAND_SUSPEND_SOURCE] = command_suspend, + + [PA_COMMAND_CORK_PLAYBACK_STREAM] = command_cork_playback_stream, + [PA_COMMAND_FLUSH_PLAYBACK_STREAM] = command_trigger_or_flush_or_prebuf_playback_stream, + [PA_COMMAND_TRIGGER_PLAYBACK_STREAM] = command_trigger_or_flush_or_prebuf_playback_stream, + [PA_COMMAND_PREBUF_PLAYBACK_STREAM] = command_trigger_or_flush_or_prebuf_playback_stream, + + [PA_COMMAND_CORK_RECORD_STREAM] = command_cork_record_stream, + [PA_COMMAND_FLUSH_RECORD_STREAM] = command_flush_record_stream, + + [PA_COMMAND_SET_DEFAULT_SINK] = command_set_default_sink_or_source, + [PA_COMMAND_SET_DEFAULT_SOURCE] = command_set_default_sink_or_source, + [PA_COMMAND_SET_PLAYBACK_STREAM_NAME] = command_set_stream_name, + [PA_COMMAND_SET_RECORD_STREAM_NAME] = command_set_stream_name, + [PA_COMMAND_KILL_CLIENT] = command_kill, + [PA_COMMAND_KILL_SINK_INPUT] = command_kill, + [PA_COMMAND_KILL_SOURCE_OUTPUT] = command_kill, + [PA_COMMAND_LOAD_MODULE] = command_load_module, + [PA_COMMAND_UNLOAD_MODULE] = command_unload_module, + + [PA_COMMAND_GET_AUTOLOAD_INFO___OBSOLETE] = NULL, + [PA_COMMAND_GET_AUTOLOAD_INFO_LIST___OBSOLETE] = NULL, + [PA_COMMAND_ADD_AUTOLOAD___OBSOLETE] = NULL, + [PA_COMMAND_REMOVE_AUTOLOAD___OBSOLETE] = NULL, + + [PA_COMMAND_MOVE_SINK_INPUT] = command_move_stream, + [PA_COMMAND_MOVE_SOURCE_OUTPUT] = command_move_stream, + + [PA_COMMAND_SET_PLAYBACK_STREAM_BUFFER_ATTR] = command_set_stream_buffer_attr, + [PA_COMMAND_SET_RECORD_STREAM_BUFFER_ATTR] = command_set_stream_buffer_attr, + + [PA_COMMAND_UPDATE_PLAYBACK_STREAM_SAMPLE_RATE] = command_update_stream_sample_rate, + [PA_COMMAND_UPDATE_RECORD_STREAM_SAMPLE_RATE] = command_update_stream_sample_rate, + + [PA_COMMAND_UPDATE_RECORD_STREAM_PROPLIST] = command_update_proplist, + [PA_COMMAND_UPDATE_PLAYBACK_STREAM_PROPLIST] = command_update_proplist, + [PA_COMMAND_UPDATE_CLIENT_PROPLIST] = command_update_proplist, + + [PA_COMMAND_REMOVE_RECORD_STREAM_PROPLIST] = command_remove_proplist, + [PA_COMMAND_REMOVE_PLAYBACK_STREAM_PROPLIST] = command_remove_proplist, + [PA_COMMAND_REMOVE_CLIENT_PROPLIST] = command_remove_proplist, + + [PA_COMMAND_SET_CARD_PROFILE] = command_set_card_profile, + + [PA_COMMAND_SET_SINK_PORT] = command_set_sink_or_source_port, + [PA_COMMAND_SET_SOURCE_PORT] = command_set_sink_or_source_port, + + [PA_COMMAND_SET_PORT_LATENCY_OFFSET] = command_set_port_latency_offset, + + [PA_COMMAND_ENABLE_SRBCHANNEL] = command_enable_srbchannel, + + [PA_COMMAND_REGISTER_MEMFD_SHMID] = command_register_memfd_shmid, + + [PA_COMMAND_EXTENSION] = command_extension +}; + +/*** pstream callbacks ***/ + +static void pstream_packet_callback(pa_pstream *p, pa_packet *packet, pa_cmsg_ancil_data *ancil_data, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + + pa_assert(p); + pa_assert(packet); + pa_native_connection_assert_ref(c); + + if (pa_pdispatch_run(c->pdispatch, packet, ancil_data, c) < 0) { + pa_log("invalid packet."); + native_connection_unlink(c); + } +} + +static void pstream_memblock_callback(pa_pstream *p, uint32_t channel, int64_t offset, pa_seek_mode_t seek, const pa_memchunk *chunk, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + output_stream *stream; + + pa_assert(p); + pa_assert(chunk); + pa_native_connection_assert_ref(c); + + if (!(stream = OUTPUT_STREAM(pa_idxset_get_by_index(c->output_streams, channel)))) { + pa_log_debug("Client sent block for invalid stream."); + /* Ignoring */ + return; + } + +#ifdef PROTOCOL_NATIVE_DEBUG + pa_log("got %lu bytes from client", (unsigned long) chunk->length); +#endif + + if (playback_stream_isinstance(stream)) { + playback_stream *ps = PLAYBACK_STREAM(stream); + + size_t frame_size = pa_frame_size(&ps->sink_input->sample_spec); + if (chunk->index % frame_size != 0 || chunk->length % frame_size != 0) { + pa_log_warn("Client sent non-aligned memblock: index %d, length %d, frame size: %d", + (int) chunk->index, (int) chunk->length, (int) frame_size); + return; + } + + pa_atomic_inc(&ps->seek_or_post_in_queue); + if (chunk->memblock) { + if (seek != PA_SEEK_RELATIVE || offset != 0) + pa_asyncmsgq_post(ps->sink_input->sink->asyncmsgq, PA_MSGOBJECT(ps->sink_input), SINK_INPUT_MESSAGE_SEEK, PA_UINT_TO_PTR(seek), offset, chunk, NULL); + else + pa_asyncmsgq_post(ps->sink_input->sink->asyncmsgq, PA_MSGOBJECT(ps->sink_input), SINK_INPUT_MESSAGE_POST_DATA, NULL, 0, chunk, NULL); + } else + pa_asyncmsgq_post(ps->sink_input->sink->asyncmsgq, PA_MSGOBJECT(ps->sink_input), SINK_INPUT_MESSAGE_SEEK, PA_UINT_TO_PTR(seek), offset+chunk->length, NULL, NULL); + + } else { + upload_stream *u = UPLOAD_STREAM(stream); + size_t l; + + if (!u->memchunk.memblock) { + if (u->length == chunk->length && chunk->memblock) { + u->memchunk = *chunk; + pa_memblock_ref(u->memchunk.memblock); + u->length = 0; + } else { + u->memchunk.memblock = pa_memblock_new(c->protocol->core->mempool, u->length); + u->memchunk.index = u->memchunk.length = 0; + } + } + + pa_assert(u->memchunk.memblock); + + l = u->length; + if (l > chunk->length) + l = chunk->length; + + if (l > 0) { + void *dst; + dst = pa_memblock_acquire(u->memchunk.memblock); + + if (chunk->memblock) { + void *src; + src = pa_memblock_acquire(chunk->memblock); + + memcpy((uint8_t*) dst + u->memchunk.index + u->memchunk.length, + (uint8_t*) src + chunk->index, l); + + pa_memblock_release(chunk->memblock); + } else + pa_silence_memory((uint8_t*) dst + u->memchunk.index + u->memchunk.length, l, &u->sample_spec); + + pa_memblock_release(u->memchunk.memblock); + + u->memchunk.length += l; + u->length -= l; + } + } +} + +static void pstream_die_callback(pa_pstream *p, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + + pa_assert(p); + pa_native_connection_assert_ref(c); + + native_connection_unlink(c); + pa_log_info("Connection died."); +} + +static void pstream_drain_callback(pa_pstream *p, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + + pa_assert(p); + pa_native_connection_assert_ref(c); + + native_connection_send_memblock(c); +} + +static void pstream_revoke_callback(pa_pstream *p, uint32_t block_id, void *userdata) { + pa_thread_mq *q; + + if (!(q = pa_thread_mq_get())) + pa_pstream_send_revoke(p, block_id); + else + pa_asyncmsgq_post(q->outq, PA_MSGOBJECT(userdata), CONNECTION_MESSAGE_REVOKE, PA_UINT_TO_PTR(block_id), 0, NULL, NULL); +} + +static void pstream_release_callback(pa_pstream *p, uint32_t block_id, void *userdata) { + pa_thread_mq *q; + + if (!(q = pa_thread_mq_get())) + pa_pstream_send_release(p, block_id); + else + pa_asyncmsgq_post(q->outq, PA_MSGOBJECT(userdata), CONNECTION_MESSAGE_RELEASE, PA_UINT_TO_PTR(block_id), 0, NULL, NULL); +} + +/*** client callbacks ***/ + +static void client_kill_cb(pa_client *c) { + pa_assert(c); + + native_connection_unlink(PA_NATIVE_CONNECTION(c->userdata)); + pa_log_info("Connection killed."); +} + +static void client_send_event_cb(pa_client *client, const char*event, pa_proplist *pl) { + pa_tagstruct *t; + pa_native_connection *c; + + pa_assert(client); + c = PA_NATIVE_CONNECTION(client->userdata); + pa_native_connection_assert_ref(c); + + if (c->version < 15) + return; + + t = pa_tagstruct_new(); + pa_tagstruct_putu32(t, PA_COMMAND_CLIENT_EVENT); + pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */ + pa_tagstruct_puts(t, event); + pa_tagstruct_put_proplist(t, pl); + pa_pstream_send_tagstruct(c->pstream, t); +} + +/*** module entry points ***/ + +static void auth_timeout(pa_mainloop_api*m, pa_time_event *e, const struct timeval *t, void *userdata) { + pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); + + pa_assert(m); + pa_native_connection_assert_ref(c); + pa_assert(c->auth_timeout_event == e); + + if (!c->authorized) { + native_connection_unlink(c); + pa_log_info("Connection terminated due to authentication timeout."); + } +} + +void pa_native_protocol_connect(pa_native_protocol *p, pa_iochannel *io, pa_native_options *o) { + pa_native_connection *c; + char pname[128]; + pa_client *client; + pa_client_new_data data; + + pa_assert(p); + pa_assert(io); + pa_assert(o); + + if (pa_idxset_size(p->connections)+1 > MAX_CONNECTIONS) { + pa_log_warn("Warning! Too many connections (%u), dropping incoming connection.", MAX_CONNECTIONS); + pa_iochannel_free(io); + return; + } + + pa_client_new_data_init(&data); + data.module = o->module; + data.driver = __FILE__; + pa_iochannel_socket_peer_to_string(io, pname, sizeof(pname)); + pa_proplist_setf(data.proplist, PA_PROP_APPLICATION_NAME, "Native client (%s)", pname); + pa_proplist_sets(data.proplist, "native-protocol.peer", pname); + client = pa_client_new(p->core, &data); + pa_client_new_data_done(&data); + + if (!client) + return; + + c = pa_msgobject_new(pa_native_connection); + c->parent.parent.free = native_connection_free; + c->parent.process_msg = native_connection_process_msg; + c->protocol = p; + c->options = pa_native_options_ref(o); + c->authorized = false; + c->srbpending = NULL; + + if (o->auth_anonymous) { + pa_log_info("Client authenticated anonymously."); + c->authorized = true; + } + + if (!c->authorized && + o->auth_ip_acl && + pa_ip_acl_check(o->auth_ip_acl, pa_iochannel_get_recv_fd(io)) > 0) { + + pa_log_info("Client authenticated by IP ACL."); + c->authorized = true; + } + + if (!c->authorized) + c->auth_timeout_event = pa_core_rttime_new(p->core, pa_rtclock_now() + AUTH_TIMEOUT, auth_timeout, c); + else + c->auth_timeout_event = NULL; + + c->is_local = pa_iochannel_socket_is_local(io); + c->version = 8; + + c->client = client; + c->client->kill = client_kill_cb; + c->client->send_event = client_send_event_cb; + c->client->userdata = c; + + c->rw_mempool = NULL; + + c->pstream = pa_pstream_new(p->core->mainloop, io, p->core->mempool); + pa_pstream_set_receive_packet_callback(c->pstream, pstream_packet_callback, c); + pa_pstream_set_receive_memblock_callback(c->pstream, pstream_memblock_callback, c); + pa_pstream_set_die_callback(c->pstream, pstream_die_callback, c); + pa_pstream_set_drain_callback(c->pstream, pstream_drain_callback, c); + pa_pstream_set_revoke_callback(c->pstream, pstream_revoke_callback, c); + pa_pstream_set_release_callback(c->pstream, pstream_release_callback, c); + + c->pdispatch = pa_pdispatch_new(p->core->mainloop, true, command_table, PA_COMMAND_MAX); + + c->record_streams = pa_idxset_new(NULL, NULL); + c->output_streams = pa_idxset_new(NULL, NULL); + + c->rrobin_index = PA_IDXSET_INVALID; + c->subscription = NULL; + + pa_idxset_put(p->connections, c, NULL); + +#ifdef HAVE_CREDS + if (pa_iochannel_creds_supported(io)) + pa_iochannel_creds_enable(io); +#endif + + pa_hook_fire(&p->hooks[PA_NATIVE_HOOK_CONNECTION_PUT], c); +} + +void pa_native_protocol_disconnect(pa_native_protocol *p, pa_module *m) { + pa_native_connection *c; + void *state = NULL; + + pa_assert(p); + pa_assert(m); + + while ((c = pa_idxset_iterate(p->connections, &state, NULL))) + if (c->options->module == m) + native_connection_unlink(c); +} + +static pa_native_protocol* native_protocol_new(pa_core *c) { + pa_native_protocol *p; + pa_native_hook_t h; + + pa_assert(c); + + p = pa_xnew(pa_native_protocol, 1); + PA_REFCNT_INIT(p); + p->core = c; + p->connections = pa_idxset_new(NULL, NULL); + + p->servers = NULL; + + p->extensions = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + + for (h = 0; h < PA_NATIVE_HOOK_MAX; h++) + pa_hook_init(&p->hooks[h], p); + + pa_assert_se(pa_shared_set(c, "native-protocol", p) >= 0); + + return p; +} + +pa_native_protocol* pa_native_protocol_get(pa_core *c) { + pa_native_protocol *p; + + if ((p = pa_shared_get(c, "native-protocol"))) + return pa_native_protocol_ref(p); + + return native_protocol_new(c); +} + +pa_native_protocol* pa_native_protocol_ref(pa_native_protocol *p) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) >= 1); + + PA_REFCNT_INC(p); + + return p; +} + +void pa_native_protocol_unref(pa_native_protocol *p) { + pa_native_connection *c; + pa_native_hook_t h; + + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) >= 1); + + if (PA_REFCNT_DEC(p) > 0) + return; + + while ((c = pa_idxset_first(p->connections, NULL))) + native_connection_unlink(c); + + pa_idxset_free(p->connections, NULL); + + pa_strlist_free(p->servers); + + for (h = 0; h < PA_NATIVE_HOOK_MAX; h++) + pa_hook_done(&p->hooks[h]); + + pa_hashmap_free(p->extensions); + + pa_assert_se(pa_shared_remove(p->core, "native-protocol") >= 0); + + pa_xfree(p); +} + +void pa_native_protocol_add_server_string(pa_native_protocol *p, const char *name) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) >= 1); + pa_assert(name); + + p->servers = pa_strlist_prepend(p->servers, name); + + pa_hook_fire(&p->hooks[PA_NATIVE_HOOK_SERVERS_CHANGED], p->servers); +} + +void pa_native_protocol_remove_server_string(pa_native_protocol *p, const char *name) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) >= 1); + pa_assert(name); + + p->servers = pa_strlist_remove(p->servers, name); + + pa_hook_fire(&p->hooks[PA_NATIVE_HOOK_SERVERS_CHANGED], p->servers); +} + +pa_hook *pa_native_protocol_hooks(pa_native_protocol *p) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) >= 1); + + return p->hooks; +} + +pa_strlist *pa_native_protocol_servers(pa_native_protocol *p) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) >= 1); + + return p->servers; +} + +int pa_native_protocol_install_ext(pa_native_protocol *p, pa_module *m, pa_native_protocol_ext_cb_t cb) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) >= 1); + pa_assert(m); + pa_assert(cb); + pa_assert(!pa_hashmap_get(p->extensions, m)); + + pa_assert_se(pa_hashmap_put(p->extensions, m, (void*) (unsigned long) cb) == 0); + return 0; +} + +void pa_native_protocol_remove_ext(pa_native_protocol *p, pa_module *m) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) >= 1); + pa_assert(m); + + pa_assert_se(pa_hashmap_remove(p->extensions, m)); +} + +pa_native_options* pa_native_options_new(void) { + pa_native_options *o; + + o = pa_xnew0(pa_native_options, 1); + PA_REFCNT_INIT(o); + + return o; +} + +pa_native_options* pa_native_options_ref(pa_native_options *o) { + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + PA_REFCNT_INC(o); + + return o; +} + +void pa_native_options_unref(pa_native_options *o) { + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (PA_REFCNT_DEC(o) > 0) + return; + + pa_xfree(o->auth_group); + + if (o->auth_ip_acl) + pa_ip_acl_free(o->auth_ip_acl); + + if (o->auth_cookie) + pa_auth_cookie_unref(o->auth_cookie); + + pa_xfree(o); +} + +int pa_native_options_parse(pa_native_options *o, pa_core *c, pa_modargs *ma) { + bool enabled; + const char *acl; + + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + pa_assert(ma); + + o->srbchannel = true; + if (pa_modargs_get_value_boolean(ma, "srbchannel", &o->srbchannel) < 0) { + pa_log("srbchannel= expects a boolean argument."); + return -1; + } + + if (pa_modargs_get_value_boolean(ma, "auth-anonymous", &o->auth_anonymous) < 0) { + pa_log("auth-anonymous= expects a boolean argument."); + return -1; + } + + enabled = true; + if (pa_modargs_get_value_boolean(ma, "auth-group-enable", &enabled) < 0) { + pa_log("auth-group-enable= expects a boolean argument."); + return -1; + } + + pa_xfree(o->auth_group); + o->auth_group = enabled ? pa_xstrdup(pa_modargs_get_value(ma, "auth-group", pa_in_system_mode() ? PA_ACCESS_GROUP : NULL)) : NULL; + +#ifndef HAVE_CREDS + if (o->auth_group) + pa_log_warn("Authentication group configured, but not available on local system. Ignoring."); +#endif + + if ((acl = pa_modargs_get_value(ma, "auth-ip-acl", NULL))) { + pa_ip_acl *ipa; + + if (!(ipa = pa_ip_acl_new(acl))) { + pa_log("Failed to parse IP ACL '%s'", acl); + return -1; + } + + if (o->auth_ip_acl) + pa_ip_acl_free(o->auth_ip_acl); + + o->auth_ip_acl = ipa; + } + + enabled = true; + if (pa_modargs_get_value_boolean(ma, "auth-cookie-enabled", &enabled) < 0) { + pa_log("auth-cookie-enabled= expects a boolean argument."); + return -1; + } + + if (o->auth_cookie) + pa_auth_cookie_unref(o->auth_cookie); + + if (enabled) { + const char *cn; + + /* The new name for this is 'auth-cookie', for compat reasons + * we check the old name too */ + cn = pa_modargs_get_value(ma, "auth-cookie", NULL); + if (!cn) + cn = pa_modargs_get_value(ma, "cookie", NULL); + + if (cn) + o->auth_cookie = pa_auth_cookie_get(c, cn, true, PA_NATIVE_COOKIE_LENGTH); + else { + o->auth_cookie = pa_auth_cookie_get(c, PA_NATIVE_COOKIE_FILE, false, PA_NATIVE_COOKIE_LENGTH); + if (!o->auth_cookie) { + char *fallback_path; + + if (pa_append_to_home_dir(PA_NATIVE_COOKIE_FILE_FALLBACK, &fallback_path) >= 0) { + o->auth_cookie = pa_auth_cookie_get(c, fallback_path, false, PA_NATIVE_COOKIE_LENGTH); + pa_xfree(fallback_path); + } + + if (!o->auth_cookie) + o->auth_cookie = pa_auth_cookie_get(c, PA_NATIVE_COOKIE_FILE, true, PA_NATIVE_COOKIE_LENGTH); + } + } + + if (!o->auth_cookie) + return -1; + + } else + o->auth_cookie = NULL; + + return 0; +} + +pa_pstream* pa_native_connection_get_pstream(pa_native_connection *c) { + pa_native_connection_assert_ref(c); + + return c->pstream; +} + +pa_client* pa_native_connection_get_client(pa_native_connection *c) { + pa_native_connection_assert_ref(c); + + return c->client; +} diff --git a/src/pulsecore/protocol-native.h b/src/pulsecore/protocol-native.h new file mode 100644 index 0000000..0347fdf --- /dev/null +++ b/src/pulsecore/protocol-native.h @@ -0,0 +1,88 @@ +#ifndef fooprotocolnativehfoo +#define fooprotocolnativehfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulsecore/core.h> +#include <pulsecore/ipacl.h> +#include <pulsecore/auth-cookie.h> +#include <pulsecore/iochannel.h> +#include <pulsecore/module.h> +#include <pulsecore/modargs.h> +#include <pulsecore/strlist.h> +#include <pulsecore/hook-list.h> +#include <pulsecore/pstream.h> +#include <pulsecore/tagstruct.h> + +typedef struct pa_native_protocol pa_native_protocol; + +typedef struct pa_native_connection pa_native_connection; + +typedef struct pa_native_options { + PA_REFCNT_DECLARE; + + pa_module *module; + + bool auth_anonymous; + bool srbchannel; + char *auth_group; + pa_ip_acl *auth_ip_acl; + pa_auth_cookie *auth_cookie; +} pa_native_options; + +typedef enum pa_native_hook { + PA_NATIVE_HOOK_SERVERS_CHANGED, + PA_NATIVE_HOOK_CONNECTION_PUT, + PA_NATIVE_HOOK_CONNECTION_UNLINK, + PA_NATIVE_HOOK_MAX +} pa_native_hook_t; + +pa_native_protocol* pa_native_protocol_get(pa_core *core); +pa_native_protocol* pa_native_protocol_ref(pa_native_protocol *p); +void pa_native_protocol_unref(pa_native_protocol *p); +void pa_native_protocol_connect(pa_native_protocol *p, pa_iochannel *io, pa_native_options *a); +void pa_native_protocol_disconnect(pa_native_protocol *p, pa_module *m); + +pa_hook *pa_native_protocol_hooks(pa_native_protocol *p); + +void pa_native_protocol_add_server_string(pa_native_protocol *p, const char *name); +void pa_native_protocol_remove_server_string(pa_native_protocol *p, const char *name); +pa_strlist *pa_native_protocol_servers(pa_native_protocol *p); + +typedef int (*pa_native_protocol_ext_cb_t)( + pa_native_protocol *p, + pa_module *m, + pa_native_connection *c, + uint32_t tag, + pa_tagstruct *t); + +int pa_native_protocol_install_ext(pa_native_protocol *p, pa_module *m, pa_native_protocol_ext_cb_t cb); +void pa_native_protocol_remove_ext(pa_native_protocol *p, pa_module *m); + +pa_pstream* pa_native_connection_get_pstream(pa_native_connection *c); +pa_client* pa_native_connection_get_client(pa_native_connection *c); + +pa_native_options* pa_native_options_new(void); +pa_native_options* pa_native_options_ref(pa_native_options *o); +void pa_native_options_unref(pa_native_options *o); +int pa_native_options_parse(pa_native_options *o, pa_core *c, pa_modargs *ma); + +#endif diff --git a/src/pulsecore/protocol-simple.c b/src/pulsecore/protocol-simple.c new file mode 100644 index 0000000..77d0539 --- /dev/null +++ b/src/pulsecore/protocol-simple.c @@ -0,0 +1,770 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> + +#include <pulse/xmalloc.h> +#include <pulse/timeval.h> + +#include <pulsecore/sink-input.h> +#include <pulsecore/source-output.h> +#include <pulsecore/client.h> +#include <pulsecore/sample-util.h> +#include <pulsecore/namereg.h> +#include <pulsecore/log.h> +#include <pulsecore/core-error.h> +#include <pulsecore/atomic.h> +#include <pulsecore/thread-mq.h> +#include <pulsecore/core-util.h> +#include <pulsecore/shared.h> + +#include "protocol-simple.h" + +/* Don't allow more than this many concurrent connections */ +#define MAX_CONNECTIONS 10 + +typedef struct connection { + pa_msgobject parent; + pa_simple_protocol *protocol; + pa_simple_options *options; + pa_iochannel *io; + pa_sink_input *sink_input; + pa_source_output *source_output; + pa_client *client; + pa_memblockq *input_memblockq, *output_memblockq; + + bool dead; + + struct { + pa_memblock *current_memblock; + size_t memblock_index; + pa_atomic_t missing; + bool underrun; + } playback; +} connection; + +PA_DEFINE_PRIVATE_CLASS(connection, pa_msgobject); +#define CONNECTION(o) (connection_cast(o)) + +struct pa_simple_protocol { + PA_REFCNT_DECLARE; + + pa_core *core; + pa_idxset *connections; +}; + +enum { + SINK_INPUT_MESSAGE_POST_DATA = PA_SINK_INPUT_MESSAGE_MAX, /* data from main loop to sink input */ + SINK_INPUT_MESSAGE_DISABLE_PREBUF /* disabled prebuf, get playback started. */ +}; + +enum { + CONNECTION_MESSAGE_REQUEST_DATA, /* data requested from sink input from the main loop */ + CONNECTION_MESSAGE_POST_DATA, /* data from source output to main loop */ + CONNECTION_MESSAGE_UNLINK_CONNECTION /* Please drop the connection now */ +}; + +#define PLAYBACK_BUFFER_SECONDS (.5) +#define PLAYBACK_BUFFER_FRAGMENTS (10) +#define RECORD_BUFFER_SECONDS (5) +#define DEFAULT_SINK_LATENCY (300*PA_USEC_PER_MSEC) +#define DEFAULT_SOURCE_LATENCY (300*PA_USEC_PER_MSEC) + +static void connection_unlink(connection *c) { + pa_assert(c); + + if (!c->protocol) + return; + + if (c->options) { + pa_simple_options_unref(c->options); + c->options = NULL; + } + + if (c->sink_input) { + pa_sink_input_unlink(c->sink_input); + pa_sink_input_unref(c->sink_input); + c->sink_input = NULL; + } + + if (c->source_output) { + pa_source_output_unlink(c->source_output); + pa_source_output_unref(c->source_output); + c->source_output = NULL; + } + + if (c->client) { + pa_client_free(c->client); + c->client = NULL; + } + + if (c->io) { + pa_iochannel_free(c->io); + c->io = NULL; + } + + pa_idxset_remove_by_data(c->protocol->connections, c, NULL); + c->protocol = NULL; + connection_unref(c); +} + +static void connection_free(pa_object *o) { + connection *c = CONNECTION(o); + pa_assert(c); + + if (c->playback.current_memblock) + pa_memblock_unref(c->playback.current_memblock); + + if (c->input_memblockq) + pa_memblockq_free(c->input_memblockq); + if (c->output_memblockq) + pa_memblockq_free(c->output_memblockq); + + pa_xfree(c); +} + +static int do_read(connection *c) { + pa_memchunk chunk; + ssize_t r; + size_t l; + void *p; + size_t space = 0; + + connection_assert_ref(c); + + if (!c->sink_input || (l = (size_t) pa_atomic_load(&c->playback.missing)) <= 0) + return 0; + + if (c->playback.current_memblock) { + + space = pa_memblock_get_length(c->playback.current_memblock) - c->playback.memblock_index; + + if (space <= 0) { + pa_memblock_unref(c->playback.current_memblock); + c->playback.current_memblock = NULL; + } + } + + if (!c->playback.current_memblock) { + pa_assert_se(c->playback.current_memblock = pa_memblock_new(c->protocol->core->mempool, (size_t) -1)); + c->playback.memblock_index = 0; + + space = pa_memblock_get_length(c->playback.current_memblock); + } + + if (l > space) + l = space; + + p = pa_memblock_acquire(c->playback.current_memblock); + r = pa_iochannel_read(c->io, (uint8_t*) p + c->playback.memblock_index, l); + pa_memblock_release(c->playback.current_memblock); + + if (r <= 0) { + + if (r < 0 && (errno == EINTR || errno == EAGAIN)) + return 0; + + pa_log_debug("read(): %s", r == 0 ? "EOF" : pa_cstrerror(errno)); + return -1; + } + + chunk.memblock = c->playback.current_memblock; + chunk.index = c->playback.memblock_index; + chunk.length = (size_t) r; + + c->playback.memblock_index += (size_t) r; + + pa_asyncmsgq_post(c->sink_input->sink->asyncmsgq, PA_MSGOBJECT(c->sink_input), SINK_INPUT_MESSAGE_POST_DATA, NULL, 0, &chunk, NULL); + pa_atomic_sub(&c->playback.missing, (int) r); + + return 0; +} + +static int do_write(connection *c) { + pa_memchunk chunk; + ssize_t r; + void *p; + + connection_assert_ref(c); + + if (!c->source_output) + return 0; + + if (pa_memblockq_peek(c->output_memblockq, &chunk) < 0) { +/* pa_log("peek failed"); */ + return 0; + } + + pa_assert(chunk.memblock); + pa_assert(chunk.length); + + p = pa_memblock_acquire(chunk.memblock); + r = pa_iochannel_write(c->io, (uint8_t*) p+chunk.index, chunk.length); + pa_memblock_release(chunk.memblock); + + pa_memblock_unref(chunk.memblock); + + if (r < 0) { + pa_log("write(): %s", pa_cstrerror(errno)); + return -1; + } + + pa_memblockq_drop(c->output_memblockq, (size_t) r); + + return 1; +} + +static void do_work(connection *c) { + connection_assert_ref(c); + + if (c->dead) + return; + + if (pa_iochannel_is_readable(c->io)) + if (do_read(c) < 0) + goto fail; + + if (!c->sink_input && pa_iochannel_is_hungup(c->io)) + goto fail; + + while (pa_iochannel_is_writable(c->io)) { + int r = do_write(c); + if (r < 0) + goto fail; + if (r == 0) + break; + } + + return; + +fail: + + if (c->sink_input) { + + /* If there is a sink input, we first drain what we already have read before shutting down the connection */ + c->dead = true; + + pa_iochannel_free(c->io); + c->io = NULL; + + pa_asyncmsgq_post(c->sink_input->sink->asyncmsgq, PA_MSGOBJECT(c->sink_input), SINK_INPUT_MESSAGE_DISABLE_PREBUF, NULL, 0, NULL, NULL); + } else + connection_unlink(c); +} + +static int connection_process_msg(pa_msgobject *o, int code, void*userdata, int64_t offset, pa_memchunk *chunk) { + connection *c = CONNECTION(o); + connection_assert_ref(c); + + if (!c->protocol) + return -1; + + switch (code) { + case CONNECTION_MESSAGE_REQUEST_DATA: + do_work(c); + break; + + case CONNECTION_MESSAGE_POST_DATA: +/* pa_log("got data %u", chunk->length); */ + pa_memblockq_push_align(c->output_memblockq, chunk); + do_work(c); + break; + + case CONNECTION_MESSAGE_UNLINK_CONNECTION: + connection_unlink(c); + break; + } + + return 0; +} + +/*** sink_input callbacks ***/ + +/* Called from thread context */ +static int sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk) { + pa_sink_input *i = PA_SINK_INPUT(o); + connection*c; + + pa_sink_input_assert_ref(i); + c = CONNECTION(i->userdata); + connection_assert_ref(c); + + switch (code) { + + case SINK_INPUT_MESSAGE_POST_DATA: { + pa_assert(chunk); + + /* New data from the main loop */ + pa_memblockq_push_align(c->input_memblockq, chunk); + + if (pa_memblockq_is_readable(c->input_memblockq) && c->playback.underrun) { + pa_log_debug("Requesting rewind due to end of underrun."); + pa_sink_input_request_rewind(c->sink_input, 0, false, true, false); + } + +/* pa_log("got data, %u", pa_memblockq_get_length(c->input_memblockq)); */ + + return 0; + } + + case SINK_INPUT_MESSAGE_DISABLE_PREBUF: + pa_memblockq_prebuf_disable(c->input_memblockq); + return 0; + + case PA_SINK_INPUT_MESSAGE_GET_LATENCY: { + pa_usec_t *r = userdata; + + /* The default handler will add in the extra latency added by the resampler.*/ + *r = pa_bytes_to_usec(pa_memblockq_get_length(c->input_memblockq), &c->sink_input->sample_spec); + } + /* Fall through. */ + + default: + return pa_sink_input_process_msg(o, code, userdata, offset, chunk); + } +} + +/* Called from thread context */ +static int sink_input_pop_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) { + connection *c; + + pa_sink_input_assert_ref(i); + c = CONNECTION(i->userdata); + connection_assert_ref(c); + pa_assert(chunk); + + if (pa_memblockq_peek(c->input_memblockq, chunk) < 0) { + + c->playback.underrun = true; + + if (c->dead && pa_sink_input_safe_to_remove(i)) + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(c), CONNECTION_MESSAGE_UNLINK_CONNECTION, NULL, 0, NULL, NULL); + + return -1; + } else { + size_t m; + + chunk->length = PA_MIN(length, chunk->length); + + c->playback.underrun = false; + + pa_memblockq_drop(c->input_memblockq, chunk->length); + m = pa_memblockq_pop_missing(c->input_memblockq); + + if (m > 0) + if (pa_atomic_add(&c->playback.missing, (int) m) <= 0) + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(c), CONNECTION_MESSAGE_REQUEST_DATA, NULL, 0, NULL, NULL); + + return 0; + } +} + +/* Called from thread context */ +static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) { + connection *c; + + pa_sink_input_assert_ref(i); + c = CONNECTION(i->userdata); + connection_assert_ref(c); + + /* If we are in an underrun, then we don't rewind */ + if (i->thread_info.underrun_for > 0) + return; + + pa_memblockq_rewind(c->input_memblockq, nbytes); +} + +/* Called from thread context */ +static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) { + connection *c; + + pa_sink_input_assert_ref(i); + c = CONNECTION(i->userdata); + connection_assert_ref(c); + + pa_memblockq_set_maxrewind(c->input_memblockq, nbytes); +} + +/* Called from main context */ +static void sink_input_kill_cb(pa_sink_input *i) { + pa_sink_input_assert_ref(i); + + connection_unlink(CONNECTION(i->userdata)); +} + +/*** source_output callbacks ***/ + +/* Called from thread context */ +static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk) { + connection *c; + + pa_source_output_assert_ref(o); + c = CONNECTION(o->userdata); + pa_assert(c); + pa_assert(chunk); + + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(c), CONNECTION_MESSAGE_POST_DATA, NULL, 0, chunk, NULL); +} + +/* Called from main context */ +static void source_output_kill_cb(pa_source_output *o) { + pa_source_output_assert_ref(o); + + connection_unlink(CONNECTION(o->userdata)); +} + +/* Called from main context */ +static pa_usec_t source_output_get_latency_cb(pa_source_output *o) { + connection*c; + + pa_source_output_assert_ref(o); + c = CONNECTION(o->userdata); + pa_assert(c); + + return pa_bytes_to_usec(pa_memblockq_get_length(c->output_memblockq), &c->source_output->sample_spec); +} + +/*** client callbacks ***/ + +static void client_kill_cb(pa_client *client) { + connection*c; + + pa_assert(client); + c = CONNECTION(client->userdata); + pa_assert(c); + + connection_unlink(c); +} + +/*** pa_iochannel callbacks ***/ + +static void io_callback(pa_iochannel*io, void *userdata) { + connection *c = CONNECTION(userdata); + + connection_assert_ref(c); + pa_assert(io); + + do_work(c); +} + +/*** socket_server callbacks ***/ + +void pa_simple_protocol_connect(pa_simple_protocol *p, pa_iochannel *io, pa_simple_options *o) { + connection *c = NULL; + char pname[128]; + pa_client_new_data client_data; + + pa_assert(p); + pa_assert(io); + pa_assert(o); + + if (pa_idxset_size(p->connections)+1 > MAX_CONNECTIONS) { + pa_log("Warning! Too many connections (%u), dropping incoming connection.", MAX_CONNECTIONS); + pa_iochannel_free(io); + return; + } + + c = pa_msgobject_new(connection); + c->parent.parent.free = connection_free; + c->parent.process_msg = connection_process_msg; + c->io = io; + pa_iochannel_set_callback(c->io, io_callback, c); + + c->sink_input = NULL; + c->source_output = NULL; + c->input_memblockq = c->output_memblockq = NULL; + c->protocol = p; + c->options = pa_simple_options_ref(o); + c->playback.current_memblock = NULL; + c->playback.memblock_index = 0; + c->dead = false; + c->playback.underrun = true; + pa_atomic_store(&c->playback.missing, 0); + + pa_client_new_data_init(&client_data); + client_data.module = o->module; + client_data.driver = __FILE__; + pa_iochannel_socket_peer_to_string(io, pname, sizeof(pname)); + pa_proplist_setf(client_data.proplist, PA_PROP_APPLICATION_NAME, "Simple client (%s)", pname); + pa_proplist_sets(client_data.proplist, "simple-protocol.peer", pname); + c->client = pa_client_new(p->core, &client_data); + pa_client_new_data_done(&client_data); + + if (!c->client) + goto fail; + + c->client->kill = client_kill_cb; + c->client->userdata = c; + + if (o->playback) { + pa_sink_input_new_data data; + pa_memchunk silence; + size_t l; + pa_sink *sink; + + if (!(sink = pa_namereg_get(c->protocol->core, o->default_sink, PA_NAMEREG_SINK))) { + pa_log("Failed to get sink."); + goto fail; + } + + pa_sink_input_new_data_init(&data); + data.driver = __FILE__; + data.module = o->module; + data.client = c->client; + pa_sink_input_new_data_set_sink(&data, sink, false, true); + pa_proplist_update(data.proplist, PA_UPDATE_MERGE, c->client->proplist); + pa_sink_input_new_data_set_sample_spec(&data, &o->sample_spec); + + pa_sink_input_new(&c->sink_input, p->core, &data); + pa_sink_input_new_data_done(&data); + + if (!c->sink_input) { + pa_log("Failed to create sink input."); + goto fail; + } + + c->sink_input->parent.process_msg = sink_input_process_msg; + c->sink_input->pop = sink_input_pop_cb; + c->sink_input->process_rewind = sink_input_process_rewind_cb; + c->sink_input->update_max_rewind = sink_input_update_max_rewind_cb; + c->sink_input->kill = sink_input_kill_cb; + c->sink_input->userdata = c; + + pa_sink_input_set_requested_latency(c->sink_input, DEFAULT_SINK_LATENCY); + + l = (size_t) ((double) pa_bytes_per_second(&o->sample_spec)*PLAYBACK_BUFFER_SECONDS); + pa_sink_input_get_silence(c->sink_input, &silence); + c->input_memblockq = pa_memblockq_new( + "simple protocol connection input_memblockq", + 0, + l, + l, + &o->sample_spec, + (size_t) -1, + l/PLAYBACK_BUFFER_FRAGMENTS, + 0, + &silence); + pa_memblock_unref(silence.memblock); + + pa_iochannel_socket_set_rcvbuf(io, l); + + pa_atomic_store(&c->playback.missing, (int) pa_memblockq_pop_missing(c->input_memblockq)); + + pa_sink_input_put(c->sink_input); + } + + if (o->record) { + pa_source_output_new_data data; + size_t l; + pa_source *source; + + if (!(source = pa_namereg_get(c->protocol->core, o->default_source, PA_NAMEREG_SOURCE))) { + pa_log("Failed to get source."); + goto fail; + } + + pa_source_output_new_data_init(&data); + data.driver = __FILE__; + data.module = o->module; + data.client = c->client; + pa_source_output_new_data_set_source(&data, source, false, true); + pa_proplist_update(data.proplist, PA_UPDATE_MERGE, c->client->proplist); + pa_source_output_new_data_set_sample_spec(&data, &o->sample_spec); + + pa_source_output_new(&c->source_output, p->core, &data); + pa_source_output_new_data_done(&data); + + if (!c->source_output) { + pa_log("Failed to create source output."); + goto fail; + } + c->source_output->push = source_output_push_cb; + c->source_output->kill = source_output_kill_cb; + c->source_output->get_latency = source_output_get_latency_cb; + c->source_output->userdata = c; + + pa_source_output_set_requested_latency(c->source_output, DEFAULT_SOURCE_LATENCY); + + l = (size_t) (pa_bytes_per_second(&o->sample_spec)*RECORD_BUFFER_SECONDS); + c->output_memblockq = pa_memblockq_new( + "simple protocol connection output_memblockq", + 0, + l, + 0, + &o->sample_spec, + 1, + 0, + 0, + NULL); + pa_iochannel_socket_set_sndbuf(io, l); + + pa_source_output_put(c->source_output); + } + + pa_idxset_put(p->connections, c, NULL); + + return; + +fail: + connection_unlink(c); +} + +void pa_simple_protocol_disconnect(pa_simple_protocol *p, pa_module *m) { + connection *c; + void *state = NULL; + + pa_assert(p); + pa_assert(m); + + while ((c = pa_idxset_iterate(p->connections, &state, NULL))) + if (c->options->module == m) + connection_unlink(c); +} + +static pa_simple_protocol* simple_protocol_new(pa_core *c) { + pa_simple_protocol *p; + + pa_assert(c); + + p = pa_xnew(pa_simple_protocol, 1); + PA_REFCNT_INIT(p); + p->core = c; + p->connections = pa_idxset_new(NULL, NULL); + + pa_assert_se(pa_shared_set(c, "simple-protocol", p) >= 0); + + return p; +} + +pa_simple_protocol* pa_simple_protocol_get(pa_core *c) { + pa_simple_protocol *p; + + if ((p = pa_shared_get(c, "simple-protocol"))) + return pa_simple_protocol_ref(p); + + return simple_protocol_new(c); +} + +pa_simple_protocol* pa_simple_protocol_ref(pa_simple_protocol *p) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) >= 1); + + PA_REFCNT_INC(p); + + return p; +} + +void pa_simple_protocol_unref(pa_simple_protocol *p) { + connection *c; + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) >= 1); + + if (PA_REFCNT_DEC(p) > 0) + return; + + while ((c = pa_idxset_first(p->connections, NULL))) + connection_unlink(c); + + pa_idxset_free(p->connections, NULL); + + pa_assert_se(pa_shared_remove(p->core, "simple-protocol") >= 0); + + pa_xfree(p); +} + +pa_simple_options* pa_simple_options_new(void) { + pa_simple_options *o; + + o = pa_xnew0(pa_simple_options, 1); + PA_REFCNT_INIT(o); + + o->record = false; + o->playback = true; + + return o; +} + +pa_simple_options* pa_simple_options_ref(pa_simple_options *o) { + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + PA_REFCNT_INC(o); + + return o; +} + +void pa_simple_options_unref(pa_simple_options *o) { + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (PA_REFCNT_DEC(o) > 0) + return; + + pa_xfree(o->default_sink); + pa_xfree(o->default_source); + + pa_xfree(o); +} + +int pa_simple_options_parse(pa_simple_options *o, pa_core *c, pa_modargs *ma) { + bool enabled; + + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + pa_assert(ma); + + o->sample_spec = c->default_sample_spec; + if (pa_modargs_get_sample_spec_and_channel_map(ma, &o->sample_spec, &o->channel_map, PA_CHANNEL_MAP_DEFAULT) < 0) { + pa_log("Failed to parse sample type specification."); + return -1; + } + + pa_xfree(o->default_source); + o->default_source = pa_xstrdup(pa_modargs_get_value(ma, "source", NULL)); + + pa_xfree(o->default_sink); + o->default_sink = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL)); + + enabled = o->record; + if (pa_modargs_get_value_boolean(ma, "record", &enabled) < 0) { + pa_log("record= expects a boolean argument."); + return -1; + } + o->record = enabled; + + enabled = o->playback; + if (pa_modargs_get_value_boolean(ma, "playback", &enabled) < 0) { + pa_log("playback= expects a boolean argument."); + return -1; + } + o->playback = enabled; + + if (!o->playback && !o->record) { + pa_log("neither playback nor recording enabled for protocol."); + return -1; + } + + return 0; +} diff --git a/src/pulsecore/protocol-simple.h b/src/pulsecore/protocol-simple.h new file mode 100644 index 0000000..0a72cd7 --- /dev/null +++ b/src/pulsecore/protocol-simple.h @@ -0,0 +1,55 @@ +#ifndef fooprotocolsimplehfoo +#define fooprotocolsimplehfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulsecore/socket-server.h> +#include <pulsecore/module.h> +#include <pulsecore/core.h> +#include <pulsecore/modargs.h> + +typedef struct pa_simple_protocol pa_simple_protocol; + +typedef struct pa_simple_options { + PA_REFCNT_DECLARE; + + pa_module *module; + + char *default_sink, *default_source; + + pa_sample_spec sample_spec; + pa_channel_map channel_map; + + bool record:1; + bool playback:1; +} pa_simple_options; + +pa_simple_protocol* pa_simple_protocol_get(pa_core*core); +pa_simple_protocol* pa_simple_protocol_ref(pa_simple_protocol *p); +void pa_simple_protocol_unref(pa_simple_protocol *p); +void pa_simple_protocol_connect(pa_simple_protocol *p, pa_iochannel *io, pa_simple_options *o); +void pa_simple_protocol_disconnect(pa_simple_protocol *p, pa_module *m); + +pa_simple_options* pa_simple_options_new(void); +pa_simple_options* pa_simple_options_ref(pa_simple_options *o); +void pa_simple_options_unref(pa_simple_options *o); +int pa_simple_options_parse(pa_simple_options *o, pa_core *c, pa_modargs *ma); + +#endif diff --git a/src/pulsecore/pstream-util.c b/src/pulsecore/pstream-util.c new file mode 100644 index 0000000..d0d6c66 --- /dev/null +++ b/src/pulsecore/pstream-util.c @@ -0,0 +1,198 @@ +/*** + 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulsecore/core-util.h> +#include <pulsecore/macro.h> +#include <pulsecore/native-common.h> +#include <pulsecore/pstream.h> +#include <pulsecore/refcnt.h> +#include <pulse/xmalloc.h> + +#include "pstream-util.h" + +static void pa_pstream_send_tagstruct_with_ancil_data(pa_pstream *p, pa_tagstruct *t, pa_cmsg_ancil_data *ancil_data) { + size_t length; + const uint8_t *data; + pa_packet *packet; + + pa_assert(p); + pa_assert(t); + + pa_assert_se(data = pa_tagstruct_data(t, &length)); + pa_assert_se(packet = pa_packet_new_data(data, length)); + pa_tagstruct_free(t); + + pa_pstream_send_packet(p, packet, ancil_data); + pa_packet_unref(packet); +} + +#ifdef HAVE_CREDS + +void pa_pstream_send_tagstruct_with_creds(pa_pstream *p, pa_tagstruct *t, const pa_creds *creds) { + if (creds) { + pa_cmsg_ancil_data a; + + a.nfd = 0; + a.creds_valid = true; + a.creds = *creds; + pa_pstream_send_tagstruct_with_ancil_data(p, t, &a); + } + else + pa_pstream_send_tagstruct_with_ancil_data(p, t, NULL); +} + +/* @close_fds: If set then the pstreams code, after invoking a sendmsg(), + * will close all passed fds. + * + * Such fds cannot be closed here as this might lead to freeing them + * before they're actually passed to the other end. The internally-used + * pa_pstream_send_packet() does not do any actual writes and just + * defers write events over the pstream. */ +void pa_pstream_send_tagstruct_with_fds(pa_pstream *p, pa_tagstruct *t, int nfd, const int *fds, + bool close_fds) { + if (nfd > 0) { + pa_cmsg_ancil_data a; + + a.nfd = nfd; + a.creds_valid = false; + a.close_fds_on_cleanup = close_fds; + pa_assert(nfd <= MAX_ANCIL_DATA_FDS); + memcpy(a.fds, fds, sizeof(int) * nfd); + pa_pstream_send_tagstruct_with_ancil_data(p, t, &a); + } + else + pa_pstream_send_tagstruct_with_ancil_data(p, t, NULL); +} + +#else + +void pa_pstream_send_tagstruct_with_creds(pa_pstream *p, pa_tagstruct *t, const pa_creds *creds) { + pa_pstream_send_tagstruct_with_ancil_data(p, t, NULL); +} + +void PA_GCC_NORETURN pa_pstream_send_tagstruct_with_fds(pa_pstream *p, pa_tagstruct *t, int nfd, const int *fds, + bool close_fds) { + pa_assert_not_reached(); +} + +#endif + +void pa_pstream_send_error(pa_pstream *p, uint32_t tag, uint32_t error) { + pa_tagstruct *t; + + pa_assert_se(t = pa_tagstruct_new()); + pa_tagstruct_putu32(t, PA_COMMAND_ERROR); + pa_tagstruct_putu32(t, tag); + pa_tagstruct_putu32(t, error); + pa_pstream_send_tagstruct(p, t); +} + +void pa_pstream_send_simple_ack(pa_pstream *p, uint32_t tag) { + pa_tagstruct *t; + + pa_assert_se(t = pa_tagstruct_new()); + pa_tagstruct_putu32(t, PA_COMMAND_REPLY); + pa_tagstruct_putu32(t, tag); + pa_pstream_send_tagstruct(p, t); +} + +/* Before sending blocks from a memfd-backed pool over the pipe, we + * must call this method first. + * + * This is needed to transfer memfd blocks without passing their fd + * every time, thus minimizing overhead and avoiding fd leaks. + * + * On registration a packet is sent with the memfd fd as ancil data; + * such packet has an ID that uniquely identifies the pool's memfd + * region. Upon arrival the other end creates a permanent mapping + * between that ID and the passed memfd memory area. + * + * By doing so, we won't need to reference the pool's memfd fd any + * further - just its ID. Both endpoints can then close their fds. */ +int pa_pstream_register_memfd_mempool(pa_pstream *p, pa_mempool *pool, const char **fail_reason) { +#if defined(HAVE_CREDS) && defined(HAVE_MEMFD) + unsigned shm_id; + int memfd_fd, ret = -1; + pa_tagstruct *t; + bool per_client_mempool; + + pa_assert(p); + pa_assert(fail_reason); + + *fail_reason = NULL; + per_client_mempool = pa_mempool_is_per_client(pool); + + pa_pstream_ref(p); + + if (!pa_mempool_is_shared(pool)) { + *fail_reason = "mempool is not shared"; + goto finish; + } + + if (!pa_mempool_is_memfd_backed(pool)) { + *fail_reason = "mempool is not memfd-backed"; + goto finish; + } + + if (pa_mempool_get_shm_id(pool, &shm_id)) { + *fail_reason = "could not extract pool SHM ID"; + goto finish; + } + + if (!pa_pstream_get_memfd(p)) { + *fail_reason = "pipe does not support memfd transport"; + goto finish; + } + + memfd_fd = (per_client_mempool) ? pa_mempool_take_memfd_fd(pool) : + pa_mempool_get_memfd_fd(pool); + + /* Note! For per-client mempools we've taken ownership of the memfd + * fd, and we're thus the sole code path responsible for closing it. + * In case of any failure, it MUST be closed. */ + + if (pa_pstream_attach_memfd_shmid(p, shm_id, memfd_fd)) { + *fail_reason = "could not attach memfd SHM ID to pipe"; + + if (per_client_mempool) + pa_assert_se(pa_close(memfd_fd) == 0); + goto finish; + } + + t = pa_tagstruct_new(); + pa_tagstruct_putu32(t, PA_COMMAND_REGISTER_MEMFD_SHMID); + pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */ + pa_tagstruct_putu32(t, shm_id); + pa_pstream_send_tagstruct_with_fds(p, t, 1, &memfd_fd, per_client_mempool); + + ret = 0; +finish: + pa_pstream_unref(p); + return ret; + +#else + pa_assert(fail_reason); + *fail_reason = "memfd support not compiled in"; + return -1; +#endif +} diff --git a/src/pulsecore/pstream-util.h b/src/pulsecore/pstream-util.h new file mode 100644 index 0000000..1191d48 --- /dev/null +++ b/src/pulsecore/pstream-util.h @@ -0,0 +1,39 @@ +#ifndef foopstreamutilhfoo +#define foopstreamutilhfoo + +/*** + 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <inttypes.h> +#include <pulsecore/pstream.h> +#include <pulsecore/tagstruct.h> +#include <pulsecore/creds.h> + +/* The tagstruct is freed!*/ +void pa_pstream_send_tagstruct_with_creds(pa_pstream *p, pa_tagstruct *t, const pa_creds *creds); +void pa_pstream_send_tagstruct_with_fds(pa_pstream *p, pa_tagstruct *t, int nfd, const int *fds, bool close_fds); + +#define pa_pstream_send_tagstruct(p, t) pa_pstream_send_tagstruct_with_creds((p), (t), NULL) + +void pa_pstream_send_error(pa_pstream *p, uint32_t tag, uint32_t error); +void pa_pstream_send_simple_ack(pa_pstream *p, uint32_t tag); + +int pa_pstream_register_memfd_mempool(pa_pstream *p, pa_mempool *pool, const char **fail_reason); + +#endif diff --git a/src/pulsecore/pstream.c b/src/pulsecore/pstream.c new file mode 100644 index 0000000..eb70508 --- /dev/null +++ b/src/pulsecore/pstream.c @@ -0,0 +1,1290 @@ +/*** + 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif + +#include <pulse/xmalloc.h> + +#include <pulsecore/idxset.h> +#include <pulsecore/socket.h> +#include <pulsecore/queue.h> +#include <pulsecore/log.h> +#include <pulsecore/creds.h> +#include <pulsecore/refcnt.h> +#include <pulsecore/flist.h> +#include <pulsecore/macro.h> + +#include "pstream.h" + +/* We piggyback information if audio data blocks are stored in SHM on the seek mode */ +#define PA_FLAG_SHMDATA 0x80000000LU +#define PA_FLAG_SHMDATA_MEMFD_BLOCK 0x20000000LU +#define PA_FLAG_SHMRELEASE 0x40000000LU +#define PA_FLAG_SHMREVOKE 0xC0000000LU +#define PA_FLAG_SHMMASK 0xFF000000LU +#define PA_FLAG_SEEKMASK 0x000000FFLU +#define PA_FLAG_SHMWRITABLE 0x00800000LU + +/* The sequence descriptor header consists of 5 32bit integers: */ +enum { + PA_PSTREAM_DESCRIPTOR_LENGTH, + PA_PSTREAM_DESCRIPTOR_CHANNEL, + PA_PSTREAM_DESCRIPTOR_OFFSET_HI, + PA_PSTREAM_DESCRIPTOR_OFFSET_LO, + PA_PSTREAM_DESCRIPTOR_FLAGS, + PA_PSTREAM_DESCRIPTOR_MAX +}; + +/* If we have an SHM block, this info follows the descriptor */ +enum { + PA_PSTREAM_SHM_BLOCKID, + PA_PSTREAM_SHM_SHMID, + PA_PSTREAM_SHM_INDEX, + PA_PSTREAM_SHM_LENGTH, + PA_PSTREAM_SHM_MAX +}; + +typedef uint32_t pa_pstream_descriptor[PA_PSTREAM_DESCRIPTOR_MAX]; + +#define PA_PSTREAM_DESCRIPTOR_SIZE (PA_PSTREAM_DESCRIPTOR_MAX*sizeof(uint32_t)) + +#define MINIBUF_SIZE (256) + +/* To allow uploading a single sample in one frame, this value should be the + * same size (16 MB) as PA_SCACHE_ENTRY_SIZE_MAX from pulsecore/core-scache.h. + */ +#define FRAME_SIZE_MAX_ALLOW (1024*1024*16) + +PA_STATIC_FLIST_DECLARE(items, 0, pa_xfree); + +struct item_info { + enum { + PA_PSTREAM_ITEM_PACKET, + PA_PSTREAM_ITEM_MEMBLOCK, + PA_PSTREAM_ITEM_SHMRELEASE, + PA_PSTREAM_ITEM_SHMREVOKE + } type; + + /* packet info */ + pa_packet *packet; +#ifdef HAVE_CREDS + bool with_ancil_data; + pa_cmsg_ancil_data ancil_data; +#endif + + /* memblock info */ + pa_memchunk chunk; + uint32_t channel; + int64_t offset; + pa_seek_mode_t seek_mode; + + /* release/revoke info */ + uint32_t block_id; +}; + +struct pstream_read { + pa_pstream_descriptor descriptor; + pa_memblock *memblock; + pa_packet *packet; + uint32_t shm_info[PA_PSTREAM_SHM_MAX]; + void *data; + size_t index; +}; + +struct pa_pstream { + PA_REFCNT_DECLARE; + + pa_mainloop_api *mainloop; + pa_defer_event *defer_event; + pa_iochannel *io; + pa_srbchannel *srb, *srbpending; + bool is_srbpending; + + pa_queue *send_queue; + + bool dead; + + struct { + union { + uint8_t minibuf[MINIBUF_SIZE]; + pa_pstream_descriptor descriptor; + }; + struct item_info* current; + void *data; + size_t index; + int minibuf_validsize; + pa_memchunk memchunk; + } write; + + struct pstream_read readio, readsrb; + + /* @use_shm: beside copying the full audio data to the other + * PA end, this pipe supports just sending references of the + * same audio data blocks if they reside in a SHM pool. + * + * @use_memfd: pipe supports sending SHM memfd block references + * + * @registered_memfd_ids: registered memfd pools SHM IDs. Check + * pa_pstream_register_memfd_mempool() for more information. */ + bool use_shm, use_memfd; + pa_idxset *registered_memfd_ids; + + pa_memimport *import; + pa_memexport *export; + + pa_pstream_packet_cb_t receive_packet_callback; + void *receive_packet_callback_userdata; + + pa_pstream_memblock_cb_t receive_memblock_callback; + void *receive_memblock_callback_userdata; + + pa_pstream_notify_cb_t drain_callback; + void *drain_callback_userdata; + + pa_pstream_notify_cb_t die_callback; + void *die_callback_userdata; + + pa_pstream_block_id_cb_t revoke_callback; + void *revoke_callback_userdata; + + pa_pstream_block_id_cb_t release_callback; + void *release_callback_userdata; + + pa_mempool *mempool; + +#ifdef HAVE_CREDS + pa_cmsg_ancil_data read_ancil_data, *write_ancil_data; + bool send_ancil_data_now; +#endif +}; + +#ifdef HAVE_CREDS +/* + * memfd-backed SHM pools blocks transfer occur without passing the pool's + * fd every time, thus minimizing overhead and avoiding fd leaks. A + * REGISTER_MEMFD_SHMID command is sent, with the pool's memfd fd, very early + * on. This command has an ID that uniquely identifies the pool in question. + * Further pool's block references can then be exclusively done using such ID; + * the fd can be safely closed – on both ends – afterwards. + * + * On the sending side of this command, we want to close the passed fds + * directly after being sent. Meanwhile we're only allowed to asynchronously + * schedule packet writes to the pstream, so the job of closing passed fds is + * left to the pstream's actual writing function do_write(): it knows the + * exact point in time where the fds are passed to the other end through + * iochannels and the sendmsg() system call. + * + * Nonetheless not all code paths in the system desire their socket-passed + * fds to be closed after the send. srbchannel needs the passed fds to still + * be open for further communication. System-wide global memfd-backed pools + * also require the passed fd to be open: they pass the same fd, with the same + * ID registration mechanism, for each newly connected client to the system. + * + * So from all of the above, never close the ancillary fds by your own and + * always call below method instead. It takes care of closing the passed fds + * _only if allowed_ by the code paths that originally created them to do so. + * Moreover, it is multiple-invocations safe: failure handlers can, and + * should, call it for passed fds cleanup without worrying too much about + * the system state. + */ +void pa_cmsg_ancil_data_close_fds(struct pa_cmsg_ancil_data *ancil) { + if (ancil && ancil->nfd > 0 && ancil->close_fds_on_cleanup) { + int i; + + pa_assert(ancil->nfd <= MAX_ANCIL_DATA_FDS); + + for (i = 0; i < ancil->nfd; i++) + if (ancil->fds[i] != -1) { + pa_assert_se(pa_close(ancil->fds[i]) == 0); + ancil->fds[i] = -1; + } + + ancil->nfd = 0; + ancil->close_fds_on_cleanup = false; + } +} +#endif + +static int do_write(pa_pstream *p); +static int do_read(pa_pstream *p, struct pstream_read *re); + +static void do_pstream_read_write(pa_pstream *p) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + + pa_pstream_ref(p); + + p->mainloop->defer_enable(p->defer_event, 0); + + if (!p->dead && p->srb) { + int r = 0; + + if(do_write(p) < 0) + goto fail; + + while (!p->dead && r == 0) { + r = do_read(p, &p->readsrb); + if (r < 0) + goto fail; + } + } + + if (!p->dead && pa_iochannel_is_readable(p->io)) { + if (do_read(p, &p->readio) < 0) + goto fail; + } else if (!p->dead && pa_iochannel_is_hungup(p->io)) + goto fail; + + while (!p->dead && pa_iochannel_is_writable(p->io)) { + int r = do_write(p); + if (r < 0) + goto fail; + if (r == 0) + break; + } + + pa_pstream_unref(p); + return; + +fail: + + if (p->die_callback) + p->die_callback(p, p->die_callback_userdata); + + pa_pstream_unlink(p); + pa_pstream_unref(p); +} + +static bool srb_callback(pa_srbchannel *srb, void *userdata) { + bool b; + pa_pstream *p = userdata; + + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + pa_assert(p->srb == srb); + + pa_pstream_ref(p); + + do_pstream_read_write(p); + + /* If either pstream or the srb is going away, return false. + We need to check this before p is destroyed. */ + b = (PA_REFCNT_VALUE(p) > 1) && (p->srb == srb); + pa_pstream_unref(p); + + return b; +} + +static void io_callback(pa_iochannel*io, void *userdata) { + pa_pstream *p = userdata; + + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + pa_assert(p->io == io); + + do_pstream_read_write(p); +} + +static void defer_callback(pa_mainloop_api *m, pa_defer_event *e, void*userdata) { + pa_pstream *p = userdata; + + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + pa_assert(p->defer_event == e); + pa_assert(p->mainloop == m); + + do_pstream_read_write(p); +} + +static void memimport_release_cb(pa_memimport *i, uint32_t block_id, void *userdata); + +pa_pstream *pa_pstream_new(pa_mainloop_api *m, pa_iochannel *io, pa_mempool *pool) { + pa_pstream *p; + + pa_assert(m); + pa_assert(io); + pa_assert(pool); + + p = pa_xnew0(pa_pstream, 1); + PA_REFCNT_INIT(p); + p->io = io; + pa_iochannel_set_callback(io, io_callback, p); + + p->mainloop = m; + p->defer_event = m->defer_new(m, defer_callback, p); + m->defer_enable(p->defer_event, 0); + + p->send_queue = pa_queue_new(); + + p->mempool = pool; + + /* We do importing unconditionally */ + p->import = pa_memimport_new(p->mempool, memimport_release_cb, p); + + pa_iochannel_socket_set_rcvbuf(io, pa_mempool_block_size_max(p->mempool)); + pa_iochannel_socket_set_sndbuf(io, pa_mempool_block_size_max(p->mempool)); + + return p; +} + +/* Attach memfd<->SHM_ID mapping to given pstream and its memimport. + * Check pa_pstream_register_memfd_mempool() for further info. + * + * Caller owns the passed @memfd_fd and must close it down when appropriate. */ +int pa_pstream_attach_memfd_shmid(pa_pstream *p, unsigned shm_id, int memfd_fd) { + int err = -1; + + pa_assert(memfd_fd != -1); + + if (!p->use_memfd) { + pa_log_warn("Received memfd ID registration request over a pipe " + "that does not support memfds"); + return err; + } + + if (pa_idxset_get_by_data(p->registered_memfd_ids, PA_UINT32_TO_PTR(shm_id), NULL)) { + pa_log_warn("previously registered memfd SHM ID = %u", shm_id); + return err; + } + + if (pa_memimport_attach_memfd(p->import, shm_id, memfd_fd, true)) { + pa_log("Failed to create permanent mapping for memfd region with ID = %u", shm_id); + return err; + } + + pa_assert_se(pa_idxset_put(p->registered_memfd_ids, PA_UINT32_TO_PTR(shm_id), NULL) == 0); + return 0; +} + +static void item_free(void *item) { + struct item_info *i = item; + pa_assert(i); + + if (i->type == PA_PSTREAM_ITEM_MEMBLOCK) { + pa_assert(i->chunk.memblock); + pa_memblock_unref(i->chunk.memblock); + } else if (i->type == PA_PSTREAM_ITEM_PACKET) { + pa_assert(i->packet); + pa_packet_unref(i->packet); + } + +#ifdef HAVE_CREDS + /* On error recovery paths, there might be lingering items + * on the pstream send queue and they are usually freed with + * a call to 'pa_queue_free(p->send_queue, item_free)'. Make + * sure we do not leak any fds in that case! */ + if (i->with_ancil_data) + pa_cmsg_ancil_data_close_fds(&i->ancil_data); +#endif + + if (pa_flist_push(PA_STATIC_FLIST_GET(items), i) < 0) + pa_xfree(i); +} + +static void pstream_free(pa_pstream *p) { + pa_assert(p); + + pa_pstream_unlink(p); + + pa_queue_free(p->send_queue, item_free); + + if (p->write.current) + item_free(p->write.current); + + if (p->write.memchunk.memblock) + pa_memblock_unref(p->write.memchunk.memblock); + + if (p->readsrb.memblock) + pa_memblock_unref(p->readsrb.memblock); + + if (p->readsrb.packet) + pa_packet_unref(p->readsrb.packet); + + if (p->readio.memblock) + pa_memblock_unref(p->readio.memblock); + + if (p->readio.packet) + pa_packet_unref(p->readio.packet); + + if (p->registered_memfd_ids) + pa_idxset_free(p->registered_memfd_ids, NULL); + + pa_xfree(p); +} + +void pa_pstream_send_packet(pa_pstream*p, pa_packet *packet, pa_cmsg_ancil_data *ancil_data) { + struct item_info *i; + + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + pa_assert(packet); + + if (p->dead) { +#ifdef HAVE_CREDS + pa_cmsg_ancil_data_close_fds(ancil_data); +#endif + return; + } + + if (!(i = pa_flist_pop(PA_STATIC_FLIST_GET(items)))) + i = pa_xnew(struct item_info, 1); + + i->type = PA_PSTREAM_ITEM_PACKET; + i->packet = pa_packet_ref(packet); + +#ifdef HAVE_CREDS + if ((i->with_ancil_data = !!ancil_data)) { + i->ancil_data = *ancil_data; + if (ancil_data->creds_valid) + pa_assert(ancil_data->nfd == 0); + else + pa_assert(ancil_data->nfd > 0); + } +#endif + + pa_queue_push(p->send_queue, i); + + p->mainloop->defer_enable(p->defer_event, 1); +} + +void pa_pstream_send_memblock(pa_pstream*p, uint32_t channel, int64_t offset, pa_seek_mode_t seek_mode, const pa_memchunk *chunk) { + size_t length, idx; + size_t bsm; + + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + pa_assert(channel != (uint32_t) -1); + pa_assert(chunk); + + if (p->dead) + return; + + idx = 0; + length = chunk->length; + + bsm = pa_mempool_block_size_max(p->mempool); + + while (length > 0) { + struct item_info *i; + size_t n; + + if (!(i = pa_flist_pop(PA_STATIC_FLIST_GET(items)))) + i = pa_xnew(struct item_info, 1); + i->type = PA_PSTREAM_ITEM_MEMBLOCK; + + n = PA_MIN(length, bsm); + i->chunk.index = chunk->index + idx; + i->chunk.length = n; + i->chunk.memblock = pa_memblock_ref(chunk->memblock); + + i->channel = channel; + i->offset = offset; + i->seek_mode = seek_mode; +#ifdef HAVE_CREDS + i->with_ancil_data = false; +#endif + + pa_queue_push(p->send_queue, i); + + idx += n; + length -= n; + } + + p->mainloop->defer_enable(p->defer_event, 1); +} + +void pa_pstream_send_release(pa_pstream *p, uint32_t block_id) { + struct item_info *item; + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + + if (p->dead) + return; + +/* pa_log("Releasing block %u", block_id); */ + + if (!(item = pa_flist_pop(PA_STATIC_FLIST_GET(items)))) + item = pa_xnew(struct item_info, 1); + item->type = PA_PSTREAM_ITEM_SHMRELEASE; + item->block_id = block_id; +#ifdef HAVE_CREDS + item->with_ancil_data = false; +#endif + + pa_queue_push(p->send_queue, item); + p->mainloop->defer_enable(p->defer_event, 1); +} + +/* might be called from thread context */ +static void memimport_release_cb(pa_memimport *i, uint32_t block_id, void *userdata) { + pa_pstream *p = userdata; + + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + + if (p->dead) + return; + + if (p->release_callback) + p->release_callback(p, block_id, p->release_callback_userdata); + else + pa_pstream_send_release(p, block_id); +} + +void pa_pstream_send_revoke(pa_pstream *p, uint32_t block_id) { + struct item_info *item; + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + + if (p->dead) + return; +/* pa_log("Revoking block %u", block_id); */ + + if (!(item = pa_flist_pop(PA_STATIC_FLIST_GET(items)))) + item = pa_xnew(struct item_info, 1); + item->type = PA_PSTREAM_ITEM_SHMREVOKE; + item->block_id = block_id; +#ifdef HAVE_CREDS + item->with_ancil_data = false; +#endif + + pa_queue_push(p->send_queue, item); + p->mainloop->defer_enable(p->defer_event, 1); +} + +/* might be called from thread context */ +static void memexport_revoke_cb(pa_memexport *e, uint32_t block_id, void *userdata) { + pa_pstream *p = userdata; + + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + + if (p->revoke_callback) + p->revoke_callback(p, block_id, p->revoke_callback_userdata); + else + pa_pstream_send_revoke(p, block_id); +} + +static void prepare_next_write_item(pa_pstream *p) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + + p->write.current = pa_queue_pop(p->send_queue); + + if (!p->write.current) + return; + p->write.index = 0; + p->write.data = NULL; + p->write.minibuf_validsize = 0; + pa_memchunk_reset(&p->write.memchunk); + + p->write.descriptor[PA_PSTREAM_DESCRIPTOR_LENGTH] = 0; + p->write.descriptor[PA_PSTREAM_DESCRIPTOR_CHANNEL] = htonl((uint32_t) -1); + p->write.descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_HI] = 0; + p->write.descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_LO] = 0; + p->write.descriptor[PA_PSTREAM_DESCRIPTOR_FLAGS] = 0; + + if (p->write.current->type == PA_PSTREAM_ITEM_PACKET) { + size_t plen; + + pa_assert(p->write.current->packet); + + p->write.data = (void *) pa_packet_data(p->write.current->packet, &plen); + p->write.descriptor[PA_PSTREAM_DESCRIPTOR_LENGTH] = htonl((uint32_t) plen); + + if (plen <= MINIBUF_SIZE - PA_PSTREAM_DESCRIPTOR_SIZE) { + memcpy(&p->write.minibuf[PA_PSTREAM_DESCRIPTOR_SIZE], p->write.data, plen); + p->write.minibuf_validsize = PA_PSTREAM_DESCRIPTOR_SIZE + plen; + } + + } else if (p->write.current->type == PA_PSTREAM_ITEM_SHMRELEASE) { + + p->write.descriptor[PA_PSTREAM_DESCRIPTOR_FLAGS] = htonl(PA_FLAG_SHMRELEASE); + p->write.descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_HI] = htonl(p->write.current->block_id); + + } else if (p->write.current->type == PA_PSTREAM_ITEM_SHMREVOKE) { + + p->write.descriptor[PA_PSTREAM_DESCRIPTOR_FLAGS] = htonl(PA_FLAG_SHMREVOKE); + p->write.descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_HI] = htonl(p->write.current->block_id); + + } else { + uint32_t flags; + bool send_payload = true; + + pa_assert(p->write.current->type == PA_PSTREAM_ITEM_MEMBLOCK); + pa_assert(p->write.current->chunk.memblock); + + p->write.descriptor[PA_PSTREAM_DESCRIPTOR_CHANNEL] = htonl(p->write.current->channel); + p->write.descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_HI] = htonl((uint32_t) (((uint64_t) p->write.current->offset) >> 32)); + p->write.descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_LO] = htonl((uint32_t) ((uint64_t) p->write.current->offset)); + + flags = (uint32_t) (p->write.current->seek_mode & PA_FLAG_SEEKMASK); + + if (p->use_shm) { + pa_mem_type_t type; + uint32_t block_id, shm_id; + size_t offset, length; + uint32_t *shm_info = (uint32_t *) &p->write.minibuf[PA_PSTREAM_DESCRIPTOR_SIZE]; + size_t shm_size = sizeof(uint32_t) * PA_PSTREAM_SHM_MAX; + pa_mempool *current_pool = pa_memblock_get_pool(p->write.current->chunk.memblock); + pa_memexport *current_export; + + if (p->mempool == current_pool) + pa_assert_se(current_export = p->export); + else + pa_assert_se(current_export = pa_memexport_new(current_pool, memexport_revoke_cb, p)); + + if (pa_memexport_put(current_export, + p->write.current->chunk.memblock, + &type, + &block_id, + &shm_id, + &offset, + &length) >= 0) { + + if (type == PA_MEM_TYPE_SHARED_POSIX) + send_payload = false; + + if (type == PA_MEM_TYPE_SHARED_MEMFD && p->use_memfd) { + if (pa_idxset_get_by_data(p->registered_memfd_ids, PA_UINT32_TO_PTR(shm_id), NULL)) { + flags |= PA_FLAG_SHMDATA_MEMFD_BLOCK; + send_payload = false; + } else { + if (pa_log_ratelimit(PA_LOG_ERROR)) { + pa_log("Cannot send block reference with non-registered memfd ID = %u", shm_id); + pa_log("Fallig back to copying full block data over socket"); + } + } + } + + if (send_payload) { + pa_assert_se(pa_memexport_process_release(current_export, block_id) == 0); + } else { + flags |= PA_FLAG_SHMDATA; + if (pa_mempool_is_remote_writable(current_pool)) + flags |= PA_FLAG_SHMWRITABLE; + + shm_info[PA_PSTREAM_SHM_BLOCKID] = htonl(block_id); + shm_info[PA_PSTREAM_SHM_SHMID] = htonl(shm_id); + shm_info[PA_PSTREAM_SHM_INDEX] = htonl((uint32_t) (offset + p->write.current->chunk.index)); + shm_info[PA_PSTREAM_SHM_LENGTH] = htonl((uint32_t) p->write.current->chunk.length); + + p->write.descriptor[PA_PSTREAM_DESCRIPTOR_LENGTH] = htonl(shm_size); + p->write.minibuf_validsize = PA_PSTREAM_DESCRIPTOR_SIZE + shm_size; + } + } +/* else */ +/* FIXME: Avoid memexport slot leaks. Call pa_memexport_process_release() */ +/* pa_log_warn("Failed to export memory block."); */ + + if (current_export != p->export) + pa_memexport_free(current_export); + pa_mempool_unref(current_pool); + } + + if (send_payload) { + p->write.descriptor[PA_PSTREAM_DESCRIPTOR_LENGTH] = htonl((uint32_t) p->write.current->chunk.length); + p->write.memchunk = p->write.current->chunk; + pa_memblock_ref(p->write.memchunk.memblock); + } + + p->write.descriptor[PA_PSTREAM_DESCRIPTOR_FLAGS] = htonl(flags); + } + +#ifdef HAVE_CREDS + if ((p->send_ancil_data_now = p->write.current->with_ancil_data)) + p->write_ancil_data = &p->write.current->ancil_data; +#endif +} + +static void check_srbpending(pa_pstream *p) { + if (!p->is_srbpending) + return; + + if (p->srb) + pa_srbchannel_free(p->srb); + + p->srb = p->srbpending; + p->is_srbpending = false; + + if (p->srb) + pa_srbchannel_set_callback(p->srb, srb_callback, p); +} + +static int do_write(pa_pstream *p) { + void *d; + size_t l; + ssize_t r; + pa_memblock *release_memblock = NULL; + + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + + if (!p->write.current) + prepare_next_write_item(p); + + if (!p->write.current) { + /* The out queue is empty, so switching channels is safe */ + check_srbpending(p); + return 0; + } + + if (p->write.minibuf_validsize > 0) { + d = p->write.minibuf + p->write.index; + l = p->write.minibuf_validsize - p->write.index; + } else if (p->write.index < PA_PSTREAM_DESCRIPTOR_SIZE) { + d = (uint8_t*) p->write.descriptor + p->write.index; + l = PA_PSTREAM_DESCRIPTOR_SIZE - p->write.index; + } else { + pa_assert(p->write.data || p->write.memchunk.memblock); + + if (p->write.data) + d = p->write.data; + else { + d = pa_memblock_acquire_chunk(&p->write.memchunk); + release_memblock = p->write.memchunk.memblock; + } + + d = (uint8_t*) d + p->write.index - PA_PSTREAM_DESCRIPTOR_SIZE; + l = ntohl(p->write.descriptor[PA_PSTREAM_DESCRIPTOR_LENGTH]) - (p->write.index - PA_PSTREAM_DESCRIPTOR_SIZE); + } + + pa_assert(l > 0); + +#ifdef HAVE_CREDS + if (p->send_ancil_data_now) { + if (p->write_ancil_data->creds_valid) { + pa_assert(p->write_ancil_data->nfd == 0); + if ((r = pa_iochannel_write_with_creds(p->io, d, l, &p->write_ancil_data->creds)) < 0) + goto fail; + } + else + if ((r = pa_iochannel_write_with_fds(p->io, d, l, p->write_ancil_data->nfd, p->write_ancil_data->fds)) < 0) + goto fail; + + pa_cmsg_ancil_data_close_fds(p->write_ancil_data); + p->send_ancil_data_now = false; + } else +#endif + if (p->srb) + r = pa_srbchannel_write(p->srb, d, l); + else if ((r = pa_iochannel_write(p->io, d, l)) < 0) + goto fail; + + if (release_memblock) + pa_memblock_release(release_memblock); + + p->write.index += (size_t) r; + + if (p->write.index >= PA_PSTREAM_DESCRIPTOR_SIZE + ntohl(p->write.descriptor[PA_PSTREAM_DESCRIPTOR_LENGTH])) { + pa_assert(p->write.current); + item_free(p->write.current); + p->write.current = NULL; + + if (p->write.memchunk.memblock) + pa_memblock_unref(p->write.memchunk.memblock); + + pa_memchunk_reset(&p->write.memchunk); + + if (p->drain_callback && !pa_pstream_is_pending(p)) + p->drain_callback(p, p->drain_callback_userdata); + } + + return (size_t) r == l ? 1 : 0; + +fail: +#ifdef HAVE_CREDS + if (p->send_ancil_data_now) + pa_cmsg_ancil_data_close_fds(p->write_ancil_data); +#endif + + if (release_memblock) + pa_memblock_release(release_memblock); + + return -1; +} + +static void memblock_complete(pa_pstream *p, struct pstream_read *re) { + pa_memchunk chunk; + int64_t offset; + + if (!p->receive_memblock_callback) + return; + + chunk.memblock = re->memblock; + chunk.index = 0; + chunk.length = re->index - PA_PSTREAM_DESCRIPTOR_SIZE; + + offset = (int64_t) ( + (((uint64_t) ntohl(re->descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_HI])) << 32) | + (((uint64_t) ntohl(re->descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_LO])))); + + p->receive_memblock_callback( + p, + ntohl(re->descriptor[PA_PSTREAM_DESCRIPTOR_CHANNEL]), + offset, + ntohl(re->descriptor[PA_PSTREAM_DESCRIPTOR_FLAGS]) & PA_FLAG_SEEKMASK, + &chunk, + p->receive_memblock_callback_userdata); +} + +static int do_read(pa_pstream *p, struct pstream_read *re) { + void *d; + size_t l; + ssize_t r; + pa_memblock *release_memblock = NULL; + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + + if (re->index < PA_PSTREAM_DESCRIPTOR_SIZE) { + d = (uint8_t*) re->descriptor + re->index; + l = PA_PSTREAM_DESCRIPTOR_SIZE - re->index; + } else { + pa_assert(re->data || re->memblock); + + if (re->data) + d = re->data; + else { + d = pa_memblock_acquire(re->memblock); + release_memblock = re->memblock; + } + + d = (uint8_t*) d + re->index - PA_PSTREAM_DESCRIPTOR_SIZE; + l = ntohl(re->descriptor[PA_PSTREAM_DESCRIPTOR_LENGTH]) - (re->index - PA_PSTREAM_DESCRIPTOR_SIZE); + } + + if (re == &p->readsrb) { + r = pa_srbchannel_read(p->srb, d, l); + if (r == 0) { + if (release_memblock) + pa_memblock_release(release_memblock); + return 1; + } + } + else +#ifdef HAVE_CREDS + { + pa_cmsg_ancil_data b; + + if ((r = pa_iochannel_read_with_ancil_data(p->io, d, l, &b)) <= 0) + goto fail; + + if (b.creds_valid) { + p->read_ancil_data.creds_valid = true; + p->read_ancil_data.creds = b.creds; + } + if (b.nfd > 0) { + pa_assert(b.nfd <= MAX_ANCIL_DATA_FDS); + p->read_ancil_data.nfd = b.nfd; + memcpy(p->read_ancil_data.fds, b.fds, sizeof(int) * b.nfd); + p->read_ancil_data.close_fds_on_cleanup = b.close_fds_on_cleanup; + } + } +#else + if ((r = pa_iochannel_read(p->io, d, l)) <= 0) + goto fail; +#endif + + if (release_memblock) + pa_memblock_release(release_memblock); + + re->index += (size_t) r; + + if (re->index == PA_PSTREAM_DESCRIPTOR_SIZE) { + uint32_t flags, length, channel; + /* Reading of frame descriptor complete */ + + flags = ntohl(re->descriptor[PA_PSTREAM_DESCRIPTOR_FLAGS]); + + if (!p->use_shm && (flags & PA_FLAG_SHMMASK) != 0) { + pa_log_warn("Received SHM frame on a socket where SHM is disabled."); + return -1; + } + + if (flags == PA_FLAG_SHMRELEASE) { + + /* This is a SHM memblock release frame with no payload */ + +/* pa_log("Got release frame for %u", ntohl(re->descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_HI])); */ + + pa_assert(p->export); + pa_memexport_process_release(p->export, ntohl(re->descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_HI])); + + goto frame_done; + + } else if (flags == PA_FLAG_SHMREVOKE) { + + /* This is a SHM memblock revoke frame with no payload */ + +/* pa_log("Got revoke frame for %u", ntohl(re->descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_HI])); */ + + pa_assert(p->import); + pa_memimport_process_revoke(p->import, ntohl(re->descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_HI])); + + goto frame_done; + } + + length = ntohl(re->descriptor[PA_PSTREAM_DESCRIPTOR_LENGTH]); + + if (length > FRAME_SIZE_MAX_ALLOW || length <= 0) { + pa_log_warn("Received invalid frame size: %lu", (unsigned long) length); + return -1; + } + + pa_assert(!re->packet && !re->memblock); + + channel = ntohl(re->descriptor[PA_PSTREAM_DESCRIPTOR_CHANNEL]); + + if (channel == (uint32_t) -1) { + size_t plen; + + if (flags != 0) { + pa_log_warn("Received packet frame with invalid flags value."); + return -1; + } + + /* Frame is a packet frame */ + re->packet = pa_packet_new(length); + re->data = (void *) pa_packet_data(re->packet, &plen); + + } else { + + if ((flags & PA_FLAG_SEEKMASK) > PA_SEEK_RELATIVE_END) { + pa_log_warn("Received memblock frame with invalid seek mode."); + return -1; + } + + if (((flags & PA_FLAG_SHMMASK) & PA_FLAG_SHMDATA) != 0) { + + if (length != sizeof(re->shm_info)) { + pa_log_warn("Received SHM memblock frame with invalid frame length."); + return -1; + } + + /* Frame is a memblock frame referencing an SHM memblock */ + re->data = re->shm_info; + + } else if ((flags & PA_FLAG_SHMMASK) == 0) { + + /* Frame is a memblock frame */ + + re->memblock = pa_memblock_new(p->mempool, length); + re->data = NULL; + } else { + + pa_log_warn("Received memblock frame with invalid flags value."); + return -1; + } + } + + } else if (re->index >= ntohl(re->descriptor[PA_PSTREAM_DESCRIPTOR_LENGTH]) + PA_PSTREAM_DESCRIPTOR_SIZE) { + /* Frame complete */ + + if (re->memblock) { + memblock_complete(p, re); + + /* This was a memblock frame. We can unref the memblock now */ + pa_memblock_unref(re->memblock); + + } else if (re->packet) { + + if (p->receive_packet_callback) +#ifdef HAVE_CREDS + p->receive_packet_callback(p, re->packet, &p->read_ancil_data, p->receive_packet_callback_userdata); +#else + p->receive_packet_callback(p, re->packet, NULL, p->receive_packet_callback_userdata); +#endif + + pa_packet_unref(re->packet); + } else { + pa_memblock *b = NULL; + uint32_t flags = ntohl(re->descriptor[PA_PSTREAM_DESCRIPTOR_FLAGS]); + uint32_t shm_id = ntohl(re->shm_info[PA_PSTREAM_SHM_SHMID]); + pa_mem_type_t type = (flags & PA_FLAG_SHMDATA_MEMFD_BLOCK) ? + PA_MEM_TYPE_SHARED_MEMFD : PA_MEM_TYPE_SHARED_POSIX; + + pa_assert(((flags & PA_FLAG_SHMMASK) & PA_FLAG_SHMDATA) != 0); + pa_assert(p->import); + + if (type == PA_MEM_TYPE_SHARED_MEMFD && p->use_memfd && + !pa_idxset_get_by_data(p->registered_memfd_ids, PA_UINT32_TO_PTR(shm_id), NULL)) { + + if (pa_log_ratelimit(PA_LOG_ERROR)) + pa_log("Ignoring received block reference with non-registered memfd ID = %u", shm_id); + + } else if (!(b = pa_memimport_get(p->import, + type, + ntohl(re->shm_info[PA_PSTREAM_SHM_BLOCKID]), + shm_id, + ntohl(re->shm_info[PA_PSTREAM_SHM_INDEX]), + ntohl(re->shm_info[PA_PSTREAM_SHM_LENGTH]), + !!(flags & PA_FLAG_SHMWRITABLE)))) { + + if (pa_log_ratelimit(PA_LOG_DEBUG)) + pa_log_debug("Failed to import memory block."); + } + + if (p->receive_memblock_callback) { + int64_t offset; + pa_memchunk chunk; + + chunk.memblock = b; + chunk.index = 0; + chunk.length = b ? pa_memblock_get_length(b) : ntohl(re->shm_info[PA_PSTREAM_SHM_LENGTH]); + + offset = (int64_t) ( + (((uint64_t) ntohl(re->descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_HI])) << 32) | + (((uint64_t) ntohl(re->descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_LO])))); + + p->receive_memblock_callback( + p, + ntohl(re->descriptor[PA_PSTREAM_DESCRIPTOR_CHANNEL]), + offset, + ntohl(re->descriptor[PA_PSTREAM_DESCRIPTOR_FLAGS]) & PA_FLAG_SEEKMASK, + &chunk, + p->receive_memblock_callback_userdata); + } + + if (b) + pa_memblock_unref(b); + } + + goto frame_done; + } + + return 0; + +frame_done: + re->memblock = NULL; + re->packet = NULL; + re->index = 0; + re->data = NULL; + +#ifdef HAVE_CREDS + /* FIXME: Close received ancillary data fds if the pstream's + * receive_packet_callback did not do so. + * + * Malicious clients can attach fds to unknown commands, or attach them + * to commands that does not expect fds. By doing so, server will reach + * its open fd limit and future clients' SHM transfers will always fail. + */ + p->read_ancil_data.creds_valid = false; + p->read_ancil_data.nfd = 0; +#endif + + return 0; + +fail: + if (release_memblock) + pa_memblock_release(release_memblock); + + return -1; +} + +void pa_pstream_set_die_callback(pa_pstream *p, pa_pstream_notify_cb_t cb, void *userdata) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + + p->die_callback = cb; + p->die_callback_userdata = userdata; +} + +void pa_pstream_set_drain_callback(pa_pstream *p, pa_pstream_notify_cb_t cb, void *userdata) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + + p->drain_callback = cb; + p->drain_callback_userdata = userdata; +} + +void pa_pstream_set_receive_packet_callback(pa_pstream *p, pa_pstream_packet_cb_t cb, void *userdata) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + + p->receive_packet_callback = cb; + p->receive_packet_callback_userdata = userdata; +} + +void pa_pstream_set_receive_memblock_callback(pa_pstream *p, pa_pstream_memblock_cb_t cb, void *userdata) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + + p->receive_memblock_callback = cb; + p->receive_memblock_callback_userdata = userdata; +} + +void pa_pstream_set_release_callback(pa_pstream *p, pa_pstream_block_id_cb_t cb, void *userdata) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + + p->release_callback = cb; + p->release_callback_userdata = userdata; +} + +void pa_pstream_set_revoke_callback(pa_pstream *p, pa_pstream_block_id_cb_t cb, void *userdata) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + + p->revoke_callback = cb; + p->revoke_callback_userdata = userdata; +} + +bool pa_pstream_is_pending(pa_pstream *p) { + bool b; + + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + + if (p->dead) + b = false; + else + b = p->write.current || !pa_queue_isempty(p->send_queue); + + return b; +} + +void pa_pstream_unref(pa_pstream*p) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + + if (PA_REFCNT_DEC(p) <= 0) + pstream_free(p); +} + +pa_pstream* pa_pstream_ref(pa_pstream*p) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + + PA_REFCNT_INC(p); + return p; +} + +void pa_pstream_unlink(pa_pstream *p) { + pa_assert(p); + + if (p->dead) + return; + + p->dead = true; + + while (p->srb || p->is_srbpending) /* In theory there could be one active and one pending */ + pa_pstream_set_srbchannel(p, NULL); + + if (p->import) { + pa_memimport_free(p->import); + p->import = NULL; + } + + if (p->export) { + pa_memexport_free(p->export); + p->export = NULL; + } + + if (p->io) { + pa_iochannel_free(p->io); + p->io = NULL; + } + + if (p->defer_event) { + p->mainloop->defer_free(p->defer_event); + p->defer_event = NULL; + } + + p->die_callback = NULL; + p->drain_callback = NULL; + p->receive_packet_callback = NULL; + p->receive_memblock_callback = NULL; +} + +void pa_pstream_enable_shm(pa_pstream *p, bool enable) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + + p->use_shm = enable; + + if (enable) { + + if (!p->export) + p->export = pa_memexport_new(p->mempool, memexport_revoke_cb, p); + + } else { + + if (p->export) { + pa_memexport_free(p->export); + p->export = NULL; + } + } +} + +void pa_pstream_enable_memfd(pa_pstream *p) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + pa_assert(p->use_shm); + + p->use_memfd = true; + + if (!p->registered_memfd_ids) { + p->registered_memfd_ids = pa_idxset_new(NULL, NULL); + } +} + +bool pa_pstream_get_shm(pa_pstream *p) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + + return p->use_shm; +} + +bool pa_pstream_get_memfd(pa_pstream *p) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + + return p->use_memfd; +} + +void pa_pstream_set_srbchannel(pa_pstream *p, pa_srbchannel *srb) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0 || srb == NULL); + + if (srb == p->srb) + return; + + /* We can't handle quick switches between srbchannels. */ + pa_assert(!p->is_srbpending); + + p->srbpending = srb; + p->is_srbpending = true; + + /* Switch immediately, if possible. */ + if (p->dead) + check_srbpending(p); + else + do_write(p); +} diff --git a/src/pulsecore/pstream.h b/src/pulsecore/pstream.h new file mode 100644 index 0000000..2bff270 --- /dev/null +++ b/src/pulsecore/pstream.h @@ -0,0 +1,76 @@ +#ifndef foopstreamhfoo +#define foopstreamhfoo + +/*** + 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <inttypes.h> + +#include <pulse/mainloop-api.h> +#include <pulse/def.h> + +#include <pulsecore/packet.h> +#include <pulsecore/memblock.h> +#include <pulsecore/iochannel.h> +#include <pulsecore/srbchannel.h> +#include <pulsecore/memchunk.h> +#include <pulsecore/creds.h> +#include <pulsecore/macro.h> + +typedef struct pa_pstream pa_pstream; + +typedef void (*pa_pstream_packet_cb_t)(pa_pstream *p, pa_packet *packet, pa_cmsg_ancil_data *ancil_data, void *userdata); +typedef void (*pa_pstream_memblock_cb_t)(pa_pstream *p, uint32_t channel, int64_t offset, pa_seek_mode_t seek, const pa_memchunk *chunk, void *userdata); +typedef void (*pa_pstream_notify_cb_t)(pa_pstream *p, void *userdata); +typedef void (*pa_pstream_block_id_cb_t)(pa_pstream *p, uint32_t block_id, void *userdata); + +pa_pstream* pa_pstream_new(pa_mainloop_api *m, pa_iochannel *io, pa_mempool *p); + +pa_pstream* pa_pstream_ref(pa_pstream*p); +void pa_pstream_unref(pa_pstream*p); + +void pa_pstream_unlink(pa_pstream *p); + +int pa_pstream_attach_memfd_shmid(pa_pstream *p, unsigned shm_id, int memfd_fd); + +void pa_pstream_send_packet(pa_pstream*p, pa_packet *packet, pa_cmsg_ancil_data *ancil_data); +void pa_pstream_send_memblock(pa_pstream*p, uint32_t channel, int64_t offset, pa_seek_mode_t seek, const pa_memchunk *chunk); +void pa_pstream_send_release(pa_pstream *p, uint32_t block_id); +void pa_pstream_send_revoke(pa_pstream *p, uint32_t block_id); + +void pa_pstream_set_receive_packet_callback(pa_pstream *p, pa_pstream_packet_cb_t cb, void *userdata); +void pa_pstream_set_receive_memblock_callback(pa_pstream *p, pa_pstream_memblock_cb_t cb, void *userdata); +void pa_pstream_set_drain_callback(pa_pstream *p, pa_pstream_notify_cb_t cb, void *userdata); +void pa_pstream_set_die_callback(pa_pstream *p, pa_pstream_notify_cb_t cb, void *userdata); +void pa_pstream_set_release_callback(pa_pstream *p, pa_pstream_block_id_cb_t cb, void *userdata); +void pa_pstream_set_revoke_callback(pa_pstream *p, pa_pstream_block_id_cb_t cb, void *userdata); + +bool pa_pstream_is_pending(pa_pstream *p); + +void pa_pstream_enable_shm(pa_pstream *p, bool enable); +void pa_pstream_enable_memfd(pa_pstream *p); +bool pa_pstream_get_shm(pa_pstream *p); +bool pa_pstream_get_memfd(pa_pstream *p); + +/* Enables shared ringbuffer channel. Note that the srbchannel is now owned by the pstream. + Setting srb to NULL will free any existing srbchannel. */ +void pa_pstream_set_srbchannel(pa_pstream *p, pa_srbchannel *srb); + +#endif diff --git a/src/pulsecore/queue.c b/src/pulsecore/queue.c new file mode 100644 index 0000000..2b952b6 --- /dev/null +++ b/src/pulsecore/queue.c @@ -0,0 +1,122 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2008 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/macro.h> +#include <pulsecore/flist.h> + +#include "queue.h" + +PA_STATIC_FLIST_DECLARE(entries, 0, pa_xfree); + +struct queue_entry { + struct queue_entry *next; + void *data; +}; + +struct pa_queue { + struct queue_entry *front, *back; + unsigned length; +}; + +pa_queue* pa_queue_new(void) { + pa_queue *q = pa_xnew(pa_queue, 1); + + q->front = q->back = NULL; + q->length = 0; + + return q; +} + +void pa_queue_free(pa_queue *q, pa_free_cb_t free_func) { + void *data; + pa_assert(q); + + while ((data = pa_queue_pop(q))) + if (free_func) + free_func(data); + + pa_assert(!q->front); + pa_assert(!q->back); + pa_assert(q->length == 0); + + pa_xfree(q); +} + +void pa_queue_push(pa_queue *q, void *p) { + struct queue_entry *e; + + pa_assert(q); + pa_assert(p); + + if (!(e = pa_flist_pop(PA_STATIC_FLIST_GET(entries)))) + e = pa_xnew(struct queue_entry, 1); + + e->data = p; + e->next = NULL; + + if (q->back) { + pa_assert(q->front); + q->back->next = e; + } else { + pa_assert(!q->front); + q->front = e; + } + + q->back = e; + q->length++; +} + +void* pa_queue_pop(pa_queue *q) { + void *p; + struct queue_entry *e; + + pa_assert(q); + + if (!(e = q->front)) + return NULL; + + q->front = e->next; + + if (q->back == e) { + pa_assert(!e->next); + q->back = NULL; + } + + p = e->data; + + if (pa_flist_push(PA_STATIC_FLIST_GET(entries), e) < 0) + pa_xfree(e); + + q->length--; + + return p; +} + +int pa_queue_isempty(pa_queue *q) { + pa_assert(q); + + return q->length == 0; +} diff --git a/src/pulsecore/queue.h b/src/pulsecore/queue.h new file mode 100644 index 0000000..14cc555 --- /dev/null +++ b/src/pulsecore/queue.h @@ -0,0 +1,41 @@ +#ifndef foopulsecorequeuehfoo +#define foopulsecorequeuehfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2008 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulse/def.h> + +typedef struct pa_queue pa_queue; + +/* A simple implementation of the abstract data type queue. Stores + * pointers as members. The memory has to be managed by the caller. */ + +pa_queue* pa_queue_new(void); + +/* Free the queue and run the specified callback function for every + * remaining entry. The callback function may be NULL. */ +void pa_queue_free(pa_queue *q, pa_free_cb_t free_func); + +void pa_queue_push(pa_queue *q, void *p); +void* pa_queue_pop(pa_queue *q); + +int pa_queue_isempty(pa_queue *q); + +#endif diff --git a/src/pulsecore/random.c b/src/pulsecore/random.c new file mode 100644 index 0000000..508a6f8 --- /dev/null +++ b/src/pulsecore/random.c @@ -0,0 +1,129 @@ +/*** + 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> +#include <stdlib.h> +#include <time.h> + +#ifdef HAVE_WINDOWS_H +#include <windows.h> +#include <wincrypt.h> +#endif + +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> + +#include "random.h" + +static bool has_whined = false; + +static const char * const devices[] = { "/dev/urandom", "/dev/random", NULL }; + +static int random_proper(void *ret_data, size_t length) { +#ifdef OS_IS_WIN32 + int ret = -1; + + HCRYPTPROV hCryptProv = 0; + + pa_assert(ret_data); + pa_assert(length > 0); + + if (CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) { + if (CryptGenRandom(hCryptProv, length, ret_data)) + ret = 0; + CryptReleaseContext(hCryptProv, 0); + } + + return ret; + +#else /* OS_IS_WIN32 */ + + int fd, ret = -1; + ssize_t r = 0; + const char *const * device; + + pa_assert(ret_data); + pa_assert(length > 0); + + device = devices; + + while (*device) { + ret = 0; + + if ((fd = pa_open_cloexec(*device, O_RDONLY, 0)) >= 0) { + + if ((r = pa_loop_read(fd, ret_data, length, NULL)) < 0 || (size_t) r != length) + ret = -1; + + pa_close(fd); + } else + ret = -1; + + if (ret == 0) + break; + + device++; + } + + return ret; +#endif /* OS_IS_WIN32 */ +} + +void pa_random_seed(void) { + unsigned int seed; + + if (random_proper(&seed, sizeof(unsigned int)) < 0) { + + if (!has_whined) { + pa_log_warn("Failed to get proper entropy. Falling back to seeding with current time."); + has_whined = true; + } + + seed = (unsigned int) time(NULL); + } + + srand(seed); +} + +void pa_random(void *ret_data, size_t length) { + uint8_t *p; + size_t l; + + pa_assert(ret_data); + pa_assert(length > 0); + + if (random_proper(ret_data, length) >= 0) + return; + + if (!has_whined) { + pa_log_warn("Failed to get proper entropy. Falling back to unsecure pseudo RNG."); + has_whined = true; + } + + for (p = ret_data, l = length; l > 0; p++, l--) + *p = (uint8_t) rand(); +} diff --git a/src/pulsecore/random.h b/src/pulsecore/random.h new file mode 100644 index 0000000..7c7755d --- /dev/null +++ b/src/pulsecore/random.h @@ -0,0 +1,29 @@ +#ifndef foorandomhfoo +#define foorandomhfoo + +/*** + 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/types.h> + +void pa_random_seed(void); +void pa_random(void *ret_data, size_t length); + +#endif diff --git a/src/pulsecore/ratelimit.c b/src/pulsecore/ratelimit.c new file mode 100644 index 0000000..0bfc956 --- /dev/null +++ b/src/pulsecore/ratelimit.c @@ -0,0 +1,74 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/rtclock.h> + +#include <pulsecore/log.h> +#include <pulsecore/mutex.h> + +#include "ratelimit.h" + +static pa_static_mutex mutex = PA_STATIC_MUTEX_INIT; + +/* Modelled after Linux' lib/ratelimit.c by Dave Young + * <hidave.darkstar@gmail.com>, which is licensed GPLv2. */ + +bool pa_ratelimit_test(pa_ratelimit *r, pa_log_level_t t) { + pa_usec_t now; + pa_mutex *m; + + now = pa_rtclock_now(); + + m = pa_static_mutex_get(&mutex, false, false); + pa_mutex_lock(m); + + pa_assert(r); + pa_assert(r->interval > 0); + pa_assert(r->burst > 0); + + if (r->begin <= 0 || + r->begin + r->interval < now) { + + if (r->n_missed > 0) + pa_logl(t, "%u events suppressed", r->n_missed); + + r->begin = now; + + /* Reset counters */ + r->n_printed = 0; + r->n_missed = 0; + goto good; + } + + if (r->n_printed <= r->burst) + goto good; + + r->n_missed++; + pa_mutex_unlock(m); + return false; + +good: + r->n_printed++; + pa_mutex_unlock(m); + return true; +} diff --git a/src/pulsecore/ratelimit.h b/src/pulsecore/ratelimit.h new file mode 100644 index 0000000..d1e2f22 --- /dev/null +++ b/src/pulsecore/ratelimit.h @@ -0,0 +1,55 @@ +#ifndef foopulsecoreratelimithfoo +#define foopulsecoreratelimithfoo + +/*** + This file is part of PulseAudio. + + Copyright 2009 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulse/sample.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> + +typedef struct pa_ratelimit { + pa_usec_t interval; + unsigned burst; + unsigned n_printed, n_missed; + pa_usec_t begin; +} pa_ratelimit; + +#define PA_DEFINE_RATELIMIT(_name, _interval, _burst) \ + pa_ratelimit _name = { \ + .interval = (_interval), \ + .burst = (_burst), \ + .n_printed = 0, \ + .n_missed = 0, \ + .begin = 0 \ + } + +#define PA_INIT_RATELIMIT(v, _interval, _burst) \ + do { \ + pa_ratelimit *r = &(v); \ + r->interval = (_interval); \ + r->burst = (_burst); \ + r->n_printed = 0; \ + r->n_missed = 0; \ + r->begin = 0; \ + } while (false); + +bool pa_ratelimit_test(pa_ratelimit *r, pa_log_level_t t); + +#endif diff --git a/src/pulsecore/refcnt.h b/src/pulsecore/refcnt.h new file mode 100644 index 0000000..2e818ab --- /dev/null +++ b/src/pulsecore/refcnt.h @@ -0,0 +1,79 @@ +#ifndef foopulserefcnthfoo +#define foopulserefcnthfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulsecore/atomic.h> +#include <pulsecore/macro.h> +#include <pulsecore/log.h> + +/* #define DEBUG_REF */ + +#define PA_REFCNT_DECLARE \ + pa_atomic_t _ref + +#define PA_REFCNT_VALUE(p) \ + pa_atomic_load(&(p)->_ref) + +#define PA_REFCNT_INIT_ZERO(p) \ + pa_atomic_store(&(p)->_ref, 0) + +#ifndef DEBUG_REF + +#define PA_REFCNT_INIT(p) \ + pa_atomic_store(&(p)->_ref, 1) + +#define PA_REFCNT_INC(p) \ + pa_atomic_inc(&(p)->_ref) + +#define PA_REFCNT_DEC(p) \ + (pa_atomic_dec(&(p)->_ref)-1) + +#else + +/* If you need to debug ref counting problems define DEBUG_REF and + * set $PULSE_LOG_BACKTRACE=5 or suchlike in the shell when running + * PA */ + +#define PA_REFCNT_INIT(p) \ + do { \ + pa_atomic_store(&(p)->_ref, 1); \ + pa_log("REF: Init %p", p); \ + } while (false) + +#define PA_REFCNT_INC(p) \ + do { \ + pa_atomic_inc(&(p)->_ref); \ + pa_log("REF: Inc %p", p); \ + } while (false) \ + +#define PA_REFCNT_DEC(p) \ + ({ \ + int _j = (pa_atomic_dec(&(p)->_ref)-1); \ + if (_j <= 0) \ + pa_log("REF: Done %p", p); \ + else \ + pa_log("REF: Dec %p", p); \ + _j; \ + }) + +#endif + +#endif diff --git a/src/pulsecore/remap.c b/src/pulsecore/remap.c new file mode 100644 index 0000000..35fffd7 --- /dev/null +++ b/src/pulsecore/remap.c @@ -0,0 +1,626 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2009 Wim Taymans <wim.taymans@collabora.co.uk.com> + + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> + +#include <pulse/xmalloc.h> +#include <pulse/sample.h> +#include <pulse/volume.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> + +#include "cpu.h" +#include "remap.h" + +static void remap_mono_to_stereo_s16ne_c(pa_remap_t *m, int16_t *dst, const int16_t *src, unsigned n) { + unsigned i; + + for (i = n >> 2; i; i--) { + dst[0] = dst[1] = src[0]; + dst[2] = dst[3] = src[1]; + dst[4] = dst[5] = src[2]; + dst[6] = dst[7] = src[3]; + src += 4; + dst += 8; + } + for (i = n & 3; i; i--) { + dst[0] = dst[1] = src[0]; + src++; + dst += 2; + } +} + +static void remap_mono_to_stereo_s32ne_c(pa_remap_t *m, int32_t *dst, const int32_t *src, unsigned n) { + unsigned i; + + for (i = n >> 2; i; i--) { + dst[0] = dst[1] = src[0]; + dst[2] = dst[3] = src[1]; + dst[4] = dst[5] = src[2]; + dst[6] = dst[7] = src[3]; + src += 4; + dst += 8; + } + for (i = n & 3; i; i--) { + dst[0] = dst[1] = src[0]; + src++; + dst += 2; + } +} + +static void remap_mono_to_stereo_float32ne_c(pa_remap_t *m, float *dst, const float *src, unsigned n) { + unsigned i; + + for (i = n >> 2; i; i--) { + dst[0] = dst[1] = src[0]; + dst[2] = dst[3] = src[1]; + dst[4] = dst[5] = src[2]; + dst[6] = dst[7] = src[3]; + src += 4; + dst += 8; + } + for (i = n & 3; i; i--) { + dst[0] = dst[1] = src[0]; + src++; + dst += 2; + } +} + +static void remap_stereo_to_mono_s16ne_c(pa_remap_t *m, int16_t *dst, const int16_t *src, unsigned n) { + unsigned i; + + for (i = n >> 2; i > 0; i--) { + dst[0] = (src[0] + src[1])/2; + dst[1] = (src[2] + src[3])/2; + dst[2] = (src[4] + src[5])/2; + dst[3] = (src[6] + src[7])/2; + src += 8; + dst += 4; + } + for (i = n & 3; i; i--) { + dst[0] = (src[0] + src[1])/2; + src += 2; + dst += 1; + } +} + +static void remap_stereo_to_mono_s32ne_c(pa_remap_t *m, int32_t *dst, const int32_t *src, unsigned n) { + unsigned i; + + for (i = n >> 2; i > 0; i--) { + /* Avoid overflow by performing division first. We accept a + * difference of +/- 1 to the ideal result. */ + dst[0] = (src[0]/2 + src[1]/2); + dst[1] = (src[2]/2 + src[3]/2); + dst[2] = (src[4]/2 + src[5]/2); + dst[3] = (src[6]/2 + src[7]/2); + src += 8; + dst += 4; + } + for (i = n & 3; i; i--) { + /* Avoid overflow by performing division first. We accept a + * difference of +/- 1 to the ideal result. */ + dst[0] = (src[0]/2 + src[1]/2); + src += 2; + dst += 1; + } +} + +static void remap_stereo_to_mono_float32ne_c(pa_remap_t *m, float *dst, const float *src, unsigned n) { + unsigned i; + + for (i = n >> 2; i > 0; i--) { + dst[0] = (src[0] + src[1])*0.5f; + dst[1] = (src[2] + src[3])*0.5f; + dst[2] = (src[4] + src[5])*0.5f; + dst[3] = (src[6] + src[7])*0.5f; + src += 8; + dst += 4; + } + for (i = n & 3; i; i--) { + dst[0] = (src[0] + src[1])*0.5f; + src += 2; + dst += 1; + } +} + +static void remap_mono_to_ch4_s16ne_c(pa_remap_t *m, int16_t *dst, const int16_t *src, unsigned n) { + unsigned i; + + for (i = n >> 2; i; i--) { + dst[0] = dst[1] = dst[2] = dst[3] = src[0]; + dst[4] = dst[5] = dst[6] = dst[7] = src[1]; + dst[8] = dst[9] = dst[10] = dst[11] = src[2]; + dst[12] = dst[13] = dst[14] = dst[15] = src[3]; + src += 4; + dst += 16; + } + for (i = n & 3; i; i--) { + dst[0] = dst[1] = dst[2] = dst[3] = src[0]; + src++; + dst += 4; + } +} + +static void remap_mono_to_ch4_s32ne_c(pa_remap_t *m, int32_t *dst, const int32_t *src, unsigned n) { + unsigned i; + + for (i = n >> 2; i; i--) { + dst[0] = dst[1] = dst[2] = dst[3] = src[0]; + dst[4] = dst[5] = dst[6] = dst[7] = src[1]; + dst[8] = dst[9] = dst[10] = dst[11] = src[2]; + dst[12] = dst[13] = dst[14] = dst[15] = src[3]; + src += 4; + dst += 16; + } + for (i = n & 3; i; i--) { + dst[0] = dst[1] = dst[2] = dst[3] = src[0]; + src++; + dst += 4; + } +} + +static void remap_mono_to_ch4_float32ne_c(pa_remap_t *m, float *dst, const float *src, unsigned n) { + unsigned i; + + for (i = n >> 2; i; i--) { + dst[0] = dst[1] = dst[2] = dst[3] = src[0]; + dst[4] = dst[5] = dst[6] = dst[7] = src[1]; + dst[8] = dst[9] = dst[10] = dst[11] = src[2]; + dst[12] = dst[13] = dst[14] = dst[15] = src[3]; + src += 4; + dst += 16; + } + for (i = n & 3; i; i--) { + dst[0] = dst[1] = dst[2] = dst[3] = src[0]; + src++; + dst += 4; + } +} + +static void remap_ch4_to_mono_s16ne_c(pa_remap_t *m, int16_t *dst, const int16_t *src, unsigned n) { + unsigned i; + + for (i = n >> 2; i > 0; i--) { + dst[0] = (src[0] + src[1] + src[2] + src[3])/4; + dst[1] = (src[4] + src[5] + src[6] + src[7])/4; + dst[2] = (src[8] + src[9] + src[10] + src[11])/4; + dst[3] = (src[12] + src[13] + src[14] + src[15])/4; + src += 16; + dst += 4; + } + for (i = n & 3; i; i--) { + dst[0] = (src[0] + src[1] + src[2] + src[3])/4; + src += 4; + dst += 1; + } +} + +static void remap_ch4_to_mono_s32ne_c(pa_remap_t *m, int32_t *dst, const int32_t *src, unsigned n) { + unsigned i; + + for (i = n >> 2; i > 0; i--) { + /* Avoid overflow by performing division first. We accept a + * difference of +/- 3 to the ideal result. */ + dst[0] = (src[0]/4 + src[1]/4 + src[2]/4 + src[3]/4); + dst[1] = (src[4]/4 + src[5]/4 + src[6]/4 + src[7]/4); + dst[2] = (src[8]/4 + src[9]/4 + src[10]/4 + src[11]/4); + dst[3] = (src[12]/4 + src[13]/4 + src[14]/4 + src[15]/4); + src += 16; + dst += 4; + } + for (i = n & 3; i; i--) { + /* Avoid overflow by performing division first. We accept a + * difference of +/- 3 to the ideal result. */ + dst[0] = (src[0]/4 + src[1]/4 + src[2]/4 + src[3]/4); + src += 4; + dst += 1; + } +} + +static void remap_ch4_to_mono_float32ne_c(pa_remap_t *m, float *dst, const float *src, unsigned n) { + unsigned i; + + for (i = n >> 2; i > 0; i--) { + dst[0] = (src[0] + src[1] + src[2] + src[3])*0.25f; + dst[1] = (src[4] + src[5] + src[6] + src[7])*0.25f; + dst[2] = (src[8] + src[9] + src[10] + src[11])*0.25f; + dst[3] = (src[12] + src[13] + src[14] + src[15])*0.25f; + src += 16; + dst += 4; + } + for (i = n & 3; i; i--) { + dst[0] = (src[0] + src[1] + src[2] + src[3])*0.25f; + src += 4; + dst += 1; + } +} + +static void remap_channels_matrix_s16ne_c(pa_remap_t *m, int16_t *dst, const int16_t *src, unsigned n) { + + unsigned oc, ic, i; + unsigned n_ic, n_oc; + + n_ic = m->i_ss.channels; + n_oc = m->o_ss.channels; + + memset(dst, 0, n * sizeof(int16_t) * n_oc); + + for (oc = 0; oc < n_oc; oc++) { + + for (ic = 0; ic < n_ic; ic++) { + int16_t *d = dst + oc; + const int16_t *s = src + ic; + int32_t vol = m->map_table_i[oc][ic]; + + if (vol <= 0) + continue; + + if (vol >= 0x10000) { + for (i = n; i > 0; i--, s += n_ic, d += n_oc) + *d += *s; + } else { + for (i = n; i > 0; i--, s += n_ic, d += n_oc) + *d += (int16_t) (((int32_t)*s * vol) >> 16); + } + } + } +} + +static void remap_channels_matrix_s32ne_c(pa_remap_t *m, int32_t *dst, const int32_t *src, unsigned n) { + unsigned oc, ic, i; + unsigned n_ic, n_oc; + + n_ic = m->i_ss.channels; + n_oc = m->o_ss.channels; + + memset(dst, 0, n * sizeof(int32_t) * n_oc); + + for (oc = 0; oc < n_oc; oc++) { + + for (ic = 0; ic < n_ic; ic++) { + int32_t *d = dst + oc; + const int32_t *s = src + ic; + int32_t vol = m->map_table_i[oc][ic]; + + if (vol <= 0) + continue; + + if (vol >= 0x10000) { + for (i = n; i > 0; i--, s += n_ic, d += n_oc) + *d += *s; + } else { + for (i = n; i > 0; i--, s += n_ic, d += n_oc) + *d += (int32_t) (((int64_t)*s * vol) >> 16); + } + } + } +} + +static void remap_channels_matrix_float32ne_c(pa_remap_t *m, float *dst, const float *src, unsigned n) { + unsigned oc, ic, i; + unsigned n_ic, n_oc; + + n_ic = m->i_ss.channels; + n_oc = m->o_ss.channels; + + memset(dst, 0, n * sizeof(float) * n_oc); + + for (oc = 0; oc < n_oc; oc++) { + + for (ic = 0; ic < n_ic; ic++) { + float *d = dst + oc; + const float *s = src + ic; + float vol = m->map_table_f[oc][ic]; + + if (vol <= 0.0f) + continue; + + if (vol >= 1.0f) { + for (i = n; i > 0; i--, s += n_ic, d += n_oc) + *d += *s; + } else { + for (i = n; i > 0; i--, s += n_ic, d += n_oc) + *d += *s * vol; + } + } + } +} + +/* Produce an array containing input channel indices to map to output channels. + * If the output channel is empty, the array element is -1. */ +bool pa_setup_remap_arrange(const pa_remap_t *m, int8_t arrange[PA_CHANNELS_MAX]) { + unsigned ic, oc; + unsigned n_ic, n_oc; + unsigned count_output = 0; + + pa_assert(m); + + n_ic = m->i_ss.channels; + n_oc = m->o_ss.channels; + + for (oc = 0; oc < n_oc; oc++) { + arrange[oc] = -1; + for (ic = 0; ic < n_ic; ic++) { + int32_t vol = m->map_table_i[oc][ic]; + + /* input channel is not used */ + if (vol == 0) + continue; + + /* if mixing this channel, we cannot just rearrange */ + if (vol != 0x10000 || arrange[oc] >= 0) + return false; + + arrange[oc] = ic; + count_output++; + } + } + + return count_output > 0; +} + +static void remap_arrange_mono_s16ne_c(pa_remap_t *m, int16_t *dst, const int16_t *src, unsigned n) { + const unsigned n_ic = m->i_ss.channels; + const int8_t *arrange = m->state; + + src += arrange[0]; + for (; n > 0; n--) { + *dst++ = *src; + src += n_ic; + } +} + +static void remap_arrange_stereo_s16ne_c(pa_remap_t *m, int16_t *dst, const int16_t *src, unsigned n) { + const unsigned n_ic = m->i_ss.channels; + const int8_t *arrange = m->state; + const int8_t ic0 = arrange[0], ic1 = arrange[1]; + + for (; n > 0; n--) { + *dst++ = (ic0 >= 0) ? *(src + ic0) : 0; + *dst++ = (ic1 >= 0) ? *(src + ic1) : 0; + src += n_ic; + } +} + +static void remap_arrange_ch4_s16ne_c(pa_remap_t *m, int16_t *dst, const int16_t *src, unsigned n) { + const unsigned n_ic = m->i_ss.channels; + const int8_t *arrange = m->state; + const int8_t ic0 = arrange[0], ic1 = arrange[1], + ic2 = arrange[2], ic3 = arrange[3]; + + for (; n > 0; n--) { + *dst++ = (ic0 >= 0) ? *(src + ic0) : 0; + *dst++ = (ic1 >= 0) ? *(src + ic1) : 0; + *dst++ = (ic2 >= 0) ? *(src + ic2) : 0; + *dst++ = (ic3 >= 0) ? *(src + ic3) : 0; + src += n_ic; + } +} + +static void remap_arrange_mono_s32ne_c(pa_remap_t *m, int32_t *dst, const int32_t *src, unsigned n) { + const unsigned n_ic = m->i_ss.channels; + const int8_t *arrange = m->state; + + src += arrange[0]; + for (; n > 0; n--) { + *dst++ = *src; + src += n_ic; + } +} + +static void remap_arrange_stereo_s32ne_c(pa_remap_t *m, int32_t *dst, const int32_t *src, unsigned n) { + const unsigned n_ic = m->i_ss.channels; + const int8_t *arrange = m->state; + const int ic0 = arrange[0], ic1 = arrange[1]; + + for (; n > 0; n--) { + *dst++ = (ic0 >= 0) ? *(src + ic0) : 0; + *dst++ = (ic1 >= 0) ? *(src + ic1) : 0; + src += n_ic; + } +} + +static void remap_arrange_ch4_s32ne_c(pa_remap_t *m, int32_t *dst, const int32_t *src, unsigned n) { + const unsigned n_ic = m->i_ss.channels; + const int8_t *arrange = m->state; + const int ic0 = arrange[0], ic1 = arrange[1], + ic2 = arrange[2], ic3 = arrange[3]; + + for (; n > 0; n--) { + *dst++ = (ic0 >= 0) ? *(src + ic0) : 0; + *dst++ = (ic1 >= 0) ? *(src + ic1) : 0; + *dst++ = (ic2 >= 0) ? *(src + ic2) : 0; + *dst++ = (ic3 >= 0) ? *(src + ic3) : 0; + src += n_ic; + } +} + +static void remap_arrange_mono_float32ne_c(pa_remap_t *m, float *dst, const float *src, unsigned n) { + const unsigned n_ic = m->i_ss.channels; + const int8_t *arrange = m->state; + + src += arrange[0]; + for (; n > 0; n--) { + *dst++ = *src; + src += n_ic; + } +} + +static void remap_arrange_stereo_float32ne_c(pa_remap_t *m, float *dst, const float *src, unsigned n) { + const unsigned n_ic = m->i_ss.channels; + const int8_t *arrange = m->state; + const int ic0 = arrange[0], ic1 = arrange[1]; + + for (; n > 0; n--) { + *dst++ = (ic0 >= 0) ? *(src + ic0) : 0.0f; + *dst++ = (ic1 >= 0) ? *(src + ic1) : 0.0f; + src += n_ic; + } +} + +static void remap_arrange_ch4_float32ne_c(pa_remap_t *m, float *dst, const float *src, unsigned n) { + const unsigned n_ic = m->i_ss.channels; + const int8_t *arrange = m->state; + const int ic0 = arrange[0], ic1 = arrange[1], + ic2 = arrange[2], ic3 = arrange[3]; + + for (; n > 0; n--) { + *dst++ = (ic0 >= 0) ? *(src + ic0) : 0.0f; + *dst++ = (ic1 >= 0) ? *(src + ic1) : 0.0f; + *dst++ = (ic2 >= 0) ? *(src + ic2) : 0.0f; + *dst++ = (ic3 >= 0) ? *(src + ic3) : 0.0f; + src += n_ic; + } +} + +void pa_set_remap_func(pa_remap_t *m, pa_do_remap_func_t func_s16, + pa_do_remap_func_t func_s32, pa_do_remap_func_t func_float) { + + pa_assert(m); + + if (m->format == PA_SAMPLE_S16NE) + m->do_remap = func_s16; + else if (m->format == PA_SAMPLE_S32NE) + m->do_remap = func_s32; + else if (m->format == PA_SAMPLE_FLOAT32NE) + m->do_remap = func_float; + else + pa_assert_not_reached(); + pa_assert(m->do_remap); +} + +static bool force_generic_code = false; + +/* set the function that will execute the remapping based on the matrices */ +static void init_remap_c(pa_remap_t *m) { + unsigned n_oc, n_ic; + int8_t arrange[PA_CHANNELS_MAX]; + + n_oc = m->o_ss.channels; + n_ic = m->i_ss.channels; + + /* find some common channel remappings, fall back to full matrix operation. */ + if (force_generic_code) { + pa_log_info("Forced to use generic matrix remapping"); + pa_set_remap_func(m, (pa_do_remap_func_t) remap_channels_matrix_s16ne_c, + (pa_do_remap_func_t) remap_channels_matrix_s32ne_c, + (pa_do_remap_func_t) remap_channels_matrix_float32ne_c); + return; + } + + if (n_ic == 1 && n_oc == 2 && + m->map_table_i[0][0] == 0x10000 && m->map_table_i[1][0] == 0x10000) { + + pa_log_info("Using mono to stereo remapping"); + pa_set_remap_func(m, (pa_do_remap_func_t) remap_mono_to_stereo_s16ne_c, + (pa_do_remap_func_t) remap_mono_to_stereo_s32ne_c, + (pa_do_remap_func_t) remap_mono_to_stereo_float32ne_c); + } else if (n_ic == 2 && n_oc == 1 && + m->map_table_i[0][0] == 0x8000 && m->map_table_i[0][1] == 0x8000) { + + pa_log_info("Using stereo to mono remapping"); + pa_set_remap_func(m, (pa_do_remap_func_t) remap_stereo_to_mono_s16ne_c, + (pa_do_remap_func_t) remap_stereo_to_mono_s32ne_c, + (pa_do_remap_func_t) remap_stereo_to_mono_float32ne_c); + } else if (n_ic == 1 && n_oc == 4 && + m->map_table_i[0][0] == 0x10000 && m->map_table_i[1][0] == 0x10000 && + m->map_table_i[2][0] == 0x10000 && m->map_table_i[3][0] == 0x10000) { + + pa_log_info("Using mono to 4-channel remapping"); + pa_set_remap_func(m, (pa_do_remap_func_t)remap_mono_to_ch4_s16ne_c, + (pa_do_remap_func_t) remap_mono_to_ch4_s32ne_c, + (pa_do_remap_func_t) remap_mono_to_ch4_float32ne_c); + } else if (n_ic == 4 && n_oc == 1 && + m->map_table_i[0][0] == 0x4000 && m->map_table_i[0][1] == 0x4000 && + m->map_table_i[0][2] == 0x4000 && m->map_table_i[0][3] == 0x4000) { + + pa_log_info("Using 4-channel to mono remapping"); + pa_set_remap_func(m, (pa_do_remap_func_t) remap_ch4_to_mono_s16ne_c, + (pa_do_remap_func_t) remap_ch4_to_mono_s32ne_c, + (pa_do_remap_func_t) remap_ch4_to_mono_float32ne_c); + } else if (pa_setup_remap_arrange(m, arrange) && n_oc == 1) { + + pa_log_info("Using mono arrange remapping"); + pa_set_remap_func(m, (pa_do_remap_func_t) remap_arrange_mono_s16ne_c, + (pa_do_remap_func_t) remap_arrange_mono_s32ne_c, + (pa_do_remap_func_t) remap_arrange_mono_float32ne_c); + + /* setup state */ + m->state = pa_xnewdup(int8_t, arrange, PA_CHANNELS_MAX); + } else if (pa_setup_remap_arrange(m, arrange) && n_oc == 2) { + + pa_log_info("Using stereo arrange remapping"); + pa_set_remap_func(m, (pa_do_remap_func_t) remap_arrange_stereo_s16ne_c, + (pa_do_remap_func_t) remap_arrange_stereo_s32ne_c, + (pa_do_remap_func_t) remap_arrange_stereo_float32ne_c); + + /* setup state */ + m->state = pa_xnewdup(int8_t, arrange, PA_CHANNELS_MAX); + } else if (pa_setup_remap_arrange(m, arrange) && n_oc == 4) { + + pa_log_info("Using 4-channel arrange remapping"); + pa_set_remap_func(m, (pa_do_remap_func_t) remap_arrange_ch4_s16ne_c, + (pa_do_remap_func_t) remap_arrange_ch4_s32ne_c, + (pa_do_remap_func_t) remap_arrange_ch4_float32ne_c); + + /* setup state */ + m->state = pa_xnewdup(int8_t, arrange, PA_CHANNELS_MAX); + } else { + + pa_log_info("Using generic matrix remapping"); + pa_set_remap_func(m, (pa_do_remap_func_t) remap_channels_matrix_s16ne_c, + (pa_do_remap_func_t) remap_channels_matrix_s32ne_c, + (pa_do_remap_func_t) remap_channels_matrix_float32ne_c); + } +} + +/* default C implementation */ +static pa_init_remap_func_t init_remap_func = init_remap_c; + +void pa_init_remap_func(pa_remap_t *m) { + pa_assert(init_remap_func); + + m->do_remap = NULL; + + /* call the installed remap init function */ + init_remap_func(m); + + if (m->do_remap == NULL) { + /* nothing was installed, fallback to C version */ + init_remap_c(m); + } +} + +pa_init_remap_func_t pa_get_init_remap_func(void) { + return init_remap_func; +} + +void pa_set_init_remap_func(pa_init_remap_func_t func) { + init_remap_func = func; +} + +void pa_remap_func_init(const pa_cpu_info *cpu_info) { + force_generic_code = cpu_info->force_generic_code; +} diff --git a/src/pulsecore/remap.h b/src/pulsecore/remap.h new file mode 100644 index 0000000..473f0ce --- /dev/null +++ b/src/pulsecore/remap.h @@ -0,0 +1,60 @@ +#ifndef fooremapfoo +#define fooremapfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2009 Wim Taymans <wim.taymans@collabora.co.uk.com> + + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulse/sample.h> + +typedef struct pa_remap pa_remap_t; + +typedef void (*pa_do_remap_func_t) (pa_remap_t *m, void *d, const void *s, unsigned n); + +struct pa_remap { + pa_sample_format_t format; + pa_sample_spec i_ss, o_ss; + float map_table_f[PA_CHANNELS_MAX][PA_CHANNELS_MAX]; + int32_t map_table_i[PA_CHANNELS_MAX][PA_CHANNELS_MAX]; + pa_do_remap_func_t do_remap; + void *state; /* optional state information for the remap function */ +}; + +void pa_init_remap_func(pa_remap_t *m); + +/* custom installation of init functions */ +typedef void (*pa_init_remap_func_t) (pa_remap_t *m); + +pa_init_remap_func_t pa_get_init_remap_func(void); +void pa_set_init_remap_func(pa_init_remap_func_t func); + +/* Check if remapping can be performed by just copying some or all input + * channels' data to output channels. Returns true and a table of input + * channel indices, or false otherwise. + * + * The table contains an entry for each output channels. Each table entry given + * either the input channel index to be copied, or -1 indicating that the + * output channel is not used and hence zero. + */ +bool pa_setup_remap_arrange(const pa_remap_t *m, int8_t arrange[PA_CHANNELS_MAX]); + +void pa_set_remap_func(pa_remap_t *m, pa_do_remap_func_t func_s16, + pa_do_remap_func_t func_s32, pa_do_remap_func_t func_float); + +#endif /* fooremapfoo */ diff --git a/src/pulsecore/remap_mmx.c b/src/pulsecore/remap_mmx.c new file mode 100644 index 0000000..9d07671 --- /dev/null +++ b/src/pulsecore/remap_mmx.c @@ -0,0 +1,155 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2009 Wim Taymans <wim.taymans@collabora.co.uk.com> + + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/sample.h> +#include <pulse/volume.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> + +#include "cpu-x86.h" +#include "remap.h" + +#define LOAD_SAMPLES \ + " movq (%1), %%mm0 \n\t" \ + " movq 8(%1), %%mm2 \n\t" \ + " movq 16(%1), %%mm4 \n\t" \ + " movq 24(%1), %%mm6 \n\t" \ + " movq %%mm0, %%mm1 \n\t" \ + " movq %%mm2, %%mm3 \n\t" \ + " movq %%mm4, %%mm5 \n\t" \ + " movq %%mm6, %%mm7 \n\t" + +#define UNPACK_SAMPLES(s) \ + " punpckl"#s" %%mm0, %%mm0 \n\t" \ + " punpckh"#s" %%mm1, %%mm1 \n\t" \ + " punpckl"#s" %%mm2, %%mm2 \n\t" \ + " punpckh"#s" %%mm3, %%mm3 \n\t" \ + " punpckl"#s" %%mm4, %%mm4 \n\t" \ + " punpckh"#s" %%mm5, %%mm5 \n\t" \ + " punpckl"#s" %%mm6, %%mm6 \n\t" \ + " punpckh"#s" %%mm7, %%mm7 \n\t" + +#define STORE_SAMPLES \ + " movq %%mm0, (%0) \n\t" \ + " movq %%mm1, 8(%0) \n\t" \ + " movq %%mm2, 16(%0) \n\t" \ + " movq %%mm3, 24(%0) \n\t" \ + " movq %%mm4, 32(%0) \n\t" \ + " movq %%mm5, 40(%0) \n\t" \ + " movq %%mm6, 48(%0) \n\t" \ + " movq %%mm7, 56(%0) \n\t" \ + " add $32, %1 \n\t" \ + " add $64, %0 \n\t" + +#define HANDLE_SINGLE_dq() \ + " movd (%1), %%mm0 \n\t" \ + " punpckldq %%mm0, %%mm0 \n\t" \ + " movq %%mm0, (%0) \n\t" \ + " add $4, %1 \n\t" \ + " add $8, %0 \n\t" + +#define HANDLE_SINGLE_wd() \ + " movw (%1), %w3 \n\t" \ + " movd %3, %%mm0 \n\t" \ + " punpcklwd %%mm0, %%mm0 \n\t" \ + " movd %%mm0, (%0) \n\t" \ + " add $2, %1 \n\t" \ + " add $4, %0 \n\t" + +#define MONO_TO_STEREO(s,shift,mask) \ + " mov %4, %2 \n\t" \ + " sar $"#shift", %2 \n\t" \ + " cmp $0, %2 \n\t" \ + " je 2f \n\t" \ + "1: \n\t" \ + LOAD_SAMPLES \ + UNPACK_SAMPLES(s) \ + STORE_SAMPLES \ + " dec %2 \n\t" \ + " jne 1b \n\t" \ + "2: \n\t" \ + " mov %4, %2 \n\t" \ + " and $"#mask", %2 \n\t" \ + " je 4f \n\t" \ + "3: \n\t" \ + HANDLE_SINGLE_##s() \ + " dec %2 \n\t" \ + " jne 3b \n\t" \ + "4: \n\t" \ + " emms \n\t" + +#if defined (__i386__) || defined (__amd64__) +static void remap_mono_to_stereo_s16ne_mmx(pa_remap_t *m, int16_t *dst, const int16_t *src, unsigned n) { + pa_reg_x86 temp, temp2; + + __asm__ __volatile__ ( + MONO_TO_STEREO(wd,4,15) /* do words to doubles */ + : "+r" (dst), "+r" (src), "=&r" (temp), "=&r" (temp2) + : "r" ((pa_reg_x86)n) + : "cc" + ); +} + +/* Works for both S32NE and FLOAT32NE */ +static void remap_mono_to_stereo_any32ne_mmx(pa_remap_t *m, float *dst, const float *src, unsigned n) { + pa_reg_x86 temp, temp2; + + __asm__ __volatile__ ( + MONO_TO_STEREO(dq,3,7) /* do doubles to quads */ + : "+r" (dst), "+r" (src), "=&r" (temp), "=&r" (temp2) + : "r" ((pa_reg_x86)n) + : "cc" + ); +} + +/* set the function that will execute the remapping based on the matrices */ +static void init_remap_mmx(pa_remap_t *m) { + unsigned n_oc, n_ic; + + n_oc = m->o_ss.channels; + n_ic = m->i_ss.channels; + + /* find some common channel remappings, fall back to full matrix operation. */ + if (n_ic == 1 && n_oc == 2 && + m->map_table_i[0][0] == 0x10000 && m->map_table_i[1][0] == 0x10000) { + + pa_log_info("Using MMX mono to stereo remapping"); + pa_set_remap_func(m, (pa_do_remap_func_t) remap_mono_to_stereo_s16ne_mmx, + (pa_do_remap_func_t) remap_mono_to_stereo_any32ne_mmx, + (pa_do_remap_func_t) remap_mono_to_stereo_any32ne_mmx); + } +} +#endif /* defined (__i386__) || defined (__amd64__) */ + +void pa_remap_func_init_mmx(pa_cpu_x86_flag_t flags) { +#if defined (__i386__) || defined (__amd64__) + + if (flags & PA_CPU_X86_MMX) { + pa_log_info("Initialising MMX optimized remappers."); + + pa_set_init_remap_func((pa_init_remap_func_t) init_remap_mmx); + } + +#endif /* defined (__i386__) || defined (__amd64__) */ +} diff --git a/src/pulsecore/remap_neon.c b/src/pulsecore/remap_neon.c new file mode 100644 index 0000000..6f71345 --- /dev/null +++ b/src/pulsecore/remap_neon.c @@ -0,0 +1,545 @@ +/*** + This file is part of PulseAudio. + + Copyright 2013 Peter Meerwald <p.meerwald@bct-electronic.com> + + 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.1 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. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/sample.h> +#include <pulse/xmalloc.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> + +#include "cpu-arm.h" +#include "remap.h" + +#include <arm_neon.h> + +static void remap_mono_to_stereo_float32ne_neon_a8(pa_remap_t *m, float *dst, const float *src, unsigned n) { + for (; n >= 4; n -= 4) { + __asm__ __volatile__ ( + "vld1.32 {q0}, [%[src]]! \n\t" + "vmov q1, q0 \n\t" + "vst2.32 {q0,q1}, [%[dst]]! \n\t" + : [dst] "+r" (dst), [src] "+r" (src) /* output operands */ + : /* input operands */ + : "memory", "q0", "q1" /* clobber list */ + ); + } + + for (; n > 0; n--) { + dst[0] = dst[1] = src[0]; + src++; + dst += 2; + } +} + +static void remap_mono_to_stereo_float32ne_generic_arm(pa_remap_t *m, float *dst, const float *src, unsigned n) { + for (; n >= 2; n -= 2) { + __asm__ __volatile__ ( + "ldm %[src]!, {r4,r6} \n\t" + "mov r5, r4 \n\t" + + /* We use r12 instead of r7 here, because r7 is reserved for the + * frame pointer when using Thumb. */ + "mov r12, r6 \n\t" + + "stm %[dst]!, {r4-r6,r12} \n\t" + : [dst] "+r" (dst), [src] "+r" (src) /* output operands */ + : /* input operands */ + : "memory", "r4", "r5", "r6", "r12" /* clobber list */ + ); + } + + if (n > 0) + dst[0] = dst[1] = src[0]; +} + +static void remap_mono_to_stereo_s16ne_neon(pa_remap_t *m, int16_t *dst, const int16_t *src, unsigned n) { + for (; n >= 8; n -= 8) { + __asm__ __volatile__ ( + "vld1.16 {q0}, [%[src]]! \n\t" + "vmov q1, q0 \n\t" + "vst2.16 {q0,q1}, [%[dst]]! \n\t" + : [dst] "+r" (dst), [src] "+r" (src) /* output operands */ + : /* input operands */ + : "memory", "q0", "q1" /* clobber list */ + ); + } + + for (; n > 0; n--) { + dst[0] = dst[1] = src[0]; + src++; + dst += 2; + } +} + +static void remap_mono_to_ch4_float32ne_neon(pa_remap_t *m, float *dst, const float *src, unsigned n) { + for (; n >= 2; n -= 2) { + __asm__ __volatile__ ( + "vld1.32 {d0}, [%[src]]! \n\t" + "vdup.f32 q1, d0[0] \n\t" + "vdup.f32 q2, d0[1] \n\t" + "vst1.32 {q1,q2}, [%[dst]]! \n\t" + : [dst] "+r" (dst), [src] "+r" (src) /* output operands */ + : /* input operands */ + : "memory", "q0", "q1", "q2" /* clobber list */ + ); + } + + if (n--) + dst[0] = dst[1] = dst[2] = dst[3] = src[0]; +} + +static void remap_mono_to_ch4_s16ne_neon(pa_remap_t *m, int16_t *dst, const int16_t *src, unsigned n) { + for (; n >= 4; n -= 4) { + __asm__ __volatile__ ( + "vld1.16 {d0}, [%[src]]! \n\t" + "vdup.s16 d1, d0[1] \n\t" + "vdup.s16 d2, d0[2] \n\t" + "vdup.s16 d3, d0[3] \n\t" + "vdup.s16 d0, d0[0] \n\t" + "vst1.16 {d0,d1,d2,d3}, [%[dst]]!\n\t" + : [dst] "+r" (dst), [src] "+r" (src) /* output operands */ + : /* input operands */ + : "memory", "d0", "d1", "d2", "d3" /* clobber list */ + ); + } + + for (; n > 0; n--) { + dst[0] = dst[1] = dst[2] = dst[3] = src[0]; + src++; + dst += 4; + } +} + +static void remap_stereo_to_mono_float32ne_neon(pa_remap_t *m, float *dst, const float *src, unsigned n) { + const float32x4_t halve = vdupq_n_f32(0.5f); + for (; n >= 4; n -= 4) { + __asm__ __volatile__ ( + "vld2.32 {q0,q1}, [%[src]]! \n\t" + "vadd.f32 q0, q0, q1 \n\t" + "vmul.f32 q0, q0, %q[halve] \n\t" + "vst1.32 {q0}, [%[dst]]! \n\t" + : [dst] "+r" (dst), [src] "+r" (src) /* output operands */ + : [halve] "w" (halve) /* input operands */ + : "memory", "q0", "q1" /* clobber list */ + ); + } + + for (; n > 0; n--) { + dst[0] = (src[0] + src[1])*0.5f; + src += 2; + dst++; + } +} + +static void remap_stereo_to_mono_s32ne_neon(pa_remap_t *m, int32_t *dst, const int32_t *src, unsigned n) { + for (; n >= 4; n -= 4) { + __asm__ __volatile__ ( + "vld2.32 {q0,q1}, [%[src]]! \n\t" + "vrhadd.s32 q0, q0, q1 \n\t" + "vst1.32 {q0}, [%[dst]]! \n\t" + : [dst] "+r" (dst), [src] "+r" (src) /* output operands */ + : /* input operands */ + : "memory", "q0", "q1" /* clobber list */ + ); + } + + for (; n > 0; n--) { + dst[0] = src[0]/2 + src[1]/2; + src += 2; + dst++; + } +} + +static void remap_stereo_to_mono_s16ne_neon(pa_remap_t *m, int16_t *dst, const int16_t *src, unsigned n) { + for (; n >= 8; n -= 8) { + __asm__ __volatile__ ( + "vld2.16 {q0,q1}, [%[src]]! \n\t" + "vrhadd.s16 q0, q0, q1 \n\t" + "vst1.16 {q0}, [%[dst]]! \n\t" + : [dst] "+r" (dst), [src] "+r" (src) /* output operands */ + : /* input operands */ + : "memory", "q0", "q1" /* clobber list */ + ); + } + + for (; n > 0; n--) { + dst[0] = (src[0] + src[1])/2; + src += 2; + dst++; + } +} + +static void remap_ch4_to_mono_float32ne_neon(pa_remap_t *m, float *dst, const float *src, unsigned n) { + const float32x2_t quart = vdup_n_f32(0.25f); + for (; n >= 2; n -= 2) { + __asm__ __volatile__ ( + "vld4.32 {d0,d1,d2,d3}, [%[src]]!\n\t" + "vadd.f32 d0, d0, d1 \n\t" + "vadd.f32 d2, d2, d3 \n\t" + "vadd.f32 d0, d0, d2 \n\t" + "vmul.f32 d0, d0, %P[quart] \n\t" + "vst1.32 {d0}, [%[dst]]! \n\t" + : [dst] "+r" (dst), [src] "+r" (src) /* output operands */ + : [quart] "w" (quart) /* input operands */ + : "memory", "d0", "d1", "d2", "d3" /* clobber list */ + ); + } + + if (n > 0) + dst[0] = (src[0] + src[1] + src[2] + src[3])*0.25f; +} + +static void remap_ch4_to_mono_s16ne_neon(pa_remap_t *m, int16_t *dst, const int16_t *src, unsigned n) { + for (; n >= 4; n -= 4) { + __asm__ __volatile__ ( + "vld4.16 {d0,d1,d2,d3}, [%[src]]!\n\t" + "vrhadd.s16 d0, d0, d1 \n\t" + "vrhadd.s16 d2, d2, d3 \n\t" + "vrhadd.s16 d0, d0, d2 \n\t" + "vst1.16 {d0}, [%[dst]]! \n\t" + : [dst] "+r" (dst), [src] "+r" (src) /* output operands */ + : /* input operands */ + : "memory", "d0", "d1", "d2", "d3" /* clobber list */ + ); + } + + for (; n > 0; n--) { + dst[0] = (src[0] + src[1] + src[2] + src[3])/4; + src += 4; + dst++; + } +} + +static void remap_ch4_s16ne_neon(pa_remap_t *m, int16_t *dst, const int16_t *src, unsigned n) { + int32x4_t *f = m->state; + const int32x4_t f0 = f[0], f1 = f[1], f2 = f[2], f3 = f[3]; + + for (; n > 0; n--) { + __asm__ __volatile__ ( + "vld1.16 {d0}, [%[src]]! \n\t" + "vmovl.s16 q0, d0 \n\t" + "vdup.s32 q1, d0[0] \n\t" + "vmul.s32 q1, q1, %q[f0] \n\t" + "vdup.s32 q2, d0[1] \n\t" + "vmla.s32 q1, q2, %q[f1] \n\t" + "vdup.s32 q2, d1[0] \n\t" + "vmla.s32 q1, q2, %q[f2] \n\t" + "vdup.s32 q2, d1[1] \n\t" + "vmla.s32 q1, q2, %q[f3] \n\t" + "vqshrn.s32 d2, q1, #16 \n\t" + "vst1.32 {d2}, [%[dst]]! \n\t" + : [dst] "+r" (dst), [src] "+r" (src) + : [f0] "w" (f0), [f1] "w" (f1), [f2] "w" (f2), [f3] "w" (f3) + : "memory", "q0", "q1", "q2" + ); + } +} + +static void remap_ch4_float32ne_neon(pa_remap_t *m, float *dst, const float *src, unsigned n) { + float32x4_t *f = m->state; + const float32x4_t f0 = f[0], f1 = f[1], f2 = f[2], f3 = f[3]; + + for (; n > 0; n--) { + __asm__ __volatile__ ( + "vld1.32 {d0,d1}, [%[src]]! \n\t" + "vdup.f32 q1, d0[0] \n\t" + "vmul.f32 q1, q1, %q[f0] \n\t" + "vdup.f32 q2, d0[1] \n\t" + "vmla.f32 q1, q2, %q[f1] \n\t" + "vdup.f32 q2, d1[0] \n\t" + "vmla.f32 q1, q2, %q[f2] \n\t" + "vdup.f32 q2, d1[1] \n\t" + "vmla.f32 q1, q2, %q[f3] \n\t" + "vst1.32 {d2,d3}, [%[dst]]! \n\t" + : [dst] "+r" (dst), [src] "+r" (src) + : [f0] "w" (f0), [f1] "w" (f1), [f2] "w" (f2), [f3] "w" (f3) + : "memory", "q0", "q1", "q2" + ); + } +} + +static void remap_arrange_stereo_s16ne_neon(pa_remap_t *m, int16_t *dst, const int16_t *src, unsigned n) { + const uint8x8_t t = ((uint8x8_t *) m->state)[0]; + + for (; n >= 2; n -= 2) { + __asm__ __volatile__ ( + "vld1.s16 d0, [%[src]]! \n\t" + "vtbl.8 d0, {d0}, %P[t] \n\t" + "vst1.s16 d0, [%[dst]]! \n\t" + : [dst] "+r" (dst), [src] "+r" (src) /* output operands */ + : [t] "w" (t) /* input operands */ + : "memory", "d0" /* clobber list */ + ); + } + + if (n > 0) { + __asm__ __volatile__ ( + "vld1.32 d0[0], [%[src]]! \n\t" + "vtbl.8 d0, {d0}, %P[t] \n\t" + "vst1.32 d0[0], [%[dst]]! \n\t" + : [dst] "+r" (dst), [src] "+r" (src) /* output operands */ + : [t] "w" (t) /* input operands */ + : "memory", "d0" /* clobber list */ + ); + } +} + +static void remap_arrange_ch2_ch4_s16ne_neon(pa_remap_t *m, int16_t *dst, const int16_t *src, unsigned n) { + const uint8x8_t t = ((uint8x8_t *) m->state)[0]; + + for (; n > 0; n--) { + __asm__ __volatile__ ( + "vld1.32 d0[0], [%[src]]! \n\t" + "vtbl.8 d0, {d0}, %P[t] \n\t" + "vst1.s16 d0, [%[dst]]! \n\t" + : [dst] "+r" (dst), [src] "+r" (src) /* output operands */ + : [t] "w" (t) /* input operands */ + : "memory", "d0" /* clobber list */ + ); + } +} + +static void remap_arrange_ch4_s16ne_neon(pa_remap_t *m, int16_t *dst, const int16_t *src, unsigned n) { + const uint8x8_t t = ((uint8x8_t *) m->state)[0]; + + for (; n > 0; n--) { + __asm__ __volatile__ ( + "vld1.s16 d0, [%[src]]! \n\t" + "vtbl.8 d0, {d0}, %P[t] \n\t" + "vst1.s16 d0, [%[dst]]! \n\t" + : [dst] "+r" (dst), [src] "+r" (src) /* output operands */ + : [t] "w" (t) /* input operands */ + : "memory", "d0" /* clobber list */ + ); + } +} + +static void remap_arrange_stereo_float32ne_neon(pa_remap_t *m, float *dst, const float *src, unsigned n) { + const uint8x8_t t = ((uint8x8_t *)m->state)[0]; + + for (; n > 0; n--) { + __asm__ __volatile__ ( + "vld1.f32 d0, [%[src]]! \n\t" + "vtbl.8 d0, {d0}, %P[t] \n\t" + "vst1.s16 {d0}, [%[dst]]! \n\t" + : [dst] "+r" (dst), [src] "+r" (src) /* output operands */ + : [t] "w" (t) /* input operands */ + : "memory", "d0" /* clobber list */ + ); + } +} + +/* Works for both S32NE and FLOAT32NE */ +static void remap_arrange_ch2_ch4_any32ne_neon(pa_remap_t *m, float *dst, const float *src, unsigned n) { + const uint8x8_t t0 = ((uint8x8_t *)m->state)[0]; + const uint8x8_t t1 = ((uint8x8_t *)m->state)[1]; + + for (; n > 0; n--) { + __asm__ __volatile__ ( + "vld1.f32 d0, [%[src]]! \n\t" + "vtbl.8 d1, {d0}, %P[t0] \n\t" + "vtbl.8 d2, {d0}, %P[t1] \n\t" + "vst1.s16 {d1,d2}, [%[dst]]! \n\t" + : [dst] "+r" (dst), [src] "+r" (src) /* output operands */ + : [t0] "w" (t0), [t1] "w" (t1) /* input operands */ + : "memory", "d0", "d1", "d2" /* clobber list */ + ); + } +} + +static void remap_arrange_ch4_float32ne_neon(pa_remap_t *m, float *dst, const float *src, unsigned n) { + const uint8x8_t t0 = ((uint8x8_t *)m->state)[0]; + const uint8x8_t t1 = ((uint8x8_t *)m->state)[1]; + + for (; n > 0; n--) { + __asm__ __volatile__ ( + "vld1.f32 {d0,d1}, [%[src]]! \n\t" + "vtbl.8 d2, {d0,d1}, %P[t0] \n\t" + "vtbl.8 d3, {d0,d1}, %P[t1] \n\t" + "vst1.s16 {d2,d3}, [%[dst]]! \n\t" + : [dst] "+r" (dst), [src] "+r" (src) /* output operands */ + : [t0] "w" (t0), [t1] "w" (t1) /* input operands */ + : "memory", "d0", "d1", "d2", "d3" /* clobber list */ + ); + } +} + +static pa_cpu_arm_flag_t arm_flags; + +static void init_remap_neon(pa_remap_t *m) { + unsigned n_oc, n_ic; + int8_t arrange[PA_CHANNELS_MAX]; + + n_oc = m->o_ss.channels; + n_ic = m->i_ss.channels; + + /* We short-circuit remap function selection for S32NE in most + * cases as the corresponding generic C code is performing + * similarly or even better. However there are a few cases where + * there actually is a significant improvement from using + * hand-crafted NEON assembly so we cannot just bail out for S32NE + * here. */ + if (n_ic == 1 && n_oc == 2 && + m->map_table_i[0][0] == 0x10000 && m->map_table_i[1][0] == 0x10000) { + if (m->format == PA_SAMPLE_S32NE) + return; + if (arm_flags & PA_CPU_ARM_CORTEX_A8) { + + pa_log_info("Using ARM NEON/A8 mono to stereo remapping"); + pa_set_remap_func(m, (pa_do_remap_func_t) remap_mono_to_stereo_s16ne_neon, + NULL, (pa_do_remap_func_t) remap_mono_to_stereo_float32ne_neon_a8); + } + else { + pa_log_info("Using ARM NEON mono to stereo remapping"); + pa_set_remap_func(m, (pa_do_remap_func_t) remap_mono_to_stereo_s16ne_neon, + NULL, (pa_do_remap_func_t) remap_mono_to_stereo_float32ne_generic_arm); + } + } else if (n_ic == 1 && n_oc == 4 && + m->map_table_i[0][0] == 0x10000 && m->map_table_i[1][0] == 0x10000 && + m->map_table_i[2][0] == 0x10000 && m->map_table_i[3][0] == 0x10000) { + + if (m->format == PA_SAMPLE_S32NE) + return; + pa_log_info("Using ARM NEON mono to 4-channel remapping"); + pa_set_remap_func(m, (pa_do_remap_func_t) remap_mono_to_ch4_s16ne_neon, + NULL, (pa_do_remap_func_t) remap_mono_to_ch4_float32ne_neon); + } else if (n_ic == 2 && n_oc == 1 && + m->map_table_i[0][0] == 0x8000 && m->map_table_i[0][1] == 0x8000) { + + pa_log_info("Using ARM NEON stereo to mono remapping"); + pa_set_remap_func(m, (pa_do_remap_func_t) remap_stereo_to_mono_s16ne_neon, + (pa_do_remap_func_t) remap_stereo_to_mono_s32ne_neon, + (pa_do_remap_func_t) remap_stereo_to_mono_float32ne_neon); + } else if (n_ic == 4 && n_oc == 1 && + m->map_table_i[0][0] == 0x4000 && m->map_table_i[0][1] == 0x4000 && + m->map_table_i[0][2] == 0x4000 && m->map_table_i[0][3] == 0x4000) { + + if (m->format == PA_SAMPLE_S32NE) + return; + pa_log_info("Using ARM NEON 4-channel to mono remapping"); + pa_set_remap_func(m, (pa_do_remap_func_t) remap_ch4_to_mono_s16ne_neon, + NULL, (pa_do_remap_func_t) remap_ch4_to_mono_float32ne_neon); + } else if (pa_setup_remap_arrange(m, arrange) && + ((n_ic == 2 && n_oc == 2) || + (n_ic == 2 && n_oc == 4) || + (n_ic == 4 && n_oc == 4))) { + unsigned o; + + if (n_ic == 2 && n_oc == 2) { + if (m->format == PA_SAMPLE_S32NE) + return; + pa_log_info("Using NEON stereo arrange remapping"); + pa_set_remap_func(m, (pa_do_remap_func_t) remap_arrange_stereo_s16ne_neon, + NULL, (pa_do_remap_func_t) remap_arrange_stereo_float32ne_neon); + } else if (n_ic == 2 && n_oc == 4) { + pa_log_info("Using NEON 2-channel to 4-channel arrange remapping"); + pa_set_remap_func(m, (pa_do_remap_func_t) remap_arrange_ch2_ch4_s16ne_neon, + (pa_do_remap_func_t) remap_arrange_ch2_ch4_any32ne_neon, + (pa_do_remap_func_t) remap_arrange_ch2_ch4_any32ne_neon); + } else if (n_ic == 4 && n_oc == 4) { + if (m->format == PA_SAMPLE_S32NE) + return; + pa_log_info("Using NEON 4-channel arrange remapping"); + pa_set_remap_func(m, (pa_do_remap_func_t) remap_arrange_ch4_s16ne_neon, + NULL, (pa_do_remap_func_t) remap_arrange_ch4_float32ne_neon); + } + + /* setup state */ + switch (m->format) { + case PA_SAMPLE_S16NE: { + uint8x8_t *t = m->state = pa_xnew0(uint8x8_t, 1); + for (o = 0; o < 4; o++) { + if (arrange[o % n_oc] >= 0) { + /* convert channel index to vtbl indices */ + unsigned frame = o / n_oc; + ((uint8_t *) t)[o * 2 + 0] = (frame * n_oc + arrange[o % n_oc]) * 2 + 0; + ((uint8_t *) t)[o * 2 + 1] = (frame * n_oc + arrange[o % n_oc]) * 2 + 1; + } else { + /* use invalid table indices to map to 0 */ + ((uint8_t *) t)[o * 2 + 0] = 0xff; + ((uint8_t *) t)[o * 2 + 1] = 0xff; + } + } + break; + } + case PA_SAMPLE_S32NE: + /* fall-through */ + case PA_SAMPLE_FLOAT32NE: { + uint8x8_t *t = m->state = pa_xnew0(uint8x8_t, 2); + for (o = 0; o < n_oc; o++) { + if (arrange[o] >= 0) { + /* convert channel index to vtbl indices */ + ((uint8_t *) t)[o * 4 + 0] = arrange[o] * 4 + 0; + ((uint8_t *) t)[o * 4 + 1] = arrange[o] * 4 + 1; + ((uint8_t *) t)[o * 4 + 2] = arrange[o] * 4 + 2; + ((uint8_t *) t)[o * 4 + 3] = arrange[o] * 4 + 3; + } else { + /* use invalid table indices to map to 0 */ + ((uint8_t *) t)[o * 4 + 0] = 0xff; + ((uint8_t *) t)[o * 4 + 1] = 0xff; + ((uint8_t *) t)[o * 4 + 2] = 0xff; + ((uint8_t *) t)[o * 4 + 3] = 0xff; + } + } + break; + } + default: + pa_assert_not_reached(); + } + } else if (n_ic == 4 && n_oc == 4) { + unsigned i, o; + + if (m->format == PA_SAMPLE_S32NE) + return; + pa_log_info("Using ARM NEON 4-channel remapping"); + pa_set_remap_func(m, (pa_do_remap_func_t) remap_ch4_s16ne_neon, + (pa_do_remap_func_t) NULL, + (pa_do_remap_func_t) remap_ch4_float32ne_neon); + + /* setup state */ + switch (m->format) { + case PA_SAMPLE_S16NE: { + int32x4_t *f = m->state = pa_xnew0(int32x4_t, 4); + for (o = 0; o < 4; o++) { + for (i = 0; i < 4; i++) { + ((int *) &f[i])[o] = PA_CLAMP_UNLIKELY(m->map_table_i[o][i], 0, 0x10000); + } + } + break; + } + case PA_SAMPLE_FLOAT32NE: { + float32x4_t *f = m->state = pa_xnew0(float32x4_t, 4); + for (o = 0; o < 4; o++) { + for (i = 0; i < 4; i++) { + ((float *) &f[i])[o] = PA_CLAMP_UNLIKELY(m->map_table_f[o][i], 0.0f, 1.0f); + } + } + break; + } + default: + pa_assert_not_reached(); + } + } +} + +void pa_remap_func_init_neon(pa_cpu_arm_flag_t flags) { + pa_log_info("Initialising ARM NEON optimized remappers."); + arm_flags = flags; + pa_set_init_remap_func((pa_init_remap_func_t) init_remap_neon); +} diff --git a/src/pulsecore/remap_sse.c b/src/pulsecore/remap_sse.c new file mode 100644 index 0000000..5c3b931 --- /dev/null +++ b/src/pulsecore/remap_sse.c @@ -0,0 +1,153 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2009 Wim Taymans <wim.taymans@collabora.co.uk.com> + + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/sample.h> +#include <pulse/volume.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> + +#include "cpu-x86.h" +#include "remap.h" + +#define LOAD_SAMPLES \ + " movdqu (%1), %%xmm0 \n\t" \ + " movdqu 16(%1), %%xmm2 \n\t" \ + " movdqu 32(%1), %%xmm4 \n\t" \ + " movdqu 48(%1), %%xmm6 \n\t" \ + " movdqa %%xmm0, %%xmm1 \n\t" \ + " movdqa %%xmm2, %%xmm3 \n\t" \ + " movdqa %%xmm4, %%xmm5 \n\t" \ + " movdqa %%xmm6, %%xmm7 \n\t" + +#define UNPACK_SAMPLES(s) \ + " punpckl"#s" %%xmm0, %%xmm0 \n\t" \ + " punpckh"#s" %%xmm1, %%xmm1 \n\t" \ + " punpckl"#s" %%xmm2, %%xmm2 \n\t" \ + " punpckh"#s" %%xmm3, %%xmm3 \n\t" \ + " punpckl"#s" %%xmm4, %%xmm4 \n\t" \ + " punpckh"#s" %%xmm5, %%xmm5 \n\t" \ + " punpckl"#s" %%xmm6, %%xmm6 \n\t" \ + " punpckh"#s" %%xmm7, %%xmm7 \n\t" + +#define STORE_SAMPLES \ + " movdqu %%xmm0, (%0) \n\t" \ + " movdqu %%xmm1, 16(%0) \n\t" \ + " movdqu %%xmm2, 32(%0) \n\t" \ + " movdqu %%xmm3, 48(%0) \n\t" \ + " movdqu %%xmm4, 64(%0) \n\t" \ + " movdqu %%xmm5, 80(%0) \n\t" \ + " movdqu %%xmm6, 96(%0) \n\t" \ + " movdqu %%xmm7, 112(%0) \n\t" \ + " add $64, %1 \n\t" \ + " add $128, %0 \n\t" + +#define HANDLE_SINGLE_dq() \ + " movd (%1), %%xmm0 \n\t" \ + " punpckldq %%xmm0, %%xmm0 \n\t" \ + " movq %%xmm0, (%0) \n\t" \ + " add $4, %1 \n\t" \ + " add $8, %0 \n\t" + +#define HANDLE_SINGLE_wd() \ + " movw (%1), %w3 \n\t" \ + " movd %3, %%xmm0 \n\t" \ + " punpcklwd %%xmm0, %%xmm0 \n\t" \ + " movd %%xmm0, (%0) \n\t" \ + " add $2, %1 \n\t" \ + " add $4, %0 \n\t" + +#define MONO_TO_STEREO(s,shift,mask) \ + " mov %4, %2 \n\t" \ + " sar $"#shift", %2 \n\t" \ + " cmp $0, %2 \n\t" \ + " je 2f \n\t" \ + "1: \n\t" \ + LOAD_SAMPLES \ + UNPACK_SAMPLES(s) \ + STORE_SAMPLES \ + " dec %2 \n\t" \ + " jne 1b \n\t" \ + "2: \n\t" \ + " mov %4, %2 \n\t" \ + " and $"#mask", %2 \n\t" \ + " je 4f \n\t" \ + "3: \n\t" \ + HANDLE_SINGLE_##s() \ + " dec %2 \n\t" \ + " jne 3b \n\t" \ + "4: \n\t" + +#if defined (__i386__) || defined (__amd64__) +static void remap_mono_to_stereo_s16ne_sse2(pa_remap_t *m, int16_t *dst, const int16_t *src, unsigned n) { + pa_reg_x86 temp, temp2; + + __asm__ __volatile__ ( + MONO_TO_STEREO(wd, 5, 31) /* do words to doubles */ + : "+r" (dst), "+r" (src), "=&r" (temp), "=&r" (temp2) + : "r" ((pa_reg_x86)n) + : "cc" + ); +} + +/* Works for both S32NE and FLOAT32NE */ +static void remap_mono_to_stereo_any32ne_sse2(pa_remap_t *m, float *dst, const float *src, unsigned n) { + pa_reg_x86 temp, temp2; + + __asm__ __volatile__ ( + MONO_TO_STEREO(dq, 4, 15) /* do doubles to quads */ + : "+r" (dst), "+r" (src), "=&r" (temp), "=&r" (temp2) + : "r" ((pa_reg_x86)n) + : "cc" + ); +} + +/* set the function that will execute the remapping based on the matrices */ +static void init_remap_sse2(pa_remap_t *m) { + unsigned n_oc, n_ic; + + n_oc = m->o_ss.channels; + n_ic = m->i_ss.channels; + + /* find some common channel remappings, fall back to full matrix operation. */ + if (n_ic == 1 && n_oc == 2 && + m->map_table_i[0][0] == 0x10000 && m->map_table_i[1][0] == 0x10000) { + + pa_log_info("Using SSE2 mono to stereo remapping"); + pa_set_remap_func(m, (pa_do_remap_func_t) remap_mono_to_stereo_s16ne_sse2, + (pa_do_remap_func_t) remap_mono_to_stereo_any32ne_sse2, + (pa_do_remap_func_t) remap_mono_to_stereo_any32ne_sse2); + } +} +#endif /* defined (__i386__) || defined (__amd64__) */ + +void pa_remap_func_init_sse(pa_cpu_x86_flag_t flags) { +#if defined (__i386__) || defined (__amd64__) + + if (flags & PA_CPU_X86_SSE2) { + pa_log_info("Initialising SSE2 optimized remappers."); + pa_set_init_remap_func ((pa_init_remap_func_t) init_remap_sse2); + } + +#endif /* defined (__i386__) || defined (__amd64__) */ +} diff --git a/src/pulsecore/resampler.c b/src/pulsecore/resampler.c new file mode 100644 index 0000000..58463f1 --- /dev/null +++ b/src/pulsecore/resampler.c @@ -0,0 +1,1507 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> +#include <pulsecore/strbuf.h> +#include <pulsecore/core-util.h> + +#include "resampler.h" + +/* Number of samples of extra space we allow the resamplers to return */ +#define EXTRA_FRAMES 128 + +struct ffmpeg_data { /* data specific to ffmpeg */ + struct AVResampleContext *state; +}; + +static int copy_init(pa_resampler *r); + +static void setup_remap(const pa_resampler *r, pa_remap_t *m, bool *lfe_remixed); +static void free_remap(pa_remap_t *m); + +static int (* const init_table[])(pa_resampler *r) = { +#ifdef HAVE_LIBSAMPLERATE + [PA_RESAMPLER_SRC_SINC_BEST_QUALITY] = pa_resampler_libsamplerate_init, + [PA_RESAMPLER_SRC_SINC_MEDIUM_QUALITY] = pa_resampler_libsamplerate_init, + [PA_RESAMPLER_SRC_SINC_FASTEST] = pa_resampler_libsamplerate_init, + [PA_RESAMPLER_SRC_ZERO_ORDER_HOLD] = pa_resampler_libsamplerate_init, + [PA_RESAMPLER_SRC_LINEAR] = pa_resampler_libsamplerate_init, +#else + [PA_RESAMPLER_SRC_SINC_BEST_QUALITY] = NULL, + [PA_RESAMPLER_SRC_SINC_MEDIUM_QUALITY] = NULL, + [PA_RESAMPLER_SRC_SINC_FASTEST] = NULL, + [PA_RESAMPLER_SRC_ZERO_ORDER_HOLD] = NULL, + [PA_RESAMPLER_SRC_LINEAR] = NULL, +#endif + [PA_RESAMPLER_TRIVIAL] = pa_resampler_trivial_init, +#ifdef HAVE_SPEEX + [PA_RESAMPLER_SPEEX_FLOAT_BASE+0] = pa_resampler_speex_init, + [PA_RESAMPLER_SPEEX_FLOAT_BASE+1] = pa_resampler_speex_init, + [PA_RESAMPLER_SPEEX_FLOAT_BASE+2] = pa_resampler_speex_init, + [PA_RESAMPLER_SPEEX_FLOAT_BASE+3] = pa_resampler_speex_init, + [PA_RESAMPLER_SPEEX_FLOAT_BASE+4] = pa_resampler_speex_init, + [PA_RESAMPLER_SPEEX_FLOAT_BASE+5] = pa_resampler_speex_init, + [PA_RESAMPLER_SPEEX_FLOAT_BASE+6] = pa_resampler_speex_init, + [PA_RESAMPLER_SPEEX_FLOAT_BASE+7] = pa_resampler_speex_init, + [PA_RESAMPLER_SPEEX_FLOAT_BASE+8] = pa_resampler_speex_init, + [PA_RESAMPLER_SPEEX_FLOAT_BASE+9] = pa_resampler_speex_init, + [PA_RESAMPLER_SPEEX_FLOAT_BASE+10] = pa_resampler_speex_init, + [PA_RESAMPLER_SPEEX_FIXED_BASE+0] = pa_resampler_speex_init, + [PA_RESAMPLER_SPEEX_FIXED_BASE+1] = pa_resampler_speex_init, + [PA_RESAMPLER_SPEEX_FIXED_BASE+2] = pa_resampler_speex_init, + [PA_RESAMPLER_SPEEX_FIXED_BASE+3] = pa_resampler_speex_init, + [PA_RESAMPLER_SPEEX_FIXED_BASE+4] = pa_resampler_speex_init, + [PA_RESAMPLER_SPEEX_FIXED_BASE+5] = pa_resampler_speex_init, + [PA_RESAMPLER_SPEEX_FIXED_BASE+6] = pa_resampler_speex_init, + [PA_RESAMPLER_SPEEX_FIXED_BASE+7] = pa_resampler_speex_init, + [PA_RESAMPLER_SPEEX_FIXED_BASE+8] = pa_resampler_speex_init, + [PA_RESAMPLER_SPEEX_FIXED_BASE+9] = pa_resampler_speex_init, + [PA_RESAMPLER_SPEEX_FIXED_BASE+10] = pa_resampler_speex_init, +#else + [PA_RESAMPLER_SPEEX_FLOAT_BASE+0] = NULL, + [PA_RESAMPLER_SPEEX_FLOAT_BASE+1] = NULL, + [PA_RESAMPLER_SPEEX_FLOAT_BASE+2] = NULL, + [PA_RESAMPLER_SPEEX_FLOAT_BASE+3] = NULL, + [PA_RESAMPLER_SPEEX_FLOAT_BASE+4] = NULL, + [PA_RESAMPLER_SPEEX_FLOAT_BASE+5] = NULL, + [PA_RESAMPLER_SPEEX_FLOAT_BASE+6] = NULL, + [PA_RESAMPLER_SPEEX_FLOAT_BASE+7] = NULL, + [PA_RESAMPLER_SPEEX_FLOAT_BASE+8] = NULL, + [PA_RESAMPLER_SPEEX_FLOAT_BASE+9] = NULL, + [PA_RESAMPLER_SPEEX_FLOAT_BASE+10] = NULL, + [PA_RESAMPLER_SPEEX_FIXED_BASE+0] = NULL, + [PA_RESAMPLER_SPEEX_FIXED_BASE+1] = NULL, + [PA_RESAMPLER_SPEEX_FIXED_BASE+2] = NULL, + [PA_RESAMPLER_SPEEX_FIXED_BASE+3] = NULL, + [PA_RESAMPLER_SPEEX_FIXED_BASE+4] = NULL, + [PA_RESAMPLER_SPEEX_FIXED_BASE+5] = NULL, + [PA_RESAMPLER_SPEEX_FIXED_BASE+6] = NULL, + [PA_RESAMPLER_SPEEX_FIXED_BASE+7] = NULL, + [PA_RESAMPLER_SPEEX_FIXED_BASE+8] = NULL, + [PA_RESAMPLER_SPEEX_FIXED_BASE+9] = NULL, + [PA_RESAMPLER_SPEEX_FIXED_BASE+10] = NULL, +#endif + [PA_RESAMPLER_FFMPEG] = pa_resampler_ffmpeg_init, + [PA_RESAMPLER_AUTO] = NULL, + [PA_RESAMPLER_COPY] = copy_init, + [PA_RESAMPLER_PEAKS] = pa_resampler_peaks_init, +#ifdef HAVE_SOXR + [PA_RESAMPLER_SOXR_MQ] = pa_resampler_soxr_init, + [PA_RESAMPLER_SOXR_HQ] = pa_resampler_soxr_init, + [PA_RESAMPLER_SOXR_VHQ] = pa_resampler_soxr_init, +#else + [PA_RESAMPLER_SOXR_MQ] = NULL, + [PA_RESAMPLER_SOXR_HQ] = NULL, + [PA_RESAMPLER_SOXR_VHQ] = NULL, +#endif +}; + +static pa_resample_method_t choose_auto_resampler(pa_resample_flags_t flags) { + pa_resample_method_t method; + + if (pa_resample_method_supported(PA_RESAMPLER_SPEEX_FLOAT_BASE + 1)) + method = PA_RESAMPLER_SPEEX_FLOAT_BASE + 1; + else if (flags & PA_RESAMPLER_VARIABLE_RATE) + method = PA_RESAMPLER_TRIVIAL; + else + method = PA_RESAMPLER_FFMPEG; + + return method; +} + +static pa_resample_method_t fix_method( + pa_resample_flags_t flags, + pa_resample_method_t method, + const uint32_t rate_a, + const uint32_t rate_b) { + + pa_assert(pa_sample_rate_valid(rate_a)); + pa_assert(pa_sample_rate_valid(rate_b)); + pa_assert(method >= 0); + pa_assert(method < PA_RESAMPLER_MAX); + + if (!(flags & PA_RESAMPLER_VARIABLE_RATE) && rate_a == rate_b) { + pa_log_info("Forcing resampler 'copy', because of fixed, identical sample rates."); + method = PA_RESAMPLER_COPY; + } + + if (!pa_resample_method_supported(method)) { + pa_log_warn("Support for resampler '%s' not compiled in, reverting to 'auto'.", pa_resample_method_to_string(method)); + method = PA_RESAMPLER_AUTO; + } + + switch (method) { + case PA_RESAMPLER_COPY: + if (rate_a != rate_b) { + pa_log_info("Resampler 'copy' cannot change sampling rate, reverting to resampler 'auto'."); + method = PA_RESAMPLER_AUTO; + break; + } + /* Else fall through */ + case PA_RESAMPLER_FFMPEG: + case PA_RESAMPLER_SOXR_MQ: + case PA_RESAMPLER_SOXR_HQ: + case PA_RESAMPLER_SOXR_VHQ: + if (flags & PA_RESAMPLER_VARIABLE_RATE) { + pa_log_info("Resampler '%s' cannot do variable rate, reverting to resampler 'auto'.", pa_resample_method_to_string(method)); + method = PA_RESAMPLER_AUTO; + } + break; + + /* The Peaks resampler only supports downsampling. + * Revert to auto if we are upsampling */ + case PA_RESAMPLER_PEAKS: + if (rate_a < rate_b) { + pa_log_warn("The 'peaks' resampler only supports downsampling, reverting to resampler 'auto'."); + method = PA_RESAMPLER_AUTO; + } + break; + + default: + break; + } + + if (method == PA_RESAMPLER_AUTO) + method = choose_auto_resampler(flags); + +#ifdef HAVE_SPEEX + /* At this point, method is supported in the sense that it + * has an init function and supports the required flags. However, + * speex-float implementation in PulseAudio relies on the + * assumption that is invalid if speex has been compiled with + * --enable-fixed-point. Besides, speex-fixed is more efficient + * in this configuration. So use it instead. + */ + if (method >= PA_RESAMPLER_SPEEX_FLOAT_BASE && method <= PA_RESAMPLER_SPEEX_FLOAT_MAX) { + if (pa_speex_is_fixed_point()) { + pa_log_info("Speex appears to be compiled with --enable-fixed-point. " + "Switching to a fixed-point resampler because it should be faster."); + method = method - PA_RESAMPLER_SPEEX_FLOAT_BASE + PA_RESAMPLER_SPEEX_FIXED_BASE; + } + } +#endif + + return method; +} + +/* Return true if a is a more precise sample format than b, else return false */ +static bool sample_format_more_precise(pa_sample_format_t a, pa_sample_format_t b) { + pa_assert(pa_sample_format_valid(a)); + pa_assert(pa_sample_format_valid(b)); + + switch (a) { + case PA_SAMPLE_U8: + case PA_SAMPLE_ALAW: + case PA_SAMPLE_ULAW: + return false; + break; + + case PA_SAMPLE_S16LE: + case PA_SAMPLE_S16BE: + if (b == PA_SAMPLE_ULAW || b == PA_SAMPLE_ALAW || b == PA_SAMPLE_U8) + return true; + else + return false; + break; + + case PA_SAMPLE_S24LE: + case PA_SAMPLE_S24BE: + case PA_SAMPLE_S24_32LE: + case PA_SAMPLE_S24_32BE: + if (b == PA_SAMPLE_ULAW || b == PA_SAMPLE_ALAW || b == PA_SAMPLE_U8 || + b == PA_SAMPLE_S16LE || b == PA_SAMPLE_S16BE) + return true; + else + return false; + break; + + case PA_SAMPLE_FLOAT32LE: + case PA_SAMPLE_FLOAT32BE: + case PA_SAMPLE_S32LE: + case PA_SAMPLE_S32BE: + if (b == PA_SAMPLE_FLOAT32LE || b == PA_SAMPLE_FLOAT32BE || + b == PA_SAMPLE_S32LE || b == PA_SAMPLE_S32BE) + return false; + else + return true; + break; + + default: + return false; + } +} + +static pa_sample_format_t choose_work_format( + pa_resample_method_t method, + pa_sample_format_t a, + pa_sample_format_t b, + bool map_required) { + pa_sample_format_t work_format; + + pa_assert(pa_sample_format_valid(a)); + pa_assert(pa_sample_format_valid(b)); + pa_assert(method >= 0); + pa_assert(method < PA_RESAMPLER_MAX); + + if (method >= PA_RESAMPLER_SPEEX_FIXED_BASE && method <= PA_RESAMPLER_SPEEX_FIXED_MAX) + method = PA_RESAMPLER_SPEEX_FIXED_BASE; + + switch (method) { + /* This block is for resampling functions that only + * support the S16 sample format. */ + case PA_RESAMPLER_SPEEX_FIXED_BASE: + case PA_RESAMPLER_FFMPEG: + work_format = PA_SAMPLE_S16NE; + break; + + /* This block is for resampling functions that support + * any sample format. */ + case PA_RESAMPLER_COPY: + case PA_RESAMPLER_TRIVIAL: + if (!map_required && a == b) { + work_format = a; + break; + } + /* If both input and output are using S32NE and we don't + * need any resampling we can use S32NE directly, avoiding + * converting back and forth between S32NE and + * FLOAT32NE. */ + if ((a == PA_SAMPLE_S32NE) && (b == PA_SAMPLE_S32NE)) { + work_format = PA_SAMPLE_S32NE; + break; + } + /* Else fall through */ + case PA_RESAMPLER_PEAKS: + /* PEAKS, COPY and TRIVIAL do not benefit from increased + * working precision, so for better performance use s16ne + * if either input or output fits in it. */ + if (a == PA_SAMPLE_S16NE || b == PA_SAMPLE_S16NE) { + work_format = PA_SAMPLE_S16NE; + break; + } + /* Else fall through */ + case PA_RESAMPLER_SOXR_MQ: + case PA_RESAMPLER_SOXR_HQ: + case PA_RESAMPLER_SOXR_VHQ: + /* Do processing with max precision of input and output. */ + if (sample_format_more_precise(a, PA_SAMPLE_S16NE) || + sample_format_more_precise(b, PA_SAMPLE_S16NE)) + work_format = PA_SAMPLE_FLOAT32NE; + else + work_format = PA_SAMPLE_S16NE; + break; + + default: + work_format = PA_SAMPLE_FLOAT32NE; + } + + return work_format; +} + +pa_resampler* pa_resampler_new( + pa_mempool *pool, + const pa_sample_spec *a, + const pa_channel_map *am, + const pa_sample_spec *b, + const pa_channel_map *bm, + unsigned crossover_freq, + pa_resample_method_t method, + pa_resample_flags_t flags) { + + pa_resampler *r = NULL; + bool lfe_remixed = false; + + pa_assert(pool); + pa_assert(a); + pa_assert(b); + pa_assert(pa_sample_spec_valid(a)); + pa_assert(pa_sample_spec_valid(b)); + pa_assert(method >= 0); + pa_assert(method < PA_RESAMPLER_MAX); + + method = fix_method(flags, method, a->rate, b->rate); + + r = pa_xnew0(pa_resampler, 1); + r->mempool = pool; + r->method = method; + r->flags = flags; + + /* Fill sample specs */ + r->i_ss = *a; + r->o_ss = *b; + + if (am) + r->i_cm = *am; + else if (!pa_channel_map_init_auto(&r->i_cm, r->i_ss.channels, PA_CHANNEL_MAP_DEFAULT)) + goto fail; + + if (bm) + r->o_cm = *bm; + else if (!pa_channel_map_init_auto(&r->o_cm, r->o_ss.channels, PA_CHANNEL_MAP_DEFAULT)) + goto fail; + + r->i_fz = pa_frame_size(a); + r->o_fz = pa_frame_size(b); + + r->map_required = (r->i_ss.channels != r->o_ss.channels || (!(r->flags & PA_RESAMPLER_NO_REMAP) && + !pa_channel_map_equal(&r->i_cm, &r->o_cm))); + + r->work_format = choose_work_format(method, a->format, b->format, r->map_required); + r->w_sz = pa_sample_size_of_format(r->work_format); + + if (r->i_ss.format != r->work_format) { + if (r->work_format == PA_SAMPLE_FLOAT32NE) { + if (!(r->to_work_format_func = pa_get_convert_to_float32ne_function(r->i_ss.format))) + goto fail; + } else { + pa_assert(r->work_format == PA_SAMPLE_S16NE); + if (!(r->to_work_format_func = pa_get_convert_to_s16ne_function(r->i_ss.format))) + goto fail; + } + } + + if (r->o_ss.format != r->work_format) { + if (r->work_format == PA_SAMPLE_FLOAT32NE) { + if (!(r->from_work_format_func = pa_get_convert_from_float32ne_function(r->o_ss.format))) + goto fail; + } else { + pa_assert(r->work_format == PA_SAMPLE_S16NE); + if (!(r->from_work_format_func = pa_get_convert_from_s16ne_function(r->o_ss.format))) + goto fail; + } + } + + if (r->o_ss.channels <= r->i_ss.channels) { + /* pipeline is: format conv. -> remap -> resample -> format conv. */ + r->work_channels = r->o_ss.channels; + + /* leftover buffer is remap output buffer (before resampling) */ + r->leftover_buf = &r->remap_buf; + r->leftover_buf_size = &r->remap_buf_size; + r->have_leftover = &r->leftover_in_remap; + } else { + /* pipeline is: format conv. -> resample -> remap -> format conv. */ + r->work_channels = r->i_ss.channels; + + /* leftover buffer is to_work output buffer (before resampling) */ + r->leftover_buf = &r->to_work_format_buf; + r->leftover_buf_size = &r->to_work_format_buf_size; + r->have_leftover = &r->leftover_in_to_work; + } + r->w_fz = pa_sample_size_of_format(r->work_format) * r->work_channels; + + pa_log_debug("Resampler:"); + pa_log_debug(" rate %d -> %d (method %s)", a->rate, b->rate, pa_resample_method_to_string(r->method)); + pa_log_debug(" format %s -> %s (intermediate %s)", pa_sample_format_to_string(a->format), + pa_sample_format_to_string(b->format), pa_sample_format_to_string(r->work_format)); + pa_log_debug(" channels %d -> %d (resampling %d)", a->channels, b->channels, r->work_channels); + + /* set up the remap structure */ + if (r->map_required) + setup_remap(r, &r->remap, &lfe_remixed); + + if (lfe_remixed && crossover_freq > 0) { + pa_sample_spec wss = r->o_ss; + wss.format = r->work_format; + /* FIXME: For now just hardcode maxrewind to 3 seconds */ + r->lfe_filter = pa_lfe_filter_new(&wss, &r->o_cm, (float)crossover_freq, b->rate * 3); + pa_log_debug(" lfe filter activated (LR4 type), the crossover_freq = %uHz", crossover_freq); + } + + /* initialize implementation */ + if (init_table[method](r) < 0) + goto fail; + + return r; + +fail: + if (r->lfe_filter) + pa_lfe_filter_free(r->lfe_filter); + pa_xfree(r); + + return NULL; +} + +void pa_resampler_free(pa_resampler *r) { + pa_assert(r); + + if (r->impl.free) + r->impl.free(r); + else + pa_xfree(r->impl.data); + + if (r->lfe_filter) + pa_lfe_filter_free(r->lfe_filter); + + if (r->to_work_format_buf.memblock) + pa_memblock_unref(r->to_work_format_buf.memblock); + if (r->remap_buf.memblock) + pa_memblock_unref(r->remap_buf.memblock); + if (r->resample_buf.memblock) + pa_memblock_unref(r->resample_buf.memblock); + if (r->from_work_format_buf.memblock) + pa_memblock_unref(r->from_work_format_buf.memblock); + + free_remap(&r->remap); + + pa_xfree(r); +} + +void pa_resampler_set_input_rate(pa_resampler *r, uint32_t rate) { + pa_assert(r); + pa_assert(rate > 0); + pa_assert(r->impl.update_rates); + + if (r->i_ss.rate == rate) + return; + + r->i_ss.rate = rate; + + r->impl.update_rates(r); +} + +void pa_resampler_set_output_rate(pa_resampler *r, uint32_t rate) { + pa_assert(r); + pa_assert(rate > 0); + pa_assert(r->impl.update_rates); + + if (r->o_ss.rate == rate) + return; + + r->o_ss.rate = rate; + + r->impl.update_rates(r); + + if (r->lfe_filter) + pa_lfe_filter_update_rate(r->lfe_filter, rate); +} + +size_t pa_resampler_request(pa_resampler *r, size_t out_length) { + pa_assert(r); + + /* Let's round up here to make it more likely that the caller will get at + * least out_length amount of data from pa_resampler_run(). + * + * We don't take the leftover into account here. If we did, then it might + * be in theory possible that this function would return 0 and + * pa_resampler_run() would also return 0. That could lead to infinite + * loops. When the leftover is ignored here, such loops would eventually + * terminate, because the leftover would grow each round, finally + * surpassing the minimum input threshold of the resampler. */ + return ((((uint64_t) ((out_length + r->o_fz-1) / r->o_fz) * r->i_ss.rate) + r->o_ss.rate-1) / r->o_ss.rate) * r->i_fz; +} + +size_t pa_resampler_result(pa_resampler *r, size_t in_length) { + size_t frames; + + pa_assert(r); + + /* Let's round up here to ensure that the caller will always allocate big + * enough output buffer. */ + + frames = (in_length + r->i_fz - 1) / r->i_fz; + if (*r->have_leftover) + frames += r->leftover_buf->length / r->w_fz; + + return (((uint64_t) frames * r->o_ss.rate + r->i_ss.rate - 1) / r->i_ss.rate) * r->o_fz; +} + +size_t pa_resampler_max_block_size(pa_resampler *r) { + size_t block_size_max; + pa_sample_spec max_ss; + size_t max_fs; + size_t frames; + + pa_assert(r); + + block_size_max = pa_mempool_block_size_max(r->mempool); + + /* We deduce the "largest" sample spec we're using during the + * conversion */ + max_ss.channels = (uint8_t) (PA_MAX(r->i_ss.channels, r->o_ss.channels)); + + /* We silently assume that the format enum is ordered by size */ + max_ss.format = PA_MAX(r->i_ss.format, r->o_ss.format); + max_ss.format = PA_MAX(max_ss.format, r->work_format); + + max_ss.rate = PA_MAX(r->i_ss.rate, r->o_ss.rate); + + max_fs = pa_frame_size(&max_ss); + frames = block_size_max / max_fs - EXTRA_FRAMES; + + pa_assert(frames >= (r->leftover_buf->length / r->w_fz)); + if (*r->have_leftover) + frames -= r->leftover_buf->length / r->w_fz; + + block_size_max = ((uint64_t) frames * r->i_ss.rate / max_ss.rate) * r->i_fz; + + if (block_size_max > 0) + return block_size_max; + else + /* A single input frame may result in so much output that it doesn't + * fit in one standard memblock (e.g. converting 1 Hz to 44100 Hz). In + * this case the max block size will be set to one frame, and some + * memory will be probably be allocated with malloc() instead of using + * the memory pool. + * + * XXX: Should we support this case at all? We could also refuse to + * create resamplers whose max block size would exceed the memory pool + * block size. In this case also updating the resampler rate should + * fail if the new rate would cause an excessive max block size (in + * which case the stream would probably have to be killed). */ + return r->i_fz; +} + +void pa_resampler_reset(pa_resampler *r) { + pa_assert(r); + + if (r->impl.reset) + r->impl.reset(r); + + if (r->lfe_filter) + pa_lfe_filter_reset(r->lfe_filter); + + *r->have_leftover = false; +} + +void pa_resampler_rewind(pa_resampler *r, size_t out_frames) { + pa_assert(r); + + /* For now, we don't have any rewindable resamplers, so we just + reset the resampler instead (and hope that nobody hears the difference). */ + if (r->impl.reset) + r->impl.reset(r); + + if (r->lfe_filter) + pa_lfe_filter_rewind(r->lfe_filter, out_frames); + + *r->have_leftover = false; +} + +pa_resample_method_t pa_resampler_get_method(pa_resampler *r) { + pa_assert(r); + + return r->method; +} + +const pa_channel_map* pa_resampler_input_channel_map(pa_resampler *r) { + pa_assert(r); + + return &r->i_cm; +} + +const pa_sample_spec* pa_resampler_input_sample_spec(pa_resampler *r) { + pa_assert(r); + + return &r->i_ss; +} + +const pa_channel_map* pa_resampler_output_channel_map(pa_resampler *r) { + pa_assert(r); + + return &r->o_cm; +} + +const pa_sample_spec* pa_resampler_output_sample_spec(pa_resampler *r) { + pa_assert(r); + + return &r->o_ss; +} + +static const char * const resample_methods[] = { + "src-sinc-best-quality", + "src-sinc-medium-quality", + "src-sinc-fastest", + "src-zero-order-hold", + "src-linear", + "trivial", + "speex-float-0", + "speex-float-1", + "speex-float-2", + "speex-float-3", + "speex-float-4", + "speex-float-5", + "speex-float-6", + "speex-float-7", + "speex-float-8", + "speex-float-9", + "speex-float-10", + "speex-fixed-0", + "speex-fixed-1", + "speex-fixed-2", + "speex-fixed-3", + "speex-fixed-4", + "speex-fixed-5", + "speex-fixed-6", + "speex-fixed-7", + "speex-fixed-8", + "speex-fixed-9", + "speex-fixed-10", + "ffmpeg", + "auto", + "copy", + "peaks", + "soxr-mq", + "soxr-hq", + "soxr-vhq" +}; + +const char *pa_resample_method_to_string(pa_resample_method_t m) { + + if (m < 0 || m >= PA_RESAMPLER_MAX) + return NULL; + + return resample_methods[m]; +} + +int pa_resample_method_supported(pa_resample_method_t m) { + + if (m < 0 || m >= PA_RESAMPLER_MAX) + return 0; + +#ifndef HAVE_LIBSAMPLERATE + if (m <= PA_RESAMPLER_SRC_LINEAR) + return 0; +#endif + +#ifndef HAVE_SPEEX + if (m >= PA_RESAMPLER_SPEEX_FLOAT_BASE && m <= PA_RESAMPLER_SPEEX_FLOAT_MAX) + return 0; + if (m >= PA_RESAMPLER_SPEEX_FIXED_BASE && m <= PA_RESAMPLER_SPEEX_FIXED_MAX) + return 0; +#endif + +#ifndef HAVE_SOXR + if (m >= PA_RESAMPLER_SOXR_MQ && m <= PA_RESAMPLER_SOXR_VHQ) + return 0; +#endif + + return 1; +} + +pa_resample_method_t pa_parse_resample_method(const char *string) { + pa_resample_method_t m; + + pa_assert(string); + + for (m = 0; m < PA_RESAMPLER_MAX; m++) + if (pa_streq(string, resample_methods[m])) + return m; + + if (pa_streq(string, "speex-fixed")) + return PA_RESAMPLER_SPEEX_FIXED_BASE + 1; + + if (pa_streq(string, "speex-float")) + return PA_RESAMPLER_SPEEX_FLOAT_BASE + 1; + + return PA_RESAMPLER_INVALID; +} + +static bool on_left(pa_channel_position_t p) { + + return + p == PA_CHANNEL_POSITION_FRONT_LEFT || + p == PA_CHANNEL_POSITION_REAR_LEFT || + p == PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER || + p == PA_CHANNEL_POSITION_SIDE_LEFT || + p == PA_CHANNEL_POSITION_TOP_FRONT_LEFT || + p == PA_CHANNEL_POSITION_TOP_REAR_LEFT; +} + +static bool on_right(pa_channel_position_t p) { + + return + p == PA_CHANNEL_POSITION_FRONT_RIGHT || + p == PA_CHANNEL_POSITION_REAR_RIGHT || + p == PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER || + p == PA_CHANNEL_POSITION_SIDE_RIGHT || + p == PA_CHANNEL_POSITION_TOP_FRONT_RIGHT || + p == PA_CHANNEL_POSITION_TOP_REAR_RIGHT; +} + +static bool on_center(pa_channel_position_t p) { + + return + p == PA_CHANNEL_POSITION_FRONT_CENTER || + p == PA_CHANNEL_POSITION_REAR_CENTER || + p == PA_CHANNEL_POSITION_TOP_CENTER || + p == PA_CHANNEL_POSITION_TOP_FRONT_CENTER || + p == PA_CHANNEL_POSITION_TOP_REAR_CENTER; +} + +static bool on_lfe(pa_channel_position_t p) { + return + p == PA_CHANNEL_POSITION_LFE; +} + +static bool on_front(pa_channel_position_t p) { + return + p == PA_CHANNEL_POSITION_FRONT_LEFT || + p == PA_CHANNEL_POSITION_FRONT_RIGHT || + p == PA_CHANNEL_POSITION_FRONT_CENTER || + p == PA_CHANNEL_POSITION_TOP_FRONT_LEFT || + p == PA_CHANNEL_POSITION_TOP_FRONT_RIGHT || + p == PA_CHANNEL_POSITION_TOP_FRONT_CENTER || + p == PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER || + p == PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER; +} + +static bool on_rear(pa_channel_position_t p) { + return + p == PA_CHANNEL_POSITION_REAR_LEFT || + p == PA_CHANNEL_POSITION_REAR_RIGHT || + p == PA_CHANNEL_POSITION_REAR_CENTER || + p == PA_CHANNEL_POSITION_TOP_REAR_LEFT || + p == PA_CHANNEL_POSITION_TOP_REAR_RIGHT || + p == PA_CHANNEL_POSITION_TOP_REAR_CENTER; +} + +static bool on_side(pa_channel_position_t p) { + return + p == PA_CHANNEL_POSITION_SIDE_LEFT || + p == PA_CHANNEL_POSITION_SIDE_RIGHT || + p == PA_CHANNEL_POSITION_TOP_CENTER; +} + +enum { + ON_FRONT, + ON_REAR, + ON_SIDE, + ON_OTHER +}; + +static int front_rear_side(pa_channel_position_t p) { + if (on_front(p)) + return ON_FRONT; + if (on_rear(p)) + return ON_REAR; + if (on_side(p)) + return ON_SIDE; + return ON_OTHER; +} + +/* Fill a map of which output channels should get mono from input, not including + * LFE output channels. (The LFE output channels are mapped separately.) + */ +static void setup_oc_mono_map(const pa_resampler *r, float *oc_mono_map) { + unsigned oc; + unsigned n_oc; + bool found_oc_for_mono = false; + + pa_assert(r); + pa_assert(oc_mono_map); + + n_oc = r->o_ss.channels; + + if (!(r->flags & PA_RESAMPLER_NO_FILL_SINK)) { + /* Mono goes to all non-LFE output channels and we're done. */ + for (oc = 0; oc < n_oc; oc++) + oc_mono_map[oc] = on_lfe(r->o_cm.map[oc]) ? 0.0f : 1.0f; + return; + } else { + /* Initialize to all zero so we can select individual channels below. */ + for (oc = 0; oc < n_oc; oc++) + oc_mono_map[oc] = 0.0f; + } + + for (oc = 0; oc < n_oc; oc++) { + if (r->o_cm.map[oc] == PA_CHANNEL_POSITION_MONO) { + oc_mono_map[oc] = 1.0f; + found_oc_for_mono = true; + } + } + if (found_oc_for_mono) + return; + + for (oc = 0; oc < n_oc; oc++) { + if (r->o_cm.map[oc] == PA_CHANNEL_POSITION_FRONT_CENTER) { + oc_mono_map[oc] = 1.0f; + found_oc_for_mono = true; + } + } + if (found_oc_for_mono) + return; + + for (oc = 0; oc < n_oc; oc++) { + if (r->o_cm.map[oc] == PA_CHANNEL_POSITION_FRONT_LEFT || r->o_cm.map[oc] == PA_CHANNEL_POSITION_FRONT_RIGHT) { + oc_mono_map[oc] = 1.0f; + found_oc_for_mono = true; + } + } + if (found_oc_for_mono) + return; + + /* Give up on finding a suitable map for mono, and just send it to all + * non-LFE output channels. + */ + for (oc = 0; oc < n_oc; oc++) + oc_mono_map[oc] = on_lfe(r->o_cm.map[oc]) ? 0.0f : 1.0f; +} + +static void setup_remap(const pa_resampler *r, pa_remap_t *m, bool *lfe_remixed) { + unsigned oc, ic; + unsigned n_oc, n_ic; + bool ic_connected[PA_CHANNELS_MAX]; + pa_strbuf *s; + char *t; + + pa_assert(r); + pa_assert(m); + pa_assert(lfe_remixed); + + n_oc = r->o_ss.channels; + n_ic = r->i_ss.channels; + + m->format = r->work_format; + m->i_ss = r->i_ss; + m->o_ss = r->o_ss; + + memset(m->map_table_f, 0, sizeof(m->map_table_f)); + memset(m->map_table_i, 0, sizeof(m->map_table_i)); + + memset(ic_connected, 0, sizeof(ic_connected)); + *lfe_remixed = false; + + if (r->flags & PA_RESAMPLER_NO_REMAP) { + for (oc = 0; oc < PA_MIN(n_ic, n_oc); oc++) + m->map_table_f[oc][oc] = 1.0f; + + } else if (r->flags & PA_RESAMPLER_NO_REMIX) { + for (oc = 0; oc < n_oc; oc++) { + pa_channel_position_t b = r->o_cm.map[oc]; + + for (ic = 0; ic < n_ic; ic++) { + pa_channel_position_t a = r->i_cm.map[ic]; + + /* We shall not do any remixing. Hence, just check by name */ + if (a == b) + m->map_table_f[oc][ic] = 1.0f; + } + } + } else { + + /* OK, we shall do the full monty: upmixing and downmixing. Our + * algorithm is relatively simple, does not do spacialization, or delay + * elements. LFE filters are done after the remap step. Patches are always + * welcome, though. Oh, and it doesn't do any matrix decoding. (Which + * probably wouldn't make any sense anyway.) + * + * This code is not idempotent: downmixing an upmixed stereo stream is + * not identical to the original. The volume will not match, and the + * two channels will be a linear combination of both. + * + * This is loosely based on random suggestions found on the Internet, + * such as this: + * http://www.halfgaar.net/surround-sound-in-linux and the alsa upmix + * plugin. + * + * The algorithm works basically like this: + * + * 1) Connect all channels with matching names. + * This also includes fixing confusion between "5.1" and + * "5.1 (Side)" layouts, done by mpv. + * + * 2) Mono Handling: + * S:Mono: See setup_oc_mono_map(). + * D:Mono: Avg all S:channels + * + * 3) Mix D:Left, D:Right (if PA_RESAMPLER_NO_FILL_SINK is clear): + * D:Left: If not connected, avg all S:Left + * D:Right: If not connected, avg all S:Right + * + * 4) Mix D:Center (if PA_RESAMPLER_NO_FILL_SINK is clear): + * If not connected, avg all S:Center + * If still not connected, avg all S:Left, S:Right + * + * 5) Mix D:LFE + * If not connected, avg all S:* + * + * 6) Make sure S:Left/S:Right is used: S:Left/S:Right: If not + * connected, mix into all D:left and all D:right channels. Gain is + * 1/9. + * + * 7) Make sure S:Center, S:LFE is used: + * + * S:Center, S:LFE: If not connected, mix into all D:left, all + * D:right, all D:center channels. Gain is 0.5 for center and 0.375 + * for LFE. C-front is only mixed into L-front/R-front if available, + * otherwise into all L/R channels. Similarly for C-rear. + * + * 8) Normalize each row in the matrix such that the sum for each row is + * not larger than 1.0 in order to avoid clipping. + * + * S: and D: shall relate to the source resp. destination channels. + * + * Rationale: 1, 2 are probably obvious. For 3: this copies front to + * rear if needed. For 4: we try to find some suitable C source for C, + * if we don't find any, we avg L and R. For 5: LFE is mixed from all + * channels. For 6: the rear channels should not be dropped entirely, + * however have only minimal impact. For 7: movies usually encode + * speech on the center channel. Thus we have to make sure this channel + * is distributed to L and R if not available in the output. Also, LFE + * is used to achieve a greater dynamic range, and thus we should try + * to do our best to pass it to L+R. + */ + + unsigned + ic_left = 0, + ic_right = 0, + ic_center = 0, + ic_unconnected_left = 0, + ic_unconnected_right = 0, + ic_unconnected_center = 0, + ic_unconnected_lfe = 0; + bool ic_unconnected_center_mixed_in = 0; + float oc_mono_map[PA_CHANNELS_MAX]; + + for (ic = 0; ic < n_ic; ic++) { + if (on_left(r->i_cm.map[ic])) + ic_left++; + if (on_right(r->i_cm.map[ic])) + ic_right++; + if (on_center(r->i_cm.map[ic])) + ic_center++; + } + + setup_oc_mono_map(r, oc_mono_map); + + for (oc = 0; oc < n_oc; oc++) { + bool oc_connected = false; + pa_channel_position_t b = r->o_cm.map[oc]; + + for (ic = 0; ic < n_ic; ic++) { + pa_channel_position_t a = r->i_cm.map[ic]; + + if (a == b) { + m->map_table_f[oc][ic] = 1.0f; + + oc_connected = true; + ic_connected[ic] = true; + } + else if (a == PA_CHANNEL_POSITION_MONO && oc_mono_map[oc] > 0.0f) { + m->map_table_f[oc][ic] = oc_mono_map[oc]; + + oc_connected = true; + ic_connected[ic] = true; + } + else if (b == PA_CHANNEL_POSITION_MONO) { + m->map_table_f[oc][ic] = 1.0f / (float) n_ic; + + oc_connected = true; + ic_connected[ic] = true; + } + } + + if (!oc_connected) { + /* Maybe it is due to 5.1 rear/side confustion? */ + for (ic = 0; ic < n_ic; ic++) { + pa_channel_position_t a = r->i_cm.map[ic]; + if (ic_connected[ic]) + continue; + + if ((a == PA_CHANNEL_POSITION_REAR_LEFT && b == PA_CHANNEL_POSITION_SIDE_LEFT) || + (a == PA_CHANNEL_POSITION_SIDE_LEFT && b == PA_CHANNEL_POSITION_REAR_LEFT) || + (a == PA_CHANNEL_POSITION_REAR_RIGHT && b == PA_CHANNEL_POSITION_SIDE_RIGHT) || + (a == PA_CHANNEL_POSITION_SIDE_RIGHT && b == PA_CHANNEL_POSITION_REAR_RIGHT)) { + + m->map_table_f[oc][ic] = 1.0f; + + oc_connected = true; + ic_connected[ic] = true; + } + } + } + + if (!oc_connected) { + /* Try to find matching input ports for this output port */ + + if (on_left(b) && !(r->flags & PA_RESAMPLER_NO_FILL_SINK)) { + + /* We are not connected and on the left side, let's + * average all left side input channels. */ + + if (ic_left > 0) + for (ic = 0; ic < n_ic; ic++) + if (on_left(r->i_cm.map[ic])) { + m->map_table_f[oc][ic] = 1.0f / (float) ic_left; + ic_connected[ic] = true; + } + + /* We ignore the case where there is no left input channel. + * Something is really wrong in this case anyway. */ + + } else if (on_right(b) && !(r->flags & PA_RESAMPLER_NO_FILL_SINK)) { + + /* We are not connected and on the right side, let's + * average all right side input channels. */ + + if (ic_right > 0) + for (ic = 0; ic < n_ic; ic++) + if (on_right(r->i_cm.map[ic])) { + m->map_table_f[oc][ic] = 1.0f / (float) ic_right; + ic_connected[ic] = true; + } + + /* We ignore the case where there is no right input + * channel. Something is really wrong in this case anyway. + * */ + + } else if (on_center(b) && !(r->flags & PA_RESAMPLER_NO_FILL_SINK)) { + + if (ic_center > 0) { + + /* We are not connected and at the center. Let's average + * all center input channels. */ + + for (ic = 0; ic < n_ic; ic++) + if (on_center(r->i_cm.map[ic])) { + m->map_table_f[oc][ic] = 1.0f / (float) ic_center; + ic_connected[ic] = true; + } + + } else if (ic_left + ic_right > 0) { + + /* Hmm, no center channel around, let's synthesize it + * by mixing L and R.*/ + + for (ic = 0; ic < n_ic; ic++) + if (on_left(r->i_cm.map[ic]) || on_right(r->i_cm.map[ic])) { + m->map_table_f[oc][ic] = 1.0f / (float) (ic_left + ic_right); + ic_connected[ic] = true; + } + } + + /* We ignore the case where there is not even a left or + * right input channel. Something is really wrong in this + * case anyway. */ + + } else if (on_lfe(b) && (r->flags & PA_RESAMPLER_PRODUCE_LFE)) { + + /* We are not connected and an LFE. Let's average all + * channels for LFE. */ + + for (ic = 0; ic < n_ic; ic++) + m->map_table_f[oc][ic] = 1.0f / (float) n_ic; + + /* Please note that a channel connected to LFE doesn't + * really count as connected. */ + + *lfe_remixed = true; + } + } + } + + for (ic = 0; ic < n_ic; ic++) { + pa_channel_position_t a = r->i_cm.map[ic]; + + if (ic_connected[ic]) + continue; + + if (on_left(a)) + ic_unconnected_left++; + else if (on_right(a)) + ic_unconnected_right++; + else if (on_center(a)) + ic_unconnected_center++; + else if (on_lfe(a)) + ic_unconnected_lfe++; + } + + for (ic = 0; ic < n_ic; ic++) { + pa_channel_position_t a = r->i_cm.map[ic]; + + if (ic_connected[ic]) + continue; + + for (oc = 0; oc < n_oc; oc++) { + pa_channel_position_t b = r->o_cm.map[oc]; + + if (on_left(a) && on_left(b)) + m->map_table_f[oc][ic] = (1.f/9.f) / (float) ic_unconnected_left; + + else if (on_right(a) && on_right(b)) + m->map_table_f[oc][ic] = (1.f/9.f) / (float) ic_unconnected_right; + + else if (on_center(a) && on_center(b)) { + m->map_table_f[oc][ic] = (1.f/9.f) / (float) ic_unconnected_center; + ic_unconnected_center_mixed_in = true; + + } else if (on_lfe(a) && (r->flags & PA_RESAMPLER_CONSUME_LFE)) + m->map_table_f[oc][ic] = .375f / (float) ic_unconnected_lfe; + } + } + + if (ic_unconnected_center > 0 && !ic_unconnected_center_mixed_in) { + unsigned ncenter[PA_CHANNELS_MAX]; + bool found_frs[PA_CHANNELS_MAX]; + + memset(ncenter, 0, sizeof(ncenter)); + memset(found_frs, 0, sizeof(found_frs)); + + /* Hmm, as it appears there was no center channel we + could mix our center channel in. In this case, mix it into + left and right. Using .5 as the factor. */ + + for (ic = 0; ic < n_ic; ic++) { + + if (ic_connected[ic]) + continue; + + if (!on_center(r->i_cm.map[ic])) + continue; + + for (oc = 0; oc < n_oc; oc++) { + + if (!on_left(r->o_cm.map[oc]) && !on_right(r->o_cm.map[oc])) + continue; + + if (front_rear_side(r->i_cm.map[ic]) == front_rear_side(r->o_cm.map[oc])) { + found_frs[ic] = true; + break; + } + } + + for (oc = 0; oc < n_oc; oc++) { + + if (!on_left(r->o_cm.map[oc]) && !on_right(r->o_cm.map[oc])) + continue; + + if (!found_frs[ic] || front_rear_side(r->i_cm.map[ic]) == front_rear_side(r->o_cm.map[oc])) + ncenter[oc]++; + } + } + + for (oc = 0; oc < n_oc; oc++) { + + if (!on_left(r->o_cm.map[oc]) && !on_right(r->o_cm.map[oc])) + continue; + + if (ncenter[oc] <= 0) + continue; + + for (ic = 0; ic < n_ic; ic++) { + + if (!on_center(r->i_cm.map[ic])) + continue; + + if (!found_frs[ic] || front_rear_side(r->i_cm.map[ic]) == front_rear_side(r->o_cm.map[oc])) + m->map_table_f[oc][ic] = .5f / (float) ncenter[oc]; + } + } + } + } + + for (oc = 0; oc < n_oc; oc++) { + float sum = 0.0f; + for (ic = 0; ic < n_ic; ic++) + sum += m->map_table_f[oc][ic]; + + if (sum > 1.0f) + for (ic = 0; ic < n_ic; ic++) + m->map_table_f[oc][ic] /= sum; + } + + /* make an 16:16 int version of the matrix */ + for (oc = 0; oc < n_oc; oc++) + for (ic = 0; ic < n_ic; ic++) + m->map_table_i[oc][ic] = (int32_t) (m->map_table_f[oc][ic] * 0x10000); + + s = pa_strbuf_new(); + + pa_strbuf_printf(s, " "); + for (ic = 0; ic < n_ic; ic++) + pa_strbuf_printf(s, " I%02u ", ic); + pa_strbuf_puts(s, "\n +"); + + for (ic = 0; ic < n_ic; ic++) + pa_strbuf_printf(s, "------"); + pa_strbuf_puts(s, "\n"); + + for (oc = 0; oc < n_oc; oc++) { + pa_strbuf_printf(s, "O%02u |", oc); + + for (ic = 0; ic < n_ic; ic++) + pa_strbuf_printf(s, " %1.3f", m->map_table_f[oc][ic]); + + pa_strbuf_puts(s, "\n"); + } + + pa_log_debug("Channel matrix:\n%s", t = pa_strbuf_to_string_free(s)); + pa_xfree(t); + + /* initialize the remapping function */ + pa_init_remap_func(m); +} + +static void free_remap(pa_remap_t *m) { + pa_assert(m); + + pa_xfree(m->state); +} + +/* check if buf's memblock is large enough to hold 'len' bytes; create a + * new memblock if necessary and optionally preserve 'copy' data bytes */ +static void fit_buf(pa_resampler *r, pa_memchunk *buf, size_t len, size_t *size, size_t copy) { + pa_assert(size); + + if (!buf->memblock || len > *size) { + pa_memblock *new_block = pa_memblock_new(r->mempool, len); + + if (buf->memblock) { + if (copy > 0) { + void *src = pa_memblock_acquire(buf->memblock); + void *dst = pa_memblock_acquire(new_block); + pa_assert(copy <= len); + memcpy(dst, src, copy); + pa_memblock_release(new_block); + pa_memblock_release(buf->memblock); + } + + pa_memblock_unref(buf->memblock); + } + + buf->memblock = new_block; + *size = len; + } + + buf->length = len; +} + +static pa_memchunk* convert_to_work_format(pa_resampler *r, pa_memchunk *input) { + unsigned in_n_samples, out_n_samples; + void *src, *dst; + bool have_leftover; + size_t leftover_length = 0; + + pa_assert(r); + pa_assert(input); + pa_assert(input->memblock); + + /* Convert the incoming sample into the work sample format and place them + * in to_work_format_buf. The leftover data is already converted, so it's + * part of the output buffer. */ + + have_leftover = r->leftover_in_to_work; + r->leftover_in_to_work = false; + + if (!have_leftover && (!r->to_work_format_func || !input->length)) + return input; + else if (input->length <= 0) + return &r->to_work_format_buf; + + in_n_samples = out_n_samples = (unsigned) ((input->length / r->i_fz) * r->i_ss.channels); + + if (have_leftover) { + leftover_length = r->to_work_format_buf.length; + out_n_samples += (unsigned) (leftover_length / r->w_sz); + } + + fit_buf(r, &r->to_work_format_buf, r->w_sz * out_n_samples, &r->to_work_format_buf_size, leftover_length); + + src = pa_memblock_acquire_chunk(input); + dst = (uint8_t *) pa_memblock_acquire(r->to_work_format_buf.memblock) + leftover_length; + + if (r->to_work_format_func) + r->to_work_format_func(in_n_samples, src, dst); + else + memcpy(dst, src, input->length); + + pa_memblock_release(input->memblock); + pa_memblock_release(r->to_work_format_buf.memblock); + + return &r->to_work_format_buf; +} + +static pa_memchunk *remap_channels(pa_resampler *r, pa_memchunk *input) { + unsigned in_n_samples, out_n_samples, in_n_frames, out_n_frames; + void *src, *dst; + size_t leftover_length = 0; + bool have_leftover; + + pa_assert(r); + pa_assert(input); + pa_assert(input->memblock); + + /* Remap channels and place the result in remap_buf. There may be leftover + * data in the beginning of remap_buf. The leftover data is already + * remapped, so it's not part of the input, it's part of the output. */ + + have_leftover = r->leftover_in_remap; + r->leftover_in_remap = false; + + if (!have_leftover && (!r->map_required || input->length <= 0)) + return input; + else if (input->length <= 0) + return &r->remap_buf; + + in_n_samples = (unsigned) (input->length / r->w_sz); + in_n_frames = out_n_frames = in_n_samples / r->i_ss.channels; + + if (have_leftover) { + leftover_length = r->remap_buf.length; + out_n_frames += leftover_length / r->w_fz; + } + + out_n_samples = out_n_frames * r->o_ss.channels; + fit_buf(r, &r->remap_buf, out_n_samples * r->w_sz, &r->remap_buf_size, leftover_length); + + src = pa_memblock_acquire_chunk(input); + dst = (uint8_t *) pa_memblock_acquire(r->remap_buf.memblock) + leftover_length; + + if (r->map_required) { + pa_remap_t *remap = &r->remap; + + pa_assert(remap->do_remap); + remap->do_remap(remap, dst, src, in_n_frames); + + } else + memcpy(dst, src, input->length); + + pa_memblock_release(input->memblock); + pa_memblock_release(r->remap_buf.memblock); + + return &r->remap_buf; +} + +static void save_leftover(pa_resampler *r, void *buf, size_t len) { + void *dst; + + pa_assert(r); + pa_assert(buf); + pa_assert(len > 0); + + /* Store the leftover data. */ + fit_buf(r, r->leftover_buf, len, r->leftover_buf_size, 0); + *r->have_leftover = true; + + dst = pa_memblock_acquire(r->leftover_buf->memblock); + memmove(dst, buf, len); + pa_memblock_release(r->leftover_buf->memblock); +} + +static pa_memchunk *resample(pa_resampler *r, pa_memchunk *input) { + unsigned in_n_frames, out_n_frames, leftover_n_frames; + + pa_assert(r); + pa_assert(input); + + /* Resample the data and place the result in resample_buf. */ + + if (!r->impl.resample || !input->length) + return input; + + in_n_frames = (unsigned) (input->length / r->w_fz); + + out_n_frames = ((in_n_frames*r->o_ss.rate)/r->i_ss.rate)+EXTRA_FRAMES; + fit_buf(r, &r->resample_buf, r->w_fz * out_n_frames, &r->resample_buf_size, 0); + + leftover_n_frames = r->impl.resample(r, input, in_n_frames, &r->resample_buf, &out_n_frames); + + if (leftover_n_frames > 0) { + void *leftover_data = (uint8_t *) pa_memblock_acquire_chunk(input) + (in_n_frames - leftover_n_frames) * r->w_fz; + save_leftover(r, leftover_data, leftover_n_frames * r->w_fz); + pa_memblock_release(input->memblock); + } + + r->resample_buf.length = out_n_frames * r->w_fz; + + return &r->resample_buf; +} + +static pa_memchunk *convert_from_work_format(pa_resampler *r, pa_memchunk *input) { + unsigned n_samples, n_frames; + void *src, *dst; + + pa_assert(r); + pa_assert(input); + + /* Convert the data into the correct sample type and place the result in + * from_work_format_buf. */ + + if (!r->from_work_format_func || !input->length) + return input; + + n_samples = (unsigned) (input->length / r->w_sz); + n_frames = n_samples / r->o_ss.channels; + fit_buf(r, &r->from_work_format_buf, r->o_fz * n_frames, &r->from_work_format_buf_size, 0); + + src = pa_memblock_acquire_chunk(input); + dst = pa_memblock_acquire(r->from_work_format_buf.memblock); + r->from_work_format_func(n_samples, src, dst); + pa_memblock_release(input->memblock); + pa_memblock_release(r->from_work_format_buf.memblock); + + return &r->from_work_format_buf; +} + +void pa_resampler_run(pa_resampler *r, const pa_memchunk *in, pa_memchunk *out) { + pa_memchunk *buf; + + pa_assert(r); + pa_assert(in); + pa_assert(out); + pa_assert(in->length); + pa_assert(in->memblock); + pa_assert(in->length % r->i_fz == 0); + + buf = (pa_memchunk*) in; + buf = convert_to_work_format(r, buf); + + /* Try to save resampling effort: if we have more output channels than + * input channels, do resampling first, then remapping. */ + if (r->o_ss.channels <= r->i_ss.channels) { + buf = remap_channels(r, buf); + buf = resample(r, buf); + } else { + buf = resample(r, buf); + buf = remap_channels(r, buf); + } + + if (r->lfe_filter) + buf = pa_lfe_filter_process(r->lfe_filter, buf); + + if (buf->length) { + buf = convert_from_work_format(r, buf); + *out = *buf; + + if (buf == in) + pa_memblock_ref(buf->memblock); + else + pa_memchunk_reset(buf); + } else + pa_memchunk_reset(out); +} + +/*** copy (noop) implementation ***/ + +static int copy_init(pa_resampler *r) { + pa_assert(r); + + pa_assert(r->o_ss.rate == r->i_ss.rate); + + return 0; +} diff --git a/src/pulsecore/resampler.h b/src/pulsecore/resampler.h new file mode 100644 index 0000000..5a264b3 --- /dev/null +++ b/src/pulsecore/resampler.h @@ -0,0 +1,181 @@ +#ifndef fooresamplerhfoo +#define fooresamplerhfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulse/sample.h> +#include <pulse/channelmap.h> +#include <pulsecore/memblock.h> +#include <pulsecore/memchunk.h> +#include <pulsecore/sconv.h> +#include <pulsecore/remap.h> +#include <pulsecore/filter/lfe-filter.h> + +typedef struct pa_resampler pa_resampler; +typedef struct pa_resampler_impl pa_resampler_impl; + +struct pa_resampler_impl { + void (*free)(pa_resampler *r); + void (*update_rates)(pa_resampler *r); + + /* Returns the number of leftover frames in the input buffer. */ + unsigned (*resample)(pa_resampler *r, const pa_memchunk *in, unsigned in_n_frames, pa_memchunk *out, unsigned *out_n_frames); + + void (*reset)(pa_resampler *r); + void *data; +}; + +typedef enum pa_resample_method { + PA_RESAMPLER_INVALID = -1, + PA_RESAMPLER_SRC_SINC_BEST_QUALITY = 0, /* = SRC_SINC_BEST_QUALITY */ + PA_RESAMPLER_SRC_SINC_MEDIUM_QUALITY = 1, /* = SRC_SINC_MEDIUM_QUALITY */ + PA_RESAMPLER_SRC_SINC_FASTEST = 2, /* = SRC_SINC_FASTEST */ + PA_RESAMPLER_SRC_ZERO_ORDER_HOLD = 3, /* = SRC_ZERO_ORDER_HOLD */ + PA_RESAMPLER_SRC_LINEAR = 4, /* = SRC_LINEAR */ + PA_RESAMPLER_TRIVIAL, + PA_RESAMPLER_SPEEX_FLOAT_BASE, + PA_RESAMPLER_SPEEX_FLOAT_MAX = PA_RESAMPLER_SPEEX_FLOAT_BASE + 10, + PA_RESAMPLER_SPEEX_FIXED_BASE, + PA_RESAMPLER_SPEEX_FIXED_MAX = PA_RESAMPLER_SPEEX_FIXED_BASE + 10, + PA_RESAMPLER_FFMPEG, + PA_RESAMPLER_AUTO, /* automatic select based on sample format */ + PA_RESAMPLER_COPY, + PA_RESAMPLER_PEAKS, + PA_RESAMPLER_SOXR_MQ, + PA_RESAMPLER_SOXR_HQ, + PA_RESAMPLER_SOXR_VHQ, + PA_RESAMPLER_MAX +} pa_resample_method_t; + +typedef enum pa_resample_flags { + PA_RESAMPLER_VARIABLE_RATE = 0x0001U, + PA_RESAMPLER_NO_REMAP = 0x0002U, /* implies NO_REMIX */ + PA_RESAMPLER_NO_REMIX = 0x0004U, + PA_RESAMPLER_NO_FILL_SINK = 0x0010U, + PA_RESAMPLER_PRODUCE_LFE = 0x0020U, + PA_RESAMPLER_CONSUME_LFE = 0x0040U, +} pa_resample_flags_t; + +struct pa_resampler { + pa_resample_method_t method; + pa_resample_flags_t flags; + + pa_sample_spec i_ss, o_ss; + pa_channel_map i_cm, o_cm; + size_t i_fz, o_fz, w_fz, w_sz; + pa_mempool *mempool; + + pa_memchunk to_work_format_buf; + pa_memchunk remap_buf; + pa_memchunk resample_buf; + pa_memchunk from_work_format_buf; + size_t to_work_format_buf_size; + size_t remap_buf_size; + size_t resample_buf_size; + size_t from_work_format_buf_size; + + /* points to buffer before resampling stage, remap or to_work */ + pa_memchunk *leftover_buf; + size_t *leftover_buf_size; + + /* have_leftover points to leftover_in_remap or leftover_in_to_work */ + bool *have_leftover; + bool leftover_in_remap; + bool leftover_in_to_work; + + pa_sample_format_t work_format; + uint8_t work_channels; + + pa_convert_func_t to_work_format_func; + pa_convert_func_t from_work_format_func; + + pa_remap_t remap; + bool map_required; + + pa_lfe_filter_t *lfe_filter; + + pa_resampler_impl impl; +}; + +pa_resampler* pa_resampler_new( + pa_mempool *pool, + const pa_sample_spec *a, + const pa_channel_map *am, + const pa_sample_spec *b, + const pa_channel_map *bm, + unsigned crossover_freq, + pa_resample_method_t resample_method, + pa_resample_flags_t flags); + +void pa_resampler_free(pa_resampler *r); + +/* Returns the size of an input memory block which is required to return the specified amount of output data */ +size_t pa_resampler_request(pa_resampler *r, size_t out_length); + +/* Inverse of pa_resampler_request() */ +size_t pa_resampler_result(pa_resampler *r, size_t in_length); + +/* Returns the maximum size of input blocks we can process without needing bounce buffers larger than the mempool tile size. */ +size_t pa_resampler_max_block_size(pa_resampler *r); + +/* Pass the specified memory chunk to the resampler and return the newly resampled data */ +void pa_resampler_run(pa_resampler *r, const pa_memchunk *in, pa_memchunk *out); + +/* Change the input rate of the resampler object */ +void pa_resampler_set_input_rate(pa_resampler *r, uint32_t rate); + +/* Change the output rate of the resampler object */ +void pa_resampler_set_output_rate(pa_resampler *r, uint32_t rate); + +/* Reinitialize state of the resampler, possibly due to seeking or other discontinuities */ +void pa_resampler_reset(pa_resampler *r); + +/* Rewind resampler */ +void pa_resampler_rewind(pa_resampler *r, size_t out_frames); + +/* Return the resampling method of the resampler object */ +pa_resample_method_t pa_resampler_get_method(pa_resampler *r); + +/* Try to parse the resampler method */ +pa_resample_method_t pa_parse_resample_method(const char *string); + +/* return a human readable string for the specified resampling method. Inverse of pa_parse_resample_method() */ +const char *pa_resample_method_to_string(pa_resample_method_t m); + +/* Return 1 when the specified resampling method is supported */ +int pa_resample_method_supported(pa_resample_method_t m); + +const pa_channel_map* pa_resampler_input_channel_map(pa_resampler *r); +const pa_sample_spec* pa_resampler_input_sample_spec(pa_resampler *r); +const pa_channel_map* pa_resampler_output_channel_map(pa_resampler *r); +const pa_sample_spec* pa_resampler_output_sample_spec(pa_resampler *r); + +/* Implementation specific init functions */ +int pa_resampler_ffmpeg_init(pa_resampler *r); +int pa_resampler_libsamplerate_init(pa_resampler *r); +int pa_resampler_peaks_init(pa_resampler *r); +int pa_resampler_speex_init(pa_resampler *r); +int pa_resampler_trivial_init(pa_resampler*r); +int pa_resampler_soxr_init(pa_resampler *r); + +/* Resampler-specific quirks */ +bool pa_speex_is_fixed_point(void); + +#endif diff --git a/src/pulsecore/resampler/ffmpeg.c b/src/pulsecore/resampler/ffmpeg.c new file mode 100644 index 0000000..388b555 --- /dev/null +++ b/src/pulsecore/resampler/ffmpeg.c @@ -0,0 +1,132 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/xmalloc.h> +#include "pulsecore/ffmpeg/avcodec.h" + +#include <pulsecore/resampler.h> + +struct ffmpeg_data { /* data specific to ffmpeg */ + struct AVResampleContext *state; +}; + +static unsigned ffmpeg_resample(pa_resampler *r, const pa_memchunk *input, unsigned in_n_frames, pa_memchunk *output, unsigned *out_n_frames) { + unsigned used_frames = 0, c; + int previous_consumed_frames = -1; + struct ffmpeg_data *ffmpeg_data; + + pa_assert(r); + pa_assert(input); + pa_assert(output); + pa_assert(out_n_frames); + + ffmpeg_data = r->impl.data; + + for (c = 0; c < r->work_channels; c++) { + unsigned u; + pa_memblock *b, *w; + int16_t *p, *t, *k, *q, *s; + int consumed_frames; + + /* Allocate a new block */ + b = pa_memblock_new(r->mempool, in_n_frames * sizeof(int16_t)); + p = pa_memblock_acquire(b); + + /* Now copy the input data, splitting up channels */ + t = (int16_t*) pa_memblock_acquire_chunk(input) + c; + k = p; + for (u = 0; u < in_n_frames; u++) { + *k = *t; + t += r->work_channels; + k ++; + } + pa_memblock_release(input->memblock); + + /* Allocate buffer for the result */ + w = pa_memblock_new(r->mempool, *out_n_frames * sizeof(int16_t)); + q = pa_memblock_acquire(w); + + /* Now, resample */ + used_frames = (unsigned) av_resample(ffmpeg_data->state, + q, p, + &consumed_frames, + (int) in_n_frames, (int) *out_n_frames, + c >= (unsigned) (r->work_channels-1)); + + pa_memblock_release(b); + pa_memblock_unref(b); + + pa_assert(consumed_frames <= (int) in_n_frames); + pa_assert(previous_consumed_frames == -1 || consumed_frames == previous_consumed_frames); + previous_consumed_frames = consumed_frames; + + /* And place the results in the output buffer */ + s = (int16_t *) pa_memblock_acquire_chunk(output) + c; + for (u = 0; u < used_frames; u++) { + *s = *q; + q++; + s += r->work_channels; + } + pa_memblock_release(output->memblock); + pa_memblock_release(w); + pa_memblock_unref(w); + } + + *out_n_frames = used_frames; + + return in_n_frames - previous_consumed_frames; +} + +static void ffmpeg_free(pa_resampler *r) { + struct ffmpeg_data *ffmpeg_data; + + pa_assert(r); + + ffmpeg_data = r->impl.data; + if (ffmpeg_data->state) + av_resample_close(ffmpeg_data->state); +} + +int pa_resampler_ffmpeg_init(pa_resampler *r) { + struct ffmpeg_data *ffmpeg_data; + + pa_assert(r); + + ffmpeg_data = pa_xnew(struct ffmpeg_data, 1); + + /* We could probably implement different quality levels by + * adjusting the filter parameters here. However, ffmpeg + * internally only uses these hardcoded values, so let's use them + * here for now as well until ffmpeg makes this configurable. */ + + if (!(ffmpeg_data->state = av_resample_init((int) r->o_ss.rate, (int) r->i_ss.rate, 16, 10, 0, 0.8))) { + pa_xfree(ffmpeg_data); + return -1; + } + + r->impl.free = ffmpeg_free; + r->impl.resample = ffmpeg_resample; + r->impl.data = (void *) ffmpeg_data; + + return 0; +} diff --git a/src/pulsecore/resampler/libsamplerate.c b/src/pulsecore/resampler/libsamplerate.c new file mode 100644 index 0000000..06704fe --- /dev/null +++ b/src/pulsecore/resampler/libsamplerate.c @@ -0,0 +1,100 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <samplerate.h> + +#include <pulsecore/resampler.h> + +static unsigned libsamplerate_resample(pa_resampler *r, const pa_memchunk *input, unsigned in_n_frames, pa_memchunk *output, unsigned *out_n_frames) { + SRC_DATA data; + SRC_STATE *state; + + pa_assert(r); + pa_assert(input); + pa_assert(output); + pa_assert(out_n_frames); + + state = r->impl.data; + memset(&data, 0, sizeof(data)); + + data.data_in = pa_memblock_acquire_chunk(input); + data.input_frames = (long int) in_n_frames; + + data.data_out = pa_memblock_acquire_chunk(output); + data.output_frames = (long int) *out_n_frames; + + data.src_ratio = (double) r->o_ss.rate / r->i_ss.rate; + data.end_of_input = 0; + + pa_assert_se(src_process(state, &data) == 0); + + pa_memblock_release(input->memblock); + pa_memblock_release(output->memblock); + + *out_n_frames = (unsigned) data.output_frames_gen; + + return in_n_frames - data.input_frames_used; +} + +static void libsamplerate_update_rates(pa_resampler *r) { + SRC_STATE *state; + pa_assert(r); + + state = r->impl.data; + pa_assert_se(src_set_ratio(state, (double) r->o_ss.rate / r->i_ss.rate) == 0); +} + +static void libsamplerate_reset(pa_resampler *r) { + SRC_STATE *state; + pa_assert(r); + + state = r->impl.data; + pa_assert_se(src_reset(state) == 0); +} + +static void libsamplerate_free(pa_resampler *r) { + SRC_STATE *state; + pa_assert(r); + + state = r->impl.data; + if (state) + src_delete(state); +} + +int pa_resampler_libsamplerate_init(pa_resampler *r) { + int err; + SRC_STATE *state; + + pa_assert(r); + + if (!(state = src_new(r->method, r->work_channels, &err))) + return -1; + + r->impl.free = libsamplerate_free; + r->impl.update_rates = libsamplerate_update_rates; + r->impl.resample = libsamplerate_resample; + r->impl.reset = libsamplerate_reset; + r->impl.data = state; + + return 0; +} diff --git a/src/pulsecore/resampler/peaks.c b/src/pulsecore/resampler/peaks.c new file mode 100644 index 0000000..c9b808e --- /dev/null +++ b/src/pulsecore/resampler/peaks.c @@ -0,0 +1,161 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/xmalloc.h> +#include <math.h> + +#include <pulsecore/resampler.h> + +struct peaks_data { /* data specific to the peak finder pseudo resampler */ + unsigned o_counter; + unsigned i_counter; + + float max_f[PA_CHANNELS_MAX]; + int16_t max_i[PA_CHANNELS_MAX]; +}; + +static unsigned peaks_resample(pa_resampler *r, const pa_memchunk *input, unsigned in_n_frames, pa_memchunk *output, unsigned *out_n_frames) { + unsigned c, o_index = 0; + unsigned i, i_end = 0; + void *src, *dst; + struct peaks_data *peaks_data; + + pa_assert(r); + pa_assert(input); + pa_assert(output); + pa_assert(out_n_frames); + + peaks_data = r->impl.data; + src = pa_memblock_acquire_chunk(input); + dst = pa_memblock_acquire_chunk(output); + + i = ((uint64_t) peaks_data->o_counter * r->i_ss.rate) / r->o_ss.rate; + i = i > peaks_data->i_counter ? i - peaks_data->i_counter : 0; + + while (i_end < in_n_frames) { + i_end = ((uint64_t) (peaks_data->o_counter + 1) * r->i_ss.rate) / r->o_ss.rate; + i_end = i_end > peaks_data->i_counter ? i_end - peaks_data->i_counter : 0; + + pa_assert_fp(o_index * r->w_fz < pa_memblock_get_length(output->memblock)); + + /* 1ch float is treated separately, because that is the common case */ + if (r->work_channels == 1 && r->work_format == PA_SAMPLE_FLOAT32NE) { + float *s = (float*) src + i; + float *d = (float*) dst + o_index; + + for (; i < i_end && i < in_n_frames; i++) { + float n = fabsf(*s++); + + if (n > peaks_data->max_f[0]) + peaks_data->max_f[0] = n; + } + + if (i == i_end) { + *d = peaks_data->max_f[0]; + peaks_data->max_f[0] = 0; + o_index++, peaks_data->o_counter++; + } + } else if (r->work_format == PA_SAMPLE_S16NE) { + int16_t *s = (int16_t*) src + r->work_channels * i; + int16_t *d = (int16_t*) dst + r->work_channels * o_index; + + for (; i < i_end && i < in_n_frames; i++) + for (c = 0; c < r->work_channels; c++) { + int16_t n = abs(*s++); + + if (n > peaks_data->max_i[c]) + peaks_data->max_i[c] = n; + } + + if (i == i_end) { + for (c = 0; c < r->work_channels; c++, d++) { + *d = peaks_data->max_i[c]; + peaks_data->max_i[c] = 0; + } + o_index++, peaks_data->o_counter++; + } + } else { + float *s = (float*) src + r->work_channels * i; + float *d = (float*) dst + r->work_channels * o_index; + + for (; i < i_end && i < in_n_frames; i++) + for (c = 0; c < r->work_channels; c++) { + float n = fabsf(*s++); + + if (n > peaks_data->max_f[c]) + peaks_data->max_f[c] = n; + } + + if (i == i_end) { + for (c = 0; c < r->work_channels; c++, d++) { + *d = peaks_data->max_f[c]; + peaks_data->max_f[c] = 0; + } + o_index++, peaks_data->o_counter++; + } + } + } + + pa_memblock_release(input->memblock); + pa_memblock_release(output->memblock); + + *out_n_frames = o_index; + + peaks_data->i_counter += in_n_frames; + + /* Normalize counters */ + while (peaks_data->i_counter >= r->i_ss.rate) { + pa_assert(peaks_data->o_counter >= r->o_ss.rate); + + peaks_data->i_counter -= r->i_ss.rate; + peaks_data->o_counter -= r->o_ss.rate; + } + + return 0; +} + +static void peaks_update_rates_or_reset(pa_resampler *r) { + struct peaks_data *peaks_data; + pa_assert(r); + + peaks_data = r->impl.data; + + peaks_data->i_counter = 0; + peaks_data->o_counter = 0; +} + +int pa_resampler_peaks_init(pa_resampler*r) { + struct peaks_data *peaks_data; + pa_assert(r); + pa_assert(r->i_ss.rate >= r->o_ss.rate); + pa_assert(r->work_format == PA_SAMPLE_S16NE || r->work_format == PA_SAMPLE_FLOAT32NE); + + peaks_data = pa_xnew0(struct peaks_data, 1); + + r->impl.resample = peaks_resample; + r->impl.update_rates = peaks_update_rates_or_reset; + r->impl.reset = peaks_update_rates_or_reset; + r->impl.data = peaks_data; + + return 0; +} diff --git a/src/pulsecore/resampler/soxr.c b/src/pulsecore/resampler/soxr.c new file mode 100644 index 0000000..b1b2e19 --- /dev/null +++ b/src/pulsecore/resampler/soxr.c @@ -0,0 +1,168 @@ +/*** + This file is part of PulseAudio. + + Copyright 2014, 2015 Andrey Semashev + + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stddef.h> +#include <soxr.h> + +#include <pulsecore/resampler.h> + +static unsigned resampler_soxr_resample(pa_resampler *r, const pa_memchunk *input, unsigned in_n_frames, + pa_memchunk *output, unsigned *out_n_frames) { + soxr_t state; + void *in, *out; + size_t consumed = 0, produced = 0; + + pa_assert(r); + pa_assert(input); + pa_assert(output); + pa_assert(out_n_frames); + + state = r->impl.data; + pa_assert(state); + + in = pa_memblock_acquire_chunk(input); + out = pa_memblock_acquire_chunk(output); + + pa_assert_se(soxr_process(state, in, in_n_frames, &consumed, out, *out_n_frames, &produced) == 0); + + pa_memblock_release(input->memblock); + pa_memblock_release(output->memblock); + + *out_n_frames = produced; + + return in_n_frames - consumed; +} + +static void resampler_soxr_free(pa_resampler *r) { + pa_assert(r); + + if (!r->impl.data) + return; + + soxr_delete(r->impl.data); + r->impl.data = NULL; +} + +static void resampler_soxr_reset(pa_resampler *r) { +#if SOXR_THIS_VERSION >= SOXR_VERSION(0, 1, 2) + pa_assert(r); + + soxr_clear(r->impl.data); +#else + /* With libsoxr prior to 0.1.2 soxr_clear() makes soxr_process() crash afterwards, + * so don't use this function and re-create the context instead. */ + soxr_t old_state; + + pa_assert(r); + + old_state = r->impl.data; + r->impl.data = NULL; + + if (pa_resampler_soxr_init(r) == 0) { + if (old_state) + soxr_delete(old_state); + } else { + r->impl.data = old_state; + pa_log_error("Failed to reset libsoxr context"); + } +#endif +} + +static void resampler_soxr_update_rates(pa_resampler *r) { + soxr_t old_state; + + pa_assert(r); + + /* There is no update method in libsoxr, + * so just re-create the resampler context */ + + old_state = r->impl.data; + r->impl.data = NULL; + + if (pa_resampler_soxr_init(r) == 0) { + if (old_state) + soxr_delete(old_state); + } else { + r->impl.data = old_state; + pa_log_error("Failed to update libsoxr sample rates"); + } +} + +int pa_resampler_soxr_init(pa_resampler *r) { + soxr_t state; + soxr_datatype_t io_format; + soxr_io_spec_t io_spec; + soxr_runtime_spec_t runtime_spec; + unsigned long quality_recipe; + soxr_quality_spec_t quality; + soxr_error_t err = NULL; + + pa_assert(r); + + switch (r->work_format) { + case PA_SAMPLE_S16NE: + io_format = SOXR_INT16_I; + break; + case PA_SAMPLE_FLOAT32NE: + io_format = SOXR_FLOAT32_I; + break; + default: + pa_assert_not_reached(); + } + + io_spec = soxr_io_spec(io_format, io_format); + + /* Resample in one thread. Multithreading makes + * performance worse with small chunks of audio. */ + runtime_spec = soxr_runtime_spec(1); + + switch (r->method) { + case PA_RESAMPLER_SOXR_MQ: + quality_recipe = SOXR_MQ | SOXR_LINEAR_PHASE; + break; + case PA_RESAMPLER_SOXR_HQ: + quality_recipe = SOXR_HQ | SOXR_LINEAR_PHASE; + break; + case PA_RESAMPLER_SOXR_VHQ: + quality_recipe = SOXR_VHQ | SOXR_LINEAR_PHASE; + break; + default: + pa_assert_not_reached(); + } + + quality = soxr_quality_spec(quality_recipe, 0); + + state = soxr_create(r->i_ss.rate, r->o_ss.rate, r->work_channels, &err, &io_spec, &quality, &runtime_spec); + if (!state) { + pa_log_error("Failed to create libsoxr resampler context: %s.", (err ? err : "[unknown error]")); + return -1; + } + + r->impl.free = resampler_soxr_free; + r->impl.reset = resampler_soxr_reset; + r->impl.update_rates = resampler_soxr_update_rates; + r->impl.resample = resampler_soxr_resample; + r->impl.data = state; + + return 0; +} diff --git a/src/pulsecore/resampler/speex.c b/src/pulsecore/resampler/speex.c new file mode 100644 index 0000000..66387e5 --- /dev/null +++ b/src/pulsecore/resampler/speex.c @@ -0,0 +1,178 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <speex/speex_resampler.h> +#include <math.h> + +#include <pulsecore/once.h> +#include <pulsecore/resampler.h> + +bool pa_speex_is_fixed_point(void) { + static bool result = false; + PA_ONCE_BEGIN { + float f_out = -1.0f, f_in = 1.0f; + spx_uint32_t in_len = 1, out_len = 1; + SpeexResamplerState *s; + + pa_assert_se(s = speex_resampler_init(1, 1, 1, + SPEEX_RESAMPLER_QUALITY_MIN, NULL)); + + /* feed one sample that is too soft for fixed-point speex */ + pa_assert_se(speex_resampler_process_float(s, 0, &f_in, &in_len, + &f_out, &out_len) == RESAMPLER_ERR_SUCCESS); + + /* expecting sample has been processed, one sample output */ + pa_assert_se(in_len == 1 && out_len == 1); + + /* speex compiled with --enable-fixed-point will output 0.0 due to insufficient precision */ + if (fabsf(f_out) < 0.00001f) + result = true; + + speex_resampler_destroy(s); + } PA_ONCE_END; + return result; +} + + +static unsigned speex_resample_float(pa_resampler *r, const pa_memchunk *input, unsigned in_n_frames, pa_memchunk *output, unsigned *out_n_frames) { + float *in, *out; + uint32_t inf = in_n_frames, outf = *out_n_frames; + SpeexResamplerState *state; + + pa_assert(r); + pa_assert(input); + pa_assert(output); + pa_assert(out_n_frames); + + state = r->impl.data; + + in = pa_memblock_acquire_chunk(input); + out = pa_memblock_acquire_chunk(output); + + /* Strictly speaking, speex resampler expects its input + * to be normalized to the [-32768.0 .. 32767.0] range. + * This matters if speex has been compiled with --enable-fixed-point, + * because such speex will round the samples to the nearest + * integer. speex with --enable-fixed-point is therefore incompatible + * with PulseAudio's floating-point sample range [-1 .. 1]. speex + * without --enable-fixed-point works fine with this range. + * Care has been taken to call speex_resample_float() only + * for speex compiled without --enable-fixed-point. + */ + pa_assert_se(speex_resampler_process_interleaved_float(state, in, &inf, out, &outf) == 0); + + pa_memblock_release(input->memblock); + pa_memblock_release(output->memblock); + + pa_assert(inf == in_n_frames); + *out_n_frames = outf; + + return 0; +} + +static unsigned speex_resample_int(pa_resampler *r, const pa_memchunk *input, unsigned in_n_frames, pa_memchunk *output, unsigned *out_n_frames) { + int16_t *in, *out; + uint32_t inf = in_n_frames, outf = *out_n_frames; + SpeexResamplerState *state; + + pa_assert(r); + pa_assert(input); + pa_assert(output); + pa_assert(out_n_frames); + + state = r->impl.data; + + in = pa_memblock_acquire_chunk(input); + out = pa_memblock_acquire_chunk(output); + + pa_assert_se(speex_resampler_process_interleaved_int(state, in, &inf, out, &outf) == 0); + + pa_memblock_release(input->memblock); + pa_memblock_release(output->memblock); + + pa_assert(inf == in_n_frames); + *out_n_frames = outf; + + return 0; +} + +static void speex_update_rates(pa_resampler *r) { + SpeexResamplerState *state; + pa_assert(r); + + state = r->impl.data; + + pa_assert_se(speex_resampler_set_rate(state, r->i_ss.rate, r->o_ss.rate) == 0); +} + +static void speex_reset(pa_resampler *r) { + SpeexResamplerState *state; + pa_assert(r); + + state = r->impl.data; + + pa_assert_se(speex_resampler_reset_mem(state) == 0); +} + +static void speex_free(pa_resampler *r) { + SpeexResamplerState *state; + pa_assert(r); + + state = r->impl.data; + if (!state) + return; + + speex_resampler_destroy(state); +} + +int pa_resampler_speex_init(pa_resampler *r) { + int q, err; + SpeexResamplerState *state; + + pa_assert(r); + + r->impl.free = speex_free; + r->impl.update_rates = speex_update_rates; + r->impl.reset = speex_reset; + + if (r->method >= PA_RESAMPLER_SPEEX_FIXED_BASE && r->method <= PA_RESAMPLER_SPEEX_FIXED_MAX) { + + q = r->method - PA_RESAMPLER_SPEEX_FIXED_BASE; + r->impl.resample = speex_resample_int; + + } else { + pa_assert(r->method >= PA_RESAMPLER_SPEEX_FLOAT_BASE && r->method <= PA_RESAMPLER_SPEEX_FLOAT_MAX); + + q = r->method - PA_RESAMPLER_SPEEX_FLOAT_BASE; + r->impl.resample = speex_resample_float; + } + + pa_log_info("Choosing speex quality setting %i.", q); + + if (!(state = speex_resampler_init(r->work_channels, r->i_ss.rate, r->o_ss.rate, q, &err))) + return -1; + + r->impl.data = state; + + return 0; +} diff --git a/src/pulsecore/resampler/trivial.c b/src/pulsecore/resampler/trivial.c new file mode 100644 index 0000000..14e7ef3 --- /dev/null +++ b/src/pulsecore/resampler/trivial.c @@ -0,0 +1,100 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/xmalloc.h> + +#include <pulsecore/resampler.h> + +struct trivial_data { /* data specific to the trivial resampler */ + unsigned o_counter; + unsigned i_counter; +}; + +static unsigned trivial_resample(pa_resampler *r, const pa_memchunk *input, unsigned in_n_frames, pa_memchunk *output, unsigned *out_n_frames) { + unsigned i_index, o_index; + void *src, *dst; + struct trivial_data *trivial_data; + + pa_assert(r); + pa_assert(input); + pa_assert(output); + pa_assert(out_n_frames); + + trivial_data = r->impl.data; + + src = pa_memblock_acquire_chunk(input); + dst = pa_memblock_acquire_chunk(output); + + for (o_index = 0;; o_index++, trivial_data->o_counter++) { + i_index = ((uint64_t) trivial_data->o_counter * r->i_ss.rate) / r->o_ss.rate; + i_index = i_index > trivial_data->i_counter ? i_index - trivial_data->i_counter : 0; + + if (i_index >= in_n_frames) + break; + + pa_assert_fp(o_index * r->w_fz < pa_memblock_get_length(output->memblock)); + + memcpy((uint8_t*) dst + r->w_fz * o_index, (uint8_t*) src + r->w_fz * i_index, (int) r->w_fz); + } + + pa_memblock_release(input->memblock); + pa_memblock_release(output->memblock); + + *out_n_frames = o_index; + + trivial_data->i_counter += in_n_frames; + + /* Normalize counters */ + while (trivial_data->i_counter >= r->i_ss.rate) { + pa_assert(trivial_data->o_counter >= r->o_ss.rate); + + trivial_data->i_counter -= r->i_ss.rate; + trivial_data->o_counter -= r->o_ss.rate; + } + + return 0; +} + +static void trivial_update_rates_or_reset(pa_resampler *r) { + struct trivial_data *trivial_data; + pa_assert(r); + + trivial_data = r->impl.data; + + trivial_data->i_counter = 0; + trivial_data->o_counter = 0; +} + +int pa_resampler_trivial_init(pa_resampler *r) { + struct trivial_data *trivial_data; + pa_assert(r); + + trivial_data = pa_xnew0(struct trivial_data, 1); + + r->impl.resample = trivial_resample; + r->impl.update_rates = trivial_update_rates_or_reset; + r->impl.reset = trivial_update_rates_or_reset; + r->impl.data = trivial_data; + + return 0; +} diff --git a/src/pulsecore/rtkit.c b/src/pulsecore/rtkit.c new file mode 100644 index 0000000..2b7eb6a --- /dev/null +++ b/src/pulsecore/rtkit.c @@ -0,0 +1,313 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +/*** + Copyright 2009 Lennart Poettering + Copyright 2010 David Henningsson <diwic@ubuntu.com> + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +***/ + +#include <errno.h> + +#include "rtkit.h" + +#if defined(__linux__) && !defined(__ANDROID__) + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/syscall.h> +#include <pulsecore/core-util.h> + +static pid_t _gettid(void) { + return (pid_t) syscall(SYS_gettid); +} + +static int translate_error(const char *name) { + if (pa_streq(name, DBUS_ERROR_NO_MEMORY)) + return -ENOMEM; + if (pa_streq(name, DBUS_ERROR_SERVICE_UNKNOWN) || + pa_streq(name, DBUS_ERROR_NAME_HAS_NO_OWNER)) + return -ENOENT; + if (pa_streq(name, DBUS_ERROR_ACCESS_DENIED) || + pa_streq(name, DBUS_ERROR_AUTH_FAILED)) + return -EACCES; + + return -EIO; +} + +static long long rtkit_get_int_property(DBusConnection *connection, const char* propname, long long* propval) { + DBusMessage *m = NULL, *r = NULL; + DBusMessageIter iter, subiter; + dbus_int64_t i64; + dbus_int32_t i32; + DBusError error; + int current_type; + long long ret; + const char * interfacestr = "org.freedesktop.RealtimeKit1"; + + dbus_error_init(&error); + + if (!(m = dbus_message_new_method_call( + RTKIT_SERVICE_NAME, + RTKIT_OBJECT_PATH, + "org.freedesktop.DBus.Properties", + "Get"))) { + ret = -ENOMEM; + goto finish; + } + + if (!dbus_message_append_args( + m, + DBUS_TYPE_STRING, &interfacestr, + DBUS_TYPE_STRING, &propname, + DBUS_TYPE_INVALID)) { + ret = -ENOMEM; + goto finish; + } + + if (!(r = dbus_connection_send_with_reply_and_block(connection, m, -1, &error))) { + ret = translate_error(error.name); + goto finish; + } + + if (dbus_set_error_from_message(&error, r)) { + ret = translate_error(error.name); + goto finish; + } + + ret = -EBADMSG; + dbus_message_iter_init(r, &iter); + while ((current_type = dbus_message_iter_get_arg_type (&iter)) != DBUS_TYPE_INVALID) { + + if (current_type == DBUS_TYPE_VARIANT) { + dbus_message_iter_recurse(&iter, &subiter); + + while ((current_type = dbus_message_iter_get_arg_type (&subiter)) != DBUS_TYPE_INVALID) { + + if (current_type == DBUS_TYPE_INT32) { + dbus_message_iter_get_basic(&subiter, &i32); + *propval = i32; + ret = 0; + } + + if (current_type == DBUS_TYPE_INT64) { + dbus_message_iter_get_basic(&subiter, &i64); + *propval = i64; + ret = 0; + } + + dbus_message_iter_next (&subiter); + } + } + dbus_message_iter_next (&iter); + } + +finish: + + if (m) + dbus_message_unref(m); + + if (r) + dbus_message_unref(r); + + dbus_error_free(&error); + + return ret; +} + +int rtkit_get_max_realtime_priority(DBusConnection *connection) { + long long retval; + int err; + + err = rtkit_get_int_property(connection, "MaxRealtimePriority", &retval); + return err < 0 ? err : retval; +} + +int rtkit_get_min_nice_level(DBusConnection *connection, int* min_nice_level) { + long long retval; + int err; + + err = rtkit_get_int_property(connection, "MinNiceLevel", &retval); + if (err >= 0) + *min_nice_level = retval; + return err; +} + +long long rtkit_get_rttime_usec_max(DBusConnection *connection) { + long long retval; + int err; + + err = rtkit_get_int_property(connection, "RTTimeUSecMax", &retval); + return err < 0 ? err : retval; +} + +int rtkit_make_realtime(DBusConnection *connection, pid_t thread, int priority) { + DBusMessage *m = NULL, *r = NULL; + dbus_uint64_t u64; + dbus_uint32_t u32; + DBusError error; + int ret; + + dbus_error_init(&error); + + if (thread == 0) + thread = _gettid(); + + if (!(m = dbus_message_new_method_call( + RTKIT_SERVICE_NAME, + RTKIT_OBJECT_PATH, + "org.freedesktop.RealtimeKit1", + "MakeThreadRealtime"))) { + ret = -ENOMEM; + goto finish; + } + + u64 = (dbus_uint64_t) thread; + u32 = (dbus_uint32_t) priority; + + if (!dbus_message_append_args( + m, + DBUS_TYPE_UINT64, &u64, + DBUS_TYPE_UINT32, &u32, + DBUS_TYPE_INVALID)) { + ret = -ENOMEM; + goto finish; + } + + if (!(r = dbus_connection_send_with_reply_and_block(connection, m, -1, &error))) { + ret = translate_error(error.name); + goto finish; + } + + + if (dbus_set_error_from_message(&error, r)) { + ret = translate_error(error.name); + goto finish; + } + + ret = 0; + +finish: + + if (m) + dbus_message_unref(m); + + if (r) + dbus_message_unref(r); + + dbus_error_free(&error); + + return ret; +} + +int rtkit_make_high_priority(DBusConnection *connection, pid_t thread, int nice_level) { + DBusMessage *m = NULL, *r = NULL; + dbus_uint64_t u64; + dbus_int32_t s32; + DBusError error; + int ret; + + dbus_error_init(&error); + + if (thread == 0) + thread = _gettid(); + + if (!(m = dbus_message_new_method_call( + RTKIT_SERVICE_NAME, + RTKIT_OBJECT_PATH, + "org.freedesktop.RealtimeKit1", + "MakeThreadHighPriority"))) { + ret = -ENOMEM; + goto finish; + } + + u64 = (dbus_uint64_t) thread; + s32 = (dbus_int32_t) nice_level; + + if (!dbus_message_append_args( + m, + DBUS_TYPE_UINT64, &u64, + DBUS_TYPE_INT32, &s32, + DBUS_TYPE_INVALID)) { + ret = -ENOMEM; + goto finish; + } + + + + if (!(r = dbus_connection_send_with_reply_and_block(connection, m, -1, &error))) { + ret = translate_error(error.name); + goto finish; + } + + + if (dbus_set_error_from_message(&error, r)) { + ret = translate_error(error.name); + goto finish; + } + + ret = 0; + +finish: + + if (m) + dbus_message_unref(m); + + if (r) + dbus_message_unref(r); + + dbus_error_free(&error); + + return ret; +} + +#else + +int rtkit_make_realtime(DBusConnection *connection, pid_t thread, int priority) { + return -ENOTSUP; +} + +int rtkit_make_high_priority(DBusConnection *connection, pid_t thread, int nice_level) { + return -ENOTSUP; +} + +int rtkit_get_max_realtime_priority(DBusConnection *connection) { + return -ENOTSUP; +} + +int rtkit_get_min_nice_level(DBusConnection *connection, int* min_nice_level) { + return -ENOTSUP; +} + +long long rtkit_get_rttime_usec_max(DBusConnection *connection) { + return -ENOTSUP; +} + +#endif diff --git a/src/pulsecore/rtkit.h b/src/pulsecore/rtkit.h new file mode 100644 index 0000000..30cde72 --- /dev/null +++ b/src/pulsecore/rtkit.h @@ -0,0 +1,79 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +#ifndef foortkithfoo +#define foortkithfoo + +/*** + Copyright 2009 Lennart Poettering + Copyright 2010 David Henningsson <diwic@ubuntu.com> + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +***/ + +#include <sys/types.h> +#include <dbus/dbus.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* This is the reference implementation for a client for + * RealtimeKit. You don't have to use this, but if do, just copy these + * sources into your repository */ + +#define RTKIT_SERVICE_NAME "org.freedesktop.RealtimeKit1" +#define RTKIT_OBJECT_PATH "/org/freedesktop/RealtimeKit1" + +/* This is mostly equivalent to sched_setparam(thread, SCHED_RR, { + * .sched_priority = priority }). 'thread' needs to be a kernel thread + * id as returned by gettid(), not a pthread_t! If 'thread' is 0 the + * current thread is used. The returned value is a negative errno + * style error code, or 0 on success. */ +int rtkit_make_realtime(DBusConnection *system_bus, pid_t thread, int priority); + +/* This is mostly equivalent to setpriority(PRIO_PROCESS, thread, + * nice_level). 'thread' needs to be a kernel thread id as returned by + * gettid(), not a pthread_t! If 'thread' is 0 the current thread is + * used. The returned value is a negative errno style error code, or 0 + * on success.*/ +int rtkit_make_high_priority(DBusConnection *system_bus, pid_t thread, int nice_level); + +/* Return the maximum value of realtime priority available. Realtime requests + * above this value will fail. A negative value is an errno style error code. + */ +int rtkit_get_max_realtime_priority(DBusConnection *system_bus); + +/* Retrieve the minimum value of nice level available. High prio requests + * below this value will fail. The returned value is a negative errno + * style error code, or 0 on success.*/ +int rtkit_get_min_nice_level(DBusConnection *system_bus, int* min_nice_level); + +/* Return the maximum value of RLIMIT_RTTIME to set before attempting a + * realtime request. A negative value is an errno style error code. + */ +long long rtkit_get_rttime_usec_max(DBusConnection *system_bus); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/pulsecore/rtpoll.c b/src/pulsecore/rtpoll.c new file mode 100644 index 0000000..1085bf9 --- /dev/null +++ b/src/pulsecore/rtpoll.c @@ -0,0 +1,631 @@ +/*** + 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <sys/types.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> + +#include <pulse/xmalloc.h> +#include <pulse/timeval.h> + +#include <pulsecore/poll.h> +#include <pulsecore/core-error.h> +#include <pulsecore/core-rtclock.h> +#include <pulsecore/macro.h> +#include <pulsecore/llist.h> +#include <pulsecore/flist.h> +#include <pulsecore/core-util.h> +#include <pulsecore/ratelimit.h> +#include <pulse/rtclock.h> + +#include "rtpoll.h" + +/* #define DEBUG_TIMING */ + +struct pa_rtpoll { + struct pollfd *pollfd, *pollfd2; + unsigned n_pollfd_alloc, n_pollfd_used; + + struct timeval next_elapse; + bool timer_enabled:1; + + bool scan_for_dead:1; + bool running:1; + bool rebuild_needed:1; + bool quit:1; + bool timer_elapsed:1; + +#ifdef DEBUG_TIMING + pa_usec_t timestamp; + pa_usec_t slept, awake; +#endif + + PA_LLIST_HEAD(pa_rtpoll_item, items); +}; + +struct pa_rtpoll_item { + pa_rtpoll *rtpoll; + bool dead; + + pa_rtpoll_priority_t priority; + + struct pollfd *pollfd; + unsigned n_pollfd; + + int (*work_cb)(pa_rtpoll_item *i); + int (*before_cb)(pa_rtpoll_item *i); + void (*after_cb)(pa_rtpoll_item *i); + void *work_userdata; + void *before_userdata; + void *after_userdata; + + PA_LLIST_FIELDS(pa_rtpoll_item); +}; + +PA_STATIC_FLIST_DECLARE(items, 0, pa_xfree); + +pa_rtpoll *pa_rtpoll_new(void) { + pa_rtpoll *p; + + p = pa_xnew0(pa_rtpoll, 1); + + p->n_pollfd_alloc = 32; + p->pollfd = pa_xnew(struct pollfd, p->n_pollfd_alloc); + p->pollfd2 = pa_xnew(struct pollfd, p->n_pollfd_alloc); + +#ifdef DEBUG_TIMING + p->timestamp = pa_rtclock_now(); +#endif + + return p; +} + +static void rtpoll_rebuild(pa_rtpoll *p) { + + struct pollfd *e, *t; + pa_rtpoll_item *i; + int ra = 0; + + pa_assert(p); + + p->rebuild_needed = false; + + if (p->n_pollfd_used > p->n_pollfd_alloc) { + /* Hmm, we have to allocate some more space */ + p->n_pollfd_alloc = p->n_pollfd_used * 2; + p->pollfd2 = pa_xrealloc(p->pollfd2, p->n_pollfd_alloc * sizeof(struct pollfd)); + ra = 1; + } + + e = p->pollfd2; + + for (i = p->items; i; i = i->next) { + + if (i->n_pollfd > 0) { + size_t l = i->n_pollfd * sizeof(struct pollfd); + + if (i->pollfd) + memcpy(e, i->pollfd, l); + else + memset(e, 0, l); + + i->pollfd = e; + } else + i->pollfd = NULL; + + e += i->n_pollfd; + } + + pa_assert((unsigned) (e - p->pollfd2) == p->n_pollfd_used); + t = p->pollfd; + p->pollfd = p->pollfd2; + p->pollfd2 = t; + + if (ra) + p->pollfd2 = pa_xrealloc(p->pollfd2, p->n_pollfd_alloc * sizeof(struct pollfd)); +} + +static void rtpoll_item_destroy(pa_rtpoll_item *i) { + pa_rtpoll *p; + + pa_assert(i); + + p = i->rtpoll; + + PA_LLIST_REMOVE(pa_rtpoll_item, p->items, i); + + p->n_pollfd_used -= i->n_pollfd; + + if (pa_flist_push(PA_STATIC_FLIST_GET(items), i) < 0) + pa_xfree(i); + + p->rebuild_needed = true; +} + +void pa_rtpoll_free(pa_rtpoll *p) { + pa_assert(p); + + while (p->items) + rtpoll_item_destroy(p->items); + + pa_xfree(p->pollfd); + pa_xfree(p->pollfd2); + + pa_xfree(p); +} + +static void reset_revents(pa_rtpoll_item *i) { + struct pollfd *f; + unsigned n; + + pa_assert(i); + + if (!(f = pa_rtpoll_item_get_pollfd(i, &n))) + return; + + for (; n > 0; n--) + f[n-1].revents = 0; +} + +static void reset_all_revents(pa_rtpoll *p) { + pa_rtpoll_item *i; + + pa_assert(p); + + for (i = p->items; i; i = i->next) { + + if (i->dead) + continue; + + reset_revents(i); + } +} + +int pa_rtpoll_run(pa_rtpoll *p) { + pa_rtpoll_item *i; + int r = 0; + struct timeval timeout; + + pa_assert(p); + pa_assert(!p->running); + +#ifdef DEBUG_TIMING + pa_log("rtpoll_run"); +#endif + + p->running = true; + p->timer_elapsed = false; + + /* First, let's do some work */ + for (i = p->items; i && i->priority < PA_RTPOLL_NEVER; i = i->next) { + int k; + + if (i->dead) + continue; + + if (!i->work_cb) + continue; + + if (p->quit) { +#ifdef DEBUG_TIMING + pa_log("rtpoll finish"); +#endif + goto finish; + } + + if ((k = i->work_cb(i)) != 0) { + if (k < 0) + r = k; +#ifdef DEBUG_TIMING + pa_log("rtpoll finish"); +#endif + goto finish; + } + } + + /* Now let's prepare for entering the sleep */ + for (i = p->items; i && i->priority < PA_RTPOLL_NEVER; i = i->next) { + int k = 0; + + if (i->dead) + continue; + + if (!i->before_cb) + continue; + + if (p->quit || (k = i->before_cb(i)) != 0) { + + /* Hmm, this one doesn't let us enter the poll, so rewind everything */ + + for (i = i->prev; i; i = i->prev) { + + if (i->dead) + continue; + + if (!i->after_cb) + continue; + + i->after_cb(i); + } + + if (k < 0) + r = k; +#ifdef DEBUG_TIMING + pa_log("rtpoll finish"); +#endif + goto finish; + } + } + + if (p->rebuild_needed) + rtpoll_rebuild(p); + + pa_zero(timeout); + + /* Calculate timeout */ + if (!p->quit && p->timer_enabled) { + struct timeval now; + pa_rtclock_get(&now); + + if (pa_timeval_cmp(&p->next_elapse, &now) > 0) + pa_timeval_add(&timeout, pa_timeval_diff(&p->next_elapse, &now)); + } + +#ifdef DEBUG_TIMING + { + pa_usec_t now = pa_rtclock_now(); + p->awake = now - p->timestamp; + p->timestamp = now; + if (!p->quit && p->timer_enabled) + pa_log("poll timeout: %d ms ",(int) ((timeout.tv_sec*1000) + (timeout.tv_usec / 1000))); + else if (p->quit) + pa_log("poll timeout is ZERO"); + else + pa_log("poll timeout is FOREVER"); + } +#endif + + /* OK, now let's sleep */ +#ifdef HAVE_PPOLL + { + struct timespec ts; + ts.tv_sec = timeout.tv_sec; + ts.tv_nsec = timeout.tv_usec * 1000; + r = ppoll(p->pollfd, p->n_pollfd_used, (p->quit || p->timer_enabled) ? &ts : NULL, NULL); + } +#else + r = pa_poll(p->pollfd, p->n_pollfd_used, (p->quit || p->timer_enabled) ? (int) ((timeout.tv_sec*1000) + (timeout.tv_usec / 1000)) : -1); +#endif + + p->timer_elapsed = r == 0; + +#ifdef DEBUG_TIMING + { + pa_usec_t now = pa_rtclock_now(); + p->slept = now - p->timestamp; + p->timestamp = now; + + pa_log("Process time %llu ms; sleep time %llu ms", + (unsigned long long) (p->awake / PA_USEC_PER_MSEC), + (unsigned long long) (p->slept / PA_USEC_PER_MSEC)); + } +#endif + + if (r < 0) { + if (errno == EAGAIN || errno == EINTR) + r = 0; + else + pa_log_error("poll(): %s", pa_cstrerror(errno)); + + reset_all_revents(p); + } + + /* Let's tell everyone that we left the sleep */ + for (i = p->items; i && i->priority < PA_RTPOLL_NEVER; i = i->next) { + + if (i->dead) + continue; + + if (!i->after_cb) + continue; + + i->after_cb(i); + } + +finish: + + p->running = false; + + if (p->scan_for_dead) { + pa_rtpoll_item *n; + + p->scan_for_dead = false; + + for (i = p->items; i; i = n) { + n = i->next; + + if (i->dead) + rtpoll_item_destroy(i); + } + } + + return r < 0 ? r : !p->quit; +} + +void pa_rtpoll_set_timer_absolute(pa_rtpoll *p, pa_usec_t usec) { + pa_assert(p); + + pa_timeval_store(&p->next_elapse, usec); + p->timer_enabled = true; +} + +void pa_rtpoll_set_timer_relative(pa_rtpoll *p, pa_usec_t usec) { + pa_assert(p); + + /* Scheduling a timeout for more than an hour is very very suspicious */ + pa_assert(usec <= PA_USEC_PER_SEC*60ULL*60ULL); + + pa_rtclock_get(&p->next_elapse); + pa_timeval_add(&p->next_elapse, usec); + p->timer_enabled = true; +} + +void pa_rtpoll_set_timer_disabled(pa_rtpoll *p) { + pa_assert(p); + + memset(&p->next_elapse, 0, sizeof(p->next_elapse)); + p->timer_enabled = false; +} + +pa_rtpoll_item *pa_rtpoll_item_new(pa_rtpoll *p, pa_rtpoll_priority_t prio, unsigned n_fds) { + pa_rtpoll_item *i, *j, *l = NULL; + + pa_assert(p); + + if (!(i = pa_flist_pop(PA_STATIC_FLIST_GET(items)))) + i = pa_xnew(pa_rtpoll_item, 1); + + i->rtpoll = p; + i->dead = false; + i->n_pollfd = n_fds; + i->pollfd = NULL; + i->priority = prio; + + i->work_userdata = NULL; + i->before_userdata = NULL; + i->work_userdata = NULL; + i->before_cb = NULL; + i->after_cb = NULL; + i->work_cb = NULL; + + for (j = p->items; j; j = j->next) { + if (prio <= j->priority) + break; + + l = j; + } + + PA_LLIST_INSERT_AFTER(pa_rtpoll_item, p->items, j ? j->prev : l, i); + + if (n_fds > 0) { + p->rebuild_needed = 1; + p->n_pollfd_used += n_fds; + } + + return i; +} + +void pa_rtpoll_item_free(pa_rtpoll_item *i) { + pa_assert(i); + + if (i->rtpoll->running) { + i->dead = true; + i->rtpoll->scan_for_dead = true; + return; + } + + rtpoll_item_destroy(i); +} + +struct pollfd *pa_rtpoll_item_get_pollfd(pa_rtpoll_item *i, unsigned *n_fds) { + pa_assert(i); + + if (i->n_pollfd > 0) + if (i->rtpoll->rebuild_needed) + rtpoll_rebuild(i->rtpoll); + + if (n_fds) + *n_fds = i->n_pollfd; + + return i->pollfd; +} + +void pa_rtpoll_item_set_before_callback(pa_rtpoll_item *i, int (*before_cb)(pa_rtpoll_item *i), void *userdata) { + pa_assert(i); + pa_assert(i->priority < PA_RTPOLL_NEVER); + + i->before_cb = before_cb; + i->before_userdata = userdata; +} + +void pa_rtpoll_item_set_after_callback(pa_rtpoll_item *i, void (*after_cb)(pa_rtpoll_item *i), void *userdata) { + pa_assert(i); + pa_assert(i->priority < PA_RTPOLL_NEVER); + + i->after_cb = after_cb; + i->after_userdata = userdata; +} + +void pa_rtpoll_item_set_work_callback(pa_rtpoll_item *i, int (*work_cb)(pa_rtpoll_item *i), void *userdata) { + pa_assert(i); + pa_assert(i->priority < PA_RTPOLL_NEVER); + + i->work_cb = work_cb; + i->work_userdata = userdata; +} + +void* pa_rtpoll_item_get_work_userdata(pa_rtpoll_item *i) { + pa_assert(i); + + return i->work_userdata; +} + +static int fdsem_before(pa_rtpoll_item *i) { + + if (pa_fdsem_before_poll(i->before_userdata) < 0) + return 1; /* 1 means immediate restart of the loop */ + + return 0; +} + +static void fdsem_after(pa_rtpoll_item *i) { + pa_assert(i); + + pa_assert((i->pollfd[0].revents & ~POLLIN) == 0); + pa_fdsem_after_poll(i->after_userdata); +} + +pa_rtpoll_item *pa_rtpoll_item_new_fdsem(pa_rtpoll *p, pa_rtpoll_priority_t prio, pa_fdsem *f) { + pa_rtpoll_item *i; + struct pollfd *pollfd; + + pa_assert(p); + pa_assert(f); + + i = pa_rtpoll_item_new(p, prio, 1); + + pollfd = pa_rtpoll_item_get_pollfd(i, NULL); + + pollfd->fd = pa_fdsem_get(f); + pollfd->events = POLLIN; + + pa_rtpoll_item_set_before_callback(i, fdsem_before, f); + pa_rtpoll_item_set_after_callback(i, fdsem_after, f); + + return i; +} + +static int asyncmsgq_read_before(pa_rtpoll_item *i) { + pa_assert(i); + + if (pa_asyncmsgq_read_before_poll(i->before_userdata) < 0) + return 1; /* 1 means immediate restart of the loop */ + + return 0; +} + +static void asyncmsgq_read_after(pa_rtpoll_item *i) { + pa_assert(i); + + pa_assert((i->pollfd[0].revents & ~POLLIN) == 0); + pa_asyncmsgq_read_after_poll(i->after_userdata); +} + +static int asyncmsgq_read_work(pa_rtpoll_item *i) { + pa_msgobject *object; + int code; + void *data; + pa_memchunk chunk; + int64_t offset; + + pa_assert(i); + + if (pa_asyncmsgq_get(i->work_userdata, &object, &code, &data, &offset, &chunk, 0) == 0) { + int ret; + + if (!object && code == PA_MESSAGE_SHUTDOWN) { + pa_asyncmsgq_done(i->work_userdata, 0); + /* Requests the loop to exit. Will cause the next iteration of + * pa_rtpoll_run() to return 0 */ + i->rtpoll->quit = true; + return 1; + } + + ret = pa_asyncmsgq_dispatch(object, code, data, offset, &chunk); + pa_asyncmsgq_done(i->work_userdata, ret); + return 1; + } + + return 0; +} + +pa_rtpoll_item *pa_rtpoll_item_new_asyncmsgq_read(pa_rtpoll *p, pa_rtpoll_priority_t prio, pa_asyncmsgq *q) { + pa_rtpoll_item *i; + struct pollfd *pollfd; + + pa_assert(p); + pa_assert(q); + + i = pa_rtpoll_item_new(p, prio, 1); + + pollfd = pa_rtpoll_item_get_pollfd(i, NULL); + pollfd->fd = pa_asyncmsgq_read_fd(q); + pollfd->events = POLLIN; + + pa_rtpoll_item_set_before_callback(i, asyncmsgq_read_before, q); + pa_rtpoll_item_set_after_callback(i, asyncmsgq_read_after, q); + pa_rtpoll_item_set_work_callback(i, asyncmsgq_read_work, q); + + return i; +} + +static int asyncmsgq_write_before(pa_rtpoll_item *i) { + pa_assert(i); + + pa_asyncmsgq_write_before_poll(i->before_userdata); + return 0; +} + +static void asyncmsgq_write_after(pa_rtpoll_item *i) { + pa_assert(i); + + pa_assert((i->pollfd[0].revents & ~POLLIN) == 0); + pa_asyncmsgq_write_after_poll(i->after_userdata); +} + +pa_rtpoll_item *pa_rtpoll_item_new_asyncmsgq_write(pa_rtpoll *p, pa_rtpoll_priority_t prio, pa_asyncmsgq *q) { + pa_rtpoll_item *i; + struct pollfd *pollfd; + + pa_assert(p); + pa_assert(q); + + i = pa_rtpoll_item_new(p, prio, 1); + + pollfd = pa_rtpoll_item_get_pollfd(i, NULL); + pollfd->fd = pa_asyncmsgq_write_fd(q); + pollfd->events = POLLIN; + + pa_rtpoll_item_set_before_callback(i, asyncmsgq_write_before, q); + pa_rtpoll_item_set_after_callback(i, asyncmsgq_write_after, q); + + return i; +} + +bool pa_rtpoll_timer_elapsed(pa_rtpoll *p) { + pa_assert(p); + + return p->timer_elapsed; +} diff --git a/src/pulsecore/rtpoll.h b/src/pulsecore/rtpoll.h new file mode 100644 index 0000000..121b51e --- /dev/null +++ b/src/pulsecore/rtpoll.h @@ -0,0 +1,100 @@ +#ifndef foopulsertpollhfoo +#define foopulsertpollhfoo + +/*** + 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/types.h> +#include <limits.h> + +#include <pulse/sample.h> +#include <pulsecore/asyncmsgq.h> +#include <pulsecore/fdsem.h> +#include <pulsecore/macro.h> + +/* An implementation of a "real-time" poll loop. Basically, this is + * yet another wrapper around poll(). However it has certain + * advantages over pa_mainloop and suchlike: + * + * 1) High resolution timers are used + * + * 2) It allows raw access to the pollfd data to users + * + * 3) It allows arbitrary functions to be run before entering the + * actual poll() and after it. + * + * Only a single interval timer is supported. */ + +typedef struct pa_rtpoll pa_rtpoll; +typedef struct pa_rtpoll_item pa_rtpoll_item; + +typedef enum pa_rtpoll_priority { + PA_RTPOLL_EARLY = -100, /* For very important stuff, like handling control messages */ + PA_RTPOLL_NORMAL = 0, /* For normal stuff */ + PA_RTPOLL_LATE = +100, /* For housekeeping */ + PA_RTPOLL_NEVER = INT_MAX, /* For stuff that doesn't register any callbacks, but only fds to listen on */ +} pa_rtpoll_priority_t; + +pa_rtpoll *pa_rtpoll_new(void); +void pa_rtpoll_free(pa_rtpoll *p); + +/* Sleep on the rtpoll until the time event, or any of the fd events + * is triggered. Returns negative on error, positive if the loop + * should continue to run, 0 when the loop should be terminated + * cleanly. */ +int pa_rtpoll_run(pa_rtpoll *f); + +void pa_rtpoll_set_timer_absolute(pa_rtpoll *p, pa_usec_t usec); +void pa_rtpoll_set_timer_relative(pa_rtpoll *p, pa_usec_t usec); +void pa_rtpoll_set_timer_disabled(pa_rtpoll *p); + +/* Return true when the elapsed timer was the reason for + * the last pa_rtpoll_run() invocation to finish */ +bool pa_rtpoll_timer_elapsed(pa_rtpoll *p); + +/* A new fd wakeup item for pa_rtpoll */ +pa_rtpoll_item *pa_rtpoll_item_new(pa_rtpoll *p, pa_rtpoll_priority_t prio, unsigned n_fds); +void pa_rtpoll_item_free(pa_rtpoll_item *i); + +/* Please note that this pointer might change on every call and when + * pa_rtpoll_run() is called. Hence: call this immediately before + * using the pointer and don't save the result anywhere */ +struct pollfd *pa_rtpoll_item_get_pollfd(pa_rtpoll_item *i, unsigned *n_fds); + +/* Set the callback that shall be called when there's time to do some work: If the + * callback returns a value > 0, the poll is skipped and the next + * iteration of the loop will start immediately. */ +void pa_rtpoll_item_set_work_callback(pa_rtpoll_item *i, int (*work_cb)(pa_rtpoll_item *i), void *userdata); + +/* Set the callback that shall be called immediately before entering + * the sleeping poll: If the callback returns a value > 0, the poll is + * skipped and the next iteration of the loop will start immediately. */ +void pa_rtpoll_item_set_before_callback(pa_rtpoll_item *i, int (*before_cb)(pa_rtpoll_item *i), void *userdata); + +/* Set the callback that shall be called immediately after having + * entered the sleeping poll */ +void pa_rtpoll_item_set_after_callback(pa_rtpoll_item *i, void (*after_cb)(pa_rtpoll_item *i), void *userdata); + +void* pa_rtpoll_item_get_work_userdata(pa_rtpoll_item *i); + +pa_rtpoll_item *pa_rtpoll_item_new_fdsem(pa_rtpoll *p, pa_rtpoll_priority_t prio, pa_fdsem *s); +pa_rtpoll_item *pa_rtpoll_item_new_asyncmsgq_read(pa_rtpoll *p, pa_rtpoll_priority_t prio, pa_asyncmsgq *q); +pa_rtpoll_item *pa_rtpoll_item_new_asyncmsgq_write(pa_rtpoll *p, pa_rtpoll_priority_t prio, pa_asyncmsgq *q); + +#endif diff --git a/src/pulsecore/sample-util.c b/src/pulsecore/sample-util.c new file mode 100644 index 0000000..b2a28eb --- /dev/null +++ b/src/pulsecore/sample-util.c @@ -0,0 +1,405 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <math.h> + +#include <pulse/timeval.h> + +#include <pulsecore/log.h> +#include <pulsecore/core-error.h> +#include <pulsecore/macro.h> +#include <pulsecore/g711.h> +#include <pulsecore/core-util.h> +#include <pulsecore/endianmacros.h> + +#include "sample-util.h" + +#define PA_SILENCE_MAX (pa_page_size()*16) + +pa_memblock *pa_silence_memblock(pa_memblock* b, const pa_sample_spec *spec) { + void *data; + + pa_assert(b); + pa_assert(spec); + + data = pa_memblock_acquire(b); + pa_silence_memory(data, pa_memblock_get_length(b), spec); + pa_memblock_release(b); + + return b; +} + +pa_memchunk* pa_silence_memchunk(pa_memchunk *c, const pa_sample_spec *spec) { + void *data; + + pa_assert(c); + pa_assert(c->memblock); + pa_assert(spec); + + data = pa_memblock_acquire(c->memblock); + pa_silence_memory((uint8_t*) data+c->index, c->length, spec); + pa_memblock_release(c->memblock); + + return c; +} + +static uint8_t silence_byte(pa_sample_format_t format) { + switch (format) { + case PA_SAMPLE_U8: + return 0x80; + case PA_SAMPLE_S16LE: + case PA_SAMPLE_S16BE: + case PA_SAMPLE_S32LE: + case PA_SAMPLE_S32BE: + case PA_SAMPLE_FLOAT32LE: + case PA_SAMPLE_FLOAT32BE: + case PA_SAMPLE_S24LE: + case PA_SAMPLE_S24BE: + case PA_SAMPLE_S24_32LE: + case PA_SAMPLE_S24_32BE: + return 0; + case PA_SAMPLE_ALAW: + return 0xd5; + case PA_SAMPLE_ULAW: + return 0xff; + default: + pa_assert_not_reached(); + } +} + +void* pa_silence_memory(void *p, size_t length, const pa_sample_spec *spec) { + pa_assert(p); + pa_assert(length > 0); + pa_assert(spec); + + memset(p, silence_byte(spec->format), length); + return p; +} + +size_t pa_frame_align(size_t l, const pa_sample_spec *ss) { + size_t fs; + + pa_assert(ss); + + fs = pa_frame_size(ss); + + return (l/fs) * fs; +} + +bool pa_frame_aligned(size_t l, const pa_sample_spec *ss) { + size_t fs; + + pa_assert(ss); + + fs = pa_frame_size(ss); + + return l % fs == 0; +} + +void pa_interleave(const void *src[], unsigned channels, void *dst, size_t ss, unsigned n) { + unsigned c; + size_t fs; + + pa_assert(src); + pa_assert(channels > 0); + pa_assert(dst); + pa_assert(ss > 0); + pa_assert(n > 0); + + fs = ss * channels; + + for (c = 0; c < channels; c++) { + unsigned j; + void *d; + const void *s; + + s = src[c]; + d = (uint8_t*) dst + c * ss; + + for (j = 0; j < n; j ++) { + memcpy(d, s, (int) ss); + s = (uint8_t*) s + ss; + d = (uint8_t*) d + fs; + } + } +} + +void pa_deinterleave(const void *src, void *dst[], unsigned channels, size_t ss, unsigned n) { + size_t fs; + unsigned c; + + pa_assert(src); + pa_assert(dst); + pa_assert(channels > 0); + pa_assert(ss > 0); + pa_assert(n > 0); + + fs = ss * channels; + + for (c = 0; c < channels; c++) { + unsigned j; + const void *s; + void *d; + + s = (uint8_t*) src + c * ss; + d = dst[c]; + + for (j = 0; j < n; j ++) { + memcpy(d, s, (int) ss); + s = (uint8_t*) s + fs; + d = (uint8_t*) d + ss; + } + } +} + +static pa_memblock *silence_memblock_new(pa_mempool *pool, uint8_t c) { + pa_memblock *b; + size_t length; + void *data; + + pa_assert(pool); + + length = PA_MIN(pa_mempool_block_size_max(pool), PA_SILENCE_MAX); + + b = pa_memblock_new(pool, length); + + data = pa_memblock_acquire(b); + memset(data, c, length); + pa_memblock_release(b); + + pa_memblock_set_is_silence(b, true); + + return b; +} + +void pa_silence_cache_init(pa_silence_cache *cache) { + pa_assert(cache); + + memset(cache, 0, sizeof(pa_silence_cache)); +} + +void pa_silence_cache_done(pa_silence_cache *cache) { + pa_sample_format_t f; + pa_assert(cache); + + for (f = 0; f < PA_SAMPLE_MAX; f++) + if (cache->blocks[f]) + pa_memblock_unref(cache->blocks[f]); + + memset(cache, 0, sizeof(pa_silence_cache)); +} + +pa_memchunk* pa_silence_memchunk_get(pa_silence_cache *cache, pa_mempool *pool, pa_memchunk* ret, const pa_sample_spec *spec, size_t length) { + pa_memblock *b; + size_t l; + + pa_assert(cache); + pa_assert(pa_sample_spec_valid(spec)); + + if (!(b = cache->blocks[spec->format])) + + switch (spec->format) { + case PA_SAMPLE_U8: + cache->blocks[PA_SAMPLE_U8] = b = silence_memblock_new(pool, 0x80); + break; + case PA_SAMPLE_S16LE: + case PA_SAMPLE_S16BE: + case PA_SAMPLE_S32LE: + case PA_SAMPLE_S32BE: + case PA_SAMPLE_S24LE: + case PA_SAMPLE_S24BE: + case PA_SAMPLE_S24_32LE: + case PA_SAMPLE_S24_32BE: + case PA_SAMPLE_FLOAT32LE: + case PA_SAMPLE_FLOAT32BE: + cache->blocks[PA_SAMPLE_S16LE] = b = silence_memblock_new(pool, 0); + cache->blocks[PA_SAMPLE_S16BE] = pa_memblock_ref(b); + cache->blocks[PA_SAMPLE_S32LE] = pa_memblock_ref(b); + cache->blocks[PA_SAMPLE_S32BE] = pa_memblock_ref(b); + cache->blocks[PA_SAMPLE_S24LE] = pa_memblock_ref(b); + cache->blocks[PA_SAMPLE_S24BE] = pa_memblock_ref(b); + cache->blocks[PA_SAMPLE_S24_32LE] = pa_memblock_ref(b); + cache->blocks[PA_SAMPLE_S24_32BE] = pa_memblock_ref(b); + cache->blocks[PA_SAMPLE_FLOAT32LE] = pa_memblock_ref(b); + cache->blocks[PA_SAMPLE_FLOAT32BE] = pa_memblock_ref(b); + break; + case PA_SAMPLE_ALAW: + cache->blocks[PA_SAMPLE_ALAW] = b = silence_memblock_new(pool, 0xd5); + break; + case PA_SAMPLE_ULAW: + cache->blocks[PA_SAMPLE_ULAW] = b = silence_memblock_new(pool, 0xff); + break; + default: + pa_assert_not_reached(); + } + + pa_assert(b); + + ret->memblock = pa_memblock_ref(b); + + l = pa_memblock_get_length(b); + if (length > l || length == 0) + length = l; + + ret->length = pa_frame_align(length, spec); + ret->index = 0; + + return ret; +} + +void pa_sample_clamp(pa_sample_format_t format, void *dst, size_t dstr, const void *src, size_t sstr, unsigned n) { + const float *s; + float *d; + + s = src; d = dst; + + if (format == PA_SAMPLE_FLOAT32NE) { + for (; n > 0; n--) { + float f; + + f = *s; + *d = PA_CLAMP_UNLIKELY(f, -1.0f, 1.0f); + + s = (const float*) ((const uint8_t*) s + sstr); + d = (float*) ((uint8_t*) d + dstr); + } + } else { + pa_assert(format == PA_SAMPLE_FLOAT32RE); + + for (; n > 0; n--) { + float f; + + f = PA_READ_FLOAT32RE(s); + f = PA_CLAMP_UNLIKELY(f, -1.0f, 1.0f); + PA_WRITE_FLOAT32RE(d, f); + + s = (const float*) ((const uint8_t*) s + sstr); + d = (float*) ((uint8_t*) d + dstr); + } + } +} + +/* Similar to pa_bytes_to_usec() but rounds up, not down */ + +pa_usec_t pa_bytes_to_usec_round_up(uint64_t length, const pa_sample_spec *spec) { + size_t fs; + pa_usec_t usec; + + pa_assert(spec); + + fs = pa_frame_size(spec); + length = (length + fs - 1) / fs; + + usec = (pa_usec_t) length * PA_USEC_PER_SEC; + + return (usec + spec->rate - 1) / spec->rate; +} + +/* Similar to pa_usec_to_bytes() but rounds up, not down */ + +size_t pa_usec_to_bytes_round_up(pa_usec_t t, const pa_sample_spec *spec) { + uint64_t u; + pa_assert(spec); + + u = (uint64_t) t * (uint64_t) spec->rate; + + u = (u + PA_USEC_PER_SEC - 1) / PA_USEC_PER_SEC; + + u *= pa_frame_size(spec); + + return (size_t) u; +} + +void pa_memchunk_dump_to_file(pa_memchunk *c, const char *fn) { + FILE *f; + void *p; + + pa_assert(c); + pa_assert(fn); + + /* Only for debugging purposes */ + + f = pa_fopen_cloexec(fn, "a"); + + if (!f) { + pa_log_warn("Failed to open '%s': %s", fn, pa_cstrerror(errno)); + return; + } + + p = pa_memblock_acquire(c->memblock); + + if (fwrite((uint8_t*) p + c->index, 1, c->length, f) != c->length) + pa_log_warn("Failed to write to '%s': %s", fn, pa_cstrerror(errno)); + + pa_memblock_release(c->memblock); + + fclose(f); +} + +static void calc_sine(float *f, size_t l, double freq) { + size_t i; + + l /= sizeof(float); + + for (i = 0; i < l; i++) + *(f++) = (float) 0.5f * sin((double) i*M_PI*2*freq / (double) l); +} + +void pa_memchunk_sine(pa_memchunk *c, pa_mempool *pool, unsigned rate, unsigned freq) { + size_t l; + unsigned gcd, n; + void *p; + + pa_memchunk_reset(c); + + gcd = pa_gcd(rate, freq); + n = rate / gcd; + + l = pa_mempool_block_size_max(pool) / sizeof(float); + + l /= n; + if (l <= 0) l = 1; + l *= n; + + c->length = l * sizeof(float); + c->memblock = pa_memblock_new(pool, c->length); + + p = pa_memblock_acquire(c->memblock); + calc_sine(p, c->length, freq * l / rate); + pa_memblock_release(c->memblock); +} + +size_t pa_convert_size(size_t size, const pa_sample_spec *from, const pa_sample_spec *to) { + pa_usec_t usec; + + pa_assert(from); + pa_assert(to); + + usec = pa_bytes_to_usec_round_up(size, from); + return pa_usec_to_bytes_round_up(usec, to); +} diff --git a/src/pulsecore/sample-util.h b/src/pulsecore/sample-util.h new file mode 100644 index 0000000..0732e8d --- /dev/null +++ b/src/pulsecore/sample-util.h @@ -0,0 +1,154 @@ +#ifndef foosampleutilhfoo +#define foosampleutilhfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <inttypes.h> +#include <limits.h> + +#include <pulse/gccmacro.h> +#include <pulse/sample.h> +#include <pulse/volume.h> +#include <pulse/channelmap.h> + +#include <pulsecore/memblock.h> +#include <pulsecore/memchunk.h> + +typedef struct pa_silence_cache { + pa_memblock* blocks[PA_SAMPLE_MAX]; +} pa_silence_cache; + +void pa_silence_cache_init(pa_silence_cache *cache); +void pa_silence_cache_done(pa_silence_cache *cache); + +void *pa_silence_memory(void *p, size_t length, const pa_sample_spec *spec); +pa_memchunk* pa_silence_memchunk(pa_memchunk *c, const pa_sample_spec *spec); +pa_memblock* pa_silence_memblock(pa_memblock *b, const pa_sample_spec *spec); + +pa_memchunk* pa_silence_memchunk_get(pa_silence_cache *cache, pa_mempool *pool, pa_memchunk* ret, const pa_sample_spec *spec, size_t length); + +size_t pa_frame_align(size_t l, const pa_sample_spec *ss) PA_GCC_PURE; + +bool pa_frame_aligned(size_t l, const pa_sample_spec *ss) PA_GCC_PURE; + +void pa_interleave(const void *src[], unsigned channels, void *dst, size_t ss, unsigned n); +void pa_deinterleave(const void *src, void *dst[], unsigned channels, size_t ss, unsigned n); + +void pa_sample_clamp(pa_sample_format_t format, void *dst, size_t dstr, const void *src, size_t sstr, unsigned n); + +static inline int32_t pa_mult_s16_volume(int16_t v, int32_t cv) { +#ifdef HAVE_FAST_64BIT_OPERATIONS + /* Multiply with 64 bit integers on 64 bit platforms */ + return (v * (int64_t) cv) >> 16; +#else + /* Multiplying the 32 bit volume factor with the + * 16 bit sample might result in an 48 bit value. We + * want to do without 64 bit integers and hence do + * the multiplication independently for the HI and + * LO part of the volume. */ + + int32_t hi = cv >> 16; + int32_t lo = cv & 0xFFFF; + return ((v * lo) >> 16) + (v * hi); +#endif +} + +pa_usec_t pa_bytes_to_usec_round_up(uint64_t length, const pa_sample_spec *spec); +size_t pa_usec_to_bytes_round_up(pa_usec_t t, const pa_sample_spec *spec); + +void pa_memchunk_dump_to_file(pa_memchunk *c, const char *fn); + +void pa_memchunk_sine(pa_memchunk *c, pa_mempool *pool, unsigned rate, unsigned freq); + +typedef void (*pa_do_volume_func_t) (void *samples, const void *volumes, unsigned channels, unsigned length); + +pa_do_volume_func_t pa_get_volume_func(pa_sample_format_t f); +void pa_set_volume_func(pa_sample_format_t f, pa_do_volume_func_t func); + +size_t pa_convert_size(size_t size, const pa_sample_spec *from, const pa_sample_spec *to); + +#define PA_CHANNEL_POSITION_MASK_LEFT \ + (PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_LEFT) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_REAR_LEFT) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_SIDE_LEFT) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_LEFT) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_LEFT)) \ + +#define PA_CHANNEL_POSITION_MASK_RIGHT \ + (PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_RIGHT) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_REAR_RIGHT) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_SIDE_RIGHT) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_RIGHT) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_RIGHT)) + +#define PA_CHANNEL_POSITION_MASK_CENTER \ + (PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_CENTER) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_REAR_CENTER) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_CENTER) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_CENTER) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_CENTER)) + +#define PA_CHANNEL_POSITION_MASK_FRONT \ + (PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_LEFT) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_RIGHT) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_CENTER) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_LEFT) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_RIGHT) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_CENTER)) + +#define PA_CHANNEL_POSITION_MASK_REAR \ + (PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_REAR_LEFT) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_REAR_RIGHT) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_REAR_CENTER) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_LEFT) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_RIGHT) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_CENTER)) + +#define PA_CHANNEL_POSITION_MASK_LFE \ + PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_LFE) + +#define PA_CHANNEL_POSITION_MASK_HFE \ + (PA_CHANNEL_POSITION_MASK_REAR | PA_CHANNEL_POSITION_MASK_FRONT \ + | PA_CHANNEL_POSITION_MASK_LEFT | PA_CHANNEL_POSITION_MASK_RIGHT \ + | PA_CHANNEL_POSITION_MASK_CENTER) + +#define PA_CHANNEL_POSITION_MASK_SIDE_OR_TOP_CENTER \ + (PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_SIDE_LEFT) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_SIDE_RIGHT) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_CENTER)) + +#define PA_CHANNEL_POSITION_MASK_TOP \ + (PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_CENTER) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_LEFT) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_RIGHT) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_CENTER) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_LEFT) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_RIGHT) \ + | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_CENTER)) + +#define PA_CHANNEL_POSITION_MASK_ALL \ + ((pa_channel_position_mask_t) (PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_MAX)-1)) + +#endif diff --git a/src/pulsecore/sconv-s16be.c b/src/pulsecore/sconv-s16be.c new file mode 100644 index 0000000..6228c74 --- /dev/null +++ b/src/pulsecore/sconv-s16be.c @@ -0,0 +1,81 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "endianmacros.h" + +#define INT16_FROM PA_INT16_FROM_BE +#define INT16_TO PA_INT16_TO_BE +#define UINT16_FROM PA_UINT16_FROM_BE +#define UINT16_TO PA_UINT16_TO_BE + +#define INT32_FROM PA_INT32_FROM_BE +#define INT32_TO PA_INT32_TO_BE +#define UINT32_FROM PA_UINT32_FROM_BE +#define UINT32_TO PA_UINT32_TO_BE + +#define READ24 PA_READ24BE +#define WRITE24 PA_WRITE24BE + +#define pa_sconv_s16le_to_float32ne pa_sconv_s16be_to_float32ne +#define pa_sconv_s16le_from_float32ne pa_sconv_s16be_from_float32ne +#define pa_sconv_s16le_to_float32re pa_sconv_s16be_to_float32re +#define pa_sconv_s16le_from_float32re pa_sconv_s16be_from_float32re + +#define pa_sconv_s32le_to_float32ne pa_sconv_s32be_to_float32ne +#define pa_sconv_s32le_from_float32ne pa_sconv_s32be_from_float32ne +#define pa_sconv_s32le_to_float32re pa_sconv_s32be_to_float32re +#define pa_sconv_s32le_from_float32re pa_sconv_s32be_from_float32re + +#define pa_sconv_s24le_to_float32ne pa_sconv_s24be_to_float32ne +#define pa_sconv_s24le_from_float32ne pa_sconv_s24be_from_float32ne +#define pa_sconv_s24le_to_float32re pa_sconv_s24be_to_float32re +#define pa_sconv_s24le_from_float32re pa_sconv_s24be_from_float32re + +#define pa_sconv_s24_32le_to_float32ne pa_sconv_s24_32be_to_float32ne +#define pa_sconv_s24_32le_from_float32ne pa_sconv_s24_32be_from_float32ne +#define pa_sconv_s24_32le_to_float32re pa_sconv_s24_32be_to_float32re +#define pa_sconv_s24_32le_from_float32re pa_sconv_s24_32be_from_float32re + +#define pa_sconv_s32le_to_s16ne pa_sconv_s32be_to_s16ne +#define pa_sconv_s32le_from_s16ne pa_sconv_s32be_from_s16ne +#define pa_sconv_s32le_to_s16re pa_sconv_s32be_to_s16re +#define pa_sconv_s32le_from_s16re pa_sconv_s32be_from_s16re + +#define pa_sconv_s24le_to_s16ne pa_sconv_s24be_to_s16ne +#define pa_sconv_s24le_from_s16ne pa_sconv_s24be_from_s16ne +#define pa_sconv_s24le_to_s16re pa_sconv_s24be_to_s16re +#define pa_sconv_s24le_from_s16re pa_sconv_s24be_from_s16re + +#define pa_sconv_s24_32le_to_s16ne pa_sconv_s24_32be_to_s16ne +#define pa_sconv_s24_32le_from_s16ne pa_sconv_s24_32be_from_s16ne +#define pa_sconv_s24_32le_to_s16re pa_sconv_s24_32be_to_s16re +#define pa_sconv_s24_32le_from_s16re pa_sconv_s24_32be_from_s16re + +#ifdef WORDS_BIGENDIAN +#define SWAP_WORDS 0 +#else +#define SWAP_WORDS 1 +#endif + +#include "sconv-s16le.h" +#include "sconv-s16le.c" diff --git a/src/pulsecore/sconv-s16be.h b/src/pulsecore/sconv-s16be.h new file mode 100644 index 0000000..83b05fd --- /dev/null +++ b/src/pulsecore/sconv-s16be.h @@ -0,0 +1,67 @@ +#ifndef foosconv_s16befoo +#define foosconv_s16befoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <inttypes.h> + +void pa_sconv_s16be_to_float32ne(unsigned n, const int16_t *a, float *b); +void pa_sconv_s16be_from_float32ne(unsigned n, const float *a, int16_t *b); +void pa_sconv_s16be_to_float32re(unsigned n, const int16_t *a, float *b); +void pa_sconv_s16be_from_float32re(unsigned n, const float *a, int16_t *b); + +void pa_sconv_s32be_to_float32ne(unsigned n, const int32_t *a, float *b); +void pa_sconv_s32be_from_float32ne(unsigned n, const float *a, int32_t *b); +void pa_sconv_s32be_to_float32re(unsigned n, const int32_t *a, float *b); +void pa_sconv_s32be_from_float32re(unsigned n, const float *a, int32_t *b); + +void pa_sconv_s24be_to_float32ne(unsigned n, const uint8_t *a, float *b); +void pa_sconv_s24be_from_float32ne(unsigned n, const float *a, uint8_t *b); +void pa_sconv_s24be_to_float32re(unsigned n, const uint8_t *a, float *b); +void pa_sconv_s24be_from_float32re(unsigned n, const float *a, uint8_t *b); + +void pa_sconv_s24_32be_to_float32ne(unsigned n, const uint32_t *a, float *b); +void pa_sconv_s24_32be_from_float32ne(unsigned n, const float *a, uint32_t *b); +void pa_sconv_s24_32be_to_float32re(unsigned n, const uint8_t *a, float *b); +void pa_sconv_s24_32be_from_float32re(unsigned n, const float *a, uint8_t *b); + +void pa_sconv_s32be_to_s16ne(unsigned n, const int32_t *a, int16_t *b); +void pa_sconv_s32be_from_s16ne(unsigned n, const int16_t *a, int32_t *b); +void pa_sconv_s32be_to_s16re(unsigned n, const int32_t *a, int16_t *b); +void pa_sconv_s32be_from_s16re(unsigned n, const int16_t *a, int32_t *b); + +void pa_sconv_s24be_to_s16ne(unsigned n, const uint8_t *a, int16_t *b); +void pa_sconv_s24be_from_s16ne(unsigned n, const int16_t *a, uint8_t *b); +void pa_sconv_s24be_to_s16re(unsigned n, const uint8_t *a, int16_t *b); +void pa_sconv_s24be_from_s16re(unsigned n, const int16_t *a, uint8_t *b); + +void pa_sconv_s24_32be_to_s16ne(unsigned n, const uint32_t *a, int16_t *b); +void pa_sconv_s24_32be_from_s16ne(unsigned n, const int16_t *a, uint32_t *b); +void pa_sconv_s24_32be_to_s16re(unsigned n, const uint8_t *a, int16_t *b); +void pa_sconv_s24_32be_from_s16re(unsigned n, const int16_t *a, uint8_t *b); + +#ifdef WORDS_BIGENDIAN +#define pa_sconv_float32be_to_s16ne pa_sconv_s16be_from_float32ne +#define pa_sconv_float32be_from_s16ne pa_sconv_s16be_to_float32ne +#define pa_sconv_float32le_to_s16ne pa_sconv_s16be_from_float32re +#define pa_sconv_float32le_from_s16ne pa_sconv_s16be_to_float32re +#endif + +#endif diff --git a/src/pulsecore/sconv-s16le.c b/src/pulsecore/sconv-s16le.c new file mode 100644 index 0000000..c503e0e --- /dev/null +++ b/src/pulsecore/sconv-s16le.c @@ -0,0 +1,439 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +/* Despite the name of this file we implement S32 and S24 handling here, too. */ + +#include <inttypes.h> +#include <stdio.h> +#include <math.h> + +#include <pulsecore/sconv.h> +#include <pulsecore/macro.h> +#include <pulsecore/endianmacros.h> + +#include "sconv-s16le.h" + +#ifndef INT16_FROM +#define INT16_FROM PA_INT16_FROM_LE +#endif +#ifndef UINT16_FROM +#define UINT16_FROM PA_UINT16_FROM_LE +#endif + +#ifndef INT16_TO +#define INT16_TO PA_INT16_TO_LE +#endif +#ifndef UINT16_TO +#define UINT16_TO PA_UINT16_TO_LE +#endif + +#ifndef INT32_FROM +#define INT32_FROM PA_INT32_FROM_LE +#endif +#ifndef UINT32_FROM +#define UINT32_FROM PA_UINT32_FROM_LE +#endif + +#ifndef INT32_TO +#define INT32_TO PA_INT32_TO_LE +#endif +#ifndef UINT32_TO +#define UINT32_TO PA_UINT32_TO_LE +#endif + +#ifndef READ24 +#define READ24 PA_READ24LE +#endif +#ifndef WRITE24 +#define WRITE24 PA_WRITE24LE +#endif + +#ifndef SWAP_WORDS +#ifdef WORDS_BIGENDIAN +#define SWAP_WORDS 1 +#else +#define SWAP_WORDS 0 +#endif +#endif + +void pa_sconv_s16le_to_float32ne(unsigned n, const int16_t *a, float *b) { + pa_assert(a); + pa_assert(b); + +#if SWAP_WORDS == 1 + for (; n > 0; n--) { + int16_t s = *(a++); + *(b++) = INT16_FROM(s) * (1.0f / (1 << 15)); + } +#else + for (; n > 0; n--) + *(b++) = *(a++) * (1.0f / (1 << 15)); +#endif +} + +void pa_sconv_s32le_to_float32ne(unsigned n, const int32_t *a, float *b) { + pa_assert(a); + pa_assert(b); + +#if SWAP_WORDS == 1 + for (; n > 0; n--) { + int32_t s = *(a++); + *(b++) = INT32_FROM(s) * (1.0f / (1U << 31)); + } +#else + for (; n > 0; n--) + *(b++) = *(a++) * (1.0f / (1U << 31)); +#endif +} + +void pa_sconv_s16le_from_float32ne(unsigned n, const float *a, int16_t *b) { + pa_assert(a); + pa_assert(b); + +#if SWAP_WORDS == 1 + for (; n > 0; n--) { + int16_t s; + float v = *(a++) * (1 << 15); + + s = (int16_t) PA_CLAMP_UNLIKELY(lrintf(v), -0x8000, 0x7FFF); + *(b++) = INT16_TO(s); + } +#else + for (; n > 0; n--) { + float v = *(a++) * (1 << 15); + + *(b++) = (int16_t) PA_CLAMP_UNLIKELY(lrintf(v), -0x8000, 0x7FFF); + } +#endif +} + +void pa_sconv_s32le_from_float32ne(unsigned n, const float *a, int32_t *b) { + pa_assert(a); + pa_assert(b); + +#if SWAP_WORDS == 1 + for (; n > 0; n--) { + int32_t s; + float v = *(a++) * (1U << 31); + + s = (int32_t) PA_CLAMP_UNLIKELY(llrintf(v), -0x80000000LL, 0x7FFFFFFFLL); + *(b++) = INT32_TO(s); + } +#else + for (; n > 0; n--) { + float v = *(a++) * (1U << 31); + + *(b++) = (int32_t) PA_CLAMP_UNLIKELY(llrintf(v), -0x80000000LL, 0x7FFFFFFFLL); + } +#endif +} + +void pa_sconv_s16le_to_float32re(unsigned n, const int16_t *a, float *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--) { + int16_t s = *(a++); + float k = INT16_FROM(s) * (1.0f / (1 << 15)); + PA_WRITE_FLOAT32RE(b++, k); + } +} + +void pa_sconv_s32le_to_float32re(unsigned n, const int32_t *a, float *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--) { + int32_t s = *(a++); + float k = INT32_FROM(s) * (1.0f / (1U << 31)); + PA_WRITE_FLOAT32RE(b++, k); + } +} + +void pa_sconv_s16le_from_float32re(unsigned n, const float *a, int16_t *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--) { + int16_t s; + float v = PA_READ_FLOAT32RE(a++) * (1 << 15); + s = (int16_t) PA_CLAMP_UNLIKELY(lrintf(v), -0x8000, 0x7FFF); + *(b++) = INT16_TO(s); + } +} + +void pa_sconv_s32le_from_float32re(unsigned n, const float *a, int32_t *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--) { + int32_t s; + float v = PA_READ_FLOAT32RE(a++) * (1U << 31); + s = (int32_t) PA_CLAMP_UNLIKELY(llrintf(v), -0x80000000LL, 0x7FFFFFFFLL); + *(b++) = INT32_TO(s); + } +} + +void pa_sconv_s32le_to_s16ne(unsigned n, const int32_t*a, int16_t *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--) { + *b = (int16_t) (INT32_FROM(*a) >> 16); + a++; + b++; + } +} + +void pa_sconv_s32le_to_s16re(unsigned n, const int32_t*a, int16_t *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--) { + int16_t s = (int16_t) (INT32_FROM(*a) >> 16); + *b = PA_INT16_SWAP(s); + a++; + b++; + } +} + +void pa_sconv_s32le_from_s16ne(unsigned n, const int16_t *a, int32_t *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--) { + *b = INT32_TO(((int32_t) *a) << 16); + a++; + b++; + } +} + +void pa_sconv_s32le_from_s16re(unsigned n, const int16_t *a, int32_t *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--) { + int32_t s = ((int32_t) PA_INT16_SWAP(*a)) << 16; + *b = INT32_TO(s); + a++; + b++; + } +} + +void pa_sconv_s24le_to_s16ne(unsigned n, const uint8_t *a, int16_t *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--) { + *b = (int16_t) (READ24(a) >> 8); + a += 3; + b++; + } +} + +void pa_sconv_s24le_from_s16ne(unsigned n, const int16_t *a, uint8_t *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--) { + WRITE24(b, ((uint32_t) *a) << 8); + a++; + b += 3; + } +} + +void pa_sconv_s24le_to_s16re(unsigned n, const uint8_t *a, int16_t *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--) { + int16_t s = (int16_t) (READ24(a) >> 8); + *b = PA_INT16_SWAP(s); + a += 3; + b++; + } +} + +void pa_sconv_s24le_from_s16re(unsigned n, const int16_t *a, uint8_t *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--) { + uint32_t s = ((uint32_t) PA_INT16_SWAP(*a)) << 8; + WRITE24(b, s); + a++; + b += 3; + } +} + +void pa_sconv_s24le_to_float32ne(unsigned n, const uint8_t *a, float *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--) { + int32_t s = READ24(a) << 8; + *b = s * (1.0f / (1U << 31)); + a += 3; + b++; + } +} + +void pa_sconv_s24le_from_float32ne(unsigned n, const float *a, uint8_t *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--) { + int32_t s; + float v = *a * (1U << 31); + s = (int32_t) PA_CLAMP_UNLIKELY(llrint(v), -0x80000000LL, 0x7FFFFFFFLL); + WRITE24(b, ((uint32_t) s) >> 8); + a++; + b += 3; + } +} + +void pa_sconv_s24le_to_float32re(unsigned n, const uint8_t *a, float *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--) { + int32_t s = READ24(a) << 8; + float k = s * (1.0f / (1U << 31)); + PA_WRITE_FLOAT32RE(b, k); + a += 3; + b++; + } +} + +void pa_sconv_s24le_from_float32re(unsigned n, const float *a, uint8_t *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--) { + int32_t s; + float v = PA_READ_FLOAT32RE(a) * (1U << 31); + s = (int32_t) PA_CLAMP_UNLIKELY(llrint(v), -0x80000000LL, 0x7FFFFFFFLL); + WRITE24(b, ((uint32_t) s) >> 8); + a++; + b+=3; + } +} + +void pa_sconv_s24_32le_to_s16ne(unsigned n, const uint32_t *a, int16_t *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--) { + *b = (int16_t) (((int32_t) (UINT32_FROM(*a) << 8)) >> 16); + a++; + b++; + } +} + +void pa_sconv_s24_32le_to_s16re(unsigned n, const uint32_t *a, int16_t *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--) { + int16_t s = (int16_t) ((int32_t) (UINT32_FROM(*a) << 8) >> 16); + *b = PA_INT16_SWAP(s); + a++; + b++; + } +} + +void pa_sconv_s24_32le_from_s16ne(unsigned n, const int16_t *a, uint32_t *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--) { + *b = UINT32_TO(((uint32_t) ((int32_t) *a << 16)) >> 8); + a++; + b++; + } +} + +void pa_sconv_s24_32le_from_s16re(unsigned n, const int16_t *a, uint32_t *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--) { + uint32_t s = ((uint32_t) ((int32_t) PA_INT16_SWAP(*a) << 16)) >> 8; + *b = UINT32_TO(s); + a++; + b++; + } +} + +void pa_sconv_s24_32le_to_float32ne(unsigned n, const uint32_t *a, float *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--) { + int32_t s = (int32_t) (UINT32_FROM(*a) << 8); + *b = s * (1.0f / (1U << 31)); + a++; + b++; + } +} + +void pa_sconv_s24_32le_to_float32re(unsigned n, const uint32_t *a, float *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--) { + int32_t s = (int32_t) (UINT32_FROM(*a) << 8); + float k = s * (1.0f / (1U << 31)); + PA_WRITE_FLOAT32RE(b, k); + a++; + b++; + } +} + +void pa_sconv_s24_32le_from_float32ne(unsigned n, const float *a, uint32_t *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--) { + int32_t s; + float v = *a * (1U << 31); + s = (int32_t) PA_CLAMP_UNLIKELY(llrint(v), -0x80000000LL, 0x7FFFFFFFLL); + *b = UINT32_TO(((uint32_t) s) >> 8); + a++; + b++; + } +} + +void pa_sconv_s24_32le_from_float32re(unsigned n, const float *a, uint32_t *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--) { + int32_t s; + float v = PA_READ_FLOAT32RE(a) * (1U << 31); + s = (int32_t) PA_CLAMP_UNLIKELY(llrint(v), -0x80000000LL, 0x7FFFFFFFLL); + *b = UINT32_TO(((uint32_t) s) >> 8); + a++; + b++; + } +} diff --git a/src/pulsecore/sconv-s16le.h b/src/pulsecore/sconv-s16le.h new file mode 100644 index 0000000..5301373 --- /dev/null +++ b/src/pulsecore/sconv-s16le.h @@ -0,0 +1,67 @@ +#ifndef foosconv_s16lefoo +#define foosconv_s16lefoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <inttypes.h> + +void pa_sconv_s16le_to_float32ne(unsigned n, const int16_t *a, float *b); +void pa_sconv_s16le_from_float32ne(unsigned n, const float *a, int16_t *b); +void pa_sconv_s16le_to_float32re(unsigned n, const int16_t *a, float *b); +void pa_sconv_s16le_from_float32re(unsigned n, const float *a, int16_t *b); + +void pa_sconv_s32le_to_float32ne(unsigned n, const int32_t *a, float *b); +void pa_sconv_s32le_from_float32ne(unsigned n, const float *a, int32_t *b); +void pa_sconv_s32le_to_float32re(unsigned n, const int32_t *a, float *b); +void pa_sconv_s32le_from_float32re(unsigned n, const float *a, int32_t *b); + +void pa_sconv_s24le_to_float32ne(unsigned n, const uint8_t *a, float *b); +void pa_sconv_s24le_from_float32ne(unsigned n, const float *a, uint8_t *b); +void pa_sconv_s24le_to_float32re(unsigned n, const uint8_t *a, float *b); +void pa_sconv_s24le_from_float32re(unsigned n, const float *a, uint8_t *b); + +void pa_sconv_s24_32le_to_float32ne(unsigned n, const uint32_t *a, float *b); +void pa_sconv_s24_32le_from_float32ne(unsigned n, const float *a, uint32_t *b); +void pa_sconv_s24_32le_to_float32re(unsigned n, const uint32_t *a, float *b); +void pa_sconv_s24_32le_from_float32re(unsigned n, const float *a, uint32_t *b); + +void pa_sconv_s32le_to_s16ne(unsigned n, const int32_t *a, int16_t *b); +void pa_sconv_s32le_from_s16ne(unsigned n, const int16_t *a, int32_t *b); +void pa_sconv_s32le_to_s16re(unsigned n, const int32_t *a, int16_t *b); +void pa_sconv_s32le_from_s16re(unsigned n, const int16_t *a, int32_t *b); + +void pa_sconv_s24le_to_s16ne(unsigned n, const uint8_t *a, int16_t *b); +void pa_sconv_s24le_from_s16ne(unsigned n, const int16_t *a, uint8_t *b); +void pa_sconv_s24le_to_s16re(unsigned n, const uint8_t *a, int16_t *b); +void pa_sconv_s24le_from_s16re(unsigned n, const int16_t *a, uint8_t *b); + +void pa_sconv_s24_32le_to_s16ne(unsigned n, const uint32_t *a, int16_t *b); +void pa_sconv_s24_32le_from_s16ne(unsigned n, const int16_t *a, uint32_t *b); +void pa_sconv_s24_32le_to_s16re(unsigned n, const uint32_t *a, int16_t *b); +void pa_sconv_s24_32le_from_s16re(unsigned n, const int16_t *a, uint32_t *b); + +#ifndef WORDS_BIGENDIAN +#define pa_sconv_float32be_to_s16ne pa_sconv_s16le_from_float32re +#define pa_sconv_float32be_from_s16ne pa_sconv_s16le_to_float32re +#define pa_sconv_float32le_to_s16ne pa_sconv_s16le_from_float32ne +#define pa_sconv_float32le_from_s16ne pa_sconv_s16le_to_float32ne +#endif + +#endif diff --git a/src/pulsecore/sconv.c b/src/pulsecore/sconv.c new file mode 100644 index 0000000..0781b6e --- /dev/null +++ b/src/pulsecore/sconv.c @@ -0,0 +1,296 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <math.h> + +#include <pulsecore/g711.h> +#include <pulsecore/macro.h> +#include <pulsecore/endianmacros.h> + +#include <pulsecore/sconv-s16le.h> +#include <pulsecore/sconv-s16be.h> + +#include "sconv.h" + +/* u8 */ +static void u8_to_float32ne(unsigned n, const uint8_t *a, float *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--, a++, b++) + *b = (*a * 1.0/128.0) - 1.0; +} + +static void u8_from_float32ne(unsigned n, const float *a, uint8_t *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--, a++, b++) { + float v; + v = (*a * 127.0) + 128.0; + v = PA_CLAMP_UNLIKELY (v, 0.0, 255.0); + *b = rint (v); + } +} + +static void u8_to_s16ne(unsigned n, const uint8_t *a, int16_t *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--, a++, b++) + *b = (((int16_t)*a) - 128) << 8; +} + +static void u8_from_s16ne(unsigned n, const int16_t *a, uint8_t *b) { + + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--, a++, b++) + *b = (uint8_t) ((uint16_t) *a >> 8) + (uint8_t) 0x80U; +} + +/* float32 */ + +static void float32ne_to_float32ne(unsigned n, const float *a, float *b) { + pa_assert(a); + pa_assert(b); + + memcpy(b, a, (int) (sizeof(float) * n)); +} + +static void float32re_to_float32ne(unsigned n, const float *a, float *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--, a++, b++) + *((uint32_t *) b) = PA_UINT32_SWAP(*((uint32_t *) a)); +} + +/* s16 */ + +static void s16ne_to_s16ne(unsigned n, const int16_t *a, int16_t *b) { + pa_assert(a); + pa_assert(b); + + memcpy(b, a, (int) (sizeof(int16_t) * n)); +} + +static void s16re_to_s16ne(unsigned n, const int16_t *a, int16_t *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--, a++, b++) + *b = PA_INT16_SWAP(*a); +} + +/* ulaw */ + +static void ulaw_to_float32ne(unsigned n, const uint8_t *a, float *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--) + *(b++) = (float) st_ulaw2linear16(*(a++)) / 0x8000; +} + +static void ulaw_from_float32ne(unsigned n, const float *a, uint8_t *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--) { + float v = *(a++); + v = PA_CLAMP_UNLIKELY(v, -1.0f, 1.0f); + v *= 0x1FFF; + *(b++) = st_14linear2ulaw((int16_t) lrintf(v)); + } +} + +static void ulaw_to_s16ne(unsigned n, const uint8_t *a, int16_t *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--, a++, b++) + *b = st_ulaw2linear16(*a); +} + +static void ulaw_from_s16ne(unsigned n, const int16_t *a, uint8_t *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--, a++, b++) + *b = st_14linear2ulaw(*a >> 2); +} + +/* alaw */ + +static void alaw_to_float32ne(unsigned n, const uint8_t *a, float *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--, a++, b++) + *b = (float) st_alaw2linear16(*a) / 0x8000; +} + +static void alaw_from_float32ne(unsigned n, const float *a, uint8_t *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--, a++, b++) { + float v = *a; + v = PA_CLAMP_UNLIKELY(v, -1.0f, 1.0f); + v *= 0xFFF; + *b = st_13linear2alaw((int16_t) lrintf(v)); + } +} + +static void alaw_to_s16ne(unsigned n, const int8_t *a, int16_t *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--, a++, b++) + *b = st_alaw2linear16((uint8_t) *a); +} + +static void alaw_from_s16ne(unsigned n, const int16_t *a, uint8_t *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--, a++, b++) + *b = st_13linear2alaw(*a >> 3); +} + +static pa_convert_func_t to_float32ne_table[] = { + [PA_SAMPLE_U8] = (pa_convert_func_t) u8_to_float32ne, + [PA_SAMPLE_ALAW] = (pa_convert_func_t) alaw_to_float32ne, + [PA_SAMPLE_ULAW] = (pa_convert_func_t) ulaw_to_float32ne, + [PA_SAMPLE_S16LE] = (pa_convert_func_t) pa_sconv_s16le_to_float32ne, + [PA_SAMPLE_S16BE] = (pa_convert_func_t) pa_sconv_s16be_to_float32ne, + [PA_SAMPLE_S32LE] = (pa_convert_func_t) pa_sconv_s32le_to_float32ne, + [PA_SAMPLE_S32BE] = (pa_convert_func_t) pa_sconv_s32be_to_float32ne, + [PA_SAMPLE_S24LE] = (pa_convert_func_t) pa_sconv_s24le_to_float32ne, + [PA_SAMPLE_S24BE] = (pa_convert_func_t) pa_sconv_s24be_to_float32ne, + [PA_SAMPLE_S24_32LE] = (pa_convert_func_t) pa_sconv_s24_32le_to_float32ne, + [PA_SAMPLE_S24_32BE] = (pa_convert_func_t) pa_sconv_s24_32be_to_float32ne, + [PA_SAMPLE_FLOAT32NE] = (pa_convert_func_t) float32ne_to_float32ne, + [PA_SAMPLE_FLOAT32RE] = (pa_convert_func_t) float32re_to_float32ne, +}; + +pa_convert_func_t pa_get_convert_to_float32ne_function(pa_sample_format_t f) { + pa_assert(pa_sample_format_valid(f)); + + return to_float32ne_table[f]; +} + +void pa_set_convert_to_float32ne_function(pa_sample_format_t f, pa_convert_func_t func) { + pa_assert(pa_sample_format_valid(f)); + + to_float32ne_table[f] = func; +} + +static pa_convert_func_t from_float32ne_table[] = { + [PA_SAMPLE_U8] = (pa_convert_func_t) u8_from_float32ne, + [PA_SAMPLE_S16LE] = (pa_convert_func_t) pa_sconv_s16le_from_float32ne, + [PA_SAMPLE_S16BE] = (pa_convert_func_t) pa_sconv_s16be_from_float32ne, + [PA_SAMPLE_S32LE] = (pa_convert_func_t) pa_sconv_s32le_from_float32ne, + [PA_SAMPLE_S32BE] = (pa_convert_func_t) pa_sconv_s32be_from_float32ne, + [PA_SAMPLE_S24LE] = (pa_convert_func_t) pa_sconv_s24le_from_float32ne, + [PA_SAMPLE_S24BE] = (pa_convert_func_t) pa_sconv_s24be_from_float32ne, + [PA_SAMPLE_S24_32LE] = (pa_convert_func_t) pa_sconv_s24_32le_from_float32ne, + [PA_SAMPLE_S24_32BE] = (pa_convert_func_t) pa_sconv_s24_32be_from_float32ne, + [PA_SAMPLE_FLOAT32NE] = (pa_convert_func_t) float32ne_to_float32ne, + [PA_SAMPLE_FLOAT32RE] = (pa_convert_func_t) float32re_to_float32ne, + [PA_SAMPLE_ALAW] = (pa_convert_func_t) alaw_from_float32ne, + [PA_SAMPLE_ULAW] = (pa_convert_func_t) ulaw_from_float32ne +}; + +pa_convert_func_t pa_get_convert_from_float32ne_function(pa_sample_format_t f) { + pa_assert(pa_sample_format_valid(f)); + + return from_float32ne_table[f]; +} + +void pa_set_convert_from_float32ne_function(pa_sample_format_t f, pa_convert_func_t func) { + pa_assert(pa_sample_format_valid(f)); + + from_float32ne_table[f] = func; +} + +static pa_convert_func_t to_s16ne_table[] = { + [PA_SAMPLE_U8] = (pa_convert_func_t) u8_to_s16ne, + [PA_SAMPLE_S16NE] = (pa_convert_func_t) s16ne_to_s16ne, + [PA_SAMPLE_S16RE] = (pa_convert_func_t) s16re_to_s16ne, + [PA_SAMPLE_FLOAT32BE] = (pa_convert_func_t) pa_sconv_float32be_to_s16ne, + [PA_SAMPLE_FLOAT32LE] = (pa_convert_func_t) pa_sconv_float32le_to_s16ne, + [PA_SAMPLE_S32BE] = (pa_convert_func_t) pa_sconv_s32be_to_s16ne, + [PA_SAMPLE_S32LE] = (pa_convert_func_t) pa_sconv_s32le_to_s16ne, + [PA_SAMPLE_S24BE] = (pa_convert_func_t) pa_sconv_s24be_to_s16ne, + [PA_SAMPLE_S24LE] = (pa_convert_func_t) pa_sconv_s24le_to_s16ne, + [PA_SAMPLE_S24_32BE] = (pa_convert_func_t) pa_sconv_s24_32be_to_s16ne, + [PA_SAMPLE_S24_32LE] = (pa_convert_func_t) pa_sconv_s24_32le_to_s16ne, + [PA_SAMPLE_ALAW] = (pa_convert_func_t) alaw_to_s16ne, + [PA_SAMPLE_ULAW] = (pa_convert_func_t) ulaw_to_s16ne +}; + +pa_convert_func_t pa_get_convert_to_s16ne_function(pa_sample_format_t f) { + pa_assert(pa_sample_format_valid(f)); + + return to_s16ne_table[f]; +} + +void pa_set_convert_to_s16ne_function(pa_sample_format_t f, pa_convert_func_t func) { + pa_assert(pa_sample_format_valid(f)); + + to_s16ne_table[f] = func; +} + +static pa_convert_func_t from_s16ne_table[] = { + [PA_SAMPLE_U8] = (pa_convert_func_t) u8_from_s16ne, + [PA_SAMPLE_S16NE] = (pa_convert_func_t) s16ne_to_s16ne, + [PA_SAMPLE_S16RE] = (pa_convert_func_t) s16re_to_s16ne, + [PA_SAMPLE_FLOAT32BE] = (pa_convert_func_t) pa_sconv_float32be_from_s16ne, + [PA_SAMPLE_FLOAT32LE] = (pa_convert_func_t) pa_sconv_float32le_from_s16ne, + [PA_SAMPLE_S32BE] = (pa_convert_func_t) pa_sconv_s32be_from_s16ne, + [PA_SAMPLE_S32LE] = (pa_convert_func_t) pa_sconv_s32le_from_s16ne, + [PA_SAMPLE_S24BE] = (pa_convert_func_t) pa_sconv_s24be_from_s16ne, + [PA_SAMPLE_S24LE] = (pa_convert_func_t) pa_sconv_s24le_from_s16ne, + [PA_SAMPLE_S24_32BE] = (pa_convert_func_t) pa_sconv_s24_32be_from_s16ne, + [PA_SAMPLE_S24_32LE] = (pa_convert_func_t) pa_sconv_s24_32le_from_s16ne, + [PA_SAMPLE_ALAW] = (pa_convert_func_t) alaw_from_s16ne, + [PA_SAMPLE_ULAW] = (pa_convert_func_t) ulaw_from_s16ne, +}; + +pa_convert_func_t pa_get_convert_from_s16ne_function(pa_sample_format_t f) { + pa_assert(pa_sample_format_valid(f)); + + return from_s16ne_table[f]; +} + +void pa_set_convert_from_s16ne_function(pa_sample_format_t f, pa_convert_func_t func) { + pa_assert(pa_sample_format_valid(f)); + + from_s16ne_table[f] = func; +} diff --git a/src/pulsecore/sconv.h b/src/pulsecore/sconv.h new file mode 100644 index 0000000..c457429 --- /dev/null +++ b/src/pulsecore/sconv.h @@ -0,0 +1,41 @@ +#ifndef foosconvhfoo +#define foosconvhfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulse/gccmacro.h> +#include <pulse/sample.h> + +typedef void (*pa_convert_func_t)(unsigned n, const void *a, void *b); + +pa_convert_func_t pa_get_convert_to_float32ne_function(pa_sample_format_t f) PA_GCC_PURE; +pa_convert_func_t pa_get_convert_from_float32ne_function(pa_sample_format_t f) PA_GCC_PURE; + +pa_convert_func_t pa_get_convert_to_s16ne_function(pa_sample_format_t f) PA_GCC_PURE; +pa_convert_func_t pa_get_convert_from_s16ne_function(pa_sample_format_t f) PA_GCC_PURE; + +void pa_set_convert_to_float32ne_function(pa_sample_format_t f, pa_convert_func_t func); +void pa_set_convert_from_float32ne_function(pa_sample_format_t f, pa_convert_func_t func); + +void pa_set_convert_to_s16ne_function(pa_sample_format_t f, pa_convert_func_t func); +void pa_set_convert_from_s16ne_function(pa_sample_format_t f, pa_convert_func_t func); + +#endif diff --git a/src/pulsecore/sconv_neon.c b/src/pulsecore/sconv_neon.c new file mode 100644 index 0000000..11d94d2 --- /dev/null +++ b/src/pulsecore/sconv_neon.c @@ -0,0 +1,96 @@ +/*** + This file is part of PulseAudio. + + Copyright 2012 Peter Meerwald <p.meerwald@bct-electronic.com> + + 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.1 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. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulsecore/macro.h> +#include <pulsecore/endianmacros.h> + +#include "cpu-arm.h" +#include "sconv.h" + +#include <math.h> +#include <arm_neon.h> + +static void pa_sconv_s16le_from_f32ne_neon(unsigned n, const float *src, int16_t *dst) { + unsigned i = n & 3; + + __asm__ __volatile__ ( + "movs %[n], %[n], lsr #2 \n\t" + "beq 2f \n\t" + + "1: \n\t" + "vld1.32 {q0}, [%[src]]! \n\t" + "vcvt.s32.f32 q0, q0, #31 \n\t" /* s32<-f32 as 16:16 fixed-point, with implicit multiplication by 32768 */ + "vqrshrn.s32 d0, q0, #16 \n\t" /* shift, round, narrow */ + "subs %[n], %[n], #1 \n\t" + "vst1.16 {d0}, [%[dst]]! \n\t" + "bgt 1b \n\t" + + "2: \n\t" + + : [dst] "+r" (dst), [src] "+r" (src), [n] "+r" (n) /* output operands (or input operands that get modified) */ + : /* input operands */ + : "memory", "cc", "q0" /* clobber list */ + ); + + /* leftovers */ + while (i--) { + *dst++ = (int16_t) PA_CLAMP_UNLIKELY(lrintf(*src * (1 << 15)), -0x8000, 0x7FFF); + src++; + } +} + +static void pa_sconv_s16le_to_f32ne_neon(unsigned n, const int16_t *src, float *dst) { + unsigned i = n & 3; + const float invscale = 1.0f / (1 << 15); + + __asm__ __volatile__ ( + "movs %[n], %[n], lsr #2 \n\t" + "beq 2f \n\t" + + "1: \n\t" + "vld1.16 {d0}, [%[src]]! \n\t" + "vmovl.s16 q0, d0 \n\t" /* widen */ + "vcvt.f32.s32 q0, q0, #15 \n\t" /* f32<-s32 and divide by (1<<15) */ + "subs %[n], %[n], #1 \n\t" + "vst1.32 {q0}, [%[dst]]! \n\t" + "bgt 1b \n\t" + + "2: \n\t" + + : [dst] "+r" (dst), [src] "+r" (src), [n] "+r" (n) /* output operands (or input operands that get modified) */ + : /* input operands */ + : "memory", "cc", "q0" /* clobber list */ + ); + + /* leftovers */ + while (i--) { + *dst++ = *src++ * invscale; + } +} + +void pa_convert_func_init_neon(pa_cpu_arm_flag_t flags) { + pa_log_info("Initialising ARM NEON optimized conversions."); + pa_set_convert_from_float32ne_function(PA_SAMPLE_S16LE, (pa_convert_func_t) pa_sconv_s16le_from_f32ne_neon); + pa_set_convert_to_float32ne_function(PA_SAMPLE_S16LE, (pa_convert_func_t) pa_sconv_s16le_to_f32ne_neon); +#ifndef WORDS_BIGENDIAN + pa_set_convert_from_s16ne_function(PA_SAMPLE_FLOAT32LE, (pa_convert_func_t) pa_sconv_s16le_to_f32ne_neon); + pa_set_convert_to_s16ne_function(PA_SAMPLE_FLOAT32LE, (pa_convert_func_t) pa_sconv_s16le_from_f32ne_neon); +#endif +} diff --git a/src/pulsecore/sconv_sse.c b/src/pulsecore/sconv_sse.c new file mode 100644 index 0000000..1b097b8 --- /dev/null +++ b/src/pulsecore/sconv_sse.c @@ -0,0 +1,177 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> + +#include <pulsecore/macro.h> +#include <pulsecore/endianmacros.h> + +#include "cpu-x86.h" +#include "sconv.h" + +#if (!defined(__APPLE__) && !defined(__FreeBSD__) && !defined(__FreeBSD_kernel__) && defined (__i386__)) || defined (__amd64__) + +static const PA_DECLARE_ALIGNED (16, float, scale[4]) = { 0x8000, 0x8000, 0x8000, 0x8000 }; + +static void pa_sconv_s16le_from_f32ne_sse(unsigned n, const float *a, int16_t *b) { + pa_reg_x86 temp, i; + + __asm__ __volatile__ ( + " movaps %5, %%xmm5 \n\t" + " xor %0, %0 \n\t" + + " mov %4, %1 \n\t" + " sar $3, %1 \n\t" /* 8 floats at a time */ + " cmp $0, %1 \n\t" + " je 2f \n\t" + + "1: \n\t" + " movups (%q2, %0, 2), %%xmm0 \n\t" /* read 8 floats */ + " movups 16(%q2, %0, 2), %%xmm2 \n\t" + " mulps %%xmm5, %%xmm0 \n\t" /* *= 0x8000 */ + " mulps %%xmm5, %%xmm2 \n\t" + + " cvtps2pi %%xmm0, %%mm0 \n\t" /* low part to int */ + " cvtps2pi %%xmm2, %%mm2 \n\t" + " movhlps %%xmm0, %%xmm0 \n\t" /* bring high part in position */ + " movhlps %%xmm2, %%xmm2 \n\t" + " cvtps2pi %%xmm0, %%mm1 \n\t" /* high part to int */ + " cvtps2pi %%xmm2, %%mm3 \n\t" + + " packssdw %%mm1, %%mm0 \n\t" /* pack parts */ + " packssdw %%mm3, %%mm2 \n\t" + " movq %%mm0, (%q3, %0) \n\t" + " movq %%mm2, 8(%q3, %0) \n\t" + + " add $16, %0 \n\t" + " dec %1 \n\t" + " jne 1b \n\t" + + "2: \n\t" + " mov %4, %1 \n\t" /* prepare for leftovers */ + " and $7, %1 \n\t" + " je 5f \n\t" + + "3: \n\t" + " movss (%q2, %0, 2), %%xmm0 \n\t" + " mulss %%xmm5, %%xmm0 \n\t" + " cvtss2si %%xmm0, %4 \n\t" + " add $0x8000, %4 \n\t" /* check for saturation */ + " and $~0xffff, %4 \n\t" + " cvtss2si %%xmm0, %4 \n\t" + " je 4f \n\t" + " sar $31, %4 \n\t" + " xor $0x7fff, %4 \n\t" + + "4: \n\t" + " movw %w4, (%q3, %0) \n\t" /* store leftover */ + " add $2, %0 \n\t" + " dec %1 \n\t" + " jne 3b \n\t" + + "5: \n\t" + " emms \n\t" + + : "=&r" (i), "=&r" (temp) + : "r" (a), "r" (b), "r" ((pa_reg_x86)n), "m" (*scale) + : "cc", "memory" + ); +} + +static void pa_sconv_s16le_from_f32ne_sse2(unsigned n, const float *a, int16_t *b) { + pa_reg_x86 temp, i; + + __asm__ __volatile__ ( + " movaps %5, %%xmm5 \n\t" + " xor %0, %0 \n\t" + + " mov %4, %1 \n\t" + " sar $3, %1 \n\t" /* 8 floats at a time */ + " cmp $0, %1 \n\t" + " je 2f \n\t" + + "1: \n\t" + " movups (%q2, %0, 2), %%xmm0 \n\t" /* read 8 floats */ + " movups 16(%q2, %0, 2), %%xmm2 \n\t" + " mulps %%xmm5, %%xmm0 \n\t" /* *= 0x8000 */ + " mulps %%xmm5, %%xmm2 \n\t" + + " cvtps2dq %%xmm0, %%xmm0 \n\t" + " cvtps2dq %%xmm2, %%xmm2 \n\t" + + " packssdw %%xmm2, %%xmm0 \n\t" + " movdqu %%xmm0, (%q3, %0) \n\t" + + " add $16, %0 \n\t" + " dec %1 \n\t" + " jne 1b \n\t" + + "2: \n\t" + " mov %4, %1 \n\t" /* prepare for leftovers */ + " and $7, %1 \n\t" + " je 5f \n\t" + + "3: \n\t" + " movss (%q2, %0, 2), %%xmm0 \n\t" + " mulss %%xmm5, %%xmm0 \n\t" + " cvtss2si %%xmm0, %4 \n\t" + " add $0x8000, %4 \n\t" + " and $~0xffff, %4 \n\t" /* check for saturation */ + " cvtss2si %%xmm0, %4 \n\t" + " je 4f \n\t" + " sar $31, %4 \n\t" + " xor $0x7fff, %4 \n\t" + + "4: \n\t" + " movw %w4, (%q3, %0) \n\t" /* store leftover */ + " add $2, %0 \n\t" + " dec %1 \n\t" + " jne 3b \n\t" + + "5: \n\t" + + : "=&r" (i), "=&r" (temp) + : "r" (a), "r" (b), "r" ((pa_reg_x86)n), "m" (*scale) + : "cc", "memory" + ); +} + +#endif /* defined (__i386__) || defined (__amd64__) */ + +void pa_convert_func_init_sse(pa_cpu_x86_flag_t flags) { +#if (!defined(__APPLE__) && !defined(__FreeBSD__) && !defined(__FreeBSD_kernel__) && defined (__i386__)) || defined (__amd64__) + + if (flags & PA_CPU_X86_SSE2) { + pa_log_info("Initialising SSE2 optimized conversions."); + pa_set_convert_from_float32ne_function(PA_SAMPLE_S16LE, (pa_convert_func_t) pa_sconv_s16le_from_f32ne_sse2); + pa_set_convert_to_s16ne_function(PA_SAMPLE_FLOAT32LE, (pa_convert_func_t) pa_sconv_s16le_from_f32ne_sse2); + } else if (flags & PA_CPU_X86_SSE) { + pa_log_info("Initialising SSE optimized conversions."); + pa_set_convert_from_float32ne_function(PA_SAMPLE_S16LE, (pa_convert_func_t) pa_sconv_s16le_from_f32ne_sse); + pa_set_convert_to_s16ne_function(PA_SAMPLE_FLOAT32LE, (pa_convert_func_t) pa_sconv_s16le_from_f32ne_sse); + } + +#endif /* defined (__i386__) || defined (__amd64__) */ +} diff --git a/src/pulsecore/semaphore-osx.c b/src/pulsecore/semaphore-osx.c new file mode 100644 index 0000000..c957f26 --- /dev/null +++ b/src/pulsecore/semaphore-osx.c @@ -0,0 +1,92 @@ +/*** + This file is part of PulseAudio. + + Copyright 2006 Lennart Poettering + Copyright 2013 Albert Zeyer + + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <errno.h> +#include <pthread.h> +#include <semaphore.h> +#include <sys/types.h> +#include <unistd.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/macro.h> +#include <pulsecore/atomic.h> +#include <pulsecore/core-util.h> + +#include "semaphore.h" + +/* OSX doesn't support unnamed semaphores (via sem_init). + * Thus, we use a counter to give them enumerated names. */ +static pa_atomic_t id_counter = PA_ATOMIC_INIT(0); + +struct pa_semaphore { + sem_t *sem; + int id; +}; + +static char *sem_name(char *fn, size_t l, int id) { + pa_snprintf(fn, l, "/pulse-sem-%u-%u", getpid(), id); + return fn; +} + +pa_semaphore *pa_semaphore_new(unsigned value) { + pa_semaphore *s; + char fn[32]; + + s = pa_xnew(pa_semaphore, 1); + s->id = pa_atomic_inc(&id_counter); + sem_name(fn, sizeof(fn), s->id); + sem_unlink(fn); /* in case an old stale semaphore is left around */ + pa_assert_se(s->sem = sem_open(fn, O_CREAT|O_EXCL, 0700, value)); + pa_assert(s->sem != SEM_FAILED); + return s; +} + +void pa_semaphore_free(pa_semaphore *s) { + char fn[32]; + + pa_assert(s); + + pa_assert_se(sem_close(s->sem) == 0); + sem_name(fn, sizeof(fn), s->id); + pa_assert_se(sem_unlink(fn) == 0); + pa_xfree(s); +} + +void pa_semaphore_post(pa_semaphore *s) { + pa_assert(s); + pa_assert_se(sem_post(s->sem) == 0); +} + +void pa_semaphore_wait(pa_semaphore *s) { + int ret; + + pa_assert(s); + + do { + ret = sem_wait(s->sem); + } while (ret < 0 && errno == EINTR); + + pa_assert(ret == 0); +} diff --git a/src/pulsecore/semaphore-posix.c b/src/pulsecore/semaphore-posix.c new file mode 100644 index 0000000..7907c19 --- /dev/null +++ b/src/pulsecore/semaphore-posix.c @@ -0,0 +1,87 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <pthread.h> +#include <semaphore.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/macro.h> + +#include "semaphore.h" + +struct pa_semaphore { + sem_t sem; +}; + +pa_semaphore* pa_semaphore_new(unsigned value) { + pa_semaphore *s; + + s = pa_xnew(pa_semaphore, 1); + pa_assert_se(sem_init(&s->sem, 0, value) == 0); + return s; +} + +void pa_semaphore_free(pa_semaphore *s) { + pa_assert(s); + pa_assert_se(sem_destroy(&s->sem) == 0); + pa_xfree(s); +} + +void pa_semaphore_post(pa_semaphore *s) { + pa_assert(s); + pa_assert_se(sem_post(&s->sem) == 0); +} + +void pa_semaphore_wait(pa_semaphore *s) { + int ret; + pa_assert(s); + + do { + ret = sem_wait(&s->sem); + } while (ret < 0 && errno == EINTR); + + pa_assert(ret == 0); +} + +pa_semaphore* pa_static_semaphore_get(pa_static_semaphore *s, unsigned value) { + pa_semaphore *m; + + pa_assert(s); + + /* First, check if already initialized and short cut */ + if ((m = pa_atomic_ptr_load(&s->ptr))) + return m; + + /* OK, not initialized, so let's allocate, and fill in */ + m = pa_semaphore_new(value); + if ((pa_atomic_ptr_cmpxchg(&s->ptr, NULL, m))) + return m; + + pa_semaphore_free(m); + + /* Him, filling in failed, so someone else must have filled in + * already */ + pa_assert_se(m = pa_atomic_ptr_load(&s->ptr)); + return m; +} diff --git a/src/pulsecore/semaphore-win32.c b/src/pulsecore/semaphore-win32.c new file mode 100644 index 0000000..660d4bd --- /dev/null +++ b/src/pulsecore/semaphore-win32.c @@ -0,0 +1,60 @@ +/*** + This file is part of PulseAudio. + + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <windows.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/macro.h> + +#include "semaphore.h" + +struct pa_semaphore { + HANDLE sema; +}; + +pa_semaphore* pa_semaphore_new(unsigned value) { + pa_semaphore *s; + + s = pa_xnew(pa_semaphore, 1); + + s->sema = CreateSemaphore(NULL, value, 32767, NULL); + pa_assert(s->sema != NULL); + + return s; +} + +void pa_semaphore_free(pa_semaphore *s) { + pa_assert(s); + CloseHandle(s->sema); + pa_xfree(s); +} + +void pa_semaphore_post(pa_semaphore *s) { + pa_assert(s); + ReleaseSemaphore(s->sema, 1, NULL); +} + +void pa_semaphore_wait(pa_semaphore *s) { + pa_assert(s); + WaitForSingleObject(s->sema, INFINITE); +} diff --git a/src/pulsecore/semaphore.h b/src/pulsecore/semaphore.h new file mode 100644 index 0000000..4c746f1 --- /dev/null +++ b/src/pulsecore/semaphore.h @@ -0,0 +1,46 @@ +#ifndef foopulsesemaphorehfoo +#define foopulsesemaphorehfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulsecore/macro.h> +#include <pulsecore/atomic.h> + +typedef struct pa_semaphore pa_semaphore; + +pa_semaphore* pa_semaphore_new(unsigned value); +void pa_semaphore_free(pa_semaphore *m); + +void pa_semaphore_post(pa_semaphore *m); +void pa_semaphore_wait(pa_semaphore *m); + +/* Static semaphores are basically just atomically updated pointers to + * pa_semaphore objects */ + +typedef struct pa_static_semaphore { + pa_atomic_ptr_t ptr; +} pa_static_semaphore; + +#define PA_STATIC_SEMAPHORE_INIT { PA_ATOMIC_PTR_INIT(NULL) } + +/* When you call this make sure to pass always the same value parameter! */ +pa_semaphore* pa_static_semaphore_get(pa_static_semaphore *m, unsigned value); + +#endif diff --git a/src/pulsecore/shared.c b/src/pulsecore/shared.c new file mode 100644 index 0000000..9bc7eb5 --- /dev/null +++ b/src/pulsecore/shared.c @@ -0,0 +1,116 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/xmalloc.h> +#include <pulsecore/macro.h> + +#include "shared.h" + +typedef struct pa_shared { + char *name; /* Points to memory allocated by the shared property system */ + void *data; /* Points to memory maintained by the caller */ +} pa_shared; + +/* Allocate a new shared property object */ +static pa_shared* shared_new(const char *name, void *data) { + pa_shared* p; + + pa_assert(name); + pa_assert(data); + + p = pa_xnew(pa_shared, 1); + p->name = pa_xstrdup(name); + p->data = data; + + return p; +} + +/* Free a shared property object */ +static void shared_free(pa_shared *p) { + pa_assert(p); + + pa_xfree(p->name); + pa_xfree(p); +} + +void* pa_shared_get(pa_core *c, const char *name) { + pa_shared *p; + + pa_assert(c); + pa_assert(name); + pa_assert(c->shared); + + if (!(p = pa_hashmap_get(c->shared, name))) + return NULL; + + return p->data; +} + +int pa_shared_set(pa_core *c, const char *name, void *data) { + pa_shared *p; + + pa_assert(c); + pa_assert(name); + pa_assert(data); + pa_assert(c->shared); + + if (pa_hashmap_get(c->shared, name)) + return -1; + + p = shared_new(name, data); + pa_hashmap_put(c->shared, p->name, p); + return 0; +} + +int pa_shared_remove(pa_core *c, const char *name) { + pa_shared *p; + + pa_assert(c); + pa_assert(name); + pa_assert(c->shared); + + if (!(p = pa_hashmap_remove(c->shared, name))) + return -1; + + shared_free(p); + return 0; +} + +void pa_shared_dump(pa_core *c, pa_strbuf *s) { + void *state = NULL; + pa_shared *p; + + pa_assert(c); + pa_assert(s); + + while ((p = pa_hashmap_iterate(c->shared, &state, NULL))) + pa_strbuf_printf(s, "[%s] -> [%p]\n", p->name, p->data); +} + +int pa_shared_replace(pa_core *c, const char *name, void *data) { + pa_assert(c); + pa_assert(name); + + (void) pa_shared_remove(c, name); + return pa_shared_set(c, name, data); +} diff --git a/src/pulsecore/shared.h b/src/pulsecore/shared.h new file mode 100644 index 0000000..19ac462 --- /dev/null +++ b/src/pulsecore/shared.h @@ -0,0 +1,55 @@ +#ifndef foosharedshfoo +#define foosharedshfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulsecore/core.h> +#include <pulsecore/strbuf.h> + +/* The shared property subsystem is to be used to share data between + * modules. Consider them to be kind of "global" variables for a + * core. Why not use the hashmap functions directly? The hashmap + * functions copy neither the key nor value, while this property + * system copies the key. Users of this system have to think about + * reference counting themselves. */ + +/* Note: please don't confuse this with the proplist framework in + * pulse/proplist.[ch]! */ + +/* Return a pointer to the value of the specified shared property. */ +void* pa_shared_get(pa_core *c, const char *name); + +/* Set the shared property 'name' to 'data'. This function fails in + * case a property by this name already exists. The property data is + * not copied or reference counted. This is the caller's job. */ +int pa_shared_set(pa_core *c, const char *name, void *data); + +/* Remove the specified shared property. Return non-zero on failure */ +int pa_shared_remove(pa_core *c, const char *name); + +/* A combination of pa_shared_remove() and pa_shared_set(); this function + * first tries to remove the property by this name and then sets the + * property. Return non-zero on failure. */ +int pa_shared_replace(pa_core *c, const char *name, void *data); + +/* Dump the current set of shared properties */ +void pa_shared_dump(pa_core *c, pa_strbuf *s); + +#endif diff --git a/src/pulsecore/shm.c b/src/pulsecore/shm.c new file mode 100644 index 0000000..0742cb8 --- /dev/null +++ b/src/pulsecore/shm.c @@ -0,0 +1,495 @@ +/*** + This file is part of PulseAudio. + + Copyright 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <dirent.h> +#include <signal.h> + +#ifdef HAVE_SYS_MMAN_H +#include <sys/mman.h> +#endif + +/* This is deprecated on glibc but is still used by FreeBSD */ +#if !defined(MAP_ANONYMOUS) && defined(MAP_ANON) +# define MAP_ANONYMOUS MAP_ANON +#endif + +#include <pulse/xmalloc.h> +#include <pulse/gccmacro.h> + +#include <pulsecore/memfd-wrappers.h> +#include <pulsecore/core-error.h> +#include <pulsecore/log.h> +#include <pulsecore/random.h> +#include <pulsecore/core-util.h> +#include <pulsecore/macro.h> +#include <pulsecore/atomic.h> +#include <pulsecore/mem.h> + +#include "shm.h" + +#if defined(__linux__) && !defined(MADV_REMOVE) +#define MADV_REMOVE 9 +#endif + +/* 1 GiB at max */ +#define MAX_SHM_SIZE (PA_ALIGN(1024*1024*1024)) + +#ifdef __linux__ +/* On Linux we know that the shared memory blocks are files in + * /dev/shm. We can use that information to list all blocks and + * cleanup unused ones */ +#define SHM_PATH "/dev/shm/" +#define SHM_ID_LEN 10 +#elif defined(__sun) +#define SHM_PATH "/tmp" +#define SHM_ID_LEN 15 +#else +#undef SHM_PATH +#undef SHM_ID_LEN +#endif + +#define SHM_MARKER ((int) 0xbeefcafe) + +/* We now put this SHM marker at the end of each segment. It's + * optional, to not require a reboot when upgrading, though. Note that + * on multiarch systems 32bit and 64bit processes might access this + * region simultaneously. The header fields need to be independent + * from the process' word with */ +struct shm_marker { + pa_atomic_t marker; /* 0xbeefcafe */ + pa_atomic_t pid; + uint64_t _reserved1; + uint64_t _reserved2; + uint64_t _reserved3; + uint64_t _reserved4; +} PA_GCC_PACKED; + +static inline size_t shm_marker_size(pa_mem_type_t type) { + if (type == PA_MEM_TYPE_SHARED_POSIX) + return PA_ALIGN(sizeof(struct shm_marker)); + + return 0; +} + +#ifdef HAVE_SHM_OPEN +static char *segment_name(char *fn, size_t l, unsigned id) { + pa_snprintf(fn, l, "/pulse-shm-%u", id); + return fn; +} +#endif + +static int privatemem_create(pa_shm *m, size_t size) { + pa_assert(m); + pa_assert(size > 0); + + m->type = PA_MEM_TYPE_PRIVATE; + m->id = 0; + m->size = size; + m->do_unlink = false; + m->fd = -1; + +#ifdef MAP_ANONYMOUS + if ((m->ptr = mmap(NULL, m->size, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, (off_t) 0)) == MAP_FAILED) { + pa_log("mmap() failed: %s", pa_cstrerror(errno)); + return -1; + } +#elif defined(HAVE_POSIX_MEMALIGN) + { + int r; + + if ((r = posix_memalign(&m->ptr, pa_page_size(), size)) < 0) { + pa_log("posix_memalign() failed: %s", pa_cstrerror(r)); + return r; + } + } +#else + m->ptr = pa_xmalloc(m->size); +#endif + + return 0; +} + +static int sharedmem_create(pa_shm *m, pa_mem_type_t type, size_t size, mode_t mode) { +#if defined(HAVE_SHM_OPEN) || defined(HAVE_MEMFD) + char fn[32]; + int fd = -1; + struct shm_marker *marker; + bool do_unlink = false; + + /* Each time we create a new SHM area, let's first drop all stale + * ones */ + pa_shm_cleanup(); + + pa_random(&m->id, sizeof(m->id)); + + switch (type) { +#ifdef HAVE_SHM_OPEN + case PA_MEM_TYPE_SHARED_POSIX: + segment_name(fn, sizeof(fn), m->id); + fd = shm_open(fn, O_RDWR|O_CREAT|O_EXCL, mode); + do_unlink = true; + break; +#endif +#ifdef HAVE_MEMFD + case PA_MEM_TYPE_SHARED_MEMFD: + fd = memfd_create("pulseaudio", MFD_ALLOW_SEALING); + break; +#endif + default: + goto fail; + } + + if (fd < 0) { + pa_log("%s open() failed: %s", pa_mem_type_to_string(type), pa_cstrerror(errno)); + goto fail; + } + + m->type = type; + m->size = size + shm_marker_size(type); + m->do_unlink = do_unlink; + + if (ftruncate(fd, (off_t) m->size) < 0) { + pa_log("ftruncate() failed: %s", pa_cstrerror(errno)); + goto fail; + } + +#ifndef MAP_NORESERVE +#define MAP_NORESERVE 0 +#endif + + if ((m->ptr = mmap(NULL, PA_PAGE_ALIGN(m->size), PROT_READ|PROT_WRITE, MAP_SHARED|MAP_NORESERVE, fd, (off_t) 0)) == MAP_FAILED) { + pa_log("mmap() failed: %s", pa_cstrerror(errno)); + goto fail; + } + + if (type == PA_MEM_TYPE_SHARED_POSIX) { + /* We store our PID at the end of the shm block, so that we + * can check for dead shm segments later */ + marker = (struct shm_marker*) ((uint8_t*) m->ptr + m->size - shm_marker_size(type)); + pa_atomic_store(&marker->pid, (int) getpid()); + pa_atomic_store(&marker->marker, SHM_MARKER); + } + + /* For memfds, we keep the fd open until we pass it + * to the other PA endpoint over unix domain socket. */ + if (type != PA_MEM_TYPE_SHARED_MEMFD) { + pa_assert_se(pa_close(fd) == 0); + m->fd = -1; + } +#ifdef HAVE_MEMFD + else + m->fd = fd; +#endif + + return 0; + +fail: + if (fd >= 0) { +#ifdef HAVE_SHM_OPEN + if (type == PA_MEM_TYPE_SHARED_POSIX) + shm_unlink(fn); +#endif + pa_close(fd); + } +#endif /* defined(HAVE_SHM_OPEN) || defined(HAVE_MEMFD) */ + + return -1; +} + +int pa_shm_create_rw(pa_shm *m, pa_mem_type_t type, size_t size, mode_t mode) { + pa_assert(m); + pa_assert(size > 0); + pa_assert(size <= MAX_SHM_SIZE); + pa_assert(!(mode & ~0777)); + pa_assert(mode >= 0600); + + /* Round up to make it page aligned */ + size = PA_PAGE_ALIGN(size); + + if (type == PA_MEM_TYPE_PRIVATE) + return privatemem_create(m, size); + + return sharedmem_create(m, type, size, mode); +} + +static void privatemem_free(pa_shm *m) { + pa_assert(m); + pa_assert(m->ptr); + pa_assert(m->size > 0); + +#ifdef MAP_ANONYMOUS + if (munmap(m->ptr, m->size) < 0) + pa_log("munmap() failed: %s", pa_cstrerror(errno)); +#elif defined(HAVE_POSIX_MEMALIGN) + free(m->ptr); +#else + pa_xfree(m->ptr); +#endif +} + +void pa_shm_free(pa_shm *m) { + pa_assert(m); + pa_assert(m->ptr); + pa_assert(m->size > 0); + +#ifdef MAP_FAILED + pa_assert(m->ptr != MAP_FAILED); +#endif + + if (m->type == PA_MEM_TYPE_PRIVATE) { + privatemem_free(m); + goto finish; + } + +#if defined(HAVE_SHM_OPEN) || defined(HAVE_MEMFD) + if (munmap(m->ptr, PA_PAGE_ALIGN(m->size)) < 0) + pa_log("munmap() failed: %s", pa_cstrerror(errno)); + +#ifdef HAVE_SHM_OPEN + if (m->type == PA_MEM_TYPE_SHARED_POSIX && m->do_unlink) { + char fn[32]; + + segment_name(fn, sizeof(fn), m->id); + if (shm_unlink(fn) < 0) + pa_log(" shm_unlink(%s) failed: %s", fn, pa_cstrerror(errno)); + } +#endif +#ifdef HAVE_MEMFD + if (m->type == PA_MEM_TYPE_SHARED_MEMFD && m->fd != -1) + pa_assert_se(pa_close(m->fd) == 0); +#endif + +#else + /* We shouldn't be here without shm or memfd support */ + pa_assert_not_reached(); +#endif + +finish: + pa_zero(*m); +} + +void pa_shm_punch(pa_shm *m, size_t offset, size_t size) { + void *ptr; + size_t o; + const size_t page_size = pa_page_size(); + + pa_assert(m); + pa_assert(m->ptr); + pa_assert(m->size > 0); + pa_assert(offset+size <= m->size); + +#ifdef MAP_FAILED + pa_assert(m->ptr != MAP_FAILED); +#endif + + /* You're welcome to implement this as NOOP on systems that don't + * support it */ + + /* Align the pointer up to multiples of the page size */ + ptr = (uint8_t*) m->ptr + offset; + o = (size_t) ((uint8_t*) ptr - (uint8_t*) PA_PAGE_ALIGN_PTR(ptr)); + + if (o > 0) { + size_t delta = page_size - o; + ptr = (uint8_t*) ptr + delta; + size -= delta; + } + + /* Align the size down to multiples of page size */ + size = (size / page_size) * page_size; + +#ifdef MADV_REMOVE + if (madvise(ptr, size, MADV_REMOVE) >= 0) + return; +#endif + +#ifdef MADV_FREE + if (madvise(ptr, size, MADV_FREE) >= 0) + return; +#endif + +#ifdef MADV_DONTNEED + madvise(ptr, size, MADV_DONTNEED); +#elif defined(POSIX_MADV_DONTNEED) + posix_madvise(ptr, size, POSIX_MADV_DONTNEED); +#endif +} + +static int shm_attach(pa_shm *m, pa_mem_type_t type, unsigned id, int memfd_fd, bool writable, bool for_cleanup) { +#if defined(HAVE_SHM_OPEN) || defined(HAVE_MEMFD) + char fn[32]; + int fd = -1; + int prot; + struct stat st; + + pa_assert(m); + + switch (type) { +#ifdef HAVE_SHM_OPEN + case PA_MEM_TYPE_SHARED_POSIX: + pa_assert(memfd_fd == -1); + segment_name(fn, sizeof(fn), id); + if ((fd = shm_open(fn, writable ? O_RDWR : O_RDONLY, 0)) < 0) { + if ((errno != EACCES && errno != ENOENT) || !for_cleanup) + pa_log("shm_open() failed: %s", pa_cstrerror(errno)); + goto fail; + } + break; +#endif +#ifdef HAVE_MEMFD + case PA_MEM_TYPE_SHARED_MEMFD: + pa_assert(memfd_fd != -1); + fd = memfd_fd; + break; +#endif + default: + goto fail; + } + + if (fstat(fd, &st) < 0) { + pa_log("fstat() failed: %s", pa_cstrerror(errno)); + goto fail; + } + + if (st.st_size <= 0 || + st.st_size > (off_t) MAX_SHM_SIZE + (off_t) shm_marker_size(type) || + PA_ALIGN((size_t) st.st_size) != (size_t) st.st_size) { + pa_log("Invalid shared memory segment size"); + goto fail; + } + + prot = writable ? PROT_READ | PROT_WRITE : PROT_READ; + if ((m->ptr = mmap(NULL, PA_PAGE_ALIGN(st.st_size), prot, MAP_SHARED, fd, (off_t) 0)) == MAP_FAILED) { + pa_log("mmap() failed: %s", pa_cstrerror(errno)); + goto fail; + } + + /* In case of attaching to memfd areas, _the caller_ maintains + * ownership of the passed fd and has the sole responsibility + * of closing it down.. For other types, we're the code path + * which created the fd in the first place and we're thus the + * ones responsible for closing it down */ + if (type != PA_MEM_TYPE_SHARED_MEMFD) + pa_assert_se(pa_close(fd) == 0); + + m->type = type; + m->id = id; + m->size = (size_t) st.st_size; + m->do_unlink = false; + m->fd = -1; + + return 0; + +fail: + /* In case of memfds, caller maintains fd ownership */ + if (fd >= 0 && type != PA_MEM_TYPE_SHARED_MEMFD) + pa_close(fd); + +#endif /* defined(HAVE_SHM_OPEN) || defined(HAVE_MEMFD) */ + + return -1; +} + +/* Caller owns passed @memfd_fd and must close it down when appropriate. */ +int pa_shm_attach(pa_shm *m, pa_mem_type_t type, unsigned id, int memfd_fd, bool writable) { + return shm_attach(m, type, id, memfd_fd, writable, false); +} + +int pa_shm_cleanup(void) { + +#ifdef HAVE_SHM_OPEN +#ifdef SHM_PATH + DIR *d; + struct dirent *de; + + if (!(d = opendir(SHM_PATH))) { + pa_log_warn("Failed to read "SHM_PATH": %s", pa_cstrerror(errno)); + return -1; + } + + while ((de = readdir(d))) { + pa_shm seg; + unsigned id; + pid_t pid; + char fn[128]; + struct shm_marker *m; + +#if defined(__sun) + if (strncmp(de->d_name, ".SHMDpulse-shm-", SHM_ID_LEN)) +#else + if (strncmp(de->d_name, "pulse-shm-", SHM_ID_LEN)) +#endif + continue; + + if (pa_atou(de->d_name + SHM_ID_LEN, &id) < 0) + continue; + + if (shm_attach(&seg, PA_MEM_TYPE_SHARED_POSIX, id, -1, false, true) < 0) + continue; + + if (seg.size < shm_marker_size(seg.type)) { + pa_shm_free(&seg); + continue; + } + + m = (struct shm_marker*) ((uint8_t*) seg.ptr + seg.size - shm_marker_size(seg.type)); + + if (pa_atomic_load(&m->marker) != SHM_MARKER) { + pa_shm_free(&seg); + continue; + } + + if (!(pid = (pid_t) pa_atomic_load(&m->pid))) { + pa_shm_free(&seg); + continue; + } + + if (kill(pid, 0) == 0 || errno != ESRCH) { + pa_shm_free(&seg); + continue; + } + + pa_shm_free(&seg); + + /* Ok, the owner of this shms segment is dead, so, let's remove the segment */ + segment_name(fn, sizeof(fn), id); + + if (shm_unlink(fn) < 0 && errno != EACCES && errno != ENOENT) + pa_log_warn("Failed to remove SHM segment %s: %s", fn, pa_cstrerror(errno)); + } + + closedir(d); +#endif /* SHM_PATH */ +#endif /* HAVE_SHM_OPEN */ + + return 0; +} diff --git a/src/pulsecore/shm.h b/src/pulsecore/shm.h new file mode 100644 index 0000000..67a2114 --- /dev/null +++ b/src/pulsecore/shm.h @@ -0,0 +1,61 @@ +#ifndef foopulseshmhfoo +#define foopulseshmhfoo + +/*** + 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/types.h> + +#include <pulsecore/macro.h> +#include <pulsecore/mem.h> + +typedef struct pa_shm { + pa_mem_type_t type; + unsigned id; + void *ptr; + size_t size; + + /* Only for type = PA_MEM_TYPE_SHARED_POSIX */ + bool do_unlink:1; + + /* Only for type = PA_MEM_TYPE_SHARED_MEMFD + * + * To avoid fd leaks, we keep this fd open only until we pass it + * to the other PA endpoint over unix domain socket. + * + * When we don't have ownership for the memfd fd in question (e.g. + * pa_shm_attach()), or the file descriptor has now been closed, + * this is set to -1. + * + * For the special case of a global mempool, we keep this fd + * always open. Check comments on top of pa_mempool_new() for + * rationale. */ + int fd; +} pa_shm; + +int pa_shm_create_rw(pa_shm *m, pa_mem_type_t type, size_t size, mode_t mode); +int pa_shm_attach(pa_shm *m, pa_mem_type_t type, unsigned id, int memfd_fd, bool writable); + +void pa_shm_punch(pa_shm *m, size_t offset, size_t size); + +void pa_shm_free(pa_shm *m); + +int pa_shm_cleanup(void); + +#endif diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c new file mode 100644 index 0000000..12e3c8b --- /dev/null +++ b/src/pulsecore/sink-input.c @@ -0,0 +1,2449 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> + +#include <pulse/utf8.h> +#include <pulse/xmalloc.h> +#include <pulse/util.h> +#include <pulse/internal.h> + +#include <pulsecore/core-format.h> +#include <pulsecore/mix.h> +#include <pulsecore/stream-util.h> +#include <pulsecore/core-subscribe.h> +#include <pulsecore/log.h> +#include <pulsecore/play-memblockq.h> +#include <pulsecore/namereg.h> +#include <pulsecore/core-util.h> + +#include "sink-input.h" + +/* #define SINK_INPUT_DEBUG */ + +#define MEMBLOCKQ_MAXLENGTH (32*1024*1024) +#define CONVERT_BUFFER_LENGTH (pa_page_size()) + +PA_DEFINE_PUBLIC_CLASS(pa_sink_input, pa_msgobject); + +struct volume_factor_entry { + char *key; + pa_cvolume volume; +}; + +static struct volume_factor_entry *volume_factor_entry_new(const char *key, const pa_cvolume *volume) { + struct volume_factor_entry *entry; + + pa_assert(key); + pa_assert(volume); + + entry = pa_xnew(struct volume_factor_entry, 1); + entry->key = pa_xstrdup(key); + + entry->volume = *volume; + + return entry; +} + +static void volume_factor_entry_free(struct volume_factor_entry *volume_entry) { + pa_assert(volume_entry); + + pa_xfree(volume_entry->key); + pa_xfree(volume_entry); +} + +static void volume_factor_from_hashmap(pa_cvolume *v, pa_hashmap *items, uint8_t channels) { + struct volume_factor_entry *entry; + void *state = NULL; + + pa_cvolume_reset(v, channels); + PA_HASHMAP_FOREACH(entry, items, state) + pa_sw_cvolume_multiply(v, v, &entry->volume); +} + +static void sink_input_free(pa_object *o); +static void set_real_ratio(pa_sink_input *i, const pa_cvolume *v); + +static int check_passthrough_connection(bool passthrough, pa_sink *dest) { + if (pa_sink_is_passthrough(dest)) { + pa_log_warn("Sink is already connected to PASSTHROUGH input"); + return -PA_ERR_BUSY; + } + + /* If current input(s) exist, check new input is not PASSTHROUGH */ + if (pa_idxset_size(dest->inputs) > 0 && passthrough) { + pa_log_warn("Sink is already connected, cannot accept new PASSTHROUGH INPUT"); + return -PA_ERR_BUSY; + } + + return PA_OK; +} + +pa_sink_input_new_data* pa_sink_input_new_data_init(pa_sink_input_new_data *data) { + pa_assert(data); + + pa_zero(*data); + data->resample_method = PA_RESAMPLER_INVALID; + data->proplist = pa_proplist_new(); + data->volume_writable = true; + + data->volume_factor_items = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, + (pa_free_cb_t) volume_factor_entry_free); + data->volume_factor_sink_items = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, + (pa_free_cb_t) volume_factor_entry_free); + + return data; +} + +void pa_sink_input_new_data_set_sample_spec(pa_sink_input_new_data *data, const pa_sample_spec *spec) { + pa_assert(data); + + if ((data->sample_spec_is_set = !!spec)) + data->sample_spec = *spec; +} + +void pa_sink_input_new_data_set_channel_map(pa_sink_input_new_data *data, const pa_channel_map *map) { + pa_assert(data); + + if ((data->channel_map_is_set = !!map)) + data->channel_map = *map; +} + +bool pa_sink_input_new_data_is_passthrough(pa_sink_input_new_data *data) { + pa_assert(data); + + if (PA_LIKELY(data->format) && PA_UNLIKELY(!pa_format_info_is_pcm(data->format))) + return true; + + if (PA_UNLIKELY(data->flags & PA_SINK_INPUT_PASSTHROUGH)) + return true; + + return false; +} + +void pa_sink_input_new_data_set_volume(pa_sink_input_new_data *data, const pa_cvolume *volume) { + pa_assert(data); + pa_assert(data->volume_writable); + + if ((data->volume_is_set = !!volume)) + data->volume = *volume; +} + +void pa_sink_input_new_data_add_volume_factor(pa_sink_input_new_data *data, const char *key, const pa_cvolume *volume_factor) { + struct volume_factor_entry *v; + + pa_assert(data); + pa_assert(key); + pa_assert(volume_factor); + + v = volume_factor_entry_new(key, volume_factor); + pa_assert_se(pa_hashmap_put(data->volume_factor_items, v->key, v) >= 0); +} + +void pa_sink_input_new_data_add_volume_factor_sink(pa_sink_input_new_data *data, const char *key, const pa_cvolume *volume_factor) { + struct volume_factor_entry *v; + + pa_assert(data); + pa_assert(key); + pa_assert(volume_factor); + + v = volume_factor_entry_new(key, volume_factor); + pa_assert_se(pa_hashmap_put(data->volume_factor_sink_items, v->key, v) >= 0); +} + +void pa_sink_input_new_data_set_muted(pa_sink_input_new_data *data, bool mute) { + pa_assert(data); + + data->muted_is_set = true; + data->muted = mute; +} + +bool pa_sink_input_new_data_set_sink(pa_sink_input_new_data *data, pa_sink *s, bool save, bool requested_by_application) { + bool ret = true; + pa_idxset *formats = NULL; + + pa_assert(data); + pa_assert(s); + + if (!data->req_formats) { + /* We're not working with the extended API */ + data->sink = s; + if (save) { + pa_xfree(data->preferred_sink); + data->preferred_sink = pa_xstrdup(s->name); + } + data->sink_requested_by_application = requested_by_application; + } else { + /* Extended API: let's see if this sink supports the formats the client can provide */ + formats = pa_sink_check_formats(s, data->req_formats); + + if (formats && !pa_idxset_isempty(formats)) { + /* Sink supports at least one of the requested formats */ + data->sink = s; + if (save) { + pa_xfree(data->preferred_sink); + data->preferred_sink = pa_xstrdup(s->name); + } + data->sink_requested_by_application = requested_by_application; + if (data->nego_formats) + pa_idxset_free(data->nego_formats, (pa_free_cb_t) pa_format_info_free); + data->nego_formats = formats; + } else { + /* Sink doesn't support any of the formats requested by the client */ + if (formats) + pa_idxset_free(formats, (pa_free_cb_t) pa_format_info_free); + ret = false; + } + } + + return ret; +} + +bool pa_sink_input_new_data_set_formats(pa_sink_input_new_data *data, pa_idxset *formats) { + pa_assert(data); + pa_assert(formats); + + if (data->req_formats) + pa_idxset_free(data->req_formats, (pa_free_cb_t) pa_format_info_free); + + data->req_formats = formats; + + if (data->sink) { + /* Trigger format negotiation */ + return pa_sink_input_new_data_set_sink(data, data->sink, (data->preferred_sink != NULL), data->sink_requested_by_application); + } + + return true; +} + +void pa_sink_input_new_data_done(pa_sink_input_new_data *data) { + pa_assert(data); + + if (data->req_formats) + pa_idxset_free(data->req_formats, (pa_free_cb_t) pa_format_info_free); + + if (data->nego_formats) + pa_idxset_free(data->nego_formats, (pa_free_cb_t) pa_format_info_free); + + if (data->format) + pa_format_info_free(data->format); + + if (data->volume_factor_items) + pa_hashmap_free(data->volume_factor_items); + + if (data->volume_factor_sink_items) + pa_hashmap_free(data->volume_factor_sink_items); + + if (data->preferred_sink) + pa_xfree(data->preferred_sink); + + pa_proplist_free(data->proplist); +} + +/* Called from main context */ +static void reset_callbacks(pa_sink_input *i) { + pa_assert(i); + + i->pop = NULL; + i->process_underrun = NULL; + i->process_rewind = NULL; + i->update_max_rewind = NULL; + i->update_max_request = NULL; + i->update_sink_requested_latency = NULL; + i->update_sink_latency_range = NULL; + i->update_sink_fixed_latency = NULL; + i->attach = NULL; + i->detach = NULL; + i->suspend = NULL; + i->suspend_within_thread = NULL; + i->moving = NULL; + i->kill = NULL; + i->get_latency = NULL; + i->state_change = NULL; + i->may_move_to = NULL; + i->send_event = NULL; + i->volume_changed = NULL; + i->mute_changed = NULL; +} + +/* Called from main context */ +int pa_sink_input_new( + pa_sink_input **_i, + pa_core *core, + pa_sink_input_new_data *data) { + + pa_sink_input *i; + pa_resampler *resampler = NULL; + char st[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX], fmt[PA_FORMAT_INFO_SNPRINT_MAX]; + pa_channel_map volume_map; + int r; + char *pt; + char *memblockq_name; + + pa_assert(_i); + pa_assert(core); + pa_assert(data); + pa_assert_ctl_context(); + + if (data->client) + pa_proplist_update(data->proplist, PA_UPDATE_MERGE, data->client->proplist); + + if (data->origin_sink && (data->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)) + data->volume_writable = false; + + if (!data->req_formats) { + /* From this point on, we want to work only with formats, and get back + * to using the sample spec and channel map after all decisions w.r.t. + * routing are complete. */ + pa_format_info *f; + pa_idxset *formats; + + f = pa_format_info_from_sample_spec2(&data->sample_spec, data->channel_map_is_set ? &data->channel_map : NULL, + !(data->flags & PA_SINK_INPUT_FIX_FORMAT), + !(data->flags & PA_SINK_INPUT_FIX_RATE), + !(data->flags & PA_SINK_INPUT_FIX_CHANNELS)); + if (!f) + return -PA_ERR_INVALID; + + formats = pa_idxset_new(NULL, NULL); + pa_idxset_put(formats, f, NULL); + pa_sink_input_new_data_set_formats(data, formats); + } + + if ((r = pa_hook_fire(&core->hooks[PA_CORE_HOOK_SINK_INPUT_NEW], data)) < 0) + return r; + + pa_return_val_if_fail(!data->driver || pa_utf8_valid(data->driver), -PA_ERR_INVALID); + + if (!data->sink) { + pa_sink *sink = pa_namereg_get(core, NULL, PA_NAMEREG_SINK); + pa_return_val_if_fail(sink, -PA_ERR_NOENTITY); + pa_sink_input_new_data_set_sink(data, sink, false, false); + } + + /* If something didn't pick a format for us, pick the top-most format since + * we assume this is sorted in priority order */ + if (!data->format && data->nego_formats && !pa_idxset_isempty(data->nego_formats)) + data->format = pa_format_info_copy(pa_idxset_first(data->nego_formats, NULL)); + + if (PA_LIKELY(data->format)) { + /* We know that data->sink is set, because data->format has been set. + * data->format is set after a successful format negotiation, and that + * can't happen before data->sink has been set. */ + pa_assert(data->sink); + + pa_log_debug("Negotiated format: %s", pa_format_info_snprint(fmt, sizeof(fmt), data->format)); + } else { + pa_format_info *format; + uint32_t idx; + + pa_log_info("Sink does not support any requested format:"); + PA_IDXSET_FOREACH(format, data->req_formats, idx) + pa_log_info(" -- %s", pa_format_info_snprint(fmt, sizeof(fmt), format)); + + return -PA_ERR_NOTSUPPORTED; + } + + pa_return_val_if_fail(PA_SINK_IS_LINKED(data->sink->state), -PA_ERR_BADSTATE); + pa_return_val_if_fail(!data->sync_base || (data->sync_base->sink == data->sink + && data->sync_base->state == PA_SINK_INPUT_CORKED), + -PA_ERR_INVALID); + + /* Routing is done. We have a sink and a format. */ + + if (data->volume_is_set && !pa_sink_input_new_data_is_passthrough(data)) { + /* If volume is set, we need to save the original data->channel_map, + * so that we can remap the volume from the original channel map to the + * final channel map of the stream in case data->channel_map gets + * modified in pa_format_info_to_sample_spec2(). */ + r = pa_stream_get_volume_channel_map(&data->volume, data->channel_map_is_set ? &data->channel_map : NULL, data->format, &volume_map); + if (r < 0) + return r; + } else { + /* Initialize volume_map to invalid state. We check the state later to + * determine if volume remapping is needed. */ + pa_channel_map_init(&volume_map); + } + + /* Now populate the sample spec and channel map according to the final + * format that we've negotiated */ + r = pa_format_info_to_sample_spec2(data->format, &data->sample_spec, &data->channel_map, &data->sink->sample_spec, + &data->sink->channel_map); + if (r < 0) + return r; + + r = check_passthrough_connection(pa_sink_input_new_data_is_passthrough(data), data->sink); + if (r != PA_OK) + return r; + + /* Don't restore (or save) stream volume for passthrough streams and + * prevent attenuation/gain */ + if (pa_sink_input_new_data_is_passthrough(data)) { + data->volume_is_set = true; + pa_cvolume_reset(&data->volume, data->sample_spec.channels); + data->volume_is_absolute = true; + data->save_volume = false; + } + + if (!data->volume_is_set) { + pa_cvolume_reset(&data->volume, data->sample_spec.channels); + data->volume_is_absolute = false; + data->save_volume = false; + } + + if (!data->volume_writable) + data->save_volume = false; + + if (pa_channel_map_valid(&volume_map)) + /* The original volume channel map may be different than the final + * stream channel map, so remapping may be needed. */ + pa_cvolume_remap(&data->volume, &volume_map, &data->channel_map); + + if (!data->muted_is_set) + data->muted = false; + + if (!(data->flags & PA_SINK_INPUT_VARIABLE_RATE) && + !pa_sample_spec_equal(&data->sample_spec, &data->sink->sample_spec)) { + /* try to change sink format and rate. This is done before the FIXATE hook since + module-suspend-on-idle can resume a sink */ + + pa_log_info("Trying to change sample spec"); + pa_sink_reconfigure(data->sink, &data->sample_spec, pa_sink_input_new_data_is_passthrough(data)); + } + + if (pa_sink_input_new_data_is_passthrough(data) && + !pa_sample_spec_equal(&data->sample_spec, &data->sink->sample_spec)) { + /* rate update failed, or other parts of sample spec didn't match */ + + pa_log_debug("Could not update sink sample spec to match passthrough stream"); + return -PA_ERR_NOTSUPPORTED; + } + + if (data->resample_method == PA_RESAMPLER_INVALID) + data->resample_method = core->resample_method; + + pa_return_val_if_fail(data->resample_method < PA_RESAMPLER_MAX, -PA_ERR_INVALID); + + if ((r = pa_hook_fire(&core->hooks[PA_CORE_HOOK_SINK_INPUT_FIXATE], data)) < 0) + return r; + + if ((data->flags & PA_SINK_INPUT_NO_CREATE_ON_SUSPEND) && + data->sink->state == PA_SINK_SUSPENDED) { + pa_log_warn("Failed to create sink input: sink is suspended."); + return -PA_ERR_BADSTATE; + } + + if (pa_idxset_size(data->sink->inputs) >= PA_MAX_INPUTS_PER_SINK) { + pa_log_warn("Failed to create sink input: too many inputs per sink."); + return -PA_ERR_TOOLARGE; + } + + if ((data->flags & PA_SINK_INPUT_VARIABLE_RATE) || + !pa_sample_spec_equal(&data->sample_spec, &data->sink->sample_spec) || + !pa_channel_map_equal(&data->channel_map, &data->sink->channel_map)) { + + /* Note: for passthrough content we need to adjust the output rate to that of the current sink-input */ + if (!pa_sink_input_new_data_is_passthrough(data)) /* no resampler for passthrough content */ + if (!(resampler = pa_resampler_new( + core->mempool, + &data->sample_spec, &data->channel_map, + &data->sink->sample_spec, &data->sink->channel_map, + core->lfe_crossover_freq, + data->resample_method, + ((data->flags & PA_SINK_INPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0) | + ((data->flags & PA_SINK_INPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) | + (core->disable_remixing || (data->flags & PA_SINK_INPUT_NO_REMIX) ? PA_RESAMPLER_NO_REMIX : 0) | + (core->remixing_use_all_sink_channels ? 0 : PA_RESAMPLER_NO_FILL_SINK) | + (core->remixing_produce_lfe ? PA_RESAMPLER_PRODUCE_LFE : 0) | + (core->remixing_consume_lfe ? PA_RESAMPLER_CONSUME_LFE : 0)))) { + pa_log_warn("Unsupported resampling operation."); + return -PA_ERR_NOTSUPPORTED; + } + } + + i = pa_msgobject_new(pa_sink_input); + i->parent.parent.free = sink_input_free; + i->parent.process_msg = pa_sink_input_process_msg; + + i->core = core; + i->state = PA_SINK_INPUT_INIT; + i->flags = data->flags; + i->proplist = pa_proplist_copy(data->proplist); + i->driver = pa_xstrdup(pa_path_get_filename(data->driver)); + i->module = data->module; + i->sink = data->sink; + i->sink_requested_by_application = data->sink_requested_by_application; + i->origin_sink = data->origin_sink; + i->client = data->client; + + i->requested_resample_method = data->resample_method; + i->actual_resample_method = resampler ? pa_resampler_get_method(resampler) : PA_RESAMPLER_INVALID; + i->sample_spec = data->sample_spec; + i->channel_map = data->channel_map; + i->format = pa_format_info_copy(data->format); + + if (!data->volume_is_absolute && pa_sink_flat_volume_enabled(i->sink)) { + pa_cvolume remapped; + + /* When the 'absolute' bool is not set then we'll treat the volume + * as relative to the sink volume even in flat volume mode */ + remapped = data->sink->reference_volume; + pa_cvolume_remap(&remapped, &data->sink->channel_map, &data->channel_map); + pa_sw_cvolume_multiply(&i->volume, &data->volume, &remapped); + } else + i->volume = data->volume; + + i->volume_factor_items = data->volume_factor_items; + data->volume_factor_items = NULL; + volume_factor_from_hashmap(&i->volume_factor, i->volume_factor_items, i->sample_spec.channels); + + i->volume_factor_sink_items = data->volume_factor_sink_items; + data->volume_factor_sink_items = NULL; + volume_factor_from_hashmap(&i->volume_factor_sink, i->volume_factor_sink_items, i->sink->sample_spec.channels); + + i->real_ratio = i->reference_ratio = data->volume; + pa_cvolume_reset(&i->soft_volume, i->sample_spec.channels); + pa_cvolume_reset(&i->real_ratio, i->sample_spec.channels); + i->volume_writable = data->volume_writable; + i->save_volume = data->save_volume; + i->preferred_sink = pa_xstrdup(data->preferred_sink); + i->save_muted = data->save_muted; + + i->muted = data->muted; + + if (data->sync_base) { + i->sync_next = data->sync_base->sync_next; + i->sync_prev = data->sync_base; + + if (data->sync_base->sync_next) + data->sync_base->sync_next->sync_prev = i; + data->sync_base->sync_next = i; + } else + i->sync_next = i->sync_prev = NULL; + + i->direct_outputs = pa_idxset_new(NULL, NULL); + + reset_callbacks(i); + i->userdata = NULL; + + i->thread_info.state = i->state; + i->thread_info.attached = false; + i->thread_info.sample_spec = i->sample_spec; + i->thread_info.resampler = resampler; + i->thread_info.soft_volume = i->soft_volume; + i->thread_info.muted = i->muted; + i->thread_info.requested_sink_latency = (pa_usec_t) -1; + i->thread_info.rewrite_nbytes = 0; + i->thread_info.rewrite_flush = false; + i->thread_info.dont_rewind_render = false; + i->thread_info.underrun_for = (uint64_t) -1; + i->thread_info.underrun_for_sink = 0; + i->thread_info.playing_for = 0; + i->thread_info.direct_outputs = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + + pa_assert_se(pa_idxset_put(core->sink_inputs, i, &i->index) == 0); + pa_assert_se(pa_idxset_put(i->sink->inputs, pa_sink_input_ref(i), NULL) == 0); + + if (i->client) + pa_assert_se(pa_idxset_put(i->client->sink_inputs, i, NULL) >= 0); + + memblockq_name = pa_sprintf_malloc("sink input render_memblockq [%u]", i->index); + i->thread_info.render_memblockq = pa_memblockq_new( + memblockq_name, + 0, + MEMBLOCKQ_MAXLENGTH, + 0, + &i->sink->sample_spec, + 0, + 1, + 0, + &i->sink->silence); + pa_xfree(memblockq_name); + + pt = pa_proplist_to_string_sep(i->proplist, "\n "); + pa_log_info("Created input %u \"%s\" on %s with sample spec %s and channel map %s\n %s", + i->index, + pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_MEDIA_NAME)), + i->sink->name, + pa_sample_spec_snprint(st, sizeof(st), &i->sample_spec), + pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map), + pt); + pa_xfree(pt); + + /* Don't forget to call pa_sink_input_put! */ + + *_i = i; + return 0; +} + +/* Called from main context */ +static void update_n_corked(pa_sink_input *i, pa_sink_input_state_t state) { + pa_assert(i); + pa_assert_ctl_context(); + + if (!i->sink) + return; + + if (i->state == PA_SINK_INPUT_CORKED && state != PA_SINK_INPUT_CORKED) + pa_assert_se(i->sink->n_corked -- >= 1); + else if (i->state != PA_SINK_INPUT_CORKED && state == PA_SINK_INPUT_CORKED) + i->sink->n_corked++; +} + +/* Called from main context */ +static void sink_input_set_state(pa_sink_input *i, pa_sink_input_state_t state) { + pa_sink_input *ssync; + pa_assert(i); + pa_assert_ctl_context(); + + if (i->state == state) + return; + + if (i->sink) { + if (i->state == PA_SINK_INPUT_CORKED && state == PA_SINK_INPUT_RUNNING && pa_sink_used_by(i->sink) == 0 && + !pa_sample_spec_equal(&i->sample_spec, &i->sink->sample_spec)) { + /* We were uncorked and the sink was not playing anything -- let's try + * to update the sample format and rate to avoid resampling */ + pa_sink_reconfigure(i->sink, &i->sample_spec, pa_sink_input_is_passthrough(i)); + } + + pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_STATE, PA_UINT_TO_PTR(state), 0, NULL) == 0); + } else { + /* If the sink is not valid, pa_sink_input_set_state_within_thread() must be called directly */ + + pa_sink_input_set_state_within_thread(i, state); + + for (ssync = i->thread_info.sync_prev; ssync; ssync = ssync->thread_info.sync_prev) + pa_sink_input_set_state_within_thread(ssync, state); + + for (ssync = i->thread_info.sync_next; ssync; ssync = ssync->thread_info.sync_next) + pa_sink_input_set_state_within_thread(ssync, state); + } + + update_n_corked(i, state); + i->state = state; + + for (ssync = i->sync_prev; ssync; ssync = ssync->sync_prev) { + update_n_corked(ssync, state); + ssync->state = state; + } + for (ssync = i->sync_next; ssync; ssync = ssync->sync_next) { + update_n_corked(ssync, state); + ssync->state = state; + } + + if (state != PA_SINK_INPUT_UNLINKED) { + pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_STATE_CHANGED], i); + + for (ssync = i->sync_prev; ssync; ssync = ssync->sync_prev) + pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_STATE_CHANGED], ssync); + + for (ssync = i->sync_next; ssync; ssync = ssync->sync_next) + pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_STATE_CHANGED], ssync); + + if (PA_SINK_INPUT_IS_LINKED(state)) + pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index); + } + + if (i->sink) + pa_sink_update_status(i->sink); +} + +/* Called from main context */ +void pa_sink_input_unlink(pa_sink_input *i) { + bool linked; + pa_source_output *o, PA_UNUSED *p = NULL; + + pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); + + /* See pa_sink_unlink() for a couple of comments how this function + * works */ + + pa_sink_input_ref(i); + + linked = PA_SINK_INPUT_IS_LINKED(i->state); + + if (linked) + pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_UNLINK], i); + + if (i->sync_prev) + i->sync_prev->sync_next = i->sync_next; + if (i->sync_next) + i->sync_next->sync_prev = i->sync_prev; + + i->sync_prev = i->sync_next = NULL; + + pa_idxset_remove_by_data(i->core->sink_inputs, i, NULL); + + if (i->sink) + if (pa_idxset_remove_by_data(i->sink->inputs, i, NULL)) + pa_sink_input_unref(i); + + if (i->client) + pa_idxset_remove_by_data(i->client->sink_inputs, i, NULL); + + while ((o = pa_idxset_first(i->direct_outputs, NULL))) { + pa_assert(o != p); + pa_source_output_kill(o); + p = o; + } + + update_n_corked(i, PA_SINK_INPUT_UNLINKED); + i->state = PA_SINK_INPUT_UNLINKED; + + if (linked && i->sink) { + if (pa_sink_input_is_passthrough(i)) + pa_sink_leave_passthrough(i->sink); + + /* We might need to update the sink's volume if we are in flat volume mode. */ + if (pa_sink_flat_volume_enabled(i->sink)) + pa_sink_set_volume(i->sink, NULL, false, false); + + if (i->sink->asyncmsgq) + pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_REMOVE_INPUT, i, 0, NULL) == 0); + } + + reset_callbacks(i); + + if (i->sink) { + if (PA_SINK_IS_LINKED(i->sink->state)) + pa_sink_update_status(i->sink); + + i->sink = NULL; + } + + if (linked) { + pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_REMOVE, i->index); + pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_UNLINK_POST], i); + } + + pa_core_maybe_vacuum(i->core); + + pa_sink_input_unref(i); +} + +/* Called from main context */ +static void sink_input_free(pa_object *o) { + pa_sink_input* i = PA_SINK_INPUT(o); + + pa_assert(i); + pa_assert_ctl_context(); + pa_assert(pa_sink_input_refcnt(i) == 0); + pa_assert(!PA_SINK_INPUT_IS_LINKED(i->state)); + + pa_log_info("Freeing input %u \"%s\"", i->index, + i->proplist ? pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_MEDIA_NAME)) : ""); + + /* Side note: this function must be able to destruct properly any + * kind of sink input in any state, even those which are + * "half-moved" or are connected to sinks that have no asyncmsgq + * and are hence half-destructed themselves! */ + + if (i->thread_info.render_memblockq) + pa_memblockq_free(i->thread_info.render_memblockq); + + if (i->thread_info.resampler) + pa_resampler_free(i->thread_info.resampler); + + if (i->format) + pa_format_info_free(i->format); + + if (i->proplist) + pa_proplist_free(i->proplist); + + if (i->direct_outputs) + pa_idxset_free(i->direct_outputs, NULL); + + if (i->thread_info.direct_outputs) + pa_hashmap_free(i->thread_info.direct_outputs); + + if (i->volume_factor_items) + pa_hashmap_free(i->volume_factor_items); + + if (i->volume_factor_sink_items) + pa_hashmap_free(i->volume_factor_sink_items); + + if (i->preferred_sink) + pa_xfree(i->preferred_sink); + + pa_xfree(i->driver); + pa_xfree(i); +} + +/* Called from main context */ +void pa_sink_input_put(pa_sink_input *i) { + pa_sink_input_state_t state; + + pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); + + pa_assert(i->state == PA_SINK_INPUT_INIT); + + /* The following fields must be initialized properly */ + pa_assert(i->pop); + pa_assert(i->process_rewind); + pa_assert(i->kill); + + state = i->flags & PA_SINK_INPUT_START_CORKED ? PA_SINK_INPUT_CORKED : PA_SINK_INPUT_RUNNING; + + update_n_corked(i, state); + i->state = state; + + /* We might need to update the sink's volume if we are in flat volume mode. */ + if (pa_sink_flat_volume_enabled(i->sink)) + pa_sink_set_volume(i->sink, NULL, false, i->save_volume); + else { + if (i->origin_sink && (i->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)) { + pa_assert(pa_cvolume_is_norm(&i->volume)); + pa_assert(pa_cvolume_is_norm(&i->reference_ratio)); + } + + set_real_ratio(i, &i->volume); + } + + if (pa_sink_input_is_passthrough(i)) + pa_sink_enter_passthrough(i->sink); + + i->thread_info.soft_volume = i->soft_volume; + i->thread_info.muted = i->muted; + + pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_ADD_INPUT, i, 0, NULL) == 0); + + pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW, i->index); + pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_PUT], i); + + pa_sink_update_status(i->sink); +} + +/* Called from main context */ +void pa_sink_input_kill(pa_sink_input*i) { + pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); + pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); + + i->kill(i); +} + +/* Called from main context */ +pa_usec_t pa_sink_input_get_latency(pa_sink_input *i, pa_usec_t *sink_latency) { + pa_usec_t r[2] = { 0, 0 }; + + pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); + pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); + + pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_GET_LATENCY, r, 0, NULL) == 0); + + if (i->get_latency) + r[0] += i->get_latency(i); + + if (sink_latency) + *sink_latency = r[1]; + + return r[0]; +} + +/* Called from thread context */ +void pa_sink_input_peek(pa_sink_input *i, size_t slength /* in sink bytes */, pa_memchunk *chunk, pa_cvolume *volume) { + bool do_volume_adj_here, need_volume_factor_sink; + bool volume_is_norm; + size_t block_size_max_sink, block_size_max_sink_input; + size_t ilength; + size_t ilength_full; + + pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); + pa_assert(PA_SINK_INPUT_IS_LINKED(i->thread_info.state)); + pa_assert(pa_frame_aligned(slength, &i->sink->sample_spec)); + pa_assert(chunk); + pa_assert(volume); + +#ifdef SINK_INPUT_DEBUG + pa_log_debug("peek"); +#endif + + block_size_max_sink_input = i->thread_info.resampler ? + pa_resampler_max_block_size(i->thread_info.resampler) : + pa_frame_align(pa_mempool_block_size_max(i->core->mempool), &i->sample_spec); + + block_size_max_sink = pa_frame_align(pa_mempool_block_size_max(i->core->mempool), &i->sink->sample_spec); + + /* Default buffer size */ + if (slength <= 0) + slength = pa_frame_align(CONVERT_BUFFER_LENGTH, &i->sink->sample_spec); + + if (slength > block_size_max_sink) + slength = block_size_max_sink; + + if (i->thread_info.resampler) { + ilength = pa_resampler_request(i->thread_info.resampler, slength); + + if (ilength <= 0) + ilength = pa_frame_align(CONVERT_BUFFER_LENGTH, &i->sample_spec); + } else + ilength = slength; + + /* Length corresponding to slength (without limiting to + * block_size_max_sink_input). */ + ilength_full = ilength; + + if (ilength > block_size_max_sink_input) + ilength = block_size_max_sink_input; + + /* If the channel maps of the sink and this stream differ, we need + * to adjust the volume *before* we resample. Otherwise we can do + * it after and leave it for the sink code */ + + do_volume_adj_here = !pa_channel_map_equal(&i->channel_map, &i->sink->channel_map); + volume_is_norm = pa_cvolume_is_norm(&i->thread_info.soft_volume) && !i->thread_info.muted; + need_volume_factor_sink = !pa_cvolume_is_norm(&i->volume_factor_sink); + + while (!pa_memblockq_is_readable(i->thread_info.render_memblockq)) { + pa_memchunk tchunk; + + /* There's nothing in our render queue. We need to fill it up + * with data from the implementor. */ + + if (i->thread_info.state == PA_SINK_INPUT_CORKED || + i->pop(i, ilength, &tchunk) < 0) { + + /* OK, we're corked or the implementor didn't give us any + * data, so let's just hand out silence */ + + pa_memblockq_seek(i->thread_info.render_memblockq, (int64_t) slength, PA_SEEK_RELATIVE, true); + i->thread_info.playing_for = 0; + if (i->thread_info.underrun_for != (uint64_t) -1) { + i->thread_info.underrun_for += ilength_full; + i->thread_info.underrun_for_sink += slength; + } + break; + } + + pa_assert(tchunk.length > 0); + pa_assert(tchunk.memblock); + + i->thread_info.underrun_for = 0; + i->thread_info.underrun_for_sink = 0; + i->thread_info.playing_for += tchunk.length; + + while (tchunk.length > 0) { + pa_memchunk wchunk; + bool nvfs = need_volume_factor_sink; + + wchunk = tchunk; + pa_memblock_ref(wchunk.memblock); + + if (wchunk.length > block_size_max_sink_input) + wchunk.length = block_size_max_sink_input; + + /* It might be necessary to adjust the volume here */ + if (do_volume_adj_here && !volume_is_norm) { + pa_memchunk_make_writable(&wchunk, 0); + + if (i->thread_info.muted) { + pa_silence_memchunk(&wchunk, &i->thread_info.sample_spec); + nvfs = false; + + } else if (!i->thread_info.resampler && nvfs) { + pa_cvolume v; + + /* If we don't need a resampler we can merge the + * post and the pre volume adjustment into one */ + + pa_sw_cvolume_multiply(&v, &i->thread_info.soft_volume, &i->volume_factor_sink); + pa_volume_memchunk(&wchunk, &i->thread_info.sample_spec, &v); + nvfs = false; + + } else + pa_volume_memchunk(&wchunk, &i->thread_info.sample_spec, &i->thread_info.soft_volume); + } + + if (!i->thread_info.resampler) { + + if (nvfs) { + pa_memchunk_make_writable(&wchunk, 0); + pa_volume_memchunk(&wchunk, &i->sink->sample_spec, &i->volume_factor_sink); + } + + pa_memblockq_push_align(i->thread_info.render_memblockq, &wchunk); + } else { + pa_memchunk rchunk; + pa_resampler_run(i->thread_info.resampler, &wchunk, &rchunk); + +#ifdef SINK_INPUT_DEBUG + pa_log_debug("pushing %lu", (unsigned long) rchunk.length); +#endif + + if (rchunk.memblock) { + + if (nvfs) { + pa_memchunk_make_writable(&rchunk, 0); + pa_volume_memchunk(&rchunk, &i->sink->sample_spec, &i->volume_factor_sink); + } + + pa_memblockq_push_align(i->thread_info.render_memblockq, &rchunk); + pa_memblock_unref(rchunk.memblock); + } + } + + pa_memblock_unref(wchunk.memblock); + + tchunk.index += wchunk.length; + tchunk.length -= wchunk.length; + } + + pa_memblock_unref(tchunk.memblock); + } + + pa_assert_se(pa_memblockq_peek(i->thread_info.render_memblockq, chunk) >= 0); + + pa_assert(chunk->length > 0); + pa_assert(chunk->memblock); + +#ifdef SINK_INPUT_DEBUG + pa_log_debug("peeking %lu", (unsigned long) chunk->length); +#endif + + if (chunk->length > block_size_max_sink) + chunk->length = block_size_max_sink; + + /* Let's see if we had to apply the volume adjustment ourselves, + * or if this can be done by the sink for us */ + + if (do_volume_adj_here) + /* We had different channel maps, so we already did the adjustment */ + pa_cvolume_reset(volume, i->sink->sample_spec.channels); + else if (i->thread_info.muted) + /* We've both the same channel map, so let's have the sink do the adjustment for us*/ + pa_cvolume_mute(volume, i->sink->sample_spec.channels); + else + *volume = i->thread_info.soft_volume; +} + +/* Called from thread context */ +void pa_sink_input_drop(pa_sink_input *i, size_t nbytes /* in sink sample spec */) { + + pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); + pa_assert(PA_SINK_INPUT_IS_LINKED(i->thread_info.state)); + pa_assert(pa_frame_aligned(nbytes, &i->sink->sample_spec)); + pa_assert(nbytes > 0); + +#ifdef SINK_INPUT_DEBUG + pa_log_debug("dropping %lu", (unsigned long) nbytes); +#endif + + pa_memblockq_drop(i->thread_info.render_memblockq, nbytes); +} + +/* Called from thread context */ +bool pa_sink_input_process_underrun(pa_sink_input *i) { + pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); + + if (pa_memblockq_is_readable(i->thread_info.render_memblockq)) + return false; + + if (i->process_underrun && i->process_underrun(i)) { + /* All valid data has been played back, so we can empty this queue. */ + pa_memblockq_silence(i->thread_info.render_memblockq); + return true; + } + return false; +} + +/* Called from thread context */ +void pa_sink_input_process_rewind(pa_sink_input *i, size_t nbytes /* in sink sample spec */) { + size_t lbq; + bool called = false; + + pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); + pa_assert(PA_SINK_INPUT_IS_LINKED(i->thread_info.state)); + pa_assert(pa_frame_aligned(nbytes, &i->sink->sample_spec)); + +#ifdef SINK_INPUT_DEBUG + pa_log_debug("rewind(%lu, %lu)", (unsigned long) nbytes, (unsigned long) i->thread_info.rewrite_nbytes); +#endif + + lbq = pa_memblockq_get_length(i->thread_info.render_memblockq); + + if (nbytes > 0 && !i->thread_info.dont_rewind_render) { + pa_log_debug("Have to rewind %lu bytes on render memblockq.", (unsigned long) nbytes); + pa_memblockq_rewind(i->thread_info.render_memblockq, nbytes); + } + + if (i->thread_info.rewrite_nbytes == (size_t) -1) { + + /* We were asked to drop all buffered data, and rerequest new + * data from implementor the next time peek() is called */ + + pa_memblockq_flush_write(i->thread_info.render_memblockq, true); + + } else if (i->thread_info.rewrite_nbytes > 0) { + size_t max_rewrite, amount; + + /* Calculate how much make sense to rewrite at most */ + max_rewrite = nbytes; + if (nbytes > 0) + max_rewrite += lbq; + + /* Transform into local domain */ + if (i->thread_info.resampler) + max_rewrite = pa_resampler_request(i->thread_info.resampler, max_rewrite); + + /* Calculate how much of the rewinded data should actually be rewritten */ + amount = PA_MIN(i->thread_info.rewrite_nbytes, max_rewrite); + + if (amount > 0) { + pa_log_debug("Have to rewind %lu bytes on implementor.", (unsigned long) amount); + + /* Tell the implementor */ + if (i->process_rewind) + i->process_rewind(i, amount); + called = true; + + /* Convert back to sink domain */ + if (i->thread_info.resampler) + amount = pa_resampler_result(i->thread_info.resampler, amount); + + if (amount > 0) + /* Ok, now update the write pointer */ + pa_memblockq_seek(i->thread_info.render_memblockq, - ((int64_t) amount), PA_SEEK_RELATIVE, true); + + if (i->thread_info.rewrite_flush) + pa_memblockq_silence(i->thread_info.render_memblockq); + + /* And rewind the resampler */ + if (i->thread_info.resampler) + pa_resampler_rewind(i->thread_info.resampler, amount); + } + } + + if (!called) + if (i->process_rewind) + i->process_rewind(i, 0); + + i->thread_info.rewrite_nbytes = 0; + i->thread_info.rewrite_flush = false; + i->thread_info.dont_rewind_render = false; +} + +/* Called from thread context */ +size_t pa_sink_input_get_max_rewind(pa_sink_input *i) { + pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); + + return i->thread_info.resampler ? pa_resampler_request(i->thread_info.resampler, i->sink->thread_info.max_rewind) : i->sink->thread_info.max_rewind; +} + +/* Called from thread context */ +size_t pa_sink_input_get_max_request(pa_sink_input *i) { + pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); + + /* We're not verifying the status here, to allow this to be called + * in the state change handler between _INIT and _RUNNING */ + + return i->thread_info.resampler ? pa_resampler_request(i->thread_info.resampler, i->sink->thread_info.max_request) : i->sink->thread_info.max_request; +} + +/* Called from thread context */ +void pa_sink_input_update_max_rewind(pa_sink_input *i, size_t nbytes /* in the sink's sample spec */) { + pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); + pa_assert(PA_SINK_INPUT_IS_LINKED(i->thread_info.state)); + pa_assert(pa_frame_aligned(nbytes, &i->sink->sample_spec)); + + pa_memblockq_set_maxrewind(i->thread_info.render_memblockq, nbytes); + + if (i->update_max_rewind) + i->update_max_rewind(i, i->thread_info.resampler ? pa_resampler_request(i->thread_info.resampler, nbytes) : nbytes); +} + +/* Called from thread context */ +void pa_sink_input_update_max_request(pa_sink_input *i, size_t nbytes /* in the sink's sample spec */) { + pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); + pa_assert(PA_SINK_INPUT_IS_LINKED(i->thread_info.state)); + pa_assert(pa_frame_aligned(nbytes, &i->sink->sample_spec)); + + if (i->update_max_request) + i->update_max_request(i, i->thread_info.resampler ? pa_resampler_request(i->thread_info.resampler, nbytes) : nbytes); +} + +/* Called from thread context */ +pa_usec_t pa_sink_input_set_requested_latency_within_thread(pa_sink_input *i, pa_usec_t usec) { + pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); + + if (!(i->sink->flags & PA_SINK_DYNAMIC_LATENCY)) + usec = i->sink->thread_info.fixed_latency; + + if (usec != (pa_usec_t) -1) + usec = PA_CLAMP(usec, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency); + + i->thread_info.requested_sink_latency = usec; + pa_sink_invalidate_requested_latency(i->sink, true); + + return usec; +} + +/* Called from main context */ +pa_usec_t pa_sink_input_set_requested_latency(pa_sink_input *i, pa_usec_t usec) { + pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); + + if (PA_SINK_INPUT_IS_LINKED(i->state) && i->sink) { + pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_REQUESTED_LATENCY, &usec, 0, NULL) == 0); + return usec; + } + + /* If this sink input is not realized yet or we are being moved, + * we have to touch the thread info data directly */ + + if (i->sink) { + if (!(i->sink->flags & PA_SINK_DYNAMIC_LATENCY)) + usec = pa_sink_get_fixed_latency(i->sink); + + if (usec != (pa_usec_t) -1) { + pa_usec_t min_latency, max_latency; + pa_sink_get_latency_range(i->sink, &min_latency, &max_latency); + usec = PA_CLAMP(usec, min_latency, max_latency); + } + } + + i->thread_info.requested_sink_latency = usec; + + return usec; +} + +/* Called from main context */ +pa_usec_t pa_sink_input_get_requested_latency(pa_sink_input *i) { + pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); + + if (PA_SINK_INPUT_IS_LINKED(i->state) && i->sink) { + pa_usec_t usec = 0; + pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_GET_REQUESTED_LATENCY, &usec, 0, NULL) == 0); + return usec; + } + + /* If this sink input is not realized yet or we are being moved, + * we have to touch the thread info data directly */ + + return i->thread_info.requested_sink_latency; +} + +/* Called from main context */ +void pa_sink_input_set_volume(pa_sink_input *i, const pa_cvolume *volume, bool save, bool absolute) { + pa_cvolume v; + + pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); + pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); + pa_assert(volume); + pa_assert(pa_cvolume_valid(volume)); + pa_assert(volume->channels == 1 || pa_cvolume_compatible(volume, &i->sample_spec)); + pa_assert(i->volume_writable); + + if (!absolute && pa_sink_flat_volume_enabled(i->sink)) { + v = i->sink->reference_volume; + pa_cvolume_remap(&v, &i->sink->channel_map, &i->channel_map); + + if (pa_cvolume_compatible(volume, &i->sample_spec)) + volume = pa_sw_cvolume_multiply(&v, &v, volume); + else + volume = pa_sw_cvolume_multiply_scalar(&v, &v, pa_cvolume_max(volume)); + } else { + if (!pa_cvolume_compatible(volume, &i->sample_spec)) { + v = i->volume; + volume = pa_cvolume_scale(&v, pa_cvolume_max(volume)); + } + } + + if (pa_cvolume_equal(volume, &i->volume)) { + i->save_volume = i->save_volume || save; + return; + } + + pa_sink_input_set_volume_direct(i, volume); + i->save_volume = save; + + if (pa_sink_flat_volume_enabled(i->sink)) { + /* We are in flat volume mode, so let's update all sink input + * volumes and update the flat volume of the sink */ + + pa_sink_set_volume(i->sink, NULL, true, save); + + } else { + /* OK, we are in normal volume mode. The volume only affects + * ourselves */ + set_real_ratio(i, volume); + pa_sink_input_set_reference_ratio(i, &i->volume); + + /* Copy the new soft_volume to the thread_info struct */ + pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_SOFT_VOLUME, NULL, 0, NULL) == 0); + } +} + +void pa_sink_input_add_volume_factor(pa_sink_input *i, const char *key, const pa_cvolume *volume_factor) { + struct volume_factor_entry *v; + + pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); + pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); + pa_assert(volume_factor); + pa_assert(key); + pa_assert(pa_cvolume_valid(volume_factor)); + pa_assert(volume_factor->channels == 1 || pa_cvolume_compatible(volume_factor, &i->sample_spec)); + + v = volume_factor_entry_new(key, volume_factor); + if (!pa_cvolume_compatible(volume_factor, &i->sample_spec)) + pa_cvolume_set(&v->volume, i->sample_spec.channels, volume_factor->values[0]); + + pa_assert_se(pa_hashmap_put(i->volume_factor_items, v->key, v) >= 0); + if (pa_hashmap_size(i->volume_factor_items) == 1) + i->volume_factor = v->volume; + else + pa_sw_cvolume_multiply(&i->volume_factor, &i->volume_factor, &v->volume); + + pa_sw_cvolume_multiply(&i->soft_volume, &i->real_ratio, &i->volume_factor); + + /* Copy the new soft_volume to the thread_info struct */ + pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_SOFT_VOLUME, NULL, 0, NULL) == 0); +} + +/* Returns 0 if an entry was removed and -1 if no entry for the given key was + * found. */ +int pa_sink_input_remove_volume_factor(pa_sink_input *i, const char *key) { + struct volume_factor_entry *v; + + pa_sink_input_assert_ref(i); + pa_assert(key); + pa_assert_ctl_context(); + pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); + + if (pa_hashmap_remove_and_free(i->volume_factor_items, key) < 0) + return -1; + + switch (pa_hashmap_size(i->volume_factor_items)) { + case 0: + pa_cvolume_reset(&i->volume_factor, i->sample_spec.channels); + break; + case 1: + v = pa_hashmap_first(i->volume_factor_items); + i->volume_factor = v->volume; + break; + default: + volume_factor_from_hashmap(&i->volume_factor, i->volume_factor_items, i->volume_factor.channels); + } + + pa_sw_cvolume_multiply(&i->soft_volume, &i->real_ratio, &i->volume_factor); + + /* Copy the new soft_volume to the thread_info struct */ + pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_SOFT_VOLUME, NULL, 0, NULL) == 0); + + return 0; +} + +/* Called from main context */ +static void set_real_ratio(pa_sink_input *i, const pa_cvolume *v) { + pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); + pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); + pa_assert(!v || pa_cvolume_compatible(v, &i->sample_spec)); + + /* This basically calculates: + * + * i->real_ratio := v + * i->soft_volume := i->real_ratio * i->volume_factor */ + + if (v) + i->real_ratio = *v; + else + pa_cvolume_reset(&i->real_ratio, i->sample_spec.channels); + + pa_sw_cvolume_multiply(&i->soft_volume, &i->real_ratio, &i->volume_factor); + /* We don't copy the data to the thread_info data. That's left for someone else to do */ +} + +/* Called from main or I/O context */ +bool pa_sink_input_is_passthrough(pa_sink_input *i) { + pa_sink_input_assert_ref(i); + + if (PA_UNLIKELY(!pa_format_info_is_pcm(i->format))) + return true; + + if (PA_UNLIKELY(i->flags & PA_SINK_INPUT_PASSTHROUGH)) + return true; + + return false; +} + +/* Called from main context */ +bool pa_sink_input_is_volume_readable(pa_sink_input *i) { + pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); + + return !pa_sink_input_is_passthrough(i); +} + +/* Called from main context */ +pa_cvolume *pa_sink_input_get_volume(pa_sink_input *i, pa_cvolume *volume, bool absolute) { + pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); + pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); + pa_assert(pa_sink_input_is_volume_readable(i)); + + if (absolute || !pa_sink_flat_volume_enabled(i->sink)) + *volume = i->volume; + else + *volume = i->reference_ratio; + + return volume; +} + +/* Called from main context */ +void pa_sink_input_set_mute(pa_sink_input *i, bool mute, bool save) { + bool old_mute; + + pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); + pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); + + old_mute = i->muted; + + if (mute == old_mute) { + i->save_muted |= save; + return; + } + + i->muted = mute; + pa_log_debug("The mute of sink input %u changed from %s to %s.", i->index, pa_yes_no(old_mute), pa_yes_no(mute)); + + i->save_muted = save; + + pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_SOFT_MUTE, NULL, 0, NULL) == 0); + + /* The mute status changed, let's tell people so */ + if (i->mute_changed) + i->mute_changed(i); + + pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index); + pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_MUTE_CHANGED], i); +} + +void pa_sink_input_set_property(pa_sink_input *i, const char *key, const char *value) { + char *old_value = NULL; + const char *new_value; + + pa_assert(i); + pa_assert(key); + + if (pa_proplist_contains(i->proplist, key)) { + old_value = pa_xstrdup(pa_proplist_gets(i->proplist, key)); + if (value && old_value && pa_streq(value, old_value)) + goto finish; + + if (!old_value) + old_value = pa_xstrdup("(data)"); + } else { + if (!value) + goto finish; + + old_value = pa_xstrdup("(unset)"); + } + + if (value) { + pa_proplist_sets(i->proplist, key, value); + new_value = value; + } else { + pa_proplist_unset(i->proplist, key); + new_value = "(unset)"; + } + + if (PA_SINK_INPUT_IS_LINKED(i->state)) { + pa_log_debug("Sink input %u: proplist[%s]: %s -> %s", i->index, key, old_value, new_value); + pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_PROPLIST_CHANGED], i); + pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT | PA_SUBSCRIPTION_EVENT_CHANGE, i->index); + } + +finish: + pa_xfree(old_value); +} + +void pa_sink_input_set_property_arbitrary(pa_sink_input *i, const char *key, const uint8_t *value, size_t nbytes) { + const uint8_t *old_value; + size_t old_nbytes; + const char *old_value_str; + const char *new_value_str; + + pa_assert(i); + pa_assert(key); + + if (pa_proplist_get(i->proplist, key, (const void **) &old_value, &old_nbytes) >= 0) { + if (value && nbytes == old_nbytes && !memcmp(value, old_value, nbytes)) + return; + + old_value_str = "(data)"; + + } else { + if (!value) + return; + + old_value_str = "(unset)"; + } + + if (value) { + pa_proplist_set(i->proplist, key, value, nbytes); + new_value_str = "(data)"; + } else { + pa_proplist_unset(i->proplist, key); + new_value_str = "(unset)"; + } + + if (PA_SINK_INPUT_IS_LINKED(i->state)) { + pa_log_debug("Sink input %u: proplist[%s]: %s -> %s", i->index, key, old_value_str, new_value_str); + pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_PROPLIST_CHANGED], i); + pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT | PA_SUBSCRIPTION_EVENT_CHANGE, i->index); + } +} + +/* Called from main thread */ +void pa_sink_input_update_proplist(pa_sink_input *i, pa_update_mode_t mode, pa_proplist *p) { + void *state; + const char *key; + const uint8_t *value; + size_t nbytes; + + pa_sink_input_assert_ref(i); + pa_assert(p); + pa_assert_ctl_context(); + + switch (mode) { + case PA_UPDATE_SET: + /* Delete everything that is not in p. */ + for (state = NULL; (key = pa_proplist_iterate(i->proplist, &state));) { + if (!pa_proplist_contains(p, key)) + pa_sink_input_set_property(i, key, NULL); + } + + /* Fall through. */ + case PA_UPDATE_REPLACE: + for (state = NULL; (key = pa_proplist_iterate(p, &state));) { + pa_proplist_get(p, key, (const void **) &value, &nbytes); + pa_sink_input_set_property_arbitrary(i, key, value, nbytes); + } + + break; + case PA_UPDATE_MERGE: + for (state = NULL; (key = pa_proplist_iterate(p, &state));) { + if (pa_proplist_contains(i->proplist, key)) + continue; + + pa_proplist_get(p, key, (const void **) &value, &nbytes); + pa_sink_input_set_property_arbitrary(i, key, value, nbytes); + } + + break; + } +} + +/* Called from main context */ +void pa_sink_input_cork(pa_sink_input *i, bool b) { + pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); + pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); + + sink_input_set_state(i, b ? PA_SINK_INPUT_CORKED : PA_SINK_INPUT_RUNNING); +} + +/* Called from main context */ +int pa_sink_input_set_rate(pa_sink_input *i, uint32_t rate) { + pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); + pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); + pa_return_val_if_fail(i->thread_info.resampler, -PA_ERR_BADSTATE); + + if (i->sample_spec.rate == rate) + return 0; + + i->sample_spec.rate = rate; + + if (i->sink) + pa_asyncmsgq_post(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_RATE, PA_UINT_TO_PTR(rate), 0, NULL, NULL); + else { + i->thread_info.sample_spec.rate = rate; + pa_resampler_set_input_rate(i->thread_info.resampler, rate); + } + + pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index); + return 0; +} + +/* Called from main context */ +pa_resample_method_t pa_sink_input_get_resample_method(pa_sink_input *i) { + pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); + + return i->actual_resample_method; +} + +/* Called from main context */ +bool pa_sink_input_may_move(pa_sink_input *i) { + pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); + pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); + + if (i->flags & PA_SINK_INPUT_DONT_MOVE) + return false; + + if (i->sync_next || i->sync_prev) { + pa_log_warn("Moving synchronized streams not supported."); + return false; + } + + return true; +} + +static bool find_filter_sink_input(pa_sink_input *target, pa_sink *s) { + unsigned PA_UNUSED i = 0; + while (s && s->input_to_master) { + if (s->input_to_master == target) + return true; + s = s->input_to_master->sink; + pa_assert(i++ < 100); + } + return false; +} + +static bool is_filter_sink_moving(pa_sink_input *i) { + pa_sink *sink = i->sink; + + if (!sink) + return false; + + while (sink->input_to_master) { + sink = sink->input_to_master->sink; + + if (!sink) + return true; + } + + return false; +} + +/* Called from main context */ +bool pa_sink_input_may_move_to(pa_sink_input *i, pa_sink *dest) { + pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); + pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); + pa_sink_assert_ref(dest); + + if (dest == i->sink) + return true; + + if (dest->unlink_requested) + return false; + + if (!pa_sink_input_may_move(i)) + return false; + + /* Make sure we're not creating a filter sink cycle */ + if (find_filter_sink_input(i, dest)) { + pa_log_debug("Can't connect input to %s, as that would create a cycle.", dest->name); + return false; + } + + /* If this sink input is connected to a filter sink that itself is moving, + * then don't allow the move. Moving requires sending a message to the IO + * thread of the old sink, and if the old sink is a filter sink that is + * moving, there's no IO thread associated to the old sink. */ + if (is_filter_sink_moving(i)) { + pa_log_debug("Can't move input from filter sink %s, because the filter sink itself is currently moving.", + i->sink->name); + return false; + } + + if (pa_idxset_size(dest->inputs) >= PA_MAX_INPUTS_PER_SINK) { + pa_log_warn("Failed to move sink input: too many inputs per sink."); + return false; + } + + if (check_passthrough_connection(pa_sink_input_is_passthrough(i), dest) < 0) + return false; + + if (i->may_move_to) + if (!i->may_move_to(i, dest)) + return false; + + return true; +} + +/* Called from main context */ +int pa_sink_input_start_move(pa_sink_input *i) { + pa_source_output *o, PA_UNUSED *p = NULL; + struct volume_factor_entry *v; + void *state = NULL; + int r; + + pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); + pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); + pa_assert(i->sink); + + if (!pa_sink_input_may_move(i)) + return -PA_ERR_NOTSUPPORTED; + + if ((r = pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_START], i)) < 0) + return r; + + pa_log_debug("Starting to move sink input %u from '%s'", (unsigned) i->index, i->sink->name); + + /* Kill directly connected outputs */ + while ((o = pa_idxset_first(i->direct_outputs, NULL))) { + pa_assert(o != p); + pa_source_output_kill(o); + p = o; + } + pa_assert(pa_idxset_isempty(i->direct_outputs)); + + pa_idxset_remove_by_data(i->sink->inputs, i, NULL); + + if (i->state == PA_SINK_INPUT_CORKED) + pa_assert_se(i->sink->n_corked-- >= 1); + + if (pa_sink_input_is_passthrough(i)) + pa_sink_leave_passthrough(i->sink); + + if (pa_sink_flat_volume_enabled(i->sink)) + /* We might need to update the sink's volume if we are in flat + * volume mode. */ + pa_sink_set_volume(i->sink, NULL, false, false); + + pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_START_MOVE, i, 0, NULL) == 0); + + pa_sink_update_status(i->sink); + + PA_HASHMAP_FOREACH(v, i->volume_factor_sink_items, state) + pa_cvolume_remap(&v->volume, &i->sink->channel_map, &i->channel_map); + + pa_cvolume_remap(&i->volume_factor_sink, &i->sink->channel_map, &i->channel_map); + + i->sink = NULL; + i->sink_requested_by_application = false; + + pa_sink_input_unref(i); + + return 0; +} + +/* Called from main context. If i has an origin sink that uses volume sharing, + * then also the origin sink and all streams connected to it need to update + * their volume - this function does all that by using recursion. */ +static void update_volume_due_to_moving(pa_sink_input *i, pa_sink *dest) { + pa_cvolume new_volume; + + pa_assert(i); + pa_assert(dest); + pa_assert(i->sink); /* The destination sink should already be set. */ + + if (i->origin_sink && (i->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)) { + pa_sink *root_sink = pa_sink_get_master(i->sink); + pa_sink_input *origin_sink_input; + uint32_t idx; + + if (PA_UNLIKELY(!root_sink)) + return; + + if (pa_sink_flat_volume_enabled(i->sink)) { + /* Ok, so the origin sink uses volume sharing, and flat volume is + * enabled. The volume will have to be updated as follows: + * + * i->volume := i->sink->real_volume + * (handled later by pa_sink_set_volume) + * i->reference_ratio := i->volume / i->sink->reference_volume + * (handled later by pa_sink_set_volume) + * i->real_ratio stays unchanged + * (streams whose origin sink uses volume sharing should + * always have real_ratio of 0 dB) + * i->soft_volume stays unchanged + * (streams whose origin sink uses volume sharing should + * always have volume_factor as soft_volume, so no change + * should be needed) */ + + pa_assert(pa_cvolume_is_norm(&i->real_ratio)); + pa_assert(pa_cvolume_equal(&i->soft_volume, &i->volume_factor)); + + /* Notifications will be sent by pa_sink_set_volume(). */ + + } else { + /* Ok, so the origin sink uses volume sharing, and flat volume is + * disabled. The volume will have to be updated as follows: + * + * i->volume := 0 dB + * i->reference_ratio := 0 dB + * i->real_ratio stays unchanged + * (streams whose origin sink uses volume sharing should + * always have real_ratio of 0 dB) + * i->soft_volume stays unchanged + * (streams whose origin sink uses volume sharing should + * always have volume_factor as soft_volume, so no change + * should be needed) */ + + pa_cvolume_reset(&new_volume, i->volume.channels); + pa_sink_input_set_volume_direct(i, &new_volume); + pa_sink_input_set_reference_ratio(i, &new_volume); + pa_assert(pa_cvolume_is_norm(&i->real_ratio)); + pa_assert(pa_cvolume_equal(&i->soft_volume, &i->volume_factor)); + } + + /* Additionally, the origin sink volume needs updating: + * + * i->origin_sink->reference_volume := root_sink->reference_volume + * i->origin_sink->real_volume := root_sink->real_volume + * i->origin_sink->soft_volume stays unchanged + * (sinks that use volume sharing should always have + * soft_volume of 0 dB) */ + + new_volume = root_sink->reference_volume; + pa_cvolume_remap(&new_volume, &root_sink->channel_map, &i->origin_sink->channel_map); + pa_sink_set_reference_volume_direct(i->origin_sink, &new_volume); + + i->origin_sink->real_volume = root_sink->real_volume; + pa_cvolume_remap(&i->origin_sink->real_volume, &root_sink->channel_map, &i->origin_sink->channel_map); + + pa_assert(pa_cvolume_is_norm(&i->origin_sink->soft_volume)); + + /* If you wonder whether i->origin_sink->set_volume() should be called + * somewhere, that's not the case, because sinks that use volume + * sharing shouldn't have any internal volume that set_volume() would + * update. If you wonder whether the thread_info variables should be + * synced, yes, they should, and it's done by the + * PA_SINK_MESSAGE_FINISH_MOVE message handler. */ + + /* Recursively update origin sink inputs. */ + PA_IDXSET_FOREACH(origin_sink_input, i->origin_sink->inputs, idx) + update_volume_due_to_moving(origin_sink_input, dest); + + } else { + if (pa_sink_flat_volume_enabled(i->sink)) { + /* Ok, so this is a regular stream, and flat volume is enabled. The + * volume will have to be updated as follows: + * + * i->volume := i->reference_ratio * i->sink->reference_volume + * i->reference_ratio stays unchanged + * i->real_ratio := i->volume / i->sink->real_volume + * (handled later by pa_sink_set_volume) + * i->soft_volume := i->real_ratio * i->volume_factor + * (handled later by pa_sink_set_volume) */ + + new_volume = i->sink->reference_volume; + pa_cvolume_remap(&new_volume, &i->sink->channel_map, &i->channel_map); + pa_sw_cvolume_multiply(&new_volume, &new_volume, &i->reference_ratio); + pa_sink_input_set_volume_direct(i, &new_volume); + + } else { + /* Ok, so this is a regular stream, and flat volume is disabled. + * The volume will have to be updated as follows: + * + * i->volume := i->reference_ratio + * i->reference_ratio stays unchanged + * i->real_ratio := i->reference_ratio + * i->soft_volume := i->real_ratio * i->volume_factor */ + + pa_sink_input_set_volume_direct(i, &i->reference_ratio); + i->real_ratio = i->reference_ratio; + pa_sw_cvolume_multiply(&i->soft_volume, &i->real_ratio, &i->volume_factor); + } + } + + /* If i->sink == dest, then recursion has finished, and we can finally call + * pa_sink_set_volume(), which will do the rest of the updates. */ + if ((i->sink == dest) && pa_sink_flat_volume_enabled(i->sink)) + pa_sink_set_volume(i->sink, NULL, false, i->save_volume); +} + +/* Called from main context */ +int pa_sink_input_finish_move(pa_sink_input *i, pa_sink *dest, bool save) { + struct volume_factor_entry *v; + void *state = NULL; + + pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); + pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); + pa_assert(!i->sink); + pa_sink_assert_ref(dest); + + if (!pa_sink_input_may_move_to(i, dest)) + return -PA_ERR_NOTSUPPORTED; + + if (pa_sink_input_is_passthrough(i) && !pa_sink_check_format(dest, i->format)) { + pa_proplist *p = pa_proplist_new(); + pa_log_debug("New sink doesn't support stream format, sending format-changed and killing"); + /* Tell the client what device we want to be on if it is going to + * reconnect */ + pa_proplist_sets(p, "device", dest->name); + pa_sink_input_send_event(i, PA_STREAM_EVENT_FORMAT_LOST, p); + pa_proplist_free(p); + return -PA_ERR_NOTSUPPORTED; + } + + if (!(i->flags & PA_SINK_INPUT_VARIABLE_RATE) && + !pa_sample_spec_equal(&i->sample_spec, &dest->sample_spec)) { + /* try to change dest sink format and rate if possible without glitches. + module-suspend-on-idle resumes destination sink with + SINK_INPUT_MOVE_FINISH hook */ + + pa_log_info("Trying to change sample spec"); + pa_sink_reconfigure(dest, &i->sample_spec, pa_sink_input_is_passthrough(i)); + } + + if (i->moving) + i->moving(i, dest); + + i->sink = dest; + /* save == true, means user is calling the move_to() and want to + save the preferred_sink */ + if (save) { + pa_xfree(i->preferred_sink); + if (dest == dest->core->default_sink) + i->preferred_sink = NULL; + else + i->preferred_sink = pa_xstrdup(dest->name); + } + + pa_idxset_put(dest->inputs, pa_sink_input_ref(i), NULL); + + PA_HASHMAP_FOREACH(v, i->volume_factor_sink_items, state) + pa_cvolume_remap(&v->volume, &i->channel_map, &i->sink->channel_map); + + pa_cvolume_remap(&i->volume_factor_sink, &i->channel_map, &i->sink->channel_map); + + if (i->state == PA_SINK_INPUT_CORKED) + i->sink->n_corked++; + + pa_sink_input_update_resampler(i); + + pa_sink_update_status(dest); + + update_volume_due_to_moving(i, dest); + + if (pa_sink_input_is_passthrough(i)) + pa_sink_enter_passthrough(i->sink); + + pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_FINISH_MOVE, i, 0, NULL) == 0); + + pa_log_debug("Successfully moved sink input %i to %s.", i->index, dest->name); + + /* Notify everyone */ + pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_FINISH], i); + pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index); + + return 0; +} + +/* Called from main context */ +void pa_sink_input_fail_move(pa_sink_input *i) { + + pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); + pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); + pa_assert(!i->sink); + + /* Check if someone wants this sink input? */ + if (pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_FAIL], i) == PA_HOOK_STOP) + return; + + /* Can we move the sink input to the default sink? */ + if (i->core->rescue_streams && pa_sink_input_may_move_to(i, i->core->default_sink)) { + if (pa_sink_input_finish_move(i, i->core->default_sink, false) >= 0) + return; + } + + if (i->moving) + i->moving(i, NULL); + + pa_sink_input_kill(i); +} + +/* Called from main context */ +int pa_sink_input_move_to(pa_sink_input *i, pa_sink *dest, bool save) { + int r; + + pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); + pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); + pa_assert(i->sink); + pa_sink_assert_ref(dest); + + if (dest == i->sink) + return 0; + + if (!pa_sink_input_may_move_to(i, dest)) + return -PA_ERR_NOTSUPPORTED; + + pa_sink_input_ref(i); + + if ((r = pa_sink_input_start_move(i)) < 0) { + pa_sink_input_unref(i); + return r; + } + + if ((r = pa_sink_input_finish_move(i, dest, save)) < 0) { + pa_sink_input_fail_move(i); + pa_sink_input_unref(i); + return r; + } + + pa_sink_input_unref(i); + + return 0; +} + +/* Called from IO thread context except when cork() is called without a valid sink. */ +void pa_sink_input_set_state_within_thread(pa_sink_input *i, pa_sink_input_state_t state) { + bool corking, uncorking; + + pa_sink_input_assert_ref(i); + + if (state == i->thread_info.state) + return; + + corking = state == PA_SINK_INPUT_CORKED && i->thread_info.state == PA_SINK_INPUT_RUNNING; + uncorking = i->thread_info.state == PA_SINK_INPUT_CORKED && state == PA_SINK_INPUT_RUNNING; + + if (i->state_change) + i->state_change(i, state); + + if (corking) { + + pa_log_debug("Requesting rewind due to corking"); + + /* This will tell the implementing sink input driver to rewind + * so that the unplayed already mixed data is not lost */ + if (i->sink) + pa_sink_input_request_rewind(i, 0, true, true, false); + + /* Set the corked state *after* requesting rewind */ + i->thread_info.state = state; + + } else if (uncorking) { + + pa_log_debug("Requesting rewind due to uncorking"); + + i->thread_info.underrun_for = (uint64_t) -1; + i->thread_info.underrun_for_sink = 0; + i->thread_info.playing_for = 0; + + /* Set the uncorked state *before* requesting rewind */ + i->thread_info.state = state; + + /* OK, we're being uncorked. Make sure we're not rewound when + * the hw buffer is remixed and request a remix. */ + if (i->sink) + pa_sink_input_request_rewind(i, 0, false, true, true); + } else + /* We may not be corking or uncorking, but we still need to set the state. */ + i->thread_info.state = state; +} + +/* Called from thread context, except when it is not. */ +int pa_sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk) { + pa_sink_input *i = PA_SINK_INPUT(o); + pa_sink_input_assert_ref(i); + + switch (code) { + + case PA_SINK_INPUT_MESSAGE_SET_SOFT_VOLUME: + if (!pa_cvolume_equal(&i->thread_info.soft_volume, &i->soft_volume)) { + i->thread_info.soft_volume = i->soft_volume; + pa_sink_input_request_rewind(i, 0, true, false, false); + } + return 0; + + case PA_SINK_INPUT_MESSAGE_SET_SOFT_MUTE: + if (i->thread_info.muted != i->muted) { + i->thread_info.muted = i->muted; + pa_sink_input_request_rewind(i, 0, true, false, false); + } + return 0; + + case PA_SINK_INPUT_MESSAGE_GET_LATENCY: { + pa_usec_t *r = userdata; + + r[0] += pa_bytes_to_usec(pa_memblockq_get_length(i->thread_info.render_memblockq), &i->sink->sample_spec); + r[1] += pa_sink_get_latency_within_thread(i->sink, false); + + return 0; + } + + case PA_SINK_INPUT_MESSAGE_SET_RATE: + + i->thread_info.sample_spec.rate = PA_PTR_TO_UINT(userdata); + pa_resampler_set_input_rate(i->thread_info.resampler, PA_PTR_TO_UINT(userdata)); + + return 0; + + case PA_SINK_INPUT_MESSAGE_SET_STATE: { + pa_sink_input *ssync; + + pa_sink_input_set_state_within_thread(i, PA_PTR_TO_UINT(userdata)); + + for (ssync = i->thread_info.sync_prev; ssync; ssync = ssync->thread_info.sync_prev) + pa_sink_input_set_state_within_thread(ssync, PA_PTR_TO_UINT(userdata)); + + for (ssync = i->thread_info.sync_next; ssync; ssync = ssync->thread_info.sync_next) + pa_sink_input_set_state_within_thread(ssync, PA_PTR_TO_UINT(userdata)); + + return 0; + } + + case PA_SINK_INPUT_MESSAGE_SET_REQUESTED_LATENCY: { + pa_usec_t *usec = userdata; + + *usec = pa_sink_input_set_requested_latency_within_thread(i, *usec); + return 0; + } + + case PA_SINK_INPUT_MESSAGE_GET_REQUESTED_LATENCY: { + pa_usec_t *r = userdata; + + *r = i->thread_info.requested_sink_latency; + return 0; + } + } + + return -PA_ERR_NOTIMPLEMENTED; +} + +/* Called from IO context */ +bool pa_sink_input_safe_to_remove(pa_sink_input *i) { + pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); + + if (PA_SINK_INPUT_IS_LINKED(i->thread_info.state)) + return pa_memblockq_is_empty(i->thread_info.render_memblockq); + + return true; +} + +/* Called from IO context */ +void pa_sink_input_request_rewind( + pa_sink_input *i, + size_t nbytes /* in our sample spec */, + bool rewrite, /* rewrite what we have, or get fresh data? */ + bool flush, /* flush render memblockq? */ + bool dont_rewind_render) { + + size_t lbq; + + /* If 'rewrite' is true the sink is rewound as far as requested + * and possible and the exact value of this is passed back the + * implementor via process_rewind(). If 'flush' is also true all + * already rendered data is also dropped. + * + * If 'rewrite' is false the sink is rewound as far as requested + * and possible and the already rendered data is dropped so that + * in the next iteration we read new data from the + * implementor. This implies 'flush' is true. If + * dont_rewind_render is true then the render memblockq is not + * rewound. */ + + /* nbytes = 0 means maximum rewind request */ + + pa_sink_input_assert_ref(i); + pa_sink_input_assert_io_context(i); + pa_assert(rewrite || flush); + pa_assert(!dont_rewind_render || !rewrite); + + /* We don't take rewind requests while we are corked */ + if (i->thread_info.state == PA_SINK_INPUT_CORKED) + return; + + nbytes = PA_MAX(i->thread_info.rewrite_nbytes, nbytes); + +#ifdef SINK_INPUT_DEBUG + pa_log_debug("request rewrite %zu", nbytes); +#endif + + /* Calculate how much we can rewind locally without having to + * touch the sink */ + if (rewrite) + lbq = pa_memblockq_get_length(i->thread_info.render_memblockq); + else + lbq = 0; + + /* Check if rewinding for the maximum is requested, and if so, fix up */ + if (nbytes <= 0) { + + /* Calculate maximum number of bytes that could be rewound in theory */ + nbytes = i->sink->thread_info.max_rewind + lbq; + + /* Transform from sink domain */ + if (i->thread_info.resampler) + nbytes = pa_resampler_request(i->thread_info.resampler, nbytes); + } + + /* Remember how much we actually want to rewrite */ + if (i->thread_info.rewrite_nbytes != (size_t) -1) { + if (rewrite) { + /* Make sure to not overwrite over underruns */ + if (nbytes > i->thread_info.playing_for) + nbytes = (size_t) i->thread_info.playing_for; + + i->thread_info.rewrite_nbytes = nbytes; + } else + i->thread_info.rewrite_nbytes = (size_t) -1; + } + + i->thread_info.rewrite_flush = + i->thread_info.rewrite_flush || flush; + + i->thread_info.dont_rewind_render = + i->thread_info.dont_rewind_render || + dont_rewind_render; + + /* nbytes is -1 if some earlier rewind request had rewrite == false. */ + if (nbytes != (size_t) -1) { + + /* Transform to sink domain */ + if (i->thread_info.resampler) + nbytes = pa_resampler_result(i->thread_info.resampler, nbytes); + + if (nbytes > lbq) + pa_sink_request_rewind(i->sink, nbytes - lbq); + else + /* This call will make sure process_rewind() is called later */ + pa_sink_request_rewind(i->sink, 0); + } +} + +/* Called from main context */ +pa_memchunk* pa_sink_input_get_silence(pa_sink_input *i, pa_memchunk *ret) { + pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); + pa_assert(ret); + + /* FIXME: Shouldn't access resampler object from main context! */ + + pa_silence_memchunk_get( + &i->core->silence_cache, + i->core->mempool, + ret, + &i->sample_spec, + i->thread_info.resampler ? pa_resampler_max_block_size(i->thread_info.resampler) : 0); + + return ret; +} + +/* Called from main context */ +void pa_sink_input_send_event(pa_sink_input *i, const char *event, pa_proplist *data) { + pa_proplist *pl = NULL; + pa_sink_input_send_event_hook_data hook_data; + + pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); + pa_assert(event); + + if (!i->send_event) + return; + + if (!data) + data = pl = pa_proplist_new(); + + hook_data.sink_input = i; + hook_data.data = data; + hook_data.event = event; + + if (pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_SEND_EVENT], &hook_data) < 0) + goto finish; + + i->send_event(i, event, data); + +finish: + if (pl) + pa_proplist_free(pl); +} + +/* Called from main context */ +/* Updates the sink input's resampler with whatever the current sink requires + * -- useful when the underlying sink's sample spec might have changed */ +int pa_sink_input_update_resampler(pa_sink_input *i) { + pa_resampler *new_resampler; + char *memblockq_name; + + pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); + + if (i->thread_info.resampler && + pa_sample_spec_equal(pa_resampler_output_sample_spec(i->thread_info.resampler), &i->sink->sample_spec) && + pa_channel_map_equal(pa_resampler_output_channel_map(i->thread_info.resampler), &i->sink->channel_map)) + + new_resampler = i->thread_info.resampler; + + else if (!pa_sink_input_is_passthrough(i) && + ((i->flags & PA_SINK_INPUT_VARIABLE_RATE) || + !pa_sample_spec_equal(&i->sample_spec, &i->sink->sample_spec) || + !pa_channel_map_equal(&i->channel_map, &i->sink->channel_map))) { + + new_resampler = pa_resampler_new(i->core->mempool, + &i->sample_spec, &i->channel_map, + &i->sink->sample_spec, &i->sink->channel_map, + i->core->lfe_crossover_freq, + i->requested_resample_method, + ((i->flags & PA_SINK_INPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0) | + ((i->flags & PA_SINK_INPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) | + (i->core->disable_remixing || (i->flags & PA_SINK_INPUT_NO_REMIX) ? PA_RESAMPLER_NO_REMIX : 0) | + (i->core->remixing_use_all_sink_channels ? 0 : PA_RESAMPLER_NO_FILL_SINK) | + (i->core->remixing_produce_lfe ? PA_RESAMPLER_PRODUCE_LFE : 0) | + (i->core->remixing_consume_lfe ? PA_RESAMPLER_CONSUME_LFE : 0)); + + if (!new_resampler) { + pa_log_warn("Unsupported resampling operation."); + return -PA_ERR_NOTSUPPORTED; + } + } else + new_resampler = NULL; + + if (new_resampler == i->thread_info.resampler) + return 0; + + if (i->thread_info.resampler) + pa_resampler_free(i->thread_info.resampler); + + i->thread_info.resampler = new_resampler; + + pa_memblockq_free(i->thread_info.render_memblockq); + + memblockq_name = pa_sprintf_malloc("sink input render_memblockq [%u]", i->index); + i->thread_info.render_memblockq = pa_memblockq_new( + memblockq_name, + 0, + MEMBLOCKQ_MAXLENGTH, + 0, + &i->sink->sample_spec, + 0, + 1, + 0, + &i->sink->silence); + pa_xfree(memblockq_name); + + i->actual_resample_method = new_resampler ? pa_resampler_get_method(new_resampler) : PA_RESAMPLER_INVALID; + + pa_log_debug("Updated resampler for sink input %d", i->index); + + return 0; +} + +/* Called from the IO thread. */ +void pa_sink_input_attach(pa_sink_input *i) { + pa_assert(i); + pa_assert(!i->thread_info.attached); + + i->thread_info.attached = true; + + if (i->attach) + i->attach(i); +} + +/* Called from the IO thread. */ +void pa_sink_input_detach(pa_sink_input *i) { + pa_assert(i); + + if (!i->thread_info.attached) + return; + + i->thread_info.attached = false; + + if (i->detach) + i->detach(i); +} + +/* Called from the main thread. */ +void pa_sink_input_set_volume_direct(pa_sink_input *i, const pa_cvolume *volume) { + pa_cvolume old_volume; + char old_volume_str[PA_CVOLUME_SNPRINT_VERBOSE_MAX]; + char new_volume_str[PA_CVOLUME_SNPRINT_VERBOSE_MAX]; + + pa_assert(i); + pa_assert(volume); + + old_volume = i->volume; + + if (pa_cvolume_equal(volume, &old_volume)) + return; + + i->volume = *volume; + pa_log_debug("The volume of sink input %u changed from %s to %s.", i->index, + pa_cvolume_snprint_verbose(old_volume_str, sizeof(old_volume_str), &old_volume, &i->channel_map, true), + pa_cvolume_snprint_verbose(new_volume_str, sizeof(new_volume_str), volume, &i->channel_map, true)); + + if (i->volume_changed) + i->volume_changed(i); + + pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index); + pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_VOLUME_CHANGED], i); +} + +/* Called from the main thread. */ +void pa_sink_input_set_reference_ratio(pa_sink_input *i, const pa_cvolume *ratio) { + pa_cvolume old_ratio; + char old_ratio_str[PA_CVOLUME_SNPRINT_VERBOSE_MAX]; + char new_ratio_str[PA_CVOLUME_SNPRINT_VERBOSE_MAX]; + + pa_assert(i); + pa_assert(ratio); + + old_ratio = i->reference_ratio; + + if (pa_cvolume_equal(ratio, &old_ratio)) + return; + + i->reference_ratio = *ratio; + + if (!PA_SINK_INPUT_IS_LINKED(i->state)) + return; + + pa_log_debug("Sink input %u reference ratio changed from %s to %s.", i->index, + pa_cvolume_snprint_verbose(old_ratio_str, sizeof(old_ratio_str), &old_ratio, &i->channel_map, true), + pa_cvolume_snprint_verbose(new_ratio_str, sizeof(new_ratio_str), ratio, &i->channel_map, true)); +} + +/* Called from the main thread. */ +void pa_sink_input_set_preferred_sink(pa_sink_input *i, pa_sink *s) { + pa_assert(i); + + pa_xfree(i->preferred_sink); + if (s) { + i->preferred_sink = pa_xstrdup(s->name); + pa_sink_input_move_to(i, s, false); + } else { + i->preferred_sink = NULL; + pa_sink_input_move_to(i, i->core->default_sink, false); + } +} diff --git a/src/pulsecore/sink-input.h b/src/pulsecore/sink-input.h new file mode 100644 index 0000000..d3de6e3 --- /dev/null +++ b/src/pulsecore/sink-input.h @@ -0,0 +1,469 @@ +#ifndef foopulsesinkinputhfoo +#define foopulsesinkinputhfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <inttypes.h> + +#include <pulsecore/typedefs.h> +#include <pulse/sample.h> +#include <pulse/format.h> +#include <pulsecore/memblockq.h> +#include <pulsecore/resampler.h> +#include <pulsecore/module.h> +#include <pulsecore/client.h> +#include <pulsecore/sink.h> +#include <pulsecore/core.h> + +typedef enum pa_sink_input_state { + PA_SINK_INPUT_INIT, /*< The stream is not active yet, because pa_sink_input_put() has not been called yet */ + PA_SINK_INPUT_RUNNING, /*< The stream is alive and kicking */ + PA_SINK_INPUT_CORKED, /*< The stream was corked on user request */ + PA_SINK_INPUT_UNLINKED /*< The stream is dead */ + /* FIXME: we need a state for MOVING here */ +} pa_sink_input_state_t; + +static inline bool PA_SINK_INPUT_IS_LINKED(pa_sink_input_state_t x) { + return x == PA_SINK_INPUT_RUNNING || x == PA_SINK_INPUT_CORKED; +} + +typedef enum pa_sink_input_flags { + PA_SINK_INPUT_VARIABLE_RATE = 1, + PA_SINK_INPUT_DONT_MOVE = 2, + PA_SINK_INPUT_START_CORKED = 4, + PA_SINK_INPUT_NO_REMAP = 8, + PA_SINK_INPUT_NO_REMIX = 16, + PA_SINK_INPUT_FIX_FORMAT = 32, + PA_SINK_INPUT_FIX_RATE = 64, + PA_SINK_INPUT_FIX_CHANNELS = 128, + PA_SINK_INPUT_DONT_INHIBIT_AUTO_SUSPEND = 256, + PA_SINK_INPUT_NO_CREATE_ON_SUSPEND = 512, + PA_SINK_INPUT_KILL_ON_SUSPEND = 1024, + PA_SINK_INPUT_PASSTHROUGH = 2048 +} pa_sink_input_flags_t; + +struct pa_sink_input { + pa_msgobject parent; + + uint32_t index; + pa_core *core; + + pa_sink_input_state_t state; + pa_sink_input_flags_t flags; + + char *driver; /* may be NULL */ + pa_proplist *proplist; + + pa_module *module; /* may be NULL */ + pa_client *client; /* may be NULL */ + + pa_sink *sink; /* NULL while we are being moved */ + + /* This is set to true when creating the sink input if the sink was + * requested by the application that created the sink input. This is + * sometimes useful for determining whether the sink input should be + * moved by some automatic policy. If the sink input is moved away from the + * sink that the application requested, this flag is reset to false. */ + bool sink_requested_by_application; + + pa_sink *origin_sink; /* only set by filter sinks */ + + /* A sink input may be connected to multiple source outputs + * directly, so that they don't get mixed data of the entire + * source. */ + pa_idxset *direct_outputs; + + pa_sample_spec sample_spec; + pa_channel_map channel_map; + pa_format_info *format; + + pa_sink_input *sync_prev, *sync_next; + + /* Also see http://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/Developer/Volumes/ */ + pa_cvolume volume; /* The volume clients are informed about */ + pa_cvolume reference_ratio; /* The ratio of the stream's volume to the sink's reference volume */ + pa_cvolume real_ratio; /* The ratio of the stream's volume to the sink's real volume */ + /* volume_factor is an internally used "additional volume" that can be used + * by modules without having the volume visible to clients. volume_factor + * calculated by merging all the individual items in volume_factor_items. + * Modules must not modify these variables directly, instead + * pa_sink_input_add/remove_volume_factor() have to be used to add and + * remove items, or pa_sink_input_new_data_add_volume_factor() during input + * creation time. */ + pa_cvolume volume_factor; + pa_hashmap *volume_factor_items; + pa_cvolume soft_volume; /* The internal software volume we apply to all PCM data while it passes through. Usually calculated as real_ratio * volume_factor */ + + pa_cvolume volume_factor_sink; /* A second volume factor in format of the sink this stream is connected to. */ + pa_hashmap *volume_factor_sink_items; + + bool volume_writable:1; + + bool muted:1; + + /* if true then the volume and the mute state of this sink-input + * are worth remembering, module-stream-restore looks for + * this.*/ + bool save_volume:1, save_muted:1; + + /* if users move the sink-input to a sink, and the sink is not default_sink, + * the sink->name will be saved in preferred_sink. And later if sink-input + * is moved to other sinks for some reason, it still can be restored to the + * preferred_sink at an appropriate time */ + char *preferred_sink; + + pa_resample_method_t requested_resample_method, actual_resample_method; + + /* Returns the chunk of audio data and drops it from the + * queue. Returns -1 on failure. Called from IO thread context. If + * data needs to be generated from scratch then please in the + * specified length request_nbytes. This is an optimization + * only. If less data is available, it's fine to return a smaller + * block. If more data is already ready, it is better to return + * the full block. */ + int (*pop) (pa_sink_input *i, size_t request_nbytes, pa_memchunk *chunk); /* may NOT be NULL */ + + /* This is called when the playback buffer has actually played back + all available data. Return true unless there is more data to play back. + Called from IO context. */ + bool (*process_underrun) (pa_sink_input *i); + + /* Rewind the queue by the specified number of bytes. Called just + * before peek() if it is called at all. Only called if the sink + * input driver ever plans to call + * pa_sink_input_request_rewind(). Called from IO context. */ + void (*process_rewind) (pa_sink_input *i, size_t nbytes); /* may NOT be NULL */ + + /* Called whenever the maximum rewindable size of the sink + * changes. Called from IO context. */ + void (*update_max_rewind) (pa_sink_input *i, size_t nbytes); /* may be NULL */ + + /* Called whenever the maximum request size of the sink + * changes. Called from IO context. */ + void (*update_max_request) (pa_sink_input *i, size_t nbytes); /* may be NULL */ + + /* Called whenever the configured latency of the sink + * changes. Called from IO context. */ + void (*update_sink_requested_latency) (pa_sink_input *i); /* may be NULL */ + + /* Called whenever the latency range of the sink changes. Called + * from IO context. */ + void (*update_sink_latency_range) (pa_sink_input *i); /* may be NULL */ + + /* Called whenever the fixed latency of the sink changes, if there + * is one. Called from IO context. */ + void (*update_sink_fixed_latency) (pa_sink_input *i); /* may be NULL */ + + /* If non-NULL this function is called when the input is first + * connected to a sink or when the rtpoll/asyncmsgq fields + * change. You usually don't need to implement this function + * unless you rewrite a sink that is piggy-backed onto + * another. Called from IO thread context */ + void (*attach) (pa_sink_input *i); /* may be NULL */ + + /* If non-NULL this function is called when the output is + * disconnected from its sink. Called from IO thread context */ + void (*detach) (pa_sink_input *i); /* may be NULL */ + + /* If non-NULL called whenever the sink this input is attached + * to suspends or resumes or if the suspend cause changes. + * Called from main context */ + void (*suspend) (pa_sink_input *i, pa_sink_state_t old_state, pa_suspend_cause_t old_suspend_cause); /* may be NULL */ + + /* If non-NULL called whenever the sink this input is attached + * to suspends or resumes. Called from IO context */ + void (*suspend_within_thread) (pa_sink_input *i, bool b); /* may be NULL */ + + /* If non-NULL called whenever the sink input is moved to a new + * sink. Called from main context after the sink input has been + * detached from the old sink and before it has been attached to + * the new sink. If dest is NULL the move was executed in two + * phases and the second one failed; the stream will be destroyed + * after this call. */ + void (*moving) (pa_sink_input *i, pa_sink *dest); /* may be NULL */ + + /* Supposed to unlink and destroy this stream. Called from main + * context. */ + void (*kill) (pa_sink_input *i); /* may NOT be NULL */ + + /* Return the current latency (i.e. length of buffered audio) of + this stream. Called from main context. This is added to what the + PA_SINK_INPUT_MESSAGE_GET_LATENCY message sent to the IO thread + returns */ + pa_usec_t (*get_latency) (pa_sink_input *i); /* may be NULL */ + + /* If non-NULL this function is called from thread context if the + * state changes. The old state is found in thread_info.state. */ + void (*state_change) (pa_sink_input *i, pa_sink_input_state_t state); /* may be NULL */ + + /* If non-NULL this function is called before this sink input is + * move to a sink and if it returns false the move will not + * be allowed */ + bool (*may_move_to) (pa_sink_input *i, pa_sink *s); /* may be NULL */ + + /* If non-NULL this function is used to dispatch asynchronous + * control events. Called from main context. */ + void (*send_event)(pa_sink_input *i, const char *event, pa_proplist* data); /* may be NULL */ + + /* If non-NULL this function is called whenever the sink input + * volume changes. Called from main context */ + void (*volume_changed)(pa_sink_input *i); /* may be NULL */ + + /* If non-NULL this function is called whenever the sink input + * mute status changes. Called from main context */ + void (*mute_changed)(pa_sink_input *i); /* may be NULL */ + + struct { + pa_sink_input_state_t state; + + pa_cvolume soft_volume; + bool muted:1; + + bool attached:1; /* True only between ->attach() and ->detach() calls */ + + /* rewrite_nbytes: 0: rewrite nothing, (size_t) -1: rewrite everything, otherwise how many bytes to rewrite */ + bool rewrite_flush:1, dont_rewind_render:1; + size_t rewrite_nbytes; + uint64_t underrun_for, playing_for; + uint64_t underrun_for_sink; /* Like underrun_for, but in sink sample spec */ + + pa_sample_spec sample_spec; + + pa_resampler *resampler; /* may be NULL */ + + /* We maintain a history of resampled audio data here. */ + pa_memblockq *render_memblockq; + + pa_sink_input *sync_prev, *sync_next; + + /* The requested latency for the sink */ + pa_usec_t requested_sink_latency; + + pa_hashmap *direct_outputs; + } thread_info; + + void *userdata; +}; + +PA_DECLARE_PUBLIC_CLASS(pa_sink_input); +#define PA_SINK_INPUT(o) pa_sink_input_cast(o) + +enum { + PA_SINK_INPUT_MESSAGE_SET_SOFT_VOLUME, + PA_SINK_INPUT_MESSAGE_SET_SOFT_MUTE, + PA_SINK_INPUT_MESSAGE_GET_LATENCY, + PA_SINK_INPUT_MESSAGE_SET_RATE, + PA_SINK_INPUT_MESSAGE_SET_STATE, + PA_SINK_INPUT_MESSAGE_SET_REQUESTED_LATENCY, + PA_SINK_INPUT_MESSAGE_GET_REQUESTED_LATENCY, + PA_SINK_INPUT_MESSAGE_MAX +}; + +typedef struct pa_sink_input_send_event_hook_data { + pa_sink_input *sink_input; + const char *event; + pa_proplist *data; +} pa_sink_input_send_event_hook_data; + +typedef struct pa_sink_input_new_data { + pa_sink_input_flags_t flags; + + pa_proplist *proplist; + + const char *driver; + pa_module *module; + pa_client *client; + + pa_sink *sink; + bool sink_requested_by_application; + pa_sink *origin_sink; + + pa_resample_method_t resample_method; + + pa_sink_input *sync_base; + + pa_sample_spec sample_spec; + pa_channel_map channel_map; + pa_format_info *format; + pa_idxset *req_formats; + pa_idxset *nego_formats; + + pa_cvolume volume; + bool muted:1; + pa_hashmap *volume_factor_items, *volume_factor_sink_items; + + bool sample_spec_is_set:1; + bool channel_map_is_set:1; + + bool volume_is_set:1; + bool muted_is_set:1; + + bool volume_is_absolute:1; + + bool volume_writable:1; + + bool save_volume:1, save_muted:1; + + char *preferred_sink; +} pa_sink_input_new_data; + +pa_sink_input_new_data* pa_sink_input_new_data_init(pa_sink_input_new_data *data); +void pa_sink_input_new_data_set_sample_spec(pa_sink_input_new_data *data, const pa_sample_spec *spec); +void pa_sink_input_new_data_set_channel_map(pa_sink_input_new_data *data, const pa_channel_map *map); +bool pa_sink_input_new_data_is_passthrough(pa_sink_input_new_data *data); +void pa_sink_input_new_data_set_volume(pa_sink_input_new_data *data, const pa_cvolume *volume); +void pa_sink_input_new_data_add_volume_factor(pa_sink_input_new_data *data, const char *key, const pa_cvolume *volume_factor); +void pa_sink_input_new_data_add_volume_factor_sink(pa_sink_input_new_data *data, const char *key, const pa_cvolume *volume_factor); +void pa_sink_input_new_data_set_muted(pa_sink_input_new_data *data, bool mute); +bool pa_sink_input_new_data_set_sink(pa_sink_input_new_data *data, pa_sink *s, bool save, bool requested_by_application); +bool pa_sink_input_new_data_set_formats(pa_sink_input_new_data *data, pa_idxset *formats); +void pa_sink_input_new_data_done(pa_sink_input_new_data *data); + +/* To be called by the implementing module only */ + +int pa_sink_input_new( + pa_sink_input **i, + pa_core *core, + pa_sink_input_new_data *data); + +void pa_sink_input_put(pa_sink_input *i); +void pa_sink_input_unlink(pa_sink_input* i); + +pa_usec_t pa_sink_input_set_requested_latency(pa_sink_input *i, pa_usec_t usec); + +/* Request that the specified number of bytes already written out to +the hw device is rewritten, if possible. Please note that this is +only a kind request. The sink driver may not be able to fulfill it +fully -- or at all. If the request for a rewrite was successful, the +sink driver will call ->rewind() and pass the number of bytes that +could be rewound in the HW device. This functionality is required for +implementing the "zero latency" write-through functionality. */ +void pa_sink_input_request_rewind(pa_sink_input *i, size_t nbytes, bool rewrite, bool flush, bool dont_rewind_render); + +void pa_sink_input_cork(pa_sink_input *i, bool b); + +int pa_sink_input_set_rate(pa_sink_input *i, uint32_t rate); +int pa_sink_input_update_resampler(pa_sink_input *i); + +/* This returns the sink's fields converted into out sample type */ +size_t pa_sink_input_get_max_rewind(pa_sink_input *i); +size_t pa_sink_input_get_max_request(pa_sink_input *i); + +/* Callable by everyone from main thread*/ + +/* External code may request disconnection with this function */ +void pa_sink_input_kill(pa_sink_input*i); + +pa_usec_t pa_sink_input_get_latency(pa_sink_input *i, pa_usec_t *sink_latency); + +bool pa_sink_input_is_passthrough(pa_sink_input *i); +bool pa_sink_input_is_volume_readable(pa_sink_input *i); +void pa_sink_input_set_volume(pa_sink_input *i, const pa_cvolume *volume, bool save, bool absolute); +void pa_sink_input_add_volume_factor(pa_sink_input *i, const char *key, const pa_cvolume *volume_factor); +int pa_sink_input_remove_volume_factor(pa_sink_input *i, const char *key); +pa_cvolume *pa_sink_input_get_volume(pa_sink_input *i, pa_cvolume *volume, bool absolute); + +void pa_sink_input_set_mute(pa_sink_input *i, bool mute, bool save); + +void pa_sink_input_set_property(pa_sink_input *i, const char *key, const char *value); +void pa_sink_input_set_property_arbitrary(pa_sink_input *i, const char *key, const uint8_t *value, size_t nbytes); +void pa_sink_input_update_proplist(pa_sink_input *i, pa_update_mode_t mode, pa_proplist *p); + +pa_resample_method_t pa_sink_input_get_resample_method(pa_sink_input *i); + +void pa_sink_input_send_event(pa_sink_input *i, const char *name, pa_proplist *data); + +int pa_sink_input_move_to(pa_sink_input *i, pa_sink *dest, bool save); +bool pa_sink_input_may_move(pa_sink_input *i); /* may this sink input move at all? */ +bool pa_sink_input_may_move_to(pa_sink_input *i, pa_sink *dest); /* may this sink input move to this sink? */ + +/* The same as pa_sink_input_move_to() but in two separate steps, + * first the detaching from the old sink, then the attaching to the + * new sink */ +int pa_sink_input_start_move(pa_sink_input *i); +int pa_sink_input_finish_move(pa_sink_input *i, pa_sink *dest, bool save); +void pa_sink_input_fail_move(pa_sink_input *i); + +pa_usec_t pa_sink_input_get_requested_latency(pa_sink_input *i); + +/* To be used exclusively by the sink driver IO thread */ + +void pa_sink_input_peek(pa_sink_input *i, size_t length, pa_memchunk *chunk, pa_cvolume *volume); +void pa_sink_input_drop(pa_sink_input *i, size_t length); +void pa_sink_input_process_rewind(pa_sink_input *i, size_t nbytes /* in the sink's sample spec */); +void pa_sink_input_update_max_rewind(pa_sink_input *i, size_t nbytes /* in the sink's sample spec */); +void pa_sink_input_update_max_request(pa_sink_input *i, size_t nbytes /* in the sink's sample spec */); + +void pa_sink_input_set_state_within_thread(pa_sink_input *i, pa_sink_input_state_t state); + +int pa_sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk); + +pa_usec_t pa_sink_input_set_requested_latency_within_thread(pa_sink_input *i, pa_usec_t usec); + +bool pa_sink_input_safe_to_remove(pa_sink_input *i); +bool pa_sink_input_process_underrun(pa_sink_input *i); + +pa_memchunk* pa_sink_input_get_silence(pa_sink_input *i, pa_memchunk *ret); + +/* Calls the attach() callback if it's set. The input must be in detached + * state. */ +void pa_sink_input_attach(pa_sink_input *i); + +/* Calls the detach() callback if it's set and the input is attached. The input + * is allowed to be already detached, in which case this does nothing. + * + * The reason why this can be called for already-detached inputs is that when + * a filter sink's input is detached, it has to detach also all inputs + * connected to the filter sink. In case the filter sink's input was detached + * because the filter sink is being removed, those other inputs will be moved + * to another sink or removed, and moving and removing involve detaching the + * inputs, but the inputs at that point are already detached. + * + * XXX: Moving or removing an input also involves sending messages to the + * input's sink. If the input's sink is a detached filter sink, shouldn't + * sending messages to it be prohibited? The messages are processed in the + * root sink's IO thread, and when the filter sink is detached, it would seem + * logical to prohibit any interaction with the IO thread that isn't any more + * associated with the filter sink. Currently sending messages to detached + * filter sinks mostly works, because the filter sinks don't update their + * asyncmsgq pointer when detaching, so messages still find their way to the + * old IO thread. */ +void pa_sink_input_detach(pa_sink_input *i); + +/* Called from the main thread, from sink.c only. The normal way to set the + * sink input volume is to call pa_sink_input_set_volume(), but the flat volume + * logic in sink.c needs also a function that doesn't do all the extra stuff + * that pa_sink_input_set_volume() does. This function simply sets i->volume + * and fires change notifications. */ +void pa_sink_input_set_volume_direct(pa_sink_input *i, const pa_cvolume *volume); + +/* Called from the main thread, from sink.c only. This shouldn't be a public + * function, but the flat volume logic in sink.c currently needs a way to + * directly set the sink input reference ratio. This function simply sets + * i->reference_ratio and logs a message if the value changes. */ +void pa_sink_input_set_reference_ratio(pa_sink_input *i, const pa_cvolume *ratio); + +void pa_sink_input_set_preferred_sink(pa_sink_input *i, pa_sink *s); + +#define pa_sink_input_assert_io_context(s) \ + pa_assert(pa_thread_mq_get() || !PA_SINK_INPUT_IS_LINKED((s)->state)) + +#endif diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c new file mode 100644 index 0000000..e89b596 --- /dev/null +++ b/src/pulsecore/sink.c @@ -0,0 +1,3996 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <pulse/introspect.h> +#include <pulse/format.h> +#include <pulse/utf8.h> +#include <pulse/xmalloc.h> +#include <pulse/timeval.h> +#include <pulse/util.h> +#include <pulse/rtclock.h> +#include <pulse/internal.h> + +#include <pulsecore/i18n.h> +#include <pulsecore/sink-input.h> +#include <pulsecore/namereg.h> +#include <pulsecore/core-util.h> +#include <pulsecore/sample-util.h> +#include <pulsecore/mix.h> +#include <pulsecore/core-subscribe.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> +#include <pulsecore/play-memblockq.h> +#include <pulsecore/flist.h> + +#include "sink.h" + +#define MAX_MIX_CHANNELS 32 +#define MIX_BUFFER_LENGTH (pa_page_size()) +#define ABSOLUTE_MIN_LATENCY (500) +#define ABSOLUTE_MAX_LATENCY (10*PA_USEC_PER_SEC) +#define DEFAULT_FIXED_LATENCY (250*PA_USEC_PER_MSEC) + +PA_DEFINE_PUBLIC_CLASS(pa_sink, pa_msgobject); + +struct pa_sink_volume_change { + pa_usec_t at; + pa_cvolume hw_volume; + + PA_LLIST_FIELDS(pa_sink_volume_change); +}; + +struct set_state_data { + pa_sink_state_t state; + pa_suspend_cause_t suspend_cause; +}; + +static void sink_free(pa_object *s); + +static void pa_sink_volume_change_push(pa_sink *s); +static void pa_sink_volume_change_flush(pa_sink *s); +static void pa_sink_volume_change_rewind(pa_sink *s, size_t nbytes); + +pa_sink_new_data* pa_sink_new_data_init(pa_sink_new_data *data) { + pa_assert(data); + + pa_zero(*data); + data->proplist = pa_proplist_new(); + data->ports = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, (pa_free_cb_t) pa_device_port_unref); + + return data; +} + +void pa_sink_new_data_set_name(pa_sink_new_data *data, const char *name) { + pa_assert(data); + + pa_xfree(data->name); + data->name = pa_xstrdup(name); +} + +void pa_sink_new_data_set_sample_spec(pa_sink_new_data *data, const pa_sample_spec *spec) { + pa_assert(data); + + if ((data->sample_spec_is_set = !!spec)) + data->sample_spec = *spec; +} + +void pa_sink_new_data_set_channel_map(pa_sink_new_data *data, const pa_channel_map *map) { + pa_assert(data); + + if ((data->channel_map_is_set = !!map)) + data->channel_map = *map; +} + +void pa_sink_new_data_set_alternate_sample_rate(pa_sink_new_data *data, const uint32_t alternate_sample_rate) { + pa_assert(data); + + data->alternate_sample_rate_is_set = true; + data->alternate_sample_rate = alternate_sample_rate; +} + +void pa_sink_new_data_set_avoid_resampling(pa_sink_new_data *data, bool avoid_resampling) { + pa_assert(data); + + data->avoid_resampling_is_set = true; + data->avoid_resampling = avoid_resampling; +} + +void pa_sink_new_data_set_volume(pa_sink_new_data *data, const pa_cvolume *volume) { + pa_assert(data); + + if ((data->volume_is_set = !!volume)) + data->volume = *volume; +} + +void pa_sink_new_data_set_muted(pa_sink_new_data *data, bool mute) { + pa_assert(data); + + data->muted_is_set = true; + data->muted = mute; +} + +void pa_sink_new_data_set_port(pa_sink_new_data *data, const char *port) { + pa_assert(data); + + pa_xfree(data->active_port); + data->active_port = pa_xstrdup(port); +} + +void pa_sink_new_data_done(pa_sink_new_data *data) { + pa_assert(data); + + pa_proplist_free(data->proplist); + + if (data->ports) + pa_hashmap_free(data->ports); + + pa_xfree(data->name); + pa_xfree(data->active_port); +} + +/* Called from main context */ +static void reset_callbacks(pa_sink *s) { + pa_assert(s); + + s->set_state_in_main_thread = NULL; + s->set_state_in_io_thread = NULL; + s->get_volume = NULL; + s->set_volume = NULL; + s->write_volume = NULL; + s->get_mute = NULL; + s->set_mute = NULL; + s->request_rewind = NULL; + s->update_requested_latency = NULL; + s->set_port = NULL; + s->get_formats = NULL; + s->set_formats = NULL; + s->reconfigure = NULL; +} + +/* Called from main context */ +pa_sink* pa_sink_new( + pa_core *core, + pa_sink_new_data *data, + pa_sink_flags_t flags) { + + pa_sink *s; + const char *name; + char st[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX]; + pa_source_new_data source_data; + const char *dn; + char *pt; + + pa_assert(core); + pa_assert(data); + pa_assert(data->name); + pa_assert_ctl_context(); + + s = pa_msgobject_new(pa_sink); + + if (!(name = pa_namereg_register(core, data->name, PA_NAMEREG_SINK, s, data->namereg_fail))) { + pa_log_debug("Failed to register name %s.", data->name); + pa_xfree(s); + return NULL; + } + + pa_sink_new_data_set_name(data, name); + + if (pa_hook_fire(&core->hooks[PA_CORE_HOOK_SINK_NEW], data) < 0) { + pa_xfree(s); + pa_namereg_unregister(core, name); + return NULL; + } + + /* FIXME, need to free s here on failure */ + + pa_return_null_if_fail(!data->driver || pa_utf8_valid(data->driver)); + pa_return_null_if_fail(data->name && pa_utf8_valid(data->name) && data->name[0]); + + pa_return_null_if_fail(data->sample_spec_is_set && pa_sample_spec_valid(&data->sample_spec)); + + if (!data->channel_map_is_set) + pa_return_null_if_fail(pa_channel_map_init_auto(&data->channel_map, data->sample_spec.channels, PA_CHANNEL_MAP_DEFAULT)); + + pa_return_null_if_fail(pa_channel_map_valid(&data->channel_map)); + pa_return_null_if_fail(data->channel_map.channels == data->sample_spec.channels); + + /* FIXME: There should probably be a general function for checking whether + * the sink volume is allowed to be set, like there is for sink inputs. */ + pa_assert(!data->volume_is_set || !(flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)); + + if (!data->volume_is_set) { + pa_cvolume_reset(&data->volume, data->sample_spec.channels); + data->save_volume = false; + } + + pa_return_null_if_fail(pa_cvolume_valid(&data->volume)); + pa_return_null_if_fail(pa_cvolume_compatible(&data->volume, &data->sample_spec)); + + if (!data->muted_is_set) + data->muted = false; + + if (data->card) + pa_proplist_update(data->proplist, PA_UPDATE_MERGE, data->card->proplist); + + pa_device_init_description(data->proplist, data->card); + pa_device_init_icon(data->proplist, true); + pa_device_init_intended_roles(data->proplist); + + if (!data->active_port) { + pa_device_port *p = pa_device_port_find_best(data->ports); + if (p) + pa_sink_new_data_set_port(data, p->name); + } + + if (pa_hook_fire(&core->hooks[PA_CORE_HOOK_SINK_FIXATE], data) < 0) { + pa_xfree(s); + pa_namereg_unregister(core, name); + return NULL; + } + + s->parent.parent.free = sink_free; + s->parent.process_msg = pa_sink_process_msg; + + s->core = core; + s->state = PA_SINK_INIT; + s->flags = flags; + s->priority = 0; + s->suspend_cause = data->suspend_cause; + s->name = pa_xstrdup(name); + s->proplist = pa_proplist_copy(data->proplist); + s->driver = pa_xstrdup(pa_path_get_filename(data->driver)); + s->module = data->module; + s->card = data->card; + + s->priority = pa_device_init_priority(s->proplist); + + s->sample_spec = data->sample_spec; + s->channel_map = data->channel_map; + s->default_sample_rate = s->sample_spec.rate; + + if (data->alternate_sample_rate_is_set) + s->alternate_sample_rate = data->alternate_sample_rate; + else + s->alternate_sample_rate = s->core->alternate_sample_rate; + + if (data->avoid_resampling_is_set) + s->avoid_resampling = data->avoid_resampling; + else + s->avoid_resampling = s->core->avoid_resampling; + + s->inputs = pa_idxset_new(NULL, NULL); + s->n_corked = 0; + s->input_to_master = NULL; + + s->reference_volume = s->real_volume = data->volume; + pa_cvolume_reset(&s->soft_volume, s->sample_spec.channels); + s->base_volume = PA_VOLUME_NORM; + s->n_volume_steps = PA_VOLUME_NORM+1; + s->muted = data->muted; + s->refresh_volume = s->refresh_muted = false; + + reset_callbacks(s); + s->userdata = NULL; + + s->asyncmsgq = NULL; + + /* As a minor optimization we just steal the list instead of + * copying it here */ + s->ports = data->ports; + data->ports = NULL; + + s->active_port = NULL; + s->save_port = false; + + if (data->active_port) + if ((s->active_port = pa_hashmap_get(s->ports, data->active_port))) + s->save_port = data->save_port; + + /* Hopefully the active port has already been assigned in the previous call + to pa_device_port_find_best, but better safe than sorry */ + if (!s->active_port) + s->active_port = pa_device_port_find_best(s->ports); + + if (s->active_port) + s->port_latency_offset = s->active_port->latency_offset; + else + s->port_latency_offset = 0; + + s->save_volume = data->save_volume; + s->save_muted = data->save_muted; + + pa_silence_memchunk_get( + &core->silence_cache, + core->mempool, + &s->silence, + &s->sample_spec, + 0); + + s->thread_info.rtpoll = NULL; + s->thread_info.inputs = pa_hashmap_new_full(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func, NULL, + (pa_free_cb_t) pa_sink_input_unref); + s->thread_info.soft_volume = s->soft_volume; + s->thread_info.soft_muted = s->muted; + s->thread_info.state = s->state; + s->thread_info.rewind_nbytes = 0; + s->thread_info.rewind_requested = false; + s->thread_info.max_rewind = 0; + s->thread_info.max_request = 0; + s->thread_info.requested_latency_valid = false; + s->thread_info.requested_latency = 0; + s->thread_info.min_latency = ABSOLUTE_MIN_LATENCY; + s->thread_info.max_latency = ABSOLUTE_MAX_LATENCY; + s->thread_info.fixed_latency = flags & PA_SINK_DYNAMIC_LATENCY ? 0 : DEFAULT_FIXED_LATENCY; + + PA_LLIST_HEAD_INIT(pa_sink_volume_change, s->thread_info.volume_changes); + s->thread_info.volume_changes_tail = NULL; + pa_sw_cvolume_divide(&s->thread_info.current_hw_volume, &s->real_volume, &s->soft_volume); + s->thread_info.volume_change_safety_margin = core->deferred_volume_safety_margin_usec; + s->thread_info.volume_change_extra_delay = core->deferred_volume_extra_delay_usec; + s->thread_info.port_latency_offset = s->port_latency_offset; + + /* FIXME: This should probably be moved to pa_sink_put() */ + pa_assert_se(pa_idxset_put(core->sinks, s, &s->index) >= 0); + + if (s->card) + pa_assert_se(pa_idxset_put(s->card->sinks, s, NULL) >= 0); + + pt = pa_proplist_to_string_sep(s->proplist, "\n "); + pa_log_info("Created sink %u \"%s\" with sample spec %s and channel map %s\n %s", + s->index, + s->name, + pa_sample_spec_snprint(st, sizeof(st), &s->sample_spec), + pa_channel_map_snprint(cm, sizeof(cm), &s->channel_map), + pt); + pa_xfree(pt); + + pa_source_new_data_init(&source_data); + pa_source_new_data_set_sample_spec(&source_data, &s->sample_spec); + pa_source_new_data_set_channel_map(&source_data, &s->channel_map); + pa_source_new_data_set_alternate_sample_rate(&source_data, s->alternate_sample_rate); + pa_source_new_data_set_avoid_resampling(&source_data, s->avoid_resampling); + source_data.name = pa_sprintf_malloc("%s.monitor", name); + source_data.driver = data->driver; + source_data.module = data->module; + source_data.card = data->card; + + dn = pa_proplist_gets(s->proplist, PA_PROP_DEVICE_DESCRIPTION); + pa_proplist_setf(source_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Monitor of %s", dn ? dn : s->name); + pa_proplist_sets(source_data.proplist, PA_PROP_DEVICE_CLASS, "monitor"); + + s->monitor_source = pa_source_new(core, &source_data, + ((flags & PA_SINK_LATENCY) ? PA_SOURCE_LATENCY : 0) | + ((flags & PA_SINK_DYNAMIC_LATENCY) ? PA_SOURCE_DYNAMIC_LATENCY : 0)); + + pa_source_new_data_done(&source_data); + + if (!s->monitor_source) { + pa_sink_unlink(s); + pa_sink_unref(s); + return NULL; + } + + s->monitor_source->monitor_of = s; + + pa_source_set_latency_range(s->monitor_source, s->thread_info.min_latency, s->thread_info.max_latency); + pa_source_set_fixed_latency(s->monitor_source, s->thread_info.fixed_latency); + pa_source_set_max_rewind(s->monitor_source, s->thread_info.max_rewind); + + return s; +} + +/* Called from main context */ +static int sink_set_state(pa_sink *s, pa_sink_state_t state, pa_suspend_cause_t suspend_cause) { + int ret = 0; + bool state_changed; + bool suspend_cause_changed; + bool suspending; + bool resuming; + pa_sink_state_t old_state; + pa_suspend_cause_t old_suspend_cause; + + pa_assert(s); + pa_assert_ctl_context(); + + state_changed = state != s->state; + suspend_cause_changed = suspend_cause != s->suspend_cause; + + if (!state_changed && !suspend_cause_changed) + return 0; + + suspending = PA_SINK_IS_OPENED(s->state) && state == PA_SINK_SUSPENDED; + resuming = s->state == PA_SINK_SUSPENDED && PA_SINK_IS_OPENED(state); + + /* If we are resuming, suspend_cause must be 0. */ + pa_assert(!resuming || !suspend_cause); + + /* Here's something to think about: what to do with the suspend cause if + * resuming the sink fails? The old suspend cause will be incorrect, so we + * can't use that. On the other hand, if we set no suspend cause (as is the + * case currently), then it looks strange to have a sink suspended without + * any cause. It might be a good idea to add a new "resume failed" suspend + * cause, or it might just add unnecessary complexity, given that the + * current approach of not setting any suspend cause works well enough. */ + + if (s->set_state_in_main_thread) { + if ((ret = s->set_state_in_main_thread(s, state, suspend_cause)) < 0) { + /* set_state_in_main_thread() is allowed to fail only when resuming. */ + pa_assert(resuming); + + /* If resuming fails, we set the state to SUSPENDED and + * suspend_cause to 0. */ + state = PA_SINK_SUSPENDED; + suspend_cause = 0; + state_changed = false; + suspend_cause_changed = suspend_cause != s->suspend_cause; + resuming = false; + + /* We know the state isn't changing. If the suspend cause isn't + * changing either, then there's nothing more to do. */ + if (!suspend_cause_changed) + return ret; + } + } + + if (s->asyncmsgq) { + struct set_state_data data = { .state = state, .suspend_cause = suspend_cause }; + + if ((ret = pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_STATE, &data, 0, NULL)) < 0) { + /* SET_STATE is allowed to fail only when resuming. */ + pa_assert(resuming); + + if (s->set_state_in_main_thread) + s->set_state_in_main_thread(s, PA_SINK_SUSPENDED, 0); + + /* If resuming fails, we set the state to SUSPENDED and + * suspend_cause to 0. */ + state = PA_SINK_SUSPENDED; + suspend_cause = 0; + state_changed = false; + suspend_cause_changed = suspend_cause != s->suspend_cause; + resuming = false; + + /* We know the state isn't changing. If the suspend cause isn't + * changing either, then there's nothing more to do. */ + if (!suspend_cause_changed) + return ret; + } + } + + old_suspend_cause = s->suspend_cause; + if (suspend_cause_changed) { + char old_cause_buf[PA_SUSPEND_CAUSE_TO_STRING_BUF_SIZE]; + char new_cause_buf[PA_SUSPEND_CAUSE_TO_STRING_BUF_SIZE]; + + pa_log_debug("%s: suspend_cause: %s -> %s", s->name, pa_suspend_cause_to_string(s->suspend_cause, old_cause_buf), + pa_suspend_cause_to_string(suspend_cause, new_cause_buf)); + s->suspend_cause = suspend_cause; + } + + old_state = s->state; + if (state_changed) { + pa_log_debug("%s: state: %s -> %s", s->name, pa_sink_state_to_string(s->state), pa_sink_state_to_string(state)); + s->state = state; + + /* If we enter UNLINKED state, then we don't send change notifications. + * pa_sink_unlink() will send unlink notifications instead. */ + if (state != PA_SINK_UNLINKED) { + pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_STATE_CHANGED], s); + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK | PA_SUBSCRIPTION_EVENT_CHANGE, s->index); + } + } + + if (suspending || resuming || suspend_cause_changed) { + pa_sink_input *i; + uint32_t idx; + + /* We're suspending or resuming, tell everyone about it */ + + PA_IDXSET_FOREACH(i, s->inputs, idx) + if (s->state == PA_SINK_SUSPENDED && + (i->flags & PA_SINK_INPUT_KILL_ON_SUSPEND)) + pa_sink_input_kill(i); + else if (i->suspend) + i->suspend(i, old_state, old_suspend_cause); + } + + if ((suspending || resuming || suspend_cause_changed) && s->monitor_source && state != PA_SINK_UNLINKED) + pa_source_sync_suspend(s->monitor_source); + + return ret; +} + +void pa_sink_set_get_volume_callback(pa_sink *s, pa_sink_cb_t cb) { + pa_assert(s); + + s->get_volume = cb; +} + +void pa_sink_set_set_volume_callback(pa_sink *s, pa_sink_cb_t cb) { + pa_sink_flags_t flags; + + pa_assert(s); + pa_assert(!s->write_volume || cb); + + s->set_volume = cb; + + /* Save the current flags so we can tell if they've changed */ + flags = s->flags; + + if (cb) { + /* The sink implementor is responsible for setting decibel volume support */ + s->flags |= PA_SINK_HW_VOLUME_CTRL; + } else { + s->flags &= ~PA_SINK_HW_VOLUME_CTRL; + /* See note below in pa_sink_put() about volume sharing and decibel volumes */ + pa_sink_enable_decibel_volume(s, !(s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)); + } + + /* If the flags have changed after init, let any clients know via a change event */ + if (s->state != PA_SINK_INIT && flags != s->flags) + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); +} + +void pa_sink_set_write_volume_callback(pa_sink *s, pa_sink_cb_t cb) { + pa_sink_flags_t flags; + + pa_assert(s); + pa_assert(!cb || s->set_volume); + + s->write_volume = cb; + + /* Save the current flags so we can tell if they've changed */ + flags = s->flags; + + if (cb) + s->flags |= PA_SINK_DEFERRED_VOLUME; + else + s->flags &= ~PA_SINK_DEFERRED_VOLUME; + + /* If the flags have changed after init, let any clients know via a change event */ + if (s->state != PA_SINK_INIT && flags != s->flags) + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); +} + +void pa_sink_set_get_mute_callback(pa_sink *s, pa_sink_get_mute_cb_t cb) { + pa_assert(s); + + s->get_mute = cb; +} + +void pa_sink_set_set_mute_callback(pa_sink *s, pa_sink_cb_t cb) { + pa_sink_flags_t flags; + + pa_assert(s); + + s->set_mute = cb; + + /* Save the current flags so we can tell if they've changed */ + flags = s->flags; + + if (cb) + s->flags |= PA_SINK_HW_MUTE_CTRL; + else + s->flags &= ~PA_SINK_HW_MUTE_CTRL; + + /* If the flags have changed after init, let any clients know via a change event */ + if (s->state != PA_SINK_INIT && flags != s->flags) + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); +} + +static void enable_flat_volume(pa_sink *s, bool enable) { + pa_sink_flags_t flags; + + pa_assert(s); + + /* Always follow the overall user preference here */ + enable = enable && s->core->flat_volumes; + + /* Save the current flags so we can tell if they've changed */ + flags = s->flags; + + if (enable) + s->flags |= PA_SINK_FLAT_VOLUME; + else + s->flags &= ~PA_SINK_FLAT_VOLUME; + + /* If the flags have changed after init, let any clients know via a change event */ + if (s->state != PA_SINK_INIT && flags != s->flags) + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); +} + +void pa_sink_enable_decibel_volume(pa_sink *s, bool enable) { + pa_sink_flags_t flags; + + pa_assert(s); + + /* Save the current flags so we can tell if they've changed */ + flags = s->flags; + + if (enable) { + s->flags |= PA_SINK_DECIBEL_VOLUME; + enable_flat_volume(s, true); + } else { + s->flags &= ~PA_SINK_DECIBEL_VOLUME; + enable_flat_volume(s, false); + } + + /* If the flags have changed after init, let any clients know via a change event */ + if (s->state != PA_SINK_INIT && flags != s->flags) + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); +} + +/* Called from main context */ +void pa_sink_put(pa_sink* s) { + pa_sink_assert_ref(s); + pa_assert_ctl_context(); + + pa_assert(s->state == PA_SINK_INIT); + pa_assert(!(s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER) || pa_sink_is_filter(s)); + + /* The following fields must be initialized properly when calling _put() */ + pa_assert(s->asyncmsgq); + pa_assert(s->thread_info.min_latency <= s->thread_info.max_latency); + + /* Generally, flags should be initialized via pa_sink_new(). As a + * special exception we allow some volume related flags to be set + * between _new() and _put() by the callback setter functions above. + * + * Thus we implement a couple safeguards here which ensure the above + * setters were used (or at least the implementor made manual changes + * in a compatible way). + * + * Note: All of these flags set here can change over the life time + * of the sink. */ + pa_assert(!(s->flags & PA_SINK_HW_VOLUME_CTRL) || s->set_volume); + pa_assert(!(s->flags & PA_SINK_DEFERRED_VOLUME) || s->write_volume); + pa_assert(!(s->flags & PA_SINK_HW_MUTE_CTRL) || s->set_mute); + + /* XXX: Currently decibel volume is disabled for all sinks that use volume + * sharing. When the master sink supports decibel volume, it would be good + * to have the flag also in the filter sink, but currently we don't do that + * so that the flags of the filter sink never change when it's moved from + * a master sink to another. One solution for this problem would be to + * remove user-visible volume altogether from filter sinks when volume + * sharing is used, but the current approach was easier to implement... */ + /* We always support decibel volumes in software, otherwise we leave it to + * the sink implementor to set this flag as needed. + * + * Note: This flag can also change over the life time of the sink. */ + if (!(s->flags & PA_SINK_HW_VOLUME_CTRL) && !(s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)) { + pa_sink_enable_decibel_volume(s, true); + s->soft_volume = s->reference_volume; + } + + /* If the sink implementor support DB volumes by itself, we should always + * try and enable flat volumes too */ + if ((s->flags & PA_SINK_DECIBEL_VOLUME)) + enable_flat_volume(s, true); + + if (s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER) { + pa_sink *root_sink = pa_sink_get_master(s); + + pa_assert(root_sink); + + s->reference_volume = root_sink->reference_volume; + pa_cvolume_remap(&s->reference_volume, &root_sink->channel_map, &s->channel_map); + + s->real_volume = root_sink->real_volume; + pa_cvolume_remap(&s->real_volume, &root_sink->channel_map, &s->channel_map); + } else + /* We assume that if the sink implementor changed the default + * volume they did so in real_volume, because that is the usual + * place where they are supposed to place their changes. */ + s->reference_volume = s->real_volume; + + s->thread_info.soft_volume = s->soft_volume; + s->thread_info.soft_muted = s->muted; + pa_sw_cvolume_divide(&s->thread_info.current_hw_volume, &s->real_volume, &s->soft_volume); + + pa_assert((s->flags & PA_SINK_HW_VOLUME_CTRL) + || (s->base_volume == PA_VOLUME_NORM + && ((s->flags & PA_SINK_DECIBEL_VOLUME || (s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER))))); + pa_assert(!(s->flags & PA_SINK_DECIBEL_VOLUME) || s->n_volume_steps == PA_VOLUME_NORM+1); + pa_assert(!(s->flags & PA_SINK_DYNAMIC_LATENCY) == !(s->thread_info.fixed_latency == 0)); + pa_assert(!(s->flags & PA_SINK_LATENCY) == !(s->monitor_source->flags & PA_SOURCE_LATENCY)); + pa_assert(!(s->flags & PA_SINK_DYNAMIC_LATENCY) == !(s->monitor_source->flags & PA_SOURCE_DYNAMIC_LATENCY)); + + pa_assert(s->monitor_source->thread_info.fixed_latency == s->thread_info.fixed_latency); + pa_assert(s->monitor_source->thread_info.min_latency == s->thread_info.min_latency); + pa_assert(s->monitor_source->thread_info.max_latency == s->thread_info.max_latency); + + if (s->suspend_cause) + pa_assert_se(sink_set_state(s, PA_SINK_SUSPENDED, s->suspend_cause) == 0); + else + pa_assert_se(sink_set_state(s, PA_SINK_IDLE, 0) == 0); + + pa_source_put(s->monitor_source); + + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK | PA_SUBSCRIPTION_EVENT_NEW, s->index); + pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_PUT], s); + + /* It's good to fire the SINK_PUT hook before updating the default sink, + * because module-switch-on-connect will set the new sink as the default + * sink, and if we were to call pa_core_update_default_sink() before that, + * the default sink might change twice, causing unnecessary stream moving. */ + + pa_core_update_default_sink(s->core); + + pa_core_move_streams_to_newly_available_preferred_sink(s->core, s); +} + +/* Called from main context */ +void pa_sink_unlink(pa_sink* s) { + bool linked; + pa_sink_input *i, PA_UNUSED *j = NULL; + + pa_sink_assert_ref(s); + pa_assert_ctl_context(); + + /* Please note that pa_sink_unlink() does more than simply + * reversing pa_sink_put(). It also undoes the registrations + * already done in pa_sink_new()! */ + + if (s->unlink_requested) + return; + + s->unlink_requested = true; + + linked = PA_SINK_IS_LINKED(s->state); + + if (linked) + pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_UNLINK], s); + + if (s->state != PA_SINK_UNLINKED) + pa_namereg_unregister(s->core, s->name); + pa_idxset_remove_by_data(s->core->sinks, s, NULL); + + pa_core_update_default_sink(s->core); + + if (linked && s->core->rescue_streams) + pa_sink_move_streams_to_default_sink(s->core, s, false); + + if (s->card) + pa_idxset_remove_by_data(s->card->sinks, s, NULL); + + while ((i = pa_idxset_first(s->inputs, NULL))) { + pa_assert(i != j); + pa_sink_input_kill(i); + j = i; + } + + if (linked) + /* It's important to keep the suspend cause unchanged when unlinking, + * because if we remove the SESSION suspend cause here, the alsa sink + * will sync its volume with the hardware while another user is + * active, messing up the volume for that other user. */ + sink_set_state(s, PA_SINK_UNLINKED, s->suspend_cause); + else + s->state = PA_SINK_UNLINKED; + + reset_callbacks(s); + + if (s->monitor_source) + pa_source_unlink(s->monitor_source); + + if (linked) { + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK | PA_SUBSCRIPTION_EVENT_REMOVE, s->index); + pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_UNLINK_POST], s); + } +} + +/* Called from main context */ +static void sink_free(pa_object *o) { + pa_sink *s = PA_SINK(o); + + pa_assert(s); + pa_assert_ctl_context(); + pa_assert(pa_sink_refcnt(s) == 0); + pa_assert(!PA_SINK_IS_LINKED(s->state)); + + pa_log_info("Freeing sink %u \"%s\"", s->index, s->name); + + pa_sink_volume_change_flush(s); + + if (s->monitor_source) { + pa_source_unref(s->monitor_source); + s->monitor_source = NULL; + } + + pa_idxset_free(s->inputs, NULL); + pa_hashmap_free(s->thread_info.inputs); + + if (s->silence.memblock) + pa_memblock_unref(s->silence.memblock); + + pa_xfree(s->name); + pa_xfree(s->driver); + + if (s->proplist) + pa_proplist_free(s->proplist); + + if (s->ports) + pa_hashmap_free(s->ports); + + pa_xfree(s); +} + +/* Called from main context, and not while the IO thread is active, please */ +void pa_sink_set_asyncmsgq(pa_sink *s, pa_asyncmsgq *q) { + pa_sink_assert_ref(s); + pa_assert_ctl_context(); + + s->asyncmsgq = q; + + if (s->monitor_source) + pa_source_set_asyncmsgq(s->monitor_source, q); +} + +/* Called from main context, and not while the IO thread is active, please */ +void pa_sink_update_flags(pa_sink *s, pa_sink_flags_t mask, pa_sink_flags_t value) { + pa_sink_flags_t old_flags; + pa_sink_input *input; + uint32_t idx; + + pa_sink_assert_ref(s); + pa_assert_ctl_context(); + + /* For now, allow only a minimal set of flags to be changed. */ + pa_assert((mask & ~(PA_SINK_DYNAMIC_LATENCY|PA_SINK_LATENCY)) == 0); + + old_flags = s->flags; + s->flags = (s->flags & ~mask) | (value & mask); + + if (s->flags == old_flags) + return; + + if ((s->flags & PA_SINK_LATENCY) != (old_flags & PA_SINK_LATENCY)) + pa_log_debug("Sink %s: LATENCY flag %s.", s->name, (s->flags & PA_SINK_LATENCY) ? "enabled" : "disabled"); + + if ((s->flags & PA_SINK_DYNAMIC_LATENCY) != (old_flags & PA_SINK_DYNAMIC_LATENCY)) + pa_log_debug("Sink %s: DYNAMIC_LATENCY flag %s.", + s->name, (s->flags & PA_SINK_DYNAMIC_LATENCY) ? "enabled" : "disabled"); + + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK | PA_SUBSCRIPTION_EVENT_CHANGE, s->index); + pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_FLAGS_CHANGED], s); + + if (s->monitor_source) + pa_source_update_flags(s->monitor_source, + ((mask & PA_SINK_LATENCY) ? PA_SOURCE_LATENCY : 0) | + ((mask & PA_SINK_DYNAMIC_LATENCY) ? PA_SOURCE_DYNAMIC_LATENCY : 0), + ((value & PA_SINK_LATENCY) ? PA_SOURCE_LATENCY : 0) | + ((value & PA_SINK_DYNAMIC_LATENCY) ? PA_SOURCE_DYNAMIC_LATENCY : 0)); + + PA_IDXSET_FOREACH(input, s->inputs, idx) { + if (input->origin_sink) + pa_sink_update_flags(input->origin_sink, mask, value); + } +} + +/* Called from IO context, or before _put() from main context */ +void pa_sink_set_rtpoll(pa_sink *s, pa_rtpoll *p) { + pa_sink_assert_ref(s); + pa_sink_assert_io_context(s); + + s->thread_info.rtpoll = p; + + if (s->monitor_source) + pa_source_set_rtpoll(s->monitor_source, p); +} + +/* Called from main context */ +int pa_sink_update_status(pa_sink*s) { + pa_sink_assert_ref(s); + pa_assert_ctl_context(); + pa_assert(PA_SINK_IS_LINKED(s->state)); + + if (s->state == PA_SINK_SUSPENDED) + return 0; + + return sink_set_state(s, pa_sink_used_by(s) ? PA_SINK_RUNNING : PA_SINK_IDLE, 0); +} + +/* Called from main context */ +int pa_sink_suspend(pa_sink *s, bool suspend, pa_suspend_cause_t cause) { + pa_suspend_cause_t merged_cause; + + pa_sink_assert_ref(s); + pa_assert_ctl_context(); + pa_assert(PA_SINK_IS_LINKED(s->state)); + pa_assert(cause != 0); + + if (suspend) + merged_cause = s->suspend_cause | cause; + else + merged_cause = s->suspend_cause & ~cause; + + if (merged_cause) + return sink_set_state(s, PA_SINK_SUSPENDED, merged_cause); + else + return sink_set_state(s, pa_sink_used_by(s) ? PA_SINK_RUNNING : PA_SINK_IDLE, 0); +} + +/* Called from main context */ +pa_queue *pa_sink_move_all_start(pa_sink *s, pa_queue *q) { + pa_sink_input *i, *n; + uint32_t idx; + + pa_sink_assert_ref(s); + pa_assert_ctl_context(); + pa_assert(PA_SINK_IS_LINKED(s->state)); + + if (!q) + q = pa_queue_new(); + + for (i = PA_SINK_INPUT(pa_idxset_first(s->inputs, &idx)); i; i = n) { + n = PA_SINK_INPUT(pa_idxset_next(s->inputs, &idx)); + + pa_sink_input_ref(i); + + if (pa_sink_input_start_move(i) >= 0) + pa_queue_push(q, i); + else + pa_sink_input_unref(i); + } + + return q; +} + +/* Called from main context */ +void pa_sink_move_all_finish(pa_sink *s, pa_queue *q, bool save) { + pa_sink_input *i; + + pa_sink_assert_ref(s); + pa_assert_ctl_context(); + pa_assert(PA_SINK_IS_LINKED(s->state)); + pa_assert(q); + + while ((i = PA_SINK_INPUT(pa_queue_pop(q)))) { + if (PA_SINK_INPUT_IS_LINKED(i->state)) { + if (pa_sink_input_finish_move(i, s, save) < 0) + pa_sink_input_fail_move(i); + + } + pa_sink_input_unref(i); + } + + pa_queue_free(q, NULL); +} + +/* Called from main context */ +void pa_sink_move_all_fail(pa_queue *q) { + pa_sink_input *i; + + pa_assert_ctl_context(); + pa_assert(q); + + while ((i = PA_SINK_INPUT(pa_queue_pop(q)))) { + pa_sink_input_fail_move(i); + pa_sink_input_unref(i); + } + + pa_queue_free(q, NULL); +} + + /* Called from IO thread context */ +size_t pa_sink_process_input_underruns(pa_sink *s, size_t left_to_play) { + pa_sink_input *i; + void *state = NULL; + size_t result = 0; + + pa_sink_assert_ref(s); + pa_sink_assert_io_context(s); + + PA_HASHMAP_FOREACH(i, s->thread_info.inputs, state) { + size_t uf = i->thread_info.underrun_for_sink; + + /* Propagate down the filter tree */ + if (i->origin_sink) { + size_t filter_result, left_to_play_origin; + + /* The recursive call works in the origin sink domain ... */ + left_to_play_origin = pa_convert_size(left_to_play, &i->sink->sample_spec, &i->origin_sink->sample_spec); + + /* .. and returns the time to sleep before waking up. We need the + * underrun duration for comparisons, so we undo the subtraction on + * the return value... */ + filter_result = left_to_play_origin - pa_sink_process_input_underruns(i->origin_sink, left_to_play_origin); + + /* ... and convert it back to the master sink domain */ + filter_result = pa_convert_size(filter_result, &i->origin_sink->sample_spec, &i->sink->sample_spec); + + /* Remember the longest underrun so far */ + if (filter_result > result) + result = filter_result; + } + + if (uf == 0) { + /* No underrun here, move on */ + continue; + } else if (uf >= left_to_play) { + /* The sink has possibly consumed all the data the sink input provided */ + pa_sink_input_process_underrun(i); + } else if (uf > result) { + /* Remember the longest underrun so far */ + result = uf; + } + } + + if (result > 0) + pa_log_debug("%s: Found underrun %ld bytes ago (%ld bytes ahead in playback buffer)", s->name, + (long) result, (long) left_to_play - result); + return left_to_play - result; +} + +/* Called from IO thread context */ +void pa_sink_process_rewind(pa_sink *s, size_t nbytes) { + pa_sink_input *i; + void *state = NULL; + + pa_sink_assert_ref(s); + pa_sink_assert_io_context(s); + pa_assert(PA_SINK_IS_LINKED(s->thread_info.state)); + + /* If nobody requested this and this is actually no real rewind + * then we can short cut this. Please note that this means that + * not all rewind requests triggered upstream will always be + * translated in actual requests! */ + if (!s->thread_info.rewind_requested && nbytes <= 0) + return; + + s->thread_info.rewind_nbytes = 0; + s->thread_info.rewind_requested = false; + + if (nbytes > 0) { + pa_log_debug("Processing rewind..."); + if (s->flags & PA_SINK_DEFERRED_VOLUME) + pa_sink_volume_change_rewind(s, nbytes); + } + + PA_HASHMAP_FOREACH(i, s->thread_info.inputs, state) { + pa_sink_input_assert_ref(i); + pa_sink_input_process_rewind(i, nbytes); + } + + if (nbytes > 0) { + if (s->monitor_source && PA_SOURCE_IS_LINKED(s->monitor_source->thread_info.state)) + pa_source_process_rewind(s->monitor_source, nbytes); + } +} + +/* Called from IO thread context */ +static unsigned fill_mix_info(pa_sink *s, size_t *length, pa_mix_info *info, unsigned maxinfo) { + pa_sink_input *i; + unsigned n = 0; + void *state = NULL; + size_t mixlength = *length; + + pa_sink_assert_ref(s); + pa_sink_assert_io_context(s); + pa_assert(info); + + while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL)) && maxinfo > 0) { + pa_sink_input_assert_ref(i); + + pa_sink_input_peek(i, *length, &info->chunk, &info->volume); + + if (mixlength == 0 || info->chunk.length < mixlength) + mixlength = info->chunk.length; + + if (pa_memblock_is_silence(info->chunk.memblock)) { + pa_memblock_unref(info->chunk.memblock); + continue; + } + + info->userdata = pa_sink_input_ref(i); + + pa_assert(info->chunk.memblock); + pa_assert(info->chunk.length > 0); + + info++; + n++; + maxinfo--; + } + + if (mixlength > 0) + *length = mixlength; + + return n; +} + +/* Called from IO thread context */ +static void inputs_drop(pa_sink *s, pa_mix_info *info, unsigned n, pa_memchunk *result) { + pa_sink_input *i; + void *state; + unsigned p = 0; + unsigned n_unreffed = 0; + + pa_sink_assert_ref(s); + pa_sink_assert_io_context(s); + pa_assert(result); + pa_assert(result->memblock); + pa_assert(result->length > 0); + + /* We optimize for the case where the order of the inputs has not changed */ + + PA_HASHMAP_FOREACH(i, s->thread_info.inputs, state) { + unsigned j; + pa_mix_info* m = NULL; + + pa_sink_input_assert_ref(i); + + /* Let's try to find the matching entry info the pa_mix_info array */ + for (j = 0; j < n; j ++) { + + if (info[p].userdata == i) { + m = info + p; + break; + } + + p++; + if (p >= n) + p = 0; + } + + /* Drop read data */ + pa_sink_input_drop(i, result->length); + + if (s->monitor_source && PA_SOURCE_IS_LINKED(s->monitor_source->thread_info.state)) { + + if (pa_hashmap_size(i->thread_info.direct_outputs) > 0) { + void *ostate = NULL; + pa_source_output *o; + pa_memchunk c; + + if (m && m->chunk.memblock) { + c = m->chunk; + pa_memblock_ref(c.memblock); + pa_assert(result->length <= c.length); + c.length = result->length; + + pa_memchunk_make_writable(&c, 0); + pa_volume_memchunk(&c, &s->sample_spec, &m->volume); + } else { + c = s->silence; + pa_memblock_ref(c.memblock); + pa_assert(result->length <= c.length); + c.length = result->length; + } + + while ((o = pa_hashmap_iterate(i->thread_info.direct_outputs, &ostate, NULL))) { + pa_source_output_assert_ref(o); + pa_assert(o->direct_on_input == i); + pa_source_post_direct(s->monitor_source, o, &c); + } + + pa_memblock_unref(c.memblock); + } + } + + if (m) { + if (m->chunk.memblock) { + pa_memblock_unref(m->chunk.memblock); + pa_memchunk_reset(&m->chunk); + } + + pa_sink_input_unref(m->userdata); + m->userdata = NULL; + + n_unreffed += 1; + } + } + + /* Now drop references to entries that are included in the + * pa_mix_info array but don't exist anymore */ + + if (n_unreffed < n) { + for (; n > 0; info++, n--) { + if (info->userdata) + pa_sink_input_unref(info->userdata); + if (info->chunk.memblock) + pa_memblock_unref(info->chunk.memblock); + } + } + + if (s->monitor_source && PA_SOURCE_IS_LINKED(s->monitor_source->thread_info.state)) + pa_source_post(s->monitor_source, result); +} + +/* Called from IO thread context */ +void pa_sink_render(pa_sink*s, size_t length, pa_memchunk *result) { + pa_mix_info info[MAX_MIX_CHANNELS]; + unsigned n; + size_t block_size_max; + + pa_sink_assert_ref(s); + pa_sink_assert_io_context(s); + pa_assert(PA_SINK_IS_LINKED(s->thread_info.state)); + pa_assert(pa_frame_aligned(length, &s->sample_spec)); + pa_assert(result); + + pa_assert(!s->thread_info.rewind_requested); + pa_assert(s->thread_info.rewind_nbytes == 0); + + if (s->thread_info.state == PA_SINK_SUSPENDED) { + result->memblock = pa_memblock_ref(s->silence.memblock); + result->index = s->silence.index; + result->length = PA_MIN(s->silence.length, length); + return; + } + + pa_sink_ref(s); + + if (length <= 0) + length = pa_frame_align(MIX_BUFFER_LENGTH, &s->sample_spec); + + block_size_max = pa_mempool_block_size_max(s->core->mempool); + if (length > block_size_max) + length = pa_frame_align(block_size_max, &s->sample_spec); + + pa_assert(length > 0); + + n = fill_mix_info(s, &length, info, MAX_MIX_CHANNELS); + + if (n == 0) { + + *result = s->silence; + pa_memblock_ref(result->memblock); + + if (result->length > length) + result->length = length; + + } else if (n == 1) { + pa_cvolume volume; + + *result = info[0].chunk; + pa_memblock_ref(result->memblock); + + if (result->length > length) + result->length = length; + + pa_sw_cvolume_multiply(&volume, &s->thread_info.soft_volume, &info[0].volume); + + if (s->thread_info.soft_muted || pa_cvolume_is_muted(&volume)) { + pa_memblock_unref(result->memblock); + pa_silence_memchunk_get(&s->core->silence_cache, + s->core->mempool, + result, + &s->sample_spec, + result->length); + } else if (!pa_cvolume_is_norm(&volume)) { + pa_memchunk_make_writable(result, 0); + pa_volume_memchunk(result, &s->sample_spec, &volume); + } + } else { + void *ptr; + result->memblock = pa_memblock_new(s->core->mempool, length); + + ptr = pa_memblock_acquire(result->memblock); + result->length = pa_mix(info, n, + ptr, length, + &s->sample_spec, + &s->thread_info.soft_volume, + s->thread_info.soft_muted); + pa_memblock_release(result->memblock); + + result->index = 0; + } + + inputs_drop(s, info, n, result); + + pa_sink_unref(s); +} + +/* Called from IO thread context */ +void pa_sink_render_into(pa_sink*s, pa_memchunk *target) { + pa_mix_info info[MAX_MIX_CHANNELS]; + unsigned n; + size_t length, block_size_max; + + pa_sink_assert_ref(s); + pa_sink_assert_io_context(s); + pa_assert(PA_SINK_IS_LINKED(s->thread_info.state)); + pa_assert(target); + pa_assert(target->memblock); + pa_assert(target->length > 0); + pa_assert(pa_frame_aligned(target->length, &s->sample_spec)); + + pa_assert(!s->thread_info.rewind_requested); + pa_assert(s->thread_info.rewind_nbytes == 0); + + if (s->thread_info.state == PA_SINK_SUSPENDED) { + pa_silence_memchunk(target, &s->sample_spec); + return; + } + + pa_sink_ref(s); + + length = target->length; + block_size_max = pa_mempool_block_size_max(s->core->mempool); + if (length > block_size_max) + length = pa_frame_align(block_size_max, &s->sample_spec); + + pa_assert(length > 0); + + n = fill_mix_info(s, &length, info, MAX_MIX_CHANNELS); + + if (n == 0) { + if (target->length > length) + target->length = length; + + pa_silence_memchunk(target, &s->sample_spec); + } else if (n == 1) { + pa_cvolume volume; + + if (target->length > length) + target->length = length; + + pa_sw_cvolume_multiply(&volume, &s->thread_info.soft_volume, &info[0].volume); + + if (s->thread_info.soft_muted || pa_cvolume_is_muted(&volume)) + pa_silence_memchunk(target, &s->sample_spec); + else { + pa_memchunk vchunk; + + vchunk = info[0].chunk; + pa_memblock_ref(vchunk.memblock); + + if (vchunk.length > length) + vchunk.length = length; + + if (!pa_cvolume_is_norm(&volume)) { + pa_memchunk_make_writable(&vchunk, 0); + pa_volume_memchunk(&vchunk, &s->sample_spec, &volume); + } + + pa_memchunk_memcpy(target, &vchunk); + pa_memblock_unref(vchunk.memblock); + } + + } else { + void *ptr; + + ptr = pa_memblock_acquire(target->memblock); + + target->length = pa_mix(info, n, + (uint8_t*) ptr + target->index, length, + &s->sample_spec, + &s->thread_info.soft_volume, + s->thread_info.soft_muted); + + pa_memblock_release(target->memblock); + } + + inputs_drop(s, info, n, target); + + pa_sink_unref(s); +} + +/* Called from IO thread context */ +void pa_sink_render_into_full(pa_sink *s, pa_memchunk *target) { + pa_memchunk chunk; + size_t l, d; + + pa_sink_assert_ref(s); + pa_sink_assert_io_context(s); + pa_assert(PA_SINK_IS_LINKED(s->thread_info.state)); + pa_assert(target); + pa_assert(target->memblock); + pa_assert(target->length > 0); + pa_assert(pa_frame_aligned(target->length, &s->sample_spec)); + + pa_assert(!s->thread_info.rewind_requested); + pa_assert(s->thread_info.rewind_nbytes == 0); + + if (s->thread_info.state == PA_SINK_SUSPENDED) { + pa_silence_memchunk(target, &s->sample_spec); + return; + } + + pa_sink_ref(s); + + l = target->length; + d = 0; + while (l > 0) { + chunk = *target; + chunk.index += d; + chunk.length -= d; + + pa_sink_render_into(s, &chunk); + + d += chunk.length; + l -= chunk.length; + } + + pa_sink_unref(s); +} + +/* Called from IO thread context */ +void pa_sink_render_full(pa_sink *s, size_t length, pa_memchunk *result) { + pa_sink_assert_ref(s); + pa_sink_assert_io_context(s); + pa_assert(PA_SINK_IS_LINKED(s->thread_info.state)); + pa_assert(length > 0); + pa_assert(pa_frame_aligned(length, &s->sample_spec)); + pa_assert(result); + + pa_assert(!s->thread_info.rewind_requested); + pa_assert(s->thread_info.rewind_nbytes == 0); + + pa_sink_ref(s); + + pa_sink_render(s, length, result); + + if (result->length < length) { + pa_memchunk chunk; + + pa_memchunk_make_writable(result, length); + + chunk.memblock = result->memblock; + chunk.index = result->index + result->length; + chunk.length = length - result->length; + + pa_sink_render_into_full(s, &chunk); + + result->length = length; + } + + pa_sink_unref(s); +} + +/* Called from main thread */ +void pa_sink_reconfigure(pa_sink *s, pa_sample_spec *spec, bool passthrough) { + pa_sample_spec desired_spec; + uint32_t default_rate = s->default_sample_rate; + uint32_t alternate_rate = s->alternate_sample_rate; + uint32_t idx; + pa_sink_input *i; + bool default_rate_is_usable = false; + bool alternate_rate_is_usable = false; + bool avoid_resampling = s->avoid_resampling; + + if (pa_sample_spec_equal(spec, &s->sample_spec)) + return; + + if (!s->reconfigure) + return; + + if (PA_UNLIKELY(default_rate == alternate_rate && !passthrough && !avoid_resampling)) { + pa_log_debug("Default and alternate sample rates are the same, so there is no point in switching."); + return; + } + + if (PA_SINK_IS_RUNNING(s->state)) { + pa_log_info("Cannot update sample spec, SINK_IS_RUNNING, will keep using %s and %u Hz", + pa_sample_format_to_string(s->sample_spec.format), s->sample_spec.rate); + return; + } + + if (s->monitor_source) { + if (PA_SOURCE_IS_RUNNING(s->monitor_source->state) == true) { + pa_log_info("Cannot update sample spec, monitor source is RUNNING"); + return; + } + } + + if (PA_UNLIKELY(!pa_sample_spec_valid(spec))) + return; + + desired_spec = s->sample_spec; + + if (passthrough) { + /* We have to try to use the sink input format and rate */ + desired_spec.format = spec->format; + desired_spec.rate = spec->rate; + + } else if (avoid_resampling) { + /* We just try to set the sink input's sample rate if it's not too low */ + if (spec->rate >= default_rate || spec->rate >= alternate_rate) + desired_spec.rate = spec->rate; + desired_spec.format = spec->format; + + } else if (default_rate == spec->rate || alternate_rate == spec->rate) { + /* We can directly try to use this rate */ + desired_spec.rate = spec->rate; + + } + + if (desired_spec.rate != spec->rate) { + /* See if we can pick a rate that results in less resampling effort */ + if (default_rate % 11025 == 0 && spec->rate % 11025 == 0) + default_rate_is_usable = true; + if (default_rate % 4000 == 0 && spec->rate % 4000 == 0) + default_rate_is_usable = true; + if (alternate_rate % 11025 == 0 && spec->rate % 11025 == 0) + alternate_rate_is_usable = true; + if (alternate_rate % 4000 == 0 && spec->rate % 4000 == 0) + alternate_rate_is_usable = true; + + if (alternate_rate_is_usable && !default_rate_is_usable) + desired_spec.rate = alternate_rate; + else + desired_spec.rate = default_rate; + } + + if (pa_sample_spec_equal(&desired_spec, &s->sample_spec) && passthrough == pa_sink_is_passthrough(s)) + return; + + if (!passthrough && pa_sink_used_by(s) > 0) + return; + + pa_log_debug("Suspending sink %s due to changing format, desired format = %s rate = %u", + s->name, pa_sample_format_to_string(desired_spec.format), desired_spec.rate); + pa_sink_suspend(s, true, PA_SUSPEND_INTERNAL); + + s->reconfigure(s, &desired_spec, passthrough); + + /* update monitor source as well */ + if (s->monitor_source && !passthrough) + pa_source_reconfigure(s->monitor_source, &s->sample_spec, false); + pa_log_info("Reconfigured successfully"); + + PA_IDXSET_FOREACH(i, s->inputs, idx) { + if (i->state == PA_SINK_INPUT_CORKED) + pa_sink_input_update_resampler(i); + } + + pa_sink_suspend(s, false, PA_SUSPEND_INTERNAL); +} + +/* Called from main thread */ +pa_usec_t pa_sink_get_latency(pa_sink *s) { + int64_t usec = 0; + + pa_sink_assert_ref(s); + pa_assert_ctl_context(); + pa_assert(PA_SINK_IS_LINKED(s->state)); + + /* The returned value is supposed to be in the time domain of the sound card! */ + + if (s->state == PA_SINK_SUSPENDED) + return 0; + + if (!(s->flags & PA_SINK_LATENCY)) + return 0; + + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL) == 0); + + /* the return value is unsigned, so check that the offset can be added to usec without + * underflowing. */ + if (-s->port_latency_offset <= usec) + usec += s->port_latency_offset; + else + usec = 0; + + return (pa_usec_t)usec; +} + +/* Called from IO thread */ +int64_t pa_sink_get_latency_within_thread(pa_sink *s, bool allow_negative) { + int64_t usec = 0; + pa_msgobject *o; + + pa_sink_assert_ref(s); + pa_sink_assert_io_context(s); + pa_assert(PA_SINK_IS_LINKED(s->thread_info.state)); + + /* The returned value is supposed to be in the time domain of the sound card! */ + + if (s->thread_info.state == PA_SINK_SUSPENDED) + return 0; + + if (!(s->flags & PA_SINK_LATENCY)) + return 0; + + o = PA_MSGOBJECT(s); + + /* FIXME: We probably should make this a proper vtable callback instead of going through process_msg() */ + + o->process_msg(o, PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL); + + /* If allow_negative is false, the call should only return positive values, */ + usec += s->thread_info.port_latency_offset; + if (!allow_negative && usec < 0) + usec = 0; + + return usec; +} + +/* Called from the main thread (and also from the IO thread while the main + * thread is waiting). + * + * When a sink uses volume sharing, it never has the PA_SINK_FLAT_VOLUME flag + * set. Instead, flat volume mode is detected by checking whether the root sink + * has the flag set. */ +bool pa_sink_flat_volume_enabled(pa_sink *s) { + pa_sink_assert_ref(s); + + s = pa_sink_get_master(s); + + if (PA_LIKELY(s)) + return (s->flags & PA_SINK_FLAT_VOLUME); + else + return false; +} + +/* Called from the main thread (and also from the IO thread while the main + * thread is waiting). */ +pa_sink *pa_sink_get_master(pa_sink *s) { + pa_sink_assert_ref(s); + + while (s && (s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)) { + if (PA_UNLIKELY(!s->input_to_master)) + return NULL; + + s = s->input_to_master->sink; + } + + return s; +} + +/* Called from main context */ +bool pa_sink_is_filter(pa_sink *s) { + pa_sink_assert_ref(s); + + return (s->input_to_master != NULL); +} + +/* Called from main context */ +bool pa_sink_is_passthrough(pa_sink *s) { + pa_sink_input *alt_i; + uint32_t idx; + + pa_sink_assert_ref(s); + + /* one and only one PASSTHROUGH input can possibly be connected */ + if (pa_idxset_size(s->inputs) == 1) { + alt_i = pa_idxset_first(s->inputs, &idx); + + if (pa_sink_input_is_passthrough(alt_i)) + return true; + } + + return false; +} + +/* Called from main context */ +void pa_sink_enter_passthrough(pa_sink *s) { + pa_cvolume volume; + + /* The sink implementation is reconfigured for passthrough in + * pa_sink_reconfigure(). This function sets the PA core objects to + * passthrough mode. */ + + /* disable the monitor in passthrough mode */ + if (s->monitor_source) { + pa_log_debug("Suspending monitor source %s, because the sink is entering the passthrough mode.", s->monitor_source->name); + pa_source_suspend(s->monitor_source, true, PA_SUSPEND_PASSTHROUGH); + } + + /* set the volume to NORM */ + s->saved_volume = *pa_sink_get_volume(s, true); + s->saved_save_volume = s->save_volume; + + pa_cvolume_set(&volume, s->sample_spec.channels, PA_MIN(s->base_volume, PA_VOLUME_NORM)); + pa_sink_set_volume(s, &volume, true, false); + + pa_log_debug("Suspending/Restarting sink %s to enter passthrough mode", s->name); +} + +/* Called from main context */ +void pa_sink_leave_passthrough(pa_sink *s) { + /* Unsuspend monitor */ + if (s->monitor_source) { + pa_log_debug("Resuming monitor source %s, because the sink is leaving the passthrough mode.", s->monitor_source->name); + pa_source_suspend(s->monitor_source, false, PA_SUSPEND_PASSTHROUGH); + } + + /* Restore sink volume to what it was before we entered passthrough mode */ + pa_sink_set_volume(s, &s->saved_volume, true, s->saved_save_volume); + + pa_cvolume_init(&s->saved_volume); + s->saved_save_volume = false; + +} + +/* Called from main context. */ +static void compute_reference_ratio(pa_sink_input *i) { + unsigned c = 0; + pa_cvolume remapped; + pa_cvolume ratio; + + pa_assert(i); + pa_assert(pa_sink_flat_volume_enabled(i->sink)); + + /* + * Calculates the reference ratio from the sink's reference + * volume. This basically calculates: + * + * i->reference_ratio = i->volume / i->sink->reference_volume + */ + + remapped = i->sink->reference_volume; + pa_cvolume_remap(&remapped, &i->sink->channel_map, &i->channel_map); + + ratio = i->reference_ratio; + + for (c = 0; c < i->sample_spec.channels; c++) { + + /* We don't update when the sink volume is 0 anyway */ + if (remapped.values[c] <= PA_VOLUME_MUTED) + continue; + + /* Don't update the reference ratio unless necessary */ + if (pa_sw_volume_multiply( + ratio.values[c], + remapped.values[c]) == i->volume.values[c]) + continue; + + ratio.values[c] = pa_sw_volume_divide( + i->volume.values[c], + remapped.values[c]); + } + + pa_sink_input_set_reference_ratio(i, &ratio); +} + +/* Called from main context. Only called for the root sink in volume sharing + * cases, except for internal recursive calls. */ +static void compute_reference_ratios(pa_sink *s) { + uint32_t idx; + pa_sink_input *i; + + pa_sink_assert_ref(s); + pa_assert_ctl_context(); + pa_assert(PA_SINK_IS_LINKED(s->state)); + pa_assert(pa_sink_flat_volume_enabled(s)); + + PA_IDXSET_FOREACH(i, s->inputs, idx) { + compute_reference_ratio(i); + + if (i->origin_sink && (i->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER) + && PA_SINK_IS_LINKED(i->origin_sink->state)) + compute_reference_ratios(i->origin_sink); + } +} + +/* Called from main context. Only called for the root sink in volume sharing + * cases, except for internal recursive calls. */ +static void compute_real_ratios(pa_sink *s) { + pa_sink_input *i; + uint32_t idx; + + pa_sink_assert_ref(s); + pa_assert_ctl_context(); + pa_assert(PA_SINK_IS_LINKED(s->state)); + pa_assert(pa_sink_flat_volume_enabled(s)); + + PA_IDXSET_FOREACH(i, s->inputs, idx) { + unsigned c; + pa_cvolume remapped; + + if (i->origin_sink && (i->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)) { + /* The origin sink uses volume sharing, so this input's real ratio + * is handled as a special case - the real ratio must be 0 dB, and + * as a result i->soft_volume must equal i->volume_factor. */ + pa_cvolume_reset(&i->real_ratio, i->real_ratio.channels); + i->soft_volume = i->volume_factor; + + if (PA_SINK_IS_LINKED(i->origin_sink->state)) + compute_real_ratios(i->origin_sink); + + continue; + } + + /* + * This basically calculates: + * + * i->real_ratio := i->volume / s->real_volume + * i->soft_volume := i->real_ratio * i->volume_factor + */ + + remapped = s->real_volume; + pa_cvolume_remap(&remapped, &s->channel_map, &i->channel_map); + + i->real_ratio.channels = i->sample_spec.channels; + i->soft_volume.channels = i->sample_spec.channels; + + for (c = 0; c < i->sample_spec.channels; c++) { + + if (remapped.values[c] <= PA_VOLUME_MUTED) { + /* We leave i->real_ratio untouched */ + i->soft_volume.values[c] = PA_VOLUME_MUTED; + continue; + } + + /* Don't lose accuracy unless necessary */ + if (pa_sw_volume_multiply( + i->real_ratio.values[c], + remapped.values[c]) != i->volume.values[c]) + + i->real_ratio.values[c] = pa_sw_volume_divide( + i->volume.values[c], + remapped.values[c]); + + i->soft_volume.values[c] = pa_sw_volume_multiply( + i->real_ratio.values[c], + i->volume_factor.values[c]); + } + + /* We don't copy the soft_volume to the thread_info data + * here. That must be done by the caller */ + } +} + +static pa_cvolume *cvolume_remap_minimal_impact( + pa_cvolume *v, + const pa_cvolume *template, + const pa_channel_map *from, + const pa_channel_map *to) { + + pa_cvolume t; + + pa_assert(v); + pa_assert(template); + pa_assert(from); + pa_assert(to); + pa_assert(pa_cvolume_compatible_with_channel_map(v, from)); + pa_assert(pa_cvolume_compatible_with_channel_map(template, to)); + + /* Much like pa_cvolume_remap(), but tries to minimize impact when + * mapping from sink input to sink volumes: + * + * If template is a possible remapping from v it is used instead + * of remapping anew. + * + * If the channel maps don't match we set an all-channel volume on + * the sink to ensure that changing a volume on one stream has no + * effect that cannot be compensated for in another stream that + * does not have the same channel map as the sink. */ + + if (pa_channel_map_equal(from, to)) + return v; + + t = *template; + if (pa_cvolume_equal(pa_cvolume_remap(&t, to, from), v)) { + *v = *template; + return v; + } + + pa_cvolume_set(v, to->channels, pa_cvolume_max(v)); + return v; +} + +/* Called from main thread. Only called for the root sink in volume sharing + * cases, except for internal recursive calls. */ +static void get_maximum_input_volume(pa_sink *s, pa_cvolume *max_volume, const pa_channel_map *channel_map) { + pa_sink_input *i; + uint32_t idx; + + pa_sink_assert_ref(s); + pa_assert(max_volume); + pa_assert(channel_map); + pa_assert(pa_sink_flat_volume_enabled(s)); + + PA_IDXSET_FOREACH(i, s->inputs, idx) { + pa_cvolume remapped; + + if (i->origin_sink && (i->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)) { + if (PA_SINK_IS_LINKED(i->origin_sink->state)) + get_maximum_input_volume(i->origin_sink, max_volume, channel_map); + + /* Ignore this input. The origin sink uses volume sharing, so this + * input's volume will be set to be equal to the root sink's real + * volume. Obviously this input's current volume must not then + * affect what the root sink's real volume will be. */ + continue; + } + + remapped = i->volume; + cvolume_remap_minimal_impact(&remapped, max_volume, &i->channel_map, channel_map); + pa_cvolume_merge(max_volume, max_volume, &remapped); + } +} + +/* Called from main thread. Only called for the root sink in volume sharing + * cases, except for internal recursive calls. */ +static bool has_inputs(pa_sink *s) { + pa_sink_input *i; + uint32_t idx; + + pa_sink_assert_ref(s); + + PA_IDXSET_FOREACH(i, s->inputs, idx) { + if (!i->origin_sink || !(i->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER) || has_inputs(i->origin_sink)) + return true; + } + + return false; +} + +/* Called from main thread. Only called for the root sink in volume sharing + * cases, except for internal recursive calls. */ +static void update_real_volume(pa_sink *s, const pa_cvolume *new_volume, pa_channel_map *channel_map) { + pa_sink_input *i; + uint32_t idx; + + pa_sink_assert_ref(s); + pa_assert(new_volume); + pa_assert(channel_map); + + s->real_volume = *new_volume; + pa_cvolume_remap(&s->real_volume, channel_map, &s->channel_map); + + PA_IDXSET_FOREACH(i, s->inputs, idx) { + if (i->origin_sink && (i->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)) { + if (pa_sink_flat_volume_enabled(s)) { + pa_cvolume new_input_volume; + + /* Follow the root sink's real volume. */ + new_input_volume = *new_volume; + pa_cvolume_remap(&new_input_volume, channel_map, &i->channel_map); + pa_sink_input_set_volume_direct(i, &new_input_volume); + compute_reference_ratio(i); + } + + if (PA_SINK_IS_LINKED(i->origin_sink->state)) + update_real_volume(i->origin_sink, new_volume, channel_map); + } + } +} + +/* Called from main thread. Only called for the root sink in shared volume + * cases. */ +static void compute_real_volume(pa_sink *s) { + pa_sink_assert_ref(s); + pa_assert_ctl_context(); + pa_assert(PA_SINK_IS_LINKED(s->state)); + pa_assert(pa_sink_flat_volume_enabled(s)); + pa_assert(!(s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)); + + /* This determines the maximum volume of all streams and sets + * s->real_volume accordingly. */ + + if (!has_inputs(s)) { + /* In the special case that we have no sink inputs we leave the + * volume unmodified. */ + update_real_volume(s, &s->reference_volume, &s->channel_map); + return; + } + + pa_cvolume_mute(&s->real_volume, s->channel_map.channels); + + /* First let's determine the new maximum volume of all inputs + * connected to this sink */ + get_maximum_input_volume(s, &s->real_volume, &s->channel_map); + update_real_volume(s, &s->real_volume, &s->channel_map); + + /* Then, let's update the real ratios/soft volumes of all inputs + * connected to this sink */ + compute_real_ratios(s); +} + +/* Called from main thread. Only called for the root sink in shared volume + * cases, except for internal recursive calls. */ +static void propagate_reference_volume(pa_sink *s) { + pa_sink_input *i; + uint32_t idx; + + pa_sink_assert_ref(s); + pa_assert_ctl_context(); + pa_assert(PA_SINK_IS_LINKED(s->state)); + pa_assert(pa_sink_flat_volume_enabled(s)); + + /* This is called whenever the sink volume changes that is not + * caused by a sink input volume change. We need to fix up the + * sink input volumes accordingly */ + + PA_IDXSET_FOREACH(i, s->inputs, idx) { + pa_cvolume new_volume; + + if (i->origin_sink && (i->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)) { + if (PA_SINK_IS_LINKED(i->origin_sink->state)) + propagate_reference_volume(i->origin_sink); + + /* Since the origin sink uses volume sharing, this input's volume + * needs to be updated to match the root sink's real volume, but + * that will be done later in update_real_volume(). */ + continue; + } + + /* This basically calculates: + * + * i->volume := s->reference_volume * i->reference_ratio */ + + new_volume = s->reference_volume; + pa_cvolume_remap(&new_volume, &s->channel_map, &i->channel_map); + pa_sw_cvolume_multiply(&new_volume, &new_volume, &i->reference_ratio); + pa_sink_input_set_volume_direct(i, &new_volume); + } +} + +/* Called from main thread. Only called for the root sink in volume sharing + * cases, except for internal recursive calls. The return value indicates + * whether any reference volume actually changed. */ +static bool update_reference_volume(pa_sink *s, const pa_cvolume *v, const pa_channel_map *channel_map, bool save) { + pa_cvolume volume; + bool reference_volume_changed; + pa_sink_input *i; + uint32_t idx; + + pa_sink_assert_ref(s); + pa_assert(PA_SINK_IS_LINKED(s->state)); + pa_assert(v); + pa_assert(channel_map); + pa_assert(pa_cvolume_valid(v)); + + volume = *v; + pa_cvolume_remap(&volume, channel_map, &s->channel_map); + + reference_volume_changed = !pa_cvolume_equal(&volume, &s->reference_volume); + pa_sink_set_reference_volume_direct(s, &volume); + + s->save_volume = (!reference_volume_changed && s->save_volume) || save; + + if (!reference_volume_changed && !(s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)) + /* If the root sink's volume doesn't change, then there can't be any + * changes in the other sinks in the sink tree either. + * + * It's probably theoretically possible that even if the root sink's + * volume changes slightly, some filter sink doesn't change its volume + * due to rounding errors. If that happens, we still want to propagate + * the changed root sink volume to the sinks connected to the + * intermediate sink that didn't change its volume. This theoretical + * possibility is the reason why we have that !(s->flags & + * PA_SINK_SHARE_VOLUME_WITH_MASTER) condition. Probably nobody would + * notice even if we returned here false always if + * reference_volume_changed is false. */ + return false; + + PA_IDXSET_FOREACH(i, s->inputs, idx) { + if (i->origin_sink && (i->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER) + && PA_SINK_IS_LINKED(i->origin_sink->state)) + update_reference_volume(i->origin_sink, v, channel_map, false); + } + + return true; +} + +/* Called from main thread */ +void pa_sink_set_volume( + pa_sink *s, + const pa_cvolume *volume, + bool send_msg, + bool save) { + + pa_cvolume new_reference_volume; + pa_sink *root_sink; + + pa_sink_assert_ref(s); + pa_assert_ctl_context(); + pa_assert(PA_SINK_IS_LINKED(s->state)); + pa_assert(!volume || pa_cvolume_valid(volume)); + pa_assert(volume || pa_sink_flat_volume_enabled(s)); + pa_assert(!volume || volume->channels == 1 || pa_cvolume_compatible(volume, &s->sample_spec)); + + /* make sure we don't change the volume when a PASSTHROUGH input is connected ... + * ... *except* if we're being invoked to reset the volume to ensure 0 dB gain */ + if (pa_sink_is_passthrough(s) && (!volume || !pa_cvolume_is_norm(volume))) { + pa_log_warn("Cannot change volume, Sink is connected to PASSTHROUGH input"); + return; + } + + /* In case of volume sharing, the volume is set for the root sink first, + * from which it's then propagated to the sharing sinks. */ + root_sink = pa_sink_get_master(s); + + if (PA_UNLIKELY(!root_sink)) + return; + + /* As a special exception we accept mono volumes on all sinks -- + * even on those with more complex channel maps */ + + if (volume) { + if (pa_cvolume_compatible(volume, &s->sample_spec)) + new_reference_volume = *volume; + else { + new_reference_volume = s->reference_volume; + pa_cvolume_scale(&new_reference_volume, pa_cvolume_max(volume)); + } + + pa_cvolume_remap(&new_reference_volume, &s->channel_map, &root_sink->channel_map); + + if (update_reference_volume(root_sink, &new_reference_volume, &root_sink->channel_map, save)) { + if (pa_sink_flat_volume_enabled(root_sink)) { + /* OK, propagate this volume change back to the inputs */ + propagate_reference_volume(root_sink); + + /* And now recalculate the real volume */ + compute_real_volume(root_sink); + } else + update_real_volume(root_sink, &root_sink->reference_volume, &root_sink->channel_map); + } + + } else { + /* If volume is NULL we synchronize the sink's real and + * reference volumes with the stream volumes. */ + + pa_assert(pa_sink_flat_volume_enabled(root_sink)); + + /* Ok, let's determine the new real volume */ + compute_real_volume(root_sink); + + /* Let's 'push' the reference volume if necessary */ + pa_cvolume_merge(&new_reference_volume, &s->reference_volume, &root_sink->real_volume); + /* If the sink and its root don't have the same number of channels, we need to remap */ + if (s != root_sink && !pa_channel_map_equal(&s->channel_map, &root_sink->channel_map)) + pa_cvolume_remap(&new_reference_volume, &s->channel_map, &root_sink->channel_map); + update_reference_volume(root_sink, &new_reference_volume, &root_sink->channel_map, save); + + /* Now that the reference volume is updated, we can update the streams' + * reference ratios. */ + compute_reference_ratios(root_sink); + } + + if (root_sink->set_volume) { + /* If we have a function set_volume(), then we do not apply a + * soft volume by default. However, set_volume() is free to + * apply one to root_sink->soft_volume */ + + pa_cvolume_reset(&root_sink->soft_volume, root_sink->sample_spec.channels); + if (!(root_sink->flags & PA_SINK_DEFERRED_VOLUME)) + root_sink->set_volume(root_sink); + + } else + /* If we have no function set_volume(), then the soft volume + * becomes the real volume */ + root_sink->soft_volume = root_sink->real_volume; + + /* This tells the sink that soft volume and/or real volume changed */ + if (send_msg) + pa_assert_se(pa_asyncmsgq_send(root_sink->asyncmsgq, PA_MSGOBJECT(root_sink), PA_SINK_MESSAGE_SET_SHARED_VOLUME, NULL, 0, NULL) == 0); +} + +/* Called from the io thread if sync volume is used, otherwise from the main thread. + * Only to be called by sink implementor */ +void pa_sink_set_soft_volume(pa_sink *s, const pa_cvolume *volume) { + + pa_sink_assert_ref(s); + pa_assert(!(s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)); + + if (s->flags & PA_SINK_DEFERRED_VOLUME) + pa_sink_assert_io_context(s); + else + pa_assert_ctl_context(); + + if (!volume) + pa_cvolume_reset(&s->soft_volume, s->sample_spec.channels); + else + s->soft_volume = *volume; + + if (PA_SINK_IS_LINKED(s->state) && !(s->flags & PA_SINK_DEFERRED_VOLUME)) + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_VOLUME, NULL, 0, NULL) == 0); + else + s->thread_info.soft_volume = s->soft_volume; +} + +/* Called from the main thread. Only called for the root sink in volume sharing + * cases, except for internal recursive calls. */ +static void propagate_real_volume(pa_sink *s, const pa_cvolume *old_real_volume) { + pa_sink_input *i; + uint32_t idx; + + pa_sink_assert_ref(s); + pa_assert(old_real_volume); + pa_assert_ctl_context(); + pa_assert(PA_SINK_IS_LINKED(s->state)); + + /* This is called when the hardware's real volume changes due to + * some external event. We copy the real volume into our + * reference volume and then rebuild the stream volumes based on + * i->real_ratio which should stay fixed. */ + + if (!(s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)) { + if (pa_cvolume_equal(old_real_volume, &s->real_volume)) + return; + + /* 1. Make the real volume the reference volume */ + update_reference_volume(s, &s->real_volume, &s->channel_map, true); + } + + if (pa_sink_flat_volume_enabled(s)) { + + PA_IDXSET_FOREACH(i, s->inputs, idx) { + pa_cvolume new_volume; + + /* 2. Since the sink's reference and real volumes are equal + * now our ratios should be too. */ + pa_sink_input_set_reference_ratio(i, &i->real_ratio); + + /* 3. Recalculate the new stream reference volume based on the + * reference ratio and the sink's reference volume. + * + * This basically calculates: + * + * i->volume = s->reference_volume * i->reference_ratio + * + * This is identical to propagate_reference_volume() */ + new_volume = s->reference_volume; + pa_cvolume_remap(&new_volume, &s->channel_map, &i->channel_map); + pa_sw_cvolume_multiply(&new_volume, &new_volume, &i->reference_ratio); + pa_sink_input_set_volume_direct(i, &new_volume); + + if (i->origin_sink && (i->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER) + && PA_SINK_IS_LINKED(i->origin_sink->state)) + propagate_real_volume(i->origin_sink, old_real_volume); + } + } + + /* Something got changed in the hardware. It probably makes sense + * to save changed hw settings given that hw volume changes not + * triggered by PA are almost certainly done by the user. */ + if (!(s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)) + s->save_volume = true; +} + +/* Called from io thread */ +void pa_sink_update_volume_and_mute(pa_sink *s) { + pa_assert(s); + pa_sink_assert_io_context(s); + + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_UPDATE_VOLUME_AND_MUTE, NULL, 0, NULL, NULL); +} + +/* Called from main thread */ +const pa_cvolume *pa_sink_get_volume(pa_sink *s, bool force_refresh) { + pa_sink_assert_ref(s); + pa_assert_ctl_context(); + pa_assert(PA_SINK_IS_LINKED(s->state)); + + if (s->refresh_volume || force_refresh) { + struct pa_cvolume old_real_volume; + + pa_assert(!(s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)); + + old_real_volume = s->real_volume; + + if (!(s->flags & PA_SINK_DEFERRED_VOLUME) && s->get_volume) + s->get_volume(s); + + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_VOLUME, NULL, 0, NULL) == 0); + + update_real_volume(s, &s->real_volume, &s->channel_map); + propagate_real_volume(s, &old_real_volume); + } + + return &s->reference_volume; +} + +/* Called from main thread. In volume sharing cases, only the root sink may + * call this. */ +void pa_sink_volume_changed(pa_sink *s, const pa_cvolume *new_real_volume) { + pa_cvolume old_real_volume; + + pa_sink_assert_ref(s); + pa_assert_ctl_context(); + pa_assert(PA_SINK_IS_LINKED(s->state)); + pa_assert(!(s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)); + + /* The sink implementor may call this if the volume changed to make sure everyone is notified */ + + old_real_volume = s->real_volume; + update_real_volume(s, new_real_volume, &s->channel_map); + propagate_real_volume(s, &old_real_volume); +} + +/* Called from main thread */ +void pa_sink_set_mute(pa_sink *s, bool mute, bool save) { + bool old_muted; + + pa_sink_assert_ref(s); + pa_assert_ctl_context(); + + old_muted = s->muted; + + if (mute == old_muted) { + s->save_muted |= save; + return; + } + + s->muted = mute; + s->save_muted = save; + + if (!(s->flags & PA_SINK_DEFERRED_VOLUME) && s->set_mute) { + s->set_mute_in_progress = true; + s->set_mute(s); + s->set_mute_in_progress = false; + } + + if (!PA_SINK_IS_LINKED(s->state)) + return; + + pa_log_debug("The mute of sink %s changed from %s to %s.", s->name, pa_yes_no(old_muted), pa_yes_no(mute)); + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_MUTE, NULL, 0, NULL) == 0); + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); + pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_MUTE_CHANGED], s); +} + +/* Called from main thread */ +bool pa_sink_get_mute(pa_sink *s, bool force_refresh) { + + pa_sink_assert_ref(s); + pa_assert_ctl_context(); + pa_assert(PA_SINK_IS_LINKED(s->state)); + + if ((s->refresh_muted || force_refresh) && s->get_mute) { + bool mute; + + if (s->flags & PA_SINK_DEFERRED_VOLUME) { + if (pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_MUTE, &mute, 0, NULL) >= 0) + pa_sink_mute_changed(s, mute); + } else { + if (s->get_mute(s, &mute) >= 0) + pa_sink_mute_changed(s, mute); + } + } + + return s->muted; +} + +/* Called from main thread */ +void pa_sink_mute_changed(pa_sink *s, bool new_muted) { + pa_sink_assert_ref(s); + pa_assert_ctl_context(); + pa_assert(PA_SINK_IS_LINKED(s->state)); + + if (s->set_mute_in_progress) + return; + + /* pa_sink_set_mute() does this same check, so this may appear redundant, + * but we must have this here also, because the save parameter of + * pa_sink_set_mute() would otherwise have unintended side effects (saving + * the mute state when it shouldn't be saved). */ + if (new_muted == s->muted) + return; + + pa_sink_set_mute(s, new_muted, true); +} + +/* Called from main thread */ +bool pa_sink_update_proplist(pa_sink *s, pa_update_mode_t mode, pa_proplist *p) { + pa_sink_assert_ref(s); + pa_assert_ctl_context(); + + if (p) + pa_proplist_update(s->proplist, mode, p); + + if (PA_SINK_IS_LINKED(s->state)) { + pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_PROPLIST_CHANGED], s); + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); + } + + return true; +} + +/* Called from main thread */ +/* FIXME -- this should be dropped and be merged into pa_sink_update_proplist() */ +void pa_sink_set_description(pa_sink *s, const char *description) { + const char *old; + pa_sink_assert_ref(s); + pa_assert_ctl_context(); + + if (!description && !pa_proplist_contains(s->proplist, PA_PROP_DEVICE_DESCRIPTION)) + return; + + old = pa_proplist_gets(s->proplist, PA_PROP_DEVICE_DESCRIPTION); + + if (old && description && pa_streq(old, description)) + return; + + if (description) + pa_proplist_sets(s->proplist, PA_PROP_DEVICE_DESCRIPTION, description); + else + pa_proplist_unset(s->proplist, PA_PROP_DEVICE_DESCRIPTION); + + if (s->monitor_source) { + char *n; + + n = pa_sprintf_malloc("Monitor Source of %s", description ? description : s->name); + pa_source_set_description(s->monitor_source, n); + pa_xfree(n); + } + + if (PA_SINK_IS_LINKED(s->state)) { + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); + pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_PROPLIST_CHANGED], s); + } +} + +/* Called from main thread */ +unsigned pa_sink_linked_by(pa_sink *s) { + unsigned ret; + + pa_sink_assert_ref(s); + pa_assert_ctl_context(); + pa_assert(PA_SINK_IS_LINKED(s->state)); + + ret = pa_idxset_size(s->inputs); + + /* We add in the number of streams connected to us here. Please + * note the asymmetry to pa_sink_used_by()! */ + + if (s->monitor_source) + ret += pa_source_linked_by(s->monitor_source); + + return ret; +} + +/* Called from main thread */ +unsigned pa_sink_used_by(pa_sink *s) { + unsigned ret; + + pa_sink_assert_ref(s); + pa_assert_ctl_context(); + pa_assert(PA_SINK_IS_LINKED(s->state)); + + ret = pa_idxset_size(s->inputs); + pa_assert(ret >= s->n_corked); + + /* Streams connected to our monitor source do not matter for + * pa_sink_used_by()!.*/ + + return ret - s->n_corked; +} + +/* Called from main thread */ +unsigned pa_sink_check_suspend(pa_sink *s, pa_sink_input *ignore_input, pa_source_output *ignore_output) { + unsigned ret; + pa_sink_input *i; + uint32_t idx; + + pa_sink_assert_ref(s); + pa_assert_ctl_context(); + + if (!PA_SINK_IS_LINKED(s->state)) + return 0; + + ret = 0; + + PA_IDXSET_FOREACH(i, s->inputs, idx) { + if (i == ignore_input) + continue; + + /* We do not assert here. It is perfectly valid for a sink input to + * be in the INIT state (i.e. created, marked done but not yet put) + * and we should not care if it's unlinked as it won't contribute + * towards our busy status. + */ + if (!PA_SINK_INPUT_IS_LINKED(i->state)) + continue; + + if (i->state == PA_SINK_INPUT_CORKED) + continue; + + if (i->flags & PA_SINK_INPUT_DONT_INHIBIT_AUTO_SUSPEND) + continue; + + ret ++; + } + + if (s->monitor_source) + ret += pa_source_check_suspend(s->monitor_source, ignore_output); + + return ret; +} + +const char *pa_sink_state_to_string(pa_sink_state_t state) { + switch (state) { + case PA_SINK_INIT: return "INIT"; + case PA_SINK_IDLE: return "IDLE"; + case PA_SINK_RUNNING: return "RUNNING"; + case PA_SINK_SUSPENDED: return "SUSPENDED"; + case PA_SINK_UNLINKED: return "UNLINKED"; + case PA_SINK_INVALID_STATE: return "INVALID_STATE"; + } + + pa_assert_not_reached(); +} + +/* Called from the IO thread */ +static void sync_input_volumes_within_thread(pa_sink *s) { + pa_sink_input *i; + void *state = NULL; + + pa_sink_assert_ref(s); + pa_sink_assert_io_context(s); + + PA_HASHMAP_FOREACH(i, s->thread_info.inputs, state) { + if (pa_cvolume_equal(&i->thread_info.soft_volume, &i->soft_volume)) + continue; + + i->thread_info.soft_volume = i->soft_volume; + pa_sink_input_request_rewind(i, 0, true, false, false); + } +} + +/* Called from the IO thread. Only called for the root sink in volume sharing + * cases, except for internal recursive calls. */ +static void set_shared_volume_within_thread(pa_sink *s) { + pa_sink_input *i = NULL; + void *state = NULL; + + pa_sink_assert_ref(s); + + PA_MSGOBJECT(s)->process_msg(PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_VOLUME_SYNCED, NULL, 0, NULL); + + PA_HASHMAP_FOREACH(i, s->thread_info.inputs, state) { + if (i->origin_sink && (i->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)) + set_shared_volume_within_thread(i->origin_sink); + } +} + +/* Called from IO thread, except when it is not */ +int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk) { + pa_sink *s = PA_SINK(o); + pa_sink_assert_ref(s); + + switch ((pa_sink_message_t) code) { + + case PA_SINK_MESSAGE_ADD_INPUT: { + pa_sink_input *i = PA_SINK_INPUT(userdata); + + /* If you change anything here, make sure to change the + * sink input handling a few lines down at + * PA_SINK_MESSAGE_FINISH_MOVE, too. */ + + pa_hashmap_put(s->thread_info.inputs, PA_UINT32_TO_PTR(i->index), pa_sink_input_ref(i)); + + /* Since the caller sleeps in pa_sink_input_put(), we can + * safely access data outside of thread_info even though + * it is mutable */ + + if ((i->thread_info.sync_prev = i->sync_prev)) { + pa_assert(i->sink == i->thread_info.sync_prev->sink); + pa_assert(i->sync_prev->sync_next == i); + i->thread_info.sync_prev->thread_info.sync_next = i; + } + + if ((i->thread_info.sync_next = i->sync_next)) { + pa_assert(i->sink == i->thread_info.sync_next->sink); + pa_assert(i->sync_next->sync_prev == i); + i->thread_info.sync_next->thread_info.sync_prev = i; + } + + pa_sink_input_attach(i); + + pa_sink_input_set_state_within_thread(i, i->state); + + /* The requested latency of the sink input needs to be fixed up and + * then configured on the sink. If this causes the sink latency to + * go down, the sink implementor is responsible for doing a rewind + * in the update_requested_latency() callback to ensure that the + * sink buffer doesn't contain more data than what the new latency + * allows. + * + * XXX: Does it really make sense to push this responsibility to + * the sink implementors? Wouldn't it be better to do it once in + * the core than many times in the modules? */ + + if (i->thread_info.requested_sink_latency != (pa_usec_t) -1) + pa_sink_input_set_requested_latency_within_thread(i, i->thread_info.requested_sink_latency); + + pa_sink_input_update_max_rewind(i, s->thread_info.max_rewind); + pa_sink_input_update_max_request(i, s->thread_info.max_request); + + /* We don't rewind here automatically. This is left to the + * sink input implementor because some sink inputs need a + * slow start, i.e. need some time to buffer client + * samples before beginning streaming. + * + * XXX: Does it really make sense to push this functionality to + * the sink implementors? Wouldn't it be better to do it once in + * the core than many times in the modules? */ + + /* In flat volume mode we need to update the volume as + * well */ + return o->process_msg(o, PA_SINK_MESSAGE_SET_SHARED_VOLUME, NULL, 0, NULL); + } + + case PA_SINK_MESSAGE_REMOVE_INPUT: { + pa_sink_input *i = PA_SINK_INPUT(userdata); + + /* If you change anything here, make sure to change the + * sink input handling a few lines down at + * PA_SINK_MESSAGE_START_MOVE, too. */ + + pa_sink_input_detach(i); + + pa_sink_input_set_state_within_thread(i, i->state); + + /* Since the caller sleeps in pa_sink_input_unlink(), + * we can safely access data outside of thread_info even + * though it is mutable */ + + pa_assert(!i->sync_prev); + pa_assert(!i->sync_next); + + if (i->thread_info.sync_prev) { + i->thread_info.sync_prev->thread_info.sync_next = i->thread_info.sync_prev->sync_next; + i->thread_info.sync_prev = NULL; + } + + if (i->thread_info.sync_next) { + i->thread_info.sync_next->thread_info.sync_prev = i->thread_info.sync_next->sync_prev; + i->thread_info.sync_next = NULL; + } + + pa_hashmap_remove_and_free(s->thread_info.inputs, PA_UINT32_TO_PTR(i->index)); + pa_sink_invalidate_requested_latency(s, true); + pa_sink_request_rewind(s, (size_t) -1); + + /* In flat volume mode we need to update the volume as + * well */ + return o->process_msg(o, PA_SINK_MESSAGE_SET_SHARED_VOLUME, NULL, 0, NULL); + } + + case PA_SINK_MESSAGE_START_MOVE: { + pa_sink_input *i = PA_SINK_INPUT(userdata); + + /* We don't support moving synchronized streams. */ + pa_assert(!i->sync_prev); + pa_assert(!i->sync_next); + pa_assert(!i->thread_info.sync_next); + pa_assert(!i->thread_info.sync_prev); + + if (i->thread_info.state != PA_SINK_INPUT_CORKED) { + pa_usec_t usec = 0; + size_t sink_nbytes, total_nbytes; + + /* The old sink probably has some audio from this + * stream in its buffer. We want to "take it back" as + * much as possible and play it to the new sink. We + * don't know at this point how much the old sink can + * rewind. We have to pick something, and that + * something is the full latency of the old sink here. + * So we rewind the stream buffer by the sink latency + * amount, which may be more than what we should + * rewind. This can result in a chunk of audio being + * played both to the old sink and the new sink. + * + * FIXME: Fix this code so that we don't have to make + * guesses about how much the sink will actually be + * able to rewind. If someone comes up with a solution + * for this, something to note is that the part of the + * latency that the old sink couldn't rewind should + * ideally be compensated after the stream has moved + * to the new sink by adding silence. The new sink + * most likely can't start playing the moved stream + * immediately, and that gap should be removed from + * the "compensation silence" (at least at the time of + * writing this, the move finish code will actually + * already take care of dropping the new sink's + * unrewindable latency, so taking into account the + * unrewindable latency of the old sink is the only + * problem). + * + * The render_memblockq contents are discarded, + * because when the sink changes, the format of the + * audio stored in the render_memblockq may change + * too, making the stored audio invalid. FIXME: + * However, the read and write indices are moved back + * the same amount, so if they are not the same now, + * they won't be the same after the rewind either. If + * the write index of the render_memblockq is ahead of + * the read index, then the render_memblockq will feed + * the new sink some silence first, which it shouldn't + * do. The write index should be flushed to be the + * same as the read index. */ + + /* Get the latency of the sink */ + usec = pa_sink_get_latency_within_thread(s, false); + sink_nbytes = pa_usec_to_bytes(usec, &s->sample_spec); + total_nbytes = sink_nbytes + pa_memblockq_get_length(i->thread_info.render_memblockq); + + if (total_nbytes > 0) { + i->thread_info.rewrite_nbytes = i->thread_info.resampler ? pa_resampler_request(i->thread_info.resampler, total_nbytes) : total_nbytes; + i->thread_info.rewrite_flush = true; + pa_sink_input_process_rewind(i, sink_nbytes); + } + } + + pa_sink_input_detach(i); + + /* Let's remove the sink input ...*/ + pa_hashmap_remove_and_free(s->thread_info.inputs, PA_UINT32_TO_PTR(i->index)); + + pa_sink_invalidate_requested_latency(s, true); + + pa_log_debug("Requesting rewind due to started move"); + pa_sink_request_rewind(s, (size_t) -1); + + /* In flat volume mode we need to update the volume as + * well */ + return o->process_msg(o, PA_SINK_MESSAGE_SET_SHARED_VOLUME, NULL, 0, NULL); + } + + case PA_SINK_MESSAGE_FINISH_MOVE: { + pa_sink_input *i = PA_SINK_INPUT(userdata); + + /* We don't support moving synchronized streams. */ + pa_assert(!i->sync_prev); + pa_assert(!i->sync_next); + pa_assert(!i->thread_info.sync_next); + pa_assert(!i->thread_info.sync_prev); + + pa_hashmap_put(s->thread_info.inputs, PA_UINT32_TO_PTR(i->index), pa_sink_input_ref(i)); + + pa_sink_input_attach(i); + + if (i->thread_info.state != PA_SINK_INPUT_CORKED) { + pa_usec_t usec = 0; + size_t nbytes; + + /* In the ideal case the new sink would start playing + * the stream immediately. That requires the sink to + * be able to rewind all of its latency, which usually + * isn't possible, so there will probably be some gap + * before the moved stream becomes audible. We then + * have two possibilities: 1) start playing the stream + * from where it is now, or 2) drop the unrewindable + * latency of the sink from the stream. With option 1 + * we won't lose any audio but the stream will have a + * pause. With option 2 we may lose some audio but the + * stream time will be somewhat in sync with the wall + * clock. Lennart seems to have chosen option 2 (one + * of the reasons might have been that option 1 is + * actually much harder to implement), so we drop the + * latency of the new sink from the moved stream and + * hope that the sink will undo most of that in the + * rewind. */ + + /* Get the latency of the sink */ + usec = pa_sink_get_latency_within_thread(s, false); + nbytes = pa_usec_to_bytes(usec, &s->sample_spec); + + if (nbytes > 0) + pa_sink_input_drop(i, nbytes); + + pa_log_debug("Requesting rewind due to finished move"); + pa_sink_request_rewind(s, nbytes); + } + + /* Updating the requested sink latency has to be done + * after the sink rewind request, not before, because + * otherwise the sink may limit the rewind amount + * needlessly. */ + + if (i->thread_info.requested_sink_latency != (pa_usec_t) -1) + pa_sink_input_set_requested_latency_within_thread(i, i->thread_info.requested_sink_latency); + + pa_sink_input_update_max_rewind(i, s->thread_info.max_rewind); + pa_sink_input_update_max_request(i, s->thread_info.max_request); + + return o->process_msg(o, PA_SINK_MESSAGE_SET_SHARED_VOLUME, NULL, 0, NULL); + } + + case PA_SINK_MESSAGE_SET_SHARED_VOLUME: { + pa_sink *root_sink = pa_sink_get_master(s); + + if (PA_LIKELY(root_sink)) + set_shared_volume_within_thread(root_sink); + + return 0; + } + + case PA_SINK_MESSAGE_SET_VOLUME_SYNCED: + + if (s->flags & PA_SINK_DEFERRED_VOLUME) { + s->set_volume(s); + pa_sink_volume_change_push(s); + } + /* Fall through ... */ + + case PA_SINK_MESSAGE_SET_VOLUME: + + if (!pa_cvolume_equal(&s->thread_info.soft_volume, &s->soft_volume)) { + s->thread_info.soft_volume = s->soft_volume; + pa_sink_request_rewind(s, (size_t) -1); + } + + /* Fall through ... */ + + case PA_SINK_MESSAGE_SYNC_VOLUMES: + sync_input_volumes_within_thread(s); + return 0; + + case PA_SINK_MESSAGE_GET_VOLUME: + + if ((s->flags & PA_SINK_DEFERRED_VOLUME) && s->get_volume) { + s->get_volume(s); + pa_sink_volume_change_flush(s); + pa_sw_cvolume_divide(&s->thread_info.current_hw_volume, &s->real_volume, &s->soft_volume); + } + + /* In case sink implementor reset SW volume. */ + if (!pa_cvolume_equal(&s->thread_info.soft_volume, &s->soft_volume)) { + s->thread_info.soft_volume = s->soft_volume; + pa_sink_request_rewind(s, (size_t) -1); + } + + return 0; + + case PA_SINK_MESSAGE_SET_MUTE: + + if (s->thread_info.soft_muted != s->muted) { + s->thread_info.soft_muted = s->muted; + pa_sink_request_rewind(s, (size_t) -1); + } + + if (s->flags & PA_SINK_DEFERRED_VOLUME && s->set_mute) + s->set_mute(s); + + return 0; + + case PA_SINK_MESSAGE_GET_MUTE: + + if (s->flags & PA_SINK_DEFERRED_VOLUME && s->get_mute) + return s->get_mute(s, userdata); + + return 0; + + case PA_SINK_MESSAGE_SET_STATE: { + struct set_state_data *data = userdata; + bool suspend_change = + (s->thread_info.state == PA_SINK_SUSPENDED && PA_SINK_IS_OPENED(data->state)) || + (PA_SINK_IS_OPENED(s->thread_info.state) && data->state == PA_SINK_SUSPENDED); + + if (s->set_state_in_io_thread) { + int r; + + if ((r = s->set_state_in_io_thread(s, data->state, data->suspend_cause)) < 0) + return r; + } + + s->thread_info.state = data->state; + + if (s->thread_info.state == PA_SINK_SUSPENDED) { + s->thread_info.rewind_nbytes = 0; + s->thread_info.rewind_requested = false; + } + + if (suspend_change) { + pa_sink_input *i; + void *state = NULL; + + while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL))) + if (i->suspend_within_thread) + i->suspend_within_thread(i, s->thread_info.state == PA_SINK_SUSPENDED); + } + + return 0; + } + + case PA_SINK_MESSAGE_GET_REQUESTED_LATENCY: { + + pa_usec_t *usec = userdata; + *usec = pa_sink_get_requested_latency_within_thread(s); + + /* Yes, that's right, the IO thread will see -1 when no + * explicit requested latency is configured, the main + * thread will see max_latency */ + if (*usec == (pa_usec_t) -1) + *usec = s->thread_info.max_latency; + + return 0; + } + + case PA_SINK_MESSAGE_SET_LATENCY_RANGE: { + pa_usec_t *r = userdata; + + pa_sink_set_latency_range_within_thread(s, r[0], r[1]); + + return 0; + } + + case PA_SINK_MESSAGE_GET_LATENCY_RANGE: { + pa_usec_t *r = userdata; + + r[0] = s->thread_info.min_latency; + r[1] = s->thread_info.max_latency; + + return 0; + } + + case PA_SINK_MESSAGE_GET_FIXED_LATENCY: + + *((pa_usec_t*) userdata) = s->thread_info.fixed_latency; + return 0; + + case PA_SINK_MESSAGE_SET_FIXED_LATENCY: + + pa_sink_set_fixed_latency_within_thread(s, (pa_usec_t) offset); + return 0; + + case PA_SINK_MESSAGE_GET_MAX_REWIND: + + *((size_t*) userdata) = s->thread_info.max_rewind; + return 0; + + case PA_SINK_MESSAGE_GET_MAX_REQUEST: + + *((size_t*) userdata) = s->thread_info.max_request; + return 0; + + case PA_SINK_MESSAGE_SET_MAX_REWIND: + + pa_sink_set_max_rewind_within_thread(s, (size_t) offset); + return 0; + + case PA_SINK_MESSAGE_SET_MAX_REQUEST: + + pa_sink_set_max_request_within_thread(s, (size_t) offset); + return 0; + + case PA_SINK_MESSAGE_UPDATE_VOLUME_AND_MUTE: + /* This message is sent from IO-thread and handled in main thread. */ + pa_assert_ctl_context(); + + /* Make sure we're not messing with main thread when no longer linked */ + if (!PA_SINK_IS_LINKED(s->state)) + return 0; + + pa_sink_get_volume(s, true); + pa_sink_get_mute(s, true); + return 0; + + case PA_SINK_MESSAGE_SET_PORT_LATENCY_OFFSET: + s->thread_info.port_latency_offset = offset; + return 0; + + case PA_SINK_MESSAGE_GET_LATENCY: + case PA_SINK_MESSAGE_MAX: + ; + } + + return -1; +} + +/* Called from main thread */ +int pa_sink_suspend_all(pa_core *c, bool suspend, pa_suspend_cause_t cause) { + pa_sink *sink; + uint32_t idx; + int ret = 0; + + pa_core_assert_ref(c); + pa_assert_ctl_context(); + pa_assert(cause != 0); + + PA_IDXSET_FOREACH(sink, c->sinks, idx) { + int r; + + if ((r = pa_sink_suspend(sink, suspend, cause)) < 0) + ret = r; + } + + return ret; +} + +/* Called from IO thread */ +void pa_sink_detach_within_thread(pa_sink *s) { + pa_sink_input *i; + void *state = NULL; + + pa_sink_assert_ref(s); + pa_sink_assert_io_context(s); + pa_assert(PA_SINK_IS_LINKED(s->thread_info.state)); + + PA_HASHMAP_FOREACH(i, s->thread_info.inputs, state) + pa_sink_input_detach(i); + + if (s->monitor_source) + pa_source_detach_within_thread(s->monitor_source); +} + +/* Called from IO thread */ +void pa_sink_attach_within_thread(pa_sink *s) { + pa_sink_input *i; + void *state = NULL; + + pa_sink_assert_ref(s); + pa_sink_assert_io_context(s); + pa_assert(PA_SINK_IS_LINKED(s->thread_info.state)); + + PA_HASHMAP_FOREACH(i, s->thread_info.inputs, state) + pa_sink_input_attach(i); + + if (s->monitor_source) + pa_source_attach_within_thread(s->monitor_source); +} + +/* Called from IO thread */ +void pa_sink_request_rewind(pa_sink*s, size_t nbytes) { + pa_sink_assert_ref(s); + pa_sink_assert_io_context(s); + pa_assert(PA_SINK_IS_LINKED(s->thread_info.state)); + + if (nbytes == (size_t) -1) + nbytes = s->thread_info.max_rewind; + + nbytes = PA_MIN(nbytes, s->thread_info.max_rewind); + + if (s->thread_info.rewind_requested && + nbytes <= s->thread_info.rewind_nbytes) + return; + + s->thread_info.rewind_nbytes = nbytes; + s->thread_info.rewind_requested = true; + + if (s->request_rewind) + s->request_rewind(s); +} + +/* Called from IO thread */ +pa_usec_t pa_sink_get_requested_latency_within_thread(pa_sink *s) { + pa_usec_t result = (pa_usec_t) -1; + pa_sink_input *i; + void *state = NULL; + pa_usec_t monitor_latency; + + pa_sink_assert_ref(s); + pa_sink_assert_io_context(s); + + if (!(s->flags & PA_SINK_DYNAMIC_LATENCY)) + return PA_CLAMP(s->thread_info.fixed_latency, s->thread_info.min_latency, s->thread_info.max_latency); + + if (s->thread_info.requested_latency_valid) + return s->thread_info.requested_latency; + + PA_HASHMAP_FOREACH(i, s->thread_info.inputs, state) + if (i->thread_info.requested_sink_latency != (pa_usec_t) -1 && + (result == (pa_usec_t) -1 || result > i->thread_info.requested_sink_latency)) + result = i->thread_info.requested_sink_latency; + + monitor_latency = pa_source_get_requested_latency_within_thread(s->monitor_source); + + if (monitor_latency != (pa_usec_t) -1 && + (result == (pa_usec_t) -1 || result > monitor_latency)) + result = monitor_latency; + + if (result != (pa_usec_t) -1) + result = PA_CLAMP(result, s->thread_info.min_latency, s->thread_info.max_latency); + + if (PA_SINK_IS_LINKED(s->thread_info.state)) { + /* Only cache if properly initialized */ + s->thread_info.requested_latency = result; + s->thread_info.requested_latency_valid = true; + } + + return result; +} + +/* Called from main thread */ +pa_usec_t pa_sink_get_requested_latency(pa_sink *s) { + pa_usec_t usec = 0; + + pa_sink_assert_ref(s); + pa_assert_ctl_context(); + pa_assert(PA_SINK_IS_LINKED(s->state)); + + if (s->state == PA_SINK_SUSPENDED) + return 0; + + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_REQUESTED_LATENCY, &usec, 0, NULL) == 0); + + return usec; +} + +/* Called from IO as well as the main thread -- the latter only before the IO thread started up */ +void pa_sink_set_max_rewind_within_thread(pa_sink *s, size_t max_rewind) { + pa_sink_input *i; + void *state = NULL; + + pa_sink_assert_ref(s); + pa_sink_assert_io_context(s); + + if (max_rewind == s->thread_info.max_rewind) + return; + + s->thread_info.max_rewind = max_rewind; + + if (PA_SINK_IS_LINKED(s->thread_info.state)) + PA_HASHMAP_FOREACH(i, s->thread_info.inputs, state) + pa_sink_input_update_max_rewind(i, s->thread_info.max_rewind); + + if (s->monitor_source) + pa_source_set_max_rewind_within_thread(s->monitor_source, s->thread_info.max_rewind); +} + +/* Called from main thread */ +void pa_sink_set_max_rewind(pa_sink *s, size_t max_rewind) { + pa_sink_assert_ref(s); + pa_assert_ctl_context(); + + if (PA_SINK_IS_LINKED(s->state)) + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_MAX_REWIND, NULL, max_rewind, NULL) == 0); + else + pa_sink_set_max_rewind_within_thread(s, max_rewind); +} + +/* Called from IO as well as the main thread -- the latter only before the IO thread started up */ +void pa_sink_set_max_request_within_thread(pa_sink *s, size_t max_request) { + void *state = NULL; + + pa_sink_assert_ref(s); + pa_sink_assert_io_context(s); + + if (max_request == s->thread_info.max_request) + return; + + s->thread_info.max_request = max_request; + + if (PA_SINK_IS_LINKED(s->thread_info.state)) { + pa_sink_input *i; + + PA_HASHMAP_FOREACH(i, s->thread_info.inputs, state) + pa_sink_input_update_max_request(i, s->thread_info.max_request); + } +} + +/* Called from main thread */ +void pa_sink_set_max_request(pa_sink *s, size_t max_request) { + pa_sink_assert_ref(s); + pa_assert_ctl_context(); + + if (PA_SINK_IS_LINKED(s->state)) + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_MAX_REQUEST, NULL, max_request, NULL) == 0); + else + pa_sink_set_max_request_within_thread(s, max_request); +} + +/* Called from IO thread */ +void pa_sink_invalidate_requested_latency(pa_sink *s, bool dynamic) { + pa_sink_input *i; + void *state = NULL; + + pa_sink_assert_ref(s); + pa_sink_assert_io_context(s); + + if ((s->flags & PA_SINK_DYNAMIC_LATENCY)) + s->thread_info.requested_latency_valid = false; + else if (dynamic) + return; + + if (PA_SINK_IS_LINKED(s->thread_info.state)) { + + if (s->update_requested_latency) + s->update_requested_latency(s); + + PA_HASHMAP_FOREACH(i, s->thread_info.inputs, state) + if (i->update_sink_requested_latency) + i->update_sink_requested_latency(i); + } +} + +/* Called from main thread */ +void pa_sink_set_latency_range(pa_sink *s, pa_usec_t min_latency, pa_usec_t max_latency) { + pa_sink_assert_ref(s); + pa_assert_ctl_context(); + + /* min_latency == 0: no limit + * min_latency anything else: specified limit + * + * Similar for max_latency */ + + if (min_latency < ABSOLUTE_MIN_LATENCY) + min_latency = ABSOLUTE_MIN_LATENCY; + + if (max_latency <= 0 || + max_latency > ABSOLUTE_MAX_LATENCY) + max_latency = ABSOLUTE_MAX_LATENCY; + + pa_assert(min_latency <= max_latency); + + /* Hmm, let's see if someone forgot to set PA_SINK_DYNAMIC_LATENCY here... */ + pa_assert((min_latency == ABSOLUTE_MIN_LATENCY && + max_latency == ABSOLUTE_MAX_LATENCY) || + (s->flags & PA_SINK_DYNAMIC_LATENCY)); + + if (PA_SINK_IS_LINKED(s->state)) { + pa_usec_t r[2]; + + r[0] = min_latency; + r[1] = max_latency; + + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_LATENCY_RANGE, r, 0, NULL) == 0); + } else + pa_sink_set_latency_range_within_thread(s, min_latency, max_latency); +} + +/* Called from main thread */ +void pa_sink_get_latency_range(pa_sink *s, pa_usec_t *min_latency, pa_usec_t *max_latency) { + pa_sink_assert_ref(s); + pa_assert_ctl_context(); + pa_assert(min_latency); + pa_assert(max_latency); + + if (PA_SINK_IS_LINKED(s->state)) { + pa_usec_t r[2] = { 0, 0 }; + + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_LATENCY_RANGE, r, 0, NULL) == 0); + + *min_latency = r[0]; + *max_latency = r[1]; + } else { + *min_latency = s->thread_info.min_latency; + *max_latency = s->thread_info.max_latency; + } +} + +/* Called from IO thread */ +void pa_sink_set_latency_range_within_thread(pa_sink *s, pa_usec_t min_latency, pa_usec_t max_latency) { + pa_sink_assert_ref(s); + pa_sink_assert_io_context(s); + + pa_assert(min_latency >= ABSOLUTE_MIN_LATENCY); + pa_assert(max_latency <= ABSOLUTE_MAX_LATENCY); + pa_assert(min_latency <= max_latency); + + /* Hmm, let's see if someone forgot to set PA_SINK_DYNAMIC_LATENCY here... */ + pa_assert((min_latency == ABSOLUTE_MIN_LATENCY && + max_latency == ABSOLUTE_MAX_LATENCY) || + (s->flags & PA_SINK_DYNAMIC_LATENCY)); + + if (s->thread_info.min_latency == min_latency && + s->thread_info.max_latency == max_latency) + return; + + s->thread_info.min_latency = min_latency; + s->thread_info.max_latency = max_latency; + + if (PA_SINK_IS_LINKED(s->thread_info.state)) { + pa_sink_input *i; + void *state = NULL; + + PA_HASHMAP_FOREACH(i, s->thread_info.inputs, state) + if (i->update_sink_latency_range) + i->update_sink_latency_range(i); + } + + pa_sink_invalidate_requested_latency(s, false); + + pa_source_set_latency_range_within_thread(s->monitor_source, min_latency, max_latency); +} + +/* Called from main thread */ +void pa_sink_set_fixed_latency(pa_sink *s, pa_usec_t latency) { + pa_sink_assert_ref(s); + pa_assert_ctl_context(); + + if (s->flags & PA_SINK_DYNAMIC_LATENCY) { + pa_assert(latency == 0); + return; + } + + if (latency < ABSOLUTE_MIN_LATENCY) + latency = ABSOLUTE_MIN_LATENCY; + + if (latency > ABSOLUTE_MAX_LATENCY) + latency = ABSOLUTE_MAX_LATENCY; + + if (PA_SINK_IS_LINKED(s->state)) + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_FIXED_LATENCY, NULL, (int64_t) latency, NULL) == 0); + else + s->thread_info.fixed_latency = latency; + + pa_source_set_fixed_latency(s->monitor_source, latency); +} + +/* Called from main thread */ +pa_usec_t pa_sink_get_fixed_latency(pa_sink *s) { + pa_usec_t latency; + + pa_sink_assert_ref(s); + pa_assert_ctl_context(); + + if (s->flags & PA_SINK_DYNAMIC_LATENCY) + return 0; + + if (PA_SINK_IS_LINKED(s->state)) + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_FIXED_LATENCY, &latency, 0, NULL) == 0); + else + latency = s->thread_info.fixed_latency; + + return latency; +} + +/* Called from IO thread */ +void pa_sink_set_fixed_latency_within_thread(pa_sink *s, pa_usec_t latency) { + pa_sink_assert_ref(s); + pa_sink_assert_io_context(s); + + if (s->flags & PA_SINK_DYNAMIC_LATENCY) { + pa_assert(latency == 0); + s->thread_info.fixed_latency = 0; + + if (s->monitor_source) + pa_source_set_fixed_latency_within_thread(s->monitor_source, 0); + + return; + } + + pa_assert(latency >= ABSOLUTE_MIN_LATENCY); + pa_assert(latency <= ABSOLUTE_MAX_LATENCY); + + if (s->thread_info.fixed_latency == latency) + return; + + s->thread_info.fixed_latency = latency; + + if (PA_SINK_IS_LINKED(s->thread_info.state)) { + pa_sink_input *i; + void *state = NULL; + + PA_HASHMAP_FOREACH(i, s->thread_info.inputs, state) + if (i->update_sink_fixed_latency) + i->update_sink_fixed_latency(i); + } + + pa_sink_invalidate_requested_latency(s, false); + + pa_source_set_fixed_latency_within_thread(s->monitor_source, latency); +} + +/* Called from main context */ +void pa_sink_set_port_latency_offset(pa_sink *s, int64_t offset) { + pa_sink_assert_ref(s); + + s->port_latency_offset = offset; + + if (PA_SINK_IS_LINKED(s->state)) + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_PORT_LATENCY_OFFSET, NULL, offset, NULL) == 0); + else + s->thread_info.port_latency_offset = offset; + + pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_PORT_LATENCY_OFFSET_CHANGED], s); +} + +/* Called from main context */ +size_t pa_sink_get_max_rewind(pa_sink *s) { + size_t r; + pa_assert_ctl_context(); + pa_sink_assert_ref(s); + + if (!PA_SINK_IS_LINKED(s->state)) + return s->thread_info.max_rewind; + + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_MAX_REWIND, &r, 0, NULL) == 0); + + return r; +} + +/* Called from main context */ +size_t pa_sink_get_max_request(pa_sink *s) { + size_t r; + pa_sink_assert_ref(s); + pa_assert_ctl_context(); + + if (!PA_SINK_IS_LINKED(s->state)) + return s->thread_info.max_request; + + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_MAX_REQUEST, &r, 0, NULL) == 0); + + return r; +} + +/* Called from main context */ +int pa_sink_set_port(pa_sink *s, const char *name, bool save) { + pa_device_port *port; + + pa_sink_assert_ref(s); + pa_assert_ctl_context(); + + if (!s->set_port) { + pa_log_debug("set_port() operation not implemented for sink %u \"%s\"", s->index, s->name); + return -PA_ERR_NOTIMPLEMENTED; + } + + if (!name) + return -PA_ERR_NOENTITY; + + if (!(port = pa_hashmap_get(s->ports, name))) + return -PA_ERR_NOENTITY; + + if (s->active_port == port) { + s->save_port = s->save_port || save; + return 0; + } + + if (s->set_port(s, port) < 0) + return -PA_ERR_NOENTITY; + + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); + + pa_log_info("Changed port of sink %u \"%s\" to %s", s->index, s->name, port->name); + + s->active_port = port; + s->save_port = save; + + pa_sink_set_port_latency_offset(s, s->active_port->latency_offset); + + /* The active port affects the default sink selection. */ + pa_core_update_default_sink(s->core); + + pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_PORT_CHANGED], s); + + return 0; +} + +bool pa_device_init_icon(pa_proplist *p, bool is_sink) { + const char *ff, *c, *t = NULL, *s = "", *profile, *bus; + + pa_assert(p); + + if (pa_proplist_contains(p, PA_PROP_DEVICE_ICON_NAME)) + return true; + + if ((ff = pa_proplist_gets(p, PA_PROP_DEVICE_FORM_FACTOR))) { + + if (pa_streq(ff, "microphone")) + t = "audio-input-microphone"; + else if (pa_streq(ff, "webcam")) + t = "camera-web"; + else if (pa_streq(ff, "computer")) + t = "computer"; + else if (pa_streq(ff, "handset")) + t = "phone"; + else if (pa_streq(ff, "portable")) + t = "multimedia-player"; + else if (pa_streq(ff, "tv")) + t = "video-display"; + + /* + * The following icons are not part of the icon naming spec, + * because Rodney Dawes sucks as the maintainer of that spec. + * + * http://lists.freedesktop.org/archives/xdg/2009-May/010397.html + */ + else if (pa_streq(ff, "headset")) + t = "audio-headset"; + else if (pa_streq(ff, "headphone")) + t = "audio-headphones"; + else if (pa_streq(ff, "speaker")) + t = "audio-speakers"; + else if (pa_streq(ff, "hands-free")) + t = "audio-handsfree"; + } + + if (!t) + if ((c = pa_proplist_gets(p, PA_PROP_DEVICE_CLASS))) + if (pa_streq(c, "modem")) + t = "modem"; + + if (!t) { + if (is_sink) + t = "audio-card"; + else + t = "audio-input-microphone"; + } + + if ((profile = pa_proplist_gets(p, PA_PROP_DEVICE_PROFILE_NAME))) { + if (strstr(profile, "analog")) + s = "-analog"; + else if (strstr(profile, "iec958")) + s = "-iec958"; + else if (strstr(profile, "hdmi")) + s = "-hdmi"; + } + + bus = pa_proplist_gets(p, PA_PROP_DEVICE_BUS); + + pa_proplist_setf(p, PA_PROP_DEVICE_ICON_NAME, "%s%s%s%s", t, pa_strempty(s), bus ? "-" : "", pa_strempty(bus)); + + return true; +} + +bool pa_device_init_description(pa_proplist *p, pa_card *card) { + const char *s, *d = NULL, *k; + pa_assert(p); + + if (pa_proplist_contains(p, PA_PROP_DEVICE_DESCRIPTION)) + return true; + + if (card) + if ((s = pa_proplist_gets(card->proplist, PA_PROP_DEVICE_DESCRIPTION))) + d = s; + + if (!d) + if ((s = pa_proplist_gets(p, PA_PROP_DEVICE_FORM_FACTOR))) + if (pa_streq(s, "internal")) + d = _("Built-in Audio"); + + if (!d) + if ((s = pa_proplist_gets(p, PA_PROP_DEVICE_CLASS))) + if (pa_streq(s, "modem")) + d = _("Modem"); + + if (!d) + d = pa_proplist_gets(p, PA_PROP_DEVICE_PRODUCT_NAME); + + if (!d) + return false; + + k = pa_proplist_gets(p, PA_PROP_DEVICE_PROFILE_DESCRIPTION); + + if (d && k) + pa_proplist_setf(p, PA_PROP_DEVICE_DESCRIPTION, "%s %s", d, k); + else if (d) + pa_proplist_sets(p, PA_PROP_DEVICE_DESCRIPTION, d); + + return true; +} + +bool pa_device_init_intended_roles(pa_proplist *p) { + const char *s; + pa_assert(p); + + if (pa_proplist_contains(p, PA_PROP_DEVICE_INTENDED_ROLES)) + return true; + + if ((s = pa_proplist_gets(p, PA_PROP_DEVICE_FORM_FACTOR))) + if (pa_streq(s, "handset") || pa_streq(s, "hands-free") + || pa_streq(s, "headset")) { + pa_proplist_sets(p, PA_PROP_DEVICE_INTENDED_ROLES, "phone"); + return true; + } + + return false; +} + +unsigned pa_device_init_priority(pa_proplist *p) { + const char *s; + unsigned priority = 0; + + pa_assert(p); + + if ((s = pa_proplist_gets(p, PA_PROP_DEVICE_CLASS))) { + + if (pa_streq(s, "sound")) + priority += 9000; + else if (!pa_streq(s, "modem")) + priority += 1000; + } + + if ((s = pa_proplist_gets(p, PA_PROP_DEVICE_FORM_FACTOR))) { + + if (pa_streq(s, "headphone")) + priority += 900; + else if (pa_streq(s, "hifi")) + priority += 600; + else if (pa_streq(s, "speaker")) + priority += 500; + else if (pa_streq(s, "portable")) + priority += 450; + } + + if ((s = pa_proplist_gets(p, PA_PROP_DEVICE_BUS))) { + + if (pa_streq(s, "bluetooth")) + priority += 50; + else if (pa_streq(s, "usb")) + priority += 40; + else if (pa_streq(s, "pci")) + priority += 30; + } + + if ((s = pa_proplist_gets(p, PA_PROP_DEVICE_PROFILE_NAME))) { + + if (pa_startswith(s, "analog-")) + priority += 9; + else if (pa_startswith(s, "iec958-")) + priority += 8; + } + + return priority; +} + +PA_STATIC_FLIST_DECLARE(pa_sink_volume_change, 0, pa_xfree); + +/* Called from the IO thread. */ +static pa_sink_volume_change *pa_sink_volume_change_new(pa_sink *s) { + pa_sink_volume_change *c; + if (!(c = pa_flist_pop(PA_STATIC_FLIST_GET(pa_sink_volume_change)))) + c = pa_xnew(pa_sink_volume_change, 1); + + PA_LLIST_INIT(pa_sink_volume_change, c); + c->at = 0; + pa_cvolume_reset(&c->hw_volume, s->sample_spec.channels); + return c; +} + +/* Called from the IO thread. */ +static void pa_sink_volume_change_free(pa_sink_volume_change *c) { + pa_assert(c); + if (pa_flist_push(PA_STATIC_FLIST_GET(pa_sink_volume_change), c) < 0) + pa_xfree(c); +} + +/* Called from the IO thread. */ +void pa_sink_volume_change_push(pa_sink *s) { + pa_sink_volume_change *c = NULL; + pa_sink_volume_change *nc = NULL; + pa_sink_volume_change *pc = NULL; + uint32_t safety_margin = s->thread_info.volume_change_safety_margin; + + const char *direction = NULL; + + pa_assert(s); + nc = pa_sink_volume_change_new(s); + + /* NOTE: There is already more different volumes in pa_sink that I can remember. + * Adding one more volume for HW would get us rid of this, but I am trying + * to survive with the ones we already have. */ + pa_sw_cvolume_divide(&nc->hw_volume, &s->real_volume, &s->soft_volume); + + if (!s->thread_info.volume_changes && pa_cvolume_equal(&nc->hw_volume, &s->thread_info.current_hw_volume)) { + pa_log_debug("Volume not changing"); + pa_sink_volume_change_free(nc); + return; + } + + nc->at = pa_sink_get_latency_within_thread(s, false); + nc->at += pa_rtclock_now() + s->thread_info.volume_change_extra_delay; + + if (s->thread_info.volume_changes_tail) { + for (c = s->thread_info.volume_changes_tail; c; c = c->prev) { + /* If volume is going up let's do it a bit late. If it is going + * down let's do it a bit early. */ + if (pa_cvolume_avg(&nc->hw_volume) > pa_cvolume_avg(&c->hw_volume)) { + if (nc->at + safety_margin > c->at) { + nc->at += safety_margin; + direction = "up"; + break; + } + } + else if (nc->at - safety_margin > c->at) { + nc->at -= safety_margin; + direction = "down"; + break; + } + } + } + + if (c == NULL) { + if (pa_cvolume_avg(&nc->hw_volume) > pa_cvolume_avg(&s->thread_info.current_hw_volume)) { + nc->at += safety_margin; + direction = "up"; + } else { + nc->at -= safety_margin; + direction = "down"; + } + PA_LLIST_PREPEND(pa_sink_volume_change, s->thread_info.volume_changes, nc); + } + else { + PA_LLIST_INSERT_AFTER(pa_sink_volume_change, s->thread_info.volume_changes, c, nc); + } + + pa_log_debug("Volume going %s to %d at %llu", direction, pa_cvolume_avg(&nc->hw_volume), (long long unsigned) nc->at); + + /* We can ignore volume events that came earlier but should happen later than this. */ + PA_LLIST_FOREACH_SAFE(c, pc, nc->next) { + pa_log_debug("Volume change to %d at %llu was dropped", pa_cvolume_avg(&c->hw_volume), (long long unsigned) c->at); + pa_sink_volume_change_free(c); + } + nc->next = NULL; + s->thread_info.volume_changes_tail = nc; +} + +/* Called from the IO thread. */ +static void pa_sink_volume_change_flush(pa_sink *s) { + pa_sink_volume_change *c = s->thread_info.volume_changes; + pa_assert(s); + s->thread_info.volume_changes = NULL; + s->thread_info.volume_changes_tail = NULL; + while (c) { + pa_sink_volume_change *next = c->next; + pa_sink_volume_change_free(c); + c = next; + } +} + +/* Called from the IO thread. */ +bool pa_sink_volume_change_apply(pa_sink *s, pa_usec_t *usec_to_next) { + pa_usec_t now; + bool ret = false; + + pa_assert(s); + + if (!s->thread_info.volume_changes || !PA_SINK_IS_LINKED(s->state)) { + if (usec_to_next) + *usec_to_next = 0; + return ret; + } + + pa_assert(s->write_volume); + + now = pa_rtclock_now(); + + while (s->thread_info.volume_changes && now >= s->thread_info.volume_changes->at) { + pa_sink_volume_change *c = s->thread_info.volume_changes; + PA_LLIST_REMOVE(pa_sink_volume_change, s->thread_info.volume_changes, c); + pa_log_debug("Volume change to %d at %llu was written %llu usec late", + pa_cvolume_avg(&c->hw_volume), (long long unsigned) c->at, (long long unsigned) (now - c->at)); + ret = true; + s->thread_info.current_hw_volume = c->hw_volume; + pa_sink_volume_change_free(c); + } + + if (ret) + s->write_volume(s); + + if (s->thread_info.volume_changes) { + if (usec_to_next) + *usec_to_next = s->thread_info.volume_changes->at - now; + if (pa_log_ratelimit(PA_LOG_DEBUG)) + pa_log_debug("Next volume change in %lld usec", (long long) (s->thread_info.volume_changes->at - now)); + } + else { + if (usec_to_next) + *usec_to_next = 0; + s->thread_info.volume_changes_tail = NULL; + } + return ret; +} + +/* Called from the IO thread. */ +static void pa_sink_volume_change_rewind(pa_sink *s, size_t nbytes) { + /* All the queued volume events later than current latency are shifted to happen earlier. */ + pa_sink_volume_change *c; + pa_volume_t prev_vol = pa_cvolume_avg(&s->thread_info.current_hw_volume); + pa_usec_t rewound = pa_bytes_to_usec(nbytes, &s->sample_spec); + pa_usec_t limit = pa_sink_get_latency_within_thread(s, false); + + pa_log_debug("latency = %lld", (long long) limit); + limit += pa_rtclock_now() + s->thread_info.volume_change_extra_delay; + + PA_LLIST_FOREACH(c, s->thread_info.volume_changes) { + pa_usec_t modified_limit = limit; + if (prev_vol > pa_cvolume_avg(&c->hw_volume)) + modified_limit -= s->thread_info.volume_change_safety_margin; + else + modified_limit += s->thread_info.volume_change_safety_margin; + if (c->at > modified_limit) { + c->at -= rewound; + if (c->at < modified_limit) + c->at = modified_limit; + } + prev_vol = pa_cvolume_avg(&c->hw_volume); + } + pa_sink_volume_change_apply(s, NULL); +} + +/* Called from the main thread */ +/* Gets the list of formats supported by the sink. The members and idxset must + * be freed by the caller. */ +pa_idxset* pa_sink_get_formats(pa_sink *s) { + pa_idxset *ret; + + pa_assert(s); + + if (s->get_formats) { + /* Sink supports format query, all is good */ + ret = s->get_formats(s); + } else { + /* Sink doesn't support format query, so assume it does PCM */ + pa_format_info *f = pa_format_info_new(); + f->encoding = PA_ENCODING_PCM; + + ret = pa_idxset_new(NULL, NULL); + pa_idxset_put(ret, f, NULL); + } + + return ret; +} + +/* Called from the main thread */ +/* Allows an external source to set what formats a sink supports if the sink + * permits this. The function makes a copy of the formats on success. */ +bool pa_sink_set_formats(pa_sink *s, pa_idxset *formats) { + pa_assert(s); + pa_assert(formats); + + if (s->set_formats) + /* Sink supports setting formats -- let's give it a shot */ + return s->set_formats(s, formats); + else + /* Sink doesn't support setting this -- bail out */ + return false; +} + +/* Called from the main thread */ +/* Checks if the sink can accept this format */ +bool pa_sink_check_format(pa_sink *s, pa_format_info *f) { + pa_idxset *formats = NULL; + bool ret = false; + + pa_assert(s); + pa_assert(f); + + formats = pa_sink_get_formats(s); + + if (formats) { + pa_format_info *finfo_device; + uint32_t i; + + PA_IDXSET_FOREACH(finfo_device, formats, i) { + if (pa_format_info_is_compatible(finfo_device, f)) { + ret = true; + break; + } + } + + pa_idxset_free(formats, (pa_free_cb_t) pa_format_info_free); + } + + return ret; +} + +/* Called from the main thread */ +/* Calculates the intersection between formats supported by the sink and + * in_formats, and returns these, in the order of the sink's formats. */ +pa_idxset* pa_sink_check_formats(pa_sink *s, pa_idxset *in_formats) { + pa_idxset *out_formats = pa_idxset_new(NULL, NULL), *sink_formats = NULL; + pa_format_info *f_sink, *f_in; + uint32_t i, j; + + pa_assert(s); + + if (!in_formats || pa_idxset_isempty(in_formats)) + goto done; + + sink_formats = pa_sink_get_formats(s); + + PA_IDXSET_FOREACH(f_sink, sink_formats, i) { + PA_IDXSET_FOREACH(f_in, in_formats, j) { + if (pa_format_info_is_compatible(f_sink, f_in)) + pa_idxset_put(out_formats, pa_format_info_copy(f_in), NULL); + } + } + +done: + if (sink_formats) + pa_idxset_free(sink_formats, (pa_free_cb_t) pa_format_info_free); + + return out_formats; +} + +/* Called from the main thread */ +void pa_sink_set_sample_format(pa_sink *s, pa_sample_format_t format) { + pa_sample_format_t old_format; + + pa_assert(s); + pa_assert(pa_sample_format_valid(format)); + + old_format = s->sample_spec.format; + if (old_format == format) + return; + + pa_log_info("%s: format: %s -> %s", + s->name, pa_sample_format_to_string(old_format), pa_sample_format_to_string(format)); + + s->sample_spec.format = format; + + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK | PA_SUBSCRIPTION_EVENT_CHANGE, s->index); +} + +/* Called from the main thread */ +void pa_sink_set_sample_rate(pa_sink *s, uint32_t rate) { + uint32_t old_rate; + + pa_assert(s); + pa_assert(pa_sample_rate_valid(rate)); + + old_rate = s->sample_spec.rate; + if (old_rate == rate) + return; + + pa_log_info("%s: rate: %u -> %u", s->name, old_rate, rate); + + s->sample_spec.rate = rate; + + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK | PA_SUBSCRIPTION_EVENT_CHANGE, s->index); +} + +/* Called from the main thread. */ +void pa_sink_set_reference_volume_direct(pa_sink *s, const pa_cvolume *volume) { + pa_cvolume old_volume; + char old_volume_str[PA_CVOLUME_SNPRINT_VERBOSE_MAX]; + char new_volume_str[PA_CVOLUME_SNPRINT_VERBOSE_MAX]; + + pa_assert(s); + pa_assert(volume); + + old_volume = s->reference_volume; + + if (pa_cvolume_equal(volume, &old_volume)) + return; + + s->reference_volume = *volume; + pa_log_debug("The reference volume of sink %s changed from %s to %s.", s->name, + pa_cvolume_snprint_verbose(old_volume_str, sizeof(old_volume_str), &old_volume, &s->channel_map, + s->flags & PA_SINK_DECIBEL_VOLUME), + pa_cvolume_snprint_verbose(new_volume_str, sizeof(new_volume_str), volume, &s->channel_map, + s->flags & PA_SINK_DECIBEL_VOLUME)); + + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); + pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_VOLUME_CHANGED], s); +} + +void pa_sink_move_streams_to_default_sink(pa_core *core, pa_sink *old_sink, bool default_sink_changed) { + pa_sink_input *i; + uint32_t idx; + + pa_assert(core); + pa_assert(old_sink); + + if (core->state == PA_CORE_SHUTDOWN) + return; + + if (core->default_sink == NULL || core->default_sink->unlink_requested) + return; + + if (old_sink == core->default_sink) + return; + + PA_IDXSET_FOREACH(i, old_sink->inputs, idx) { + if (!PA_SINK_INPUT_IS_LINKED(i->state)) + continue; + + if (!i->sink) + continue; + + /* Don't move sink-inputs which connect filter sinks to their target sinks */ + if (i->origin_sink) + continue; + + /* If default_sink_changed is false, the old sink became unavailable, so all streams must be moved. */ + if (pa_safe_streq(old_sink->name, i->preferred_sink) && default_sink_changed) + continue; + + if (!pa_sink_input_may_move_to(i, core->default_sink)) + continue; + + if (default_sink_changed) + pa_log_info("The sink input %u \"%s\" is moving to %s due to change of the default sink.", + i->index, pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME)), core->default_sink->name); + else + pa_log_info("The sink input %u \"%s\" is moving to %s, because the old sink became unavailable.", + i->index, pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME)), core->default_sink->name); + + pa_sink_input_move_to(i, core->default_sink, false); + } +} diff --git a/src/pulsecore/sink.h b/src/pulsecore/sink.h new file mode 100644 index 0000000..c3f5fbc --- /dev/null +++ b/src/pulsecore/sink.h @@ -0,0 +1,575 @@ +#ifndef foopulsesinkhfoo +#define foopulsesinkhfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <inttypes.h> + +#include <pulsecore/typedefs.h> +#include <pulse/def.h> +#include <pulse/format.h> +#include <pulse/sample.h> +#include <pulse/channelmap.h> +#include <pulse/volume.h> + +#include <pulsecore/core.h> +#include <pulsecore/idxset.h> +#include <pulsecore/memchunk.h> +#include <pulsecore/source.h> +#include <pulsecore/module.h> +#include <pulsecore/asyncmsgq.h> +#include <pulsecore/msgobject.h> +#include <pulsecore/rtpoll.h> +#include <pulsecore/device-port.h> +#include <pulsecore/card.h> +#include <pulsecore/queue.h> +#include <pulsecore/thread-mq.h> +#include <pulsecore/sink-input.h> + +#define PA_MAX_INPUTS_PER_SINK 256 + +/* Returns true if sink is linked: registered and accessible from client side. */ +static inline bool PA_SINK_IS_LINKED(pa_sink_state_t x) { + return x == PA_SINK_RUNNING || x == PA_SINK_IDLE || x == PA_SINK_SUSPENDED; +} + +/* A generic definition for void callback functions */ +typedef void(*pa_sink_cb_t)(pa_sink *s); + +typedef int (*pa_sink_get_mute_cb_t)(pa_sink *s, bool *mute); + +struct pa_sink { + pa_msgobject parent; + + uint32_t index; + pa_core *core; + + pa_sink_state_t state; + + /* Set in the beginning of pa_sink_unlink() before setting the sink state + * to UNLINKED. The purpose is to prevent moving streams to a sink that is + * about to be removed. */ + bool unlink_requested; + + pa_sink_flags_t flags; + pa_suspend_cause_t suspend_cause; + + char *name; + char *driver; /* may be NULL */ + pa_proplist *proplist; + + pa_module *module; /* may be NULL */ + pa_card *card; /* may be NULL */ + + pa_sample_spec sample_spec; + pa_channel_map channel_map; + uint32_t default_sample_rate; + uint32_t alternate_sample_rate; + bool avoid_resampling:1; + + pa_idxset *inputs; + unsigned n_corked; + pa_source *monitor_source; + pa_sink_input *input_to_master; /* non-NULL only for filter sinks */ + + pa_volume_t base_volume; /* shall be constant */ + unsigned n_volume_steps; /* shall be constant */ + + /* Also see http://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/Developer/Volumes/ */ + pa_cvolume reference_volume; /* The volume exported and taken as reference base for relative sink input volumes */ + pa_cvolume real_volume; /* The volume that the hardware is configured to */ + pa_cvolume soft_volume; /* The internal software volume we apply to all PCM data while it passes through */ + + bool muted:1; + + bool refresh_volume:1; + bool refresh_muted:1; + bool save_port:1; + bool save_volume:1; + bool save_muted:1; + + /* Saved volume state while we're in passthrough mode */ + pa_cvolume saved_volume; + bool saved_save_volume:1; + + pa_asyncmsgq *asyncmsgq; + + pa_memchunk silence; + + pa_hashmap *ports; + pa_device_port *active_port; + + /* The latency offset is inherited from the currently active port */ + int64_t port_latency_offset; + + unsigned priority; + + bool set_mute_in_progress; + + /* Callbacks for doing things when the sink state and/or suspend cause is + * changed. It's fine to set either or both of the callbacks to NULL if the + * implementation doesn't have anything to do on state or suspend cause + * changes. + * + * set_state_in_main_thread() is called first. The callback is allowed to + * report failure if and only if the sink changes its state from + * SUSPENDED to IDLE or RUNNING. (FIXME: It would make sense to allow + * failure also when changing state from INIT to IDLE or RUNNING, but + * currently that will crash pa_sink_put().) If + * set_state_in_main_thread() fails, set_state_in_io_thread() won't be + * called. + * + * If set_state_in_main_thread() is successful (or not set), then + * set_state_in_io_thread() is called. Again, failure is allowed if and + * only if the sink changes state from SUSPENDED to IDLE or RUNNING. If + * set_state_in_io_thread() fails, then set_state_in_main_thread() is + * called again, this time with the state parameter set to SUSPENDED and + * the suspend_cause parameter set to 0. + * + * pa_sink.state, pa_sink.thread_info.state and pa_sink.suspend_cause + * are updated only after all the callback calls. In case of failure, the + * state is set to SUSPENDED and the suspend cause is set to 0. */ + int (*set_state_in_main_thread)(pa_sink *s, pa_sink_state_t state, pa_suspend_cause_t suspend_cause); /* may be NULL */ + int (*set_state_in_io_thread)(pa_sink *s, pa_sink_state_t state, pa_suspend_cause_t suspend_cause); /* may be NULL */ + + /* Sink drivers that support hardware volume may set this + * callback. This is called when the current volume needs to be + * re-read from the hardware. + * + * There are two ways for drivers to implement hardware volume + * query: either set this callback or handle + * PA_SINK_MESSAGE_GET_VOLUME. The callback implementation or the + * message handler must update s->real_volume and s->soft_volume + * (using pa_sink_set_soft_volume()) to match the current hardware + * volume. + * + * If PA_SINK_DEFERRED_VOLUME is not set, then this is called from the + * main thread before sending PA_SINK_MESSAGE_GET_VOLUME, so in + * this case the driver can choose whether to read the volume from + * the hardware in the main thread or in the IO thread. + * + * If PA_SINK_DEFERRED_VOLUME is set, then this is called from the IO + * thread within the default handler for + * PA_SINK_MESSAGE_GET_VOLUME (the main thread is waiting while + * the message is being processed), so there's no choice of where + * to do the volume reading - it has to be done in the IO thread + * always. + * + * You must use the function pa_sink_set_get_volume_callback() to + * set this callback. */ + pa_sink_cb_t get_volume; /* may be NULL */ + + /* Sink drivers that support hardware volume must set this + * callback. This is called when the hardware volume needs to be + * updated. + * + * If PA_SINK_DEFERRED_VOLUME is not set, then this is called from the + * main thread. The callback implementation must set the hardware + * volume according to s->real_volume. If the driver can't set the + * hardware volume to the exact requested value, it has to update + * s->real_volume and/or s->soft_volume so that they together + * match the actual hardware volume that was set. + * + * If PA_SINK_DEFERRED_VOLUME is set, then this is called from the IO + * thread. The callback implementation must not actually set the + * hardware volume yet, but it must check how close to the + * requested volume the hardware volume can be set, and update + * s->real_volume and/or s->soft_volume so that they together + * match the actual hardware volume that will be set later in the + * write_volume callback. + * + * You must use the function pa_sink_set_set_volume_callback() to + * set this callback. */ + pa_sink_cb_t set_volume; /* may be NULL */ + + /* Sink drivers that set PA_SINK_DEFERRED_VOLUME must provide this + * callback. This callback is not used with sinks that do not set + * PA_SINK_DEFERRED_VOLUME. This is called from the IO thread when a + * pending hardware volume change has to be written to the + * hardware. The requested volume is passed to the callback + * implementation in s->thread_info.current_hw_volume. + * + * The call is done inside pa_sink_volume_change_apply(), which is + * not called automatically - it is the driver's responsibility to + * schedule that function to be called at the right times in the + * IO thread. + * + * You must use the function pa_sink_set_write_volume_callback() to + * set this callback. */ + pa_sink_cb_t write_volume; /* may be NULL */ + + /* If the sink mute can change "spontaneously" (i.e. initiated by the sink + * implementation, not by someone else calling pa_sink_set_mute()), then + * the sink implementation can notify about changed mute either by calling + * pa_sink_mute_changed() or by calling pa_sink_get_mute() with + * force_refresh=true. If the implementation chooses the latter approach, + * it should implement the get_mute callback. Otherwise get_mute can be + * NULL. + * + * This is called when pa_sink_get_mute() is called with + * force_refresh=true. This is called from the IO thread if the + * PA_SINK_DEFERRED_VOLUME flag is set, otherwise this is called from the + * main thread. On success, the implementation is expected to return 0 and + * set the mute parameter that is passed as a reference. On failure, the + * implementation is expected to return -1. + * + * You must use the function pa_sink_set_get_mute_callback() to + * set this callback. */ + pa_sink_get_mute_cb_t get_mute; + + /* Called when the mute setting shall be changed. A PA_SINK_MESSAGE_SET_MUTE + * message will also be sent. Called from IO thread if PA_SINK_DEFERRED_VOLUME + * flag is set otherwise from main loop context. + * + * You must use the function pa_sink_set_set_mute_callback() to + * set this callback. */ + pa_sink_cb_t set_mute; /* may be NULL */ + + /* Called when a rewind request is issued. Called from IO thread + * context. */ + pa_sink_cb_t request_rewind; /* may be NULL */ + + /* Called when a the requested latency is changed. Called from IO + * thread context. */ + pa_sink_cb_t update_requested_latency; /* may be NULL */ + + /* Called whenever the port shall be changed. Called from the main + * thread. */ + int (*set_port)(pa_sink *s, pa_device_port *port); /* may be NULL */ + + /* Called to get the list of formats supported by the sink, sorted + * in descending order of preference. */ + pa_idxset* (*get_formats)(pa_sink *s); /* may be NULL */ + + /* Called to set the list of formats supported by the sink. Can be + * NULL if the sink does not support this. Returns true on success, + * false otherwise (for example when an unsupportable format is + * set). Makes a copy of the formats passed in. */ + bool (*set_formats)(pa_sink *s, pa_idxset *formats); /* may be NULL */ + + /* Called whenever device parameters need to be changed. Called from + * main thread. */ + void (*reconfigure)(pa_sink *s, pa_sample_spec *spec, bool passthrough); + + /* Contains copies of the above data so that the real-time worker + * thread can work without access locking */ + struct { + pa_sink_state_t state; + pa_hashmap *inputs; + + pa_rtpoll *rtpoll; + + pa_cvolume soft_volume; + bool soft_muted:1; + + /* The requested latency is used for dynamic latency + * sinks. For fixed latency sinks it is always identical to + * the fixed_latency. See below. */ + bool requested_latency_valid:1; + pa_usec_t requested_latency; + + /* The number of bytes streams need to keep around as history to + * be able to satisfy every DMA buffer rewrite */ + size_t max_rewind; + + /* The number of bytes streams need to keep around to satisfy + * every DMA write request */ + size_t max_request; + + /* Maximum of what clients requested to rewind in this cycle */ + size_t rewind_nbytes; + bool rewind_requested; + + /* Both dynamic and fixed latencies will be clamped to this + * range. */ + pa_usec_t min_latency; /* we won't go below this latency */ + pa_usec_t max_latency; /* An upper limit for the latencies */ + + /* 'Fixed' simply means that the latency is exclusively + * decided on by the sink, and the clients have no influence + * in changing it */ + pa_usec_t fixed_latency; /* for sinks with PA_SINK_DYNAMIC_LATENCY this is 0 */ + + /* This latency offset is a direct copy from s->port_latency_offset */ + int64_t port_latency_offset; + + /* Delayed volume change events are queued here. The events + * are stored in expiration order. The one expiring next is in + * the head of the list. */ + PA_LLIST_HEAD(pa_sink_volume_change, volume_changes); + pa_sink_volume_change *volume_changes_tail; + /* This value is updated in pa_sink_volume_change_apply() and + * used only by sinks with PA_SINK_DEFERRED_VOLUME. */ + pa_cvolume current_hw_volume; + + /* The amount of usec volume up events are delayed and volume + * down events are made earlier. */ + uint32_t volume_change_safety_margin; + /* Usec delay added to all volume change events, may be negative. */ + int32_t volume_change_extra_delay; + } thread_info; + + void *userdata; +}; + +PA_DECLARE_PUBLIC_CLASS(pa_sink); +#define PA_SINK(s) (pa_sink_cast(s)) + +typedef enum pa_sink_message { + PA_SINK_MESSAGE_ADD_INPUT, + PA_SINK_MESSAGE_REMOVE_INPUT, + PA_SINK_MESSAGE_GET_VOLUME, + PA_SINK_MESSAGE_SET_SHARED_VOLUME, + PA_SINK_MESSAGE_SET_VOLUME_SYNCED, + PA_SINK_MESSAGE_SET_VOLUME, + PA_SINK_MESSAGE_SYNC_VOLUMES, + PA_SINK_MESSAGE_GET_MUTE, + PA_SINK_MESSAGE_SET_MUTE, + PA_SINK_MESSAGE_GET_LATENCY, + PA_SINK_MESSAGE_GET_REQUESTED_LATENCY, + PA_SINK_MESSAGE_SET_STATE, + PA_SINK_MESSAGE_START_MOVE, + PA_SINK_MESSAGE_FINISH_MOVE, + PA_SINK_MESSAGE_SET_LATENCY_RANGE, + PA_SINK_MESSAGE_GET_LATENCY_RANGE, + PA_SINK_MESSAGE_SET_FIXED_LATENCY, + PA_SINK_MESSAGE_GET_FIXED_LATENCY, + PA_SINK_MESSAGE_GET_MAX_REWIND, + PA_SINK_MESSAGE_GET_MAX_REQUEST, + PA_SINK_MESSAGE_SET_MAX_REWIND, + PA_SINK_MESSAGE_SET_MAX_REQUEST, + PA_SINK_MESSAGE_UPDATE_VOLUME_AND_MUTE, + PA_SINK_MESSAGE_SET_PORT_LATENCY_OFFSET, + PA_SINK_MESSAGE_MAX +} pa_sink_message_t; + +typedef struct pa_sink_new_data { + pa_suspend_cause_t suspend_cause; + + char *name; + pa_proplist *proplist; + + const char *driver; + pa_module *module; + pa_card *card; + + pa_hashmap *ports; + char *active_port; + + pa_sample_spec sample_spec; + pa_channel_map channel_map; + uint32_t alternate_sample_rate; + bool avoid_resampling:1; + pa_cvolume volume; + bool muted:1; + + bool sample_spec_is_set:1; + bool channel_map_is_set:1; + bool alternate_sample_rate_is_set:1; + bool avoid_resampling_is_set:1; + bool volume_is_set:1; + bool muted_is_set:1; + + bool namereg_fail:1; + + bool save_port:1; + bool save_volume:1; + bool save_muted:1; +} pa_sink_new_data; + +pa_sink_new_data* pa_sink_new_data_init(pa_sink_new_data *data); +void pa_sink_new_data_set_name(pa_sink_new_data *data, const char *name); +void pa_sink_new_data_set_sample_spec(pa_sink_new_data *data, const pa_sample_spec *spec); +void pa_sink_new_data_set_channel_map(pa_sink_new_data *data, const pa_channel_map *map); +void pa_sink_new_data_set_alternate_sample_rate(pa_sink_new_data *data, const uint32_t alternate_sample_rate); +void pa_sink_new_data_set_avoid_resampling(pa_sink_new_data *data, bool avoid_resampling); +void pa_sink_new_data_set_volume(pa_sink_new_data *data, const pa_cvolume *volume); +void pa_sink_new_data_set_muted(pa_sink_new_data *data, bool mute); +void pa_sink_new_data_set_port(pa_sink_new_data *data, const char *port); +void pa_sink_new_data_done(pa_sink_new_data *data); + +/*** To be called exclusively by the sink driver, from main context */ + +pa_sink* pa_sink_new( + pa_core *core, + pa_sink_new_data *data, + pa_sink_flags_t flags); + +void pa_sink_set_get_volume_callback(pa_sink *s, pa_sink_cb_t cb); +void pa_sink_set_set_volume_callback(pa_sink *s, pa_sink_cb_t cb); +void pa_sink_set_write_volume_callback(pa_sink *s, pa_sink_cb_t cb); +void pa_sink_set_get_mute_callback(pa_sink *s, pa_sink_get_mute_cb_t cb); +void pa_sink_set_set_mute_callback(pa_sink *s, pa_sink_cb_t cb); +void pa_sink_enable_decibel_volume(pa_sink *s, bool enable); + +void pa_sink_put(pa_sink *s); +void pa_sink_unlink(pa_sink* s); + +void pa_sink_set_description(pa_sink *s, const char *description); +void pa_sink_set_asyncmsgq(pa_sink *s, pa_asyncmsgq *q); +void pa_sink_set_rtpoll(pa_sink *s, pa_rtpoll *p); + +void pa_sink_set_max_rewind(pa_sink *s, size_t max_rewind); +void pa_sink_set_max_request(pa_sink *s, size_t max_request); +void pa_sink_set_latency_range(pa_sink *s, pa_usec_t min_latency, pa_usec_t max_latency); +void pa_sink_set_fixed_latency(pa_sink *s, pa_usec_t latency); + +void pa_sink_set_soft_volume(pa_sink *s, const pa_cvolume *volume); +void pa_sink_volume_changed(pa_sink *s, const pa_cvolume *new_volume); +void pa_sink_mute_changed(pa_sink *s, bool new_muted); + +void pa_sink_update_flags(pa_sink *s, pa_sink_flags_t mask, pa_sink_flags_t value); + +bool pa_device_init_description(pa_proplist *p, pa_card *card); +bool pa_device_init_icon(pa_proplist *p, bool is_sink); +bool pa_device_init_intended_roles(pa_proplist *p); +unsigned pa_device_init_priority(pa_proplist *p); + +/**** May be called by everyone, from main context */ + +void pa_sink_reconfigure(pa_sink *s, pa_sample_spec *spec, bool passthrough); +void pa_sink_set_port_latency_offset(pa_sink *s, int64_t offset); + +/* The returned value is supposed to be in the time domain of the sound card! */ +pa_usec_t pa_sink_get_latency(pa_sink *s); +pa_usec_t pa_sink_get_requested_latency(pa_sink *s); +void pa_sink_get_latency_range(pa_sink *s, pa_usec_t *min_latency, pa_usec_t *max_latency); +pa_usec_t pa_sink_get_fixed_latency(pa_sink *s); + +size_t pa_sink_get_max_rewind(pa_sink *s); +size_t pa_sink_get_max_request(pa_sink *s); + +int pa_sink_update_status(pa_sink*s); +int pa_sink_suspend(pa_sink *s, bool suspend, pa_suspend_cause_t cause); +int pa_sink_suspend_all(pa_core *c, bool suspend, pa_suspend_cause_t cause); + +/* Use this instead of checking s->flags & PA_SINK_FLAT_VOLUME directly. */ +bool pa_sink_flat_volume_enabled(pa_sink *s); + +/* Get the master sink when sharing volumes */ +pa_sink *pa_sink_get_master(pa_sink *s); + +bool pa_sink_is_filter(pa_sink *s); + +/* Is the sink in passthrough mode? (that is, is there a passthrough sink input + * connected to this sink? */ +bool pa_sink_is_passthrough(pa_sink *s); +/* These should be called when a sink enters/leaves passthrough mode */ +void pa_sink_enter_passthrough(pa_sink *s); +void pa_sink_leave_passthrough(pa_sink *s); + +void pa_sink_set_volume(pa_sink *sink, const pa_cvolume *volume, bool sendmsg, bool save); +const pa_cvolume *pa_sink_get_volume(pa_sink *sink, bool force_refresh); + +void pa_sink_set_mute(pa_sink *sink, bool mute, bool save); +bool pa_sink_get_mute(pa_sink *sink, bool force_refresh); + +bool pa_sink_update_proplist(pa_sink *s, pa_update_mode_t mode, pa_proplist *p); + +int pa_sink_set_port(pa_sink *s, const char *name, bool save); + +unsigned pa_sink_linked_by(pa_sink *s); /* Number of connected streams */ +unsigned pa_sink_used_by(pa_sink *s); /* Number of connected streams which are not corked */ + +/* Returns how many streams are active that don't allow suspensions. If + * "ignore_input" or "ignore_output" is non-NULL, that stream is not included + * in the count (the returned count includes the value from + * pa_source_check_suspend(), which is called for the monitor source, so that's + * why "ignore_output" may be relevant). */ +unsigned pa_sink_check_suspend(pa_sink *s, pa_sink_input *ignore_input, pa_source_output *ignore_output); + +const char *pa_sink_state_to_string(pa_sink_state_t state); + +/* Moves all inputs away, and stores them in pa_queue */ +pa_queue *pa_sink_move_all_start(pa_sink *s, pa_queue *q); +void pa_sink_move_all_finish(pa_sink *s, pa_queue *q, bool save); +void pa_sink_move_all_fail(pa_queue *q); + +/* Returns a copy of the sink formats. TODO: Get rid of this function (or at + * least get rid of the copying). There's no good reason to copy the formats + * every time someone wants to know what formats the sink supports. The formats + * idxset could be stored directly in the pa_sink struct. + * https://bugs.freedesktop.org/show_bug.cgi?id=71924 */ +pa_idxset* pa_sink_get_formats(pa_sink *s); + +bool pa_sink_set_formats(pa_sink *s, pa_idxset *formats); +bool pa_sink_check_format(pa_sink *s, pa_format_info *f); +pa_idxset* pa_sink_check_formats(pa_sink *s, pa_idxset *in_formats); + +void pa_sink_set_sample_format(pa_sink *s, pa_sample_format_t format); +void pa_sink_set_sample_rate(pa_sink *s, uint32_t rate); + +/*** To be called exclusively by the sink driver, from IO context */ + +void pa_sink_render(pa_sink*s, size_t length, pa_memchunk *result); +void pa_sink_render_full(pa_sink *s, size_t length, pa_memchunk *result); +void pa_sink_render_into(pa_sink*s, pa_memchunk *target); +void pa_sink_render_into_full(pa_sink *s, pa_memchunk *target); + +void pa_sink_process_rewind(pa_sink *s, size_t nbytes); + +int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk); + +void pa_sink_attach_within_thread(pa_sink *s); +void pa_sink_detach_within_thread(pa_sink *s); + +pa_usec_t pa_sink_get_requested_latency_within_thread(pa_sink *s); + +void pa_sink_set_max_rewind_within_thread(pa_sink *s, size_t max_rewind); +void pa_sink_set_max_request_within_thread(pa_sink *s, size_t max_request); + +void pa_sink_set_latency_range_within_thread(pa_sink *s, pa_usec_t min_latency, pa_usec_t max_latency); +void pa_sink_set_fixed_latency_within_thread(pa_sink *s, pa_usec_t latency); + +void pa_sink_update_volume_and_mute(pa_sink *s); + +bool pa_sink_volume_change_apply(pa_sink *s, pa_usec_t *usec_to_next); + +size_t pa_sink_process_input_underruns(pa_sink *s, size_t left_to_play); + +/*** To be called exclusively by sink input drivers, from IO context */ + +void pa_sink_request_rewind(pa_sink*s, size_t nbytes); + +void pa_sink_invalidate_requested_latency(pa_sink *s, bool dynamic); + +int64_t pa_sink_get_latency_within_thread(pa_sink *s, bool allow_negative); + +/* Called from the main thread, from sink-input.c only. The normal way to set + * the sink reference volume is to call pa_sink_set_volume(), but the flat + * volume logic in sink-input.c needs also a function that doesn't do all the + * extra stuff that pa_sink_set_volume() does. This function simply sets + * s->reference_volume and fires change notifications. */ +void pa_sink_set_reference_volume_direct(pa_sink *s, const pa_cvolume *volume); + +/* When the default_sink is changed or the active_port of a sink is changed to + * PA_AVAILABLE_NO, this function is called to move the streams of the old + * default_sink or the sink with active_port equals PA_AVAILABLE_NO to the + * current default_sink conditionally*/ +void pa_sink_move_streams_to_default_sink(pa_core *core, pa_sink *old_sink, bool default_sink_changed); + +/* Verify that we called in IO context (aka 'thread context), or that + * the sink is not yet set up, i.e. the thread not set up yet. See + * pa_assert_io_context() in thread-mq.h for more information. */ +#define pa_sink_assert_io_context(s) \ + pa_assert(pa_thread_mq_get() || !PA_SINK_IS_LINKED((s)->state)) + +#endif diff --git a/src/pulsecore/sioman.c b/src/pulsecore/sioman.c new file mode 100644 index 0000000..315f10a --- /dev/null +++ b/src/pulsecore/sioman.c @@ -0,0 +1,37 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulsecore/macro.h> +#include <pulsecore/atomic.h> + +#include "sioman.h" + +static pa_atomic_t stdio_inuse = PA_ATOMIC_INIT(0); + +int pa_stdio_acquire(void) { + return pa_atomic_cmpxchg(&stdio_inuse, 0, 1) ? 0 : -1; +} + +void pa_stdio_release(void) { + pa_assert_se(pa_atomic_cmpxchg(&stdio_inuse, 1, 0)); +} diff --git a/src/pulsecore/sioman.h b/src/pulsecore/sioman.h new file mode 100644 index 0000000..10ad382 --- /dev/null +++ b/src/pulsecore/sioman.h @@ -0,0 +1,26 @@ +#ifndef foosiomanhfoo +#define foosiomanhfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +int pa_stdio_acquire(void); +void pa_stdio_release(void); + +#endif diff --git a/src/pulsecore/sndfile-util.c b/src/pulsecore/sndfile-util.c new file mode 100644 index 0000000..b6cc65e --- /dev/null +++ b/src/pulsecore/sndfile-util.c @@ -0,0 +1,461 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +/* Shared between pacat/parec/paplay and the server */ + +#include <pulse/xmalloc.h> +#include <pulse/utf8.h> + +#include <pulsecore/macro.h> + +#include "sndfile-util.h" + +int pa_sndfile_read_sample_spec(SNDFILE *sf, pa_sample_spec *ss) { + SF_INFO sfi; + int sf_errno; + + pa_assert(sf); + pa_assert(ss); + + pa_zero(sfi); + if ((sf_errno = sf_command(sf, SFC_GET_CURRENT_SF_INFO, &sfi, sizeof(sfi)))) { + pa_log_error("sndfile: %s", sf_error_number(sf_errno)); + return -1; + } + + switch (sfi.format & SF_FORMAT_SUBMASK) { + + case SF_FORMAT_PCM_16: + case SF_FORMAT_PCM_U8: + case SF_FORMAT_PCM_S8: + ss->format = PA_SAMPLE_S16NE; + break; + + case SF_FORMAT_PCM_24: + ss->format = PA_SAMPLE_S24NE; + break; + + case SF_FORMAT_PCM_32: + ss->format = PA_SAMPLE_S32NE; + break; + + case SF_FORMAT_ULAW: + ss->format = PA_SAMPLE_ULAW; + break; + + case SF_FORMAT_ALAW: + ss->format = PA_SAMPLE_ALAW; + break; + + case SF_FORMAT_FLOAT: + case SF_FORMAT_DOUBLE: + default: + ss->format = PA_SAMPLE_FLOAT32NE; + break; + } + + ss->rate = (uint32_t) sfi.samplerate; + ss->channels = (uint8_t) sfi.channels; + + if (!pa_sample_spec_valid(ss)) + return -1; + + return 0; +} + +int pa_sndfile_write_sample_spec(SF_INFO *sfi, pa_sample_spec *ss) { + pa_assert(sfi); + pa_assert(ss); + + sfi->samplerate = (int) ss->rate; + sfi->channels = (int) ss->channels; + + if (pa_sample_format_is_le(ss->format) > 0) + sfi->format = SF_ENDIAN_LITTLE; + else if (pa_sample_format_is_be(ss->format) > 0) + sfi->format = SF_ENDIAN_BIG; + + switch (ss->format) { + + case PA_SAMPLE_U8: + ss->format = PA_SAMPLE_S16NE; + sfi->format = SF_FORMAT_PCM_U8; + break; + + case PA_SAMPLE_S16LE: + case PA_SAMPLE_S16BE: + ss->format = PA_SAMPLE_S16NE; + sfi->format |= SF_FORMAT_PCM_16; + break; + + case PA_SAMPLE_S24LE: + case PA_SAMPLE_S24BE: + ss->format = PA_SAMPLE_S24NE; + sfi->format |= SF_FORMAT_PCM_24; + break; + + case PA_SAMPLE_S24_32LE: + case PA_SAMPLE_S24_32BE: + ss->format = PA_SAMPLE_S24_32NE; + sfi->format |= SF_FORMAT_PCM_32; + break; + + case PA_SAMPLE_S32LE: + case PA_SAMPLE_S32BE: + ss->format = PA_SAMPLE_S32NE; + sfi->format |= SF_FORMAT_PCM_32; + break; + + case PA_SAMPLE_ULAW: + sfi->format = SF_FORMAT_ULAW; + break; + + case PA_SAMPLE_ALAW: + sfi->format = SF_FORMAT_ALAW; + break; + + case PA_SAMPLE_FLOAT32LE: + case PA_SAMPLE_FLOAT32BE: + default: + ss->format = PA_SAMPLE_FLOAT32NE; + sfi->format |= SF_FORMAT_FLOAT; + break; + } + + if (!pa_sample_spec_valid(ss)) + return -1; + + return 0; +} + +int pa_sndfile_read_channel_map(SNDFILE *sf, pa_channel_map *cm) { + + static const pa_channel_position_t table[] = { + [SF_CHANNEL_MAP_MONO] = PA_CHANNEL_POSITION_MONO, + [SF_CHANNEL_MAP_LEFT] = PA_CHANNEL_POSITION_FRONT_LEFT, /* libsndfile distinguishes left and front-left, which we don't */ + [SF_CHANNEL_MAP_RIGHT] = PA_CHANNEL_POSITION_FRONT_RIGHT, + [SF_CHANNEL_MAP_CENTER] = PA_CHANNEL_POSITION_FRONT_CENTER, + [SF_CHANNEL_MAP_FRONT_LEFT] = PA_CHANNEL_POSITION_FRONT_LEFT, + [SF_CHANNEL_MAP_FRONT_RIGHT] = PA_CHANNEL_POSITION_FRONT_RIGHT, + [SF_CHANNEL_MAP_FRONT_CENTER] = PA_CHANNEL_POSITION_FRONT_CENTER, + [SF_CHANNEL_MAP_REAR_CENTER] = PA_CHANNEL_POSITION_REAR_CENTER, + [SF_CHANNEL_MAP_REAR_LEFT] = PA_CHANNEL_POSITION_REAR_LEFT, + [SF_CHANNEL_MAP_REAR_RIGHT] = PA_CHANNEL_POSITION_REAR_RIGHT, + [SF_CHANNEL_MAP_LFE] = PA_CHANNEL_POSITION_LFE, + [SF_CHANNEL_MAP_FRONT_LEFT_OF_CENTER] = PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER, + [SF_CHANNEL_MAP_FRONT_RIGHT_OF_CENTER] = PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER, + [SF_CHANNEL_MAP_SIDE_LEFT] = PA_CHANNEL_POSITION_SIDE_LEFT, + [SF_CHANNEL_MAP_SIDE_RIGHT] = PA_CHANNEL_POSITION_SIDE_RIGHT, + [SF_CHANNEL_MAP_TOP_CENTER] = PA_CHANNEL_POSITION_TOP_CENTER, + [SF_CHANNEL_MAP_TOP_FRONT_LEFT] = PA_CHANNEL_POSITION_TOP_FRONT_LEFT, + [SF_CHANNEL_MAP_TOP_FRONT_RIGHT] = PA_CHANNEL_POSITION_TOP_FRONT_RIGHT, + [SF_CHANNEL_MAP_TOP_FRONT_CENTER] = PA_CHANNEL_POSITION_TOP_FRONT_CENTER, + [SF_CHANNEL_MAP_TOP_REAR_LEFT] = PA_CHANNEL_POSITION_TOP_REAR_LEFT, + [SF_CHANNEL_MAP_TOP_REAR_RIGHT] = PA_CHANNEL_POSITION_TOP_REAR_RIGHT, + [SF_CHANNEL_MAP_TOP_REAR_CENTER] = PA_CHANNEL_POSITION_TOP_REAR_CENTER + }; + + SF_INFO sfi; + int sf_errno; + int *channels; + unsigned c; + + pa_assert(sf); + pa_assert(cm); + + pa_zero(sfi); + if ((sf_errno = sf_command(sf, SFC_GET_CURRENT_SF_INFO, &sfi, sizeof(sfi)))) { + pa_log_error("sndfile: %s", sf_error_number(sf_errno)); + return -1; + } + + channels = pa_xnew(int, sfi.channels); + if (!sf_command(sf, SFC_GET_CHANNEL_MAP_INFO, channels, sizeof(channels[0]) * sfi.channels)) { + pa_xfree(channels); + return -1; + } + + cm->channels = (uint8_t) sfi.channels; + for (c = 0; c < cm->channels; c++) { + if (channels[c] <= SF_CHANNEL_MAP_INVALID || + (unsigned) channels[c] >= PA_ELEMENTSOF(table)) { + pa_xfree(channels); + return -1; + } + + cm->map[c] = table[channels[c]]; + } + + pa_xfree(channels); + + if (!pa_channel_map_valid(cm)) + return -1; + + return 0; +} + +int pa_sndfile_write_channel_map(SNDFILE *sf, pa_channel_map *cm) { + static const int table[PA_CHANNEL_POSITION_MAX] = { + [PA_CHANNEL_POSITION_MONO] = SF_CHANNEL_MAP_MONO, + + [PA_CHANNEL_POSITION_FRONT_LEFT] = SF_CHANNEL_MAP_FRONT_LEFT, + [PA_CHANNEL_POSITION_FRONT_RIGHT] = SF_CHANNEL_MAP_FRONT_RIGHT, + [PA_CHANNEL_POSITION_FRONT_CENTER] = SF_CHANNEL_MAP_FRONT_CENTER, + + [PA_CHANNEL_POSITION_REAR_CENTER] = SF_CHANNEL_MAP_REAR_CENTER, + [PA_CHANNEL_POSITION_REAR_LEFT] = SF_CHANNEL_MAP_REAR_LEFT, + [PA_CHANNEL_POSITION_REAR_RIGHT] = SF_CHANNEL_MAP_REAR_RIGHT, + + [PA_CHANNEL_POSITION_LFE] = SF_CHANNEL_MAP_LFE, + + [PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER] = SF_CHANNEL_MAP_FRONT_LEFT_OF_CENTER, + [PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER] = SF_CHANNEL_MAP_FRONT_RIGHT_OF_CENTER, + + [PA_CHANNEL_POSITION_SIDE_LEFT] = SF_CHANNEL_MAP_SIDE_LEFT, + [PA_CHANNEL_POSITION_SIDE_RIGHT] = SF_CHANNEL_MAP_SIDE_RIGHT, + + [PA_CHANNEL_POSITION_AUX0] = -1, + [PA_CHANNEL_POSITION_AUX1] = -1, + [PA_CHANNEL_POSITION_AUX2] = -1, + [PA_CHANNEL_POSITION_AUX3] = -1, + [PA_CHANNEL_POSITION_AUX4] = -1, + [PA_CHANNEL_POSITION_AUX5] = -1, + [PA_CHANNEL_POSITION_AUX6] = -1, + [PA_CHANNEL_POSITION_AUX7] = -1, + [PA_CHANNEL_POSITION_AUX8] = -1, + [PA_CHANNEL_POSITION_AUX9] = -1, + [PA_CHANNEL_POSITION_AUX10] = -1, + [PA_CHANNEL_POSITION_AUX11] = -1, + [PA_CHANNEL_POSITION_AUX12] = -1, + [PA_CHANNEL_POSITION_AUX13] = -1, + [PA_CHANNEL_POSITION_AUX14] = -1, + [PA_CHANNEL_POSITION_AUX15] = -1, + [PA_CHANNEL_POSITION_AUX16] = -1, + [PA_CHANNEL_POSITION_AUX17] = -1, + [PA_CHANNEL_POSITION_AUX18] = -1, + [PA_CHANNEL_POSITION_AUX19] = -1, + [PA_CHANNEL_POSITION_AUX20] = -1, + [PA_CHANNEL_POSITION_AUX21] = -1, + [PA_CHANNEL_POSITION_AUX22] = -1, + [PA_CHANNEL_POSITION_AUX23] = -1, + [PA_CHANNEL_POSITION_AUX24] = -1, + [PA_CHANNEL_POSITION_AUX25] = -1, + [PA_CHANNEL_POSITION_AUX26] = -1, + [PA_CHANNEL_POSITION_AUX27] = -1, + [PA_CHANNEL_POSITION_AUX28] = -1, + [PA_CHANNEL_POSITION_AUX29] = -1, + [PA_CHANNEL_POSITION_AUX30] = -1, + [PA_CHANNEL_POSITION_AUX31] = -1, + + [PA_CHANNEL_POSITION_TOP_CENTER] = SF_CHANNEL_MAP_TOP_CENTER, + + [PA_CHANNEL_POSITION_TOP_FRONT_LEFT] = SF_CHANNEL_MAP_TOP_FRONT_LEFT, + [PA_CHANNEL_POSITION_TOP_FRONT_RIGHT] = SF_CHANNEL_MAP_TOP_FRONT_RIGHT, + [PA_CHANNEL_POSITION_TOP_FRONT_CENTER] = SF_CHANNEL_MAP_TOP_FRONT_CENTER , + + [PA_CHANNEL_POSITION_TOP_REAR_LEFT] = SF_CHANNEL_MAP_TOP_REAR_LEFT, + [PA_CHANNEL_POSITION_TOP_REAR_RIGHT] = SF_CHANNEL_MAP_TOP_REAR_RIGHT, + [PA_CHANNEL_POSITION_TOP_REAR_CENTER] = SF_CHANNEL_MAP_TOP_REAR_CENTER, + }; + + int *channels; + unsigned c; + + pa_assert(sf); + pa_assert(cm); + + /* Suppress channel mapping for the obvious cases */ + if (cm->channels == 1 && cm->map[0] == PA_CHANNEL_POSITION_MONO) + return 0; + + if (cm->channels == 2 && + cm->map[0] == PA_CHANNEL_POSITION_FRONT_LEFT && + cm->map[1] == PA_CHANNEL_POSITION_FRONT_RIGHT) + return 0; + + channels = pa_xnew(int, cm->channels); + for (c = 0; c < cm->channels; c++) { + + if (cm->map[c] < 0 || + cm->map[c] >= PA_CHANNEL_POSITION_MAX || + table[cm->map[c]] < 0) { + pa_xfree(channels); + return -1; + } + + channels[c] = table[cm->map[c]]; + } + + if (!sf_command(sf, SFC_SET_CHANNEL_MAP_INFO, channels, sizeof(channels[0]) * cm->channels)) { + pa_xfree(channels); + return -1; + } + + pa_xfree(channels); + return 0; +} + +void pa_sndfile_init_proplist(SNDFILE *sf, pa_proplist *p) { + + static const char* table[] = { + [SF_STR_TITLE] = PA_PROP_MEDIA_TITLE, + [SF_STR_COPYRIGHT] = PA_PROP_MEDIA_COPYRIGHT, + [SF_STR_SOFTWARE] = PA_PROP_MEDIA_SOFTWARE, + [SF_STR_ARTIST] = PA_PROP_MEDIA_ARTIST, + [SF_STR_COMMENT] = "media.comment", + [SF_STR_DATE] = "media.date" + }; + + SF_INFO sfi; + SF_FORMAT_INFO fi; + int sf_errno; + unsigned c; + + pa_assert(sf); + pa_assert(p); + + for (c = 0; c < PA_ELEMENTSOF(table); c++) { + const char *s; + char *t; + + if (!table[c]) + continue; + + if (!(s = sf_get_string(sf, c))) + continue; + + t = pa_utf8_filter(s); + pa_proplist_sets(p, table[c], t); + pa_xfree(t); + } + + pa_zero(sfi); + if ((sf_errno = sf_command(sf, SFC_GET_CURRENT_SF_INFO, &sfi, sizeof(sfi)))) { + pa_log_error("sndfile: %s", sf_error_number(sf_errno)); + return; + } + + pa_zero(fi); + fi.format = sfi.format; + if (sf_command(sf, SFC_GET_FORMAT_INFO, &fi, sizeof(fi)) == 0 && fi.name) { + char *t; + + t = pa_utf8_filter(fi.name); + pa_proplist_sets(p, "media.format", t); + pa_xfree(t); + } +} + +pa_sndfile_readf_t pa_sndfile_readf_function(const pa_sample_spec *ss) { + pa_assert(ss); + + switch (ss->format) { + case PA_SAMPLE_S16NE: + return (pa_sndfile_readf_t) sf_readf_short; + + case PA_SAMPLE_S32NE: + case PA_SAMPLE_S24_32NE: + return (pa_sndfile_readf_t) sf_readf_int; + + case PA_SAMPLE_FLOAT32NE: + return (pa_sndfile_readf_t) sf_readf_float; + + case PA_SAMPLE_ULAW: + case PA_SAMPLE_ALAW: + case PA_SAMPLE_S24NE: + return NULL; + + default: + pa_assert_not_reached(); + } +} + +pa_sndfile_writef_t pa_sndfile_writef_function(const pa_sample_spec *ss) { + pa_assert(ss); + + switch (ss->format) { + case PA_SAMPLE_S16NE: + return (pa_sndfile_writef_t) sf_writef_short; + + case PA_SAMPLE_S32NE: + case PA_SAMPLE_S24_32NE: + return (pa_sndfile_writef_t) sf_writef_int; + + case PA_SAMPLE_FLOAT32NE: + return (pa_sndfile_writef_t) sf_writef_float; + + case PA_SAMPLE_ULAW: + case PA_SAMPLE_ALAW: + case PA_SAMPLE_S24NE: + return NULL; + + default: + pa_assert_not_reached(); + } +} + +int pa_sndfile_format_from_string(const char *name) { + int i, count = 0; + + if (!name[0]) + return -1; + + pa_assert_se(sf_command(NULL, SFC_GET_FORMAT_MAJOR_COUNT, &count, sizeof(int)) == 0); + + for (i = 0; i < count; i++) { + SF_FORMAT_INFO fi; + pa_zero(fi); + fi.format = i; + + pa_assert_se(sf_command(NULL, SFC_GET_FORMAT_MAJOR, &fi, sizeof(fi)) == 0); + + /* First try to match via full type string */ + if (strcasecmp(name, fi.name) == 0) + return fi.format; + + /* Then, try to match via the full extension */ + if (strcasecmp(name, fi.extension) == 0) + return fi.format; + + /* Then, try to match via the start of the type string */ + if (strncasecmp(name, fi.name, strlen(name)) == 0) + return fi.format; + } + + return -1; +} + +void pa_sndfile_dump_formats(void) { + int i, count = 0; + + pa_assert_se(sf_command(NULL, SFC_GET_FORMAT_MAJOR_COUNT, &count, sizeof(int)) == 0); + + for (i = 0; i < count; i++) { + SF_FORMAT_INFO fi; + pa_zero(fi); + fi.format = i; + + pa_assert_se(sf_command(NULL, SFC_GET_FORMAT_MAJOR, &fi, sizeof(fi)) == 0); + printf("%s\t%s\n", fi.extension, fi.name); + } +} diff --git a/src/pulsecore/sndfile-util.h b/src/pulsecore/sndfile-util.h new file mode 100644 index 0000000..1f67ac3 --- /dev/null +++ b/src/pulsecore/sndfile-util.h @@ -0,0 +1,50 @@ +#ifndef foopulsecoresndfileutilhfoo +#define foopulsecoresndfileutilhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2009 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <sndfile.h> + +#include <pulse/sample.h> +#include <pulse/channelmap.h> +#include <pulse/proplist.h> + +int pa_sndfile_read_sample_spec(SNDFILE *sf, pa_sample_spec *ss); +int pa_sndfile_read_channel_map(SNDFILE *sf, pa_channel_map *cm); + +int pa_sndfile_write_sample_spec(SF_INFO *sfi, pa_sample_spec *ss); +int pa_sndfile_write_channel_map(SNDFILE *sf, pa_channel_map *cm); + +void pa_sndfile_init_proplist(SNDFILE *sf, pa_proplist *p); + +typedef sf_count_t (*pa_sndfile_readf_t)(SNDFILE *sndfile, void *ptr, sf_count_t frames); +typedef sf_count_t (*pa_sndfile_writef_t)(SNDFILE *sndfile, const void *ptr, sf_count_t frames); + +/* Returns NULL if sf_read_raw() shall be used */ +pa_sndfile_readf_t pa_sndfile_readf_function(const pa_sample_spec *ss); + +/* Returns NULL if sf_write_raw() shall be used */ +pa_sndfile_writef_t pa_sndfile_writef_function(const pa_sample_spec *ss); + +int pa_sndfile_format_from_string(const char *extension); + +void pa_sndfile_dump_formats(void); + +#endif diff --git a/src/pulsecore/socket-client.c b/src/pulsecore/socket-client.c new file mode 100644 index 0000000..c87406d --- /dev/null +++ b/src/pulsecore/socket-client.c @@ -0,0 +1,563 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + 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 by the Free Software Foundation; either version 2.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +/* #undef HAVE_LIBASYNCNS */ + +#include <unistd.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <stdlib.h> + +#ifdef HAVE_SYS_UN_H +#include <sys/un.h> +#endif +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif + +#ifdef HAVE_LIBASYNCNS +#include <asyncns.h> +#endif + +#include <pulse/rtclock.h> +#include <pulse/timeval.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/socket.h> +#include <pulsecore/socket-util.h> +#include <pulsecore/core-error.h> +#include <pulsecore/core-rtclock.h> +#include <pulsecore/core-util.h> +#include <pulsecore/socket-util.h> +#include <pulsecore/log.h> +#include <pulsecore/parseaddr.h> +#include <pulsecore/macro.h> +#include <pulsecore/refcnt.h> +#include <pulsecore/arpa-inet.h> + +#include "socket-client.h" + +#define CONNECT_TIMEOUT 5 + +struct pa_socket_client { + PA_REFCNT_DECLARE; + int fd; + + pa_mainloop_api *mainloop; + pa_io_event *io_event; + pa_time_event *timeout_event; + pa_defer_event *defer_event; + + pa_socket_client_cb_t callback; + void *userdata; + + bool local; + +#ifdef HAVE_LIBASYNCNS + asyncns_t *asyncns; + asyncns_query_t * asyncns_query; + pa_io_event *asyncns_io_event; +#endif +}; + +static pa_socket_client* socket_client_new(pa_mainloop_api *m) { + pa_socket_client *c; + pa_assert(m); + + c = pa_xnew0(pa_socket_client, 1); + PA_REFCNT_INIT(c); + c->mainloop = m; + c->fd = -1; + + return c; +} + +static void free_events(pa_socket_client *c) { + pa_assert(c); + + if (c->io_event) { + c->mainloop->io_free(c->io_event); + c->io_event = NULL; + } + + if (c->timeout_event) { + c->mainloop->time_free(c->timeout_event); + c->timeout_event = NULL; + } + + if (c->defer_event) { + c->mainloop->defer_free(c->defer_event); + c->defer_event = NULL; + } +} + +static void do_call(pa_socket_client *c) { + pa_iochannel *io = NULL; + int error; + socklen_t lerror; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(c->callback); + + pa_socket_client_ref(c); + + if (c->fd < 0) + goto finish; + + lerror = sizeof(error); + if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, (void*)&error, &lerror) < 0) { + pa_log("getsockopt(): %s", pa_cstrerror(errno)); + goto finish; + } + + if (lerror != sizeof(error)) { + pa_log("getsockopt() returned invalid size."); + goto finish; + } + + if (error != 0) { + pa_log_debug("connect(): %s", pa_cstrerror(error)); + errno = error; + goto finish; + } + + io = pa_iochannel_new(c->mainloop, c->fd, c->fd); + +finish: + if (!io && c->fd >= 0) + pa_close(c->fd); + c->fd = -1; + + free_events(c); + + c->callback(c, io, c->userdata); + + pa_socket_client_unref(c); +} + +static void connect_defer_cb(pa_mainloop_api *m, pa_defer_event *e, void *userdata) { + pa_socket_client *c = userdata; + + pa_assert(m); + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(c->defer_event == e); + + do_call(c); +} + +static void connect_io_cb(pa_mainloop_api*m, pa_io_event *e, int fd, pa_io_event_flags_t f, void *userdata) { + pa_socket_client *c = userdata; + + pa_assert(m); + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(c->io_event == e); + pa_assert(fd >= 0); + + do_call(c); +} + +static int do_connect(pa_socket_client *c, const struct sockaddr *sa, socklen_t len) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(sa); + pa_assert(len > 0); + + pa_make_fd_nonblock(c->fd); + + if (connect(c->fd, sa, len) < 0) { +#ifdef OS_IS_WIN32 + if (WSAGetLastError() != EWOULDBLOCK) { + pa_log_debug("connect(): %d", WSAGetLastError()); +#else + if (errno != EINPROGRESS) { + pa_log_debug("connect(): %s (%d)", pa_cstrerror(errno), errno); +#endif + return -1; + } + + c->io_event = c->mainloop->io_new(c->mainloop, c->fd, PA_IO_EVENT_OUTPUT, connect_io_cb, c); + } else + c->defer_event = c->mainloop->defer_new(c->mainloop, connect_defer_cb, c); + + return 0; +} + +pa_socket_client* pa_socket_client_new_ipv4(pa_mainloop_api *m, uint32_t address, uint16_t port) { + struct sockaddr_in sa; + + pa_assert(m); + pa_assert(port > 0); + + pa_zero(sa); + sa.sin_family = AF_INET; + sa.sin_port = htons(port); + sa.sin_addr.s_addr = htonl(address); + + return pa_socket_client_new_sockaddr(m, (struct sockaddr*) &sa, sizeof(sa)); +} + +pa_socket_client* pa_socket_client_new_unix(pa_mainloop_api *m, const char *filename) { +#ifdef HAVE_SYS_UN_H + struct sockaddr_un sa; + + pa_assert(m); + pa_assert(filename); + + pa_zero(sa); + sa.sun_family = AF_UNIX; + pa_strlcpy(sa.sun_path, filename, sizeof(sa.sun_path)); + + return pa_socket_client_new_sockaddr(m, (struct sockaddr*) &sa, sizeof(sa)); +#else /* HAVE_SYS_UN_H */ + + return NULL; +#endif /* HAVE_SYS_UN_H */ +} + +static int sockaddr_prepare(pa_socket_client *c, const struct sockaddr *sa, size_t salen) { + pa_assert(c); + pa_assert(sa); + pa_assert(salen); + + c->local = pa_socket_address_is_local(sa); + + if ((c->fd = pa_socket_cloexec(sa->sa_family, SOCK_STREAM, 0)) < 0) { + pa_log("socket(): %s", pa_cstrerror(errno)); + return -1; + } + +#ifdef HAVE_IPV6 + if (sa->sa_family == AF_INET || sa->sa_family == AF_INET6) +#else + if (sa->sa_family == AF_INET) +#endif + pa_make_tcp_socket_low_delay(c->fd); + else + pa_make_socket_low_delay(c->fd); + + if (do_connect(c, sa, (socklen_t) salen) < 0) + return -1; + + return 0; +} + +pa_socket_client* pa_socket_client_new_sockaddr(pa_mainloop_api *m, const struct sockaddr *sa, size_t salen) { + pa_socket_client *c; + + pa_assert(m); + pa_assert(sa); + pa_assert(salen > 0); + + c = socket_client_new(m); + + if (sockaddr_prepare(c, sa, salen) < 0) + goto fail; + + return c; + +fail: + pa_socket_client_unref(c); + return NULL; +} + +static void socket_client_free(pa_socket_client *c) { + pa_assert(c); + pa_assert(c->mainloop); + + free_events(c); + + if (c->fd >= 0) + pa_close(c->fd); + +#ifdef HAVE_LIBASYNCNS + if (c->asyncns_query) + asyncns_cancel(c->asyncns, c->asyncns_query); + if (c->asyncns) + asyncns_free(c->asyncns); + if (c->asyncns_io_event) + c->mainloop->io_free(c->asyncns_io_event); +#endif + + pa_xfree(c); +} + +void pa_socket_client_unref(pa_socket_client *c) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + if (PA_REFCNT_DEC(c) <= 0) + socket_client_free(c); +} + +pa_socket_client* pa_socket_client_ref(pa_socket_client *c) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_REFCNT_INC(c); + return c; +} + +void pa_socket_client_set_callback(pa_socket_client *c, pa_socket_client_cb_t on_connection, void *userdata) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + c->callback = on_connection; + c->userdata = userdata; +} + +pa_socket_client* pa_socket_client_new_ipv6(pa_mainloop_api *m, uint8_t address[16], uint16_t port) { +#ifdef HAVE_IPV6 + struct sockaddr_in6 sa; + + pa_assert(m); + pa_assert(address); + pa_assert(port > 0); + + pa_zero(sa); + sa.sin6_family = AF_INET6; + sa.sin6_port = htons(port); + memcpy(&sa.sin6_addr, address, sizeof(sa.sin6_addr)); + + return pa_socket_client_new_sockaddr(m, (struct sockaddr*) &sa, sizeof(sa)); + +#else + return NULL; +#endif +} + +#ifdef HAVE_LIBASYNCNS + +static void asyncns_cb(pa_mainloop_api*m, pa_io_event *e, int fd, pa_io_event_flags_t f, void *userdata) { + pa_socket_client *c = userdata; + struct addrinfo *res = NULL; + int ret; + + pa_assert(m); + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(c->asyncns_io_event == e); + pa_assert(fd >= 0); + + if (asyncns_wait(c->asyncns, 0) < 0) + goto fail; + + if (!asyncns_isdone(c->asyncns, c->asyncns_query)) + return; + + ret = asyncns_getaddrinfo_done(c->asyncns, c->asyncns_query, &res); + c->asyncns_query = NULL; + + if (ret != 0 || !res) + goto fail; + + if (res->ai_addr) + if (sockaddr_prepare(c, res->ai_addr, res->ai_addrlen) < 0) + goto fail; + + asyncns_freeaddrinfo(res); + + m->io_free(c->asyncns_io_event); + c->asyncns_io_event = NULL; + return; + +fail: + m->io_free(c->asyncns_io_event); + c->asyncns_io_event = NULL; + + errno = EHOSTUNREACH; + do_call(c); + return; + +} + +#endif + +static void timeout_cb(pa_mainloop_api *m, pa_time_event *e, const struct timeval *t, void *userdata) { + pa_socket_client *c = userdata; + + pa_assert(m); + pa_assert(e); + pa_assert(c); + + if (c->fd >= 0) { + pa_close(c->fd); + c->fd = -1; + } + + errno = ETIMEDOUT; + do_call(c); +} + +static void start_timeout(pa_socket_client *c, bool use_rtclock) { + struct timeval tv; + + pa_assert(c); + pa_assert(!c->timeout_event); + + c->timeout_event = c->mainloop->time_new(c->mainloop, pa_timeval_rtstore(&tv, pa_rtclock_now() + CONNECT_TIMEOUT * PA_USEC_PER_SEC, use_rtclock), timeout_cb, c); +} + +pa_socket_client* pa_socket_client_new_string(pa_mainloop_api *m, bool use_rtclock, const char*name, uint16_t default_port) { + pa_socket_client *c = NULL; + pa_parsed_address a; + char *name_buf; + + pa_assert(m); + pa_assert(name); + + a.path_or_host = NULL; + + if (pa_is_ip6_address(name)) { + size_t len = strlen(name); + name_buf = pa_xmalloc(len + 3); + memcpy(name_buf + 1, name, len); + name_buf[0] = '['; + name_buf[len + 1] = ']'; + name_buf[len + 2] = '\0'; + } else { + name_buf = pa_xstrdup(name); + } + + if (pa_parse_address(name_buf, &a) < 0) { + pa_log_warn("parsing address failed: %s", name_buf); + goto finish; + } + + if (!a.port) + a.port = default_port; + + switch (a.type) { + case PA_PARSED_ADDRESS_UNIX: + if ((c = pa_socket_client_new_unix(m, a.path_or_host))) + start_timeout(c, use_rtclock); + break; + + case PA_PARSED_ADDRESS_TCP4: /* Fallthrough */ + case PA_PARSED_ADDRESS_TCP6: /* Fallthrough */ + case PA_PARSED_ADDRESS_TCP_AUTO: { + struct addrinfo hints; + char port[12]; + + pa_snprintf(port, sizeof(port), "%u", (unsigned) a.port); + + pa_zero(hints); + if (a.type == PA_PARSED_ADDRESS_TCP4) + hints.ai_family = PF_INET; +#ifdef HAVE_IPV6 + else if (a.type == PA_PARSED_ADDRESS_TCP6) + hints.ai_family = PF_INET6; +#endif + else + hints.ai_family = PF_UNSPEC; + + hints.ai_socktype = SOCK_STREAM; + +#if defined(HAVE_LIBASYNCNS) + { + asyncns_t *asyncns; + + if (!(asyncns = asyncns_new(1))) + goto finish; + + c = socket_client_new(m); + c->asyncns = asyncns; + c->asyncns_io_event = m->io_new(m, asyncns_fd(c->asyncns), PA_IO_EVENT_INPUT, asyncns_cb, c); + pa_assert_se(c->asyncns_query = asyncns_getaddrinfo(c->asyncns, a.path_or_host, port, &hints)); + start_timeout(c, use_rtclock); + } +#elif defined(HAVE_GETADDRINFO) + { + int ret; + struct addrinfo *res = NULL; + + ret = getaddrinfo(a.path_or_host, port, &hints, &res); + + if (ret < 0 || !res) + goto finish; + + if (res->ai_addr) { + if ((c = pa_socket_client_new_sockaddr(m, res->ai_addr, res->ai_addrlen))) + start_timeout(c, use_rtclock); + } + + freeaddrinfo(res); + } +#else + { + struct hostent *host = NULL; + struct sockaddr_in s; + +#ifdef HAVE_IPV6 + /* FIXME: PF_INET6 support */ + if (hints.ai_family == PF_INET6) { + pa_log_error("IPv6 is not supported on Windows"); + goto finish; + } +#endif + + host = gethostbyname(a.path_or_host); + if (!host) { + unsigned int addr = inet_addr(a.path_or_host); + if (addr != INADDR_NONE) + host = gethostbyaddr((char*)&addr, 4, AF_INET); + } + + if (!host) + goto finish; + + pa_zero(s); + s.sin_family = AF_INET; + memcpy(&s.sin_addr, host->h_addr, sizeof(struct in_addr)); + s.sin_port = htons(a.port); + + if ((c = pa_socket_client_new_sockaddr(m, (struct sockaddr*)&s, sizeof(s)))) + start_timeout(c, use_rtclock); + } +#endif /* HAVE_LIBASYNCNS */ + } + } + +finish: + pa_xfree(name_buf); + pa_xfree(a.path_or_host); + return c; + +} + +/* Return non-zero when the target sockaddr is considered + local. "local" means UNIX socket or TCP socket on localhost. Other + local IP addresses are not considered local. */ +bool pa_socket_client_is_local(pa_socket_client *c) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + return c->local; +} diff --git a/src/pulsecore/socket-client.h b/src/pulsecore/socket-client.h new file mode 100644 index 0000000..9d22072 --- /dev/null +++ b/src/pulsecore/socket-client.h @@ -0,0 +1,48 @@ +#ifndef foosocketclienthfoo +#define foosocketclienthfoo + +/*** + 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <inttypes.h> + +#include <pulse/mainloop-api.h> +#include <pulsecore/iochannel.h> + +struct sockaddr; + +typedef struct pa_socket_client pa_socket_client; + +typedef void (*pa_socket_client_cb_t)(pa_socket_client *c, pa_iochannel*io, void *userdata); + +pa_socket_client* pa_socket_client_new_ipv4(pa_mainloop_api *m, uint32_t address, uint16_t port); +pa_socket_client* pa_socket_client_new_ipv6(pa_mainloop_api *m, uint8_t address[16], uint16_t port); +pa_socket_client* pa_socket_client_new_unix(pa_mainloop_api *m, const char *filename); +pa_socket_client* pa_socket_client_new_sockaddr(pa_mainloop_api *m, const struct sockaddr *sa, size_t salen); +pa_socket_client* pa_socket_client_new_string(pa_mainloop_api *m, bool use_rtclock, const char *a, uint16_t default_port); + +pa_socket_client* pa_socket_client_ref(pa_socket_client *c); +void pa_socket_client_unref(pa_socket_client *c); + +void pa_socket_client_set_callback(pa_socket_client *c, pa_socket_client_cb_t on_connection, void *userdata); + +bool pa_socket_client_is_local(pa_socket_client *c); + +#endif diff --git a/src/pulsecore/socket-server.c b/src/pulsecore/socket-server.c new file mode 100644 index 0000000..bc5116a --- /dev/null +++ b/src/pulsecore/socket-server.c @@ -0,0 +1,613 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + 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 + by the Free Software Foundation; either version 2.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <errno.h> +#include <string.h> +#include <sys/types.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/stat.h> + +#ifdef HAVE_SYS_UN_H +#include <sys/un.h> +#ifndef SUN_LEN +#define SUN_LEN(ptr) \ + ((size_t)(((struct sockaddr_un *) 0)->sun_path) + strlen((ptr)->sun_path)) +#endif +#endif +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif + +#ifdef HAVE_LIBWRAP +#include <tcpd.h> + +/* Solaris requires that the allow_severity and deny_severity variables be + * defined in the client program. */ +#ifdef __sun +#include <syslog.h> +int allow_severity = LOG_INFO; +int deny_severity = LOG_WARNING; +#endif + +#endif /* HAVE_LIBWRAP */ + +#ifdef HAVE_SYSTEMD_DAEMON +#include <systemd/sd-daemon.h> +#endif + +#include <pulse/xmalloc.h> +#include <pulse/util.h> + +#include <pulsecore/socket.h> +#include <pulsecore/socket-util.h> +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> +#include <pulsecore/core-error.h> +#include <pulsecore/refcnt.h> +#include <pulsecore/arpa-inet.h> + +#include "socket-server.h" + +struct pa_socket_server { + PA_REFCNT_DECLARE; + int fd; + char *filename; + bool activated; + char *tcpwrap_service; + + pa_socket_server_on_connection_cb_t on_connection; + void *userdata; + + pa_io_event *io_event; + pa_mainloop_api *mainloop; + enum { + SOCKET_SERVER_IPV4, + SOCKET_SERVER_UNIX, + SOCKET_SERVER_IPV6 + } type; +}; + +static void callback(pa_mainloop_api *mainloop, pa_io_event *e, int fd, pa_io_event_flags_t f, void *userdata) { + pa_socket_server *s = userdata; + pa_iochannel *io; + int nfd; + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + pa_assert(s->mainloop == mainloop); + pa_assert(s->io_event == e); + pa_assert(e); + pa_assert(fd >= 0); + pa_assert(fd == s->fd); + + pa_socket_server_ref(s); + + if ((nfd = pa_accept_cloexec(fd, NULL, NULL)) < 0) { + pa_log("accept(): %s", pa_cstrerror(errno)); + goto finish; + } + + if (!s->on_connection) { + pa_close(nfd); + goto finish; + } + +#ifdef HAVE_LIBWRAP + + if (s->tcpwrap_service) { + struct request_info req; + + request_init(&req, RQ_DAEMON, s->tcpwrap_service, RQ_FILE, nfd, NULL); + fromhost(&req); + if (!hosts_access(&req)) { + pa_log_warn("TCP connection refused by tcpwrap."); + pa_close(nfd); + goto finish; + } + + pa_log_info("TCP connection accepted by tcpwrap."); + } +#endif + + /* There should be a check for socket type here */ + if (s->type == SOCKET_SERVER_IPV4) + pa_make_tcp_socket_low_delay(nfd); + else + pa_make_socket_low_delay(nfd); + + pa_assert_se(io = pa_iochannel_new(s->mainloop, nfd, nfd)); + s->on_connection(s, io, s->userdata); + +finish: + pa_socket_server_unref(s); +} + +static pa_socket_server* socket_server_new(pa_mainloop_api *m, int fd) { + pa_socket_server *s; + + pa_assert(m); + pa_assert(fd >= 0); + + s = pa_xnew0(pa_socket_server, 1); + PA_REFCNT_INIT(s); + s->fd = fd; + s->mainloop = m; + + pa_assert_se(s->io_event = m->io_new(m, fd, PA_IO_EVENT_INPUT, callback, s)); + + return s; +} + +pa_socket_server* pa_socket_server_ref(pa_socket_server *s) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_REFCNT_INC(s); + return s; +} + +#ifdef HAVE_SYS_UN_H + +pa_socket_server* pa_socket_server_new_unix(pa_mainloop_api *m, const char *filename) { + int fd = -1; + bool activated = false; + struct sockaddr_un sa; + pa_socket_server *s; + + pa_assert(m); + pa_assert(filename); + +#ifdef HAVE_SYSTEMD_DAEMON + { + int n = sd_listen_fds(0); + if (n > 0) { + for (int i = 0; i < n; ++i) { + if (sd_is_socket_unix(SD_LISTEN_FDS_START + i, SOCK_STREAM, 1, filename, 0) > 0) { + fd = SD_LISTEN_FDS_START + i; + activated = true; + pa_log_info("Found socket activation socket for '%s' \\o/", filename); + break; + } + } + } + } +#endif + + if (fd < 0) { + if ((fd = pa_socket_cloexec(PF_UNIX, SOCK_STREAM, 0)) < 0) { + pa_log("socket(PF_UNIX): %s", pa_cstrerror(errno)); + goto fail; + } + + memset(&sa, 0, sizeof(sa)); + sa.sun_family = AF_UNIX; + pa_strlcpy(sa.sun_path, filename, sizeof(sa.sun_path)); + + pa_make_socket_low_delay(fd); + + if (bind(fd, (struct sockaddr*) &sa, (socklen_t) SUN_LEN(&sa)) < 0) { + pa_log("bind(): %s", pa_cstrerror(errno)); + goto fail; + } + + /* Allow access from all clients. Sockets like this one should + * always be put inside a directory with proper access rights, + * because not all OS check the access rights on the socket + * inodes. */ + chmod(filename, 0777); + + if (listen(fd, 5) < 0) { + pa_log("listen(): %s", pa_cstrerror(errno)); + goto fail; + } + } + + pa_assert_se(s = socket_server_new(m, fd)); + + s->filename = pa_xstrdup(filename); + s->type = SOCKET_SERVER_UNIX; + s->activated = activated; + + return s; + +fail: + if (fd >= 0) + pa_close(fd); + + return NULL; +} + +#else /* HAVE_SYS_UN_H */ + +pa_socket_server* pa_socket_server_new_unix(pa_mainloop_api *m, const char *filename) { + return NULL; +} + +#endif /* HAVE_SYS_UN_H */ + +pa_socket_server* pa_socket_server_new_ipv4(pa_mainloop_api *m, uint32_t address, uint16_t port, bool fallback, const char *tcpwrap_service) { + pa_socket_server *ss; + int fd = -1; + bool activated = false; + struct sockaddr_in sa; + int on = 1; + + pa_assert(m); + pa_assert(port); + +#ifdef HAVE_SYSTEMD_DAEMON + { + int n = sd_listen_fds(0); + if (n > 0) { + for (int i = 0; i < n; ++i) { + if (sd_is_socket_inet(SD_LISTEN_FDS_START + i, AF_INET, SOCK_STREAM, 1, port) > 0) { + fd = SD_LISTEN_FDS_START + i; + activated = true; + pa_log_info("Found socket activation socket for ipv4 in port '%d' \\o/", port); + break; + } + } + } + } +#endif + + if (fd < 0) { + if ((fd = pa_socket_cloexec(PF_INET, SOCK_STREAM, 0)) < 0) { + pa_log("socket(PF_INET): %s", pa_cstrerror(errno)); + goto fail; + } + +#ifdef SO_REUSEADDR + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const void *) &on, sizeof(on)) < 0) + pa_log("setsockopt(): %s", pa_cstrerror(errno)); +#endif + + pa_make_tcp_socket_low_delay(fd); + + memset(&sa, 0, sizeof(sa)); + sa.sin_family = AF_INET; + sa.sin_port = htons(port); + sa.sin_addr.s_addr = htonl(address); + + if (bind(fd, (struct sockaddr *) &sa, sizeof(sa)) < 0) { + + if (errno == EADDRINUSE && fallback) { + sa.sin_port = 0; + + if (bind(fd, (struct sockaddr *) &sa, sizeof(sa)) < 0) { + pa_log("bind(): %s", pa_cstrerror(errno)); + goto fail; + } + } else { + pa_log("bind(): %s", pa_cstrerror(errno)); + goto fail; + } + } + + if (listen(fd, 5) < 0) { + pa_log("listen(): %s", pa_cstrerror(errno)); + goto fail; + } + } + + pa_assert_se(ss = socket_server_new(m, fd)); + + ss->type = SOCKET_SERVER_IPV4; + ss->tcpwrap_service = pa_xstrdup(tcpwrap_service); + ss->activated = activated; + + return ss; + +fail: + if (fd >= 0) + pa_close(fd); + + return NULL; +} + +#ifdef HAVE_IPV6 +pa_socket_server* pa_socket_server_new_ipv6(pa_mainloop_api *m, const uint8_t address[16], uint16_t port, bool fallback, const char *tcpwrap_service) { + pa_socket_server *ss; + int fd = -1; + bool activated = false; + struct sockaddr_in6 sa; + int on; + + pa_assert(m); + pa_assert(port > 0); + +#ifdef HAVE_SYSTEMD_DAEMON + { + int n = sd_listen_fds(0); + if (n > 0) { + for (int i = 0; i < n; ++i) { + if (sd_is_socket_inet(SD_LISTEN_FDS_START + i, AF_INET6, SOCK_STREAM, 1, port) > 0) { + fd = SD_LISTEN_FDS_START + i; + activated = true; + pa_log_info("Found socket activation socket for ipv6 in port '%d' \\o/", port); + break; + } + } + } + } +#endif + + if (fd < 0) { + if ((fd = pa_socket_cloexec(PF_INET6, SOCK_STREAM, 0)) < 0) { + pa_log("socket(PF_INET6): %s", pa_cstrerror(errno)); + goto fail; + } + +#ifdef IPV6_V6ONLY + on = 1; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (const void *) &on, sizeof(on)) < 0) + pa_log("setsockopt(IPPROTO_IPV6, IPV6_V6ONLY): %s", pa_cstrerror(errno)); +#endif + +#ifdef SO_REUSEADDR + on = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const void *) &on, sizeof(on)) < 0) + pa_log("setsockopt(SOL_SOCKET, SO_REUSEADDR, 1): %s", pa_cstrerror(errno)); +#endif + + pa_make_tcp_socket_low_delay(fd); + + memset(&sa, 0, sizeof(sa)); + sa.sin6_family = AF_INET6; + sa.sin6_port = htons(port); + memcpy(sa.sin6_addr.s6_addr, address, 16); + + if (bind(fd, (struct sockaddr *) &sa, sizeof(sa)) < 0) { + + if (errno == EADDRINUSE && fallback) { + sa.sin6_port = 0; + + if (bind(fd, (struct sockaddr *) &sa, sizeof(sa)) < 0) { + pa_log("bind(): %s", pa_cstrerror(errno)); + goto fail; + } + } else { + pa_log("bind(): %s", pa_cstrerror(errno)); + goto fail; + } + } + + if (listen(fd, 5) < 0) { + pa_log("listen(): %s", pa_cstrerror(errno)); + goto fail; + } + } + + pa_assert_se(ss = socket_server_new(m, fd)); + + ss->type = SOCKET_SERVER_IPV6; + ss->tcpwrap_service = pa_xstrdup(tcpwrap_service); + ss->activated = activated; + + return ss; + +fail: + if (fd >= 0) + pa_close(fd); + + return NULL; +} +#endif + +pa_socket_server* pa_socket_server_new_ipv4_loopback(pa_mainloop_api *m, uint16_t port, bool fallback, const char *tcpwrap_service) { + pa_assert(m); + pa_assert(port > 0); + + return pa_socket_server_new_ipv4(m, INADDR_LOOPBACK, port, fallback, tcpwrap_service); +} + +#ifdef HAVE_IPV6 +pa_socket_server* pa_socket_server_new_ipv6_loopback(pa_mainloop_api *m, uint16_t port, bool fallback, const char *tcpwrap_service) { + pa_assert(m); + pa_assert(port > 0); + + return pa_socket_server_new_ipv6(m, in6addr_loopback.s6_addr, port, fallback, tcpwrap_service); +} +#endif + +pa_socket_server* pa_socket_server_new_ipv4_any(pa_mainloop_api *m, uint16_t port, bool fallback, const char *tcpwrap_service) { + pa_assert(m); + pa_assert(port > 0); + + return pa_socket_server_new_ipv4(m, INADDR_ANY, port, fallback, tcpwrap_service); +} + +#ifdef HAVE_IPV6 +pa_socket_server* pa_socket_server_new_ipv6_any(pa_mainloop_api *m, uint16_t port, bool fallback, const char *tcpwrap_service) { + pa_assert(m); + pa_assert(port > 0); + + return pa_socket_server_new_ipv6(m, in6addr_any.s6_addr, port, fallback, tcpwrap_service); +} +#endif + +pa_socket_server* pa_socket_server_new_ipv4_string(pa_mainloop_api *m, const char *name, uint16_t port, bool fallback, const char *tcpwrap_service) { + struct in_addr ipv4; + + pa_assert(m); + pa_assert(name); + pa_assert(port > 0); + + if (inet_pton(AF_INET, name, &ipv4) > 0) + return pa_socket_server_new_ipv4(m, ntohl(ipv4.s_addr), port, fallback, tcpwrap_service); + + return NULL; +} + +#ifdef HAVE_IPV6 +pa_socket_server* pa_socket_server_new_ipv6_string(pa_mainloop_api *m, const char *name, uint16_t port, bool fallback, const char *tcpwrap_service) { + struct in6_addr ipv6; + + pa_assert(m); + pa_assert(name); + pa_assert(port > 0); + + if (inet_pton(AF_INET6, name, &ipv6) > 0) + return pa_socket_server_new_ipv6(m, ipv6.s6_addr, port, fallback, tcpwrap_service); + + return NULL; +} +#endif + +static void socket_server_free(pa_socket_server*s) { + pa_assert(s); + + if (!s->activated && s->filename) + unlink(s->filename); + pa_xfree(s->filename); + + pa_close(s->fd); + + pa_xfree(s->tcpwrap_service); + + s->mainloop->io_free(s->io_event); + pa_xfree(s); +} + +void pa_socket_server_unref(pa_socket_server *s) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + if (PA_REFCNT_DEC(s) <= 0) + socket_server_free(s); +} + +void pa_socket_server_set_callback(pa_socket_server*s, pa_socket_server_on_connection_cb_t on_connection, void *userdata) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + s->on_connection = on_connection; + s->userdata = userdata; +} + +char *pa_socket_server_get_address(pa_socket_server *s, char *c, size_t l) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + pa_assert(c); + pa_assert(l > 0); + + switch (s->type) { +#ifdef HAVE_IPV6 + case SOCKET_SERVER_IPV6: { + struct sockaddr_in6 sa; + socklen_t sa_len = sizeof(sa); + + if (getsockname(s->fd, (struct sockaddr*) &sa, &sa_len) < 0) { + pa_log("getsockname(): %s", pa_cstrerror(errno)); + return NULL; + } + + if (memcmp(&in6addr_any, &sa.sin6_addr, sizeof(in6addr_any)) == 0) { + char fqdn[256]; + if (!pa_get_fqdn(fqdn, sizeof(fqdn))) + return NULL; + + pa_snprintf(c, l, "tcp6:%s:%u", fqdn, (unsigned) ntohs(sa.sin6_port)); + + } else if (memcmp(&in6addr_loopback, &sa.sin6_addr, sizeof(in6addr_loopback)) == 0) { + char *id; + + if (!(id = pa_machine_id())) + return NULL; + + pa_snprintf(c, l, "{%s}tcp6:localhost:%u", id, (unsigned) ntohs(sa.sin6_port)); + pa_xfree(id); + } else { + char ip[INET6_ADDRSTRLEN]; + + if (!inet_ntop(AF_INET6, &sa.sin6_addr, ip, sizeof(ip))) { + pa_log("inet_ntop(): %s", pa_cstrerror(errno)); + return NULL; + } + + pa_snprintf(c, l, "tcp6:[%s]:%u", ip, (unsigned) ntohs(sa.sin6_port)); + } + + return c; + } +#endif + + case SOCKET_SERVER_IPV4: { + struct sockaddr_in sa; + socklen_t sa_len = sizeof(sa); + + if (getsockname(s->fd, (struct sockaddr*) &sa, &sa_len) < 0) { + pa_log("getsockname(): %s", pa_cstrerror(errno)); + return NULL; + } + + if (sa.sin_addr.s_addr == INADDR_ANY) { + char fqdn[256]; + if (!pa_get_fqdn(fqdn, sizeof(fqdn))) + return NULL; + + pa_snprintf(c, l, "tcp:%s:%u", fqdn, (unsigned) ntohs(sa.sin_port)); + } else if (sa.sin_addr.s_addr == INADDR_LOOPBACK) { + char *id; + + if (!(id = pa_machine_id())) + return NULL; + + pa_snprintf(c, l, "{%s}tcp:localhost:%u", id, (unsigned) ntohs(sa.sin_port)); + pa_xfree(id); + } else { + char ip[INET_ADDRSTRLEN]; + + if (!inet_ntop(AF_INET, &sa.sin_addr, ip, sizeof(ip))) { + pa_log("inet_ntop(): %s", pa_cstrerror(errno)); + return NULL; + } + + pa_snprintf(c, l, "tcp:[%s]:%u", ip, (unsigned) ntohs(sa.sin_port)); + } + + return c; + } + + case SOCKET_SERVER_UNIX: { + char *id; + + if (!s->filename) + return NULL; + + if (!(id = pa_machine_id())) + return NULL; + + pa_snprintf(c, l, "{%s}unix:%s", id, s->filename); + pa_xfree(id); + return c; + } + + default: + return NULL; + } +} diff --git a/src/pulsecore/socket-server.h b/src/pulsecore/socket-server.h new file mode 100644 index 0000000..0793baf --- /dev/null +++ b/src/pulsecore/socket-server.h @@ -0,0 +1,53 @@ +#ifndef foosocketserverhfoo +#define foosocketserverhfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <inttypes.h> +#include <pulse/mainloop-api.h> +#include <pulsecore/iochannel.h> + +/* It is safe to destroy the calling socket_server object from the callback */ + +typedef struct pa_socket_server pa_socket_server; + +pa_socket_server* pa_socket_server_new_unix(pa_mainloop_api *m, const char *filename); +pa_socket_server* pa_socket_server_new_ipv4(pa_mainloop_api *m, uint32_t address, uint16_t port, bool fallback, const char *tcpwrap_service); +pa_socket_server* pa_socket_server_new_ipv4_loopback(pa_mainloop_api *m, uint16_t port, bool fallback, const char *tcpwrap_service); +pa_socket_server* pa_socket_server_new_ipv4_any(pa_mainloop_api *m, uint16_t port, bool fallback, const char *tcpwrap_service); +pa_socket_server* pa_socket_server_new_ipv4_string(pa_mainloop_api *m, const char *name, uint16_t port, bool fallback, const char *tcpwrap_service); +#ifdef HAVE_IPV6 +pa_socket_server* pa_socket_server_new_ipv6(pa_mainloop_api *m, const uint8_t address[16], uint16_t port, bool fallback, const char *tcpwrap_service); +pa_socket_server* pa_socket_server_new_ipv6_loopback(pa_mainloop_api *m, uint16_t port, bool fallback, const char *tcpwrap_service); +pa_socket_server* pa_socket_server_new_ipv6_any(pa_mainloop_api *m, uint16_t port, bool fallback, const char *tcpwrap_service); +pa_socket_server* pa_socket_server_new_ipv6_string(pa_mainloop_api *m, const char *name, uint16_t port, bool fallback, const char *tcpwrap_service); +#endif + +void pa_socket_server_unref(pa_socket_server*s); +pa_socket_server* pa_socket_server_ref(pa_socket_server *s); + +typedef void (*pa_socket_server_on_connection_cb_t)(pa_socket_server*s, pa_iochannel *io, void *userdata); + +void pa_socket_server_set_callback(pa_socket_server*s, pa_socket_server_on_connection_cb_t connection_cb, void *userdata); + +char *pa_socket_server_get_address(pa_socket_server *s, char *c, size_t l); + +#endif diff --git a/src/pulsecore/socket-util.c b/src/pulsecore/socket-util.c new file mode 100644 index 0000000..e389ef2 --- /dev/null +++ b/src/pulsecore/socket-util.c @@ -0,0 +1,338 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2004 Joe Marcus Clarke + 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 + by the Free Software Foundation; either version 2.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <signal.h> +#include <errno.h> +#include <string.h> +#include <stdio.h> +#include <sys/types.h> +#include <unistd.h> +#include <sys/stat.h> + +#ifdef HAVE_SYS_UN_H +#include <sys/un.h> +#endif +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#ifdef HAVE_NETINET_IN_SYSTM_H +#include <netinet/in_systm.h> +#endif +#ifdef HAVE_NETINET_IP_H +#include <netinet/ip.h> +#endif +#ifdef HAVE_NETINET_TCP_H +#include <netinet/tcp.h> +#endif +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif +#ifdef HAVE_SYSTEMD_DAEMON +#include <systemd/sd-daemon.h> +#endif + +#include <pulsecore/core-error.h> +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> +#include <pulsecore/socket.h> +#include <pulsecore/arpa-inet.h> + +#include "socket-util.h" + +void pa_socket_peer_to_string(int fd, char *c, size_t l) { +#ifndef OS_IS_WIN32 + struct stat st; +#endif + + pa_assert(fd >= 0); + pa_assert(c); + pa_assert(l > 0); + +#ifndef OS_IS_WIN32 + pa_assert_se(fstat(fd, &st) == 0); + + if (S_ISSOCK(st.st_mode)) +#endif + { + union { + struct sockaddr_storage storage; + struct sockaddr sa; + struct sockaddr_in in; +#ifdef HAVE_IPV6 + struct sockaddr_in6 in6; +#endif +#ifdef HAVE_SYS_UN_H + struct sockaddr_un un; +#endif + } sa; + socklen_t sa_len = sizeof(sa); + + if (getpeername(fd, &sa.sa, &sa_len) >= 0) { + + if (sa.sa.sa_family == AF_INET) { + uint32_t ip = ntohl(sa.in.sin_addr.s_addr); + + pa_snprintf(c, l, "TCP/IP client from %i.%i.%i.%i:%u", + ip >> 24, + (ip >> 16) & 0xFF, + (ip >> 8) & 0xFF, + ip & 0xFF, + ntohs(sa.in.sin_port)); + return; +#ifdef HAVE_IPV6 + } else if (sa.sa.sa_family == AF_INET6) { + char buf[INET6_ADDRSTRLEN]; + const char *res; + + res = inet_ntop(AF_INET6, &sa.in6.sin6_addr, buf, sizeof(buf)); + if (res) { + pa_snprintf(c, l, "TCP/IP client from [%s]:%u", buf, ntohs(sa.in6.sin6_port)); + return; + } +#endif +#ifdef HAVE_SYS_UN_H + } else if (sa.sa.sa_family == AF_UNIX) { + pa_snprintf(c, l, "UNIX socket client"); + return; +#endif + } + } + + pa_snprintf(c, l, "Unknown network client"); + return; + } +#ifndef OS_IS_WIN32 + else if (S_ISCHR(st.st_mode) && (fd == 0 || fd == 1)) { + pa_snprintf(c, l, "STDIN/STDOUT client"); + return; + } +#endif /* OS_IS_WIN32 */ + + pa_snprintf(c, l, "Unknown client"); +} + +void pa_make_socket_low_delay(int fd) { + +#ifdef SO_PRIORITY + int priority; + pa_assert(fd >= 0); + + priority = 6; + if (setsockopt(fd, SOL_SOCKET, SO_PRIORITY, (const void *) &priority, sizeof(priority)) < 0) + pa_log_warn("SO_PRIORITY failed: %s", pa_cstrerror(errno)); +#endif +} + +void pa_make_tcp_socket_low_delay(int fd) { + pa_assert(fd >= 0); + + pa_make_socket_low_delay(fd); + +#if defined(SOL_TCP) || defined(IPPROTO_TCP) + { + int on = 1; +#if defined(SOL_TCP) + if (setsockopt(fd, SOL_TCP, TCP_NODELAY, (const void *) &on, sizeof(on)) < 0) +#else + if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (const void *) &on, sizeof(on)) < 0) +#endif + pa_log_warn("TCP_NODELAY failed: %s", pa_cstrerror(errno)); + } +#endif + +#if defined(IPTOS_LOWDELAY) && defined(IP_TOS) && (defined(SOL_IP) || defined(IPPROTO_IP)) + { + int tos = IPTOS_LOWDELAY; +#ifdef SOL_IP + if (setsockopt(fd, SOL_IP, IP_TOS, (const void *) &tos, sizeof(tos)) < 0) +#else + if (setsockopt(fd, IPPROTO_IP, IP_TOS, (const void *) &tos, sizeof(tos)) < 0) +#endif + pa_log_warn("IP_TOS failed: %s", pa_cstrerror(errno)); + } +#endif +} + +void pa_make_udp_socket_low_delay(int fd) { + pa_assert(fd >= 0); + + pa_make_socket_low_delay(fd); + +#if defined(IPTOS_LOWDELAY) && defined(IP_TOS) && (defined(SOL_IP) || defined(IPPROTO_IP)) + { + int tos = IPTOS_LOWDELAY; +#ifdef SOL_IP + if (setsockopt(fd, SOL_IP, IP_TOS, (const void *) &tos, sizeof(tos)) < 0) +#else + if (setsockopt(fd, IPPROTO_IP, IP_TOS, (const void *) &tos, sizeof(tos)) < 0) +#endif + pa_log_warn("IP_TOS failed: %s", pa_cstrerror(errno)); + } +#endif +} + +int pa_socket_set_rcvbuf(int fd, size_t l) { + int bufsz = (int) l; + + pa_assert(fd >= 0); + + if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (const void *) &bufsz, sizeof(bufsz)) < 0) { + pa_log_warn("SO_RCVBUF: %s", pa_cstrerror(errno)); + return -1; + } + + return 0; +} + +int pa_socket_set_sndbuf(int fd, size_t l) { + int bufsz = (int) l; + + pa_assert(fd >= 0); + + if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (const void *) &bufsz, sizeof(bufsz)) < 0) { + pa_log_warn("SO_SNDBUF: %s", pa_cstrerror(errno)); + return -1; + } + + return 0; +} + +#ifdef HAVE_SYS_UN_H + +int pa_unix_socket_is_stale(const char *fn) { + struct sockaddr_un sa; + int fd = -1, ret = -1; + + pa_assert(fn); + + if ((fd = pa_socket_cloexec(PF_UNIX, SOCK_STREAM, 0)) < 0) { + pa_log("socket(): %s", pa_cstrerror(errno)); + goto finish; + } + + sa.sun_family = AF_UNIX; + strncpy(sa.sun_path, fn, sizeof(sa.sun_path)-1); + sa.sun_path[sizeof(sa.sun_path) - 1] = 0; + + if (connect(fd, (struct sockaddr*) &sa, sizeof(sa)) < 0) { + if (errno == ECONNREFUSED) + ret = 1; + } else + ret = 0; + +finish: + if (fd >= 0) + pa_close(fd); + + return ret; +} + +int pa_unix_socket_remove_stale(const char *fn) { + int r; + + pa_assert(fn); + +#ifdef HAVE_SYSTEMD_DAEMON + { + int n = sd_listen_fds(0); + if (n > 0) { + for (int i = 0; i < n; ++i) { + if (sd_is_socket_unix(SD_LISTEN_FDS_START + i, SOCK_STREAM, 1, fn, 0) > 0) { + /* This is a socket activated socket, therefore do not consider + * it stale. */ + return 0; + } + } + } + } +#endif + + if ((r = pa_unix_socket_is_stale(fn)) < 0) + return errno != ENOENT ? -1 : 0; + + if (!r) + return 0; + + /* Yes, here is a race condition. But who cares? */ + if (unlink(fn) < 0) + return -1; + + return 0; +} + +#else /* HAVE_SYS_UN_H */ + +int pa_unix_socket_is_stale(const char *fn) { + return -1; +} + +int pa_unix_socket_remove_stale(const char *fn) { + return -1; +} + +#endif /* HAVE_SYS_UN_H */ + +bool pa_socket_address_is_local(const struct sockaddr *sa) { + pa_assert(sa); + + switch (sa->sa_family) { + case AF_UNIX: + return true; + + case AF_INET: + return ((const struct sockaddr_in*) sa)->sin_addr.s_addr == INADDR_LOOPBACK; + +#ifdef HAVE_IPV6 + case AF_INET6: + return memcmp(&((const struct sockaddr_in6*) sa)->sin6_addr, &in6addr_loopback, sizeof(struct in6_addr)) == 0; +#endif + + default: + return false; + } +} + +bool pa_socket_is_local(int fd) { + + union { + struct sockaddr_storage storage; + struct sockaddr sa; + struct sockaddr_in in; +#ifdef HAVE_IPV6 + struct sockaddr_in6 in6; +#endif +#ifdef HAVE_SYS_UN_H + struct sockaddr_un un; +#endif + } sa; + socklen_t sa_len = sizeof(sa); + + if (getpeername(fd, &sa.sa, &sa_len) < 0) + return false; + + return pa_socket_address_is_local(&sa.sa); +} diff --git a/src/pulsecore/socket-util.h b/src/pulsecore/socket-util.h new file mode 100644 index 0000000..f120769 --- /dev/null +++ b/src/pulsecore/socket-util.h @@ -0,0 +1,44 @@ +#ifndef foosocketutilhfoo +#define foosocketutilhfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/types.h> + +#include <pulsecore/socket.h> +#include <pulsecore/macro.h> + +void pa_socket_peer_to_string(int fd, char *c, size_t l); + +void pa_make_socket_low_delay(int fd); +void pa_make_tcp_socket_low_delay(int fd); +void pa_make_udp_socket_low_delay(int fd); + +int pa_socket_set_sndbuf(int fd, size_t l); +int pa_socket_set_rcvbuf(int fd, size_t l); + +int pa_unix_socket_is_stale(const char *fn); +int pa_unix_socket_remove_stale(const char *fn); + +bool pa_socket_address_is_local(const struct sockaddr *sa); +bool pa_socket_is_local(int fd); + +#endif diff --git a/src/pulsecore/socket.h b/src/pulsecore/socket.h new file mode 100644 index 0000000..72f2228 --- /dev/null +++ b/src/pulsecore/socket.h @@ -0,0 +1,20 @@ +#ifndef foopulsecoresockethfoo +#define foopulsecoresockethfoo + +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif + +#ifdef HAVE_WINSOCK2_H +#include <winsock2.h> +#include "winerrno.h" + +typedef long suseconds_t; + +#endif + +#ifdef HAVE_WS2TCPIP_H +#include <ws2tcpip.h> +#endif + +#endif diff --git a/src/pulsecore/sound-file-stream.c b/src/pulsecore/sound-file-stream.c new file mode 100644 index 0000000..147aa22 --- /dev/null +++ b/src/pulsecore/sound-file-stream.c @@ -0,0 +1,339 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2008 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> + +#include <sndfile.h> + +#include <pulse/xmalloc.h> +#include <pulse/util.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/sink-input.h> +#include <pulsecore/log.h> +#include <pulsecore/thread-mq.h> +#include <pulsecore/core-util.h> +#include <pulsecore/mix.h> +#include <pulsecore/sndfile-util.h> + +#include "sound-file-stream.h" + +#define MEMBLOCKQ_MAXLENGTH (16*1024*1024) + +typedef struct file_stream { + pa_msgobject parent; + pa_core *core; + pa_sink_input *sink_input; + + SNDFILE *sndfile; + sf_count_t (*readf_function)(SNDFILE *sndfile, void *ptr, sf_count_t frames); + + /* We need this memblockq here to easily fulfill rewind requests + * (even beyond the file start!) */ + pa_memblockq *memblockq; +} file_stream; + +enum { + FILE_STREAM_MESSAGE_UNLINK +}; + +PA_DEFINE_PRIVATE_CLASS(file_stream, pa_msgobject); +#define FILE_STREAM(o) (file_stream_cast(o)) + +/* Called from main context */ +static void file_stream_unlink(file_stream *u) { + pa_assert(u); + + if (!u->sink_input) + return; + + pa_sink_input_unlink(u->sink_input); + pa_sink_input_unref(u->sink_input); + u->sink_input = NULL; + + /* Make sure we don't decrease the ref count twice. */ + file_stream_unref(u); +} + +/* Called from main context */ +static void file_stream_free(pa_object *o) { + file_stream *u = FILE_STREAM(o); + pa_assert(u); + + if (u->memblockq) + pa_memblockq_free(u->memblockq); + + if (u->sndfile) + sf_close(u->sndfile); + + pa_xfree(u); +} + +/* Called from main context */ +static int file_stream_process_msg(pa_msgobject *o, int code, void*userdata, int64_t offset, pa_memchunk *chunk) { + file_stream *u = FILE_STREAM(o); + file_stream_assert_ref(u); + + switch (code) { + case FILE_STREAM_MESSAGE_UNLINK: + file_stream_unlink(u); + break; + } + + return 0; +} + +/* Called from main context */ +static void sink_input_kill_cb(pa_sink_input *i) { + file_stream *u; + + pa_sink_input_assert_ref(i); + u = FILE_STREAM(i->userdata); + file_stream_assert_ref(u); + + file_stream_unlink(u); +} + +/* Called from IO thread context */ +static void sink_input_state_change_cb(pa_sink_input *i, pa_sink_input_state_t state) { + file_stream *u; + + pa_sink_input_assert_ref(i); + u = FILE_STREAM(i->userdata); + file_stream_assert_ref(u); + + /* If we are added for the first time, ask for a rewinding so that + * we are heard right-away. */ + if (PA_SINK_INPUT_IS_LINKED(state) && + i->thread_info.state == PA_SINK_INPUT_INIT && i->sink) + pa_sink_input_request_rewind(i, 0, false, true, true); +} + +/* Called from IO thread context */ +static int sink_input_pop_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) { + file_stream *u; + + pa_sink_input_assert_ref(i); + pa_assert(chunk); + u = FILE_STREAM(i->userdata); + file_stream_assert_ref(u); + + if (!u->memblockq) + return -1; + + for (;;) { + pa_memchunk tchunk; + size_t fs; + void *p; + sf_count_t n; + + if (pa_memblockq_peek(u->memblockq, chunk) >= 0) { + chunk->length = PA_MIN(chunk->length, length); + pa_memblockq_drop(u->memblockq, chunk->length); + return 0; + } + + if (!u->sndfile) + break; + + tchunk.memblock = pa_memblock_new(i->sink->core->mempool, length); + tchunk.index = 0; + + p = pa_memblock_acquire(tchunk.memblock); + + if (u->readf_function) { + fs = pa_frame_size(&i->sample_spec); + n = u->readf_function(u->sndfile, p, (sf_count_t) (length/fs)); + } else { + fs = 1; + n = sf_read_raw(u->sndfile, p, (sf_count_t) length); + } + + pa_memblock_release(tchunk.memblock); + + if (n <= 0) { + pa_memblock_unref(tchunk.memblock); + + sf_close(u->sndfile); + u->sndfile = NULL; + break; + } + + tchunk.length = (size_t) n * fs; + + pa_memblockq_push(u->memblockq, &tchunk); + pa_memblock_unref(tchunk.memblock); + } + + if (pa_sink_input_safe_to_remove(i)) { + pa_memblockq_free(u->memblockq); + u->memblockq = NULL; + + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(u), FILE_STREAM_MESSAGE_UNLINK, NULL, 0, NULL, NULL); + } + + return -1; +} + +static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) { + file_stream *u; + + pa_sink_input_assert_ref(i); + u = FILE_STREAM(i->userdata); + file_stream_assert_ref(u); + + if (!u->memblockq) + return; + + pa_memblockq_rewind(u->memblockq, nbytes); +} + +static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) { + file_stream *u; + + pa_sink_input_assert_ref(i); + u = FILE_STREAM(i->userdata); + file_stream_assert_ref(u); + + if (!u->memblockq) + return; + + pa_memblockq_set_maxrewind(u->memblockq, nbytes); +} + +int pa_play_file( + pa_sink *sink, + const char *fname, + const pa_cvolume *volume) { + + file_stream *u = NULL; + pa_sample_spec ss; + pa_channel_map cm; + pa_sink_input_new_data data; + int fd; + SF_INFO sfi; + pa_memchunk silence; + + pa_assert(sink); + pa_assert(fname); + + u = pa_msgobject_new(file_stream); + u->parent.parent.free = file_stream_free; + u->parent.process_msg = file_stream_process_msg; + u->core = sink->core; + u->sink_input = NULL; + u->sndfile = NULL; + u->readf_function = NULL; + u->memblockq = NULL; + + if ((fd = pa_open_cloexec(fname, O_RDONLY, 0)) < 0) { + pa_log("Failed to open file %s: %s", fname, pa_cstrerror(errno)); + goto fail; + } + + /* FIXME: For now we just use posix_fadvise to avoid page faults + * when accessing the file data. Eventually we should move the + * file reader into the main event loop and pass the data over the + * asyncmsgq. */ + +#ifdef HAVE_POSIX_FADVISE + if (posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL) < 0) { + pa_log_warn("POSIX_FADV_SEQUENTIAL failed: %s", pa_cstrerror(errno)); + goto fail; + } else + pa_log_debug("POSIX_FADV_SEQUENTIAL succeeded."); + + if (posix_fadvise(fd, 0, 0, POSIX_FADV_WILLNEED) < 0) { + pa_log_warn("POSIX_FADV_WILLNEED failed: %s", pa_cstrerror(errno)); + goto fail; + } else + pa_log_debug("POSIX_FADV_WILLNEED succeeded."); +#endif + + pa_zero(sfi); + if (!(u->sndfile = sf_open_fd(fd, SFM_READ, &sfi, 1))) { + pa_log("Failed to open file %s", fname); + goto fail; + } + + fd = -1; + + if (pa_sndfile_read_sample_spec(u->sndfile, &ss) < 0) { + pa_log("Failed to determine file sample format."); + goto fail; + } + + if (pa_sndfile_read_channel_map(u->sndfile, &cm) < 0) { + if (ss.channels > 2) + pa_log_info("Failed to determine file channel map, synthesizing one."); + pa_channel_map_init_extend(&cm, ss.channels, PA_CHANNEL_MAP_DEFAULT); + } + + u->readf_function = pa_sndfile_readf_function(&ss); + + pa_sink_input_new_data_init(&data); + pa_sink_input_new_data_set_sink(&data, sink, false, true); + data.driver = __FILE__; + pa_sink_input_new_data_set_sample_spec(&data, &ss); + pa_sink_input_new_data_set_channel_map(&data, &cm); + pa_sink_input_new_data_set_volume(&data, volume); + pa_proplist_sets(data.proplist, PA_PROP_MEDIA_NAME, pa_path_get_filename(fname)); + pa_proplist_sets(data.proplist, PA_PROP_MEDIA_FILENAME, fname); + pa_sndfile_init_proplist(u->sndfile, data.proplist); + + pa_sink_input_new(&u->sink_input, sink->core, &data); + pa_sink_input_new_data_done(&data); + + if (!u->sink_input) + goto fail; + + u->sink_input->pop = sink_input_pop_cb; + u->sink_input->process_rewind = sink_input_process_rewind_cb; + u->sink_input->update_max_rewind = sink_input_update_max_rewind_cb; + u->sink_input->kill = sink_input_kill_cb; + u->sink_input->state_change = sink_input_state_change_cb; + u->sink_input->userdata = u; + + pa_sink_input_get_silence(u->sink_input, &silence); + u->memblockq = pa_memblockq_new("sound-file-stream memblockq", 0, MEMBLOCKQ_MAXLENGTH, 0, &ss, 1, 1, 0, &silence); + pa_memblock_unref(silence.memblock); + + pa_sink_input_put(u->sink_input); + + /* The reference to u is dangling here, because we want to keep + * this stream around until it is fully played. */ + + return 0; + +fail: + file_stream_unref(u); + + if (fd >= 0) + pa_close(fd); + + return -1; +} diff --git a/src/pulsecore/sound-file-stream.h b/src/pulsecore/sound-file-stream.h new file mode 100644 index 0000000..de55c6d --- /dev/null +++ b/src/pulsecore/sound-file-stream.h @@ -0,0 +1,27 @@ +#ifndef foosoundfilestreamhfoo +#define foosoundfilestreamhfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulsecore/sink.h> + +int pa_play_file(pa_sink *sink, const char *fname, const pa_cvolume *volume); + +#endif diff --git a/src/pulsecore/sound-file.c b/src/pulsecore/sound-file.c new file mode 100644 index 0000000..f678430 --- /dev/null +++ b/src/pulsecore/sound-file.c @@ -0,0 +1,163 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> + +#include <sndfile.h> + +#include <pulse/sample.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> +#include <pulsecore/core-error.h> +#include <pulsecore/core-util.h> +#include <pulsecore/core-scache.h> +#include <pulsecore/sndfile-util.h> + +#include "sound-file.h" + +int pa_sound_file_load( + pa_mempool *pool, + const char *fname, + pa_sample_spec *ss, + pa_channel_map *map, + pa_memchunk *chunk, + pa_proplist *p) { + + SNDFILE *sf = NULL; + SF_INFO sfi; + int ret = -1; + size_t l; + sf_count_t (*readf_function)(SNDFILE *sndfile, void *ptr, sf_count_t frames) = NULL; + void *ptr = NULL; + int fd; + + pa_assert(fname); + pa_assert(ss); + pa_assert(chunk); + + pa_memchunk_reset(chunk); + + if ((fd = pa_open_cloexec(fname, O_RDONLY, 0)) < 0) { + pa_log("Failed to open file %s: %s", fname, pa_cstrerror(errno)); + goto finish; + } + +#ifdef HAVE_POSIX_FADVISE + if (posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL) < 0) { + pa_log_warn("POSIX_FADV_SEQUENTIAL failed: %s", pa_cstrerror(errno)); + goto finish; + } else + pa_log_debug("POSIX_FADV_SEQUENTIAL succeeded."); +#endif + + pa_zero(sfi); + if (!(sf = sf_open_fd(fd, SFM_READ, &sfi, 1))) { + pa_log("Failed to open file %s", fname); + goto finish; + } + + fd = -1; + + if (pa_sndfile_read_sample_spec(sf, ss) < 0) { + pa_log("Failed to determine file sample format."); + goto finish; + } + + if ((map && pa_sndfile_read_channel_map(sf, map) < 0)) { + if (ss->channels > 2) + pa_log("Failed to determine file channel map, synthesizing one."); + pa_channel_map_init_extend(map, ss->channels, PA_CHANNEL_MAP_DEFAULT); + } + + if (p) + pa_sndfile_init_proplist(sf, p); + + if ((l = pa_frame_size(ss) * (size_t) sfi.frames) > PA_SCACHE_ENTRY_SIZE_MAX) { + pa_log("File too large"); + goto finish; + } + + chunk->memblock = pa_memblock_new(pool, l); + chunk->index = 0; + chunk->length = l; + + readf_function = pa_sndfile_readf_function(ss); + + ptr = pa_memblock_acquire(chunk->memblock); + + if ((readf_function && readf_function(sf, ptr, sfi.frames) != sfi.frames) || + (!readf_function && sf_read_raw(sf, ptr, (sf_count_t) l) != (sf_count_t) l)) { + pa_log("Premature file end"); + goto finish; + } + + ret = 0; + +finish: + + if (sf) + sf_close(sf); + + if (ptr) + pa_memblock_release(chunk->memblock); + + if (ret != 0 && chunk->memblock) + pa_memblock_unref(chunk->memblock); + + if (fd >= 0) + pa_close(fd); + + return ret; +} + +int pa_sound_file_too_big_to_cache(const char *fname) { + + SNDFILE*sf = NULL; + SF_INFO sfi; + pa_sample_spec ss; + + pa_assert(fname); + + pa_zero(sfi); + if (!(sf = sf_open(fname, SFM_READ, &sfi))) { + pa_log("Failed to open file %s", fname); + return -1; + } + + if (pa_sndfile_read_sample_spec(sf, &ss) < 0) { + pa_log("Failed to determine file sample format."); + sf_close(sf); + return -1; + } + + sf_close(sf); + + if ((pa_frame_size(&ss) * (size_t) sfi.frames) > PA_SCACHE_ENTRY_SIZE_MAX) { + pa_log("File too large: %s", fname); + return 1; + } + + return 0; +} diff --git a/src/pulsecore/sound-file.h b/src/pulsecore/sound-file.h new file mode 100644 index 0000000..01afa4e --- /dev/null +++ b/src/pulsecore/sound-file.h @@ -0,0 +1,31 @@ +#ifndef soundfilehfoo +#define soundfilehfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulse/sample.h> +#include <pulse/channelmap.h> +#include <pulsecore/memchunk.h> + +int pa_sound_file_load(pa_mempool *pool, const char *fname, pa_sample_spec *ss, pa_channel_map *map, pa_memchunk *chunk, pa_proplist *p); + +int pa_sound_file_too_big_to_cache(const char *fname); + +#endif diff --git a/src/pulsecore/source-output.c b/src/pulsecore/source-output.c new file mode 100644 index 0000000..92f74d4 --- /dev/null +++ b/src/pulsecore/source-output.c @@ -0,0 +1,1912 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <pulse/utf8.h> +#include <pulse/xmalloc.h> +#include <pulse/util.h> +#include <pulse/internal.h> + +#include <pulsecore/core-format.h> +#include <pulsecore/mix.h> +#include <pulsecore/stream-util.h> +#include <pulsecore/core-subscribe.h> +#include <pulsecore/log.h> +#include <pulsecore/namereg.h> +#include <pulsecore/core-util.h> + +#include "source-output.h" + +#define MEMBLOCKQ_MAXLENGTH (32*1024*1024) + +PA_DEFINE_PUBLIC_CLASS(pa_source_output, pa_msgobject); + +static void source_output_free(pa_object* mo); +static void set_real_ratio(pa_source_output *o, const pa_cvolume *v); + +pa_source_output_new_data* pa_source_output_new_data_init(pa_source_output_new_data *data) { + pa_assert(data); + + pa_zero(*data); + data->resample_method = PA_RESAMPLER_INVALID; + data->proplist = pa_proplist_new(); + data->volume_writable = true; + + return data; +} + +void pa_source_output_new_data_set_sample_spec(pa_source_output_new_data *data, const pa_sample_spec *spec) { + pa_assert(data); + + if ((data->sample_spec_is_set = !!spec)) + data->sample_spec = *spec; +} + +void pa_source_output_new_data_set_channel_map(pa_source_output_new_data *data, const pa_channel_map *map) { + pa_assert(data); + + if ((data->channel_map_is_set = !!map)) + data->channel_map = *map; +} + +bool pa_source_output_new_data_is_passthrough(pa_source_output_new_data *data) { + pa_assert(data); + + if (PA_LIKELY(data->format) && PA_UNLIKELY(!pa_format_info_is_pcm(data->format))) + return true; + + if (PA_UNLIKELY(data->flags & PA_SOURCE_OUTPUT_PASSTHROUGH)) + return true; + + return false; +} + +void pa_source_output_new_data_set_volume(pa_source_output_new_data *data, const pa_cvolume *volume) { + pa_assert(data); + pa_assert(data->volume_writable); + + if ((data->volume_is_set = !!volume)) + data->volume = *volume; +} + +void pa_source_output_new_data_apply_volume_factor(pa_source_output_new_data *data, const pa_cvolume *volume_factor) { + pa_assert(data); + pa_assert(volume_factor); + + if (data->volume_factor_is_set) + pa_sw_cvolume_multiply(&data->volume_factor, &data->volume_factor, volume_factor); + else { + data->volume_factor_is_set = true; + data->volume_factor = *volume_factor; + } +} + +void pa_source_output_new_data_apply_volume_factor_source(pa_source_output_new_data *data, const pa_cvolume *volume_factor) { + pa_assert(data); + pa_assert(volume_factor); + + if (data->volume_factor_source_is_set) + pa_sw_cvolume_multiply(&data->volume_factor_source, &data->volume_factor_source, volume_factor); + else { + data->volume_factor_source_is_set = true; + data->volume_factor_source = *volume_factor; + } +} + +void pa_source_output_new_data_set_muted(pa_source_output_new_data *data, bool mute) { + pa_assert(data); + + data->muted_is_set = true; + data->muted = mute; +} + +bool pa_source_output_new_data_set_source(pa_source_output_new_data *data, pa_source *s, bool save, + bool requested_by_application) { + bool ret = true; + pa_idxset *formats = NULL; + + pa_assert(data); + pa_assert(s); + + if (!data->req_formats) { + /* We're not working with the extended API */ + data->source = s; + if (save) { + pa_xfree(data->preferred_source); + data->preferred_source = pa_xstrdup(s->name); + } + data->source_requested_by_application = requested_by_application; + } else { + /* Extended API: let's see if this source supports the formats the client would like */ + formats = pa_source_check_formats(s, data->req_formats); + + if (formats && !pa_idxset_isempty(formats)) { + /* Source supports at least one of the requested formats */ + data->source = s; + if (save) { + pa_xfree(data->preferred_source); + data->preferred_source = pa_xstrdup(s->name); + } + data->source_requested_by_application = requested_by_application; + if (data->nego_formats) + pa_idxset_free(data->nego_formats, (pa_free_cb_t) pa_format_info_free); + data->nego_formats = formats; + } else { + /* Source doesn't support any of the formats requested by the client */ + if (formats) + pa_idxset_free(formats, (pa_free_cb_t) pa_format_info_free); + ret = false; + } + } + + return ret; +} + +bool pa_source_output_new_data_set_formats(pa_source_output_new_data *data, pa_idxset *formats) { + pa_assert(data); + pa_assert(formats); + + if (data->req_formats) + pa_idxset_free(data->req_formats, (pa_free_cb_t) pa_format_info_free); + + data->req_formats = formats; + + if (data->source) { + /* Trigger format negotiation */ + return pa_source_output_new_data_set_source(data, data->source, (data->preferred_source != NULL), + data->source_requested_by_application); + } + + return true; +} + +void pa_source_output_new_data_done(pa_source_output_new_data *data) { + pa_assert(data); + + if (data->req_formats) + pa_idxset_free(data->req_formats, (pa_free_cb_t) pa_format_info_free); + + if (data->nego_formats) + pa_idxset_free(data->nego_formats, (pa_free_cb_t) pa_format_info_free); + + if (data->format) + pa_format_info_free(data->format); + + if (data->preferred_source) + pa_xfree(data->preferred_source); + + pa_proplist_free(data->proplist); +} + +/* Called from main context */ +static void reset_callbacks(pa_source_output *o) { + pa_assert(o); + + o->push = NULL; + o->process_rewind = NULL; + o->update_max_rewind = NULL; + o->update_source_requested_latency = NULL; + o->update_source_latency_range = NULL; + o->update_source_fixed_latency = NULL; + o->attach = NULL; + o->detach = NULL; + o->suspend = NULL; + o->suspend_within_thread = NULL; + o->moving = NULL; + o->kill = NULL; + o->get_latency = NULL; + o->state_change = NULL; + o->may_move_to = NULL; + o->send_event = NULL; + o->volume_changed = NULL; + o->mute_changed = NULL; +} + +/* Called from main context */ +int pa_source_output_new( + pa_source_output**_o, + pa_core *core, + pa_source_output_new_data *data) { + + pa_source_output *o; + pa_resampler *resampler = NULL; + char st[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX], fmt[PA_FORMAT_INFO_SNPRINT_MAX]; + pa_channel_map volume_map; + int r; + char *pt; + + pa_assert(_o); + pa_assert(core); + pa_assert(data); + pa_assert_ctl_context(); + + if (data->client) + pa_proplist_update(data->proplist, PA_UPDATE_MERGE, data->client->proplist); + + if (data->destination_source && (data->destination_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)) + data->volume_writable = false; + + if (!data->req_formats) { + /* From this point on, we want to work only with formats, and get back + * to using the sample spec and channel map after all decisions w.r.t. + * routing are complete. */ + pa_format_info *f; + pa_idxset *formats; + + f = pa_format_info_from_sample_spec2(&data->sample_spec, data->channel_map_is_set ? &data->channel_map : NULL, + !(data->flags & PA_SOURCE_OUTPUT_FIX_FORMAT), + !(data->flags & PA_SOURCE_OUTPUT_FIX_RATE), + !(data->flags & PA_SOURCE_OUTPUT_FIX_CHANNELS)); + if (!f) + return -PA_ERR_INVALID; + + formats = pa_idxset_new(NULL, NULL); + pa_idxset_put(formats, f, NULL); + pa_source_output_new_data_set_formats(data, formats); + } + + if ((r = pa_hook_fire(&core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_NEW], data)) < 0) + return r; + + pa_return_val_if_fail(!data->driver || pa_utf8_valid(data->driver), -PA_ERR_INVALID); + + if (!data->source) { + pa_source *source; + + if (data->direct_on_input) { + source = data->direct_on_input->sink->monitor_source; + pa_return_val_if_fail(source, -PA_ERR_INVALID); + } else { + source = pa_namereg_get(core, NULL, PA_NAMEREG_SOURCE); + pa_return_val_if_fail(source, -PA_ERR_NOENTITY); + } + + pa_source_output_new_data_set_source(data, source, false, false); + } + + /* If something didn't pick a format for us, pick the top-most format since + * we assume this is sorted in priority order */ + if (!data->format && data->nego_formats && !pa_idxset_isempty(data->nego_formats)) + data->format = pa_format_info_copy(pa_idxset_first(data->nego_formats, NULL)); + + if (PA_LIKELY(data->format)) { + /* We know that data->source is set, because data->format has been set. + * data->format is set after a successful format negotiation, and that + * can't happen before data->source has been set. */ + pa_assert(data->source); + + pa_log_debug("Negotiated format: %s", pa_format_info_snprint(fmt, sizeof(fmt), data->format)); + } else { + pa_format_info *format; + uint32_t idx; + + pa_log_info("Source does not support any requested format:"); + PA_IDXSET_FOREACH(format, data->req_formats, idx) + pa_log_info(" -- %s", pa_format_info_snprint(fmt, sizeof(fmt), format)); + + return -PA_ERR_NOTSUPPORTED; + } + + pa_return_val_if_fail(PA_SOURCE_IS_LINKED(data->source->state), -PA_ERR_BADSTATE); + pa_return_val_if_fail(!data->direct_on_input || data->direct_on_input->sink == data->source->monitor_of, -PA_ERR_INVALID); + + /* Routing is done. We have a source and a format. */ + + if (data->volume_is_set && !pa_source_output_new_data_is_passthrough(data)) { + /* If volume is set, we need to save the original data->channel_map, + * so that we can remap the volume from the original channel map to the + * final channel map of the stream in case data->channel_map gets + * modified in pa_format_info_to_sample_spec2(). */ + r = pa_stream_get_volume_channel_map(&data->volume, data->channel_map_is_set ? &data->channel_map : NULL, data->format, &volume_map); + if (r < 0) + return r; + } else { + /* Initialize volume_map to invalid state. We check the state later to + * determine if volume remapping is needed. */ + pa_channel_map_init(&volume_map); + } + + /* Now populate the sample spec and channel map according to the final + * format that we've negotiated */ + r = pa_format_info_to_sample_spec2(data->format, &data->sample_spec, &data->channel_map, &data->source->sample_spec, + &data->source->channel_map); + if (r < 0) + return r; + + /* Don't restore (or save) stream volume for passthrough streams and + * prevent attenuation/gain */ + if (pa_source_output_new_data_is_passthrough(data)) { + data->volume_is_set = true; + pa_cvolume_reset(&data->volume, data->sample_spec.channels); + data->volume_is_absolute = true; + data->save_volume = false; + } + + if (!data->volume_is_set) { + pa_cvolume_reset(&data->volume, data->sample_spec.channels); + data->volume_is_absolute = false; + data->save_volume = false; + } + + if (!data->volume_writable) + data->save_volume = false; + + if (pa_channel_map_valid(&volume_map)) + /* The original volume channel map may be different than the final + * stream channel map, so remapping may be needed. */ + pa_cvolume_remap(&data->volume, &volume_map, &data->channel_map); + + if (!data->volume_factor_is_set) + pa_cvolume_reset(&data->volume_factor, data->sample_spec.channels); + + pa_return_val_if_fail(pa_cvolume_compatible(&data->volume_factor, &data->sample_spec), -PA_ERR_INVALID); + + if (!data->volume_factor_source_is_set) + pa_cvolume_reset(&data->volume_factor_source, data->source->sample_spec.channels); + + pa_return_val_if_fail(pa_cvolume_compatible(&data->volume_factor_source, &data->source->sample_spec), -PA_ERR_INVALID); + + if (!data->muted_is_set) + data->muted = false; + + if (!(data->flags & PA_SOURCE_OUTPUT_VARIABLE_RATE) && + !pa_sample_spec_equal(&data->sample_spec, &data->source->sample_spec)) { + /* try to change source format and rate. This is done before the FIXATE hook since + module-suspend-on-idle can resume a source */ + + pa_log_info("Trying to change sample spec"); + pa_source_reconfigure(data->source, &data->sample_spec, pa_source_output_new_data_is_passthrough(data)); + } + + if (pa_source_output_new_data_is_passthrough(data) && + !pa_sample_spec_equal(&data->sample_spec, &data->source->sample_spec)) { + /* rate update failed, or other parts of sample spec didn't match */ + + pa_log_debug("Could not update source sample spec to match passthrough stream"); + return -PA_ERR_NOTSUPPORTED; + } + + if (data->resample_method == PA_RESAMPLER_INVALID) + data->resample_method = core->resample_method; + + pa_return_val_if_fail(data->resample_method < PA_RESAMPLER_MAX, -PA_ERR_INVALID); + + if ((r = pa_hook_fire(&core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_FIXATE], data)) < 0) + return r; + + if ((data->flags & PA_SOURCE_OUTPUT_NO_CREATE_ON_SUSPEND) && + data->source->state == PA_SOURCE_SUSPENDED) { + pa_log("Failed to create source output: source is suspended."); + return -PA_ERR_BADSTATE; + } + + if (pa_idxset_size(data->source->outputs) >= PA_MAX_OUTPUTS_PER_SOURCE) { + pa_log("Failed to create source output: too many outputs per source."); + return -PA_ERR_TOOLARGE; + } + + if ((data->flags & PA_SOURCE_OUTPUT_VARIABLE_RATE) || + !pa_sample_spec_equal(&data->sample_spec, &data->source->sample_spec) || + !pa_channel_map_equal(&data->channel_map, &data->source->channel_map)) { + + if (!pa_source_output_new_data_is_passthrough(data)) /* no resampler for passthrough content */ + if (!(resampler = pa_resampler_new( + core->mempool, + &data->source->sample_spec, &data->source->channel_map, + &data->sample_spec, &data->channel_map, + core->lfe_crossover_freq, + data->resample_method, + ((data->flags & PA_SOURCE_OUTPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0) | + ((data->flags & PA_SOURCE_OUTPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) | + (core->disable_remixing || (data->flags & PA_SOURCE_OUTPUT_NO_REMIX) ? PA_RESAMPLER_NO_REMIX : 0) | + (core->remixing_use_all_sink_channels ? 0 : PA_RESAMPLER_NO_FILL_SINK) | + (core->remixing_produce_lfe ? PA_RESAMPLER_PRODUCE_LFE : 0) | + (core->remixing_consume_lfe ? PA_RESAMPLER_CONSUME_LFE : 0)))) { + pa_log_warn("Unsupported resampling operation."); + return -PA_ERR_NOTSUPPORTED; + } + } + + o = pa_msgobject_new(pa_source_output); + o->parent.parent.free = source_output_free; + o->parent.process_msg = pa_source_output_process_msg; + + o->core = core; + o->state = PA_SOURCE_OUTPUT_INIT; + o->flags = data->flags; + o->proplist = pa_proplist_copy(data->proplist); + o->driver = pa_xstrdup(pa_path_get_filename(data->driver)); + o->module = data->module; + o->source = data->source; + o->source_requested_by_application = data->source_requested_by_application; + o->destination_source = data->destination_source; + o->client = data->client; + + o->requested_resample_method = data->resample_method; + o->actual_resample_method = resampler ? pa_resampler_get_method(resampler) : PA_RESAMPLER_INVALID; + o->sample_spec = data->sample_spec; + o->channel_map = data->channel_map; + o->format = pa_format_info_copy(data->format); + + if (!data->volume_is_absolute && pa_source_flat_volume_enabled(o->source)) { + pa_cvolume remapped; + + /* When the 'absolute' bool is not set then we'll treat the volume + * as relative to the source volume even in flat volume mode */ + remapped = data->source->reference_volume; + pa_cvolume_remap(&remapped, &data->source->channel_map, &data->channel_map); + pa_sw_cvolume_multiply(&o->volume, &data->volume, &remapped); + } else + o->volume = data->volume; + + o->volume_factor = data->volume_factor; + o->volume_factor_source = data->volume_factor_source; + o->real_ratio = o->reference_ratio = data->volume; + pa_cvolume_reset(&o->soft_volume, o->sample_spec.channels); + pa_cvolume_reset(&o->real_ratio, o->sample_spec.channels); + o->volume_writable = data->volume_writable; + o->save_volume = data->save_volume; + o->preferred_source = pa_xstrdup(data->preferred_source); + o->save_muted = data->save_muted; + + o->muted = data->muted; + + o->direct_on_input = data->direct_on_input; + + reset_callbacks(o); + o->userdata = NULL; + + o->thread_info.state = o->state; + o->thread_info.attached = false; + o->thread_info.sample_spec = o->sample_spec; + o->thread_info.resampler = resampler; + o->thread_info.soft_volume = o->soft_volume; + o->thread_info.muted = o->muted; + o->thread_info.requested_source_latency = (pa_usec_t) -1; + o->thread_info.direct_on_input = o->direct_on_input; + + o->thread_info.delay_memblockq = pa_memblockq_new( + "source output delay_memblockq", + 0, + MEMBLOCKQ_MAXLENGTH, + 0, + &o->source->sample_spec, + 0, + 1, + 0, + &o->source->silence); + + pa_assert_se(pa_idxset_put(core->source_outputs, o, &o->index) == 0); + pa_assert_se(pa_idxset_put(o->source->outputs, pa_source_output_ref(o), NULL) == 0); + + if (o->client) + pa_assert_se(pa_idxset_put(o->client->source_outputs, o, NULL) >= 0); + + if (o->direct_on_input) + pa_assert_se(pa_idxset_put(o->direct_on_input->direct_outputs, o, NULL) == 0); + + pt = pa_proplist_to_string_sep(o->proplist, "\n "); + pa_log_info("Created output %u \"%s\" on %s with sample spec %s and channel map %s\n %s", + o->index, + pa_strnull(pa_proplist_gets(o->proplist, PA_PROP_MEDIA_NAME)), + o->source->name, + pa_sample_spec_snprint(st, sizeof(st), &o->sample_spec), + pa_channel_map_snprint(cm, sizeof(cm), &o->channel_map), + pt); + pa_xfree(pt); + + /* Don't forget to call pa_source_output_put! */ + + *_o = o; + return 0; +} + +/* Called from main context */ +static void update_n_corked(pa_source_output *o, pa_source_output_state_t state) { + pa_assert(o); + pa_assert_ctl_context(); + + if (!o->source) + return; + + if (o->state == PA_SOURCE_OUTPUT_CORKED && state != PA_SOURCE_OUTPUT_CORKED) + pa_assert_se(o->source->n_corked -- >= 1); + else if (o->state != PA_SOURCE_OUTPUT_CORKED && state == PA_SOURCE_OUTPUT_CORKED) + o->source->n_corked++; +} + +/* Called from main context */ +static void source_output_set_state(pa_source_output *o, pa_source_output_state_t state) { + + pa_assert(o); + pa_assert_ctl_context(); + + if (o->state == state) + return; + + if (o->source) { + if (o->state == PA_SOURCE_OUTPUT_CORKED && state == PA_SOURCE_OUTPUT_RUNNING && pa_source_used_by(o->source) == 0 && + !pa_sample_spec_equal(&o->sample_spec, &o->source->sample_spec)) { + /* We were uncorked and the source was not playing anything -- let's try + * to update the sample format and rate to avoid resampling */ + pa_source_reconfigure(o->source, &o->sample_spec, pa_source_output_is_passthrough(o)); + } + + pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_SET_STATE, PA_UINT_TO_PTR(state), 0, NULL) == 0); + } else + /* If the source is not valid, pa_source_output_set_state_within_thread() must be called directly */ + pa_source_output_set_state_within_thread(o, state); + + update_n_corked(o, state); + o->state = state; + + if (state != PA_SOURCE_OUTPUT_UNLINKED) { + pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_STATE_CHANGED], o); + + if (PA_SOURCE_OUTPUT_IS_LINKED(state)) + pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index); + } + + if (o->source) + pa_source_update_status(o->source); +} + +/* Called from main context */ +void pa_source_output_unlink(pa_source_output*o) { + bool linked; + + pa_source_output_assert_ref(o); + pa_assert_ctl_context(); + + /* See pa_sink_unlink() for a couple of comments how this function + * works */ + + pa_source_output_ref(o); + + linked = PA_SOURCE_OUTPUT_IS_LINKED(o->state); + + if (linked) + pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK], o); + + if (o->direct_on_input) + pa_idxset_remove_by_data(o->direct_on_input->direct_outputs, o, NULL); + + pa_idxset_remove_by_data(o->core->source_outputs, o, NULL); + + if (o->source) + if (pa_idxset_remove_by_data(o->source->outputs, o, NULL)) + pa_source_output_unref(o); + + if (o->client) + pa_idxset_remove_by_data(o->client->source_outputs, o, NULL); + + update_n_corked(o, PA_SOURCE_OUTPUT_UNLINKED); + o->state = PA_SOURCE_OUTPUT_UNLINKED; + + if (linked && o->source) { + if (pa_source_output_is_passthrough(o)) + pa_source_leave_passthrough(o->source); + + /* We might need to update the source's volume if we are in flat volume mode. */ + if (pa_source_flat_volume_enabled(o->source)) + pa_source_set_volume(o->source, NULL, false, false); + + if (o->source->asyncmsgq) + pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_REMOVE_OUTPUT, o, 0, NULL) == 0); + } + + reset_callbacks(o); + + if (o->source) { + if (PA_SOURCE_IS_LINKED(o->source->state)) + pa_source_update_status(o->source); + + o->source = NULL; + } + + if (linked) { + pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_REMOVE, o->index); + pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK_POST], o); + } + + pa_core_maybe_vacuum(o->core); + + pa_source_output_unref(o); +} + +/* Called from main context */ +static void source_output_free(pa_object* mo) { + pa_source_output *o = PA_SOURCE_OUTPUT(mo); + + pa_assert(o); + pa_assert_ctl_context(); + pa_assert(pa_source_output_refcnt(o) == 0); + pa_assert(!PA_SOURCE_OUTPUT_IS_LINKED(o->state)); + + pa_log_info("Freeing output %u \"%s\"", o->index, + o->proplist ? pa_strnull(pa_proplist_gets(o->proplist, PA_PROP_MEDIA_NAME)) : ""); + + if (o->thread_info.delay_memblockq) + pa_memblockq_free(o->thread_info.delay_memblockq); + + if (o->thread_info.resampler) + pa_resampler_free(o->thread_info.resampler); + + if (o->format) + pa_format_info_free(o->format); + + if (o->proplist) + pa_proplist_free(o->proplist); + + if (o->preferred_source) + pa_xfree(o->preferred_source); + + pa_xfree(o->driver); + pa_xfree(o); +} + +/* Called from main context */ +void pa_source_output_put(pa_source_output *o) { + pa_source_output_state_t state; + + pa_source_output_assert_ref(o); + pa_assert_ctl_context(); + + pa_assert(o->state == PA_SOURCE_OUTPUT_INIT); + + /* The following fields must be initialized properly */ + pa_assert(o->push); + pa_assert(o->kill); + + state = o->flags & PA_SOURCE_OUTPUT_START_CORKED ? PA_SOURCE_OUTPUT_CORKED : PA_SOURCE_OUTPUT_RUNNING; + + update_n_corked(o, state); + o->state = state; + + /* We might need to update the source's volume if we are in flat volume mode. */ + if (pa_source_flat_volume_enabled(o->source)) + pa_source_set_volume(o->source, NULL, false, o->save_volume); + else { + if (o->destination_source && (o->destination_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)) { + pa_assert(pa_cvolume_is_norm(&o->volume)); + pa_assert(pa_cvolume_is_norm(&o->reference_ratio)); + } + + set_real_ratio(o, &o->volume); + } + + if (pa_source_output_is_passthrough(o)) + pa_source_enter_passthrough(o->source); + + o->thread_info.soft_volume = o->soft_volume; + o->thread_info.muted = o->muted; + + pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_ADD_OUTPUT, o, 0, NULL) == 0); + + pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_NEW, o->index); + pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PUT], o); + + pa_source_update_status(o->source); +} + +/* Called from main context */ +void pa_source_output_kill(pa_source_output*o) { + pa_source_output_assert_ref(o); + pa_assert_ctl_context(); + pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state)); + + o->kill(o); +} + +/* Called from main context */ +pa_usec_t pa_source_output_get_latency(pa_source_output *o, pa_usec_t *source_latency) { + pa_usec_t r[2] = { 0, 0 }; + + pa_source_output_assert_ref(o); + pa_assert_ctl_context(); + pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state)); + + pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_GET_LATENCY, r, 0, NULL) == 0); + + if (o->get_latency) + r[0] += o->get_latency(o); + + if (source_latency) + *source_latency = r[1]; + + return r[0]; +} + +/* Called from thread context */ +void pa_source_output_push(pa_source_output *o, const pa_memchunk *chunk) { + bool need_volume_factor_source; + bool volume_is_norm; + size_t length; + size_t limit, mbs = 0; + + pa_source_output_assert_ref(o); + pa_source_output_assert_io_context(o); + pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->thread_info.state)); + pa_assert(chunk); + pa_assert(pa_frame_aligned(chunk->length, &o->source->sample_spec)); + + if (!o->push || o->thread_info.state == PA_SOURCE_OUTPUT_CORKED) + return; + + pa_assert(o->thread_info.state == PA_SOURCE_OUTPUT_RUNNING); + + if (pa_memblockq_push(o->thread_info.delay_memblockq, chunk) < 0) { + pa_log_debug("Delay queue overflow!"); + pa_memblockq_seek(o->thread_info.delay_memblockq, (int64_t) chunk->length, PA_SEEK_RELATIVE, true); + } + + limit = o->process_rewind ? 0 : o->source->thread_info.max_rewind; + + volume_is_norm = pa_cvolume_is_norm(&o->thread_info.soft_volume) && !o->thread_info.muted; + need_volume_factor_source = !pa_cvolume_is_norm(&o->volume_factor_source); + + if (limit > 0 && o->source->monitor_of) { + pa_usec_t latency; + size_t n; + + /* Hmm, check the latency for knowing how much of the buffered + * data is actually still unplayed and might hence still + * change. This is suboptimal. Ideally we'd have a call like + * pa_sink_get_changeable_size() or so that tells us how much + * of the queued data is actually still changeable. Hence + * FIXME! */ + + latency = pa_sink_get_latency_within_thread(o->source->monitor_of, false); + + n = pa_usec_to_bytes(latency, &o->source->sample_spec); + + if (n < limit) + limit = n; + } + + /* Implement the delay queue */ + while ((length = pa_memblockq_get_length(o->thread_info.delay_memblockq)) > limit) { + pa_memchunk qchunk; + bool nvfs = need_volume_factor_source; + + length -= limit; + + pa_assert_se(pa_memblockq_peek(o->thread_info.delay_memblockq, &qchunk) >= 0); + + if (qchunk.length > length) + qchunk.length = length; + + pa_assert(qchunk.length > 0); + + /* It might be necessary to adjust the volume here */ + if (!volume_is_norm) { + pa_memchunk_make_writable(&qchunk, 0); + + if (o->thread_info.muted) { + pa_silence_memchunk(&qchunk, &o->source->sample_spec); + nvfs = false; + + } else if (!o->thread_info.resampler && nvfs) { + pa_cvolume v; + + /* If we don't need a resampler we can merge the + * post and the pre volume adjustment into one */ + + pa_sw_cvolume_multiply(&v, &o->thread_info.soft_volume, &o->volume_factor_source); + pa_volume_memchunk(&qchunk, &o->source->sample_spec, &v); + nvfs = false; + + } else + pa_volume_memchunk(&qchunk, &o->source->sample_spec, &o->thread_info.soft_volume); + } + + if (nvfs) { + pa_memchunk_make_writable(&qchunk, 0); + pa_volume_memchunk(&qchunk, &o->source->sample_spec, &o->volume_factor_source); + } + + if (!o->thread_info.resampler) + o->push(o, &qchunk); + else { + pa_memchunk rchunk; + + if (mbs == 0) + mbs = pa_resampler_max_block_size(o->thread_info.resampler); + + if (qchunk.length > mbs) + qchunk.length = mbs; + + pa_resampler_run(o->thread_info.resampler, &qchunk, &rchunk); + + if (rchunk.length > 0) + o->push(o, &rchunk); + + if (rchunk.memblock) + pa_memblock_unref(rchunk.memblock); + } + + pa_memblock_unref(qchunk.memblock); + pa_memblockq_drop(o->thread_info.delay_memblockq, qchunk.length); + } +} + +/* Called from thread context */ +void pa_source_output_process_rewind(pa_source_output *o, size_t nbytes /* in source sample spec */) { + + pa_source_output_assert_ref(o); + pa_source_output_assert_io_context(o); + pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->thread_info.state)); + pa_assert(pa_frame_aligned(nbytes, &o->source->sample_spec)); + + if (nbytes <= 0) + return; + + if (o->process_rewind) { + pa_assert(pa_memblockq_get_length(o->thread_info.delay_memblockq) == 0); + + if (o->thread_info.resampler) + nbytes = pa_resampler_result(o->thread_info.resampler, nbytes); + + pa_log_debug("Have to rewind %lu bytes on implementor.", (unsigned long) nbytes); + + if (nbytes > 0) + o->process_rewind(o, nbytes); + + if (o->thread_info.resampler) + pa_resampler_rewind(o->thread_info.resampler, nbytes); + + } else + pa_memblockq_seek(o->thread_info.delay_memblockq, - ((int64_t) nbytes), PA_SEEK_RELATIVE, true); +} + +/* Called from thread context */ +size_t pa_source_output_get_max_rewind(pa_source_output *o) { + pa_source_output_assert_ref(o); + pa_source_output_assert_io_context(o); + + return o->thread_info.resampler ? pa_resampler_request(o->thread_info.resampler, o->source->thread_info.max_rewind) : o->source->thread_info.max_rewind; +} + +/* Called from thread context */ +void pa_source_output_update_max_rewind(pa_source_output *o, size_t nbytes /* in the source's sample spec */) { + pa_source_output_assert_ref(o); + pa_source_output_assert_io_context(o); + pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->thread_info.state)); + pa_assert(pa_frame_aligned(nbytes, &o->source->sample_spec)); + + if (o->update_max_rewind) + o->update_max_rewind(o, o->thread_info.resampler ? pa_resampler_result(o->thread_info.resampler, nbytes) : nbytes); +} + +/* Called from thread context */ +pa_usec_t pa_source_output_set_requested_latency_within_thread(pa_source_output *o, pa_usec_t usec) { + pa_source_output_assert_ref(o); + pa_source_output_assert_io_context(o); + + if (!(o->source->flags & PA_SOURCE_DYNAMIC_LATENCY)) + usec = o->source->thread_info.fixed_latency; + + if (usec != (pa_usec_t) -1) + usec = PA_CLAMP(usec, o->source->thread_info.min_latency, o->source->thread_info.max_latency); + + o->thread_info.requested_source_latency = usec; + pa_source_invalidate_requested_latency(o->source, true); + + return usec; +} + +/* Called from main context */ +pa_usec_t pa_source_output_set_requested_latency(pa_source_output *o, pa_usec_t usec) { + pa_source_output_assert_ref(o); + pa_assert_ctl_context(); + + if (PA_SOURCE_OUTPUT_IS_LINKED(o->state) && o->source) { + pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_SET_REQUESTED_LATENCY, &usec, 0, NULL) == 0); + return usec; + } + + /* If this source output is not realized yet or is being moved, we + * have to touch the thread info data directly */ + + if (o->source) { + if (!(o->source->flags & PA_SOURCE_DYNAMIC_LATENCY)) + usec = pa_source_get_fixed_latency(o->source); + + if (usec != (pa_usec_t) -1) { + pa_usec_t min_latency, max_latency; + pa_source_get_latency_range(o->source, &min_latency, &max_latency); + usec = PA_CLAMP(usec, min_latency, max_latency); + } + } + + o->thread_info.requested_source_latency = usec; + + return usec; +} + +/* Called from main context */ +pa_usec_t pa_source_output_get_requested_latency(pa_source_output *o) { + pa_source_output_assert_ref(o); + pa_assert_ctl_context(); + + if (PA_SOURCE_OUTPUT_IS_LINKED(o->state) && o->source) { + pa_usec_t usec = 0; + pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_GET_REQUESTED_LATENCY, &usec, 0, NULL) == 0); + return usec; + } + + /* If this source output is not realized yet or is being moved, we + * have to touch the thread info data directly */ + + return o->thread_info.requested_source_latency; +} + +/* Called from main context */ +void pa_source_output_set_volume(pa_source_output *o, const pa_cvolume *volume, bool save, bool absolute) { + pa_cvolume v; + + pa_source_output_assert_ref(o); + pa_assert_ctl_context(); + pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state)); + pa_assert(volume); + pa_assert(pa_cvolume_valid(volume)); + pa_assert(volume->channels == 1 || pa_cvolume_compatible(volume, &o->sample_spec)); + pa_assert(o->volume_writable); + + if (!absolute && pa_source_flat_volume_enabled(o->source)) { + v = o->source->reference_volume; + pa_cvolume_remap(&v, &o->source->channel_map, &o->channel_map); + + if (pa_cvolume_compatible(volume, &o->sample_spec)) + volume = pa_sw_cvolume_multiply(&v, &v, volume); + else + volume = pa_sw_cvolume_multiply_scalar(&v, &v, pa_cvolume_max(volume)); + } else { + if (!pa_cvolume_compatible(volume, &o->sample_spec)) { + v = o->volume; + volume = pa_cvolume_scale(&v, pa_cvolume_max(volume)); + } + } + + if (pa_cvolume_equal(volume, &o->volume)) { + o->save_volume = o->save_volume || save; + return; + } + + pa_source_output_set_volume_direct(o, volume); + o->save_volume = save; + + if (pa_source_flat_volume_enabled(o->source)) { + /* We are in flat volume mode, so let's update all source input + * volumes and update the flat volume of the source */ + + pa_source_set_volume(o->source, NULL, true, save); + + } else { + /* OK, we are in normal volume mode. The volume only affects + * ourselves */ + set_real_ratio(o, volume); + + /* Copy the new soft_volume to the thread_info struct */ + pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_SET_SOFT_VOLUME, NULL, 0, NULL) == 0); + } + + /* The volume changed, let's tell people so */ + if (o->volume_changed) + o->volume_changed(o); + + /* The virtual volume changed, let's tell people so */ + pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index); +} + +/* Called from main context */ +static void set_real_ratio(pa_source_output *o, const pa_cvolume *v) { + pa_source_output_assert_ref(o); + pa_assert_ctl_context(); + pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state)); + pa_assert(!v || pa_cvolume_compatible(v, &o->sample_spec)); + + /* This basically calculates: + * + * o->real_ratio := v + * o->soft_volume := o->real_ratio * o->volume_factor */ + + if (v) + o->real_ratio = *v; + else + pa_cvolume_reset(&o->real_ratio, o->sample_spec.channels); + + pa_sw_cvolume_multiply(&o->soft_volume, &o->real_ratio, &o->volume_factor); + /* We don't copy the data to the thread_info data. That's left for someone else to do */ +} + +/* Called from main or I/O context */ +bool pa_source_output_is_passthrough(pa_source_output *o) { + pa_source_output_assert_ref(o); + + if (PA_UNLIKELY(!pa_format_info_is_pcm(o->format))) + return true; + + if (PA_UNLIKELY(o->flags & PA_SOURCE_OUTPUT_PASSTHROUGH)) + return true; + + return false; +} + +/* Called from main context */ +bool pa_source_output_is_volume_readable(pa_source_output *o) { + pa_source_output_assert_ref(o); + pa_assert_ctl_context(); + + return !pa_source_output_is_passthrough(o); +} + +/* Called from main context */ +pa_cvolume *pa_source_output_get_volume(pa_source_output *o, pa_cvolume *volume, bool absolute) { + pa_source_output_assert_ref(o); + pa_assert_ctl_context(); + pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state)); + pa_assert(pa_source_output_is_volume_readable(o)); + + if (absolute || !pa_source_flat_volume_enabled(o->source)) + *volume = o->volume; + else + *volume = o->reference_ratio; + + return volume; +} + +/* Called from main context */ +void pa_source_output_set_mute(pa_source_output *o, bool mute, bool save) { + bool old_mute; + + pa_source_output_assert_ref(o); + pa_assert_ctl_context(); + pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state)); + + old_mute = o->muted; + + if (mute == old_mute) { + o->save_muted |= save; + return; + } + + o->muted = mute; + pa_log_debug("The mute of source output %u changed from %s to %s.", o->index, pa_yes_no(old_mute), pa_yes_no(mute)); + + o->save_muted = save; + + pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_SET_SOFT_MUTE, NULL, 0, NULL) == 0); + + /* The mute status changed, let's tell people so */ + if (o->mute_changed) + o->mute_changed(o); + + pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index); + pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MUTE_CHANGED], o); +} + +void pa_source_output_set_property(pa_source_output *o, const char *key, const char *value) { + char *old_value = NULL; + const char *new_value; + + pa_assert(o); + pa_assert(key); + + if (pa_proplist_contains(o->proplist, key)) { + old_value = pa_xstrdup(pa_proplist_gets(o->proplist, key)); + if (value && old_value && pa_streq(value, old_value)) + goto finish; + + if (!old_value) + old_value = pa_xstrdup("(data)"); + } else { + if (!value) + goto finish; + + old_value = pa_xstrdup("(unset)"); + } + + if (value) { + pa_proplist_sets(o->proplist, key, value); + new_value = value; + } else { + pa_proplist_unset(o->proplist, key); + new_value = "(unset)"; + } + + if (PA_SOURCE_OUTPUT_IS_LINKED(o->state)) { + pa_log_debug("Source output %u: proplist[%s]: %s -> %s", o->index, key, old_value, new_value); + pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PROPLIST_CHANGED], o); + pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT | PA_SUBSCRIPTION_EVENT_CHANGE, o->index); + } + +finish: + pa_xfree(old_value); +} + +void pa_source_output_set_property_arbitrary(pa_source_output *o, const char *key, const uint8_t *value, size_t nbytes) { + const uint8_t *old_value; + size_t old_nbytes; + const char *old_value_str; + const char *new_value_str; + + pa_assert(o); + pa_assert(key); + + if (pa_proplist_get(o->proplist, key, (const void **) &old_value, &old_nbytes) >= 0) { + if (value && nbytes == old_nbytes && !memcmp(value, old_value, nbytes)) + return; + + old_value_str = "(data)"; + + } else { + if (!value) + return; + + old_value_str = "(unset)"; + } + + if (value) { + pa_proplist_set(o->proplist, key, value, nbytes); + new_value_str = "(data)"; + } else { + pa_proplist_unset(o->proplist, key); + new_value_str = "(unset)"; + } + + if (PA_SOURCE_OUTPUT_IS_LINKED(o->state)) { + pa_log_debug("Source output %u: proplist[%s]: %s -> %s", o->index, key, old_value_str, new_value_str); + pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PROPLIST_CHANGED], o); + pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT | PA_SUBSCRIPTION_EVENT_CHANGE, o->index); + } +} + +/* Called from main thread */ +void pa_source_output_update_proplist(pa_source_output *o, pa_update_mode_t mode, pa_proplist *p) { + void *state; + const char *key; + const uint8_t *value; + size_t nbytes; + + pa_source_output_assert_ref(o); + pa_assert(p); + pa_assert_ctl_context(); + + switch (mode) { + case PA_UPDATE_SET: + /* Delete everything that is not in p. */ + for (state = NULL; (key = pa_proplist_iterate(o->proplist, &state));) { + if (!pa_proplist_contains(p, key)) + pa_source_output_set_property(o, key, NULL); + } + + /* Fall through. */ + case PA_UPDATE_REPLACE: + for (state = NULL; (key = pa_proplist_iterate(p, &state));) { + pa_proplist_get(p, key, (const void **) &value, &nbytes); + pa_source_output_set_property_arbitrary(o, key, value, nbytes); + } + + break; + case PA_UPDATE_MERGE: + for (state = NULL; (key = pa_proplist_iterate(p, &state));) { + if (pa_proplist_contains(o->proplist, key)) + continue; + + pa_proplist_get(p, key, (const void **) &value, &nbytes); + pa_source_output_set_property_arbitrary(o, key, value, nbytes); + } + + break; + } +} + +/* Called from main context */ +void pa_source_output_cork(pa_source_output *o, bool b) { + pa_source_output_assert_ref(o); + pa_assert_ctl_context(); + pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state)); + + source_output_set_state(o, b ? PA_SOURCE_OUTPUT_CORKED : PA_SOURCE_OUTPUT_RUNNING); +} + +/* Called from main context */ +int pa_source_output_set_rate(pa_source_output *o, uint32_t rate) { + pa_source_output_assert_ref(o); + pa_assert_ctl_context(); + pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state)); + pa_return_val_if_fail(o->thread_info.resampler, -PA_ERR_BADSTATE); + + if (o->sample_spec.rate == rate) + return 0; + + o->sample_spec.rate = rate; + + pa_asyncmsgq_post(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_SET_RATE, PA_UINT_TO_PTR(rate), 0, NULL, NULL); + + pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index); + return 0; +} + +/* Called from main context */ +pa_resample_method_t pa_source_output_get_resample_method(pa_source_output *o) { + pa_source_output_assert_ref(o); + pa_assert_ctl_context(); + + return o->actual_resample_method; +} + +/* Called from main context */ +bool pa_source_output_may_move(pa_source_output *o) { + pa_source_output_assert_ref(o); + pa_assert_ctl_context(); + pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state)); + + if (o->flags & PA_SOURCE_OUTPUT_DONT_MOVE) + return false; + + if (o->direct_on_input) + return false; + + return true; +} + +static bool find_filter_source_output(pa_source_output *target, pa_source *s) { + unsigned PA_UNUSED i = 0; + while (s && s->output_from_master) { + if (s->output_from_master == target) + return true; + s = s->output_from_master->source; + pa_assert(i++ < 100); + } + return false; +} + +static bool is_filter_source_moving(pa_source_output *o) { + pa_source *source = o->source; + + if (!source) + return false; + + while (source->output_from_master) { + source = source->output_from_master->source; + + if (!source) + return true; + } + + return false; +} + +/* Called from main context */ +bool pa_source_output_may_move_to(pa_source_output *o, pa_source *dest) { + pa_source_output_assert_ref(o); + pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state)); + pa_source_assert_ref(dest); + + if (dest == o->source) + return true; + + if (dest->unlink_requested) + return false; + + if (!pa_source_output_may_move(o)) + return false; + + /* Make sure we're not creating a filter source cycle */ + if (find_filter_source_output(o, dest)) { + pa_log_debug("Can't connect output to %s, as that would create a cycle.", dest->name); + return false; + } + + /* If this source output is connected to a filter source that itself is + * moving, then don't allow the move. Moving requires sending a message to + * the IO thread of the old source, and if the old source is a filter + * source that is moving, there's no IO thread associated to the old + * source. */ + if (is_filter_source_moving(o)) { + pa_log_debug("Can't move output from filter source %s, because the filter source itself is currently moving.", + o->source->name); + return false; + } + + if (pa_idxset_size(dest->outputs) >= PA_MAX_OUTPUTS_PER_SOURCE) { + pa_log_warn("Failed to move source output: too many outputs per source."); + return false; + } + + if (o->may_move_to) + if (!o->may_move_to(o, dest)) + return false; + + return true; +} + +/* Called from main context */ +int pa_source_output_start_move(pa_source_output *o) { + pa_source *origin; + int r; + + pa_source_output_assert_ref(o); + pa_assert_ctl_context(); + pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state)); + pa_assert(o->source); + + if (!pa_source_output_may_move(o)) + return -PA_ERR_NOTSUPPORTED; + + if ((r = pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_START], o)) < 0) + return r; + + pa_log_debug("Starting to move source output %u from '%s'", (unsigned) o->index, o->source->name); + + origin = o->source; + + pa_idxset_remove_by_data(o->source->outputs, o, NULL); + + if (o->state == PA_SOURCE_OUTPUT_CORKED) + pa_assert_se(origin->n_corked-- >= 1); + + if (pa_source_output_is_passthrough(o)) + pa_source_leave_passthrough(o->source); + + if (pa_source_flat_volume_enabled(o->source)) + /* We might need to update the source's volume if we are in flat + * volume mode. */ + pa_source_set_volume(o->source, NULL, false, false); + + pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_REMOVE_OUTPUT, o, 0, NULL) == 0); + + pa_source_update_status(o->source); + + pa_cvolume_remap(&o->volume_factor_source, &o->source->channel_map, &o->channel_map); + + o->source = NULL; + o->source_requested_by_application = false; + + pa_source_output_unref(o); + + return 0; +} + +/* Called from main context. If it has an origin source that uses volume sharing, + * then also the origin source and all streams connected to it need to update + * their volume - this function does all that by using recursion. */ +static void update_volume_due_to_moving(pa_source_output *o, pa_source *dest) { + pa_cvolume new_volume; + + pa_assert(o); + pa_assert(dest); + pa_assert(o->source); /* The destination source should already be set. */ + + if (o->destination_source && (o->destination_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)) { + pa_source *root_source; + pa_source_output *destination_source_output; + uint32_t idx; + + root_source = pa_source_get_master(o->source); + + if (PA_UNLIKELY(!root_source)) + return; + + if (pa_source_flat_volume_enabled(o->source)) { + /* Ok, so the origin source uses volume sharing, and flat volume is + * enabled. The volume will have to be updated as follows: + * + * o->volume := o->source->real_volume + * (handled later by pa_source_set_volume) + * o->reference_ratio := o->volume / o->source->reference_volume + * (handled later by pa_source_set_volume) + * o->real_ratio stays unchanged + * (streams whose origin source uses volume sharing should + * always have real_ratio of 0 dB) + * o->soft_volume stays unchanged + * (streams whose origin source uses volume sharing should + * always have volume_factor as soft_volume, so no change + * should be needed) */ + + pa_assert(pa_cvolume_is_norm(&o->real_ratio)); + pa_assert(pa_cvolume_equal(&o->soft_volume, &o->volume_factor)); + + /* Notifications will be sent by pa_source_set_volume(). */ + + } else { + /* Ok, so the origin source uses volume sharing, and flat volume is + * disabled. The volume will have to be updated as follows: + * + * o->volume := 0 dB + * o->reference_ratio := 0 dB + * o->real_ratio stays unchanged + * (streams whose origin source uses volume sharing should + * always have real_ratio of 0 dB) + * o->soft_volume stays unchanged + * (streams whose origin source uses volume sharing should + * always have volume_factor as soft_volume, so no change + * should be needed) */ + + pa_cvolume_reset(&new_volume, o->volume.channels); + pa_source_output_set_volume_direct(o, &new_volume); + pa_source_output_set_reference_ratio(o, &new_volume); + pa_assert(pa_cvolume_is_norm(&o->real_ratio)); + pa_assert(pa_cvolume_equal(&o->soft_volume, &o->volume_factor)); + } + + /* Additionally, the origin source volume needs updating: + * + * o->destination_source->reference_volume := root_source->reference_volume + * o->destination_source->real_volume := root_source->real_volume + * o->destination_source->soft_volume stays unchanged + * (sources that use volume sharing should always have + * soft_volume of 0 dB) */ + + new_volume = root_source->reference_volume; + pa_cvolume_remap(&new_volume, &root_source->channel_map, &o->destination_source->channel_map); + pa_source_set_reference_volume_direct(o->destination_source, &new_volume); + + o->destination_source->real_volume = root_source->real_volume; + pa_cvolume_remap(&o->destination_source->real_volume, &root_source->channel_map, &o->destination_source->channel_map); + + pa_assert(pa_cvolume_is_norm(&o->destination_source->soft_volume)); + + /* If you wonder whether o->destination_source->set_volume() should be + * called somewhere, that's not the case, because sources that use + * volume sharing shouldn't have any internal volume that set_volume() + * would update. If you wonder whether the thread_info variables should + * be synced, yes, they should, and it's done by the + * PA_SOURCE_MESSAGE_FINISH_MOVE message handler. */ + + /* Recursively update origin source outputs. */ + PA_IDXSET_FOREACH(destination_source_output, o->destination_source->outputs, idx) + update_volume_due_to_moving(destination_source_output, dest); + + } else { + if (pa_source_flat_volume_enabled(o->source)) { + /* Ok, so this is a regular stream, and flat volume is enabled. The + * volume will have to be updated as follows: + * + * o->volume := o->reference_ratio * o->source->reference_volume + * o->reference_ratio stays unchanged + * o->real_ratio := o->volume / o->source->real_volume + * (handled later by pa_source_set_volume) + * o->soft_volume := o->real_ratio * o->volume_factor + * (handled later by pa_source_set_volume) */ + + new_volume = o->source->reference_volume; + pa_cvolume_remap(&new_volume, &o->source->channel_map, &o->channel_map); + pa_sw_cvolume_multiply(&new_volume, &new_volume, &o->reference_ratio); + pa_source_output_set_volume_direct(o, &new_volume); + + } else { + /* Ok, so this is a regular stream, and flat volume is disabled. + * The volume will have to be updated as follows: + * + * o->volume := o->reference_ratio + * o->reference_ratio stays unchanged + * o->real_ratio := o->reference_ratio + * o->soft_volume := o->real_ratio * o->volume_factor */ + + pa_source_output_set_volume_direct(o, &o->reference_ratio); + o->real_ratio = o->reference_ratio; + pa_sw_cvolume_multiply(&o->soft_volume, &o->real_ratio, &o->volume_factor); + } + } + + /* If o->source == dest, then recursion has finished, and we can finally call + * pa_source_set_volume(), which will do the rest of the updates. */ + if ((o->source == dest) && pa_source_flat_volume_enabled(o->source)) + pa_source_set_volume(o->source, NULL, false, o->save_volume); +} + +/* Called from main context */ +int pa_source_output_finish_move(pa_source_output *o, pa_source *dest, bool save) { + pa_source_output_assert_ref(o); + pa_assert_ctl_context(); + pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state)); + pa_assert(!o->source); + pa_source_assert_ref(dest); + + if (!pa_source_output_may_move_to(o, dest)) + return -PA_ERR_NOTSUPPORTED; + + if (pa_source_output_is_passthrough(o) && !pa_source_check_format(dest, o->format)) { + pa_proplist *p = pa_proplist_new(); + pa_log_debug("New source doesn't support stream format, sending format-changed and killing"); + /* Tell the client what device we want to be on if it is going to + * reconnect */ + pa_proplist_sets(p, "device", dest->name); + pa_source_output_send_event(o, PA_STREAM_EVENT_FORMAT_LOST, p); + pa_proplist_free(p); + return -PA_ERR_NOTSUPPORTED; + } + + if (!(o->flags & PA_SOURCE_OUTPUT_VARIABLE_RATE) && + !pa_sample_spec_equal(&o->sample_spec, &dest->sample_spec)) { + /* try to change dest source format and rate if possible without glitches. + module-suspend-on-idle resumes destination source with + SOURCE_OUTPUT_MOVE_FINISH hook */ + + pa_log_info("Trying to change sample spec"); + pa_source_reconfigure(dest, &o->sample_spec, pa_source_output_is_passthrough(o)); + } + + if (o->moving) + o->moving(o, dest); + + o->source = dest; + /* save == true, means user is calling the move_to() and want to + save the preferred_source */ + if (save) { + pa_xfree(o->preferred_source); + if (dest == dest->core->default_source) + o->preferred_source = NULL; + else + o->preferred_source = pa_xstrdup(dest->name); + } + + pa_idxset_put(o->source->outputs, pa_source_output_ref(o), NULL); + + pa_cvolume_remap(&o->volume_factor_source, &o->channel_map, &o->source->channel_map); + + if (o->state == PA_SOURCE_OUTPUT_CORKED) + o->source->n_corked++; + + pa_source_output_update_resampler(o); + + pa_source_update_status(dest); + + update_volume_due_to_moving(o, dest); + + if (pa_source_output_is_passthrough(o)) + pa_source_enter_passthrough(o->source); + + pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_ADD_OUTPUT, o, 0, NULL) == 0); + + pa_log_debug("Successfully moved source output %i to %s.", o->index, dest->name); + + /* Notify everyone */ + pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_FINISH], o); + pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index); + + return 0; +} + +/* Called from main context */ +void pa_source_output_fail_move(pa_source_output *o) { + + pa_source_output_assert_ref(o); + pa_assert_ctl_context(); + pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state)); + pa_assert(!o->source); + + /* Check if someone wants this source output? */ + if (pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_FAIL], o) == PA_HOOK_STOP) + return; + + /* Can we move the source output to the default source? */ + if (o->core->rescue_streams && pa_source_output_may_move_to(o, o->core->default_source)) { + if (pa_source_output_finish_move(o, o->core->default_source, false) >= 0) + return; + } + + if (o->moving) + o->moving(o, NULL); + + pa_source_output_kill(o); +} + +/* Called from main context */ +int pa_source_output_move_to(pa_source_output *o, pa_source *dest, bool save) { + int r; + + pa_source_output_assert_ref(o); + pa_assert_ctl_context(); + pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state)); + pa_assert(o->source); + pa_source_assert_ref(dest); + + if (dest == o->source) + return 0; + + if (!pa_source_output_may_move_to(o, dest)) + return -PA_ERR_NOTSUPPORTED; + + pa_source_output_ref(o); + + if ((r = pa_source_output_start_move(o)) < 0) { + pa_source_output_unref(o); + return r; + } + + if ((r = pa_source_output_finish_move(o, dest, save)) < 0) { + pa_source_output_fail_move(o); + pa_source_output_unref(o); + return r; + } + + pa_source_output_unref(o); + + return 0; +} + +/* Called from IO thread context except when cork() is called without a valid source. */ +void pa_source_output_set_state_within_thread(pa_source_output *o, pa_source_output_state_t state) { + pa_source_output_assert_ref(o); + + if (state == o->thread_info.state) + return; + + if (o->state_change) + o->state_change(o, state); + + o->thread_info.state = state; +} + +/* Called from IO thread context, except when it is not */ +int pa_source_output_process_msg(pa_msgobject *mo, int code, void *userdata, int64_t offset, pa_memchunk* chunk) { + pa_source_output *o = PA_SOURCE_OUTPUT(mo); + pa_source_output_assert_ref(o); + + switch (code) { + + case PA_SOURCE_OUTPUT_MESSAGE_GET_LATENCY: { + pa_usec_t *r = userdata; + + r[0] += pa_bytes_to_usec(pa_memblockq_get_length(o->thread_info.delay_memblockq), &o->source->sample_spec); + r[1] += pa_source_get_latency_within_thread(o->source, false); + + return 0; + } + + case PA_SOURCE_OUTPUT_MESSAGE_SET_RATE: + + o->thread_info.sample_spec.rate = PA_PTR_TO_UINT(userdata); + pa_resampler_set_output_rate(o->thread_info.resampler, PA_PTR_TO_UINT(userdata)); + return 0; + + case PA_SOURCE_OUTPUT_MESSAGE_SET_STATE: + + pa_source_output_set_state_within_thread(o, PA_PTR_TO_UINT(userdata)); + + return 0; + + case PA_SOURCE_OUTPUT_MESSAGE_SET_REQUESTED_LATENCY: { + pa_usec_t *usec = userdata; + + *usec = pa_source_output_set_requested_latency_within_thread(o, *usec); + + return 0; + } + + case PA_SOURCE_OUTPUT_MESSAGE_GET_REQUESTED_LATENCY: { + pa_usec_t *r = userdata; + + *r = o->thread_info.requested_source_latency; + return 0; + } + + case PA_SOURCE_OUTPUT_MESSAGE_SET_SOFT_VOLUME: + if (!pa_cvolume_equal(&o->thread_info.soft_volume, &o->soft_volume)) { + o->thread_info.soft_volume = o->soft_volume; + } + return 0; + + case PA_SOURCE_OUTPUT_MESSAGE_SET_SOFT_MUTE: + if (o->thread_info.muted != o->muted) { + o->thread_info.muted = o->muted; + } + return 0; + } + + return -PA_ERR_NOTIMPLEMENTED; +} + +/* Called from main context */ +void pa_source_output_send_event(pa_source_output *o, const char *event, pa_proplist *data) { + pa_proplist *pl = NULL; + pa_source_output_send_event_hook_data hook_data; + + pa_source_output_assert_ref(o); + pa_assert_ctl_context(); + pa_assert(event); + + if (!o->send_event) + return; + + if (!data) + data = pl = pa_proplist_new(); + + hook_data.source_output = o; + hook_data.data = data; + hook_data.event = event; + + if (pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_SEND_EVENT], &hook_data) < 0) + goto finish; + + o->send_event(o, event, data); + +finish: + if (pl) + pa_proplist_free(pl); +} + +/* Called from main context */ +/* Updates the source output's resampler with whatever the current source + * requires -- useful when the underlying source's sample spec might have changed */ +int pa_source_output_update_resampler(pa_source_output *o) { + pa_resampler *new_resampler; + char *memblockq_name; + + pa_source_output_assert_ref(o); + pa_assert_ctl_context(); + + if (o->thread_info.resampler && + pa_sample_spec_equal(pa_resampler_input_sample_spec(o->thread_info.resampler), &o->source->sample_spec) && + pa_channel_map_equal(pa_resampler_input_channel_map(o->thread_info.resampler), &o->source->channel_map)) + + new_resampler = o->thread_info.resampler; + + else if (!pa_source_output_is_passthrough(o) && + ((o->flags & PA_SOURCE_OUTPUT_VARIABLE_RATE) || + !pa_sample_spec_equal(&o->sample_spec, &o->source->sample_spec) || + !pa_channel_map_equal(&o->channel_map, &o->source->channel_map))) { + + new_resampler = pa_resampler_new(o->core->mempool, + &o->source->sample_spec, &o->source->channel_map, + &o->sample_spec, &o->channel_map, + o->core->lfe_crossover_freq, + o->requested_resample_method, + ((o->flags & PA_SOURCE_OUTPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0) | + ((o->flags & PA_SOURCE_OUTPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) | + (o->core->disable_remixing || (o->flags & PA_SOURCE_OUTPUT_NO_REMIX) ? PA_RESAMPLER_NO_REMIX : 0) | + (o->core->remixing_use_all_sink_channels ? 0 : PA_RESAMPLER_NO_FILL_SINK) | + (o->core->remixing_produce_lfe ? PA_RESAMPLER_PRODUCE_LFE : 0) | + (o->core->remixing_consume_lfe ? PA_RESAMPLER_CONSUME_LFE : 0)); + + if (!new_resampler) { + pa_log_warn("Unsupported resampling operation."); + return -PA_ERR_NOTSUPPORTED; + } + } else + new_resampler = NULL; + + if (new_resampler == o->thread_info.resampler) + return 0; + + if (o->thread_info.resampler) + pa_resampler_free(o->thread_info.resampler); + + o->thread_info.resampler = new_resampler; + + pa_memblockq_free(o->thread_info.delay_memblockq); + + memblockq_name = pa_sprintf_malloc("source output delay_memblockq [%u]", o->index); + o->thread_info.delay_memblockq = pa_memblockq_new( + memblockq_name, + 0, + MEMBLOCKQ_MAXLENGTH, + 0, + &o->source->sample_spec, + 0, + 1, + 0, + &o->source->silence); + pa_xfree(memblockq_name); + + o->actual_resample_method = new_resampler ? pa_resampler_get_method(new_resampler) : PA_RESAMPLER_INVALID; + + pa_log_debug("Updated resampler for source output %d", o->index); + + return 0; +} + +/* Called from the IO thread. */ +void pa_source_output_attach(pa_source_output *o) { + pa_assert(o); + pa_assert(!o->thread_info.attached); + + o->thread_info.attached = true; + + if (o->attach) + o->attach(o); +} + +/* Called from the IO thread. */ +void pa_source_output_detach(pa_source_output *o) { + pa_assert(o); + + if (!o->thread_info.attached) + return; + + o->thread_info.attached = false; + + if (o->detach) + o->detach(o); +} + +/* Called from the main thread. */ +void pa_source_output_set_volume_direct(pa_source_output *o, const pa_cvolume *volume) { + pa_cvolume old_volume; + char old_volume_str[PA_CVOLUME_SNPRINT_VERBOSE_MAX]; + char new_volume_str[PA_CVOLUME_SNPRINT_VERBOSE_MAX]; + + pa_assert(o); + pa_assert(volume); + + old_volume = o->volume; + + if (pa_cvolume_equal(volume, &old_volume)) + return; + + o->volume = *volume; + pa_log_debug("The volume of source output %u changed from %s to %s.", o->index, + pa_cvolume_snprint_verbose(old_volume_str, sizeof(old_volume_str), &old_volume, &o->channel_map, true), + pa_cvolume_snprint_verbose(new_volume_str, sizeof(new_volume_str), volume, &o->channel_map, true)); + + if (o->volume_changed) + o->volume_changed(o); + + pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index); + pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_VOLUME_CHANGED], o); +} + +/* Called from the main thread. */ +void pa_source_output_set_reference_ratio(pa_source_output *o, const pa_cvolume *ratio) { + pa_cvolume old_ratio; + char old_ratio_str[PA_CVOLUME_SNPRINT_VERBOSE_MAX]; + char new_ratio_str[PA_CVOLUME_SNPRINT_VERBOSE_MAX]; + + pa_assert(o); + pa_assert(ratio); + + old_ratio = o->reference_ratio; + + if (pa_cvolume_equal(ratio, &old_ratio)) + return; + + o->reference_ratio = *ratio; + + if (!PA_SOURCE_OUTPUT_IS_LINKED(o->state)) + return; + + pa_log_debug("Source output %u reference ratio changed from %s to %s.", o->index, + pa_cvolume_snprint_verbose(old_ratio_str, sizeof(old_ratio_str), &old_ratio, &o->channel_map, true), + pa_cvolume_snprint_verbose(new_ratio_str, sizeof(new_ratio_str), ratio, &o->channel_map, true)); +} + +/* Called from the main thread. */ +void pa_source_output_set_preferred_source(pa_source_output *o, pa_source *s) { + pa_assert(o); + + pa_xfree(o->preferred_source); + if (s) { + o->preferred_source = pa_xstrdup(s->name); + pa_source_output_move_to(o, s, false); + } else { + o->preferred_source = NULL; + pa_source_output_move_to(o, o->core->default_source, false); + } +} diff --git a/src/pulsecore/source-output.h b/src/pulsecore/source-output.h new file mode 100644 index 0000000..2bf5682 --- /dev/null +++ b/src/pulsecore/source-output.h @@ -0,0 +1,410 @@ +#ifndef foopulsesourceoutputhfoo +#define foopulsesourceoutputhfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <inttypes.h> + +#include <pulsecore/typedefs.h> +#include <pulse/sample.h> +#include <pulse/format.h> +#include <pulsecore/memblockq.h> +#include <pulsecore/resampler.h> +#include <pulsecore/module.h> +#include <pulsecore/client.h> +#include <pulsecore/source.h> +#include <pulsecore/core.h> +#include <pulsecore/sink-input.h> + +typedef enum pa_source_output_state { + PA_SOURCE_OUTPUT_INIT, + PA_SOURCE_OUTPUT_RUNNING, + PA_SOURCE_OUTPUT_CORKED, + PA_SOURCE_OUTPUT_UNLINKED +} pa_source_output_state_t; + +static inline bool PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_state_t x) { + return x == PA_SOURCE_OUTPUT_RUNNING || x == PA_SOURCE_OUTPUT_CORKED; +} + +typedef enum pa_source_output_flags { + PA_SOURCE_OUTPUT_VARIABLE_RATE = 1, + PA_SOURCE_OUTPUT_DONT_MOVE = 2, + PA_SOURCE_OUTPUT_START_CORKED = 4, + PA_SOURCE_OUTPUT_NO_REMAP = 8, + PA_SOURCE_OUTPUT_NO_REMIX = 16, + PA_SOURCE_OUTPUT_FIX_FORMAT = 32, + PA_SOURCE_OUTPUT_FIX_RATE = 64, + PA_SOURCE_OUTPUT_FIX_CHANNELS = 128, + PA_SOURCE_OUTPUT_DONT_INHIBIT_AUTO_SUSPEND = 256, + PA_SOURCE_OUTPUT_NO_CREATE_ON_SUSPEND = 512, + PA_SOURCE_OUTPUT_KILL_ON_SUSPEND = 1024, + PA_SOURCE_OUTPUT_PASSTHROUGH = 2048 +} pa_source_output_flags_t; + +struct pa_source_output { + pa_msgobject parent; + + uint32_t index; + pa_core *core; + + pa_source_output_state_t state; + pa_source_output_flags_t flags; + + char *driver; /* may be NULL */ + pa_proplist *proplist; + + pa_module *module; /* may be NULL */ + pa_client *client; /* may be NULL */ + + pa_source *source; /* NULL while being moved */ + + /* This is set to true when creating the source output if the source was + * requested by the application that created the source output. This is + * sometimes useful for determining whether the source output should be + * moved by some automatic policy. If the source output is moved away from + * the source that the application requested, this flag is reset to + * false. */ + bool source_requested_by_application; + + pa_source *destination_source; /* only set by filter sources */ + + /* A source output can monitor just a single input of a sink, in which case we find it here */ + pa_sink_input *direct_on_input; /* may be NULL */ + + pa_sample_spec sample_spec; + pa_channel_map channel_map; + pa_format_info *format; + + /* Also see http://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/Developer/Volumes/ */ + pa_cvolume volume; /* The volume clients are informed about */ + pa_cvolume reference_ratio; /* The ratio of the stream's volume to the source's reference volume */ + pa_cvolume real_ratio; /* The ratio of the stream's volume to the source's real volume */ + pa_cvolume volume_factor; /* An internally used volume factor that can be used by modules to apply effects and suchlike without having that visible to the outside */ + pa_cvolume soft_volume; /* The internal software volume we apply to all PCM data while it passes through. Usually calculated as real_ratio * volume_factor */ + + pa_cvolume volume_factor_source; /* A second volume factor in format of the source this stream is connected to */ + + bool volume_writable:1; + + bool muted:1; + + /* if true then the volume and the mute state of this source-output + * are worth remembering, module-stream-restore looks for this. */ + bool save_volume:1, save_muted:1; + + /* if users move the source-output to a source, and the source is not + * default_source, the source->name will be saved in preferred_source. And + * later if source-output is moved to other sources for some reason, it + * still can be restored to the preferred_source at an appropriate time */ + char *preferred_source; + + pa_resample_method_t requested_resample_method, actual_resample_method; + + /* Pushes a new memchunk into the output. Called from IO thread + * context. */ + void (*push)(pa_source_output *o, const pa_memchunk *chunk); /* may NOT be NULL */ + + /* Only relevant for monitor sources right now: called when the + * recorded stream is rewound. Called from IO context */ + void (*process_rewind)(pa_source_output *o, size_t nbytes); /* may be NULL */ + + /* Called whenever the maximum rewindable size of the source + * changes. Called from IO thread context. */ + void (*update_max_rewind) (pa_source_output *o, size_t nbytes); /* may be NULL */ + + /* Called whenever the configured latency of the source + * changes. Called from IO context. */ + void (*update_source_requested_latency) (pa_source_output *o); /* may be NULL */ + + /* Called whenever the latency range of the source changes. Called + * from IO context. */ + void (*update_source_latency_range) (pa_source_output *o); /* may be NULL */ + + /* Called whenever the fixed latency of the source changes, if there + * is one. Called from IO context. */ + void (*update_source_fixed_latency) (pa_source_output *i); /* may be NULL */ + + /* If non-NULL this function is called when the output is first + * connected to a source or when the rtpoll/asyncmsgq fields + * change. You usually don't need to implement this function + * unless you rewrite a source that is piggy-backed onto + * another. Called from IO thread context */ + void (*attach) (pa_source_output *o); /* may be NULL */ + + /* If non-NULL this function is called when the output is + * disconnected from its source. Called from IO thread context */ + void (*detach) (pa_source_output *o); /* may be NULL */ + + /* If non-NULL called whenever the source this output is attached + * to suspends or resumes or if the suspend cause changes. + * Called from main context */ + void (*suspend) (pa_source_output *o, pa_source_state_t old_state, pa_suspend_cause_t old_suspend_cause); /* may be NULL */ + + /* If non-NULL called whenever the source this output is attached + * to suspends or resumes. Called from IO context */ + void (*suspend_within_thread) (pa_source_output *o, bool b); /* may be NULL */ + + /* If non-NULL called whenever the source output is moved to a new + * source. Called from main context after the source output has been + * detached from the old source and before it has been attached to + * the new source. If dest is NULL the move was executed in two + * phases and the second one failed; the stream will be destroyed + * after this call. */ + void (*moving) (pa_source_output *o, pa_source *dest); /* may be NULL */ + + /* Supposed to unlink and destroy this stream. Called from main + * context. */ + void (*kill)(pa_source_output* o); /* may NOT be NULL */ + + /* Return the current latency (i.e. length of buffered audio) of + this stream. Called from main context. This is added to what the + PA_SOURCE_OUTPUT_MESSAGE_GET_LATENCY message sent to the IO thread + returns */ + pa_usec_t (*get_latency) (pa_source_output *o); /* may be NULL */ + + /* If non-NULL this function is called from thread context if the + * state changes. The old state is found in thread_info.state. */ + void (*state_change) (pa_source_output *o, pa_source_output_state_t state); /* may be NULL */ + + /* If non-NULL this function is called before this source output + * is moved to a source and if it returns false the move + * will not be allowed */ + bool (*may_move_to) (pa_source_output *o, pa_source *s); /* may be NULL */ + + /* If non-NULL this function is used to dispatch asynchronous + * control events. */ + void (*send_event)(pa_source_output *o, const char *event, pa_proplist* data); + + /* If non-NULL this function is called whenever the source output + * volume changes. Called from main context */ + void (*volume_changed)(pa_source_output *o); /* may be NULL */ + + /* If non-NULL this function is called whenever the source output + * mute status changes. Called from main context */ + void (*mute_changed)(pa_source_output *o); /* may be NULL */ + + struct { + pa_source_output_state_t state; + + pa_cvolume soft_volume; + bool muted:1; + + bool attached:1; /* True only between ->attach() and ->detach() calls */ + + pa_sample_spec sample_spec; + + pa_resampler* resampler; /* may be NULL */ + + /* We maintain a delay memblockq here for source outputs that + * don't implement rewind() */ + pa_memblockq *delay_memblockq; + + /* The requested latency for the source */ + pa_usec_t requested_source_latency; + + pa_sink_input *direct_on_input; /* may be NULL */ + } thread_info; + + void *userdata; +}; + +PA_DECLARE_PUBLIC_CLASS(pa_source_output); +#define PA_SOURCE_OUTPUT(o) pa_source_output_cast(o) + +enum { + PA_SOURCE_OUTPUT_MESSAGE_GET_LATENCY, + PA_SOURCE_OUTPUT_MESSAGE_SET_RATE, + PA_SOURCE_OUTPUT_MESSAGE_SET_STATE, + PA_SOURCE_OUTPUT_MESSAGE_SET_REQUESTED_LATENCY, + PA_SOURCE_OUTPUT_MESSAGE_GET_REQUESTED_LATENCY, + PA_SOURCE_OUTPUT_MESSAGE_SET_SOFT_VOLUME, + PA_SOURCE_OUTPUT_MESSAGE_SET_SOFT_MUTE, + PA_SOURCE_OUTPUT_MESSAGE_MAX +}; + +typedef struct pa_source_output_send_event_hook_data { + pa_source_output *source_output; + const char *event; + pa_proplist *data; +} pa_source_output_send_event_hook_data; + +typedef struct pa_source_output_new_data { + pa_source_output_flags_t flags; + + pa_proplist *proplist; + pa_sink_input *direct_on_input; + + const char *driver; + pa_module *module; + pa_client *client; + + pa_source *source; + bool source_requested_by_application; + pa_source *destination_source; + + pa_resample_method_t resample_method; + + pa_sample_spec sample_spec; + pa_channel_map channel_map; + pa_format_info *format; + pa_idxset *req_formats; + pa_idxset *nego_formats; + + pa_cvolume volume, volume_factor, volume_factor_source; + bool muted:1; + + bool sample_spec_is_set:1; + bool channel_map_is_set:1; + + bool volume_is_set:1, volume_factor_is_set:1, volume_factor_source_is_set:1; + bool muted_is_set:1; + + bool volume_is_absolute:1; + + bool volume_writable:1; + + bool save_volume:1, save_muted:1; + char *preferred_source; +} pa_source_output_new_data; + +pa_source_output_new_data* pa_source_output_new_data_init(pa_source_output_new_data *data); +void pa_source_output_new_data_set_sample_spec(pa_source_output_new_data *data, const pa_sample_spec *spec); +void pa_source_output_new_data_set_channel_map(pa_source_output_new_data *data, const pa_channel_map *map); +bool pa_source_output_new_data_is_passthrough(pa_source_output_new_data *data); +void pa_source_output_new_data_set_volume(pa_source_output_new_data *data, const pa_cvolume *volume); +void pa_source_output_new_data_apply_volume_factor(pa_source_output_new_data *data, const pa_cvolume *volume_factor); +void pa_source_output_new_data_apply_volume_factor_source(pa_source_output_new_data *data, const pa_cvolume *volume_factor); +void pa_source_output_new_data_set_muted(pa_source_output_new_data *data, bool mute); +bool pa_source_output_new_data_set_source(pa_source_output_new_data *data, pa_source *s, bool save, + bool requested_by_application); +bool pa_source_output_new_data_set_formats(pa_source_output_new_data *data, pa_idxset *formats); +void pa_source_output_new_data_done(pa_source_output_new_data *data); + +/* To be called by the implementing module only */ + +int pa_source_output_new( + pa_source_output**o, + pa_core *core, + pa_source_output_new_data *data); + +void pa_source_output_put(pa_source_output *o); +void pa_source_output_unlink(pa_source_output*o); + +pa_usec_t pa_source_output_set_requested_latency(pa_source_output *o, pa_usec_t usec); + +void pa_source_output_cork(pa_source_output *o, bool b); + +int pa_source_output_set_rate(pa_source_output *o, uint32_t rate); +int pa_source_output_update_resampler(pa_source_output *o); + +size_t pa_source_output_get_max_rewind(pa_source_output *o); + +/* Callable by everyone */ + +/* External code may request disconnection with this function */ +void pa_source_output_kill(pa_source_output*o); + +pa_usec_t pa_source_output_get_latency(pa_source_output *o, pa_usec_t *source_latency); + +bool pa_source_output_is_volume_readable(pa_source_output *o); +bool pa_source_output_is_passthrough(pa_source_output *o); +void pa_source_output_set_volume(pa_source_output *o, const pa_cvolume *volume, bool save, bool absolute); +pa_cvolume *pa_source_output_get_volume(pa_source_output *o, pa_cvolume *volume, bool absolute); + +void pa_source_output_set_mute(pa_source_output *o, bool mute, bool save); + +void pa_source_output_set_property(pa_source_output *o, const char *key, const char *value); +void pa_source_output_set_property_arbitrary(pa_source_output *o, const char *key, const uint8_t *value, size_t nbytes); +void pa_source_output_update_proplist(pa_source_output *o, pa_update_mode_t mode, pa_proplist *p); + +pa_resample_method_t pa_source_output_get_resample_method(pa_source_output *o); + +void pa_source_output_send_event(pa_source_output *o, const char *name, pa_proplist *data); + +bool pa_source_output_may_move(pa_source_output *o); +bool pa_source_output_may_move_to(pa_source_output *o, pa_source *dest); +int pa_source_output_move_to(pa_source_output *o, pa_source *dest, bool save); + +/* The same as pa_source_output_move_to() but in two separate steps, + * first the detaching from the old source, then the attaching to the + * new source */ +int pa_source_output_start_move(pa_source_output *o); +int pa_source_output_finish_move(pa_source_output *o, pa_source *dest, bool save); +void pa_source_output_fail_move(pa_source_output *o); + +pa_usec_t pa_source_output_get_requested_latency(pa_source_output *o); + +/* To be used exclusively by the source driver thread */ + +void pa_source_output_push(pa_source_output *o, const pa_memchunk *chunk); +void pa_source_output_process_rewind(pa_source_output *o, size_t nbytes); +void pa_source_output_update_max_rewind(pa_source_output *o, size_t nbytes); + +void pa_source_output_set_state_within_thread(pa_source_output *o, pa_source_output_state_t state); + +int pa_source_output_process_msg(pa_msgobject *mo, int code, void *userdata, int64_t offset, pa_memchunk *chunk); + +pa_usec_t pa_source_output_set_requested_latency_within_thread(pa_source_output *o, pa_usec_t usec); + +/* Calls the attach() callback if it's set. The output must be in detached + * state. */ +void pa_source_output_attach(pa_source_output *o); + +/* Calls the detach() callback if it's set and the output is attached. The + * output is allowed to be already detached, in which case this does nothing. + * + * The reason why this can be called for already-detached outputs is that when + * a filter source's output is detached, it has to detach also all outputs + * connected to the filter source. In case the filter source's output was + * detached because the filter source is being removed, those other outputs + * will be moved to another source or removed, and moving and removing involve + * detaching the outputs, but the outputs at that point are already detached. + * + * XXX: Moving or removing an output also involves sending messages to the + * output's source. If the output's source is a detached filter source, + * shouldn't sending messages to it be prohibited? The messages are processed + * in the root source's IO thread, and when the filter source is detached, it + * would seem logical to prohibit any interaction with the IO thread that isn't + * any more associated with the filter source. Currently sending messages to + * detached filter sources mostly works, because the filter sources don't + * update their asyncmsgq pointer when detaching, so messages still find their + * way to the old IO thread. */ +void pa_source_output_detach(pa_source_output *o); + +/* Called from the main thread, from source.c only. The normal way to set the + * source output volume is to call pa_source_output_set_volume(), but the flat + * volume logic in source.c needs also a function that doesn't do all the extra + * stuff that pa_source_output_set_volume() does. This function simply sets + * o->volume and fires change notifications. */ +void pa_source_output_set_volume_direct(pa_source_output *o, const pa_cvolume *volume); + +/* Called from the main thread, from source.c only. This shouldn't be a public + * function, but the flat volume logic in source.c currently needs a way to + * directly set the source output reference ratio. This function simply sets + * o->reference_ratio and logs a message if the value changes. */ +void pa_source_output_set_reference_ratio(pa_source_output *o, const pa_cvolume *ratio); + +void pa_source_output_set_preferred_source(pa_source_output *o, pa_source *s); + +#define pa_source_output_assert_io_context(s) \ + pa_assert(pa_thread_mq_get() || !PA_SOURCE_OUTPUT_IS_LINKED((s)->state)) + +#endif diff --git a/src/pulsecore/source.c b/src/pulsecore/source.c new file mode 100644 index 0000000..efc3640 --- /dev/null +++ b/src/pulsecore/source.c @@ -0,0 +1,3052 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> + +#include <pulse/format.h> +#include <pulse/utf8.h> +#include <pulse/xmalloc.h> +#include <pulse/timeval.h> +#include <pulse/util.h> +#include <pulse/rtclock.h> +#include <pulse/internal.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/source-output.h> +#include <pulsecore/namereg.h> +#include <pulsecore/core-subscribe.h> +#include <pulsecore/log.h> +#include <pulsecore/mix.h> +#include <pulsecore/flist.h> + +#include "source.h" + +#define ABSOLUTE_MIN_LATENCY (500) +#define ABSOLUTE_MAX_LATENCY (10*PA_USEC_PER_SEC) +#define DEFAULT_FIXED_LATENCY (250*PA_USEC_PER_MSEC) + +PA_DEFINE_PUBLIC_CLASS(pa_source, pa_msgobject); + +struct pa_source_volume_change { + pa_usec_t at; + pa_cvolume hw_volume; + + PA_LLIST_FIELDS(pa_source_volume_change); +}; + +struct set_state_data { + pa_source_state_t state; + pa_suspend_cause_t suspend_cause; +}; + +static void source_free(pa_object *o); + +static void pa_source_volume_change_push(pa_source *s); +static void pa_source_volume_change_flush(pa_source *s); + +pa_source_new_data* pa_source_new_data_init(pa_source_new_data *data) { + pa_assert(data); + + pa_zero(*data); + data->proplist = pa_proplist_new(); + data->ports = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, (pa_free_cb_t) pa_device_port_unref); + + return data; +} + +void pa_source_new_data_set_name(pa_source_new_data *data, const char *name) { + pa_assert(data); + + pa_xfree(data->name); + data->name = pa_xstrdup(name); +} + +void pa_source_new_data_set_sample_spec(pa_source_new_data *data, const pa_sample_spec *spec) { + pa_assert(data); + + if ((data->sample_spec_is_set = !!spec)) + data->sample_spec = *spec; +} + +void pa_source_new_data_set_channel_map(pa_source_new_data *data, const pa_channel_map *map) { + pa_assert(data); + + if ((data->channel_map_is_set = !!map)) + data->channel_map = *map; +} + +void pa_source_new_data_set_alternate_sample_rate(pa_source_new_data *data, const uint32_t alternate_sample_rate) { + pa_assert(data); + + data->alternate_sample_rate_is_set = true; + data->alternate_sample_rate = alternate_sample_rate; +} + +void pa_source_new_data_set_avoid_resampling(pa_source_new_data *data, bool avoid_resampling) { + pa_assert(data); + + data->avoid_resampling_is_set = true; + data->avoid_resampling = avoid_resampling; +} + +void pa_source_new_data_set_volume(pa_source_new_data *data, const pa_cvolume *volume) { + pa_assert(data); + + if ((data->volume_is_set = !!volume)) + data->volume = *volume; +} + +void pa_source_new_data_set_muted(pa_source_new_data *data, bool mute) { + pa_assert(data); + + data->muted_is_set = true; + data->muted = mute; +} + +void pa_source_new_data_set_port(pa_source_new_data *data, const char *port) { + pa_assert(data); + + pa_xfree(data->active_port); + data->active_port = pa_xstrdup(port); +} + +void pa_source_new_data_done(pa_source_new_data *data) { + pa_assert(data); + + pa_proplist_free(data->proplist); + + if (data->ports) + pa_hashmap_free(data->ports); + + pa_xfree(data->name); + pa_xfree(data->active_port); +} + +/* Called from main context */ +static void reset_callbacks(pa_source *s) { + pa_assert(s); + + s->set_state_in_main_thread = NULL; + s->set_state_in_io_thread = NULL; + s->get_volume = NULL; + s->set_volume = NULL; + s->write_volume = NULL; + s->get_mute = NULL; + s->set_mute = NULL; + s->update_requested_latency = NULL; + s->set_port = NULL; + s->get_formats = NULL; + s->reconfigure = NULL; +} + +/* Called from main context */ +pa_source* pa_source_new( + pa_core *core, + pa_source_new_data *data, + pa_source_flags_t flags) { + + pa_source *s; + const char *name; + char st[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX]; + char *pt; + + pa_assert(core); + pa_assert(data); + pa_assert(data->name); + pa_assert_ctl_context(); + + s = pa_msgobject_new(pa_source); + + if (!(name = pa_namereg_register(core, data->name, PA_NAMEREG_SOURCE, s, data->namereg_fail))) { + pa_log_debug("Failed to register name %s.", data->name); + pa_xfree(s); + return NULL; + } + + pa_source_new_data_set_name(data, name); + + if (pa_hook_fire(&core->hooks[PA_CORE_HOOK_SOURCE_NEW], data) < 0) { + pa_xfree(s); + pa_namereg_unregister(core, name); + return NULL; + } + + /* FIXME, need to free s here on failure */ + + pa_return_null_if_fail(!data->driver || pa_utf8_valid(data->driver)); + pa_return_null_if_fail(data->name && pa_utf8_valid(data->name) && data->name[0]); + + pa_return_null_if_fail(data->sample_spec_is_set && pa_sample_spec_valid(&data->sample_spec)); + + if (!data->channel_map_is_set) + pa_return_null_if_fail(pa_channel_map_init_auto(&data->channel_map, data->sample_spec.channels, PA_CHANNEL_MAP_DEFAULT)); + + pa_return_null_if_fail(pa_channel_map_valid(&data->channel_map)); + pa_return_null_if_fail(data->channel_map.channels == data->sample_spec.channels); + + /* FIXME: There should probably be a general function for checking whether + * the source volume is allowed to be set, like there is for source outputs. */ + pa_assert(!data->volume_is_set || !(flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)); + + if (!data->volume_is_set) { + pa_cvolume_reset(&data->volume, data->sample_spec.channels); + data->save_volume = false; + } + + pa_return_null_if_fail(pa_cvolume_valid(&data->volume)); + pa_return_null_if_fail(pa_cvolume_compatible(&data->volume, &data->sample_spec)); + + if (!data->muted_is_set) + data->muted = false; + + if (data->card) + pa_proplist_update(data->proplist, PA_UPDATE_MERGE, data->card->proplist); + + pa_device_init_description(data->proplist, data->card); + pa_device_init_icon(data->proplist, false); + pa_device_init_intended_roles(data->proplist); + + if (!data->active_port) { + pa_device_port *p = pa_device_port_find_best(data->ports); + if (p) + pa_source_new_data_set_port(data, p->name); + } + + if (pa_hook_fire(&core->hooks[PA_CORE_HOOK_SOURCE_FIXATE], data) < 0) { + pa_xfree(s); + pa_namereg_unregister(core, name); + return NULL; + } + + s->parent.parent.free = source_free; + s->parent.process_msg = pa_source_process_msg; + + s->core = core; + s->state = PA_SOURCE_INIT; + s->flags = flags; + s->priority = 0; + s->suspend_cause = data->suspend_cause; + s->name = pa_xstrdup(name); + s->proplist = pa_proplist_copy(data->proplist); + s->driver = pa_xstrdup(pa_path_get_filename(data->driver)); + s->module = data->module; + s->card = data->card; + + s->priority = pa_device_init_priority(s->proplist); + + s->sample_spec = data->sample_spec; + s->channel_map = data->channel_map; + s->default_sample_rate = s->sample_spec.rate; + + if (data->alternate_sample_rate_is_set) + s->alternate_sample_rate = data->alternate_sample_rate; + else + s->alternate_sample_rate = s->core->alternate_sample_rate; + + if (data->avoid_resampling_is_set) + s->avoid_resampling = data->avoid_resampling; + else + s->avoid_resampling = s->core->avoid_resampling; + + s->outputs = pa_idxset_new(NULL, NULL); + s->n_corked = 0; + s->monitor_of = NULL; + s->output_from_master = NULL; + + s->reference_volume = s->real_volume = data->volume; + pa_cvolume_reset(&s->soft_volume, s->sample_spec.channels); + s->base_volume = PA_VOLUME_NORM; + s->n_volume_steps = PA_VOLUME_NORM+1; + s->muted = data->muted; + s->refresh_volume = s->refresh_muted = false; + + reset_callbacks(s); + s->userdata = NULL; + + s->asyncmsgq = NULL; + + /* As a minor optimization we just steal the list instead of + * copying it here */ + s->ports = data->ports; + data->ports = NULL; + + s->active_port = NULL; + s->save_port = false; + + if (data->active_port) + if ((s->active_port = pa_hashmap_get(s->ports, data->active_port))) + s->save_port = data->save_port; + + /* Hopefully the active port has already been assigned in the previous call + to pa_device_port_find_best, but better safe than sorry */ + if (!s->active_port) + s->active_port = pa_device_port_find_best(s->ports); + + if (s->active_port) + s->port_latency_offset = s->active_port->latency_offset; + else + s->port_latency_offset = 0; + + s->save_volume = data->save_volume; + s->save_muted = data->save_muted; + + pa_silence_memchunk_get( + &core->silence_cache, + core->mempool, + &s->silence, + &s->sample_spec, + 0); + + s->thread_info.rtpoll = NULL; + s->thread_info.outputs = pa_hashmap_new_full(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func, NULL, + (pa_free_cb_t) pa_source_output_unref); + s->thread_info.soft_volume = s->soft_volume; + s->thread_info.soft_muted = s->muted; + s->thread_info.state = s->state; + s->thread_info.max_rewind = 0; + s->thread_info.requested_latency_valid = false; + s->thread_info.requested_latency = 0; + s->thread_info.min_latency = ABSOLUTE_MIN_LATENCY; + s->thread_info.max_latency = ABSOLUTE_MAX_LATENCY; + s->thread_info.fixed_latency = flags & PA_SOURCE_DYNAMIC_LATENCY ? 0 : DEFAULT_FIXED_LATENCY; + + PA_LLIST_HEAD_INIT(pa_source_volume_change, s->thread_info.volume_changes); + s->thread_info.volume_changes_tail = NULL; + pa_sw_cvolume_divide(&s->thread_info.current_hw_volume, &s->real_volume, &s->soft_volume); + s->thread_info.volume_change_safety_margin = core->deferred_volume_safety_margin_usec; + s->thread_info.volume_change_extra_delay = core->deferred_volume_extra_delay_usec; + s->thread_info.port_latency_offset = s->port_latency_offset; + + /* FIXME: This should probably be moved to pa_source_put() */ + pa_assert_se(pa_idxset_put(core->sources, s, &s->index) >= 0); + + if (s->card) + pa_assert_se(pa_idxset_put(s->card->sources, s, NULL) >= 0); + + pt = pa_proplist_to_string_sep(s->proplist, "\n "); + pa_log_info("Created source %u \"%s\" with sample spec %s and channel map %s\n %s", + s->index, + s->name, + pa_sample_spec_snprint(st, sizeof(st), &s->sample_spec), + pa_channel_map_snprint(cm, sizeof(cm), &s->channel_map), + pt); + pa_xfree(pt); + + return s; +} + +/* Called from main context */ +static int source_set_state(pa_source *s, pa_source_state_t state, pa_suspend_cause_t suspend_cause) { + int ret = 0; + bool state_changed; + bool suspend_cause_changed; + bool suspending; + bool resuming; + pa_source_state_t old_state; + pa_suspend_cause_t old_suspend_cause; + + pa_assert(s); + pa_assert_ctl_context(); + + state_changed = state != s->state; + suspend_cause_changed = suspend_cause != s->suspend_cause; + + if (!state_changed && !suspend_cause_changed) + return 0; + + suspending = PA_SOURCE_IS_OPENED(s->state) && state == PA_SOURCE_SUSPENDED; + resuming = s->state == PA_SOURCE_SUSPENDED && PA_SOURCE_IS_OPENED(state); + + /* If we are resuming, suspend_cause must be 0. */ + pa_assert(!resuming || !suspend_cause); + + /* Here's something to think about: what to do with the suspend cause if + * resuming the source fails? The old suspend cause will be incorrect, so we + * can't use that. On the other hand, if we set no suspend cause (as is the + * case currently), then it looks strange to have a source suspended without + * any cause. It might be a good idea to add a new "resume failed" suspend + * cause, or it might just add unnecessary complexity, given that the + * current approach of not setting any suspend cause works well enough. */ + + if (s->set_state_in_main_thread) { + if ((ret = s->set_state_in_main_thread(s, state, suspend_cause)) < 0) { + /* set_state_in_main_thread() is allowed to fail only when resuming. */ + pa_assert(resuming); + + /* If resuming fails, we set the state to SUSPENDED and + * suspend_cause to 0. */ + state = PA_SOURCE_SUSPENDED; + suspend_cause = 0; + state_changed = false; + suspend_cause_changed = suspend_cause != s->suspend_cause; + resuming = false; + + /* We know the state isn't changing. If the suspend cause isn't + * changing either, then there's nothing more to do. */ + if (!suspend_cause_changed) + return ret; + } + } + + if (s->asyncmsgq) { + struct set_state_data data = { .state = state, .suspend_cause = suspend_cause }; + + if ((ret = pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_STATE, &data, 0, NULL)) < 0) { + /* SET_STATE is allowed to fail only when resuming. */ + pa_assert(resuming); + + if (s->set_state_in_main_thread) + s->set_state_in_main_thread(s, PA_SOURCE_SUSPENDED, 0); + + /* If resuming fails, we set the state to SUSPENDED and + * suspend_cause to 0. */ + state = PA_SOURCE_SUSPENDED; + suspend_cause = 0; + state_changed = false; + suspend_cause_changed = suspend_cause != s->suspend_cause; + resuming = false; + + /* We know the state isn't changing. If the suspend cause isn't + * changing either, then there's nothing more to do. */ + if (!suspend_cause_changed) + return ret; + } + } + + old_suspend_cause = s->suspend_cause; + if (suspend_cause_changed) { + char old_cause_buf[PA_SUSPEND_CAUSE_TO_STRING_BUF_SIZE]; + char new_cause_buf[PA_SUSPEND_CAUSE_TO_STRING_BUF_SIZE]; + + pa_log_debug("%s: suspend_cause: %s -> %s", s->name, pa_suspend_cause_to_string(s->suspend_cause, old_cause_buf), + pa_suspend_cause_to_string(suspend_cause, new_cause_buf)); + s->suspend_cause = suspend_cause; + } + + old_state = s->state; + if (state_changed) { + pa_log_debug("%s: state: %s -> %s", s->name, pa_source_state_to_string(s->state), pa_source_state_to_string(state)); + s->state = state; + + /* If we enter UNLINKED state, then we don't send change notifications. + * pa_source_unlink() will send unlink notifications instead. */ + if (state != PA_SOURCE_UNLINKED) { + pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_STATE_CHANGED], s); + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE | PA_SUBSCRIPTION_EVENT_CHANGE, s->index); + } + } + + if (suspending || resuming || suspend_cause_changed) { + pa_source_output *o; + uint32_t idx; + + /* We're suspending or resuming, tell everyone about it */ + + PA_IDXSET_FOREACH(o, s->outputs, idx) + if (s->state == PA_SOURCE_SUSPENDED && + (o->flags & PA_SOURCE_OUTPUT_KILL_ON_SUSPEND)) + pa_source_output_kill(o); + else if (o->suspend) + o->suspend(o, old_state, old_suspend_cause); + } + + return ret; +} + +void pa_source_set_get_volume_callback(pa_source *s, pa_source_cb_t cb) { + pa_assert(s); + + s->get_volume = cb; +} + +void pa_source_set_set_volume_callback(pa_source *s, pa_source_cb_t cb) { + pa_source_flags_t flags; + + pa_assert(s); + pa_assert(!s->write_volume || cb); + + s->set_volume = cb; + + /* Save the current flags so we can tell if they've changed */ + flags = s->flags; + + if (cb) { + /* The source implementor is responsible for setting decibel volume support */ + s->flags |= PA_SOURCE_HW_VOLUME_CTRL; + } else { + s->flags &= ~PA_SOURCE_HW_VOLUME_CTRL; + /* See note below in pa_source_put() about volume sharing and decibel volumes */ + pa_source_enable_decibel_volume(s, !(s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)); + } + + /* If the flags have changed after init, let any clients know via a change event */ + if (s->state != PA_SOURCE_INIT && flags != s->flags) + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); +} + +void pa_source_set_write_volume_callback(pa_source *s, pa_source_cb_t cb) { + pa_source_flags_t flags; + + pa_assert(s); + pa_assert(!cb || s->set_volume); + + s->write_volume = cb; + + /* Save the current flags so we can tell if they've changed */ + flags = s->flags; + + if (cb) + s->flags |= PA_SOURCE_DEFERRED_VOLUME; + else + s->flags &= ~PA_SOURCE_DEFERRED_VOLUME; + + /* If the flags have changed after init, let any clients know via a change event */ + if (s->state != PA_SOURCE_INIT && flags != s->flags) + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); +} + +void pa_source_set_get_mute_callback(pa_source *s, pa_source_get_mute_cb_t cb) { + pa_assert(s); + + s->get_mute = cb; +} + +void pa_source_set_set_mute_callback(pa_source *s, pa_source_cb_t cb) { + pa_source_flags_t flags; + + pa_assert(s); + + s->set_mute = cb; + + /* Save the current flags so we can tell if they've changed */ + flags = s->flags; + + if (cb) + s->flags |= PA_SOURCE_HW_MUTE_CTRL; + else + s->flags &= ~PA_SOURCE_HW_MUTE_CTRL; + + /* If the flags have changed after init, let any clients know via a change event */ + if (s->state != PA_SOURCE_INIT && flags != s->flags) + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); +} + +static void enable_flat_volume(pa_source *s, bool enable) { + pa_source_flags_t flags; + + pa_assert(s); + + /* Always follow the overall user preference here */ + enable = enable && s->core->flat_volumes; + + /* Save the current flags so we can tell if they've changed */ + flags = s->flags; + + if (enable) + s->flags |= PA_SOURCE_FLAT_VOLUME; + else + s->flags &= ~PA_SOURCE_FLAT_VOLUME; + + /* If the flags have changed after init, let any clients know via a change event */ + if (s->state != PA_SOURCE_INIT && flags != s->flags) + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); +} + +void pa_source_enable_decibel_volume(pa_source *s, bool enable) { + pa_source_flags_t flags; + + pa_assert(s); + + /* Save the current flags so we can tell if they've changed */ + flags = s->flags; + + if (enable) { + s->flags |= PA_SOURCE_DECIBEL_VOLUME; + enable_flat_volume(s, true); + } else { + s->flags &= ~PA_SOURCE_DECIBEL_VOLUME; + enable_flat_volume(s, false); + } + + /* If the flags have changed after init, let any clients know via a change event */ + if (s->state != PA_SOURCE_INIT && flags != s->flags) + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); +} + +/* Called from main context */ +void pa_source_put(pa_source *s) { + pa_source_assert_ref(s); + pa_assert_ctl_context(); + + pa_assert(s->state == PA_SOURCE_INIT); + pa_assert(!(s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER) || pa_source_is_filter(s)); + + /* The following fields must be initialized properly when calling _put() */ + pa_assert(s->asyncmsgq); + pa_assert(s->thread_info.min_latency <= s->thread_info.max_latency); + + /* Generally, flags should be initialized via pa_source_new(). As a + * special exception we allow some volume related flags to be set + * between _new() and _put() by the callback setter functions above. + * + * Thus we implement a couple safeguards here which ensure the above + * setters were used (or at least the implementor made manual changes + * in a compatible way). + * + * Note: All of these flags set here can change over the life time + * of the source. */ + pa_assert(!(s->flags & PA_SOURCE_HW_VOLUME_CTRL) || s->set_volume); + pa_assert(!(s->flags & PA_SOURCE_DEFERRED_VOLUME) || s->write_volume); + pa_assert(!(s->flags & PA_SOURCE_HW_MUTE_CTRL) || s->set_mute); + + /* XXX: Currently decibel volume is disabled for all sources that use volume + * sharing. When the master source supports decibel volume, it would be good + * to have the flag also in the filter source, but currently we don't do that + * so that the flags of the filter source never change when it's moved from + * a master source to another. One solution for this problem would be to + * remove user-visible volume altogether from filter sources when volume + * sharing is used, but the current approach was easier to implement... */ + /* We always support decibel volumes in software, otherwise we leave it to + * the source implementor to set this flag as needed. + * + * Note: This flag can also change over the life time of the source. */ + if (!(s->flags & PA_SOURCE_HW_VOLUME_CTRL) && !(s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)) { + pa_source_enable_decibel_volume(s, true); + s->soft_volume = s->reference_volume; + } + + /* If the source implementor support DB volumes by itself, we should always + * try and enable flat volumes too */ + if ((s->flags & PA_SOURCE_DECIBEL_VOLUME)) + enable_flat_volume(s, true); + + if (s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER) { + pa_source *root_source = pa_source_get_master(s); + + pa_assert(PA_LIKELY(root_source)); + + s->reference_volume = root_source->reference_volume; + pa_cvolume_remap(&s->reference_volume, &root_source->channel_map, &s->channel_map); + + s->real_volume = root_source->real_volume; + pa_cvolume_remap(&s->real_volume, &root_source->channel_map, &s->channel_map); + } else + /* We assume that if the sink implementor changed the default + * volume they did so in real_volume, because that is the usual + * place where they are supposed to place their changes. */ + s->reference_volume = s->real_volume; + + s->thread_info.soft_volume = s->soft_volume; + s->thread_info.soft_muted = s->muted; + pa_sw_cvolume_divide(&s->thread_info.current_hw_volume, &s->real_volume, &s->soft_volume); + + pa_assert((s->flags & PA_SOURCE_HW_VOLUME_CTRL) + || (s->base_volume == PA_VOLUME_NORM + && ((s->flags & PA_SOURCE_DECIBEL_VOLUME || (s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER))))); + pa_assert(!(s->flags & PA_SOURCE_DECIBEL_VOLUME) || s->n_volume_steps == PA_VOLUME_NORM+1); + pa_assert(!(s->flags & PA_SOURCE_DYNAMIC_LATENCY) == !(s->thread_info.fixed_latency == 0)); + + if (s->suspend_cause) + pa_assert_se(source_set_state(s, PA_SOURCE_SUSPENDED, s->suspend_cause) == 0); + else + pa_assert_se(source_set_state(s, PA_SOURCE_IDLE, 0) == 0); + + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE | PA_SUBSCRIPTION_EVENT_NEW, s->index); + pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_PUT], s); + + /* It's good to fire the SOURCE_PUT hook before updating the default source, + * because module-switch-on-connect will set the new source as the default + * source, and if we were to call pa_core_update_default_source() before that, + * the default source might change twice, causing unnecessary stream moving. */ + pa_core_update_default_source(s->core); + + pa_core_move_streams_to_newly_available_preferred_source(s->core, s); +} + +/* Called from main context */ +void pa_source_unlink(pa_source *s) { + bool linked; + pa_source_output *o, PA_UNUSED *j = NULL; + + pa_source_assert_ref(s); + pa_assert_ctl_context(); + + /* See pa_sink_unlink() for a couple of comments how this function + * works. */ + + if (s->unlink_requested) + return; + + s->unlink_requested = true; + + linked = PA_SOURCE_IS_LINKED(s->state); + + if (linked) + pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], s); + + if (s->state != PA_SOURCE_UNLINKED) + pa_namereg_unregister(s->core, s->name); + pa_idxset_remove_by_data(s->core->sources, s, NULL); + + pa_core_update_default_source(s->core); + + if (linked && s->core->rescue_streams) + pa_source_move_streams_to_default_source(s->core, s, false); + + if (s->card) + pa_idxset_remove_by_data(s->card->sources, s, NULL); + + while ((o = pa_idxset_first(s->outputs, NULL))) { + pa_assert(o != j); + pa_source_output_kill(o); + j = o; + } + + if (linked) + /* It's important to keep the suspend cause unchanged when unlinking, + * because if we remove the SESSION suspend cause here, the alsa + * source will sync its volume with the hardware while another user is + * active, messing up the volume for that other user. */ + source_set_state(s, PA_SOURCE_UNLINKED, s->suspend_cause); + else + s->state = PA_SOURCE_UNLINKED; + + reset_callbacks(s); + + if (linked) { + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE | PA_SUBSCRIPTION_EVENT_REMOVE, s->index); + pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK_POST], s); + } +} + +/* Called from main context */ +static void source_free(pa_object *o) { + pa_source *s = PA_SOURCE(o); + + pa_assert(s); + pa_assert_ctl_context(); + pa_assert(pa_source_refcnt(s) == 0); + pa_assert(!PA_SOURCE_IS_LINKED(s->state)); + + pa_log_info("Freeing source %u \"%s\"", s->index, s->name); + + pa_source_volume_change_flush(s); + + pa_idxset_free(s->outputs, NULL); + pa_hashmap_free(s->thread_info.outputs); + + if (s->silence.memblock) + pa_memblock_unref(s->silence.memblock); + + pa_xfree(s->name); + pa_xfree(s->driver); + + if (s->proplist) + pa_proplist_free(s->proplist); + + if (s->ports) + pa_hashmap_free(s->ports); + + pa_xfree(s); +} + +/* Called from main context, and not while the IO thread is active, please */ +void pa_source_set_asyncmsgq(pa_source *s, pa_asyncmsgq *q) { + pa_source_assert_ref(s); + pa_assert_ctl_context(); + + s->asyncmsgq = q; +} + +/* Called from main context, and not while the IO thread is active, please */ +void pa_source_update_flags(pa_source *s, pa_source_flags_t mask, pa_source_flags_t value) { + pa_source_flags_t old_flags; + pa_source_output *output; + uint32_t idx; + + pa_source_assert_ref(s); + pa_assert_ctl_context(); + + /* For now, allow only a minimal set of flags to be changed. */ + pa_assert((mask & ~(PA_SOURCE_DYNAMIC_LATENCY|PA_SOURCE_LATENCY)) == 0); + + old_flags = s->flags; + s->flags = (s->flags & ~mask) | (value & mask); + + if (s->flags == old_flags) + return; + + if ((s->flags & PA_SOURCE_LATENCY) != (old_flags & PA_SOURCE_LATENCY)) + pa_log_debug("Source %s: LATENCY flag %s.", s->name, (s->flags & PA_SOURCE_LATENCY) ? "enabled" : "disabled"); + + if ((s->flags & PA_SOURCE_DYNAMIC_LATENCY) != (old_flags & PA_SOURCE_DYNAMIC_LATENCY)) + pa_log_debug("Source %s: DYNAMIC_LATENCY flag %s.", + s->name, (s->flags & PA_SOURCE_DYNAMIC_LATENCY) ? "enabled" : "disabled"); + + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE | PA_SUBSCRIPTION_EVENT_CHANGE, s->index); + pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_FLAGS_CHANGED], s); + + PA_IDXSET_FOREACH(output, s->outputs, idx) { + if (output->destination_source) + pa_source_update_flags(output->destination_source, mask, value); + } +} + +/* Called from IO context, or before _put() from main context */ +void pa_source_set_rtpoll(pa_source *s, pa_rtpoll *p) { + pa_source_assert_ref(s); + pa_source_assert_io_context(s); + + s->thread_info.rtpoll = p; +} + +/* Called from main context */ +int pa_source_update_status(pa_source*s) { + pa_source_assert_ref(s); + pa_assert_ctl_context(); + pa_assert(PA_SOURCE_IS_LINKED(s->state)); + + if (s->state == PA_SOURCE_SUSPENDED) + return 0; + + return source_set_state(s, pa_source_used_by(s) ? PA_SOURCE_RUNNING : PA_SOURCE_IDLE, 0); +} + +/* Called from main context */ +int pa_source_suspend(pa_source *s, bool suspend, pa_suspend_cause_t cause) { + pa_suspend_cause_t merged_cause; + + pa_source_assert_ref(s); + pa_assert_ctl_context(); + pa_assert(PA_SOURCE_IS_LINKED(s->state)); + pa_assert(cause != 0); + + if (s->monitor_of && cause != PA_SUSPEND_PASSTHROUGH) + return -PA_ERR_NOTSUPPORTED; + + if (suspend) + merged_cause = s->suspend_cause | cause; + else + merged_cause = s->suspend_cause & ~cause; + + if (merged_cause) + return source_set_state(s, PA_SOURCE_SUSPENDED, merged_cause); + else + return source_set_state(s, pa_source_used_by(s) ? PA_SOURCE_RUNNING : PA_SOURCE_IDLE, 0); +} + +/* Called from main context */ +int pa_source_sync_suspend(pa_source *s) { + pa_sink_state_t state; + pa_suspend_cause_t suspend_cause; + + pa_source_assert_ref(s); + pa_assert_ctl_context(); + pa_assert(PA_SOURCE_IS_LINKED(s->state)); + pa_assert(s->monitor_of); + + state = s->monitor_of->state; + suspend_cause = s->monitor_of->suspend_cause; + + /* The monitor source usually has the same state and suspend cause as the + * sink, the only exception is when the monitor source is suspended due to + * the sink being in the passthrough mode. If the monitor currently has the + * PASSTHROUGH suspend cause, then we have to keep the monitor suspended + * even if the sink is running. */ + if (s->suspend_cause & PA_SUSPEND_PASSTHROUGH) + suspend_cause |= PA_SUSPEND_PASSTHROUGH; + + if (state == PA_SINK_SUSPENDED || suspend_cause) + return source_set_state(s, PA_SOURCE_SUSPENDED, suspend_cause); + + pa_assert(PA_SINK_IS_OPENED(state)); + + return source_set_state(s, pa_source_used_by(s) ? PA_SOURCE_RUNNING : PA_SOURCE_IDLE, 0); +} + +/* Called from main context */ +pa_queue *pa_source_move_all_start(pa_source *s, pa_queue *q) { + pa_source_output *o, *n; + uint32_t idx; + + pa_source_assert_ref(s); + pa_assert_ctl_context(); + pa_assert(PA_SOURCE_IS_LINKED(s->state)); + + if (!q) + q = pa_queue_new(); + + for (o = PA_SOURCE_OUTPUT(pa_idxset_first(s->outputs, &idx)); o; o = n) { + n = PA_SOURCE_OUTPUT(pa_idxset_next(s->outputs, &idx)); + + pa_source_output_ref(o); + + if (pa_source_output_start_move(o) >= 0) + pa_queue_push(q, o); + else + pa_source_output_unref(o); + } + + return q; +} + +/* Called from main context */ +void pa_source_move_all_finish(pa_source *s, pa_queue *q, bool save) { + pa_source_output *o; + + pa_source_assert_ref(s); + pa_assert_ctl_context(); + pa_assert(PA_SOURCE_IS_LINKED(s->state)); + pa_assert(q); + + while ((o = PA_SOURCE_OUTPUT(pa_queue_pop(q)))) { + if (PA_SOURCE_OUTPUT_IS_LINKED(o->state)) { + if (pa_source_output_finish_move(o, s, save) < 0) + pa_source_output_fail_move(o); + + } + pa_source_output_unref(o); + } + + pa_queue_free(q, NULL); +} + +/* Called from main context */ +void pa_source_move_all_fail(pa_queue *q) { + pa_source_output *o; + + pa_assert_ctl_context(); + pa_assert(q); + + while ((o = PA_SOURCE_OUTPUT(pa_queue_pop(q)))) { + pa_source_output_fail_move(o); + pa_source_output_unref(o); + } + + pa_queue_free(q, NULL); +} + +/* Called from IO thread context */ +void pa_source_process_rewind(pa_source *s, size_t nbytes) { + pa_source_output *o; + void *state = NULL; + + pa_source_assert_ref(s); + pa_source_assert_io_context(s); + pa_assert(PA_SOURCE_IS_LINKED(s->thread_info.state)); + + if (nbytes <= 0) + return; + + if (s->thread_info.state == PA_SOURCE_SUSPENDED) + return; + + pa_log_debug("Processing rewind..."); + + PA_HASHMAP_FOREACH(o, s->thread_info.outputs, state) { + pa_source_output_assert_ref(o); + pa_source_output_process_rewind(o, nbytes); + } +} + +/* Called from IO thread context */ +void pa_source_post(pa_source*s, const pa_memchunk *chunk) { + pa_source_output *o; + void *state = NULL; + + pa_source_assert_ref(s); + pa_source_assert_io_context(s); + pa_assert(PA_SOURCE_IS_LINKED(s->thread_info.state)); + pa_assert(chunk); + + if (s->thread_info.state == PA_SOURCE_SUSPENDED) + return; + + if (s->thread_info.soft_muted || !pa_cvolume_is_norm(&s->thread_info.soft_volume)) { + pa_memchunk vchunk = *chunk; + + pa_memblock_ref(vchunk.memblock); + pa_memchunk_make_writable(&vchunk, 0); + + if (s->thread_info.soft_muted || pa_cvolume_is_muted(&s->thread_info.soft_volume)) + pa_silence_memchunk(&vchunk, &s->sample_spec); + else + pa_volume_memchunk(&vchunk, &s->sample_spec, &s->thread_info.soft_volume); + + while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL))) { + pa_source_output_assert_ref(o); + + if (!o->thread_info.direct_on_input) + pa_source_output_push(o, &vchunk); + } + + pa_memblock_unref(vchunk.memblock); + } else { + + while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL))) { + pa_source_output_assert_ref(o); + + if (!o->thread_info.direct_on_input) + pa_source_output_push(o, chunk); + } + } +} + +/* Called from IO thread context */ +void pa_source_post_direct(pa_source*s, pa_source_output *o, const pa_memchunk *chunk) { + pa_source_assert_ref(s); + pa_source_assert_io_context(s); + pa_assert(PA_SOURCE_IS_LINKED(s->thread_info.state)); + pa_source_output_assert_ref(o); + pa_assert(o->thread_info.direct_on_input); + pa_assert(chunk); + + if (s->thread_info.state == PA_SOURCE_SUSPENDED) + return; + + if (s->thread_info.soft_muted || !pa_cvolume_is_norm(&s->thread_info.soft_volume)) { + pa_memchunk vchunk = *chunk; + + pa_memblock_ref(vchunk.memblock); + pa_memchunk_make_writable(&vchunk, 0); + + if (s->thread_info.soft_muted || pa_cvolume_is_muted(&s->thread_info.soft_volume)) + pa_silence_memchunk(&vchunk, &s->sample_spec); + else + pa_volume_memchunk(&vchunk, &s->sample_spec, &s->thread_info.soft_volume); + + pa_source_output_push(o, &vchunk); + + pa_memblock_unref(vchunk.memblock); + } else + pa_source_output_push(o, chunk); +} + +/* Called from main thread */ +void pa_source_reconfigure(pa_source *s, pa_sample_spec *spec, bool passthrough) { + uint32_t idx; + pa_source_output *o; + pa_sample_spec desired_spec; + uint32_t default_rate = s->default_sample_rate; + uint32_t alternate_rate = s->alternate_sample_rate; + bool default_rate_is_usable = false; + bool alternate_rate_is_usable = false; + bool avoid_resampling = s->avoid_resampling; + + if (pa_sample_spec_equal(spec, &s->sample_spec)) + return; + + if (!s->reconfigure && !s->monitor_of) + return; + + if (PA_UNLIKELY(default_rate == alternate_rate && !passthrough && !avoid_resampling)) { + pa_log_debug("Default and alternate sample rates are the same, so there is no point in switching."); + return; + } + + if (PA_SOURCE_IS_RUNNING(s->state)) { + pa_log_info("Cannot update sample spec, SOURCE_IS_RUNNING, will keep using %s and %u Hz", + pa_sample_format_to_string(s->sample_spec.format), s->sample_spec.rate); + return; + } + + if (s->monitor_of) { + if (PA_SINK_IS_RUNNING(s->monitor_of->state)) { + pa_log_info("Cannot update sample spec, this is a monitor source and the sink is running."); + return; + } + } + + if (PA_UNLIKELY(!pa_sample_spec_valid(spec))) + return; + + desired_spec = s->sample_spec; + + if (passthrough) { + /* We have to try to use the source output format and rate */ + desired_spec.format = spec->format; + desired_spec.rate = spec->rate; + + } else if (avoid_resampling) { + /* We just try to set the source output's sample rate if it's not too low */ + if (spec->rate >= default_rate || spec->rate >= alternate_rate) + desired_spec.rate = spec->rate; + desired_spec.format = spec->format; + + } else if (default_rate == spec->rate || alternate_rate == spec->rate) { + /* We can directly try to use this rate */ + desired_spec.rate = spec->rate; + + } + + if (desired_spec.rate != spec->rate) { + /* See if we can pick a rate that results in less resampling effort */ + if (default_rate % 11025 == 0 && spec->rate % 11025 == 0) + default_rate_is_usable = true; + if (default_rate % 4000 == 0 && spec->rate % 4000 == 0) + default_rate_is_usable = true; + if (alternate_rate % 11025 == 0 && spec->rate % 11025 == 0) + alternate_rate_is_usable = true; + if (alternate_rate % 4000 == 0 && spec->rate % 4000 == 0) + alternate_rate_is_usable = true; + + if (alternate_rate_is_usable && !default_rate_is_usable) + desired_spec.rate = alternate_rate; + else + desired_spec.rate = default_rate; + } + + if (pa_sample_spec_equal(&desired_spec, &s->sample_spec) && passthrough == pa_source_is_passthrough(s)) + return; + + if (!passthrough && pa_source_used_by(s) > 0) + return; + + pa_log_debug("Suspending source %s due to changing format, desired format = %s rate = %u", + s->name, pa_sample_format_to_string(desired_spec.format), desired_spec.rate); + pa_source_suspend(s, true, PA_SUSPEND_INTERNAL); + + if (s->reconfigure) + s->reconfigure(s, &desired_spec, passthrough); + else { + /* This is a monitor source. */ + + /* XXX: This code is written with non-passthrough streams in mind. I + * have no idea whether the behaviour with passthrough streams is + * sensible. */ + if (!passthrough) { + s->sample_spec = desired_spec; + pa_sink_reconfigure(s->monitor_of, &desired_spec, false); + s->sample_spec = s->monitor_of->sample_spec; + } else + goto unsuspend; + } + + PA_IDXSET_FOREACH(o, s->outputs, idx) { + if (o->state == PA_SOURCE_OUTPUT_CORKED) + pa_source_output_update_resampler(o); + } + + pa_log_info("Reconfigured successfully"); + +unsuspend: + pa_source_suspend(s, false, PA_SUSPEND_INTERNAL); +} + +/* Called from main thread */ +pa_usec_t pa_source_get_latency(pa_source *s) { + int64_t usec; + + pa_source_assert_ref(s); + pa_assert_ctl_context(); + pa_assert(PA_SOURCE_IS_LINKED(s->state)); + + if (s->state == PA_SOURCE_SUSPENDED) + return 0; + + if (!(s->flags & PA_SOURCE_LATENCY)) + return 0; + + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_GET_LATENCY, &usec, 0, NULL) == 0); + + /* The return value is unsigned, so check that the offset can be added to usec without + * underflowing. */ + if (-s->port_latency_offset <= usec) + usec += s->port_latency_offset; + else + usec = 0; + + return (pa_usec_t)usec; +} + +/* Called from IO thread */ +int64_t pa_source_get_latency_within_thread(pa_source *s, bool allow_negative) { + int64_t usec = 0; + pa_msgobject *o; + + pa_source_assert_ref(s); + pa_source_assert_io_context(s); + pa_assert(PA_SOURCE_IS_LINKED(s->thread_info.state)); + + /* The returned value is supposed to be in the time domain of the sound card! */ + + if (s->thread_info.state == PA_SOURCE_SUSPENDED) + return 0; + + if (!(s->flags & PA_SOURCE_LATENCY)) + return 0; + + o = PA_MSGOBJECT(s); + + /* FIXME: We probably should make this a proper vtable callback instead of going through process_msg() */ + + o->process_msg(o, PA_SOURCE_MESSAGE_GET_LATENCY, &usec, 0, NULL); + + /* If allow_negative is false, the call should only return positive values, */ + usec += s->thread_info.port_latency_offset; + if (!allow_negative && usec < 0) + usec = 0; + + return usec; +} + +/* Called from the main thread (and also from the IO thread while the main + * thread is waiting). + * + * When a source uses volume sharing, it never has the PA_SOURCE_FLAT_VOLUME flag + * set. Instead, flat volume mode is detected by checking whether the root source + * has the flag set. */ +bool pa_source_flat_volume_enabled(pa_source *s) { + pa_source_assert_ref(s); + + s = pa_source_get_master(s); + + if (PA_LIKELY(s)) + return (s->flags & PA_SOURCE_FLAT_VOLUME); + else + return false; +} + +/* Called from the main thread (and also from the IO thread while the main + * thread is waiting). */ +pa_source *pa_source_get_master(pa_source *s) { + pa_source_assert_ref(s); + + while (s && (s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)) { + if (PA_UNLIKELY(!s->output_from_master)) + return NULL; + + s = s->output_from_master->source; + } + + return s; +} + +/* Called from main context */ +bool pa_source_is_filter(pa_source *s) { + pa_source_assert_ref(s); + + return (s->output_from_master != NULL); +} + +/* Called from main context */ +bool pa_source_is_passthrough(pa_source *s) { + + pa_source_assert_ref(s); + + /* NB Currently only monitor sources support passthrough mode */ + return (s->monitor_of && pa_sink_is_passthrough(s->monitor_of)); +} + +/* Called from main context */ +void pa_source_enter_passthrough(pa_source *s) { + pa_cvolume volume; + + /* set the volume to NORM */ + s->saved_volume = *pa_source_get_volume(s, true); + s->saved_save_volume = s->save_volume; + + pa_cvolume_set(&volume, s->sample_spec.channels, PA_MIN(s->base_volume, PA_VOLUME_NORM)); + pa_source_set_volume(s, &volume, true, false); +} + +/* Called from main context */ +void pa_source_leave_passthrough(pa_source *s) { + /* Restore source volume to what it was before we entered passthrough mode */ + pa_source_set_volume(s, &s->saved_volume, true, s->saved_save_volume); + + pa_cvolume_init(&s->saved_volume); + s->saved_save_volume = false; +} + +/* Called from main context. */ +static void compute_reference_ratio(pa_source_output *o) { + unsigned c = 0; + pa_cvolume remapped; + pa_cvolume ratio; + + pa_assert(o); + pa_assert(pa_source_flat_volume_enabled(o->source)); + + /* + * Calculates the reference ratio from the source's reference + * volume. This basically calculates: + * + * o->reference_ratio = o->volume / o->source->reference_volume + */ + + remapped = o->source->reference_volume; + pa_cvolume_remap(&remapped, &o->source->channel_map, &o->channel_map); + + ratio = o->reference_ratio; + + for (c = 0; c < o->sample_spec.channels; c++) { + + /* We don't update when the source volume is 0 anyway */ + if (remapped.values[c] <= PA_VOLUME_MUTED) + continue; + + /* Don't update the reference ratio unless necessary */ + if (pa_sw_volume_multiply( + ratio.values[c], + remapped.values[c]) == o->volume.values[c]) + continue; + + ratio.values[c] = pa_sw_volume_divide( + o->volume.values[c], + remapped.values[c]); + } + + pa_source_output_set_reference_ratio(o, &ratio); +} + +/* Called from main context. Only called for the root source in volume sharing + * cases, except for internal recursive calls. */ +static void compute_reference_ratios(pa_source *s) { + uint32_t idx; + pa_source_output *o; + + pa_source_assert_ref(s); + pa_assert_ctl_context(); + pa_assert(PA_SOURCE_IS_LINKED(s->state)); + pa_assert(pa_source_flat_volume_enabled(s)); + + PA_IDXSET_FOREACH(o, s->outputs, idx) { + compute_reference_ratio(o); + + if (o->destination_source && (o->destination_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER) + && PA_SOURCE_IS_LINKED(o->destination_source->state)) + compute_reference_ratios(o->destination_source); + } +} + +/* Called from main context. Only called for the root source in volume sharing + * cases, except for internal recursive calls. */ +static void compute_real_ratios(pa_source *s) { + pa_source_output *o; + uint32_t idx; + + pa_source_assert_ref(s); + pa_assert_ctl_context(); + pa_assert(PA_SOURCE_IS_LINKED(s->state)); + pa_assert(pa_source_flat_volume_enabled(s)); + + PA_IDXSET_FOREACH(o, s->outputs, idx) { + unsigned c; + pa_cvolume remapped; + + if (o->destination_source && (o->destination_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)) { + /* The origin source uses volume sharing, so this input's real ratio + * is handled as a special case - the real ratio must be 0 dB, and + * as a result i->soft_volume must equal i->volume_factor. */ + pa_cvolume_reset(&o->real_ratio, o->real_ratio.channels); + o->soft_volume = o->volume_factor; + + if (PA_SOURCE_IS_LINKED(o->destination_source->state)) + compute_real_ratios(o->destination_source); + + continue; + } + + /* + * This basically calculates: + * + * i->real_ratio := i->volume / s->real_volume + * i->soft_volume := i->real_ratio * i->volume_factor + */ + + remapped = s->real_volume; + pa_cvolume_remap(&remapped, &s->channel_map, &o->channel_map); + + o->real_ratio.channels = o->sample_spec.channels; + o->soft_volume.channels = o->sample_spec.channels; + + for (c = 0; c < o->sample_spec.channels; c++) { + + if (remapped.values[c] <= PA_VOLUME_MUTED) { + /* We leave o->real_ratio untouched */ + o->soft_volume.values[c] = PA_VOLUME_MUTED; + continue; + } + + /* Don't lose accuracy unless necessary */ + if (pa_sw_volume_multiply( + o->real_ratio.values[c], + remapped.values[c]) != o->volume.values[c]) + + o->real_ratio.values[c] = pa_sw_volume_divide( + o->volume.values[c], + remapped.values[c]); + + o->soft_volume.values[c] = pa_sw_volume_multiply( + o->real_ratio.values[c], + o->volume_factor.values[c]); + } + + /* We don't copy the soft_volume to the thread_info data + * here. That must be done by the caller */ + } +} + +static pa_cvolume *cvolume_remap_minimal_impact( + pa_cvolume *v, + const pa_cvolume *template, + const pa_channel_map *from, + const pa_channel_map *to) { + + pa_cvolume t; + + pa_assert(v); + pa_assert(template); + pa_assert(from); + pa_assert(to); + pa_assert(pa_cvolume_compatible_with_channel_map(v, from)); + pa_assert(pa_cvolume_compatible_with_channel_map(template, to)); + + /* Much like pa_cvolume_remap(), but tries to minimize impact when + * mapping from source output to source volumes: + * + * If template is a possible remapping from v it is used instead + * of remapping anew. + * + * If the channel maps don't match we set an all-channel volume on + * the source to ensure that changing a volume on one stream has no + * effect that cannot be compensated for in another stream that + * does not have the same channel map as the source. */ + + if (pa_channel_map_equal(from, to)) + return v; + + t = *template; + if (pa_cvolume_equal(pa_cvolume_remap(&t, to, from), v)) { + *v = *template; + return v; + } + + pa_cvolume_set(v, to->channels, pa_cvolume_max(v)); + return v; +} + +/* Called from main thread. Only called for the root source in volume sharing + * cases, except for internal recursive calls. */ +static void get_maximum_output_volume(pa_source *s, pa_cvolume *max_volume, const pa_channel_map *channel_map) { + pa_source_output *o; + uint32_t idx; + + pa_source_assert_ref(s); + pa_assert(max_volume); + pa_assert(channel_map); + pa_assert(pa_source_flat_volume_enabled(s)); + + PA_IDXSET_FOREACH(o, s->outputs, idx) { + pa_cvolume remapped; + + if (o->destination_source && (o->destination_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)) { + if (PA_SOURCE_IS_LINKED(o->destination_source->state)) + get_maximum_output_volume(o->destination_source, max_volume, channel_map); + + /* Ignore this output. The origin source uses volume sharing, so this + * output's volume will be set to be equal to the root source's real + * volume. Obviously this output's current volume must not then + * affect what the root source's real volume will be. */ + continue; + } + + remapped = o->volume; + cvolume_remap_minimal_impact(&remapped, max_volume, &o->channel_map, channel_map); + pa_cvolume_merge(max_volume, max_volume, &remapped); + } +} + +/* Called from main thread. Only called for the root source in volume sharing + * cases, except for internal recursive calls. */ +static bool has_outputs(pa_source *s) { + pa_source_output *o; + uint32_t idx; + + pa_source_assert_ref(s); + + PA_IDXSET_FOREACH(o, s->outputs, idx) { + if (!o->destination_source || !(o->destination_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER) || has_outputs(o->destination_source)) + return true; + } + + return false; +} + +/* Called from main thread. Only called for the root source in volume sharing + * cases, except for internal recursive calls. */ +static void update_real_volume(pa_source *s, const pa_cvolume *new_volume, pa_channel_map *channel_map) { + pa_source_output *o; + uint32_t idx; + + pa_source_assert_ref(s); + pa_assert(new_volume); + pa_assert(channel_map); + + s->real_volume = *new_volume; + pa_cvolume_remap(&s->real_volume, channel_map, &s->channel_map); + + PA_IDXSET_FOREACH(o, s->outputs, idx) { + if (o->destination_source && (o->destination_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)) { + if (pa_source_flat_volume_enabled(s)) { + pa_cvolume new_output_volume; + + /* Follow the root source's real volume. */ + new_output_volume = *new_volume; + pa_cvolume_remap(&new_output_volume, channel_map, &o->channel_map); + pa_source_output_set_volume_direct(o, &new_output_volume); + compute_reference_ratio(o); + } + + if (PA_SOURCE_IS_LINKED(o->destination_source->state)) + update_real_volume(o->destination_source, new_volume, channel_map); + } + } +} + +/* Called from main thread. Only called for the root source in shared volume + * cases. */ +static void compute_real_volume(pa_source *s) { + pa_source_assert_ref(s); + pa_assert_ctl_context(); + pa_assert(PA_SOURCE_IS_LINKED(s->state)); + pa_assert(pa_source_flat_volume_enabled(s)); + pa_assert(!(s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)); + + /* This determines the maximum volume of all streams and sets + * s->real_volume accordingly. */ + + if (!has_outputs(s)) { + /* In the special case that we have no source outputs we leave the + * volume unmodified. */ + update_real_volume(s, &s->reference_volume, &s->channel_map); + return; + } + + pa_cvolume_mute(&s->real_volume, s->channel_map.channels); + + /* First let's determine the new maximum volume of all outputs + * connected to this source */ + get_maximum_output_volume(s, &s->real_volume, &s->channel_map); + update_real_volume(s, &s->real_volume, &s->channel_map); + + /* Then, let's update the real ratios/soft volumes of all outputs + * connected to this source */ + compute_real_ratios(s); +} + +/* Called from main thread. Only called for the root source in shared volume + * cases, except for internal recursive calls. */ +static void propagate_reference_volume(pa_source *s) { + pa_source_output *o; + uint32_t idx; + + pa_source_assert_ref(s); + pa_assert_ctl_context(); + pa_assert(PA_SOURCE_IS_LINKED(s->state)); + pa_assert(pa_source_flat_volume_enabled(s)); + + /* This is called whenever the source volume changes that is not + * caused by a source output volume change. We need to fix up the + * source output volumes accordingly */ + + PA_IDXSET_FOREACH(o, s->outputs, idx) { + pa_cvolume new_volume; + + if (o->destination_source && (o->destination_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)) { + if (PA_SOURCE_IS_LINKED(o->destination_source->state)) + propagate_reference_volume(o->destination_source); + + /* Since the origin source uses volume sharing, this output's volume + * needs to be updated to match the root source's real volume, but + * that will be done later in update_real_volume(). */ + continue; + } + + /* This basically calculates: + * + * o->volume := o->reference_volume * o->reference_ratio */ + + new_volume = s->reference_volume; + pa_cvolume_remap(&new_volume, &s->channel_map, &o->channel_map); + pa_sw_cvolume_multiply(&new_volume, &new_volume, &o->reference_ratio); + pa_source_output_set_volume_direct(o, &new_volume); + } +} + +/* Called from main thread. Only called for the root source in volume sharing + * cases, except for internal recursive calls. The return value indicates + * whether any reference volume actually changed. */ +static bool update_reference_volume(pa_source *s, const pa_cvolume *v, const pa_channel_map *channel_map, bool save) { + pa_cvolume volume; + bool reference_volume_changed; + pa_source_output *o; + uint32_t idx; + + pa_source_assert_ref(s); + pa_assert(PA_SOURCE_IS_LINKED(s->state)); + pa_assert(v); + pa_assert(channel_map); + pa_assert(pa_cvolume_valid(v)); + + volume = *v; + pa_cvolume_remap(&volume, channel_map, &s->channel_map); + + reference_volume_changed = !pa_cvolume_equal(&volume, &s->reference_volume); + pa_source_set_reference_volume_direct(s, &volume); + + s->save_volume = (!reference_volume_changed && s->save_volume) || save; + + if (!reference_volume_changed && !(s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)) + /* If the root source's volume doesn't change, then there can't be any + * changes in the other source in the source tree either. + * + * It's probably theoretically possible that even if the root source's + * volume changes slightly, some filter source doesn't change its volume + * due to rounding errors. If that happens, we still want to propagate + * the changed root source volume to the sources connected to the + * intermediate source that didn't change its volume. This theoretical + * possibility is the reason why we have that !(s->flags & + * PA_SOURCE_SHARE_VOLUME_WITH_MASTER) condition. Probably nobody would + * notice even if we returned here false always if + * reference_volume_changed is false. */ + return false; + + PA_IDXSET_FOREACH(o, s->outputs, idx) { + if (o->destination_source && (o->destination_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER) + && PA_SOURCE_IS_LINKED(o->destination_source->state)) + update_reference_volume(o->destination_source, v, channel_map, false); + } + + return true; +} + +/* Called from main thread */ +void pa_source_set_volume( + pa_source *s, + const pa_cvolume *volume, + bool send_msg, + bool save) { + + pa_cvolume new_reference_volume, root_real_volume; + pa_source *root_source; + + pa_source_assert_ref(s); + pa_assert_ctl_context(); + pa_assert(PA_SOURCE_IS_LINKED(s->state)); + pa_assert(!volume || pa_cvolume_valid(volume)); + pa_assert(volume || pa_source_flat_volume_enabled(s)); + pa_assert(!volume || volume->channels == 1 || pa_cvolume_compatible(volume, &s->sample_spec)); + + /* make sure we don't change the volume in PASSTHROUGH mode ... + * ... *except* if we're being invoked to reset the volume to ensure 0 dB gain */ + if (pa_source_is_passthrough(s) && (!volume || !pa_cvolume_is_norm(volume))) { + pa_log_warn("Cannot change volume, source is monitor of a PASSTHROUGH sink"); + return; + } + + /* In case of volume sharing, the volume is set for the root source first, + * from which it's then propagated to the sharing sources. */ + root_source = pa_source_get_master(s); + + if (PA_UNLIKELY(!root_source)) + return; + + /* As a special exception we accept mono volumes on all sources -- + * even on those with more complex channel maps */ + + if (volume) { + if (pa_cvolume_compatible(volume, &s->sample_spec)) + new_reference_volume = *volume; + else { + new_reference_volume = s->reference_volume; + pa_cvolume_scale(&new_reference_volume, pa_cvolume_max(volume)); + } + + pa_cvolume_remap(&new_reference_volume, &s->channel_map, &root_source->channel_map); + + if (update_reference_volume(root_source, &new_reference_volume, &root_source->channel_map, save)) { + if (pa_source_flat_volume_enabled(root_source)) { + /* OK, propagate this volume change back to the outputs */ + propagate_reference_volume(root_source); + + /* And now recalculate the real volume */ + compute_real_volume(root_source); + } else + update_real_volume(root_source, &root_source->reference_volume, &root_source->channel_map); + } + + } else { + /* If volume is NULL we synchronize the source's real and + * reference volumes with the stream volumes. */ + + pa_assert(pa_source_flat_volume_enabled(root_source)); + + /* Ok, let's determine the new real volume */ + compute_real_volume(root_source); + + /* To propagate the reference volume from the filter to the root source, + * we first take the real volume from the root source and remap it to + * match the filter. Then, we merge in the reference volume from the + * filter on top of this, and remap it back to the root source channel + * count and map */ + root_real_volume = root_source->real_volume; + /* First we remap root's real volume to filter channel count and map if needed */ + if (s != root_source && !pa_channel_map_equal(&s->channel_map, &root_source->channel_map)) + pa_cvolume_remap(&root_real_volume, &root_source->channel_map, &s->channel_map); + /* Then let's 'push' the reference volume if necessary */ + pa_cvolume_merge(&new_reference_volume, &s->reference_volume, &root_real_volume); + /* If the source and its root don't have the same number of channels, we need to remap back */ + if (s != root_source && !pa_channel_map_equal(&s->channel_map, &root_source->channel_map)) + pa_cvolume_remap(&new_reference_volume, &s->channel_map, &root_source->channel_map); + + update_reference_volume(root_source, &new_reference_volume, &root_source->channel_map, save); + + /* Now that the reference volume is updated, we can update the streams' + * reference ratios. */ + compute_reference_ratios(root_source); + } + + if (root_source->set_volume) { + /* If we have a function set_volume(), then we do not apply a + * soft volume by default. However, set_volume() is free to + * apply one to root_source->soft_volume */ + + pa_cvolume_reset(&root_source->soft_volume, root_source->sample_spec.channels); + if (!(root_source->flags & PA_SOURCE_DEFERRED_VOLUME)) + root_source->set_volume(root_source); + + } else + /* If we have no function set_volume(), then the soft volume + * becomes the real volume */ + root_source->soft_volume = root_source->real_volume; + + /* This tells the source that soft volume and/or real volume changed */ + if (send_msg) + pa_assert_se(pa_asyncmsgq_send(root_source->asyncmsgq, PA_MSGOBJECT(root_source), PA_SOURCE_MESSAGE_SET_SHARED_VOLUME, NULL, 0, NULL) == 0); +} + +/* Called from the io thread if sync volume is used, otherwise from the main thread. + * Only to be called by source implementor */ +void pa_source_set_soft_volume(pa_source *s, const pa_cvolume *volume) { + + pa_source_assert_ref(s); + pa_assert(!(s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)); + + if (s->flags & PA_SOURCE_DEFERRED_VOLUME) + pa_source_assert_io_context(s); + else + pa_assert_ctl_context(); + + if (!volume) + pa_cvolume_reset(&s->soft_volume, s->sample_spec.channels); + else + s->soft_volume = *volume; + + if (PA_SOURCE_IS_LINKED(s->state) && !(s->flags & PA_SOURCE_DEFERRED_VOLUME)) + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_VOLUME, NULL, 0, NULL) == 0); + else + s->thread_info.soft_volume = s->soft_volume; +} + +/* Called from the main thread. Only called for the root source in volume sharing + * cases, except for internal recursive calls. */ +static void propagate_real_volume(pa_source *s, const pa_cvolume *old_real_volume) { + pa_source_output *o; + uint32_t idx; + + pa_source_assert_ref(s); + pa_assert(old_real_volume); + pa_assert_ctl_context(); + pa_assert(PA_SOURCE_IS_LINKED(s->state)); + + /* This is called when the hardware's real volume changes due to + * some external event. We copy the real volume into our + * reference volume and then rebuild the stream volumes based on + * i->real_ratio which should stay fixed. */ + + if (!(s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)) { + if (pa_cvolume_equal(old_real_volume, &s->real_volume)) + return; + + /* 1. Make the real volume the reference volume */ + update_reference_volume(s, &s->real_volume, &s->channel_map, true); + } + + if (pa_source_flat_volume_enabled(s)) { + PA_IDXSET_FOREACH(o, s->outputs, idx) { + pa_cvolume new_volume; + + /* 2. Since the source's reference and real volumes are equal + * now our ratios should be too. */ + pa_source_output_set_reference_ratio(o, &o->real_ratio); + + /* 3. Recalculate the new stream reference volume based on the + * reference ratio and the sink's reference volume. + * + * This basically calculates: + * + * o->volume = s->reference_volume * o->reference_ratio + * + * This is identical to propagate_reference_volume() */ + new_volume = s->reference_volume; + pa_cvolume_remap(&new_volume, &s->channel_map, &o->channel_map); + pa_sw_cvolume_multiply(&new_volume, &new_volume, &o->reference_ratio); + pa_source_output_set_volume_direct(o, &new_volume); + + if (o->destination_source && (o->destination_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER) + && PA_SOURCE_IS_LINKED(o->destination_source->state)) + propagate_real_volume(o->destination_source, old_real_volume); + } + } + + /* Something got changed in the hardware. It probably makes sense + * to save changed hw settings given that hw volume changes not + * triggered by PA are almost certainly done by the user. */ + if (!(s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)) + s->save_volume = true; +} + +/* Called from io thread */ +void pa_source_update_volume_and_mute(pa_source *s) { + pa_assert(s); + pa_source_assert_io_context(s); + + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_UPDATE_VOLUME_AND_MUTE, NULL, 0, NULL, NULL); +} + +/* Called from main thread */ +const pa_cvolume *pa_source_get_volume(pa_source *s, bool force_refresh) { + pa_source_assert_ref(s); + pa_assert_ctl_context(); + pa_assert(PA_SOURCE_IS_LINKED(s->state)); + + if (s->refresh_volume || force_refresh) { + struct pa_cvolume old_real_volume; + + pa_assert(!(s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)); + + old_real_volume = s->real_volume; + + if (!(s->flags & PA_SOURCE_DEFERRED_VOLUME) && s->get_volume) + s->get_volume(s); + + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_GET_VOLUME, NULL, 0, NULL) == 0); + + update_real_volume(s, &s->real_volume, &s->channel_map); + propagate_real_volume(s, &old_real_volume); + } + + return &s->reference_volume; +} + +/* Called from main thread. In volume sharing cases, only the root source may + * call this. */ +void pa_source_volume_changed(pa_source *s, const pa_cvolume *new_real_volume) { + pa_cvolume old_real_volume; + + pa_source_assert_ref(s); + pa_assert_ctl_context(); + pa_assert(PA_SOURCE_IS_LINKED(s->state)); + pa_assert(!(s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)); + + /* The source implementor may call this if the volume changed to make sure everyone is notified */ + + old_real_volume = s->real_volume; + update_real_volume(s, new_real_volume, &s->channel_map); + propagate_real_volume(s, &old_real_volume); +} + +/* Called from main thread */ +void pa_source_set_mute(pa_source *s, bool mute, bool save) { + bool old_muted; + + pa_source_assert_ref(s); + pa_assert_ctl_context(); + + old_muted = s->muted; + + if (mute == old_muted) { + s->save_muted |= save; + return; + } + + s->muted = mute; + s->save_muted = save; + + if (!(s->flags & PA_SOURCE_DEFERRED_VOLUME) && s->set_mute) { + s->set_mute_in_progress = true; + s->set_mute(s); + s->set_mute_in_progress = false; + } + + if (!PA_SOURCE_IS_LINKED(s->state)) + return; + + pa_log_debug("The mute of source %s changed from %s to %s.", s->name, pa_yes_no(old_muted), pa_yes_no(mute)); + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_MUTE, NULL, 0, NULL) == 0); + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); + pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_MUTE_CHANGED], s); +} + +/* Called from main thread */ +bool pa_source_get_mute(pa_source *s, bool force_refresh) { + + pa_source_assert_ref(s); + pa_assert_ctl_context(); + pa_assert(PA_SOURCE_IS_LINKED(s->state)); + + if ((s->refresh_muted || force_refresh) && s->get_mute) { + bool mute; + + if (s->flags & PA_SOURCE_DEFERRED_VOLUME) { + if (pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_GET_MUTE, &mute, 0, NULL) >= 0) + pa_source_mute_changed(s, mute); + } else { + if (s->get_mute(s, &mute) >= 0) + pa_source_mute_changed(s, mute); + } + } + + return s->muted; +} + +/* Called from main thread */ +void pa_source_mute_changed(pa_source *s, bool new_muted) { + pa_source_assert_ref(s); + pa_assert_ctl_context(); + pa_assert(PA_SOURCE_IS_LINKED(s->state)); + + if (s->set_mute_in_progress) + return; + + /* pa_source_set_mute() does this same check, so this may appear redundant, + * but we must have this here also, because the save parameter of + * pa_source_set_mute() would otherwise have unintended side effects + * (saving the mute state when it shouldn't be saved). */ + if (new_muted == s->muted) + return; + + pa_source_set_mute(s, new_muted, true); +} + +/* Called from main thread */ +bool pa_source_update_proplist(pa_source *s, pa_update_mode_t mode, pa_proplist *p) { + pa_source_assert_ref(s); + pa_assert_ctl_context(); + + if (p) + pa_proplist_update(s->proplist, mode, p); + + if (PA_SOURCE_IS_LINKED(s->state)) { + pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_PROPLIST_CHANGED], s); + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); + } + + return true; +} + +/* Called from main thread */ +/* FIXME -- this should be dropped and be merged into pa_source_update_proplist() */ +void pa_source_set_description(pa_source *s, const char *description) { + const char *old; + pa_source_assert_ref(s); + pa_assert_ctl_context(); + + if (!description && !pa_proplist_contains(s->proplist, PA_PROP_DEVICE_DESCRIPTION)) + return; + + old = pa_proplist_gets(s->proplist, PA_PROP_DEVICE_DESCRIPTION); + + if (old && description && pa_streq(old, description)) + return; + + if (description) + pa_proplist_sets(s->proplist, PA_PROP_DEVICE_DESCRIPTION, description); + else + pa_proplist_unset(s->proplist, PA_PROP_DEVICE_DESCRIPTION); + + if (PA_SOURCE_IS_LINKED(s->state)) { + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); + pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_PROPLIST_CHANGED], s); + } +} + +/* Called from main thread */ +unsigned pa_source_linked_by(pa_source *s) { + pa_source_assert_ref(s); + pa_assert_ctl_context(); + pa_assert(PA_SOURCE_IS_LINKED(s->state)); + + return pa_idxset_size(s->outputs); +} + +/* Called from main thread */ +unsigned pa_source_used_by(pa_source *s) { + unsigned ret; + + pa_source_assert_ref(s); + pa_assert_ctl_context(); + pa_assert(PA_SOURCE_IS_LINKED(s->state)); + + ret = pa_idxset_size(s->outputs); + pa_assert(ret >= s->n_corked); + + return ret - s->n_corked; +} + +/* Called from main thread */ +unsigned pa_source_check_suspend(pa_source *s, pa_source_output *ignore) { + unsigned ret; + pa_source_output *o; + uint32_t idx; + + pa_source_assert_ref(s); + pa_assert_ctl_context(); + + if (!PA_SOURCE_IS_LINKED(s->state)) + return 0; + + ret = 0; + + PA_IDXSET_FOREACH(o, s->outputs, idx) { + if (o == ignore) + continue; + + /* We do not assert here. It is perfectly valid for a source output to + * be in the INIT state (i.e. created, marked done but not yet put) + * and we should not care if it's unlinked as it won't contribute + * towards our busy status. + */ + if (!PA_SOURCE_OUTPUT_IS_LINKED(o->state)) + continue; + + if (o->state == PA_SOURCE_OUTPUT_CORKED) + continue; + + if (o->flags & PA_SOURCE_OUTPUT_DONT_INHIBIT_AUTO_SUSPEND) + continue; + + ret ++; + } + + return ret; +} + +const char *pa_source_state_to_string(pa_source_state_t state) { + switch (state) { + case PA_SOURCE_INIT: return "INIT"; + case PA_SOURCE_IDLE: return "IDLE"; + case PA_SOURCE_RUNNING: return "RUNNING"; + case PA_SOURCE_SUSPENDED: return "SUSPENDED"; + case PA_SOURCE_UNLINKED: return "UNLINKED"; + case PA_SOURCE_INVALID_STATE: return "INVALID_STATE"; + } + + pa_assert_not_reached(); +} + +/* Called from the IO thread */ +static void sync_output_volumes_within_thread(pa_source *s) { + pa_source_output *o; + void *state = NULL; + + pa_source_assert_ref(s); + pa_source_assert_io_context(s); + + PA_HASHMAP_FOREACH(o, s->thread_info.outputs, state) { + if (pa_cvolume_equal(&o->thread_info.soft_volume, &o->soft_volume)) + continue; + + o->thread_info.soft_volume = o->soft_volume; + //pa_source_output_request_rewind(o, 0, true, false, false); + } +} + +/* Called from the IO thread. Only called for the root source in volume sharing + * cases, except for internal recursive calls. */ +static void set_shared_volume_within_thread(pa_source *s) { + pa_source_output *o; + void *state = NULL; + + pa_source_assert_ref(s); + + PA_MSGOBJECT(s)->process_msg(PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_VOLUME_SYNCED, NULL, 0, NULL); + + PA_HASHMAP_FOREACH(o, s->thread_info.outputs, state) { + if (o->destination_source && (o->destination_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)) + set_shared_volume_within_thread(o->destination_source); + } +} + +/* Called from IO thread, except when it is not */ +int pa_source_process_msg(pa_msgobject *object, int code, void *userdata, int64_t offset, pa_memchunk *chunk) { + pa_source *s = PA_SOURCE(object); + pa_source_assert_ref(s); + + switch ((pa_source_message_t) code) { + + case PA_SOURCE_MESSAGE_ADD_OUTPUT: { + pa_source_output *o = PA_SOURCE_OUTPUT(userdata); + + pa_hashmap_put(s->thread_info.outputs, PA_UINT32_TO_PTR(o->index), pa_source_output_ref(o)); + + if (o->direct_on_input) { + o->thread_info.direct_on_input = o->direct_on_input; + pa_hashmap_put(o->thread_info.direct_on_input->thread_info.direct_outputs, PA_UINT32_TO_PTR(o->index), o); + } + + pa_source_output_attach(o); + + pa_source_output_set_state_within_thread(o, o->state); + + if (o->thread_info.requested_source_latency != (pa_usec_t) -1) + pa_source_output_set_requested_latency_within_thread(o, o->thread_info.requested_source_latency); + + pa_source_output_update_max_rewind(o, s->thread_info.max_rewind); + + /* We don't just invalidate the requested latency here, + * because if we are in a move we might need to fix up the + * requested latency. */ + pa_source_output_set_requested_latency_within_thread(o, o->thread_info.requested_source_latency); + + /* In flat volume mode we need to update the volume as + * well */ + return object->process_msg(object, PA_SOURCE_MESSAGE_SET_SHARED_VOLUME, NULL, 0, NULL); + } + + case PA_SOURCE_MESSAGE_REMOVE_OUTPUT: { + pa_source_output *o = PA_SOURCE_OUTPUT(userdata); + + pa_source_output_set_state_within_thread(o, o->state); + + pa_source_output_detach(o); + + if (o->thread_info.direct_on_input) { + pa_hashmap_remove(o->thread_info.direct_on_input->thread_info.direct_outputs, PA_UINT32_TO_PTR(o->index)); + o->thread_info.direct_on_input = NULL; + } + + pa_hashmap_remove_and_free(s->thread_info.outputs, PA_UINT32_TO_PTR(o->index)); + pa_source_invalidate_requested_latency(s, true); + + /* In flat volume mode we need to update the volume as + * well */ + return object->process_msg(object, PA_SOURCE_MESSAGE_SET_SHARED_VOLUME, NULL, 0, NULL); + } + + case PA_SOURCE_MESSAGE_SET_SHARED_VOLUME: { + pa_source *root_source = pa_source_get_master(s); + + if (PA_LIKELY(root_source)) + set_shared_volume_within_thread(root_source); + + return 0; + } + + case PA_SOURCE_MESSAGE_SET_VOLUME_SYNCED: + + if (s->flags & PA_SOURCE_DEFERRED_VOLUME) { + s->set_volume(s); + pa_source_volume_change_push(s); + } + /* Fall through ... */ + + case PA_SOURCE_MESSAGE_SET_VOLUME: + + if (!pa_cvolume_equal(&s->thread_info.soft_volume, &s->soft_volume)) { + s->thread_info.soft_volume = s->soft_volume; + } + + /* Fall through ... */ + + case PA_SOURCE_MESSAGE_SYNC_VOLUMES: + sync_output_volumes_within_thread(s); + return 0; + + case PA_SOURCE_MESSAGE_GET_VOLUME: + + if ((s->flags & PA_SOURCE_DEFERRED_VOLUME) && s->get_volume) { + s->get_volume(s); + pa_source_volume_change_flush(s); + pa_sw_cvolume_divide(&s->thread_info.current_hw_volume, &s->real_volume, &s->soft_volume); + } + + /* In case source implementor reset SW volume. */ + if (!pa_cvolume_equal(&s->thread_info.soft_volume, &s->soft_volume)) { + s->thread_info.soft_volume = s->soft_volume; + } + + return 0; + + case PA_SOURCE_MESSAGE_SET_MUTE: + + if (s->thread_info.soft_muted != s->muted) { + s->thread_info.soft_muted = s->muted; + } + + if (s->flags & PA_SOURCE_DEFERRED_VOLUME && s->set_mute) + s->set_mute(s); + + return 0; + + case PA_SOURCE_MESSAGE_GET_MUTE: + + if (s->flags & PA_SOURCE_DEFERRED_VOLUME && s->get_mute) + return s->get_mute(s, userdata); + + return 0; + + case PA_SOURCE_MESSAGE_SET_STATE: { + struct set_state_data *data = userdata; + bool suspend_change = + (s->thread_info.state == PA_SOURCE_SUSPENDED && PA_SOURCE_IS_OPENED(data->state)) || + (PA_SOURCE_IS_OPENED(s->thread_info.state) && data->state == PA_SOURCE_SUSPENDED); + + if (s->set_state_in_io_thread) { + int r; + + if ((r = s->set_state_in_io_thread(s, data->state, data->suspend_cause)) < 0) + return r; + } + + s->thread_info.state = data->state; + + if (suspend_change) { + pa_source_output *o; + void *state = NULL; + + while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL))) + if (o->suspend_within_thread) + o->suspend_within_thread(o, s->thread_info.state == PA_SOURCE_SUSPENDED); + } + + return 0; + } + + case PA_SOURCE_MESSAGE_GET_REQUESTED_LATENCY: { + + pa_usec_t *usec = userdata; + *usec = pa_source_get_requested_latency_within_thread(s); + + /* Yes, that's right, the IO thread will see -1 when no + * explicit requested latency is configured, the main + * thread will see max_latency */ + if (*usec == (pa_usec_t) -1) + *usec = s->thread_info.max_latency; + + return 0; + } + + case PA_SOURCE_MESSAGE_SET_LATENCY_RANGE: { + pa_usec_t *r = userdata; + + pa_source_set_latency_range_within_thread(s, r[0], r[1]); + + return 0; + } + + case PA_SOURCE_MESSAGE_GET_LATENCY_RANGE: { + pa_usec_t *r = userdata; + + r[0] = s->thread_info.min_latency; + r[1] = s->thread_info.max_latency; + + return 0; + } + + case PA_SOURCE_MESSAGE_GET_FIXED_LATENCY: + + *((pa_usec_t*) userdata) = s->thread_info.fixed_latency; + return 0; + + case PA_SOURCE_MESSAGE_SET_FIXED_LATENCY: + + pa_source_set_fixed_latency_within_thread(s, (pa_usec_t) offset); + return 0; + + case PA_SOURCE_MESSAGE_GET_MAX_REWIND: + + *((size_t*) userdata) = s->thread_info.max_rewind; + return 0; + + case PA_SOURCE_MESSAGE_SET_MAX_REWIND: + + pa_source_set_max_rewind_within_thread(s, (size_t) offset); + return 0; + + case PA_SOURCE_MESSAGE_GET_LATENCY: + + if (s->monitor_of) { + *((int64_t*) userdata) = -pa_sink_get_latency_within_thread(s->monitor_of, true); + return 0; + } + + /* Implementors need to overwrite this implementation! */ + return -1; + + case PA_SOURCE_MESSAGE_UPDATE_VOLUME_AND_MUTE: + /* This message is sent from IO-thread and handled in main thread. */ + pa_assert_ctl_context(); + + /* Make sure we're not messing with main thread when no longer linked */ + if (!PA_SOURCE_IS_LINKED(s->state)) + return 0; + + pa_source_get_volume(s, true); + pa_source_get_mute(s, true); + return 0; + + case PA_SOURCE_MESSAGE_SET_PORT_LATENCY_OFFSET: + s->thread_info.port_latency_offset = offset; + return 0; + + case PA_SOURCE_MESSAGE_MAX: + ; + } + + return -1; +} + +/* Called from main thread */ +int pa_source_suspend_all(pa_core *c, bool suspend, pa_suspend_cause_t cause) { + pa_source *source; + uint32_t idx; + int ret = 0; + + pa_core_assert_ref(c); + pa_assert_ctl_context(); + pa_assert(cause != 0); + + for (source = PA_SOURCE(pa_idxset_first(c->sources, &idx)); source; source = PA_SOURCE(pa_idxset_next(c->sources, &idx))) { + int r; + + if (source->monitor_of) + continue; + + if ((r = pa_source_suspend(source, suspend, cause)) < 0) + ret = r; + } + + return ret; +} + +/* Called from IO thread */ +void pa_source_detach_within_thread(pa_source *s) { + pa_source_output *o; + void *state = NULL; + + pa_source_assert_ref(s); + pa_source_assert_io_context(s); + pa_assert(PA_SOURCE_IS_LINKED(s->thread_info.state)); + + PA_HASHMAP_FOREACH(o, s->thread_info.outputs, state) + pa_source_output_detach(o); +} + +/* Called from IO thread */ +void pa_source_attach_within_thread(pa_source *s) { + pa_source_output *o; + void *state = NULL; + + pa_source_assert_ref(s); + pa_source_assert_io_context(s); + pa_assert(PA_SOURCE_IS_LINKED(s->thread_info.state)); + + PA_HASHMAP_FOREACH(o, s->thread_info.outputs, state) + pa_source_output_attach(o); +} + +/* Called from IO thread */ +pa_usec_t pa_source_get_requested_latency_within_thread(pa_source *s) { + pa_usec_t result = (pa_usec_t) -1; + pa_source_output *o; + void *state = NULL; + + pa_source_assert_ref(s); + pa_source_assert_io_context(s); + + if (!(s->flags & PA_SOURCE_DYNAMIC_LATENCY)) + return PA_CLAMP(s->thread_info.fixed_latency, s->thread_info.min_latency, s->thread_info.max_latency); + + if (s->thread_info.requested_latency_valid) + return s->thread_info.requested_latency; + + PA_HASHMAP_FOREACH(o, s->thread_info.outputs, state) + if (o->thread_info.requested_source_latency != (pa_usec_t) -1 && + (result == (pa_usec_t) -1 || result > o->thread_info.requested_source_latency)) + result = o->thread_info.requested_source_latency; + + if (result != (pa_usec_t) -1) + result = PA_CLAMP(result, s->thread_info.min_latency, s->thread_info.max_latency); + + if (PA_SOURCE_IS_LINKED(s->thread_info.state)) { + /* Only cache this if we are fully set up */ + s->thread_info.requested_latency = result; + s->thread_info.requested_latency_valid = true; + } + + return result; +} + +/* Called from main thread */ +pa_usec_t pa_source_get_requested_latency(pa_source *s) { + pa_usec_t usec = 0; + + pa_source_assert_ref(s); + pa_assert_ctl_context(); + pa_assert(PA_SOURCE_IS_LINKED(s->state)); + + if (s->state == PA_SOURCE_SUSPENDED) + return 0; + + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_GET_REQUESTED_LATENCY, &usec, 0, NULL) == 0); + + return usec; +} + +/* Called from IO thread */ +void pa_source_set_max_rewind_within_thread(pa_source *s, size_t max_rewind) { + pa_source_output *o; + void *state = NULL; + + pa_source_assert_ref(s); + pa_source_assert_io_context(s); + + if (max_rewind == s->thread_info.max_rewind) + return; + + s->thread_info.max_rewind = max_rewind; + + if (PA_SOURCE_IS_LINKED(s->thread_info.state)) + PA_HASHMAP_FOREACH(o, s->thread_info.outputs, state) + pa_source_output_update_max_rewind(o, s->thread_info.max_rewind); +} + +/* Called from main thread */ +void pa_source_set_max_rewind(pa_source *s, size_t max_rewind) { + pa_source_assert_ref(s); + pa_assert_ctl_context(); + + if (PA_SOURCE_IS_LINKED(s->state)) + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_MAX_REWIND, NULL, max_rewind, NULL) == 0); + else + pa_source_set_max_rewind_within_thread(s, max_rewind); +} + +/* Called from IO thread */ +void pa_source_invalidate_requested_latency(pa_source *s, bool dynamic) { + pa_source_output *o; + void *state = NULL; + + pa_source_assert_ref(s); + pa_source_assert_io_context(s); + + if ((s->flags & PA_SOURCE_DYNAMIC_LATENCY)) + s->thread_info.requested_latency_valid = false; + else if (dynamic) + return; + + if (PA_SOURCE_IS_LINKED(s->thread_info.state)) { + + if (s->update_requested_latency) + s->update_requested_latency(s); + + while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL))) + if (o->update_source_requested_latency) + o->update_source_requested_latency(o); + } + + if (s->monitor_of) + pa_sink_invalidate_requested_latency(s->monitor_of, dynamic); +} + +/* Called from main thread */ +void pa_source_set_latency_range(pa_source *s, pa_usec_t min_latency, pa_usec_t max_latency) { + pa_source_assert_ref(s); + pa_assert_ctl_context(); + + /* min_latency == 0: no limit + * min_latency anything else: specified limit + * + * Similar for max_latency */ + + if (min_latency < ABSOLUTE_MIN_LATENCY) + min_latency = ABSOLUTE_MIN_LATENCY; + + if (max_latency <= 0 || + max_latency > ABSOLUTE_MAX_LATENCY) + max_latency = ABSOLUTE_MAX_LATENCY; + + pa_assert(min_latency <= max_latency); + + /* Hmm, let's see if someone forgot to set PA_SOURCE_DYNAMIC_LATENCY here... */ + pa_assert((min_latency == ABSOLUTE_MIN_LATENCY && + max_latency == ABSOLUTE_MAX_LATENCY) || + (s->flags & PA_SOURCE_DYNAMIC_LATENCY)); + + if (PA_SOURCE_IS_LINKED(s->state)) { + pa_usec_t r[2]; + + r[0] = min_latency; + r[1] = max_latency; + + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_LATENCY_RANGE, r, 0, NULL) == 0); + } else + pa_source_set_latency_range_within_thread(s, min_latency, max_latency); +} + +/* Called from main thread */ +void pa_source_get_latency_range(pa_source *s, pa_usec_t *min_latency, pa_usec_t *max_latency) { + pa_source_assert_ref(s); + pa_assert_ctl_context(); + pa_assert(min_latency); + pa_assert(max_latency); + + if (PA_SOURCE_IS_LINKED(s->state)) { + pa_usec_t r[2] = { 0, 0 }; + + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_GET_LATENCY_RANGE, r, 0, NULL) == 0); + + *min_latency = r[0]; + *max_latency = r[1]; + } else { + *min_latency = s->thread_info.min_latency; + *max_latency = s->thread_info.max_latency; + } +} + +/* Called from IO thread, and from main thread before pa_source_put() is called */ +void pa_source_set_latency_range_within_thread(pa_source *s, pa_usec_t min_latency, pa_usec_t max_latency) { + pa_source_assert_ref(s); + pa_source_assert_io_context(s); + + pa_assert(min_latency >= ABSOLUTE_MIN_LATENCY); + pa_assert(max_latency <= ABSOLUTE_MAX_LATENCY); + pa_assert(min_latency <= max_latency); + + /* Hmm, let's see if someone forgot to set PA_SOURCE_DYNAMIC_LATENCY here... */ + pa_assert((min_latency == ABSOLUTE_MIN_LATENCY && + max_latency == ABSOLUTE_MAX_LATENCY) || + (s->flags & PA_SOURCE_DYNAMIC_LATENCY) || + s->monitor_of); + + if (s->thread_info.min_latency == min_latency && + s->thread_info.max_latency == max_latency) + return; + + s->thread_info.min_latency = min_latency; + s->thread_info.max_latency = max_latency; + + if (PA_SOURCE_IS_LINKED(s->thread_info.state)) { + pa_source_output *o; + void *state = NULL; + + PA_HASHMAP_FOREACH(o, s->thread_info.outputs, state) + if (o->update_source_latency_range) + o->update_source_latency_range(o); + } + + pa_source_invalidate_requested_latency(s, false); +} + +/* Called from main thread, before the source is put */ +void pa_source_set_fixed_latency(pa_source *s, pa_usec_t latency) { + pa_source_assert_ref(s); + pa_assert_ctl_context(); + + if (s->flags & PA_SOURCE_DYNAMIC_LATENCY) { + pa_assert(latency == 0); + return; + } + + if (latency < ABSOLUTE_MIN_LATENCY) + latency = ABSOLUTE_MIN_LATENCY; + + if (latency > ABSOLUTE_MAX_LATENCY) + latency = ABSOLUTE_MAX_LATENCY; + + if (PA_SOURCE_IS_LINKED(s->state)) + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_FIXED_LATENCY, NULL, (int64_t) latency, NULL) == 0); + else + s->thread_info.fixed_latency = latency; +} + +/* Called from main thread */ +pa_usec_t pa_source_get_fixed_latency(pa_source *s) { + pa_usec_t latency; + + pa_source_assert_ref(s); + pa_assert_ctl_context(); + + if (s->flags & PA_SOURCE_DYNAMIC_LATENCY) + return 0; + + if (PA_SOURCE_IS_LINKED(s->state)) + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_GET_FIXED_LATENCY, &latency, 0, NULL) == 0); + else + latency = s->thread_info.fixed_latency; + + return latency; +} + +/* Called from IO thread */ +void pa_source_set_fixed_latency_within_thread(pa_source *s, pa_usec_t latency) { + pa_source_assert_ref(s); + pa_source_assert_io_context(s); + + if (s->flags & PA_SOURCE_DYNAMIC_LATENCY) { + pa_assert(latency == 0); + s->thread_info.fixed_latency = 0; + + return; + } + + pa_assert(latency >= ABSOLUTE_MIN_LATENCY); + pa_assert(latency <= ABSOLUTE_MAX_LATENCY); + + if (s->thread_info.fixed_latency == latency) + return; + + s->thread_info.fixed_latency = latency; + + if (PA_SOURCE_IS_LINKED(s->thread_info.state)) { + pa_source_output *o; + void *state = NULL; + + PA_HASHMAP_FOREACH(o, s->thread_info.outputs, state) + if (o->update_source_fixed_latency) + o->update_source_fixed_latency(o); + } + + pa_source_invalidate_requested_latency(s, false); +} + +/* Called from main thread */ +void pa_source_set_port_latency_offset(pa_source *s, int64_t offset) { + pa_source_assert_ref(s); + + s->port_latency_offset = offset; + + if (PA_SOURCE_IS_LINKED(s->state)) + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_PORT_LATENCY_OFFSET, NULL, offset, NULL) == 0); + else + s->thread_info.port_latency_offset = offset; + + pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_PORT_LATENCY_OFFSET_CHANGED], s); +} + +/* Called from main thread */ +size_t pa_source_get_max_rewind(pa_source *s) { + size_t r; + pa_assert_ctl_context(); + pa_source_assert_ref(s); + + if (!PA_SOURCE_IS_LINKED(s->state)) + return s->thread_info.max_rewind; + + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_GET_MAX_REWIND, &r, 0, NULL) == 0); + + return r; +} + +/* Called from main context */ +int pa_source_set_port(pa_source *s, const char *name, bool save) { + pa_device_port *port; + + pa_source_assert_ref(s); + pa_assert_ctl_context(); + + if (!s->set_port) { + pa_log_debug("set_port() operation not implemented for source %u \"%s\"", s->index, s->name); + return -PA_ERR_NOTIMPLEMENTED; + } + + if (!name) + return -PA_ERR_NOENTITY; + + if (!(port = pa_hashmap_get(s->ports, name))) + return -PA_ERR_NOENTITY; + + if (s->active_port == port) { + s->save_port = s->save_port || save; + return 0; + } + + if (s->set_port(s, port) < 0) + return -PA_ERR_NOENTITY; + + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); + + pa_log_info("Changed port of source %u \"%s\" to %s", s->index, s->name, port->name); + + s->active_port = port; + s->save_port = save; + + /* The active port affects the default source selection. */ + pa_core_update_default_source(s->core); + + pa_source_set_port_latency_offset(s, s->active_port->latency_offset); + + pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_PORT_CHANGED], s); + + return 0; +} + +PA_STATIC_FLIST_DECLARE(pa_source_volume_change, 0, pa_xfree); + +/* Called from the IO thread. */ +static pa_source_volume_change *pa_source_volume_change_new(pa_source *s) { + pa_source_volume_change *c; + if (!(c = pa_flist_pop(PA_STATIC_FLIST_GET(pa_source_volume_change)))) + c = pa_xnew(pa_source_volume_change, 1); + + PA_LLIST_INIT(pa_source_volume_change, c); + c->at = 0; + pa_cvolume_reset(&c->hw_volume, s->sample_spec.channels); + return c; +} + +/* Called from the IO thread. */ +static void pa_source_volume_change_free(pa_source_volume_change *c) { + pa_assert(c); + if (pa_flist_push(PA_STATIC_FLIST_GET(pa_source_volume_change), c) < 0) + pa_xfree(c); +} + +/* Called from the IO thread. */ +void pa_source_volume_change_push(pa_source *s) { + pa_source_volume_change *c = NULL; + pa_source_volume_change *nc = NULL; + pa_source_volume_change *pc = NULL; + uint32_t safety_margin = s->thread_info.volume_change_safety_margin; + + const char *direction = NULL; + + pa_assert(s); + nc = pa_source_volume_change_new(s); + + /* NOTE: There is already more different volumes in pa_source that I can remember. + * Adding one more volume for HW would get us rid of this, but I am trying + * to survive with the ones we already have. */ + pa_sw_cvolume_divide(&nc->hw_volume, &s->real_volume, &s->soft_volume); + + if (!s->thread_info.volume_changes && pa_cvolume_equal(&nc->hw_volume, &s->thread_info.current_hw_volume)) { + pa_log_debug("Volume not changing"); + pa_source_volume_change_free(nc); + return; + } + + nc->at = pa_source_get_latency_within_thread(s, false); + nc->at += pa_rtclock_now() + s->thread_info.volume_change_extra_delay; + + if (s->thread_info.volume_changes_tail) { + for (c = s->thread_info.volume_changes_tail; c; c = c->prev) { + /* If volume is going up let's do it a bit late. If it is going + * down let's do it a bit early. */ + if (pa_cvolume_avg(&nc->hw_volume) > pa_cvolume_avg(&c->hw_volume)) { + if (nc->at + safety_margin > c->at) { + nc->at += safety_margin; + direction = "up"; + break; + } + } + else if (nc->at - safety_margin > c->at) { + nc->at -= safety_margin; + direction = "down"; + break; + } + } + } + + if (c == NULL) { + if (pa_cvolume_avg(&nc->hw_volume) > pa_cvolume_avg(&s->thread_info.current_hw_volume)) { + nc->at += safety_margin; + direction = "up"; + } else { + nc->at -= safety_margin; + direction = "down"; + } + PA_LLIST_PREPEND(pa_source_volume_change, s->thread_info.volume_changes, nc); + } + else { + PA_LLIST_INSERT_AFTER(pa_source_volume_change, s->thread_info.volume_changes, c, nc); + } + + pa_log_debug("Volume going %s to %d at %llu", direction, pa_cvolume_avg(&nc->hw_volume), (long long unsigned) nc->at); + + /* We can ignore volume events that came earlier but should happen later than this. */ + PA_LLIST_FOREACH_SAFE(c, pc, nc->next) { + pa_log_debug("Volume change to %d at %llu was dropped", pa_cvolume_avg(&c->hw_volume), (long long unsigned) c->at); + pa_source_volume_change_free(c); + } + nc->next = NULL; + s->thread_info.volume_changes_tail = nc; +} + +/* Called from the IO thread. */ +static void pa_source_volume_change_flush(pa_source *s) { + pa_source_volume_change *c = s->thread_info.volume_changes; + pa_assert(s); + s->thread_info.volume_changes = NULL; + s->thread_info.volume_changes_tail = NULL; + while (c) { + pa_source_volume_change *next = c->next; + pa_source_volume_change_free(c); + c = next; + } +} + +/* Called from the IO thread. */ +bool pa_source_volume_change_apply(pa_source *s, pa_usec_t *usec_to_next) { + pa_usec_t now; + bool ret = false; + + pa_assert(s); + + if (!s->thread_info.volume_changes || !PA_SOURCE_IS_LINKED(s->state)) { + if (usec_to_next) + *usec_to_next = 0; + return ret; + } + + pa_assert(s->write_volume); + + now = pa_rtclock_now(); + + while (s->thread_info.volume_changes && now >= s->thread_info.volume_changes->at) { + pa_source_volume_change *c = s->thread_info.volume_changes; + PA_LLIST_REMOVE(pa_source_volume_change, s->thread_info.volume_changes, c); + pa_log_debug("Volume change to %d at %llu was written %llu usec late", + pa_cvolume_avg(&c->hw_volume), (long long unsigned) c->at, (long long unsigned) (now - c->at)); + ret = true; + s->thread_info.current_hw_volume = c->hw_volume; + pa_source_volume_change_free(c); + } + + if (ret) + s->write_volume(s); + + if (s->thread_info.volume_changes) { + if (usec_to_next) + *usec_to_next = s->thread_info.volume_changes->at - now; + if (pa_log_ratelimit(PA_LOG_DEBUG)) + pa_log_debug("Next volume change in %lld usec", (long long) (s->thread_info.volume_changes->at - now)); + } + else { + if (usec_to_next) + *usec_to_next = 0; + s->thread_info.volume_changes_tail = NULL; + } + return ret; +} + +/* Called from the main thread */ +/* Gets the list of formats supported by the source. The members and idxset must + * be freed by the caller. */ +pa_idxset* pa_source_get_formats(pa_source *s) { + pa_idxset *ret; + + pa_assert(s); + + if (s->get_formats) { + /* Source supports format query, all is good */ + ret = s->get_formats(s); + } else { + /* Source doesn't support format query, so assume it does PCM */ + pa_format_info *f = pa_format_info_new(); + f->encoding = PA_ENCODING_PCM; + + ret = pa_idxset_new(NULL, NULL); + pa_idxset_put(ret, f, NULL); + } + + return ret; +} + +/* Called from the main thread */ +/* Checks if the source can accept this format */ +bool pa_source_check_format(pa_source *s, pa_format_info *f) { + pa_idxset *formats = NULL; + bool ret = false; + + pa_assert(s); + pa_assert(f); + + formats = pa_source_get_formats(s); + + if (formats) { + pa_format_info *finfo_device; + uint32_t i; + + PA_IDXSET_FOREACH(finfo_device, formats, i) { + if (pa_format_info_is_compatible(finfo_device, f)) { + ret = true; + break; + } + } + + pa_idxset_free(formats, (pa_free_cb_t) pa_format_info_free); + } + + return ret; +} + +/* Called from the main thread */ +/* Calculates the intersection between formats supported by the source and + * in_formats, and returns these, in the order of the source's formats. */ +pa_idxset* pa_source_check_formats(pa_source *s, pa_idxset *in_formats) { + pa_idxset *out_formats = pa_idxset_new(NULL, NULL), *source_formats = NULL; + pa_format_info *f_source, *f_in; + uint32_t i, j; + + pa_assert(s); + + if (!in_formats || pa_idxset_isempty(in_formats)) + goto done; + + source_formats = pa_source_get_formats(s); + + PA_IDXSET_FOREACH(f_source, source_formats, i) { + PA_IDXSET_FOREACH(f_in, in_formats, j) { + if (pa_format_info_is_compatible(f_source, f_in)) + pa_idxset_put(out_formats, pa_format_info_copy(f_in), NULL); + } + } + +done: + if (source_formats) + pa_idxset_free(source_formats, (pa_free_cb_t) pa_format_info_free); + + return out_formats; +} + +/* Called from the main thread */ +void pa_source_set_sample_format(pa_source *s, pa_sample_format_t format) { + pa_sample_format_t old_format; + + pa_assert(s); + pa_assert(pa_sample_format_valid(format)); + + old_format = s->sample_spec.format; + if (old_format == format) + return; + + pa_log_info("%s: format: %s -> %s", + s->name, pa_sample_format_to_string(old_format), pa_sample_format_to_string(format)); + + s->sample_spec.format = format; + + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE | PA_SUBSCRIPTION_EVENT_CHANGE, s->index); +} + +/* Called from the main thread */ +void pa_source_set_sample_rate(pa_source *s, uint32_t rate) { + uint32_t old_rate; + + pa_assert(s); + pa_assert(pa_sample_rate_valid(rate)); + + old_rate = s->sample_spec.rate; + if (old_rate == rate) + return; + + pa_log_info("%s: rate: %u -> %u", s->name, old_rate, rate); + + s->sample_spec.rate = rate; + + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE | PA_SUBSCRIPTION_EVENT_CHANGE, s->index); +} + +/* Called from the main thread. */ +void pa_source_set_reference_volume_direct(pa_source *s, const pa_cvolume *volume) { + pa_cvolume old_volume; + char old_volume_str[PA_CVOLUME_SNPRINT_VERBOSE_MAX]; + char new_volume_str[PA_CVOLUME_SNPRINT_VERBOSE_MAX]; + + pa_assert(s); + pa_assert(volume); + + old_volume = s->reference_volume; + + if (pa_cvolume_equal(volume, &old_volume)) + return; + + s->reference_volume = *volume; + pa_log_debug("The reference volume of source %s changed from %s to %s.", s->name, + pa_cvolume_snprint_verbose(old_volume_str, sizeof(old_volume_str), &old_volume, &s->channel_map, + s->flags & PA_SOURCE_DECIBEL_VOLUME), + pa_cvolume_snprint_verbose(new_volume_str, sizeof(new_volume_str), volume, &s->channel_map, + s->flags & PA_SOURCE_DECIBEL_VOLUME)); + + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); + pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_VOLUME_CHANGED], s); +} + +void pa_source_move_streams_to_default_source(pa_core *core, pa_source *old_source, bool default_source_changed) { + pa_source_output *o; + uint32_t idx; + + pa_assert(core); + pa_assert(old_source); + + if (core->state == PA_CORE_SHUTDOWN) + return; + + if (core->default_source == NULL || core->default_source->unlink_requested) + return; + + if (old_source == core->default_source) + return; + + PA_IDXSET_FOREACH(o, old_source->outputs, idx) { + if (!PA_SOURCE_OUTPUT_IS_LINKED(o->state)) + continue; + + if (!o->source) + continue; + + /* Don't move source-outputs which connect sources to filter sources */ + if (o->destination_source) + continue; + + /* If default_source_changed is false, the old source became unavailable, so all streams must be moved. */ + if (pa_safe_streq(old_source->name, o->preferred_source) && default_source_changed) + continue; + + if (!pa_source_output_may_move_to(o, core->default_source)) + continue; + + if (default_source_changed) + pa_log_info("The source output %u \"%s\" is moving to %s due to change of the default source.", + o->index, pa_strnull(pa_proplist_gets(o->proplist, PA_PROP_APPLICATION_NAME)), core->default_source->name); + else + pa_log_info("The source output %u \"%s\" is moving to %s, because the old source became unavailable.", + o->index, pa_strnull(pa_proplist_gets(o->proplist, PA_PROP_APPLICATION_NAME)), core->default_source->name); + + pa_source_output_move_to(o, core->default_source, false); + } +} diff --git a/src/pulsecore/source.h b/src/pulsecore/source.h new file mode 100644 index 0000000..aa45e6d --- /dev/null +++ b/src/pulsecore/source.h @@ -0,0 +1,493 @@ +#ifndef foopulsesourcehfoo +#define foopulsesourcehfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + + +#include <inttypes.h> + +#include <pulsecore/typedefs.h> +#include <pulse/def.h> +#include <pulse/format.h> +#include <pulse/sample.h> +#include <pulse/channelmap.h> +#include <pulse/volume.h> + +#include <pulsecore/core.h> +#include <pulsecore/idxset.h> +#include <pulsecore/memchunk.h> +#include <pulsecore/sink.h> +#include <pulsecore/module.h> +#include <pulsecore/asyncmsgq.h> +#include <pulsecore/msgobject.h> +#include <pulsecore/rtpoll.h> +#include <pulsecore/card.h> +#include <pulsecore/device-port.h> +#include <pulsecore/queue.h> +#include <pulsecore/thread-mq.h> +#include <pulsecore/source-output.h> + +#define PA_MAX_OUTPUTS_PER_SOURCE 256 + +/* Returns true if source is linked: registered and accessible from client side. */ +static inline bool PA_SOURCE_IS_LINKED(pa_source_state_t x) { + return x == PA_SOURCE_RUNNING || x == PA_SOURCE_IDLE || x == PA_SOURCE_SUSPENDED; +} + +/* A generic definition for void callback functions */ +typedef void(*pa_source_cb_t)(pa_source *s); + +typedef int (*pa_source_get_mute_cb_t)(pa_source *s, bool *mute); + +struct pa_source { + pa_msgobject parent; + + uint32_t index; + pa_core *core; + + pa_source_state_t state; + + /* Set in the beginning of pa_source_unlink() before setting the source + * state to UNLINKED. The purpose is to prevent moving streams to a source + * that is about to be removed. */ + bool unlink_requested; + + pa_source_flags_t flags; + pa_suspend_cause_t suspend_cause; + + char *name; + char *driver; /* may be NULL */ + pa_proplist *proplist; + + pa_module *module; /* may be NULL */ + pa_card *card; /* may be NULL */ + + pa_sample_spec sample_spec; + pa_channel_map channel_map; + uint32_t default_sample_rate; + uint32_t alternate_sample_rate; + bool avoid_resampling:1; + + pa_idxset *outputs; + unsigned n_corked; + pa_sink *monitor_of; /* may be NULL */ + pa_source_output *output_from_master; /* non-NULL only for filter sources */ + + pa_volume_t base_volume; /* shall be constant */ + unsigned n_volume_steps; /* shall be constant */ + + /* Also see http://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/Developer/Volumes/ */ + pa_cvolume reference_volume; /* The volume exported and taken as reference base for relative source output volumes */ + pa_cvolume real_volume; /* The volume that the hardware is configured to */ + pa_cvolume soft_volume; /* The internal software volume we apply to all PCM data while it passes through */ + + bool muted:1; + + bool refresh_volume:1; + bool refresh_muted:1; + bool save_port:1; + bool save_volume:1; + bool save_muted:1; + + /* Saved volume state while we're in passthrough mode */ + pa_cvolume saved_volume; + bool saved_save_volume:1; + + pa_asyncmsgq *asyncmsgq; + + pa_memchunk silence; + + pa_hashmap *ports; + pa_device_port *active_port; + + /* The latency offset is inherited from the currently active port */ + int64_t port_latency_offset; + + unsigned priority; + + bool set_mute_in_progress; + + /* Callbacks for doing things when the source state and/or suspend cause is + * changed. It's fine to set either or both of the callbacks to NULL if the + * implementation doesn't have anything to do on state or suspend cause + * changes. + * + * set_state_in_main_thread() is called first. The callback is allowed to + * report failure if and only if the source changes its state from + * SUSPENDED to IDLE or RUNNING. (FIXME: It would make sense to allow + * failure also when changing state from INIT to IDLE or RUNNING, but + * currently that will crash pa_source_put().) If + * set_state_in_main_thread() fails, set_state_in_io_thread() won't be + * called. + * + * If set_state_in_main_thread() is successful (or not set), then + * set_state_in_io_thread() is called. Again, failure is allowed if and + * only if the source changes state from SUSPENDED to IDLE or RUNNING. If + * set_state_in_io_thread() fails, then set_state_in_main_thread() is + * called again, this time with the state parameter set to SUSPENDED and + * the suspend_cause parameter set to 0. + * + * pa_source.state, pa_source.thread_info.state and pa_source.suspend_cause + * are updated only after all the callback calls. In case of failure, the + * state is set to SUSPENDED and the suspend cause is set to 0. */ + int (*set_state_in_main_thread)(pa_source *s, pa_source_state_t state, pa_suspend_cause_t suspend_cause); /* may be NULL */ + int (*set_state_in_io_thread)(pa_source *s, pa_source_state_t state, pa_suspend_cause_t suspend_cause); /* may be NULL */ + + /* Called when the volume is queried. Called from main loop + * context. If this is NULL a PA_SOURCE_MESSAGE_GET_VOLUME message + * will be sent to the IO thread instead. If refresh_volume is + * false neither this function is called nor a message is sent. + * + * You must use the function pa_source_set_get_volume_callback() to + * set this callback. */ + pa_source_cb_t get_volume; /* may be NULL */ + + /* Called when the volume shall be changed. Called from main loop + * context. If this is NULL a PA_SOURCE_MESSAGE_SET_VOLUME message + * will be sent to the IO thread instead. + * + * You must use the function pa_source_set_set_volume_callback() to + * set this callback. */ + pa_source_cb_t set_volume; /* may be NULL */ + + /* Source drivers that set PA_SOURCE_DEFERRED_VOLUME must provide this + * callback. This callback is not used with source that do not set + * PA_SOURCE_DEFERRED_VOLUME. This is called from the IO thread when a + * pending hardware volume change has to be written to the + * hardware. The requested volume is passed to the callback + * implementation in s->thread_info.current_hw_volume. + * + * The call is done inside pa_source_volume_change_apply(), which is + * not called automatically - it is the driver's responsibility to + * schedule that function to be called at the right times in the + * IO thread. + * + * You must use the function pa_source_set_write_volume_callback() to + * set this callback. */ + pa_source_cb_t write_volume; /* may be NULL */ + + /* If the source mute can change "spontaneously" (i.e. initiated by the + * source implementation, not by someone else calling + * pa_source_set_mute()), then the source implementation can notify about + * changed mute either by calling pa_source_mute_changed() or by calling + * pa_source_get_mute() with force_refresh=true. If the implementation + * chooses the latter approach, it should implement the get_mute callback. + * Otherwise get_mute can be NULL. + * + * This is called when pa_source_get_mute() is called with + * force_refresh=true. This is called from the IO thread if the + * PA_SINK_DEFERRED_VOLUME flag is set, otherwise this is called from the + * main thread. On success, the implementation is expected to return 0 and + * set the mute parameter that is passed as a reference. On failure, the + * implementation is expected to return -1. + * + * You must use the function pa_source_set_get_mute_callback() to + * set this callback. */ + pa_source_get_mute_cb_t get_mute; + + /* Called when the mute setting shall be changed. Called from main + * loop context. If this is NULL a PA_SOURCE_MESSAGE_SET_MUTE + * message will be sent to the IO thread instead. + * + * You must use the function pa_source_set_set_mute_callback() to + * set this callback. */ + pa_source_cb_t set_mute; /* may be NULL */ + + /* Called when a the requested latency is changed. Called from IO + * thread context. */ + pa_source_cb_t update_requested_latency; /* may be NULL */ + + /* Called whenever the port shall be changed. Called from the main + * thread. */ + int (*set_port)(pa_source *s, pa_device_port *port); /*ditto */ + + /* Called to get the list of formats supported by the source, sorted + * in descending order of preference. */ + pa_idxset* (*get_formats)(pa_source *s); /* ditto */ + + /* Called whenever device parameters need to be changed. Called from + * main thread. */ + void (*reconfigure)(pa_source *s, pa_sample_spec *spec, bool passthrough); + + /* Contains copies of the above data so that the real-time worker + * thread can work without access locking */ + struct { + pa_source_state_t state; + pa_hashmap *outputs; + + pa_rtpoll *rtpoll; + + pa_cvolume soft_volume; + bool soft_muted:1; + + bool requested_latency_valid:1; + pa_usec_t requested_latency; + + /* Then number of bytes this source will be rewound for at + * max. (Only used on monitor sources) */ + size_t max_rewind; + + pa_usec_t min_latency; /* we won't go below this latency */ + pa_usec_t max_latency; /* An upper limit for the latencies */ + + pa_usec_t fixed_latency; /* for sources with PA_SOURCE_DYNAMIC_LATENCY this is 0 */ + + /* This latency offset is a direct copy from s->port_latency_offset */ + int64_t port_latency_offset; + + /* Delayed volume change events are queued here. The events + * are stored in expiration order. The one expiring next is in + * the head of the list. */ + PA_LLIST_HEAD(pa_source_volume_change, volume_changes); + pa_source_volume_change *volume_changes_tail; + /* This value is updated in pa_source_volume_change_apply() and + * used only by sources with PA_SOURCE_DEFERRED_VOLUME. */ + pa_cvolume current_hw_volume; + + /* The amount of usec volume up events are delayed and volume + * down events are made earlier. */ + uint32_t volume_change_safety_margin; + /* Usec delay added to all volume change events, may be negative. */ + int32_t volume_change_extra_delay; + } thread_info; + + void *userdata; +}; + +PA_DECLARE_PUBLIC_CLASS(pa_source); +#define PA_SOURCE(s) pa_source_cast(s) + +typedef enum pa_source_message { + PA_SOURCE_MESSAGE_ADD_OUTPUT, + PA_SOURCE_MESSAGE_REMOVE_OUTPUT, + PA_SOURCE_MESSAGE_GET_VOLUME, + PA_SOURCE_MESSAGE_SET_SHARED_VOLUME, + PA_SOURCE_MESSAGE_SET_VOLUME_SYNCED, + PA_SOURCE_MESSAGE_SET_VOLUME, + PA_SOURCE_MESSAGE_SYNC_VOLUMES, + PA_SOURCE_MESSAGE_GET_MUTE, + PA_SOURCE_MESSAGE_SET_MUTE, + PA_SOURCE_MESSAGE_GET_LATENCY, + PA_SOURCE_MESSAGE_GET_REQUESTED_LATENCY, + PA_SOURCE_MESSAGE_SET_STATE, + PA_SOURCE_MESSAGE_SET_LATENCY_RANGE, + PA_SOURCE_MESSAGE_GET_LATENCY_RANGE, + PA_SOURCE_MESSAGE_SET_FIXED_LATENCY, + PA_SOURCE_MESSAGE_GET_FIXED_LATENCY, + PA_SOURCE_MESSAGE_GET_MAX_REWIND, + PA_SOURCE_MESSAGE_SET_MAX_REWIND, + PA_SOURCE_MESSAGE_UPDATE_VOLUME_AND_MUTE, + PA_SOURCE_MESSAGE_SET_PORT_LATENCY_OFFSET, + PA_SOURCE_MESSAGE_MAX +} pa_source_message_t; + +typedef struct pa_source_new_data { + pa_suspend_cause_t suspend_cause; + + char *name; + pa_proplist *proplist; + + const char *driver; + pa_module *module; + pa_card *card; + + pa_hashmap *ports; + char *active_port; + + pa_sample_spec sample_spec; + pa_channel_map channel_map; + uint32_t alternate_sample_rate; + bool avoid_resampling:1; + pa_cvolume volume; + bool muted:1; + + bool volume_is_set:1; + bool muted_is_set:1; + bool sample_spec_is_set:1; + bool channel_map_is_set:1; + bool alternate_sample_rate_is_set:1; + bool avoid_resampling_is_set:1; + + bool namereg_fail:1; + + bool save_port:1; + bool save_volume:1; + bool save_muted:1; +} pa_source_new_data; + +pa_source_new_data* pa_source_new_data_init(pa_source_new_data *data); +void pa_source_new_data_set_name(pa_source_new_data *data, const char *name); +void pa_source_new_data_set_sample_spec(pa_source_new_data *data, const pa_sample_spec *spec); +void pa_source_new_data_set_channel_map(pa_source_new_data *data, const pa_channel_map *map); +void pa_source_new_data_set_alternate_sample_rate(pa_source_new_data *data, const uint32_t alternate_sample_rate); +void pa_source_new_data_set_avoid_resampling(pa_source_new_data *data, bool avoid_resampling); +void pa_source_new_data_set_volume(pa_source_new_data *data, const pa_cvolume *volume); +void pa_source_new_data_set_muted(pa_source_new_data *data, bool mute); +void pa_source_new_data_set_port(pa_source_new_data *data, const char *port); +void pa_source_new_data_done(pa_source_new_data *data); + +/*** To be called exclusively by the source driver, from main context */ + +pa_source* pa_source_new( + pa_core *core, + pa_source_new_data *data, + pa_source_flags_t flags); + +void pa_source_set_get_volume_callback(pa_source *s, pa_source_cb_t cb); +void pa_source_set_set_volume_callback(pa_source *s, pa_source_cb_t cb); +void pa_source_set_write_volume_callback(pa_source *s, pa_source_cb_t cb); +void pa_source_set_get_mute_callback(pa_source *s, pa_source_get_mute_cb_t cb); +void pa_source_set_set_mute_callback(pa_source *s, pa_source_cb_t cb); +void pa_source_enable_decibel_volume(pa_source *s, bool enable); + +void pa_source_put(pa_source *s); +void pa_source_unlink(pa_source *s); + +void pa_source_set_description(pa_source *s, const char *description); +void pa_source_set_asyncmsgq(pa_source *s, pa_asyncmsgq *q); +void pa_source_set_rtpoll(pa_source *s, pa_rtpoll *p); + +void pa_source_set_max_rewind(pa_source *s, size_t max_rewind); +void pa_source_set_latency_range(pa_source *s, pa_usec_t min_latency, pa_usec_t max_latency); +void pa_source_set_fixed_latency(pa_source *s, pa_usec_t latency); + +void pa_source_set_soft_volume(pa_source *s, const pa_cvolume *volume); +void pa_source_volume_changed(pa_source *s, const pa_cvolume *new_volume); +void pa_source_mute_changed(pa_source *s, bool new_muted); + +int pa_source_sync_suspend(pa_source *s); + +void pa_source_update_flags(pa_source *s, pa_source_flags_t mask, pa_source_flags_t value); + +/*** May be called by everyone, from main context */ + +void pa_source_set_port_latency_offset(pa_source *s, int64_t offset); + +/* The returned value is supposed to be in the time domain of the sound card! */ +pa_usec_t pa_source_get_latency(pa_source *s); +pa_usec_t pa_source_get_requested_latency(pa_source *s); +void pa_source_get_latency_range(pa_source *s, pa_usec_t *min_latency, pa_usec_t *max_latency); +pa_usec_t pa_source_get_fixed_latency(pa_source *s); + +size_t pa_source_get_max_rewind(pa_source *s); + +int pa_source_update_status(pa_source*s); +int pa_source_suspend(pa_source *s, bool suspend, pa_suspend_cause_t cause); +int pa_source_suspend_all(pa_core *c, bool suspend, pa_suspend_cause_t cause); + +/* Use this instead of checking s->flags & PA_SOURCE_FLAT_VOLUME directly. */ +bool pa_source_flat_volume_enabled(pa_source *s); + +/* Get the master source when sharing volumes */ +pa_source *pa_source_get_master(pa_source *s); + +bool pa_source_is_filter(pa_source *s); + +/* Is the source in passthrough mode? (that is, is this a monitor source for a sink + * that has a passthrough sink input connected to it. */ +bool pa_source_is_passthrough(pa_source *s); +/* These should be called when a source enters/leaves passthrough mode */ +void pa_source_enter_passthrough(pa_source *s); +void pa_source_leave_passthrough(pa_source *s); + +void pa_source_set_volume(pa_source *source, const pa_cvolume *volume, bool sendmsg, bool save); +const pa_cvolume *pa_source_get_volume(pa_source *source, bool force_refresh); + +void pa_source_set_mute(pa_source *source, bool mute, bool save); +bool pa_source_get_mute(pa_source *source, bool force_refresh); + +bool pa_source_update_proplist(pa_source *s, pa_update_mode_t mode, pa_proplist *p); + +int pa_source_set_port(pa_source *s, const char *name, bool save); + +void pa_source_reconfigure(pa_source *s, pa_sample_spec *spec, bool passthrough); + +unsigned pa_source_linked_by(pa_source *s); /* Number of connected streams */ +unsigned pa_source_used_by(pa_source *s); /* Number of connected streams that are not corked */ + +/* Returns how many streams are active that don't allow suspensions. If + * "ignore" is non-NULL, that stream is not included in the count. */ +unsigned pa_source_check_suspend(pa_source *s, pa_source_output *ignore); + +const char *pa_source_state_to_string(pa_source_state_t state); + +/* Moves all inputs away, and stores them in pa_queue */ +pa_queue *pa_source_move_all_start(pa_source *s, pa_queue *q); +void pa_source_move_all_finish(pa_source *s, pa_queue *q, bool save); +void pa_source_move_all_fail(pa_queue *q); + +/* Returns a copy of the source formats. TODO: Get rid of this function (or at + * least get rid of the copying). There's no good reason to copy the formats + * every time someone wants to know what formats the source supports. The + * formats idxset could be stored directly in the pa_source struct. + * https://bugs.freedesktop.org/show_bug.cgi?id=71924 */ +pa_idxset* pa_source_get_formats(pa_source *s); + +bool pa_source_check_format(pa_source *s, pa_format_info *f); +pa_idxset* pa_source_check_formats(pa_source *s, pa_idxset *in_formats); + +void pa_source_set_sample_format(pa_source *s, pa_sample_format_t format); +void pa_source_set_sample_rate(pa_source *s, uint32_t rate); + +/*** To be called exclusively by the source driver, from IO context */ + +void pa_source_post(pa_source*s, const pa_memchunk *chunk); +void pa_source_post_direct(pa_source*s, pa_source_output *o, const pa_memchunk *chunk); +void pa_source_process_rewind(pa_source *s, size_t nbytes); + +int pa_source_process_msg(pa_msgobject *o, int code, void *userdata, int64_t, pa_memchunk *chunk); + +void pa_source_attach_within_thread(pa_source *s); +void pa_source_detach_within_thread(pa_source *s); + +pa_usec_t pa_source_get_requested_latency_within_thread(pa_source *s); + +void pa_source_set_max_rewind_within_thread(pa_source *s, size_t max_rewind); + +void pa_source_set_latency_range_within_thread(pa_source *s, pa_usec_t min_latency, pa_usec_t max_latency); +void pa_source_set_fixed_latency_within_thread(pa_source *s, pa_usec_t latency); + +void pa_source_update_volume_and_mute(pa_source *s); + +bool pa_source_volume_change_apply(pa_source *s, pa_usec_t *usec_to_next); + +/*** To be called exclusively by source output drivers, from IO context */ + +void pa_source_invalidate_requested_latency(pa_source *s, bool dynamic); +int64_t pa_source_get_latency_within_thread(pa_source *s, bool allow_negative); + +/* Called from the main thread, from source-output.c only. The normal way to + * set the source reference volume is to call pa_source_set_volume(), but the + * flat volume logic in source-output.c needs also a function that doesn't do + * all the extra stuff that pa_source_set_volume() does. This function simply + * sets s->reference_volume and fires change notifications. */ +void pa_source_set_reference_volume_direct(pa_source *s, const pa_cvolume *volume); + +/* When the default_source is changed or the active_port of a source is changed to + * PA_AVAILABLE_NO, this function is called to move the streams of the old + * default_source or the source with active_port equals PA_AVAILABLE_NO to the + * current default_source conditionally*/ +void pa_source_move_streams_to_default_source(pa_core *core, pa_source *old_source, bool default_source_changed); + +#define pa_source_assert_io_context(s) \ + pa_assert(pa_thread_mq_get() || !PA_SOURCE_IS_LINKED((s)->state)) + +#endif diff --git a/src/pulsecore/srbchannel.c b/src/pulsecore/srbchannel.c new file mode 100644 index 0000000..4cdfaa7 --- /dev/null +++ b/src/pulsecore/srbchannel.c @@ -0,0 +1,379 @@ +/*** + This file is part of PulseAudio. + + Copyright 2014 David Henningsson, Canonical Ltd. + + 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "srbchannel.h" + +#include <pulsecore/atomic.h> +#include <pulse/xmalloc.h> + +/* #define DEBUG_SRBCHANNEL */ + +/* This ringbuffer might be useful in other contexts too, but + * right now it's only used inside the srbchannel, so let's keep it here + * for the time being. */ +typedef struct pa_ringbuffer pa_ringbuffer; + +struct pa_ringbuffer { + pa_atomic_t *count; /* amount of data in the buffer */ + int capacity; + uint8_t *memory; + int readindex, writeindex; +}; + +static void *pa_ringbuffer_peek(pa_ringbuffer *r, int *count) { + int c = pa_atomic_load(r->count); + + if (r->readindex + c > r->capacity) + *count = r->capacity - r->readindex; + else + *count = c; + + return r->memory + r->readindex; +} + +/* Returns true only if the buffer was completely full before the drop. */ +static bool pa_ringbuffer_drop(pa_ringbuffer *r, int count) { + bool b = pa_atomic_sub(r->count, count) >= r->capacity; + + r->readindex += count; + r->readindex %= r->capacity; + + return b; +} + +static void *pa_ringbuffer_begin_write(pa_ringbuffer *r, int *count) { + int c = pa_atomic_load(r->count); + + *count = PA_MIN(r->capacity - r->writeindex, r->capacity - c); + + return r->memory + r->writeindex; +} + +static void pa_ringbuffer_end_write(pa_ringbuffer *r, int count) { + pa_atomic_add(r->count, count); + r->writeindex += count; + r->writeindex %= r->capacity; +} + +struct pa_srbchannel { + pa_ringbuffer rb_read, rb_write; + pa_fdsem *sem_read, *sem_write; + pa_memblock *memblock; + + void *cb_userdata; + pa_srbchannel_cb_t callback; + + pa_io_event *read_event; + pa_defer_event *defer_event; + pa_mainloop_api *mainloop; +}; + +/* We always listen to sem_read, and always signal on sem_write. + * + * This means we signal the same semaphore for two scenarios: + * 1) We have written something to our send buffer, and want the other + * side to read it + * 2) We have read something from our receive buffer that was previously + * completely full, and want the other side to continue writing +*/ + +size_t pa_srbchannel_write(pa_srbchannel *sr, const void *data, size_t l) { + size_t written = 0; + + while (l > 0) { + int towrite; + void *ptr = pa_ringbuffer_begin_write(&sr->rb_write, &towrite); + + if ((size_t) towrite > l) + towrite = l; + + if (towrite == 0) { +#ifdef DEBUG_SRBCHANNEL + pa_log("srbchannel output buffer full"); +#endif + break; + } + + memcpy(ptr, data, towrite); + pa_ringbuffer_end_write(&sr->rb_write, towrite); + written += towrite; + data = (uint8_t*) data + towrite; + l -= towrite; + } +#ifdef DEBUG_SRBCHANNEL + pa_log("Wrote %d bytes to srbchannel, signalling fdsem", (int) written); +#endif + + pa_fdsem_post(sr->sem_write); + return written; +} + +size_t pa_srbchannel_read(pa_srbchannel *sr, void *data, size_t l) { + size_t isread = 0; + + while (l > 0) { + int toread; + void *ptr = pa_ringbuffer_peek(&sr->rb_read, &toread); + + if ((size_t) toread > l) + toread = l; + + if (toread == 0) + break; + + memcpy(data, ptr, toread); + + if (pa_ringbuffer_drop(&sr->rb_read, toread)) { +#ifdef DEBUG_SRBCHANNEL + pa_log("Read from full output buffer, signalling fdsem"); +#endif + pa_fdsem_post(sr->sem_write); + } + + isread += toread; + data = (uint8_t*) data + toread; + l -= toread; + } + +#ifdef DEBUG_SRBCHANNEL + pa_log("Read %d bytes from srbchannel", (int) isread); +#endif + + return isread; +} + +/* This is the memory layout of the ringbuffer shm block. It is followed by + read and write ringbuffer memory. */ +struct srbheader { + pa_atomic_t read_count; + pa_atomic_t write_count; + + pa_fdsem_data read_semdata; + pa_fdsem_data write_semdata; + + int capacity; + int readbuf_offset; + int writebuf_offset; + + /* TODO: Maybe a marker here to make sure we talk to a server with equally sized struct */ +}; + +static void srbchannel_rwloop(pa_srbchannel* sr) { + do { +#ifdef DEBUG_SRBCHANNEL + int q; + pa_ringbuffer_peek(&sr->rb_read, &q); + pa_log("In rw loop from srbchannel, before callback, count = %d", q); +#endif + + if (sr->callback) { + if (!sr->callback(sr, sr->cb_userdata)) { +#ifdef DEBUG_SRBCHANNEL + pa_log("Aborting read loop from srbchannel"); +#endif + return; + } + } + +#ifdef DEBUG_SRBCHANNEL + pa_ringbuffer_peek(&sr->rb_read, &q); + pa_log("In rw loop from srbchannel, after callback, count = %d", q); +#endif + + } while (pa_fdsem_before_poll(sr->sem_read) < 0); +} + +static void semread_cb(pa_mainloop_api *m, pa_io_event *e, int fd, pa_io_event_flags_t events, void *userdata) { + pa_srbchannel* sr = userdata; + + pa_fdsem_after_poll(sr->sem_read); + srbchannel_rwloop(sr); +} + +static void defer_cb(pa_mainloop_api *m, pa_defer_event *e, void *userdata) { + pa_srbchannel* sr = userdata; + +#ifdef DEBUG_SRBCHANNEL + pa_log("Calling rw loop from deferred event"); +#endif + + m->defer_enable(e, 0); + srbchannel_rwloop(sr); +} + +pa_srbchannel* pa_srbchannel_new(pa_mainloop_api *m, pa_mempool *p) { + int capacity; + int readfd; + struct srbheader *srh; + + pa_srbchannel* sr = pa_xmalloc0(sizeof(pa_srbchannel)); + sr->mainloop = m; + sr->memblock = pa_memblock_new_pool(p, -1); + if (!sr->memblock) + goto fail; + + srh = pa_memblock_acquire(sr->memblock); + pa_zero(*srh); + + sr->rb_read.memory = (uint8_t*) srh + PA_ALIGN(sizeof(*srh)); + srh->readbuf_offset = sr->rb_read.memory - (uint8_t*) srh; + + capacity = (pa_memblock_get_length(sr->memblock) - srh->readbuf_offset) / 2; + + sr->rb_write.memory = PA_ALIGN_PTR(sr->rb_read.memory + capacity); + srh->writebuf_offset = sr->rb_write.memory - (uint8_t*) srh; + + capacity = PA_MIN(capacity, srh->writebuf_offset - srh->readbuf_offset); + + pa_log_debug("SHM block is %d bytes, ringbuffer capacity is 2 * %d bytes", + (int) pa_memblock_get_length(sr->memblock), capacity); + + srh->capacity = sr->rb_read.capacity = sr->rb_write.capacity = capacity; + + sr->rb_read.count = &srh->read_count; + sr->rb_write.count = &srh->write_count; + + sr->sem_read = pa_fdsem_new_shm(&srh->read_semdata); + if (!sr->sem_read) + goto fail; + + sr->sem_write = pa_fdsem_new_shm(&srh->write_semdata); + if (!sr->sem_write) + goto fail; + + readfd = pa_fdsem_get(sr->sem_read); + +#ifdef DEBUG_SRBCHANNEL + pa_log("Enabling io event on fd %d", readfd); +#endif + + sr->read_event = m->io_new(m, readfd, PA_IO_EVENT_INPUT, semread_cb, sr); + m->io_enable(sr->read_event, PA_IO_EVENT_INPUT); + + return sr; + +fail: + pa_srbchannel_free(sr); + + return NULL; +} + +static void pa_srbchannel_swap(pa_srbchannel *sr) { + pa_srbchannel temp = *sr; + + sr->sem_read = temp.sem_write; + sr->sem_write = temp.sem_read; + sr->rb_read = temp.rb_write; + sr->rb_write = temp.rb_read; +} + +pa_srbchannel* pa_srbchannel_new_from_template(pa_mainloop_api *m, pa_srbchannel_template *t) +{ + int temp; + struct srbheader *srh; + pa_srbchannel* sr = pa_xmalloc0(sizeof(pa_srbchannel)); + + sr->mainloop = m; + sr->memblock = t->memblock; + pa_memblock_ref(sr->memblock); + srh = pa_memblock_acquire(sr->memblock); + + sr->rb_read.capacity = sr->rb_write.capacity = srh->capacity; + sr->rb_read.count = &srh->read_count; + sr->rb_write.count = &srh->write_count; + + sr->rb_read.memory = (uint8_t*) srh + srh->readbuf_offset; + sr->rb_write.memory = (uint8_t*) srh + srh->writebuf_offset; + + sr->sem_read = pa_fdsem_open_shm(&srh->read_semdata, t->readfd); + if (!sr->sem_read) + goto fail; + + sr->sem_write = pa_fdsem_open_shm(&srh->write_semdata, t->writefd); + if (!sr->sem_write) + goto fail; + + pa_srbchannel_swap(sr); + temp = t->readfd; t->readfd = t->writefd; t->writefd = temp; + +#ifdef DEBUG_SRBCHANNEL + pa_log("Enabling io event on fd %d", t->readfd); +#endif + + sr->read_event = m->io_new(m, t->readfd, PA_IO_EVENT_INPUT, semread_cb, sr); + m->io_enable(sr->read_event, PA_IO_EVENT_INPUT); + + return sr; + +fail: + pa_srbchannel_free(sr); + + return NULL; +} + +void pa_srbchannel_export(pa_srbchannel *sr, pa_srbchannel_template *t) { + t->memblock = sr->memblock; + t->readfd = pa_fdsem_get(sr->sem_read); + t->writefd = pa_fdsem_get(sr->sem_write); +} + +void pa_srbchannel_set_callback(pa_srbchannel *sr, pa_srbchannel_cb_t callback, void *userdata) { + if (sr->callback) + pa_fdsem_after_poll(sr->sem_read); + + sr->callback = callback; + sr->cb_userdata = userdata; + + if (sr->callback) { + /* If there are events to be read already in the ringbuffer, we will not get any IO event for that, + because that's how pa_fdsem works. Therefore check the ringbuffer in a defer event instead. */ + if (!sr->defer_event) + sr->defer_event = sr->mainloop->defer_new(sr->mainloop, defer_cb, sr); + sr->mainloop->defer_enable(sr->defer_event, 1); + } +} + +void pa_srbchannel_free(pa_srbchannel *sr) +{ +#ifdef DEBUG_SRBCHANNEL + pa_log("Freeing srbchannel"); +#endif + pa_assert(sr); + + if (sr->defer_event) + sr->mainloop->defer_free(sr->defer_event); + if (sr->read_event) + sr->mainloop->io_free(sr->read_event); + + if (sr->sem_read) + pa_fdsem_free(sr->sem_read); + if (sr->sem_write) + pa_fdsem_free(sr->sem_write); + + if (sr->memblock) { + pa_memblock_release(sr->memblock); + pa_memblock_unref(sr->memblock); + } + + pa_xfree(sr); +} diff --git a/src/pulsecore/srbchannel.h b/src/pulsecore/srbchannel.h new file mode 100644 index 0000000..09193e0 --- /dev/null +++ b/src/pulsecore/srbchannel.h @@ -0,0 +1,57 @@ +#ifndef foopulsesrbchannelhfoo +#define foopulsesrbchannelhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2014 David Henningsson, Canonical Ltd. + + 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulse/mainloop-api.h> +#include <pulsecore/fdsem.h> +#include <pulsecore/memblock.h> + +/* An shm ringbuffer that is used for low overhead server-client communication. + * Signaling is done through eventfd semaphores (pa_fdsem). */ + +typedef struct pa_srbchannel pa_srbchannel; + +typedef struct pa_srbchannel_template { + int readfd, writefd; + pa_memblock *memblock; +} pa_srbchannel_template; + +pa_srbchannel* pa_srbchannel_new(pa_mainloop_api *m, pa_mempool *p); +/* Note: this creates a srbchannel with swapped read and write. */ +pa_srbchannel* pa_srbchannel_new_from_template(pa_mainloop_api *m, pa_srbchannel_template *t); + +void pa_srbchannel_free(pa_srbchannel *sr); + +void pa_srbchannel_export(pa_srbchannel *sr, pa_srbchannel_template *t); + +size_t pa_srbchannel_write(pa_srbchannel *sr, const void *data, size_t l); +size_t pa_srbchannel_read(pa_srbchannel *sr, void *data, size_t l); + +/* Set the callback function that is called whenever data becomes available for reading. + * It can also be called if the output buffer was full and can now be written to. + * + * Return false to abort all processing (e g if the srbchannel has been freed during the callback). + * Otherwise return true. +*/ +typedef bool (*pa_srbchannel_cb_t)(pa_srbchannel *sr, void *userdata); +void pa_srbchannel_set_callback(pa_srbchannel *sr, pa_srbchannel_cb_t callback, void *userdata); + +#endif diff --git a/src/pulsecore/start-child.c b/src/pulsecore/start-child.c new file mode 100644 index 0000000..7f0719c --- /dev/null +++ b/src/pulsecore/start-child.c @@ -0,0 +1,113 @@ +/*** + This file is part of PulseAudio. + + Copyright 2007 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <stdio.h> +#include <sys/types.h> +#include <dirent.h> +#include <unistd.h> +#include <fcntl.h> +#include <signal.h> + +#ifdef HAVE_SYS_PRCTL_H +#include <sys/prctl.h> +#endif +#ifdef HAVE_SYS_RESOURCE_H +#include <sys/resource.h> +#endif + +#include <pulsecore/core-util.h> +#include <pulsecore/core-error.h> +#include <pulsecore/pipe.h> + +#include "start-child.h" + +int pa_start_child_for_read(const char *name, const char *argv1, pid_t *pid) { +#ifdef HAVE_FORK + pid_t child; + int pipe_fds[2] = { -1, -1 }; + + if (pipe(pipe_fds) < 0) { + pa_log("pipe() failed: %s", pa_cstrerror(errno)); + goto fail; + } + + if ((child = fork()) == (pid_t) -1) { + pa_log("fork() failed: %s", pa_cstrerror(errno)); + goto fail; + + } else if (child != 0) { + + /* Parent */ + pa_assert_se(pa_close(pipe_fds[1]) == 0); + + if (pid) + *pid = child; + + return pipe_fds[0]; + } else { + /* child */ + + pa_reset_personality(); + + pa_assert_se(pa_close(pipe_fds[0]) == 0); + pa_assert_se(dup2(pipe_fds[1], STDOUT_FILENO) == STDOUT_FILENO); + + if (pipe_fds[1] != STDOUT_FILENO) + pa_assert_se(pa_close(pipe_fds[1]) == 0); + + pa_close(STDIN_FILENO); + pa_assert_se(open("/dev/null", O_RDONLY) == STDIN_FILENO); + + pa_close(STDERR_FILENO); + pa_assert_se(open("/dev/null", O_WRONLY) == STDERR_FILENO); + + pa_close_all(-1); + pa_reset_sigs(-1); + pa_unblock_sigs(-1); + pa_reset_priority(); + pa_unset_env_recorded(); + + /* Make sure our children are not influenced by the + * LD_BIND_NOW we set for ourselves. */ + pa_unset_env("LD_BIND_NOW"); + +#ifdef PR_SET_PDEATHSIG + /* On Linux we can use PR_SET_PDEATHSIG to have the helper + process killed when the daemon dies abnormally. On non-Linux + machines the client will die as soon as it writes data to + stdout again (SIGPIPE) */ + + prctl(PR_SET_PDEATHSIG, SIGTERM, 0, 0, 0); +#endif + + execl(name, name, argv1, NULL); + _exit(1); + } + +fail: + pa_close_pipe(pipe_fds); +#endif /* HAVE_FORK */ + + return -1; +} diff --git a/src/pulsecore/start-child.h b/src/pulsecore/start-child.h new file mode 100644 index 0000000..6222f45 --- /dev/null +++ b/src/pulsecore/start-child.h @@ -0,0 +1,28 @@ +#ifndef foopulsestartchildhfoo +#define foopulsestartchildhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2007 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/types.h> +#include <unistd.h> + +int pa_start_child_for_read(const char *name, const char *argv1, pid_t *pid); + +#endif diff --git a/src/pulsecore/strbuf.c b/src/pulsecore/strbuf.c new file mode 100644 index 0000000..11f131b --- /dev/null +++ b/src/pulsecore/strbuf.c @@ -0,0 +1,193 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <sys/types.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <stdio.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/macro.h> + +#include "strbuf.h" + +/* A chunk of the linked list that makes up the string */ +struct chunk { + struct chunk *next; + size_t length; +}; + +#define CHUNK_TO_TEXT(c) ((char*) (c) + PA_ALIGN(sizeof(struct chunk))) + +struct pa_strbuf { + size_t length; + struct chunk *head, *tail; +}; + +pa_strbuf *pa_strbuf_new(void) { + pa_strbuf *sb; + + sb = pa_xnew(pa_strbuf, 1); + sb->length = 0; + sb->head = sb->tail = NULL; + + return sb; +} + +void pa_strbuf_free(pa_strbuf *sb) { + pa_assert(sb); + + while (sb->head) { + struct chunk *c = sb->head; + sb->head = sb->head->next; + pa_xfree(c); + } + + pa_xfree(sb); +} + +/* Make a C string from the string buffer. The caller has to free + * string with pa_xfree(). */ +char *pa_strbuf_to_string(pa_strbuf *sb) { + char *t, *e; + struct chunk *c; + + pa_assert(sb); + + e = t = pa_xmalloc(sb->length+1); + + for (c = sb->head; c; c = c->next) { + pa_assert((size_t) (e-t) <= sb->length); + memcpy(e, CHUNK_TO_TEXT(c), c->length); + e += c->length; + } + + /* Trailing NUL */ + *e = 0; + + pa_assert(e == t+sb->length); + + return t; +} + +/* Combination of pa_strbuf_free() and pa_strbuf_to_string() */ +char *pa_strbuf_to_string_free(pa_strbuf *sb) { + char *t; + + pa_assert(sb); + t = pa_strbuf_to_string(sb); + pa_strbuf_free(sb); + + return t; +} + +/* Append a string to the string buffer */ +void pa_strbuf_puts(pa_strbuf *sb, const char *t) { + + pa_assert(sb); + pa_assert(t); + + pa_strbuf_putsn(sb, t, strlen(t)); +} + +/* Append a character to the string buffer */ +void pa_strbuf_putc(pa_strbuf *sb, char c) { + pa_assert(sb); + + pa_strbuf_putsn(sb, &c, 1); +} + +/* Append a new chunk to the linked list */ +static void append(pa_strbuf *sb, struct chunk *c) { + pa_assert(sb); + pa_assert(c); + + if (sb->tail) { + pa_assert(sb->head); + sb->tail->next = c; + } else { + pa_assert(!sb->head); + sb->head = c; + } + + sb->tail = c; + sb->length += c->length; + c->next = NULL; +} + +/* Append up to l bytes of a string to the string buffer */ +void pa_strbuf_putsn(pa_strbuf *sb, const char *t, size_t l) { + struct chunk *c; + + pa_assert(sb); + pa_assert(t); + + if (!l) + return; + + c = pa_xmalloc(PA_ALIGN(sizeof(struct chunk)) + l); + c->length = l; + memcpy(CHUNK_TO_TEXT(c), t, l); + + append(sb, c); +} + +/* Append a printf() style formatted string to the string buffer. */ +/* The following is based on an example from the GNU libc documentation */ +size_t pa_strbuf_printf(pa_strbuf *sb, const char *format, ...) { + size_t size = 100; + struct chunk *c = NULL; + + pa_assert(sb); + pa_assert(format); + + for(;;) { + va_list ap; + int r; + + c = pa_xrealloc(c, PA_ALIGN(sizeof(struct chunk)) + size); + + va_start(ap, format); + r = vsnprintf(CHUNK_TO_TEXT(c), size, format, ap); + CHUNK_TO_TEXT(c)[size-1] = 0; + va_end(ap); + + if (r > -1 && (size_t) r < size) { + c->length = (size_t) r; + append(sb, c); + return (size_t) r; + } + + if (r > -1) /* glibc 2.1 */ + size = (size_t) r+1; + else /* glibc 2.0 */ + size *= 2; + } +} + +bool pa_strbuf_isempty(pa_strbuf *sb) { + pa_assert(sb); + + return sb->length <= 0; +} diff --git a/src/pulsecore/strbuf.h b/src/pulsecore/strbuf.h new file mode 100644 index 0000000..469f6f7 --- /dev/null +++ b/src/pulsecore/strbuf.h @@ -0,0 +1,40 @@ +#ifndef foostrbufhfoo +#define foostrbufhfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulse/gccmacro.h> +#include <pulsecore/macro.h> + +typedef struct pa_strbuf pa_strbuf; + +pa_strbuf *pa_strbuf_new(void); +void pa_strbuf_free(pa_strbuf *sb); +char *pa_strbuf_to_string(pa_strbuf *sb); +char *pa_strbuf_to_string_free(pa_strbuf *sb); + +size_t pa_strbuf_printf(pa_strbuf *sb, const char *format, ...) PA_GCC_PRINTF_ATTR(2,3); +void pa_strbuf_puts(pa_strbuf *sb, const char *t); +void pa_strbuf_putsn(pa_strbuf *sb, const char *t, size_t m); +void pa_strbuf_putc(pa_strbuf *sb, char c); + +bool pa_strbuf_isempty(pa_strbuf *sb); + +#endif diff --git a/src/pulsecore/stream-util.c b/src/pulsecore/stream-util.c new file mode 100644 index 0000000..2574501 --- /dev/null +++ b/src/pulsecore/stream-util.c @@ -0,0 +1,84 @@ +/*** + This file is part of PulseAudio. + + Copyright 2013 Intel Corporation + + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "stream-util.h" + +#include <pulse/def.h> + +#include <pulsecore/core-format.h> +#include <pulsecore/macro.h> + +int pa_stream_get_volume_channel_map(const pa_cvolume *volume, const pa_channel_map *original_map, const pa_format_info *format, + pa_channel_map *volume_map) { + int r; + pa_channel_map volume_map_local; + + pa_assert(volume); + pa_assert(format); + pa_assert(volume_map); + + if (original_map) { + if (volume->channels == original_map->channels) { + *volume_map = *original_map; + return 0; + } + + if (volume->channels == 1) { + pa_channel_map_init_mono(volume_map); + return 0; + } + + pa_log_info("Invalid stream parameters: the volume is incompatible with the channel map."); + return -PA_ERR_INVALID; + } + + r = pa_format_info_get_channel_map(format, &volume_map_local); + if (r == -PA_ERR_NOENTITY) { + if (volume->channels == 1) { + pa_channel_map_init_mono(volume_map); + return 0; + } + + pa_log_info("Invalid stream parameters: multi-channel volume is set, but channel map is not."); + return -PA_ERR_INVALID; + } + + if (r < 0) { + pa_log_info("Invalid channel map."); + return -PA_ERR_INVALID; + } + + if (volume->channels == volume_map_local.channels) { + *volume_map = volume_map_local; + return 0; + } + + if (volume->channels == 1) { + pa_channel_map_init_mono(volume_map); + return 0; + } + + pa_log_info("Invalid stream parameters: the volume is incompatible with the channel map."); + + return -PA_ERR_INVALID; +} diff --git a/src/pulsecore/stream-util.h b/src/pulsecore/stream-util.h new file mode 100644 index 0000000..b2e47fe --- /dev/null +++ b/src/pulsecore/stream-util.h @@ -0,0 +1,48 @@ +#ifndef foostreamutilhfoo +#define foostreamutilhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2013 Intel Corporation + + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulse/format.h> +#include <pulse/volume.h> + +/* This is a helper function that is called from pa_sink_input_new() and + * pa_source_output_new(). The job of this function is to figure out what + * channel map should be used for interpreting the volume that was set for the + * stream. The channel map that the client intended for the volume may be + * different than the final stream channel map, because the client may want the + * server to decide the stream channel map. + * + * volume is the volume for which the channel map should be figured out. + * + * original_map is the channel map that is set in the new data struct's + * channel_map field. If the channel map hasn't been set in the new data, then + * original_map should be NULL. + * + * format is the negotiated format for the stream. It's used as a fallback if + * original_map is not available. + * + * On success, the result is saved in volume_map. It's possible that this + * function fails to figure out the right channel map for the volume, in which + * case a negative error code is returned. */ +int pa_stream_get_volume_channel_map(const pa_cvolume *volume, const pa_channel_map *original_map, const pa_format_info *format, + pa_channel_map *volume_map); + +#endif diff --git a/src/pulsecore/strlist.c b/src/pulsecore/strlist.c new file mode 100644 index 0000000..7e5b070 --- /dev/null +++ b/src/pulsecore/strlist.c @@ -0,0 +1,171 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/strbuf.h> +#include <pulsecore/macro.h> +#include <pulsecore/core-util.h> + +#include "strlist.h" + +struct pa_strlist { + pa_strlist *next; +}; + +#define ITEM_TO_TEXT(c) ((char*) (c) + PA_ALIGN(sizeof(pa_strlist))) + +pa_strlist* pa_strlist_prepend(pa_strlist *l, const char *s) { + pa_strlist *n; + size_t size; + + pa_assert(s); + size = strlen(s); + n = pa_xmalloc(PA_ALIGN(sizeof(pa_strlist)) + size + 1); + memcpy(ITEM_TO_TEXT(n), s, size + 1); + n->next = l; + + return n; +} + +char *pa_strlist_to_string(pa_strlist *l) { + int first = 1; + pa_strbuf *b; + + b = pa_strbuf_new(); + for (; l; l = l->next) { + if (!first) + pa_strbuf_puts(b, " "); + first = 0; + pa_strbuf_puts(b, ITEM_TO_TEXT(l)); + } + + return pa_strbuf_to_string_free(b); +} + +pa_strlist* pa_strlist_remove(pa_strlist *l, const char *s) { + pa_strlist *ret = l, *prev = NULL; + + pa_assert(l); + pa_assert(s); + + while (l) { + if (pa_streq(ITEM_TO_TEXT(l), s)) { + pa_strlist *n = l->next; + + if (!prev) { + pa_assert(ret == l); + ret = n; + } else + prev->next = n; + + pa_xfree(l); + + l = n; + + } else { + prev = l; + l = l->next; + } + } + + return ret; +} + +void pa_strlist_free(pa_strlist *l) { + while (l) { + pa_strlist *c = l; + l = l->next; + pa_xfree(c); + } +} + +pa_strlist* pa_strlist_pop(pa_strlist *l, char **s) { + pa_strlist *r; + + pa_assert(s); + + if (!l) { + *s = NULL; + return NULL; + } + + *s = pa_xstrdup(ITEM_TO_TEXT(l)); + r = l->next; + pa_xfree(l); + return r; +} + +pa_strlist* pa_strlist_parse(const char *s) { + pa_strlist *head = NULL, *p = NULL; + const char *state = NULL; + char *r; + + while ((r = pa_split_spaces(s, &state))) { + pa_strlist *n; + size_t size = strlen(r); + + n = pa_xmalloc(PA_ALIGN(sizeof(pa_strlist)) + size + 1); + n->next = NULL; + memcpy(ITEM_TO_TEXT(n), r, size+1); + pa_xfree(r); + + if (p) + p->next = n; + else + head = n; + + p = n; + } + + return head; +} + +pa_strlist *pa_strlist_reverse(pa_strlist *l) { + pa_strlist *r = NULL; + + while (l) { + pa_strlist *n; + + n = l->next; + l->next = r; + r = l; + l = n; + } + + return r; +} + +pa_strlist *pa_strlist_next(pa_strlist *s) { + pa_assert(s); + + return s->next; +} + +const char *pa_strlist_data(pa_strlist *s) { + pa_assert(s); + + return ITEM_TO_TEXT(s); +} diff --git a/src/pulsecore/strlist.h b/src/pulsecore/strlist.h new file mode 100644 index 0000000..3cc71e8 --- /dev/null +++ b/src/pulsecore/strlist.h @@ -0,0 +1,54 @@ +#ifndef foostrlisthfoo +#define foostrlisthfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +typedef struct pa_strlist pa_strlist; + +/* Add the specified server string to the list, return the new linked list head */ +pa_strlist* pa_strlist_prepend(pa_strlist *l, const char *s); + +/* Remove the specified string from the list, return the new linked list head */ +pa_strlist* pa_strlist_remove(pa_strlist *l, const char *s); + +/* Make a whitespace separated string of all server strings. Returned memory has to be freed with pa_xfree() */ +char *pa_strlist_to_string(pa_strlist *l); + +/* Free the entire list */ +void pa_strlist_free(pa_strlist *l); + +/* Return the next entry in the list in *string and remove it from + * the list. Returns the new list head. The memory *string points to + * has to be freed with pa_xfree() */ +pa_strlist* pa_strlist_pop(pa_strlist *l, char **s); + +/* Parse a whitespace separated server list */ +pa_strlist* pa_strlist_parse(const char *s); + +/* Reverse string list */ +pa_strlist *pa_strlist_reverse(pa_strlist *l); + +/* Return the next item in the list */ +pa_strlist *pa_strlist_next(pa_strlist *s); + +/* Return the string associated to the current item */ +const char *pa_strlist_data(pa_strlist *s); + +#endif diff --git a/src/pulsecore/svolume.orc b/src/pulsecore/svolume.orc new file mode 100644 index 0000000..26f618e --- /dev/null +++ b/src/pulsecore/svolume.orc @@ -0,0 +1,84 @@ +# This file is part of PulseAudio. +# +# Copyright 2010 Lennart Poettering +# Copyright 2010 Wim Taymans <wim.taymans@collabora.co.uk> +# Copyright 2010 Arun Raghavan <arun.raghavan@collabora.co.uk> +# +# 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.1 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, see <http://www.gnu.org/licenses/>. + +# S16NE 1- and 2-channel volume scaling work as follows: +# +# params: samples s (signed 16-bit), volume v (signed 32-bit < 2^31) +# +# 32 16 0 (type of operation) +# sample = | sample | (signed) +# s = | 0 | sample | (unsigned) +# +# if (sample < 0) +# signc = | 0 | 0xffff | (unsigned) +# else +# signc = | 0 | 0 | (unsigned) +# +# if (sample < 0) +# ml = | 0 | -((s*vl) >> 16) | (unsigned) +# else +# ml = | 0 | (s*vl) >> 16 | (unsigned) +# +# vh = | v >> 16 | (signed, but sign bit is always zero +# since PA_VOLUME_MAX is 0x0fffffff) +# mh = | (s * vh) >> 16 | (signed) +# ml = | ml + mh | (signed) +# sample = | (ml >> 16) | (signed, saturated) + +.function pa_volume_s16ne_orc_1ch +.dest 2 samples int16_t +.param 4 vols int32_t +.temp 4 v +.temp 2 vh +.temp 4 s +.temp 4 mh +.temp 4 ml +.temp 4 signc + +loadpl v, vols +convuwl s, samples +x2 cmpgtsw signc, 0, s +x2 andw signc, signc, v +x2 mulhuw ml, s, v +subl ml, ml, signc +convhlw vh, v +mulswl mh, samples, vh +addl ml, ml, mh +convssslw samples, ml + +.function pa_volume_s16ne_orc_2ch +.dest 4 samples int16_t +.longparam 8 vols +.temp 8 v +.temp 4 vh +.temp 8 s +.temp 8 mh +.temp 8 ml +.temp 8 signc + +loadpq v, vols +x2 convuwl s, samples +x4 cmpgtsw signc, 0, s +x4 andw signc, signc, v +x4 mulhuw ml, s, v +x2 subl ml, ml, signc +x2 convhlw vh, v +x2 mulswl mh, samples, vh +x2 addl ml, ml, mh +x2 convssslw samples, ml diff --git a/src/pulsecore/svolume_arm.c b/src/pulsecore/svolume_arm.c new file mode 100644 index 0000000..fdef313 --- /dev/null +++ b/src/pulsecore/svolume_arm.c @@ -0,0 +1,167 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2009 Wim Taymans <wim.taymans@collabora.co.uk> + + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulsecore/random.h> +#include <pulsecore/macro.h> +#include <pulsecore/endianmacros.h> + +#include "cpu-arm.h" + +#include "sample-util.h" + +#if defined (__arm__) && defined (HAVE_ARMV6) + +#define MOD_INC() \ + " subs r0, r6, %2 \n\t" \ + " itt cs \n\t" \ + " addcs r0, %1 \n\t" \ + " movcs r6, r0 \n\t" + +static pa_do_volume_func_t _volume_ref; + +static void pa_volume_s16ne_arm(int16_t *samples, const int32_t *volumes, unsigned channels, unsigned length) { + /* Channels must be at least 4, and always a multiple of the original number. + * This is also the max amount we overread the volume array, which should + * have enough padding. */ + const int32_t *ve = volumes + (channels == 3 ? 6 : PA_MAX (4U, channels)); + unsigned rem = PA_ALIGN((size_t) samples) - (size_t) samples; + + /* Make sure we're word-aligned, else performance _really_ sucks */ + if (rem) { + _volume_ref(samples, volumes, channels, rem < length ? rem : length); + + if (rem < length) { + length -= rem; + samples += rem / sizeof(*samples); + } else + return; /* we're done */ + } + + __asm__ __volatile__ ( + " mov r6, %4 \n\t" /* r6 = volumes + rem */ + " mov %3, %3, LSR #1 \n\t" /* length /= sizeof (int16_t) */ + + " cmp %3, #4 \n\t" /* check for 4+ samples */ + " blt 2f \n\t" + + /* See final case for how the multiplication works */ + + "1: \n\t" + " ldrd r2, [r6], #8 \n\t" /* 4 samples at a time */ + " ldrd r4, [r6], #8 \n\t" + " ldrd r0, [%0] \n\t" + +#ifdef WORDS_BIGENDIAN + " smulwt r2, r2, r0 \n\t" + " smulwb r3, r3, r0 \n\t" + " smulwt r4, r4, r1 \n\t" + " smulwb r5, r5, r1 \n\t" +#else + " smulwb r2, r2, r0 \n\t" + " smulwt r3, r3, r0 \n\t" + " smulwb r4, r4, r1 \n\t" + " smulwt r5, r5, r1 \n\t" +#endif + + " ssat r2, #16, r2 \n\t" + " ssat r3, #16, r3 \n\t" + " ssat r4, #16, r4 \n\t" + " ssat r5, #16, r5 \n\t" + +#ifdef WORDS_BIGENDIAN + " pkhbt r0, r3, r2, LSL #16 \n\t" + " pkhbt r1, r5, r4, LSL #16 \n\t" +#else + " pkhbt r0, r2, r3, LSL #16 \n\t" + " pkhbt r1, r4, r5, LSL #16 \n\t" +#endif + " strd r0, [%0], #8 \n\t" + + MOD_INC() + + " subs %3, %3, #4 \n\t" + " cmp %3, #4 \n\t" + " bge 1b \n\t" + + "2: \n\t" + " cmp %3, #2 \n\t" + " blt 3f \n\t" + + " ldrd r2, [r6], #8 \n\t" /* 2 samples at a time */ + " ldr r0, [%0] \n\t" + +#ifdef WORDS_BIGENDIAN + " smulwt r2, r2, r0 \n\t" + " smulwb r3, r3, r0 \n\t" +#else + " smulwb r2, r2, r0 \n\t" + " smulwt r3, r3, r0 \n\t" +#endif + + " ssat r2, #16, r2 \n\t" + " ssat r3, #16, r3 \n\t" + +#ifdef WORDS_BIGENDIAN + " pkhbt r0, r3, r2, LSL #16 \n\t" +#else + " pkhbt r0, r2, r3, LSL #16 \n\t" +#endif + " str r0, [%0], #4 \n\t" + + MOD_INC() + + " subs %3, %3, #2 \n\t" + + "3: \n\t" /* check for odd # of samples */ + " cmp %3, #1 \n\t" + " bne 4f \n\t" + + " ldr r0, [r6], #4 \n\t" /* r0 = volume */ + " ldrh r2, [%0] \n\t" /* r2 = sample */ + + " smulwb r0, r0, r2 \n\t" /* r0 = (r0 * r2) >> 16 */ + " ssat r0, #16, r0 \n\t" /* r0 = PA_CLAMP(r0, 0x7FFF) */ + + " strh r0, [%0], #2 \n\t" /* sample = r0 */ + + "4: \n\t" + + : "+r" (samples), "+r" (volumes), "+r" (ve), "+r" (length) + : "r" (volumes + ((rem / sizeof(*samples)) % channels)) + : "r6", "r5", "r4", "r3", "r2", "r1", "r0", "cc" + ); +} + +#endif /* defined (__arm__) && defined (HAVE_ARMV6) */ + +void pa_volume_func_init_arm(pa_cpu_arm_flag_t flags) { +#if defined (__arm__) && defined (HAVE_ARMV6) + pa_log_info("Initialising ARM optimized volume functions."); + + if (!_volume_ref) + _volume_ref = pa_get_volume_func(PA_SAMPLE_S16NE); + + pa_set_volume_func(PA_SAMPLE_S16NE, (pa_do_volume_func_t) pa_volume_s16ne_arm); +#endif /* defined (__arm__) && defined (HAVE_ARMV6) */ +} diff --git a/src/pulsecore/svolume_c.c b/src/pulsecore/svolume_c.c new file mode 100644 index 0000000..659b337 --- /dev/null +++ b/src/pulsecore/svolume_c.c @@ -0,0 +1,271 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulsecore/macro.h> +#include <pulsecore/g711.h> +#include <pulsecore/endianmacros.h> + +#include "sample-util.h" + +static void pa_volume_u8_c(uint8_t *samples, const int32_t *volumes, unsigned channels, unsigned length) { + unsigned channel; + + for (channel = 0; length; length--) { + int32_t t = pa_mult_s16_volume(*samples - 0x80, volumes[channel]); + + t = PA_CLAMP_UNLIKELY(t, -0x80, 0x7F); + *samples++ = (uint8_t) (t + 0x80); + + if (PA_UNLIKELY(++channel >= channels)) + channel = 0; + } +} + +static void pa_volume_alaw_c(uint8_t *samples, const int32_t *volumes, unsigned channels, unsigned length) { + unsigned channel; + + for (channel = 0; length; length--) { + int32_t t = pa_mult_s16_volume(st_alaw2linear16(*samples), volumes[channel]); + + t = PA_CLAMP_UNLIKELY(t, -0x8000, 0x7FFF); + *samples++ = (uint8_t) st_13linear2alaw((int16_t) t >> 3); + + if (PA_UNLIKELY(++channel >= channels)) + channel = 0; + } +} + +static void pa_volume_ulaw_c(uint8_t *samples, const int32_t *volumes, unsigned channels, unsigned length) { + unsigned channel; + + for (channel = 0; length; length--) { + int32_t t = pa_mult_s16_volume(st_ulaw2linear16(*samples), volumes[channel]); + + t = PA_CLAMP_UNLIKELY(t, -0x8000, 0x7FFF); + *samples++ = (uint8_t) st_14linear2ulaw((int16_t) t >> 2); + + if (PA_UNLIKELY(++channel >= channels)) + channel = 0; + } +} + +static void pa_volume_s16ne_c(int16_t *samples, const int32_t *volumes, unsigned channels, unsigned length) { + unsigned channel; + + length /= sizeof(int16_t); + + for (channel = 0; length; length--) { + int32_t t = pa_mult_s16_volume(*samples, volumes[channel]); + + t = PA_CLAMP_UNLIKELY(t, -0x8000, 0x7FFF); + *samples++ = (int16_t) t; + + if (PA_UNLIKELY(++channel >= channels)) + channel = 0; + } +} + +static void pa_volume_s16re_c(int16_t *samples, const int32_t *volumes, unsigned channels, unsigned length) { + unsigned channel; + + length /= sizeof(int16_t); + + for (channel = 0; length; length--) { + int32_t t = pa_mult_s16_volume(PA_INT16_SWAP(*samples), volumes[channel]); + + t = PA_CLAMP_UNLIKELY(t, -0x8000, 0x7FFF); + *samples++ = PA_INT16_SWAP((int16_t) t); + + if (PA_UNLIKELY(++channel >= channels)) + channel = 0; + } +} + +static void pa_volume_float32ne_c(float *samples, const float *volumes, unsigned channels, unsigned length) { + unsigned channel; + + length /= sizeof(float); + + for (channel = 0; length; length--) { + *samples++ *= volumes[channel]; + + if (PA_UNLIKELY(++channel >= channels)) + channel = 0; + } +} + +static void pa_volume_float32re_c(float *samples, const float *volumes, unsigned channels, unsigned length) { + unsigned channel; + + length /= sizeof(float); + + for (channel = 0; length; length--) { + float t; + + t = PA_READ_FLOAT32RE(samples); + t *= volumes[channel]; + PA_WRITE_FLOAT32RE(samples++, t); + + if (PA_UNLIKELY(++channel >= channels)) + channel = 0; + } +} + +static void pa_volume_s32ne_c(int32_t *samples, const int32_t *volumes, unsigned channels, unsigned length) { + unsigned channel; + + length /= sizeof(int32_t); + + for (channel = 0; length; length--) { + int64_t t; + + t = (int64_t)(*samples); + t = (t * volumes[channel]) >> 16; + t = PA_CLAMP_UNLIKELY(t, -0x80000000LL, 0x7FFFFFFFLL); + *samples++ = (int32_t) t; + + if (PA_UNLIKELY(++channel >= channels)) + channel = 0; + } +} + +static void pa_volume_s32re_c(int32_t *samples, const int32_t *volumes, unsigned channels, unsigned length) { + unsigned channel; + + length /= sizeof(int32_t); + + for (channel = 0; length; length--) { + int64_t t; + + t = (int64_t) PA_INT32_SWAP(*samples); + t = (t * volumes[channel]) >> 16; + t = PA_CLAMP_UNLIKELY(t, -0x80000000LL, 0x7FFFFFFFLL); + *samples++ = PA_INT32_SWAP((int32_t) t); + + if (PA_UNLIKELY(++channel >= channels)) + channel = 0; + } +} + +static void pa_volume_s24ne_c(uint8_t *samples, const int32_t *volumes, unsigned channels, unsigned length) { + unsigned channel; + uint8_t *e; + + e = samples + length; + + for (channel = 0; samples < e; samples += 3) { + int64_t t; + + t = (int64_t)((int32_t) (PA_READ24NE(samples) << 8)); + t = (t * volumes[channel]) >> 16; + t = PA_CLAMP_UNLIKELY(t, -0x80000000LL, 0x7FFFFFFFLL); + PA_WRITE24NE(samples, ((uint32_t) (int32_t) t) >> 8); + + if (PA_UNLIKELY(++channel >= channels)) + channel = 0; + } +} + +static void pa_volume_s24re_c(uint8_t *samples, const int32_t *volumes, unsigned channels, unsigned length) { + unsigned channel; + uint8_t *e; + + e = samples + length; + + for (channel = 0; samples < e; samples += 3) { + int64_t t; + + t = (int64_t)((int32_t) (PA_READ24RE(samples) << 8)); + t = (t * volumes[channel]) >> 16; + t = PA_CLAMP_UNLIKELY(t, -0x80000000LL, 0x7FFFFFFFLL); + PA_WRITE24RE(samples, ((uint32_t) (int32_t) t) >> 8); + + if (PA_UNLIKELY(++channel >= channels)) + channel = 0; + } +} + +static void pa_volume_s24_32ne_c(uint32_t *samples, const int32_t *volumes, unsigned channels, unsigned length) { + unsigned channel; + + length /= sizeof(uint32_t); + + for (channel = 0; length; length--) { + int64_t t; + + t = (int64_t) ((int32_t) (*samples << 8)); + t = (t * volumes[channel]) >> 16; + t = PA_CLAMP_UNLIKELY(t, -0x80000000LL, 0x7FFFFFFFLL); + *samples++ = ((uint32_t) ((int32_t) t)) >> 8; + + if (PA_UNLIKELY(++channel >= channels)) + channel = 0; + } +} + +static void pa_volume_s24_32re_c(uint32_t *samples, const int32_t *volumes, unsigned channels, unsigned length) { + unsigned channel; + + length /= sizeof(uint32_t); + + for (channel = 0; length; length--) { + int64_t t; + + t = (int64_t) ((int32_t) (PA_UINT32_SWAP(*samples) << 8)); + t = (t * volumes[channel]) >> 16; + t = PA_CLAMP_UNLIKELY(t, -0x80000000LL, 0x7FFFFFFFLL); + *samples++ = PA_UINT32_SWAP(((uint32_t) ((int32_t) t)) >> 8); + + if (PA_UNLIKELY(++channel >= channels)) + channel = 0; + } +} + +static pa_do_volume_func_t do_volume_table[] = { + [PA_SAMPLE_U8] = (pa_do_volume_func_t) pa_volume_u8_c, + [PA_SAMPLE_ALAW] = (pa_do_volume_func_t) pa_volume_alaw_c, + [PA_SAMPLE_ULAW] = (pa_do_volume_func_t) pa_volume_ulaw_c, + [PA_SAMPLE_S16NE] = (pa_do_volume_func_t) pa_volume_s16ne_c, + [PA_SAMPLE_S16RE] = (pa_do_volume_func_t) pa_volume_s16re_c, + [PA_SAMPLE_FLOAT32NE] = (pa_do_volume_func_t) pa_volume_float32ne_c, + [PA_SAMPLE_FLOAT32RE] = (pa_do_volume_func_t) pa_volume_float32re_c, + [PA_SAMPLE_S32NE] = (pa_do_volume_func_t) pa_volume_s32ne_c, + [PA_SAMPLE_S32RE] = (pa_do_volume_func_t) pa_volume_s32re_c, + [PA_SAMPLE_S24NE] = (pa_do_volume_func_t) pa_volume_s24ne_c, + [PA_SAMPLE_S24RE] = (pa_do_volume_func_t) pa_volume_s24re_c, + [PA_SAMPLE_S24_32NE] = (pa_do_volume_func_t) pa_volume_s24_32ne_c, + [PA_SAMPLE_S24_32RE] = (pa_do_volume_func_t) pa_volume_s24_32re_c +}; + +pa_do_volume_func_t pa_get_volume_func(pa_sample_format_t f) { + pa_assert(pa_sample_format_valid(f)); + + return do_volume_table[f]; +} + +void pa_set_volume_func(pa_sample_format_t f, pa_do_volume_func_t func) { + pa_assert(pa_sample_format_valid(f)); + + do_volume_table[f] = func; +} diff --git a/src/pulsecore/svolume_mmx.c b/src/pulsecore/svolume_mmx.c new file mode 100644 index 0000000..c5848cc --- /dev/null +++ b/src/pulsecore/svolume_mmx.c @@ -0,0 +1,252 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2009 Wim Taymans <wim.taymans@collabora.co.uk> + + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/rtclock.h> + +#include <pulsecore/random.h> +#include <pulsecore/macro.h> +#include <pulsecore/endianmacros.h> + +#include "cpu-x86.h" + +#include "sample-util.h" + +#if (!defined(__APPLE__) && !defined(__FreeBSD__) && !defined(__FreeBSD_kernel__) && defined (__i386__)) || defined (__amd64__) +/* in s: 2 int16_t samples + * in v: 2 int32_t volumes, fixed point 16:16 + * out s: contains scaled and clamped int16_t samples. + * + * We calculate the high 32 bits of a 32x16 multiply which we then + * clamp to 16 bits. The calculation is: + * + * vl = (v & 0xffff) + * vh = (v >> 16) + * s = ((s * vl) >> 16) + (s * vh); + * + * For the first multiply we have to do a sign correction as we need to + * multiply a signed int with an unsigned int. Hacker's delight 8-3 gives a + * simple formula to correct the sign of the high word after the signed + * multiply. + */ +#define VOLUME_32x16(s,v) /* .. | vh | vl | */ \ + " pxor %%mm4, %%mm4 \n\t" /* .. | 0 | 0 | */ \ + " punpcklwd %%mm4, "#s" \n\t" /* .. | 0 | p0 | */ \ + " pcmpgtw "#v", %%mm4 \n\t" /* .. | 0 | s(vl) | */ \ + " pand "#s", %%mm4 \n\t" /* .. | 0 | (p0) | (vl >> 15) & p */ \ + " movq "#s", %%mm5 \n\t" \ + " pmulhw "#v", "#s" \n\t" /* .. | 0 | vl*p0 | */ \ + " paddw %%mm4, "#s" \n\t" /* .. | 0 | vl*p0 | + sign correct */ \ + " pslld $16, "#s" \n\t" /* .. | vl*p0 | 0 | */ \ + " psrld $16, "#v" \n\t" /* .. | 0 | vh | */ \ + " psrad $16, "#s" \n\t" /* .. | vl*p0 | sign extend */ \ + " pmaddwd %%mm5, "#v" \n\t" /* .. | p0 * vh | */ \ + " paddd "#s", "#v" \n\t" /* .. | p0 * v0 | */ \ + " packssdw "#v", "#v" \n\t" /* .. | p1*v1 | p0*v0 | */ + +/* approximately advances %3 = (%3 + a) % b. This function requires that + * a <= b. */ +#define MOD_ADD(a,b) \ + " add "#a", %3 \n\t" \ + " mov %3, %4 \n\t" \ + " sub "#b", %4 \n\t" \ + " cmovae %4, %3 \n\t" + +/* swap 16 bits */ +#define SWAP_16(s) \ + " movq "#s", %%mm4 \n\t" /* .. | h l | */ \ + " psrlw $8, %%mm4 \n\t" /* .. | 0 h | */ \ + " psllw $8, "#s" \n\t" /* .. | l 0 | */ \ + " por %%mm4, "#s" \n\t" /* .. | l h | */ + +/* swap 2 registers 16 bits for better pairing */ +#define SWAP_16_2(s1,s2) \ + " movq "#s1", %%mm4 \n\t" /* .. | h l | */ \ + " movq "#s2", %%mm5 \n\t" \ + " psrlw $8, %%mm4 \n\t" /* .. | 0 h | */ \ + " psrlw $8, %%mm5 \n\t" \ + " psllw $8, "#s1" \n\t" /* .. | l 0 | */ \ + " psllw $8, "#s2" \n\t" \ + " por %%mm4, "#s1" \n\t" /* .. | l h | */ \ + " por %%mm5, "#s2" \n\t" + +static void pa_volume_s16ne_mmx(int16_t *samples, const int32_t *volumes, unsigned channels, unsigned length) { + pa_reg_x86 channel, temp; + + /* Channels must be at least 4, and always a multiple of the original number. + * This is also the max amount we overread the volume array, which should + * have enough padding. */ + channels = channels == 3 ? 6 : PA_MAX (4U, channels); + + __asm__ __volatile__ ( + " xor %3, %3 \n\t" + " sar $1, %2 \n\t" /* length /= sizeof (int16_t) */ + + " test $1, %2 \n\t" /* check for odd samples */ + " je 2f \n\t" + + " movd (%q1, %3, 4), %%mm0 \n\t" /* | v0h | v0l | */ + " movw (%0), %w4 \n\t" /* .. | p0 | */ + " movd %4, %%mm1 \n\t" + VOLUME_32x16 (%%mm1, %%mm0) + " movd %%mm0, %4 \n\t" /* .. | p0*v0 | */ + " movw %w4, (%0) \n\t" + " add $2, %0 \n\t" + MOD_ADD ($1, %5) + + "2: \n\t" + " sar $1, %2 \n\t" /* prepare for processing 2 samples at a time */ + " test $1, %2 \n\t" /* check for odd samples */ + " je 4f \n\t" + + "3: \n\t" /* do samples in groups of 2 */ + " movq (%q1, %3, 4), %%mm0 \n\t" /* | v1h | v1l | v0h | v0l | */ + " movd (%0), %%mm1 \n\t" /* .. | p1 | p0 | */ + VOLUME_32x16 (%%mm1, %%mm0) + " movd %%mm0, (%0) \n\t" /* .. | p1*v1 | p0*v0 | */ + " add $4, %0 \n\t" + MOD_ADD ($2, %5) + + "4: \n\t" + " sar $1, %2 \n\t" /* prepare for processing 4 samples at a time */ + " cmp $0, %2 \n\t" + " je 6f \n\t" + + "5: \n\t" /* do samples in groups of 4 */ + " movq (%q1, %3, 4), %%mm0 \n\t" /* | v1h | v1l | v0h | v0l | */ + " movq 8(%q1, %3, 4), %%mm2 \n\t" /* | v3h | v3l | v2h | v2l | */ + " movd (%0), %%mm1 \n\t" /* .. | p1 | p0 | */ + " movd 4(%0), %%mm3 \n\t" /* .. | p3 | p2 | */ + VOLUME_32x16 (%%mm1, %%mm0) + VOLUME_32x16 (%%mm3, %%mm2) + " movd %%mm0, (%0) \n\t" /* .. | p1*v1 | p0*v0 | */ + " movd %%mm2, 4(%0) \n\t" /* .. | p3*v3 | p2*v2 | */ + " add $8, %0 \n\t" + MOD_ADD ($4, %5) + " dec %2 \n\t" + " jne 5b \n\t" + + "6: \n\t" + " emms \n\t" + + : "+r" (samples), "+r" (volumes), "+r" (length), "=&D" (channel), "=&r" (temp) +#if defined (__i386__) + : "m" (channels) +#else + : "r" ((pa_reg_x86)channels) +#endif + : "cc" + ); +} + +static void pa_volume_s16re_mmx(int16_t *samples, const int32_t *volumes, unsigned channels, unsigned length) { + pa_reg_x86 channel, temp; + + /* Channels must be at least 4, and always a multiple of the original number. + * This is also the max amount we overread the volume array, which should + * have enough padding. */ + channels = channels == 3 ? 6 : PA_MAX (4U, channels); + + __asm__ __volatile__ ( + " xor %3, %3 \n\t" + " sar $1, %2 \n\t" /* length /= sizeof (int16_t) */ + " pcmpeqw %%mm6, %%mm6 \n\t" /* .. | ffff | ffff | */ + " pcmpeqw %%mm7, %%mm7 \n\t" /* .. | ffff | ffff | */ + " pslld $16, %%mm6 \n\t" /* .. | ffff | 0 | */ + " psrld $31, %%mm7 \n\t" /* .. | 0 | 1 | */ + + " test $1, %2 \n\t" /* check for odd samples */ + " je 2f \n\t" + + " movd (%q1, %3, 4), %%mm0 \n\t" /* | v0h | v0l | */ + " movw (%0), %w4 \n\t" /* .. | p0 | */ + " rorw $8, %w4 \n\t" + " movd %4, %%mm1 \n\t" + VOLUME_32x16 (%%mm1, %%mm0) + " movd %%mm0, %4 \n\t" /* .. | p0*v0 | */ + " rorw $8, %w4 \n\t" + " movw %w4, (%0) \n\t" + " add $2, %0 \n\t" + MOD_ADD ($1, %5) + + "2: \n\t" + " sar $1, %2 \n\t" /* prepare for processing 2 samples at a time */ + " test $1, %2 \n\t" /* check for odd samples */ + " je 4f \n\t" + + "3: \n\t" /* do samples in groups of 2 */ + " movq (%q1, %3, 4), %%mm0 \n\t" /* | v1h | v1l | v0h | v0l | */ + " movd (%0), %%mm1 \n\t" /* .. | p1 | p0 | */ + SWAP_16 (%%mm1) + VOLUME_32x16 (%%mm1, %%mm0) + SWAP_16 (%%mm0) + " movd %%mm0, (%0) \n\t" /* .. | p1*v1 | p0*v0 | */ + " add $4, %0 \n\t" + MOD_ADD ($2, %5) + + "4: \n\t" + " sar $1, %2 \n\t" /* prepare for processing 4 samples at a time */ + " cmp $0, %2 \n\t" + " je 6f \n\t" + + "5: \n\t" /* do samples in groups of 4 */ + " movq (%q1, %3, 4), %%mm0 \n\t" /* | v1h | v1l | v0h | v0l | */ + " movq 8(%q1, %3, 4), %%mm2 \n\t" /* | v3h | v3l | v2h | v2l | */ + " movd (%0), %%mm1 \n\t" /* .. | p1 | p0 | */ + " movd 4(%0), %%mm3 \n\t" /* .. | p3 | p2 | */ + SWAP_16_2 (%%mm1, %%mm3) + VOLUME_32x16 (%%mm1, %%mm0) + VOLUME_32x16 (%%mm3, %%mm2) + SWAP_16_2 (%%mm0, %%mm2) + " movd %%mm0, (%0) \n\t" /* .. | p1*v1 | p0*v0 | */ + " movd %%mm2, 4(%0) \n\t" /* .. | p3*v3 | p2*v2 | */ + " add $8, %0 \n\t" + MOD_ADD ($4, %5) + " dec %2 \n\t" + " jne 5b \n\t" + + "6: \n\t" + " emms \n\t" + + : "+r" (samples), "+r" (volumes), "+r" (length), "=&D" (channel), "=&r" (temp) +#if defined (__i386__) + : "m" (channels) +#else + : "r" ((pa_reg_x86)channels) +#endif + : "cc" + ); +} + +#endif /* (!defined(__APPLE__) && !defined(__FreeBSD__) && !defined(__FreeBSD_kernel__) && defined (__i386__)) || defined (__amd64__) */ + +void pa_volume_func_init_mmx(pa_cpu_x86_flag_t flags) { +#if (!defined(__APPLE__) && !defined(__FreeBSD__) && !defined(__FreeBSD_kernel__) && defined (__i386__)) || defined (__amd64__) + if ((flags & PA_CPU_X86_MMX) && (flags & PA_CPU_X86_CMOV)) { + pa_log_info("Initialising MMX optimized volume functions."); + + pa_set_volume_func(PA_SAMPLE_S16NE, (pa_do_volume_func_t) pa_volume_s16ne_mmx); + pa_set_volume_func(PA_SAMPLE_S16RE, (pa_do_volume_func_t) pa_volume_s16re_mmx); + } +#endif /* (!defined(__APPLE__) && !defined(__FreeBSD__) && !defined(__FreeBSD_kernel__) && defined (__i386__)) || defined (__amd64__) */ +} diff --git a/src/pulsecore/svolume_orc.c b/src/pulsecore/svolume_orc.c new file mode 100644 index 0000000..730f817 --- /dev/null +++ b/src/pulsecore/svolume_orc.c @@ -0,0 +1,50 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2009 Wim Taymans <wim.taymans@collabora.co.uk> + Copyright 2010 Arun Raghavan <arun.raghavan@collabora.co.uk> + + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "cpu-orc.h" +#include <pulse/rtclock.h> +#include <pulsecore/sample-util.h> +#include <pulsecore/random.h> +#include <pulsecore/svolume-orc-gen.h> + +pa_do_volume_func_t fallback; + +static void +pa_volume_s16ne_orc(int16_t *samples, const int32_t *volumes, unsigned channels, unsigned length) { + if (channels == 2) { + int64_t v = (int64_t)volumes[1] << 32 | volumes[0]; + pa_volume_s16ne_orc_2ch (samples, v, ((length / (sizeof(int16_t))) / 2)); + } else if (channels == 1) + pa_volume_s16ne_orc_1ch (samples, volumes[0], length / (sizeof(int16_t))); + else + fallback(samples, volumes, channels, length); +} + +void pa_volume_func_init_orc(void) { + pa_log_info("Initialising ORC optimized volume functions."); + + fallback = pa_get_volume_func(PA_SAMPLE_S16NE); + pa_set_volume_func(PA_SAMPLE_S16NE, (pa_do_volume_func_t) pa_volume_s16ne_orc); +} diff --git a/src/pulsecore/svolume_sse.c b/src/pulsecore/svolume_sse.c new file mode 100644 index 0000000..222ff18 --- /dev/null +++ b/src/pulsecore/svolume_sse.c @@ -0,0 +1,263 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2009 Wim Taymans <wim.taymans@collabora.co.uk> + + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/rtclock.h> + +#include <pulsecore/random.h> +#include <pulsecore/macro.h> +#include <pulsecore/endianmacros.h> + +#include "cpu-x86.h" + +#include "sample-util.h" + +#if (!defined(__APPLE__) && !defined(__FreeBSD__) && !defined(__FreeBSD_kernel__) && defined (__i386__)) || defined (__amd64__) + +#define VOLUME_32x16(s,v) /* .. | vh | vl | */ \ + " pxor %%xmm4, %%xmm4 \n\t" /* .. | 0 | 0 | */ \ + " punpcklwd %%xmm4, "#s" \n\t" /* .. | 0 | p0 | */ \ + " pcmpgtw "#s", %%xmm4 \n\t" /* .. | 0 | s(p0) | */ \ + " pand "#v", %%xmm4 \n\t" /* .. | 0 | (vl) | */ \ + " movdqa "#s", %%xmm5 \n\t" \ + " pmulhuw "#v", "#s" \n\t" /* .. | 0 | vl*p0 | */ \ + " psubd %%xmm4, "#s" \n\t" /* .. | 0 | vl*p0 | + sign correct */ \ + " psrld $16, "#v" \n\t" /* .. | 0 | vh | */ \ + " pmaddwd %%xmm5, "#v" \n\t" /* .. | p0 * vh | */ \ + " paddd "#s", "#v" \n\t" /* .. | p0 * v0 | */ \ + " packssdw "#v", "#v" \n\t" /* .. | p1*v1 | p0*v0 | */ + +#define MOD_ADD(a,b) \ + " add "#a", %3 \n\t" /* channel += inc */ \ + " mov %3, %4 \n\t" \ + " sub "#b", %4 \n\t" /* tmp = channel - channels */ \ + " cmovae %4, %3 \n\t" /* if (tmp >= 0) channel = tmp */ + +/* swap 16 bits */ +#define SWAP_16(s) \ + " movdqa "#s", %%xmm4 \n\t" /* .. | h l | */ \ + " psrlw $8, %%xmm4 \n\t" /* .. | 0 h | */ \ + " psllw $8, "#s" \n\t" /* .. | l 0 | */ \ + " por %%xmm4, "#s" \n\t" /* .. | l h | */ + +/* swap 2 registers 16 bits for better pairing */ +#define SWAP_16_2(s1,s2) \ + " movdqa "#s1", %%xmm4 \n\t" /* .. | h l | */ \ + " movdqa "#s2", %%xmm5 \n\t" \ + " psrlw $8, %%xmm4 \n\t" /* .. | 0 h | */ \ + " psrlw $8, %%xmm5 \n\t" \ + " psllw $8, "#s1" \n\t" /* .. | l 0 | */ \ + " psllw $8, "#s2" \n\t" \ + " por %%xmm4, "#s1" \n\t" /* .. | l h | */ \ + " por %%xmm5, "#s2" \n\t" + +static int channel_overread_table[8] = {8,8,8,12,8,10,12,14}; + +static void pa_volume_s16ne_sse2(int16_t *samples, const int32_t *volumes, unsigned channels, unsigned length) { + pa_reg_x86 channel, temp; + + /* Channels must be at least 8 and always a multiple of the original number. + * This is also the max amount we overread the volume array, which should + * have enough padding. */ + if (channels < 8) + channels = channel_overread_table[channels]; + + __asm__ __volatile__ ( + " xor %3, %3 \n\t" + " sar $1, %2 \n\t" /* length /= sizeof (int16_t) */ + + " test $1, %2 \n\t" /* check for odd samples */ + " je 2f \n\t" + + " movd (%q1, %3, 4), %%xmm0 \n\t" /* | v0h | v0l | */ + " movw (%0), %w4 \n\t" /* .. | p0 | */ + " movd %4, %%xmm1 \n\t" + VOLUME_32x16 (%%xmm1, %%xmm0) + " movd %%xmm0, %4 \n\t" /* .. | p0*v0 | */ + " movw %w4, (%0) \n\t" + " add $2, %0 \n\t" + MOD_ADD ($1, %5) + + "2: \n\t" + " sar $1, %2 \n\t" /* prepare for processing 2 samples at a time */ + " test $1, %2 \n\t" + " je 4f \n\t" + + "3: \n\t" /* do samples in groups of 2 */ + " movq (%q1, %3, 4), %%xmm0 \n\t" /* | v1h | v1l | v0h | v0l | */ + " movd (%0), %%xmm1 \n\t" /* .. | p1 | p0 | */ + VOLUME_32x16 (%%xmm1, %%xmm0) + " movd %%xmm0, (%0) \n\t" /* .. | p1*v1 | p0*v0 | */ + " add $4, %0 \n\t" + MOD_ADD ($2, %5) + + "4: \n\t" + " sar $1, %2 \n\t" /* prepare for processing 4 samples at a time */ + " test $1, %2 \n\t" + " je 6f \n\t" + + /* FIXME, we can do aligned access of the volume values if we can guarantee + * that the array is 16 bytes aligned, we probably have to do the odd values + * after this then. */ + "5: \n\t" /* do samples in groups of 4 */ + " movdqu (%q1, %3, 4), %%xmm0 \n\t" /* | v3h | v3l .. v0h | v0l | */ + " movq (%0), %%xmm1 \n\t" /* .. | p3 .. p0 | */ + VOLUME_32x16 (%%xmm1, %%xmm0) + " movq %%xmm0, (%0) \n\t" /* .. | p3*v3 .. p0*v0 | */ + " add $8, %0 \n\t" + MOD_ADD ($4, %5) + + "6: \n\t" + " sar $1, %2 \n\t" /* prepare for processing 8 samples at a time */ + " cmp $0, %2 \n\t" + " je 8f \n\t" + + "7: \n\t" /* do samples in groups of 8 */ + " movdqu (%q1, %3, 4), %%xmm0 \n\t" /* | v3h | v3l .. v0h | v0l | */ + " movdqu 16(%q1, %3, 4), %%xmm2 \n\t" /* | v7h | v7l .. v4h | v4l | */ + " movq (%0), %%xmm1 \n\t" /* .. | p3 .. p0 | */ + " movq 8(%0), %%xmm3 \n\t" /* .. | p7 .. p4 | */ + VOLUME_32x16 (%%xmm1, %%xmm0) + VOLUME_32x16 (%%xmm3, %%xmm2) + " movq %%xmm0, (%0) \n\t" /* .. | p3*v3 .. p0*v0 | */ + " movq %%xmm2, 8(%0) \n\t" /* .. | p7*v7 .. p4*v4 | */ + " add $16, %0 \n\t" + MOD_ADD ($8, %5) + " dec %2 \n\t" + " jne 7b \n\t" + "8: \n\t" + + : "+r" (samples), "+r" (volumes), "+r" (length), "=&D" (channel), "=&r" (temp) +#if defined (__i386__) + : "m" (channels) +#else + : "r" ((pa_reg_x86)channels) +#endif + : "cc" + ); +} + +static void pa_volume_s16re_sse2(int16_t *samples, const int32_t *volumes, unsigned channels, unsigned length) { + pa_reg_x86 channel, temp; + + /* Channels must be at least 8 and always a multiple of the original number. + * This is also the max amount we overread the volume array, which should + * have enough padding. */ + if (channels < 8) + channels = channel_overread_table[channels]; + + __asm__ __volatile__ ( + " xor %3, %3 \n\t" + " sar $1, %2 \n\t" /* length /= sizeof (int16_t) */ + + " test $1, %2 \n\t" /* check for odd samples */ + " je 2f \n\t" + + " movd (%q1, %3, 4), %%xmm0 \n\t" /* | v0h | v0l | */ + " movw (%0), %w4 \n\t" /* .. | p0 | */ + " rorw $8, %w4 \n\t" + " movd %4, %%xmm1 \n\t" + VOLUME_32x16 (%%xmm1, %%xmm0) + " movd %%xmm0, %4 \n\t" /* .. | p0*v0 | */ + " rorw $8, %w4 \n\t" + " movw %w4, (%0) \n\t" + " add $2, %0 \n\t" + MOD_ADD ($1, %5) + + "2: \n\t" + " sar $1, %2 \n\t" /* prepare for processing 2 samples at a time */ + " test $1, %2 \n\t" + " je 4f \n\t" + + "3: \n\t" /* do samples in groups of 2 */ + " movq (%q1, %3, 4), %%xmm0 \n\t" /* | v1h | v1l | v0h | v0l | */ + " movd (%0), %%xmm1 \n\t" /* .. | p1 | p0 | */ + SWAP_16 (%%xmm1) + VOLUME_32x16 (%%xmm1, %%xmm0) + SWAP_16 (%%xmm0) + " movd %%xmm0, (%0) \n\t" /* .. | p1*v1 | p0*v0 | */ + " add $4, %0 \n\t" + MOD_ADD ($2, %5) + + "4: \n\t" + " sar $1, %2 \n\t" /* prepare for processing 4 samples at a time */ + " test $1, %2 \n\t" + " je 6f \n\t" + + /* FIXME, we can do aligned access of the volume values if we can guarantee + * that the array is 16 bytes aligned, we probably have to do the odd values + * after this then. */ + "5: \n\t" /* do samples in groups of 4 */ + " movdqu (%q1, %3, 4), %%xmm0 \n\t" /* | v3h | v3l .. v0h | v0l | */ + " movq (%0), %%xmm1 \n\t" /* .. | p3 .. p0 | */ + SWAP_16 (%%xmm1) + VOLUME_32x16 (%%xmm1, %%xmm0) + SWAP_16 (%%xmm0) + " movq %%xmm0, (%0) \n\t" /* .. | p3*v3 .. p0*v0 | */ + " add $8, %0 \n\t" + MOD_ADD ($4, %5) + + "6: \n\t" + " sar $1, %2 \n\t" /* prepare for processing 8 samples at a time */ + " cmp $0, %2 \n\t" + " je 8f \n\t" + + "7: \n\t" /* do samples in groups of 8 */ + " movdqu (%q1, %3, 4), %%xmm0 \n\t" /* | v3h | v3l .. v0h | v0l | */ + " movdqu 16(%q1, %3, 4), %%xmm2 \n\t" /* | v7h | v7l .. v4h | v4l | */ + " movq (%0), %%xmm1 \n\t" /* .. | p3 .. p0 | */ + " movq 8(%0), %%xmm3 \n\t" /* .. | p7 .. p4 | */ + SWAP_16_2 (%%xmm1, %%xmm3) + VOLUME_32x16 (%%xmm1, %%xmm0) + VOLUME_32x16 (%%xmm3, %%xmm2) + SWAP_16_2 (%%xmm0, %%xmm2) + " movq %%xmm0, (%0) \n\t" /* .. | p3*v3 .. p0*v0 | */ + " movq %%xmm2, 8(%0) \n\t" /* .. | p7*v7 .. p4*v4 | */ + " add $16, %0 \n\t" + MOD_ADD ($8, %5) + " dec %2 \n\t" + " jne 7b \n\t" + "8: \n\t" + + : "+r" (samples), "+r" (volumes), "+r" (length), "=&D" (channel), "=&r" (temp) +#if defined (__i386__) + : "m" (channels) +#else + : "r" ((pa_reg_x86)channels) +#endif + : "cc" + ); +} + +#endif /* (!defined(__APPLE__) && !defined(__FreeBSD__) && !defined(__FreeBSD_kernel__) && defined (__i386__)) || defined (__amd64__) */ + +void pa_volume_func_init_sse(pa_cpu_x86_flag_t flags) { +#if (!defined(__APPLE__) && !defined(__FreeBSD__) && !defined(__FreeBSD_kernel__) && defined (__i386__)) || defined (__amd64__) + if (flags & PA_CPU_X86_SSE2) { + pa_log_info("Initialising SSE2 optimized volume functions."); + + pa_set_volume_func(PA_SAMPLE_S16NE, (pa_do_volume_func_t) pa_volume_s16ne_sse2); + pa_set_volume_func(PA_SAMPLE_S16RE, (pa_do_volume_func_t) pa_volume_s16re_sse2); + } +#endif /* (!defined(__APPLE__) && !defined(__FreeBSD__) && !defined(__FreeBSD_kernel__) && defined (__i386__)) || defined (__amd64__) */ +} diff --git a/src/pulsecore/tagstruct.c b/src/pulsecore/tagstruct.c new file mode 100644 index 0000000..6e9c7d1 --- /dev/null +++ b/src/pulsecore/tagstruct.c @@ -0,0 +1,800 @@ +/*** + 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/time.h> +#include <stdarg.h> + +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif + +#include <pulse/xmalloc.h> + +#include <pulsecore/socket.h> +#include <pulsecore/macro.h> +#include <pulsecore/flist.h> + +#include "tagstruct.h" + +#define MAX_TAG_SIZE (64*1024) +#define MAX_APPENDED_SIZE 128 +#define GROW_TAG_SIZE 100 + +struct pa_tagstruct { + uint8_t *data; + size_t length, allocated; + size_t rindex; + + enum { + PA_TAGSTRUCT_FIXED, /* The tagstruct does not own the data, buffer was provided by caller. */ + PA_TAGSTRUCT_DYNAMIC, /* Buffer owned by tagstruct, data must be freed. */ + PA_TAGSTRUCT_APPENDED, /* Data points to appended buffer, used for small tagstructs. Will change to dynamic if needed. */ + } type; + union { + uint8_t appended[MAX_APPENDED_SIZE]; + } per_type; +}; + +PA_STATIC_FLIST_DECLARE(tagstructs, 0, pa_xfree); + +pa_tagstruct *pa_tagstruct_new(void) { + pa_tagstruct*t; + + if (!(t = pa_flist_pop(PA_STATIC_FLIST_GET(tagstructs)))) + t = pa_xnew(pa_tagstruct, 1); + t->data = t->per_type.appended; + t->allocated = MAX_APPENDED_SIZE; + t->length = t->rindex = 0; + t->type = PA_TAGSTRUCT_APPENDED; + + return t; +} + +pa_tagstruct *pa_tagstruct_new_fixed(const uint8_t* data, size_t length) { + pa_tagstruct*t; + + pa_assert(data && length); + + if (!(t = pa_flist_pop(PA_STATIC_FLIST_GET(tagstructs)))) + t = pa_xnew(pa_tagstruct, 1); + t->data = (uint8_t*) data; + t->allocated = t->length = length; + t->rindex = 0; + t->type = PA_TAGSTRUCT_FIXED; + + return t; +} + +void pa_tagstruct_free(pa_tagstruct*t) { + pa_assert(t); + + if (t->type == PA_TAGSTRUCT_DYNAMIC) + pa_xfree(t->data); + if (pa_flist_push(PA_STATIC_FLIST_GET(tagstructs), t) < 0) + pa_xfree(t); +} + +static inline void extend(pa_tagstruct*t, size_t l) { + pa_assert(t); + pa_assert(t->type != PA_TAGSTRUCT_FIXED); + + if (t->length+l <= t->allocated) + return; + + if (t->type == PA_TAGSTRUCT_DYNAMIC) + t->data = pa_xrealloc(t->data, t->allocated = t->length + l + GROW_TAG_SIZE); + else if (t->type == PA_TAGSTRUCT_APPENDED) { + t->type = PA_TAGSTRUCT_DYNAMIC; + t->data = pa_xmalloc(t->allocated = t->length + l + GROW_TAG_SIZE); + memcpy(t->data, t->per_type.appended, t->length); + } +} + +static void write_u8(pa_tagstruct *t, uint8_t u) { + extend(t, 1); + t->data[t->length++] = u; +} + +static int read_u8(pa_tagstruct *t, uint8_t *u) { + if (t->rindex + 1 > t->length) + return -1; + + *u = t->data[t->rindex++]; + return 0; +} + +static void write_u32(pa_tagstruct *t, uint32_t u) { + extend(t, 4); + u = htonl(u); + memcpy(t->data + t->length, &u, 4); + t->length += 4; +} + +static int read_u32(pa_tagstruct *t, uint32_t *u) { + if (t->rindex + 4 > t->length) + return -1; + + memcpy(u, t->data + t->rindex, 4); + *u = ntohl(*u); + t->rindex += 4; + + return 0; +} + +static void write_u64(pa_tagstruct *t, uint64_t u) { + write_u32(t, u >> 32); + write_u32(t, u); +} + +static int read_u64(pa_tagstruct *t, uint64_t *u) { + uint32_t tmp; + + if (read_u32(t, &tmp) < 0) + return -1; + + *u = ((uint64_t) tmp) << 32; + + if (read_u32(t, &tmp) < 0) + return -1; + + *u |= tmp; + return 0; +} + +static int read_s64(pa_tagstruct *t, int64_t *u) { + uint32_t tmp; + + if (read_u32(t, &tmp) < 0) + return -1; + + *u = (int64_t) (((uint64_t) tmp) << 32); + + if (read_u32(t, &tmp) < 0) + return -1; + + *u |= (int64_t) tmp; + return 0; +} + +static void write_arbitrary(pa_tagstruct *t, const void *p, size_t len) { + extend(t, len); + + if (len > 0) + memcpy(t->data + t->length, p, len); + + t->length += len; +} + +static int read_arbitrary(pa_tagstruct *t, const void **p, size_t length) { + if (t->rindex + length > t->length) + return -1; + + *p = t->data + t->rindex; + t->rindex += length; + return 0; +} + +void pa_tagstruct_puts(pa_tagstruct*t, const char *s) { + size_t l; + pa_assert(t); + + if (s) { + write_u8(t, PA_TAG_STRING); + l = strlen(s)+1; + write_arbitrary(t, s, l); + } else + write_u8(t, PA_TAG_STRING_NULL); +} + +void pa_tagstruct_putu32(pa_tagstruct*t, uint32_t i) { + pa_assert(t); + + write_u8(t, PA_TAG_U32); + write_u32(t, i); +} + +void pa_tagstruct_putu8(pa_tagstruct*t, uint8_t c) { + pa_assert(t); + + write_u8(t, PA_TAG_U8); + write_u8(t, c); +} + +void pa_tagstruct_put_sample_spec(pa_tagstruct *t, const pa_sample_spec *ss) { + pa_assert(t); + pa_assert(ss); + + write_u8(t, PA_TAG_SAMPLE_SPEC); + write_u8(t, ss->format); + write_u8(t, ss->channels); + write_u32(t, ss->rate); +} + +void pa_tagstruct_put_arbitrary(pa_tagstruct *t, const void *p, size_t length) { + pa_assert(t); + pa_assert(p); + + write_u8(t, PA_TAG_ARBITRARY); + write_u32(t, length); + write_arbitrary(t, p, length); +} + +void pa_tagstruct_put_boolean(pa_tagstruct*t, bool b) { + pa_assert(t); + + write_u8(t, b ? PA_TAG_BOOLEAN_TRUE : PA_TAG_BOOLEAN_FALSE); +} + +void pa_tagstruct_put_timeval(pa_tagstruct*t, const struct timeval *tv) { + pa_assert(t); + + write_u8(t, PA_TAG_TIMEVAL); + write_u32(t, tv->tv_sec); + write_u32(t, tv->tv_usec); +} + +void pa_tagstruct_put_usec(pa_tagstruct*t, pa_usec_t u) { + pa_assert(t); + + write_u8(t, PA_TAG_USEC); + write_u64(t, u); +} + +void pa_tagstruct_putu64(pa_tagstruct*t, uint64_t u) { + pa_assert(t); + + write_u8(t, PA_TAG_U64); + write_u64(t, u); +} + +void pa_tagstruct_puts64(pa_tagstruct*t, int64_t u) { + pa_assert(t); + + write_u8(t, PA_TAG_S64); + write_u64(t, u); +} + +void pa_tagstruct_put_channel_map(pa_tagstruct *t, const pa_channel_map *map) { + unsigned i; + + pa_assert(t); + pa_assert(map); + + write_u8(t, PA_TAG_CHANNEL_MAP); + write_u8(t, map->channels); + + for (i = 0; i < map->channels; i ++) + write_u8(t, map->map[i]); +} + +void pa_tagstruct_put_cvolume(pa_tagstruct *t, const pa_cvolume *cvolume) { + unsigned i; + + pa_assert(t); + pa_assert(cvolume); + + write_u8(t, PA_TAG_CVOLUME); + write_u8(t, cvolume->channels); + + for (i = 0; i < cvolume->channels; i ++) + write_u32(t, cvolume->values[i]); +} + +void pa_tagstruct_put_volume(pa_tagstruct *t, pa_volume_t vol) { + pa_assert(t); + + write_u8(t, PA_TAG_VOLUME); + write_u32(t, vol); +} + +void pa_tagstruct_put_proplist(pa_tagstruct *t, const pa_proplist *p) { + void *state = NULL; + pa_assert(t); + pa_assert(p); + + write_u8(t, PA_TAG_PROPLIST); + + for (;;) { + const char *k; + const void *d; + size_t l; + + if (!(k = pa_proplist_iterate(p, &state))) + break; + + pa_tagstruct_puts(t, k); + pa_assert_se(pa_proplist_get(p, k, &d, &l) >= 0); + pa_tagstruct_putu32(t, (uint32_t) l); + pa_tagstruct_put_arbitrary(t, d, l); + } + + pa_tagstruct_puts(t, NULL); +} + +void pa_tagstruct_put_format_info(pa_tagstruct *t, const pa_format_info *f) { + pa_assert(t); + pa_assert(f); + + write_u8(t, PA_TAG_FORMAT_INFO); + pa_tagstruct_putu8(t, (uint8_t) f->encoding); + pa_tagstruct_put_proplist(t, f->plist); +} + +static int read_tag(pa_tagstruct *t, uint8_t type) { + if (t->rindex + 1 > t->length) + return -1; + + if (t->data[t->rindex] != type) + return -1; + + t->rindex++; + + return 0; +} + +int pa_tagstruct_gets(pa_tagstruct*t, const char **s) { + int error = 0; + size_t n; + char *c; + + pa_assert(t); + pa_assert(s); + + if (t->rindex+1 > t->length) + return -1; + + if (t->data[t->rindex] == PA_TAG_STRING_NULL) { + t->rindex++; + *s = NULL; + return 0; + } + + if (read_tag(t, PA_TAG_STRING) < 0) + return -1; + + if (t->rindex + 1 > t->length) + return -1; + + error = 1; + for (n = 0, c = (char*) (t->data + t->rindex); t->rindex + n < t->length; n++, c++) + if (!*c) { + error = 0; + break; + } + + if (error) + return -1; + + *s = (char*) (t->data + t->rindex); + + t->rindex += n + 1; + return 0; +} + +int pa_tagstruct_getu32(pa_tagstruct*t, uint32_t *i) { + pa_assert(t); + pa_assert(i); + + if (read_tag(t, PA_TAG_U32) < 0) + return -1; + + return read_u32(t, i); +} + +int pa_tagstruct_getu8(pa_tagstruct*t, uint8_t *c) { + pa_assert(t); + pa_assert(c); + + if (read_tag(t, PA_TAG_U8) < 0) + return -1; + + return read_u8(t, c); +} + +int pa_tagstruct_get_sample_spec(pa_tagstruct *t, pa_sample_spec *ss) { + uint8_t tmp; + + pa_assert(t); + pa_assert(ss); + + if (read_tag(t, PA_TAG_SAMPLE_SPEC) < 0) + return -1; + + if (read_u8(t, &tmp) < 0) + return -1; + + ss->format = tmp; + + if (read_u8(t, &ss->channels) < 0) + return -1; + + return read_u32(t, &ss->rate); +} + +int pa_tagstruct_get_arbitrary(pa_tagstruct *t, const void **p, size_t length) { + uint32_t len; + + pa_assert(t); + pa_assert(p); + + if (read_tag(t, PA_TAG_ARBITRARY) < 0) + return -1; + + if (read_u32(t, &len) < 0 || len != length) + return -1; + + return read_arbitrary(t, p, length); +} + +int pa_tagstruct_eof(pa_tagstruct*t) { + pa_assert(t); + + return t->rindex >= t->length; +} + +const uint8_t* pa_tagstruct_data(pa_tagstruct*t, size_t *l) { + pa_assert(t); + pa_assert(l); + + *l = t->length; + return t->data; +} + +int pa_tagstruct_get_boolean(pa_tagstruct*t, bool *b) { + pa_assert(t); + pa_assert(b); + + if (t->rindex+1 > t->length) + return -1; + + if (t->data[t->rindex] == PA_TAG_BOOLEAN_TRUE) + *b = true; + else if (t->data[t->rindex] == PA_TAG_BOOLEAN_FALSE) + *b = false; + else + return -1; + + t->rindex +=1; + return 0; +} + +int pa_tagstruct_get_timeval(pa_tagstruct*t, struct timeval *tv) { + uint32_t tmp; + + pa_assert(t); + pa_assert(tv); + + if (read_tag(t, PA_TAG_TIMEVAL) < 0) + return -1; + + if (read_u32(t, &tmp) < 0) + return -1; + + tv->tv_sec = tmp; + + if (read_u32(t, &tmp) < 0) + return -1; + + tv->tv_usec = tmp; + + return 0; +} + +int pa_tagstruct_get_usec(pa_tagstruct*t, pa_usec_t *u) { + pa_assert(t); + pa_assert(u); + + if (read_tag(t, PA_TAG_USEC) < 0) + return -1; + + return read_u64(t, u); +} + +int pa_tagstruct_getu64(pa_tagstruct*t, uint64_t *u) { + pa_assert(t); + pa_assert(u); + + if (read_tag(t, PA_TAG_U64) < 0) + return -1; + + return read_u64(t, u); +} + +int pa_tagstruct_gets64(pa_tagstruct*t, int64_t *u) { + pa_assert(t); + pa_assert(u); + + if (read_tag(t, PA_TAG_S64) < 0) + return -1; + + return read_s64(t, u); +} + +int pa_tagstruct_get_channel_map(pa_tagstruct *t, pa_channel_map *map) { + unsigned i; + + pa_assert(t); + pa_assert(map); + + if (read_tag(t, PA_TAG_CHANNEL_MAP) < 0) + return -1; + + if (read_u8(t, &map->channels) < 0 || map->channels > PA_CHANNELS_MAX) + return -1; + + for (i = 0; i < map->channels; i ++) { + uint8_t tmp; + + if (read_u8(t, &tmp) < 0) + return -1; + + map->map[i] = tmp; + } + + return 0; +} + +int pa_tagstruct_get_cvolume(pa_tagstruct *t, pa_cvolume *cvolume) { + unsigned i; + + pa_assert(t); + pa_assert(cvolume); + + if (read_tag(t, PA_TAG_CVOLUME) < 0) + return -1; + + if (read_u8(t, &cvolume->channels) < 0 || cvolume->channels > PA_CHANNELS_MAX) + return -1; + + for (i = 0; i < cvolume->channels; i ++) { + if (read_u32(t, &cvolume->values[i]) < 0) + return -1; + } + + return 0; +} + +int pa_tagstruct_get_volume(pa_tagstruct*t, pa_volume_t *vol) { + pa_assert(t); + pa_assert(vol); + + if (read_tag(t, PA_TAG_VOLUME) < 0) + return -1; + + return read_u32(t, vol); +} + +int pa_tagstruct_get_proplist(pa_tagstruct *t, pa_proplist *p) { + pa_assert(t); + + if (read_tag(t, PA_TAG_PROPLIST) < 0) + return -1; + + for (;;) { + const char *k; + const void *d; + uint32_t length; + + if (pa_tagstruct_gets(t, &k) < 0) + return -1; + + if (!k) + break; + + if (!pa_proplist_key_valid(k)) + return -1; + + if (pa_tagstruct_getu32(t, &length) < 0) + return -1; + + if (length > MAX_TAG_SIZE) + return -1; + + if (pa_tagstruct_get_arbitrary(t, &d, length) < 0) + return -1; + + if (p) + pa_assert_se(pa_proplist_set(p, k, d, length) >= 0); + } + + return 0; +} + +int pa_tagstruct_get_format_info(pa_tagstruct *t, pa_format_info *f) { + uint8_t encoding; + + pa_assert(t); + pa_assert(f); + + if (read_tag(t, PA_TAG_FORMAT_INFO) < 0) + return -1; + + if (pa_tagstruct_getu8(t, &encoding) < 0) + return -1; + + f->encoding = encoding; + + return pa_tagstruct_get_proplist(t, f->plist); +} + +void pa_tagstruct_put(pa_tagstruct *t, ...) { + va_list va; + pa_assert(t); + + va_start(va, t); + + for (;;) { + int tag = va_arg(va, int); + + if (tag == PA_TAG_INVALID) + break; + + switch (tag) { + case PA_TAG_STRING: + case PA_TAG_STRING_NULL: + pa_tagstruct_puts(t, va_arg(va, char*)); + break; + + case PA_TAG_U32: + pa_tagstruct_putu32(t, va_arg(va, uint32_t)); + break; + + case PA_TAG_U8: + pa_tagstruct_putu8(t, (uint8_t) va_arg(va, int)); + break; + + case PA_TAG_U64: + pa_tagstruct_putu64(t, va_arg(va, uint64_t)); + break; + + case PA_TAG_SAMPLE_SPEC: + pa_tagstruct_put_sample_spec(t, va_arg(va, pa_sample_spec*)); + break; + + case PA_TAG_ARBITRARY: { + void *p = va_arg(va, void*); + size_t size = va_arg(va, size_t); + pa_tagstruct_put_arbitrary(t, p, size); + break; + } + + case PA_TAG_BOOLEAN_TRUE: + case PA_TAG_BOOLEAN_FALSE: + pa_tagstruct_put_boolean(t, va_arg(va, int)); + break; + + case PA_TAG_TIMEVAL: + pa_tagstruct_put_timeval(t, va_arg(va, struct timeval*)); + break; + + case PA_TAG_USEC: + pa_tagstruct_put_usec(t, va_arg(va, pa_usec_t)); + break; + + case PA_TAG_CHANNEL_MAP: + pa_tagstruct_put_channel_map(t, va_arg(va, pa_channel_map *)); + break; + + case PA_TAG_CVOLUME: + pa_tagstruct_put_cvolume(t, va_arg(va, pa_cvolume *)); + break; + + case PA_TAG_VOLUME: + pa_tagstruct_put_volume(t, va_arg(va, pa_volume_t)); + break; + + case PA_TAG_PROPLIST: + pa_tagstruct_put_proplist(t, va_arg(va, pa_proplist *)); + break; + + default: + pa_assert_not_reached(); + } + } + + va_end(va); +} + +int pa_tagstruct_get(pa_tagstruct *t, ...) { + va_list va; + int ret = 0; + + pa_assert(t); + + va_start(va, t); + while (ret == 0) { + int tag = va_arg(va, int); + + if (tag == PA_TAG_INVALID) + break; + + switch (tag) { + case PA_TAG_STRING: + case PA_TAG_STRING_NULL: + ret = pa_tagstruct_gets(t, va_arg(va, const char**)); + break; + + case PA_TAG_U32: + ret = pa_tagstruct_getu32(t, va_arg(va, uint32_t*)); + break; + + case PA_TAG_U8: + ret = pa_tagstruct_getu8(t, va_arg(va, uint8_t*)); + break; + + case PA_TAG_U64: + ret = pa_tagstruct_getu64(t, va_arg(va, uint64_t*)); + break; + + case PA_TAG_SAMPLE_SPEC: + ret = pa_tagstruct_get_sample_spec(t, va_arg(va, pa_sample_spec*)); + break; + + case PA_TAG_ARBITRARY: { + const void **p = va_arg(va, const void**); + size_t size = va_arg(va, size_t); + ret = pa_tagstruct_get_arbitrary(t, p, size); + break; + } + + case PA_TAG_BOOLEAN_TRUE: + case PA_TAG_BOOLEAN_FALSE: + ret = pa_tagstruct_get_boolean(t, va_arg(va, bool*)); + break; + + case PA_TAG_TIMEVAL: + ret = pa_tagstruct_get_timeval(t, va_arg(va, struct timeval*)); + break; + + case PA_TAG_USEC: + ret = pa_tagstruct_get_usec(t, va_arg(va, pa_usec_t*)); + break; + + case PA_TAG_CHANNEL_MAP: + ret = pa_tagstruct_get_channel_map(t, va_arg(va, pa_channel_map *)); + break; + + case PA_TAG_CVOLUME: + ret = pa_tagstruct_get_cvolume(t, va_arg(va, pa_cvolume *)); + break; + + case PA_TAG_VOLUME: + ret = pa_tagstruct_get_volume(t, va_arg(va, pa_volume_t *)); + break; + + case PA_TAG_PROPLIST: + ret = pa_tagstruct_get_proplist(t, va_arg(va, pa_proplist *)); + break; + + default: + pa_assert_not_reached(); + } + + } + + va_end(va); + return ret; +} diff --git a/src/pulsecore/tagstruct.h b/src/pulsecore/tagstruct.h new file mode 100644 index 0000000..dcb51cb --- /dev/null +++ b/src/pulsecore/tagstruct.h @@ -0,0 +1,106 @@ +#ifndef footagstructhfoo +#define footagstructhfoo + +/*** + 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <inttypes.h> +#include <sys/types.h> +#include <sys/time.h> + +#include <pulse/sample.h> +#include <pulse/format.h> +#include <pulse/channelmap.h> +#include <pulse/volume.h> +#include <pulse/proplist.h> + +#include <pulsecore/macro.h> + +typedef struct pa_tagstruct pa_tagstruct; + +/* Due to a stupid design flaw, proplists may only be at the END of a + * packet or not before a STRING! Don't forget that! We can't really + * fix this without breaking compat. */ + +enum { + PA_TAG_INVALID = 0, + PA_TAG_STRING = 't', + PA_TAG_STRING_NULL = 'N', + PA_TAG_U32 = 'L', + PA_TAG_U8 = 'B', + PA_TAG_U64 = 'R', + PA_TAG_S64 = 'r', + PA_TAG_SAMPLE_SPEC = 'a', + PA_TAG_ARBITRARY = 'x', + PA_TAG_BOOLEAN_TRUE = '1', + PA_TAG_BOOLEAN_FALSE = '0', + PA_TAG_BOOLEAN = PA_TAG_BOOLEAN_TRUE, + PA_TAG_TIMEVAL = 'T', + PA_TAG_USEC = 'U' /* 64bit unsigned */, + PA_TAG_CHANNEL_MAP = 'm', + PA_TAG_CVOLUME = 'v', + PA_TAG_PROPLIST = 'P', + PA_TAG_VOLUME = 'V', + PA_TAG_FORMAT_INFO = 'f', +}; + +pa_tagstruct *pa_tagstruct_new(void); +pa_tagstruct *pa_tagstruct_new_fixed(const uint8_t* data, size_t length); +void pa_tagstruct_free(pa_tagstruct*t); + +int pa_tagstruct_eof(pa_tagstruct*t); +const uint8_t* pa_tagstruct_data(pa_tagstruct*t, size_t *l); + +void pa_tagstruct_put(pa_tagstruct *t, ...); + +void pa_tagstruct_puts(pa_tagstruct*t, const char *s); +void pa_tagstruct_putu8(pa_tagstruct*t, uint8_t c); +void pa_tagstruct_putu32(pa_tagstruct*t, uint32_t i); +void pa_tagstruct_putu64(pa_tagstruct*t, uint64_t i); +void pa_tagstruct_puts64(pa_tagstruct*t, int64_t i); +void pa_tagstruct_put_sample_spec(pa_tagstruct *t, const pa_sample_spec *ss); +void pa_tagstruct_put_arbitrary(pa_tagstruct*t, const void *p, size_t length); +void pa_tagstruct_put_boolean(pa_tagstruct*t, bool b); +void pa_tagstruct_put_timeval(pa_tagstruct*t, const struct timeval *tv); +void pa_tagstruct_put_usec(pa_tagstruct*t, pa_usec_t u); +void pa_tagstruct_put_channel_map(pa_tagstruct *t, const pa_channel_map *map); +void pa_tagstruct_put_cvolume(pa_tagstruct *t, const pa_cvolume *cvolume); +void pa_tagstruct_put_proplist(pa_tagstruct *t, const pa_proplist *p); +void pa_tagstruct_put_volume(pa_tagstruct *t, pa_volume_t volume); +void pa_tagstruct_put_format_info(pa_tagstruct *t, const pa_format_info *f); + +int pa_tagstruct_get(pa_tagstruct *t, ...); + +int pa_tagstruct_gets(pa_tagstruct*t, const char **s); +int pa_tagstruct_getu8(pa_tagstruct*t, uint8_t *c); +int pa_tagstruct_getu32(pa_tagstruct*t, uint32_t *i); +int pa_tagstruct_getu64(pa_tagstruct*t, uint64_t *i); +int pa_tagstruct_gets64(pa_tagstruct*t, int64_t *i); +int pa_tagstruct_get_sample_spec(pa_tagstruct *t, pa_sample_spec *ss); +int pa_tagstruct_get_arbitrary(pa_tagstruct *t, const void **p, size_t length); +int pa_tagstruct_get_boolean(pa_tagstruct *t, bool *b); +int pa_tagstruct_get_timeval(pa_tagstruct*t, struct timeval *tv); +int pa_tagstruct_get_usec(pa_tagstruct*t, pa_usec_t *u); +int pa_tagstruct_get_channel_map(pa_tagstruct *t, pa_channel_map *map); +int pa_tagstruct_get_cvolume(pa_tagstruct *t, pa_cvolume *v); +int pa_tagstruct_get_proplist(pa_tagstruct *t, pa_proplist *p); +int pa_tagstruct_get_volume(pa_tagstruct *t, pa_volume_t *v); +int pa_tagstruct_get_format_info(pa_tagstruct *t, pa_format_info *f); + +#endif diff --git a/src/pulsecore/thread-mq.c b/src/pulsecore/thread-mq.c new file mode 100644 index 0000000..ab3863b --- /dev/null +++ b/src/pulsecore/thread-mq.c @@ -0,0 +1,223 @@ +/*** + 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <unistd.h> +#include <errno.h> + +#include <pulsecore/thread.h> +#include <pulsecore/semaphore.h> +#include <pulsecore/macro.h> + +#include <pulse/mainloop-api.h> + +#include "thread-mq.h" + +PA_STATIC_TLS_DECLARE_NO_FREE(thread_mq); + +static void asyncmsgq_read_cb(pa_mainloop_api *api, pa_io_event *e, int fd, pa_io_event_flags_t events, void *userdata) { + pa_thread_mq *q = userdata; + pa_asyncmsgq *aq; + + pa_assert(events == PA_IO_EVENT_INPUT); + + if (pa_asyncmsgq_read_fd(q->outq) == fd) + pa_asyncmsgq_ref(aq = q->outq); + else if (pa_asyncmsgq_read_fd(q->inq) == fd) + pa_asyncmsgq_ref(aq = q->inq); + else + pa_assert_not_reached(); + + pa_asyncmsgq_read_after_poll(aq); + + for (;;) { + pa_msgobject *object; + int code; + void *data; + int64_t offset; + pa_memchunk chunk; + + /* Check whether there is a message for us to process */ + while (pa_asyncmsgq_get(aq, &object, &code, &data, &offset, &chunk, 0) >= 0) { + int ret; + + if (!object && code == PA_MESSAGE_SHUTDOWN) { + pa_asyncmsgq_done(aq, 0); + api->quit(api, 0); + break; + } + + ret = pa_asyncmsgq_dispatch(object, code, data, offset, &chunk); + pa_asyncmsgq_done(aq, ret); + } + + if (pa_asyncmsgq_read_before_poll(aq) == 0) + break; + } + + pa_asyncmsgq_unref(aq); +} + +static void asyncmsgq_write_inq_cb(pa_mainloop_api *api, pa_io_event *e, int fd, pa_io_event_flags_t events, void *userdata) { + pa_thread_mq *q = userdata; + + pa_assert(pa_asyncmsgq_write_fd(q->inq) == fd); + pa_assert(events == PA_IO_EVENT_INPUT); + + pa_asyncmsgq_write_after_poll(q->inq); + pa_asyncmsgq_write_before_poll(q->inq); +} + +static void asyncmsgq_write_outq_cb(pa_mainloop_api *api, pa_io_event *e, int fd, pa_io_event_flags_t events, void *userdata) { + pa_thread_mq *q = userdata; + + pa_assert(pa_asyncmsgq_write_fd(q->outq) == fd); + pa_assert(events == PA_IO_EVENT_INPUT); + + pa_asyncmsgq_write_after_poll(q->outq); + pa_asyncmsgq_write_before_poll(q->outq); +} + +int pa_thread_mq_init_thread_mainloop(pa_thread_mq *q, pa_mainloop_api *main_mainloop, pa_mainloop_api *thread_mainloop) { + pa_assert(q); + pa_assert(main_mainloop); + pa_assert(thread_mainloop); + + pa_zero(*q); + + q->inq = pa_asyncmsgq_new(0); + if (!q->inq) + goto fail; + + q->outq = pa_asyncmsgq_new(0); + if (!q->outq) + goto fail; + + q->main_mainloop = main_mainloop; + q->thread_mainloop = thread_mainloop; + + pa_assert_se(pa_asyncmsgq_read_before_poll(q->outq) == 0); + pa_asyncmsgq_write_before_poll(q->outq); + pa_assert_se(q->read_main_event = main_mainloop->io_new(main_mainloop, pa_asyncmsgq_read_fd(q->outq), PA_IO_EVENT_INPUT, asyncmsgq_read_cb, q)); + pa_assert_se(q->write_thread_event = thread_mainloop->io_new(thread_mainloop, pa_asyncmsgq_write_fd(q->outq), PA_IO_EVENT_INPUT, asyncmsgq_write_outq_cb, q)); + + pa_asyncmsgq_read_before_poll(q->inq); + pa_asyncmsgq_write_before_poll(q->inq); + pa_assert_se(q->read_thread_event = thread_mainloop->io_new(thread_mainloop, pa_asyncmsgq_read_fd(q->inq), PA_IO_EVENT_INPUT, asyncmsgq_read_cb, q)); + pa_assert_se(q->write_main_event = main_mainloop->io_new(main_mainloop, pa_asyncmsgq_write_fd(q->inq), PA_IO_EVENT_INPUT, asyncmsgq_write_inq_cb, q)); + + return 0; + +fail: + pa_thread_mq_done(q); + + return -1; +} + +int pa_thread_mq_init(pa_thread_mq *q, pa_mainloop_api *mainloop, pa_rtpoll *rtpoll) { + pa_assert(q); + pa_assert(mainloop); + + pa_zero(*q); + + q->main_mainloop = mainloop; + q->thread_mainloop = NULL; + + q->inq = pa_asyncmsgq_new(0); + if (!q->inq) + goto fail; + + q->outq = pa_asyncmsgq_new(0); + if (!q->outq) + goto fail; + + pa_assert_se(pa_asyncmsgq_read_before_poll(q->outq) == 0); + pa_assert_se(q->read_main_event = mainloop->io_new(mainloop, pa_asyncmsgq_read_fd(q->outq), PA_IO_EVENT_INPUT, asyncmsgq_read_cb, q)); + + pa_asyncmsgq_write_before_poll(q->inq); + pa_assert_se(q->write_main_event = mainloop->io_new(mainloop, pa_asyncmsgq_write_fd(q->inq), PA_IO_EVENT_INPUT, asyncmsgq_write_inq_cb, q)); + + pa_rtpoll_item_new_asyncmsgq_read(rtpoll, PA_RTPOLL_EARLY, q->inq); + pa_rtpoll_item_new_asyncmsgq_write(rtpoll, PA_RTPOLL_LATE, q->outq); + + return 0; + +fail: + pa_thread_mq_done(q); + + return -1; +} + +void pa_thread_mq_done(pa_thread_mq *q) { + pa_assert(q); + + /* Since we are called from main context we can be sure that the + * inq is empty. However, the outq might still contain messages + * for the main loop, which we need to dispatch (e.g. release + * msgs, other stuff). Hence do so if we aren't currently + * dispatching anyway. */ + + if (q->outq && !pa_asyncmsgq_dispatching(q->outq)) { + /* Flushing the asyncmsgq can cause arbitrarily callbacks to run, + potentially causing recursion into pa_thread_mq_done again. */ + pa_asyncmsgq *z = q->outq; + pa_asyncmsgq_ref(z); + pa_asyncmsgq_flush(z, true); + pa_asyncmsgq_unref(z); + } + + if (q->main_mainloop) { + if (q->read_main_event) + q->main_mainloop->io_free(q->read_main_event); + if (q->write_main_event) + q->main_mainloop->io_free(q->write_main_event); + q->read_main_event = q->write_main_event = NULL; + } + + if (q->thread_mainloop) { + if (q->read_thread_event) + q->thread_mainloop->io_free(q->read_thread_event); + if (q->write_thread_event) + q->thread_mainloop->io_free(q->write_thread_event); + q->read_thread_event = q->write_thread_event = NULL; + } + + if (q->inq) + pa_asyncmsgq_unref(q->inq); + if (q->outq) + pa_asyncmsgq_unref(q->outq); + q->inq = q->outq = NULL; + + q->main_mainloop = NULL; + q->thread_mainloop = NULL; +} + +void pa_thread_mq_install(pa_thread_mq *q) { + pa_assert(q); + + pa_assert(!(PA_STATIC_TLS_GET(thread_mq))); + PA_STATIC_TLS_SET(thread_mq, q); +} + +pa_thread_mq *pa_thread_mq_get(void) { + return PA_STATIC_TLS_GET(thread_mq); +} diff --git a/src/pulsecore/thread-mq.h b/src/pulsecore/thread-mq.h new file mode 100644 index 0000000..f6daa7f --- /dev/null +++ b/src/pulsecore/thread-mq.h @@ -0,0 +1,57 @@ +#ifndef foopulsethreadmqhfoo +#define foopulsethreadmqhfoo + +/*** + 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulse/mainloop-api.h> +#include <pulsecore/asyncmsgq.h> +#include <pulsecore/rtpoll.h> + +/* Two way communication between a thread and a mainloop. Before the + * thread is started a pa_thread_mq should be initialized and than + * attached to the thread using pa_thread_mq_install(). */ + +typedef struct pa_thread_mq { + pa_mainloop_api *main_mainloop; + pa_mainloop_api *thread_mainloop; + pa_asyncmsgq *inq, *outq; + pa_io_event *read_main_event, *write_main_event; + pa_io_event *read_thread_event, *write_thread_event; +} pa_thread_mq; + +int pa_thread_mq_init(pa_thread_mq *q, pa_mainloop_api *mainloop, pa_rtpoll *rtpoll); +int pa_thread_mq_init_thread_mainloop(pa_thread_mq *q, pa_mainloop_api *main_mainloop, pa_mainloop_api *thread_mainloop); +void pa_thread_mq_done(pa_thread_mq *q); + +/* Install the specified pa_thread_mq object for the current thread */ +void pa_thread_mq_install(pa_thread_mq *q); + +/* Return the pa_thread_mq object that is set for the current thread */ +pa_thread_mq *pa_thread_mq_get(void); + +/* Verify that we are in control context (aka 'main context'). */ +#define pa_assert_ctl_context(s) \ + pa_assert(!pa_thread_mq_get()) + +/* Verify that we are in IO context (aka 'thread context'). */ +#define pa_assert_io_context(s) \ + pa_assert(pa_thread_mq_get()) + +#endif diff --git a/src/pulsecore/thread-posix.c b/src/pulsecore/thread-posix.c new file mode 100644 index 0000000..62b0ec4 --- /dev/null +++ b/src/pulsecore/thread-posix.c @@ -0,0 +1,254 @@ +/*** + This file is part of PulseAudio. + + Copyright 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pthread.h> +#include <sched.h> +#include <errno.h> + +#ifdef __linux__ +#include <sys/prctl.h> +#endif + +#include <pulse/xmalloc.h> +#include <pulsecore/atomic.h> +#include <pulsecore/macro.h> + +#include "thread.h" + +struct pa_thread { + pthread_t id; + pa_thread_func_t thread_func; + void *userdata; + pa_atomic_t running; + bool joined; + char *name; +}; + +struct pa_tls { + pthread_key_t key; +}; + +static void thread_free_cb(void *p) { + pa_thread *t = p; + + pa_assert(t); + + if (!t->thread_func) { + /* This is a foreign thread, we need to free the struct */ + pa_xfree(t->name); + pa_xfree(t); + } +} + +PA_STATIC_TLS_DECLARE(current_thread, thread_free_cb); + +static void* internal_thread_func(void *userdata) { + pa_thread *t = userdata; + pa_assert(t); + +#ifdef __linux__ + prctl(PR_SET_NAME, t->name); +#elif defined(HAVE_PTHREAD_SETNAME_NP) && defined(OS_IS_DARWIN) + pthread_setname_np(t->name); +#endif + + t->id = pthread_self(); + + PA_STATIC_TLS_SET(current_thread, t); + + pa_atomic_inc(&t->running); + t->thread_func(t->userdata); + pa_atomic_sub(&t->running, 2); + + return NULL; +} + +pa_thread* pa_thread_new(const char *name, pa_thread_func_t thread_func, void *userdata) { + pa_thread *t; + + pa_assert(thread_func); + + t = pa_xnew0(pa_thread, 1); + t->name = pa_xstrdup(name); + t->thread_func = thread_func; + t->userdata = userdata; + + if (pthread_create(&t->id, NULL, internal_thread_func, t) < 0) { + pa_xfree(t); + return NULL; + } + + pa_atomic_inc(&t->running); + + return t; +} + +int pa_thread_is_running(pa_thread *t) { + pa_assert(t); + + /* Unfortunately there is no way to tell whether a "foreign" + * thread is still running. See + * http://udrepper.livejournal.com/16844.html for more + * information */ + pa_assert(t->thread_func); + + return pa_atomic_load(&t->running) > 0; +} + +void pa_thread_free(pa_thread *t) { + pa_assert(t); + + pa_thread_join(t); + + pa_xfree(t->name); + pa_xfree(t); +} + +void pa_thread_free_nojoin(pa_thread *t) { + pa_assert(t); + + pa_xfree(t->name); + pa_xfree(t); +} + +int pa_thread_join(pa_thread *t) { + pa_assert(t); + pa_assert(t->thread_func); + + if (t->joined) + return -1; + + t->joined = true; + return pthread_join(t->id, NULL); +} + +pa_thread* pa_thread_self(void) { + pa_thread *t; + + if ((t = PA_STATIC_TLS_GET(current_thread))) + return t; + + /* This is a foreign thread, let's create a pthread structure to + * make sure that we can always return a sensible pointer */ + + t = pa_xnew0(pa_thread, 1); + t->id = pthread_self(); + t->joined = true; + pa_atomic_store(&t->running, 2); + + PA_STATIC_TLS_SET(current_thread, t); + + return t; +} + +void* pa_thread_get_data(pa_thread *t) { + pa_assert(t); + + return t->userdata; +} + +void pa_thread_set_data(pa_thread *t, void *userdata) { + pa_assert(t); + + t->userdata = userdata; +} + +void pa_thread_set_name(pa_thread *t, const char *name) { + pa_assert(t); + + pa_xfree(t->name); + t->name = pa_xstrdup(name); + +#ifdef __linux__ + prctl(PR_SET_NAME, name); +#elif defined(HAVE_PTHREAD_SETNAME_NP) && defined(OS_IS_DARWIN) + pthread_setname_np(name); +#endif +} + +const char *pa_thread_get_name(pa_thread *t) { + pa_assert(t); + +#ifdef __linux__ + if (!t->name) { + t->name = pa_xmalloc(17); + + if (prctl(PR_GET_NAME, t->name) >= 0) + t->name[16] = 0; + else { + pa_xfree(t->name); + t->name = NULL; + } + } +#elif defined(HAVE_PTHREAD_GETNAME_NP) && defined(OS_IS_DARWIN) + if (!t->name) { + t->name = pa_xmalloc0(17); + pthread_getname_np(t->id, t->name, 16); + } +#endif + + return t->name; +} + +void pa_thread_yield(void) { +#ifdef HAVE_PTHREAD_YIELD + pthread_yield(); +#else + pa_assert_se(sched_yield() == 0); +#endif +} + +pa_tls* pa_tls_new(pa_free_cb_t free_cb) { + pa_tls *t; + + t = pa_xnew(pa_tls, 1); + + if (pthread_key_create(&t->key, free_cb) < 0) { + pa_xfree(t); + return NULL; + } + + return t; +} + +void pa_tls_free(pa_tls *t) { + pa_assert(t); + + pa_assert_se(pthread_key_delete(t->key) == 0); + pa_xfree(t); +} + +void *pa_tls_get(pa_tls *t) { + pa_assert(t); + + return pthread_getspecific(t->key); +} + +void *pa_tls_set(pa_tls *t, void *userdata) { + void *r; + + r = pthread_getspecific(t->key); + pa_assert_se(pthread_setspecific(t->key, userdata) == 0); + return r; +} diff --git a/src/pulsecore/thread-win32.c b/src/pulsecore/thread-win32.c new file mode 100644 index 0000000..310c04e --- /dev/null +++ b/src/pulsecore/thread-win32.c @@ -0,0 +1,240 @@ +/*** + This file is part of PulseAudio. + + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> + +#include <windows.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/once.h> + +#include "thread.h" + +struct pa_thread { + HANDLE thread; + pa_thread_func_t thread_func; + void *userdata; +}; + +struct pa_tls { + DWORD index; + pa_free_cb_t free_func; +}; + +struct pa_tls_monitor { + HANDLE thread; + pa_free_cb_t free_func; + void *data; +}; + +static pa_tls *thread_tls; +static pa_once thread_tls_once = PA_ONCE_INIT; +static pa_tls *monitor_tls; + +static void thread_tls_once_func(void) { + thread_tls = pa_tls_new(NULL); + assert(thread_tls); +} + +static DWORD WINAPI internal_thread_func(LPVOID param) { + pa_thread *t = param; + assert(t); + + pa_run_once(&thread_tls_once, thread_tls_once_func); + pa_tls_set(thread_tls, t); + + t->thread_func(t->userdata); + + return 0; +} + +pa_thread* pa_thread_new(const char *name, pa_thread_func_t thread_func, void *userdata) { + pa_thread *t; + DWORD thread_id; + + assert(thread_func); + + t = pa_xnew(pa_thread, 1); + t->thread_func = thread_func; + t->userdata = userdata; + + t->thread = CreateThread(NULL, 0, internal_thread_func, t, 0, &thread_id); + + if (!t->thread) { + pa_xfree(t); + return NULL; + } + + return t; +} + +int pa_thread_is_running(pa_thread *t) { + DWORD code; + + assert(t); + + if (!GetExitCodeThread(t->thread, &code)) + return 0; + + return code == STILL_ACTIVE; +} + +void pa_thread_free(pa_thread *t) { + assert(t); + + pa_thread_join(t); + CloseHandle(t->thread); + pa_xfree(t); +} + +void pa_thread_free_nojoin(pa_thread *t) { + pa_assert(t); + + CloseHandle(t->thread); + pa_xfree(t); +} + +int pa_thread_join(pa_thread *t) { + assert(t); + + if (WaitForSingleObject(t->thread, INFINITE) == WAIT_FAILED) + return -1; + + return 0; +} + +pa_thread* pa_thread_self(void) { + pa_run_once(&thread_tls_once, thread_tls_once_func); + return pa_tls_get(thread_tls); +} + +void* pa_thread_get_data(pa_thread *t) { + pa_assert(t); + + return t->userdata; +} + +void pa_thread_set_data(pa_thread *t, void *userdata) { + pa_assert(t); + + t->userdata = userdata; +} + +void pa_thread_set_name(pa_thread *t, const char *name) { + /* Not implemented */ +} + +const char *pa_thread_get_name(pa_thread *t) { + /* Not implemented */ + return NULL; +} + +void pa_thread_yield(void) { + Sleep(0); +} + +static DWORD WINAPI monitor_thread_func(LPVOID param) { + struct pa_tls_monitor *m = param; + assert(m); + + WaitForSingleObject(m->thread, INFINITE); + + CloseHandle(m->thread); + + m->free_func(m->data); + + pa_xfree(m); + + return 0; +} + +pa_tls* pa_tls_new(pa_free_cb_t free_cb) { + pa_tls *t; + + t = pa_xnew(pa_tls, 1); + t->index = TlsAlloc(); + t->free_func = free_cb; + + if (t->index == TLS_OUT_OF_INDEXES) { + pa_xfree(t); + return NULL; + } + + return t; +} + +void pa_tls_free(pa_tls *t) { + assert(t); + + TlsFree(t->index); + pa_xfree(t); +} + +void *pa_tls_get(pa_tls *t) { + assert(t); + + return TlsGetValue(t->index); +} + +void *pa_tls_set(pa_tls *t, void *userdata) { + void *r; + + assert(t); + + r = TlsGetValue(t->index); + + TlsSetValue(t->index, userdata); + + if (t->free_func) { + struct pa_tls_monitor *m; + + PA_ONCE_BEGIN { + monitor_tls = pa_tls_new(NULL); + assert(monitor_tls); + pa_tls_set(monitor_tls, NULL); + } PA_ONCE_END; + + m = pa_tls_get(monitor_tls); + if (!m) { + HANDLE thread; + + m = pa_xnew(struct pa_tls_monitor, 1); + + DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), + GetCurrentProcess(), &m->thread, 0, FALSE, + DUPLICATE_SAME_ACCESS); + + m->free_func = t->free_func; + + pa_tls_set(monitor_tls, m); + + thread = CreateThread(NULL, 0, monitor_thread_func, m, 0, NULL); + assert(thread); + CloseHandle(thread); + } + + m->data = userdata; + } + + return r; +} diff --git a/src/pulsecore/thread.h b/src/pulsecore/thread.h new file mode 100644 index 0000000..8a14558 --- /dev/null +++ b/src/pulsecore/thread.h @@ -0,0 +1,117 @@ +#ifndef foopulsethreadhfoo +#define foopulsethreadhfoo + +/*** + This file is part of PulseAudio. + + Copyright 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulse/def.h> +#include <pulse/gccmacro.h> + +#include <pulsecore/once.h> +#include <pulsecore/core-util.h> + +#ifndef PACKAGE +#error "Please include config.h before including this file!" +#endif + +typedef struct pa_thread pa_thread; + +typedef void (*pa_thread_func_t) (void *userdata); + +pa_thread* pa_thread_new(const char *name, pa_thread_func_t thread_func, void *userdata); +void pa_thread_free(pa_thread *t); +void pa_thread_free_nojoin(pa_thread *t); +int pa_thread_join(pa_thread *t); +int pa_thread_is_running(pa_thread *t); +pa_thread *pa_thread_self(void); +void pa_thread_yield(void); + +void* pa_thread_get_data(pa_thread *t); +void pa_thread_set_data(pa_thread *t, void *userdata); + +const char *pa_thread_get_name(pa_thread *t); +void pa_thread_set_name(pa_thread *t, const char *name); + +typedef struct pa_tls pa_tls; + +pa_tls* pa_tls_new(pa_free_cb_t free_cb); +void pa_tls_free(pa_tls *t); +void * pa_tls_get(pa_tls *t); +void *pa_tls_set(pa_tls *t, void *userdata); + +#define PA_STATIC_TLS_DECLARE(name, free_cb) \ + static struct { \ + pa_once once; \ + pa_tls *volatile tls; \ + } name##_tls = { \ + .once = PA_ONCE_INIT, \ + .tls = NULL \ + }; \ + static void name##_tls_init(void) { \ + name##_tls.tls = pa_tls_new(free_cb); \ + } \ + static inline pa_tls* name##_tls_obj(void) { \ + pa_run_once(&name##_tls.once, name##_tls_init); \ + return name##_tls.tls; \ + } \ + static void name##_tls_destructor(void) PA_GCC_DESTRUCTOR; \ + static void name##_tls_destructor(void) { \ + static void (*_free_cb)(void*) = free_cb; \ + if (!pa_in_valgrind()) \ + return; \ + if (!name##_tls.tls) \ + return; \ + if (_free_cb) { \ + void *p; \ + if ((p = pa_tls_get(name##_tls.tls))) \ + _free_cb(p); \ + } \ + pa_tls_free(name##_tls.tls); \ + } \ + static inline void* name##_tls_get(void) { \ + return pa_tls_get(name##_tls_obj()); \ + } \ + static inline void* name##_tls_set(void *p) { \ + return pa_tls_set(name##_tls_obj(), p); \ + } \ + struct __stupid_useless_struct_to_allow_trailing_semicolon + +#if defined(SUPPORT_TLS___THREAD) && !defined(OS_IS_WIN32) +/* An optimized version of the above that requires no dynamic + * allocation if the compiler supports __thread */ +#define PA_STATIC_TLS_DECLARE_NO_FREE(name) \ + static __thread void *name##_tls = NULL; \ + static inline void* name##_tls_get(void) { \ + return name##_tls; \ + } \ + static inline void* name##_tls_set(void *p) { \ + void *r = name##_tls; \ + name##_tls = p; \ + return r; \ + } \ + struct __stupid_useless_struct_to_allow_trailing_semicolon +#else +#define PA_STATIC_TLS_DECLARE_NO_FREE(name) PA_STATIC_TLS_DECLARE(name, NULL) +#endif + +#define PA_STATIC_TLS_GET(name) (name##_tls_get()) +#define PA_STATIC_TLS_SET(name, p) (name##_tls_set(p)) + +#endif diff --git a/src/pulsecore/time-smoother.c b/src/pulsecore/time-smoother.c new file mode 100644 index 0000000..2c82ff6 --- /dev/null +++ b/src/pulsecore/time-smoother.c @@ -0,0 +1,524 @@ +/*** + This file is part of PulseAudio. + + Copyright 2007 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <math.h> + +#include <pulse/sample.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/macro.h> + +#include "time-smoother.h" + +#define HISTORY_MAX 64 + +/* + * Implementation of a time smoothing algorithm to synchronize remote + * clocks to a local one. Evens out noise, adjusts to clock skew and + * allows cheap estimations of the remote time while clock updates may + * be seldom and received in non-equidistant intervals. + * + * Basically, we estimate the gradient of received clock samples in a + * certain history window (of size 'history_time') with linear + * regression. With that info we estimate the remote time in + * 'adjust_time' ahead and smoothen our current estimation function + * towards that point with a 3rd order polynomial interpolation with + * fitting derivatives. (more or less a b-spline) + * + * The larger 'history_time' is chosen the better we will suppress + * noise -- but we'll adjust to clock skew slower.. + * + * The larger 'adjust_time' is chosen the smoother our estimation + * function will be -- but we'll adjust to clock skew slower, too. + * + * If 'monotonic' is true the resulting estimation function is + * guaranteed to be monotonic. + */ + +struct pa_smoother { + pa_usec_t adjust_time, history_time; + + pa_usec_t time_offset; + + pa_usec_t px, py; /* Point p, where we want to reach stability */ + double dp; /* Gradient we want at point p */ + + pa_usec_t ex, ey; /* Point e, which we estimated before and need to smooth to */ + double de; /* Gradient we estimated for point e */ + pa_usec_t ry; /* The original y value for ex */ + + /* History of last measurements */ + pa_usec_t history_x[HISTORY_MAX], history_y[HISTORY_MAX]; + unsigned history_idx, n_history; + + /* To even out for monotonicity */ + pa_usec_t last_y, last_x; + + /* Cached parameters for our interpolation polynomial y=ax^3+b^2+cx */ + double a, b, c; + bool abc_valid:1; + + bool monotonic:1; + bool paused:1; + bool smoothing:1; /* If false we skip the polynomial interpolation step */ + + pa_usec_t pause_time; + + unsigned min_history; +}; + +pa_smoother* pa_smoother_new( + pa_usec_t adjust_time, + pa_usec_t history_time, + bool monotonic, + bool smoothing, + unsigned min_history, + pa_usec_t time_offset, + bool paused) { + + pa_smoother *s; + + pa_assert(adjust_time > 0); + pa_assert(history_time > 0); + pa_assert(min_history >= 2); + pa_assert(min_history <= HISTORY_MAX); + + s = pa_xnew(pa_smoother, 1); + s->adjust_time = adjust_time; + s->history_time = history_time; + s->min_history = min_history; + s->monotonic = monotonic; + s->smoothing = smoothing; + + pa_smoother_reset(s, time_offset, paused); + + return s; +} + +void pa_smoother_free(pa_smoother* s) { + pa_assert(s); + + pa_xfree(s); +} + +#define REDUCE(x) \ + do { \ + x = (x) % HISTORY_MAX; \ + } while(false) + +#define REDUCE_INC(x) \ + do { \ + x = ((x)+1) % HISTORY_MAX; \ + } while(false) + +static void drop_old(pa_smoother *s, pa_usec_t x) { + + /* Drop items from history which are too old, but make sure to + * always keep min_history in the history */ + + while (s->n_history > s->min_history) { + + if (s->history_x[s->history_idx] + s->history_time >= x) + /* This item is still valid, and thus all following ones + * are too, so let's quit this loop */ + break; + + /* Item is too old, let's drop it */ + REDUCE_INC(s->history_idx); + + s->n_history --; + } +} + +static void add_to_history(pa_smoother *s, pa_usec_t x, pa_usec_t y) { + unsigned j, i; + pa_assert(s); + + /* First try to update an existing history entry */ + i = s->history_idx; + for (j = s->n_history; j > 0; j--) { + + if (s->history_x[i] == x) { + s->history_y[i] = y; + return; + } + + REDUCE_INC(i); + } + + /* Drop old entries */ + drop_old(s, x); + + /* Calculate position for new entry */ + j = s->history_idx + s->n_history; + REDUCE(j); + + /* Fill in entry */ + s->history_x[j] = x; + s->history_y[j] = y; + + /* Adjust counter */ + s->n_history ++; + + /* And make sure we don't store more entries than fit in */ + if (s->n_history > HISTORY_MAX) { + s->history_idx += s->n_history - HISTORY_MAX; + REDUCE(s->history_idx); + s->n_history = HISTORY_MAX; + } +} + +static double avg_gradient(pa_smoother *s, pa_usec_t x) { + unsigned i, j, c = 0; + int64_t ax = 0, ay = 0, k, t; + double r; + + /* FIXME: Optimization: Jason Newton suggested that instead of + * going through the history on each iteration we could calculated + * avg_gradient() as we go. + * + * Second idea: it might make sense to weight history entries: + * more recent entries should matter more than old ones. */ + + /* Too few measurements, assume gradient of 1 */ + if (s->n_history < s->min_history) + return 1; + + /* First, calculate average of all measurements */ + i = s->history_idx; + for (j = s->n_history; j > 0; j--) { + + ax += (int64_t) s->history_x[i]; + ay += (int64_t) s->history_y[i]; + c++; + + REDUCE_INC(i); + } + + pa_assert(c >= s->min_history); + ax /= c; + ay /= c; + + /* Now, do linear regression */ + k = t = 0; + + i = s->history_idx; + for (j = s->n_history; j > 0; j--) { + int64_t dx, dy; + + dx = (int64_t) s->history_x[i] - ax; + dy = (int64_t) s->history_y[i] - ay; + + k += dx*dy; + t += dx*dx; + + REDUCE_INC(i); + } + + r = (double) k / (double) t; + + return (s->monotonic && r < 0) ? 0 : r; +} + +static void calc_abc(pa_smoother *s) { + pa_usec_t ex, ey, px, py; + int64_t kx, ky; + double de, dp; + + pa_assert(s); + + if (s->abc_valid) + return; + + /* We have two points: (ex|ey) and (px|py) with two gradients at + * these points de and dp. We do a polynomial + * interpolation of degree 3 with these 6 values */ + + ex = s->ex; ey = s->ey; + px = s->px; py = s->py; + de = s->de; dp = s->dp; + + pa_assert(ex < px); + + /* To increase the dynamic range and simplify calculation, we + * move these values to the origin */ + kx = (int64_t) px - (int64_t) ex; + ky = (int64_t) py - (int64_t) ey; + + /* Calculate a, b, c for y=ax^3+bx^2+cx */ + s->c = de; + s->b = (((double) (3*ky)/ (double) kx - dp - (double) (2*de))) / (double) kx; + s->a = (dp/(double) kx - 2*s->b - de/(double) kx) / (double) (3*kx); + + s->abc_valid = true; +} + +static void estimate(pa_smoother *s, pa_usec_t x, pa_usec_t *y, double *deriv) { + pa_assert(s); + pa_assert(y); + + if (x >= s->px) { + /* Linear interpolation right from px */ + int64_t t; + + /* The requested point is right of the point where we wanted + * to be on track again, thus just linearly estimate */ + + t = (int64_t) s->py + (int64_t) llrint(s->dp * (double) (x - s->px)); + + if (t < 0) + t = 0; + + *y = (pa_usec_t) t; + + if (deriv) + *deriv = s->dp; + + } else if (x <= s->ex) { + /* Linear interpolation left from ex */ + int64_t t; + + t = (int64_t) s->ey - (int64_t) llrint(s->de * (double) (s->ex - x)); + + if (t < 0) + t = 0; + + *y = (pa_usec_t) t; + + if (deriv) + *deriv = s->de; + + } else { + /* Spline interpolation between ex and px */ + double tx, ty; + + /* Ok, we're not yet on track, thus let's interpolate, and + * make sure that the first derivative is smooth */ + + calc_abc(s); + + /* Move to origin */ + tx = (double) (x - s->ex); + + /* Horner scheme */ + ty = (tx * (s->c + tx * (s->b + tx * s->a))); + + /* Move back from origin */ + ty += (double) s->ey; + + *y = ty >= 0 ? (pa_usec_t) llrint(ty) : 0; + + /* Horner scheme */ + if (deriv) + *deriv = s->c + (tx * (s->b*2 + tx * s->a*3)); + } + + /* Guarantee monotonicity */ + if (s->monotonic) { + + if (deriv && *deriv < 0) + *deriv = 0; + } +} + +void pa_smoother_put(pa_smoother *s, pa_usec_t x, pa_usec_t y) { + pa_usec_t ney; + double nde; + bool is_new; + + pa_assert(s); + + /* Fix up x value */ + if (s->paused) + x = s->pause_time; + + x = PA_LIKELY(x >= s->time_offset) ? x - s->time_offset : 0; + + is_new = x >= s->ex; + + if (is_new) { + /* First, we calculate the position we'd estimate for x, so that + * we can adjust our position smoothly from this one */ + estimate(s, x, &ney, &nde); + s->ex = x; s->ey = ney; s->de = nde; + s->ry = y; + } + + /* Then, we add the new measurement to our history */ + add_to_history(s, x, y); + + /* And determine the average gradient of the history */ + s->dp = avg_gradient(s, x); + + /* And calculate when we want to be on track again */ + if (s->smoothing) { + s->px = s->ex + s->adjust_time; + s->py = s->ry + (pa_usec_t) llrint(s->dp * (double) s->adjust_time); + } else { + s->px = s->ex; + s->py = s->ry; + } + + s->abc_valid = false; + +#ifdef DEBUG_DATA + pa_log_debug("%p, put(%llu | %llu) = %llu", s, (unsigned long long) (x + s->time_offset), (unsigned long long) x, (unsigned long long) y); +#endif +} + +pa_usec_t pa_smoother_get(pa_smoother *s, pa_usec_t x) { + pa_usec_t y; + + pa_assert(s); + + /* Fix up x value */ + if (s->paused) + x = s->pause_time; + + x = PA_LIKELY(x >= s->time_offset) ? x - s->time_offset : 0; + + if (s->monotonic) + if (x <= s->last_x) + x = s->last_x; + + estimate(s, x, &y, NULL); + + if (s->monotonic) { + + /* Make sure the querier doesn't jump forth and back. */ + s->last_x = x; + + if (y < s->last_y) + y = s->last_y; + else + s->last_y = y; + } + +#ifdef DEBUG_DATA + pa_log_debug("%p, get(%llu | %llu) = %llu", s, (unsigned long long) (x + s->time_offset), (unsigned long long) x, (unsigned long long) y); +#endif + + return y; +} + +void pa_smoother_set_time_offset(pa_smoother *s, pa_usec_t offset) { + pa_assert(s); + + s->time_offset = offset; + +#ifdef DEBUG_DATA + pa_log_debug("offset(%llu)", (unsigned long long) offset); +#endif +} + +void pa_smoother_pause(pa_smoother *s, pa_usec_t x) { + pa_assert(s); + + if (s->paused) + return; + +#ifdef DEBUG_DATA + pa_log_debug("pause(%llu)", (unsigned long long) x); +#endif + + s->paused = true; + s->pause_time = x; +} + +void pa_smoother_resume(pa_smoother *s, pa_usec_t x, bool fix_now) { + pa_assert(s); + + if (!s->paused) + return; + + if (x < s->pause_time) + x = s->pause_time; + +#ifdef DEBUG_DATA + pa_log_debug("resume(%llu)", (unsigned long long) x); +#endif + + s->paused = false; + s->time_offset += x - s->pause_time; + + if (fix_now) + pa_smoother_fix_now(s); +} + +void pa_smoother_fix_now(pa_smoother *s) { + pa_assert(s); + + s->px = s->ex; + s->py = s->ry; +} + +pa_usec_t pa_smoother_translate(pa_smoother *s, pa_usec_t x, pa_usec_t y_delay) { + pa_usec_t ney; + double nde; + + pa_assert(s); + + /* Fix up x value */ + if (s->paused) + x = s->pause_time; + + x = PA_LIKELY(x >= s->time_offset) ? x - s->time_offset : 0; + + estimate(s, x, &ney, &nde); + + /* Play safe and take the larger gradient, so that we wakeup + * earlier when this is used for sleeping */ + if (s->dp > nde) + nde = s->dp; + +#ifdef DEBUG_DATA + pa_log_debug("translate(%llu) = %llu (%0.2f)", (unsigned long long) y_delay, (unsigned long long) ((double) y_delay / nde), nde); +#endif + + return (pa_usec_t) llrint((double) y_delay / nde); +} + +void pa_smoother_reset(pa_smoother *s, pa_usec_t time_offset, bool paused) { + pa_assert(s); + + s->px = s->py = 0; + s->dp = 1; + + s->ex = s->ey = s->ry = 0; + s->de = 1; + + s->history_idx = 0; + s->n_history = 0; + + s->last_y = s->last_x = 0; + + s->abc_valid = false; + + s->paused = paused; + s->time_offset = s->pause_time = time_offset; + +#ifdef DEBUG_DATA + pa_log_debug("reset()"); +#endif +} diff --git a/src/pulsecore/time-smoother.h b/src/pulsecore/time-smoother.h new file mode 100644 index 0000000..49ae08c --- /dev/null +++ b/src/pulsecore/time-smoother.h @@ -0,0 +1,57 @@ +#ifndef foopulsetimesmootherhfoo +#define foopulsetimesmootherhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2007 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulsecore/macro.h> +#include <pulse/sample.h> + +typedef struct pa_smoother pa_smoother; + +pa_smoother* pa_smoother_new( + pa_usec_t x_adjust_time, + pa_usec_t x_history_time, + bool monotonic, + bool smoothing, + unsigned min_history, + pa_usec_t x_offset, + bool paused); + +void pa_smoother_free(pa_smoother* s); + +/* Adds a new value to our dataset. x = local/system time, y = remote time */ +void pa_smoother_put(pa_smoother *s, pa_usec_t x, pa_usec_t y); + +/* Returns an interpolated value based on the dataset. x = local/system time, return value = remote time */ +pa_usec_t pa_smoother_get(pa_smoother *s, pa_usec_t x); + +/* Translates a time span from the remote time domain to the local one. x = local/system time when to estimate, y_delay = remote time span */ +pa_usec_t pa_smoother_translate(pa_smoother *s, pa_usec_t x, pa_usec_t y_delay); + +void pa_smoother_set_time_offset(pa_smoother *s, pa_usec_t x_offset); + +void pa_smoother_pause(pa_smoother *s, pa_usec_t x); +void pa_smoother_resume(pa_smoother *s, pa_usec_t x, bool abrupt); + +void pa_smoother_reset(pa_smoother *s, pa_usec_t time_offset, bool paused); + +void pa_smoother_fix_now(pa_smoother *s); + +#endif diff --git a/src/pulsecore/tokenizer.c b/src/pulsecore/tokenizer.c new file mode 100644 index 0000000..e81dd6b --- /dev/null +++ b/src/pulsecore/tokenizer.c @@ -0,0 +1,82 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <stdlib.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/dynarray.h> +#include <pulsecore/macro.h> + +#include "tokenizer.h" + +static void parse(pa_dynarray*a, const char *s, unsigned args) { + int infty = 0; + const char delimiter[] = " \t\n\r"; + const char *p; + + pa_assert(a); + pa_assert(s); + + if (args == 0) + infty = 1; + + p = s+strspn(s, delimiter); + while (*p && (infty || args >= 2)) { + size_t l = strcspn(p, delimiter); + char *n = pa_xstrndup(p, l); + pa_dynarray_append(a, n); + p += l; + p += strspn(p, delimiter); + args--; + } + + if (args && *p) { + char *n = pa_xstrdup(p); + pa_dynarray_append(a, n); + } +} + +pa_tokenizer* pa_tokenizer_new(const char *s, unsigned args) { + pa_dynarray *a; + + a = pa_dynarray_new(pa_xfree); + parse(a, s, args); + return (pa_tokenizer*) a; +} + +void pa_tokenizer_free(pa_tokenizer *t) { + pa_dynarray *a = (pa_dynarray*) t; + + pa_assert(a); + pa_dynarray_free(a); +} + +const char *pa_tokenizer_get(pa_tokenizer *t, unsigned i) { + pa_dynarray *a = (pa_dynarray*) t; + + pa_assert(a); + + return pa_dynarray_get(a, i); +} diff --git a/src/pulsecore/tokenizer.h b/src/pulsecore/tokenizer.h new file mode 100644 index 0000000..806d40c --- /dev/null +++ b/src/pulsecore/tokenizer.h @@ -0,0 +1,30 @@ +#ifndef footokenizerhfoo +#define footokenizerhfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +typedef struct pa_tokenizer pa_tokenizer; + +pa_tokenizer* pa_tokenizer_new(const char *s, unsigned args); +void pa_tokenizer_free(pa_tokenizer *t); + +const char *pa_tokenizer_get(pa_tokenizer *t, unsigned i); + +#endif diff --git a/src/pulsecore/typedefs.h b/src/pulsecore/typedefs.h new file mode 100644 index 0000000..3652f8f --- /dev/null +++ b/src/pulsecore/typedefs.h @@ -0,0 +1,37 @@ +#ifndef footypedefshfoo +#define footypedefshfoo + +/*** + This file is part of PulseAudio. + + Copyright 2015 Canonical Ltd. + Written by David Henningsson <david.henningsson@canonical.com> + + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +typedef struct pa_card pa_card; +typedef struct pa_card_profile pa_card_profile; +typedef struct pa_client pa_client; +typedef struct pa_core pa_core; +typedef struct pa_device_port pa_device_port; +typedef struct pa_sink pa_sink; +typedef struct pa_sink_volume_change pa_sink_volume_change; +typedef struct pa_sink_input pa_sink_input; +typedef struct pa_source pa_source; +typedef struct pa_source_volume_change pa_source_volume_change; +typedef struct pa_source_output pa_source_output; + + +#endif diff --git a/src/pulsecore/usergroup.c b/src/pulsecore/usergroup.c new file mode 100644 index 0000000..40dad28 --- /dev/null +++ b/src/pulsecore/usergroup.c @@ -0,0 +1,362 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Ted Percival + + 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <sys/types.h> +#include <errno.h> + +#ifdef HAVE_PWD_H +#include <pwd.h> +#endif + +#ifdef HAVE_GRP_H +#include <grp.h> +#endif + +#include <pulse/xmalloc.h> +#include <pulsecore/macro.h> + +#include "usergroup.h" + +#ifdef HAVE_GRP_H + +/* Returns a suitable starting size for a getgrnam_r() or getgrgid_r() buffer, + plus the size of a struct group. + */ +static size_t starting_getgr_buflen(void) { + size_t full_size; + long n; +#ifdef _SC_GETGR_R_SIZE_MAX + n = sysconf(_SC_GETGR_R_SIZE_MAX); +#else + n = -1; +#endif + if (n <= 0) + n = 512; + + full_size = (size_t) n + sizeof(struct group); + + if (full_size < (size_t) n) /* check for integer overflow */ + return (size_t) n; + + return full_size; +} + +/* Returns a suitable starting size for a getpwnam_r() or getpwuid_r() buffer, + plus the size of a struct passwd. + */ +static size_t starting_getpw_buflen(void) { + long n; + size_t full_size; + +#ifdef _SC_GETPW_R_SIZE_MAX + n = sysconf(_SC_GETPW_R_SIZE_MAX); +#else + n = -1; +#endif + if (n <= 0) + n = 512; + + full_size = (size_t) n + sizeof(struct passwd); + + if (full_size < (size_t) n) /* check for integer overflow */ + return (size_t) n; + + return full_size; +} + +/* Given a memory allocation (*bufptr) and its length (*buflenptr), + double the size of the allocation, updating the given buffer and length + arguments. This function should be used in conjunction with the pa_*alloc + and pa_xfree functions. + + Unlike realloc(), this function does *not* retain the original buffer's + contents. + + Returns 0 on success, nonzero on error. The error cause is indicated by + errno. + */ +static int expand_buffer_trashcontents(void **bufptr, size_t *buflenptr) { + size_t newlen; + + if (!bufptr || !*bufptr || !buflenptr) { + errno = EINVAL; + return -1; + } + + newlen = *buflenptr * 2; + + if (newlen < *buflenptr) { + errno = EOVERFLOW; + return -1; + } + + /* Don't bother retaining memory contents; free & alloc anew */ + pa_xfree(*bufptr); + + *bufptr = pa_xmalloc(newlen); + *buflenptr = newlen; + + return 0; +} + +#ifdef HAVE_GETGRGID_R +/* Thread-safe getgrgid() replacement. + Returned value should be freed using pa_getgrgid_free() when the caller is + finished with the returned group data. + + API is the same as getgrgid(), errors are indicated by a NULL return; + consult errno for the error cause (zero it before calling). + */ +struct group *pa_getgrgid_malloc(gid_t gid) { + size_t buflen, getgr_buflen; + int err; + void *buf; + void *getgr_buf; + struct group *result = NULL; + + buflen = starting_getgr_buflen(); + buf = pa_xmalloc(buflen); + + getgr_buflen = buflen - sizeof(struct group); + getgr_buf = (char *)buf + sizeof(struct group); + + while ((err = getgrgid_r(gid, (struct group *)buf, getgr_buf, getgr_buflen, &result)) == ERANGE) { + if (expand_buffer_trashcontents(&buf, &buflen)) + break; + + getgr_buflen = buflen - sizeof(struct group); + getgr_buf = (char *)buf + sizeof(struct group); + } + + if (err || !result) { + result = NULL; + if (buf) { + pa_xfree(buf); + buf = NULL; + } + } + + pa_assert(result == buf || result == NULL); + + return result; +} + +void pa_getgrgid_free(struct group *grp) { + pa_xfree(grp); +} + +#else /* !HAVE_GETGRGID_R */ + +struct group *pa_getgrgid_malloc(gid_t gid) { + return getgrgid(gid); +} + +void pa_getgrgid_free(struct group *grp) { + /* nothing */ + return; +} + +#endif /* !HAVE_GETGRGID_R */ + +#ifdef HAVE_GETGRNAM_R +/* Thread-safe getgrnam() function. + Returned value should be freed using pa_getgrnam_free() when the caller is + finished with the returned group data. + + API is the same as getgrnam(), errors are indicated by a NULL return; + consult errno for the error cause (zero it before calling). + */ +struct group *pa_getgrnam_malloc(const char *name) { + size_t buflen, getgr_buflen; + int err; + void *buf; + void *getgr_buf; + struct group *result = NULL; + + buflen = starting_getgr_buflen(); + buf = pa_xmalloc(buflen); + + getgr_buflen = buflen - sizeof(struct group); + getgr_buf = (char *)buf + sizeof(struct group); + + while ((err = getgrnam_r(name, (struct group *)buf, getgr_buf, getgr_buflen, &result)) == ERANGE) { + if (expand_buffer_trashcontents(&buf, &buflen)) + break; + + getgr_buflen = buflen - sizeof(struct group); + getgr_buf = (char *)buf + sizeof(struct group); + } + + if (err || !result) { + result = NULL; + if (buf) { + pa_xfree(buf); + buf = NULL; + } + } + + pa_assert(result == buf || result == NULL); + + return result; +} + +void pa_getgrnam_free(struct group *group) { + pa_xfree(group); +} + +#else /* !HAVE_GETGRNAM_R */ + +struct group *pa_getgrnam_malloc(const char *name) { + return getgrnam(name); +} + +void pa_getgrnam_free(struct group *group) { + /* nothing */ + return; +} + +#endif /* HAVE_GETGRNAM_R */ + +#endif /* HAVE_GRP_H */ + +#ifdef HAVE_PWD_H + +#ifdef HAVE_GETPWNAM_R +/* Thread-safe getpwnam() function. + Returned value should be freed using pa_getpwnam_free() when the caller is + finished with the returned passwd data. + + API is the same as getpwnam(), errors are indicated by a NULL return; + consult errno for the error cause (zero it before calling). + */ +struct passwd *pa_getpwnam_malloc(const char *name) { + size_t buflen, getpw_buflen; + int err; + void *buf; + void *getpw_buf; + struct passwd *result = NULL; + + buflen = starting_getpw_buflen(); + buf = pa_xmalloc(buflen); + + getpw_buflen = buflen - sizeof(struct passwd); + getpw_buf = (char *)buf + sizeof(struct passwd); + + while ((err = getpwnam_r(name, (struct passwd *)buf, getpw_buf, getpw_buflen, &result)) == ERANGE) { + if (expand_buffer_trashcontents(&buf, &buflen)) + break; + + getpw_buflen = buflen - sizeof(struct passwd); + getpw_buf = (char *)buf + sizeof(struct passwd); + } + + if (err || !result) { + result = NULL; + if (buf) { + pa_xfree(buf); + buf = NULL; + } + } + + pa_assert(result == buf || result == NULL); + + return result; +} + +void pa_getpwnam_free(struct passwd *passwd) { + pa_xfree(passwd); +} + +#else /* !HAVE_GETPWNAM_R */ + +struct passwd *pa_getpwnam_malloc(const char *name) { + return getpwnam(name); +} + +void pa_getpwnam_free(struct passwd *passwd) { + /* nothing */ + return; +} + +#endif /* !HAVE_GETPWNAM_R */ + +#ifdef HAVE_GETPWUID_R +/* Thread-safe getpwuid() function. + Returned value should be freed using pa_getpwuid_free() when the caller is + finished with the returned group data. + + API is the same as getpwuid(), errors are indicated by a NULL return; + consult errno for the error cause (zero it before calling). + */ +struct passwd *pa_getpwuid_malloc(uid_t uid) { + size_t buflen, getpw_buflen; + int err; + void *buf; + void *getpw_buf; + struct passwd *result = NULL; + + buflen = starting_getpw_buflen(); + buf = pa_xmalloc(buflen); + + getpw_buflen = buflen - sizeof(struct passwd); + getpw_buf = (char *)buf + sizeof(struct passwd); + + while ((err = getpwuid_r(uid, (struct passwd *)buf, getpw_buf, getpw_buflen, &result)) == ERANGE) { + if (expand_buffer_trashcontents(&buf, &buflen)) + break; + + getpw_buflen = buflen - sizeof(struct passwd); + getpw_buf = (char *)buf + sizeof(struct passwd); + } + + if (err || !result) { + result = NULL; + if (buf) { + pa_xfree(buf); + buf = NULL; + } + } + + pa_assert(result == buf || result == NULL); + + return result; +} + +void pa_getpwuid_free(struct passwd *passwd) { + pa_xfree(passwd); +} + +#else /* !HAVE_GETPWUID_R */ + +struct passwd *pa_getpwuid_malloc(uid_t uid) { + return getpwuid(uid); +} + +void pa_getpwuid_free(struct passwd *passwd) { + /* nothing */ + return; +} + +#endif /* !HAVE_GETPWUID_R */ + +#endif /* HAVE_PWD_H */ diff --git a/src/pulsecore/usergroup.h b/src/pulsecore/usergroup.h new file mode 100644 index 0000000..0945059 --- /dev/null +++ b/src/pulsecore/usergroup.h @@ -0,0 +1,49 @@ +#ifndef foousergrouphfoo +#define foousergrouphfoo + +/*** + This file is part of PulseAudio. + + Copyright 2009 Ted Percival + + 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.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/types.h> + +#ifndef PACKAGE +#error "Please include config.h before including this file!" +#endif + +#ifdef HAVE_GRP_H + +struct group *pa_getgrgid_malloc(gid_t gid); +void pa_getgrgid_free(struct group *grp); + +struct group *pa_getgrnam_malloc(const char *name); +void pa_getgrnam_free(struct group *group); + +#endif /* HAVE_GRP_H */ + +#ifdef HAVE_PWD_H + +struct passwd *pa_getpwuid_malloc(uid_t uid); +void pa_getpwuid_free(struct passwd *passwd); + +struct passwd *pa_getpwnam_malloc(const char *name); +void pa_getpwnam_free(struct passwd *passwd); + +#endif /* HAVE_PWD_H */ + +#endif /* foousergrouphfoo */ diff --git a/src/pulsecore/winerrno.h b/src/pulsecore/winerrno.h new file mode 100644 index 0000000..052d4de --- /dev/null +++ b/src/pulsecore/winerrno.h @@ -0,0 +1,89 @@ + +/* Generated with: +cat /usr/i686-pc-mingw32/sys-root/mingw/include/winerror.h \ + | awk '/#define WSAE.*WSABASE/{gsub("WSA", ""); print "#undef " $2 "\n#define " $2 " WSA" $2}' \ + | egrep -v 'EINTR|EBADF|EACCES|EFAULT|EINVAL|EMFILE|_QOS|PROVIDER|PROCTABLE' +*/ + +#undef EWOULDBLOCK +#define EWOULDBLOCK WSAEWOULDBLOCK +#undef EINPROGRESS +#define EINPROGRESS WSAEINPROGRESS +#undef EALREADY +#define EALREADY WSAEALREADY +#undef ENOTSOCK +#define ENOTSOCK WSAENOTSOCK +#undef EDESTADDRREQ +#define EDESTADDRREQ WSAEDESTADDRREQ +#undef EMSGSIZE +#define EMSGSIZE WSAEMSGSIZE +#undef EPROTOTYPE +#define EPROTOTYPE WSAEPROTOTYPE +#undef ENOPROTOOPT +#define ENOPROTOOPT WSAENOPROTOOPT +#undef EPROTONOSUPPORT +#define EPROTONOSUPPORT WSAEPROTONOSUPPORT +#undef ESOCKTNOSUPPORT +#define ESOCKTNOSUPPORT WSAESOCKTNOSUPPORT +#undef EOPNOTSUPP +#define EOPNOTSUPP WSAEOPNOTSUPP +#undef EPFNOSUPPORT +#define EPFNOSUPPORT WSAEPFNOSUPPORT +#undef EAFNOSUPPORT +#define EAFNOSUPPORT WSAEAFNOSUPPORT +#undef EADDRINUSE +#define EADDRINUSE WSAEADDRINUSE +#undef EADDRNOTAVAIL +#define EADDRNOTAVAIL WSAEADDRNOTAVAIL +#undef ENETDOWN +#define ENETDOWN WSAENETDOWN +#undef ENETUNREACH +#define ENETUNREACH WSAENETUNREACH +#undef ENETRESET +#define ENETRESET WSAENETRESET +#undef ECONNABORTED +#define ECONNABORTED WSAECONNABORTED +#undef ECONNRESET +#define ECONNRESET WSAECONNRESET +#undef ENOBUFS +#define ENOBUFS WSAENOBUFS +#undef EISCONN +#define EISCONN WSAEISCONN +#undef ENOTCONN +#define ENOTCONN WSAENOTCONN +#undef ESHUTDOWN +#define ESHUTDOWN WSAESHUTDOWN +#undef ETOOMANYREFS +#define ETOOMANYREFS WSAETOOMANYREFS +#undef ETIMEDOUT +#define ETIMEDOUT WSAETIMEDOUT +#undef ECONNREFUSED +#define ECONNREFUSED WSAECONNREFUSED +#undef ELOOP +#define ELOOP WSAELOOP +#undef ENAMETOOLONG +#define ENAMETOOLONG WSAENAMETOOLONG +#undef EHOSTDOWN +#define EHOSTDOWN WSAEHOSTDOWN +#undef EHOSTUNREACH +#define EHOSTUNREACH WSAEHOSTUNREACH +#undef ENOTEMPTY +#define ENOTEMPTY WSAENOTEMPTY +#undef EPROCLIM +#define EPROCLIM WSAEPROCLIM +#undef EUSERS +#define EUSERS WSAEUSERS +#undef EDQUOT +#define EDQUOT WSAEDQUOT +#undef ESTALE +#define ESTALE WSAESTALE +#undef EREMOTE +#define EREMOTE WSAEREMOTE +#undef EDISCON +#define EDISCON WSAEDISCON +#undef ENOMORE +#define ENOMORE WSAENOMORE +#undef ECANCELLED +#define ECANCELLED WSAECANCELLED +#undef EREFUSED +#define EREFUSED WSAEREFUSED diff --git a/src/pulsecore/x11prop.c b/src/pulsecore/x11prop.c new file mode 100644 index 0000000..f3f7737 --- /dev/null +++ b/src/pulsecore/x11prop.c @@ -0,0 +1,145 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> + +#include "x11prop.h" + +#include <pulsecore/macro.h> + +#include <xcb/xproto.h> + +#define PA_XCB_FORMAT 8 + +static xcb_screen_t *screen_of_display(xcb_connection_t *xcb, int screen) { + const xcb_setup_t *s; + xcb_screen_iterator_t iter; + + if ((s = xcb_get_setup(xcb))) { + iter = xcb_setup_roots_iterator(s); + for (; iter.rem; --screen, xcb_screen_next(&iter)) + if (0 == screen) + return iter.data; + } + return NULL; +} + +void pa_x11_set_prop(xcb_connection_t *xcb, int screen, const char *name, const char *data) { + xcb_screen_t *xs; + xcb_intern_atom_reply_t *reply; + + pa_assert(xcb); + pa_assert(name); + pa_assert(data); + + if ((xs = screen_of_display(xcb, screen))) { + reply = xcb_intern_atom_reply(xcb, + xcb_intern_atom(xcb, 0, strlen(name), name), + NULL); + + if (reply) { + xcb_change_property(xcb, XCB_PROP_MODE_REPLACE, xs->root, reply->atom, + XCB_ATOM_STRING, PA_XCB_FORMAT, + (int) strlen(data), (const void*) data); + + free(reply); + } + } +} + +void pa_x11_del_prop(xcb_connection_t *xcb, int screen, const char *name) { + xcb_screen_t *xs; + xcb_intern_atom_reply_t *reply; + + pa_assert(xcb); + pa_assert(name); + + if ((xs = screen_of_display(xcb, screen))) { + reply = xcb_intern_atom_reply(xcb, + xcb_intern_atom(xcb, 0, strlen(name), name), + NULL); + + if (reply) { + xcb_delete_property(xcb, xs->root, reply->atom); + free(reply); + } + } +} + +char* pa_x11_get_prop(xcb_connection_t *xcb, int screen, const char *name, char *p, size_t l) { + char *ret = NULL; + int len; + xcb_get_property_cookie_t req; + xcb_get_property_reply_t* prop = NULL; + xcb_screen_t *xs; + xcb_intern_atom_reply_t *reply; + + pa_assert(xcb); + pa_assert(name); + pa_assert(p); + + xs = screen_of_display(xcb, screen); + /* + * Also try and get the settings from the first screen. + * This allows for e.g. a Media Center to run on screen 1 (e.g. HDMI) and have + * different defaults (e.g. prefer the HDMI sink) than the primary screen 0 + * which uses the Internal Audio sink. + */ + if (!xs && 0 != screen) + xs = screen_of_display(xcb, 0); + + if (xs) { + reply = xcb_intern_atom_reply(xcb, + xcb_intern_atom(xcb, 0, strlen(name), name), + NULL); + + if (!reply) + goto finish; + + req = xcb_get_property(xcb, 0, xs->root, reply->atom, XCB_ATOM_STRING, 0, (uint32_t)(l-1)); + free(reply); + prop = xcb_get_property_reply(xcb, req, NULL); + + if (!prop) + goto finish; + + if (PA_XCB_FORMAT != prop->format) + goto finish; + + len = xcb_get_property_value_length(prop); + if (len < 1 || len >= (int)l) + goto finish; + + memcpy(p, xcb_get_property_value(prop), len); + p[len] = 0; + + ret = p; + } + +finish: + + if (prop) + free(prop); + + return ret; +} diff --git a/src/pulsecore/x11prop.h b/src/pulsecore/x11prop.h new file mode 100644 index 0000000..2b9d3f1 --- /dev/null +++ b/src/pulsecore/x11prop.h @@ -0,0 +1,32 @@ +#ifndef foox11prophfoo +#define foox11prophfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2010 Colin Guthrie + + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/types.h> + +#include <xcb/xcb.h> + +void pa_x11_set_prop(xcb_connection_t *xcb, int screen, const char *name, const char *data); +void pa_x11_del_prop(xcb_connection_t *xcb, int screen, const char *name); +char* pa_x11_get_prop(xcb_connection_t *xcb, int screen, const char *name, char *p, size_t l); + +#endif diff --git a/src/pulsecore/x11wrap.c b/src/pulsecore/x11wrap.c new file mode 100644 index 0000000..0c040cf --- /dev/null +++ b/src/pulsecore/x11wrap.c @@ -0,0 +1,305 @@ +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/llist.h> +#include <pulsecore/log.h> +#include <pulsecore/shared.h> +#include <pulsecore/core-util.h> +#include <pulsecore/macro.h> + +#include "x11wrap.h" + +typedef struct pa_x11_internal pa_x11_internal; + +struct pa_x11_internal { + PA_LLIST_FIELDS(pa_x11_internal); + pa_x11_wrapper *wrapper; + pa_io_event* io_event; + int fd; +}; + +struct pa_x11_wrapper { + PA_REFCNT_DECLARE; + pa_core *core; + + char *property_name; + Display *display; + + pa_defer_event* defer_event; + pa_io_event* io_event; + + PA_LLIST_HEAD(pa_x11_client, clients); + PA_LLIST_HEAD(pa_x11_internal, internals); +}; + +struct pa_x11_client { + PA_LLIST_FIELDS(pa_x11_client); + pa_x11_wrapper *wrapper; + pa_x11_event_cb_t event_cb; + pa_x11_kill_cb_t kill_cb; + void *userdata; +}; + +/* Dispatch all pending X11 events */ +static void work(pa_x11_wrapper *w) { + pa_assert(w); + pa_assert(PA_REFCNT_VALUE(w) >= 1); + + pa_x11_wrapper_ref(w); + + while (XPending(w->display)) { + pa_x11_client *c, *n; + XEvent e; + XNextEvent(w->display, &e); + + for (c = w->clients; c; c = n) { + n = c->next; + + if (c->event_cb) + if (c->event_cb(w, &e, c->userdata) != 0) + break; + } + } + + pa_x11_wrapper_unref(w); +} + +/* IO notification event for the X11 display connection */ +static void display_io_event(pa_mainloop_api *m, pa_io_event *e, int fd, pa_io_event_flags_t f, void *userdata) { + pa_x11_wrapper *w = userdata; + + pa_assert(m); + pa_assert(e); + pa_assert(fd >= 0); + pa_assert(w); + pa_assert(PA_REFCNT_VALUE(w) >= 1); + + work(w); +} + +/* Deferred notification event. Called once each main loop iteration */ +static void defer_event(pa_mainloop_api *m, pa_defer_event *e, void *userdata) { + pa_x11_wrapper *w = userdata; + + pa_assert(m); + pa_assert(e); + pa_assert(w); + pa_assert(PA_REFCNT_VALUE(w) >= 1); + + m->defer_enable(e, 0); + + work(w); +} + +/* IO notification event for X11 internal connections */ +static void internal_io_event(pa_mainloop_api *m, pa_io_event *e, int fd, pa_io_event_flags_t f, void *userdata) { + pa_x11_wrapper *w = userdata; + + pa_assert(m); + pa_assert(e); + pa_assert(fd >= 0); + pa_assert(w); + pa_assert(PA_REFCNT_VALUE(w) >= 1); + + XProcessInternalConnection(w->display, fd); + + work(w); +} + +/* Add a new IO source for the specified X11 internal connection */ +static pa_x11_internal* x11_internal_add(pa_x11_wrapper *w, int fd) { + pa_x11_internal *i; + pa_assert(fd >= 0); + + i = pa_xnew(pa_x11_internal, 1); + i->wrapper = w; + i->io_event = w->core->mainloop->io_new(w->core->mainloop, fd, PA_IO_EVENT_INPUT, internal_io_event, w); + i->fd = fd; + + PA_LLIST_PREPEND(pa_x11_internal, w->internals, i); + return i; +} + +/* Remove an IO source for an X11 internal connection */ +static void x11_internal_remove(pa_x11_wrapper *w, pa_x11_internal *i) { + pa_assert(i); + + PA_LLIST_REMOVE(pa_x11_internal, w->internals, i); + w->core->mainloop->io_free(i->io_event); + pa_xfree(i); +} + +/* Implementation of XConnectionWatchProc */ +static void x11_watch(Display *display, XPointer userdata, int fd, Bool opening, XPointer *watch_data) { + pa_x11_wrapper *w = (pa_x11_wrapper*) userdata; + + pa_assert(display); + pa_assert(w); + pa_assert(fd >= 0); + + if (opening) + *watch_data = (XPointer) x11_internal_add(w, fd); + else + x11_internal_remove(w, (pa_x11_internal*) *watch_data); +} + +static pa_x11_wrapper* x11_wrapper_new(pa_core *c, const char *name, const char *t) { + pa_x11_wrapper*w; + Display *d; + + if (!(d = XOpenDisplay(name))) { + pa_log("XOpenDisplay() failed"); + return NULL; + } + + w = pa_xnew(pa_x11_wrapper, 1); + PA_REFCNT_INIT(w); + w->core = c; + w->property_name = pa_xstrdup(t); + w->display = d; + + PA_LLIST_HEAD_INIT(pa_x11_client, w->clients); + PA_LLIST_HEAD_INIT(pa_x11_internal, w->internals); + + w->defer_event = c->mainloop->defer_new(c->mainloop, defer_event, w); + w->io_event = c->mainloop->io_new(c->mainloop, ConnectionNumber(d), PA_IO_EVENT_INPUT, display_io_event, w); + + XAddConnectionWatch(d, x11_watch, (XPointer) w); + + pa_assert_se(pa_shared_set(c, w->property_name, w) >= 0); + + return w; +} + +static void x11_wrapper_free(pa_x11_wrapper*w) { + pa_assert(w); + + pa_assert_se(pa_shared_remove(w->core, w->property_name) >= 0); + + pa_assert(!w->clients); + + XRemoveConnectionWatch(w->display, x11_watch, (XPointer) w); + XCloseDisplay(w->display); + + w->core->mainloop->io_free(w->io_event); + w->core->mainloop->defer_free(w->defer_event); + + while (w->internals) + x11_internal_remove(w, w->internals); + + pa_xfree(w->property_name); + pa_xfree(w); +} + +pa_x11_wrapper* pa_x11_wrapper_get(pa_core *c, const char *name) { + char t[256]; + pa_x11_wrapper *w; + + pa_core_assert_ref(c); + + pa_snprintf(t, sizeof(t), "x11-wrapper%s%s", name ? "@" : "", name ? name : ""); + + if ((w = pa_shared_get(c, t))) + return pa_x11_wrapper_ref(w); + + return x11_wrapper_new(c, name, t); +} + +pa_x11_wrapper* pa_x11_wrapper_ref(pa_x11_wrapper *w) { + pa_assert(w); + pa_assert(PA_REFCNT_VALUE(w) >= 1); + + PA_REFCNT_INC(w); + return w; +} + +void pa_x11_wrapper_unref(pa_x11_wrapper* w) { + pa_assert(w); + pa_assert(PA_REFCNT_VALUE(w) >= 1); + + if (PA_REFCNT_DEC(w) > 0) + return; + + x11_wrapper_free(w); +} + +Display *pa_x11_wrapper_get_display(pa_x11_wrapper *w) { + pa_assert(w); + pa_assert(PA_REFCNT_VALUE(w) >= 1); + + /* Somebody is using us, schedule a output buffer flush */ + w->core->mainloop->defer_enable(w->defer_event, 1); + + return w->display; +} + +xcb_connection_t *pa_x11_wrapper_get_xcb_connection(pa_x11_wrapper *w) { + return XGetXCBConnection(pa_x11_wrapper_get_display(w)); +} + +void pa_x11_wrapper_kill(pa_x11_wrapper *w) { + pa_x11_client *c, *n; + + pa_assert(w); + + pa_x11_wrapper_ref(w); + + for (c = w->clients; c; c = n) { + n = c->next; + + if (c->kill_cb) + c->kill_cb(w, c->userdata); + } + + pa_x11_wrapper_unref(w); +} + +pa_x11_client* pa_x11_client_new(pa_x11_wrapper *w, pa_x11_event_cb_t event_cb, pa_x11_kill_cb_t kill_cb, void *userdata) { + pa_x11_client *c; + + pa_assert(w); + pa_assert(PA_REFCNT_VALUE(w) >= 1); + + c = pa_xnew(pa_x11_client, 1); + c->wrapper = w; + c->event_cb = event_cb; + c->kill_cb = kill_cb; + c->userdata = userdata; + + PA_LLIST_PREPEND(pa_x11_client, w->clients, c); + + return c; +} + +void pa_x11_client_free(pa_x11_client *c) { + pa_assert(c); + pa_assert(c->wrapper); + pa_assert(PA_REFCNT_VALUE(c->wrapper) >= 1); + + PA_LLIST_REMOVE(pa_x11_client, c->wrapper->clients, c); + pa_xfree(c); +} diff --git a/src/pulsecore/x11wrap.h b/src/pulsecore/x11wrap.h new file mode 100644 index 0000000..0539303 --- /dev/null +++ b/src/pulsecore/x11wrap.h @@ -0,0 +1,60 @@ +#ifndef foox11wraphfoo +#define foox11wraphfoo + +/*** + 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.1 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, see <http://www.gnu.org/licenses/>. +***/ + +#include <X11/Xlib.h> +#include <X11/Xlib-xcb.h> + +#include <pulsecore/core.h> + +typedef struct pa_x11_wrapper pa_x11_wrapper; + +typedef struct pa_x11_client pa_x11_client; + +typedef int (*pa_x11_event_cb_t)(pa_x11_wrapper *w, XEvent *e, void *userdata); +typedef void (*pa_x11_kill_cb_t)(pa_x11_wrapper *w, void *userdata); + +/* Return the X11 wrapper for this core. In case no wrapper was + existent before, allocate a new one */ +pa_x11_wrapper* pa_x11_wrapper_get(pa_core *c, const char *name); + +/* Increase the wrapper's reference count by one */ +pa_x11_wrapper* pa_x11_wrapper_ref(pa_x11_wrapper *w); + +/* Decrease the reference counter of an X11 wrapper object */ +void pa_x11_wrapper_unref(pa_x11_wrapper* w); + +/* Return the X11 display object for this connection */ +Display *pa_x11_wrapper_get_display(pa_x11_wrapper *w); + +/* Return the XCB connection object for this connection */ +xcb_connection_t *pa_x11_wrapper_get_xcb_connection(pa_x11_wrapper *w); + +/* Kill the connection to the X11 display */ +void pa_x11_wrapper_kill(pa_x11_wrapper *w); + +/* Register an X11 client, that is called for each X11 event */ +pa_x11_client* pa_x11_client_new(pa_x11_wrapper *w, pa_x11_event_cb_t event_cb, pa_x11_kill_cb_t kill_cb, void *userdata); + +/* Free an X11 client object */ +void pa_x11_client_free(pa_x11_client *c); + +#endif |