diff options
Diffstat (limited to 'src/pulsecore')
58 files changed, 2826 insertions, 471 deletions
diff --git a/src/pulsecore/aupdate.c b/src/pulsecore/aupdate.c new file mode 100644 index 00000000..56ebb8e5 --- /dev/null +++ b/src/pulsecore/aupdate.c @@ -0,0 +1,129 @@ +/*** + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/xmalloc.h> + +#include <pulsecore/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; +}; + +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 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); + + 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; + } + + return WHICH(n); +} + +void pa_aupdate_write_end(pa_aupdate *a) { + pa_assert(a); + + pa_mutex_unlock(a->write_lock); +} diff --git a/src/pulsecore/aupdate.h b/src/pulsecore/aupdate.h new file mode 100644 index 00000000..072e382d --- /dev/null +++ b/src/pulsecore/aupdate.h @@ -0,0 +1,98 @@ +#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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +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 exisiting. 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') modifes 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) + * } + * + */ + +#endif diff --git a/src/pulsecore/card.c b/src/pulsecore/card.c index 8101a92e..59b8cda6 100644 --- a/src/pulsecore/card.c +++ b/src/pulsecore/card.c @@ -244,19 +244,20 @@ int pa_card_set_profile(pa_card *c, const char *name, pa_bool_t save) { return 0; } -int pa_card_suspend(pa_card *c, pa_bool_t suspend) { +int pa_card_suspend(pa_card *c, pa_bool_t suspend, pa_suspend_cause_t cause) { pa_sink *sink; pa_source *source; uint32_t idx; int ret = 0; pa_assert(c); + pa_assert(cause != 0); for (sink = pa_idxset_first(c->sinks, &idx); sink; sink = pa_idxset_next(c->sinks, &idx)) - ret -= pa_sink_suspend(sink, suspend) < 0; + ret -= pa_sink_suspend(sink, suspend, cause) < 0; for (source = pa_idxset_first(c->sources, &idx); source; source = pa_idxset_next(c->sources, &idx)) - ret -= pa_source_suspend(source, suspend) < 0; + ret -= pa_source_suspend(source, suspend, cause) < 0; return ret; } diff --git a/src/pulsecore/card.h b/src/pulsecore/card.h index 3b7608f6..415ab678 100644 --- a/src/pulsecore/card.h +++ b/src/pulsecore/card.h @@ -99,6 +99,6 @@ void pa_card_free(pa_card *c); int pa_card_set_profile(pa_card *c, const char *name, pa_bool_t save); -int pa_card_suspend(pa_card *c, pa_bool_t suspend); +int pa_card_suspend(pa_card *c, pa_bool_t suspend, pa_suspend_cause_t cause); #endif diff --git a/src/pulsecore/cli-command.c b/src/pulsecore/cli-command.c index 15fe525c..644de96e 100644 --- a/src/pulsecore/cli-command.c +++ b/src/pulsecore/cli-command.c @@ -483,6 +483,8 @@ static int pa_cli_command_describe(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, 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); @@ -1276,7 +1278,7 @@ static int pa_cli_command_suspend_sink(pa_core *c, pa_tokenizer *t, pa_strbuf *b return -1; } - if ((r = pa_sink_suspend(sink, suspend)) < 0) + 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; @@ -1312,7 +1314,7 @@ static int pa_cli_command_suspend_source(pa_core *c, pa_tokenizer *t, pa_strbuf return -1; } - if ((r = pa_source_suspend(source, suspend)) < 0) + 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; @@ -1337,10 +1339,10 @@ static int pa_cli_command_suspend(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, p return -1; } - if ((r = pa_sink_suspend_all(c, suspend)) < 0) + 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)) < 0) + 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; diff --git a/src/pulsecore/cli-text.c b/src/pulsecore/cli-text.c index 604678be..bc863f05 100644 --- a/src/pulsecore/cli-text.c +++ b/src/pulsecore/cli-text.c @@ -232,6 +232,7 @@ char *pa_sink_list_to_string(pa_core *c) { "\tdriver: <%s>\n" "\tflags: %s%s%s%s%s%s%s%s\n" "\tstate: %s\n" + "\tsuspend cause: %s%s%s%s\n" "\tvolume: %s%s%s\n" "\t balance %0.2f\n" "\tbase volume: %s%s%s\n" @@ -258,6 +259,10 @@ char *pa_sink_list_to_string(pa_core *c) { sink->flags & PA_SINK_FLAT_VOLUME ? "FLAT_VOLUME " : "", sink->flags & PA_SINK_DYNAMIC_LATENCY ? "DYNAMIC_LATENCY" : "", sink_state_to_string(pa_sink_get_state(sink)), + sink->suspend_cause & PA_SUSPEND_USER ? "USER " : "", + sink->suspend_cause & PA_SUSPEND_APPLICATION ? "APPLICATION " : "", + sink->suspend_cause & PA_SUSPEND_IDLE ? "IDLE " : "", + sink->suspend_cause & PA_SUSPEND_SESSION ? "SESSION" : "", pa_cvolume_snprint(cv, sizeof(cv), pa_sink_get_volume(sink, FALSE, FALSE)), sink->flags & PA_SINK_DECIBEL_VOLUME ? "\n\t " : "", sink->flags & PA_SINK_DECIBEL_VOLUME ? pa_sw_cvolume_snprint_dB(cvdb, sizeof(cvdb), pa_sink_get_volume(sink, FALSE, FALSE)) : "", @@ -335,6 +340,7 @@ char *pa_source_list_to_string(pa_core *c) { "\tdriver: <%s>\n" "\tflags: %s%s%s%s%s%s%s\n" "\tstate: %s\n" + "\tsuspend cause: %s%s%s%s\n" "\tvolume: %s%s%s\n" "\t balance %0.2f\n" "\tbase volume: %s%s%s\n" @@ -358,6 +364,10 @@ char *pa_source_list_to_string(pa_core *c) { source->flags & PA_SOURCE_LATENCY ? "LATENCY " : "", source->flags & PA_SOURCE_DYNAMIC_LATENCY ? "DYNAMIC_LATENCY" : "", source_state_to_string(pa_source_get_state(source)), + source->suspend_cause & PA_SUSPEND_USER ? "USER " : "", + source->suspend_cause & PA_SUSPEND_APPLICATION ? "APPLICATION " : "", + source->suspend_cause & PA_SUSPEND_IDLE ? "IDLE " : "", + source->suspend_cause & PA_SUSPEND_SESSION ? "SESSION" : "", pa_cvolume_snprint(cv, sizeof(cv), pa_source_get_volume(source, FALSE)), source->flags & PA_SOURCE_DECIBEL_VOLUME ? "\n\t " : "", source->flags & PA_SOURCE_DECIBEL_VOLUME ? pa_sw_cvolume_snprint_dB(cvdb, sizeof(cvdb), pa_source_get_volume(source, FALSE)) : "", diff --git a/src/pulsecore/cli.c b/src/pulsecore/cli.c index a784f583..54514e7f 100644 --- a/src/pulsecore/cli.c +++ b/src/pulsecore/cli.c @@ -59,6 +59,8 @@ struct pa_cli { pa_bool_t fail, kill_requested; int defer_kill; + + char *last_line; }; static void line_callback(pa_ioline *line, const char *s, void *userdata); @@ -101,6 +103,8 @@ pa_cli* pa_cli_new(pa_core *core, pa_iochannel *io, pa_module *m) { c->fail = c->kill_requested = FALSE; c->defer_kill = 0; + c->last_line = NULL; + return c; } @@ -110,6 +114,7 @@ void pa_cli_free(pa_cli *c) { pa_ioline_close(c->line); pa_ioline_unref(c->line); pa_client_free(c->client); + pa_xfree(c->last_line); pa_xfree(c); } @@ -144,6 +149,14 @@ static void line_callback(pa_ioline *line, const char *s, void *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++; pa_cli_command_execute_line(c->core, s, buf, &c->fail); diff --git a/src/pulsecore/core-scache.c b/src/pulsecore/core-scache.c index 34d60a8f..086f5fcb 100644 --- a/src/pulsecore/core-scache.c +++ b/src/pulsecore/core-scache.c @@ -219,11 +219,14 @@ int pa_scache_add_file(pa_core *c, const char *name, const char *filename, uint3 pa_assert(name); pa_assert(filename); - if (pa_sound_file_load(c->mempool, filename, &ss, &map, &chunk) < 0) - return -1; - 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); @@ -311,11 +314,14 @@ int pa_scache_play_item(pa_core *c, const char *name, pa_sink *sink, pa_volume_t if (!(e = pa_namereg_get(c, name, PA_NAMEREG_SAMPLE))) return -1; + merged = pa_proplist_new(); + pa_proplist_setf(merged, PA_PROP_MEDIA_NAME, "Sample %s", 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) < 0) - return -1; + 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); @@ -328,7 +334,7 @@ int pa_scache_play_item(pa_core *c, const char *name, pa_sink *sink, pa_volume_t } if (!e->memchunk.memblock) - return -1; + goto fail; pa_log_debug("Playing sample \"%s\" on \"%s\"", name, sink->name); @@ -344,17 +350,13 @@ int pa_scache_play_item(pa_core *c, const char *name, pa_sink *sink, pa_volume_t else pass_volume = FALSE; - merged = pa_proplist_new(); - pa_proplist_setf(merged, PA_PROP_MEDIA_NAME, "Sample %s", name); 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, sink_input_idx) < 0) { - pa_proplist_free(merged); - return -1; - } + if (pa_play_memchunk(sink, &e->sample_spec, &e->channel_map, &e->memchunk, pass_volume ? &r : NULL, merged, sink_input_idx) < 0) + goto fail; pa_proplist_free(merged); @@ -362,6 +364,10 @@ int pa_scache_play_item(pa_core *c, const char *name, pa_sink *sink, pa_volume_t 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) { diff --git a/src/pulsecore/core-util.c b/src/pulsecore/core-util.c index 294f63cb..b747cd84 100644 --- a/src/pulsecore/core-util.c +++ b/src/pulsecore/core-util.c @@ -2235,7 +2235,7 @@ int pa_close_all(int except_fd, ...) { int pa_close_allv(const int except_fds[]) { struct rlimit rl; - int fd; + int maxfd, fd; int saved_errno; #ifdef __linux__ @@ -2302,10 +2302,12 @@ int pa_close_allv(const int except_fds[]) { #endif - if (getrlimit(RLIMIT_NOFILE, &rl) < 0) - return -1; + if (getrlimit(RLIMIT_NOFILE, &rl) >= 0) + maxfd = (int) rl.rlim_max; + else + maxfd = sysconf(_SC_OPEN_MAX); - for (fd = 3; fd < (int) rl.rlim_max; fd++) { + for (fd = 3; fd < maxfd; fd++) { int i; pa_bool_t found; @@ -2467,31 +2469,29 @@ pa_bool_t pa_in_system_mode(void) { return !!atoi(e); } -char *pa_machine_id(void) { - FILE *f; - size_t l; +char *pa_get_user_name_malloc(void) { + ssize_t k; + char *u; - /* The returned value is supposed be some kind of ascii identifier - * that is unique and stable across reboots. */ +#ifdef _SC_LOGIN_NAME_MAX + k = (ssize_t) sysconf(_SC_LOGIN_NAME_MAX); - /* First we try the D-Bus UUID, which is the best option we have, - * since it fits perfectly our needs and is not as volatile as the - * hostname which might be set from dhcp. */ - - if ((f = fopen(PA_MACHINE_ID, "r"))) { - char ln[34] = "", *r; + if (k <= 0) +#endif + k = 32; - r = fgets(ln, sizeof(ln)-1, f); - fclose(f); + u = pa_xnew(char, k+1); - pa_strip_nl(ln); - - if (r && ln[0]) - return pa_utf8_filter(ln); + if (!(pa_get_user_name(u, k))) { + pa_xfree(u); + return NULL; } - /* The we fall back to the host name. It supposed to be somewhat - * unique, at least in a network, but may change. */ + return u; +} + +char *pa_get_host_name_malloc(void) { + size_t l; l = 100; for (;;) { @@ -2525,6 +2525,35 @@ char *pa_machine_id(void) { 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 the D-Bus UUID, which is the best option we have, + * since it fits perfectly our needs and is not as volatile as the + * hostname which might be set from dhcp. */ + + if ((f = fopen(PA_MACHINE_ID, "r"))) { + 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 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); @@ -2682,3 +2711,24 @@ char *pa_realpath(const char *path) { 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 +} diff --git a/src/pulsecore/core-util.h b/src/pulsecore/core-util.h index f96fa443..d073b750 100644 --- a/src/pulsecore/core-util.h +++ b/src/pulsecore/core-util.h @@ -201,6 +201,9 @@ pa_bool_t pa_in_system_mode(void); #define pa_streq(a,b) (!strcmp((a),(b))) +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); @@ -224,4 +227,6 @@ char *pa_unescape(char *p); char *pa_realpath(const char *path); +void pa_disable_sigpipe(void); + #endif diff --git a/src/pulsecore/core.h b/src/pulsecore/core.h index c6794445..09a880c4 100644 --- a/src/pulsecore/core.h +++ b/src/pulsecore/core.h @@ -27,6 +27,16 @@ typedef struct pa_core pa_core; +/* This is a bitmask that encodes the cause why a sink/source is + * suspended. */ +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_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> diff --git a/src/pulsecore/database-gdbm.c b/src/pulsecore/database-gdbm.c new file mode 100644 index 00000000..aeaac64b --- /dev/null +++ b/src/pulsecore/database-gdbm.c @@ -0,0 +1,246 @@ +/*** + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <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); +} + +pa_database* pa_database_open(const char *fn, pa_bool_t for_write) { + GDBM_FILE f; + int gdbm_cache_size; + char *path; + + pa_assert(fn); + + /* We include the host identifier in the file name because gdbm + * files are CPU dependant, and we don't want things to go wrong + * if we are on a multiarch system. */ + + path = pa_sprintf_malloc("%s."CANONICAL_HOST".gdbm", fn); + errno = 0; + f = gdbm_open((char*) path, 0, GDBM_NOLOCK | (for_write ? GDBM_WRCREAT : GDBM_READER), 0644, NULL); + + if (f) + pa_log_debug("Opened GDBM database '%s'", path); + + pa_xfree(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, pa_bool_t 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-tdb.c b/src/pulsecore/database-tdb.c new file mode 100644 index 00000000..b79d2837 --- /dev/null +++ b/src/pulsecore/database-tdb.c @@ -0,0 +1,227 @@ +/*** + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <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); +} + +pa_database* pa_database_open(const char *fn, pa_bool_t for_write) { + struct tdb_context *c; + char *path; + + pa_assert(fn); + + path = pa_sprintf_malloc("%s.tdb", fn); + errno = 0; + c = tdb_open(path, 0, TDB_NOSYNC|TDB_NOLOCK, + (for_write ? O_RDWR|O_CREAT : O_RDONLY)|O_NOCTTY +#ifdef O_CLOEXEC + |O_CLOEXEC +#endif + , 0644); + + if (c) + pa_log_debug("Opened TDB database '%s'", path); + + pa_xfree(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, pa_bool_t 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.h b/src/pulsecore/database.h new file mode 100644 index 00000000..17455d4c --- /dev/null +++ b/src/pulsecore/database.h @@ -0,0 +1,61 @@ +#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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#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 independant. */ + +typedef struct pa_database pa_database; + +typedef struct pa_datum { + void *data; + size_t size; +} pa_datum; + +void pa_datum_free(pa_datum *d); + +/* This will append a suffix to the filename */ +pa_database* pa_database_open(const char *fn, pa_bool_t 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, pa_bool_t 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/endianmacros.h b/src/pulsecore/endianmacros.h index 22579376..2b18cf8d 100644 --- a/src/pulsecore/endianmacros.h +++ b/src/pulsecore/endianmacros.h @@ -45,27 +45,27 @@ #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_READ24LE(const uint8_t *p) { +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_READ24BE(const uint8_t *p) { +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_WRITE24LE(uint8_t *p, uint32_t u) { +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_WRITE24BE(uint8_t *p, uint32_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; diff --git a/src/pulsecore/hashmap.h b/src/pulsecore/hashmap.h index 08e18ead..828e2448 100644 --- a/src/pulsecore/hashmap.h +++ b/src/pulsecore/hashmap.h @@ -65,4 +65,8 @@ void *pa_hashmap_steal_first(pa_hashmap *h); /* Return the oldest entry in the hashmap */ void* pa_hashmap_first(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)) + #endif diff --git a/src/pulsecore/idxset.h b/src/pulsecore/idxset.h index 7531ea32..a6179fcf 100644 --- a/src/pulsecore/idxset.h +++ b/src/pulsecore/idxset.h @@ -103,4 +103,8 @@ unsigned pa_idxset_size(pa_idxset*s); /* Return TRUE of the idxset is empty */ pa_bool_t pa_idxset_isempty(pa_idxset *s); +/* 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/ioline.c b/src/pulsecore/ioline.c index 5c38d6e5..7afdb08c 100644 --- a/src/pulsecore/ioline.c +++ b/src/pulsecore/ioline.c @@ -57,6 +57,9 @@ struct pa_ioline { pa_ioline_cb_t callback; void *userdata; + pa_ioline_drain_cb_t drain_callback; + void *drain_userdata; + pa_bool_t dead:1; pa_bool_t defer_close:1; }; @@ -81,6 +84,9 @@ pa_ioline* pa_ioline_new(pa_iochannel *io) { 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); @@ -202,6 +208,17 @@ void pa_ioline_set_callback(pa_ioline*l, pa_ioline_cb_t callback, void *userdata 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, pa_bool_t process_leftover) { pa_assert(l); pa_assert(PA_REFCNT_VALUE(l) >= 1); @@ -266,7 +283,7 @@ static int do_read(pa_ioline *l) { pa_assert(l); pa_assert(PA_REFCNT_VALUE(l) >= 1); - while (!l->dead && pa_iochannel_is_readable(l->io)) { + while (l->io && !l->dead && pa_iochannel_is_readable(l->io)) { ssize_t r; size_t len; @@ -331,12 +348,12 @@ static int do_write(pa_ioline *l) { pa_assert(l); pa_assert(PA_REFCNT_VALUE(l) >= 1); - while (!l->dead && pa_iochannel_is_writable(l->io) && l->wbuf_valid_length) { + 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 (r < 0 && errno == EAGAIN) - return 0; + break; if (r < 0 && errno != EPIPE) pa_log("write(): %s", pa_cstrerror(errno)); @@ -354,6 +371,9 @@ static int do_write(pa_ioline *l) { l->wbuf_index = 0; } + if (l->wbuf_valid_length <= 0 && l->drain_callback) + l->drain_callback(l, l->drain_userdata); + return 0; } @@ -423,3 +443,25 @@ void pa_ioline_printf(pa_ioline *l, const char *format, ...) { 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; +} + +pa_bool_t 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 index 9f32d60f..d973a3c7 100644 --- a/src/pulsecore/ioline.h +++ b/src/pulsecore/ioline.h @@ -32,6 +32,7 @@ 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); @@ -47,7 +48,17 @@ void pa_ioline_printf(pa_ioline *s, const char *format, ...) PA_GCC_PRINTF_ATTR( /* Set the callback function that is called for every recieved 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 */ +pa_bool_t 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.h b/src/pulsecore/ipacl.h index 7b7ffa61..a3661397 100644 --- a/src/pulsecore/ipacl.h +++ b/src/pulsecore/ipacl.h @@ -1,5 +1,5 @@ -#ifndef fooparseaddrhfoo -#define fooparseaddrhfoo +#ifndef foopulsecoreipaclhfoo +#define foopulsecoreipaclhfoo /*** This file is part of PulseAudio. diff --git a/src/pulsecore/ltdl-helper.h b/src/pulsecore/ltdl-helper.h index 4c4f018a..9f346969 100644 --- a/src/pulsecore/ltdl-helper.h +++ b/src/pulsecore/ltdl-helper.h @@ -29,4 +29,3 @@ 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 index a5ca6964..cf662510 100644 --- a/src/pulsecore/macro.h +++ b/src/pulsecore/macro.h @@ -29,6 +29,7 @@ #include <unistd.h> #include <stdio.h> #include <stdlib.h> +#include <string.h> #include <pulse/gccmacro.h> @@ -57,18 +58,27 @@ #define PA_PAGE_SIZE ((size_t) 4096) #endif +/* Rounds down */ +static inline void* pa_align_ptr(const void *p) { + return (void*) (((size_t) p) & ~(sizeof(void*)-1)); +} +#define PA_ALIGN_PTR(x) (pa_align_ptr(x)) + +/* Rounds up */ static inline size_t pa_align(size_t l) { return (((l + sizeof(void*) - 1) / sizeof(void*)) * sizeof(void*)); } #define PA_ALIGN(x) (pa_align(x)) +/* Rounds down */ static inline void* pa_page_align_ptr(const void *p) { return (void*) (((size_t) p) & ~(PA_PAGE_SIZE-1)); } #define PA_PAGE_ALIGN_PTR(x) (pa_page_align_ptr(x)) +/* Rounds up */ static inline size_t pa_page_align(size_t l) { - return l & ~(PA_PAGE_SIZE-1); + return ((l + PA_PAGE_SIZE - 1) / PA_PAGE_SIZE) * PA_PAGE_SIZE; } #define PA_PAGE_ALIGN(x) (pa_page_align(x)) @@ -252,6 +262,9 @@ typedef int pa_bool_t; #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))) + /* We include this at the very last place */ #include <pulsecore/log.h> diff --git a/src/pulsecore/memtrap.c b/src/pulsecore/memtrap.c index e04e838e..e06f60ca 100644 --- a/src/pulsecore/memtrap.c +++ b/src/pulsecore/memtrap.c @@ -26,12 +26,17 @@ #include <signal.h> #include <sys/mman.h> +/* 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/semaphore.h> -#include <pulsecore/macro.h> -#include <pulsecore/mutex.h> #include <pulsecore/core-util.h> +#include <pulsecore/aupdate.h> +#include <pulsecore/atomic.h> +#include <pulsecore/once.h> #include "memtrap.h" @@ -43,13 +48,13 @@ struct pa_memtrap { }; static pa_memtrap *memtraps[2] = { NULL, NULL }; -static pa_atomic_t read_lock = PA_ATOMIC_INIT(0); -static pa_static_semaphore semaphore = PA_STATIC_SEMAPHORE_INIT; -static pa_static_mutex write_lock = PA_STATIC_MUTEX_INIT; +static pa_aupdate *aupdate; -#define MSB (1U << (sizeof(unsigned)*8U-1)) -#define WHICH(n) (!!((n) & MSB)) -#define COUNTER(n) ((n) & ~MSB) +static void allocate_aupdate(void) { + PA_ONCE_BEGIN { + aupdate = pa_aupdate_new(); + } PA_ONCE_END; +} pa_bool_t pa_memtrap_is_good(pa_memtrap *m) { pa_assert(m); @@ -62,19 +67,11 @@ static void sigsafe_error(const char *s) { } static void signal_handler(int sig, siginfo_t* si, void *data) { - unsigned n, j; + unsigned j; pa_memtrap *m; void *r; - /* Increase the lock counter */ - n = (unsigned) pa_atomic_inc(&read_lock); - - /* The uppermost bit tells us which list to look at */ - j = WHICH(n); - - /* When n is 0 we have about 2^31 threads running that - * all got a sigbus at the same time, oh my! */ - pa_assert(COUNTER(n)+1 > 0); + j = pa_aupdate_read_begin(aupdate); for (m = memtraps[j]; m; m = m->next[j]) if (si->si_addr >= m->start && @@ -94,33 +91,16 @@ static void signal_handler(int sig, siginfo_t* si, void *data) { pa_assert(r == m->start); - pa_atomic_dec(&read_lock); - - /* Post the semaphore */ - pa_semaphore_post(pa_static_semaphore_get(&semaphore, 0)); - + pa_aupdate_read_end(aupdate); return; fail: + pa_aupdate_read_end(aupdate); + sigsafe_error("Failed to handle SIGBUS.\n"); - pa_atomic_dec(&read_lock); abort(); } -static void memtrap_swap(unsigned n) { - - for (;;) { - - /* If the read counter is > 0 wait; if it is 0 try to swap the lists */ - if (COUNTER(n) > 0) - pa_semaphore_wait(pa_static_semaphore_get(&semaphore, 0)); - else if (pa_atomic_cmpxchg(&read_lock, (int) n, (int) (n ^ MSB))) - break; - - n = (unsigned) pa_atomic_load(&read_lock); - } -} - static void memtrap_link(pa_memtrap *m, unsigned j) { pa_assert(m); @@ -143,92 +123,76 @@ static void memtrap_unlink(pa_memtrap *m, unsigned j) { pa_memtrap* pa_memtrap_add(const void *start, size_t size) { pa_memtrap *m = NULL; - pa_mutex *lock; - unsigned n, j; + unsigned j; pa_assert(start); pa_assert(size > 0); - pa_assert(PA_PAGE_ALIGN_PTR(start) == start); - pa_assert(PA_PAGE_ALIGN(size) == size); - lock = pa_static_mutex_get(&write_lock, FALSE, FALSE); - pa_mutex_lock(lock); - - n = (unsigned) pa_atomic_load(&read_lock); - j = WHICH(n); + 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); - memtrap_link(m, !j); - memtrap_swap(n); - memtrap_link(m, j); + allocate_aupdate(); - pa_mutex_unlock(lock); + 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); return m; } void pa_memtrap_remove(pa_memtrap *m) { - unsigned n, j; - pa_mutex *lock; + unsigned j; pa_assert(m); - lock = pa_static_mutex_get(&write_lock, FALSE, FALSE); - pa_mutex_lock(lock); - - n = (unsigned) pa_atomic_load(&read_lock); - j = WHICH(n); + allocate_aupdate(); - memtrap_unlink(m, !j); - memtrap_swap(n); + 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_xfree(m); - - pa_mutex_unlock(lock); } pa_memtrap *pa_memtrap_update(pa_memtrap *m, const void *start, size_t size) { - unsigned n, j; - pa_mutex *lock; + unsigned j; pa_assert(m); pa_assert(start); pa_assert(size > 0); - pa_assert(PA_PAGE_ALIGN_PTR(start) == start); - pa_assert(PA_PAGE_ALIGN(size) == size); - lock = pa_static_mutex_get(&write_lock, FALSE, FALSE); - pa_mutex_lock(lock); + start = PA_PAGE_ALIGN_PTR(start); + size = PA_PAGE_ALIGN(size); + + allocate_aupdate(); + + j = pa_aupdate_write_begin(aupdate); if (m->start == start && m->size == size) goto unlock; - n = (unsigned) pa_atomic_load(&read_lock); - j = WHICH(n); - - memtrap_unlink(m, !j); - memtrap_swap(n); memtrap_unlink(m, j); + j = pa_aupdate_write_swap(aupdate); m->start = (void*) start; m->size = size; pa_atomic_store(&m->bad, 0); - n = (unsigned) pa_atomic_load(&read_lock); - j = WHICH(n); - - memtrap_link(m, !j); - memtrap_swap(n); + j = pa_aupdate_write_swap(aupdate); memtrap_link(m, j); unlock: - pa_mutex_unlock(lock); + pa_aupdate_write_end(aupdate); return m; } @@ -236,10 +200,7 @@ unlock: void pa_memtrap_install(void) { struct sigaction sa; - /* Before we install the signal handler, make sure the semaphore - * is valid so that the initialization of the semaphore - * doesn't have to happen from the signal handler */ - pa_static_semaphore_get(&semaphore, 0); + allocate_aupdate(); memset(&sa, 0, sizeof(sa)); sa.sa_sigaction = signal_handler; diff --git a/src/pulsecore/memtrap.h b/src/pulsecore/memtrap.h index f7da7083..fa38da58 100644 --- a/src/pulsecore/memtrap.h +++ b/src/pulsecore/memtrap.h @@ -34,7 +34,7 @@ * 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 in + * other processes that might execute ftruncate() or when mapping inb * hardware resources that might get invalidated when unplugged. */ typedef struct pa_memtrap pa_memtrap; diff --git a/src/pulsecore/mime-type.c b/src/pulsecore/mime-type.c new file mode 100644 index 00000000..b9fe9444 --- /dev/null +++ b/src/pulsecore/mime-type.c @@ -0,0 +1,182 @@ +/*** + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/xmalloc.h> +#include <pulsecore/core-util.h> + +#include "mime-type.h" + +pa_bool_t 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)); + + 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(); + } + + pa_assert(pa_sample_spec_valid(ss)); +} + +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 00000000..db77379b --- /dev/null +++ b/src/pulsecore/mime-type.h @@ -0,0 +1,37 @@ +#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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulsecore/macro.h> +#include <pulse/sample.h> +#include <pulse/channelmap.h> + +pa_bool_t 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/modargs.c b/src/pulsecore/modargs.c index 73c67a8b..c7d734d9 100644 --- a/src/pulsecore/modargs.c +++ b/src/pulsecore/modargs.c @@ -84,8 +84,11 @@ pa_modargs *pa_modargs_new(const char *args, const char* const* valid_keys) { KEY, VALUE_START, VALUE_SIMPLE, + VALUE_SIMPLE_ESCAPED, VALUE_DOUBLE_QUOTES, - VALUE_TICKS + VALUE_DOUBLE_QUOTES_ESCAPED, + VALUE_TICKS, + VALUE_TICKS_ESCAPED } state; const char *p, *key = NULL, *value = NULL; @@ -131,9 +134,16 @@ pa_modargs *pa_modargs_new(const char *args, const char* const* valid_keys) { value = p+1; value_len = 0; } else if (isspace(*p)) { - if (add_key_value(map, pa_xstrndup(key, key_len), pa_xstrdup(""), valid_keys) < 0) + if (add_key_value(map, + pa_xstrndup(key, key_len), + pa_xstrdup(""), + valid_keys) < 0) goto fail; state = WHITESPACE; + } else if (*p == '\\') { + state = VALUE_SIMPLE_ESCAPED; + value = p; + value_len = 1; } else { state = VALUE_SIMPLE; value = p; @@ -143,30 +153,63 @@ pa_modargs *pa_modargs_new(const char *args, const char* const* valid_keys) { case VALUE_SIMPLE: if (isspace(*p)) { - if (add_key_value(map, pa_xstrndup(key, key_len), pa_xstrndup(value, value_len), valid_keys) < 0) + if (add_key_value(map, + pa_xstrndup(key, key_len), + pa_unescape(pa_xstrndup(value, value_len)), + valid_keys) < 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(map, pa_xstrndup(key, key_len), pa_xstrndup(value, value_len), valid_keys) < 0) + if (add_key_value(map, + pa_xstrndup(key, key_len), + pa_unescape(pa_xstrndup(value, value_len)), + valid_keys) < 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(map, pa_xstrndup(key, key_len), pa_xstrndup(value, value_len), valid_keys) < 0) + if (add_key_value(map, + pa_xstrndup(key, key_len), + pa_unescape(pa_xstrndup(value, value_len)), + valid_keys) < 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; } } @@ -352,3 +395,23 @@ int pa_modargs_get_sample_spec_and_channel_map( 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 = pa_modargs_get_value(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; +} diff --git a/src/pulsecore/modargs.h b/src/pulsecore/modargs.h index 809fb27e..b3125b10 100644 --- a/src/pulsecore/modargs.h +++ b/src/pulsecore/modargs.h @@ -58,4 +58,6 @@ 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); +int pa_modargs_get_proplist(pa_modargs *ma, const char *name, pa_proplist *p, pa_update_mode_t m); + #endif diff --git a/src/pulsecore/modinfo.c b/src/pulsecore/modinfo.c index 00fb9c43..b5ee9f56 100644 --- a/src/pulsecore/modinfo.c +++ b/src/pulsecore/modinfo.c @@ -38,6 +38,7 @@ #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) { @@ -61,6 +62,9 @@ pa_modinfo *pa_modinfo_get_by_handle(lt_dlhandle dl, const char *module_name) { 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 = (pa_bool_t (*)(void)) pa_load_sym(dl, module_name, PA_SYMBOL_LOAD_ONCE))) i->load_once = func2(); @@ -91,5 +95,6 @@ void pa_modinfo_free(pa_modinfo *i) { 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 index 407e602a..baad0de7 100644 --- a/src/pulsecore/modinfo.h +++ b/src/pulsecore/modinfo.h @@ -30,6 +30,7 @@ typedef struct pa_modinfo { char *description; char *usage; char *version; + char *deprecated; pa_bool_t load_once; } pa_modinfo; diff --git a/src/pulsecore/module.c b/src/pulsecore/module.c index 42fd912c..5bcdd898 100644 --- a/src/pulsecore/module.c +++ b/src/pulsecore/module.c @@ -48,10 +48,12 @@ #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" pa_module* pa_module_load(pa_core *c, const char *name, const char *argument) { pa_module *m = NULL; pa_bool_t (*load_once)(void); + const char* (*get_deprecated)(void); pa_modinfo *mi; pa_assert(c); @@ -89,6 +91,13 @@ pa_module* pa_module_load(pa_core *c, const char *name, const char *argument) { } } + 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); goto fail; diff --git a/src/pulsecore/module.h b/src/pulsecore/module.h index 3f697348..af89d793 100644 --- a/src/pulsecore/module.h +++ b/src/pulsecore/module.h @@ -78,6 +78,10 @@ int pa_module_get_n_used(pa_module*m); 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) \ pa_bool_t pa__load_once(void) { return b; } \ struct __stupid_useless_struct_to_allow_trailing_semicolon diff --git a/src/pulsecore/parseaddr.c b/src/pulsecore/parseaddr.c index 5b531220..44cd9a05 100644 --- a/src/pulsecore/parseaddr.c +++ b/src/pulsecore/parseaddr.c @@ -25,6 +25,8 @@ #include <string.h> #include <stdlib.h> +#include <arpa/inet.h> +#include <sys/socket.h> #include <pulse/xmalloc.h> #include <pulse/util.h> @@ -131,3 +133,17 @@ int pa_parse_address(const char *name, pa_parsed_address *ret_p) { return 0; } + +pa_bool_t 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; +} diff --git a/src/pulsecore/parseaddr.h b/src/pulsecore/parseaddr.h index 5fbcb9a7..a1071b0a 100644 --- a/src/pulsecore/parseaddr.h +++ b/src/pulsecore/parseaddr.h @@ -1,5 +1,5 @@ -#ifndef fooparseaddrhfoo -#define fooparseaddrhfoo +#ifndef foopulsecoreparseaddrhfoo +#define foopulsecoreparseaddrhfoo /*** This file is part of PulseAudio. @@ -24,6 +24,8 @@ #include <inttypes.h> +#include <pulsecore/macro.h> + typedef enum pa_parsed_address_type { PA_PARSED_ADDRESS_UNIX, PA_PARSED_ADDRESS_TCP4, @@ -39,4 +41,6 @@ typedef struct pa_parsed_address { int pa_parse_address(const char *a, pa_parsed_address *ret_p); +pa_bool_t pa_is_ip_address(const char *a); + #endif diff --git a/src/pulsecore/proplist-util.c b/src/pulsecore/proplist-util.c index eac8927c..d9769bc7 100644 --- a/src/pulsecore/proplist-util.c +++ b/src/pulsecore/proplist-util.c @@ -168,20 +168,20 @@ void pa_init_proplist(pa_proplist *p) { } if (!pa_proplist_contains(p, PA_PROP_APPLICATION_PROCESS_USER)) { - char t[64]; - if (pa_get_user_name(t, sizeof(t))) { - char *c = pa_utf8_filter(t); - pa_proplist_sets(p, PA_PROP_APPLICATION_PROCESS_USER, c); - pa_xfree(c); + 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 t[64]; - if (pa_get_host_name(t, sizeof(t))) { - char *c = pa_utf8_filter(t); - pa_proplist_sets(p, PA_PROP_APPLICATION_PROCESS_HOST, c); - pa_xfree(c); + char *h; + + if ((h = pa_get_host_name_malloc())) { + pa_proplist_sets(p, PA_PROP_APPLICATION_PROCESS_HOST, h); + pa_xfree(h); } } diff --git a/src/pulsecore/protocol-esound.c b/src/pulsecore/protocol-esound.c index 7e7126ea..ad7cd045 100644 --- a/src/pulsecore/protocol-esound.c +++ b/src/pulsecore/protocol-esound.c @@ -947,10 +947,10 @@ static int esd_proto_standby_or_resume(connection *c, esd_proto_t request, const connection_write(c, &ok, sizeof(int32_t)); if (request == ESD_PROTO_STANDBY) - ok = pa_sink_suspend_all(c->protocol->core, TRUE) >= 0; + ok = pa_sink_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) >= 0; + ok = pa_sink_suspend_all(c->protocol->core, FALSE, PA_SUSPEND_USER) >= 0; } connection_write(c, &ok, sizeof(int32_t)); diff --git a/src/pulsecore/protocol-http.c b/src/pulsecore/protocol-http.c index f3b93819..5220cc91 100644 --- a/src/pulsecore/protocol-http.c +++ b/src/pulsecore/protocol-http.c @@ -1,7 +1,7 @@ /*** This file is part of PulseAudio. - Copyright 2005-2006 Lennart Poettering + 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 @@ -26,36 +26,68 @@ #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/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 internal_server_error(c) http_message((c), 500, "Internal Server Error", NULL) - #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 +}; struct connection { pa_http_protocol *protocol; + pa_iochannel *io; pa_ioline *line; - enum { - REQUEST_LINE, - MIME_HEADER, - DATA - } state; + pa_memblockq *output_memblockq; + pa_source_output *source_output; + pa_client *client; + enum state state; char *url; pa_module *module; }; @@ -65,58 +97,500 @@ struct pa_http_protocol { pa_core *core; pa_idxset *connections; + + pa_strlist *servers; }; -static void http_response(struct connection *c, int code, const char *msg, const char *mime) { - char s[256]; +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) { + + if (errno == EINTR || errno == EAGAIN) + return 0; + + pa_log("write(): %s", pa_cstrerror(errno)); + return -1; + } + + pa_memblockq_drop(c->output_memblockq, (size_t) r); + + return 0; +} + +/* Called from main context */ +static void do_work(struct connection *c) { + pa_assert(c); + + if (pa_iochannel_is_hungup(c->io)) + goto fail; + + if (pa_iochannel_is_writable(c->io)) + if (do_write(c) < 0) + goto fail; + + 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_tostring_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); - pa_snprintf(s, sizeof(s), - "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); - + 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 http_message(struct connection *c, int code, const char *msg, const char *text) { - char s[256]; +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, "text/html"); + http_response(c, code, msg, MIME_HTML); if (!text) text = msg; - pa_snprintf(s, sizeof(s), - "<?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\"><head><title>%s</title></head>\n" - "<body>%s</body></html>\n", - text, text); + 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; -static void connection_unlink(struct connection *c) { pa_assert(c); - if (c->url) - pa_xfree(c->url); + http_response(c, 200, "OK", MIME_HTML); - pa_idxset_remove_by_data(c->protocol->connections, c, NULL); + 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); + + 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); + 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"); + + 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); - pa_xfree(c); + 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; + data.source = source; + 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, 0); + 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( + 0, + l, + 0, + pa_frame_size(&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); + + 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) { @@ -131,93 +605,27 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) { } switch (c->state) { - case REQUEST_LINE: { - if (memcmp(s, "GET ", 4)) + case STATE_REQUEST_LINE: { + if (!pa_startswith(s, "GET ")) goto fail; s +=4; c->url = pa_xstrndup(s, strcspn(s, " \r\n\t?")); - c->state = MIME_HEADER; + c->state = STATE_MIME_HEADER; break; - } - case MIME_HEADER: { + case STATE_MIME_HEADER: { /* Ignore MIME headers */ if (strcspn(s, " \r\n") != 0) break; /* We're done */ - c->state = DATA; - - pa_log_info("request for %s", c->url); - - if (!strcmp(c->url, URL_ROOT)) { - char txt[256]; - pa_sink *def_sink; - pa_source *def_source; - http_response(c, 200, "OK", "text/html"); - - pa_ioline_puts(c->line, - "<?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\"><title>"PACKAGE_NAME" "PACKAGE_VERSION"</title>\n" - "<link rel=\"stylesheet\" type=\"text/css\" href=\"style\"/></head><body>\n"); - - pa_ioline_puts(c->line, - "<h1>"PACKAGE_NAME" "PACKAGE_VERSION"</h1>\n" - "<table>"); - -#define PRINTF_FIELD(a,b) pa_ioline_printf(c->line, "<tr><td><b>%s</b></td><td>%s</td></tr>\n",(a),(b)) - - PRINTF_FIELD("User Name:", pa_get_user_name(txt, sizeof(txt))); - PRINTF_FIELD("Host name:", pa_get_host_name(txt, sizeof(txt))); - PRINTF_FIELD("Default Sample Specification:", pa_sample_spec_snprint(txt, sizeof(txt), &c->protocol->core->default_sample_spec)); - - def_sink = pa_namereg_get_default_sink(c->protocol->core); - def_source = pa_namereg_get_default_source(c->protocol->core); - - PRINTF_FIELD("Default Sink:", def_sink ? def_sink->name : "n/a"); - PRINTF_FIELD("Default Source:", def_source ? def_source->name : "n/a"); - - pa_ioline_puts(c->line, "</table>"); - - pa_ioline_puts(c->line, "<p><a href=\"/status\">Click here</a> for an extensive server status report.</p>"); - - pa_ioline_puts(c->line, "</body></html>\n"); - - pa_ioline_defer_close(c->line); - } else if (!strcmp(c->url, URL_CSS)) { - http_response(c, 200, "OK", "text/css"); - - pa_ioline_puts(c->line, - "body { color: black; background-color: white; margin: 0.5cm; }\n" - "a:link, a:visited { color: #900000; }\n" - "p { margin-left: 0.5cm; margin-right: 0.5cm; }\n" - "h1 { color: #00009F; }\n" - "h2 { color: #00009F; }\n" - "ul { margin-left: .5cm; }\n" - "ol { margin-left: .5cm; }\n" - "pre { margin-left: .5cm; background-color: #f0f0f0; padding: 0.4cm;}\n" - ".grey { color: #afafaf; }\n" - "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); - } else if (!strcmp(c->url, URL_STATUS)) { - char *r; - - http_response(c, 200, "OK", "text/plain"); - r = pa_full_status_string(c->protocol->core); - pa_ioline_puts(c->line, r); - pa_xfree(r); - - pa_ioline_defer_close(c->line); - } else - http_message(c, 404, "Not Found", NULL); + c->state = STATE_DATA; + handle_url(c); break; } @@ -228,11 +636,13 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) { return; fail: - internal_server_error(c); + 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); @@ -244,26 +654,46 @@ void pa_http_protocol_connect(pa_http_protocol *p, pa_iochannel *io, pa_module * return; } - c = pa_xnew(struct connection, 1); + c = pa_xnew0(struct connection, 1); c->protocol = p; - c->line = pa_ioline_new(io); - c->state = REQUEST_LINE; - c->url = NULL; + 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; - void *state = NULL; + uint32_t idx; pa_assert(p); pa_assert(m); - while ((c = pa_idxset_iterate(p->connections, &state, NULL))) + PA_IDXSET_FOREACH(c, p->connections, idx) if (c->module == m) connection_unlink(c); } @@ -273,7 +703,7 @@ static pa_http_protocol* http_protocol_new(pa_core *c) { pa_assert(c); - p = pa_xnew(pa_http_protocol, 1); + p = pa_xnew0(pa_http_protocol, 1); PA_REFCNT_INIT(p); p->core = c; p->connections = pa_idxset_new(NULL, NULL); @@ -315,7 +745,32 @@ void pa_http_protocol_unref(pa_http_protocol *p) { pa_idxset_free(p->connections, NULL, 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 index 40b3d82c..f7517e81 100644 --- a/src/pulsecore/protocol-http.h +++ b/src/pulsecore/protocol-http.h @@ -26,7 +26,7 @@ #include <pulsecore/module.h> #include <pulsecore/modargs.h> #include <pulsecore/iochannel.h> - +#include <pulsecore/strlist.h> typedef struct pa_http_protocol pa_http_protocol; @@ -36,4 +36,8 @@ 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 index aecaf71c..e9e2d601 100644 --- a/src/pulsecore/protocol-native.c +++ b/src/pulsecore/protocol-native.c @@ -3182,10 +3182,10 @@ static void command_get_info_list(pa_pdispatch *pd, uint32_t command, uint32_t t 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; - char txt[256]; pa_sink *def_sink; pa_source *def_source; pa_sample_spec fixed_ss; + char *h, *u; pa_native_connection_assert_ref(c); pa_assert(t); @@ -3200,8 +3200,14 @@ static void command_get_server_info(pa_pdispatch *pd, uint32_t command, uint32_t reply = reply_new(tag); pa_tagstruct_puts(reply, PACKAGE_NAME); pa_tagstruct_puts(reply, PACKAGE_VERSION); - pa_tagstruct_puts(reply, pa_get_user_name(txt, sizeof(txt))); - pa_tagstruct_puts(reply, pa_get_host_name(txt, sizeof(txt))); + + 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); fixup_sample_spec(c, &fixed_ss, &c->protocol->core->default_sample_spec); pa_tagstruct_put_sample_spec(reply, &fixed_ss); @@ -4092,7 +4098,7 @@ static void command_suspend(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa pa_log_debug("%s all sinks", b ? "Suspending" : "Resuming"); - if (pa_sink_suspend_all(c->protocol->core, b) < 0) { + if (pa_sink_suspend_all(c->protocol->core, b, PA_SUSPEND_USER) < 0) { pa_pstream_send_error(c->pstream, tag, PA_ERR_INVALID); return; } @@ -4106,7 +4112,7 @@ static void command_suspend(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa CHECK_VALIDITY(c->pstream, sink, tag, PA_ERR_NOENTITY); - if (pa_sink_suspend(sink, b) < 0) { + if (pa_sink_suspend(sink, b, PA_SUSPEND_USER) < 0) { pa_pstream_send_error(c->pstream, tag, PA_ERR_INVALID); return; } @@ -4119,7 +4125,7 @@ static void command_suspend(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa pa_log_debug("%s all sources", b ? "Suspending" : "Resuming"); - if (pa_source_suspend_all(c->protocol->core, b) < 0) { + if (pa_source_suspend_all(c->protocol->core, b, PA_SUSPEND_USER) < 0) { pa_pstream_send_error(c->pstream, tag, PA_ERR_INVALID); return; } @@ -4134,7 +4140,7 @@ static void command_suspend(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa CHECK_VALIDITY(c->pstream, source, tag, PA_ERR_NOENTITY); - if (pa_source_suspend(source, b) < 0) { + if (pa_source_suspend(source, b, PA_SUSPEND_USER) < 0) { pa_pstream_send_error(c->pstream, tag, PA_ERR_INVALID); return; } diff --git a/src/pulsecore/protocol-simple.c b/src/pulsecore/protocol-simple.c index 44fe5973..776d74b6 100644 --- a/src/pulsecore/protocol-simple.c +++ b/src/pulsecore/protocol-simple.c @@ -130,7 +130,7 @@ static void connection_unlink(connection *c) { c->io = NULL; } - pa_assert_se(pa_idxset_remove_by_data(c->protocol->connections, c, NULL) == c); + pa_idxset_remove_by_data(c->protocol->connections, c, NULL); c->protocol = NULL; connection_unref(c); } diff --git a/src/pulsecore/sample-util.c b/src/pulsecore/sample-util.c index 3a9b384d..dda38834 100644 --- a/src/pulsecore/sample-util.c +++ b/src/pulsecore/sample-util.c @@ -831,9 +831,9 @@ void pa_volume_memchunk( calc_linear_integer_volume(linear, volume); - e = (uint8_t*) ptr + c->length/3; + e = (uint8_t*) ptr + c->length; - for (channel = 0, d = ptr; d < e; d++) { + for (channel = 0, d = ptr; d < e; d += 3) { int64_t t; t = (int64_t)((int32_t) (PA_READ24NE(d) << 8)); @@ -854,9 +854,9 @@ void pa_volume_memchunk( calc_linear_integer_volume(linear, volume); - e = (uint8_t*) ptr + c->length/3; + e = (uint8_t*) ptr + c->length; - for (channel = 0, d = ptr; d < e; d++) { + for (channel = 0, d = ptr; d < e; d += 3) { int64_t t; t = (int64_t)((int32_t) (PA_READ24RE(d) << 8)); @@ -1181,6 +1181,8 @@ pa_memchunk* pa_silence_memchunk_get(pa_silence_cache *cache, pa_mempool *pool, case PA_SAMPLE_S32BE: case PA_SAMPLE_S24LE: case PA_SAMPLE_S24BE: + case PA_SAMPLE_S24_32LE: + case PA_SAMPLE_S24_32RE: case PA_SAMPLE_FLOAT32LE: case PA_SAMPLE_FLOAT32BE: cache->blocks[PA_SAMPLE_S16LE] = b = silence_memblock_new(pool, 0); @@ -1189,6 +1191,8 @@ pa_memchunk* pa_silence_memchunk_get(pa_silence_cache *cache, pa_mempool *pool, 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; diff --git a/src/pulsecore/sconv-s16le.c b/src/pulsecore/sconv-s16le.c index 307ce7b7..43b8cb3e 100644 --- a/src/pulsecore/sconv-s16le.c +++ b/src/pulsecore/sconv-s16le.c @@ -370,7 +370,7 @@ void pa_sconv_s24_32le_to_s16ne(unsigned n, const uint32_t *a, int16_t *b) { pa_assert(b); for (; n > 0; n--) { - *b = (int16_t) ((int32_t) (UINT32_FROM(*a) << 8) >> 16); + *b = (int16_t) (((int32_t) (UINT32_FROM(*a) << 8)) >> 16); a++; b++; } @@ -416,8 +416,8 @@ void pa_sconv_s24_32le_to_float32ne(unsigned n, const uint32_t *a, float *b) { pa_assert(b); for (; n > 0; n--) { - int32_t s = (int16_t) ((int32_t) (UINT32_FROM(*a) << 8)); - *b = ((float) s) / 0x7FFFFFFF; + int32_t s = (int32_t) (UINT32_FROM(*a) << 8); + *b = (float) s / (float) 0x7FFFFFFF; a ++; b ++; } @@ -428,8 +428,8 @@ void pa_sconv_s24_32le_to_float32re(unsigned n, const uint32_t *a, float *b) { pa_assert(b); for (; n > 0; n--) { - int32_t s = (int16_t) ((int32_t) (UINT32_FROM(*a) << 8)); - float k = ((float) s) / 0x7FFFFFFF; + int32_t s = (int32_t) (UINT32_FROM(*a) << 8); + float k = (float) s / (float) 0x7FFFFFFF; *b = PA_FLOAT32_SWAP(k); a ++; b ++; diff --git a/src/pulsecore/sconv.c b/src/pulsecore/sconv.c index 29a9a453..d89f4283 100644 --- a/src/pulsecore/sconv.c +++ b/src/pulsecore/sconv.c @@ -75,7 +75,7 @@ static void u8_from_s16ne(unsigned n, const int16_t *a, uint8_t *b) { pa_assert(b); for (; n > 0; n--, a++, b++) - *b = (uint8_t) (*a / 0x100 + 0x80); + *b = (uint8_t) ((uint16_t) *a >> 8) + (uint8_t) 0x80U; } /* float32 */ diff --git a/src/pulsecore/shm.c b/src/pulsecore/shm.c index b8c5f786..fab2b3b6 100644 --- a/src/pulsecore/shm.c +++ b/src/pulsecore/shm.c @@ -69,7 +69,10 @@ #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 */ + * 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 independant + * from the process' word with */ struct shm_marker { pa_atomic_t marker; /* 0xbeefcafe */ pa_atomic_t pid; @@ -79,6 +82,8 @@ struct shm_marker { uint64_t _reserved4; } PA_GCC_PACKED; +#define SHM_MARKER_SIZE PA_ALIGN(sizeof(struct shm_marker)) + static char *segment_name(char *fn, size_t l, unsigned id) { pa_snprintf(fn, l, "/pulse-shm-%u", id); return fn; @@ -97,8 +102,8 @@ int pa_shm_create_rw(pa_shm *m, size_t size, pa_bool_t shared, mode_t mode) { * ones */ pa_shm_cleanup(); - /* Round up to make it aligned */ - size = PA_ALIGN(size); + /* Round up to make it page aligned */ + size = PA_PAGE_ALIGN(size); if (!shared) { m->id = 0; @@ -136,25 +141,25 @@ int pa_shm_create_rw(pa_shm *m, size_t size, pa_bool_t shared, mode_t mode) { goto fail; } - m->size = size + PA_ALIGN(sizeof(struct shm_marker)); + m->size = size + SHM_MARKER_SIZE; if (ftruncate(fd, (off_t) m->size) < 0) { pa_log("ftruncate() failed: %s", pa_cstrerror(errno)); goto fail; } - if ((m->ptr = mmap(NULL, m->size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, (off_t) 0)) == MAP_FAILED) { + if ((m->ptr = mmap(NULL, PA_PAGE_ALIGN(m->size), PROT_READ|PROT_WRITE, MAP_SHARED, fd, (off_t) 0)) == MAP_FAILED) { pa_log("mmap() failed: %s", pa_cstrerror(errno)); goto fail; } /* 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 - PA_ALIGN(sizeof(struct shm_marker))); + marker = (struct shm_marker*) ((uint8_t*) m->ptr + m->size - SHM_MARKER_SIZE); pa_atomic_store(&marker->pid, (int) getpid()); pa_atomic_store(&marker->marker, SHM_MARKER); - pa_assert_se(close(fd) == 0); + pa_assert_se(pa_close(fd) == 0); m->do_unlink = TRUE; #else return -1; @@ -197,7 +202,7 @@ void pa_shm_free(pa_shm *m) { #endif } else { #ifdef HAVE_SHM_OPEN - if (munmap(m->ptr, m->size) < 0) + if (munmap(m->ptr, PA_PAGE_ALIGN(m->size)) < 0) pa_log("munmap() failed: %s", pa_cstrerror(errno)); if (m->do_unlink) { @@ -214,12 +219,12 @@ void pa_shm_free(pa_shm *m) { #endif } - memset(m, 0, sizeof(*m)); + pa_zero(*m); } void pa_shm_punch(pa_shm *m, size_t offset, size_t size) { void *ptr; - size_t o, ps; + size_t o; pa_assert(m); pa_assert(m->ptr); @@ -233,16 +238,19 @@ void pa_shm_punch(pa_shm *m, size_t offset, size_t size) { /* You're welcome to implement this as NOOP on systems that don't * support it */ - /* Align this to multiples of the page size */ + /* 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) { - ps = PA_PAGE_SIZE; - ptr = (uint8_t*) ptr + (ps - o); - size -= ps - o; + size_t delta = PA_PAGE_SIZE - o; + ptr = (uint8_t*) ptr + delta; + size -= delta; } + /* Align the size down to multiples of page size */ + size = (size / PA_PAGE_SIZE) * PA_PAGE_SIZE; + #ifdef MADV_REMOVE if (madvise(ptr, size, MADV_REMOVE) >= 0) return; @@ -254,9 +262,9 @@ void pa_shm_punch(pa_shm *m, size_t offset, size_t size) { #endif #ifdef MADV_DONTNEED - pa_assert_se(madvise(ptr, size, MADV_DONTNEED) == 0); + madvise(ptr, size, MADV_DONTNEED); #elif defined(POSIX_MADV_DONTNEED) - pa_assert_se(posix_madvise(ptr, size, POSIX_MADV_DONTNEED) == 0); + posix_madvise(ptr, size, POSIX_MADV_DONTNEED); #endif } @@ -283,7 +291,7 @@ int pa_shm_attach_ro(pa_shm *m, unsigned id) { } if (st.st_size <= 0 || - st.st_size > (off_t) (MAX_SHM_SIZE+PA_ALIGN(sizeof(struct shm_marker))) || + st.st_size > (off_t) (MAX_SHM_SIZE+SHM_MARKER_SIZE) || PA_ALIGN((size_t) st.st_size) != (size_t) st.st_size) { pa_log("Invalid shared memory segment size"); goto fail; @@ -291,13 +299,13 @@ int pa_shm_attach_ro(pa_shm *m, unsigned id) { m->size = (size_t) st.st_size; - if ((m->ptr = mmap(NULL, m->size, PROT_READ, MAP_SHARED, fd, (off_t) 0)) == MAP_FAILED) { + if ((m->ptr = mmap(NULL, PA_PAGE_ALIGN(m->size), PROT_READ, MAP_SHARED, fd, (off_t) 0)) == MAP_FAILED) { pa_log("mmap() failed: %s", pa_cstrerror(errno)); goto fail; } - m->do_unlink = 0; - m->shared = 1; + m->do_unlink = FALSE; + m->shared = TRUE; pa_assert_se(pa_close(fd) == 0); @@ -346,12 +354,12 @@ int pa_shm_cleanup(void) { if (pa_shm_attach_ro(&seg, id) < 0) continue; - if (seg.size < PA_ALIGN(sizeof(struct shm_marker))) { + if (seg.size < SHM_MARKER_SIZE) { pa_shm_free(&seg); continue; } - m = (struct shm_marker*) ((uint8_t*) seg.ptr + seg.size - PA_ALIGN(sizeof(struct shm_marker))); + m = (struct shm_marker*) ((uint8_t*) seg.ptr + seg.size - SHM_MARKER_SIZE); if (pa_atomic_load(&m->marker) != SHM_MARKER) { pa_shm_free(&seg); diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c index 0229918c..0d05b00a 100644 --- a/src/pulsecore/sink-input.c +++ b/src/pulsecore/sink-input.c @@ -819,7 +819,7 @@ pa_usec_t pa_sink_input_set_requested_latency_within_thread(pa_sink_input *i, pa usec = i->sink->fixed_latency; if (usec != (pa_usec_t) -1) - usec = PA_CLAMP(usec, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency); + 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); diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c index 30fa5579..13f0e11e 100644 --- a/src/pulsecore/sink.c +++ b/src/pulsecore/sink.c @@ -190,6 +190,7 @@ pa_sink* pa_sink_new( s->core = core; s->state = PA_SINK_INIT; s->flags = flags; + s->suspend_cause = 0; s->name = pa_xstrdup(name); s->proplist = pa_proplist_copy(data->proplist); s->driver = pa_xstrdup(pa_path_get_filename(data->driver)); @@ -263,7 +264,9 @@ pa_sink* pa_sink_new( 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, 0); + 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); @@ -349,31 +352,28 @@ void pa_sink_put(pa_sink* s) { pa_assert(s->rtpoll); pa_assert(s->thread_info.min_latency <= s->thread_info.max_latency); - if (!(s->flags & PA_SINK_HW_VOLUME_CTRL)) { + /* Generally, flags should be initialized via pa_sink_new(). As a + * special exception we allow volume related flags to be set + * between _new() and _put(). */ + + if (!(s->flags & PA_SINK_HW_VOLUME_CTRL)) s->flags |= PA_SINK_DECIBEL_VOLUME; - s->base_volume = PA_VOLUME_NORM; - } + + if ((s->flags & PA_SINK_DECIBEL_VOLUME) && s->core->flat_volumes) + s->flags |= PA_SINK_FLAT_VOLUME; s->thread_info.soft_volume = s->soft_volume; s->thread_info.soft_muted = s->muted; - if (s->flags & PA_SINK_DECIBEL_VOLUME) - s->n_volume_steps = PA_VOLUME_NORM+1; - - if (s->core->flat_volumes) - if (s->flags & PA_SINK_DECIBEL_VOLUME) - s->flags |= PA_SINK_FLAT_VOLUME; - - if (s->flags & PA_SINK_LATENCY) - s->monitor_source->flags |= PA_SOURCE_LATENCY; + pa_assert((s->flags & PA_SINK_HW_VOLUME_CTRL) || (s->base_volume == PA_VOLUME_NORM && s->flags & PA_SINK_DECIBEL_VOLUME)); + 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->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)); - if (s->flags & PA_SINK_DYNAMIC_LATENCY) { - s->monitor_source->flags |= PA_SOURCE_DYNAMIC_LATENCY; - s->fixed_latency = 0; - } else if (s->fixed_latency <= 0) - s->fixed_latency = DEFAULT_FIXED_LATENCY; - - s->monitor_source->fixed_latency = s->fixed_latency; + pa_assert(s->monitor_source->fixed_latency == s->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); pa_assert_se(sink_set_state(s, PA_SINK_IDLE) == 0); @@ -500,11 +500,25 @@ int pa_sink_update_status(pa_sink*s) { } /* Called from main context */ -int pa_sink_suspend(pa_sink *s, pa_bool_t suspend) { +int pa_sink_suspend(pa_sink *s, pa_bool_t suspend, pa_suspend_cause_t cause) { pa_sink_assert_ref(s); pa_assert(PA_SINK_IS_LINKED(s->state)); + pa_assert(cause != 0); - if (suspend) + if (suspend) { + s->suspend_cause |= cause; + s->monitor_source->suspend_cause |= cause; + } else { + s->suspend_cause &= ~cause; + s->monitor_source->suspend_cause &= ~cause; + } + + if ((pa_sink_get_state(s) == PA_SINK_SUSPENDED) == !!s->suspend_cause) + return 0; + + pa_log_debug("Suspend cause of sink %s is 0x%04x, %s", s->name, s->suspend_cause, s->suspend_cause ? "suspending" : "resuming"); + + if (s->suspend_cause) return sink_set_state(s, PA_SINK_SUSPENDED); else return sink_set_state(s, pa_sink_used_by(s) ? PA_SINK_RUNNING : PA_SINK_IDLE); @@ -788,11 +802,17 @@ void pa_sink_render(pa_sink*s, size_t length, pa_memchunk *result) { pa_sw_cvolume_multiply(&volume, &s->thread_info.soft_volume, &info[0].volume); if (s->thread_info.soft_muted || !pa_cvolume_is_norm(&volume)) { - pa_memchunk_make_writable(result, 0); - if (s->thread_info.soft_muted || pa_cvolume_is_muted(&volume)) - pa_silence_memchunk(result, &s->sample_spec); - else + 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 { + pa_memchunk_make_writable(result, 0); pa_volume_memchunk(result, &s->sample_spec, &volume); + } } } else { void *ptr; @@ -933,22 +953,95 @@ void pa_sink_render_into_full(pa_sink *s, pa_memchunk *target) { /* Called from IO thread context */ void pa_sink_render_full(pa_sink *s, size_t length, pa_memchunk *result) { + pa_mix_info info[MAX_MIX_CHANNELS]; + size_t length1st = length; + unsigned n; + pa_sink_assert_ref(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_sink_ref(s); + pa_assert(!s->thread_info.rewind_requested); pa_assert(s->thread_info.rewind_nbytes == 0); - /*** This needs optimization ***/ + pa_assert(length > 0); - result->index = 0; - result->length = length; - result->memblock = pa_memblock_new(s->core->mempool, length); + n = fill_mix_info(s, &length1st, info, MAX_MIX_CHANNELS); - pa_sink_render_into_full(s, result); + if (n == 0) { + pa_silence_memchunk_get(&s->core->silence_cache, + s->core->mempool, + result, + &s->sample_spec, + length1st); + } 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_norm(&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 { + pa_memchunk_make_writable(result, length); + pa_volume_memchunk(result, &s->sample_spec, &volume); + } + } + } else { + void *ptr; + + result->index = 0; + result->memblock = pa_memblock_new(s->core->mempool, length); + + ptr = pa_memblock_acquire(result->memblock); + + result->length = pa_mix(info, n, + (uint8_t*) ptr + result->index, length1st, + &s->sample_spec, + &s->thread_info.soft_volume, + s->thread_info.soft_muted); + + pa_memblock_release(result->memblock); + } + + inputs_drop(s, info, n, result); + + if (result->length < length) { + pa_memchunk chunk; + size_t l, d; + pa_memchunk_make_writable(result, length); + + l = length - result->length; + d = result->index + result->length; + while (l > 0) { + chunk = *result; + chunk.index = d; + chunk.length = l; + + pa_sink_render_into(s, &chunk); + + d += chunk.length; + l -= chunk.length; + } + result->length = length; + } + + pa_sink_unref(s); } /* Called from main thread */ @@ -1272,8 +1365,12 @@ pa_bool_t pa_sink_get_mute(pa_sink *s, pa_bool_t force_refresh) { pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_MUTE, NULL, 0, NULL) == 0); - if (old_muted != s->muted) + if (old_muted != s->muted) { pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); + + /* Make sure the soft mute status stays in sync */ + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_MUTE, NULL, 0, NULL) == 0); + } } return s->muted; @@ -1744,17 +1841,18 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse } /* Called from main thread */ -int pa_sink_suspend_all(pa_core *c, pa_bool_t suspend) { +int pa_sink_suspend_all(pa_core *c, pa_bool_t suspend, pa_suspend_cause_t cause) { pa_sink *sink; uint32_t idx; int ret = 0; pa_core_assert_ref(c); + pa_assert(cause != 0); for (sink = PA_SINK(pa_idxset_first(c->sinks, &idx)); sink; sink = PA_SINK(pa_idxset_next(c->sinks, &idx))) { int r; - if ((r = pa_sink_suspend(sink, suspend)) < 0) + if ((r = pa_sink_suspend(sink, suspend, cause)) < 0) ret = r; } @@ -1863,8 +1961,11 @@ pa_usec_t pa_sink_get_requested_latency_within_thread(pa_sink *s) { if (result != (pa_usec_t) -1) result = PA_CLAMP(result, s->thread_info.min_latency, s->thread_info.max_latency); - s->thread_info.requested_latency = result; - s->thread_info.requested_latency_valid = TRUE; + 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; } @@ -2050,6 +2151,22 @@ void pa_sink_set_latency_range_within_thread(pa_sink *s, pa_usec_t min_latency, pa_source_set_latency_range_within_thread(s->monitor_source, min_latency, max_latency); } +/* Called from main thread, before the sink is put */ +void pa_sink_set_fixed_latency(pa_sink *s, pa_usec_t latency) { + pa_sink_assert_ref(s); + + pa_assert(pa_sink_get_state(s) == PA_SINK_INIT); + + if (latency < ABSOLUTE_MIN_LATENCY) + latency = ABSOLUTE_MIN_LATENCY; + + if (latency > ABSOLUTE_MAX_LATENCY) + latency = ABSOLUTE_MAX_LATENCY; + + s->fixed_latency = latency; + pa_source_set_fixed_latency(s->monitor_source, latency); +} + /* Called from main context */ size_t pa_sink_get_max_rewind(pa_sink *s) { size_t r; @@ -2099,6 +2216,21 @@ pa_bool_t pa_device_init_icon(pa_proplist *p, pa_bool_t is_sink) { 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) diff --git a/src/pulsecore/sink.h b/src/pulsecore/sink.h index 352282b8..4dce3f93 100644 --- a/src/pulsecore/sink.h +++ b/src/pulsecore/sink.h @@ -56,6 +56,7 @@ struct pa_sink { pa_core *core; pa_sink_state_t state; pa_sink_flags_t flags; + pa_suspend_cause_t suspend_cause; char *name; char *driver; /* may be NULL */ @@ -229,6 +230,7 @@ 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_detach(pa_sink *s); void pa_sink_attach(pa_sink *s); @@ -251,8 +253,8 @@ 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, pa_bool_t suspend); -int pa_sink_suspend_all(pa_core *c, pa_bool_t suspend); +int pa_sink_suspend(pa_sink *s, pa_bool_t suspend, pa_suspend_cause_t cause); +int pa_sink_suspend_all(pa_core *c, pa_bool_t suspend, pa_suspend_cause_t cause); void pa_sink_update_flat_volume(pa_sink *s, pa_cvolume *new_volume); void pa_sink_propagate_flat_volume(pa_sink *s); diff --git a/src/pulsecore/sndfile-util.c b/src/pulsecore/sndfile-util.c new file mode 100644 index 00000000..032aefca --- /dev/null +++ b/src/pulsecore/sndfile-util.c @@ -0,0 +1,462 @@ +/*** + 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +/* Shared between pacat/parec/paplay and the server */ + +#include <pulse/xmalloc.h> +#include <pulse/utf8.h> + +#include <pulsecore/macro.h> +#include <pulsecore/core-util.h> + +#include "sndfile-util.h" + +int pa_sndfile_read_sample_spec(SNDFILE *sf, pa_sample_spec *ss) { + SF_INFO sfi; + + pa_assert(sf); + pa_assert(ss); + + pa_zero(sfi); + pa_assert_se(sf_command(sf, SFC_GET_CURRENT_SF_INFO, &sfi, sizeof(sfi)) == 0); + + 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: + 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: + case PA_SAMPLE_S24_32LE: + case PA_SAMPLE_S24_32BE: + ss->format = PA_SAMPLE_S32NE; + sfi->format |= SF_FORMAT_PCM_24; + break; + + case PA_SAMPLE_S32LE: + case PA_SAMPLE_S32RE: + 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 distuingishes left und 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 *channels; + unsigned c; + + pa_assert(sf); + pa_assert(cm); + + pa_zero(sfi); + pa_assert_se(sf_command(sf, SFC_GET_CURRENT_SF_INFO, &sfi, sizeof(sfi)) == 0); + + 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; + 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); + pa_assert_se(sf_command(sf, SFC_GET_CURRENT_SF_INFO, &sfi, sizeof(sfi)) == 0); + + 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: + 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: + 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: + 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: + 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); + + /* First try to match via full type string */ + 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); + + if (strcasecmp(name, fi.name) == 0) + return i; + } + + /* Then, try to match via the full extension */ + 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); + + if (strcasecmp(name, fi.extension) == 0) + return i; + } + + /* Then, try to match via the start of the type string */ + 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); + + if (strncasecmp(name, fi.extension, strlen(name)) == 0) + return i; + } + + 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 00000000..74021da1 --- /dev/null +++ b/src/pulsecore/sndfile-util.h @@ -0,0 +1,52 @@ +#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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#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/sound-file-stream.c b/src/pulsecore/sound-file-stream.c index 3453637f..502e5c69 100644 --- a/src/pulsecore/sound-file-stream.c +++ b/src/pulsecore/sound-file-stream.c @@ -41,6 +41,7 @@ #include <pulsecore/thread-mq.h> #include <pulsecore/core-util.h> #include <pulsecore/sample-util.h> +#include <pulsecore/sndfile-util.h> #include "sound-file-stream.h" @@ -234,10 +235,11 @@ int pa_play_file( const pa_cvolume *volume) { file_stream *u = NULL; - SF_INFO sfinfo; pa_sample_spec ss; + pa_channel_map cm; pa_sink_input_new_data data; int fd; + SF_INFO sfi; pa_assert(sink); pa_assert(fname); @@ -251,8 +253,6 @@ int pa_play_file( u->readf_function = NULL; u->memblockq = NULL; - memset(&sfinfo, 0, sizeof(sfinfo)); - if ((fd = open(fname, O_RDONLY #ifdef O_NOCTTY |O_NOCTTY @@ -281,50 +281,36 @@ int pa_play_file( pa_log_debug("POSIX_FADV_WILLNEED succeeded."); #endif - if (!(u->sndfile = sf_open_fd(fd, SFM_READ, &sfinfo, 1))) { + pa_zero(sfi); + if (!(u->sndfile = sf_open_fd(fd, SFM_READ, &sfi, 1))) { pa_log("Failed to open file %s", fname); - pa_close(fd); goto fail; } - switch (sfinfo.format & 0xFF) { - case SF_FORMAT_PCM_16: - case SF_FORMAT_PCM_U8: - case SF_FORMAT_PCM_S8: - ss.format = PA_SAMPLE_S16NE; - u->readf_function = (sf_count_t (*)(SNDFILE *sndfile, void *ptr, sf_count_t frames)) sf_readf_short; - break; - - case SF_FORMAT_ULAW: - ss.format = PA_SAMPLE_ULAW; - break; - - case SF_FORMAT_ALAW: - ss.format = PA_SAMPLE_ALAW; - break; + fd = -1; - case SF_FORMAT_FLOAT: - default: - ss.format = PA_SAMPLE_FLOAT32NE; - u->readf_function = (sf_count_t (*)(SNDFILE *sndfile, void *ptr, sf_count_t frames)) sf_readf_float; - break; + if (pa_sndfile_read_sample_spec(u->sndfile, &ss) < 0) { + pa_log("Failed to determine file sample format."); + goto fail; } - ss.rate = (uint32_t) sfinfo.samplerate; - ss.channels = (uint8_t) sfinfo.channels; - - if (!pa_sample_spec_valid(&ss)) { - pa_log("Unsupported sample format in file %s", fname); - 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); data.sink = sink; 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, 0); pa_sink_input_new_data_done(&data); @@ -352,5 +338,8 @@ fail: if (u) file_stream_unref(u); + if (fd >= 0) + pa_close(fd); + return -1; } diff --git a/src/pulsecore/sound-file.c b/src/pulsecore/sound-file.c index db75ae08..2d9b76ad 100644 --- a/src/pulsecore/sound-file.c +++ b/src/pulsecore/sound-file.c @@ -35,19 +35,21 @@ #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" -#include "core-scache.h" int pa_sound_file_load( pa_mempool *pool, const char *fname, pa_sample_spec *ss, pa_channel_map *map, - pa_memchunk *chunk) { + pa_memchunk *chunk, + pa_proplist *p) { SNDFILE *sf = NULL; - SF_INFO sfinfo; + SF_INFO sfi; int ret = -1; size_t l; sf_count_t (*readf_function)(SNDFILE *sndfile, void *ptr, sf_count_t frames) = NULL; @@ -59,7 +61,6 @@ int pa_sound_file_load( pa_assert(chunk); pa_memchunk_reset(chunk); - memset(&sfinfo, 0, sizeof(sfinfo)); if ((fd = open(fname, O_RDONLY #ifdef O_NOCTTY @@ -78,48 +79,29 @@ int pa_sound_file_load( pa_log_debug("POSIX_FADV_SEQUENTIAL succeeded."); #endif - if (!(sf = sf_open_fd(fd, SFM_READ, &sfinfo, 1))) { + pa_zero(sfi); + if (!(sf = sf_open_fd(fd, SFM_READ, &sfi, 1))) { pa_log("Failed to open file %s", fname); - pa_close(fd); goto finish; } - switch (sfinfo.format & SF_FORMAT_SUBMASK) { - case SF_FORMAT_PCM_16: - case SF_FORMAT_PCM_U8: - case SF_FORMAT_PCM_S8: - ss->format = PA_SAMPLE_S16NE; - readf_function = (sf_count_t (*)(SNDFILE *sndfile, void *_ptr, sf_count_t frames)) sf_readf_short; - 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; - readf_function = (sf_count_t (*)(SNDFILE *sndfile, void *_ptr, sf_count_t frames)) sf_readf_float; - break; - } - - ss->rate = (uint32_t) sfinfo.samplerate; - ss->channels = (uint8_t) sfinfo.channels; + fd = -1; - if (!pa_sample_spec_valid(ss)) { - pa_log("Unsupported sample format in file %s", fname); + if (pa_sndfile_read_sample_spec(sf, ss) < 0) { + pa_log("Failed to determine file sample format."); goto finish; } - if (map) + 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 ((l = pa_frame_size(ss) * (size_t) sfinfo.frames) > PA_SCACHE_ENTRY_SIZE_MAX) { + 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; } @@ -128,9 +110,11 @@ int pa_sound_file_load( 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, sfinfo.frames) != sfinfo.frames) || + 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; @@ -149,55 +133,35 @@ finish: 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 sfinfo; + SF_INFO sfi; pa_sample_spec ss; pa_assert(fname); - if (!(sf = sf_open(fname, SFM_READ, &sfinfo))) { + pa_zero(sfi); + if (!(sf = sf_open(fname, SFM_READ, &sfi))) { pa_log("Failed to open file %s", fname); return -1; } - sf_close(sf); - - switch (sfinfo.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_ULAW: - ss.format = PA_SAMPLE_ULAW; - break; - - case SF_FORMAT_ALAW: - ss.format = PA_SAMPLE_ALAW; - break; - - case SF_FORMAT_DOUBLE: - case SF_FORMAT_FLOAT: - default: - ss.format = PA_SAMPLE_FLOAT32NE; - break; - } - - ss.rate = (uint32_t) sfinfo.samplerate; - ss.channels = (uint8_t) sfinfo.channels; - - if (!pa_sample_spec_valid(&ss)) { - pa_log("Unsupported sample format in file %s", fname); + if (pa_sndfile_read_sample_spec(sf, &ss) < 0) { + pa_log("Failed to determine file sample format."); + sf_close(sf); return -1; } - if ((pa_frame_size(&ss) * (size_t) sfinfo.frames) > PA_SCACHE_ENTRY_SIZE_MAX) { + 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; } diff --git a/src/pulsecore/sound-file.h b/src/pulsecore/sound-file.h index 34e02616..4dc0c7e1 100644 --- a/src/pulsecore/sound-file.h +++ b/src/pulsecore/sound-file.h @@ -26,7 +26,7 @@ #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); +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); diff --git a/src/pulsecore/source.c b/src/pulsecore/source.c index 21902509..53697c57 100644 --- a/src/pulsecore/source.c +++ b/src/pulsecore/source.c @@ -180,6 +180,7 @@ pa_source* pa_source_new( s->core = core; s->state = PA_SOURCE_INIT; s->flags = flags; + s->suspend_cause = 0; s->name = pa_xstrdup(name); s->proplist = pa_proplist_copy(data->proplist); s->driver = pa_xstrdup(pa_path_get_filename(data->driver)); @@ -308,20 +309,19 @@ void pa_source_put(pa_source *s) { pa_assert(s->rtpoll); pa_assert(s->thread_info.min_latency <= s->thread_info.max_latency); - if (!(s->flags & PA_SOURCE_HW_VOLUME_CTRL)) { - s->flags |= PA_SOURCE_DECIBEL_VOLUME; + /* Generally, flags should be initialized via pa_source_new(). As + * a special exception we allow volume related flags to be set + * between _new() and _put(). */ - s->thread_info.soft_volume = s->soft_volume; - s->thread_info.soft_muted = s->muted; - } + if (!(s->flags & PA_SOURCE_HW_VOLUME_CTRL)) + s->flags |= PA_SOURCE_DECIBEL_VOLUME; - if (s->flags & PA_SOURCE_DECIBEL_VOLUME) - s->n_volume_steps = PA_VOLUME_NORM+1; + s->thread_info.soft_volume = s->soft_volume; + s->thread_info.soft_muted = s->muted; - if (s->flags & PA_SOURCE_DYNAMIC_LATENCY) - s->fixed_latency = 0; - else if (s->fixed_latency <= 0) - s->fixed_latency = DEFAULT_FIXED_LATENCY; + pa_assert((s->flags & PA_SOURCE_HW_VOLUME_CTRL) || (s->base_volume == PA_VOLUME_NORM && s->flags & PA_SOURCE_DECIBEL_VOLUME)); + 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->fixed_latency != 0)); pa_assert_se(source_set_state(s, PA_SOURCE_IDLE) == 0); @@ -428,14 +428,25 @@ int pa_source_update_status(pa_source*s) { } /* Called from main context */ -int pa_source_suspend(pa_source *s, pa_bool_t suspend) { +int pa_source_suspend(pa_source *s, pa_bool_t suspend, pa_suspend_cause_t cause) { pa_source_assert_ref(s); pa_assert(PA_SOURCE_IS_LINKED(s->state)); + pa_assert(cause != 0); if (s->monitor_of) return -PA_ERR_NOTSUPPORTED; if (suspend) + s->suspend_cause |= cause; + else + s->suspend_cause &= ~cause; + + if ((pa_source_get_state(s) == PA_SOURCE_SUSPENDED) == !!s->suspend_cause) + return 0; + + pa_log_debug("Suspend cause of source %s is 0x%04x, %s", s->name, s->suspend_cause, s->suspend_cause ? "suspending" : "resuming"); + + if (suspend) return source_set_state(s, PA_SOURCE_SUSPENDED); else return source_set_state(s, pa_source_used_by(s) ? PA_SOURCE_RUNNING : PA_SOURCE_IDLE); @@ -757,8 +768,12 @@ pa_bool_t pa_source_get_mute(pa_source *s, pa_bool_t force_refresh) { pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_GET_MUTE, NULL, 0, NULL) == 0); - if (old_muted != s->muted) + if (old_muted != s->muted) { pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); + + /* Make sure the soft mute status stays in sync */ + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_MUTE, NULL, 0, NULL) == 0); + } } return s->muted; @@ -1033,12 +1048,13 @@ int pa_source_process_msg(pa_msgobject *object, int code, void *userdata, int64_ } /* Called from main thread */ -int pa_source_suspend_all(pa_core *c, pa_bool_t suspend) { +int pa_source_suspend_all(pa_core *c, pa_bool_t suspend, pa_suspend_cause_t cause) { uint32_t idx; pa_source *source; int ret = 0; pa_core_assert_ref(c); + 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; @@ -1046,7 +1062,7 @@ int pa_source_suspend_all(pa_core *c, pa_bool_t suspend) { if (source->monitor_of) continue; - if ((r = pa_source_suspend(source, suspend)) < 0) + if ((r = pa_source_suspend(source, suspend, cause)) < 0) ret = r; } @@ -1118,8 +1134,11 @@ pa_usec_t pa_source_get_requested_latency_within_thread(pa_source *s) { if (result != (pa_usec_t) -1) result = PA_CLAMP(result, s->thread_info.min_latency, s->thread_info.max_latency); - s->thread_info.requested_latency = result; - s->thread_info.requested_latency_valid = TRUE; + 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; } @@ -1276,6 +1295,21 @@ void pa_source_set_latency_range_within_thread(pa_source *s, pa_usec_t min_laten pa_source_invalidate_requested_latency(s); } +/* 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(pa_source_get_state(s) == PA_SOURCE_INIT); + + if (latency < ABSOLUTE_MIN_LATENCY) + latency = ABSOLUTE_MIN_LATENCY; + + if (latency > ABSOLUTE_MAX_LATENCY) + latency = ABSOLUTE_MAX_LATENCY; + + s->fixed_latency = latency; +} + /* Called from main thread */ size_t pa_source_get_max_rewind(pa_source *s) { size_t r; diff --git a/src/pulsecore/source.h b/src/pulsecore/source.h index b502c228..1fbed70e 100644 --- a/src/pulsecore/source.h +++ b/src/pulsecore/source.h @@ -58,6 +58,7 @@ struct pa_source { pa_core *core; pa_source_state_t state; pa_source_flags_t flags; + pa_suspend_cause_t suspend_cause; char *name; char *driver; /* may be NULL */ @@ -210,6 +211,7 @@ 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_detach(pa_source *s); void pa_source_attach(pa_source *s); @@ -230,8 +232,8 @@ void pa_source_get_latency_range(pa_source *s, pa_usec_t *min_latency, pa_usec_t 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, pa_bool_t suspend); -int pa_source_suspend_all(pa_core *c, pa_bool_t suspend); +int pa_source_suspend(pa_source *s, pa_bool_t suspend, pa_suspend_cause_t cause); +int pa_source_suspend_all(pa_core *c, pa_bool_t suspend, pa_suspend_cause_t cause); void pa_source_set_volume(pa_source *source, const pa_cvolume *volume); const pa_cvolume *pa_source_get_volume(pa_source *source, pa_bool_t force_refresh); diff --git a/src/pulsecore/strbuf.c b/src/pulsecore/strbuf.c index 9f5a84b4..4fc82ded 100644 --- a/src/pulsecore/strbuf.c +++ b/src/pulsecore/strbuf.c @@ -113,6 +113,13 @@ void pa_strbuf_puts(pa_strbuf *sb, const char *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); diff --git a/src/pulsecore/strbuf.h b/src/pulsecore/strbuf.h index 05e69e03..d71ecb98 100644 --- a/src/pulsecore/strbuf.h +++ b/src/pulsecore/strbuf.h @@ -35,6 +35,7 @@ char *pa_strbuf_tostring_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); pa_bool_t pa_strbuf_isempty(pa_strbuf *sb); diff --git a/src/pulsecore/strlist.c b/src/pulsecore/strlist.c index cbafbba6..0f4ca867 100644 --- a/src/pulsecore/strlist.c +++ b/src/pulsecore/strlist.c @@ -159,3 +159,15 @@ pa_strlist *pa_strlist_reverse(pa_strlist *l) { 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 index 2584e86c..e57203c5 100644 --- a/src/pulsecore/strlist.h +++ b/src/pulsecore/strlist.h @@ -47,4 +47,10 @@ 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 |