diff options
Diffstat (limited to 'src/pulsecore')
38 files changed, 2177 insertions, 712 deletions
diff --git a/src/pulsecore/authkey.c b/src/pulsecore/authkey.c index b122fee..1e31d07 100644 --- a/src/pulsecore/authkey.c +++ b/src/pulsecore/authkey.c @@ -190,7 +190,7 @@ int pa_authkey_load_auto(const char *fn, void *data, size_t length) { return pa_authkey_load(p, data, length); } -/* Store the specified cookie in the speicified cookie file */ +/* Store the specified cookie in the specified cookie file */ int pa_authkey_save(const char *fn, const void *data, size_t length) { int fd = -1; int unlock = 0, ret = -1; diff --git a/src/pulsecore/card.c b/src/pulsecore/card.c index 6419c23..8101a92 100644 --- a/src/pulsecore/card.c +++ b/src/pulsecore/card.c @@ -141,9 +141,11 @@ pa_card *pa_card_new(pa_core *core, pa_card_new_data *data) { data->profiles = NULL; c->active_profile = NULL; + c->save_profile = FALSE; if (data->active_profile && c->profiles) - c->active_profile = pa_hashmap_get(c->profiles, data->active_profile); + if ((c->active_profile = pa_hashmap_get(c->profiles, data->active_profile))) + c->save_profile = data->save_profile; if (!c->active_profile && c->profiles) { void *state = NULL; @@ -209,7 +211,7 @@ void pa_card_free(pa_card *c) { pa_xfree(c); } -int pa_card_set_profile(pa_card *c, const char *name) { +int pa_card_set_profile(pa_card *c, const char *name, pa_bool_t save) { pa_card_profile *profile; pa_assert(c); @@ -224,8 +226,10 @@ int pa_card_set_profile(pa_card *c, const char *name) { if (!(profile = pa_hashmap_get(c->profiles, name))) return -1; - if (c->active_profile == profile) + if (c->active_profile == profile) { + c->save_profile = c->save_profile || save; return 0; + } if (c->set_profile(c, profile) < 0) return -1; @@ -235,6 +239,7 @@ int pa_card_set_profile(pa_card *c, const char *name) { pa_log_info("Changed profile of card %u \"%s\" to %s", c->index, c->name, profile->name); c->active_profile = profile; + c->save_profile = save; return 0; } diff --git a/src/pulsecore/card.h b/src/pulsecore/card.h index c80d4e2..3b7608f 100644 --- a/src/pulsecore/card.h +++ b/src/pulsecore/card.h @@ -63,6 +63,8 @@ struct pa_card { pa_hashmap *profiles; pa_card_profile *active_profile; + pa_bool_t save_profile; + void *userdata; int (*set_profile)(pa_card *c, pa_card_profile *profile); @@ -80,6 +82,8 @@ typedef struct pa_card_new_data { char *active_profile; pa_bool_t namereg_fail:1; + + pa_bool_t save_profile:1; } pa_card_new_data; pa_card_profile *pa_card_profile_new(const char *name, const char *description, size_t extra); @@ -93,7 +97,7 @@ void pa_card_new_data_done(pa_card_new_data *data); pa_card *pa_card_new(pa_core *c, pa_card_new_data *data); void pa_card_free(pa_card *c); -int pa_card_set_profile(pa_card *c, const char *name); +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); diff --git a/src/pulsecore/cli-command.c b/src/pulsecore/cli-command.c index b5f7e7f..15fe525 100644 --- a/src/pulsecore/cli-command.c +++ b/src/pulsecore/cli-command.c @@ -175,7 +175,7 @@ static const struct command commands[] = { { "suspend-sink", pa_cli_command_suspend_sink, "Suspend sink (args: index|name, bool)", 3}, { "suspend-source", pa_cli_command_suspend_source, "Suspend source (args: index|name, bool)", 3}, { "suspend", pa_cli_command_suspend, "Suspend all sinks and all sources (args: bool)", 2}, - { "set-card-profile", pa_cli_command_card_profile, "Change the profile of a card (aargs: index, name)", 3}, + { "set-card-profile", pa_cli_command_card_profile, "Change the profile of a card (args: index, name)", 3}, { "set-log-level", pa_cli_command_log_level, "Change the log level (args: numeric level)", 2}, { "set-log-meta", pa_cli_command_log_meta, "Show source code location in log messages (args: bool)", 2}, { "set-log-time", pa_cli_command_log_time, "Show timestamps in log messages (args: bool)", 2}, @@ -524,7 +524,7 @@ static int pa_cli_command_sink_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *bu } pa_cvolume_set(&cvolume, sink->sample_spec.channels, volume); - pa_sink_set_volume(sink, &cvolume, TRUE, TRUE); + pa_sink_set_volume(sink, &cvolume, TRUE, TRUE, TRUE); return 0; } @@ -566,7 +566,7 @@ static int pa_cli_command_sink_input_volume(pa_core *c, pa_tokenizer *t, pa_strb } pa_cvolume_set(&cvolume, si->sample_spec.channels, volume); - pa_sink_input_set_volume(si, &cvolume, TRUE); + pa_sink_input_set_volume(si, &cvolume, TRUE, TRUE); return 0; } @@ -699,7 +699,10 @@ static int pa_cli_command_update_sink_proplist(pa_core *c, pa_tokenizer *t, pa_s return -1; } - p = pa_proplist_from_string(s); + if (!(p = pa_proplist_from_string(s))) { + pa_strbuf_puts(buf, "Failed to parse proplist.\n"); + return -1; + } pa_sink_update_proplist(sink, PA_UPDATE_REPLACE, p); @@ -733,7 +736,10 @@ static int pa_cli_command_update_source_proplist(pa_core *c, pa_tokenizer *t, pa return -1; } - p = pa_proplist_from_string(s); + if (!(p = pa_proplist_from_string(s))) { + pa_strbuf_puts(buf, "Failed to parse proplist.\n"); + return -1; + } pa_source_update_proplist(source, PA_UPDATE_REPLACE, p); @@ -773,7 +779,10 @@ static int pa_cli_command_update_sink_input_proplist(pa_core *c, pa_tokenizer *t return -1; } - p = pa_proplist_from_string(s); + if (!(p = pa_proplist_from_string(s))) { + pa_strbuf_puts(buf, "Failed to parse proplist.\n"); + return -1; + } pa_sink_input_update_proplist(si, PA_UPDATE_REPLACE, p); @@ -813,7 +822,10 @@ static int pa_cli_command_update_source_output_proplist(pa_core *c, pa_tokenizer return -1; } - p = pa_proplist_from_string(s); + if (!(p = pa_proplist_from_string(s))) { + pa_strbuf_puts(buf, "Failed to parse proplist.\n"); + return -1; + } pa_source_output_update_proplist(so, PA_UPDATE_REPLACE, p); @@ -1454,7 +1466,7 @@ static int pa_cli_command_card_profile(pa_core *c, pa_tokenizer *t, pa_strbuf *b return -1; } - if (pa_card_set_profile(card, p) < 0) { + if (pa_card_set_profile(card, p, TRUE) < 0) { pa_strbuf_printf(buf, "Failed to set card profile to '%s'.\n", p); return -1; } @@ -1504,7 +1516,7 @@ static int pa_cli_command_dump(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_b nl = 1; } - pa_strbuf_printf(buf, "set-sink-volume %s 0x%03x\n", sink->name, pa_cvolume_avg(pa_sink_get_volume(sink, FALSE))); + pa_strbuf_printf(buf, "set-sink-volume %s 0x%03x\n", sink->name, pa_cvolume_avg(pa_sink_get_volume(sink, FALSE, TRUE))); pa_strbuf_printf(buf, "set-sink-mute %s %s\n", sink->name, pa_yes_no(pa_sink_get_mute(sink, FALSE))); pa_strbuf_printf(buf, "suspend-sink %s %s\n", sink->name, pa_yes_no(pa_sink_get_state(sink) == PA_SINK_SUSPENDED)); } diff --git a/src/pulsecore/cli-text.c b/src/pulsecore/cli-text.c index 76adc4d..604678b 100644 --- a/src/pulsecore/cli-text.c +++ b/src/pulsecore/cli-text.c @@ -220,19 +220,17 @@ char *pa_sink_list_to_string(pa_core *c) { v[PA_VOLUME_SNPRINT_MAX], vdb[PA_SW_VOLUME_SNPRINT_DB_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX], *t; - pa_usec_t min_latency, max_latency; const char *cmn; cmn = pa_channel_map_to_pretty_name(&sink->channel_map); - pa_sink_get_latency_range(sink, &min_latency, &max_latency); pa_strbuf_printf( s, " %c index: %u\n" "\tname: <%s>\n" "\tdriver: <%s>\n" - "\tflags: %s%s%s%s%s%s%s\n" + "\tflags: %s%s%s%s%s%s%s%s\n" "\tstate: %s\n" "\tvolume: %s%s%s\n" "\t balance %0.2f\n" @@ -240,7 +238,6 @@ char *pa_sink_list_to_string(pa_core *c) { "\tvolume steps: %u\n" "\tmuted: %s\n" "\tcurrent latency: %0.2f ms\n" - "\tconfigured latency: %0.2f ms; range is %0.2f .. %0.2f ms\n" "\tmax request: %lu KiB\n" "\tmax rewind: %lu KiB\n" "\tmonitor source: %u\n" @@ -258,21 +255,19 @@ char *pa_sink_list_to_string(pa_core *c) { sink->flags & PA_SINK_HW_VOLUME_CTRL ? "HW_VOLUME_CTRL " : "", sink->flags & PA_SINK_DECIBEL_VOLUME ? "DECIBEL_VOLUME " : "", sink->flags & PA_SINK_LATENCY ? "LATENCY " : "", - sink->flags & PA_SINK_FLAT_VOLUME ? "FLAT_VOLUME" : "", + sink->flags & PA_SINK_FLAT_VOLUME ? "FLAT_VOLUME " : "", + sink->flags & PA_SINK_DYNAMIC_LATENCY ? "DYNAMIC_LATENCY" : "", sink_state_to_string(pa_sink_get_state(sink)), - pa_cvolume_snprint(cv, sizeof(cv), pa_sink_get_volume(sink, FALSE)), + 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)) : "", - pa_cvolume_get_balance(pa_sink_get_volume(sink, FALSE), &sink->channel_map), + sink->flags & PA_SINK_DECIBEL_VOLUME ? pa_sw_cvolume_snprint_dB(cvdb, sizeof(cvdb), pa_sink_get_volume(sink, FALSE, FALSE)) : "", + pa_cvolume_get_balance(pa_sink_get_volume(sink, FALSE, FALSE), &sink->channel_map), pa_volume_snprint(v, sizeof(v), sink->base_volume), sink->flags & PA_SINK_DECIBEL_VOLUME ? "\n\t " : "", sink->flags & PA_SINK_DECIBEL_VOLUME ? pa_sw_volume_snprint_dB(vdb, sizeof(vdb), sink->base_volume) : "", sink->n_volume_steps, pa_yes_no(pa_sink_get_mute(sink, FALSE)), (double) pa_sink_get_latency(sink) / (double) PA_USEC_PER_MSEC, - (double) pa_sink_get_requested_latency(sink) / (double) PA_USEC_PER_MSEC, - (double) min_latency / PA_USEC_PER_MSEC, - (double) max_latency / PA_USEC_PER_MSEC, (unsigned long) pa_sink_get_max_request(sink) / 1024, (unsigned long) pa_sink_get_max_rewind(sink) / 1024, sink->monitor_source ? sink->monitor_source->index : PA_INVALID_INDEX, @@ -283,6 +278,22 @@ char *pa_sink_list_to_string(pa_core *c) { pa_sink_used_by(sink), pa_sink_linked_by(sink)); + if (sink->flags & PA_SINK_DYNAMIC_LATENCY) { + pa_usec_t min_latency, max_latency; + pa_sink_get_latency_range(sink, &min_latency, &max_latency); + + pa_strbuf_printf( + s, + "\tconfigured latency: %0.2f ms; range is %0.2f .. %0.2f ms\n", + (double) pa_sink_get_requested_latency(sink) / (double) PA_USEC_PER_MSEC, + (double) min_latency / PA_USEC_PER_MSEC, + (double) max_latency / PA_USEC_PER_MSEC); + } else + pa_strbuf_printf( + s, + "\tfixed latency: %0.2f ms\n", + (double) pa_sink_get_requested_latency(sink) / PA_USEC_PER_MSEC); + if (sink->card) pa_strbuf_printf(s, "\tcard: %u <%s>\n", sink->card->index, sink->card->name); if (sink->module) @@ -313,19 +324,16 @@ char *pa_source_list_to_string(pa_core *c) { v[PA_VOLUME_SNPRINT_MAX], vdb[PA_SW_VOLUME_SNPRINT_DB_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX], *t; - pa_usec_t min_latency, max_latency; const char *cmn; cmn = pa_channel_map_to_pretty_name(&source->channel_map); - pa_source_get_latency_range(source, &min_latency, &max_latency); - pa_strbuf_printf( s, " %c index: %u\n" "\tname: <%s>\n" "\tdriver: <%s>\n" - "\tflags: %s%s%s%s%s%s\n" + "\tflags: %s%s%s%s%s%s%s\n" "\tstate: %s\n" "\tvolume: %s%s%s\n" "\t balance %0.2f\n" @@ -333,7 +341,6 @@ char *pa_source_list_to_string(pa_core *c) { "\tvolume steps: %u\n" "\tmuted: %s\n" "\tcurrent latency: %0.2f ms\n" - "\tconfigured latency: %0.2f ms; range is %0.2f .. %0.2f ms\n" "\tmax rewind: %lu KiB\n" "\tsample spec: %s\n" "\tchannel map: %s%s%s\n" @@ -349,6 +356,7 @@ char *pa_source_list_to_string(pa_core *c) { source->flags & PA_SOURCE_HW_VOLUME_CTRL ? "HW_VOLUME_CTRL " : "", source->flags & PA_SOURCE_DECIBEL_VOLUME ? "DECIBEL_VOLUME " : "", source->flags & PA_SOURCE_LATENCY ? "LATENCY " : "", + source->flags & PA_SOURCE_DYNAMIC_LATENCY ? "DYNAMIC_LATENCY" : "", source_state_to_string(pa_source_get_state(source)), pa_cvolume_snprint(cv, sizeof(cv), pa_source_get_volume(source, FALSE)), source->flags & PA_SOURCE_DECIBEL_VOLUME ? "\n\t " : "", @@ -360,9 +368,6 @@ char *pa_source_list_to_string(pa_core *c) { source->n_volume_steps, pa_yes_no(pa_source_get_mute(source, FALSE)), (double) pa_source_get_latency(source) / PA_USEC_PER_MSEC, - (double) pa_source_get_requested_latency(source) / PA_USEC_PER_MSEC, - (double) min_latency / PA_USEC_PER_MSEC, - (double) max_latency / PA_USEC_PER_MSEC, (unsigned long) pa_source_get_max_rewind(source) / 1024, pa_sample_spec_snprint(ss, sizeof(ss), &source->sample_spec), pa_channel_map_snprint(cm, sizeof(cm), &source->channel_map), @@ -371,6 +376,22 @@ char *pa_source_list_to_string(pa_core *c) { pa_source_used_by(source), pa_source_linked_by(source)); + if (source->flags & PA_SOURCE_DYNAMIC_LATENCY) { + pa_usec_t min_latency, max_latency; + pa_source_get_latency_range(source, &min_latency, &max_latency); + + pa_strbuf_printf( + s, + "\tconfigured latency: %0.2f ms; range is %0.2f .. %0.2f ms\n", + (double) pa_source_get_requested_latency(source) / PA_USEC_PER_MSEC, + (double) min_latency / PA_USEC_PER_MSEC, + (double) max_latency / PA_USEC_PER_MSEC); + } else + pa_strbuf_printf( + s, + "\tfixed latency: %0.2f ms\n", + (double) pa_source_get_requested_latency(source) / PA_USEC_PER_MSEC); + if (source->monitor_of) pa_strbuf_printf(s, "\tmonitor_of: %u\n", source->monitor_of->index); if (source->card) @@ -486,6 +507,9 @@ char *pa_sink_input_list_to_string(pa_core *c) { char ss[PA_SAMPLE_SPEC_SNPRINT_MAX], cvdb[PA_SW_CVOLUME_SNPRINT_DB_MAX], cv[PA_CVOLUME_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX], *t, clt[28]; pa_usec_t cl; const char *cmn; + pa_cvolume v; + + pa_sink_input_get_volume(i, &v, TRUE); cmn = pa_channel_map_to_pretty_name(&i->channel_map); @@ -526,9 +550,9 @@ char *pa_sink_input_list_to_string(pa_core *c) { i->flags & PA_SINK_INPUT_FAIL_ON_SUSPEND ? "FAIL_ON_SUSPEND " : "", state_table[pa_sink_input_get_state(i)], i->sink->index, i->sink->name, - pa_cvolume_snprint(cv, sizeof(cv), pa_sink_input_get_volume(i)), - pa_sw_cvolume_snprint_dB(cvdb, sizeof(cvdb), pa_sink_input_get_volume(i)), - pa_cvolume_get_balance(pa_sink_input_get_volume(i), &i->channel_map), + pa_cvolume_snprint(cv, sizeof(cv), &v), + pa_sw_cvolume_snprint_dB(cvdb, sizeof(cvdb), &v), + pa_cvolume_get_balance(&v, &i->channel_map), pa_yes_no(pa_sink_input_get_mute(i)), (double) pa_sink_input_get_latency(i, NULL) / PA_USEC_PER_MSEC, clt, diff --git a/src/pulsecore/core-util.c b/src/pulsecore/core-util.c index 0d243ee..24d929d 100644 --- a/src/pulsecore/core-util.c +++ b/src/pulsecore/core-util.c @@ -326,7 +326,7 @@ ssize_t pa_write(int fd, const void *buf, size_t count, int *type) { } /** Calls read() in a loop. Makes sure that as much as 'size' bytes, - * unless EOF is reached or an error occured */ + * unless EOF is reached or an error occurred */ ssize_t pa_loop_read(int fd, void*data, size_t size, int *type) { ssize_t ret = 0; int _type; @@ -1242,7 +1242,7 @@ int pa_lock_lockfile(const char *fn) { goto fail; } - /* Check wheter the file has been removed meanwhile. When yes, + /* Check whether the file has been removed meanwhile. When yes, * restart this loop, otherwise, we're done */ if (st.st_nlink >= 1) break; @@ -2462,7 +2462,7 @@ char *pa_machine_id(void) { pa_strip_nl(ln); if (r && ln[0]) - return pa_xstrdup(ln); + return pa_utf8_filter(ln); } /* The we fall back to the host name. It supposed to be somewhat @@ -2480,13 +2480,16 @@ char *pa_machine_id(void) { break; } else if (strlen(c) < l-1) { + char *u; if (*c == 0) { pa_xfree(c); break; } - return c; + u = pa_utf8_filter(c); + pa_xfree(c); + return u; } /* Hmm, the hostname is as long the space we offered the @@ -2498,10 +2501,19 @@ char *pa_machine_id(void) { } /* If no hostname was set we use the POSIX hostid. It's usually - * the IPv4 address. Mit not be that stable. */ + * the IPv4 address. Might not be that stable. */ return pa_sprintf_malloc("%08lx", (unsigned long) gethostid); } +char *pa_session_id(void) { + const char *e; + + if (!(e = getenv("XDG_SESSION_COOKIE"))) + return NULL; + + return pa_utf8_filter(e); +} + char *pa_uname_string(void) { struct utsname u; @@ -2608,7 +2620,7 @@ char *pa_unescape(char *p) { } char *pa_realpath(const char *path) { - char *r, *t; + char *t; pa_assert(path); /* We want only abolsute paths */ @@ -2617,17 +2629,31 @@ char *pa_realpath(const char *path) { return NULL; } -#ifndef __GLIBC__ -#error "It's not clear whether this system supports realpath(..., NULL) like GNU libc does. If it doesn't we need a private version of realpath() here." -#endif +#if defined(__GLIBC__) || defined(__APPLE__) + { + char *r; - if (!(r = realpath(path, NULL))) - return NULL; + if (!(r = realpath(path, NULL))) + return NULL; + + /* We copy this here in case our pa_xmalloc() is not + * implemented on top of libc malloc() */ + t = pa_xstrdup(r); + pa_xfree(r); + } +#elif defined(PATH_MAX) + { + char *path_buf; + path_buf = pa_xmalloc(PATH_MAX); - /* We copy this here in case our pa_xmalloc() is not implemented - * on top of libc malloc() */ - t = pa_xstrdup(r); - pa_xfree(r); + if (!(t = realpath(path, path_buf))) { + pa_xfree(path_buf); + return NULL; + } + } +#else +#error "It's not clear whether this system supports realpath(..., NULL) like GNU libc does. If it doesn't we need a private version of realpath() here." +#endif return t; } diff --git a/src/pulsecore/core-util.h b/src/pulsecore/core-util.h index 0ba33f3..f96fa44 100644 --- a/src/pulsecore/core-util.h +++ b/src/pulsecore/core-util.h @@ -202,6 +202,7 @@ pa_bool_t pa_in_system_mode(void); #define pa_streq(a,b) (!strcmp((a),(b))) char *pa_machine_id(void); +char *pa_session_id(void); char *pa_uname_string(void); #ifdef HAVE_VALGRIND_MEMCHECK_H diff --git a/src/pulsecore/dbus-shared.c b/src/pulsecore/dbus-shared.c new file mode 100644 index 0000000..9d9445b --- /dev/null +++ b/src/pulsecore/dbus-shared.c @@ -0,0 +1,107 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006, 2009 Lennart Poettering + Copyright 2006 Shams E. King + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, 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 <stdarg.h> + +#include <pulse/xmalloc.h> +#include <pulse/timeval.h> +#include <pulsecore/log.h> +#include <pulsecore/shared.h> + +#include "dbus-shared.h" + +struct pa_dbus_connection { + PA_REFCNT_DECLARE; + + pa_dbus_wrap_connection *connection; + pa_core *core; + const char *property_name; +}; + +static pa_dbus_connection* dbus_connection_new(pa_core *c, pa_dbus_wrap_connection *conn, const char *name) { + pa_dbus_connection *pconn; + + pconn = pa_xnew(pa_dbus_connection, 1); + PA_REFCNT_INIT(pconn); + pconn->core = c; + pconn->property_name = name; + pconn->connection = conn; + + pa_shared_set(c, name, pconn); + + return pconn; +} + +pa_dbus_connection* pa_dbus_bus_get(pa_core *c, DBusBusType type, DBusError *error) { + + static const char *const prop_name[] = { + [DBUS_BUS_SESSION] = "dbus-connection-session", + [DBUS_BUS_SYSTEM] = "dbus-connection-system", + [DBUS_BUS_STARTER] = "dbus-connection-starter" + }; + pa_dbus_wrap_connection *conn; + pa_dbus_connection *pconn; + + pa_assert(type == DBUS_BUS_SYSTEM || type == DBUS_BUS_SESSION || type == DBUS_BUS_STARTER); + + if ((pconn = pa_shared_get(c, prop_name[type]))) + return pa_dbus_connection_ref(pconn); + + if (!(conn = pa_dbus_wrap_connection_new(c->mainloop, type, error))) + return NULL; + + return dbus_connection_new(c, conn, prop_name[type]); +} + +DBusConnection* pa_dbus_connection_get(pa_dbus_connection *c){ + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) > 0); + pa_assert(c->connection); + + return pa_dbus_wrap_connection_get(c->connection); +} + +void pa_dbus_connection_unref(pa_dbus_connection *c) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) > 0); + + if (PA_REFCNT_DEC(c) > 0) + return; + + pa_dbus_wrap_connection_free(c->connection); + + pa_shared_remove(c->core, c->property_name); + pa_xfree(c); +} + +pa_dbus_connection* pa_dbus_connection_ref(pa_dbus_connection *c) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) > 0); + + PA_REFCNT_INC(c); + + return c; +} diff --git a/src/pulsecore/dbus-shared.h b/src/pulsecore/dbus-shared.h new file mode 100644 index 0000000..4c15455 --- /dev/null +++ b/src/pulsecore/dbus-shared.h @@ -0,0 +1,42 @@ +#ifndef foodbussharedhfoo +#define foodbussharedhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006, 2009 Lennart Poettering + Copyright 2006 Shams E. King + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include <dbus/dbus.h> + +#include <pulsecore/core.h> +#include <pulsecore/dbus-util.h> + +typedef struct pa_dbus_connection pa_dbus_connection; + +/* return a pa_dbus_connection of the specified type for the given core, + * like dbus_bus_get(), but integrates the connection with the pa_core */ +pa_dbus_connection* pa_dbus_bus_get(pa_core *c, DBusBusType type, DBusError *error); + +DBusConnection* pa_dbus_connection_get(pa_dbus_connection *conn); + +pa_dbus_connection* pa_dbus_connection_ref(pa_dbus_connection *conn); +void pa_dbus_connection_unref(pa_dbus_connection *conn); + +#endif diff --git a/src/pulsecore/dbus-util.c b/src/pulsecore/dbus-util.c new file mode 100644 index 0000000..ece36de --- /dev/null +++ b/src/pulsecore/dbus-util.c @@ -0,0 +1,410 @@ +/*** + This file is part of PulseAudio. + + Copyright 2006 Lennart Poettering + Copyright 2006 Shams E. King + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, 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 <stdarg.h> + +#include <pulse/xmalloc.h> +#include <pulse/timeval.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> + +#include "dbus-util.h" + +struct pa_dbus_wrap_connection { + pa_mainloop_api *mainloop; + DBusConnection *connection; + pa_defer_event* dispatch_event; +}; + +static void dispatch_cb(pa_mainloop_api *ea, pa_defer_event *ev, void *userdata) { + DBusConnection *conn = userdata; + + if (dbus_connection_dispatch(conn) == DBUS_DISPATCH_COMPLETE) { + /* no more data to process, disable the deferred */ + ea->defer_enable(ev, 0); + } +} + +/* DBusDispatchStatusFunction callback for the pa mainloop */ +static void dispatch_status(DBusConnection *conn, DBusDispatchStatus status, void *userdata) { + pa_dbus_wrap_connection *c = userdata; + + pa_assert(c); + + switch(status) { + + case DBUS_DISPATCH_COMPLETE: + c->mainloop->defer_enable(c->dispatch_event, 0); + break; + + case DBUS_DISPATCH_DATA_REMAINS: + case DBUS_DISPATCH_NEED_MEMORY: + default: + c->mainloop->defer_enable(c->dispatch_event, 1); + break; + } +} + +static pa_io_event_flags_t get_watch_flags(DBusWatch *watch) { + unsigned int flags; + pa_io_event_flags_t events = 0; + + pa_assert(watch); + + flags = dbus_watch_get_flags(watch); + + /* no watch flags for disabled watches */ + if (!dbus_watch_get_enabled(watch)) + return PA_IO_EVENT_NULL; + + if (flags & DBUS_WATCH_READABLE) + events |= PA_IO_EVENT_INPUT; + if (flags & DBUS_WATCH_WRITABLE) + events |= PA_IO_EVENT_OUTPUT; + + return events | PA_IO_EVENT_HANGUP | PA_IO_EVENT_ERROR; +} + +/* pa_io_event_cb_t IO event handler */ +static void handle_io_event(pa_mainloop_api *ea, pa_io_event *e, int fd, pa_io_event_flags_t events, void *userdata) { + unsigned int flags = 0; + DBusWatch *watch = userdata; + +#if HAVE_DBUS_WATCH_GET_UNIX_FD + pa_assert(fd == dbus_watch_get_unix_fd(watch)); +#else + pa_assert(fd == dbus_watch_get_fd(watch)); +#endif + + if (!dbus_watch_get_enabled(watch)) { + pa_log_warn("Asked to handle disabled watch: %p %i", (void*) watch, fd); + return; + } + + if (events & PA_IO_EVENT_INPUT) + flags |= DBUS_WATCH_READABLE; + if (events & PA_IO_EVENT_OUTPUT) + flags |= DBUS_WATCH_WRITABLE; + if (events & PA_IO_EVENT_HANGUP) + flags |= DBUS_WATCH_HANGUP; + if (events & PA_IO_EVENT_ERROR) + flags |= DBUS_WATCH_ERROR; + + dbus_watch_handle(watch, flags); +} + +/* pa_time_event_cb_t timer event handler */ +static void handle_time_event(pa_mainloop_api *ea, pa_time_event* e, const struct timeval *tv, void *userdata) { + DBusTimeout *timeout = userdata; + + if (dbus_timeout_get_enabled(timeout)) { + struct timeval next = *tv; + dbus_timeout_handle(timeout); + + /* restart it for the next scheduled time */ + pa_timeval_add(&next, (pa_usec_t) dbus_timeout_get_interval(timeout) * 1000); + ea->time_restart(e, &next); + } +} + +/* DBusAddWatchFunction callback for pa mainloop */ +static dbus_bool_t add_watch(DBusWatch *watch, void *data) { + pa_dbus_wrap_connection *c = data; + pa_io_event *ev; + + pa_assert(watch); + pa_assert(c); + + ev = c->mainloop->io_new( + c->mainloop, +#if HAVE_DBUS_WATCH_GET_UNIX_FD + dbus_watch_get_unix_fd(watch), +#else + dbus_watch_get_fd(watch), +#endif + get_watch_flags(watch), handle_io_event, watch); + + dbus_watch_set_data(watch, ev, NULL); + + return TRUE; +} + +/* DBusRemoveWatchFunction callback for pa mainloop */ +static void remove_watch(DBusWatch *watch, void *data) { + pa_dbus_wrap_connection *c = data; + pa_io_event *ev; + + pa_assert(watch); + pa_assert(c); + + if ((ev = dbus_watch_get_data(watch))) + c->mainloop->io_free(ev); +} + +/* DBusWatchToggledFunction callback for pa mainloop */ +static void toggle_watch(DBusWatch *watch, void *data) { + pa_dbus_wrap_connection *c = data; + pa_io_event *ev; + + pa_assert(watch); + pa_assert(c); + + pa_assert_se(ev = dbus_watch_get_data(watch)); + + /* get_watch_flags() checks if the watch is enabled */ + c->mainloop->io_enable(ev, get_watch_flags(watch)); +} + +/* DBusAddTimeoutFunction callback for pa mainloop */ +static dbus_bool_t add_timeout(DBusTimeout *timeout, void *data) { + pa_dbus_wrap_connection *c = data; + pa_time_event *ev; + struct timeval tv; + + pa_assert(timeout); + pa_assert(c); + + if (!dbus_timeout_get_enabled(timeout)) + return FALSE; + + pa_gettimeofday(&tv); + pa_timeval_add(&tv, (pa_usec_t) dbus_timeout_get_interval(timeout) * 1000); + + ev = c->mainloop->time_new(c->mainloop, &tv, handle_time_event, timeout); + + dbus_timeout_set_data(timeout, ev, NULL); + + return TRUE; +} + +/* DBusRemoveTimeoutFunction callback for pa mainloop */ +static void remove_timeout(DBusTimeout *timeout, void *data) { + pa_dbus_wrap_connection *c = data; + pa_time_event *ev; + + pa_assert(timeout); + pa_assert(c); + + if ((ev = dbus_timeout_get_data(timeout))) + c->mainloop->time_free(ev); +} + +/* DBusTimeoutToggledFunction callback for pa mainloop */ +static void toggle_timeout(DBusTimeout *timeout, void *data) { + pa_dbus_wrap_connection *c = data; + pa_time_event *ev; + + pa_assert(timeout); + pa_assert(c); + + pa_assert_se(ev = dbus_timeout_get_data(timeout)); + + if (dbus_timeout_get_enabled(timeout)) { + struct timeval tv; + + pa_gettimeofday(&tv); + pa_timeval_add(&tv, (pa_usec_t) dbus_timeout_get_interval(timeout) * 1000); + + c->mainloop->time_restart(ev, &tv); + } else + c->mainloop->time_restart(ev, NULL); +} + +static void wakeup_main(void *userdata) { + pa_dbus_wrap_connection *c = userdata; + + pa_assert(c); + + /* this will wakeup the mainloop and dispatch events, although + * it may not be the cleanest way of accomplishing it */ + c->mainloop->defer_enable(c->dispatch_event, 1); +} + +pa_dbus_wrap_connection* pa_dbus_wrap_connection_new(pa_mainloop_api *m, DBusBusType type, DBusError *error) { + DBusConnection *conn; + pa_dbus_wrap_connection *pconn; + char *id; + + pa_assert(type == DBUS_BUS_SYSTEM || type == DBUS_BUS_SESSION || type == DBUS_BUS_STARTER); + + if (!(conn = dbus_bus_get_private(type, error))) + return NULL; + + pconn = pa_xnew(pa_dbus_wrap_connection, 1); + pconn->mainloop = m; + pconn->connection = conn; + + dbus_connection_set_exit_on_disconnect(conn, FALSE); + dbus_connection_set_dispatch_status_function(conn, dispatch_status, pconn, NULL); + dbus_connection_set_watch_functions(conn, add_watch, remove_watch, toggle_watch, pconn, NULL); + dbus_connection_set_timeout_functions(conn, add_timeout, remove_timeout, toggle_timeout, pconn, NULL); + dbus_connection_set_wakeup_main_function(conn, wakeup_main, pconn, NULL); + + pconn->dispatch_event = pconn->mainloop->defer_new(pconn->mainloop, dispatch_cb, conn); + + pa_log_debug("Successfully connected to D-Bus %s bus %s as %s", + type == DBUS_BUS_SYSTEM ? "system" : (type == DBUS_BUS_SESSION ? "session" : "starter"), + pa_strnull((id = dbus_connection_get_server_id(conn))), + pa_strnull(dbus_bus_get_unique_name(conn))); + + dbus_free(id); + + return pconn; +} + +void pa_dbus_wrap_connection_free(pa_dbus_wrap_connection* c) { + pa_assert(c); + + if (dbus_connection_get_is_connected(c->connection)) { + dbus_connection_close(c->connection); + /* must process remaining messages, bit of a kludge to handle + * both unload and shutdown */ + while (dbus_connection_read_write_dispatch(c->connection, -1)) + ; + } + + c->mainloop->defer_free(c->dispatch_event); + dbus_connection_unref(c->connection); + pa_xfree(c); +} + +DBusConnection* pa_dbus_wrap_connection_get(pa_dbus_wrap_connection *c) { + pa_assert(c); + pa_assert(c->connection); + + return c->connection; +} + +int pa_dbus_add_matches(DBusConnection *c, DBusError *error, ...) { + const char *t; + va_list ap; + unsigned k = 0; + + pa_assert(c); + pa_assert(error); + + va_start(ap, error); + while ((t = va_arg(ap, const char*))) { + dbus_bus_add_match(c, t, error); + + if (dbus_error_is_set(error)) + goto fail; + + k++; + } + va_end(ap); + return 0; + +fail: + + va_end(ap); + va_start(ap, error); + for (; k > 0; k--) { + DBusError e; + + pa_assert_se(t = va_arg(ap, const char*)); + + dbus_error_init(&e); + dbus_bus_remove_match(c, t, &e); + dbus_error_free(&e); + } + va_end(ap); + + return -1; +} + +void pa_dbus_remove_matches(DBusConnection *c, ...) { + const char *t; + va_list ap; + DBusError error; + + pa_assert(c); + + dbus_error_init(&error); + + va_start(ap, c); + while ((t = va_arg(ap, const char*))) { + dbus_bus_remove_match(c, t, &error); + dbus_error_free(&error); + } + va_end(ap); +} + +pa_dbus_pending *pa_dbus_pending_new( + DBusConnection *c, + DBusMessage *m, + DBusPendingCall *pending, + void *context_data, + void *call_data) { + + pa_dbus_pending *p; + + pa_assert(pending); + + p = pa_xnew(pa_dbus_pending, 1); + p->connection = c; + p->message = m; + p->pending = pending; + p->context_data = context_data; + p->call_data = call_data; + + PA_LLIST_INIT(pa_dbus_pending, p); + + return p; +} + +void pa_dbus_pending_free(pa_dbus_pending *p) { + pa_assert(p); + + if (p->pending) { + dbus_pending_call_cancel(p->pending); + dbus_pending_call_unref(p->pending); + } + + if (p->message) + dbus_message_unref(p->message); + + pa_xfree(p); +} + +void pa_dbus_sync_pending_list(pa_dbus_pending **p) { + pa_assert(p); + + while (*p && dbus_connection_read_write_dispatch((*p)->connection, -1)) + ; +} + +void pa_dbus_free_pending_list(pa_dbus_pending **p) { + pa_dbus_pending *i; + + pa_assert(p); + + while ((i = *p)) { + PA_LLIST_REMOVE(pa_dbus_pending, *p, i); + pa_dbus_pending_free(i); + } +} diff --git a/src/pulsecore/dbus-util.h b/src/pulsecore/dbus-util.h new file mode 100644 index 0000000..55cda7a --- /dev/null +++ b/src/pulsecore/dbus-util.h @@ -0,0 +1,63 @@ +#ifndef foodbusutilhfoo +#define foodbusutilhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2006 Shams E. King + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include <dbus/dbus.h> + +#include <pulsecore/llist.h> +#include <pulse/mainloop-api.h> + +/* A wrap connection is not shared or refcounted, it is available in client side */ +typedef struct pa_dbus_wrap_connection pa_dbus_wrap_connection; + +pa_dbus_wrap_connection* pa_dbus_wrap_connection_new(pa_mainloop_api *mainloop, DBusBusType type, DBusError *error); +void pa_dbus_wrap_connection_free(pa_dbus_wrap_connection* conn); + +DBusConnection* pa_dbus_wrap_connection_get(pa_dbus_wrap_connection *conn); + +int pa_dbus_add_matches(DBusConnection *c, DBusError *error, ...) PA_GCC_SENTINEL; +void pa_dbus_remove_matches(DBusConnection *c, ...) PA_GCC_SENTINEL; + +typedef struct pa_dbus_pending pa_dbus_pending; + +struct pa_dbus_pending { + DBusConnection *connection; + DBusMessage *message; + DBusPendingCall *pending; + + void *context_data; + void *call_data; + + PA_LLIST_FIELDS(pa_dbus_pending); +}; + +pa_dbus_pending *pa_dbus_pending_new(DBusConnection *c, DBusMessage *m, DBusPendingCall *pending, void *context_data, void *call_data); +void pa_dbus_pending_free(pa_dbus_pending *p); + +/* Sync up a list of pa_dbus_pending_call objects */ +void pa_dbus_sync_pending_list(pa_dbus_pending **p); + +/* Free up a list of pa_dbus_pending_call objects */ +void pa_dbus_free_pending_list(pa_dbus_pending **p); + +#endif diff --git a/src/pulsecore/hook-list.c b/src/pulsecore/hook-list.c index 5f7a866..a00116d 100644 --- a/src/pulsecore/hook-list.c +++ b/src/pulsecore/hook-list.c @@ -121,3 +121,9 @@ pa_hook_result_t pa_hook_fire(pa_hook *hook, void *data) { return result; } + +pa_bool_t pa_hook_is_firing(pa_hook *hook) { + pa_assert(hook); + + return hook->n_firing > 0; +} diff --git a/src/pulsecore/hook-list.h b/src/pulsecore/hook-list.h index 8514cce..86ce9d2 100644 --- a/src/pulsecore/hook-list.h +++ b/src/pulsecore/hook-list.h @@ -71,4 +71,6 @@ void pa_hook_slot_free(pa_hook_slot *slot); pa_hook_result_t pa_hook_fire(pa_hook *hook, void *data); +pa_bool_t pa_hook_is_firing(pa_hook *hook); + #endif diff --git a/src/pulsecore/log.c b/src/pulsecore/log.c index d4d3b76..60ea9c5 100644 --- a/src/pulsecore/log.c +++ b/src/pulsecore/log.c @@ -59,12 +59,13 @@ #define ENV_LOG_PRINT_META "PULSE_LOG_META" #define ENV_LOG_PRINT_LEVEL "PULSE_LOG_LEVEL" #define ENV_LOG_BACKTRACE "PULSE_LOG_BACKTRACE" +#define ENV_LOG_BACKTRACE_SKIP "PULSE_LOG_BACKTRACE_SKIP" static char *ident = NULL; /* in local charset format */ static pa_log_target_t target = PA_LOG_STDERR, target_override; static pa_bool_t target_override_set = FALSE; static pa_log_level_t maximum_level = PA_LOG_ERROR, maximum_level_override = PA_LOG_ERROR; -static unsigned show_backtrace = 0, show_backtrace_override = 0; +static unsigned show_backtrace = 0, show_backtrace_override = 0, skip_backtrace = 0; static pa_log_flags_t flags = 0, flags_override = 0; #ifdef HAVE_SYSLOG_H @@ -128,13 +129,17 @@ void pa_log_set_show_backtrace(unsigned nlevels) { show_backtrace = nlevels; } +void pa_log_set_skip_backtrace(unsigned nlevels) { + skip_backtrace = nlevels; +} + #ifdef HAVE_EXECINFO_H static char* get_backtrace(unsigned show_nframes) { void* trace[32]; int n_frames; char **symbols, *e, *r; - unsigned j, n; + unsigned j, n, s; size_t a; pa_assert(show_nframes > 0); @@ -149,14 +154,15 @@ static char* get_backtrace(unsigned show_nframes) { if (!symbols) return NULL; - n = PA_MIN((unsigned) n_frames, show_nframes); + s = skip_backtrace; + n = PA_MIN((unsigned) n_frames, s + show_nframes); a = 4; - for (j = 0; j < n; j++) { - if (j > 0) + for (j = s; j < n; j++) { + if (j > s) a += 2; - a += strlen(symbols[j]); + a += strlen(pa_path_get_filename(symbols[j])); } r = pa_xnew(char, a); @@ -164,14 +170,18 @@ static char* get_backtrace(unsigned show_nframes) { strcpy(r, " ("); e = r + 2; - for (j = 0; j < n; j++) { - if (j > 0) { + for (j = s; j < n; j++) { + const char *sym; + + if (j > s) { strcpy(e, "<<"); e += 2; } - strcpy(e, symbols[j]); - e += strlen(symbols[j]); + sym = pa_path_get_filename(symbols[j]); + + strcpy(e, sym); + e += strlen(sym); } strcpy(e, ")"); @@ -225,6 +235,13 @@ static void init_defaults(void) { if (show_backtrace_override <= 0) show_backtrace_override = 0; } + + if ((e = getenv(ENV_LOG_BACKTRACE_SKIP))) { + skip_backtrace = (unsigned) atoi(e); + + if (skip_backtrace <= 0) + skip_backtrace = 0; + } } void pa_log_levelv_meta( @@ -245,7 +262,7 @@ void pa_log_levelv_meta( /* We don't use dynamic memory allocation here to minimize the hit * in RT threads */ - char text[4096], location[128], timestamp[32]; + char text[16*1024], location[128], timestamp[32]; pa_assert(level < PA_LOG_LEVEL_MAX); pa_assert(format); diff --git a/src/pulsecore/log.h b/src/pulsecore/log.h index 153e11e..2f379f6 100644 --- a/src/pulsecore/log.h +++ b/src/pulsecore/log.h @@ -77,6 +77,9 @@ void pa_log_set_flags(pa_log_flags_t flags, pa_log_merge_t merge); /* Enable backtrace */ void pa_log_set_show_backtrace(unsigned nlevels); +/* Skip the first backtrace frames */ +void pa_log_set_skip_backtrace(unsigned nlevels); + void pa_log_level_meta( pa_log_level_t level, const char*file, @@ -110,6 +113,7 @@ void pa_log_levelv( #define pa_log_notice(...) pa_log_level_meta(PA_LOG_NOTICE, __FILE__, __LINE__, __func__, __VA_ARGS__) #define pa_log_warn(...) pa_log_level_meta(PA_LOG_WARN, __FILE__, __LINE__, __func__, __VA_ARGS__) #define pa_log_error(...) pa_log_level_meta(PA_LOG_ERROR, __FILE__, __LINE__, __func__, __VA_ARGS__) +#define pa_logl(level, ...) pa_log_level_meta(level, __FILE__, __LINE__, __func__, __VA_ARGS__) #else diff --git a/src/pulsecore/macro.h b/src/pulsecore/macro.h index 20015bf..a5ca696 100644 --- a/src/pulsecore/macro.h +++ b/src/pulsecore/macro.h @@ -164,8 +164,8 @@ typedef int pa_bool_t; #define pa_return_null_if_fail(expr) pa_return_val_if_fail(expr, NULL) -/* An assert which guarantees side effects of x, i.e. is never - * optimized away */ +/* pa_assert_se() is an assert which guarantees side effects of x, + * i.e. is never optimized away, regardless of NDEBUG or FASTPATH. */ #define pa_assert_se(expr) \ do { \ if (PA_UNLIKELY(!(expr))) { \ @@ -174,18 +174,44 @@ typedef int pa_bool_t; } \ } while (FALSE) -/* An assert that may be optimized away by defining NDEBUG */ +/* Does exactly nothing */ +#define pa_nop() do {} while (FALSE) + +/* pa_assert() is an assert that may be optimized away by defining + * NDEBUG. pa_assert_fp() is an assert that may be optimized away by + * defining FASTPATH. It is supposed to be used in inner loops. It's + * there for extra paranoia checking and should probably be removed in + * production builds. */ #ifdef NDEBUG -#define pa_assert(expr) do {} while (FALSE) +#define pa_assert(expr) pa_nop() +#define pa_assert_fp(expr) pa_nop() +#elif defined (FASTPATH) +#define pa_assert(expr) pa_assert_se(expr) +#define pa_assert_fp(expr) pa_nop() #else #define pa_assert(expr) pa_assert_se(expr) +#define pa_assert_fp(expr) pa_assert_se(expr) #endif +#ifdef NDEBUG +#define pa_assert_not_reached() pa_nop() +#else #define pa_assert_not_reached() \ do { \ pa_log_error("Code should not be reached at %s:%u, function %s(). Aborting.", __FILE__, __LINE__, PA_PRETTY_FUNCTION); \ abort(); \ } while (FALSE) +#endif + +/* A compile time assertion */ +#define pa_assert_cc(expr) \ + do { \ + switch (0) { \ + case 0: \ + case !!(expr): \ + ; \ + } \ + } while (FALSE) #define PA_PTR_TO_UINT(p) ((unsigned int) ((uintptr_t) (p))) #define PA_UINT_TO_PTR(u) ((void*) ((uintptr_t) (u))) diff --git a/src/pulsecore/memblock.c b/src/pulsecore/memblock.c index fbf0a47..6cc0ff3 100644 --- a/src/pulsecore/memblock.c +++ b/src/pulsecore/memblock.c @@ -745,8 +745,47 @@ void pa_mempool_free(pa_mempool *p) { pa_flist_free(p->free_slots, NULL); if (pa_atomic_load(&p->stat.n_allocated) > 0) { -/* raise(SIGTRAP); */ - pa_log_warn("Memory pool destroyed but not all memory blocks freed! %u remain.", pa_atomic_load(&p->stat.n_allocated)); + + /* Ouch, somebody is retaining a memory block reference! */ + +#ifdef DEBUG_REF + unsigned i; + pa_flist *list; + + /* Let's try to find at least one of those leaked memory blocks */ + + list = pa_flist_new(p->n_blocks); + + for (i = 0; i < (unsigned) pa_atomic_load(&p->n_init); i++) { + struct mempool_slot *slot; + pa_memblock *b, *k; + + slot = (struct mempool_slot*) ((uint8_t*) p->memory.ptr + (p->block_size * (size_t) i)); + b = mempool_slot_data(slot); + + while ((k = pa_flist_pop(p->free_slots))) { + while (pa_flist_push(list, k) < 0) + ; + + if (b == k) + break; + } + + if (!k) + pa_log("REF: Leaked memory block %p", b); + + while ((k = pa_flist_pop(list))) + while (pa_flist_push(p->free_slots, k) < 0) + ; + } + + pa_flist_free(list, NULL); + +#endif + + pa_log_error("Memory pool destroyed but not all memory blocks freed! %u remain.", pa_atomic_load(&p->stat.n_allocated)); + +/* PA_DEBUG_TRAP; */ } pa_shm_free(&p->memory); diff --git a/src/pulsecore/memblockq.c b/src/pulsecore/memblockq.c index e2be42b..77f9efc 100644 --- a/src/pulsecore/memblockq.c +++ b/src/pulsecore/memblockq.c @@ -90,8 +90,8 @@ pa_memblockq* pa_memblockq_new( pa_memblockq_set_maxlength(bq, maxlength); pa_memblockq_set_tlength(bq, tlength); - pa_memblockq_set_prebuf(bq, prebuf); pa_memblockq_set_minreq(bq, minreq); + pa_memblockq_set_prebuf(bq, prebuf); pa_memblockq_set_maxrewind(bq, maxrewind); pa_log_debug("memblockq sanitized: maxlength=%lu, tlength=%lu, base=%lu, prebuf=%lu, minreq=%lu maxrewind=%lu", @@ -601,7 +601,7 @@ size_t pa_memblockq_missing(pa_memblockq *bq) { return l >= bq->minreq ? l : 0; } -void pa_memblockq_seek(pa_memblockq *bq, int64_t offset, pa_seek_mode_t seek) { +void pa_memblockq_seek(pa_memblockq *bq, int64_t offset, pa_seek_mode_t seek, pa_bool_t account) { int64_t old, delta; pa_assert(bq); @@ -628,12 +628,14 @@ void pa_memblockq_seek(pa_memblockq *bq, int64_t offset, pa_seek_mode_t seek) { delta = bq->write_index - old; - if (delta >= (int64_t) bq->requested) { - delta -= (int64_t) bq->requested; - bq->requested = 0; - } else if (delta >= 0) { - bq->requested -= (size_t) delta; - delta = 0; + if (account) { + if (delta >= (int64_t) bq->requested) { + delta -= (int64_t) bq->requested; + bq->requested = 0; + } else if (delta >= 0) { + bq->requested -= (size_t) delta; + delta = 0; + } } bq->missing -= delta; @@ -782,16 +784,13 @@ void pa_memblockq_set_maxlength(pa_memblockq *bq, size_t maxlength) { if (bq->tlength > bq->maxlength) pa_memblockq_set_tlength(bq, bq->maxlength); - - if (bq->prebuf > bq->maxlength) - pa_memblockq_set_prebuf(bq, bq->maxlength); } void pa_memblockq_set_tlength(pa_memblockq *bq, size_t tlength) { size_t old_tlength; pa_assert(bq); - if (tlength <= 0) + if (tlength <= 0 || tlength == (size_t) -1) tlength = bq->maxlength; old_tlength = bq->tlength; @@ -800,55 +799,72 @@ void pa_memblockq_set_tlength(pa_memblockq *bq, size_t tlength) { if (bq->tlength > bq->maxlength) bq->tlength = bq->maxlength; - if (bq->prebuf > bq->tlength) - pa_memblockq_set_prebuf(bq, bq->tlength); - if (bq->minreq > bq->tlength) pa_memblockq_set_minreq(bq, bq->tlength); + if (bq->prebuf > bq->tlength+bq->base-bq->minreq) + pa_memblockq_set_prebuf(bq, bq->tlength+bq->base-bq->minreq); + bq->missing += (int64_t) bq->tlength - (int64_t) old_tlength; } +void pa_memblockq_set_minreq(pa_memblockq *bq, size_t minreq) { + pa_assert(bq); + + bq->minreq = (minreq/bq->base)*bq->base; + + if (bq->minreq > bq->tlength) + bq->minreq = bq->tlength; + + if (bq->minreq < bq->base) + bq->minreq = bq->base; + + if (bq->prebuf > bq->tlength+bq->base-bq->minreq) + pa_memblockq_set_prebuf(bq, bq->tlength+bq->base-bq->minreq); +} + void pa_memblockq_set_prebuf(pa_memblockq *bq, size_t prebuf) { pa_assert(bq); if (prebuf == (size_t) -1) - prebuf = bq->tlength; + prebuf = bq->tlength+bq->base-bq->minreq; bq->prebuf = ((prebuf+bq->base-1)/bq->base)*bq->base; if (prebuf > 0 && bq->prebuf < bq->base) bq->prebuf = bq->base; - if (bq->prebuf > bq->tlength) - bq->prebuf = bq->tlength; + if (bq->prebuf > bq->tlength+bq->base-bq->minreq) + bq->prebuf = bq->tlength+bq->base-bq->minreq; if (bq->prebuf <= 0 || pa_memblockq_get_length(bq) >= bq->prebuf) bq->in_prebuf = FALSE; - - if (bq->minreq > bq->prebuf) - pa_memblockq_set_minreq(bq, bq->prebuf); } -void pa_memblockq_set_minreq(pa_memblockq *bq, size_t minreq) { +void pa_memblockq_set_maxrewind(pa_memblockq *bq, size_t maxrewind) { pa_assert(bq); - bq->minreq = (minreq/bq->base)*bq->base; - - if (bq->minreq > bq->tlength) - bq->minreq = bq->tlength; + bq->maxrewind = (maxrewind/bq->base)*bq->base; +} - if (bq->minreq > bq->prebuf) - bq->minreq = bq->prebuf; +void pa_memblockq_apply_attr(pa_memblockq *bq, const pa_buffer_attr *a) { + pa_assert(bq); + pa_assert(a); - if (bq->minreq < bq->base) - bq->minreq = bq->base; + pa_memblockq_set_maxlength(bq, a->maxlength); + pa_memblockq_set_tlength(bq, a->tlength); + pa_memblockq_set_prebuf(bq, a->prebuf); + pa_memblockq_set_minreq(bq, a->minreq); } -void pa_memblockq_set_maxrewind(pa_memblockq *bq, size_t maxrewind) { +void pa_memblockq_get_attr(pa_memblockq *bq, pa_buffer_attr *a) { pa_assert(bq); + pa_assert(a); - bq->maxrewind = (maxrewind/bq->base)*bq->base; + a->maxlength = (uint32_t) pa_memblockq_get_maxlength(bq); + a->tlength = (uint32_t) pa_memblockq_get_tlength(bq); + a->prebuf = (uint32_t) pa_memblockq_get_prebuf(bq); + a->minreq = (uint32_t) pa_memblockq_get_minreq(bq); } int pa_memblockq_splice(pa_memblockq *bq, pa_memblockq *source) { @@ -875,7 +891,7 @@ int pa_memblockq_splice(pa_memblockq *bq, pa_memblockq *source) { pa_memblock_unref(chunk.memblock); } else - pa_memblockq_seek(bq, (int64_t) chunk.length, PA_SEEK_RELATIVE); + pa_memblockq_seek(bq, (int64_t) chunk.length, PA_SEEK_RELATIVE, TRUE); pa_memblockq_drop(bq, chunk.length); } diff --git a/src/pulsecore/memblockq.h b/src/pulsecore/memblockq.h index 0a74aa3..146d261 100644 --- a/src/pulsecore/memblockq.h +++ b/src/pulsecore/memblockq.h @@ -85,7 +85,7 @@ int pa_memblockq_push(pa_memblockq* bq, const pa_memchunk *chunk); int pa_memblockq_push_align(pa_memblockq* bq, const pa_memchunk *chunk); /* Manipulate the write pointer */ -void pa_memblockq_seek(pa_memblockq *bq, int64_t offset, pa_seek_mode_t seek); +void pa_memblockq_seek(pa_memblockq *bq, int64_t offset, pa_seek_mode_t seek, pa_bool_t account); /* Return a copy of the next memory chunk in the queue. It is not * removed from the queue. There are two reasons this function might @@ -158,6 +158,10 @@ void pa_memblockq_set_minreq(pa_memblockq *memblockq, size_t minreq); void pa_memblockq_set_maxrewind(pa_memblockq *memblockq, size_t maxrewind); /* Set the maximum history size */ void pa_memblockq_set_silence(pa_memblockq *memblockq, pa_memchunk *silence); +/* Apply the data from pa_buffer_attr */ +void pa_memblockq_apply_attr(pa_memblockq *memblockq, const pa_buffer_attr *a); +void pa_memblockq_get_attr(pa_memblockq *bq, pa_buffer_attr *a); + /* Call pa_memchunk_willneed() for every chunk in the queue from the current read pointer to the end */ void pa_memblockq_willneed(pa_memblockq *bq); @@ -175,5 +179,4 @@ pa_bool_t pa_memblockq_prebuf_active(pa_memblockq *bq); /* Return how many items are currently stored in the queue */ unsigned pa_memblockq_get_nblocks(pa_memblockq *bq); - #endif diff --git a/src/pulsecore/native-common.h b/src/pulsecore/native-common.h index 6951e10..d4d7f3e 100644 --- a/src/pulsecore/native-common.h +++ b/src/pulsecore/native-common.h @@ -152,7 +152,7 @@ enum { /* Supported since protocol v14 (0.9.12) */ PA_COMMAND_EXTENSION, - /* Supported since protocol v15 (0.9.15*/ + /* Supported since protocol v15 (0.9.15) */ PA_COMMAND_GET_CARD_INFO, PA_COMMAND_GET_CARD_INFO_LIST, PA_COMMAND_SET_CARD_PROFILE, @@ -161,6 +161,10 @@ enum { PA_COMMAND_PLAYBACK_STREAM_EVENT, PA_COMMAND_RECORD_STREAM_EVENT, + /* SERVER->CLIENT */ + PA_COMMAND_PLAYBACK_BUFFER_ATTR_CHANGED, + PA_COMMAND_RECORD_BUFFER_ATTR_CHANGED, + PA_COMMAND_MAX }; diff --git a/src/pulsecore/parseaddr.c b/src/pulsecore/parseaddr.c index c5cd7fe..5b53122 100644 --- a/src/pulsecore/parseaddr.c +++ b/src/pulsecore/parseaddr.c @@ -87,13 +87,15 @@ int pa_parse_address(const char *name, pa_parsed_address *ret_p) { ret_p->type = PA_PARSED_ADDRESS_TCP_AUTO; if (*name == '{') { - char hn[256], *pfx; - /* The URL starts with a host specification for detecting local connections */ + char *id, *pfx; - if (!pa_get_host_name(hn, sizeof(hn))) + /* The URL starts with a host id for detecting local connections */ + if (!(id = pa_machine_id())) return -1; - pfx = pa_sprintf_malloc("{%s}", hn); + pfx = pa_sprintf_malloc("{%s}", id); + pa_xfree(id); + if (!pa_startswith(name, pfx)) { pa_xfree(pfx); /* Not local */ diff --git a/src/pulsecore/pdispatch.c b/src/pulsecore/pdispatch.c index 305941a..d00106b 100644 --- a/src/pulsecore/pdispatch.c +++ b/src/pulsecore/pdispatch.c @@ -165,7 +165,20 @@ static const char *command_names[PA_COMMAND_MAX] = { [PA_COMMAND_STARTED] = "STARTED", /* Supported since protocol v14 (0.9.12) */ - [PA_COMMAND_EXTENSION] = "EXTENSION" + [PA_COMMAND_EXTENSION] = "EXTENSION", + + + [PA_COMMAND_GET_CARD_INFO] = "GET_CARD_INFO", + [PA_COMMAND_GET_CARD_INFO_LIST] = "GET_CARD_INFO_LIST", + [PA_COMMAND_SET_CARD_PROFILE] = "SET_CARD_PROFILE", + + [PA_COMMAND_CLIENT_EVENT] = "GET_CLIENT_EVENT", + [PA_COMMAND_PLAYBACK_STREAM_EVENT] = "PLAYBACK_STREAM_EVENT", + [PA_COMMAND_RECORD_STREAM_EVENT] = "RECORD_STREAM_EVENT", + + /* SERVER->CLIENT */ + [PA_COMMAND_PLAYBACK_BUFFER_ATTR_CHANGED] = "PLAYBACK_BUFFER_ATTR_CHANGED", + [PA_COMMAND_RECORD_BUFFER_ATTR_CHANGED] = "RECORD_BUFFER_ATTR_CHANGED" }; #endif diff --git a/src/pulsecore/proplist-util.c b/src/pulsecore/proplist-util.c index af5f0aa..eac8927 100644 --- a/src/pulsecore/proplist-util.c +++ b/src/pulsecore/proplist-util.c @@ -231,12 +231,11 @@ void pa_init_proplist(pa_proplist *p) { } if (!pa_proplist_contains(p, PA_PROP_APPLICATION_PROCESS_SESSION_ID)) { - const char *t; + char *s; - if ((t = getenv("XDG_SESSION_COOKIE"))) { - char *c = pa_utf8_filter(t); - pa_proplist_sets(p, PA_PROP_APPLICATION_PROCESS_SESSION_ID, c); - pa_xfree(c); + if ((s = pa_session_id())) { + pa_proplist_sets(p, PA_PROP_APPLICATION_PROCESS_SESSION_ID, s); + pa_xfree(s); } } } diff --git a/src/pulsecore/protocol-esound.c b/src/pulsecore/protocol-esound.c index 2b80c65..7e7126e 100644 --- a/src/pulsecore/protocol-esound.c +++ b/src/pulsecore/protocol-esound.c @@ -562,7 +562,7 @@ static int esd_proto_get_latency(connection *c, esd_proto_t request, const void pa_sink *sink; int32_t latency; - connection_ref(c); + connection_assert_ref(c); pa_assert(!data); pa_assert(length == 0); @@ -575,6 +575,7 @@ static int esd_proto_get_latency(connection *c, esd_proto_t request, const void latency = PA_MAYBE_INT32_SWAP(c->swap_byte_order, latency); connection_write(c, &latency, sizeof(int32_t)); + return 0; } @@ -583,7 +584,7 @@ static int esd_proto_server_info(connection *c, esd_proto_t request, const void int32_t response; pa_sink *sink; - connection_ref(c); + connection_assert_ref(c); pa_assert(data); pa_assert(length == sizeof(int32_t)); @@ -611,7 +612,7 @@ static int esd_proto_all_info(connection *c, esd_proto_t request, const void *da unsigned nsamples; char terminator[sizeof(int32_t)*6+ESD_NAME_MAX]; - connection_ref(c); + connection_assert_ref(c); pa_assert(data); pa_assert(length == sizeof(int32_t)); @@ -637,7 +638,8 @@ static int esd_proto_all_info(connection *c, esd_proto_t request, const void *da pa_assert(t >= k*2+s); if (conn->sink_input) { - pa_cvolume volume = *pa_sink_input_get_volume(conn->sink_input); + pa_cvolume volume; + pa_sink_input_get_volume(conn->sink_input, &volume, TRUE); rate = (int32_t) conn->sink_input->sample_spec.rate; lvolume = (int32_t) ((volume.values[0]*ESD_VOLUME_BASE)/PA_VOLUME_NORM); rvolume = (int32_t) ((volume.values[volume.channels == 2 ? 1 : 0]*ESD_VOLUME_BASE)/PA_VOLUME_NORM); @@ -777,7 +779,7 @@ static int esd_proto_stream_pan(connection *c, esd_proto_t request, const void * volume.values[1] = (rvolume*PA_VOLUME_NORM)/ESD_VOLUME_BASE; volume.channels = conn->sink_input->sample_spec.channels; - pa_sink_input_set_volume(conn->sink_input, &volume, TRUE); + pa_sink_input_set_volume(conn->sink_input, &volume, TRUE, TRUE); ok = 1; } else ok = 0; @@ -1105,8 +1107,7 @@ static int do_read(connection *c) { pa_scache_add_item(c->protocol->core, c->scache.name, &c->scache.sample_spec, NULL, &c->scache.memchunk, c->client->proplist, &idx); pa_memblock_unref(c->scache.memchunk.memblock); - c->scache.memchunk.memblock = NULL; - c->scache.memchunk.index = c->scache.memchunk.length = 0; + pa_memchunk_reset(&c->scache.memchunk); pa_xfree(c->scache.name); c->scache.name = NULL; diff --git a/src/pulsecore/protocol-native.c b/src/pulsecore/protocol-native.c index 50a9191..aecaf71 100644 --- a/src/pulsecore/protocol-native.c +++ b/src/pulsecore/protocol-native.c @@ -81,8 +81,20 @@ typedef struct record_stream { pa_source_output *source_output; pa_memblockq *memblockq; - size_t fragment_size; - pa_usec_t source_latency; + + pa_bool_t adjust_latency:1; + pa_bool_t early_requests:1; + + pa_buffer_attr buffer_attr; + + pa_atomic_t on_the_fly; + pa_usec_t configured_source_latency; + size_t drop_initial; + + /* Only updated after SOURCE_OUTPUT_MESSAGE_UPDATE_LATENCY */ + size_t on_the_fly_snapshot; + pa_usec_t current_monitor_latency; + pa_usec_t current_source_latency; } record_stream; PA_DECLARE_CLASS(record_stream); @@ -105,18 +117,24 @@ typedef struct playback_stream { pa_sink_input *sink_input; pa_memblockq *memblockq; + + pa_bool_t adjust_latency:1; + pa_bool_t early_requests:1; + pa_bool_t is_underrun:1; pa_bool_t drain_request:1; uint32_t drain_tag; uint32_t syncid; pa_atomic_t missing; - size_t minreq; - pa_usec_t sink_latency; + pa_usec_t configured_sink_latency; + pa_buffer_attr buffer_attr; /* Only updated after SINK_INPUT_MESSAGE_UPDATE_LATENCY */ int64_t read_index, write_index; size_t render_memblockq_length; + pa_usec_t current_sink_latency; + uint64_t playing_for, underrun_for; } playback_stream; PA_DECLARE_CLASS(playback_stream); @@ -174,13 +192,18 @@ struct pa_native_protocol { }; enum { + SOURCE_OUTPUT_MESSAGE_UPDATE_LATENCY = PA_SOURCE_OUTPUT_MESSAGE_MAX +}; + +enum { SINK_INPUT_MESSAGE_POST_DATA = PA_SINK_INPUT_MESSAGE_MAX, /* data from main loop to sink input */ SINK_INPUT_MESSAGE_DRAIN, /* disabled prebuf, get playback started. */ SINK_INPUT_MESSAGE_FLUSH, SINK_INPUT_MESSAGE_TRIGGER, SINK_INPUT_MESSAGE_SEEK, SINK_INPUT_MESSAGE_PREBUF_FORCE, - SINK_INPUT_MESSAGE_UPDATE_LATENCY + SINK_INPUT_MESSAGE_UPDATE_LATENCY, + SINK_INPUT_MESSAGE_UPDATE_BUFFER_ATTR }; enum { @@ -188,7 +211,8 @@ enum { PLAYBACK_STREAM_MESSAGE_UNDERFLOW, PLAYBACK_STREAM_MESSAGE_OVERFLOW, PLAYBACK_STREAM_MESSAGE_DRAIN_ACK, - PLAYBACK_STREAM_MESSAGE_STARTED + PLAYBACK_STREAM_MESSAGE_STARTED, + PLAYBACK_STREAM_MESSAGE_UPDATE_TLENGTH }; enum { @@ -203,7 +227,7 @@ enum { static int sink_input_pop_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk); static void sink_input_kill_cb(pa_sink_input *i); static void sink_input_suspend_cb(pa_sink_input *i, pa_bool_t suspend); -static void sink_input_moved_cb(pa_sink_input *i); +static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest); static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes); static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes); static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes); @@ -215,11 +239,12 @@ static void playback_stream_request_bytes(struct playback_stream*s); static void source_output_kill_cb(pa_source_output *o); static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk); static void source_output_suspend_cb(pa_source_output *o, pa_bool_t suspend); -static void source_output_moved_cb(pa_source_output *o); +static void source_output_moving_cb(pa_source_output *o, pa_source *dest); static pa_usec_t source_output_get_latency_cb(pa_source_output *o); static void source_output_send_event_cb(pa_source_output *o, const char *event, pa_proplist *pl); static int sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk); +static int source_output_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk); static void command_exit(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); static void command_create_playback_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); @@ -360,6 +385,7 @@ static const pa_pdispatch_cb_t command_table[PA_COMMAND_MAX] = { /* structure management */ +/* Called from main context */ static void upload_stream_unlink(upload_stream *s) { pa_assert(s); @@ -371,6 +397,7 @@ static void upload_stream_unlink(upload_stream *s) { upload_stream_unref(s); } +/* Called from main context */ static void upload_stream_free(pa_object *o) { upload_stream *s = UPLOAD_STREAM(o); pa_assert(s); @@ -388,6 +415,7 @@ static void upload_stream_free(pa_object *o) { pa_xfree(s); } +/* Called from main context */ static upload_stream* upload_stream_new( pa_native_connection *c, const pa_sample_spec *ss, @@ -420,6 +448,7 @@ static upload_stream* upload_stream_new( return s; } +/* Called from main context */ static void record_stream_unlink(record_stream *s) { pa_assert(s); @@ -437,6 +466,7 @@ static void record_stream_unlink(record_stream *s) { record_stream_unref(s); } +/* Called from main context */ static void record_stream_free(pa_object *o) { record_stream *s = RECORD_STREAM(o); pa_assert(s); @@ -447,6 +477,7 @@ static void record_stream_free(pa_object *o) { pa_xfree(s); } +/* Called from main context */ static int record_stream_process_msg(pa_msgobject *o, int code, void*userdata, int64_t offset, pa_memchunk *chunk) { record_stream *s = RECORD_STREAM(o); record_stream_assert_ref(s); @@ -458,6 +489,10 @@ static int record_stream_process_msg(pa_msgobject *o, int code, void*userdata, i case RECORD_STREAM_MESSAGE_POST_DATA: + /* We try to keep up to date with how many bytes are + * currently on the fly */ + pa_atomic_sub(&s->on_the_fly, chunk->length); + if (pa_memblockq_push_align(s->memblockq, chunk) < 0) { /* pa_log_warn("Failed to push data into output queue."); */ return -1; @@ -472,35 +507,34 @@ static int record_stream_process_msg(pa_msgobject *o, int code, void*userdata, i return 0; } -static void fix_record_buffer_attr_pre( - record_stream *s, - pa_bool_t adjust_latency, - pa_bool_t early_requests, - uint32_t *maxlength, - uint32_t *fragsize) { +/* Called from main context */ +static void fix_record_buffer_attr_pre(record_stream *s) { size_t frame_size; pa_usec_t orig_fragsize_usec, fragsize_usec, source_usec; pa_assert(s); - pa_assert(maxlength); - pa_assert(fragsize); + + /* This function will be called from the main thread, before as + * well as after the source output has been activated using + * pa_source_output_put()! That means it may not touch any + * ->thread_info data! */ frame_size = pa_frame_size(&s->source_output->sample_spec); - if (*maxlength == (uint32_t) -1 || *maxlength > MAX_MEMBLOCKQ_LENGTH) - *maxlength = MAX_MEMBLOCKQ_LENGTH; - if (*maxlength <= 0) - *maxlength = (uint32_t) frame_size; + if (s->buffer_attr.maxlength == (uint32_t) -1 || s->buffer_attr.maxlength > MAX_MEMBLOCKQ_LENGTH) + s->buffer_attr.maxlength = MAX_MEMBLOCKQ_LENGTH; + if (s->buffer_attr.maxlength <= 0) + s->buffer_attr.maxlength = (uint32_t) frame_size; - if (*fragsize == (uint32_t) -1) - *fragsize = (uint32_t) pa_usec_to_bytes(DEFAULT_FRAGSIZE_MSEC*PA_USEC_PER_MSEC, &s->source_output->sample_spec); - if (*fragsize <= 0) - *fragsize = (uint32_t) frame_size; + if (s->buffer_attr.fragsize == (uint32_t) -1) + s->buffer_attr.fragsize = (uint32_t) pa_usec_to_bytes(DEFAULT_FRAGSIZE_MSEC*PA_USEC_PER_MSEC, &s->source_output->sample_spec); + if (s->buffer_attr.fragsize <= 0) + s->buffer_attr.fragsize = (uint32_t) frame_size; - orig_fragsize_usec = fragsize_usec = pa_bytes_to_usec(*fragsize, &s->source_output->sample_spec); + orig_fragsize_usec = fragsize_usec = pa_bytes_to_usec(s->buffer_attr.fragsize, &s->source_output->sample_spec); - if (early_requests) { + if (s->early_requests) { /* In early request mode we need to emulate the classic * fragment-based playback model. We do this setting the source @@ -508,7 +542,7 @@ static void fix_record_buffer_attr_pre( source_usec = fragsize_usec; - } else if (adjust_latency) { + } else if (s->adjust_latency) { /* So, the user asked us to adjust the latency according to * what the source can provide. Half the latency will be @@ -522,73 +556,69 @@ static void fix_record_buffer_attr_pre( /* Ok, the user didn't ask us to adjust the latency, hence we * don't */ - source_usec = 0; + source_usec = (pa_usec_t) -1; } - if (source_usec > 0) - s->source_latency = pa_source_output_set_requested_latency(s->source_output, source_usec); + if (source_usec != (pa_usec_t) -1) + s->configured_source_latency = pa_source_output_set_requested_latency(s->source_output, source_usec); else - s->source_latency = 0; + s->configured_source_latency = 0; - if (early_requests) { + if (s->early_requests) { /* Ok, we didn't necessarily get what we were asking for, so * let's tell the user */ - fragsize_usec = s->source_latency; + fragsize_usec = s->configured_source_latency; - } else if (adjust_latency) { + } else if (s->adjust_latency) { /* Now subtract what we actually got */ - if (fragsize_usec >= s->source_latency*2) - fragsize_usec -= s->source_latency; + if (fragsize_usec >= s->configured_source_latency*2) + fragsize_usec -= s->configured_source_latency; else - fragsize_usec = s->source_latency; + fragsize_usec = s->configured_source_latency; } if (pa_usec_to_bytes(orig_fragsize_usec, &s->source_output->sample_spec) != pa_usec_to_bytes(fragsize_usec, &s->source_output->sample_spec)) - *fragsize = (uint32_t) pa_usec_to_bytes(fragsize_usec, &s->source_output->sample_spec); + s->buffer_attr.fragsize = (uint32_t) pa_usec_to_bytes(fragsize_usec, &s->source_output->sample_spec); - if (*fragsize <= 0) - *fragsize = (uint32_t) frame_size; + if (s->buffer_attr.fragsize <= 0) + s->buffer_attr.fragsize = (uint32_t) frame_size; } -static void fix_record_buffer_attr_post( - record_stream *s, - uint32_t *maxlength, - uint32_t *fragsize) { - +/* Called from main context */ +static void fix_record_buffer_attr_post(record_stream *s) { size_t base; pa_assert(s); - pa_assert(maxlength); - pa_assert(fragsize); - *maxlength = (uint32_t) pa_memblockq_get_maxlength(s->memblockq); + /* This function will be called from the main thread, before as + * well as after the source output has been activated using + * pa_source_output_put()! That means it may not touch and + * ->thread_info data! */ base = pa_frame_size(&s->source_output->sample_spec); - s->fragment_size = (*fragsize/base)*base; - if (s->fragment_size <= 0) - s->fragment_size = base; + s->buffer_attr.fragsize = (s->buffer_attr.fragsize/base)*base; + if (s->buffer_attr.fragsize <= 0) + s->buffer_attr.fragsize = base; - if (s->fragment_size > *maxlength) - s->fragment_size = *maxlength; - - *fragsize = (uint32_t) s->fragment_size; + if (s->buffer_attr.fragsize > s->buffer_attr.maxlength) + s->buffer_attr.fragsize = s->buffer_attr.maxlength; } +/* Called from main context */ static record_stream* record_stream_new( pa_native_connection *c, pa_source *source, pa_sample_spec *ss, pa_channel_map *map, pa_bool_t peak_detect, - uint32_t *maxlength, - uint32_t *fragsize, + pa_buffer_attr *attr, pa_source_output_flags_t flags, pa_proplist *p, pa_bool_t adjust_latency, @@ -603,7 +633,6 @@ static record_stream* record_stream_new( pa_assert(c); pa_assert(ss); - pa_assert(maxlength); pa_assert(p); pa_assert(ret); @@ -632,20 +661,25 @@ static record_stream* record_stream_new( s->parent.process_msg = record_stream_process_msg; s->connection = c; s->source_output = source_output; + s->buffer_attr = *attr; + s->adjust_latency = adjust_latency; + s->early_requests = early_requests; + pa_atomic_store(&s->on_the_fly, 0); + s->source_output->parent.process_msg = source_output_process_msg; s->source_output->push = source_output_push_cb; s->source_output->kill = source_output_kill_cb; s->source_output->get_latency = source_output_get_latency_cb; - s->source_output->moved = source_output_moved_cb; + s->source_output->moving = source_output_moving_cb; s->source_output->suspend = source_output_suspend_cb; s->source_output->send_event = source_output_send_event_cb; s->source_output->userdata = s; - fix_record_buffer_attr_pre(s, adjust_latency, early_requests, maxlength, fragsize); + fix_record_buffer_attr_pre(s); s->memblockq = pa_memblockq_new( 0, - *maxlength, + s->buffer_attr.maxlength, 0, base = pa_frame_size(&source_output->sample_spec), 1, @@ -653,7 +687,8 @@ static record_stream* record_stream_new( 0, NULL); - fix_record_buffer_attr_post(s, maxlength, fragsize); + pa_memblockq_get_attr(s->memblockq, &s->buffer_attr); + fix_record_buffer_attr_post(s); *ss = s->source_output->sample_spec; *map = s->source_output->channel_map; @@ -661,14 +696,15 @@ static record_stream* record_stream_new( pa_idxset_put(c->record_streams, s, &s->index); pa_log_info("Final latency %0.2f ms = %0.2f ms + %0.2f ms", - ((double) pa_bytes_to_usec(s->fragment_size, &source_output->sample_spec) + (double) s->source_latency) / PA_USEC_PER_MSEC, - (double) pa_bytes_to_usec(s->fragment_size, &source_output->sample_spec) / PA_USEC_PER_MSEC, - (double) s->source_latency / PA_USEC_PER_MSEC); + ((double) pa_bytes_to_usec(s->buffer_attr.fragsize, &source_output->sample_spec) + (double) s->configured_source_latency) / PA_USEC_PER_MSEC, + (double) pa_bytes_to_usec(s->buffer_attr.fragsize, &source_output->sample_spec) / PA_USEC_PER_MSEC, + (double) s->configured_source_latency / PA_USEC_PER_MSEC); pa_source_output_put(s->source_output); return s; } +/* Called from main context */ static void record_stream_send_killed(record_stream *r) { pa_tagstruct *t; record_stream_assert_ref(r); @@ -680,6 +716,7 @@ static void record_stream_send_killed(record_stream *r) { pa_pstream_send_tagstruct(r->connection->pstream, t); } +/* Called from main context */ static void playback_stream_unlink(playback_stream *s) { pa_assert(s); @@ -700,6 +737,7 @@ static void playback_stream_unlink(playback_stream *s) { playback_stream_unref(s); } +/* Called from main context */ static void playback_stream_free(pa_object* o) { playback_stream *s = PLAYBACK_STREAM(o); pa_assert(s); @@ -710,6 +748,7 @@ static void playback_stream_free(pa_object* o) { pa_xfree(s); } +/* Called from main context */ static int playback_stream_process_msg(pa_msgobject *o, int code, void*userdata, int64_t offset, pa_memchunk *chunk) { playback_stream *s = PLAYBACK_STREAM(o); playback_stream_assert_ref(s); @@ -720,24 +759,21 @@ static int playback_stream_process_msg(pa_msgobject *o, int code, void*userdata, switch (code) { case PLAYBACK_STREAM_MESSAGE_REQUEST_DATA: { pa_tagstruct *t; - uint32_t l = 0; + int l = 0; for (;;) { - if ((l = (uint32_t) pa_atomic_load(&s->missing)) <= 0) - break; + if ((l = pa_atomic_load(&s->missing)) <= 0) + return 0; - if (pa_atomic_cmpxchg(&s->missing, (int) l, 0)) + if (pa_atomic_cmpxchg(&s->missing, l, 0)) break; } - if (l <= 0) - break; - t = pa_tagstruct_new(NULL, 0); pa_tagstruct_putu32(t, PA_COMMAND_REQUEST); pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */ pa_tagstruct_putu32(t, s->index); - pa_tagstruct_putu32(t, l); + pa_tagstruct_putu32(t, (uint32_t) l); pa_pstream_send_tagstruct(s->connection->pstream, t); /* pa_log("Requesting %lu bytes", (unsigned long) l); */ @@ -775,7 +811,7 @@ static int playback_stream_process_msg(pa_msgobject *o, int code, void*userdata, if (s->connection->version >= 13) { pa_tagstruct *t; - /* Notify the user we're overflowed*/ + /* Notify the user we started playback */ t = pa_tagstruct_new(NULL, 0); pa_tagstruct_putu32(t, PA_COMMAND_STARTED); pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */ @@ -788,67 +824,79 @@ static int playback_stream_process_msg(pa_msgobject *o, int code, void*userdata, case PLAYBACK_STREAM_MESSAGE_DRAIN_ACK: pa_pstream_send_simple_ack(s->connection->pstream, PA_PTR_TO_UINT(userdata)); break; + + case PLAYBACK_STREAM_MESSAGE_UPDATE_TLENGTH: { + pa_tagstruct *t; + + s->buffer_attr.tlength = (uint32_t) offset; + + t = pa_tagstruct_new(NULL, 0); + pa_tagstruct_putu32(t, PA_COMMAND_PLAYBACK_BUFFER_ATTR_CHANGED); + pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */ + pa_tagstruct_putu32(t, s->index); + pa_tagstruct_putu32(t, s->buffer_attr.maxlength); + pa_tagstruct_putu32(t, s->buffer_attr.tlength); + pa_tagstruct_putu32(t, s->buffer_attr.prebuf); + pa_tagstruct_putu32(t, s->buffer_attr.minreq); + pa_tagstruct_put_usec(t, s->configured_sink_latency); + pa_pstream_send_tagstruct(s->connection->pstream, t); + + break; + } } return 0; } -static void fix_playback_buffer_attr_pre( - playback_stream *s, - pa_bool_t adjust_latency, - pa_bool_t early_requests, - uint32_t *maxlength, - uint32_t *tlength, - uint32_t* prebuf, - uint32_t* minreq) { - - size_t frame_size; +/* Called from main context */ +static void fix_playback_buffer_attr(playback_stream *s) { + size_t frame_size, max_prebuf; pa_usec_t orig_tlength_usec, tlength_usec, orig_minreq_usec, minreq_usec, sink_usec; pa_assert(s); - pa_assert(maxlength); - pa_assert(tlength); - pa_assert(prebuf); - pa_assert(minreq); + + /* This function will be called from the main thread, before as + * well as after the sink input has been activated using + * pa_sink_input_put()! That means it may not touch any + * ->thread_info data, such as the memblockq! */ frame_size = pa_frame_size(&s->sink_input->sample_spec); - if (*maxlength == (uint32_t) -1 || *maxlength > MAX_MEMBLOCKQ_LENGTH) - *maxlength = MAX_MEMBLOCKQ_LENGTH; - if (*maxlength <= 0) - *maxlength = (uint32_t) frame_size; + if (s->buffer_attr.maxlength == (uint32_t) -1 || s->buffer_attr.maxlength > MAX_MEMBLOCKQ_LENGTH) + s->buffer_attr.maxlength = MAX_MEMBLOCKQ_LENGTH; + if (s->buffer_attr.maxlength <= 0) + s->buffer_attr.maxlength = (uint32_t) frame_size; - if (*tlength == (uint32_t) -1) - *tlength = (uint32_t) pa_usec_to_bytes_round_up(DEFAULT_TLENGTH_MSEC*PA_USEC_PER_MSEC, &s->sink_input->sample_spec); - if (*tlength <= 0) - *tlength = (uint32_t) frame_size; + if (s->buffer_attr.tlength == (uint32_t) -1) + s->buffer_attr.tlength = (uint32_t) pa_usec_to_bytes_round_up(DEFAULT_TLENGTH_MSEC*PA_USEC_PER_MSEC, &s->sink_input->sample_spec); + if (s->buffer_attr.tlength <= 0) + s->buffer_attr.tlength = (uint32_t) frame_size; - if (*minreq == (uint32_t) -1) - *minreq = (uint32_t) pa_usec_to_bytes_round_up(DEFAULT_PROCESS_MSEC*PA_USEC_PER_MSEC, &s->sink_input->sample_spec); - if (*minreq <= 0) - *minreq = (uint32_t) frame_size; + if (s->buffer_attr.minreq == (uint32_t) -1) + s->buffer_attr.minreq = (uint32_t) pa_usec_to_bytes_round_up(DEFAULT_PROCESS_MSEC*PA_USEC_PER_MSEC, &s->sink_input->sample_spec); + if (s->buffer_attr.minreq <= 0) + s->buffer_attr.minreq = (uint32_t) frame_size; - if (*tlength < *minreq+frame_size) - *tlength = *minreq+(uint32_t) frame_size; + if (s->buffer_attr.tlength < s->buffer_attr.minreq+frame_size) + s->buffer_attr.tlength = s->buffer_attr.minreq+(uint32_t) frame_size; - orig_tlength_usec = tlength_usec = pa_bytes_to_usec(*tlength, &s->sink_input->sample_spec); - orig_minreq_usec = minreq_usec = pa_bytes_to_usec(*minreq, &s->sink_input->sample_spec); + orig_tlength_usec = tlength_usec = pa_bytes_to_usec(s->buffer_attr.tlength, &s->sink_input->sample_spec); + orig_minreq_usec = minreq_usec = pa_bytes_to_usec(s->buffer_attr.minreq, &s->sink_input->sample_spec); pa_log_info("Requested tlength=%0.2f ms, minreq=%0.2f ms", (double) tlength_usec / PA_USEC_PER_MSEC, (double) minreq_usec / PA_USEC_PER_MSEC); - if (early_requests) { + if (s->early_requests) { /* In early request mode we need to emulate the classic * fragment-based playback model. We do this setting the sink * latency to the fragment size. */ sink_usec = minreq_usec; - pa_log_debug("Early requests mode enabled, configuring sink latency to minreq."); - } else if (adjust_latency) { + } else if (s->adjust_latency) { /* So, the user asked us to adjust the latency of the stream * buffer according to the what the sink can provide. The @@ -888,80 +936,60 @@ static void fix_playback_buffer_attr_pre( pa_log_debug("Traditional mode enabled, modifying sink usec only for compat with minreq."); } - s->sink_latency = pa_sink_input_set_requested_latency(s->sink_input, sink_usec); + s->configured_sink_latency = pa_sink_input_set_requested_latency(s->sink_input, sink_usec); - if (early_requests) { + if (s->early_requests) { /* Ok, we didn't necessarily get what we were asking for, so * let's tell the user */ - minreq_usec = s->sink_latency; + minreq_usec = s->configured_sink_latency; - } else if (adjust_latency) { + } else if (s->adjust_latency) { /* Ok, we didn't necessarily get what we were asking for, so * let's subtract from what we asked for for the remaining * buffer space */ - if (tlength_usec >= s->sink_latency) - tlength_usec -= s->sink_latency; + if (tlength_usec >= s->configured_sink_latency) + tlength_usec -= s->configured_sink_latency; } /* FIXME: This is actually larger than necessary, since not all of * the sink latency is actually rewritable. */ - if (tlength_usec < s->sink_latency + 2*minreq_usec) - tlength_usec = s->sink_latency + 2*minreq_usec; + if (tlength_usec < s->configured_sink_latency + 2*minreq_usec) + tlength_usec = s->configured_sink_latency + 2*minreq_usec; if (pa_usec_to_bytes_round_up(orig_tlength_usec, &s->sink_input->sample_spec) != pa_usec_to_bytes_round_up(tlength_usec, &s->sink_input->sample_spec)) - *tlength = (uint32_t) pa_usec_to_bytes_round_up(tlength_usec, &s->sink_input->sample_spec); + s->buffer_attr.tlength = (uint32_t) pa_usec_to_bytes_round_up(tlength_usec, &s->sink_input->sample_spec); if (pa_usec_to_bytes(orig_minreq_usec, &s->sink_input->sample_spec) != pa_usec_to_bytes(minreq_usec, &s->sink_input->sample_spec)) - *minreq = (uint32_t) pa_usec_to_bytes(minreq_usec, &s->sink_input->sample_spec); + s->buffer_attr.minreq = (uint32_t) pa_usec_to_bytes(minreq_usec, &s->sink_input->sample_spec); - if (*minreq <= 0) { - *minreq = (uint32_t) frame_size; - *tlength += (uint32_t) frame_size*2; + if (s->buffer_attr.minreq <= 0) { + s->buffer_attr.minreq = (uint32_t) frame_size; + s->buffer_attr.tlength += (uint32_t) frame_size*2; } - if (*tlength <= *minreq) - *tlength = *minreq*2 + (uint32_t) frame_size; - - if (*prebuf == (uint32_t) -1 || *prebuf > *tlength) - *prebuf = *tlength; -} - -static void fix_playback_buffer_attr_post( - playback_stream *s, - uint32_t *maxlength, - uint32_t *tlength, - uint32_t* prebuf, - uint32_t* minreq) { - - pa_assert(s); - pa_assert(maxlength); - pa_assert(tlength); - pa_assert(prebuf); - pa_assert(minreq); + if (s->buffer_attr.tlength <= s->buffer_attr.minreq) + s->buffer_attr.tlength = s->buffer_attr.minreq*2 + (uint32_t) frame_size; - *maxlength = (uint32_t) pa_memblockq_get_maxlength(s->memblockq); - *tlength = (uint32_t) pa_memblockq_get_tlength(s->memblockq); - *prebuf = (uint32_t) pa_memblockq_get_prebuf(s->memblockq); - *minreq = (uint32_t) pa_memblockq_get_minreq(s->memblockq); + max_prebuf = s->buffer_attr.tlength + (uint32_t)frame_size - s->buffer_attr.minreq; - s->minreq = *minreq; + if (s->buffer_attr.prebuf == (uint32_t) -1 || + s->buffer_attr.prebuf > max_prebuf) + s->buffer_attr.prebuf = max_prebuf; } +/* Called from main context */ static playback_stream* playback_stream_new( pa_native_connection *c, pa_sink *sink, pa_sample_spec *ss, pa_channel_map *map, - uint32_t *maxlength, - uint32_t *tlength, - uint32_t *prebuf, - uint32_t *minreq, + pa_buffer_attr *a, pa_cvolume *volume, pa_bool_t muted, pa_bool_t muted_set, @@ -982,10 +1010,6 @@ static playback_stream* playback_stream_new( pa_assert(c); pa_assert(ss); - pa_assert(maxlength); - pa_assert(tlength); - pa_assert(prebuf); - pa_assert(minreq); pa_assert(missing); pa_assert(p); pa_assert(ret); @@ -1042,6 +1066,9 @@ static playback_stream* playback_stream_new( s->is_underrun = TRUE; s->drain_request = FALSE; pa_atomic_store(&s->missing, 0); + s->buffer_attr = *a; + s->adjust_latency = adjust_latency; + s->early_requests = early_requests; s->sink_input->parent.process_msg = sink_input_process_msg; s->sink_input->pop = sink_input_pop_cb; @@ -1049,28 +1076,28 @@ static playback_stream* playback_stream_new( s->sink_input->update_max_rewind = sink_input_update_max_rewind_cb; s->sink_input->update_max_request = sink_input_update_max_request_cb; s->sink_input->kill = sink_input_kill_cb; - s->sink_input->moved = sink_input_moved_cb; + s->sink_input->moving = sink_input_moving_cb; s->sink_input->suspend = sink_input_suspend_cb; s->sink_input->send_event = sink_input_send_event_cb; s->sink_input->userdata = s; start_index = ssync ? pa_memblockq_get_read_index(ssync->memblockq) : 0; - fix_playback_buffer_attr_pre(s, adjust_latency, early_requests, maxlength, tlength, prebuf, minreq); - pa_sink_input_get_silence(sink_input, &silence); + fix_playback_buffer_attr(s); + pa_sink_input_get_silence(sink_input, &silence); s->memblockq = pa_memblockq_new( start_index, - *maxlength, - *tlength, + s->buffer_attr.maxlength, + s->buffer_attr.tlength, pa_frame_size(&sink_input->sample_spec), - *prebuf, - *minreq, + s->buffer_attr.prebuf, + s->buffer_attr.minreq, 0, &silence); - pa_memblock_unref(silence.memblock); - fix_playback_buffer_attr_post(s, maxlength, tlength, prebuf, minreq); + + pa_memblockq_get_attr(s->memblockq, &s->buffer_attr); *missing = (uint32_t) pa_memblockq_pop_missing(s->memblockq); @@ -1080,18 +1107,19 @@ static playback_stream* playback_stream_new( pa_idxset_put(c->output_streams, s, &s->index); pa_log_info("Final latency %0.2f ms = %0.2f ms + 2*%0.2f ms + %0.2f ms", - ((double) pa_bytes_to_usec(*tlength, &sink_input->sample_spec) + (double) s->sink_latency) / PA_USEC_PER_MSEC, - (double) pa_bytes_to_usec(*tlength-*minreq*2, &sink_input->sample_spec) / PA_USEC_PER_MSEC, - (double) pa_bytes_to_usec(*minreq, &sink_input->sample_spec) / PA_USEC_PER_MSEC, - (double) s->sink_latency / PA_USEC_PER_MSEC); + ((double) pa_bytes_to_usec(s->buffer_attr.tlength, &sink_input->sample_spec) + (double) s->configured_sink_latency) / PA_USEC_PER_MSEC, + (double) pa_bytes_to_usec(s->buffer_attr.tlength-s->buffer_attr.minreq*2, &sink_input->sample_spec) / PA_USEC_PER_MSEC, + (double) pa_bytes_to_usec(s->buffer_attr.minreq, &sink_input->sample_spec) / PA_USEC_PER_MSEC, + (double) s->configured_sink_latency / PA_USEC_PER_MSEC); pa_sink_input_put(s->sink_input); return s; } -/* Called from thread context */ +/* Called from IO context */ static void playback_stream_request_bytes(playback_stream *s) { - size_t m, previous_missing; + size_t m, minreq; + int previous_missing; playback_stream_assert_ref(s); @@ -1102,14 +1130,16 @@ static void playback_stream_request_bytes(playback_stream *s) { /* pa_log("request_bytes(%lu)", (unsigned long) m); */ - previous_missing = (size_t) pa_atomic_add(&s->missing, (int) m); + previous_missing = pa_atomic_add(&s->missing, (int) m); + minreq = pa_memblockq_get_minreq(s->memblockq); if (pa_memblockq_prebuf_active(s->memblockq) || - (previous_missing < s->minreq && previous_missing+m >= s->minreq)) + (previous_missing < (int) minreq && previous_missing + (int) m >= (int) minreq)) pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PLAYBACK_STREAM_MESSAGE_REQUEST_DATA, NULL, 0, NULL, NULL); } +/* Called from main context */ static void playback_stream_send_killed(playback_stream *p) { pa_tagstruct *t; playback_stream_assert_ref(p); @@ -1121,6 +1151,7 @@ static void playback_stream_send_killed(playback_stream *p) { pa_pstream_send_tagstruct(p->connection->pstream, t); } +/* Called from main context */ static int native_connection_process_msg(pa_msgobject *o, int code, void*userdata, int64_t offset, pa_memchunk *chunk) { pa_native_connection *c = PA_NATIVE_CONNECTION(o); pa_native_connection_assert_ref(c); @@ -1142,6 +1173,7 @@ static int native_connection_process_msg(pa_msgobject *o, int code, void*userdat return 0; } +/* Called from main context */ static void native_connection_unlink(pa_native_connection *c) { record_stream *r; output_stream *o; @@ -1181,6 +1213,7 @@ static void native_connection_unlink(pa_native_connection *c) { pa_native_connection_unref(c); } +/* Called from main context */ static void native_connection_free(pa_object *o) { pa_native_connection *c = PA_NATIVE_CONNECTION(o); @@ -1198,6 +1231,7 @@ static void native_connection_free(pa_object *o) { pa_xfree(c); } +/* Called from main context */ static void native_connection_send_memblock(pa_native_connection *c) { uint32_t start; record_stream *r; @@ -1217,8 +1251,8 @@ static void native_connection_send_memblock(pa_native_connection *c) { if (pa_memblockq_peek(r->memblockq, &chunk) >= 0) { pa_memchunk schunk = chunk; - if (schunk.length > r->fragment_size) - schunk.length = r->fragment_size; + if (schunk.length > r->buffer_attr.fragsize) + schunk.length = r->buffer_attr.fragsize; pa_pstream_send_memblock(c->pstream, r->index, 0, PA_SEEK_RELATIVE, &schunk); @@ -1232,6 +1266,7 @@ static void native_connection_send_memblock(pa_native_connection *c) { /*** sink input callbacks ***/ +/* Called from thread context */ static void handle_seek(playback_stream *s, int64_t indexw) { playback_stream_assert_ref(s); @@ -1284,7 +1319,12 @@ static int sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int int64_t windex; windex = pa_memblockq_get_write_index(s->memblockq); - pa_memblockq_seek(s->memblockq, offset, PA_PTR_TO_UINT(userdata)); + + /* The client side is incapable of accounting correctly + * for seeks of a type != PA_SEEK_RELATIVE. We need to be + * able to deal with that. */ + + pa_memblockq_seek(s->memblockq, offset, PA_PTR_TO_UINT(userdata), PA_PTR_TO_UINT(userdata) == PA_SEEK_RELATIVE); handle_seek(s, windex); return 0; @@ -1302,7 +1342,7 @@ static int sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int if (pa_memblockq_push_align(s->memblockq, chunk) < 0) { pa_log_warn("Failed to push data into queue"); pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PLAYBACK_STREAM_MESSAGE_OVERFLOW, NULL, 0, NULL, NULL); - pa_memblockq_seek(s->memblockq, (int64_t) chunk->length, PA_SEEK_RELATIVE); + pa_memblockq_seek(s->memblockq, (int64_t) chunk->length, PA_SEEK_RELATIVE, TRUE); } handle_seek(s, windex); @@ -1371,10 +1411,14 @@ static int sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int } case SINK_INPUT_MESSAGE_UPDATE_LATENCY: - + /* Atomically get a snapshot of all timing parameters... */ s->read_index = pa_memblockq_get_read_index(s->memblockq); s->write_index = pa_memblockq_get_write_index(s->memblockq); s->render_memblockq_length = pa_memblockq_get_length(s->sink_input->thread_info.render_memblockq); + s->current_sink_latency = pa_sink_get_latency_within_thread(s->sink_input->sink); + s->underrun_for = s->sink_input->thread_info.underrun_for; + s->playing_for = s->sink_input->thread_info.playing_for; + return 0; case PA_SINK_INPUT_MESSAGE_SET_STATE: { @@ -1399,6 +1443,12 @@ static int sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int * latency added by the resampler */ break; } + + case SINK_INPUT_MESSAGE_UPDATE_BUFFER_ATTR: { + pa_memblockq_apply_attr(s->memblockq, &s->buffer_attr); + pa_memblockq_get_attr(s->memblockq, &s->buffer_attr); + return 0; + } } return pa_sink_input_process_msg(o, code, userdata, offset, chunk); @@ -1418,7 +1468,8 @@ static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk if (pa_memblockq_is_readable(s->memblockq)) s->is_underrun = FALSE; else { -/* pa_log("%s, UNDERRUN: %lu", pa_proplist_gets(i->proplist, PA_PROP_MEDIA_NAME), (unsigned long) pa_memblockq_get_length(s->memblockq)); */ + if (!s->is_underrun) + pa_log_debug("Underrun on '%s', %lu bytes in queue.", pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_MEDIA_NAME)), (unsigned long) pa_memblockq_get_length(s->memblockq)); if (s->drain_request && pa_sink_input_safe_to_remove(i)) { s->drain_request = FALSE; @@ -1447,6 +1498,7 @@ static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk return 0; } +/* Called from thread context */ static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) { playback_stream *s; @@ -1461,6 +1513,7 @@ static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) { pa_memblockq_rewind(s->memblockq, nbytes); } +/* Called from thread context */ static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) { playback_stream *s; @@ -1471,18 +1524,30 @@ static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) { pa_memblockq_set_maxrewind(s->memblockq, nbytes); } +/* Called from thread context */ static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) { playback_stream *s; - size_t tlength; + size_t new_tlength, old_tlength; pa_sink_input_assert_ref(i); s = PLAYBACK_STREAM(i->userdata); playback_stream_assert_ref(s); - tlength = nbytes+2*pa_memblockq_get_minreq(s->memblockq); + old_tlength = pa_memblockq_get_tlength(s->memblockq); + new_tlength = nbytes+2*pa_memblockq_get_minreq(s->memblockq); + + if (old_tlength < new_tlength) { + pa_log_debug("max_request changed, trying to update from %zu to %zu.", old_tlength, new_tlength); + pa_memblockq_set_tlength(s->memblockq, new_tlength); + new_tlength = pa_memblockq_get_tlength(s->memblockq); - if (pa_memblockq_get_tlength(s->memblockq) < tlength) - pa_memblockq_set_tlength(s->memblockq, tlength); + if (new_tlength == old_tlength) + pa_log_debug("Failed to increase tlength"); + else { + pa_log_debug("Notifying client about increased tlength"); + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PLAYBACK_STREAM_MESSAGE_UPDATE_TLENGTH, NULL, pa_memblockq_get_tlength(s->memblockq), NULL, NULL); + } + } } /* Called from main context */ @@ -1539,26 +1604,17 @@ static void sink_input_suspend_cb(pa_sink_input *i, pa_bool_t suspend) { } /* Called from main context */ -static void sink_input_moved_cb(pa_sink_input *i) { +static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest) { playback_stream *s; pa_tagstruct *t; - uint32_t maxlength, tlength, prebuf, minreq; pa_sink_input_assert_ref(i); s = PLAYBACK_STREAM(i->userdata); playback_stream_assert_ref(s); - maxlength = (uint32_t) pa_memblockq_get_maxlength(s->memblockq); - tlength = (uint32_t) pa_memblockq_get_tlength(s->memblockq); - prebuf = (uint32_t) pa_memblockq_get_prebuf(s->memblockq); - minreq = (uint32_t) pa_memblockq_get_minreq(s->memblockq); - - fix_playback_buffer_attr_pre(s, TRUE, FALSE, &maxlength, &tlength, &prebuf, &minreq); - pa_memblockq_set_maxlength(s->memblockq, maxlength); - pa_memblockq_set_tlength(s->memblockq, tlength); - pa_memblockq_set_prebuf(s->memblockq, prebuf); - pa_memblockq_set_minreq(s->memblockq, minreq); - fix_playback_buffer_attr_post(s, &maxlength, &tlength, &prebuf, &minreq); + fix_playback_buffer_attr(s); + pa_memblockq_apply_attr(s->memblockq, &s->buffer_attr); + pa_memblockq_get_attr(s->memblockq, &s->buffer_attr); if (s->connection->version < 12) return; @@ -1567,16 +1623,16 @@ static void sink_input_moved_cb(pa_sink_input *i) { pa_tagstruct_putu32(t, PA_COMMAND_PLAYBACK_STREAM_MOVED); pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */ pa_tagstruct_putu32(t, s->index); - pa_tagstruct_putu32(t, i->sink->index); - pa_tagstruct_puts(t, i->sink->name); - pa_tagstruct_put_boolean(t, pa_sink_get_state(i->sink) == PA_SINK_SUSPENDED); + pa_tagstruct_putu32(t, dest->index); + pa_tagstruct_puts(t, dest->name); + pa_tagstruct_put_boolean(t, pa_sink_get_state(dest) == PA_SINK_SUSPENDED); if (s->connection->version >= 13) { - pa_tagstruct_putu32(t, maxlength); - pa_tagstruct_putu32(t, tlength); - pa_tagstruct_putu32(t, prebuf); - pa_tagstruct_putu32(t, minreq); - pa_tagstruct_put_usec(t, s->sink_latency); + pa_tagstruct_putu32(t, s->buffer_attr.maxlength); + pa_tagstruct_putu32(t, s->buffer_attr.tlength); + pa_tagstruct_putu32(t, s->buffer_attr.prebuf); + pa_tagstruct_putu32(t, s->buffer_attr.minreq); + pa_tagstruct_put_usec(t, s->configured_sink_latency); } pa_pstream_send_tagstruct(s->connection->pstream, t); @@ -1585,6 +1641,27 @@ static void sink_input_moved_cb(pa_sink_input *i) { /*** source_output callbacks ***/ /* Called from thread context */ +static int source_output_process_msg(pa_msgobject *_o, int code, void *userdata, int64_t offset, pa_memchunk *chunk) { + pa_source_output *o = PA_SOURCE_OUTPUT(_o); + record_stream *s; + + pa_source_output_assert_ref(o); + s = RECORD_STREAM(o->userdata); + record_stream_assert_ref(s); + + switch (code) { + case SOURCE_OUTPUT_MESSAGE_UPDATE_LATENCY: + /* Atomically get a snapshot of all timing parameters... */ + s->current_monitor_latency = o->source->monitor_of ? pa_sink_get_latency_within_thread(o->source->monitor_of) : 0; + s->current_source_latency = pa_source_get_latency_within_thread(o->source); + s->on_the_fly_snapshot = pa_atomic_load(&s->on_the_fly); + return 0; + } + + return pa_source_output_process_msg(_o, code, userdata, offset, chunk); +} + +/* Called from thread context */ static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk) { record_stream *s; @@ -1593,6 +1670,7 @@ static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk) record_stream_assert_ref(s); pa_assert(chunk); + pa_atomic_add(&s->on_the_fly, chunk->length); pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), RECORD_STREAM_MESSAGE_POST_DATA, NULL, 0, chunk, NULL); } @@ -1661,21 +1739,18 @@ static void source_output_suspend_cb(pa_source_output *o, pa_bool_t suspend) { } /* Called from main context */ -static void source_output_moved_cb(pa_source_output *o) { +static void source_output_moving_cb(pa_source_output *o, pa_source *dest) { record_stream *s; pa_tagstruct *t; - uint32_t maxlength, fragsize; pa_source_output_assert_ref(o); s = RECORD_STREAM(o->userdata); record_stream_assert_ref(s); - fragsize = (uint32_t) s->fragment_size; - maxlength = (uint32_t) pa_memblockq_get_length(s->memblockq); - - fix_record_buffer_attr_pre(s, TRUE, FALSE, &maxlength, &fragsize); - pa_memblockq_set_maxlength(s->memblockq, maxlength); - fix_record_buffer_attr_post(s, &maxlength, &fragsize); + fix_record_buffer_attr_pre(s); + pa_memblockq_set_maxlength(s->memblockq, s->buffer_attr.maxlength); + pa_memblockq_get_attr(s->memblockq, &s->buffer_attr); + fix_record_buffer_attr_post(s); if (s->connection->version < 12) return; @@ -1684,14 +1759,14 @@ static void source_output_moved_cb(pa_source_output *o) { pa_tagstruct_putu32(t, PA_COMMAND_RECORD_STREAM_MOVED); pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */ pa_tagstruct_putu32(t, s->index); - pa_tagstruct_putu32(t, o->source->index); - pa_tagstruct_puts(t, o->source->name); - pa_tagstruct_put_boolean(t, pa_source_get_state(o->source) == PA_SOURCE_SUSPENDED); + pa_tagstruct_putu32(t, dest->index); + pa_tagstruct_puts(t, dest->name); + pa_tagstruct_put_boolean(t, pa_source_get_state(dest) == PA_SOURCE_SUSPENDED); if (s->connection->version >= 13) { - pa_tagstruct_putu32(t, maxlength); - pa_tagstruct_putu32(t, fragsize); - pa_tagstruct_put_usec(t, s->source_latency); + pa_tagstruct_putu32(t, s->buffer_attr.maxlength); + pa_tagstruct_putu32(t, s->buffer_attr.fragsize); + pa_tagstruct_put_usec(t, s->configured_source_latency); } pa_pstream_send_tagstruct(s->connection->pstream, t); @@ -1723,7 +1798,8 @@ static pa_tagstruct *reply_new(uint32_t tag) { static void command_create_playback_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); playback_stream *s; - uint32_t maxlength, tlength, prebuf, minreq, sink_index, syncid, missing; + uint32_t sink_index, syncid, missing; + pa_buffer_attr attr; const char *name = NULL, *sink_name; pa_sample_spec ss; pa_channel_map map; @@ -1752,6 +1828,7 @@ static void command_create_playback_stream(pa_pdispatch *pd, uint32_t command, u pa_native_connection_assert_ref(c); pa_assert(t); + memset(&attr, 0, sizeof(attr)); if ((c->version < 13 && (pa_tagstruct_gets(t, &name) < 0 || !name)) || pa_tagstruct_get( @@ -1760,11 +1837,11 @@ static void command_create_playback_stream(pa_pdispatch *pd, uint32_t command, u PA_TAG_CHANNEL_MAP, &map, PA_TAG_U32, &sink_index, PA_TAG_STRING, &sink_name, - PA_TAG_U32, &maxlength, + PA_TAG_U32, &attr.maxlength, PA_TAG_BOOLEAN, &corked, - PA_TAG_U32, &tlength, - PA_TAG_U32, &prebuf, - PA_TAG_U32, &minreq, + PA_TAG_U32, &attr.tlength, + PA_TAG_U32, &attr.prebuf, + PA_TAG_U32, &attr.minreq, PA_TAG_U32, &syncid, PA_TAG_CVOLUME, &volume, PA_TAG_INVALID) < 0) { @@ -1875,7 +1952,7 @@ static void command_create_playback_stream(pa_pdispatch *pd, uint32_t command, u * flag. For older versions we synthesize it here */ muted_set = muted_set || muted; - s = playback_stream_new(c, sink, &ss, &map, &maxlength, &tlength, &prebuf, &minreq, volume_set ? &volume : NULL, muted, muted_set, syncid, &missing, flags, p, adjust_latency, early_requests, &ret); + s = playback_stream_new(c, sink, &ss, &map, &attr, volume_set ? &volume : NULL, muted, muted_set, syncid, &missing, flags, p, adjust_latency, early_requests, &ret); pa_proplist_free(p); CHECK_VALIDITY(c->pstream, s, tag, ret); @@ -1891,10 +1968,10 @@ static void command_create_playback_stream(pa_pdispatch *pd, uint32_t command, u if (c->version >= 9) { /* Since 0.9.0 we support sending the buffer metrics back to the client */ - pa_tagstruct_putu32(reply, (uint32_t) maxlength); - pa_tagstruct_putu32(reply, (uint32_t) tlength); - pa_tagstruct_putu32(reply, (uint32_t) prebuf); - pa_tagstruct_putu32(reply, (uint32_t) minreq); + pa_tagstruct_putu32(reply, (uint32_t) s->buffer_attr.maxlength); + pa_tagstruct_putu32(reply, (uint32_t) s->buffer_attr.tlength); + pa_tagstruct_putu32(reply, (uint32_t) s->buffer_attr.prebuf); + pa_tagstruct_putu32(reply, (uint32_t) s->buffer_attr.minreq); } if (c->version >= 12) { @@ -1912,7 +1989,7 @@ static void command_create_playback_stream(pa_pdispatch *pd, uint32_t command, u } if (c->version >= 13) - pa_tagstruct_put_usec(reply, s->sink_latency); + pa_tagstruct_put_usec(reply, s->configured_sink_latency); pa_pstream_send_tagstruct(c->pstream, reply); } @@ -1978,7 +2055,7 @@ static void command_delete_stream(pa_pdispatch *pd, uint32_t command, uint32_t t static void command_create_record_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); record_stream *s; - uint32_t maxlength, fragment_size; + pa_buffer_attr attr; uint32_t source_index; const char *name = NULL, *source_name; pa_sample_spec ss; @@ -2008,14 +2085,16 @@ static void command_create_record_stream(pa_pdispatch *pd, uint32_t command, uin pa_native_connection_assert_ref(c); pa_assert(t); + memset(&attr, 0, sizeof(attr)); + if ((c->version < 13 && (pa_tagstruct_gets(t, &name) < 0 || !name)) || pa_tagstruct_get_sample_spec(t, &ss) < 0 || pa_tagstruct_get_channel_map(t, &map) < 0 || pa_tagstruct_getu32(t, &source_index) < 0 || pa_tagstruct_gets(t, &source_name) < 0 || - pa_tagstruct_getu32(t, &maxlength) < 0 || + pa_tagstruct_getu32(t, &attr.maxlength) < 0 || pa_tagstruct_get_boolean(t, &corked) < 0 || - pa_tagstruct_getu32(t, &fragment_size) < 0) { + pa_tagstruct_getu32(t, &attr.fragsize) < 0) { protocol_error(c); return; } @@ -2125,7 +2204,7 @@ static void command_create_record_stream(pa_pdispatch *pd, uint32_t command, uin (dont_inhibit_auto_suspend ? PA_SOURCE_OUTPUT_DONT_INHIBIT_AUTO_SUSPEND : 0) | (fail_on_suspend ? PA_SOURCE_OUTPUT_FAIL_ON_SUSPEND : 0); - s = record_stream_new(c, source, &ss, &map, peak_detect, &maxlength, &fragment_size, flags, p, adjust_latency, direct_on_input, early_requests, &ret); + s = record_stream_new(c, source, &ss, &map, peak_detect, &attr, flags, p, adjust_latency, direct_on_input, early_requests, &ret); pa_proplist_free(p); CHECK_VALIDITY(c->pstream, s, tag, ret); @@ -2138,8 +2217,8 @@ static void command_create_record_stream(pa_pdispatch *pd, uint32_t command, uin if (c->version >= 9) { /* Since 0.9 we support sending the buffer metrics back to the client */ - pa_tagstruct_putu32(reply, (uint32_t) maxlength); - pa_tagstruct_putu32(reply, (uint32_t) fragment_size); + pa_tagstruct_putu32(reply, (uint32_t) s->buffer_attr.maxlength); + pa_tagstruct_putu32(reply, (uint32_t) s->buffer_attr.fragsize); } if (c->version >= 12) { @@ -2157,7 +2236,7 @@ static void command_create_record_stream(pa_pdispatch *pd, uint32_t command, uin } if (c->version >= 13) - pa_tagstruct_put_usec(reply, s->source_latency); + pa_tagstruct_put_usec(reply, s->configured_source_latency); pa_pstream_send_tagstruct(c->pstream, reply); } @@ -2444,7 +2523,6 @@ static void command_get_playback_latency(pa_pdispatch *pd, uint32_t command, uin playback_stream *s; struct timeval tv, now; uint32_t idx; - pa_usec_t latency; pa_native_connection_assert_ref(c); pa_assert(t); @@ -2460,25 +2538,27 @@ static void command_get_playback_latency(pa_pdispatch *pd, uint32_t command, uin s = pa_idxset_get_by_index(c->output_streams, idx); CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY); CHECK_VALIDITY(c->pstream, playback_stream_isinstance(s), tag, PA_ERR_NOENTITY); - CHECK_VALIDITY(c->pstream, pa_asyncmsgq_send(s->sink_input->sink->asyncmsgq, PA_MSGOBJECT(s->sink_input), SINK_INPUT_MESSAGE_UPDATE_LATENCY, s, 0, NULL) == 0, tag, PA_ERR_NOENTITY) - - reply = reply_new(tag); - - latency = pa_sink_get_latency(s->sink_input->sink); - latency += pa_bytes_to_usec(s->render_memblockq_length, &s->sink_input->sample_spec); - pa_tagstruct_put_usec(reply, latency); + /* Get an atomic snapshot of all timing parameters */ + pa_assert_se(pa_asyncmsgq_send(s->sink_input->sink->asyncmsgq, PA_MSGOBJECT(s->sink_input), SINK_INPUT_MESSAGE_UPDATE_LATENCY, s, 0, NULL) == 0); + reply = reply_new(tag); + pa_tagstruct_put_usec(reply, + s->current_sink_latency + + pa_bytes_to_usec(s->render_memblockq_length, &s->sink_input->sample_spec)); pa_tagstruct_put_usec(reply, 0); - pa_tagstruct_put_boolean(reply, s->sink_input->thread_info.playing_for > 0); + pa_tagstruct_put_boolean(reply, + s->playing_for > 0 && + pa_sink_get_state(s->sink_input->sink) == PA_SINK_RUNNING && + pa_sink_input_get_state(s->sink_input) == PA_SINK_INPUT_RUNNING); pa_tagstruct_put_timeval(reply, &tv); pa_tagstruct_put_timeval(reply, pa_gettimeofday(&now)); pa_tagstruct_puts64(reply, s->write_index); pa_tagstruct_puts64(reply, s->read_index); if (c->version >= 13) { - pa_tagstruct_putu64(reply, s->sink_input->thread_info.underrun_for); - pa_tagstruct_putu64(reply, s->sink_input->thread_info.playing_for); + pa_tagstruct_putu64(reply, s->underrun_for); + pa_tagstruct_putu64(reply, s->playing_for); } pa_pstream_send_tagstruct(c->pstream, reply); @@ -2505,10 +2585,17 @@ static void command_get_record_latency(pa_pdispatch *pd, uint32_t command, uint3 s = pa_idxset_get_by_index(c->record_streams, idx); CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY); + /* Get an atomic snapshot of all timing parameters */ + pa_assert_se(pa_asyncmsgq_send(s->source_output->source->asyncmsgq, PA_MSGOBJECT(s->source_output), SOURCE_OUTPUT_MESSAGE_UPDATE_LATENCY, s, 0, NULL) == 0); + reply = reply_new(tag); - pa_tagstruct_put_usec(reply, s->source_output->source->monitor_of ? pa_sink_get_latency(s->source_output->source->monitor_of) : 0); - pa_tagstruct_put_usec(reply, pa_source_get_latency(s->source_output->source)); - pa_tagstruct_put_boolean(reply, pa_source_get_state(s->source_output->source) == PA_SOURCE_RUNNING); + pa_tagstruct_put_usec(reply, s->current_monitor_latency); + pa_tagstruct_put_usec(reply, + s->current_source_latency + + pa_bytes_to_usec(s->on_the_fly_snapshot, &s->source_output->sample_spec)); + pa_tagstruct_put_boolean(reply, + pa_source_get_state(s->source_output->source) == PA_SOURCE_RUNNING && + pa_source_output_get_state(s->source_output) == PA_SOURCE_OUTPUT_RUNNING); pa_tagstruct_put_timeval(reply, &tv); pa_tagstruct_put_timeval(reply, pa_gettimeofday(&now)); pa_tagstruct_puts64(reply, pa_memblockq_get_write_index(s->memblockq)); @@ -2732,7 +2819,7 @@ static void sink_fill_tagstruct(pa_native_connection *c, pa_tagstruct *t, pa_sin PA_TAG_SAMPLE_SPEC, &fixed_ss, PA_TAG_CHANNEL_MAP, &sink->channel_map, PA_TAG_U32, sink->module ? sink->module->index : PA_INVALID_INDEX, - PA_TAG_CVOLUME, pa_sink_get_volume(sink, FALSE), + PA_TAG_CVOLUME, pa_sink_get_volume(sink, FALSE, FALSE), PA_TAG_BOOLEAN, pa_sink_get_mute(sink, FALSE), PA_TAG_U32, sink->monitor_source ? sink->monitor_source->index : PA_INVALID_INDEX, PA_TAG_STRING, sink->monitor_source ? sink->monitor_source->name : NULL, @@ -2856,6 +2943,7 @@ static void module_fill_tagstruct(pa_native_connection *c, pa_tagstruct *t, pa_m static void sink_input_fill_tagstruct(pa_native_connection *c, pa_tagstruct *t, pa_sink_input *s) { pa_sample_spec fixed_ss; pa_usec_t sink_latency; + pa_cvolume v; pa_assert(t); pa_sink_input_assert_ref(s); @@ -2869,7 +2957,7 @@ static void sink_input_fill_tagstruct(pa_native_connection *c, pa_tagstruct *t, pa_tagstruct_putu32(t, s->sink->index); pa_tagstruct_put_sample_spec(t, &fixed_ss); pa_tagstruct_put_channel_map(t, &s->channel_map); - pa_tagstruct_put_cvolume(t, pa_sink_input_get_volume(s)); + pa_tagstruct_put_cvolume(t, pa_sink_input_get_volume(s, &v, TRUE)); pa_tagstruct_put_usec(t, pa_sink_input_get_latency(s, &sink_latency)); pa_tagstruct_put_usec(t, sink_latency); pa_tagstruct_puts(t, pa_resample_method_to_string(pa_sink_input_get_resample_method(s))); @@ -3234,11 +3322,11 @@ static void command_set_volume( CHECK_VALIDITY(c->pstream, si || sink || source, tag, PA_ERR_NOENTITY); if (sink) - pa_sink_set_volume(sink, &volume, TRUE, TRUE); + pa_sink_set_volume(sink, &volume, TRUE, TRUE, TRUE); else if (source) pa_source_set_volume(source, &volume); else if (si) - pa_sink_input_set_volume(si, &volume, TRUE); + pa_sink_input_set_volume(si, &volume, TRUE, TRUE); pa_pstream_send_simple_ack(c->pstream, tag); } @@ -3435,12 +3523,14 @@ static void command_flush_record_stream(pa_pdispatch *pd, uint32_t command, uint static void command_set_stream_buffer_attr(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { pa_native_connection *c = PA_NATIVE_CONNECTION(userdata); uint32_t idx; - uint32_t maxlength, tlength, prebuf, minreq, fragsize; + pa_buffer_attr a; pa_tagstruct *reply; pa_native_connection_assert_ref(c); pa_assert(t); + memset(&a, 0, sizeof(a)); + if (pa_tagstruct_getu32(t, &idx) < 0) { protocol_error(c); return; @@ -3458,10 +3548,10 @@ static void command_set_stream_buffer_attr(pa_pdispatch *pd, uint32_t command, u if (pa_tagstruct_get( t, - PA_TAG_U32, &maxlength, - PA_TAG_U32, &tlength, - PA_TAG_U32, &prebuf, - PA_TAG_U32, &minreq, + PA_TAG_U32, &a.maxlength, + PA_TAG_U32, &a.tlength, + PA_TAG_U32, &a.prebuf, + PA_TAG_U32, &a.minreq, PA_TAG_INVALID) < 0 || (c->version >= 13 && pa_tagstruct_get_boolean(t, &adjust_latency) < 0) || (c->version >= 14 && pa_tagstruct_get_boolean(t, &early_requests) < 0) || @@ -3470,21 +3560,21 @@ static void command_set_stream_buffer_attr(pa_pdispatch *pd, uint32_t command, u return; } - fix_playback_buffer_attr_pre(s, adjust_latency, early_requests, &maxlength, &tlength, &prebuf, &minreq); - pa_memblockq_set_maxlength(s->memblockq, maxlength); - pa_memblockq_set_tlength(s->memblockq, tlength); - pa_memblockq_set_prebuf(s->memblockq, prebuf); - pa_memblockq_set_minreq(s->memblockq, minreq); - fix_playback_buffer_attr_post(s, &maxlength, &tlength, &prebuf, &minreq); + s->adjust_latency = adjust_latency; + s->early_requests = early_requests; + s->buffer_attr = a; + + fix_playback_buffer_attr(s); + pa_assert_se(pa_asyncmsgq_send(s->sink_input->sink->asyncmsgq, PA_MSGOBJECT(s->sink_input), SINK_INPUT_MESSAGE_UPDATE_BUFFER_ATTR, NULL, 0, NULL) == 0); reply = reply_new(tag); - pa_tagstruct_putu32(reply, maxlength); - pa_tagstruct_putu32(reply, tlength); - pa_tagstruct_putu32(reply, prebuf); - pa_tagstruct_putu32(reply, minreq); + pa_tagstruct_putu32(reply, s->buffer_attr.maxlength); + pa_tagstruct_putu32(reply, s->buffer_attr.tlength); + pa_tagstruct_putu32(reply, s->buffer_attr.prebuf); + pa_tagstruct_putu32(reply, s->buffer_attr.minreq); if (c->version >= 13) - pa_tagstruct_put_usec(reply, s->sink_latency); + pa_tagstruct_put_usec(reply, s->configured_sink_latency); } else { record_stream *s; @@ -3496,8 +3586,8 @@ static void command_set_stream_buffer_attr(pa_pdispatch *pd, uint32_t command, u if (pa_tagstruct_get( t, - PA_TAG_U32, &maxlength, - PA_TAG_U32, &fragsize, + PA_TAG_U32, &a.maxlength, + PA_TAG_U32, &a.fragsize, PA_TAG_INVALID) < 0 || (c->version >= 13 && pa_tagstruct_get_boolean(t, &adjust_latency) < 0) || (c->version >= 14 && pa_tagstruct_get_boolean(t, &early_requests) < 0) || @@ -3506,16 +3596,21 @@ static void command_set_stream_buffer_attr(pa_pdispatch *pd, uint32_t command, u return; } - fix_record_buffer_attr_pre(s, adjust_latency, early_requests, &maxlength, &fragsize); - pa_memblockq_set_maxlength(s->memblockq, maxlength); - fix_record_buffer_attr_post(s, &maxlength, &fragsize); + s->adjust_latency = adjust_latency; + s->early_requests = early_requests; + s->buffer_attr = a; + + fix_record_buffer_attr_pre(s); + pa_memblockq_set_maxlength(s->memblockq, s->buffer_attr.maxlength); + pa_memblockq_get_attr(s->memblockq, &s->buffer_attr); + fix_record_buffer_attr_post(s); reply = reply_new(tag); - pa_tagstruct_putu32(reply, maxlength); - pa_tagstruct_putu32(reply, fragsize); + pa_tagstruct_putu32(reply, s->buffer_attr.maxlength); + pa_tagstruct_putu32(reply, s->buffer_attr.fragsize); if (c->version >= 13) - pa_tagstruct_put_usec(reply, s->source_latency); + pa_tagstruct_put_usec(reply, s->configured_source_latency); } pa_pstream_send_tagstruct(c->pstream, reply); @@ -4119,7 +4214,7 @@ static void command_set_card_profile(pa_pdispatch *pd, uint32_t command, uint32_ CHECK_VALIDITY(c->pstream, card, tag, PA_ERR_NOENTITY); - if (pa_card_set_profile(card, profile) < 0) { + if (pa_card_set_profile(card, profile, TRUE) < 0) { pa_pstream_send_error(c->pstream, tag, PA_ERR_INVALID); return; } @@ -4151,7 +4246,7 @@ static void pstream_memblock_callback(pa_pstream *p, uint32_t channel, int64_t o pa_native_connection_assert_ref(c); if (!(stream = OUTPUT_STREAM(pa_idxset_get_by_index(c->output_streams, channel)))) { - pa_log("client sent block for invalid stream."); + pa_log_debug("Client sent block for invalid stream."); /* Ignoring */ return; } diff --git a/src/pulsecore/refcnt.h b/src/pulsecore/refcnt.h index 1e98832..782436b 100644 --- a/src/pulsecore/refcnt.h +++ b/src/pulsecore/refcnt.h @@ -23,23 +23,59 @@ ***/ #include <pulsecore/atomic.h> +#include <pulsecore/macro.h> +#include <pulsecore/log.h> + +/* #define DEBUG_REF */ #define PA_REFCNT_DECLARE \ pa_atomic_t _ref -#define PA_REFCNT_INIT(p) \ - pa_atomic_store(&(p)->_ref, 1) +#define PA_REFCNT_VALUE(p) \ + pa_atomic_load(&(p)->_ref) #define PA_REFCNT_INIT_ZERO(p) \ pa_atomic_store(&(p)->_ref, 0) +#ifndef DEBUG_REF + +#define PA_REFCNT_INIT(p) \ + pa_atomic_store(&(p)->_ref, 1) + #define PA_REFCNT_INC(p) \ pa_atomic_inc(&(p)->_ref) #define PA_REFCNT_DEC(p) \ (pa_atomic_dec(&(p)->_ref)-1) -#define PA_REFCNT_VALUE(p) \ - pa_atomic_load(&(p)->_ref) +#else + +/* If you need to debug ref counting problems define DEBUG_REF and + * set $PULSE_LOG_BACKTRACE=5 or suchlike in the shell when running + * PA */ + +#define PA_REFCNT_INIT(p) \ + do { \ + pa_atomic_store(&(p)->_ref, 1); \ + pa_log("REF: Init %p", p); \ + } while (FALSE) + +#define PA_REFCNT_INC(p) \ + do { \ + pa_atomic_inc(&(p)->_ref); \ + pa_log("REF: Inc %p", p); \ + } while (FALSE) \ + +#define PA_REFCNT_DEC(p) \ + ({ \ + int _j = (pa_atomic_dec(&(p)->_ref)-1); \ + if (_j <= 0) \ + pa_log("REF: Done %p", p); \ + else \ + pa_log("REF: Dec %p", p); \ + _j; \ + }) + +#endif #endif diff --git a/src/pulsecore/rtclock.c b/src/pulsecore/rtclock.c index 56ab2ef..65c826a 100644 --- a/src/pulsecore/rtclock.c +++ b/src/pulsecore/rtclock.c @@ -95,6 +95,8 @@ pa_bool_t pa_rtclock_hrtimer(void) { #endif } +#define TIMER_SLACK_NS (int) ((500 * PA_NSEC_PER_USEC)) + void pa_rtclock_hrtimer_enable(void) { #ifdef PR_SET_TIMERSLACK int slack_ns; @@ -104,15 +106,17 @@ void pa_rtclock_hrtimer_enable(void) { return; } - pa_log_debug("Timer slack set to %i us.", slack_ns/1000); + pa_log_debug("Timer slack is set to %i us.", (int) (slack_ns/PA_NSEC_PER_USEC)); - slack_ns = 500000000; + if (slack_ns > TIMER_SLACK_NS) { + slack_ns = TIMER_SLACK_NS; - pa_log_debug("Setting timer slack to %i us.", slack_ns/1000); + pa_log_debug("Setting timer slack to %i us.", (int) (slack_ns/PA_NSEC_PER_USEC)); - if (prctl(PR_SET_TIMERSLACK, slack_ns, 0, 0, 0) < 0) { - pa_log_warn("PR_SET_TIMERSLACK failed: %s", pa_cstrerror(errno)); - return; + if (prctl(PR_SET_TIMERSLACK, slack_ns, 0, 0, 0) < 0) { + pa_log_warn("PR_SET_TIMERSLACK failed: %s", pa_cstrerror(errno)); + return; + } } #endif diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c index 53e727b..0229918 100644 --- a/src/pulsecore/sink-input.c +++ b/src/pulsecore/sink-input.c @@ -117,7 +117,8 @@ static void reset_callbacks(pa_sink_input *i) { i->attach = NULL; i->detach = NULL; i->suspend = NULL; - i->moved = NULL; + i->suspend_within_thread = NULL; + i->moving = NULL; i->kill = NULL; i->get_latency = NULL; i->state_change = NULL; @@ -175,16 +176,8 @@ int pa_sink_input_new( pa_return_val_if_fail(pa_channel_map_compatible(&data->channel_map, &data->sample_spec), -PA_ERR_INVALID); if (!data->volume_is_set) { - - if (data->sink->flags & PA_SINK_FLAT_VOLUME) { - data->volume = *pa_sink_get_volume(data->sink, FALSE); - pa_cvolume_remap(&data->volume, &data->sink->channel_map, &data->channel_map); - data->volume_is_absolute = TRUE; - } else { - pa_cvolume_reset(&data->volume, data->sample_spec.channels); - data->volume_is_absolute = FALSE; - } - + pa_cvolume_reset(&data->volume, data->sample_spec.channels); + data->volume_is_absolute = FALSE; data->save_volume = FALSE; } @@ -278,15 +271,15 @@ int pa_sink_input_new( /* When the 'absolute' bool is not set then we'll treat the volume * as relative to the sink volume even in flat volume mode */ - pa_cvolume t = *pa_sink_get_volume(data->sink, FALSE); - pa_cvolume_remap(&t, &data->sink->channel_map, &data->channel_map); - - pa_sw_cvolume_multiply(&i->virtual_volume, &data->volume, &t); + pa_cvolume v = data->sink->reference_volume; + pa_cvolume_remap(&v, &data->sink->channel_map, &data->channel_map); + pa_sw_cvolume_multiply(&i->virtual_volume, &data->volume, &v); } else i->virtual_volume = data->volume; i->volume_factor = data->volume_factor; pa_cvolume_init(&i->soft_volume); + memset(i->relative_volume, 0, sizeof(i->relative_volume)); i->save_volume = data->save_volume; i->save_sink = data->save_sink; i->save_muted = data->save_muted; @@ -333,8 +326,8 @@ int pa_sink_input_new( 0, &i->sink->silence); - pa_assert_se(pa_idxset_put(core->sink_inputs, pa_sink_input_ref(i), &i->index) == 0); - pa_assert_se(pa_idxset_put(i->sink->inputs, i, NULL) == 0); + pa_assert_se(pa_idxset_put(core->sink_inputs, i, &i->index) == 0); + pa_assert_se(pa_idxset_put(i->sink->inputs, pa_sink_input_ref(i), NULL) == 0); if (i->client) pa_assert_se(pa_idxset_put(i->client->sink_inputs, i, NULL) >= 0); @@ -449,7 +442,7 @@ void pa_sink_input_unlink(pa_sink_input *i) { if (i->sink->flags & PA_SINK_FLAT_VOLUME) { pa_cvolume new_volume; pa_sink_update_flat_volume(i->sink, &new_volume); - pa_sink_set_volume(i->sink, &new_volume, FALSE, FALSE); + pa_sink_set_volume(i->sink, &new_volume, FALSE, FALSE, FALSE); } if (i->sink->asyncmsgq) @@ -527,9 +520,9 @@ void pa_sink_input_put(pa_sink_input *i) { if (i->sink->flags & PA_SINK_FLAT_VOLUME) { pa_cvolume new_volume; pa_sink_update_flat_volume(i->sink, &new_volume); - pa_sink_set_volume(i->sink, &new_volume, FALSE, FALSE); + pa_sink_set_volume(i->sink, &new_volume, FALSE, FALSE, FALSE); } else - pa_sw_cvolume_multiply(&i->soft_volume, &i->virtual_volume, &i->volume_factor); + pa_sink_input_set_relative_volume(i, &i->virtual_volume); i->thread_info.soft_volume = i->soft_volume; i->thread_info.muted = i->muted; @@ -631,7 +624,7 @@ void pa_sink_input_peek(pa_sink_input *i, size_t slength /* in sink frames */, p * data, so let's just hand out silence */ pa_atomic_store(&i->thread_info.drained, 1); - pa_memblockq_seek(i->thread_info.render_memblockq, (int64_t) slength, PA_SEEK_RELATIVE); + pa_memblockq_seek(i->thread_info.render_memblockq, (int64_t) slength, PA_SEEK_RELATIVE, TRUE); i->thread_info.playing_for = 0; if (i->thread_info.underrun_for != (uint64_t) -1) i->thread_info.underrun_for += ilength; @@ -776,7 +769,7 @@ void pa_sink_input_process_rewind(pa_sink_input *i, size_t nbytes /* in sink sam if (amount > 0) /* Ok, now update the write pointer */ - pa_memblockq_seek(i->thread_info.render_memblockq, - ((int64_t) amount), PA_SEEK_RELATIVE); + pa_memblockq_seek(i->thread_info.render_memblockq, - ((int64_t) amount), PA_SEEK_RELATIVE, TRUE); if (i->thread_info.rewrite_flush) pa_memblockq_silence(i->thread_info.render_memblockq); @@ -819,26 +812,15 @@ void pa_sink_input_update_max_request(pa_sink_input *i, size_t nbytes /* in the } /* Called from thread context */ -static pa_usec_t fixup_latency(pa_sink *s, pa_usec_t usec) { - pa_sink_assert_ref(s); - - if (usec == (pa_usec_t) -1) - return usec; - - if (s->thread_info.max_latency > 0 && usec > s->thread_info.max_latency) - usec = s->thread_info.max_latency; - - if (s->thread_info.min_latency > 0 && usec < s->thread_info.min_latency) - usec = s->thread_info.min_latency; - - return usec; -} - -/* Called from thread context */ pa_usec_t pa_sink_input_set_requested_latency_within_thread(pa_sink_input *i, pa_usec_t usec) { pa_sink_input_assert_ref(i); - usec = fixup_latency(i->sink, usec); + if (!(i->sink->flags & PA_SINK_DYNAMIC_LATENCY)) + 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); + i->thread_info.requested_sink_latency = usec; pa_sink_invalidate_requested_latency(i->sink); @@ -849,41 +831,62 @@ pa_usec_t pa_sink_input_set_requested_latency_within_thread(pa_sink_input *i, pa pa_usec_t pa_sink_input_set_requested_latency(pa_sink_input *i, pa_usec_t usec) { pa_sink_input_assert_ref(i); - if (PA_SINK_INPUT_IS_LINKED(i->state)) + if (PA_SINK_INPUT_IS_LINKED(i->state) && i->sink) { pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_REQUESTED_LATENCY, &usec, 0, NULL) == 0); - else - /* If this sink input is not realized yet, we have to touch - * the thread info data directly */ + return usec; + } - i->thread_info.requested_sink_latency = usec; + /* If this sink input is not realized yet or we are being moved, + * we have to touch the thread info data directly */ + + if (i->sink) { + if (!(i->sink->flags & PA_SINK_DYNAMIC_LATENCY)) + usec = i->sink->fixed_latency; + + if (usec != (pa_usec_t) -1) { + pa_usec_t min_latency, max_latency; + pa_sink_get_latency_range(i->sink, &min_latency, &max_latency); + usec = PA_CLAMP(usec, min_latency, max_latency); + } + } + + i->thread_info.requested_sink_latency = usec; return usec; } /* Called from main context */ pa_usec_t pa_sink_input_get_requested_latency(pa_sink_input *i) { - pa_usec_t usec = 0; - pa_sink_input_assert_ref(i); - if (PA_SINK_INPUT_IS_LINKED(i->state)) + if (PA_SINK_INPUT_IS_LINKED(i->state) && i->sink) { + pa_usec_t usec = 0; pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_GET_REQUESTED_LATENCY, &usec, 0, NULL) == 0); - else - /* If this sink input is not realized yet, we have to touch - * the thread info data directly */ - usec = i->thread_info.requested_sink_latency; + return usec; + } - return usec; + /* If this sink input is not realized yet or we are being moved, + * we have to touch the thread info data directly */ + + return i->thread_info.requested_sink_latency; } /* Called from main context */ -void pa_sink_input_set_volume(pa_sink_input *i, const pa_cvolume *volume, pa_bool_t save) { +void pa_sink_input_set_volume(pa_sink_input *i, const pa_cvolume *volume, pa_bool_t save, pa_bool_t absolute) { + pa_cvolume v; + pa_sink_input_assert_ref(i); pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); pa_assert(volume); pa_assert(pa_cvolume_valid(volume)); pa_assert(pa_cvolume_compatible(volume, &i->sample_spec)); + if ((i->sink->flags & PA_SINK_FLAT_VOLUME) && !absolute) { + v = i->sink->reference_volume; + pa_cvolume_remap(&v, &i->sink->channel_map, &i->channel_map); + volume = pa_sw_cvolume_multiply(&v, &v, volume); + } + if (pa_cvolume_equal(volume, &i->virtual_volume)) return; @@ -897,17 +900,18 @@ void pa_sink_input_set_volume(pa_sink_input *i, const pa_cvolume *volume, pa_boo * volumes and update the flat volume of the sink */ pa_sink_update_flat_volume(i->sink, &new_volume); - pa_sink_set_volume(i->sink, &new_volume, FALSE, TRUE); + pa_sink_set_volume(i->sink, &new_volume, FALSE, TRUE, FALSE); } else { /* OK, we are in normal volume mode. The volume only affects * ourselves */ - pa_sw_cvolume_multiply(&i->soft_volume, volume, &i->volume_factor); + pa_sink_input_set_relative_volume(i, volume); /* Hooks have the ability to play games with i->soft_volume */ pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_SET_VOLUME], i); + /* Copy the new soft_volume to the thread_info struct */ pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_SOFT_VOLUME, NULL, 0, NULL) == 0); } @@ -916,11 +920,67 @@ void pa_sink_input_set_volume(pa_sink_input *i, const pa_cvolume *volume, pa_boo } /* Called from main context */ -const pa_cvolume *pa_sink_input_get_volume(pa_sink_input *i) { +pa_cvolume *pa_sink_input_get_volume(pa_sink_input *i, pa_cvolume *volume, pa_bool_t absolute) { pa_sink_input_assert_ref(i); pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); - return &i->virtual_volume; + if ((i->sink->flags & PA_SINK_FLAT_VOLUME) && !absolute) { + pa_cvolume v = i->sink->reference_volume; + pa_cvolume_remap(&v, &i->sink->channel_map, &i->channel_map); + pa_sw_cvolume_divide(volume, &i->virtual_volume, &v); + } else + *volume = i->virtual_volume; + + return volume; +} + +/* Called from main context */ +pa_cvolume *pa_sink_input_get_relative_volume(pa_sink_input *i, pa_cvolume *v) { + unsigned c; + + pa_sink_input_assert_ref(i); + pa_assert(v); + pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); + + /* This always returns the relative volume. Converts the float + * version into a pa_cvolume */ + + v->channels = i->sample_spec.channels; + + for (c = 0; c < v->channels; c++) + v->values[c] = pa_sw_volume_from_linear(i->relative_volume[c]); + + return v; +} + +/* Called from main context */ +void pa_sink_input_set_relative_volume(pa_sink_input *i, const pa_cvolume *v) { + unsigned c; + pa_cvolume _v; + + pa_sink_input_assert_ref(i); + pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); + pa_assert(!v || pa_cvolume_compatible(v, &i->sample_spec)); + + if (!v) + v = pa_cvolume_reset(&_v, i->sample_spec.channels); + + /* This basically calculates: + * + * i->relative_volume := v + * i->soft_volume := i->relative_volume * i->volume_factor */ + + i->soft_volume.channels = i->sample_spec.channels; + + for (c = 0; c < i->sample_spec.channels; c++) { + i->relative_volume[c] = pa_sw_volume_to_linear(v->values[c]); + + i->soft_volume.values[c] = pa_sw_volume_from_linear( + i->relative_volume[c] * + pa_sw_volume_to_linear(i->volume_factor.values[c])); + } + + /* We don't copy the data to the thread_info data. That's left for someone else to do */ } /* Called from main context */ @@ -1090,14 +1150,16 @@ int pa_sink_input_start_move(pa_sink_input *i) { if (i->sink->flags & PA_SINK_FLAT_VOLUME) { pa_cvolume new_volume; - /* Make the absolute volume relative */ - i->virtual_volume = i->soft_volume; - i->soft_volume = i->volume_factor; + /* Make the virtual volume relative */ + pa_sink_input_get_relative_volume(i, &i->virtual_volume); + + /* And reset the the relative volume */ + pa_sink_input_set_relative_volume(i, NULL); /* We might need to update the sink's volume if we are in flat * volume mode. */ pa_sink_update_flat_volume(i->sink, &new_volume); - pa_sink_set_volume(i->sink, &new_volume, FALSE, FALSE); + pa_sink_set_volume(i->sink, &new_volume, FALSE, FALSE, FALSE); } pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_START_MOVE, i, 0, NULL) == 0); @@ -1105,6 +1167,8 @@ int pa_sink_input_start_move(pa_sink_input *i) { pa_sink_update_status(i->sink); i->sink = NULL; + pa_sink_input_unref(i); + return 0; } @@ -1147,9 +1211,12 @@ int pa_sink_input_finish_move(pa_sink_input *i, pa_sink *dest, pa_bool_t save) { } else new_resampler = NULL; + if (i->moving) + i->moving(i, dest); + i->sink = dest; i->save_sink = save; - pa_idxset_put(dest->inputs, i, NULL); + pa_idxset_put(dest->inputs, pa_sink_input_ref(i), NULL); if (pa_sink_input_get_state(i) == PA_SINK_INPUT_CORKED) i->sink->n_corked++; @@ -1173,20 +1240,19 @@ int pa_sink_input_finish_move(pa_sink_input *i, pa_sink *dest, pa_bool_t save) { 0, &i->sink->silence); } - pa_sink_update_status(dest); if (i->sink->flags & PA_SINK_FLAT_VOLUME) { pa_cvolume new_volume; /* Make relative volume absolute again */ - pa_cvolume t = dest->virtual_volume; + pa_cvolume t = dest->reference_volume; pa_cvolume_remap(&t, &dest->channel_map, &i->channel_map); pa_sw_cvolume_multiply(&i->virtual_volume, &i->virtual_volume, &t); /* We might need to update the sink's volume if we are in flat volume mode. */ pa_sink_update_flat_volume(i->sink, &new_volume); - pa_sink_set_volume(i->sink, &new_volume, FALSE, FALSE); + pa_sink_set_volume(i->sink, &new_volume, FALSE, FALSE, FALSE); } pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_FINISH_MOVE, i, 0, NULL) == 0); @@ -1194,9 +1260,6 @@ int pa_sink_input_finish_move(pa_sink_input *i, pa_sink *dest, pa_bool_t save) { pa_log_debug("Successfully moved sink input %i to %s.", i->index, dest->name); /* Notify everyone */ - if (i->moved) - i->moved(i); - pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_FINISH], i); pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index); @@ -1218,11 +1281,19 @@ int pa_sink_input_move_to(pa_sink_input *i, pa_sink *dest, pa_bool_t save) { if (!pa_sink_input_may_move_to(i, dest)) return -PA_ERR_NOTSUPPORTED; - if ((r = pa_sink_input_start_move(i)) < 0) + pa_sink_input_ref(i); + + if ((r = pa_sink_input_start_move(i)) < 0) { + pa_sink_input_unref(i); return r; + } - if ((r = pa_sink_input_finish_move(i, dest, save)) < 0) + if ((r = pa_sink_input_finish_move(i, dest, save)) < 0) { + pa_sink_input_unref(i); return r; + } + + pa_sink_input_unref(i); return 0; } @@ -1291,12 +1362,9 @@ int pa_sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t case PA_SINK_INPUT_MESSAGE_GET_LATENCY: { pa_usec_t *r = userdata; - pa_usec_t sink_usec = 0; r[0] += pa_bytes_to_usec(pa_memblockq_get_length(i->thread_info.render_memblockq), &i->sink->sample_spec); - - if (i->sink->parent.process_msg(PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_GET_LATENCY, &sink_usec, 0, NULL) >= 0) - r[1] += sink_usec; + r[1] += pa_sink_get_latency_within_thread(i->sink); return 0; } diff --git a/src/pulsecore/sink-input.h b/src/pulsecore/sink-input.h index e380168..98144d4 100644 --- a/src/pulsecore/sink-input.h +++ b/src/pulsecore/sink-input.h @@ -91,7 +91,11 @@ struct pa_sink_input { pa_sink_input *sync_prev, *sync_next; - pa_cvolume virtual_volume, soft_volume, volume_factor; + /* Also see http://pulseaudio.org/wiki/InternalVolumes */ + pa_cvolume virtual_volume; /* The volume clients are informed about */ + pa_cvolume volume_factor; /* An internally used volume factor that can be used by modules to apply effects and suchlike without having that visible to the outside */ + double relative_volume[PA_CHANNELS_MAX]; /* The calculated volume relative to the sink volume as linear factors. */ + pa_cvolume soft_volume; /* The internal software volume we apply to all PCM data while it passes through. Usually calculated as relative_volume * volume_factor */ pa_bool_t muted:1; /* if TRUE then the source we are connected to and/or the volume @@ -121,7 +125,7 @@ struct pa_sink_input { * changes. Called from IO context. */ void (*update_max_rewind) (pa_sink_input *i, size_t nbytes); /* may be NULL */ - /* Called whenever the maxiumum request size of the sink + /* Called whenever the maximum request size of the sink * changes. Called from IO context. */ void (*update_max_request) (pa_sink_input *i, size_t nbytes); /* may be NULL */ @@ -144,13 +148,19 @@ struct pa_sink_input { * disconnected from its sink. Called from IO thread context */ void (*detach) (pa_sink_input *i); /* may be NULL */ - /* If non-NULL called whenever the the sink this input is attached + /* If non-NULL called whenever the sink this input is attached * to suspends or resumes. Called from main context */ void (*suspend) (pa_sink_input *i, pa_bool_t b); /* may be NULL */ - /* If non-NULL called whenever the the sink this input is attached - * to changes. Called from main context */ - void (*moved) (pa_sink_input *i); /* may be NULL */ + /* If non-NULL called whenever the sink this input is attached + * to suspends or resumes. Called from IO context */ + void (*suspend_within_thread) (pa_sink_input *i, pa_bool_t b); /* may be NULL */ + + /* If non-NULL called whenever the sink input is moved to a new + * sink. Called from main context after the sink input has been + * detached from the old sink and before it has been attached to + * the new sink. */ + void (*moving) (pa_sink_input *i, pa_sink *dest); /* may be NULL */ /* Supposed to unlink and destroy this stream. Called from main * context. */ @@ -300,10 +310,14 @@ void pa_sink_input_kill(pa_sink_input*i); pa_usec_t pa_sink_input_get_latency(pa_sink_input *i, pa_usec_t *sink_latency); -void pa_sink_input_set_volume(pa_sink_input *i, const pa_cvolume *volume, pa_bool_t save); -const pa_cvolume *pa_sink_input_get_volume(pa_sink_input *i); +void pa_sink_input_set_volume(pa_sink_input *i, const pa_cvolume *volume, pa_bool_t save, pa_bool_t absolute); +pa_cvolume *pa_sink_input_get_volume(pa_sink_input *i, pa_cvolume *volume, pa_bool_t absolute); + +pa_cvolume *pa_sink_input_get_relative_volume(pa_sink_input *i, pa_cvolume *v); + void pa_sink_input_set_mute(pa_sink_input *i, pa_bool_t mute, pa_bool_t save); pa_bool_t pa_sink_input_get_mute(pa_sink_input *i); + void pa_sink_input_update_proplist(pa_sink_input *i, pa_update_mode_t mode, pa_proplist *p); pa_resample_method_t pa_sink_input_get_resample_method(pa_sink_input *i); @@ -342,4 +356,7 @@ pa_bool_t pa_sink_input_safe_to_remove(pa_sink_input *i); pa_memchunk* pa_sink_input_get_silence(pa_sink_input *i, pa_memchunk *ret); +/* To be used by sink.c only */ +void pa_sink_input_set_relative_volume(pa_sink_input *i, const pa_cvolume *v); + #endif diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c index ff8700c..30fa557 100644 --- a/src/pulsecore/sink.c +++ b/src/pulsecore/sink.c @@ -48,7 +48,9 @@ #define MAX_MIX_CHANNELS 32 #define MIX_BUFFER_LENGTH (PA_PAGE_SIZE) -#define DEFAULT_MIN_LATENCY (4*PA_USEC_PER_MSEC) +#define ABSOLUTE_MIN_LATENCY (500) +#define ABSOLUTE_MAX_LATENCY (10*PA_USEC_PER_SEC) +#define DEFAULT_FIXED_LATENCY (250*PA_USEC_PER_MSEC) static PA_DEFINE_CHECK_TYPE(pa_sink, pa_msgobject); @@ -200,13 +202,15 @@ pa_sink* pa_sink_new( s->inputs = pa_idxset_new(NULL, NULL); s->n_corked = 0; - s->virtual_volume = data->volume; + s->reference_volume = s->virtual_volume = data->volume; pa_cvolume_reset(&s->soft_volume, s->sample_spec.channels); s->base_volume = PA_VOLUME_NORM; s->n_volume_steps = PA_VOLUME_NORM+1; s->muted = data->muted; s->refresh_volume = s->refresh_muted = FALSE; + s->fixed_latency = flags & PA_SINK_DYNAMIC_LATENCY ? 0 : DEFAULT_FIXED_LATENCY; + reset_callbacks(s); s->userdata = NULL; @@ -230,8 +234,8 @@ pa_sink* pa_sink_new( s->thread_info.max_request = 0; s->thread_info.requested_latency_valid = FALSE; s->thread_info.requested_latency = 0; - s->thread_info.min_latency = DEFAULT_MIN_LATENCY; - s->thread_info.max_latency = 0; + s->thread_info.min_latency = ABSOLUTE_MIN_LATENCY; + s->thread_info.max_latency = ABSOLUTE_MAX_LATENCY; pa_assert_se(pa_idxset_put(core->sinks, s, &s->index) >= 0); @@ -259,7 +263,7 @@ 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, PA_SOURCE_LATENCY); + s->monitor_source = pa_source_new(core, &source_data, 0); pa_source_new_data_done(&source_data); @@ -343,16 +347,16 @@ void pa_sink_put(pa_sink* s) { /* The following fields must be initialized properly when calling _put() */ pa_assert(s->asyncmsgq); pa_assert(s->rtpoll); - pa_assert(!s->thread_info.min_latency || !s->thread_info.max_latency || - s->thread_info.min_latency <= s->thread_info.max_latency); + pa_assert(s->thread_info.min_latency <= s->thread_info.max_latency); if (!(s->flags & PA_SINK_HW_VOLUME_CTRL)) { s->flags |= PA_SINK_DECIBEL_VOLUME; - - s->thread_info.soft_volume = s->soft_volume; - s->thread_info.soft_muted = s->muted; + s->base_volume = PA_VOLUME_NORM; } + 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; @@ -360,6 +364,17 @@ void pa_sink_put(pa_sink* s) { 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; + + 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_se(sink_set_state(s, PA_SINK_IDLE) == 0); pa_source_put(s->monitor_source); @@ -509,8 +524,12 @@ pa_queue *pa_sink_move_all_start(pa_sink *s) { for (i = PA_SINK_INPUT(pa_idxset_first(s->inputs, &idx)); i; i = n) { n = PA_SINK_INPUT(pa_idxset_next(s->inputs, &idx)); + pa_sink_input_ref(i); + if (pa_sink_input_start_move(i) >= 0) - pa_queue_push(q, pa_sink_input_ref(i)); + pa_queue_push(q, i); + else + pa_sink_input_unref(i); } return q; @@ -952,6 +971,72 @@ pa_usec_t pa_sink_get_latency(pa_sink *s) { return usec; } +/* Called from IO thread */ +pa_usec_t pa_sink_get_latency_within_thread(pa_sink *s) { + pa_usec_t usec = 0; + pa_msgobject *o; + + pa_sink_assert_ref(s); + pa_assert(PA_SINK_IS_LINKED(s->thread_info.state)); + + /* The returned value is supposed to be in the time domain of the sound card! */ + + if (s->thread_info.state == PA_SINK_SUSPENDED) + return 0; + + if (!(s->flags & PA_SINK_LATENCY)) + return 0; + + o = PA_MSGOBJECT(s); + + /* We probably should make this a proper vtable callback instead of going through process_msg() */ + + if (o->process_msg(o, PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0) + return -1; + + return usec; +} + +static void compute_new_soft_volume(pa_sink_input *i, const pa_cvolume *new_volume) { + unsigned c; + + pa_sink_input_assert_ref(i); + pa_assert(new_volume->channels == i->sample_spec.channels); + + /* + * This basically calculates: + * + * i->relative_volume := i->virtual_volume / new_volume + * i->soft_volume := i->relative_volume * i->volume_factor + */ + + /* The new sink volume passed in here must already be remapped to + * the sink input's channel map! */ + + i->soft_volume.channels = i->sample_spec.channels; + + for (c = 0; c < i->sample_spec.channels; c++) + + if (new_volume->values[c] <= PA_VOLUME_MUTED) + /* We leave i->relative_volume untouched */ + i->soft_volume.values[c] = PA_VOLUME_MUTED; + else { + i->relative_volume[c] = + pa_sw_volume_to_linear(i->virtual_volume.values[c]) / + pa_sw_volume_to_linear(new_volume->values[c]); + + i->soft_volume.values[c] = pa_sw_volume_from_linear( + i->relative_volume[c] * + pa_sw_volume_to_linear(i->volume_factor.values[c])); + } + + /* Hooks have the ability to play games with i->soft_volume */ + pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_SET_VOLUME], i); + + /* We don't copy the soft_volume to the thread_info data + * here. That must be done by the caller */ +} + /* Called from main thread */ void pa_sink_update_flat_volume(pa_sink *s, pa_cvolume *new_volume) { pa_sink_input *i; @@ -962,16 +1047,16 @@ void pa_sink_update_flat_volume(pa_sink *s, pa_cvolume *new_volume) { pa_assert(PA_SINK_IS_LINKED(s->state)); pa_assert(s->flags & PA_SINK_FLAT_VOLUME); - /* This is called whenever a sink input volume changes and we - * might need to fix up the sink volume accordingly. Please note - * that we don't actually update the sinks volume here, we only - * return how it needs to be updated. The caller should then call - * pa_sink_set_flat_volume().*/ + /* This is called whenever a sink input volume changes or a sink + * input is added/removed and we might need to fix up the sink + * volume accordingly. Please note that we don't actually update + * the sinks volume here, we only return how it needs to be + * updated. The caller should then call pa_sink_set_volume().*/ if (pa_idxset_isempty(s->inputs)) { /* In the special case that we have no sink input we leave the * volume unmodified. */ - *new_volume = s->virtual_volume; + *new_volume = s->reference_volume; return; } @@ -998,26 +1083,22 @@ void pa_sink_update_flat_volume(pa_sink *s, pa_cvolume *new_volume) { remapped_new_volume = *new_volume; pa_cvolume_remap(&remapped_new_volume, &s->channel_map, &i->channel_map); - pa_sw_cvolume_divide(&i->soft_volume, &i->virtual_volume, &remapped_new_volume); - pa_sw_cvolume_multiply(&i->soft_volume, &i->soft_volume, &i->volume_factor); + compute_new_soft_volume(i, &remapped_new_volume); - /* Hooks have the ability to play games with i->soft_volume */ - pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_INPUT_SET_VOLUME], i); - - /* We don't issue PA_SINK_INPUT_MESSAGE_SET_VOLUME because - * we want the update to have atomically with the sink - * volume update, hence we do it within the - * pa_sink_set_flat_volume() call below*/ + /* We don't copy soft_volume to the thread_info data here + * (i.e. issue PA_SINK_INPUT_MESSAGE_SET_VOLUME) because we + * want the update to be atomically with the sink volume + * update, hence we do it within the pa_sink_set_volume() call + * below */ } } /* Called from main thread */ -void pa_sink_propagate_flat_volume(pa_sink *s, const pa_cvolume *old_volume) { +void pa_sink_propagate_flat_volume(pa_sink *s) { pa_sink_input *i; uint32_t idx; pa_sink_assert_ref(s); - pa_assert(old_volume); pa_assert(PA_SINK_IS_LINKED(s->state)); pa_assert(s->flags & PA_SINK_FLAT_VOLUME); @@ -1026,39 +1107,43 @@ void pa_sink_propagate_flat_volume(pa_sink *s, const pa_cvolume *old_volume) { * sink input volumes accordingly */ for (i = PA_SINK_INPUT(pa_idxset_first(s->inputs, &idx)); i; i = PA_SINK_INPUT(pa_idxset_next(s->inputs, &idx))) { - pa_cvolume remapped_old_volume, remapped_new_volume, fixed_volume; + pa_cvolume sink_volume, new_virtual_volume; unsigned c; - remapped_new_volume = s->virtual_volume; - pa_cvolume_remap(&remapped_new_volume, &s->channel_map, &i->channel_map); + /* This basically calculates i->virtual_volume := i->relative_volume * s->virtual_volume */ - remapped_old_volume = *old_volume; - pa_cvolume_remap(&remapped_old_volume, &s->channel_map, &i->channel_map); + sink_volume = s->virtual_volume; + pa_cvolume_remap(&sink_volume, &s->channel_map, &i->channel_map); for (c = 0; c < i->sample_spec.channels; c++) + new_virtual_volume.values[c] = pa_sw_volume_from_linear( + i->relative_volume[c] * + pa_sw_volume_to_linear(sink_volume.values[c])); - if (remapped_old_volume.values[c] == PA_VOLUME_MUTED) - fixed_volume.values[c] = PA_VOLUME_MUTED; - else - fixed_volume.values[c] = (pa_volume_t) - ((uint64_t) i->virtual_volume.values[c] * - (uint64_t) remapped_new_volume.values[c] / - (uint64_t) remapped_old_volume.values[c]); + new_virtual_volume.channels = i->sample_spec.channels; - fixed_volume.channels = i->virtual_volume.channels; + if (!pa_cvolume_equal(&new_virtual_volume, &i->virtual_volume)) { + i->virtual_volume = new_virtual_volume; - if (!pa_cvolume_equal(&fixed_volume, &i->virtual_volume)) { - i->virtual_volume = fixed_volume; + /* Hmm, the soft volume might no longer actually match + * what has been chosen as new virtual volume here, + * especially when the old volume was + * PA_VOLUME_MUTED. Hence let's recalculate the soft + * volumes here. */ + compute_new_soft_volume(i, &sink_volume); /* The virtual volume changed, let's tell people so */ pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index); } } + + /* If the soft_volume of any of the sink inputs got changed, let's + * make sure the thread copies are synced up. */ + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SYNC_VOLUMES, NULL, 0, NULL) == 0); } /* Called from main thread */ -void pa_sink_set_volume(pa_sink *s, const pa_cvolume *volume, pa_bool_t propagate, pa_bool_t sendmsg) { - pa_cvolume old_virtual_volume; +void pa_sink_set_volume(pa_sink *s, const pa_cvolume *volume, pa_bool_t propagate, pa_bool_t sendmsg, pa_bool_t become_reference) { pa_bool_t virtual_volume_changed; pa_sink_assert_ref(s); @@ -1067,19 +1152,21 @@ void pa_sink_set_volume(pa_sink *s, const pa_cvolume *volume, pa_bool_t propagat pa_assert(pa_cvolume_valid(volume)); pa_assert(pa_cvolume_compatible(volume, &s->sample_spec)); - old_virtual_volume = s->virtual_volume; + virtual_volume_changed = !pa_cvolume_equal(volume, &s->virtual_volume); s->virtual_volume = *volume; - virtual_volume_changed = !pa_cvolume_equal(&old_virtual_volume, &s->virtual_volume); + + if (become_reference) + s->reference_volume = s->virtual_volume; /* Propagate this volume change back to the inputs */ if (virtual_volume_changed) if (propagate && (s->flags & PA_SINK_FLAT_VOLUME)) - pa_sink_propagate_flat_volume(s, &old_virtual_volume); + pa_sink_propagate_flat_volume(s); if (s->set_volume) { /* If we have a function set_volume(), then we do not apply a - * soft volume by default. However, set_volume() is apply one - * to s->soft_volume */ + * soft volume by default. However, set_volume() is free to + * apply one to s->soft_volume */ pa_cvolume_reset(&s->soft_volume, s->sample_spec.channels); s->set_volume(s); @@ -1111,7 +1198,7 @@ void pa_sink_set_soft_volume(pa_sink *s, const pa_cvolume *volume) { } /* Called from main thread */ -const pa_cvolume *pa_sink_get_volume(pa_sink *s, pa_bool_t force_refresh) { +const pa_cvolume *pa_sink_get_volume(pa_sink *s, pa_bool_t force_refresh, pa_bool_t reference) { pa_sink_assert_ref(s); if (s->refresh_volume || force_refresh) { @@ -1124,14 +1211,33 @@ const pa_cvolume *pa_sink_get_volume(pa_sink *s, pa_bool_t force_refresh) { if (!pa_cvolume_equal(&old_virtual_volume, &s->virtual_volume)) { + s->reference_volume = s->virtual_volume; + if (s->flags & PA_SINK_FLAT_VOLUME) - pa_sink_propagate_flat_volume(s, &old_virtual_volume); + pa_sink_propagate_flat_volume(s); pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); } } - return &s->virtual_volume; + return reference ? &s->reference_volume : &s->virtual_volume; +} + +/* Called from main thread */ +void pa_sink_volume_changed(pa_sink *s, const pa_cvolume *new_volume) { + pa_sink_assert_ref(s); + + /* The sink implementor may call this if the volume changed to make sure everyone is notified */ + + if (pa_cvolume_equal(&s->virtual_volume, new_volume)) + return; + + s->reference_volume = s->virtual_volume = *new_volume; + + if (s->flags & PA_SINK_FLAT_VOLUME) + pa_sink_propagate_flat_volume(s); + + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); } /* Called from main thread */ @@ -1174,12 +1280,24 @@ pa_bool_t pa_sink_get_mute(pa_sink *s, pa_bool_t force_refresh) { } /* Called from main thread */ -pa_bool_t pa_sink_update_proplist(pa_sink *s, pa_update_mode_t mode, pa_proplist *p) { +void pa_sink_mute_changed(pa_sink *s, pa_bool_t new_muted) { + pa_sink_assert_ref(s); + + /* The sink implementor may call this if the volume changed to make sure everyone is notified */ + if (s->muted == new_muted) + return; + + s->muted = new_muted; + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); +} + +/* Called from main thread */ +pa_bool_t pa_sink_update_proplist(pa_sink *s, pa_update_mode_t mode, pa_proplist *p) { pa_sink_assert_ref(s); - pa_assert(p); - pa_proplist_update(s->proplist, mode, p); + if (p) + pa_proplist_update(s->proplist, mode, p); if (PA_SINK_IS_LINKED(s->state)) { pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_PROPLIST_CHANGED], s); @@ -1508,9 +1626,13 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse pa_sink_request_rewind(s, (size_t) -1); } - if (s->flags & PA_SINK_FLAT_VOLUME) - sync_input_volumes_within_thread(s); + if (!(s->flags & PA_SINK_FLAT_VOLUME)) + return 0; + /* Fall through ... */ + + case PA_SINK_MESSAGE_SYNC_VOLUMES: + sync_input_volumes_within_thread(s); return 0; case PA_SINK_MESSAGE_GET_VOLUME: @@ -1528,7 +1650,11 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse case PA_SINK_MESSAGE_GET_MUTE: return 0; - case PA_SINK_MESSAGE_SET_STATE: + case PA_SINK_MESSAGE_SET_STATE: { + + pa_bool_t suspend_change = + (s->thread_info.state == PA_SINK_SUSPENDED && PA_SINK_IS_OPENED(PA_PTR_TO_UINT(userdata))) || + (PA_SINK_IS_OPENED(s->thread_info.state) && PA_PTR_TO_UINT(userdata) == PA_SINK_SUSPENDED); s->thread_info.state = PA_PTR_TO_UINT(userdata); @@ -1537,7 +1663,17 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse s->thread_info.rewind_requested = FALSE; } + if (suspend_change) { + pa_sink_input *i; + void *state = NULL; + + while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL))) + if (i->suspend_within_thread) + i->suspend_within_thread(i, s->thread_info.state == PA_SINK_SUSPENDED); + } + return 0; + } case PA_SINK_MESSAGE_DETACH: @@ -1565,7 +1701,7 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse case PA_SINK_MESSAGE_SET_LATENCY_RANGE: { pa_usec_t *r = userdata; - pa_sink_update_latency_range(s, r[0], r[1]); + pa_sink_set_latency_range_within_thread(s, r[0], r[1]); return 0; } @@ -1589,6 +1725,16 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse *((size_t*) userdata) = s->thread_info.max_request; return 0; + case PA_SINK_MESSAGE_SET_MAX_REWIND: + + pa_sink_set_max_rewind_within_thread(s, (size_t) offset); + return 0; + + case PA_SINK_MESSAGE_SET_MAX_REQUEST: + + pa_sink_set_max_request_within_thread(s, (size_t) offset); + return 0; + case PA_SINK_MESSAGE_GET_LATENCY: case PA_SINK_MESSAGE_MAX: ; @@ -1696,6 +1842,9 @@ pa_usec_t pa_sink_get_requested_latency_within_thread(pa_sink *s) { pa_sink_assert_ref(s); + if (!(s->flags & PA_SINK_DYNAMIC_LATENCY)) + return PA_CLAMP(s->fixed_latency, s->thread_info.min_latency, s->thread_info.max_latency); + if (s->thread_info.requested_latency_valid) return s->thread_info.requested_latency; @@ -1711,13 +1860,8 @@ pa_usec_t pa_sink_get_requested_latency_within_thread(pa_sink *s) { (result == (pa_usec_t) -1 || result > monitor_latency)) result = monitor_latency; - if (result != (pa_usec_t) -1) { - if (s->thread_info.max_latency > 0 && result > s->thread_info.max_latency) - result = s->thread_info.max_latency; - - if (s->thread_info.min_latency > 0 && result < s->thread_info.min_latency) - result = s->thread_info.min_latency; - } + 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; @@ -1740,7 +1884,7 @@ pa_usec_t pa_sink_get_requested_latency(pa_sink *s) { } /* Called from IO as well as the main thread -- the latter only before the IO thread started up */ -void pa_sink_set_max_rewind(pa_sink *s, size_t max_rewind) { +void pa_sink_set_max_rewind_within_thread(pa_sink *s, size_t max_rewind) { pa_sink_input *i; void *state = NULL; @@ -1757,11 +1901,21 @@ void pa_sink_set_max_rewind(pa_sink *s, size_t max_rewind) { } if (s->monitor_source) - pa_source_set_max_rewind(s->monitor_source, s->thread_info.max_rewind); + pa_source_set_max_rewind_within_thread(s->monitor_source, s->thread_info.max_rewind); +} + +/* Called from main thread */ +void pa_sink_set_max_rewind(pa_sink *s, size_t max_rewind) { + pa_sink_assert_ref(s); + + if (PA_SINK_IS_LINKED(s->state)) + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_MAX_REWIND, NULL, max_rewind, NULL) == 0); + else + pa_sink_set_max_rewind_within_thread(s, max_rewind); } /* Called from IO as well as the main thread -- the latter only before the IO thread started up */ -void pa_sink_set_max_request(pa_sink *s, size_t max_request) { +void pa_sink_set_max_request_within_thread(pa_sink *s, size_t max_request) { void *state = NULL; pa_sink_assert_ref(s); @@ -1779,6 +1933,16 @@ void pa_sink_set_max_request(pa_sink *s, size_t max_request) { } } +/* Called from main thread */ +void pa_sink_set_max_request(pa_sink *s, size_t max_request) { + pa_sink_assert_ref(s); + + if (PA_SINK_IS_LINKED(s->state)) + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_MAX_REQUEST, NULL, max_request, NULL) == 0); + else + pa_sink_set_max_request_within_thread(s, max_request); +} + /* Called from IO thread */ void pa_sink_invalidate_requested_latency(pa_sink *s) { pa_sink_input *i; @@ -1786,14 +1950,20 @@ void pa_sink_invalidate_requested_latency(pa_sink *s) { pa_sink_assert_ref(s); + if (!(s->flags & PA_SINK_DYNAMIC_LATENCY)) + return; + s->thread_info.requested_latency_valid = FALSE; - if (s->update_requested_latency) - s->update_requested_latency(s); + if (PA_SINK_IS_LINKED(s->thread_info.state)) { - while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL))) - if (i->update_sink_requested_latency) - i->update_sink_requested_latency(i); + if (s->update_requested_latency) + s->update_requested_latency(s); + + while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL))) + if (i->update_sink_requested_latency) + i->update_sink_requested_latency(i); + } } /* Called from main thread */ @@ -1801,19 +1971,23 @@ void pa_sink_set_latency_range(pa_sink *s, pa_usec_t min_latency, pa_usec_t max_ pa_sink_assert_ref(s); /* min_latency == 0: no limit - * min_latency == (size_t) -1: default limit * min_latency anything else: specified limit * * Similar for max_latency */ - if (min_latency == (pa_usec_t) -1) - min_latency = DEFAULT_MIN_LATENCY; + if (min_latency < ABSOLUTE_MIN_LATENCY) + min_latency = ABSOLUTE_MIN_LATENCY; + + if (max_latency <= 0 || + max_latency > ABSOLUTE_MAX_LATENCY) + max_latency = ABSOLUTE_MAX_LATENCY; - if (max_latency == (pa_usec_t) -1) - max_latency = min_latency; + pa_assert(min_latency <= max_latency); - pa_assert(!min_latency || !max_latency || - min_latency <= max_latency); + /* Hmm, let's see if someone forgot to set PA_SINK_DYNAMIC_LATENCY here... */ + pa_assert((min_latency == ABSOLUTE_MIN_LATENCY && + max_latency == ABSOLUTE_MAX_LATENCY) || + (s->flags & PA_SINK_DYNAMIC_LATENCY)); if (PA_SINK_IS_LINKED(s->state)) { pa_usec_t r[2]; @@ -1822,15 +1996,8 @@ void pa_sink_set_latency_range(pa_sink *s, pa_usec_t min_latency, pa_usec_t max_ r[1] = max_latency; pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_LATENCY_RANGE, r, 0, NULL) == 0); - } else { - s->thread_info.min_latency = min_latency; - s->thread_info.max_latency = max_latency; - - s->monitor_source->thread_info.min_latency = min_latency; - s->monitor_source->thread_info.max_latency = max_latency; - - s->thread_info.requested_latency_valid = s->monitor_source->thread_info.requested_latency_valid = FALSE; - } + } else + pa_sink_set_latency_range_within_thread(s, min_latency, max_latency); } /* Called from main thread */ @@ -1853,25 +2020,34 @@ void pa_sink_get_latency_range(pa_sink *s, pa_usec_t *min_latency, pa_usec_t *ma } /* Called from IO thread */ -void pa_sink_update_latency_range(pa_sink *s, pa_usec_t min_latency, pa_usec_t max_latency) { - pa_sink_input *i; +void pa_sink_set_latency_range_within_thread(pa_sink *s, pa_usec_t min_latency, pa_usec_t max_latency) { void *state = NULL; pa_sink_assert_ref(s); - pa_assert(!min_latency || !max_latency || - min_latency <= max_latency); + pa_assert(min_latency >= ABSOLUTE_MIN_LATENCY); + pa_assert(max_latency <= ABSOLUTE_MAX_LATENCY); + pa_assert(min_latency <= max_latency); + + /* Hmm, let's see if someone forgot to set PA_SINK_DYNAMIC_LATENCY here... */ + pa_assert((min_latency == ABSOLUTE_MIN_LATENCY && + max_latency == ABSOLUTE_MAX_LATENCY) || + (s->flags & PA_SINK_DYNAMIC_LATENCY)); s->thread_info.min_latency = min_latency; s->thread_info.max_latency = max_latency; - while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL))) - if (i->update_sink_latency_range) - i->update_sink_latency_range(i); + if (PA_SINK_IS_LINKED(s->thread_info.state)) { + pa_sink_input *i; + + while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL))) + if (i->update_sink_latency_range) + i->update_sink_latency_range(i); + } pa_sink_invalidate_requested_latency(s); - pa_source_update_latency_range(s->monitor_source, min_latency, max_latency); + pa_source_set_latency_range_within_thread(s->monitor_source, min_latency, max_latency); } /* Called from main context */ @@ -1921,6 +2097,8 @@ pa_bool_t pa_device_init_icon(pa_proplist *p, pa_bool_t is_sink) { t = "phone"; else if (pa_streq(ff, "portable")) t = "multimedia-player"; + else if (pa_streq(ff, "tv")) + t = "video-display"; } if (!t) diff --git a/src/pulsecore/sink.h b/src/pulsecore/sink.h index 0d33679..352282b 100644 --- a/src/pulsecore/sink.h +++ b/src/pulsecore/sink.h @@ -74,7 +74,10 @@ struct pa_sink { pa_volume_t base_volume; /* shall be constant */ unsigned n_volume_steps; /* shall be constant */ - pa_cvolume virtual_volume, soft_volume; + /* Also see http://pulseaudio.org/wiki/InternalVolumes */ + pa_cvolume virtual_volume; /* The volume clients are informed about */ + pa_cvolume reference_volume; /* The volume taken as refernce base for relative sink input volumes */ + pa_cvolume soft_volume; /* The internal software volume we apply to all PCM data while it passes through */ pa_bool_t muted:1; pa_bool_t refresh_volume:1; @@ -85,6 +88,8 @@ struct pa_sink { pa_memchunk silence; + pa_usec_t fixed_latency; /* for sinks with PA_SINK_DYNAMIC_LATENCY this is 0 */ + /* Called when the main loop requests a state change. Called from * main loop context. If returns -1 the state change will be * inhibited */ @@ -159,6 +164,7 @@ typedef enum pa_sink_message { PA_SINK_MESSAGE_REMOVE_INPUT, PA_SINK_MESSAGE_GET_VOLUME, PA_SINK_MESSAGE_SET_VOLUME, + PA_SINK_MESSAGE_SYNC_VOLUMES, PA_SINK_MESSAGE_GET_MUTE, PA_SINK_MESSAGE_SET_MUTE, PA_SINK_MESSAGE_GET_LATENCY, @@ -172,6 +178,8 @@ typedef enum pa_sink_message { PA_SINK_MESSAGE_GET_LATENCY_RANGE, PA_SINK_MESSAGE_GET_MAX_REWIND, PA_SINK_MESSAGE_GET_MAX_REQUEST, + PA_SINK_MESSAGE_SET_MAX_REWIND, + PA_SINK_MESSAGE_SET_MAX_REQUEST, PA_SINK_MESSAGE_MAX } pa_sink_message_t; @@ -218,12 +226,16 @@ void pa_sink_set_description(pa_sink *s, const char *description); void pa_sink_set_asyncmsgq(pa_sink *s, pa_asyncmsgq *q); void pa_sink_set_rtpoll(pa_sink *s, pa_rtpoll *p); +void pa_sink_set_max_rewind(pa_sink *s, size_t max_rewind); +void pa_sink_set_max_request(pa_sink *s, size_t max_request); void pa_sink_set_latency_range(pa_sink *s, pa_usec_t min_latency, pa_usec_t max_latency); void pa_sink_detach(pa_sink *s); void pa_sink_attach(pa_sink *s); void pa_sink_set_soft_volume(pa_sink *s, const pa_cvolume *volume); +void pa_sink_volume_changed(pa_sink *s, const pa_cvolume *new_volume); +void pa_sink_mute_changed(pa_sink *s, pa_bool_t new_muted); pa_bool_t pa_device_init_description(pa_proplist *p); pa_bool_t pa_device_init_icon(pa_proplist *p, pa_bool_t is_sink); @@ -243,10 +255,11 @@ int pa_sink_suspend(pa_sink *s, pa_bool_t suspend); int pa_sink_suspend_all(pa_core *c, pa_bool_t suspend); void pa_sink_update_flat_volume(pa_sink *s, pa_cvolume *new_volume); -void pa_sink_propagate_flat_volume(pa_sink *s, const pa_cvolume *old_volume); +void pa_sink_propagate_flat_volume(pa_sink *s); + +void pa_sink_set_volume(pa_sink *sink, const pa_cvolume *volume, pa_bool_t propagate, pa_bool_t sendmsg, pa_bool_t become_reference); +const pa_cvolume *pa_sink_get_volume(pa_sink *sink, pa_bool_t force_refresh, pa_bool_t reference); -void pa_sink_set_volume(pa_sink *sink, const pa_cvolume *volume, pa_bool_t propagate, pa_bool_t sendmsg); -const pa_cvolume *pa_sink_get_volume(pa_sink *sink, pa_bool_t force_refresh); void pa_sink_set_mute(pa_sink *sink, pa_bool_t mute); pa_bool_t pa_sink_get_mute(pa_sink *sink, pa_bool_t force_refresh); @@ -278,10 +291,10 @@ void pa_sink_detach_within_thread(pa_sink *s); pa_usec_t pa_sink_get_requested_latency_within_thread(pa_sink *s); -void pa_sink_set_max_rewind(pa_sink *s, size_t max_rewind); -void pa_sink_set_max_request(pa_sink *s, size_t max_request); +void pa_sink_set_max_rewind_within_thread(pa_sink *s, size_t max_rewind); +void pa_sink_set_max_request_within_thread(pa_sink *s, size_t max_request); -void pa_sink_update_latency_range(pa_sink *s, pa_usec_t min_latency, pa_usec_t max_latency); +void pa_sink_set_latency_range_within_thread(pa_sink *s, pa_usec_t min_latency, pa_usec_t max_latency); /*** To be called exclusively by sink input drivers, from IO context */ @@ -289,4 +302,6 @@ void pa_sink_request_rewind(pa_sink*s, size_t nbytes); void pa_sink_invalidate_requested_latency(pa_sink *s); +pa_usec_t pa_sink_get_latency_within_thread(pa_sink *s); + #endif diff --git a/src/pulsecore/socket-server.c b/src/pulsecore/socket-server.c index 6a4405e..e660700 100644 --- a/src/pulsecore/socket-server.c +++ b/src/pulsecore/socket-server.c @@ -467,11 +467,13 @@ char *pa_socket_server_get_address(pa_socket_server *s, char *c, size_t l) { pa_snprintf(c, l, "tcp6:%s:%u", fqdn, (unsigned) ntohs(sa.sin6_port)); } else if (memcmp(&in6addr_loopback, &sa.sin6_addr, sizeof(in6addr_loopback)) == 0) { - char hn[256]; - if (!pa_get_host_name(hn, sizeof(hn))) + char *id; + + if (!(id = pa_machine_id())) return NULL; - pa_snprintf(c, l, "{%s}tcp6:localhost:%u", hn, (unsigned) ntohs(sa.sin6_port)); + pa_snprintf(c, l, "{%s}tcp6:localhost:%u", id, (unsigned) ntohs(sa.sin6_port)); + pa_xfree(id); } else { char ip[INET6_ADDRSTRLEN]; @@ -503,11 +505,13 @@ char *pa_socket_server_get_address(pa_socket_server *s, char *c, size_t l) { pa_snprintf(c, l, "tcp:%s:%u", fqdn, (unsigned) ntohs(sa.sin_port)); } else if (sa.sin_addr.s_addr == INADDR_LOOPBACK) { - char hn[256]; - if (!pa_get_host_name(hn, sizeof(hn))) + char *id; + + if (!(id = pa_machine_id())) return NULL; - pa_snprintf(c, l, "{%s}tcp:localhost:%u", hn, (unsigned) ntohs(sa.sin_port)); + pa_snprintf(c, l, "{%s}tcp:localhost:%u", id, (unsigned) ntohs(sa.sin_port)); + pa_xfree(id); } else { char ip[INET_ADDRSTRLEN]; @@ -523,15 +527,16 @@ char *pa_socket_server_get_address(pa_socket_server *s, char *c, size_t l) { } case SOCKET_SERVER_UNIX: { - char hn[256]; + char *id; if (!s->filename) return NULL; - if (!pa_get_host_name(hn, sizeof(hn))) + if (!(id = pa_machine_id())) return NULL; - pa_snprintf(c, l, "{%s}unix:%s", hn, s->filename); + pa_snprintf(c, l, "{%s}unix:%s", id, s->filename); + pa_xfree(id); return c; } diff --git a/src/pulsecore/source-output.c b/src/pulsecore/source-output.c index 373d563..4ba25ae 100644 --- a/src/pulsecore/source-output.c +++ b/src/pulsecore/source-output.c @@ -87,7 +87,8 @@ static void reset_callbacks(pa_source_output *o) { o->attach = NULL; o->detach = NULL; o->suspend = NULL; - o->moved = NULL; + o->suspend_within_thread = NULL; + o->moving = NULL; o->kill = NULL; o->get_latency = NULL; o->state_change = NULL; @@ -434,11 +435,30 @@ void pa_source_output_push(pa_source_output *o, const pa_memchunk *chunk) { if (pa_memblockq_push(o->thread_info.delay_memblockq, chunk) < 0) { pa_log_debug("Delay queue overflow!"); - pa_memblockq_seek(o->thread_info.delay_memblockq, (int64_t) chunk->length, PA_SEEK_RELATIVE); + pa_memblockq_seek(o->thread_info.delay_memblockq, (int64_t) chunk->length, PA_SEEK_RELATIVE, TRUE); } limit = o->process_rewind ? 0 : o->source->thread_info.max_rewind; + if (limit > 0 && o->source->monitor_of) { + pa_usec_t latency; + size_t n; + + /* Hmm, check the latency for knowing how much of the buffered + * data is actually still unplayed and might hence still + * change. This is suboptimal. Ideally we'd have a call like + * pa_sink_get_changeable_size() or so that tells us how much + * of the queued data is actually still changeable. Hence + * FIXME! */ + + latency = pa_sink_get_latency_within_thread(o->source->monitor_of); + + n = pa_usec_to_bytes(latency, &o->source->sample_spec); + + if (n < limit) + limit = n; + } + /* Implement the delay queue */ while ((length = pa_memblockq_get_length(o->thread_info.delay_memblockq)) > limit) { pa_memchunk qchunk; @@ -516,26 +536,15 @@ void pa_source_output_update_max_rewind(pa_source_output *o, size_t nbytes /* i } /* Called from thread context */ -static pa_usec_t fixup_latency(pa_source *s, pa_usec_t usec) { - pa_source_assert_ref(s); - - if (usec == (pa_usec_t) -1) - return usec; - - if (s->thread_info.max_latency > 0 && usec > s->thread_info.max_latency) - usec = s->thread_info.max_latency; - - if (s->thread_info.min_latency > 0 && usec < s->thread_info.min_latency) - usec = s->thread_info.min_latency; - - return usec; -} - -/* Called from thread context */ pa_usec_t pa_source_output_set_requested_latency_within_thread(pa_source_output *o, pa_usec_t usec) { pa_source_output_assert_ref(o); - usec = fixup_latency(o->source, usec); + if (!(o->source->flags & PA_SOURCE_DYNAMIC_LATENCY)) + usec = o->source->fixed_latency; + + if (usec != (pa_usec_t) -1) + usec = PA_CLAMP(usec, o->source->thread_info.min_latency, o->source->thread_info.max_latency); + o->thread_info.requested_source_latency = usec; pa_source_invalidate_requested_latency(o->source); @@ -546,31 +555,44 @@ pa_usec_t pa_source_output_set_requested_latency_within_thread(pa_source_output pa_usec_t pa_source_output_set_requested_latency(pa_source_output *o, pa_usec_t usec) { pa_source_output_assert_ref(o); - if (PA_SOURCE_OUTPUT_IS_LINKED(o->state)) + if (PA_SOURCE_OUTPUT_IS_LINKED(o->state) && o->source) { pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_SET_REQUESTED_LATENCY, &usec, 0, NULL) == 0); - else - /* If this source output is not realized yet, we have to touch - * the thread info data directly */ + return usec; + } - o->thread_info.requested_source_latency = usec; + /* If this source output is not realized yet or is being moved, we + * have to touch the thread info data directly */ + + if (o->source) { + if (!(o->source->flags & PA_SOURCE_DYNAMIC_LATENCY)) + usec = o->source->fixed_latency; + + if (usec != (pa_usec_t) -1) { + pa_usec_t min_latency, max_latency; + pa_source_get_latency_range(o->source, &min_latency, &max_latency); + usec = PA_CLAMP(usec, min_latency, max_latency); + } + } + + o->thread_info.requested_source_latency = usec; return usec; } /* Called from main context */ pa_usec_t pa_source_output_get_requested_latency(pa_source_output *o) { - pa_usec_t usec = 0; - pa_source_output_assert_ref(o); - if (PA_SOURCE_OUTPUT_IS_LINKED(o->state)) + if (PA_SOURCE_OUTPUT_IS_LINKED(o->state) && o->source) { + pa_usec_t usec = 0; pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_GET_REQUESTED_LATENCY, &usec, 0, NULL) == 0); - else - /* If this source output is not realized yet, we have to touch - * the thread info data directly */ - usec = o->thread_info.requested_source_latency; + return usec; + } - return usec; + /* If this source output is not realized yet or is being moved, we + * have to touch the thread info data directly */ + + return o->thread_info.requested_source_latency; } /* Called from main context */ @@ -707,6 +729,8 @@ int pa_source_output_start_move(pa_source_output *o) { pa_source_update_status(o->source); o->source = NULL; + pa_source_output_unref(o); + return 0; } @@ -749,9 +773,12 @@ int pa_source_output_finish_move(pa_source_output *o, pa_source *dest, pa_bool_t } else new_resampler = NULL; + if (o->moving) + o->moving(o, dest); + o->source = dest; o->save_source = save; - pa_idxset_put(o->source->outputs, o, NULL); + pa_idxset_put(o->source->outputs, pa_source_output_ref(o), NULL); if (pa_source_output_get_state(o) == PA_SOURCE_OUTPUT_CORKED) o->source->n_corked++; @@ -776,14 +803,12 @@ int pa_source_output_finish_move(pa_source_output *o, pa_source *dest, pa_bool_t } pa_source_update_status(dest); + pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_ADD_OUTPUT, o, 0, NULL) == 0); pa_log_debug("Successfully moved source output %i to %s.", o->index, dest->name); /* Notify everyone */ - if (o->moved) - o->moved(o); - pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_FINISH], o); pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index); @@ -805,11 +830,19 @@ int pa_source_output_move_to(pa_source_output *o, pa_source *dest, pa_bool_t sav if (!pa_source_output_may_move_to(o, dest)) return -PA_ERR_NOTSUPPORTED; - if ((r = pa_source_output_start_move(o)) < 0) + pa_source_output_ref(o); + + if ((r = pa_source_output_start_move(o)) < 0) { + pa_source_output_unref(o); return r; + } - if ((r = pa_source_output_finish_move(o, dest, save)) < 0) + if ((r = pa_source_output_finish_move(o, dest, save)) < 0) { + pa_source_output_unref(o); return r; + } + + pa_source_output_unref(o); return 0; } @@ -836,12 +869,9 @@ int pa_source_output_process_msg(pa_msgobject *mo, int code, void *userdata, int case PA_SOURCE_OUTPUT_MESSAGE_GET_LATENCY: { pa_usec_t *r = userdata; - pa_usec_t source_usec = 0; r[0] += pa_bytes_to_usec(pa_memblockq_get_length(o->thread_info.delay_memblockq), &o->source->sample_spec); - - if (o->source->parent.process_msg(PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_GET_LATENCY, &source_usec, 0, NULL) >= 0) - r[1] += source_usec; + r[1] += pa_source_get_latency_within_thread(o->source); return 0; } diff --git a/src/pulsecore/source-output.h b/src/pulsecore/source-output.h index 018ec88..9824e16 100644 --- a/src/pulsecore/source-output.h +++ b/src/pulsecore/source-output.h @@ -116,13 +116,19 @@ struct pa_source_output { * disconnected from its source. Called from IO thread context */ void (*detach) (pa_source_output *o); /* may be NULL */ - /* If non-NULL called whenever the the source this output is attached + /* If non-NULL called whenever the source this output is attached * to suspends or resumes. Called from main context */ void (*suspend) (pa_source_output *o, pa_bool_t b); /* may be NULL */ - /* If non-NULL called whenever the the source this output is attached - * to changes. Called from main context */ - void (*moved) (pa_source_output *o); /* may be NULL */ + /* If non-NULL called whenever the source this output is attached + * to suspends or resumes. Called from IO context */ + void (*suspend_within_thread) (pa_source_output *o, pa_bool_t b); /* may be NULL */ + + /* If non-NULL called whenever the source output is moved to a new + * source. Called from main context after the stream was detached + * from the old source and before it is attached to the new + * source. */ + void (*moving) (pa_source_output *o, pa_source *dest); /* may be NULL */ /* Supposed to unlink and destroy this stream. Called from main * context. */ diff --git a/src/pulsecore/source.c b/src/pulsecore/source.c index cc6dfc4..2190250 100644 --- a/src/pulsecore/source.c +++ b/src/pulsecore/source.c @@ -41,7 +41,9 @@ #include "source.h" -#define DEFAULT_MIN_LATENCY (4*PA_USEC_PER_MSEC) +#define ABSOLUTE_MIN_LATENCY (500) +#define ABSOLUTE_MAX_LATENCY (10*PA_USEC_PER_SEC) +#define DEFAULT_FIXED_LATENCY (250*PA_USEC_PER_MSEC) static PA_DEFINE_CHECK_TYPE(pa_source, pa_msgobject); @@ -198,6 +200,8 @@ pa_source* pa_source_new( s->muted = data->muted; s->refresh_volume = s->refresh_muted = FALSE; + s->fixed_latency = flags & PA_SOURCE_DYNAMIC_LATENCY ? 0 : DEFAULT_FIXED_LATENCY; + reset_callbacks(s); s->userdata = NULL; @@ -218,8 +222,8 @@ pa_source* pa_source_new( s->thread_info.max_rewind = 0; s->thread_info.requested_latency_valid = FALSE; s->thread_info.requested_latency = 0; - s->thread_info.min_latency = DEFAULT_MIN_LATENCY; - s->thread_info.max_latency = 0; + s->thread_info.min_latency = ABSOLUTE_MIN_LATENCY; + s->thread_info.max_latency = ABSOLUTE_MAX_LATENCY; pa_assert_se(pa_idxset_put(core->sources, s, &s->index) >= 0); @@ -302,8 +306,7 @@ void pa_source_put(pa_source *s) { /* The following fields must be initialized properly when calling _put() */ pa_assert(s->asyncmsgq); pa_assert(s->rtpoll); - pa_assert(!s->thread_info.min_latency || !s->thread_info.max_latency || - s->thread_info.min_latency <= s->thread_info.max_latency); + 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; @@ -315,6 +318,11 @@ void pa_source_put(pa_source *s) { if (s->flags & PA_SOURCE_DECIBEL_VOLUME) s->n_volume_steps = PA_VOLUME_NORM+1; + 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_se(source_set_state(s, PA_SOURCE_IDLE) == 0); pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE | PA_SUBSCRIPTION_EVENT_NEW, s->index); @@ -465,8 +473,12 @@ pa_queue *pa_source_move_all_start(pa_source *s) { for (o = PA_SOURCE_OUTPUT(pa_idxset_first(s->outputs, &idx)); o; o = n) { n = PA_SOURCE_OUTPUT(pa_idxset_next(s->outputs, &idx)); + pa_source_output_ref(o); + if (pa_source_output_start_move(o) >= 0) - pa_queue_push(q, pa_source_output_ref(o)); + pa_queue_push(q, o); + else + pa_source_output_unref(o); } return q; @@ -616,6 +628,32 @@ pa_usec_t pa_source_get_latency(pa_source *s) { return usec; } +/* Called from IO thread */ +pa_usec_t pa_source_get_latency_within_thread(pa_source *s) { + pa_usec_t usec = 0; + pa_msgobject *o; + + pa_source_assert_ref(s); + pa_assert(PA_SOURCE_IS_LINKED(s->thread_info.state)); + + /* The returned value is supposed to be in the time domain of the sound card! */ + + if (s->thread_info.state == PA_SOURCE_SUSPENDED) + return 0; + + if (!(s->flags & PA_SOURCE_LATENCY)) + return 0; + + o = PA_MSGOBJECT(s); + + /* We probably should make this a proper vtable callback instead of going through process_msg() */ + + if (o->process_msg(o, PA_SOURCE_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0) + return -1; + + return usec; +} + /* Called from main thread */ void pa_source_set_volume(pa_source *s, const pa_cvolume *volume) { pa_cvolume old_virtual_volume; @@ -675,6 +713,19 @@ const pa_cvolume *pa_source_get_volume(pa_source *s, pa_bool_t force_refresh) { } /* Called from main thread */ +void pa_source_volume_changed(pa_source *s, const pa_cvolume *new_volume) { + pa_source_assert_ref(s); + + /* The source implementor may call this if the volume changed to make sure everyone is notified */ + + if (pa_cvolume_equal(&s->virtual_volume, new_volume)) + return; + + s->virtual_volume = *new_volume; + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); +} + +/* Called from main thread */ void pa_source_set_mute(pa_source *s, pa_bool_t mute) { pa_bool_t old_muted; @@ -695,7 +746,6 @@ void pa_source_set_mute(pa_source *s, pa_bool_t mute) { /* Called from main thread */ pa_bool_t pa_source_get_mute(pa_source *s, pa_bool_t force_refresh) { - pa_source_assert_ref(s); pa_assert(PA_SOURCE_IS_LINKED(s->state)); @@ -715,11 +765,24 @@ pa_bool_t pa_source_get_mute(pa_source *s, pa_bool_t force_refresh) { } /* Called from main thread */ +void pa_source_mute_changed(pa_source *s, pa_bool_t new_muted) { + pa_source_assert_ref(s); + + /* The source implementor may call this if the mute state changed to make sure everyone is notified */ + + if (s->muted == new_muted) + return; + + s->muted = new_muted; + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); +} + +/* Called from main thread */ pa_bool_t pa_source_update_proplist(pa_source *s, pa_update_mode_t mode, pa_proplist *p) { pa_source_assert_ref(s); - pa_assert(p); - pa_proplist_update(s->proplist, mode, p); + if (p) + pa_proplist_update(s->proplist, mode, p); if (PA_SOURCE_IS_LINKED(s->state)) { pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_PROPLIST_CHANGED], s); @@ -881,9 +944,26 @@ int pa_source_process_msg(pa_msgobject *object, int code, void *userdata, int64_ case PA_SOURCE_MESSAGE_GET_MUTE: return 0; - case PA_SOURCE_MESSAGE_SET_STATE: + case PA_SOURCE_MESSAGE_SET_STATE: { + + pa_bool_t suspend_change = + (s->thread_info.state == PA_SOURCE_SUSPENDED && PA_SOURCE_IS_OPENED(PA_PTR_TO_UINT(userdata))) || + (PA_SOURCE_IS_OPENED(s->thread_info.state) && PA_PTR_TO_UINT(userdata) == PA_SOURCE_SUSPENDED); + s->thread_info.state = PA_PTR_TO_UINT(userdata); + + if (suspend_change) { + pa_source_output *o; + void *state = NULL; + + while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL))) + if (o->suspend_within_thread) + o->suspend_within_thread(o, s->thread_info.state == PA_SOURCE_SUSPENDED); + } + + return 0; + } case PA_SOURCE_MESSAGE_DETACH: @@ -911,7 +991,7 @@ int pa_source_process_msg(pa_msgobject *object, int code, void *userdata, int64_ case PA_SOURCE_MESSAGE_SET_LATENCY_RANGE: { pa_usec_t *r = userdata; - pa_source_update_latency_range(s, r[0], r[1]); + pa_source_set_latency_range_within_thread(s, r[0], r[1]); return 0; } @@ -930,6 +1010,11 @@ int pa_source_process_msg(pa_msgobject *object, int code, void *userdata, int64_ *((size_t*) userdata) = s->thread_info.max_rewind; return 0; + case PA_SOURCE_MESSAGE_SET_MAX_REWIND: + + pa_source_set_max_rewind_within_thread(s, (size_t) offset); + return 0; + case PA_SOURCE_MESSAGE_GET_LATENCY: if (s->monitor_of) { @@ -1018,6 +1103,9 @@ pa_usec_t pa_source_get_requested_latency_within_thread(pa_source *s) { pa_source_assert_ref(s); + if (!(s->flags & PA_SOURCE_DYNAMIC_LATENCY)) + return PA_CLAMP(s->fixed_latency, s->thread_info.min_latency, s->thread_info.max_latency); + if (s->thread_info.requested_latency_valid) return s->thread_info.requested_latency; @@ -1027,13 +1115,8 @@ pa_usec_t pa_source_get_requested_latency_within_thread(pa_source *s) { (result == (pa_usec_t) -1 || result > o->thread_info.requested_source_latency)) result = o->thread_info.requested_source_latency; - if (result != (pa_usec_t) -1) { - if (s->thread_info.max_latency > 0 && result > s->thread_info.max_latency) - result = s->thread_info.max_latency; - - if (s->thread_info.min_latency > 0 && result < s->thread_info.min_latency) - result = s->thread_info.min_latency; - } + 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; @@ -1043,7 +1126,7 @@ pa_usec_t pa_source_get_requested_latency_within_thread(pa_source *s) { /* Called from main thread */ pa_usec_t pa_source_get_requested_latency(pa_source *s) { - pa_usec_t usec; + pa_usec_t usec = 0; pa_source_assert_ref(s); pa_assert(PA_SOURCE_IS_LINKED(s->state)); @@ -1057,7 +1140,7 @@ pa_usec_t pa_source_get_requested_latency(pa_source *s) { } /* Called from IO thread */ -void pa_source_set_max_rewind(pa_source *s, size_t max_rewind) { +void pa_source_set_max_rewind_within_thread(pa_source *s, size_t max_rewind) { pa_source_output *o; void *state = NULL; @@ -1074,42 +1157,64 @@ void pa_source_set_max_rewind(pa_source *s, size_t max_rewind) { } } +/* Called from main thread */ +void pa_source_set_max_rewind(pa_source *s, size_t max_rewind) { + pa_source_assert_ref(s); + + if (PA_SOURCE_IS_LINKED(s->state)) + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_MAX_REWIND, NULL, max_rewind, NULL) == 0); + else + pa_source_set_max_rewind_within_thread(s, max_rewind); +} + +/* Called from IO thread */ void pa_source_invalidate_requested_latency(pa_source *s) { pa_source_output *o; void *state = NULL; pa_source_assert_ref(s); + if (!(s->flags & PA_SOURCE_DYNAMIC_LATENCY)) + return; + s->thread_info.requested_latency_valid = FALSE; - if (s->update_requested_latency) - s->update_requested_latency(s); + if (PA_SOURCE_IS_LINKED(s->thread_info.state)) { - while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL))) - if (o->update_source_requested_latency) - o->update_source_requested_latency(o); + if (s->update_requested_latency) + s->update_requested_latency(s); + + while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL))) + if (o->update_source_requested_latency) + o->update_source_requested_latency(o); + } if (s->monitor_of) pa_sink_invalidate_requested_latency(s->monitor_of); } +/* Called from main thread */ void pa_source_set_latency_range(pa_source *s, pa_usec_t min_latency, pa_usec_t max_latency) { pa_source_assert_ref(s); /* min_latency == 0: no limit - * min_latency == (size_t) -1: default limit * min_latency anything else: specified limit * * Similar for max_latency */ - if (min_latency == (pa_usec_t) -1) - min_latency = DEFAULT_MIN_LATENCY; + if (min_latency < ABSOLUTE_MIN_LATENCY) + min_latency = ABSOLUTE_MIN_LATENCY; - if (max_latency == (pa_usec_t) -1) - max_latency = min_latency; + if (max_latency <= 0 || + max_latency > ABSOLUTE_MAX_LATENCY) + max_latency = ABSOLUTE_MAX_LATENCY; - pa_assert(!min_latency || !max_latency || - min_latency <= max_latency); + pa_assert(min_latency <= max_latency); + + /* Hmm, let's see if someone forgot to set PA_SOURCE_DYNAMIC_LATENCY here... */ + pa_assert((min_latency == ABSOLUTE_MIN_LATENCY && + max_latency == ABSOLUTE_MAX_LATENCY) || + (s->flags & PA_SOURCE_DYNAMIC_LATENCY)); if (PA_SOURCE_IS_LINKED(s->state)) { pa_usec_t r[2]; @@ -1118,14 +1223,11 @@ void pa_source_set_latency_range(pa_source *s, pa_usec_t min_latency, pa_usec_t r[1] = max_latency; pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_LATENCY_RANGE, r, 0, NULL) == 0); - } else { - s->thread_info.min_latency = min_latency; - s->thread_info.max_latency = max_latency; - - s->thread_info.requested_latency_valid = FALSE; - } + } else + pa_source_set_latency_range_within_thread(s, min_latency, max_latency); } +/* Called from main thread */ void pa_source_get_latency_range(pa_source *s, pa_usec_t *min_latency, pa_usec_t *max_latency) { pa_source_assert_ref(s); pa_assert(min_latency); @@ -1144,26 +1246,37 @@ void pa_source_get_latency_range(pa_source *s, pa_usec_t *min_latency, pa_usec_t } } -/* Called from IO thread */ -void pa_source_update_latency_range(pa_source *s, pa_usec_t min_latency, pa_usec_t max_latency) { - pa_source_output *o; +/* Called from IO thread, and from main thread before pa_source_put() is called */ +void pa_source_set_latency_range_within_thread(pa_source *s, pa_usec_t min_latency, pa_usec_t max_latency) { void *state = NULL; pa_source_assert_ref(s); - pa_assert(!min_latency || !max_latency || - min_latency <= max_latency); + pa_assert(min_latency >= ABSOLUTE_MIN_LATENCY); + pa_assert(max_latency <= ABSOLUTE_MAX_LATENCY); + pa_assert(min_latency <= max_latency); + + /* Hmm, let's see if someone forgot to set PA_SOURCE_DYNAMIC_LATENCY here... */ + pa_assert((min_latency == ABSOLUTE_MIN_LATENCY && + max_latency == ABSOLUTE_MAX_LATENCY) || + (s->flags & PA_SOURCE_DYNAMIC_LATENCY) || + s->monitor_of); s->thread_info.min_latency = min_latency; s->thread_info.max_latency = max_latency; - while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL))) - if (o->update_source_latency_range) - o->update_source_latency_range(o); + if (PA_SOURCE_IS_LINKED(s->thread_info.state)) { + pa_source_output *o; + + while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL))) + if (o->update_source_latency_range) + o->update_source_latency_range(o); + } pa_source_invalidate_requested_latency(s); } +/* Called from main thread */ size_t pa_source_get_max_rewind(pa_source *s) { size_t r; pa_source_assert_ref(s); diff --git a/src/pulsecore/source.h b/src/pulsecore/source.h index 26471de..b502c22 100644 --- a/src/pulsecore/source.h +++ b/src/pulsecore/source.h @@ -87,6 +87,8 @@ struct pa_source { pa_memchunk silence; + pa_usec_t fixed_latency; /* for sources with PA_SOURCE_DYNAMIC_LATENCY this is 0 */ + /* Called when the main loop requests a state change. Called from * main loop context. If returns -1 the state change will be * inhibited */ @@ -159,6 +161,7 @@ typedef enum pa_source_message { PA_SOURCE_MESSAGE_SET_LATENCY_RANGE, PA_SOURCE_MESSAGE_GET_LATENCY_RANGE, PA_SOURCE_MESSAGE_GET_MAX_REWIND, + PA_SOURCE_MESSAGE_SET_MAX_REWIND, PA_SOURCE_MESSAGE_MAX } pa_source_message_t; @@ -205,12 +208,15 @@ void pa_source_set_description(pa_source *s, const char *description); void pa_source_set_asyncmsgq(pa_source *s, pa_asyncmsgq *q); void pa_source_set_rtpoll(pa_source *s, pa_rtpoll *p); +void pa_source_set_max_rewind(pa_source *s, size_t max_rewind); void pa_source_set_latency_range(pa_source *s, pa_usec_t min_latency, pa_usec_t max_latency); void pa_source_detach(pa_source *s); void pa_source_attach(pa_source *s); void pa_source_set_soft_volume(pa_source *s, const pa_cvolume *volume); +void pa_source_volume_changed(pa_source *s, const pa_cvolume *new_volume); +void pa_source_mute_changed(pa_source *s, pa_bool_t new_muted); int pa_source_sync_suspend(pa_source *s); @@ -257,11 +263,12 @@ void pa_source_detach_within_thread(pa_source *s); pa_usec_t pa_source_get_requested_latency_within_thread(pa_source *s); -void pa_source_set_max_rewind(pa_source *s, size_t max_rewind); -void pa_source_update_latency_range(pa_source *s, pa_usec_t min_latency, pa_usec_t max_latency); +void pa_source_set_max_rewind_within_thread(pa_source *s, size_t max_rewind); +void pa_source_set_latency_range_within_thread(pa_source *s, pa_usec_t min_latency, pa_usec_t max_latency); /*** To be called exclusively by source output drivers, from IO context */ void pa_source_invalidate_requested_latency(pa_source *s); +pa_usec_t pa_source_get_latency_within_thread(pa_source *s); #endif diff --git a/src/pulsecore/time-smoother.c b/src/pulsecore/time-smoother.c index 6562194..9d5a070 100644 --- a/src/pulsecore/time-smoother.c +++ b/src/pulsecore/time-smoother.c @@ -78,17 +78,26 @@ struct pa_smoother { /* Cached parameters for our interpolation polynomial y=ax^3+b^2+cx */ double a, b, c; - pa_bool_t abc_valid; + pa_bool_t abc_valid:1; pa_bool_t monotonic:1; pa_bool_t paused:1; + pa_bool_t smoothing:1; /* If FALSE we skip the polonyomial interpolation step */ pa_usec_t pause_time; unsigned min_history; }; -pa_smoother* pa_smoother_new(pa_usec_t adjust_time, pa_usec_t history_time, pa_bool_t monotonic, unsigned min_history) { +pa_smoother* pa_smoother_new( + pa_usec_t adjust_time, + pa_usec_t history_time, + pa_bool_t monotonic, + pa_bool_t smoothing, + unsigned min_history, + pa_usec_t time_offset, + pa_bool_t paused) { + pa_smoother *s; pa_assert(adjust_time > 0); @@ -116,9 +125,13 @@ pa_smoother* pa_smoother_new(pa_usec_t adjust_time, pa_usec_t history_time, pa_b s->abc_valid = FALSE; s->paused = FALSE; + s->smoothing = smoothing; s->min_history = min_history; + s->paused = paused; + s->time_offset = s->pause_time = time_offset; + return s; } @@ -279,6 +292,7 @@ static void estimate(pa_smoother *s, pa_usec_t x, pa_usec_t *y, double *deriv) { pa_assert(y); if (x >= s->px) { + /* Linear interpolation right from px */ int64_t t; /* The requested point is right of the point where we wanted @@ -294,7 +308,22 @@ static void estimate(pa_smoother *s, pa_usec_t x, pa_usec_t *y, double *deriv) { if (deriv) *deriv = s->dp; + } else if (x <= s->ex) { + /* Linear interpolation left from ex */ + int64_t t; + + t = (int64_t) s->ey - (int64_t) llrint(s->de * (double) (s->ex - x)); + + if (t < 0) + t = 0; + + *y = (pa_usec_t) t; + + if (deriv) + *deriv = s->de; + } else { + /* Spline interpolation between ex and px */ double tx, ty; /* Ok, we're not yet on track, thus let's interpolate, and @@ -348,7 +377,6 @@ void pa_smoother_put(pa_smoother *s, pa_usec_t x, pa_usec_t y) { * we can adjust our position smoothly from this one */ estimate(s, x, &ney, &nde); s->ex = x; s->ey = ney; s->de = nde; - s->ry = y; } @@ -359,12 +387,19 @@ void pa_smoother_put(pa_smoother *s, pa_usec_t x, pa_usec_t y) { s->dp = avg_gradient(s, x); /* And calculate when we want to be on track again */ - s->px = s->ex + s->adjust_time; - s->py = s->ry + (pa_usec_t) llrint(s->dp * (double) s->adjust_time); + if (s->smoothing) { + s->px = s->ex + s->adjust_time; + s->py = s->ry + (pa_usec_t) llrint(s->dp * (double) s->adjust_time); + } else { + s->px = s->ex; + s->py = s->ry; + } s->abc_valid = FALSE; -/* pa_log_debug("put(%llu | %llu) = %llu", (unsigned long long) (x + s->time_offset), (unsigned long long) x, (unsigned long long) y); */ +#ifdef DEBUG_DATA + pa_log_debug("%p, put(%llu | %llu) = %llu", s, (unsigned long long) (x + s->time_offset), (unsigned long long) x, (unsigned long long) y); +#endif } pa_usec_t pa_smoother_get(pa_smoother *s, pa_usec_t x) { @@ -395,7 +430,9 @@ pa_usec_t pa_smoother_get(pa_smoother *s, pa_usec_t x) { s->last_y = y; } -/* pa_log_debug("get(%llu | %llu) = %llu", (unsigned long long) (x + s->time_offset), (unsigned long long) x, (unsigned long long) y); */ +#ifdef DEBUG_DATA + pa_log_debug("%p, get(%llu | %llu) = %llu", s, (unsigned long long) (x + s->time_offset), (unsigned long long) x, (unsigned long long) y); +#endif return y; } @@ -405,7 +442,9 @@ void pa_smoother_set_time_offset(pa_smoother *s, pa_usec_t offset) { s->time_offset = offset; -/* pa_log_debug("offset(%llu)", (unsigned long long) offset); */ +#ifdef DEBUG_DATA + pa_log_debug("offset(%llu)", (unsigned long long) offset); +#endif } void pa_smoother_pause(pa_smoother *s, pa_usec_t x) { @@ -414,13 +453,15 @@ void pa_smoother_pause(pa_smoother *s, pa_usec_t x) { if (s->paused) return; -/* pa_log_debug("pause(%llu)", (unsigned long long) x); */ +#ifdef DEBUG_DATA + pa_log_debug("pause(%llu)", (unsigned long long) x); +#endif s->paused = TRUE; s->pause_time = x; } -void pa_smoother_resume(pa_smoother *s, pa_usec_t x) { +void pa_smoother_resume(pa_smoother *s, pa_usec_t x, pa_bool_t fix_now) { pa_assert(s); if (!s->paused) @@ -429,10 +470,22 @@ void pa_smoother_resume(pa_smoother *s, pa_usec_t x) { if (x < s->pause_time) x = s->pause_time; -/* pa_log_debug("resume(%llu)", (unsigned long long) x); */ +#ifdef DEBUG_DATA + pa_log_debug("resume(%llu)", (unsigned long long) x); +#endif s->paused = FALSE; s->time_offset += x - s->pause_time; + + if (fix_now) + pa_smoother_fix_now(s); +} + +void pa_smoother_fix_now(pa_smoother *s) { + pa_assert(s); + + s->px = s->ex; + s->py = s->ry; } pa_usec_t pa_smoother_translate(pa_smoother *s, pa_usec_t x, pa_usec_t y_delay) { @@ -454,7 +507,9 @@ pa_usec_t pa_smoother_translate(pa_smoother *s, pa_usec_t x, pa_usec_t y_delay) if (s->dp > nde) nde = s->dp; -/* pa_log_debug("translate(%llu) = %llu (%0.2f)", (unsigned long long) y_delay, (unsigned long long) ((double) y_delay / nde), nde); */ +#ifdef DEBUG_DATA + pa_log_debug("translate(%llu) = %llu (%0.2f)", (unsigned long long) y_delay, (unsigned long long) ((double) y_delay / nde), nde); +#endif return (pa_usec_t) llrint((double) y_delay / nde); } diff --git a/src/pulsecore/time-smoother.h b/src/pulsecore/time-smoother.h index 2051e64..5244a7e 100644 --- a/src/pulsecore/time-smoother.h +++ b/src/pulsecore/time-smoother.h @@ -27,7 +27,15 @@ typedef struct pa_smoother pa_smoother; -pa_smoother* pa_smoother_new(pa_usec_t x_adjust_time, pa_usec_t x_history_time, pa_bool_t monotonic, unsigned min_history); +pa_smoother* pa_smoother_new( + pa_usec_t x_adjust_time, + pa_usec_t x_history_time, + pa_bool_t monotonic, + pa_bool_t smoothing, + unsigned min_history, + pa_usec_t x_offset, + pa_bool_t paused); + void pa_smoother_free(pa_smoother* s); /* Adds a new value to our dataset. x = local/system time, y = remote time */ @@ -42,8 +50,10 @@ pa_usec_t pa_smoother_translate(pa_smoother *s, pa_usec_t x, pa_usec_t y_delay); void pa_smoother_set_time_offset(pa_smoother *s, pa_usec_t x_offset); void pa_smoother_pause(pa_smoother *s, pa_usec_t x); -void pa_smoother_resume(pa_smoother *s, pa_usec_t x); +void pa_smoother_resume(pa_smoother *s, pa_usec_t x, pa_bool_t abrupt); void pa_smoother_reset(pa_smoother *s); +void pa_smoother_fix_now(pa_smoother *s); + #endif |