diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:03:18 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:03:18 +0000 |
commit | 2dd5bc6a074165ddfbd57c4bd52c2d2dac8f47a1 (patch) | |
tree | 465b29cb405d3af0b0ad50c78e1dccc636594fec /src/pulse | |
parent | Initial commit. (diff) | |
download | pulseaudio-2dd5bc6a074165ddfbd57c4bd52c2d2dac8f47a1.tar.xz pulseaudio-2dd5bc6a074165ddfbd57c4bd52c2d2dac8f47a1.zip |
Adding upstream version 14.2.upstream/14.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/pulse')
72 files changed, 25173 insertions, 0 deletions
diff --git a/src/pulse/.gitignore b/src/pulse/.gitignore new file mode 100644 index 0000000..6702033 --- /dev/null +++ b/src/pulse/.gitignore @@ -0,0 +1 @@ +version.h diff --git a/src/pulse/cdecl.h b/src/pulse/cdecl.h new file mode 100644 index 0000000..ac817d5 --- /dev/null +++ b/src/pulse/cdecl.h @@ -0,0 +1,40 @@ +#ifndef foopulsecdeclhfoo +#define foopulsecdeclhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +/** \file + * C++ compatibility support */ + +#ifdef __cplusplus +/** If using C++ this macro enables C mode, otherwise does nothing */ +#define PA_C_DECL_BEGIN extern "C" { +/** If using C++ this macros switches back to C++ mode, otherwise does nothing */ +#define PA_C_DECL_END } + +#else +/** If using C++ this macro enables C mode, otherwise does nothing */ +#define PA_C_DECL_BEGIN +/** If using C++ this macros switches back to C++ mode, otherwise does nothing */ +#define PA_C_DECL_END + +#endif + +#endif diff --git a/src/pulse/channelmap.c b/src/pulse/channelmap.c new file mode 100644 index 0000000..c44dca4 --- /dev/null +++ b/src/pulse/channelmap.c @@ -0,0 +1,833 @@ +/*** + This file is part of PulseAudio. + + Copyright 2005-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/i18n.h> +#include <pulsecore/core-util.h> +#include <pulsecore/macro.h> +#include <pulsecore/bitset.h> +#include <pulsecore/sample-util.h> + +#include "channelmap.h" + +const char *const table[PA_CHANNEL_POSITION_MAX] = { + [PA_CHANNEL_POSITION_MONO] = "mono", + + [PA_CHANNEL_POSITION_FRONT_CENTER] = "front-center", + [PA_CHANNEL_POSITION_FRONT_LEFT] = "front-left", + [PA_CHANNEL_POSITION_FRONT_RIGHT] = "front-right", + + [PA_CHANNEL_POSITION_REAR_CENTER] = "rear-center", + [PA_CHANNEL_POSITION_REAR_LEFT] = "rear-left", + [PA_CHANNEL_POSITION_REAR_RIGHT] = "rear-right", + + [PA_CHANNEL_POSITION_LFE] = "lfe", + + [PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER] = "front-left-of-center", + [PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER] = "front-right-of-center", + + [PA_CHANNEL_POSITION_SIDE_LEFT] = "side-left", + [PA_CHANNEL_POSITION_SIDE_RIGHT] = "side-right", + + [PA_CHANNEL_POSITION_AUX0] = "aux0", + [PA_CHANNEL_POSITION_AUX1] = "aux1", + [PA_CHANNEL_POSITION_AUX2] = "aux2", + [PA_CHANNEL_POSITION_AUX3] = "aux3", + [PA_CHANNEL_POSITION_AUX4] = "aux4", + [PA_CHANNEL_POSITION_AUX5] = "aux5", + [PA_CHANNEL_POSITION_AUX6] = "aux6", + [PA_CHANNEL_POSITION_AUX7] = "aux7", + [PA_CHANNEL_POSITION_AUX8] = "aux8", + [PA_CHANNEL_POSITION_AUX9] = "aux9", + [PA_CHANNEL_POSITION_AUX10] = "aux10", + [PA_CHANNEL_POSITION_AUX11] = "aux11", + [PA_CHANNEL_POSITION_AUX12] = "aux12", + [PA_CHANNEL_POSITION_AUX13] = "aux13", + [PA_CHANNEL_POSITION_AUX14] = "aux14", + [PA_CHANNEL_POSITION_AUX15] = "aux15", + [PA_CHANNEL_POSITION_AUX16] = "aux16", + [PA_CHANNEL_POSITION_AUX17] = "aux17", + [PA_CHANNEL_POSITION_AUX18] = "aux18", + [PA_CHANNEL_POSITION_AUX19] = "aux19", + [PA_CHANNEL_POSITION_AUX20] = "aux20", + [PA_CHANNEL_POSITION_AUX21] = "aux21", + [PA_CHANNEL_POSITION_AUX22] = "aux22", + [PA_CHANNEL_POSITION_AUX23] = "aux23", + [PA_CHANNEL_POSITION_AUX24] = "aux24", + [PA_CHANNEL_POSITION_AUX25] = "aux25", + [PA_CHANNEL_POSITION_AUX26] = "aux26", + [PA_CHANNEL_POSITION_AUX27] = "aux27", + [PA_CHANNEL_POSITION_AUX28] = "aux28", + [PA_CHANNEL_POSITION_AUX29] = "aux29", + [PA_CHANNEL_POSITION_AUX30] = "aux30", + [PA_CHANNEL_POSITION_AUX31] = "aux31", + + [PA_CHANNEL_POSITION_TOP_CENTER] = "top-center", + + [PA_CHANNEL_POSITION_TOP_FRONT_CENTER] = "top-front-center", + [PA_CHANNEL_POSITION_TOP_FRONT_LEFT] = "top-front-left", + [PA_CHANNEL_POSITION_TOP_FRONT_RIGHT] = "top-front-right", + + [PA_CHANNEL_POSITION_TOP_REAR_CENTER] = "top-rear-center", + [PA_CHANNEL_POSITION_TOP_REAR_LEFT] = "top-rear-left", + [PA_CHANNEL_POSITION_TOP_REAR_RIGHT] = "top-rear-right" +}; + +const char *const pretty_table[PA_CHANNEL_POSITION_MAX] = { + [PA_CHANNEL_POSITION_MONO] = N_("Mono"), + + [PA_CHANNEL_POSITION_FRONT_CENTER] = N_("Front Center"), + [PA_CHANNEL_POSITION_FRONT_LEFT] = N_("Front Left"), + [PA_CHANNEL_POSITION_FRONT_RIGHT] = N_("Front Right"), + + [PA_CHANNEL_POSITION_REAR_CENTER] = N_("Rear Center"), + [PA_CHANNEL_POSITION_REAR_LEFT] = N_("Rear Left"), + [PA_CHANNEL_POSITION_REAR_RIGHT] = N_("Rear Right"), + + [PA_CHANNEL_POSITION_LFE] = N_("Subwoofer"), + + [PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER] = N_("Front Left-of-center"), + [PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER] = N_("Front Right-of-center"), + + [PA_CHANNEL_POSITION_SIDE_LEFT] = N_("Side Left"), + [PA_CHANNEL_POSITION_SIDE_RIGHT] = N_("Side Right"), + + [PA_CHANNEL_POSITION_AUX0] = N_("Auxiliary 0"), + [PA_CHANNEL_POSITION_AUX1] = N_("Auxiliary 1"), + [PA_CHANNEL_POSITION_AUX2] = N_("Auxiliary 2"), + [PA_CHANNEL_POSITION_AUX3] = N_("Auxiliary 3"), + [PA_CHANNEL_POSITION_AUX4] = N_("Auxiliary 4"), + [PA_CHANNEL_POSITION_AUX5] = N_("Auxiliary 5"), + [PA_CHANNEL_POSITION_AUX6] = N_("Auxiliary 6"), + [PA_CHANNEL_POSITION_AUX7] = N_("Auxiliary 7"), + [PA_CHANNEL_POSITION_AUX8] = N_("Auxiliary 8"), + [PA_CHANNEL_POSITION_AUX9] = N_("Auxiliary 9"), + [PA_CHANNEL_POSITION_AUX10] = N_("Auxiliary 10"), + [PA_CHANNEL_POSITION_AUX11] = N_("Auxiliary 11"), + [PA_CHANNEL_POSITION_AUX12] = N_("Auxiliary 12"), + [PA_CHANNEL_POSITION_AUX13] = N_("Auxiliary 13"), + [PA_CHANNEL_POSITION_AUX14] = N_("Auxiliary 14"), + [PA_CHANNEL_POSITION_AUX15] = N_("Auxiliary 15"), + [PA_CHANNEL_POSITION_AUX16] = N_("Auxiliary 16"), + [PA_CHANNEL_POSITION_AUX17] = N_("Auxiliary 17"), + [PA_CHANNEL_POSITION_AUX18] = N_("Auxiliary 18"), + [PA_CHANNEL_POSITION_AUX19] = N_("Auxiliary 19"), + [PA_CHANNEL_POSITION_AUX20] = N_("Auxiliary 20"), + [PA_CHANNEL_POSITION_AUX21] = N_("Auxiliary 21"), + [PA_CHANNEL_POSITION_AUX22] = N_("Auxiliary 22"), + [PA_CHANNEL_POSITION_AUX23] = N_("Auxiliary 23"), + [PA_CHANNEL_POSITION_AUX24] = N_("Auxiliary 24"), + [PA_CHANNEL_POSITION_AUX25] = N_("Auxiliary 25"), + [PA_CHANNEL_POSITION_AUX26] = N_("Auxiliary 26"), + [PA_CHANNEL_POSITION_AUX27] = N_("Auxiliary 27"), + [PA_CHANNEL_POSITION_AUX28] = N_("Auxiliary 28"), + [PA_CHANNEL_POSITION_AUX29] = N_("Auxiliary 29"), + [PA_CHANNEL_POSITION_AUX30] = N_("Auxiliary 30"), + [PA_CHANNEL_POSITION_AUX31] = N_("Auxiliary 31"), + + [PA_CHANNEL_POSITION_TOP_CENTER] = N_("Top Center"), + + [PA_CHANNEL_POSITION_TOP_FRONT_CENTER] = N_("Top Front Center"), + [PA_CHANNEL_POSITION_TOP_FRONT_LEFT] = N_("Top Front Left"), + [PA_CHANNEL_POSITION_TOP_FRONT_RIGHT] = N_("Top Front Right"), + + [PA_CHANNEL_POSITION_TOP_REAR_CENTER] = N_("Top Rear Center"), + [PA_CHANNEL_POSITION_TOP_REAR_LEFT] = N_("Top Rear Left"), + [PA_CHANNEL_POSITION_TOP_REAR_RIGHT] = N_("Top Rear Right") +}; + +pa_channel_map* pa_channel_map_init(pa_channel_map *m) { + unsigned c; + pa_assert(m); + + m->channels = 0; + + for (c = 0; c < PA_CHANNELS_MAX; c++) + m->map[c] = PA_CHANNEL_POSITION_INVALID; + + return m; +} + +pa_channel_map* pa_channel_map_init_mono(pa_channel_map *m) { + pa_assert(m); + + pa_channel_map_init(m); + + m->channels = 1; + m->map[0] = PA_CHANNEL_POSITION_MONO; + return m; +} + +pa_channel_map* pa_channel_map_init_stereo(pa_channel_map *m) { + pa_assert(m); + + pa_channel_map_init(m); + + m->channels = 2; + m->map[0] = PA_CHANNEL_POSITION_LEFT; + m->map[1] = PA_CHANNEL_POSITION_RIGHT; + return m; +} + +pa_channel_map* pa_channel_map_init_auto(pa_channel_map *m, unsigned channels, pa_channel_map_def_t def) { + pa_assert(m); + pa_assert(pa_channels_valid(channels)); + pa_assert(def < PA_CHANNEL_MAP_DEF_MAX); + + pa_channel_map_init(m); + + m->channels = (uint8_t) channels; + + switch (def) { + case PA_CHANNEL_MAP_AIFF: + + /* This is somewhat compatible with RFC3551 */ + + switch (channels) { + case 1: + m->map[0] = PA_CHANNEL_POSITION_MONO; + return m; + + case 6: + m->map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; + m->map[1] = PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER; + m->map[2] = PA_CHANNEL_POSITION_FRONT_CENTER; + m->map[3] = PA_CHANNEL_POSITION_FRONT_RIGHT; + m->map[4] = PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER; + m->map[5] = PA_CHANNEL_POSITION_REAR_CENTER; + return m; + + case 5: + m->map[2] = PA_CHANNEL_POSITION_FRONT_CENTER; + m->map[3] = PA_CHANNEL_POSITION_REAR_LEFT; + m->map[4] = PA_CHANNEL_POSITION_REAR_RIGHT; + /* Fall through */ + + case 2: + m->map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; + m->map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT; + return m; + + case 3: + m->map[0] = PA_CHANNEL_POSITION_LEFT; + m->map[1] = PA_CHANNEL_POSITION_RIGHT; + m->map[2] = PA_CHANNEL_POSITION_CENTER; + return m; + + case 4: + m->map[0] = PA_CHANNEL_POSITION_LEFT; + m->map[1] = PA_CHANNEL_POSITION_CENTER; + m->map[2] = PA_CHANNEL_POSITION_RIGHT; + m->map[3] = PA_CHANNEL_POSITION_REAR_CENTER; + return m; + + default: + return NULL; + } + + case PA_CHANNEL_MAP_ALSA: + + switch (channels) { + case 1: + m->map[0] = PA_CHANNEL_POSITION_MONO; + return m; + + case 8: + m->map[6] = PA_CHANNEL_POSITION_SIDE_LEFT; + m->map[7] = PA_CHANNEL_POSITION_SIDE_RIGHT; + /* Fall through */ + + case 6: + m->map[5] = PA_CHANNEL_POSITION_LFE; + /* Fall through */ + + case 5: + m->map[4] = PA_CHANNEL_POSITION_FRONT_CENTER; + /* Fall through */ + + case 4: + m->map[2] = PA_CHANNEL_POSITION_REAR_LEFT; + m->map[3] = PA_CHANNEL_POSITION_REAR_RIGHT; + /* Fall through */ + + case 2: + m->map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; + m->map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT; + return m; + + default: + return NULL; + } + + case PA_CHANNEL_MAP_AUX: { + unsigned i; + + for (i = 0; i < channels; i++) + m->map[i] = PA_CHANNEL_POSITION_AUX0 + i; + + return m; + } + + case PA_CHANNEL_MAP_WAVEEX: + + /* Following http://www.microsoft.com/whdc/device/audio/multichaud.mspx#EKLAC */ + + switch (channels) { + case 1: + m->map[0] = PA_CHANNEL_POSITION_MONO; + return m; + + case 18: + m->map[15] = PA_CHANNEL_POSITION_TOP_REAR_LEFT; + m->map[16] = PA_CHANNEL_POSITION_TOP_REAR_CENTER; + m->map[17] = PA_CHANNEL_POSITION_TOP_REAR_RIGHT; + /* Fall through */ + + case 15: + m->map[12] = PA_CHANNEL_POSITION_TOP_FRONT_LEFT; + m->map[13] = PA_CHANNEL_POSITION_TOP_FRONT_CENTER; + m->map[14] = PA_CHANNEL_POSITION_TOP_FRONT_RIGHT; + /* Fall through */ + + case 12: + m->map[11] = PA_CHANNEL_POSITION_TOP_CENTER; + /* Fall through */ + + case 11: + m->map[9] = PA_CHANNEL_POSITION_SIDE_LEFT; + m->map[10] = PA_CHANNEL_POSITION_SIDE_RIGHT; + /* Fall through */ + + case 9: + m->map[8] = PA_CHANNEL_POSITION_REAR_CENTER; + /* Fall through */ + + case 8: + m->map[6] = PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER; + m->map[7] = PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER; + /* Fall through */ + + case 6: + m->map[4] = PA_CHANNEL_POSITION_REAR_LEFT; + m->map[5] = PA_CHANNEL_POSITION_REAR_RIGHT; + /* Fall through */ + + case 4: + m->map[3] = PA_CHANNEL_POSITION_LFE; + /* Fall through */ + + case 3: + m->map[2] = PA_CHANNEL_POSITION_FRONT_CENTER; + /* Fall through */ + + case 2: + m->map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; + m->map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT; + return m; + + default: + return NULL; + } + + case PA_CHANNEL_MAP_OSS: + + switch (channels) { + case 1: + m->map[0] = PA_CHANNEL_POSITION_MONO; + return m; + + case 8: + m->map[6] = PA_CHANNEL_POSITION_REAR_LEFT; + m->map[7] = PA_CHANNEL_POSITION_REAR_RIGHT; + /* Fall through */ + + case 6: + m->map[4] = PA_CHANNEL_POSITION_SIDE_LEFT; + m->map[5] = PA_CHANNEL_POSITION_SIDE_RIGHT; + /* Fall through */ + + case 4: + m->map[3] = PA_CHANNEL_POSITION_LFE; + /* Fall through */ + + case 3: + m->map[2] = PA_CHANNEL_POSITION_FRONT_CENTER; + /* Fall through */ + + case 2: + m->map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; + m->map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT; + return m; + + default: + return NULL; + } + + default: + pa_assert_not_reached(); + } +} + +pa_channel_map* pa_channel_map_init_extend(pa_channel_map *m, unsigned channels, pa_channel_map_def_t def) { + unsigned c; + + pa_assert(m); + pa_assert(pa_channels_valid(channels)); + pa_assert(def < PA_CHANNEL_MAP_DEF_MAX); + + pa_channel_map_init(m); + + for (c = channels; c > 0; c--) { + + if (pa_channel_map_init_auto(m, c, def)) { + unsigned i = 0; + + for (; c < channels; c++) { + + m->map[c] = PA_CHANNEL_POSITION_AUX0 + i; + i++; + } + + m->channels = (uint8_t) channels; + + return m; + } + } + + return NULL; +} + +const char* pa_channel_position_to_string(pa_channel_position_t pos) { + + if (pos < 0 || pos >= PA_CHANNEL_POSITION_MAX) + return NULL; + + return table[pos]; +} + +const char* pa_channel_position_to_pretty_string(pa_channel_position_t pos) { + + if (pos < 0 || pos >= PA_CHANNEL_POSITION_MAX) + return NULL; + + pa_init_i18n(); + + return _(pretty_table[pos]); +} + +int pa_channel_map_equal(const pa_channel_map *a, const pa_channel_map *b) { + unsigned c; + + pa_assert(a); + pa_assert(b); + + pa_return_val_if_fail(pa_channel_map_valid(a), 0); + + if (PA_UNLIKELY(a == b)) + return 1; + + pa_return_val_if_fail(pa_channel_map_valid(b), 0); + + if (a->channels != b->channels) + return 0; + + for (c = 0; c < a->channels; c++) + if (a->map[c] != b->map[c]) + return 0; + + return 1; +} + +char* pa_channel_map_snprint(char *s, size_t l, const pa_channel_map *map) { + unsigned channel; + bool first = true; + char *e; + + pa_assert(s); + pa_assert(l > 0); + pa_assert(map); + + pa_init_i18n(); + + if (!pa_channel_map_valid(map)) { + pa_snprintf(s, l, _("(invalid)")); + return s; + } + + *(e = s) = 0; + + for (channel = 0; channel < map->channels && l > 1; channel++) { + l -= pa_snprintf(e, l, "%s%s", + first ? "" : ",", + pa_channel_position_to_string(map->map[channel])); + + e = strchr(e, 0); + first = false; + } + + return s; +} + +pa_channel_position_t pa_channel_position_from_string(const char *p) { + pa_channel_position_t i; + pa_assert(p); + + /* Some special aliases */ + if (pa_streq(p, "left")) + return PA_CHANNEL_POSITION_LEFT; + else if (pa_streq(p, "right")) + return PA_CHANNEL_POSITION_RIGHT; + else if (pa_streq(p, "center")) + return PA_CHANNEL_POSITION_CENTER; + else if (pa_streq(p, "subwoofer")) + return PA_CHANNEL_POSITION_SUBWOOFER; + + for (i = 0; i < PA_CHANNEL_POSITION_MAX; i++) + if (pa_streq(p, table[i])) + return i; + + return PA_CHANNEL_POSITION_INVALID; +} + +pa_channel_map *pa_channel_map_parse(pa_channel_map *rmap, const char *s) { + const char *state; + pa_channel_map map; + char *p; + + pa_assert(rmap); + pa_assert(s); + + pa_channel_map_init(&map); + + /* We don't need to match against the well known channel mapping + * "mono" here explicitly, because that can be understood as + * listing with one channel called "mono". */ + + if (pa_streq(s, "stereo")) { + map.channels = 2; + map.map[0] = PA_CHANNEL_POSITION_LEFT; + map.map[1] = PA_CHANNEL_POSITION_RIGHT; + goto finish; + } else if (pa_streq(s, "surround-21")) { + map.channels = 3; + map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; + map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT; + map.map[2] = PA_CHANNEL_POSITION_LFE; + goto finish; + } else if (pa_streq(s, "surround-40")) { + map.channels = 4; + map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; + map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT; + map.map[2] = PA_CHANNEL_POSITION_REAR_LEFT; + map.map[3] = PA_CHANNEL_POSITION_REAR_RIGHT; + goto finish; + } else if (pa_streq(s, "surround-41")) { + map.channels = 5; + map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; + map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT; + map.map[2] = PA_CHANNEL_POSITION_REAR_LEFT; + map.map[3] = PA_CHANNEL_POSITION_REAR_RIGHT; + map.map[4] = PA_CHANNEL_POSITION_LFE; + goto finish; + } else if (pa_streq(s, "surround-50")) { + map.channels = 5; + map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; + map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT; + map.map[2] = PA_CHANNEL_POSITION_REAR_LEFT; + map.map[3] = PA_CHANNEL_POSITION_REAR_RIGHT; + map.map[4] = PA_CHANNEL_POSITION_FRONT_CENTER; + goto finish; + } else if (pa_streq(s, "surround-51")) { + map.channels = 6; + map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; + map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT; + map.map[2] = PA_CHANNEL_POSITION_REAR_LEFT; + map.map[3] = PA_CHANNEL_POSITION_REAR_RIGHT; + map.map[4] = PA_CHANNEL_POSITION_FRONT_CENTER; + map.map[5] = PA_CHANNEL_POSITION_LFE; + goto finish; + } else if (pa_streq(s, "surround-71")) { + map.channels = 8; + map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; + map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT; + map.map[2] = PA_CHANNEL_POSITION_REAR_LEFT; + map.map[3] = PA_CHANNEL_POSITION_REAR_RIGHT; + map.map[4] = PA_CHANNEL_POSITION_FRONT_CENTER; + map.map[5] = PA_CHANNEL_POSITION_LFE; + map.map[6] = PA_CHANNEL_POSITION_SIDE_LEFT; + map.map[7] = PA_CHANNEL_POSITION_SIDE_RIGHT; + goto finish; + } + + state = NULL; + map.channels = 0; + + while ((p = pa_split(s, ",", &state))) { + pa_channel_position_t f; + + if (map.channels >= PA_CHANNELS_MAX) { + pa_xfree(p); + return NULL; + } + + if ((f = pa_channel_position_from_string(p)) == PA_CHANNEL_POSITION_INVALID) { + pa_xfree(p); + return NULL; + } + + map.map[map.channels++] = f; + pa_xfree(p); + } + +finish: + + if (!pa_channel_map_valid(&map)) + return NULL; + + *rmap = map; + return rmap; +} + +int pa_channel_map_valid(const pa_channel_map *map) { + unsigned c; + + pa_assert(map); + + if (!pa_channels_valid(map->channels)) + return 0; + + for (c = 0; c < map->channels; c++) + if (map->map[c] < 0 || map->map[c] >= PA_CHANNEL_POSITION_MAX) + return 0; + + return 1; +} + +int pa_channel_map_compatible(const pa_channel_map *map, const pa_sample_spec *ss) { + pa_assert(map); + pa_assert(ss); + + pa_return_val_if_fail(pa_channel_map_valid(map), 0); + pa_return_val_if_fail(pa_sample_spec_valid(ss), 0); + + return map->channels == ss->channels; +} + +int pa_channel_map_superset(const pa_channel_map *a, const pa_channel_map *b) { + pa_channel_position_mask_t am, bm; + + pa_assert(a); + pa_assert(b); + + pa_return_val_if_fail(pa_channel_map_valid(a), 0); + + if (PA_UNLIKELY(a == b)) + return 1; + + pa_return_val_if_fail(pa_channel_map_valid(b), 0); + + am = pa_channel_map_mask(a); + bm = pa_channel_map_mask(b); + + return (bm & am) == bm; +} + +int pa_channel_map_can_balance(const pa_channel_map *map) { + pa_channel_position_mask_t m; + + pa_assert(map); + pa_return_val_if_fail(pa_channel_map_valid(map), 0); + + m = pa_channel_map_mask(map); + + return + (PA_CHANNEL_POSITION_MASK_LEFT & m) && + (PA_CHANNEL_POSITION_MASK_RIGHT & m); +} + +int pa_channel_map_can_fade(const pa_channel_map *map) { + pa_channel_position_mask_t m; + + pa_assert(map); + pa_return_val_if_fail(pa_channel_map_valid(map), 0); + + m = pa_channel_map_mask(map); + + return + (PA_CHANNEL_POSITION_MASK_FRONT & m) && + (PA_CHANNEL_POSITION_MASK_REAR & m); +} + +int pa_channel_map_can_lfe_balance(const pa_channel_map *map) { + pa_channel_position_mask_t m; + + pa_assert(map); + pa_return_val_if_fail(pa_channel_map_valid(map), 0); + + m = pa_channel_map_mask(map); + + return + (PA_CHANNEL_POSITION_MASK_LFE & m) && + (PA_CHANNEL_POSITION_MASK_HFE & m); +} + +const char* pa_channel_map_to_name(const pa_channel_map *map) { + pa_bitset_t in_map[PA_BITSET_ELEMENTS(PA_CHANNEL_POSITION_MAX)]; + unsigned c; + + pa_assert(map); + + pa_return_val_if_fail(pa_channel_map_valid(map), NULL); + + memset(in_map, 0, sizeof(in_map)); + + for (c = 0; c < map->channels; c++) + pa_bitset_set(in_map, map->map[c], true); + + if (pa_bitset_equals(in_map, PA_CHANNEL_POSITION_MAX, + PA_CHANNEL_POSITION_MONO, -1)) + return "mono"; + + if (pa_bitset_equals(in_map, PA_CHANNEL_POSITION_MAX, + PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT, -1)) + return "stereo"; + + if (pa_bitset_equals(in_map, PA_CHANNEL_POSITION_MAX, + PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, -1)) + return "surround-40"; + + if (pa_bitset_equals(in_map, PA_CHANNEL_POSITION_MAX, + PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, + PA_CHANNEL_POSITION_LFE, -1)) + return "surround-41"; + + if (pa_bitset_equals(in_map, PA_CHANNEL_POSITION_MAX, + PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, + PA_CHANNEL_POSITION_FRONT_CENTER, -1)) + return "surround-50"; + + if (pa_bitset_equals(in_map, PA_CHANNEL_POSITION_MAX, + PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, + PA_CHANNEL_POSITION_FRONT_CENTER, PA_CHANNEL_POSITION_LFE, -1)) + return "surround-51"; + + if (pa_bitset_equals(in_map, PA_CHANNEL_POSITION_MAX, + PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, + PA_CHANNEL_POSITION_FRONT_CENTER, PA_CHANNEL_POSITION_LFE, + PA_CHANNEL_POSITION_SIDE_LEFT, PA_CHANNEL_POSITION_SIDE_RIGHT, -1)) + return "surround-71"; + + return NULL; +} + +const char* pa_channel_map_to_pretty_name(const pa_channel_map *map) { + pa_bitset_t in_map[PA_BITSET_ELEMENTS(PA_CHANNEL_POSITION_MAX)]; + unsigned c; + + pa_assert(map); + + pa_return_val_if_fail(pa_channel_map_valid(map), NULL); + + memset(in_map, 0, sizeof(in_map)); + + for (c = 0; c < map->channels; c++) + pa_bitset_set(in_map, map->map[c], true); + + pa_init_i18n(); + + if (pa_bitset_equals(in_map, PA_CHANNEL_POSITION_MAX, + PA_CHANNEL_POSITION_MONO, -1)) + return _("Mono"); + + if (pa_bitset_equals(in_map, PA_CHANNEL_POSITION_MAX, + PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT, -1)) + return _("Stereo"); + + if (pa_bitset_equals(in_map, PA_CHANNEL_POSITION_MAX, + PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, -1)) + return _("Surround 4.0"); + + if (pa_bitset_equals(in_map, PA_CHANNEL_POSITION_MAX, + PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, + PA_CHANNEL_POSITION_LFE, -1)) + return _("Surround 4.1"); + + if (pa_bitset_equals(in_map, PA_CHANNEL_POSITION_MAX, + PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, + PA_CHANNEL_POSITION_FRONT_CENTER, -1)) + return _("Surround 5.0"); + + if (pa_bitset_equals(in_map, PA_CHANNEL_POSITION_MAX, + PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, + PA_CHANNEL_POSITION_FRONT_CENTER, PA_CHANNEL_POSITION_LFE, -1)) + return _("Surround 5.1"); + + if (pa_bitset_equals(in_map, PA_CHANNEL_POSITION_MAX, + PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, + PA_CHANNEL_POSITION_FRONT_CENTER, PA_CHANNEL_POSITION_LFE, + PA_CHANNEL_POSITION_SIDE_LEFT, PA_CHANNEL_POSITION_SIDE_RIGHT, -1)) + return _("Surround 7.1"); + + return NULL; +} + +int pa_channel_map_has_position(const pa_channel_map *map, pa_channel_position_t p) { + unsigned c; + + pa_return_val_if_fail(pa_channel_map_valid(map), 0); + pa_return_val_if_fail(p < PA_CHANNEL_POSITION_MAX, 0); + + for (c = 0; c < map->channels; c++) + if (map->map[c] == p) + return 1; + + return 0; +} + +pa_channel_position_mask_t pa_channel_map_mask(const pa_channel_map *map) { + unsigned c; + pa_channel_position_mask_t r = 0; + + pa_return_val_if_fail(pa_channel_map_valid(map), 0); + + for (c = 0; c < map->channels; c++) + r |= PA_CHANNEL_POSITION_MASK(map->map[c]); + + return r; +} diff --git a/src/pulse/channelmap.h b/src/pulse/channelmap.h new file mode 100644 index 0000000..290e1ed --- /dev/null +++ b/src/pulse/channelmap.h @@ -0,0 +1,368 @@ +#ifndef foochannelmaphfoo +#define foochannelmaphfoo + +/*** + This file is part of PulseAudio. + + Copyright 2005-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulse/sample.h> +#include <pulse/cdecl.h> +#include <pulse/gccmacro.h> +#include <pulse/version.h> + +/** \page channelmap Channel Maps + * + * \section overv_sec Overview + * + * Channel maps provide a way to associate channels in a stream with a + * specific speaker position. This relieves applications of having to + * make sure their channel order is identical to the final output. + * + * \section init_sec Initialisation + * + * A channel map consists of an array of \ref pa_channel_position values, + * one for each channel. This array is stored together with a channel count + * in a pa_channel_map structure. + * + * Before filling the structure, the application must initialise it using + * pa_channel_map_init(). There are also a number of convenience functions + * for standard channel mappings: + * + * \li pa_channel_map_init_mono() - Create a channel map with only mono audio. + * \li pa_channel_map_init_stereo() - Create a standard stereo mapping. + * \li pa_channel_map_init_auto() - Create a standard channel map for a specific + * number of channels. + * \li pa_channel_map_init_extend() - Similar to pa_channel_map_init_auto() but + * synthesize a channel map if no predefined + * one is known for the specified number of + * channels. + * + * \section conv_sec Convenience Functions + * + * The library contains a number of convenience functions for dealing with + * channel maps: + * + * \li pa_channel_map_valid() - Tests if a channel map is valid. + * \li pa_channel_map_equal() - Tests if two channel maps are identical. + * \li pa_channel_map_snprint() - Creates a textual description of a channel + * map. + */ + +/** \file + * Constants and routines for channel mapping handling + * + * See also \subpage channelmap + */ + +PA_C_DECL_BEGIN + +/** A list of channel labels */ +typedef enum pa_channel_position { + PA_CHANNEL_POSITION_INVALID = -1, + PA_CHANNEL_POSITION_MONO = 0, + + PA_CHANNEL_POSITION_FRONT_LEFT, /**< Apple, Dolby call this 'Left' */ + PA_CHANNEL_POSITION_FRONT_RIGHT, /**< Apple, Dolby call this 'Right' */ + PA_CHANNEL_POSITION_FRONT_CENTER, /**< Apple, Dolby call this 'Center' */ + +/** \cond fulldocs */ + PA_CHANNEL_POSITION_LEFT = PA_CHANNEL_POSITION_FRONT_LEFT, + PA_CHANNEL_POSITION_RIGHT = PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_CENTER = PA_CHANNEL_POSITION_FRONT_CENTER, +/** \endcond */ + + PA_CHANNEL_POSITION_REAR_CENTER, /**< Microsoft calls this 'Back Center', Apple calls this 'Center Surround', Dolby calls this 'Surround Rear Center' */ + PA_CHANNEL_POSITION_REAR_LEFT, /**< Microsoft calls this 'Back Left', Apple calls this 'Left Surround' (!), Dolby calls this 'Surround Rear Left' */ + PA_CHANNEL_POSITION_REAR_RIGHT, /**< Microsoft calls this 'Back Right', Apple calls this 'Right Surround' (!), Dolby calls this 'Surround Rear Right' */ + + PA_CHANNEL_POSITION_LFE, /**< Microsoft calls this 'Low Frequency', Apple calls this 'LFEScreen' */ +/** \cond fulldocs */ + PA_CHANNEL_POSITION_SUBWOOFER = PA_CHANNEL_POSITION_LFE, +/** \endcond */ + + PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER, /**< Apple, Dolby call this 'Left Center' */ + PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER, /**< Apple, Dolby call this 'Right Center */ + + PA_CHANNEL_POSITION_SIDE_LEFT, /**< Apple calls this 'Left Surround Direct', Dolby calls this 'Surround Left' (!) */ + PA_CHANNEL_POSITION_SIDE_RIGHT, /**< Apple calls this 'Right Surround Direct', Dolby calls this 'Surround Right' (!) */ + + PA_CHANNEL_POSITION_AUX0, + PA_CHANNEL_POSITION_AUX1, + PA_CHANNEL_POSITION_AUX2, + PA_CHANNEL_POSITION_AUX3, + PA_CHANNEL_POSITION_AUX4, + PA_CHANNEL_POSITION_AUX5, + PA_CHANNEL_POSITION_AUX6, + PA_CHANNEL_POSITION_AUX7, + PA_CHANNEL_POSITION_AUX8, + PA_CHANNEL_POSITION_AUX9, + PA_CHANNEL_POSITION_AUX10, + PA_CHANNEL_POSITION_AUX11, + PA_CHANNEL_POSITION_AUX12, + PA_CHANNEL_POSITION_AUX13, + PA_CHANNEL_POSITION_AUX14, + PA_CHANNEL_POSITION_AUX15, + PA_CHANNEL_POSITION_AUX16, + PA_CHANNEL_POSITION_AUX17, + PA_CHANNEL_POSITION_AUX18, + PA_CHANNEL_POSITION_AUX19, + PA_CHANNEL_POSITION_AUX20, + PA_CHANNEL_POSITION_AUX21, + PA_CHANNEL_POSITION_AUX22, + PA_CHANNEL_POSITION_AUX23, + PA_CHANNEL_POSITION_AUX24, + PA_CHANNEL_POSITION_AUX25, + PA_CHANNEL_POSITION_AUX26, + PA_CHANNEL_POSITION_AUX27, + PA_CHANNEL_POSITION_AUX28, + PA_CHANNEL_POSITION_AUX29, + PA_CHANNEL_POSITION_AUX30, + PA_CHANNEL_POSITION_AUX31, + + PA_CHANNEL_POSITION_TOP_CENTER, /**< Apple calls this 'Top Center Surround' */ + + PA_CHANNEL_POSITION_TOP_FRONT_LEFT, /**< Apple calls this 'Vertical Height Left' */ + PA_CHANNEL_POSITION_TOP_FRONT_RIGHT, /**< Apple calls this 'Vertical Height Right' */ + PA_CHANNEL_POSITION_TOP_FRONT_CENTER, /**< Apple calls this 'Vertical Height Center' */ + + PA_CHANNEL_POSITION_TOP_REAR_LEFT, /**< Microsoft and Apple call this 'Top Back Left' */ + PA_CHANNEL_POSITION_TOP_REAR_RIGHT, /**< Microsoft and Apple call this 'Top Back Right' */ + PA_CHANNEL_POSITION_TOP_REAR_CENTER, /**< Microsoft and Apple call this 'Top Back Center' */ + + PA_CHANNEL_POSITION_MAX +} pa_channel_position_t; + +/** \cond fulldocs */ +#define PA_CHANNEL_POSITION_INVALID PA_CHANNEL_POSITION_INVALID +#define PA_CHANNEL_POSITION_MONO PA_CHANNEL_POSITION_MONO +#define PA_CHANNEL_POSITION_LEFT PA_CHANNEL_POSITION_LEFT +#define PA_CHANNEL_POSITION_RIGHT PA_CHANNEL_POSITION_RIGHT +#define PA_CHANNEL_POSITION_CENTER PA_CHANNEL_POSITION_CENTER +#define PA_CHANNEL_POSITION_FRONT_LEFT PA_CHANNEL_POSITION_FRONT_LEFT +#define PA_CHANNEL_POSITION_FRONT_RIGHT PA_CHANNEL_POSITION_FRONT_RIGHT +#define PA_CHANNEL_POSITION_FRONT_CENTER PA_CHANNEL_POSITION_FRONT_CENTER +#define PA_CHANNEL_POSITION_REAR_CENTER PA_CHANNEL_POSITION_REAR_CENTER +#define PA_CHANNEL_POSITION_REAR_LEFT PA_CHANNEL_POSITION_REAR_LEFT +#define PA_CHANNEL_POSITION_REAR_RIGHT PA_CHANNEL_POSITION_REAR_RIGHT +#define PA_CHANNEL_POSITION_LFE PA_CHANNEL_POSITION_LFE +#define PA_CHANNEL_POSITION_SUBWOOFER PA_CHANNEL_POSITION_SUBWOOFER +#define PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER +#define PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER +#define PA_CHANNEL_POSITION_SIDE_LEFT PA_CHANNEL_POSITION_SIDE_LEFT +#define PA_CHANNEL_POSITION_SIDE_RIGHT PA_CHANNEL_POSITION_SIDE_RIGHT +#define PA_CHANNEL_POSITION_AUX0 PA_CHANNEL_POSITION_AUX0 +#define PA_CHANNEL_POSITION_AUX1 PA_CHANNEL_POSITION_AUX1 +#define PA_CHANNEL_POSITION_AUX2 PA_CHANNEL_POSITION_AUX2 +#define PA_CHANNEL_POSITION_AUX3 PA_CHANNEL_POSITION_AUX3 +#define PA_CHANNEL_POSITION_AUX4 PA_CHANNEL_POSITION_AUX4 +#define PA_CHANNEL_POSITION_AUX5 PA_CHANNEL_POSITION_AUX5 +#define PA_CHANNEL_POSITION_AUX6 PA_CHANNEL_POSITION_AUX6 +#define PA_CHANNEL_POSITION_AUX7 PA_CHANNEL_POSITION_AUX7 +#define PA_CHANNEL_POSITION_AUX8 PA_CHANNEL_POSITION_AUX8 +#define PA_CHANNEL_POSITION_AUX9 PA_CHANNEL_POSITION_AUX9 +#define PA_CHANNEL_POSITION_AUX10 PA_CHANNEL_POSITION_AUX10 +#define PA_CHANNEL_POSITION_AUX11 PA_CHANNEL_POSITION_AUX11 +#define PA_CHANNEL_POSITION_AUX12 PA_CHANNEL_POSITION_AUX12 +#define PA_CHANNEL_POSITION_AUX13 PA_CHANNEL_POSITION_AUX13 +#define PA_CHANNEL_POSITION_AUX14 PA_CHANNEL_POSITION_AUX14 +#define PA_CHANNEL_POSITION_AUX15 PA_CHANNEL_POSITION_AUX15 +#define PA_CHANNEL_POSITION_AUX16 PA_CHANNEL_POSITION_AUX16 +#define PA_CHANNEL_POSITION_AUX17 PA_CHANNEL_POSITION_AUX17 +#define PA_CHANNEL_POSITION_AUX18 PA_CHANNEL_POSITION_AUX18 +#define PA_CHANNEL_POSITION_AUX19 PA_CHANNEL_POSITION_AUX19 +#define PA_CHANNEL_POSITION_AUX20 PA_CHANNEL_POSITION_AUX20 +#define PA_CHANNEL_POSITION_AUX21 PA_CHANNEL_POSITION_AUX21 +#define PA_CHANNEL_POSITION_AUX22 PA_CHANNEL_POSITION_AUX22 +#define PA_CHANNEL_POSITION_AUX23 PA_CHANNEL_POSITION_AUX23 +#define PA_CHANNEL_POSITION_AUX24 PA_CHANNEL_POSITION_AUX24 +#define PA_CHANNEL_POSITION_AUX25 PA_CHANNEL_POSITION_AUX25 +#define PA_CHANNEL_POSITION_AUX26 PA_CHANNEL_POSITION_AUX26 +#define PA_CHANNEL_POSITION_AUX27 PA_CHANNEL_POSITION_AUX27 +#define PA_CHANNEL_POSITION_AUX28 PA_CHANNEL_POSITION_AUX28 +#define PA_CHANNEL_POSITION_AUX29 PA_CHANNEL_POSITION_AUX29 +#define PA_CHANNEL_POSITION_AUX30 PA_CHANNEL_POSITION_AUX30 +#define PA_CHANNEL_POSITION_AUX31 PA_CHANNEL_POSITION_AUX31 +#define PA_CHANNEL_POSITION_TOP_CENTER PA_CHANNEL_POSITION_TOP_CENTER +#define PA_CHANNEL_POSITION_TOP_FRONT_LEFT PA_CHANNEL_POSITION_TOP_FRONT_LEFT +#define PA_CHANNEL_POSITION_TOP_FRONT_RIGHT PA_CHANNEL_POSITION_TOP_FRONT_RIGHT +#define PA_CHANNEL_POSITION_TOP_FRONT_CENTER PA_CHANNEL_POSITION_TOP_FRONT_CENTER +#define PA_CHANNEL_POSITION_TOP_REAR_LEFT PA_CHANNEL_POSITION_TOP_REAR_LEFT +#define PA_CHANNEL_POSITION_TOP_REAR_RIGHT PA_CHANNEL_POSITION_TOP_REAR_RIGHT +#define PA_CHANNEL_POSITION_TOP_REAR_CENTER PA_CHANNEL_POSITION_TOP_REAR_CENTER +#define PA_CHANNEL_POSITION_MAX PA_CHANNEL_POSITION_MAX +/** \endcond */ + +/** A mask of channel positions. \since 0.9.16 */ +typedef uint64_t pa_channel_position_mask_t; + +/** Makes a bit mask from a channel position. \since 0.9.16 */ +#define PA_CHANNEL_POSITION_MASK(f) ((pa_channel_position_mask_t) (1ULL << (f))) + +/** A list of channel mapping definitions for pa_channel_map_init_auto() */ +typedef enum pa_channel_map_def { + PA_CHANNEL_MAP_AIFF, + /**< The mapping from RFC3551, which is based on AIFF-C */ + +/** \cond fulldocs */ + PA_CHANNEL_MAP_ALSA, + /**< The default mapping used by ALSA. This mapping is probably + * not too useful since ALSA's default channel mapping depends on + * the device string used. */ +/** \endcond */ + + PA_CHANNEL_MAP_AUX, + /**< Only aux channels */ + + PA_CHANNEL_MAP_WAVEEX, + /**< Microsoft's WAVEFORMATEXTENSIBLE mapping. This mapping works + * as if all LSBs of dwChannelMask are set. */ + +/** \cond fulldocs */ + PA_CHANNEL_MAP_OSS, + /**< The default channel mapping used by OSS as defined in the OSS + * 4.0 API specs. This mapping is probably not too useful since + * the OSS API has changed in this respect and no longer knows a + * default channel mapping based on the number of channels. */ +/** \endcond */ + + /**< Upper limit of valid channel mapping definitions */ + PA_CHANNEL_MAP_DEF_MAX, + + PA_CHANNEL_MAP_DEFAULT = PA_CHANNEL_MAP_AIFF + /**< The default channel map */ +} pa_channel_map_def_t; + +/** \cond fulldocs */ +#define PA_CHANNEL_MAP_AIFF PA_CHANNEL_MAP_AIFF +#define PA_CHANNEL_MAP_ALSA PA_CHANNEL_MAP_ALSA +#define PA_CHANNEL_MAP_AUX PA_CHANNEL_MAP_AUX +#define PA_CHANNEL_MAP_WAVEEX PA_CHANNEL_MAP_WAVEEX +#define PA_CHANNEL_MAP_OSS PA_CHANNEL_MAP_OSS +#define PA_CHANNEL_MAP_DEF_MAX PA_CHANNEL_MAP_DEF_MAX +#define PA_CHANNEL_MAP_DEFAULT PA_CHANNEL_MAP_DEFAULT +/** \endcond */ + +/** A channel map which can be used to attach labels to specific + * channels of a stream. These values are relevant for conversion and + * mixing of streams */ +typedef struct pa_channel_map { + uint8_t channels; + /**< Number of channels mapped */ + + pa_channel_position_t map[PA_CHANNELS_MAX]; + /**< Channel labels */ +} pa_channel_map; + +/** Initialize the specified channel map and return a pointer to + * it. The channel map will have a defined state but + * pa_channel_map_valid() will fail for it. */ +pa_channel_map* pa_channel_map_init(pa_channel_map *m); + +/** Initialize the specified channel map for monaural audio and return a pointer to it */ +pa_channel_map* pa_channel_map_init_mono(pa_channel_map *m); + +/** Initialize the specified channel map for stereophonic audio and return a pointer to it */ +pa_channel_map* pa_channel_map_init_stereo(pa_channel_map *m); + +/** Initialize the specified channel map for the specified number of + * channels using default labels and return a pointer to it. This call + * will fail (return NULL) if there is no default channel map known for this + * specific number of channels and mapping. */ +pa_channel_map* pa_channel_map_init_auto(pa_channel_map *m, unsigned channels, pa_channel_map_def_t def); + +/** Similar to pa_channel_map_init_auto() but instead of failing if no + * default mapping is known with the specified parameters it will + * synthesize a mapping based on a known mapping with fewer channels + * and fill up the rest with AUX0...AUX31 channels \since 0.9.11 */ +pa_channel_map* pa_channel_map_init_extend(pa_channel_map *m, unsigned channels, pa_channel_map_def_t def); + +/** Return a text label for the specified channel position */ +const char* pa_channel_position_to_string(pa_channel_position_t pos) PA_GCC_PURE; + +/** The inverse of pa_channel_position_to_string(). \since 0.9.16 */ +pa_channel_position_t pa_channel_position_from_string(const char *s) PA_GCC_PURE; + +/** Return a human readable text label for the specified channel position. \since 0.9.7 */ +const char* pa_channel_position_to_pretty_string(pa_channel_position_t pos); + +/** The maximum length of strings returned by + * pa_channel_map_snprint(). Please note that this value can change + * with any release without warning and without being considered API + * or ABI breakage. You should not use this definition anywhere where + * it might become part of an ABI. */ +#define PA_CHANNEL_MAP_SNPRINT_MAX 336 + +/** Make a human readable string from the specified channel map. Returns \a s. */ +char* pa_channel_map_snprint(char *s, size_t l, const pa_channel_map *map); + +/** Parse a channel position list or well-known mapping name into a + * channel map structure. This turns the output of + * pa_channel_map_snprint() and pa_channel_map_to_name() back into a + * pa_channel_map */ +pa_channel_map *pa_channel_map_parse(pa_channel_map *map, const char *s); + +/** Compare two channel maps. Return 1 if both match. */ +int pa_channel_map_equal(const pa_channel_map *a, const pa_channel_map *b) PA_GCC_PURE; + +/** Return non-zero if the specified channel map is considered valid */ +int pa_channel_map_valid(const pa_channel_map *map) PA_GCC_PURE; + +/** Return non-zero if the specified channel map is compatible with + * the specified sample spec. \since 0.9.12 */ +int pa_channel_map_compatible(const pa_channel_map *map, const pa_sample_spec *ss) PA_GCC_PURE; + +/** Returns non-zero if every channel defined in b is also defined in a. \since 0.9.15 */ +int pa_channel_map_superset(const pa_channel_map *a, const pa_channel_map *b) PA_GCC_PURE; + +/** Returns non-zero if it makes sense to apply a volume 'balance' + * with this mapping, i.e.\ if there are left/right channels + * available. \since 0.9.15 */ +int pa_channel_map_can_balance(const pa_channel_map *map) PA_GCC_PURE; + +/** Returns non-zero if it makes sense to apply a volume 'fade' + * (i.e.\ 'balance' between front and rear) with this mapping, i.e.\ if + * there are front/rear channels available. \since 0.9.15 */ +int pa_channel_map_can_fade(const pa_channel_map *map) PA_GCC_PURE; + +/** Returns non-zero if it makes sense to apply a volume 'lfe balance' + * (i.e.\ 'balance' between LFE and non-LFE channels) with this mapping, + * i.e.\ if there are LFE and non-LFE channels available. \since 8.0 */ +int pa_channel_map_can_lfe_balance(const pa_channel_map *map) PA_GCC_PURE; + +/** Tries to find a well-known channel mapping name for this channel + * mapping, i.e.\ "stereo", "surround-71" and so on. If the channel + * mapping is unknown NULL will be returned. This name can be parsed + * with pa_channel_map_parse() \since 0.9.15 */ +const char* pa_channel_map_to_name(const pa_channel_map *map) PA_GCC_PURE; + +/** Tries to find a human readable text label for this channel +mapping, i.e.\ "Stereo", "Surround 7.1" and so on. If the channel +mapping is unknown NULL will be returned. \since 0.9.15 */ +const char* pa_channel_map_to_pretty_name(const pa_channel_map *map) PA_GCC_PURE; + +/** Returns non-zero if the specified channel position is available at + * least once in the channel map. \since 0.9.16 */ +int pa_channel_map_has_position(const pa_channel_map *map, pa_channel_position_t p) PA_GCC_PURE; + +/** Generates a bit mask from a channel map. \since 0.9.16 */ +pa_channel_position_mask_t pa_channel_map_mask(const pa_channel_map *map) PA_GCC_PURE; + +PA_C_DECL_END + +#endif diff --git a/src/pulse/client-conf-x11.c b/src/pulse/client-conf-x11.c new file mode 100644 index 0000000..e0c985e --- /dev/null +++ b/src/pulse/client-conf-x11.c @@ -0,0 +1,117 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> + +#include <xcb/xcb.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/i18n.h> +#include <pulsecore/x11prop.h> +#include <pulsecore/log.h> +#include <pulsecore/core-util.h> +#include <pulsecore/macro.h> + +#include "client-conf-x11.h" + +int pa_client_conf_from_x11(pa_client_conf *c) { + const char *dname; + xcb_connection_t *xcb = NULL; + int ret = -1, screen = 0; + char t[1024]; + + pa_assert(c); + + /* Local connections will have configuration and X root window + * properties match 1:1, these paths are only strictly necessary + * for remote clients, so check for SSH_CONNECTION to make sure + * this is a remote session with X forwarding. + */ + if (!getenv("SSH_CONNECTION")) + goto finish; + + if (!(dname = getenv("DISPLAY"))) + goto finish; + + if (*dname == 0) + goto finish; + + if (!(xcb = xcb_connect(dname, NULL))) { + pa_log(_("xcb_connect() failed")); + goto finish; + } + + if (xcb_connection_has_error(xcb)) { + pa_log(_("xcb_connection_has_error() returned true")); + goto finish; + } + + if (pa_x11_get_prop(xcb, screen, "PULSE_SERVER", t, sizeof(t))) { + bool disable_autospawn = true; + + pa_xfree(c->default_server); + c->default_server = pa_xstrdup(t); + + if (pa_x11_get_prop(xcb, screen, "PULSE_SESSION_ID", t, sizeof(t))) { + char *id; + + if ((id = pa_session_id())) { + if (pa_streq(t, id)) + disable_autospawn = false; + pa_xfree(id); + } + } + + if (disable_autospawn) + c->autospawn = false; + } + + if (pa_x11_get_prop(xcb, screen, "PULSE_SINK", t, sizeof(t))) { + pa_xfree(c->default_sink); + c->default_sink = pa_xstrdup(t); + } + + if (pa_x11_get_prop(xcb, screen, "PULSE_SOURCE", t, sizeof(t))) { + pa_xfree(c->default_source); + c->default_source = pa_xstrdup(t); + } + + if (pa_x11_get_prop(xcb, screen, "PULSE_COOKIE", t, sizeof(t))) { + if (pa_parsehex(t, c->cookie_from_x11, sizeof(c->cookie_from_x11)) != sizeof(c->cookie_from_x11)) { + pa_log(_("Failed to parse cookie data")); + goto finish; + } + + c->cookie_from_x11_valid = true; + } + + ret = 0; + +finish: + if (xcb) + xcb_disconnect(xcb); + + return ret; + +} diff --git a/src/pulse/client-conf-x11.h b/src/pulse/client-conf-x11.h new file mode 100644 index 0000000..4f82041 --- /dev/null +++ b/src/pulse/client-conf-x11.h @@ -0,0 +1,29 @@ +#ifndef fooclientconfx11hfoo +#define fooclientconfx11hfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include "client-conf.h" + +/* Load client configuration data from X11, overwriting the current settings in + * *c. */ +int pa_client_conf_from_x11(pa_client_conf *c); + +#endif diff --git a/src/pulse/client-conf.c b/src/pulse/client-conf.c new file mode 100644 index 0000000..1daaf91 --- /dev/null +++ b/src/pulse/client-conf.c @@ -0,0 +1,240 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/i18n.h> +#include <pulsecore/macro.h> +#include <pulsecore/core-error.h> +#include <pulsecore/log.h> +#include <pulsecore/conf-parser.h> +#include <pulsecore/core-util.h> +#include <pulsecore/authkey.h> + +#include "client-conf.h" + +#ifdef HAVE_X11 +#include <pulse/client-conf-x11.h> +#endif + +#define DEFAULT_CLIENT_CONFIG_FILE PA_DEFAULT_CONFIG_DIR PA_PATH_SEP "client.conf" +#define DEFAULT_CLIENT_CONFIG_FILE_USER "client.conf" + +#define ENV_CLIENT_CONFIG_FILE "PULSE_CLIENTCONFIG" +#define ENV_DEFAULT_SINK "PULSE_SINK" +#define ENV_DEFAULT_SOURCE "PULSE_SOURCE" +#define ENV_DEFAULT_SERVER "PULSE_SERVER" +#define ENV_DAEMON_BINARY "PULSE_BINARY" +#define ENV_COOKIE_FILE "PULSE_COOKIE" + +static const pa_client_conf default_conf = { + .daemon_binary = NULL, + .extra_arguments = NULL, + .default_sink = NULL, + .default_source = NULL, + .default_server = NULL, + .default_dbus_server = NULL, + .cookie_file_from_env = NULL, + .cookie_from_x11_valid = false, + .cookie_file_from_application = NULL, + .cookie_file_from_client_conf = NULL, + .autospawn = true, + .disable_shm = false, + .disable_memfd = false, + .shm_size = 0, + .auto_connect_localhost = false, + .auto_connect_display = false +}; + +pa_client_conf *pa_client_conf_new(void) { + pa_client_conf *c = pa_xmemdup(&default_conf, sizeof(default_conf)); + + c->daemon_binary = pa_xstrdup(PA_BINARY); + c->extra_arguments = pa_xstrdup("--log-target=syslog"); + + return c; +} + +void pa_client_conf_free(pa_client_conf *c) { + pa_assert(c); + pa_xfree(c->daemon_binary); + pa_xfree(c->extra_arguments); + pa_xfree(c->default_sink); + pa_xfree(c->default_source); + pa_xfree(c->default_server); + pa_xfree(c->default_dbus_server); + pa_xfree(c->cookie_file_from_env); + pa_xfree(c->cookie_file_from_application); + pa_xfree(c->cookie_file_from_client_conf); + pa_xfree(c); +} + +static void load_env(pa_client_conf *c) { + char *e; + + if ((e = getenv(ENV_DEFAULT_SINK))) { + pa_xfree(c->default_sink); + c->default_sink = pa_xstrdup(e); + } + + if ((e = getenv(ENV_DEFAULT_SOURCE))) { + pa_xfree(c->default_source); + c->default_source = pa_xstrdup(e); + } + + if ((e = getenv(ENV_DEFAULT_SERVER))) { + pa_xfree(c->default_server); + c->default_server = pa_xstrdup(e); + + /* We disable autospawning automatically if a specific server was set */ + c->autospawn = false; + } + + if ((e = getenv(ENV_DAEMON_BINARY))) { + pa_xfree(c->daemon_binary); + c->daemon_binary = pa_xstrdup(e); + } + + if ((e = getenv(ENV_COOKIE_FILE)) && *e) { + pa_xfree(c->cookie_file_from_env); + c->cookie_file_from_env = pa_xstrdup(e); + } +} + +void pa_client_conf_load(pa_client_conf *c, bool load_from_x11, bool load_from_env) { + FILE *f = NULL; + char *fn = NULL; + + /* Prepare the configuration parse table */ + pa_config_item table[] = { + { "daemon-binary", pa_config_parse_string, &c->daemon_binary, NULL }, + { "extra-arguments", pa_config_parse_string, &c->extra_arguments, NULL }, + { "default-sink", pa_config_parse_string, &c->default_sink, NULL }, + { "default-source", pa_config_parse_string, &c->default_source, NULL }, + { "default-server", pa_config_parse_string, &c->default_server, NULL }, + { "default-dbus-server", pa_config_parse_string, &c->default_dbus_server, NULL }, + { "autospawn", pa_config_parse_bool, &c->autospawn, NULL }, + { "cookie-file", pa_config_parse_string, &c->cookie_file_from_client_conf, NULL }, + { "disable-shm", pa_config_parse_bool, &c->disable_shm, NULL }, + { "enable-shm", pa_config_parse_not_bool, &c->disable_shm, NULL }, + { "enable-memfd", pa_config_parse_not_bool, &c->disable_memfd, NULL }, + { "shm-size-bytes", pa_config_parse_size, &c->shm_size, NULL }, + { "auto-connect-localhost", pa_config_parse_bool, &c->auto_connect_localhost, NULL }, + { "auto-connect-display", pa_config_parse_bool, &c->auto_connect_display, NULL }, + { NULL, NULL, NULL, NULL }, + }; + + f = pa_open_config_file(DEFAULT_CLIENT_CONFIG_FILE, DEFAULT_CLIENT_CONFIG_FILE_USER, ENV_CLIENT_CONFIG_FILE, &fn); + if (f) { + pa_config_parse(fn, f, table, NULL, true, NULL); + pa_xfree(fn); + fclose(f); + } + + if (load_from_x11) { +#ifdef HAVE_X11 + pa_client_conf_from_x11(c); +#endif + } + + if (load_from_env) + load_env(c); +} + +int pa_client_conf_load_cookie(pa_client_conf *c, uint8_t *cookie, size_t cookie_length) { + int r; + char *fallback_path; + + pa_assert(c); + pa_assert(cookie); + pa_assert(cookie_length > 0); + + if (c->cookie_file_from_env) { + r = pa_authkey_load(c->cookie_file_from_env, true, cookie, cookie_length); + if (r >= 0) + return 0; + + pa_log_warn("Failed to load cookie from %s (configured with environment variable PULSE_COOKIE): %s", + c->cookie_file_from_env, pa_cstrerror(errno)); + } + + if (c->cookie_from_x11_valid) { + if (cookie_length == sizeof(c->cookie_from_x11)) { + memcpy(cookie, c->cookie_from_x11, cookie_length); + return 0; + } + + pa_log_warn("Failed to load cookie from X11 root window property PULSE_COOKIE: size mismatch."); + } + + if (c->cookie_file_from_application) { + r = pa_authkey_load(c->cookie_file_from_application, true, cookie, cookie_length); + if (r >= 0) + return 0; + + pa_log_warn("Failed to load cookie from %s (configured by the application): %s", c->cookie_file_from_application, + pa_cstrerror(errno)); + } + + if (c->cookie_file_from_client_conf) { + r = pa_authkey_load(c->cookie_file_from_client_conf, true, cookie, cookie_length); + if (r >= 0) + return 0; + + pa_log_warn("Failed to load cookie from %s (configured in client.conf): %s", c->cookie_file_from_client_conf, + pa_cstrerror(errno)); + } + + r = pa_authkey_load(PA_NATIVE_COOKIE_FILE, false, cookie, cookie_length); + if (r >= 0) + return 0; + + if (pa_append_to_home_dir(PA_NATIVE_COOKIE_FILE_FALLBACK, &fallback_path) >= 0) { + r = pa_authkey_load(fallback_path, false, cookie, cookie_length); + pa_xfree(fallback_path); + if (r >= 0) + return 0; + } + + r = pa_authkey_load(PA_NATIVE_COOKIE_FILE, true, cookie, cookie_length); + if (r >= 0) + return 0; + + pa_log("Failed to load cookie file from %s: %s", PA_NATIVE_COOKIE_FILE, pa_cstrerror(errno)); + memset(cookie, 0, cookie_length); + + return -1; +} + +void pa_client_conf_set_cookie_file_from_application(pa_client_conf *c, const char *cookie_file) { + pa_assert(c); + pa_assert(!cookie_file || *cookie_file); + + pa_xfree(c->cookie_file_from_application); + c->cookie_file_from_application = pa_xstrdup(cookie_file); +} diff --git a/src/pulse/client-conf.h b/src/pulse/client-conf.h new file mode 100644 index 0000000..7691ec7 --- /dev/null +++ b/src/pulse/client-conf.h @@ -0,0 +1,61 @@ +#ifndef fooclientconfhfoo +#define fooclientconfhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulsecore/macro.h> +#include <pulsecore/native-common.h> + +/* A structure containing configuration data for PulseAudio clients. */ + +typedef struct pa_client_conf { + char *daemon_binary; + char *extra_arguments; + char *default_sink; + char *default_source; + char *default_server; + char *default_dbus_server; + char *cookie_file_from_env; + uint8_t cookie_from_x11[PA_NATIVE_COOKIE_LENGTH]; + bool cookie_from_x11_valid; + char *cookie_file_from_application; + char *cookie_file_from_client_conf; + bool autospawn, disable_shm, disable_memfd, auto_connect_localhost, auto_connect_display; + size_t shm_size; +} pa_client_conf; + +/* Create a new configuration data object and reset it to defaults */ +pa_client_conf *pa_client_conf_new(void); +void pa_client_conf_free(pa_client_conf *c); + +/* Load the configuration data from the client configuration file and + * optionally from X11 and/or environment variables, overwriting the current + * settings in *c. */ +void pa_client_conf_load(pa_client_conf *c, bool load_from_x11, bool load_from_env); + +/* Load the cookie from the cookie sources specified in the configuration, or + * if nothing is specified or none of the sources work, load the cookie from + * the default source. If the default source doesn't work either, this function + * returns a negative value and initializes the cookie to all-zeroes. */ +int pa_client_conf_load_cookie(pa_client_conf *c, uint8_t *cookie, size_t cookie_length); + +void pa_client_conf_set_cookie_file_from_application(pa_client_conf *c, const char *cookie_file); + +#endif diff --git a/src/pulse/client.conf.in b/src/pulse/client.conf.in new file mode 100644 index 0000000..26b7790 --- /dev/null +++ b/src/pulse/client.conf.in @@ -0,0 +1,35 @@ +# This file is part of PulseAudio. +# +# PulseAudio is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# PulseAudio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. + +## Configuration file for PulseAudio clients. See pulse-client.conf(5) for +## more information. Default values are commented out. Use either ; or # for +## commenting. + +; default-sink = +; default-source = +; default-server = +; default-dbus-server = + +; autospawn = yes +; daemon-binary = @PA_BINARY@ +; extra-arguments = --log-target=syslog + +; cookie-file = + +; enable-shm = yes +; shm-size-bytes = 0 # setting this 0 will use the system-default, usually 64 MiB + +; auto-connect-localhost = no +; auto-connect-display = no diff --git a/src/pulse/context.c b/src/pulse/context.c new file mode 100644 index 0000000..1d1bb9e --- /dev/null +++ b/src/pulse/context.c @@ -0,0 +1,1650 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2008 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <unistd.h> +#include <sys/stat.h> +#include <errno.h> +#include <signal.h> + +#ifdef HAVE_SYS_WAIT_H +#include <sys/wait.h> +#endif + +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif + +#include <pulse/version.h> +#include <pulse/xmalloc.h> +#include <pulse/util.h> +#include <pulse/mainloop.h> +#include <pulse/timeval.h> +#include <pulse/fork-detect.h> +#include <pulse/client-conf.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/i18n.h> +#include <pulsecore/native-common.h> +#include <pulsecore/pdispatch.h> +#include <pulsecore/pstream.h> +#include <pulsecore/hashmap.h> +#include <pulsecore/socket-client.h> +#include <pulsecore/pstream-util.h> +#include <pulsecore/core-rtclock.h> +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> +#include <pulsecore/socket.h> +#include <pulsecore/creds.h> +#include <pulsecore/macro.h> +#include <pulsecore/proplist-util.h> + +#include "internal.h" +#include "context.h" + +void pa_command_extension(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void pa_command_enable_srbchannel(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void pa_command_disable_srbchannel(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void pa_command_register_memfd_shmid(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); + +static const pa_pdispatch_cb_t command_table[PA_COMMAND_MAX] = { + [PA_COMMAND_REQUEST] = pa_command_request, + [PA_COMMAND_OVERFLOW] = pa_command_overflow_or_underflow, + [PA_COMMAND_UNDERFLOW] = pa_command_overflow_or_underflow, + [PA_COMMAND_PLAYBACK_STREAM_KILLED] = pa_command_stream_killed, + [PA_COMMAND_RECORD_STREAM_KILLED] = pa_command_stream_killed, + [PA_COMMAND_PLAYBACK_STREAM_MOVED] = pa_command_stream_moved, + [PA_COMMAND_RECORD_STREAM_MOVED] = pa_command_stream_moved, + [PA_COMMAND_PLAYBACK_STREAM_SUSPENDED] = pa_command_stream_suspended, + [PA_COMMAND_RECORD_STREAM_SUSPENDED] = pa_command_stream_suspended, + [PA_COMMAND_STARTED] = pa_command_stream_started, + [PA_COMMAND_SUBSCRIBE_EVENT] = pa_command_subscribe_event, + [PA_COMMAND_EXTENSION] = pa_command_extension, + [PA_COMMAND_PLAYBACK_STREAM_EVENT] = pa_command_stream_event, + [PA_COMMAND_RECORD_STREAM_EVENT] = pa_command_stream_event, + [PA_COMMAND_CLIENT_EVENT] = pa_command_client_event, + [PA_COMMAND_PLAYBACK_BUFFER_ATTR_CHANGED] = pa_command_stream_buffer_attr, + [PA_COMMAND_RECORD_BUFFER_ATTR_CHANGED] = pa_command_stream_buffer_attr, + [PA_COMMAND_ENABLE_SRBCHANNEL] = pa_command_enable_srbchannel, + [PA_COMMAND_DISABLE_SRBCHANNEL] = pa_command_disable_srbchannel, + [PA_COMMAND_REGISTER_MEMFD_SHMID] = pa_command_register_memfd_shmid, +}; +static void context_free(pa_context *c); + +#ifdef HAVE_DBUS +static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, void *userdata); +#endif + +pa_context *pa_context_new(pa_mainloop_api *mainloop, const char *name) { + return pa_context_new_with_proplist(mainloop, name, NULL); +} + +static void reset_callbacks(pa_context *c) { + pa_assert(c); + + c->state_callback = NULL; + c->state_userdata = NULL; + + c->subscribe_callback = NULL; + c->subscribe_userdata = NULL; + + c->event_callback = NULL; + c->event_userdata = NULL; + + c->ext_device_manager.callback = NULL; + c->ext_device_manager.userdata = NULL; + + c->ext_device_restore.callback = NULL; + c->ext_device_restore.userdata = NULL; + + c->ext_stream_restore.callback = NULL; + c->ext_stream_restore.userdata = NULL; +} + +pa_context *pa_context_new_with_proplist(pa_mainloop_api *mainloop, const char *name, const pa_proplist *p) { + pa_context *c; + pa_mem_type_t type; + + pa_assert(mainloop); + + if (pa_detect_fork()) + return NULL; + + pa_init_i18n(); + + c = pa_xnew0(pa_context, 1); + PA_REFCNT_INIT(c); + + c->error = pa_xnew0(pa_context_error, 1); + assert(c->error); + + c->proplist = p ? pa_proplist_copy(p) : pa_proplist_new(); + + if (name) + pa_proplist_sets(c->proplist, PA_PROP_APPLICATION_NAME, name); + +#ifdef HAVE_DBUS + c->system_bus = c->session_bus = NULL; +#endif + c->mainloop = mainloop; + c->playback_streams = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + c->record_streams = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + c->client_index = PA_INVALID_INDEX; + c->use_rtclock = pa_mainloop_is_our_api(mainloop); + + PA_LLIST_HEAD_INIT(pa_stream, c->streams); + PA_LLIST_HEAD_INIT(pa_operation, c->operations); + + c->error->error = PA_OK; + c->state = PA_CONTEXT_UNCONNECTED; + + reset_callbacks(c); + +#ifndef MSG_NOSIGNAL +#ifdef SIGPIPE + pa_check_signal_is_blocked(SIGPIPE); +#endif +#endif + + c->conf = pa_client_conf_new(); + pa_client_conf_load(c->conf, true, true); + + c->srb_template.readfd = -1; + c->srb_template.writefd = -1; + + c->memfd_on_local = (!c->conf->disable_memfd && pa_memfd_is_locally_supported()); + + type = (c->conf->disable_shm) ? PA_MEM_TYPE_PRIVATE : + ((!c->memfd_on_local) ? + PA_MEM_TYPE_SHARED_POSIX : PA_MEM_TYPE_SHARED_MEMFD); + + if (!(c->mempool = pa_mempool_new(type, c->conf->shm_size, true))) { + + if (!c->conf->disable_shm) { + pa_log_warn("Failed to allocate shared memory pool. Falling back to a normal private one."); + c->mempool = pa_mempool_new(PA_MEM_TYPE_PRIVATE, c->conf->shm_size, true); + } + + if (!c->mempool) { + context_free(c); + return NULL; + } + } + + return c; +} + +static void context_unlink(pa_context *c) { + pa_stream *s; + + pa_assert(c); + + s = c->streams ? pa_stream_ref(c->streams) : NULL; + while (s) { + pa_stream *n = s->next ? pa_stream_ref(s->next) : NULL; + pa_stream_set_state(s, c->state == PA_CONTEXT_FAILED ? PA_STREAM_FAILED : PA_STREAM_TERMINATED); + pa_stream_unref(s); + s = n; + } + + while (c->operations) + pa_operation_cancel(c->operations); + + if (c->pdispatch) { + pa_pdispatch_unref(c->pdispatch); + c->pdispatch = NULL; + } + + if (c->pstream) { + pa_pstream_unlink(c->pstream); + pa_pstream_unref(c->pstream); + c->pstream = NULL; + } + + if (c->srb_template.memblock) { + pa_memblock_unref(c->srb_template.memblock); + c->srb_template.memblock = NULL; + } + + if (c->client) { + pa_socket_client_unref(c->client); + c->client = NULL; + } + + reset_callbacks(c); +} + +static void context_free(pa_context *c) { + pa_assert(c); + + context_unlink(c); + +#ifdef HAVE_DBUS + if (c->system_bus) { + if (c->filter_added) + dbus_connection_remove_filter(pa_dbus_wrap_connection_get(c->system_bus), filter_cb, c); + pa_dbus_wrap_connection_free(c->system_bus); + } + + if (c->session_bus) { + if (c->filter_added) + dbus_connection_remove_filter(pa_dbus_wrap_connection_get(c->session_bus), filter_cb, c); + pa_dbus_wrap_connection_free(c->session_bus); + } +#endif + + if (c->record_streams) + pa_hashmap_free(c->record_streams); + if (c->playback_streams) + pa_hashmap_free(c->playback_streams); + + if (c->mempool) + pa_mempool_unref(c->mempool); + + if (c->conf) + pa_client_conf_free(c->conf); + + pa_strlist_free(c->server_list); + + if (c->proplist) + pa_proplist_free(c->proplist); + + pa_xfree(c->server); + pa_xfree(c->error); + pa_xfree(c); +} + +pa_context* pa_context_ref(pa_context *c) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_REFCNT_INC(c); + return c; +} + +void pa_context_unref(pa_context *c) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + if (PA_REFCNT_DEC(c) <= 0) + context_free(c); +} + +void pa_context_set_state(pa_context *c, pa_context_state_t st) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + if (c->state == st) + return; + + pa_context_ref(c); + + c->state = st; + + if (c->state_callback) + c->state_callback(c, c->state_userdata); + + if (st == PA_CONTEXT_FAILED || st == PA_CONTEXT_TERMINATED) + context_unlink(c); + + pa_context_unref(c); +} + +int pa_context_set_error(const pa_context *c, int error) { + pa_assert(error >= 0); + pa_assert(error < PA_ERR_MAX); + + if (c) + c->error->error = error; + + return error; +} + +void pa_context_fail(pa_context *c, int error) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + pa_context_set_error(c, error); + pa_context_set_state(c, PA_CONTEXT_FAILED); +} + +static void pstream_die_callback(pa_pstream *p, void *userdata) { + pa_context *c = userdata; + + pa_assert(p); + pa_assert(c); + + pa_context_fail(c, PA_ERR_CONNECTIONTERMINATED); +} + +static void pstream_packet_callback(pa_pstream *p, pa_packet *packet, pa_cmsg_ancil_data *ancil_data, void *userdata) { + pa_context *c = userdata; + + pa_assert(p); + pa_assert(packet); + pa_assert(c); + + pa_context_ref(c); + + if (pa_pdispatch_run(c->pdispatch, packet, ancil_data, c) < 0) + pa_context_fail(c, PA_ERR_PROTOCOL); + + pa_context_unref(c); +} + +static void handle_srbchannel_memblock(pa_context *c, pa_memblock *memblock) { + pa_srbchannel *sr; + pa_tagstruct *t; + + pa_assert(c); + + /* Memblock sanity check */ + if (!memblock) { + pa_context_fail(c, PA_ERR_PROTOCOL); + return; + } else if (pa_memblock_is_read_only(memblock)) { + pa_context_fail(c, PA_ERR_PROTOCOL); + return; + } else if (pa_memblock_is_ours(memblock)) { + pa_context_fail(c, PA_ERR_PROTOCOL); + return; + } + + /* Create the srbchannel */ + c->srb_template.memblock = memblock; + pa_memblock_ref(memblock); + sr = pa_srbchannel_new_from_template(c->mainloop, &c->srb_template); + if (!sr) { + pa_log_warn("Failed to create srbchannel from template"); + c->srb_template.readfd = -1; + c->srb_template.writefd = -1; + pa_memblock_unref(c->srb_template.memblock); + c->srb_template.memblock = NULL; + return; + } + + /* Ack the enable command */ + t = pa_tagstruct_new(); + pa_tagstruct_putu32(t, PA_COMMAND_ENABLE_SRBCHANNEL); + pa_tagstruct_putu32(t, c->srb_setup_tag); + pa_pstream_send_tagstruct(c->pstream, t); + + /* ...and switch over */ + pa_pstream_set_srbchannel(c->pstream, sr); +} + +static void pstream_memblock_callback(pa_pstream *p, uint32_t channel, int64_t offset, pa_seek_mode_t seek, const pa_memchunk *chunk, void *userdata) { + pa_context *c = userdata; + pa_stream *s; + + pa_assert(p); + pa_assert(chunk); + pa_assert(chunk->length > 0); + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + pa_context_ref(c); + + if (c->srb_template.readfd != -1 && c->srb_template.memblock == NULL) { + handle_srbchannel_memblock(c, chunk->memblock); + pa_context_unref(c); + return; + } + + if ((s = pa_hashmap_get(c->record_streams, PA_UINT32_TO_PTR(channel)))) { + + if (chunk->memblock) { + pa_memblockq_seek(s->record_memblockq, offset, seek, true); + pa_memblockq_push_align(s->record_memblockq, chunk); + } else + pa_memblockq_seek(s->record_memblockq, offset+chunk->length, seek, true); + + if (s->read_callback) { + size_t l; + + if ((l = pa_memblockq_get_length(s->record_memblockq)) > 0) + s->read_callback(s, l, s->read_userdata); + } + } + + pa_context_unref(c); +} + +int pa_context_handle_error(pa_context *c, uint32_t command, pa_tagstruct *t, bool fail) { + uint32_t err; + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + if (command == PA_COMMAND_ERROR) { + pa_assert(t); + + if (pa_tagstruct_getu32(t, &err) < 0 || + !pa_tagstruct_eof(t)) { + pa_context_fail(c, PA_ERR_PROTOCOL); + return -1; + } + + } else if (command == PA_COMMAND_TIMEOUT) + err = PA_ERR_TIMEOUT; + else { + pa_context_fail(c, PA_ERR_PROTOCOL); + return -1; + } + + if (err == PA_OK) { + pa_context_fail(c, PA_ERR_PROTOCOL); + return -1; + } + + if (err >= PA_ERR_MAX) + err = PA_ERR_UNKNOWN; + + if (fail) { + pa_context_fail(c, (int) err); + return -1; + } + + pa_context_set_error(c, (int) err); + + return 0; +} + +static void setup_complete_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_context *c = userdata; + + pa_assert(pd); + pa_assert(c); + pa_assert(c->state == PA_CONTEXT_AUTHORIZING || c->state == PA_CONTEXT_SETTING_NAME); + + pa_context_ref(c); + + if (command != PA_COMMAND_REPLY) { + pa_context_handle_error(c, command, t, true); + goto finish; + } + + switch(c->state) { + case PA_CONTEXT_AUTHORIZING: { + pa_tagstruct *reply; + bool shm_on_remote = false; + bool memfd_on_remote = false; + + if (pa_tagstruct_getu32(t, &c->version) < 0 || + !pa_tagstruct_eof(t)) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + + /* Minimum supported version */ + if (c->version < 8) { + pa_context_fail(c, PA_ERR_VERSION); + goto finish; + } + + /* Starting with protocol version 13 the MSB of the version + tag reflects if shm is available for this connection or + not. */ + if ((c->version & PA_PROTOCOL_VERSION_MASK) >= 13) { + shm_on_remote = !!(c->version & PA_PROTOCOL_FLAG_SHM); + + /* Starting with protocol version 31, the second MSB of the version + * tag reflects whether memfd is supported on the other PA end. */ + if ((c->version & PA_PROTOCOL_VERSION_MASK) >= 31) + memfd_on_remote = !!(c->version & PA_PROTOCOL_FLAG_MEMFD); + + /* Reserve the two most-significant _bytes_ of the version tag + * for flags. */ + c->version &= PA_PROTOCOL_VERSION_MASK; + } + + pa_log_debug("Protocol version: remote %u, local %u", c->version, PA_PROTOCOL_VERSION); + + /* Enable shared memory support if possible */ + if (c->do_shm) + if (c->version < 10 || (c->version >= 13 && !shm_on_remote)) + c->do_shm = false; + + if (c->do_shm) { + + /* Only enable SHM if both sides are owned by the same + * user. This is a security measure because otherwise + * data private to the user might leak. */ + +#ifdef HAVE_CREDS + const pa_creds *creds; + if (!(creds = pa_pdispatch_creds(pd)) || getuid() != creds->uid) + c->do_shm = false; +#endif + } + + pa_log_debug("Negotiated SHM: %s", pa_yes_no(c->do_shm)); + pa_pstream_enable_shm(c->pstream, c->do_shm); + + c->shm_type = PA_MEM_TYPE_PRIVATE; + if (c->do_shm) { + if (c->version >= 31 && memfd_on_remote && c->memfd_on_local) { + const char *reason; + + pa_pstream_enable_memfd(c->pstream); + if (pa_mempool_is_memfd_backed(c->mempool)) + if (pa_pstream_register_memfd_mempool(c->pstream, c->mempool, &reason)) + pa_log("Failed to regester memfd mempool. Reason: %s", reason); + + /* Even if memfd pool registration fails, the negotiated SHM type + * shall remain memfd as both endpoints claim to support it. */ + c->shm_type = PA_MEM_TYPE_SHARED_MEMFD; + } else + c->shm_type = PA_MEM_TYPE_SHARED_POSIX; + } + + pa_log_debug("Memfd possible: %s", pa_yes_no(c->memfd_on_local)); + pa_log_debug("Negotiated SHM type: %s", pa_mem_type_to_string(c->shm_type)); + + reply = pa_tagstruct_command(c, PA_COMMAND_SET_CLIENT_NAME, &tag); + + if (c->version >= 13) { + pa_init_proplist(c->proplist); + pa_tagstruct_put_proplist(reply, c->proplist); + } else + pa_tagstruct_puts(reply, pa_proplist_gets(c->proplist, PA_PROP_APPLICATION_NAME)); + + pa_pstream_send_tagstruct(c->pstream, reply); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, setup_complete_callback, c, NULL); + + pa_context_set_state(c, PA_CONTEXT_SETTING_NAME); + break; + } + + case PA_CONTEXT_SETTING_NAME : + + if ((c->version >= 13 && (pa_tagstruct_getu32(t, &c->client_index) < 0 || + c->client_index == PA_INVALID_INDEX)) || + !pa_tagstruct_eof(t)) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + + pa_context_set_state(c, PA_CONTEXT_READY); + break; + + default: + pa_assert_not_reached(); + } + +finish: + pa_context_unref(c); +} + +static void setup_context(pa_context *c, pa_iochannel *io) { + uint8_t cookie[PA_NATIVE_COOKIE_LENGTH]; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(io); + + pa_context_ref(c); + + pa_assert(!c->pstream); + c->pstream = pa_pstream_new(c->mainloop, io, c->mempool); + + pa_pstream_set_die_callback(c->pstream, pstream_die_callback, c); + pa_pstream_set_receive_packet_callback(c->pstream, pstream_packet_callback, c); + pa_pstream_set_receive_memblock_callback(c->pstream, pstream_memblock_callback, c); + + pa_assert(!c->pdispatch); + c->pdispatch = pa_pdispatch_new(c->mainloop, c->use_rtclock, command_table, PA_COMMAND_MAX); + + if (pa_client_conf_load_cookie(c->conf, cookie, sizeof(cookie)) < 0) + pa_log_info("No cookie loaded. Attempting to connect without."); + + t = pa_tagstruct_command(c, PA_COMMAND_AUTH, &tag); + + c->do_shm = + pa_mempool_is_shared(c->mempool) && + c->is_local; + + pa_log_debug("SHM possible: %s", pa_yes_no(c->do_shm)); + + /* Starting with protocol version 13 we use the MSB of the version + * tag for informing the other side if we could do SHM or not. + * Starting from version 31, second MSB is used to flag memfd support. */ + pa_tagstruct_putu32(t, PA_PROTOCOL_VERSION | (c->do_shm ? PA_PROTOCOL_FLAG_SHM : 0) | + (c->memfd_on_local ? PA_PROTOCOL_FLAG_MEMFD: 0)); + pa_tagstruct_put_arbitrary(t, cookie, sizeof(cookie)); + +#ifdef HAVE_CREDS +{ + pa_creds ucred; + + if (pa_iochannel_creds_supported(io)) + pa_iochannel_creds_enable(io); + + ucred.uid = getuid(); + ucred.gid = getgid(); + + pa_pstream_send_tagstruct_with_creds(c->pstream, t, &ucred); +} +#else + pa_pstream_send_tagstruct(c->pstream, t); +#endif + + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, setup_complete_callback, c, NULL); + + pa_context_set_state(c, PA_CONTEXT_AUTHORIZING); + + pa_context_unref(c); +} + +static pa_strlist *prepend_per_user(pa_strlist *l) { + char *ufn; + + /* The per-user instance */ + if ((ufn = pa_runtime_path(PA_NATIVE_DEFAULT_UNIX_SOCKET))) { + l = pa_strlist_prepend(l, ufn); + pa_xfree(ufn); + } + + return l; +} + +#ifndef OS_IS_WIN32 + +static int context_autospawn(pa_context *c) { + pid_t pid; + int status, r; + struct sigaction sa; + + pa_context_ref(c); + + if (sigaction(SIGCHLD, NULL, &sa) < 0) { + pa_log_debug("sigaction() failed: %s", pa_cstrerror(errno)); + pa_context_fail(c, PA_ERR_INTERNAL); + goto fail; + } + +#ifdef SA_NOCLDWAIT + if ((sa.sa_flags & SA_NOCLDWAIT) || sa.sa_handler == SIG_IGN) { +#else + if (sa.sa_handler == SIG_IGN) { +#endif + pa_log_debug("Process disabled waitpid(), cannot autospawn."); + pa_context_fail(c, PA_ERR_CONNECTIONREFUSED); + goto fail; + } + + pa_log_debug("Trying to autospawn..."); + + if (c->spawn_api.prefork) + c->spawn_api.prefork(); + + if ((pid = fork()) < 0) { + pa_log_error(_("fork(): %s"), pa_cstrerror(errno)); + pa_context_fail(c, PA_ERR_INTERNAL); + + if (c->spawn_api.postfork) + c->spawn_api.postfork(); + + goto fail; + } else if (!pid) { + /* Child */ + + const char *state = NULL; + const char * argv[32]; + unsigned n = 0; + + if (c->spawn_api.atfork) + c->spawn_api.atfork(); + + /* We leave most of the cleaning up of the process environment + * to the executable. We only clean up the file descriptors to + * make sure the executable can actually be loaded + * correctly. */ + pa_close_all(-1); + + /* Setup argv */ + argv[n++] = c->conf->daemon_binary; + argv[n++] = "--start"; + + while (n < PA_ELEMENTSOF(argv)-1) { + char *a; + + if (!(a = pa_split_spaces(c->conf->extra_arguments, &state))) + break; + + argv[n++] = a; + } + + argv[n++] = NULL; + pa_assert(n <= PA_ELEMENTSOF(argv)); + + execv(argv[0], (char * const *) argv); + _exit(1); + } + + /* Parent */ + + if (c->spawn_api.postfork) + c->spawn_api.postfork(); + + do { + r = waitpid(pid, &status, 0); + } while (r < 0 && errno == EINTR); + + if (r < 0) { + + if (errno != ECHILD) { + pa_log(_("waitpid(): %s"), pa_cstrerror(errno)); + pa_context_fail(c, PA_ERR_INTERNAL); + goto fail; + } + + /* hmm, something already reaped our child, so we assume + * startup worked, even if we cannot know */ + + } else if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + pa_context_fail(c, PA_ERR_CONNECTIONREFUSED); + goto fail; + } + + pa_context_unref(c); + + return 0; + +fail: + + pa_context_unref(c); + + return -1; +} + +#endif /* OS_IS_WIN32 */ + +static void on_connection(pa_socket_client *client, pa_iochannel*io, void *userdata); + +#ifdef HAVE_DBUS +static void track_pulseaudio_on_dbus(pa_context *c, DBusBusType type, pa_dbus_wrap_connection **conn) { + DBusError error; + + pa_assert(c); + pa_assert(conn); + + dbus_error_init(&error); + + if (!(*conn = pa_dbus_wrap_connection_new(c->mainloop, c->use_rtclock, type, &error)) || dbus_error_is_set(&error)) { + pa_log_warn("Unable to contact DBUS: %s: %s", error.name, error.message); + goto fail; + } + + if (!dbus_connection_add_filter(pa_dbus_wrap_connection_get(*conn), filter_cb, c, NULL)) { + pa_log_warn("Failed to add filter function"); + goto fail; + } + c->filter_added = true; + + if (pa_dbus_add_matches( + pa_dbus_wrap_connection_get(*conn), &error, + "type='signal',sender='" DBUS_SERVICE_DBUS "',interface='" DBUS_INTERFACE_DBUS "',member='NameOwnerChanged',arg0='org.pulseaudio.Server',arg1=''", NULL) < 0) { + + pa_log_warn("Unable to track org.pulseaudio.Server: %s: %s", error.name, error.message); + goto fail; + } + + return; + +fail: + if (*conn) { + pa_dbus_wrap_connection_free(*conn); + *conn = NULL; + } + + dbus_error_free(&error); +} +#endif + +static int try_next_connection(pa_context *c) { + char *u = NULL; + int r = -1; + + pa_assert(c); + pa_assert(!c->client); + + for (;;) { + pa_xfree(u); + u = NULL; + + c->server_list = pa_strlist_pop(c->server_list, &u); + + if (!u) { + +#ifndef OS_IS_WIN32 + if (c->do_autospawn) { + + if ((r = context_autospawn(c)) < 0) + goto finish; + + /* Autospawn only once */ + c->do_autospawn = false; + + /* Connect only to per-user sockets this time */ + c->server_list = prepend_per_user(c->server_list); + + /* Retry connection */ + continue; + } +#endif + +#ifdef HAVE_DBUS + if (c->no_fail && !c->server_specified) { + if (!c->session_bus) + track_pulseaudio_on_dbus(c, DBUS_BUS_SESSION, &c->session_bus); + if (!c->system_bus) + track_pulseaudio_on_dbus(c, DBUS_BUS_SYSTEM, &c->system_bus); + + if (c->session_bus || c->system_bus) { + pa_log_debug("Waiting for PA on D-Bus..."); + break; + } + } else +#endif + pa_context_fail(c, PA_ERR_CONNECTIONREFUSED); + + goto finish; + } + + pa_log_debug("Trying to connect to %s...", u); + + pa_xfree(c->server); + c->server = pa_xstrdup(u); + + if (!(c->client = pa_socket_client_new_string(c->mainloop, c->use_rtclock, u, PA_NATIVE_DEFAULT_PORT))) + continue; + + c->is_local = pa_socket_client_is_local(c->client); + pa_socket_client_set_callback(c->client, on_connection, c); + break; + } + + r = 0; + +finish: + pa_xfree(u); + + return r; +} + +static void on_connection(pa_socket_client *client, pa_iochannel*io, void *userdata) { + pa_context *c = userdata; + int saved_errno = errno; + + pa_assert(client); + pa_assert(c); + pa_assert(c->state == PA_CONTEXT_CONNECTING); + + pa_context_ref(c); + + pa_socket_client_unref(client); + c->client = NULL; + + if (!io) { + /* Try the next item in the list */ + if (saved_errno == ECONNREFUSED || + saved_errno == ETIMEDOUT || + saved_errno == EHOSTUNREACH) { + try_next_connection(c); + goto finish; + } + + pa_context_fail(c, PA_ERR_CONNECTIONREFUSED); + goto finish; + } + + setup_context(c, io); + +finish: + pa_context_unref(c); +} + +#ifdef HAVE_DBUS +static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, void *userdata) { + pa_context *c = userdata; + bool is_session; + + pa_assert(bus); + pa_assert(message); + pa_assert(c); + + if (c->state != PA_CONTEXT_CONNECTING) + goto finish; + + if (!c->no_fail) + goto finish; + + /* FIXME: We probably should check if this is actually the NameOwnerChanged we were looking for */ + + is_session = c->session_bus && bus == pa_dbus_wrap_connection_get(c->session_bus); + pa_log_debug("Rock!! PulseAudio might be back on %s bus", is_session ? "session" : "system"); + + if (is_session) + /* The user instance via PF_LOCAL */ + c->server_list = prepend_per_user(c->server_list); + else + /* The system wide instance via PF_LOCAL */ + c->server_list = pa_strlist_prepend(c->server_list, PA_SYSTEM_RUNTIME_PATH PA_PATH_SEP PA_NATIVE_DEFAULT_UNIX_SOCKET); + + if (!c->client) + try_next_connection(c); + +finish: + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} +#endif + +int pa_context_connect( + pa_context *c, + const char *server, + pa_context_flags_t flags, + const pa_spawn_api *api) { + + int r = -1; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY(c, c->state == PA_CONTEXT_UNCONNECTED, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(c, !(flags & ~(PA_CONTEXT_NOAUTOSPAWN|PA_CONTEXT_NOFAIL)), PA_ERR_INVALID); + PA_CHECK_VALIDITY(c, !server || *server, PA_ERR_INVALID); + + if (server) + c->conf->autospawn = false; + else + server = c->conf->default_server; + + pa_context_ref(c); + + c->no_fail = !!(flags & PA_CONTEXT_NOFAIL); + c->server_specified = !!server; + pa_assert(!c->server_list); + + if (server) { + if (!(c->server_list = pa_strlist_parse(server))) { + pa_context_fail(c, PA_ERR_INVALIDSERVER); + goto finish; + } + + } else { + char *d; + + /* Prepend in reverse order */ + + /* Follow the X display */ + if (c->conf->auto_connect_display) { + if ((d = getenv("DISPLAY"))) { + d = pa_xstrndup(d, strcspn(d, ":")); + + if (*d) + c->server_list = pa_strlist_prepend(c->server_list, d); + + pa_xfree(d); + } + } + + /* Add TCP/IP on the localhost */ + if (c->conf->auto_connect_localhost) { + c->server_list = pa_strlist_prepend(c->server_list, "tcp6:[::1]"); + c->server_list = pa_strlist_prepend(c->server_list, "tcp4:127.0.0.1"); + } + + /* The system wide instance via PF_LOCAL */ + c->server_list = pa_strlist_prepend(c->server_list, PA_SYSTEM_RUNTIME_PATH PA_PATH_SEP PA_NATIVE_DEFAULT_UNIX_SOCKET); + + /* The user instance via PF_LOCAL */ + c->server_list = prepend_per_user(c->server_list); + } + + /* Set up autospawning */ + if (!(flags & PA_CONTEXT_NOAUTOSPAWN) && c->conf->autospawn) { + +#ifdef HAVE_GETUID + if (getuid() == 0) + pa_log_debug("Not doing autospawn since we are root."); + else { + c->do_autospawn = true; + + if (api) + c->spawn_api = *api; + } +#endif + } + + pa_context_set_state(c, PA_CONTEXT_CONNECTING); + r = try_next_connection(c); + +finish: + pa_context_unref(c); + + return r; +} + +void pa_context_disconnect(pa_context *c) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + if (pa_detect_fork()) + return; + + if (PA_CONTEXT_IS_GOOD(c->state)) + pa_context_set_state(c, PA_CONTEXT_TERMINATED); +} + +pa_context_state_t pa_context_get_state(const pa_context *c) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + return c->state; +} + +int pa_context_errno(const pa_context *c) { + + if (!c) + return PA_ERR_INVALID; + + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + return c->error->error; +} + +void pa_context_set_state_callback(pa_context *c, pa_context_notify_cb_t cb, void *userdata) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + if (pa_detect_fork()) + return; + + if (c->state == PA_CONTEXT_TERMINATED || c->state == PA_CONTEXT_FAILED) + return; + + c->state_callback = cb; + c->state_userdata = userdata; +} + +void pa_context_set_event_callback(pa_context *c, pa_context_event_cb_t cb, void *userdata) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + if (pa_detect_fork()) + return; + + if (c->state == PA_CONTEXT_TERMINATED || c->state == PA_CONTEXT_FAILED) + return; + + c->event_callback = cb; + c->event_userdata = userdata; +} + +int pa_context_is_pending(const pa_context *c) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY(c, PA_CONTEXT_IS_GOOD(c->state), PA_ERR_BADSTATE); + + return (c->pstream && pa_pstream_is_pending(c->pstream)) || + (c->pdispatch && pa_pdispatch_is_pending(c->pdispatch)) || + c->client; +} + +static void set_dispatch_callbacks(pa_operation *o); + +static void pdispatch_drain_callback(pa_pdispatch*pd, void *userdata) { + set_dispatch_callbacks(userdata); +} + +static void pstream_drain_callback(pa_pstream *s, void *userdata) { + set_dispatch_callbacks(userdata); +} + +static void set_dispatch_callbacks(pa_operation *o) { + int done = 1; + + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + pa_assert(o->context); + pa_assert(PA_REFCNT_VALUE(o->context) >= 1); + pa_assert(o->context->state == PA_CONTEXT_READY); + + pa_pstream_set_drain_callback(o->context->pstream, NULL, NULL); + pa_pdispatch_set_drain_callback(o->context->pdispatch, NULL, NULL); + + if (pa_pdispatch_is_pending(o->context->pdispatch)) { + pa_pdispatch_set_drain_callback(o->context->pdispatch, pdispatch_drain_callback, o); + done = 0; + } + + if (pa_pstream_is_pending(o->context->pstream)) { + pa_pstream_set_drain_callback(o->context->pstream, pstream_drain_callback, o); + done = 0; + } + + if (done) { + if (o->callback) { + pa_context_notify_cb_t cb = (pa_context_notify_cb_t) o->callback; + cb(o->context, o->userdata); + } + + pa_operation_done(o); + pa_operation_unref(o); + } +} + +pa_operation* pa_context_drain(pa_context *c, pa_context_notify_cb_t cb, void *userdata) { + pa_operation *o; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, pa_context_is_pending(c), PA_ERR_BADSTATE); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + set_dispatch_callbacks(pa_operation_ref(o)); + + return o; +} + +void pa_context_simple_ack_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_operation *o = userdata; + int success = 1; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (!o->context) + goto finish; + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(o->context, command, t, false) < 0) + goto finish; + + success = 0; + } else if (!pa_tagstruct_eof(t)) { + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + + if (o->callback) { + pa_context_success_cb_t cb = (pa_context_success_cb_t) o->callback; + cb(o->context, success, o->userdata); + } + +finish: + pa_operation_done(o); + pa_operation_unref(o); +} + +pa_operation* pa_context_send_simple_command(pa_context *c, uint32_t command, pa_pdispatch_cb_t internal_cb, pa_operation_cb_t cb, void *userdata) { + pa_tagstruct *t; + pa_operation *o; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + o = pa_operation_new(c, NULL, cb, userdata); + + t = pa_tagstruct_command(c, command, &tag); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, internal_cb, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_exit_daemon(pa_context *c, pa_context_success_cb_t cb, void *userdata) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + return pa_context_send_simple_command(c, PA_COMMAND_EXIT, pa_context_simple_ack_callback, (pa_operation_cb_t) cb, userdata); +} + +pa_operation* pa_context_set_default_sink(pa_context *c, const char *name, pa_context_success_cb_t cb, void *userdata) { + pa_tagstruct *t; + pa_operation *o; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + t = pa_tagstruct_command(c, PA_COMMAND_SET_DEFAULT_SINK, &tag); + pa_tagstruct_puts(t, name); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_set_default_source(pa_context *c, const char *name, pa_context_success_cb_t cb, void *userdata) { + pa_tagstruct *t; + pa_operation *o; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + t = pa_tagstruct_command(c, PA_COMMAND_SET_DEFAULT_SOURCE, &tag); + pa_tagstruct_puts(t, name); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +int pa_context_is_local(const pa_context *c) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_ANY(c, !pa_detect_fork(), PA_ERR_FORKED, -1); + PA_CHECK_VALIDITY_RETURN_ANY(c, PA_CONTEXT_IS_GOOD(c->state), PA_ERR_BADSTATE, -1); + + return c->is_local; +} + +pa_operation* pa_context_set_name(pa_context *c, const char *name, pa_context_success_cb_t cb, void *userdata) { + pa_operation *o; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(name); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + if (c->version >= 13) { + pa_proplist *p = pa_proplist_new(); + + pa_proplist_sets(p, PA_PROP_APPLICATION_NAME, name); + o = pa_context_proplist_update(c, PA_UPDATE_REPLACE, p, cb, userdata); + pa_proplist_free(p); + } else { + pa_tagstruct *t; + uint32_t tag; + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + t = pa_tagstruct_command(c, PA_COMMAND_SET_CLIENT_NAME, &tag); + pa_tagstruct_puts(t, name); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + } + + return o; +} + +const char* pa_get_library_version(void) { + return pa_get_headers_version(); +} + +const char* pa_context_get_server(const pa_context *c) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->server, PA_ERR_NOENTITY); + + if (*c->server == '{') { + char *e = strchr(c->server+1, '}'); + return e ? e+1 : c->server; + } + + return c->server; +} + +uint32_t pa_context_get_protocol_version(const pa_context *c) { + return PA_PROTOCOL_VERSION; +} + +uint32_t pa_context_get_server_protocol_version(const pa_context *c) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_ANY(c, !pa_detect_fork(), PA_ERR_FORKED, PA_INVALID_INDEX); + PA_CHECK_VALIDITY_RETURN_ANY(c, PA_CONTEXT_IS_GOOD(c->state), PA_ERR_BADSTATE, PA_INVALID_INDEX); + + return c->version; +} + +pa_tagstruct *pa_tagstruct_command(pa_context *c, uint32_t command, uint32_t *tag) { + pa_tagstruct *t; + + pa_assert(c); + pa_assert(tag); + + t = pa_tagstruct_new(); + pa_tagstruct_putu32(t, command); + pa_tagstruct_putu32(t, *tag = c->ctag++); + + return t; +} + +uint32_t pa_context_get_index(const pa_context *c) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_ANY(c, !pa_detect_fork(), PA_ERR_FORKED, PA_INVALID_INDEX); + PA_CHECK_VALIDITY_RETURN_ANY(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE, PA_INVALID_INDEX); + PA_CHECK_VALIDITY_RETURN_ANY(c, c->version >= 13, PA_ERR_NOTSUPPORTED, PA_INVALID_INDEX); + + return c->client_index; +} + +pa_operation *pa_context_proplist_update(pa_context *c, pa_update_mode_t mode, const pa_proplist *p, pa_context_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, mode == PA_UPDATE_SET || mode == PA_UPDATE_MERGE || mode == PA_UPDATE_REPLACE, PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 13, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_UPDATE_CLIENT_PROPLIST, &tag); + pa_tagstruct_putu32(t, (uint32_t) mode); + pa_tagstruct_put_proplist(t, p); + + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + /* Please note that we don't update c->proplist here, because we + * don't export that field */ + + return o; +} + +pa_operation *pa_context_proplist_remove(pa_context *c, const char *const keys[], pa_context_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + const char * const *k; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, keys && keys[0], PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 13, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_REMOVE_CLIENT_PROPLIST, &tag); + + for (k = keys; *k; k++) + pa_tagstruct_puts(t, *k); + + pa_tagstruct_puts(t, NULL); + + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + /* Please note that we don't update c->proplist here, because we + * don't export that field */ + + return o; +} + +void pa_command_extension(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_context *c = userdata; + uint32_t idx; + const char *name; + + pa_assert(pd); + pa_assert(command == PA_COMMAND_EXTENSION); + pa_assert(t); + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + pa_context_ref(c); + + if (c->version < 15) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + + if (pa_tagstruct_getu32(t, &idx) < 0 || + pa_tagstruct_gets(t, &name) < 0) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + + if (pa_streq(name, "module-device-manager")) + pa_ext_device_manager_command(c, tag, t); + else if (pa_streq(name, "module-device-restore")) + pa_ext_device_restore_command(c, tag, t); + else if (pa_streq(name, "module-stream-restore")) + pa_ext_stream_restore_command(c, tag, t); + else + pa_log(_("Received message for unknown extension '%s'"), name); + +finish: + pa_context_unref(c); +} + +static void pa_command_enable_srbchannel(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_context *c = userdata; + +#ifdef HAVE_CREDS + pa_cmsg_ancil_data *ancil = NULL; + + pa_assert(pd); + pa_assert(command == PA_COMMAND_ENABLE_SRBCHANNEL); + pa_assert(t); + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + ancil = pa_pdispatch_take_ancil_data(pd); + if (!ancil) + goto fail; + + /* Currently only one srb channel is supported, might change in future versions */ + if (c->srb_template.readfd != -1) + goto fail; + + if (ancil->nfd != 2 || ancil->fds[0] == -1 || ancil->fds[1] == -1) + goto fail; + + pa_context_ref(c); + + c->srb_template.readfd = ancil->fds[0]; + c->srb_template.writefd = ancil->fds[1]; + c->srb_setup_tag = tag; + + pa_context_unref(c); + + ancil->close_fds_on_cleanup = false; + return; + +fail: + if (ancil) + pa_cmsg_ancil_data_close_fds(ancil); + + pa_context_fail(c, PA_ERR_PROTOCOL); + return; +#else + pa_assert(c); + pa_context_fail(c, PA_ERR_PROTOCOL); +#endif +} + +static void pa_command_disable_srbchannel(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_context *c = userdata; + pa_tagstruct *t2; + + pa_assert(pd); + pa_assert(command == PA_COMMAND_DISABLE_SRBCHANNEL); + pa_assert(t); + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + pa_pstream_set_srbchannel(c->pstream, NULL); + + c->srb_template.readfd = -1; + c->srb_template.writefd = -1; + if (c->srb_template.memblock) { + pa_memblock_unref(c->srb_template.memblock); + c->srb_template.memblock = NULL; + } + + /* Send disable command back again */ + t2 = pa_tagstruct_new(); + pa_tagstruct_putu32(t2, PA_COMMAND_DISABLE_SRBCHANNEL); + pa_tagstruct_putu32(t2, tag); + pa_pstream_send_tagstruct(c->pstream, t2); +} + +static void pa_command_register_memfd_shmid(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_context *c = userdata; + + pa_assert(pd); + pa_assert(command == PA_COMMAND_REGISTER_MEMFD_SHMID); + pa_assert(t); + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + if (pa_common_command_register_memfd_shmid(c->pstream, pd, c->version, command, t)) + pa_context_fail(c, PA_ERR_PROTOCOL); +} + +void pa_command_client_event(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_context *c = userdata; + pa_proplist *pl = NULL; + const char *event; + + pa_assert(pd); + pa_assert(command == PA_COMMAND_CLIENT_EVENT); + pa_assert(t); + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + pa_context_ref(c); + + if (c->version < 15) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + + pl = pa_proplist_new(); + + if (pa_tagstruct_gets(t, &event) < 0 || + pa_tagstruct_get_proplist(t, pl) < 0 || + !pa_tagstruct_eof(t) || !event) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + + if (c->event_callback) + c->event_callback(c, event, pl, c->event_userdata); + +finish: + pa_context_unref(c); + + if (pl) + pa_proplist_free(pl); +} + +pa_time_event* pa_context_rttime_new(const pa_context *c, pa_usec_t usec, pa_time_event_cb_t cb, void *userdata) { + struct timeval tv; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(c->mainloop); + + if (usec == PA_USEC_INVALID) + return c->mainloop->time_new(c->mainloop, NULL, cb, userdata); + + pa_timeval_rtstore(&tv, usec, c->use_rtclock); + + return c->mainloop->time_new(c->mainloop, &tv, cb, userdata); +} + +void pa_context_rttime_restart(const pa_context *c, pa_time_event *e, pa_usec_t usec) { + struct timeval tv; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(c->mainloop); + + if (usec == PA_USEC_INVALID) + c->mainloop->time_restart(e, NULL); + else { + pa_timeval_rtstore(&tv, usec, c->use_rtclock); + c->mainloop->time_restart(e, &tv); + } +} + +size_t pa_context_get_tile_size(const pa_context *c, const pa_sample_spec *ss) { + size_t fs, mbs; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_ANY(c, !pa_detect_fork(), PA_ERR_FORKED, (size_t) -1); + PA_CHECK_VALIDITY_RETURN_ANY(c, !ss || pa_sample_spec_valid(ss), PA_ERR_INVALID, (size_t) -1); + + fs = ss ? pa_frame_size(ss) : 1; + mbs = PA_ROUND_DOWN(pa_mempool_block_size_max(c->mempool), fs); + return PA_MAX(mbs, fs); +} + +int pa_context_load_cookie_from_file(pa_context *c, const char *cookie_file_path) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY(c, c->state == PA_CONTEXT_UNCONNECTED, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(c, !cookie_file_path || *cookie_file_path, PA_ERR_INVALID); + + pa_client_conf_set_cookie_file_from_application(c->conf, cookie_file_path); + + return 0; +} diff --git a/src/pulse/context.h b/src/pulse/context.h new file mode 100644 index 0000000..38656f8 --- /dev/null +++ b/src/pulse/context.h @@ -0,0 +1,295 @@ +#ifndef foocontexthfoo +#define foocontexthfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulse/sample.h> +#include <pulse/def.h> +#include <pulse/mainloop-api.h> +#include <pulse/cdecl.h> +#include <pulse/operation.h> +#include <pulse/proplist.h> +#include <pulse/version.h> + +/** \page async Asynchronous API + * + * \section overv_sec Overview + * + * The asynchronous API is the native interface to the PulseAudio library. + * It allows full access to all available functionality. This however means that + * it is rather complex and can take some time to fully master. + * + * \section mainloop_sec Main Loop Abstraction + * + * The API is based around an asynchronous event loop, or main loop, + * abstraction. This abstraction contains three basic elements: + * + * \li Deferred events - Events that will trigger as soon as possible. Note + * that some implementations may block all other events + * when a deferred event is active. + * \li I/O events - Events that trigger on file descriptor activities. + * \li Timer events - Events that trigger after a fixed amount of time. + * + * The abstraction is represented as a number of function pointers in the + * pa_mainloop_api structure. + * + * To actually be able to use these functions, an implementation needs to + * be coupled to the abstraction. There are three of these shipped with + * PulseAudio, but any other can be used with a minimal amount of work, + * provided it supports the three basic events listed above. + * + * The implementations shipped with PulseAudio are: + * + * \li \subpage mainloop - A minimal but fast implementation based on poll(). + * \li \subpage threaded_mainloop - A special version of the previous + * implementation where all of PulseAudio's + * internal handling runs in a separate + * thread. + * \li \subpage glib-mainloop - A wrapper around GLib's main loop. + * + * UNIX signals may be hooked to a main loop using the functions from + * \ref mainloop-signal.h. These rely only on the main loop abstraction + * and can therefore be used with any of the implementations. + * + * \section refcnt_sec Reference Counting + * + * Almost all objects in PulseAudio are reference counted. What that means + * is that you rarely malloc() or free() any objects. Instead you increase + * and decrease their reference counts. Whenever an object's reference + * count reaches zero, that object gets destroy and any resources it uses + * get freed. + * + * The benefit of this design is that an application need not worry about + * whether or not it needs to keep an object around in case the library is + * using it internally. If it is, then it has made sure it has its own + * reference to it. + * + * Whenever the library creates an object, it will have an initial + * reference count of one. Most of the time, this single reference will be + * sufficient for the application, so all required reference count + * interaction will be a single call to the object's unref function. + * + * \section context_sec Context + * + * A context is the basic object for a connection to a PulseAudio server. + * It multiplexes commands, data streams and events through a single + * channel. + * + * There is no need for more than one context per application, unless + * connections to multiple servers are needed. + * + * \subsection ops_subsec Operations + * + * All operations on the context are performed asynchronously. I.e. the + * client will not wait for the server to complete the request. To keep + * track of all these in-flight operations, the application is given a + * pa_operation object for each asynchronous operation. + * + * There are only two actions (besides reference counting) that can be + * performed on a pa_operation: querying its state with + * pa_operation_get_state() and aborting it with pa_operation_cancel(). + * + * A pa_operation object is reference counted, so an application must + * make sure to unreference it, even if it has no intention of using it. + * + * \subsection conn_subsec Connecting + * + * A context must be connected to a server before any operation can be + * issued. Calling pa_context_connect() will initiate the connection + * procedure. Unlike most asynchronous operations, connecting does not + * result in a pa_operation object. Instead, the application should + * register a callback using pa_context_set_state_callback(). + * + * \subsection disc_subsec Disconnecting + * + * When the sound support is no longer needed, the connection needs to be + * closed using pa_context_disconnect(). This is an immediate function that + * works synchronously. + * + * Since the context object has references to other objects it must be + * disconnected after use or there is a high risk of memory leaks. If the + * connection has terminated by itself, then there is no need to explicitly + * disconnect the context using pa_context_disconnect(). + * + * \section Functions + * + * The sound server's functionality can be divided into a number of + * subsections: + * + * \li \subpage streams + * \li \subpage scache + * \li \subpage introspect + * \li \subpage subscribe + */ + +/** \file + * Connection contexts for asynchronous communication with a + * server. A pa_context object wraps a connection to a PulseAudio + * server using its native protocol. + * + * See also \subpage async + */ + +PA_C_DECL_BEGIN + +/** An opaque connection context to a daemon */ +typedef struct pa_context pa_context; + +/** Generic notification callback prototype */ +typedef void (*pa_context_notify_cb_t)(pa_context *c, void *userdata); + +/** A generic callback for operation completion */ +typedef void (*pa_context_success_cb_t) (pa_context *c, int success, void *userdata); + +/** A callback for asynchronous meta/policy event messages. The set + * of defined events can be extended at any time. Also, server modules + * may introduce additional message types so make sure that your + * callback function ignores messages it doesn't know. \since + * 0.9.15 */ +typedef void (*pa_context_event_cb_t)(pa_context *c, const char *name, pa_proplist *p, void *userdata); + +/** Instantiate a new connection context with an abstract mainloop API + * and an application name. It is recommended to use pa_context_new_with_proplist() + * instead and specify some initial properties.*/ +pa_context *pa_context_new(pa_mainloop_api *mainloop, const char *name); + +/** Instantiate a new connection context with an abstract mainloop API + * and an application name, and specify the initial client property + * list. \since 0.9.11 */ +pa_context *pa_context_new_with_proplist(pa_mainloop_api *mainloop, const char *name, const pa_proplist *proplist); + +/** Decrease the reference counter of the context by one */ +void pa_context_unref(pa_context *c); + +/** Increase the reference counter of the context by one */ +pa_context* pa_context_ref(pa_context *c); + +/** Set a callback function that is called whenever the context status changes */ +void pa_context_set_state_callback(pa_context *c, pa_context_notify_cb_t cb, void *userdata); + +/** Set a callback function that is called whenever a meta/policy + * control event is received. \since 0.9.15 */ +void pa_context_set_event_callback(pa_context *p, pa_context_event_cb_t cb, void *userdata); + +/** Return the error number of the last failed operation */ +int pa_context_errno(const pa_context *c); + +/** Return non-zero if some data is pending to be written to the connection */ +int pa_context_is_pending(const pa_context *c); + +/** Return the current context status */ +pa_context_state_t pa_context_get_state(const pa_context *c); + +/** Connect the context to the specified server. If server is NULL, + * connect to the default server. This routine may but will not always + * return synchronously on error. Use pa_context_set_state_callback() to + * be notified when the connection is established. If flags doesn't have + * PA_CONTEXT_NOAUTOSPAWN set and no specific server is specified or + * accessible a new daemon is spawned. If api is non-NULL, the functions + * specified in the structure are used when forking a new child + * process. Returns negative on certain errors such as invalid state + * or parameters. */ +int pa_context_connect(pa_context *c, const char *server, pa_context_flags_t flags, const pa_spawn_api *api); + +/** Terminate the context connection immediately */ +void pa_context_disconnect(pa_context *c); + +/** Drain the context. If there is nothing to drain, the function returns NULL */ +pa_operation* pa_context_drain(pa_context *c, pa_context_notify_cb_t cb, void *userdata); + +/** Tell the daemon to exit. The returned operation is unlikely to + * complete successfully, since the daemon probably died before + * returning a success notification */ +pa_operation* pa_context_exit_daemon(pa_context *c, pa_context_success_cb_t cb, void *userdata); + +/** Set the name of the default sink. */ +pa_operation* pa_context_set_default_sink(pa_context *c, const char *name, pa_context_success_cb_t cb, void *userdata); + +/** Set the name of the default source. */ +pa_operation* pa_context_set_default_source(pa_context *c, const char *name, pa_context_success_cb_t cb, void *userdata); + +/** Returns 1 when the connection is to a local daemon. Returns negative when no connection has been made yet. */ +int pa_context_is_local(const pa_context *c); + +/** Set a different application name for context on the server. */ +pa_operation* pa_context_set_name(pa_context *c, const char *name, pa_context_success_cb_t cb, void *userdata); + +/** Return the server name this context is connected to. */ +const char* pa_context_get_server(const pa_context *c); + +/** Return the protocol version of the library. */ +uint32_t pa_context_get_protocol_version(const pa_context *c); + +/** Return the protocol version of the connected server. + * Returns PA_INVALID_INDEX on error. */ +uint32_t pa_context_get_server_protocol_version(const pa_context *c); + +/** Update the property list of the client, adding new entries. Please + * note that it is highly recommended to set as many properties + * initially via pa_context_new_with_proplist() as possible instead a + * posteriori with this function, since that information may then be + * used to route streams of the client to the right device. \since 0.9.11 */ +pa_operation *pa_context_proplist_update(pa_context *c, pa_update_mode_t mode, const pa_proplist *p, pa_context_success_cb_t cb, void *userdata); + +/** Update the property list of the client, remove entries. \since 0.9.11 */ +pa_operation *pa_context_proplist_remove(pa_context *c, const char *const keys[], pa_context_success_cb_t cb, void *userdata); + +/** Return the client index this context is + * identified in the server with. This is useful for usage with the + * introspection functions, such as pa_context_get_client_info(). + * Returns PA_INVALID_INDEX on error. \since 0.9.11 */ +uint32_t pa_context_get_index(const pa_context *s); + +/** Create a new timer event source for the specified time (wrapper + * for mainloop->time_new). \since 0.9.16 */ +pa_time_event* pa_context_rttime_new(const pa_context *c, pa_usec_t usec, pa_time_event_cb_t cb, void *userdata); + +/** Restart a running or expired timer event source (wrapper for + * mainloop->time_restart). \since 0.9.16 */ +void pa_context_rttime_restart(const pa_context *c, pa_time_event *e, pa_usec_t usec); + +/** Return the optimal block size for passing around audio buffers. It + * is recommended to allocate buffers of the size returned here when + * writing audio data to playback streams, if the latency constraints + * permit this. It is not recommended writing larger blocks than this + * because usually they will then be split up internally into chunks + * of this size. It is not recommended writing smaller blocks than + * this (unless required due to latency demands) because this + * increases CPU usage. If ss is NULL you will be returned the + * byte-exact tile size. if ss is invalid, (size_t) -1 will be + * returned. If you pass a valid ss, then the tile size + * will be rounded down to multiple of the frame size. This is + * supposed to be used in a construct such as + * pa_context_get_tile_size(pa_stream_get_context(s), + * pa_stream_get_sample_spec(ss)); \since 0.9.20 */ +size_t pa_context_get_tile_size(const pa_context *c, const pa_sample_spec *ss); + +/** Load the authentication cookie from a file. This function is primarily + * meant for PulseAudio's own tunnel modules, which need to load the cookie + * from a custom location. Applications don't usually need to care about the + * cookie at all, but if it happens that you know what the authentication + * cookie is and your application needs to load it from a non-standard + * location, feel free to use this function. \since 5.0 */ +int pa_context_load_cookie_from_file(pa_context *c, const char *cookie_file_path); + +PA_C_DECL_END + +#endif diff --git a/src/pulse/def.h b/src/pulse/def.h new file mode 100644 index 0000000..4097bd8 --- /dev/null +++ b/src/pulse/def.h @@ -0,0 +1,1104 @@ +#ifndef foodefhfoo +#define foodefhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <inttypes.h> +#include <sys/time.h> + +#include <pulse/cdecl.h> +#include <pulse/sample.h> +#include <pulse/version.h> + +/** \file + * Global definitions */ + +PA_C_DECL_BEGIN + +/** The state of a connection context */ +typedef enum pa_context_state { + PA_CONTEXT_UNCONNECTED, /**< The context hasn't been connected yet */ + PA_CONTEXT_CONNECTING, /**< A connection is being established */ + PA_CONTEXT_AUTHORIZING, /**< The client is authorizing itself to the daemon */ + PA_CONTEXT_SETTING_NAME, /**< The client is passing its application name to the daemon */ + PA_CONTEXT_READY, /**< The connection is established, the context is ready to execute operations */ + PA_CONTEXT_FAILED, /**< The connection failed or was disconnected */ + PA_CONTEXT_TERMINATED /**< The connection was terminated cleanly */ +} pa_context_state_t; + +/** Return non-zero if the passed state is one of the connected states. \since 0.9.11 */ +static inline int PA_CONTEXT_IS_GOOD(pa_context_state_t x) { + return + x == PA_CONTEXT_CONNECTING || + x == PA_CONTEXT_AUTHORIZING || + x == PA_CONTEXT_SETTING_NAME || + x == PA_CONTEXT_READY; +} + +/** \cond fulldocs */ +#define PA_CONTEXT_UNCONNECTED PA_CONTEXT_UNCONNECTED +#define PA_CONTEXT_CONNECTING PA_CONTEXT_CONNECTING +#define PA_CONTEXT_AUTHORIZING PA_CONTEXT_AUTHORIZING +#define PA_CONTEXT_SETTING_NAME PA_CONTEXT_SETTING_NAME +#define PA_CONTEXT_READY PA_CONTEXT_READY +#define PA_CONTEXT_FAILED PA_CONTEXT_FAILED +#define PA_CONTEXT_TERMINATED PA_CONTEXT_TERMINATED +#define PA_CONTEXT_IS_GOOD PA_CONTEXT_IS_GOOD +/** \endcond */ + +/** The state of a stream */ +typedef enum pa_stream_state { + PA_STREAM_UNCONNECTED, /**< The stream is not yet connected to any sink or source */ + PA_STREAM_CREATING, /**< The stream is being created */ + PA_STREAM_READY, /**< The stream is established, you may pass audio data to it now */ + PA_STREAM_FAILED, /**< An error occurred that made the stream invalid */ + PA_STREAM_TERMINATED /**< The stream has been terminated cleanly */ +} pa_stream_state_t; + +/** Return non-zero if the passed state is one of the connected states. \since 0.9.11 */ +static inline int PA_STREAM_IS_GOOD(pa_stream_state_t x) { + return + x == PA_STREAM_CREATING || + x == PA_STREAM_READY; +} + +/** \cond fulldocs */ +#define PA_STREAM_UNCONNECTED PA_STREAM_UNCONNECTED +#define PA_STREAM_CREATING PA_STREAM_CREATING +#define PA_STREAM_READY PA_STREAM_READY +#define PA_STREAM_FAILED PA_STREAM_FAILED +#define PA_STREAM_TERMINATED PA_STREAM_TERMINATED +#define PA_STREAM_IS_GOOD PA_STREAM_IS_GOOD +/** \endcond */ + +/** The state of an operation */ +typedef enum pa_operation_state { + PA_OPERATION_RUNNING, + /**< The operation is still running */ + PA_OPERATION_DONE, + /**< The operation has completed */ + PA_OPERATION_CANCELLED + /**< The operation has been cancelled. Operations may get cancelled by the + * application, or as a result of the context getting disconnected while the + * operation is pending. */ +} pa_operation_state_t; + +/** \cond fulldocs */ +#define PA_OPERATION_RUNNING PA_OPERATION_RUNNING +#define PA_OPERATION_DONE PA_OPERATION_DONE +#define PA_OPERATION_CANCELED PA_OPERATION_CANCELLED +#define PA_OPERATION_CANCELLED PA_OPERATION_CANCELLED +/** \endcond */ + +/** An invalid index */ +#define PA_INVALID_INDEX ((uint32_t) -1) + +/** Some special flags for contexts. */ +typedef enum pa_context_flags { + PA_CONTEXT_NOFLAGS = 0x0000U, + /**< Flag to pass when no specific options are needed (used to avoid casting) \since 0.9.19 */ + PA_CONTEXT_NOAUTOSPAWN = 0x0001U, + /**< Disabled autospawning of the PulseAudio daemon if required */ + PA_CONTEXT_NOFAIL = 0x0002U + /**< Don't fail if the daemon is not available when pa_context_connect() is + * called, instead enter PA_CONTEXT_CONNECTING state and wait for the daemon + * to appear. \since 0.9.15 */ +} pa_context_flags_t; + +/** \cond fulldocs */ +/* Allow clients to check with #ifdef for those flags */ +#define PA_CONTEXT_NOAUTOSPAWN PA_CONTEXT_NOAUTOSPAWN +#define PA_CONTEXT_NOFAIL PA_CONTEXT_NOFAIL +/** \endcond */ + +/** Direction bitfield - while we currently do not expose anything bidirectional, + one should test against the bit instead of the value (e.g.\ if (d & PA_DIRECTION_OUTPUT)), + because we might add bidirectional stuff in the future. \since 2.0 +*/ +typedef enum pa_direction { + PA_DIRECTION_OUTPUT = 0x0001U, /**< Output direction */ + PA_DIRECTION_INPUT = 0x0002U /**< Input direction */ +} pa_direction_t; + +/** \cond fulldocs */ +#define PA_DIRECTION_OUTPUT PA_DIRECTION_OUTPUT +#define PA_DIRECTION_INPUT PA_DIRECTION_INPUT +/** \endcond */ + +/** The type of device we are dealing with */ +typedef enum pa_device_type { + PA_DEVICE_TYPE_SINK, /**< Playback device */ + PA_DEVICE_TYPE_SOURCE /**< Recording device */ +} pa_device_type_t; + +/** \cond fulldocs */ +#define PA_DEVICE_TYPE_SINK PA_DEVICE_TYPE_SINK +#define PA_DEVICE_TYPE_SOURCE PA_DEVICE_TYPE_SOURCE +/** \endcond */ + +/** The direction of a pa_stream object */ +typedef enum pa_stream_direction { + PA_STREAM_NODIRECTION, /**< Invalid direction */ + PA_STREAM_PLAYBACK, /**< Playback stream */ + PA_STREAM_RECORD, /**< Record stream */ + PA_STREAM_UPLOAD /**< Sample upload stream */ +} pa_stream_direction_t; + +/** \cond fulldocs */ +#define PA_STREAM_NODIRECTION PA_STREAM_NODIRECTION +#define PA_STREAM_PLAYBACK PA_STREAM_PLAYBACK +#define PA_STREAM_RECORD PA_STREAM_RECORD +#define PA_STREAM_UPLOAD PA_STREAM_UPLOAD +/** \endcond */ + +/** Some special flags for stream connections. */ +typedef enum pa_stream_flags { + + PA_STREAM_NOFLAGS = 0x0000U, + /**< Flag to pass when no specific options are needed (used to avoid casting) \since 0.9.19 */ + + PA_STREAM_START_CORKED = 0x0001U, + /**< Create the stream corked, requiring an explicit + * pa_stream_cork() call to uncork it. */ + + PA_STREAM_INTERPOLATE_TIMING = 0x0002U, + /**< Interpolate the latency for this stream. When enabled, + * pa_stream_get_latency() and pa_stream_get_time() will try to + * estimate the current record/playback time based on the local + * time that passed since the last timing info update. Using this + * option has the advantage of not requiring a whole roundtrip + * when the current playback/recording time is needed. Consider + * using this option when requesting latency information + * frequently. This is especially useful on long latency network + * connections. It makes a lot of sense to combine this option + * with PA_STREAM_AUTO_TIMING_UPDATE. */ + + PA_STREAM_NOT_MONOTONIC = 0x0004U, + /**< Don't force the time to increase monotonically. If this + * option is enabled, pa_stream_get_time() will not necessarily + * return always monotonically increasing time values on each + * call. This may confuse applications which cannot deal with time + * going 'backwards', but has the advantage that bad transport + * latency estimations that caused the time to jump ahead can + * be corrected quickly, without the need to wait. (Please note + * that this flag was named PA_STREAM_NOT_MONOTONOUS in releases + * prior to 0.9.11. The old name is still defined too, for + * compatibility reasons. */ + + PA_STREAM_AUTO_TIMING_UPDATE = 0x0008U, + /**< If set timing update requests are issued periodically + * automatically. Combined with PA_STREAM_INTERPOLATE_TIMING you + * will be able to query the current time and latency with + * pa_stream_get_time() and pa_stream_get_latency() at all times + * without a packet round trip.*/ + + PA_STREAM_NO_REMAP_CHANNELS = 0x0010U, + /**< Don't remap channels by their name, instead map them simply + * by their index. Implies PA_STREAM_NO_REMIX_CHANNELS. Only + * supported when the server is at least PA 0.9.8. It is ignored + * on older servers.\since 0.9.8 */ + + PA_STREAM_NO_REMIX_CHANNELS = 0x0020U, + /**< When remapping channels by name, don't upmix or downmix them + * to related channels. Copy them into matching channels of the + * device 1:1. Only supported when the server is at least PA + * 0.9.8. It is ignored on older servers. \since 0.9.8 */ + + PA_STREAM_FIX_FORMAT = 0x0040U, + /**< Use the sample format of the sink/device this stream is being + * connected to, and possibly ignore the format the sample spec + * contains -- but you still have to pass a valid value in it as a + * hint to PulseAudio what would suit your stream best. If this is + * used you should query the used sample format after creating the + * stream by using pa_stream_get_sample_spec(). Also, if you + * specified manual buffer metrics it is recommended to update + * them with pa_stream_set_buffer_attr() to compensate for the + * changed frame sizes. Only supported when the server is at least + * PA 0.9.8. It is ignored on older servers. + * + * When creating streams with pa_stream_new_extended(), this flag has no + * effect. If you specify a format with PCM encoding, and you want the + * server to choose the sample format, then you should leave the sample + * format unspecified in the pa_format_info object. This also means that + * you can't use pa_format_info_from_sample_spec(), because that function + * always sets the sample format. + * + * \since 0.9.8 */ + + PA_STREAM_FIX_RATE = 0x0080U, + /**< Use the sample rate of the sink, and possibly ignore the rate + * the sample spec contains. Usage similar to + * PA_STREAM_FIX_FORMAT. Only supported when the server is at least + * PA 0.9.8. It is ignored on older servers. + * + * When creating streams with pa_stream_new_extended(), this flag has no + * effect. If you specify a format with PCM encoding, and you want the + * server to choose the sample rate, then you should leave the rate + * unspecified in the pa_format_info object. This also means that you can't + * use pa_format_info_from_sample_spec(), because that function always sets + * the sample rate. + * + * \since 0.9.8 */ + + PA_STREAM_FIX_CHANNELS = 0x0100, + /**< Use the number of channels and the channel map of the sink, + * and possibly ignore the number of channels and the map the + * sample spec and the passed channel map contain. Usage similar + * to PA_STREAM_FIX_FORMAT. Only supported when the server is at + * least PA 0.9.8. It is ignored on older servers. + * + * When creating streams with pa_stream_new_extended(), this flag has no + * effect. If you specify a format with PCM encoding, and you want the + * server to choose the channel count and/or channel map, then you should + * leave the channels and/or the channel map unspecified in the + * pa_format_info object. This also means that you can't use + * pa_format_info_from_sample_spec(), because that function always sets + * the channel count (but if you only want to leave the channel map + * unspecified, then pa_format_info_from_sample_spec() works, because it + * accepts a NULL channel map). + * + * \since 0.9.8 */ + + PA_STREAM_DONT_MOVE = 0x0200U, + /**< Don't allow moving of this stream to another + * sink/device. Useful if you use any of the PA_STREAM_FIX_ flags + * and want to make sure that resampling never takes place -- + * which might happen if the stream is moved to another + * sink/source with a different sample spec/channel map. Only + * supported when the server is at least PA 0.9.8. It is ignored + * on older servers. \since 0.9.8 */ + + PA_STREAM_VARIABLE_RATE = 0x0400U, + /**< Allow dynamic changing of the sampling rate during playback + * with pa_stream_update_sample_rate(). Only supported when the + * server is at least PA 0.9.8. It is ignored on older + * servers. \since 0.9.8 */ + + PA_STREAM_PEAK_DETECT = 0x0800U, + /**< Find peaks instead of resampling. \since 0.9.11 */ + + PA_STREAM_START_MUTED = 0x1000U, + /**< Create in muted state. If neither PA_STREAM_START_UNMUTED nor + * PA_STREAM_START_MUTED are set, it is left to the server to decide + * whether to create the stream in muted or in unmuted + * state. \since 0.9.11 */ + + PA_STREAM_ADJUST_LATENCY = 0x2000U, + /**< Try to adjust the latency of the sink/source based on the + * requested buffer metrics and adjust buffer metrics + * accordingly. Also see pa_buffer_attr. This option may not be + * specified at the same time as PA_STREAM_EARLY_REQUESTS. \since + * 0.9.11 */ + + PA_STREAM_EARLY_REQUESTS = 0x4000U, + /**< Enable compatibility mode for legacy clients that rely on a + * "classic" hardware device fragment-style playback model. If + * this option is set, the minreq value of the buffer metrics gets + * a new meaning: instead of just specifying that no requests + * asking for less new data than this value will be made to the + * client it will also guarantee that requests are generated as + * early as this limit is reached. This flag should only be set in + * very few situations where compatibility with a fragment-based + * playback model needs to be kept and the client applications + * cannot deal with data requests that are delayed to the latest + * moment possible. (Usually these are programs that use usleep() + * or a similar call in their playback loops instead of sleeping + * on the device itself.) Also see pa_buffer_attr. This option may + * not be specified at the same time as + * PA_STREAM_ADJUST_LATENCY. \since 0.9.12 */ + + PA_STREAM_DONT_INHIBIT_AUTO_SUSPEND = 0x8000U, + /**< If set this stream won't be taken into account when it is + * checked whether the device this stream is connected to should + * auto-suspend. \since 0.9.15 */ + + PA_STREAM_START_UNMUTED = 0x10000U, + /**< Create in unmuted state. If neither PA_STREAM_START_UNMUTED + * nor PA_STREAM_START_MUTED are set it is left to the server to decide + * whether to create the stream in muted or in unmuted + * state. \since 0.9.15 */ + + PA_STREAM_FAIL_ON_SUSPEND = 0x20000U, + /**< If the sink/source this stream is connected to is suspended + * during the creation of this stream, cause it to fail. If the + * sink/source is being suspended during creation of this stream, + * make sure this stream is terminated. \since 0.9.15 */ + + PA_STREAM_RELATIVE_VOLUME = 0x40000U, + /**< If a volume is passed when this stream is created, consider + * it relative to the sink's current volume, never as absolute + * device volume. If this is not specified the volume will be + * consider absolute when the sink is in flat volume mode, + * relative otherwise. \since 0.9.20 */ + + PA_STREAM_PASSTHROUGH = 0x80000U + /**< Used to tag content that will be rendered by passthrough sinks. + * The data will be left as is and not reformatted, resampled. + * \since 1.0 */ + +} pa_stream_flags_t; + +/** \cond fulldocs */ + +/* English is an evil language */ +#define PA_STREAM_NOT_MONOTONOUS PA_STREAM_NOT_MONOTONIC + +/* Allow clients to check with #ifdef for those flags */ +#define PA_STREAM_START_CORKED PA_STREAM_START_CORKED +#define PA_STREAM_INTERPOLATE_TIMING PA_STREAM_INTERPOLATE_TIMING +#define PA_STREAM_NOT_MONOTONIC PA_STREAM_NOT_MONOTONIC +#define PA_STREAM_AUTO_TIMING_UPDATE PA_STREAM_AUTO_TIMING_UPDATE +#define PA_STREAM_NO_REMAP_CHANNELS PA_STREAM_NO_REMAP_CHANNELS +#define PA_STREAM_NO_REMIX_CHANNELS PA_STREAM_NO_REMIX_CHANNELS +#define PA_STREAM_FIX_FORMAT PA_STREAM_FIX_FORMAT +#define PA_STREAM_FIX_RATE PA_STREAM_FIX_RATE +#define PA_STREAM_FIX_CHANNELS PA_STREAM_FIX_CHANNELS +#define PA_STREAM_DONT_MOVE PA_STREAM_DONT_MOVE +#define PA_STREAM_VARIABLE_RATE PA_STREAM_VARIABLE_RATE +#define PA_STREAM_PEAK_DETECT PA_STREAM_PEAK_DETECT +#define PA_STREAM_START_MUTED PA_STREAM_START_MUTED +#define PA_STREAM_ADJUST_LATENCY PA_STREAM_ADJUST_LATENCY +#define PA_STREAM_EARLY_REQUESTS PA_STREAM_EARLY_REQUESTS +#define PA_STREAM_DONT_INHIBIT_AUTO_SUSPEND PA_STREAM_DONT_INHIBIT_AUTO_SUSPEND +#define PA_STREAM_START_UNMUTED PA_STREAM_START_UNMUTED +#define PA_STREAM_FAIL_ON_SUSPEND PA_STREAM_FAIL_ON_SUSPEND +#define PA_STREAM_RELATIVE_VOLUME PA_STREAM_RELATIVE_VOLUME +#define PA_STREAM_PASSTHROUGH PA_STREAM_PASSTHROUGH + +/** \endcond */ + +/** Playback and record buffer metrics */ +typedef struct pa_buffer_attr { + uint32_t maxlength; + /**< Maximum length of the buffer in bytes. Setting this to (uint32_t) -1 + * will initialize this to the maximum value supported by server, + * which is recommended. + * + * In strict low-latency playback scenarios you might want to set this to + * a lower value, likely together with the PA_STREAM_ADJUST_LATENCY flag. + * If you do so, you ensure that the latency doesn't grow beyond what is + * acceptable for the use case, at the cost of getting more underruns if + * the latency is lower than what the server can reliably handle. */ + + uint32_t tlength; + /**< Playback only: target length of the buffer. The server tries + * to assure that at least tlength bytes are always available in + * the per-stream server-side playback buffer. The server will + * only send requests for more data as long as the buffer has + * less than this number of bytes of data. + * + * It is recommended to set this to (uint32_t) -1, which will + * initialize this to a value that is deemed sensible by the + * server. However, this value will default to something like 2s; + * for applications that have specific latency requirements + * this value should be set to the maximum latency that the + * application can deal with. + * + * When PA_STREAM_ADJUST_LATENCY is not set this value will + * influence only the per-stream playback buffer size. When + * PA_STREAM_ADJUST_LATENCY is set the overall latency of the sink + * plus the playback buffer size is configured to this value. Set + * PA_STREAM_ADJUST_LATENCY if you are interested in adjusting the + * overall latency. Don't set it if you are interested in + * configuring the server-side per-stream playback buffer + * size. */ + + uint32_t prebuf; + /**< Playback only: pre-buffering. The server does not start with + * playback before at least prebuf bytes are available in the + * buffer. It is recommended to set this to (uint32_t) -1, which + * will initialize this to the same value as tlength, whatever + * that may be. + * + * Initialize to 0 to enable manual start/stop control of the stream. + * This means that playback will not stop on underrun and playback + * will not start automatically, instead pa_stream_cork() needs to + * be called explicitly. If you set this value to 0 you should also + * set PA_STREAM_START_CORKED. Should underrun occur, the read index + * of the output buffer overtakes the write index, and hence the + * fill level of the buffer is negative. + * + * Start of playback can be forced using pa_stream_trigger() even + * though the prebuffer size hasn't been reached. If a buffer + * underrun occurs, this prebuffering will be again enabled. */ + + uint32_t minreq; + /**< Playback only: minimum request. The server does not request + * less than minreq bytes from the client, instead waits until the + * buffer is free enough to request more bytes at once. It is + * recommended to set this to (uint32_t) -1, which will initialize + * this to a value that is deemed sensible by the server. This + * should be set to a value that gives PulseAudio enough time to + * move the data from the per-stream playback buffer into the + * hardware playback buffer. */ + + uint32_t fragsize; + /**< Recording only: fragment size. The server sends data in + * blocks of fragsize bytes size. Large values diminish + * interactivity with other operations on the connection context + * but decrease control overhead. It is recommended to set this to + * (uint32_t) -1, which will initialize this to a value that is + * deemed sensible by the server. However, this value will default + * to something like 2s; For applications that have specific + * latency requirements this value should be set to the maximum + * latency that the application can deal with. + * + * If PA_STREAM_ADJUST_LATENCY is set the overall source latency + * will be adjusted according to this value. If it is not set the + * source latency is left unmodified. */ + +} pa_buffer_attr; + +/** Error values as used by pa_context_errno(). Use pa_strerror() to convert these values to human readable strings */ +typedef enum pa_error_code { + PA_OK = 0, /**< No error */ + PA_ERR_ACCESS, /**< Access failure */ + PA_ERR_COMMAND, /**< Unknown command */ + PA_ERR_INVALID, /**< Invalid argument */ + PA_ERR_EXIST, /**< Entity exists */ + PA_ERR_NOENTITY, /**< No such entity */ + PA_ERR_CONNECTIONREFUSED, /**< Connection refused */ + PA_ERR_PROTOCOL, /**< Protocol error */ + PA_ERR_TIMEOUT, /**< Timeout */ + PA_ERR_AUTHKEY, /**< No authentication key */ + PA_ERR_INTERNAL, /**< Internal error */ + PA_ERR_CONNECTIONTERMINATED, /**< Connection terminated */ + PA_ERR_KILLED, /**< Entity killed */ + PA_ERR_INVALIDSERVER, /**< Invalid server */ + PA_ERR_MODINITFAILED, /**< Module initialization failed */ + PA_ERR_BADSTATE, /**< Bad state */ + PA_ERR_NODATA, /**< No data */ + PA_ERR_VERSION, /**< Incompatible protocol version */ + PA_ERR_TOOLARGE, /**< Data too large */ + PA_ERR_NOTSUPPORTED, /**< Operation not supported \since 0.9.5 */ + PA_ERR_UNKNOWN, /**< The error code was unknown to the client */ + PA_ERR_NOEXTENSION, /**< Extension does not exist. \since 0.9.12 */ + PA_ERR_OBSOLETE, /**< Obsolete functionality. \since 0.9.15 */ + PA_ERR_NOTIMPLEMENTED, /**< Missing implementation. \since 0.9.15 */ + PA_ERR_FORKED, /**< The caller forked without calling execve() and tried to reuse the context. \since 0.9.15 */ + PA_ERR_IO, /**< An IO error happened. \since 0.9.16 */ + PA_ERR_BUSY, /**< Device or resource busy. \since 0.9.17 */ + PA_ERR_MAX /**< Not really an error but the first invalid error code */ +} pa_error_code_t; + +/** \cond fulldocs */ +#define PA_OK PA_OK +#define PA_ERR_ACCESS PA_ERR_ACCESS +#define PA_ERR_COMMAND PA_ERR_COMMAND +#define PA_ERR_INVALID PA_ERR_INVALID +#define PA_ERR_EXIST PA_ERR_EXIST +#define PA_ERR_NOENTITY PA_ERR_NOENTITY +#define PA_ERR_CONNECTIONREFUSED PA_ERR_CONNECTIONREFUSED +#define PA_ERR_PROTOCOL PA_ERR_PROTOCOL +#define PA_ERR_TIMEOUT PA_ERR_TIMEOUT +#define PA_ERR_AUTHKEY PA_ERR_AUTHKEY +#define PA_ERR_INTERNAL PA_ERR_INTERNAL +#define PA_ERR_CONNECTIONTERMINATED PA_ERR_CONNECTIONTERMINATED +#define PA_ERR_KILLED PA_ERR_KILLED +#define PA_ERR_INVALIDSERVER PA_ERR_INVALIDSERVER +#define PA_ERR_MODINITFAILED PA_ERR_MODINITFAILED +#define PA_ERR_BADSTATE PA_ERR_BADSTATE +#define PA_ERR_NODATA PA_ERR_NODATA +#define PA_ERR_VERSION PA_ERR_VERSION +#define PA_ERR_TOOLARGE PA_ERR_TOOLARGE +#define PA_ERR_NOTSUPPORTED PA_ERR_NOTSUPPORTED +#define PA_ERR_UNKNOWN PA_ERR_UNKNOWN +#define PA_ERR_NOEXTENSION PA_ERR_NOEXTENSION +#define PA_ERR_OBSOLETE PA_ERR_OBSOLETE +#define PA_ERR_NOTIMPLEMENTED PA_ERR_NOTIMPLEMENTED +#define PA_ERR_FORKED PA_ERR_FORKED +#define PA_ERR_MAX PA_ERR_MAX +/** \endcond */ + +/** Subscription event mask, as used by pa_context_subscribe() */ +typedef enum pa_subscription_mask { + PA_SUBSCRIPTION_MASK_NULL = 0x0000U, + /**< No events */ + + PA_SUBSCRIPTION_MASK_SINK = 0x0001U, + /**< Sink events */ + + PA_SUBSCRIPTION_MASK_SOURCE = 0x0002U, + /**< Source events */ + + PA_SUBSCRIPTION_MASK_SINK_INPUT = 0x0004U, + /**< Sink input events */ + + PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT = 0x0008U, + /**< Source output events */ + + PA_SUBSCRIPTION_MASK_MODULE = 0x0010U, + /**< Module events */ + + PA_SUBSCRIPTION_MASK_CLIENT = 0x0020U, + /**< Client events */ + + PA_SUBSCRIPTION_MASK_SAMPLE_CACHE = 0x0040U, + /**< Sample cache events */ + + PA_SUBSCRIPTION_MASK_SERVER = 0x0080U, + /**< Other global server changes. */ + +/** \cond fulldocs */ + PA_SUBSCRIPTION_MASK_AUTOLOAD = 0x0100U, + /**< \deprecated Autoload table events. */ +/** \endcond */ + + PA_SUBSCRIPTION_MASK_CARD = 0x0200U, + /**< Card events. \since 0.9.15 */ + + PA_SUBSCRIPTION_MASK_ALL = 0x02ffU + /**< Catch all events */ +} pa_subscription_mask_t; + +/** Subscription event types, as used by pa_context_subscribe() */ +typedef enum pa_subscription_event_type { + PA_SUBSCRIPTION_EVENT_SINK = 0x0000U, + /**< Event type: Sink */ + + PA_SUBSCRIPTION_EVENT_SOURCE = 0x0001U, + /**< Event type: Source */ + + PA_SUBSCRIPTION_EVENT_SINK_INPUT = 0x0002U, + /**< Event type: Sink input */ + + PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT = 0x0003U, + /**< Event type: Source output */ + + PA_SUBSCRIPTION_EVENT_MODULE = 0x0004U, + /**< Event type: Module */ + + PA_SUBSCRIPTION_EVENT_CLIENT = 0x0005U, + /**< Event type: Client */ + + PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE = 0x0006U, + /**< Event type: Sample cache item */ + + PA_SUBSCRIPTION_EVENT_SERVER = 0x0007U, + /**< Event type: Global server change, only occurring with PA_SUBSCRIPTION_EVENT_CHANGE. */ + +/** \cond fulldocs */ + PA_SUBSCRIPTION_EVENT_AUTOLOAD = 0x0008U, + /**< \deprecated Event type: Autoload table changes. */ +/** \endcond */ + + PA_SUBSCRIPTION_EVENT_CARD = 0x0009U, + /**< Event type: Card \since 0.9.15 */ + + PA_SUBSCRIPTION_EVENT_FACILITY_MASK = 0x000FU, + /**< A mask to extract the event type from an event value */ + + PA_SUBSCRIPTION_EVENT_NEW = 0x0000U, + /**< A new object was created */ + + PA_SUBSCRIPTION_EVENT_CHANGE = 0x0010U, + /**< A property of the object was modified */ + + PA_SUBSCRIPTION_EVENT_REMOVE = 0x0020U, + /**< An object was removed */ + + PA_SUBSCRIPTION_EVENT_TYPE_MASK = 0x0030U + /**< A mask to extract the event operation from an event value */ + +} pa_subscription_event_type_t; + +/** Return one if an event type t matches an event mask bitfield */ +#define pa_subscription_match_flags(m, t) (!!((m) & (1 << ((t) & PA_SUBSCRIPTION_EVENT_FACILITY_MASK)))) + +/** \cond fulldocs */ +#define PA_SUBSCRIPTION_MASK_NULL PA_SUBSCRIPTION_MASK_NULL +#define PA_SUBSCRIPTION_MASK_SINK PA_SUBSCRIPTION_MASK_SINK +#define PA_SUBSCRIPTION_MASK_SOURCE PA_SUBSCRIPTION_MASK_SOURCE +#define PA_SUBSCRIPTION_MASK_SINK_INPUT PA_SUBSCRIPTION_MASK_SINK_INPUT +#define PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT +#define PA_SUBSCRIPTION_MASK_MODULE PA_SUBSCRIPTION_MASK_MODULE +#define PA_SUBSCRIPTION_MASK_CLIENT PA_SUBSCRIPTION_MASK_CLIENT +#define PA_SUBSCRIPTION_MASK_SAMPLE_CACHE PA_SUBSCRIPTION_MASK_SAMPLE_CACHE +#define PA_SUBSCRIPTION_MASK_SERVER PA_SUBSCRIPTION_MASK_SERVER +#define PA_SUBSCRIPTION_MASK_AUTOLOAD PA_SUBSCRIPTION_MASK_AUTOLOAD +#define PA_SUBSCRIPTION_MASK_CARD PA_SUBSCRIPTION_MASK_CARD +#define PA_SUBSCRIPTION_MASK_ALL PA_SUBSCRIPTION_MASK_ALL +#define PA_SUBSCRIPTION_EVENT_SINK PA_SUBSCRIPTION_EVENT_SINK +#define PA_SUBSCRIPTION_EVENT_SOURCE PA_SUBSCRIPTION_EVENT_SOURCE +#define PA_SUBSCRIPTION_EVENT_SINK_INPUT PA_SUBSCRIPTION_EVENT_SINK_INPUT +#define PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT +#define PA_SUBSCRIPTION_EVENT_MODULE PA_SUBSCRIPTION_EVENT_MODULE +#define PA_SUBSCRIPTION_EVENT_CLIENT PA_SUBSCRIPTION_EVENT_CLIENT +#define PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE +#define PA_SUBSCRIPTION_EVENT_SERVER PA_SUBSCRIPTION_EVENT_SERVER +#define PA_SUBSCRIPTION_EVENT_AUTOLOAD PA_SUBSCRIPTION_EVENT_AUTOLOAD +#define PA_SUBSCRIPTION_EVENT_CARD PA_SUBSCRIPTION_EVENT_CARD +#define PA_SUBSCRIPTION_EVENT_FACILITY_MASK PA_SUBSCRIPTION_EVENT_FACILITY_MASK +#define PA_SUBSCRIPTION_EVENT_NEW PA_SUBSCRIPTION_EVENT_NEW +#define PA_SUBSCRIPTION_EVENT_CHANGE PA_SUBSCRIPTION_EVENT_CHANGE +#define PA_SUBSCRIPTION_EVENT_REMOVE PA_SUBSCRIPTION_EVENT_REMOVE +#define PA_SUBSCRIPTION_EVENT_TYPE_MASK PA_SUBSCRIPTION_EVENT_TYPE_MASK +/** \endcond */ + +/** A structure for all kinds of timing information of a stream. See + * pa_stream_update_timing_info() and pa_stream_get_timing_info(). The + * total output latency a sample that is written with + * pa_stream_write() takes to be played may be estimated by + * sink_usec+buffer_usec+transport_usec (where buffer_usec is defined + * as pa_bytes_to_usec(write_index-read_index)). The output buffer + * which buffer_usec relates to may be manipulated freely (with + * pa_stream_write()'s seek argument, pa_stream_flush() and friends), + * the buffers sink_usec and source_usec relate to are first-in + * first-out (FIFO) buffers which cannot be flushed or manipulated in + * any way. The total input latency a sample that is recorded takes to + * be delivered to the application is: + * source_usec+buffer_usec+transport_usec-sink_usec. (Take care of + * sign issues!) When connected to a monitor source sink_usec contains + * the latency of the owning sink. The two latency estimations + * described here are implemented in pa_stream_get_latency(). + * + * All time values are in the sound card clock domain, unless noted + * otherwise. The sound card clock usually runs at a slightly different + * rate than the system clock. + * + * Please note that this structure can be extended as part of evolutionary + * API updates at any time in any new release. + * */ +typedef struct pa_timing_info { + struct timeval timestamp; + /**< The system clock time when this timing info structure was + * current. */ + + int synchronized_clocks; + /**< Non-zero if the local and the remote machine have + * synchronized clocks. If synchronized clocks are detected + * transport_usec becomes much more reliable. However, the code + * that detects synchronized clocks is very limited and unreliable + * itself. */ + + pa_usec_t sink_usec; + /**< Time in usecs a sample takes to be played on the sink. For + * playback streams and record streams connected to a monitor + * source. */ + + pa_usec_t source_usec; + /**< Time in usecs a sample takes from being recorded to being + * delivered to the application. Only for record streams. */ + + pa_usec_t transport_usec; + /**< Estimated time in usecs a sample takes to be transferred + * to/from the daemon. For both playback and record streams. */ + + int playing; + /**< Non-zero when the stream is currently not underrun and data + * is being passed on to the device. Only for playback + * streams. This field does not say whether the data is actually + * already being played. To determine this check whether + * since_underrun (converted to usec) is larger than sink_usec.*/ + + int write_index_corrupt; + /**< Non-zero if write_index is not up-to-date because a local + * write command that corrupted it has been issued in the time + * since this latency info was current . Only write commands with + * SEEK_RELATIVE_ON_READ and SEEK_RELATIVE_END can corrupt + * write_index. */ + + int64_t write_index; + /**< Current write index into the playback buffer in bytes. Think + * twice before using this for seeking purposes: it might be out + * of date at the time you want to use it. Consider using + * PA_SEEK_RELATIVE instead. */ + + int read_index_corrupt; + /**< Non-zero if read_index is not up-to-date because a local + * pause or flush request that corrupted it has been issued in the + * time since this latency info was current. */ + + int64_t read_index; + /**< Current read index into the playback buffer in bytes. Think + * twice before using this for seeking purposes: it might be out + * of date at the time you want to use it. Consider using + * PA_SEEK_RELATIVE_ON_READ instead. */ + + pa_usec_t configured_sink_usec; + /**< The configured latency for the sink. \since 0.9.11 */ + + pa_usec_t configured_source_usec; + /**< The configured latency for the source. \since 0.9.11 */ + + int64_t since_underrun; + /**< Bytes that were handed to the sink since the last underrun + * happened, or since playback started again after the last + * underrun. playing will tell you which case it is. \since + * 0.9.11 */ + +} pa_timing_info; + +/** A structure for the spawn api. This may be used to integrate auto + * spawned daemons into your application. For more information see + * pa_context_connect(). When spawning a new child process the + * waitpid() is used on the child's PID. The spawn routine will not + * block or ignore SIGCHLD signals, since this cannot be done in a + * thread compatible way. You might have to do this in + * prefork/postfork. */ +typedef struct pa_spawn_api { + void (*prefork)(void); + /**< Is called just before the fork in the parent process. May be + * NULL. */ + + void (*postfork)(void); + /**< Is called immediately after the fork in the parent + * process. May be NULL.*/ + + void (*atfork)(void); + /**< Is called immediately after the fork in the child + * process. May be NULL. It is not safe to close all file + * descriptors in this function unconditionally, since a UNIX + * socket (created using socketpair()) is passed to the new + * process. */ +} pa_spawn_api; + +/** Seek type for pa_stream_write(). */ +typedef enum pa_seek_mode { + PA_SEEK_RELATIVE = 0, + /**< Seek relative to the write index. */ + + PA_SEEK_ABSOLUTE = 1, + /**< Seek relative to the start of the buffer queue. */ + + PA_SEEK_RELATIVE_ON_READ = 2, + /**< Seek relative to the read index. */ + + PA_SEEK_RELATIVE_END = 3 + /**< Seek relative to the current end of the buffer queue. */ +} pa_seek_mode_t; + +/** \cond fulldocs */ +#define PA_SEEK_RELATIVE PA_SEEK_RELATIVE +#define PA_SEEK_ABSOLUTE PA_SEEK_ABSOLUTE +#define PA_SEEK_RELATIVE_ON_READ PA_SEEK_RELATIVE_ON_READ +#define PA_SEEK_RELATIVE_END PA_SEEK_RELATIVE_END +/** \endcond */ + +/** Special sink flags. */ +typedef enum pa_sink_flags { + PA_SINK_NOFLAGS = 0x0000U, + /**< Flag to pass when no specific options are needed (used to avoid casting) \since 0.9.19 */ + + PA_SINK_HW_VOLUME_CTRL = 0x0001U, + /**< Supports hardware volume control. This is a dynamic flag and may + * change at runtime after the sink has initialized */ + + PA_SINK_LATENCY = 0x0002U, + /**< Supports latency querying */ + + PA_SINK_HARDWARE = 0x0004U, + /**< Is a hardware sink of some kind, in contrast to + * "virtual"/software sinks \since 0.9.3 */ + + PA_SINK_NETWORK = 0x0008U, + /**< Is a networked sink of some kind. \since 0.9.7 */ + + PA_SINK_HW_MUTE_CTRL = 0x0010U, + /**< Supports hardware mute control. This is a dynamic flag and may + * change at runtime after the sink has initialized \since 0.9.11 */ + + PA_SINK_DECIBEL_VOLUME = 0x0020U, + /**< Volume can be translated to dB with pa_sw_volume_to_dB(). This is a + * dynamic flag and may change at runtime after the sink has initialized + * \since 0.9.11 */ + + PA_SINK_FLAT_VOLUME = 0x0040U, + /**< This sink is in flat volume mode, i.e.\ always the maximum of + * the volume of all connected inputs. \since 0.9.15 */ + + PA_SINK_DYNAMIC_LATENCY = 0x0080U, + /**< The latency can be adjusted dynamically depending on the + * needs of the connected streams. \since 0.9.15 */ + + PA_SINK_SET_FORMATS = 0x0100U, + /**< The sink allows setting what formats are supported by the connected + * hardware. The actual functionality to do this might be provided by an + * extension. \since 1.0 */ + +#ifdef __INCLUDED_FROM_PULSE_AUDIO +/** \cond fulldocs */ + /* PRIVATE: Server-side values -- do not try to use these at client-side. + * The server will filter out these flags anyway, so you should never see + * these flags in sinks. */ + + PA_SINK_SHARE_VOLUME_WITH_MASTER = 0x1000000U, + /**< This sink shares the volume with the master sink (used by some filter + * sinks). */ + + PA_SINK_DEFERRED_VOLUME = 0x2000000U, + /**< The HW volume changes are syncronized with SW volume. */ +/** \endcond */ +#endif + +} pa_sink_flags_t; + +/** \cond fulldocs */ +#define PA_SINK_HW_VOLUME_CTRL PA_SINK_HW_VOLUME_CTRL +#define PA_SINK_LATENCY PA_SINK_LATENCY +#define PA_SINK_HARDWARE PA_SINK_HARDWARE +#define PA_SINK_NETWORK PA_SINK_NETWORK +#define PA_SINK_HW_MUTE_CTRL PA_SINK_HW_MUTE_CTRL +#define PA_SINK_DECIBEL_VOLUME PA_SINK_DECIBEL_VOLUME +#define PA_SINK_FLAT_VOLUME PA_SINK_FLAT_VOLUME +#define PA_SINK_DYNAMIC_LATENCY PA_SINK_DYNAMIC_LATENCY +#define PA_SINK_SET_FORMATS PA_SINK_SET_FORMATS +#ifdef __INCLUDED_FROM_PULSE_AUDIO +#define PA_SINK_CLIENT_FLAGS_MASK 0xFFFFFF +#endif + +/** \endcond */ + +/** Sink state. \since 0.9.15 */ +typedef enum pa_sink_state { /* enum serialized in u8 */ + PA_SINK_INVALID_STATE = -1, + /**< This state is used when the server does not support sink state introspection \since 0.9.15 */ + + PA_SINK_RUNNING = 0, + /**< Running, sink is playing and used by at least one non-corked sink-input \since 0.9.15 */ + + PA_SINK_IDLE = 1, + /**< When idle, the sink is playing but there is no non-corked sink-input attached to it \since 0.9.15 */ + + PA_SINK_SUSPENDED = 2, + /**< When suspended, actual sink access can be closed, for instance \since 0.9.15 */ + +/** \cond fulldocs */ + /* PRIVATE: Server-side values -- DO NOT USE THIS ON THE CLIENT + * SIDE! These values are *not* considered part of the official PA + * API/ABI. If you use them your application might break when PA + * is upgraded. Also, please note that these values are not useful + * on the client side anyway. */ + + PA_SINK_INIT = -2, + /**< Initialization state */ + + PA_SINK_UNLINKED = -3 + /**< The state when the sink is getting unregistered and removed from client access */ +/** \endcond */ + +} pa_sink_state_t; + +/** Returns non-zero if sink is playing: running or idle. \since 0.9.15 */ +static inline int PA_SINK_IS_OPENED(pa_sink_state_t x) { + return x == PA_SINK_RUNNING || x == PA_SINK_IDLE; +} + +/** Returns non-zero if sink is running. \since 1.0 */ +static inline int PA_SINK_IS_RUNNING(pa_sink_state_t x) { + return x == PA_SINK_RUNNING; +} + +/** \cond fulldocs */ +#define PA_SINK_INVALID_STATE PA_SINK_INVALID_STATE +#define PA_SINK_RUNNING PA_SINK_RUNNING +#define PA_SINK_IDLE PA_SINK_IDLE +#define PA_SINK_SUSPENDED PA_SINK_SUSPENDED +#define PA_SINK_INIT PA_SINK_INIT +#define PA_SINK_UNLINKED PA_SINK_UNLINKED +#define PA_SINK_IS_OPENED PA_SINK_IS_OPENED +/** \endcond */ + +/** Special source flags. */ +typedef enum pa_source_flags { + PA_SOURCE_NOFLAGS = 0x0000U, + /**< Flag to pass when no specific options are needed (used to avoid casting) \since 0.9.19 */ + + PA_SOURCE_HW_VOLUME_CTRL = 0x0001U, + /**< Supports hardware volume control. This is a dynamic flag and may + * change at runtime after the source has initialized */ + + PA_SOURCE_LATENCY = 0x0002U, + /**< Supports latency querying */ + + PA_SOURCE_HARDWARE = 0x0004U, + /**< Is a hardware source of some kind, in contrast to + * "virtual"/software source \since 0.9.3 */ + + PA_SOURCE_NETWORK = 0x0008U, + /**< Is a networked source of some kind. \since 0.9.7 */ + + PA_SOURCE_HW_MUTE_CTRL = 0x0010U, + /**< Supports hardware mute control. This is a dynamic flag and may + * change at runtime after the source has initialized \since 0.9.11 */ + + PA_SOURCE_DECIBEL_VOLUME = 0x0020U, + /**< Volume can be translated to dB with pa_sw_volume_to_dB(). This is a + * dynamic flag and may change at runtime after the source has initialized + * \since 0.9.11 */ + + PA_SOURCE_DYNAMIC_LATENCY = 0x0040U, + /**< The latency can be adjusted dynamically depending on the + * needs of the connected streams. \since 0.9.15 */ + + PA_SOURCE_FLAT_VOLUME = 0x0080U, + /**< This source is in flat volume mode, i.e.\ always the maximum of + * the volume of all connected outputs. \since 1.0 */ + +#ifdef __INCLUDED_FROM_PULSE_AUDIO +/** \cond fulldocs */ + /* PRIVATE: Server-side values -- do not try to use these at client-side. + * The server will filter out these flags anyway, so you should never see + * these flags in sources. */ + + PA_SOURCE_SHARE_VOLUME_WITH_MASTER = 0x1000000U, + /**< This source shares the volume with the master source (used by some filter + * sources). */ + + PA_SOURCE_DEFERRED_VOLUME = 0x2000000U, + /**< The HW volume changes are syncronized with SW volume. */ +#endif +} pa_source_flags_t; + +/** \cond fulldocs */ +#define PA_SOURCE_HW_VOLUME_CTRL PA_SOURCE_HW_VOLUME_CTRL +#define PA_SOURCE_LATENCY PA_SOURCE_LATENCY +#define PA_SOURCE_HARDWARE PA_SOURCE_HARDWARE +#define PA_SOURCE_NETWORK PA_SOURCE_NETWORK +#define PA_SOURCE_HW_MUTE_CTRL PA_SOURCE_HW_MUTE_CTRL +#define PA_SOURCE_DECIBEL_VOLUME PA_SOURCE_DECIBEL_VOLUME +#define PA_SOURCE_DYNAMIC_LATENCY PA_SOURCE_DYNAMIC_LATENCY +#define PA_SOURCE_FLAT_VOLUME PA_SOURCE_FLAT_VOLUME +#ifdef __INCLUDED_FROM_PULSE_AUDIO +#define PA_SOURCE_CLIENT_FLAGS_MASK 0xFFFFFF +#endif + +/** \endcond */ + +/** Source state. \since 0.9.15 */ +typedef enum pa_source_state { + PA_SOURCE_INVALID_STATE = -1, + /**< This state is used when the server does not support source state introspection \since 0.9.15 */ + + PA_SOURCE_RUNNING = 0, + /**< Running, source is recording and used by at least one non-corked source-output \since 0.9.15 */ + + PA_SOURCE_IDLE = 1, + /**< When idle, the source is still recording but there is no non-corked source-output \since 0.9.15 */ + + PA_SOURCE_SUSPENDED = 2, + /**< When suspended, actual source access can be closed, for instance \since 0.9.15 */ + +/** \cond fulldocs */ + /* PRIVATE: Server-side values -- DO NOT USE THIS ON THE CLIENT + * SIDE! These values are *not* considered part of the official PA + * API/ABI. If you use them your application might break when PA + * is upgraded. Also, please note that these values are not useful + * on the client side anyway. */ + + PA_SOURCE_INIT = -2, + /**< Initialization state */ + + PA_SOURCE_UNLINKED = -3 + /**< The state when the source is getting unregistered and removed from client access */ +/** \endcond */ + +} pa_source_state_t; + +/** Returns non-zero if source is recording: running or idle. \since 0.9.15 */ +static inline int PA_SOURCE_IS_OPENED(pa_source_state_t x) { + return x == PA_SOURCE_RUNNING || x == PA_SOURCE_IDLE; +} + +/** Returns non-zero if source is running \since 1.0 */ +static inline int PA_SOURCE_IS_RUNNING(pa_source_state_t x) { + return x == PA_SOURCE_RUNNING; +} + +/** \cond fulldocs */ +#define PA_SOURCE_INVALID_STATE PA_SOURCE_INVALID_STATE +#define PA_SOURCE_RUNNING PA_SOURCE_RUNNING +#define PA_SOURCE_IDLE PA_SOURCE_IDLE +#define PA_SOURCE_SUSPENDED PA_SOURCE_SUSPENDED +#define PA_SOURCE_INIT PA_SOURCE_INIT +#define PA_SOURCE_UNLINKED PA_SOURCE_UNLINKED +#define PA_SOURCE_IS_OPENED PA_SOURCE_IS_OPENED +/** \endcond */ + +/** A generic free() like callback prototype */ +typedef void (*pa_free_cb_t)(void *p); + +/** A stream policy/meta event requesting that an application should + * cork a specific stream. See pa_stream_event_cb_t for more + * information. \since 0.9.15 */ +#define PA_STREAM_EVENT_REQUEST_CORK "request-cork" + +/** A stream policy/meta event requesting that an application should + * cork a specific stream. See pa_stream_event_cb_t for more + * information, \since 0.9.15 */ +#define PA_STREAM_EVENT_REQUEST_UNCORK "request-uncork" + +/** A stream event notifying that the stream is going to be + * disconnected because the underlying sink changed and no longer + * supports the format that was originally negotiated. Clients need + * to connect a new stream to renegotiate a format and continue + * playback. \since 1.0 */ +#define PA_STREAM_EVENT_FORMAT_LOST "format-lost" + +#ifndef __INCLUDED_FROM_PULSE_AUDIO +/** Port availability / jack detection status + * \since 2.0 */ +typedef enum pa_port_available { + PA_PORT_AVAILABLE_UNKNOWN = 0, /**< This port does not support jack detection \since 2.0 */ + PA_PORT_AVAILABLE_NO = 1, /**< This port is not available, likely because the jack is not plugged in. \since 2.0 */ + PA_PORT_AVAILABLE_YES = 2, /**< This port is available, likely because the jack is plugged in. \since 2.0 */ +} pa_port_available_t; + +/** \cond fulldocs */ +#define PA_PORT_AVAILABLE_UNKNOWN PA_PORT_AVAILABLE_UNKNOWN +#define PA_PORT_AVAILABLE_NO PA_PORT_AVAILABLE_NO +#define PA_PORT_AVAILABLE_YES PA_PORT_AVAILABLE_YES + +/** \endcond */ +#endif + +/** Port type. New types can be added in the future, so applications should + * gracefully handle situations where a type identifier doesn't match any item + * in this enumeration. \since 14.0 */ +typedef enum pa_device_port_type { + PA_DEVICE_PORT_TYPE_UNKNOWN = 0, + PA_DEVICE_PORT_TYPE_AUX = 1, + PA_DEVICE_PORT_TYPE_SPEAKER = 2, + PA_DEVICE_PORT_TYPE_HEADPHONES = 3, + PA_DEVICE_PORT_TYPE_LINE = 4, + PA_DEVICE_PORT_TYPE_MIC = 5, + PA_DEVICE_PORT_TYPE_HEADSET = 6, + PA_DEVICE_PORT_TYPE_HANDSET = 7, + PA_DEVICE_PORT_TYPE_EARPIECE = 8, + PA_DEVICE_PORT_TYPE_SPDIF = 9, + PA_DEVICE_PORT_TYPE_HDMI = 10, + PA_DEVICE_PORT_TYPE_TV = 11, + PA_DEVICE_PORT_TYPE_RADIO = 12, + PA_DEVICE_PORT_TYPE_VIDEO = 13, + PA_DEVICE_PORT_TYPE_USB = 14, + PA_DEVICE_PORT_TYPE_BLUETOOTH = 15, + PA_DEVICE_PORT_TYPE_PORTABLE = 16, + PA_DEVICE_PORT_TYPE_HANDSFREE = 17, + PA_DEVICE_PORT_TYPE_CAR = 18, + PA_DEVICE_PORT_TYPE_HIFI = 19, + PA_DEVICE_PORT_TYPE_PHONE = 20, + PA_DEVICE_PORT_TYPE_NETWORK = 21, + PA_DEVICE_PORT_TYPE_ANALOG = 22, +} pa_device_port_type_t; + +PA_C_DECL_END + +#endif diff --git a/src/pulse/direction.c b/src/pulse/direction.c new file mode 100644 index 0000000..ab64f49 --- /dev/null +++ b/src/pulse/direction.c @@ -0,0 +1,44 @@ +/*** + This file is part of PulseAudio. + + Copyright 2014 Intel Corporation + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include "direction.h" + +#include <pulsecore/i18n.h> + +int pa_direction_valid(pa_direction_t direction) { + if (direction != PA_DIRECTION_INPUT + && direction != PA_DIRECTION_OUTPUT + && direction != (PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT)) + return 0; + + return 1; +} + +const char *pa_direction_to_string(pa_direction_t direction) { + pa_init_i18n(); + + if (direction == PA_DIRECTION_INPUT) + return _("input"); + if (direction == PA_DIRECTION_OUTPUT) + return _("output"); + if (direction == (PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT)) + return _("bidirectional"); + + return _("invalid"); +} diff --git a/src/pulse/direction.h b/src/pulse/direction.h new file mode 100644 index 0000000..65ece69 --- /dev/null +++ b/src/pulse/direction.h @@ -0,0 +1,35 @@ +#ifndef foodirectionhfoo +#define foodirectionhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2014 Intel Corporation + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulse/def.h> + +/** \file + * Utility functions for \ref pa_direction_t. */ + +/** Return non-zero if the given value is a valid direction (either input, + * output or bidirectional). \since 6.0 */ +int pa_direction_valid(pa_direction_t direction) PA_GCC_CONST; + +/** Return a textual representation of the direction. \since 6.0 */ +const char *pa_direction_to_string(pa_direction_t direction); + +#endif diff --git a/src/pulse/error.c b/src/pulse/error.c new file mode 100644 index 0000000..2760928 --- /dev/null +++ b/src/pulse/error.c @@ -0,0 +1,76 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> + +#include <pulse/def.h> + +#include <pulsecore/i18n.h> + +#include "error.h" + +const char*pa_strerror(int error) { + + static const char* const errortab[PA_ERR_MAX] = { + [PA_OK] = N_("OK"), + [PA_ERR_ACCESS] = N_("Access denied"), + [PA_ERR_COMMAND] = N_("Unknown command"), + [PA_ERR_INVALID] = N_("Invalid argument"), + [PA_ERR_EXIST] = N_("Entity exists"), + [PA_ERR_NOENTITY] = N_("No such entity"), + [PA_ERR_CONNECTIONREFUSED] = N_("Connection refused"), + [PA_ERR_PROTOCOL] = N_("Protocol error"), + [PA_ERR_TIMEOUT] = N_("Timeout"), + [PA_ERR_AUTHKEY] = N_("No authentication key"), + [PA_ERR_INTERNAL] = N_("Internal error"), + [PA_ERR_CONNECTIONTERMINATED] = N_("Connection terminated"), + [PA_ERR_KILLED] = N_("Entity killed"), + [PA_ERR_INVALIDSERVER] = N_("Invalid server"), + [PA_ERR_MODINITFAILED] = N_("Module initialization failed"), + [PA_ERR_BADSTATE] = N_("Bad state"), + [PA_ERR_NODATA] = N_("No data"), + [PA_ERR_VERSION] = N_("Incompatible protocol version"), + [PA_ERR_TOOLARGE] = N_("Too large"), + [PA_ERR_NOTSUPPORTED] = N_("Not supported"), + [PA_ERR_UNKNOWN] = N_("Unknown error code"), + [PA_ERR_NOEXTENSION] = N_("No such extension"), + [PA_ERR_OBSOLETE] = N_("Obsolete functionality"), + [PA_ERR_NOTIMPLEMENTED] = N_("Missing implementation"), + [PA_ERR_FORKED] = N_("Client forked"), + [PA_ERR_IO] = N_("Input/Output error"), + [PA_ERR_BUSY] = N_("Device or resource busy") + }; + + pa_init_i18n(); + + if (error < 0) + error = -error; + + if (error >= PA_ERR_MAX) + return NULL; + + return _(errortab[error]); +} diff --git a/src/pulse/error.h b/src/pulse/error.h new file mode 100644 index 0000000..7b9b84a --- /dev/null +++ b/src/pulse/error.h @@ -0,0 +1,37 @@ +#ifndef fooerrorhfoo +#define fooerrorhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulse/cdecl.h> +#include <pulse/version.h> + +/** \file + * Error management */ + +PA_C_DECL_BEGIN + +/** Return a human readable error message for the specified numeric error code */ +const char* pa_strerror(int error); + +PA_C_DECL_END + +#endif diff --git a/src/pulse/ext-device-manager.c b/src/pulse/ext-device-manager.c new file mode 100644 index 0000000..38f96da --- /dev/null +++ b/src/pulse/ext-device-manager.c @@ -0,0 +1,434 @@ +/*** + This file is part of PulseAudio. + + Copyright 2008 Lennart Poettering + Copyright 2009 Colin Guthrie + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/context.h> +#include <pulse/xmalloc.h> +#include <pulse/fork-detect.h> +#include <pulse/operation.h> + +#include <pulsecore/macro.h> +#include <pulsecore/pstream-util.h> + +#include "internal.h" +#include "ext-device-manager.h" + +enum { + SUBCOMMAND_TEST, + SUBCOMMAND_READ, + SUBCOMMAND_RENAME, + SUBCOMMAND_DELETE, + SUBCOMMAND_ROLE_DEVICE_PRIORITY_ROUTING, + SUBCOMMAND_REORDER, + SUBCOMMAND_SUBSCRIBE, + SUBCOMMAND_EVENT +}; + +static void ext_device_manager_test_cb(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_operation *o = userdata; + uint32_t version = PA_INVALID_INDEX; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (!o->context) + goto finish; + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(o->context, command, t, false) < 0) + goto finish; + + } else if (pa_tagstruct_getu32(t, &version) < 0 || + !pa_tagstruct_eof(t)) { + + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + + if (o->callback) { + pa_ext_device_manager_test_cb_t cb = (pa_ext_device_manager_test_cb_t) o->callback; + cb(o->context, version, o->userdata); + } + +finish: + pa_operation_done(o); + pa_operation_unref(o); +} + +pa_operation *pa_ext_device_manager_test( + pa_context *c, + pa_ext_device_manager_test_cb_t cb, + void *userdata) { + + uint32_t tag; + pa_operation *o; + pa_tagstruct *t; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 14, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_EXTENSION, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, "module-device-manager"); + pa_tagstruct_putu32(t, SUBCOMMAND_TEST); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, ext_device_manager_test_cb, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +static void ext_device_manager_read_cb(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_operation *o = userdata; + int eol = 1; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (!o->context) + goto finish; + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(o->context, command, t, false) < 0) + goto finish; + + eol = -1; + } else { + + while (!pa_tagstruct_eof(t)) { + pa_ext_device_manager_info i; + + memset(&i, 0, sizeof(i)); + + if (pa_tagstruct_gets(t, &i.name) < 0 || + pa_tagstruct_gets(t, &i.description) < 0 || + pa_tagstruct_gets(t, &i.icon) < 0 || + pa_tagstruct_getu32(t, &i.index) < 0 || + pa_tagstruct_getu32(t, &i.n_role_priorities) < 0) { + + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + + if (i.n_role_priorities > 0) { + uint32_t j; + i.role_priorities = pa_xnew0(pa_ext_device_manager_role_priority_info, i.n_role_priorities+1); + + for (j = 0; j < i.n_role_priorities; j++) { + + if (pa_tagstruct_gets(t, &i.role_priorities[j].role) < 0 || + pa_tagstruct_getu32(t, &i.role_priorities[j].priority) < 0) { + + pa_context_fail(o->context, PA_ERR_PROTOCOL); + pa_xfree(i.role_priorities); + goto finish; + } + } + + /* Terminate with an extra NULL entry, just to make sure */ + i.role_priorities[j].role = NULL; + i.role_priorities[j].priority = 0; + } + + if (o->callback) { + pa_ext_device_manager_read_cb_t cb = (pa_ext_device_manager_read_cb_t) o->callback; + cb(o->context, &i, 0, o->userdata); + } + + pa_xfree(i.role_priorities); + } + } + + if (o->callback) { + pa_ext_device_manager_read_cb_t cb = (pa_ext_device_manager_read_cb_t) o->callback; + cb(o->context, NULL, eol, o->userdata); + } + +finish: + pa_operation_done(o); + pa_operation_unref(o); +} + +pa_operation *pa_ext_device_manager_read( + pa_context *c, + pa_ext_device_manager_read_cb_t cb, + void *userdata) { + + uint32_t tag; + pa_operation *o; + pa_tagstruct *t; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 14, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_EXTENSION, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, "module-device-manager"); + pa_tagstruct_putu32(t, SUBCOMMAND_READ); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, ext_device_manager_read_cb, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation *pa_ext_device_manager_set_device_description( + pa_context *c, + const char* device, + const char* description, + pa_context_success_cb_t cb, + void *userdata) { + + uint32_t tag; + pa_operation *o = NULL; + pa_tagstruct *t = NULL; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(device); + pa_assert(description); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 14, PA_ERR_NOTSUPPORTED); + PA_CHECK_VALIDITY_RETURN_NULL(c, *description, PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_EXTENSION, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, "module-device-manager"); + pa_tagstruct_putu32(t, SUBCOMMAND_RENAME); + + pa_tagstruct_puts(t, device); + pa_tagstruct_puts(t, description); + + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation *pa_ext_device_manager_delete( + pa_context *c, + const char *const s[], + pa_context_success_cb_t cb, + void *userdata) { + + uint32_t tag; + pa_operation *o = NULL; + pa_tagstruct *t = NULL; + const char *const *k; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(s); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 14, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_EXTENSION, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, "module-device-manager"); + pa_tagstruct_putu32(t, SUBCOMMAND_DELETE); + + for (k = s; *k; k++) { + if (!*k || !**k) + goto fail; + + pa_tagstruct_puts(t, *k); + } + + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; + +fail: + if (o) { + pa_operation_cancel(o); + pa_operation_unref(o); + } + + if (t) + pa_tagstruct_free(t); + + pa_context_set_error(c, PA_ERR_INVALID); + return NULL; +} + +pa_operation *pa_ext_device_manager_enable_role_device_priority_routing( + pa_context *c, + int enable, + pa_context_success_cb_t cb, + void *userdata) { + + uint32_t tag; + pa_operation *o = NULL; + pa_tagstruct *t = NULL; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 14, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_EXTENSION, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, "module-device-manager"); + pa_tagstruct_putu32(t, SUBCOMMAND_ROLE_DEVICE_PRIORITY_ROUTING); + pa_tagstruct_put_boolean(t, !!enable); + + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation *pa_ext_device_manager_reorder_devices_for_role( + pa_context *c, + const char* role, + const char** devices, + pa_context_success_cb_t cb, + void *userdata) { + + uint32_t tag, i; + pa_operation *o = NULL; + pa_tagstruct *t = NULL; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 14, PA_ERR_NOTSUPPORTED); + + pa_assert(role); + pa_assert(devices); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_EXTENSION, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, "module-device-manager"); + pa_tagstruct_putu32(t, SUBCOMMAND_REORDER); + pa_tagstruct_puts(t, role); + + i = 0; while (devices[i]) i++; + pa_tagstruct_putu32(t, i); + + i = 0; + while (devices[i]) + pa_tagstruct_puts(t, devices[i++]); + + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation *pa_ext_device_manager_subscribe( + pa_context *c, + int enable, + pa_context_success_cb_t cb, + void *userdata) { + + uint32_t tag; + pa_operation *o; + pa_tagstruct *t; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 14, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_EXTENSION, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, "module-device-manager"); + pa_tagstruct_putu32(t, SUBCOMMAND_SUBSCRIBE); + pa_tagstruct_put_boolean(t, enable); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +void pa_ext_device_manager_set_subscribe_cb( + pa_context *c, + pa_ext_device_manager_subscribe_cb_t cb, + void *userdata) { + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + if (pa_detect_fork()) + return; + + c->ext_device_manager.callback = cb; + c->ext_device_manager.userdata = userdata; +} + +void pa_ext_device_manager_command(pa_context *c, uint32_t tag, pa_tagstruct *t) { + uint32_t subcommand; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &subcommand) < 0 || + !pa_tagstruct_eof(t)) { + + pa_context_fail(c, PA_ERR_PROTOCOL); + return; + } + + if (subcommand != SUBCOMMAND_EVENT) { + pa_context_fail(c, PA_ERR_PROTOCOL); + return; + } + + if (c->ext_device_manager.callback) + c->ext_device_manager.callback(c, c->ext_device_manager.userdata); +} diff --git a/src/pulse/ext-device-manager.h b/src/pulse/ext-device-manager.h new file mode 100644 index 0000000..8c05e1c --- /dev/null +++ b/src/pulse/ext-device-manager.h @@ -0,0 +1,130 @@ +#ifndef foopulseextdevicemanagerhfoo +#define foopulseextdevicemanagerhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2008 Lennart Poettering + Copyright 2009 Colin Guthrie + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulse/cdecl.h> +#include <pulse/context.h> +#include <pulse/version.h> + +/** \file + * + * Routines for controlling module-device-manager + */ + +PA_C_DECL_BEGIN + +/* Don't extend this struct! It will break binary compatibility, because + * pa_ext_device_manager_info.role_priorities points to an array of structs + * instead of an array of pointers to structs. */ +typedef struct pa_ext_device_manager_role_priority_info { + const char *role; + uint32_t priority; +} pa_ext_device_manager_role_priority_info; + +/** Stores information about one device in the device database that is + * maintained by module-device-manager. \since 0.9.21 */ +typedef struct pa_ext_device_manager_info { + const char *name; /**< Identifier string of the device. A string like "sink:" or similar followed by the name of the device. */ + const char *description; /**< The description of the device when it was last seen, if applicable and saved */ + const char *icon; /**< The icon given to the device */ + uint32_t index; /**< The device index if it is currently available or PA_INVALID_INDEX */ + uint32_t n_role_priorities; /**< How many role priorities do we have? */ + pa_ext_device_manager_role_priority_info *role_priorities; /**< An array of role priority structures or NULL */ +} pa_ext_device_manager_info; + +/** Callback prototype for pa_ext_device_manager_test(). \since 0.9.21 */ +typedef void (*pa_ext_device_manager_test_cb_t)( + pa_context *c, + uint32_t version, + void *userdata); + +/** Test if this extension module is available in the server. \since 0.9.21 */ +pa_operation *pa_ext_device_manager_test( + pa_context *c, + pa_ext_device_manager_test_cb_t cb, + void *userdata); + +/** Callback prototype for pa_ext_device_manager_read(). \since 0.9.21 */ +typedef void (*pa_ext_device_manager_read_cb_t)( + pa_context *c, + const pa_ext_device_manager_info *info, + int eol, + void *userdata); + +/** Read all entries from the device database. \since 0.9.21 */ +pa_operation *pa_ext_device_manager_read( + pa_context *c, + pa_ext_device_manager_read_cb_t cb, + void *userdata); + +/** Sets the description for a device. \since 0.9.21 */ +pa_operation *pa_ext_device_manager_set_device_description( + pa_context *c, + const char* device, + const char* description, + pa_context_success_cb_t cb, + void *userdata); + +/** Delete entries from the device database. \since 0.9.21 */ +pa_operation *pa_ext_device_manager_delete( + pa_context *c, + const char *const s[], + pa_context_success_cb_t cb, + void *userdata); + +/** Enable the role-based device-priority routing mode. \since 0.9.21 */ +pa_operation *pa_ext_device_manager_enable_role_device_priority_routing( + pa_context *c, + int enable, + pa_context_success_cb_t cb, + void *userdata); + +/** Prefer a given device in the priority list. \since 0.9.21 */ +pa_operation *pa_ext_device_manager_reorder_devices_for_role( + pa_context *c, + const char* role, + const char** devices, + pa_context_success_cb_t cb, + void *userdata); + +/** Subscribe to changes in the device database. \since 0.9.21 */ +pa_operation *pa_ext_device_manager_subscribe( + pa_context *c, + int enable, + pa_context_success_cb_t cb, + void *userdata); + +/** Callback prototype for pa_ext_device_manager_set_subscribe_cb(). \since 0.9.21 */ +typedef void (*pa_ext_device_manager_subscribe_cb_t)( + pa_context *c, + void *userdata); + +/** Set the subscription callback that is called when + * pa_ext_device_manager_subscribe() was called. \since 0.9.21 */ +void pa_ext_device_manager_set_subscribe_cb( + pa_context *c, + pa_ext_device_manager_subscribe_cb_t cb, + void *userdata); + +PA_C_DECL_END + +#endif diff --git a/src/pulse/ext-device-restore.c b/src/pulse/ext-device-restore.c new file mode 100644 index 0000000..5c6d88b --- /dev/null +++ b/src/pulse/ext-device-restore.c @@ -0,0 +1,373 @@ +/*** + This file is part of PulseAudio. + + Copyright 2008 Lennart Poettering + Copyright 2011 Colin Guthrie + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/context.h> +#include <pulse/gccmacro.h> +#include <pulse/xmalloc.h> +#include <pulse/fork-detect.h> +#include <pulse/operation.h> +#include <pulse/format.h> + +#include <pulsecore/macro.h> +#include <pulsecore/pstream-util.h> + +#include "internal.h" +#include "ext-device-restore.h" + +/* Protocol extension commands */ +enum { + SUBCOMMAND_TEST, + SUBCOMMAND_SUBSCRIBE, + SUBCOMMAND_EVENT, + SUBCOMMAND_READ_FORMATS_ALL, + SUBCOMMAND_READ_FORMATS, + SUBCOMMAND_SAVE_FORMATS +}; + +static void ext_device_restore_test_cb(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_operation *o = userdata; + uint32_t version = PA_INVALID_INDEX; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (!o->context) + goto finish; + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(o->context, command, t, false) < 0) + goto finish; + + } else if (pa_tagstruct_getu32(t, &version) < 0 || + !pa_tagstruct_eof(t)) { + + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + + if (o->callback) { + pa_ext_device_restore_test_cb_t cb = (pa_ext_device_restore_test_cb_t) o->callback; + cb(o->context, version, o->userdata); + } + +finish: + pa_operation_done(o); + pa_operation_unref(o); +} + +pa_operation *pa_ext_device_restore_test( + pa_context *c, + pa_ext_device_restore_test_cb_t cb, + void *userdata) { + + uint32_t tag; + pa_operation *o; + pa_tagstruct *t; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 14, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_EXTENSION, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, "module-device-restore"); + pa_tagstruct_putu32(t, SUBCOMMAND_TEST); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, ext_device_restore_test_cb, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation *pa_ext_device_restore_subscribe( + pa_context *c, + int enable, + pa_context_success_cb_t cb, + void *userdata) { + + uint32_t tag; + pa_operation *o; + pa_tagstruct *t; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 14, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_EXTENSION, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, "module-device-restore"); + pa_tagstruct_putu32(t, SUBCOMMAND_SUBSCRIBE); + pa_tagstruct_put_boolean(t, enable); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +void pa_ext_device_restore_set_subscribe_cb( + pa_context *c, + pa_ext_device_restore_subscribe_cb_t cb, + void *userdata) { + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + if (pa_detect_fork()) + return; + + c->ext_device_restore.callback = cb; + c->ext_device_restore.userdata = userdata; +} + +static void ext_device_restore_read_device_formats_cb(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_operation *o = userdata; + int eol = 1; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (!o->context) + goto finish; + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(o->context, command, t, false) < 0) + goto finish; + + eol = -1; + } else { + uint8_t j; + + while (!pa_tagstruct_eof(t)) { + pa_ext_device_restore_info i; + pa_zero(i); + + if (pa_tagstruct_getu32(t, &i.type) < 0 || + pa_tagstruct_getu32(t, &i.index) < 0 || + pa_tagstruct_getu8(t, &i.n_formats) < 0) { + + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + + if (PA_DEVICE_TYPE_SINK != i.type && PA_DEVICE_TYPE_SOURCE != i.type) { + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + + if (i.index == PA_INVALID_INDEX) { + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + + if (i.n_formats > 0) { + i.formats = pa_xnew0(pa_format_info*, i.n_formats); + + for (j = 0; j < i.n_formats; j++) { + + pa_format_info *f = i.formats[j] = pa_format_info_new(); + if (pa_tagstruct_get_format_info(t, f) < 0) { + uint8_t k; + + pa_context_fail(o->context, PA_ERR_PROTOCOL); + for (k = 0; k < j+1; k++) + pa_format_info_free(i.formats[k]); + pa_xfree(i.formats); + goto finish; + } + } + } + + if (o->callback) { + pa_ext_device_restore_read_device_formats_cb_t cb = (pa_ext_device_restore_read_device_formats_cb_t) o->callback; + cb(o->context, &i, 0, o->userdata); + } + + for (j = 0; j < i.n_formats; j++) + pa_format_info_free(i.formats[j]); + pa_xfree(i.formats); + } + } + + if (o->callback) { + pa_ext_device_restore_read_device_formats_cb_t cb = (pa_ext_device_restore_read_device_formats_cb_t) o->callback; + cb(o->context, NULL, eol, o->userdata); + } + +finish: + pa_operation_done(o); + pa_operation_unref(o); +} + +pa_operation *pa_ext_device_restore_read_formats_all( + pa_context *c, + pa_ext_device_restore_read_device_formats_cb_t cb, + void *userdata) { + + uint32_t tag; + pa_operation *o; + pa_tagstruct *t; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 14, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_EXTENSION, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, "module-device-restore"); + pa_tagstruct_putu32(t, SUBCOMMAND_READ_FORMATS_ALL); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, ext_device_restore_read_device_formats_cb, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation *pa_ext_device_restore_read_formats( + pa_context *c, + pa_device_type_t type, + uint32_t idx, + pa_ext_device_restore_read_device_formats_cb_t cb, + void *userdata) { + + uint32_t tag; + pa_operation *o; + pa_tagstruct *t; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(idx != PA_INVALID_INDEX); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 14, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_EXTENSION, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, "module-device-restore"); + pa_tagstruct_putu32(t, SUBCOMMAND_READ_FORMATS); + pa_tagstruct_putu32(t, type); + pa_tagstruct_putu32(t, idx); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, ext_device_restore_read_device_formats_cb, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation *pa_ext_device_restore_save_formats( + pa_context *c, + pa_device_type_t type, + uint32_t idx, + uint8_t n_formats, + pa_format_info **formats, + pa_context_success_cb_t cb, + void *userdata) { + + uint32_t tag; + pa_operation *o; + pa_tagstruct *t; + uint8_t j; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(idx != PA_INVALID_INDEX); + pa_assert(n_formats > 0); + pa_assert(formats && *formats); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 14, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_EXTENSION, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, "module-device-restore"); + pa_tagstruct_putu32(t, SUBCOMMAND_SAVE_FORMATS); + + pa_tagstruct_putu32(t, type); + pa_tagstruct_putu32(t, idx); + pa_tagstruct_putu8(t, n_formats); + for (j = 0; j < n_formats; j++) + pa_tagstruct_put_format_info(t, formats[j]); + + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +/* Command function defined in internal.h */ +void pa_ext_device_restore_command(pa_context *c, uint32_t tag, pa_tagstruct *t) { + uint32_t subcommand; + pa_device_type_t type; + uint32_t idx; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &subcommand) < 0 || + pa_tagstruct_getu32(t, &type) < 0 || + pa_tagstruct_getu32(t, &idx) < 0 || + !pa_tagstruct_eof(t)) { + + pa_context_fail(c, PA_ERR_PROTOCOL); + return; + } + + if (subcommand != SUBCOMMAND_EVENT) { + pa_context_fail(c, PA_ERR_PROTOCOL); + return; + } + + if (PA_DEVICE_TYPE_SINK != type && PA_DEVICE_TYPE_SOURCE != type) { + pa_context_fail(c, PA_ERR_PROTOCOL); + return; + } + + if (idx == PA_INVALID_INDEX) { + pa_context_fail(c, PA_ERR_PROTOCOL); + return; + } + + if (c->ext_device_restore.callback) + c->ext_device_restore.callback(c, type, idx, c->ext_device_restore.userdata); +} diff --git a/src/pulse/ext-device-restore.h b/src/pulse/ext-device-restore.h new file mode 100644 index 0000000..246b060 --- /dev/null +++ b/src/pulse/ext-device-restore.h @@ -0,0 +1,110 @@ +#ifndef foopulseextdevicerestorehfoo +#define foopulseextdevicerestorehfoo + +/*** + This file is part of PulseAudio. + + Copyright 2008 Lennart Poettering + Copyright 2011 Colin Guthrie + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulse/context.h> +#include <pulse/format.h> +#include <pulse/version.h> + +/** \file + * + * Routines for controlling module-device-restore + */ + +PA_C_DECL_BEGIN + +/** Stores information about one device in the device database that is + * maintained by module-device-manager. \since 1.0 */ +typedef struct pa_ext_device_restore_info { + pa_device_type_t type; /**< Device type sink or source? */ + uint32_t index; /**< The device index */ + uint8_t n_formats; /**< How many formats do we have? */ + pa_format_info **formats; /**< An array of formats (may be NULL if n_formats == 0) */ +} pa_ext_device_restore_info; + +/** Callback prototype for pa_ext_device_restore_test(). \since 1.0 */ +typedef void (*pa_ext_device_restore_test_cb_t)( + pa_context *c, + uint32_t version, + void *userdata); + +/** Test if this extension module is available in the server. \since 1.0 */ +pa_operation *pa_ext_device_restore_test( + pa_context *c, + pa_ext_device_restore_test_cb_t cb, + void *userdata); + +/** Subscribe to changes in the device database. \since 1.0 */ +pa_operation *pa_ext_device_restore_subscribe( + pa_context *c, + int enable, + pa_context_success_cb_t cb, + void *userdata); + +/** Callback prototype for pa_ext_device_restore_set_subscribe_cb(). \since 1.0 */ +typedef void (*pa_ext_device_restore_subscribe_cb_t)( + pa_context *c, + pa_device_type_t type, + uint32_t idx, + void *userdata); + +/** Set the subscription callback that is called when + * pa_ext_device_restore_subscribe() was called. \since 1.0 */ +void pa_ext_device_restore_set_subscribe_cb( + pa_context *c, + pa_ext_device_restore_subscribe_cb_t cb, + void *userdata); + +/** Callback prototype for pa_ext_device_restore_read_formats(). \since 1.0 */ +typedef void (*pa_ext_device_restore_read_device_formats_cb_t)( + pa_context *c, + const pa_ext_device_restore_info *info, + int eol, + void *userdata); + +/** Read the formats for all present devices from the device database. \since 1.0 */ +pa_operation *pa_ext_device_restore_read_formats_all( + pa_context *c, + pa_ext_device_restore_read_device_formats_cb_t cb, + void *userdata); + +/** Read an entry from the device database. \since 1.0 */ +pa_operation *pa_ext_device_restore_read_formats( + pa_context *c, + pa_device_type_t type, + uint32_t idx, + pa_ext_device_restore_read_device_formats_cb_t cb, + void *userdata); + +/** Read an entry from the device database. \since 1.0 */ +pa_operation *pa_ext_device_restore_save_formats( + pa_context *c, + pa_device_type_t type, + uint32_t idx, + uint8_t n_formats, + pa_format_info **formats, + pa_context_success_cb_t cb, + void *userdata); + +PA_C_DECL_END + +#endif diff --git a/src/pulse/ext-stream-restore.c b/src/pulse/ext-stream-restore.c new file mode 100644 index 0000000..a250d24 --- /dev/null +++ b/src/pulse/ext-stream-restore.c @@ -0,0 +1,361 @@ +/*** + This file is part of PulseAudio. + + Copyright 2008 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/context.h> +#include <pulse/fork-detect.h> +#include <pulse/operation.h> + +#include <pulsecore/macro.h> +#include <pulsecore/pstream-util.h> + +#include "internal.h" +#include "ext-stream-restore.h" + +enum { + SUBCOMMAND_TEST, + SUBCOMMAND_READ, + SUBCOMMAND_WRITE, + SUBCOMMAND_DELETE, + SUBCOMMAND_SUBSCRIBE, + SUBCOMMAND_EVENT +}; + +static void ext_stream_restore_test_cb(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_operation *o = userdata; + uint32_t version = PA_INVALID_INDEX; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (!o->context) + goto finish; + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(o->context, command, t, false) < 0) + goto finish; + + } else if (pa_tagstruct_getu32(t, &version) < 0 || + !pa_tagstruct_eof(t)) { + + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + + if (o->callback) { + pa_ext_stream_restore_test_cb_t cb = (pa_ext_stream_restore_test_cb_t) o->callback; + cb(o->context, version, o->userdata); + } + +finish: + pa_operation_done(o); + pa_operation_unref(o); +} + +pa_operation *pa_ext_stream_restore_test( + pa_context *c, + pa_ext_stream_restore_test_cb_t cb, + void *userdata) { + + uint32_t tag; + pa_operation *o; + pa_tagstruct *t; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 14, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_EXTENSION, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, "module-stream-restore"); + pa_tagstruct_putu32(t, SUBCOMMAND_TEST); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, ext_stream_restore_test_cb, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +static void ext_stream_restore_read_cb(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_operation *o = userdata; + int eol = 1; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (!o->context) + goto finish; + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(o->context, command, t, false) < 0) + goto finish; + + eol = -1; + } else { + + while (!pa_tagstruct_eof(t)) { + pa_ext_stream_restore_info i; + bool mute = false; + + memset(&i, 0, sizeof(i)); + + if (pa_tagstruct_gets(t, &i.name) < 0 || + pa_tagstruct_get_channel_map(t, &i.channel_map) < 0 || + pa_tagstruct_get_cvolume(t, &i.volume) < 0 || + pa_tagstruct_gets(t, &i.device) < 0 || + pa_tagstruct_get_boolean(t, &mute) < 0) { + + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + + i.mute = (int) mute; + + if (o->callback) { + pa_ext_stream_restore_read_cb_t cb = (pa_ext_stream_restore_read_cb_t) o->callback; + cb(o->context, &i, 0, o->userdata); + } + } + } + + if (o->callback) { + pa_ext_stream_restore_read_cb_t cb = (pa_ext_stream_restore_read_cb_t) o->callback; + cb(o->context, NULL, eol, o->userdata); + } + +finish: + pa_operation_done(o); + pa_operation_unref(o); +} + +pa_operation *pa_ext_stream_restore_read( + pa_context *c, + pa_ext_stream_restore_read_cb_t cb, + void *userdata) { + + uint32_t tag; + pa_operation *o; + pa_tagstruct *t; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 14, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_EXTENSION, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, "module-stream-restore"); + pa_tagstruct_putu32(t, SUBCOMMAND_READ); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, ext_stream_restore_read_cb, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation *pa_ext_stream_restore_write( + pa_context *c, + pa_update_mode_t mode, + const pa_ext_stream_restore_info data[], + unsigned n, + int apply_immediately, + pa_context_success_cb_t cb, + void *userdata) { + + uint32_t tag; + pa_operation *o = NULL; + pa_tagstruct *t = NULL; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(mode == PA_UPDATE_MERGE || mode == PA_UPDATE_REPLACE || mode == PA_UPDATE_SET); + pa_assert(data); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 14, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_EXTENSION, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, "module-stream-restore"); + pa_tagstruct_putu32(t, SUBCOMMAND_WRITE); + + pa_tagstruct_putu32(t, mode); + pa_tagstruct_put_boolean(t, apply_immediately); + + for (; n > 0; n--, data++) { + if (!data->name || !*data->name) + goto fail; + + pa_tagstruct_puts(t, data->name); + + if (data->volume.channels > 0 && + !pa_cvolume_compatible_with_channel_map(&data->volume, &data->channel_map)) + goto fail; + + pa_tagstruct_put_channel_map(t, &data->channel_map); + pa_tagstruct_put_cvolume(t, &data->volume); + pa_tagstruct_puts(t, data->device); + pa_tagstruct_put_boolean(t, data->mute); + } + + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; + +fail: + pa_operation_cancel(o); + pa_operation_unref(o); + + pa_tagstruct_free(t); + + pa_context_set_error(c, PA_ERR_INVALID); + return NULL; +} + +pa_operation *pa_ext_stream_restore_delete( + pa_context *c, + const char *const s[], + pa_context_success_cb_t cb, + void *userdata) { + + uint32_t tag; + pa_operation *o = NULL; + pa_tagstruct *t = NULL; + const char *const *k; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(s); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 14, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_EXTENSION, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, "module-stream-restore"); + pa_tagstruct_putu32(t, SUBCOMMAND_DELETE); + + for (k = s; *k; k++) { + if (!*k || !**k) + goto fail; + + pa_tagstruct_puts(t, *k); + } + + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; + +fail: + pa_operation_cancel(o); + pa_operation_unref(o); + + pa_tagstruct_free(t); + + pa_context_set_error(c, PA_ERR_INVALID); + return NULL; +} + +pa_operation *pa_ext_stream_restore_subscribe( + pa_context *c, + int enable, + pa_context_success_cb_t cb, + void *userdata) { + + uint32_t tag; + pa_operation *o; + pa_tagstruct *t; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 14, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_EXTENSION, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, "module-stream-restore"); + pa_tagstruct_putu32(t, SUBCOMMAND_SUBSCRIBE); + pa_tagstruct_put_boolean(t, enable); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +void pa_ext_stream_restore_set_subscribe_cb( + pa_context *c, + pa_ext_stream_restore_subscribe_cb_t cb, + void *userdata) { + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + if (pa_detect_fork()) + return; + + c->ext_stream_restore.callback = cb; + c->ext_stream_restore.userdata = userdata; +} + +void pa_ext_stream_restore_command(pa_context *c, uint32_t tag, pa_tagstruct *t) { + uint32_t subcommand; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &subcommand) < 0 || + !pa_tagstruct_eof(t)) { + + pa_context_fail(c, PA_ERR_PROTOCOL); + return; + } + + if (subcommand != SUBCOMMAND_EVENT) { + pa_context_fail(c, PA_ERR_PROTOCOL); + return; + } + + if (c->ext_stream_restore.callback) + c->ext_stream_restore.callback(c, c->ext_stream_restore.userdata); +} diff --git a/src/pulse/ext-stream-restore.h b/src/pulse/ext-stream-restore.h new file mode 100644 index 0000000..dd7f4ae --- /dev/null +++ b/src/pulse/ext-stream-restore.h @@ -0,0 +1,109 @@ +#ifndef foopulseextstreamrestorehfoo +#define foopulseextstreamrestorehfoo + +/*** + This file is part of PulseAudio. + + Copyright 2008 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulse/cdecl.h> +#include <pulse/context.h> +#include <pulse/version.h> +#include <pulse/volume.h> +#include <pulse/channelmap.h> + +/** \file + * + * Routines for controlling module-stream-restore + */ + +PA_C_DECL_BEGIN + +/** Stores information about one entry in the stream database that is + * maintained by module-stream-restore. \since 0.9.12 */ +typedef struct pa_ext_stream_restore_info { + const char *name; /**< Identifier string of the stream. A string like "sink-input-by-role:" or similar followed by some arbitrary property value. */ + pa_channel_map channel_map; /**< The channel map for the volume field, if applicable */ + pa_cvolume volume; /**< The volume of the stream when it was seen last, if applicable and saved */ + const char *device; /**< The sink/source of the stream when it was last seen, if applicable and saved */ + int mute; /**< The boolean mute state of the stream when it was last seen, if applicable and saved */ +} pa_ext_stream_restore_info; + +/** Callback prototype for pa_ext_stream_restore_test(). \since 0.9.12 */ +typedef void (*pa_ext_stream_restore_test_cb_t)( + pa_context *c, + uint32_t version, + void *userdata); + +/** Test if this extension module is available in the server. \since 0.9.12 */ +pa_operation *pa_ext_stream_restore_test( + pa_context *c, + pa_ext_stream_restore_test_cb_t cb, + void *userdata); + +/** Callback prototype for pa_ext_stream_restore_read(). \since 0.9.12 */ +typedef void (*pa_ext_stream_restore_read_cb_t)( + pa_context *c, + const pa_ext_stream_restore_info *info, + int eol, + void *userdata); + +/** Read all entries from the stream database. \since 0.9.12 */ +pa_operation *pa_ext_stream_restore_read( + pa_context *c, + pa_ext_stream_restore_read_cb_t cb, + void *userdata); + +/** Store entries in the stream database. \since 0.9.12 */ +pa_operation *pa_ext_stream_restore_write( + pa_context *c, + pa_update_mode_t mode, + const pa_ext_stream_restore_info data[], + unsigned n, + int apply_immediately, + pa_context_success_cb_t cb, + void *userdata); + +/** Delete entries from the stream database. \since 0.9.12 */ +pa_operation *pa_ext_stream_restore_delete( + pa_context *c, + const char *const s[], + pa_context_success_cb_t cb, + void *userdata); + +/** Subscribe to changes in the stream database. \since 0.9.12 */ +pa_operation *pa_ext_stream_restore_subscribe( + pa_context *c, + int enable, + pa_context_success_cb_t cb, + void *userdata); + +/** Callback prototype for pa_ext_stream_restore_set_subscribe_cb(). \since 0.9.12 */ +typedef void (*pa_ext_stream_restore_subscribe_cb_t)( + pa_context *c, + void *userdata); + +/** Set the subscription callback that is called when + * pa_ext_stream_restore_subscribe() was called. \since 0.9.12 */ +void pa_ext_stream_restore_set_subscribe_cb( + pa_context *c, + pa_ext_stream_restore_subscribe_cb_t cb, + void *userdata); + +PA_C_DECL_END + +#endif diff --git a/src/pulse/fork-detect.c b/src/pulse/fork-detect.c new file mode 100644 index 0000000..9299304 --- /dev/null +++ b/src/pulse/fork-detect.c @@ -0,0 +1,57 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <unistd.h> + +#include <pulsecore/atomic.h> +#include <pulsecore/macro.h> + +#include "fork-detect.h" + +int pa_detect_fork(void) { + static pa_atomic_t pid = PA_ATOMIC_INIT((int) -1); + + /* Some really stupid applications (Hey, vim, that means you!) + * love to fork after initializing + * gtk/libcanberra/pulseaudio. This is really bad style. We + * however have to deal with this cleanly, so we try to detect the + * forks making sure all our calls fail cleanly after the fork. */ + + pa_assert_cc(sizeof(pa_atomic_t) >= sizeof(pid_t)); + + for (;;) { + pid_t stored_pid = (pid_t) pa_atomic_load(&pid); + + /* First let's check whether the current pid matches the stored one */ + if (stored_pid == getpid()) + return false; + + /* Does it contain a different PID than ours? Then the process got forked. */ + if ((int) stored_pid != (int) -1) + return true; + + /* Ok, it still contains no PID, then store it */ + if (pa_atomic_cmpxchg(&pid, (int) -1, (int) getpid())) + return false; + } +} diff --git a/src/pulse/fork-detect.h b/src/pulse/fork-detect.h new file mode 100644 index 0000000..d455e5d --- /dev/null +++ b/src/pulse/fork-detect.h @@ -0,0 +1,25 @@ +#ifndef fooforkdetecthfoo +#define fooforkdetecthfoo + +/*** + This file is part of PulseAudio. + + Copyright 2009 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +int pa_detect_fork(void); + +#endif diff --git a/src/pulse/format.c b/src/pulse/format.c new file mode 100644 index 0000000..2e90821 --- /dev/null +++ b/src/pulse/format.c @@ -0,0 +1,774 @@ +/*** + This file is part of PulseAudio. + + Copyright 2011 Intel Corporation + Copyright 2011 Collabora Multimedia + Copyright 2011 Arun Raghavan <arun.raghavan@collabora.co.uk> + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/json.h> +#include <pulse/internal.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/core-format.h> +#include <pulsecore/core-util.h> +#include <pulsecore/i18n.h> +#include <pulsecore/macro.h> +#include <pulsecore/strbuf.h> + +#include "format.h" + +#define PA_JSON_MIN_KEY "min" +#define PA_JSON_MAX_KEY "max" + +static int pa_format_info_prop_compatible(const char *one, const char *two); + +static const char* const _encoding_str_table[]= { + [PA_ENCODING_PCM] = "pcm", + [PA_ENCODING_AC3_IEC61937] = "ac3-iec61937", + [PA_ENCODING_EAC3_IEC61937] = "eac3-iec61937", + [PA_ENCODING_MPEG_IEC61937] = "mpeg-iec61937", + [PA_ENCODING_DTS_IEC61937] = "dts-iec61937", + [PA_ENCODING_MPEG2_AAC_IEC61937] = "mpeg2-aac-iec61937", + [PA_ENCODING_TRUEHD_IEC61937] = "truehd-iec61937", + [PA_ENCODING_DTSHD_IEC61937] = "dtshd-iec61937", + [PA_ENCODING_ANY] = "any", +}; + +const char *pa_encoding_to_string(pa_encoding_t e) { + if (e < 0 || e >= PA_ENCODING_MAX) + return NULL; + + return _encoding_str_table[e]; +} + +pa_encoding_t pa_encoding_from_string(const char *encoding) { + pa_encoding_t e; + + for (e = PA_ENCODING_ANY; e < PA_ENCODING_MAX; e++) + if (pa_streq(_encoding_str_table[e], encoding)) + return e; + + return PA_ENCODING_INVALID; +} + +pa_format_info* pa_format_info_new(void) { + pa_format_info *f = pa_xnew(pa_format_info, 1); + + f->encoding = PA_ENCODING_INVALID; + f->plist = pa_proplist_new(); + + return f; +} + +pa_format_info* pa_format_info_copy(const pa_format_info *src) { + pa_format_info *dest; + + pa_assert(src); + + dest = pa_xnew(pa_format_info, 1); + + dest->encoding = src->encoding; + + if (src->plist) + dest->plist = pa_proplist_copy(src->plist); + else + dest->plist = NULL; + + return dest; +} + +void pa_format_info_free(pa_format_info *f) { + pa_assert(f); + + pa_proplist_free(f->plist); + pa_xfree(f); +} + +int pa_format_info_valid(const pa_format_info *f) { + return (f->encoding >= 0 && f->encoding < PA_ENCODING_MAX && f->plist != NULL); +} + +int pa_format_info_is_pcm(const pa_format_info *f) { + return f->encoding == PA_ENCODING_PCM; +} + +char *pa_format_info_snprint(char *s, size_t l, const pa_format_info *f) { + char *tmp; + + pa_assert(s); + pa_assert(l > 0); + pa_assert(f); + + pa_init_i18n(); + + if (!pa_format_info_valid(f)) + pa_snprintf(s, l, _("(invalid)")); + else { + tmp = pa_proplist_to_string_sep(f->plist, " "); + if (tmp[0]) + pa_snprintf(s, l, "%s, %s", pa_encoding_to_string(f->encoding), tmp); + else + pa_snprintf(s, l, "%s", pa_encoding_to_string(f->encoding)); + pa_xfree(tmp); + } + + return s; +} + +pa_format_info* pa_format_info_from_string(const char *str) { + pa_format_info *f = pa_format_info_new(); + char *encoding = NULL, *properties = NULL; + size_t pos; + + pos = strcspn(str, ","); + + encoding = pa_xstrndup(str, pos); + f->encoding = pa_encoding_from_string(pa_strip(encoding)); + if (f->encoding == PA_ENCODING_INVALID) + goto error; + + if (pos != strlen(str)) { + pa_proplist *plist; + + properties = pa_xstrdup(&str[pos+1]); + plist = pa_proplist_from_string(properties); + + if (!plist) + goto error; + + pa_proplist_free(f->plist); + f->plist = plist; + } + +out: + if (encoding) + pa_xfree(encoding); + if (properties) + pa_xfree(properties); + return f; + +error: + pa_format_info_free(f); + f = NULL; + goto out; +} + +int pa_format_info_is_compatible(const pa_format_info *first, const pa_format_info *second) { + const char *key; + void *state = NULL; + + pa_assert(first); + pa_assert(second); + + if (first->encoding != second->encoding) + return false; + + while ((key = pa_proplist_iterate(first->plist, &state))) { + const char *value_one, *value_two; + + value_one = pa_proplist_gets(first->plist, key); + value_two = pa_proplist_gets(second->plist, key); + + if (!value_two || !pa_format_info_prop_compatible(value_one, value_two)) + return false; + } + + return true; +} + +pa_format_info* pa_format_info_from_sample_spec(const pa_sample_spec *ss, const pa_channel_map *map) { + char cm[PA_CHANNEL_MAP_SNPRINT_MAX]; + pa_format_info *f; + + pa_assert(ss && pa_sample_spec_valid(ss)); + pa_assert(!map || pa_channel_map_valid(map)); + + f = pa_format_info_new(); + f->encoding = PA_ENCODING_PCM; + + pa_format_info_set_sample_format(f, ss->format); + pa_format_info_set_rate(f, ss->rate); + pa_format_info_set_channels(f, ss->channels); + + if (map) { + pa_channel_map_snprint(cm, sizeof(cm), map); + pa_format_info_set_prop_string(f, PA_PROP_FORMAT_CHANNEL_MAP, cm); + } + + return f; +} + +/* For PCM streams */ +int pa_format_info_to_sample_spec(const pa_format_info *f, pa_sample_spec *ss, pa_channel_map *map) { + pa_assert(f); + pa_assert(ss); + + if (!pa_format_info_is_pcm(f)) + return pa_format_info_to_sample_spec_fake(f, ss, map); + + if (pa_format_info_get_sample_format(f, &ss->format) < 0) + return -PA_ERR_INVALID; + if (pa_format_info_get_rate(f, &ss->rate) < 0) + return -PA_ERR_INVALID; + if (pa_format_info_get_channels(f, &ss->channels) < 0) + return -PA_ERR_INVALID; + if (map && pa_format_info_get_channel_map(f, map) < 0) + return -PA_ERR_INVALID; + + return 0; +} + +pa_prop_type_t pa_format_info_get_prop_type(const pa_format_info *f, const char *key) { + const char *str; + pa_json_object *o; + const pa_json_object *o1; + pa_prop_type_t type; + + pa_assert(f); + pa_assert(key); + + str = pa_proplist_gets(f->plist, key); + if (!str) + return PA_PROP_TYPE_INVALID; + + o = pa_json_parse(str); + if (!o) + return PA_PROP_TYPE_INVALID; + + switch (pa_json_object_get_type(o)) { + case PA_JSON_TYPE_INT: + type = PA_PROP_TYPE_INT; + break; + + case PA_JSON_TYPE_STRING: + type = PA_PROP_TYPE_STRING; + break; + + case PA_JSON_TYPE_ARRAY: + if (pa_json_object_get_array_length(o) == 0) { + /* Unlikely, but let's account for this anyway. We need at + * least one element to figure out the array type. */ + type = PA_PROP_TYPE_INVALID; + break; + } + + o1 = pa_json_object_get_array_member(o, 0); + + if (pa_json_object_get_type(o1) == PA_JSON_TYPE_INT) + type = PA_PROP_TYPE_INT_ARRAY; + else if (pa_json_object_get_type(o1) == PA_JSON_TYPE_STRING) + type = PA_PROP_TYPE_STRING_ARRAY; + else + type = PA_PROP_TYPE_INVALID; + + break; + + case PA_JSON_TYPE_OBJECT: + /* We actually know at this point that it's a int range, but let's + * confirm. */ + if (!pa_json_object_get_object_member(o, PA_JSON_MIN_KEY)) { + type = PA_PROP_TYPE_INVALID; + break; + } + + if (!pa_json_object_get_object_member(o, PA_JSON_MAX_KEY)) { + type = PA_PROP_TYPE_INVALID; + break; + } + + type = PA_PROP_TYPE_INT_RANGE; + break; + + default: + type = PA_PROP_TYPE_INVALID; + break; + } + + pa_json_object_free(o); + return type; +} + +int pa_format_info_get_prop_int(const pa_format_info *f, const char *key, int *v) { + const char *str; + pa_json_object *o; + + pa_assert(f); + pa_assert(key); + pa_assert(v); + + str = pa_proplist_gets(f->plist, key); + if (!str) + return -PA_ERR_NOENTITY; + + o = pa_json_parse(str); + if (!o) { + pa_log_debug("Failed to parse format info property '%s'.", key); + return -PA_ERR_INVALID; + } + + if (pa_json_object_get_type(o) != PA_JSON_TYPE_INT) { + pa_log_debug("Format info property '%s' type is not int.", key); + pa_json_object_free(o); + return -PA_ERR_INVALID; + } + + *v = pa_json_object_get_int(o); + pa_json_object_free(o); + + return 0; +} + +int pa_format_info_get_prop_int_range(const pa_format_info *f, const char *key, int *min, int *max) { + const char *str; + pa_json_object *o; + const pa_json_object *o1; + int ret = -PA_ERR_INVALID; + + pa_assert(f); + pa_assert(key); + pa_assert(min); + pa_assert(max); + + str = pa_proplist_gets(f->plist, key); + if (!str) + return -PA_ERR_NOENTITY; + + o = pa_json_parse(str); + if (!o) { + pa_log_debug("Failed to parse format info property '%s'.", key); + return -PA_ERR_INVALID; + } + + if (pa_json_object_get_type(o) != PA_JSON_TYPE_OBJECT) + goto out; + + if (!(o1 = pa_json_object_get_object_member(o, PA_JSON_MIN_KEY)) || + (pa_json_object_get_type(o1) != PA_JSON_TYPE_INT)) + goto out; + + *min = pa_json_object_get_int(o1); + + if (!(o1 = pa_json_object_get_object_member(o, PA_JSON_MAX_KEY)) || + (pa_json_object_get_type(o1) != PA_JSON_TYPE_INT)) + goto out; + + *max = pa_json_object_get_int(o1); + + ret = 0; + +out: + if (ret < 0) + pa_log_debug("Format info property '%s' is not a valid int range.", key); + + pa_json_object_free(o); + return ret; +} + +int pa_format_info_get_prop_int_array(const pa_format_info *f, const char *key, int **values, int *n_values) { + const char *str; + pa_json_object *o; + const pa_json_object *o1; + int i, ret = -PA_ERR_INVALID; + + pa_assert(f); + pa_assert(key); + pa_assert(values); + pa_assert(n_values); + + str = pa_proplist_gets(f->plist, key); + if (!str) + return -PA_ERR_NOENTITY; + + o = pa_json_parse(str); + if (!o) { + pa_log_debug("Failed to parse format info property '%s'.", key); + return -PA_ERR_INVALID; + } + + if (pa_json_object_get_type(o) != PA_JSON_TYPE_ARRAY) + goto out; + + *n_values = pa_json_object_get_array_length(o); + *values = pa_xnew(int, *n_values); + + for (i = 0; i < *n_values; i++) { + o1 = pa_json_object_get_array_member(o, i); + + if (pa_json_object_get_type(o1) != PA_JSON_TYPE_INT) { + goto out; + } + + (*values)[i] = pa_json_object_get_int(o1); + } + + ret = 0; + +out: + if (ret < 0) + pa_log_debug("Format info property '%s' is not a valid int array.", key); + + pa_json_object_free(o); + return ret; +} + +int pa_format_info_get_prop_string(const pa_format_info *f, const char *key, char **v) { + const char *str = NULL; + pa_json_object *o; + + pa_assert(f); + pa_assert(key); + pa_assert(v); + + str = pa_proplist_gets(f->plist, key); + if (!str) + return -PA_ERR_NOENTITY; + + o = pa_json_parse(str); + if (!o) { + pa_log_debug("Failed to parse format info property '%s'.", key); + return -PA_ERR_INVALID; + } + + if (pa_json_object_get_type(o) != PA_JSON_TYPE_STRING) { + pa_log_debug("Format info property '%s' type is not string.", key); + pa_json_object_free(o); + return -PA_ERR_INVALID; + } + + *v = pa_xstrdup(pa_json_object_get_string(o)); + pa_json_object_free(o); + + return 0; +} + +int pa_format_info_get_prop_string_array(const pa_format_info *f, const char *key, char ***values, int *n_values) { + const char *str; + pa_json_object *o; + const pa_json_object *o1; + int i, ret = -PA_ERR_INVALID; + + pa_assert(f); + pa_assert(key); + pa_assert(values); + pa_assert(n_values); + + str = pa_proplist_gets(f->plist, key); + if (!str) + return -PA_ERR_NOENTITY; + + o = pa_json_parse(str); + if (!o) { + pa_log_debug("Failed to parse format info property '%s'.", key); + return -PA_ERR_INVALID; + } + + if (pa_json_object_get_type(o) != PA_JSON_TYPE_ARRAY) + goto out; + + *n_values = pa_json_object_get_array_length(o); + *values = pa_xnew(char *, *n_values); + + for (i = 0; i < *n_values; i++) { + o1 = pa_json_object_get_array_member(o, i); + + if (pa_json_object_get_type(o1) != PA_JSON_TYPE_STRING) { + goto out; + } + + (*values)[i] = pa_xstrdup(pa_json_object_get_string(o1)); + } + + ret = 0; + +out: + if (ret < 0) + pa_log_debug("Format info property '%s' is not a valid string array.", key); + + pa_json_object_free(o); + return ret; +} + +void pa_format_info_free_string_array(char **values, int n_values) { + int i; + + for (i = 0; i < n_values; i++) + pa_xfree(values[i]); + + pa_xfree(values); +} + +int pa_format_info_get_sample_format(const pa_format_info *f, pa_sample_format_t *sf) { + int r; + char *sf_str; + pa_sample_format_t sf_local; + + pa_assert(f); + pa_assert(sf); + + r = pa_format_info_get_prop_string(f, PA_PROP_FORMAT_SAMPLE_FORMAT, &sf_str); + if (r < 0) + return r; + + sf_local = pa_parse_sample_format(sf_str); + pa_xfree(sf_str); + + if (!pa_sample_format_valid(sf_local)) { + pa_log_debug("Invalid sample format."); + return -PA_ERR_INVALID; + } + + *sf = sf_local; + + return 0; +} + +int pa_format_info_get_rate(const pa_format_info *f, uint32_t *rate) { + int r; + int rate_local; + + pa_assert(f); + pa_assert(rate); + + r = pa_format_info_get_prop_int(f, PA_PROP_FORMAT_RATE, &rate_local); + if (r < 0) + return r; + + if (!pa_sample_rate_valid(rate_local)) { + pa_log_debug("Invalid sample rate: %i", rate_local); + return -PA_ERR_INVALID; + } + + *rate = rate_local; + + return 0; +} + +int pa_format_info_get_channels(const pa_format_info *f, uint8_t *channels) { + int r; + int channels_local; + + pa_assert(f); + pa_assert(channels); + + r = pa_format_info_get_prop_int(f, PA_PROP_FORMAT_CHANNELS, &channels_local); + if (r < 0) + return r; + + if (!pa_channels_valid(channels_local)) { + pa_log_debug("Invalid channel count: %i", channels_local); + return -PA_ERR_INVALID; + } + + *channels = channels_local; + + return 0; +} + +int pa_format_info_get_channel_map(const pa_format_info *f, pa_channel_map *map) { + int r; + char *map_str; + + pa_assert(f); + pa_assert(map); + + r = pa_format_info_get_prop_string(f, PA_PROP_FORMAT_CHANNEL_MAP, &map_str); + if (r < 0) + return r; + + map = pa_channel_map_parse(map, map_str); + pa_xfree(map_str); + + if (!map) { + pa_log_debug("Failed to parse channel map."); + return -PA_ERR_INVALID; + } + + return 0; +} + +void pa_format_info_set_sample_format(pa_format_info *f, pa_sample_format_t sf) { + pa_format_info_set_prop_string(f, PA_PROP_FORMAT_SAMPLE_FORMAT, pa_sample_format_to_string(sf)); +} + +void pa_format_info_set_rate(pa_format_info *f, int rate) { + pa_format_info_set_prop_int(f, PA_PROP_FORMAT_RATE, rate); +} + +void pa_format_info_set_channels(pa_format_info *f, int channels) { + pa_format_info_set_prop_int(f, PA_PROP_FORMAT_CHANNELS, channels); +} + +void pa_format_info_set_channel_map(pa_format_info *f, const pa_channel_map *map) { + char map_str[PA_CHANNEL_MAP_SNPRINT_MAX]; + + pa_channel_map_snprint(map_str, sizeof(map_str), map); + + pa_format_info_set_prop_string(f, PA_PROP_FORMAT_CHANNEL_MAP, map_str); +} + +void pa_format_info_set_prop_int(pa_format_info *f, const char *key, int value) { + pa_assert(f); + pa_assert(key); + + pa_proplist_setf(f->plist, key, "%d", value); +} + +void pa_format_info_set_prop_int_array(pa_format_info *f, const char *key, const int *values, int n_values) { + pa_strbuf *buf; + char *str; + int i; + + pa_assert(f); + pa_assert(key); + pa_assert(n_values > 0); + + buf = pa_strbuf_new(); + + pa_strbuf_printf(buf, "[ %d", values[0]); + + for (i = 1; i < n_values; i++) + pa_strbuf_printf(buf, ", %d", values[i]); + + pa_strbuf_printf(buf, " ]"); + str = pa_strbuf_to_string_free(buf); + + pa_proplist_sets(f->plist, key, str); + pa_xfree (str); +} + +void pa_format_info_set_prop_int_range(pa_format_info *f, const char *key, int min, int max) { + pa_assert(f); + pa_assert(key); + + pa_proplist_setf(f->plist, key, "{ \"" PA_JSON_MIN_KEY "\": %d, \"" PA_JSON_MAX_KEY "\": %d }", + min, max); +} + +void pa_format_info_set_prop_string(pa_format_info *f, const char *key, const char *value) { + pa_assert(f); + pa_assert(key); + + pa_proplist_setf(f->plist, key, "\"%s\"", value); +} + +void pa_format_info_set_prop_string_array(pa_format_info *f, const char *key, const char **values, int n_values) { + pa_strbuf *buf; + char *str; + int i; + + pa_assert(f); + pa_assert(key); + + buf = pa_strbuf_new(); + + pa_strbuf_printf(buf, "[ \"%s\"", values[0]); + + for (i = 1; i < n_values; i++) + pa_strbuf_printf(buf, ", \"%s\"", values[i]); + + pa_strbuf_printf(buf, " ]"); + str = pa_strbuf_to_string_free(buf); + + pa_proplist_sets(f->plist, key, str); + pa_xfree (str); +} + +static bool pa_json_is_fixed_type(pa_json_object *o) { + switch(pa_json_object_get_type(o)) { + case PA_JSON_TYPE_OBJECT: + case PA_JSON_TYPE_ARRAY: + return false; + + default: + return true; + } +} + +static int pa_format_info_prop_compatible(const char *one, const char *two) { + pa_json_object *o1 = NULL, *o2 = NULL; + int i, ret = 0; + + o1 = pa_json_parse(one); + if (!o1) + goto out; + + o2 = pa_json_parse(two); + if (!o2) + goto out; + + /* We don't deal with both values being non-fixed - just because there is no immediate need (FIXME) */ + pa_return_val_if_fail(pa_json_is_fixed_type(o1) || pa_json_is_fixed_type(o2), false); + + if (pa_json_is_fixed_type(o1) && pa_json_is_fixed_type(o2)) { + ret = pa_json_object_equal(o1, o2); + goto out; + } + + if (pa_json_is_fixed_type(o1)) { + pa_json_object *tmp = o2; + o2 = o1; + o1 = tmp; + } + + /* o2 is now a fixed type, and o1 is not */ + + if (pa_json_object_get_type(o1) == PA_JSON_TYPE_ARRAY) { + for (i = 0; i < pa_json_object_get_array_length(o1); i++) { + if (pa_json_object_equal(pa_json_object_get_array_member(o1, i), o2)) { + ret = 1; + break; + } + } + } else if (pa_json_object_get_type(o1) == PA_JSON_TYPE_OBJECT) { + /* o1 should be a range type */ + int min, max, v; + const pa_json_object *o_min = NULL, *o_max = NULL; + + if (pa_json_object_get_type(o2) != PA_JSON_TYPE_INT) { + /* We don't support non-integer ranges */ + goto out; + } + + if (!(o_min = pa_json_object_get_object_member(o1, PA_JSON_MIN_KEY)) || + pa_json_object_get_type(o_min) != PA_JSON_TYPE_INT) + goto out; + + if (!(o_max = pa_json_object_get_object_member(o1, PA_JSON_MAX_KEY)) || + pa_json_object_get_type(o_max) != PA_JSON_TYPE_INT) + goto out; + + v = pa_json_object_get_int(o2); + min = pa_json_object_get_int(o_min); + max = pa_json_object_get_int(o_max); + + ret = v >= min && v <= max; + } else { + pa_log_warn("Got a format type that we don't support"); + } + +out: + if (o1) + pa_json_object_free(o1); + if (o2) + pa_json_object_free(o2); + + return ret; +} diff --git a/src/pulse/format.h b/src/pulse/format.h new file mode 100644 index 0000000..5f97929 --- /dev/null +++ b/src/pulse/format.h @@ -0,0 +1,297 @@ +#ifndef fooformathfoo +#define fooformathfoo + +/*** + This file is part of PulseAudio. + + Copyright 2011 Intel Corporation + Copyright 2011 Collabora Multimedia + Copyright 2011 Arun Raghavan <arun.raghavan@collabora.co.uk> + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulse/cdecl.h> +#include <pulse/gccmacro.h> +#include <pulse/proplist.h> +#include <pulse/sample.h> +#include <pulse/channelmap.h> + +/** \file + * Utility functions for handling a stream or sink format. */ + +PA_C_DECL_BEGIN + +/** Represents the type of encoding used in a stream or accepted by a sink. \since 1.0 */ +typedef enum pa_encoding { + PA_ENCODING_ANY, + /**< Any encoding format, PCM or compressed */ + + PA_ENCODING_PCM, + /**< Any PCM format */ + + PA_ENCODING_AC3_IEC61937, + /**< AC3 data encapsulated in IEC 61937 header/padding */ + + PA_ENCODING_EAC3_IEC61937, + /**< EAC3 data encapsulated in IEC 61937 header/padding */ + + PA_ENCODING_MPEG_IEC61937, + /**< MPEG-1 or MPEG-2 (Part 3, not AAC) data encapsulated in IEC 61937 header/padding */ + + PA_ENCODING_DTS_IEC61937, + /**< DTS data encapsulated in IEC 61937 header/padding */ + + PA_ENCODING_MPEG2_AAC_IEC61937, + /**< MPEG-2 AAC data encapsulated in IEC 61937 header/padding. \since 4.0 */ + + PA_ENCODING_TRUEHD_IEC61937, + /**< Dolby TrueHD data encapsulated in IEC 61937 header/padding. \since 13.0 */ + + PA_ENCODING_DTSHD_IEC61937, + /**< DTS-HD Master Audio encapsulated in IEC 61937 header/padding. \since 13.0 */ + + /* Remeber to update + * https://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/User/SupportedAudioFormats/ + * when adding new encodings! */ + + PA_ENCODING_MAX, + /**< Valid encoding types must be less than this value */ + + PA_ENCODING_INVALID = -1, + /**< Represents an invalid encoding */ +} pa_encoding_t; + +/** \cond fulldocs */ +#define PA_ENCODING_ANY PA_ENCODING_ANY +#define PA_ENCODING_PCM PA_ENCODING_PCM +#define PA_ENCODING_AC3_IEC61937 PA_ENCODING_AC3_IEC61937 +#define PA_ENCODING_EAC3_IEC61937 PA_ENCODING_EAC3_IEC61937 +#define PA_ENCODING_MPEG_IEC61937 PA_ENCODING_MPEG_IEC61937 +#define PA_ENCODING_DTS_IEC61937 PA_ENCODING_DTS_IEC61937 +#define PA_ENCODING_MPEG2_AAC_IEC61937 PA_ENCODING_MPEG2_AAC_IEC61937 +#define PA_ENCODING_TRUEHD_IEC61937 PA_ENCODING_TRUEHD_IEC61937 +#define PA_ENCODING_DTSHD_IEC61937 PA_ENCODING_DTSHD_IEC61937 +#define PA_ENCODING_MAX PA_ENCODING_MAX +#define PA_ENCODING_INVALID PA_ENCODING_INVALID +/** \endcond */ + +/** Returns a printable string representing the given encoding type. \since 1.0 */ +const char *pa_encoding_to_string(pa_encoding_t e) PA_GCC_CONST; + +/** Converts a string of the form returned by \a pa_encoding_to_string() back to + * a \a pa_encoding_t. \since 1.0 */ +pa_encoding_t pa_encoding_from_string(const char *encoding); + +/** Represents the format of data provided in a stream or processed by a sink. \since 1.0 */ +typedef struct pa_format_info { + pa_encoding_t encoding; + /**< The encoding used for the format */ + + pa_proplist *plist; + /**< Additional encoding-specific properties such as sample rate, bitrate, etc. */ +} pa_format_info; + +/** Allocates a new \a pa_format_info structure. Clients must initialise at + * least the encoding field themselves. Free with pa_format_info_free. \since 1.0 */ +pa_format_info* pa_format_info_new(void); + +/** Returns a new \a pa_format_info struct and representing the same format as \a src. \since 1.0 */ +pa_format_info* pa_format_info_copy(const pa_format_info *src); + +/** Frees a \a pa_format_info structure. \since 1.0 */ +void pa_format_info_free(pa_format_info *f); + +/** Returns non-zero when the format info structure is valid. \since 1.0 */ +int pa_format_info_valid(const pa_format_info *f); + +/** Returns non-zero when the format info structure represents a PCM + * (i.e.\ uncompressed data) format. \since 1.0 */ +int pa_format_info_is_pcm(const pa_format_info *f); + +/** Returns non-zero if the format represented by \a first is a subset of + * the format represented by \a second. This means that \a second must + * have all the fields that \a first does, but the reverse need not + * be true. This is typically expected to be used to check if a + * stream's format is compatible with a given sink. In such a case, + * \a first would be the sink's format and \a second would be the + * stream's. \since 1.0 */ +int pa_format_info_is_compatible(const pa_format_info *first, const pa_format_info *second); + +/** Maximum required string length for + * pa_format_info_snprint(). Please note that this value can change + * with any release without warning and without being considered API + * or ABI breakage. You should not use this definition anywhere where + * it might become part of an ABI. \since 1.0 */ +#define PA_FORMAT_INFO_SNPRINT_MAX 256 + +/** Make a human-readable string representing the given format. Returns \a s. \since 1.0 */ +char *pa_format_info_snprint(char *s, size_t l, const pa_format_info *f); + +/** Parse a human-readable string of the form generated by + * \a pa_format_info_snprint() into a pa_format_info structure. \since 1.0 */ +pa_format_info* pa_format_info_from_string(const char *str); + +/** Utility function to take a \a pa_sample_spec and generate the corresponding + * \a pa_format_info. + * + * Note that if you want the server to choose some of the stream parameters, + * for example the sample rate, so that they match the device parameters, then + * you shouldn't use this function. In order to allow the server to choose + * a parameter value, that parameter must be left unspecified in the + * pa_format_info object, and this function always specifies all parameters. An + * exception is the channel map: if you pass NULL for the channel map, then the + * channel map will be left unspecified, allowing the server to choose it. + * + * \since 2.0 */ +pa_format_info* pa_format_info_from_sample_spec(const pa_sample_spec *ss, const pa_channel_map *map); + +/** Utility function to generate a \a pa_sample_spec and \a pa_channel_map corresponding to a given \a pa_format_info. The + * conversion for PCM formats is straight-forward. For non-PCM formats, if there is a fixed size-time conversion (i.e. all + * IEC61937-encapsulated formats), a "fake" sample spec whose size-time conversion corresponds to this format is provided and + * the channel map argument is ignored. For formats with variable size-time conversion, this function will fail. Returns a + * negative integer if conversion failed and 0 on success. \since 2.0 */ +int pa_format_info_to_sample_spec(const pa_format_info *f, pa_sample_spec *ss, pa_channel_map *map); + +/** Represents the type of value type of a property on a \ref pa_format_info. \since 2.0 */ +typedef enum pa_prop_type_t { + PA_PROP_TYPE_INT, + /**< Integer property */ + + PA_PROP_TYPE_INT_RANGE, + /**< Integer range property */ + + PA_PROP_TYPE_INT_ARRAY, + /**< Integer array property */ + + PA_PROP_TYPE_STRING, + /**< String property */ + + PA_PROP_TYPE_STRING_ARRAY, + /**< String array property */ + + PA_PROP_TYPE_INVALID = -1, + /**< Represents an invalid type */ +} pa_prop_type_t; + +/** \cond fulldocs */ +#define PA_PROP_TYPE_INT PA_PROP_TYPE_INT +#define PA_PROP_TYPE_INT_RANGE PA_PROP_TYPE_INT_RANGE +#define PA_PROP_TYPE_INT_ARRAY PA_PROP_TYPE_INT_ARRAY +#define PA_PROP_TYPE_STRING PA_PROP_TYPE_STRING +#define PA_PROP_TYPE_STRING_ARRAY PA_PROP_TYPE_STRING_ARRAY +#define PA_PROP_TYPE_INVALID PA_PROP_TYPE_INVALID +/** \endcond */ + +/** Gets the type of property \a key in a given \ref pa_format_info. \since 2.0 */ +pa_prop_type_t pa_format_info_get_prop_type(const pa_format_info *f, const char *key); + +/** Gets an integer property from the given format info. Returns 0 on success and a negative integer on failure. \since 2.0 */ +int pa_format_info_get_prop_int(const pa_format_info *f, const char *key, int *v); +/** Gets an integer range property from the given format info. Returns 0 on success and a negative integer on failure. + * \since 2.0 */ +int pa_format_info_get_prop_int_range(const pa_format_info *f, const char *key, int *min, int *max); +/** Gets an integer array property from the given format info. \a values contains the values and \a n_values contains the + * number of elements. The caller must free \a values using \ref pa_xfree. Returns 0 on success and a negative integer on + * failure. \since 2.0 */ +int pa_format_info_get_prop_int_array(const pa_format_info *f, const char *key, int **values, int *n_values); +/** Gets a string property from the given format info. The caller must free the returned string using \ref pa_xfree. Returns + * 0 on success and a negative integer on failure. \since 2.0 */ +int pa_format_info_get_prop_string(const pa_format_info *f, const char *key, char **v); +/** Gets a string array property from the given format info. \a values contains the values and \a n_values contains + * the number of elements. The caller must free \a values using \ref pa_format_info_free_string_array. Returns 0 on success and + * a negative integer on failure. \since 2.0 */ +int pa_format_info_get_prop_string_array(const pa_format_info *f, const char *key, char ***values, int *n_values); + +/** Frees a string array returned by \ref pa_format_info_get_prop_string_array. \since 2.0 */ +void pa_format_info_free_string_array(char **values, int n_values); + +/** Gets the sample format stored in the format info. Returns a negative error + * code on failure. If the sample format property is not set at all, returns a + * negative integer. \since 13.0 */ +int pa_format_info_get_sample_format(const pa_format_info *f, pa_sample_format_t *sf); + +/** Gets the sample rate stored in the format info. Returns a negative error + * code on failure. If the sample rate property is not set at all, returns a + * negative integer. \since 13.0 */ +int pa_format_info_get_rate(const pa_format_info *f, uint32_t *rate); + +/** Gets the channel count stored in the format info. Returns a negative error + * code on failure. If the channels property is not set at all, returns a + * negative integer. \since 13.0 */ +int pa_format_info_get_channels(const pa_format_info *f, uint8_t *channels); + +/** Gets the channel map stored in the format info. Returns a negative error + * code on failure. If the channel map property is not + * set at all, returns a negative integer. \since 13.0 */ +int pa_format_info_get_channel_map(const pa_format_info *f, pa_channel_map *map); + +/** Sets an integer property on the given format info. \since 1.0 */ +void pa_format_info_set_prop_int(pa_format_info *f, const char *key, int value); +/** Sets a property with a list of integer values on the given format info. \since 1.0 */ +void pa_format_info_set_prop_int_array(pa_format_info *f, const char *key, const int *values, int n_values); +/** Sets a property which can have any value in a given integer range on the given format info. \since 1.0 */ +void pa_format_info_set_prop_int_range(pa_format_info *f, const char *key, int min, int max); +/** Sets a string property on the given format info. \since 1.0 */ +void pa_format_info_set_prop_string(pa_format_info *f, const char *key, const char *value); +/** Sets a property with a list of string values on the given format info. \since 1.0 */ +void pa_format_info_set_prop_string_array(pa_format_info *f, const char *key, const char **values, int n_values); + +/** Convenience method to set the sample format as a property on the given + * format. + * + * Note for PCM: If the sample format is left unspecified in the pa_format_info + * object, then the server will select the stream sample format. In that case + * the stream sample format will most likely match the device sample format, + * meaning that sample format conversion will be avoided. + * + * \since 1.0 */ +void pa_format_info_set_sample_format(pa_format_info *f, pa_sample_format_t sf); + +/** Convenience method to set the sampling rate as a property on the given + * format. + * + * Note for PCM: If the sample rate is left unspecified in the pa_format_info + * object, then the server will select the stream sample rate. In that case the + * stream sample rate will most likely match the device sample rate, meaning + * that sample rate conversion will be avoided. + * + * \since 1.0 */ +void pa_format_info_set_rate(pa_format_info *f, int rate); + +/** Convenience method to set the number of channels as a property on the given + * format. + * + * Note for PCM: If the channel count is left unspecified in the pa_format_info + * object, then the server will select the stream channel count. In that case + * the stream channel count will most likely match the device channel count, + * meaning that up/downmixing will be avoided. + * + * \since 1.0 */ +void pa_format_info_set_channels(pa_format_info *f, int channels); + +/** Convenience method to set the channel map as a property on the given + * format. + * + * Note for PCM: If the channel map is left unspecified in the pa_format_info + * object, then the server will select the stream channel map. In that case the + * stream channel map will most likely match the device channel map, meaning + * that remixing will be avoided. + * + * \since 1.0 */ +void pa_format_info_set_channel_map(pa_format_info *f, const pa_channel_map *map); + +PA_C_DECL_END + +#endif diff --git a/src/pulse/gccmacro.h b/src/pulse/gccmacro.h new file mode 100644 index 0000000..81729db --- /dev/null +++ b/src/pulse/gccmacro.h @@ -0,0 +1,170 @@ +#ifndef foopulsegccmacrohfoo +#define foopulsegccmacrohfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +/** \file + * GCC attribute macros */ + +#if defined(__GNUC__) +#ifdef __MINGW32__ +/* libintl overrides printf with a #define. As this breaks this attribute, + * it has a workaround. However the workaround isn't enabled for MINGW + * builds (only cygwin) */ +#define PA_GCC_PRINTF_ATTR(a,b) __attribute__ ((format (__printf__, a, b))) +#else +#define PA_GCC_PRINTF_ATTR(a,b) __attribute__ ((format (printf, a, b))) +#endif +#else +/** If we're in GNU C, use some magic for detecting invalid format strings */ +#define PA_GCC_PRINTF_ATTR(a,b) +#endif + +#if defined(__GNUC__) && (__GNUC__ >= 4) +#define PA_GCC_SENTINEL __attribute__ ((sentinel)) +#else +/** Macro for usage of GCC's sentinel compilation warnings */ +#define PA_GCC_SENTINEL +#endif + +#ifdef __GNUC__ +#define PA_GCC_NORETURN __attribute__((noreturn)) +#else +/** Macro for no-return functions */ +#define PA_GCC_NORETURN +#endif + +#ifdef __GNUC__ +#define PA_GCC_UNUSED __attribute__ ((unused)) +#else +/** Macro for not used function, variable or parameter */ +#define PA_GCC_UNUSED +#endif + +#ifdef __GNUC__ +#define PA_GCC_DESTRUCTOR __attribute__ ((destructor)) +#else +/** Call this function when process terminates */ +#define PA_GCC_DESTRUCTOR +#endif + +#ifndef PA_GCC_PURE +#ifdef __GNUC__ +#define PA_GCC_PURE __attribute__ ((pure)) +#else +/** This function's return value depends only the arguments list and global state **/ +#define PA_GCC_PURE +#endif +#endif + +#ifndef PA_GCC_CONST +#ifdef __GNUC__ +#define PA_GCC_CONST __attribute__ ((const)) +#else +/** This function's return value depends only the arguments list (stricter version of PA_GCC_PURE) **/ +#define PA_GCC_CONST +#endif +#endif + +#ifndef PA_GCC_DEPRECATED +#ifdef __GNUC__ +#define PA_GCC_DEPRECATED __attribute__ ((deprecated)) +#else +/** This function is deprecated **/ +#define PA_GCC_DEPRECATED +#endif +#endif + +#ifndef PA_GCC_PACKED +#ifdef __GNUC__ +#define PA_GCC_PACKED __attribute__ ((packed)) +#else +/** Structure shall be packed in memory **/ +#define PA_GCC_PACKED +#endif +#endif + +#ifndef PA_GCC_ALLOC_SIZE +#if defined(__GNUC__) && (__GNUC__ >= 4) && (__GNUC_MINOR__ >= 3) +#define PA_GCC_ALLOC_SIZE(x) __attribute__ ((__alloc_size__(x))) +#define PA_GCC_ALLOC_SIZE2(x,y) __attribute__ ((__alloc_size__(x,y))) +#else +/** Macro for usage of GCC's alloc_size attribute */ +#define PA_GCC_ALLOC_SIZE(x) +/** Macro for usage of GCC's alloc_size attribute */ +#define PA_GCC_ALLOC_SIZE2(x,y) +#endif +#endif + +#ifndef PA_GCC_MALLOC +#ifdef __GNUC__ +#define PA_GCC_MALLOC __attribute__ ((malloc)) +#else +/** Macro for usage of GCC's malloc attribute */ +#define PA_GCC_MALLOC +#endif +#endif + +#ifndef PA_GCC_WEAKREF +#if defined(__GNUC__) && defined(__ELF__) && (((__GNUC__ == 4) && (__GNUC_MINOR__ > 1)) || (__GNUC__ > 4)) +/** Macro for usage of GCC's weakref attribute */ +#define PA_GCC_WEAKREF(x) __attribute__((weakref(#x))) +#endif +#endif + +#ifndef PA_LIKELY +#ifdef __GNUC__ +#define PA_LIKELY(x) (__builtin_expect(!!(x),1)) +#define PA_UNLIKELY(x) (__builtin_expect(!!(x),0)) +#else +#define PA_LIKELY(x) (x) +#define PA_UNLIKELY(x) (x) +#endif +#endif + +#ifdef __GNUC__ +#define PA_CLAMP(x, low, high) \ + __extension__ ({ \ + typeof(x) _x = (x); \ + typeof(low) _low = (low); \ + typeof(high) _high = (high); \ + ((_x > _high) ? _high : ((_x < _low) ? _low : _x)); \ + }) +#else +#define PA_CLAMP(x, low, high) (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x))) +#endif + +#ifdef __GNUC__ +#define PA_CLAMP_UNLIKELY(x, low, high) \ + __extension__ ({ \ + typeof(x) _x = (x); \ + typeof(low) _low = (low); \ + typeof(high) _high = (high); \ + (PA_UNLIKELY(_x > _high) ? _high : (PA_UNLIKELY(_x < _low) ? _low : _x)); \ + }) +#else +#define PA_CLAMP_UNLIKELY(x, low, high) (PA_UNLIKELY((x) > (high)) ? (high) : (PA_UNLIKELY((x) < (low)) ? (low) : (x))) +#endif + +/* We don't define a PA_CLAMP_LIKELY here, because it doesn't really + * make sense: we cannot know if it is more likely that the value is + * lower or greater than the boundaries. */ + +#endif diff --git a/src/pulse/glib-mainloop.c b/src/pulse/glib-mainloop.c new file mode 100644 index 0000000..1ce3cd3 --- /dev/null +++ b/src/pulse/glib-mainloop.c @@ -0,0 +1,662 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/xmalloc.h> +#include <pulse/timeval.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> +#include <pulsecore/llist.h> + +#include <glib.h> +#include "glib-mainloop.h" + +struct pa_io_event { + pa_glib_mainloop *mainloop; + int dead; + + GPollFD poll_fd; + int poll_fd_added; + + pa_io_event_cb_t callback; + void *userdata; + pa_io_event_destroy_cb_t destroy_callback; + + PA_LLIST_FIELDS(pa_io_event); +}; + +struct pa_time_event { + pa_glib_mainloop *mainloop; + int dead; + + int enabled; + struct timeval timeval; + + pa_time_event_cb_t callback; + void *userdata; + pa_time_event_destroy_cb_t destroy_callback; + + PA_LLIST_FIELDS(pa_time_event); +}; + +struct pa_defer_event { + pa_glib_mainloop *mainloop; + int dead; + + int enabled; + + pa_defer_event_cb_t callback; + void *userdata; + pa_defer_event_destroy_cb_t destroy_callback; + + PA_LLIST_FIELDS(pa_defer_event); +}; + +struct pa_glib_mainloop { + GSource source; + + pa_mainloop_api api; + GMainContext *context; + + PA_LLIST_HEAD(pa_io_event, io_events); + PA_LLIST_HEAD(pa_time_event, time_events); + PA_LLIST_HEAD(pa_defer_event, defer_events); + + int n_enabled_defer_events, n_enabled_time_events; + int io_events_please_scan, time_events_please_scan, defer_events_please_scan; + + pa_time_event *cached_next_time_event; +}; + +static void cleanup_io_events(pa_glib_mainloop *g, int force) { + pa_io_event *e; + + e = g->io_events; + while (e) { + pa_io_event *n = e->next; + + if (!force && g->io_events_please_scan <= 0) + break; + + if (force || e->dead) { + PA_LLIST_REMOVE(pa_io_event, g->io_events, e); + + if (e->dead) { + g_assert(g->io_events_please_scan > 0); + g->io_events_please_scan--; + } + + if (e->poll_fd_added) + g_source_remove_poll(&g->source, &e->poll_fd); + + if (e->destroy_callback) + e->destroy_callback(&g->api, e, e->userdata); + + pa_xfree(e); + } + + e = n; + } + + g_assert(g->io_events_please_scan == 0); +} + +static void cleanup_time_events(pa_glib_mainloop *g, int force) { + pa_time_event *e; + + e = g->time_events; + while (e) { + pa_time_event *n = e->next; + + if (!force && g->time_events_please_scan <= 0) + break; + + if (force || e->dead) { + PA_LLIST_REMOVE(pa_time_event, g->time_events, e); + + if (e->dead) { + g_assert(g->time_events_please_scan > 0); + g->time_events_please_scan--; + } + + if (!e->dead && e->enabled) { + g_assert(g->n_enabled_time_events > 0); + g->n_enabled_time_events--; + } + + if (e->destroy_callback) + e->destroy_callback(&g->api, e, e->userdata); + + pa_xfree(e); + } + + e = n; + } + + g_assert(g->time_events_please_scan == 0); +} + +static void cleanup_defer_events(pa_glib_mainloop *g, int force) { + pa_defer_event *e; + + e = g->defer_events; + while (e) { + pa_defer_event *n = e->next; + + if (!force && g->defer_events_please_scan <= 0) + break; + + if (force || e->dead) { + PA_LLIST_REMOVE(pa_defer_event, g->defer_events, e); + + if (e->dead) { + g_assert(g->defer_events_please_scan > 0); + g->defer_events_please_scan--; + } + + if (!e->dead && e->enabled) { + g_assert(g->n_enabled_defer_events > 0); + g->n_enabled_defer_events--; + } + + if (e->destroy_callback) + e->destroy_callback(&g->api, e, e->userdata); + + pa_xfree(e); + } + + e = n; + } + + g_assert(g->defer_events_please_scan == 0); +} + +static gushort map_flags_to_glib(pa_io_event_flags_t flags) { + return (gushort) + ((flags & PA_IO_EVENT_INPUT ? G_IO_IN : 0) | + (flags & PA_IO_EVENT_OUTPUT ? G_IO_OUT : 0) | + (flags & PA_IO_EVENT_ERROR ? G_IO_ERR : 0) | + (flags & PA_IO_EVENT_HANGUP ? G_IO_HUP : 0)); +} + +static pa_io_event_flags_t map_flags_from_glib(gushort flags) { + return + (flags & G_IO_IN ? PA_IO_EVENT_INPUT : 0) | + (flags & G_IO_OUT ? PA_IO_EVENT_OUTPUT : 0) | + (flags & G_IO_ERR ? PA_IO_EVENT_ERROR : 0) | + (flags & G_IO_HUP ? PA_IO_EVENT_HANGUP : 0); +} + +static pa_io_event* glib_io_new( + pa_mainloop_api*m, + int fd, + pa_io_event_flags_t f, + pa_io_event_cb_t cb, + void *userdata) { + + pa_io_event *e; + pa_glib_mainloop *g; + + g_assert(m); + g_assert(m->userdata); + g_assert(fd >= 0); + g_assert(cb); + + g = m->userdata; + + e = pa_xnew(pa_io_event, 1); + e->mainloop = g; + e->dead = 0; + + e->poll_fd.fd = fd; + e->poll_fd.events = map_flags_to_glib(f); + e->poll_fd.revents = 0; + + e->callback = cb; + e->userdata = userdata; + e->destroy_callback = NULL; + + PA_LLIST_PREPEND(pa_io_event, g->io_events, e); + + g_source_add_poll(&g->source, &e->poll_fd); + e->poll_fd_added = 1; + + return e; +} + +static void glib_io_enable(pa_io_event*e, pa_io_event_flags_t f) { + g_assert(e); + g_assert(!e->dead); + + e->poll_fd.events = map_flags_to_glib(f); +} + +static void glib_io_free(pa_io_event*e) { + g_assert(e); + g_assert(!e->dead); + + e->dead = 1; + e->mainloop->io_events_please_scan++; + + if (e->poll_fd_added) { + g_source_remove_poll(&e->mainloop->source, &e->poll_fd); + e->poll_fd_added = 0; + } +} + +static void glib_io_set_destroy(pa_io_event*e, pa_io_event_destroy_cb_t cb) { + g_assert(e); + g_assert(!e->dead); + + e->destroy_callback = cb; +} + +/* Time sources */ + +static pa_time_event* glib_time_new( + pa_mainloop_api*m, + const struct timeval *tv, + pa_time_event_cb_t cb, + void *userdata) { + + pa_glib_mainloop *g; + pa_time_event *e; + + g_assert(m); + g_assert(m->userdata); + g_assert(cb); + + g = m->userdata; + + e = pa_xnew(pa_time_event, 1); + e->mainloop = g; + e->dead = 0; + + if ((e->enabled = !!tv)) { + e->timeval = *tv; + g->n_enabled_time_events++; + + if (g->cached_next_time_event) { + g_assert(g->cached_next_time_event->enabled); + + if (pa_timeval_cmp(tv, &g->cached_next_time_event->timeval) < 0) + g->cached_next_time_event = e; + } + } + + e->callback = cb; + e->userdata = userdata; + e->destroy_callback = NULL; + + PA_LLIST_PREPEND(pa_time_event, g->time_events, e); + + return e; +} + +static void glib_time_restart(pa_time_event*e, const struct timeval *tv) { + g_assert(e); + g_assert(!e->dead); + + if (e->enabled && !tv) { + g_assert(e->mainloop->n_enabled_time_events > 0); + e->mainloop->n_enabled_time_events--; + } else if (!e->enabled && tv) + e->mainloop->n_enabled_time_events++; + + if ((e->enabled = !!tv)) + e->timeval = *tv; + + if (e->mainloop->cached_next_time_event == e) + e->mainloop->cached_next_time_event = NULL; + + if (e->mainloop->cached_next_time_event && e->enabled) { + g_assert(e->mainloop->cached_next_time_event->enabled); + + if (pa_timeval_cmp(tv, &e->mainloop->cached_next_time_event->timeval) < 0) + e->mainloop->cached_next_time_event = e; + } +} + +static void glib_time_free(pa_time_event *e) { + g_assert(e); + g_assert(!e->dead); + + e->dead = 1; + e->mainloop->time_events_please_scan++; + + if (e->enabled) + e->mainloop->n_enabled_time_events--; + + if (e->mainloop->cached_next_time_event == e) + e->mainloop->cached_next_time_event = NULL; +} + +static void glib_time_set_destroy(pa_time_event *e, pa_time_event_destroy_cb_t cb) { + g_assert(e); + g_assert(!e->dead); + + e->destroy_callback = cb; +} + +/* Deferred sources */ + +static pa_defer_event* glib_defer_new( + pa_mainloop_api*m, + pa_defer_event_cb_t cb, + void *userdata) { + + pa_defer_event *e; + pa_glib_mainloop *g; + + g_assert(m); + g_assert(m->userdata); + g_assert(cb); + + g = m->userdata; + + e = pa_xnew(pa_defer_event, 1); + e->mainloop = g; + e->dead = 0; + + e->enabled = 1; + g->n_enabled_defer_events++; + + e->callback = cb; + e->userdata = userdata; + e->destroy_callback = NULL; + + PA_LLIST_PREPEND(pa_defer_event, g->defer_events, e); + return e; +} + +static void glib_defer_enable(pa_defer_event *e, int b) { + g_assert(e); + g_assert(!e->dead); + + if (e->enabled && !b) { + g_assert(e->mainloop->n_enabled_defer_events > 0); + e->mainloop->n_enabled_defer_events--; + } else if (!e->enabled && b) + e->mainloop->n_enabled_defer_events++; + + e->enabled = b; +} + +static void glib_defer_free(pa_defer_event *e) { + g_assert(e); + g_assert(!e->dead); + + e->dead = 1; + e->mainloop->defer_events_please_scan++; + + if (e->enabled) { + g_assert(e->mainloop->n_enabled_defer_events > 0); + e->mainloop->n_enabled_defer_events--; + } +} + +static void glib_defer_set_destroy(pa_defer_event *e, pa_defer_event_destroy_cb_t cb) { + g_assert(e); + g_assert(!e->dead); + + e->destroy_callback = cb; +} + +/* quit() */ + +static void glib_quit(pa_mainloop_api*a, int retval) { + + g_warning("quit() ignored"); + + /* NOOP */ +} + +static pa_time_event* find_next_time_event(pa_glib_mainloop *g) { + pa_time_event *t, *n = NULL; + g_assert(g); + + if (g->cached_next_time_event) + return g->cached_next_time_event; + + for (t = g->time_events; t; t = t->next) { + + if (t->dead || !t->enabled) + continue; + + if (!n || pa_timeval_cmp(&t->timeval, &n->timeval) < 0) { + n = t; + + /* Shortcut for tv = { 0, 0 } */ + if (n->timeval.tv_sec <= 0) + break; + } + } + + g->cached_next_time_event = n; + return n; +} + +static void scan_dead(pa_glib_mainloop *g) { + g_assert(g); + + if (g->io_events_please_scan) + cleanup_io_events(g, 0); + + if (g->time_events_please_scan) + cleanup_time_events(g, 0); + + if (g->defer_events_please_scan) + cleanup_defer_events(g, 0); +} + +static gboolean prepare_func(GSource *source, gint *timeout) { + pa_glib_mainloop *g = (pa_glib_mainloop*) source; + + g_assert(g); + g_assert(timeout); + + scan_dead(g); + + if (g->n_enabled_defer_events) { + *timeout = 0; + return TRUE; + } else if (g->n_enabled_time_events) { + pa_time_event *t; + GTimeVal now; + struct timeval tvnow; + pa_usec_t usec; + + t = find_next_time_event(g); + g_assert(t); + + g_get_current_time(&now); + tvnow.tv_sec = now.tv_sec; + tvnow.tv_usec = now.tv_usec; + + if (pa_timeval_cmp(&t->timeval, &tvnow) <= 0) { + *timeout = 0; + return TRUE; + } + usec = pa_timeval_diff(&t->timeval, &tvnow); + *timeout = (gint) (usec / 1000); + } else + *timeout = -1; + + return FALSE; +} +static gboolean check_func(GSource *source) { + pa_glib_mainloop *g = (pa_glib_mainloop*) source; + pa_io_event *e; + + g_assert(g); + + if (g->n_enabled_defer_events) + return TRUE; + else if (g->n_enabled_time_events) { + pa_time_event *t; + GTimeVal now; + struct timeval tvnow; + + t = find_next_time_event(g); + g_assert(t); + + g_get_current_time(&now); + tvnow.tv_sec = now.tv_sec; + tvnow.tv_usec = now.tv_usec; + + if (pa_timeval_cmp(&t->timeval, &tvnow) <= 0) + return TRUE; + } + + for (e = g->io_events; e; e = e->next) + if (!e->dead && e->poll_fd.revents != 0) + return TRUE; + + return FALSE; +} + +static gboolean dispatch_func(GSource *source, GSourceFunc callback, gpointer userdata) { + pa_glib_mainloop *g = (pa_glib_mainloop*) source; + pa_io_event *e; + + g_assert(g); + + if (g->n_enabled_defer_events) { + pa_defer_event *d; + + for (d = g->defer_events; d; d = d->next) { + if (d->dead || !d->enabled) + continue; + + break; + } + + g_assert(d); + + d->callback(&g->api, d, d->userdata); + return TRUE; + } + + if (g->n_enabled_time_events) { + GTimeVal now; + struct timeval tvnow; + pa_time_event *t; + + t = find_next_time_event(g); + g_assert(t); + + g_get_current_time(&now); + tvnow.tv_sec = now.tv_sec; + tvnow.tv_usec = now.tv_usec; + + if (pa_timeval_cmp(&t->timeval, &tvnow) <= 0) { + + /* Disable time event */ + glib_time_restart(t, NULL); + + t->callback(&g->api, t, &t->timeval, t->userdata); + return TRUE; + } + } + + for (e = g->io_events; e; e = e->next) + if (!e->dead && e->poll_fd.revents != 0) { + e->callback(&g->api, e, e->poll_fd.fd, map_flags_from_glib(e->poll_fd.revents), e->userdata); + e->poll_fd.revents = 0; + return TRUE; + } + + return FALSE; +} + +static const pa_mainloop_api vtable = { + .userdata = NULL, + + .io_new = glib_io_new, + .io_enable = glib_io_enable, + .io_free = glib_io_free, + .io_set_destroy = glib_io_set_destroy, + + .time_new = glib_time_new, + .time_restart = glib_time_restart, + .time_free = glib_time_free, + .time_set_destroy = glib_time_set_destroy, + + .defer_new = glib_defer_new, + .defer_enable = glib_defer_enable, + .defer_free = glib_defer_free, + .defer_set_destroy = glib_defer_set_destroy, + + .quit = glib_quit, +}; + +pa_glib_mainloop *pa_glib_mainloop_new(GMainContext *c) { + pa_glib_mainloop *g; + + static GSourceFuncs source_funcs = { + prepare_func, + check_func, + dispatch_func, + NULL, + NULL, + NULL + }; + + g = (pa_glib_mainloop*) g_source_new(&source_funcs, sizeof(pa_glib_mainloop)); + g_main_context_ref(g->context = c ? c : g_main_context_default()); + + g->api = vtable; + g->api.userdata = g; + + PA_LLIST_HEAD_INIT(pa_io_event, g->io_events); + PA_LLIST_HEAD_INIT(pa_time_event, g->time_events); + PA_LLIST_HEAD_INIT(pa_defer_event, g->defer_events); + + g->n_enabled_defer_events = g->n_enabled_time_events = 0; + g->io_events_please_scan = g->time_events_please_scan = g->defer_events_please_scan = 0; + + g->cached_next_time_event = NULL; + + g_source_attach(&g->source, g->context); + g_source_set_can_recurse(&g->source, FALSE); + + return g; +} + +void pa_glib_mainloop_free(pa_glib_mainloop* g) { + g_assert(g); + + cleanup_io_events(g, 1); + cleanup_defer_events(g, 1); + cleanup_time_events(g, 1); + + g_main_context_unref(g->context); + g_source_destroy(&g->source); + g_source_unref(&g->source); +} + +pa_mainloop_api* pa_glib_mainloop_get_api(pa_glib_mainloop *g) { + g_assert(g); + + return &g->api; +} diff --git a/src/pulse/glib-mainloop.h b/src/pulse/glib-mainloop.h new file mode 100644 index 0000000..52d9a75 --- /dev/null +++ b/src/pulse/glib-mainloop.h @@ -0,0 +1,67 @@ +#ifndef fooglibmainloophfoo +#define fooglibmainloophfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <glib.h> + +#include <pulse/mainloop-api.h> +#include <pulse/cdecl.h> +#include <pulse/version.h> + +/** \page glib-mainloop GLIB Main Loop Bindings + * + * \section overv_sec Overview + * + * The GLIB main loop bindings are extremely easy to use. All that is + * required is to create a pa_glib_mainloop object using + * pa_glib_mainloop_new(). When the main loop abstraction is needed, it is + * provided by pa_glib_mainloop_get_api(). + * + */ + +/** \file + * GLIB main loop support + * + * See also \subpage glib-mainloop + */ + +PA_C_DECL_BEGIN + +/** An opaque GLIB main loop object */ +typedef struct pa_glib_mainloop pa_glib_mainloop; + +/** Create a new GLIB main loop object for the specified GLIB main + * loop context. Takes an argument c for the + * GMainContext to use. If c is NULL the default context is used. */ +pa_glib_mainloop *pa_glib_mainloop_new(GMainContext *c); + +/** Free the GLIB main loop object */ +void pa_glib_mainloop_free(pa_glib_mainloop* g); + +/** Return the abstract main loop API vtable for the GLIB main loop + object. No need to free the API as it is owned by the loop + and is destroyed when the loop is freed. */ +pa_mainloop_api* pa_glib_mainloop_get_api(pa_glib_mainloop *g); + +PA_C_DECL_END + +#endif diff --git a/src/pulse/internal.h b/src/pulse/internal.h new file mode 100644 index 0000000..a10b47d --- /dev/null +++ b/src/pulse/internal.h @@ -0,0 +1,323 @@ +#ifndef foointernalhfoo +#define foointernalhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulse/mainloop-api.h> +#include <pulse/context.h> +#include <pulse/stream.h> +#include <pulse/operation.h> +#include <pulse/subscribe.h> +#include <pulse/ext-device-manager.h> +#include <pulse/ext-device-restore.h> +#include <pulse/ext-stream-restore.h> + +#include <pulsecore/socket-client.h> +#include <pulsecore/pstream.h> +#include <pulsecore/pdispatch.h> +#include <pulsecore/llist.h> +#include <pulsecore/native-common.h> +#include <pulsecore/strlist.h> +#include <pulsecore/mcalign.h> +#include <pulsecore/memblockq.h> +#include <pulsecore/hashmap.h> +#include <pulsecore/refcnt.h> +#include <pulsecore/time-smoother.h> +#ifdef HAVE_DBUS +#include <pulsecore/dbus-util.h> +#endif + +#include "client-conf.h" + +#define DEFAULT_TIMEOUT (30) + +#define PA_PROTOCOL_FLAG_MASK 0xFFFF0000U +#define PA_PROTOCOL_VERSION_MASK 0x0000FFFFU + +#define PA_PROTOCOL_FLAG_SHM 0x80000000U +#define PA_PROTOCOL_FLAG_MEMFD 0x40000000U + +typedef struct pa_context_error { + int error; +} pa_context_error; + +struct pa_context { + PA_REFCNT_DECLARE; + +#ifdef HAVE_DBUS + pa_dbus_wrap_connection *system_bus; + pa_dbus_wrap_connection *session_bus; +#endif + + pa_proplist *proplist; + pa_mainloop_api* mainloop; + + pa_socket_client *client; + pa_pstream *pstream; + pa_pdispatch *pdispatch; + + pa_srbchannel_template srb_template; + uint32_t srb_setup_tag; + + pa_hashmap *record_streams, *playback_streams; + PA_LLIST_HEAD(pa_stream, streams); + PA_LLIST_HEAD(pa_operation, operations); + + uint32_t version; + uint32_t ctag; + uint32_t csyncid; + pa_context_error *error; + pa_context_state_t state; + + pa_context_notify_cb_t state_callback; + void *state_userdata; + pa_context_subscribe_cb_t subscribe_callback; + void *subscribe_userdata; + pa_context_event_cb_t event_callback; + void *event_userdata; + + pa_mempool *mempool; + + bool is_local:1; + bool do_shm:1; + bool memfd_on_local:1; + bool server_specified:1; + bool no_fail:1; + bool do_autospawn:1; + bool use_rtclock:1; + bool filter_added:1; + pa_spawn_api spawn_api; + + pa_mem_type_t shm_type; + + pa_strlist *server_list; + + char *server; + + pa_client_conf *conf; + + uint32_t client_index; + + /* Extension specific data */ + struct { + pa_ext_device_manager_subscribe_cb_t callback; + void *userdata; + } ext_device_manager; + struct { + pa_ext_device_restore_subscribe_cb_t callback; + void *userdata; + } ext_device_restore; + struct { + pa_ext_stream_restore_subscribe_cb_t callback; + void *userdata; + } ext_stream_restore; +}; + +#define PA_MAX_WRITE_INDEX_CORRECTIONS 32 + +typedef struct pa_index_correction { + uint32_t tag; + int64_t value; + bool valid:1; + bool absolute:1; + bool corrupt:1; +} pa_index_correction; + +#define PA_MAX_FORMATS (PA_ENCODING_MAX) + +struct pa_stream { + PA_REFCNT_DECLARE; + PA_LLIST_FIELDS(pa_stream); + + pa_context *context; + pa_mainloop_api *mainloop; + + uint32_t direct_on_input; + + pa_stream_direction_t direction; + pa_stream_state_t state; + pa_stream_flags_t flags; + + pa_sample_spec sample_spec; + pa_channel_map channel_map; + uint8_t n_formats; + pa_format_info *req_formats[PA_MAX_FORMATS]; + pa_format_info *format; + + pa_proplist *proplist; + + bool channel_valid:1; + bool suspended:1; + bool corked:1; + bool timing_info_valid:1; + bool auto_timing_update_requested:1; + + uint32_t channel; + uint32_t syncid; + uint32_t stream_index; + + int64_t requested_bytes; + pa_buffer_attr buffer_attr; + + uint32_t device_index; + char *device_name; + + /* playback */ + pa_memblock *write_memblock; + void *write_data; + int64_t latest_underrun_at_index; + + /* recording */ + pa_memchunk peek_memchunk; + void *peek_data; + pa_memblockq *record_memblockq; + + /* Store latest latency info */ + pa_timing_info timing_info; + + /* Use to make sure that time advances monotonically */ + pa_usec_t previous_time; + + /* time updates with tags older than these are invalid */ + uint32_t write_index_not_before; + uint32_t read_index_not_before; + + /* Data about individual timing update corrections */ + pa_index_correction write_index_corrections[PA_MAX_WRITE_INDEX_CORRECTIONS]; + int current_write_index_correction; + + /* Latency interpolation stuff */ + pa_time_event *auto_timing_update_event; + pa_usec_t auto_timing_interval_usec; + + pa_smoother *smoother; + + /* Callbacks */ + pa_stream_notify_cb_t state_callback; + void *state_userdata; + pa_stream_request_cb_t read_callback; + void *read_userdata; + pa_stream_request_cb_t write_callback; + void *write_userdata; + pa_stream_notify_cb_t overflow_callback; + void *overflow_userdata; + pa_stream_notify_cb_t underflow_callback; + void *underflow_userdata; + pa_stream_notify_cb_t latency_update_callback; + void *latency_update_userdata; + pa_stream_notify_cb_t moved_callback; + void *moved_userdata; + pa_stream_notify_cb_t suspended_callback; + void *suspended_userdata; + pa_stream_notify_cb_t started_callback; + void *started_userdata; + pa_stream_event_cb_t event_callback; + void *event_userdata; + pa_stream_notify_cb_t buffer_attr_callback; + void *buffer_attr_userdata; +}; + +typedef void (*pa_operation_cb_t)(void); + +struct pa_operation { + PA_REFCNT_DECLARE; + + pa_context *context; + pa_stream *stream; + + PA_LLIST_FIELDS(pa_operation); + + pa_operation_state_t state; + void *userdata; + pa_operation_cb_t callback; + void *state_userdata; + pa_operation_notify_cb_t state_callback; + + void *private; /* some operations might need this */ +}; + +void pa_command_request(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +void pa_command_stream_killed(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +void pa_command_subscribe_event(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +void pa_command_overflow_or_underflow(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +void pa_command_stream_suspended(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +void pa_command_stream_moved(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +void pa_command_stream_started(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +void pa_command_stream_event(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +void pa_command_client_event(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +void pa_command_stream_buffer_attr(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); + +pa_operation *pa_operation_new(pa_context *c, pa_stream *s, pa_operation_cb_t callback, void *userdata); +void pa_operation_done(pa_operation *o); + +void pa_create_stream_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +void pa_stream_disconnect_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +void pa_context_simple_ack_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +void pa_stream_simple_ack_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); + +void pa_context_fail(pa_context *c, int error); +int pa_context_set_error(const pa_context *c, int error); +void pa_context_set_state(pa_context *c, pa_context_state_t st); +int pa_context_handle_error(pa_context *c, uint32_t command, pa_tagstruct *t, bool fail); +pa_operation* pa_context_send_simple_command(pa_context *c, uint32_t command, void (*internal_callback)(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata), void (*cb)(void), void *userdata); + +void pa_stream_set_state(pa_stream *s, pa_stream_state_t st); + +pa_tagstruct *pa_tagstruct_command(pa_context *c, uint32_t command, uint32_t *tag); + +#define PA_CHECK_VALIDITY(context, expression, error) \ + do { \ + if (!(expression)) \ + return -pa_context_set_error((context), (error)); \ + } while(false) + +#define PA_CHECK_VALIDITY_RETURN_ANY(context, expression, error, value) \ + do { \ + if (!(expression)) { \ + pa_context_set_error((context), (error)); \ + return value; \ + } \ + } while(false) + +#define PA_CHECK_VALIDITY_RETURN_NULL(context, expression, error) \ + PA_CHECK_VALIDITY_RETURN_ANY(context, expression, error, NULL) + +#define PA_FAIL(context, error) \ + do { \ + return -pa_context_set_error((context), (error)); \ + } while(false) + +#define PA_FAIL_RETURN_ANY(context, error, value) \ + do { \ + pa_context_set_error((context), (error)); \ + return value; \ + } while(false) + +#define PA_FAIL_RETURN_NULL(context, error) \ + PA_FAIL_RETURN_ANY(context, error, NULL) + +void pa_ext_device_manager_command(pa_context *c, uint32_t tag, pa_tagstruct *t); +void pa_ext_device_restore_command(pa_context *c, uint32_t tag, pa_tagstruct *t); +void pa_ext_stream_restore_command(pa_context *c, uint32_t tag, pa_tagstruct *t); + +bool pa_mainloop_is_our_api(const pa_mainloop_api*m); + +#endif diff --git a/src/pulse/introspect.c b/src/pulse/introspect.c new file mode 100644 index 0000000..3027f38 --- /dev/null +++ b/src/pulse/introspect.c @@ -0,0 +1,2207 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/context.h> +#include <pulse/direction.h> +#include <pulse/xmalloc.h> +#include <pulse/fork-detect.h> + +#include <pulsecore/macro.h> +#include <pulsecore/core-util.h> +#include <pulsecore/pstream-util.h> + +#include "internal.h" +#include "introspect.h" + +/*** Statistics ***/ + +static void context_stat_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_operation *o = userdata; + pa_stat_info i, *p = &i; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + pa_zero(i); + + if (!o->context) + goto finish; + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(o->context, command, t, false) < 0) + goto finish; + + p = NULL; + } else if (pa_tagstruct_getu32(t, &i.memblock_total) < 0 || + pa_tagstruct_getu32(t, &i.memblock_total_size) < 0 || + pa_tagstruct_getu32(t, &i.memblock_allocated) < 0 || + pa_tagstruct_getu32(t, &i.memblock_allocated_size) < 0 || + pa_tagstruct_getu32(t, &i.scache_size) < 0 || + !pa_tagstruct_eof(t)) { + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + + if (o->callback) { + pa_stat_info_cb_t cb = (pa_stat_info_cb_t) o->callback; + cb(o->context, p, o->userdata); + } + +finish: + pa_operation_done(o); + pa_operation_unref(o); +} + +pa_operation* pa_context_stat(pa_context *c, pa_stat_info_cb_t cb, void *userdata) { + return pa_context_send_simple_command(c, PA_COMMAND_STAT, context_stat_callback, (pa_operation_cb_t) cb, userdata); +} + +/*** Server Info ***/ + +static void context_get_server_info_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_operation *o = userdata; + pa_server_info i, *p = &i; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + pa_zero(i); + + if (!o->context) + goto finish; + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(o->context, command, t, false) < 0) + goto finish; + + p = NULL; + } else if (pa_tagstruct_gets(t, &i.server_name) < 0 || + pa_tagstruct_gets(t, &i.server_version) < 0 || + pa_tagstruct_gets(t, &i.user_name) < 0 || + pa_tagstruct_gets(t, &i.host_name) < 0 || + pa_tagstruct_get_sample_spec(t, &i.sample_spec) < 0 || + pa_tagstruct_gets(t, &i.default_sink_name) < 0 || + pa_tagstruct_gets(t, &i.default_source_name) < 0 || + pa_tagstruct_getu32(t, &i.cookie) < 0 || + (o->context->version >= 15 && + pa_tagstruct_get_channel_map(t, &i.channel_map) < 0) || + !pa_tagstruct_eof(t)) { + + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + + if (p && o->context->version < 15) + pa_channel_map_init_extend(&i.channel_map, i.sample_spec.channels, PA_CHANNEL_MAP_DEFAULT); + + if (o->callback) { + pa_server_info_cb_t cb = (pa_server_info_cb_t) o->callback; + cb(o->context, p, o->userdata); + } + +finish: + pa_operation_done(o); + pa_operation_unref(o); +} + +pa_operation* pa_context_get_server_info(pa_context *c, pa_server_info_cb_t cb, void *userdata) { + return pa_context_send_simple_command(c, PA_COMMAND_GET_SERVER_INFO, context_get_server_info_callback, (pa_operation_cb_t) cb, userdata); +} + +/*** Sink Info ***/ + +static void context_get_sink_info_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_operation *o = userdata; + int eol = 1; + pa_sink_info i; + uint32_t j; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + /* For safety in case someone use fail: outside the while loop below */ + pa_zero(i); + + if (!o->context) + goto finish; + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(o->context, command, t, false) < 0) + goto finish; + + eol = -1; + } else { + + while (!pa_tagstruct_eof(t)) { + bool mute; + uint32_t flags; + uint32_t state; + const char *ap = NULL; + + pa_zero(i); + i.proplist = pa_proplist_new(); + i.base_volume = PA_VOLUME_NORM; + i.n_volume_steps = PA_VOLUME_NORM+1; + mute = false; + state = PA_SINK_INVALID_STATE; + i.card = PA_INVALID_INDEX; + + if (pa_tagstruct_getu32(t, &i.index) < 0 || + pa_tagstruct_gets(t, &i.name) < 0 || + pa_tagstruct_gets(t, &i.description) < 0 || + pa_tagstruct_get_sample_spec(t, &i.sample_spec) < 0 || + pa_tagstruct_get_channel_map(t, &i.channel_map) < 0 || + pa_tagstruct_getu32(t, &i.owner_module) < 0 || + pa_tagstruct_get_cvolume(t, &i.volume) < 0 || + pa_tagstruct_get_boolean(t, &mute) < 0 || + pa_tagstruct_getu32(t, &i.monitor_source) < 0 || + pa_tagstruct_gets(t, &i.monitor_source_name) < 0 || + pa_tagstruct_get_usec(t, &i.latency) < 0 || + pa_tagstruct_gets(t, &i.driver) < 0 || + pa_tagstruct_getu32(t, &flags) < 0 || + (o->context->version >= 13 && + (pa_tagstruct_get_proplist(t, i.proplist) < 0 || + pa_tagstruct_get_usec(t, &i.configured_latency) < 0)) || + (o->context->version >= 15 && + (pa_tagstruct_get_volume(t, &i.base_volume) < 0 || + pa_tagstruct_getu32(t, &state) < 0 || + pa_tagstruct_getu32(t, &i.n_volume_steps) < 0 || + pa_tagstruct_getu32(t, &i.card) < 0)) || + (o->context->version >= 16 && + (pa_tagstruct_getu32(t, &i.n_ports)))) { + + goto fail; + } + + if (o->context->version >= 16) { + if (i.n_ports > 0) { + i.ports = pa_xnew(pa_sink_port_info*, i.n_ports+1); + i.ports[0] = pa_xnew(pa_sink_port_info, i.n_ports); + + for (j = 0; j < i.n_ports; j++) { + i.ports[j] = &i.ports[0][j]; + + if (pa_tagstruct_gets(t, &i.ports[j]->name) < 0 || + pa_tagstruct_gets(t, &i.ports[j]->description) < 0 || + pa_tagstruct_getu32(t, &i.ports[j]->priority) < 0) { + + goto fail; + } + + i.ports[j]->available = PA_PORT_AVAILABLE_UNKNOWN; + if (o->context->version >= 24) { + uint32_t av; + if (pa_tagstruct_getu32(t, &av) < 0 || av > PA_PORT_AVAILABLE_YES) + goto fail; + i.ports[j]->available = av; + } + i.ports[j]->availability_group = NULL; + i.ports[j]->type = PA_DEVICE_PORT_TYPE_UNKNOWN; + if (o->context->version >= 34) { + if (pa_tagstruct_gets(t, &i.ports[j]->availability_group) < 0 || + pa_tagstruct_getu32(t, &i.ports[j]->type) < 0) + goto fail; + } + } + + i.ports[j] = NULL; + } + + if (pa_tagstruct_gets(t, &ap) < 0) + goto fail; + + if (ap) { + for (j = 0; j < i.n_ports; j++) + if (pa_streq(i.ports[j]->name, ap)) { + i.active_port = i.ports[j]; + break; + } + } + } + + if (o->context->version >= 21) { + uint8_t n_formats; + if (pa_tagstruct_getu8(t, &n_formats) < 0 || n_formats < 1) + goto fail; + + i.formats = pa_xnew0(pa_format_info*, n_formats); + + for (j = 0; j < n_formats; j++) { + i.n_formats++; + i.formats[j] = pa_format_info_new(); + + if (pa_tagstruct_get_format_info(t, i.formats[j]) < 0) + goto fail; + } + } + + i.mute = (int) mute; + i.flags = (pa_sink_flags_t) flags; + i.state = (pa_sink_state_t) state; + + if (o->callback) { + pa_sink_info_cb_t cb = (pa_sink_info_cb_t) o->callback; + cb(o->context, &i, 0, o->userdata); + } + + if (i.formats) { + for (j = 0; j < i.n_formats; j++) + pa_format_info_free(i.formats[j]); + pa_xfree(i.formats); + } + if (i.ports) { + pa_xfree(i.ports[0]); + pa_xfree(i.ports); + } + pa_proplist_free(i.proplist); + } + } + + if (o->callback) { + pa_sink_info_cb_t cb = (pa_sink_info_cb_t) o->callback; + cb(o->context, NULL, eol, o->userdata); + } + +finish: + pa_operation_done(o); + pa_operation_unref(o); + return; + +fail: + pa_assert(i.proplist); + + pa_context_fail(o->context, PA_ERR_PROTOCOL); + + if (i.formats) { + for (j = 0; j < i.n_formats; j++) + pa_format_info_free(i.formats[j]); + pa_xfree(i.formats); + } + if (i.ports) { + pa_xfree(i.ports[0]); + pa_xfree(i.ports); + } + pa_proplist_free(i.proplist); + + goto finish; +} + +pa_operation* pa_context_get_sink_info_list(pa_context *c, pa_sink_info_cb_t cb, void *userdata) { + return pa_context_send_simple_command(c, PA_COMMAND_GET_SINK_INFO_LIST, context_get_sink_info_callback, (pa_operation_cb_t) cb, userdata); +} + +pa_operation* pa_context_get_sink_info_by_index(pa_context *c, uint32_t idx, pa_sink_info_cb_t cb, void *userdata) { + pa_tagstruct *t; + pa_operation *o; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(cb); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_GET_SINK_INFO, &tag); + pa_tagstruct_putu32(t, idx); + pa_tagstruct_puts(t, NULL); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_get_sink_info_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_get_sink_info_by_name(pa_context *c, const char *name, pa_sink_info_cb_t cb, void *userdata) { + pa_tagstruct *t; + pa_operation *o; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(cb); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, !name || *name, PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_GET_SINK_INFO, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, name); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_get_sink_info_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_set_sink_port_by_index(pa_context *c, uint32_t idx, const char*port, pa_context_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 16, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_SET_SINK_PORT, &tag); + pa_tagstruct_putu32(t, idx); + pa_tagstruct_puts(t, NULL); + pa_tagstruct_puts(t, port); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_set_sink_port_by_name(pa_context *c, const char *name, const char*port, pa_context_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, !name || *name, PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 16, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_SET_SINK_PORT, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, name); + pa_tagstruct_puts(t, port); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +/*** Source info ***/ + +static void context_get_source_info_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_operation *o = userdata; + int eol = 1; + pa_source_info i; + uint32_t j; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + /* For safety in case someone use fail: outside the while loop below */ + pa_zero(i); + + if (!o->context) + goto finish; + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(o->context, command, t, false) < 0) + goto finish; + + eol = -1; + } else { + + while (!pa_tagstruct_eof(t)) { + bool mute; + uint32_t flags; + uint32_t state; + const char *ap; + + pa_zero(i); + i.proplist = pa_proplist_new(); + i.base_volume = PA_VOLUME_NORM; + i.n_volume_steps = PA_VOLUME_NORM+1; + mute = false; + state = PA_SOURCE_INVALID_STATE; + i.card = PA_INVALID_INDEX; + + if (pa_tagstruct_getu32(t, &i.index) < 0 || + pa_tagstruct_gets(t, &i.name) < 0 || + pa_tagstruct_gets(t, &i.description) < 0 || + pa_tagstruct_get_sample_spec(t, &i.sample_spec) < 0 || + pa_tagstruct_get_channel_map(t, &i.channel_map) < 0 || + pa_tagstruct_getu32(t, &i.owner_module) < 0 || + pa_tagstruct_get_cvolume(t, &i.volume) < 0 || + pa_tagstruct_get_boolean(t, &mute) < 0 || + pa_tagstruct_getu32(t, &i.monitor_of_sink) < 0 || + pa_tagstruct_gets(t, &i.monitor_of_sink_name) < 0 || + pa_tagstruct_get_usec(t, &i.latency) < 0 || + pa_tagstruct_gets(t, &i.driver) < 0 || + pa_tagstruct_getu32(t, &flags) < 0 || + (o->context->version >= 13 && + (pa_tagstruct_get_proplist(t, i.proplist) < 0 || + pa_tagstruct_get_usec(t, &i.configured_latency) < 0)) || + (o->context->version >= 15 && + (pa_tagstruct_get_volume(t, &i.base_volume) < 0 || + pa_tagstruct_getu32(t, &state) < 0 || + pa_tagstruct_getu32(t, &i.n_volume_steps) < 0 || + pa_tagstruct_getu32(t, &i.card) < 0)) || + (o->context->version >= 16 && + (pa_tagstruct_getu32(t, &i.n_ports)))) { + + goto fail; + } + + if (o->context->version >= 16) { + if (i.n_ports > 0) { + i.ports = pa_xnew(pa_source_port_info*, i.n_ports+1); + i.ports[0] = pa_xnew(pa_source_port_info, i.n_ports); + + for (j = 0; j < i.n_ports; j++) { + i.ports[j] = &i.ports[0][j]; + + if (pa_tagstruct_gets(t, &i.ports[j]->name) < 0 || + pa_tagstruct_gets(t, &i.ports[j]->description) < 0 || + pa_tagstruct_getu32(t, &i.ports[j]->priority) < 0) { + + goto fail; + } + + i.ports[j]->available = PA_PORT_AVAILABLE_UNKNOWN; + if (o->context->version >= 24) { + uint32_t av; + if (pa_tagstruct_getu32(t, &av) < 0 || av > PA_PORT_AVAILABLE_YES) + goto fail; + i.ports[j]->available = av; + } + i.ports[j]->availability_group = NULL; + i.ports[j]->type = PA_DEVICE_PORT_TYPE_UNKNOWN; + if (o->context->version >= 34) { + if (pa_tagstruct_gets(t, &i.ports[j]->availability_group) < 0 || + pa_tagstruct_getu32(t, &i.ports[j]->type)) + goto fail; + } + } + + i.ports[j] = NULL; + } + if (pa_tagstruct_gets(t, &ap) < 0) + goto fail; + + if (ap) { + for (j = 0; j < i.n_ports; j++) + if (pa_streq(i.ports[j]->name, ap)) { + i.active_port = i.ports[j]; + break; + } + } + } + + if (o->context->version >= 22) { + uint8_t n_formats; + if (pa_tagstruct_getu8(t, &n_formats) < 0 || n_formats < 1) + goto fail; + + i.formats = pa_xnew0(pa_format_info*, n_formats); + + for (j = 0; j < n_formats; j++) { + i.n_formats++; + i.formats[j] = pa_format_info_new(); + + if (pa_tagstruct_get_format_info(t, i.formats[j]) < 0) + goto fail; + } + } + + i.mute = (int) mute; + i.flags = (pa_source_flags_t) flags; + i.state = (pa_source_state_t) state; + + if (o->callback) { + pa_source_info_cb_t cb = (pa_source_info_cb_t) o->callback; + cb(o->context, &i, 0, o->userdata); + } + + if (i.formats) { + for (j = 0; j < i.n_formats; j++) + pa_format_info_free(i.formats[j]); + pa_xfree(i.formats); + } + if (i.ports) { + pa_xfree(i.ports[0]); + pa_xfree(i.ports); + } + pa_proplist_free(i.proplist); + } + } + + if (o->callback) { + pa_source_info_cb_t cb = (pa_source_info_cb_t) o->callback; + cb(o->context, NULL, eol, o->userdata); + } + +finish: + pa_operation_done(o); + pa_operation_unref(o); + return; + +fail: + pa_assert(i.proplist); + + pa_context_fail(o->context, PA_ERR_PROTOCOL); + + if (i.formats) { + for (j = 0; j < i.n_formats; j++) + pa_format_info_free(i.formats[j]); + pa_xfree(i.formats); + } + if (i.ports) { + pa_xfree(i.ports[0]); + pa_xfree(i.ports); + } + pa_proplist_free(i.proplist); + + goto finish; +} + +pa_operation* pa_context_get_source_info_list(pa_context *c, pa_source_info_cb_t cb, void *userdata) { + return pa_context_send_simple_command(c, PA_COMMAND_GET_SOURCE_INFO_LIST, context_get_source_info_callback, (pa_operation_cb_t) cb, userdata); +} + +pa_operation* pa_context_get_source_info_by_index(pa_context *c, uint32_t idx, pa_source_info_cb_t cb, void *userdata) { + pa_tagstruct *t; + pa_operation *o; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(cb); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_GET_SOURCE_INFO, &tag); + pa_tagstruct_putu32(t, idx); + pa_tagstruct_puts(t, NULL); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_get_source_info_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_get_source_info_by_name(pa_context *c, const char *name, pa_source_info_cb_t cb, void *userdata) { + pa_tagstruct *t; + pa_operation *o; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(cb); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, !name || *name, PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_GET_SOURCE_INFO, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, name); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_get_source_info_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_set_source_port_by_index(pa_context *c, uint32_t idx, const char*port, pa_context_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 16, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_SET_SOURCE_PORT, &tag); + pa_tagstruct_putu32(t, idx); + pa_tagstruct_puts(t, NULL); + pa_tagstruct_puts(t, port); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_set_source_port_by_name(pa_context *c, const char *name, const char*port, pa_context_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, !name || *name, PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 16, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_SET_SOURCE_PORT, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, name); + pa_tagstruct_puts(t, port); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +/*** Client info ***/ + +static void context_get_client_info_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_operation *o = userdata; + int eol = 1; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (!o->context) + goto finish; + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(o->context, command, t, false) < 0) + goto finish; + + eol = -1; + } else { + + while (!pa_tagstruct_eof(t)) { + pa_client_info i; + + pa_zero(i); + i.proplist = pa_proplist_new(); + + if (pa_tagstruct_getu32(t, &i.index) < 0 || + pa_tagstruct_gets(t, &i.name) < 0 || + pa_tagstruct_getu32(t, &i.owner_module) < 0 || + pa_tagstruct_gets(t, &i.driver) < 0 || + (o->context->version >= 13 && pa_tagstruct_get_proplist(t, i.proplist) < 0)) { + + pa_context_fail(o->context, PA_ERR_PROTOCOL); + pa_proplist_free(i.proplist); + goto finish; + } + + if (o->callback) { + pa_client_info_cb_t cb = (pa_client_info_cb_t) o->callback; + cb(o->context, &i, 0, o->userdata); + } + + pa_proplist_free(i.proplist); + } + } + + if (o->callback) { + pa_client_info_cb_t cb = (pa_client_info_cb_t) o->callback; + cb(o->context, NULL, eol, o->userdata); + } + +finish: + pa_operation_done(o); + pa_operation_unref(o); +} + +pa_operation* pa_context_get_client_info(pa_context *c, uint32_t idx, pa_client_info_cb_t cb, void *userdata) { + pa_tagstruct *t; + pa_operation *o; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(cb); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_GET_CLIENT_INFO, &tag); + pa_tagstruct_putu32(t, idx); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_get_client_info_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_get_client_info_list(pa_context *c, pa_client_info_cb_t cb, void *userdata) { + return pa_context_send_simple_command(c, PA_COMMAND_GET_CLIENT_INFO_LIST, context_get_client_info_callback, (pa_operation_cb_t) cb, userdata); +} + +/*** Card info ***/ + +static void card_info_free(pa_card_info* i) { + if (i->proplist) + pa_proplist_free(i->proplist); + + pa_xfree(i->profiles); + + if (i->n_profiles) { + uint32_t j; + + for (j = 0; j < i->n_profiles; j++) + pa_xfree(i->profiles2[j]); + + pa_xfree(i->profiles2); + } + + if (i->ports) { + uint32_t j; + + for (j = 0; j < i->n_ports; j++) { + if (i->ports[j]) { + if (i->ports[j]->profiles) + pa_xfree(i->ports[j]->profiles); + if (i->ports[j]->profiles2) + pa_xfree(i->ports[j]->profiles2); + if (i->ports[j]->proplist) + pa_proplist_free(i->ports[j]->proplist); + } + } + + pa_xfree(i->ports[0]); + pa_xfree(i->ports); + } +} + +static int fill_card_port_info(pa_context *context, pa_tagstruct* t, pa_card_info* i) { + uint32_t j, k, l; + + if (pa_tagstruct_getu32(t, &i->n_ports) < 0) + return -PA_ERR_PROTOCOL; + + if (i->n_ports == 0) { + i->ports = NULL; + return 0; + } + + i->ports = pa_xnew0(pa_card_port_info*, i->n_ports+1); + i->ports[0] = pa_xnew0(pa_card_port_info, i->n_ports); + + for (j = 0; j < i->n_ports; j++) { + uint8_t direction; + uint32_t available; + pa_card_port_info* port = i->ports[j] = &i->ports[0][j]; + + port->proplist = pa_proplist_new(); + + if (pa_tagstruct_gets(t, &port->name) < 0 || + pa_tagstruct_gets(t, &port->description) < 0 || + pa_tagstruct_getu32(t, &port->priority) < 0 || + pa_tagstruct_getu32(t, &available) < 0 || + pa_tagstruct_getu8(t, &direction) < 0 || + !pa_direction_valid(direction) || + pa_tagstruct_get_proplist(t, port->proplist) < 0 || + pa_tagstruct_getu32(t, &port->n_profiles) < 0) { + + return -PA_ERR_PROTOCOL; + } + + if (available > PA_PORT_AVAILABLE_YES ) { + return -PA_ERR_PROTOCOL; + } + + port->direction = direction; + port->available = available; + + if (port->n_profiles > 0) { + port->profiles = pa_xnew0(pa_card_profile_info*, i->n_profiles+1); + port->profiles2 = pa_xnew0(pa_card_profile_info2*, i->n_profiles+1); + + for (k = 0; k < port->n_profiles; k++) { + const char* profilename; + + if (pa_tagstruct_gets(t, &profilename) < 0) + return -PA_ERR_PROTOCOL; + + for (l = 0; l < i->n_profiles; l++) { + if (pa_streq(i->profiles[l].name, profilename)) { + port->profiles[k] = &i->profiles[l]; + port->profiles2[k] = i->profiles2[l]; + break; + } + } + + if (l >= i->n_profiles) + return -PA_ERR_PROTOCOL; + } + } + if (context->version >= 27) { + if (pa_tagstruct_gets64(t, &port->latency_offset) < 0) + return -PA_ERR_PROTOCOL; + } else + port->latency_offset = 0; + + port->type = PA_DEVICE_PORT_TYPE_UNKNOWN; + if (context->version >= 34) { + if (pa_tagstruct_gets(t, &port->availability_group) < 0 || + pa_tagstruct_getu32(t, &port->type) < 0) + return -PA_ERR_PROTOCOL; + } else + port->availability_group = NULL; + } + + return 0; +} + +static int fill_card_profile_info(pa_context *context, pa_tagstruct* t, pa_card_info* i) { + uint32_t j; + + i->profiles = pa_xnew0(pa_card_profile_info, i->n_profiles+1); + i->profiles2 = pa_xnew0(pa_card_profile_info2*, i->n_profiles+1); + + for (j = 0; j < i->n_profiles; j++) { + if (pa_tagstruct_gets(t, &i->profiles[j].name) < 0 || + pa_tagstruct_gets(t, &i->profiles[j].description) < 0 || + pa_tagstruct_getu32(t, &i->profiles[j].n_sinks) < 0 || + pa_tagstruct_getu32(t, &i->profiles[j].n_sources) < 0 || + pa_tagstruct_getu32(t, &i->profiles[j].priority) < 0) + return -PA_ERR_PROTOCOL; + + i->profiles2[j] = pa_xnew0(pa_card_profile_info2, 1); + i->profiles2[j]->name = i->profiles[j].name; + i->profiles2[j]->description = i->profiles[j].description; + i->profiles2[j]->n_sinks = i->profiles[j].n_sinks; + i->profiles2[j]->n_sources = i->profiles[j].n_sources; + i->profiles2[j]->priority = i->profiles[j].priority; + i->profiles2[j]->available = 1; + + if (context->version >= 29) { + uint32_t av; + + if (pa_tagstruct_getu32(t, &av) < 0) + return -PA_ERR_PROTOCOL; + + i->profiles2[j]->available = av; + } + } + + return 0; +} + +static void context_get_card_info_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_operation *o = userdata; + int eol = 1; + pa_card_info i; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (!o->context) + goto finish; + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(o->context, command, t, false) < 0) + goto finish; + + eol = -1; + } else { + + while (!pa_tagstruct_eof(t)) { + uint32_t j; + const char*ap; + + pa_zero(i); + + if (pa_tagstruct_getu32(t, &i.index) < 0 || + pa_tagstruct_gets(t, &i.name) < 0 || + pa_tagstruct_getu32(t, &i.owner_module) < 0 || + pa_tagstruct_gets(t, &i.driver) < 0 || + pa_tagstruct_getu32(t, &i.n_profiles) < 0) + goto fail; + + if (i.n_profiles > 0) { + if (fill_card_profile_info(o->context, t, &i) < 0) + goto fail; + } + + i.proplist = pa_proplist_new(); + + if (pa_tagstruct_gets(t, &ap) < 0 || + pa_tagstruct_get_proplist(t, i.proplist) < 0) { + + pa_context_fail(o->context, PA_ERR_PROTOCOL); + card_info_free(&i); + goto finish; + } + + if (ap) { + for (j = 0; j < i.n_profiles; j++) + if (pa_streq(i.profiles[j].name, ap)) { + i.active_profile = &i.profiles[j]; + i.active_profile2 = i.profiles2[j]; + break; + } + } + + if (o->context->version >= 26) { + if (fill_card_port_info(o->context, t, &i) < 0) + goto fail; + } + + if (o->callback) { + pa_card_info_cb_t cb = (pa_card_info_cb_t) o->callback; + cb(o->context, &i, 0, o->userdata); + } + + card_info_free(&i); + } + } + + if (o->callback) { + pa_card_info_cb_t cb = (pa_card_info_cb_t) o->callback; + cb(o->context, NULL, eol, o->userdata); + } + +finish: + pa_operation_done(o); + pa_operation_unref(o); + return; + +fail: + pa_context_fail(o->context, PA_ERR_PROTOCOL); + card_info_free(&i); + goto finish; +} + +pa_operation* pa_context_get_card_info_by_index(pa_context *c, uint32_t idx, pa_card_info_cb_t cb, void *userdata) { + pa_tagstruct *t; + pa_operation *o; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(cb); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 15, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_GET_CARD_INFO, &tag); + pa_tagstruct_putu32(t, idx); + pa_tagstruct_puts(t, NULL); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_get_card_info_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_get_card_info_by_name(pa_context *c, const char*name, pa_card_info_cb_t cb, void *userdata) { + pa_tagstruct *t; + pa_operation *o; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(cb); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, !name || *name, PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 15, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_GET_CARD_INFO, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, name); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_get_card_info_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_get_card_info_list(pa_context *c, pa_card_info_cb_t cb, void *userdata) { + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 15, PA_ERR_NOTSUPPORTED); + + return pa_context_send_simple_command(c, PA_COMMAND_GET_CARD_INFO_LIST, context_get_card_info_callback, (pa_operation_cb_t) cb, userdata); +} + +pa_operation* pa_context_set_card_profile_by_index(pa_context *c, uint32_t idx, const char*profile, pa_context_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 15, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_SET_CARD_PROFILE, &tag); + pa_tagstruct_putu32(t, idx); + pa_tagstruct_puts(t, NULL); + pa_tagstruct_puts(t, profile); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_set_card_profile_by_name(pa_context *c, const char *name, const char*profile, pa_context_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, !name || *name, PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 15, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_SET_CARD_PROFILE, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, name); + pa_tagstruct_puts(t, profile); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +/*** Module info ***/ + +static void context_get_module_info_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_operation *o = userdata; + int eol = 1; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (!o->context) + goto finish; + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(o->context, command, t, false) < 0) + goto finish; + + eol = -1; + } else { + + while (!pa_tagstruct_eof(t)) { + pa_module_info i; + bool auto_unload = false; + + pa_zero(i); + i.proplist = pa_proplist_new(); + + if (pa_tagstruct_getu32(t, &i.index) < 0 || + pa_tagstruct_gets(t, &i.name) < 0 || + pa_tagstruct_gets(t, &i.argument) < 0 || + pa_tagstruct_getu32(t, &i.n_used) < 0 || + (o->context->version < 15 && pa_tagstruct_get_boolean(t, &auto_unload) < 0) || + (o->context->version >= 15 && pa_tagstruct_get_proplist(t, i.proplist) < 0)) { + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + + i.auto_unload = (int) auto_unload; + + if (o->callback) { + pa_module_info_cb_t cb = (pa_module_info_cb_t) o->callback; + cb(o->context, &i, 0, o->userdata); + } + + pa_proplist_free(i.proplist); + } + } + + if (o->callback) { + pa_module_info_cb_t cb = (pa_module_info_cb_t) o->callback; + cb(o->context, NULL, eol, o->userdata); + } + +finish: + pa_operation_done(o); + pa_operation_unref(o); +} + +pa_operation* pa_context_get_module_info(pa_context *c, uint32_t idx, pa_module_info_cb_t cb, void *userdata) { + pa_tagstruct *t; + pa_operation *o; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(cb); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_GET_MODULE_INFO, &tag); + pa_tagstruct_putu32(t, idx); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_get_module_info_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_get_module_info_list(pa_context *c, pa_module_info_cb_t cb, void *userdata) { + return pa_context_send_simple_command(c, PA_COMMAND_GET_MODULE_INFO_LIST, context_get_module_info_callback, (pa_operation_cb_t) cb, userdata); +} + +/*** Sink input info ***/ + +static void context_get_sink_input_info_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_operation *o = userdata; + int eol = 1; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (!o->context) + goto finish; + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(o->context, command, t, false) < 0) + goto finish; + + eol = -1; + } else { + + while (!pa_tagstruct_eof(t)) { + pa_sink_input_info i; + bool mute = false, corked = false, has_volume = false, volume_writable = true; + + pa_zero(i); + i.proplist = pa_proplist_new(); + i.format = pa_format_info_new(); + + if (pa_tagstruct_getu32(t, &i.index) < 0 || + pa_tagstruct_gets(t, &i.name) < 0 || + pa_tagstruct_getu32(t, &i.owner_module) < 0 || + pa_tagstruct_getu32(t, &i.client) < 0 || + pa_tagstruct_getu32(t, &i.sink) < 0 || + pa_tagstruct_get_sample_spec(t, &i.sample_spec) < 0 || + pa_tagstruct_get_channel_map(t, &i.channel_map) < 0 || + pa_tagstruct_get_cvolume(t, &i.volume) < 0 || + pa_tagstruct_get_usec(t, &i.buffer_usec) < 0 || + pa_tagstruct_get_usec(t, &i.sink_usec) < 0 || + pa_tagstruct_gets(t, &i.resample_method) < 0 || + pa_tagstruct_gets(t, &i.driver) < 0 || + (o->context->version >= 11 && pa_tagstruct_get_boolean(t, &mute) < 0) || + (o->context->version >= 13 && pa_tagstruct_get_proplist(t, i.proplist) < 0) || + (o->context->version >= 19 && pa_tagstruct_get_boolean(t, &corked) < 0) || + (o->context->version >= 20 && (pa_tagstruct_get_boolean(t, &has_volume) < 0 || + pa_tagstruct_get_boolean(t, &volume_writable) < 0)) || + (o->context->version >= 21 && pa_tagstruct_get_format_info(t, i.format) < 0)) { + + pa_context_fail(o->context, PA_ERR_PROTOCOL); + pa_proplist_free(i.proplist); + pa_format_info_free(i.format); + goto finish; + } + + i.mute = (int) mute; + i.corked = (int) corked; + i.has_volume = (int) has_volume; + i.volume_writable = (int) volume_writable; + + if (o->callback) { + pa_sink_input_info_cb_t cb = (pa_sink_input_info_cb_t) o->callback; + cb(o->context, &i, 0, o->userdata); + } + + pa_proplist_free(i.proplist); + pa_format_info_free(i.format); + } + } + + if (o->callback) { + pa_sink_input_info_cb_t cb = (pa_sink_input_info_cb_t) o->callback; + cb(o->context, NULL, eol, o->userdata); + } + +finish: + pa_operation_done(o); + pa_operation_unref(o); +} + +pa_operation* pa_context_get_sink_input_info(pa_context *c, uint32_t idx, pa_sink_input_info_cb_t cb, void *userdata) { + pa_tagstruct *t; + pa_operation *o; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(cb); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_GET_SINK_INPUT_INFO, &tag); + pa_tagstruct_putu32(t, idx); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_get_sink_input_info_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_get_sink_input_info_list(pa_context *c, void (*cb)(pa_context *c, const pa_sink_input_info*i, int is_last, void *userdata), void *userdata) { + return pa_context_send_simple_command(c, PA_COMMAND_GET_SINK_INPUT_INFO_LIST, context_get_sink_input_info_callback, (pa_operation_cb_t) cb, userdata); +} + +/*** Source output info ***/ + +static void context_get_source_output_info_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_operation *o = userdata; + int eol = 1; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (!o->context) + goto finish; + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(o->context, command, t, false) < 0) + goto finish; + + eol = -1; + } else { + + while (!pa_tagstruct_eof(t)) { + pa_source_output_info i; + bool mute = false, corked = false, has_volume = false, volume_writable = true; + + pa_zero(i); + i.proplist = pa_proplist_new(); + i.format = pa_format_info_new(); + + if (pa_tagstruct_getu32(t, &i.index) < 0 || + pa_tagstruct_gets(t, &i.name) < 0 || + pa_tagstruct_getu32(t, &i.owner_module) < 0 || + pa_tagstruct_getu32(t, &i.client) < 0 || + pa_tagstruct_getu32(t, &i.source) < 0 || + pa_tagstruct_get_sample_spec(t, &i.sample_spec) < 0 || + pa_tagstruct_get_channel_map(t, &i.channel_map) < 0 || + pa_tagstruct_get_usec(t, &i.buffer_usec) < 0 || + pa_tagstruct_get_usec(t, &i.source_usec) < 0 || + pa_tagstruct_gets(t, &i.resample_method) < 0 || + pa_tagstruct_gets(t, &i.driver) < 0 || + (o->context->version >= 13 && pa_tagstruct_get_proplist(t, i.proplist) < 0) || + (o->context->version >= 19 && pa_tagstruct_get_boolean(t, &corked) < 0) || + (o->context->version >= 22 && (pa_tagstruct_get_cvolume(t, &i.volume) < 0 || + pa_tagstruct_get_boolean(t, &mute) < 0 || + pa_tagstruct_get_boolean(t, &has_volume) < 0 || + pa_tagstruct_get_boolean(t, &volume_writable) < 0 || + pa_tagstruct_get_format_info(t, i.format) < 0))) { + + pa_context_fail(o->context, PA_ERR_PROTOCOL); + pa_proplist_free(i.proplist); + pa_format_info_free(i.format); + goto finish; + } + + i.mute = (int) mute; + i.corked = (int) corked; + i.has_volume = (int) has_volume; + i.volume_writable = (int) volume_writable; + + if (o->callback) { + pa_source_output_info_cb_t cb = (pa_source_output_info_cb_t) o->callback; + cb(o->context, &i, 0, o->userdata); + } + + pa_proplist_free(i.proplist); + pa_format_info_free(i.format); + } + } + + if (o->callback) { + pa_source_output_info_cb_t cb = (pa_source_output_info_cb_t) o->callback; + cb(o->context, NULL, eol, o->userdata); + } + +finish: + pa_operation_done(o); + pa_operation_unref(o); +} + +pa_operation* pa_context_get_source_output_info(pa_context *c, uint32_t idx, pa_source_output_info_cb_t cb, void *userdata) { + pa_tagstruct *t; + pa_operation *o; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(cb); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_GET_SOURCE_OUTPUT_INFO, &tag); + pa_tagstruct_putu32(t, idx); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_get_source_output_info_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_get_source_output_info_list(pa_context *c, pa_source_output_info_cb_t cb, void *userdata) { + return pa_context_send_simple_command(c, PA_COMMAND_GET_SOURCE_OUTPUT_INFO_LIST, context_get_source_output_info_callback, (pa_operation_cb_t) cb, userdata); +} + +/*** Volume manipulation ***/ + +pa_operation* pa_context_set_sink_volume_by_index(pa_context *c, uint32_t idx, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(volume); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, pa_cvolume_valid(volume), PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_SET_SINK_VOLUME, &tag); + pa_tagstruct_putu32(t, idx); + pa_tagstruct_puts(t, NULL); + pa_tagstruct_put_cvolume(t, volume); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_set_sink_volume_by_name(pa_context *c, const char *name, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(name); + pa_assert(volume); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, pa_cvolume_valid(volume), PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, !name || *name, PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_SET_SINK_VOLUME, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, name); + pa_tagstruct_put_cvolume(t, volume); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_set_sink_mute_by_index(pa_context *c, uint32_t idx, int mute, pa_context_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_SET_SINK_MUTE, &tag); + pa_tagstruct_putu32(t, idx); + pa_tagstruct_puts(t, NULL); + pa_tagstruct_put_boolean(t, mute); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_set_sink_mute_by_name(pa_context *c, const char *name, int mute, pa_context_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(name); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, !name || *name, PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_SET_SINK_MUTE, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, name); + pa_tagstruct_put_boolean(t, mute); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_set_sink_input_volume(pa_context *c, uint32_t idx, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(volume); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, pa_cvolume_valid(volume), PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_SET_SINK_INPUT_VOLUME, &tag); + pa_tagstruct_putu32(t, idx); + pa_tagstruct_put_cvolume(t, volume); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_set_sink_input_mute(pa_context *c, uint32_t idx, int mute, pa_context_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 11, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_SET_SINK_INPUT_MUTE, &tag); + pa_tagstruct_putu32(t, idx); + pa_tagstruct_put_boolean(t, mute); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_set_source_volume_by_index(pa_context *c, uint32_t idx, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(volume); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, pa_cvolume_valid(volume), PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_SET_SOURCE_VOLUME, &tag); + pa_tagstruct_putu32(t, idx); + pa_tagstruct_puts(t, NULL); + pa_tagstruct_put_cvolume(t, volume); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_set_source_volume_by_name(pa_context *c, const char *name, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(name); + pa_assert(volume); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, pa_cvolume_valid(volume), PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, !name || *name, PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_SET_SOURCE_VOLUME, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, name); + pa_tagstruct_put_cvolume(t, volume); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_set_source_mute_by_index(pa_context *c, uint32_t idx, int mute, pa_context_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_SET_SOURCE_MUTE, &tag); + pa_tagstruct_putu32(t, idx); + pa_tagstruct_puts(t, NULL); + pa_tagstruct_put_boolean(t, mute); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_set_source_mute_by_name(pa_context *c, const char *name, int mute, pa_context_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(name); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, !name || *name, PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_SET_SOURCE_MUTE, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, name); + pa_tagstruct_put_boolean(t, mute); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_set_source_output_volume(pa_context *c, uint32_t idx, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(volume); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 22, PA_ERR_NOTSUPPORTED); + PA_CHECK_VALIDITY_RETURN_NULL(c, pa_cvolume_valid(volume), PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_SET_SOURCE_OUTPUT_VOLUME, &tag); + pa_tagstruct_putu32(t, idx); + pa_tagstruct_put_cvolume(t, volume); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_set_source_output_mute(pa_context *c, uint32_t idx, int mute, pa_context_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 22, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_SET_SOURCE_OUTPUT_MUTE, &tag); + pa_tagstruct_putu32(t, idx); + pa_tagstruct_put_boolean(t, mute); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +/** Sample Cache **/ + +static void context_get_sample_info_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_operation *o = userdata; + int eol = 1; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (!o->context) + goto finish; + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(o->context, command, t, false) < 0) + goto finish; + + eol = -1; + } else { + + while (!pa_tagstruct_eof(t)) { + pa_sample_info i; + bool lazy = false; + + pa_zero(i); + i.proplist = pa_proplist_new(); + + if (pa_tagstruct_getu32(t, &i.index) < 0 || + pa_tagstruct_gets(t, &i.name) < 0 || + pa_tagstruct_get_cvolume(t, &i.volume) < 0 || + pa_tagstruct_get_usec(t, &i.duration) < 0 || + pa_tagstruct_get_sample_spec(t, &i.sample_spec) < 0 || + pa_tagstruct_get_channel_map(t, &i.channel_map) < 0 || + pa_tagstruct_getu32(t, &i.bytes) < 0 || + pa_tagstruct_get_boolean(t, &lazy) < 0 || + pa_tagstruct_gets(t, &i.filename) < 0 || + (o->context->version >= 13 && pa_tagstruct_get_proplist(t, i.proplist) < 0)) { + + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + + i.lazy = (int) lazy; + + if (o->callback) { + pa_sample_info_cb_t cb = (pa_sample_info_cb_t) o->callback; + cb(o->context, &i, 0, o->userdata); + } + + pa_proplist_free(i.proplist); + } + } + + if (o->callback) { + pa_sample_info_cb_t cb = (pa_sample_info_cb_t) o->callback; + cb(o->context, NULL, eol, o->userdata); + } + +finish: + pa_operation_done(o); + pa_operation_unref(o); +} + +pa_operation* pa_context_get_sample_info_by_name(pa_context *c, const char *name, pa_sample_info_cb_t cb, void *userdata) { + pa_tagstruct *t; + pa_operation *o; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(cb); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, name && *name, PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_GET_SAMPLE_INFO, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, name); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_get_sample_info_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_get_sample_info_by_index(pa_context *c, uint32_t idx, pa_sample_info_cb_t cb, void *userdata) { + pa_tagstruct *t; + pa_operation *o; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(cb); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_GET_SAMPLE_INFO, &tag); + pa_tagstruct_putu32(t, idx); + pa_tagstruct_puts(t, NULL); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_get_sample_info_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_get_sample_info_list(pa_context *c, pa_sample_info_cb_t cb, void *userdata) { + return pa_context_send_simple_command(c, PA_COMMAND_GET_SAMPLE_INFO_LIST, context_get_sample_info_callback, (pa_operation_cb_t) cb, userdata); +} + +static pa_operation* command_kill(pa_context *c, uint32_t command, uint32_t idx, pa_context_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, command, &tag); + pa_tagstruct_putu32(t, idx); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_kill_client(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void *userdata) { + return command_kill(c, PA_COMMAND_KILL_CLIENT, idx, cb, userdata); +} + +pa_operation* pa_context_kill_sink_input(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void *userdata) { + return command_kill(c, PA_COMMAND_KILL_SINK_INPUT, idx, cb, userdata); +} + +pa_operation* pa_context_kill_source_output(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void *userdata) { + return command_kill(c, PA_COMMAND_KILL_SOURCE_OUTPUT, idx, cb, userdata); +} + +static void context_index_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_operation *o = userdata; + uint32_t idx; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (!o->context) + goto finish; + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(o->context, command, t, false) < 0) + goto finish; + + idx = PA_INVALID_INDEX; + } else if (pa_tagstruct_getu32(t, &idx) || + !pa_tagstruct_eof(t)) { + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + + if (o->callback) { + pa_context_index_cb_t cb = (pa_context_index_cb_t) o->callback; + cb(o->context, idx, o->userdata); + } + +finish: + pa_operation_done(o); + pa_operation_unref(o); +} + +pa_operation* pa_context_load_module(pa_context *c, const char*name, const char *argument, pa_context_index_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, name && *name, PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_LOAD_MODULE, &tag); + pa_tagstruct_puts(t, name); + pa_tagstruct_puts(t, argument); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_index_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_unload_module(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void *userdata) { + return command_kill(c, PA_COMMAND_UNLOAD_MODULE, idx, cb, userdata); +} + +pa_operation* pa_context_set_port_latency_offset(pa_context *c, const char *card_name, const char *port_name, int64_t offset, pa_context_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, card_name && *card_name, PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, port_name && *port_name, PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 27, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_SET_PORT_LATENCY_OFFSET, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, card_name); + pa_tagstruct_puts(t, port_name); + pa_tagstruct_puts64(t, offset); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +/*** Autoload stuff ***/ + +PA_WARN_REFERENCE(pa_context_get_autoload_info_by_name, "Module auto-loading no longer supported."); + +pa_operation* pa_context_get_autoload_info_by_name(pa_context *c, const char *name, pa_autoload_type_t type, pa_autoload_info_cb_t cb, void *userdata) { + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_FAIL_RETURN_NULL(c, PA_ERR_OBSOLETE); +} + +PA_WARN_REFERENCE(pa_context_get_autoload_info_by_index, "Module auto-loading no longer supported."); + +pa_operation* pa_context_get_autoload_info_by_index(pa_context *c, uint32_t idx, pa_autoload_info_cb_t cb, void *userdata) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_FAIL_RETURN_NULL(c, PA_ERR_OBSOLETE); +} + +PA_WARN_REFERENCE(pa_context_get_autoload_info_list, "Module auto-loading no longer supported."); + +pa_operation* pa_context_get_autoload_info_list(pa_context *c, pa_autoload_info_cb_t cb, void *userdata) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_FAIL_RETURN_NULL(c, PA_ERR_OBSOLETE); +} + +PA_WARN_REFERENCE(pa_context_add_autoload, "Module auto-loading no longer supported."); + +pa_operation* pa_context_add_autoload(pa_context *c, const char *name, pa_autoload_type_t type, const char *module, const char*argument, pa_context_index_cb_t cb, void* userdata) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_FAIL_RETURN_NULL(c, PA_ERR_OBSOLETE); +} + +PA_WARN_REFERENCE(pa_context_remove_autoload_by_name, "Module auto-loading no longer supported."); + +pa_operation* pa_context_remove_autoload_by_name(pa_context *c, const char *name, pa_autoload_type_t type, pa_context_success_cb_t cb, void* userdata) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_FAIL_RETURN_NULL(c, PA_ERR_OBSOLETE); +} + +PA_WARN_REFERENCE(pa_context_remove_autoload_by_index, "Module auto-loading no longer supported."); + +pa_operation* pa_context_remove_autoload_by_index(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void* userdata) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_FAIL_RETURN_NULL(c, PA_ERR_OBSOLETE); +} + +pa_operation* pa_context_move_sink_input_by_name(pa_context *c, uint32_t idx, const char *sink_name, pa_context_success_cb_t cb, void* userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 10, PA_ERR_NOTSUPPORTED); + PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, sink_name && *sink_name, PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_MOVE_SINK_INPUT, &tag); + pa_tagstruct_putu32(t, idx); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, sink_name); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_move_sink_input_by_index(pa_context *c, uint32_t idx, uint32_t sink_idx, pa_context_success_cb_t cb, void* userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 10, PA_ERR_NOTSUPPORTED); + PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, sink_idx != PA_INVALID_INDEX, PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_MOVE_SINK_INPUT, &tag); + pa_tagstruct_putu32(t, idx); + pa_tagstruct_putu32(t, sink_idx); + pa_tagstruct_puts(t, NULL); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_move_source_output_by_name(pa_context *c, uint32_t idx, const char *source_name, pa_context_success_cb_t cb, void* userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 10, PA_ERR_NOTSUPPORTED); + PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, source_name && *source_name, PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_MOVE_SOURCE_OUTPUT, &tag); + pa_tagstruct_putu32(t, idx); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, source_name); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_move_source_output_by_index(pa_context *c, uint32_t idx, uint32_t source_idx, pa_context_success_cb_t cb, void* userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 10, PA_ERR_NOTSUPPORTED); + PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, source_idx != PA_INVALID_INDEX, PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_MOVE_SOURCE_OUTPUT, &tag); + pa_tagstruct_putu32(t, idx); + pa_tagstruct_putu32(t, source_idx); + pa_tagstruct_puts(t, NULL); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_suspend_sink_by_name(pa_context *c, const char *sink_name, int suspend, pa_context_success_cb_t cb, void* userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 11, PA_ERR_NOTSUPPORTED); + PA_CHECK_VALIDITY_RETURN_NULL(c, !sink_name || *sink_name, PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_SUSPEND_SINK, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, sink_name); + pa_tagstruct_put_boolean(t, suspend); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_suspend_sink_by_index(pa_context *c, uint32_t idx, int suspend, pa_context_success_cb_t cb, void* userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 11, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_SUSPEND_SINK, &tag); + pa_tagstruct_putu32(t, idx); + pa_tagstruct_puts(t, idx == PA_INVALID_INDEX ? "" : NULL); + pa_tagstruct_put_boolean(t, suspend); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_suspend_source_by_name(pa_context *c, const char *source_name, int suspend, pa_context_success_cb_t cb, void* userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 11, PA_ERR_NOTSUPPORTED); + PA_CHECK_VALIDITY_RETURN_NULL(c, !source_name || *source_name, PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_SUSPEND_SOURCE, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, source_name); + pa_tagstruct_put_boolean(t, suspend); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_suspend_source_by_index(pa_context *c, uint32_t idx, int suspend, pa_context_success_cb_t cb, void* userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 11, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_SUSPEND_SOURCE, &tag); + pa_tagstruct_putu32(t, idx); + pa_tagstruct_puts(t, idx == PA_INVALID_INDEX ? "" : NULL); + pa_tagstruct_put_boolean(t, suspend); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} diff --git a/src/pulse/introspect.h b/src/pulse/introspect.h new file mode 100644 index 0000000..c547bde --- /dev/null +++ b/src/pulse/introspect.h @@ -0,0 +1,829 @@ +#ifndef foointrospecthfoo +#define foointrospecthfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <inttypes.h> + +#include <pulse/operation.h> +#include <pulse/context.h> +#include <pulse/cdecl.h> +#include <pulse/gccmacro.h> +#include <pulse/channelmap.h> +#include <pulse/volume.h> +#include <pulse/proplist.h> +#include <pulse/format.h> +#include <pulse/version.h> + +/** \page introspect Server Query and Control + * + * \section overv_sec Overview + * + * Sometimes it is necessary to query and modify global settings in the + * server. For this, PulseAudio has the introspection API. It can list sinks, + * sources, samples and other aspects of the server. It can also modify the + * attributes of the server that will affect operations on a global level, + * and not just the application's context. + * + * \section query_sec Querying + * + * All querying is done through callbacks. This approach is necessary to + * maintain an asynchronous design. The client will request the information + * and some time later, the server will respond with the desired data. + * + * Some objects can have multiple instances on the server. When requesting all + * of these at once, the callback will be called multiple times, once for + * each object. When the list has been exhausted, the callback will be called + * without an information structure and the eol parameter set to a positive + * value. + * + * Note that even if a single object is requested, and not the entire list, + * the terminating call will still be made. + * + * If an error occurs, the callback will be invoked without an information + * structure and eol set to a negative value.. + * + * Data members in the information structures are only valid during the + * duration of the callback. If they are required after the callback is + * finished, a deep copy of the information structure must be performed. + * + * \subsection server_subsec Server Information + * + * The server can be queried about its name, the environment it's running on + * and the currently active global defaults. Calling + * pa_context_get_server_info() provides access to a pa_server_info structure + * containing all of these. + * + * \subsection memstat_subsec Memory Usage + * + * Statistics about memory usage can be fetched using pa_context_stat(), + * giving a pa_stat_info structure. + * + * \subsection sinksrc_subsec Sinks and Sources + * + * The server can have an arbitrary number of sinks and sources. Each sink + * and source have both an index and a name associated with it. As such, + * there are three ways to get access to them: + * + * \li By index - pa_context_get_sink_info_by_index() / + * pa_context_get_source_info_by_index() + * \li By name - pa_context_get_sink_info_by_name() / + * pa_context_get_source_info_by_name() + * \li All - pa_context_get_sink_info_list() / + * pa_context_get_source_info_list() + * + * All three method use the same callback and will provide a pa_sink_info or + * pa_source_info structure. + * + * \subsection siso_subsec Sink Inputs and Source Outputs + * + * Sink inputs and source outputs are the representations of the client ends + * of streams inside the server. I.e. they connect a client stream to one of + * the global sinks or sources. + * + * Sink inputs and source outputs only have an index to identify them. As + * such, there are only two ways to get information about them: + * + * \li By index - pa_context_get_sink_input_info() / + * pa_context_get_source_output_info() + * \li All - pa_context_get_sink_input_info_list() / + * pa_context_get_source_output_info_list() + * + * The structure returned is the pa_sink_input_info or pa_source_output_info + * structure. + * + * \subsection samples_subsec Samples + * + * The list of cached samples can be retrieved from the server. Three methods + * exist for querying the sample cache list: + * + * \li By index - pa_context_get_sample_info_by_index() + * \li By name - pa_context_get_sample_info_by_name() + * \li All - pa_context_get_sample_info_list() + * + * Note that this only retrieves information about the sample, not the sample + * data itself. + * + * \subsection module_subsec Driver Modules + * + * PulseAudio driver modules are identified by index and are retrieved using either + * pa_context_get_module_info() or pa_context_get_module_info_list(). The + * information structure is called pa_module_info. + * + * \subsection client_subsec Clients + * + * PulseAudio clients are also identified by index and are retrieved using + * either pa_context_get_client_info() or pa_context_get_client_info_list(). + * The information structure is called pa_client_info. + * + * \section ctrl_sec Control + * + * Some parts of the server are only possible to read, but most can also be + * modified in different ways. Note that these changes will affect all + * connected clients and not just the one issuing the request. + * + * \subsection sinksrc_subsec Sinks and Sources + * + * The most common change one would want to apply to sinks and sources is to + * modify the volume of the audio. Identically to how sinks and sources can + * be queried, there are two ways of identifying them: + * + * \li By index - pa_context_set_sink_volume_by_index() / + * pa_context_set_source_volume_by_index() + * \li By name - pa_context_set_sink_volume_by_name() / + * pa_context_set_source_volume_by_name() + * + * It is also possible to mute a sink or source: + * + * \li By index - pa_context_set_sink_mute_by_index() / + * pa_context_set_source_mute_by_index() + * \li By name - pa_context_set_sink_mute_by_name() / + * pa_context_set_source_mute_by_name() + * + * \subsection siso_subsec Sink Inputs and Source Outputs + * + * If an application desires to modify the volume of just a single stream + * (commonly one of its own streams), this can be done by setting the volume + * of its associated sink input or source output, using + * pa_context_set_sink_input_volume() or pa_context_set_source_output_volume(). + * + * It is also possible to remove sink inputs and source outputs, terminating + * the streams associated with them: + * + * \li Sink input - pa_context_kill_sink_input() + * \li Source output - pa_context_kill_source_output() + * + * It is strongly recommended that all volume changes are done as a direct + * result of user input. With automated requests, such as those resulting + * from misguided attempts of crossfading, PulseAudio can store the stream + * volume at an inappropriate moment and restore it later. Besides, such + * attempts lead to OSD popups in some desktop environments. + * + * As a special case of the general rule above, it is recommended that your + * application leaves the task of saving and restoring the volume of its + * streams to PulseAudio and does not attempt to do it by itself. PulseAudio + * really knows better about events such as stream moving or headphone + * plugging that would make the volume stored by the application inapplicable + * to the new configuration. + * + * Another important case where setting a sink input volume may be a bad idea + * is related to interpreters that interpret potentially untrusted scripts. + * PulseAudio relies on your application not making malicious requests (such + * as repeatedly setting the volume to 100%). Thus, script interpreters that + * represent a security boundary must sandbox volume-changing requests coming + * from their scripts. In the worst case, it may be necessary to apply the + * script-requested volume to the script-produced sounds by altering the + * samples in the script interpreter and not touching the sink or sink input + * volume as seen by PulseAudio. + * + * If an application changes any volume, it should also listen to changes of + * the same volume originating from outside the application (e.g., from the + * system mixer application) and update its user interface accordingly. Use + * \ref subscribe to get such notifications. + * + * \subsection module_subsec Modules + * + * Server modules can be remotely loaded and unloaded using + * pa_context_load_module() and pa_context_unload_module(). + * + * \subsection client_subsec Clients + * + * The only operation supported on clients is the possibility of kicking + * them off the server using pa_context_kill_client(). + */ + +/** \file + * + * Routines for daemon introspection. + * + * See also \subpage introspect + */ + +PA_C_DECL_BEGIN + +/** @{ \name Sinks */ + +/** Stores information about a specific port of a sink. Please + * note that this structure can be extended as part of evolutionary + * API updates at any time in any new release. \since 0.9.16 */ +typedef struct pa_sink_port_info { + const char *name; /**< Name of this port */ + const char *description; /**< Description of this port */ + uint32_t priority; /**< The higher this value is, the more useful this port is as a default. */ + int available; /**< A flags (see #pa_port_available), indicating availability status of this port. \since 2.0 */ + const char *availability_group; /**< An indentifier for the group of ports that share their availability status with + * each other. This is meant especially for handling cases where one 3.5 mm connector + * is used for headphones, headsets and microphones, and the hardware can only tell + * that something was plugged in but not what exactly. In this situation the ports for + * all those devices share their availability status, and PulseAudio can't tell which + * one is actually plugged in, and some application may ask the user what was plugged + * in. Such applications should get a list of all card ports and compare their + * `availability_group` fields. Ports that have the same group are those that need + * input from the user to determine which device was plugged in. The application should + * then activate the user-chosen port. + * + * May be NULL, in which case the port is not part of any availability group. + * + * The group identifier must be treated as an opaque identifier. The string may look + * like an ALSA control name, but applications must not assume any such relationship. + * The group naming scheme can change without a warning. + * + * Since one group can include both input and output ports, the grouping should be done + * using pa_card_port_info instead of pa_sink_port_info, but this field is duplicated + * also in pa_sink_port_info (and pa_source_port_info) in case someone finds that + * convenient. + * + * \since 14.0 */ + uint32_t type; /**< Port type, see #pa_device_port_type. \since 14.0 */ +} pa_sink_port_info; + +/** Stores information about sinks. Please note that this structure + * can be extended as part of evolutionary API updates at any time in + * any new release. */ +typedef struct pa_sink_info { + const char *name; /**< Name of the sink */ + uint32_t index; /**< Index of the sink */ + const char *description; /**< Description of this sink */ + pa_sample_spec sample_spec; /**< Sample spec of this sink */ + pa_channel_map channel_map; /**< Channel map */ + uint32_t owner_module; /**< Index of the owning module of this sink, or PA_INVALID_INDEX. */ + pa_cvolume volume; /**< Volume of the sink */ + int mute; /**< Mute switch of the sink */ + uint32_t monitor_source; /**< Index of the monitor source connected to this sink. */ + const char *monitor_source_name; /**< The name of the monitor source. */ + pa_usec_t latency; /**< Length of queued audio in the output buffer. */ + const char *driver; /**< Driver name */ + pa_sink_flags_t flags; /**< Flags */ + pa_proplist *proplist; /**< Property list \since 0.9.11 */ + pa_usec_t configured_latency; /**< The latency this device has been configured to. \since 0.9.11 */ + pa_volume_t base_volume; /**< Some kind of "base" volume that refers to unamplified/unattenuated volume in the context of the output device. \since 0.9.15 */ + pa_sink_state_t state; /**< State \since 0.9.15 */ + uint32_t n_volume_steps; /**< Number of volume steps for sinks which do not support arbitrary volumes. \since 0.9.15 */ + uint32_t card; /**< Card index, or PA_INVALID_INDEX. \since 0.9.15 */ + uint32_t n_ports; /**< Number of entries in port array \since 0.9.16 */ + pa_sink_port_info** ports; /**< Array of available ports, or NULL. Array is terminated by an entry set to NULL. The number of entries is stored in n_ports. \since 0.9.16 */ + pa_sink_port_info* active_port; /**< Pointer to active port in the array, or NULL. \since 0.9.16 */ + uint8_t n_formats; /**< Number of formats supported by the sink. \since 1.0 */ + pa_format_info **formats; /**< Array of formats supported by the sink. \since 1.0 */ +} pa_sink_info; + +/** Callback prototype for pa_context_get_sink_info_by_name() and friends */ +typedef void (*pa_sink_info_cb_t)(pa_context *c, const pa_sink_info *i, int eol, void *userdata); + +/** Get information about a sink by its name */ +pa_operation* pa_context_get_sink_info_by_name(pa_context *c, const char *name, pa_sink_info_cb_t cb, void *userdata); + +/** Get information about a sink by its index */ +pa_operation* pa_context_get_sink_info_by_index(pa_context *c, uint32_t idx, pa_sink_info_cb_t cb, void *userdata); + +/** Get the complete sink list */ +pa_operation* pa_context_get_sink_info_list(pa_context *c, pa_sink_info_cb_t cb, void *userdata); + +/** Set the volume of a sink device specified by its index */ +pa_operation* pa_context_set_sink_volume_by_index(pa_context *c, uint32_t idx, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata); + +/** Set the volume of a sink device specified by its name */ +pa_operation* pa_context_set_sink_volume_by_name(pa_context *c, const char *name, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata); + +/** Set the mute switch of a sink device specified by its index */ +pa_operation* pa_context_set_sink_mute_by_index(pa_context *c, uint32_t idx, int mute, pa_context_success_cb_t cb, void *userdata); + +/** Set the mute switch of a sink device specified by its name */ +pa_operation* pa_context_set_sink_mute_by_name(pa_context *c, const char *name, int mute, pa_context_success_cb_t cb, void *userdata); + +/** Suspend/Resume a sink. \since 0.9.7 */ +pa_operation* pa_context_suspend_sink_by_name(pa_context *c, const char *sink_name, int suspend, pa_context_success_cb_t cb, void* userdata); + +/** Suspend/Resume a sink. If idx is PA_INVALID_INDEX all sinks will be suspended. \since 0.9.7 */ +pa_operation* pa_context_suspend_sink_by_index(pa_context *c, uint32_t idx, int suspend, pa_context_success_cb_t cb, void* userdata); + +/** Change the profile of a sink. \since 0.9.16 */ +pa_operation* pa_context_set_sink_port_by_index(pa_context *c, uint32_t idx, const char*port, pa_context_success_cb_t cb, void *userdata); + +/** Change the profile of a sink. \since 0.9.15 */ +pa_operation* pa_context_set_sink_port_by_name(pa_context *c, const char*name, const char*port, pa_context_success_cb_t cb, void *userdata); + +/** @} */ + +/** @{ \name Sources */ + +/** Stores information about a specific port of a source. Please + * note that this structure can be extended as part of evolutionary + * API updates at any time in any new release. \since 0.9.16 */ +typedef struct pa_source_port_info { + const char *name; /**< Name of this port */ + const char *description; /**< Description of this port */ + uint32_t priority; /**< The higher this value is, the more useful this port is as a default. */ + int available; /**< A flags (see #pa_port_available), indicating availability status of this port. \since 2.0 */ + const char *availability_group; /**< An indentifier for the group of ports that share their availability status with + * each other. This is meant especially for handling cases where one 3.5 mm connector + * is used for headphones, headsets and microphones, and the hardware can only tell + * that something was plugged in but not what exactly. In this situation the ports for + * all those devices share their availability status, and PulseAudio can't tell which + * one is actually plugged in, and some application may ask the user what was plugged + * in. Such applications should get a list of all card ports and compare their + * `availability_group` fields. Ports that have the same group are those that need + * input from the user to determine which device was plugged in. The application should + * then activate the user-chosen port. + * + * May be NULL, in which case the port is not part of any availability group (which is + * the same as having a group with only one member). + * + * The group identifier must be treated as an opaque identifier. The string may look + * like an ALSA control name, but applications must not assume any such relationship. + * The group naming scheme can change without a warning. + * + * Since one group can include both input and output ports, the grouping should be done + * using pa_card_port_info instead of pa_source_port_info, but this field is duplicated + * also in pa_source_port_info (and pa_sink_port_info) in case someone finds that + * convenient. + * + * \since 14.0 */ + uint32_t type; /**< Port type, see #pa_device_port_type. \since 14.0 */ +} pa_source_port_info; + +/** Stores information about sources. Please note that this structure + * can be extended as part of evolutionary API updates at any time in + * any new release. */ +typedef struct pa_source_info { + const char *name; /**< Name of the source */ + uint32_t index; /**< Index of the source */ + const char *description; /**< Description of this source */ + pa_sample_spec sample_spec; /**< Sample spec of this source */ + pa_channel_map channel_map; /**< Channel map */ + uint32_t owner_module; /**< Owning module index, or PA_INVALID_INDEX. */ + pa_cvolume volume; /**< Volume of the source */ + int mute; /**< Mute switch of the sink */ + uint32_t monitor_of_sink; /**< If this is a monitor source, the index of the owning sink, otherwise PA_INVALID_INDEX. */ + const char *monitor_of_sink_name; /**< Name of the owning sink, or NULL. */ + pa_usec_t latency; /**< Length of filled record buffer of this source. */ + const char *driver; /**< Driver name */ + pa_source_flags_t flags; /**< Flags */ + pa_proplist *proplist; /**< Property list \since 0.9.11 */ + pa_usec_t configured_latency; /**< The latency this device has been configured to. \since 0.9.11 */ + pa_volume_t base_volume; /**< Some kind of "base" volume that refers to unamplified/unattenuated volume in the context of the input device. \since 0.9.15 */ + pa_source_state_t state; /**< State \since 0.9.15 */ + uint32_t n_volume_steps; /**< Number of volume steps for sources which do not support arbitrary volumes. \since 0.9.15 */ + uint32_t card; /**< Card index, or PA_INVALID_INDEX. \since 0.9.15 */ + uint32_t n_ports; /**< Number of entries in port array \since 0.9.16 */ + pa_source_port_info** ports; /**< Array of available ports, or NULL. Array is terminated by an entry set to NULL. The number of entries is stored in n_ports. \since 0.9.16 */ + pa_source_port_info* active_port; /**< Pointer to active port in the array, or NULL. \since 0.9.16 */ + uint8_t n_formats; /**< Number of formats supported by the source. \since 1.0 */ + pa_format_info **formats; /**< Array of formats supported by the source. \since 1.0 */ +} pa_source_info; + +/** Callback prototype for pa_context_get_source_info_by_name() and friends */ +typedef void (*pa_source_info_cb_t)(pa_context *c, const pa_source_info *i, int eol, void *userdata); + +/** Get information about a source by its name */ +pa_operation* pa_context_get_source_info_by_name(pa_context *c, const char *name, pa_source_info_cb_t cb, void *userdata); + +/** Get information about a source by its index */ +pa_operation* pa_context_get_source_info_by_index(pa_context *c, uint32_t idx, pa_source_info_cb_t cb, void *userdata); + +/** Get the complete source list */ +pa_operation* pa_context_get_source_info_list(pa_context *c, pa_source_info_cb_t cb, void *userdata); + +/** Set the volume of a source device specified by its index */ +pa_operation* pa_context_set_source_volume_by_index(pa_context *c, uint32_t idx, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata); + +/** Set the volume of a source device specified by its name */ +pa_operation* pa_context_set_source_volume_by_name(pa_context *c, const char *name, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata); + +/** Set the mute switch of a source device specified by its index */ +pa_operation* pa_context_set_source_mute_by_index(pa_context *c, uint32_t idx, int mute, pa_context_success_cb_t cb, void *userdata); + +/** Set the mute switch of a source device specified by its name */ +pa_operation* pa_context_set_source_mute_by_name(pa_context *c, const char *name, int mute, pa_context_success_cb_t cb, void *userdata); + +/** Suspend/Resume a source. \since 0.9.7 */ +pa_operation* pa_context_suspend_source_by_name(pa_context *c, const char *source_name, int suspend, pa_context_success_cb_t cb, void* userdata); + +/** Suspend/Resume a source. If idx is PA_INVALID_INDEX, all sources will be suspended. \since 0.9.7 */ +pa_operation* pa_context_suspend_source_by_index(pa_context *c, uint32_t idx, int suspend, pa_context_success_cb_t cb, void* userdata); + +/** Change the profile of a source. \since 0.9.16 */ +pa_operation* pa_context_set_source_port_by_index(pa_context *c, uint32_t idx, const char*port, pa_context_success_cb_t cb, void *userdata); + +/** Change the profile of a source. \since 0.9.15 */ +pa_operation* pa_context_set_source_port_by_name(pa_context *c, const char*name, const char*port, pa_context_success_cb_t cb, void *userdata); + +/** @} */ + +/** @{ \name Server */ + +/** Server information. Please note that this structure can be + * extended as part of evolutionary API updates at any time in any new + * release. */ +typedef struct pa_server_info { + const char *user_name; /**< User name of the daemon process */ + const char *host_name; /**< Host name the daemon is running on */ + const char *server_version; /**< Version string of the daemon */ + const char *server_name; /**< Server package name (usually "pulseaudio") */ + pa_sample_spec sample_spec; /**< Default sample specification */ + const char *default_sink_name; /**< Name of default sink. */ + const char *default_source_name; /**< Name of default source. */ + uint32_t cookie; /**< A random cookie for identifying this instance of PulseAudio. */ + pa_channel_map channel_map; /**< Default channel map. \since 0.9.15 */ +} pa_server_info; + +/** Callback prototype for pa_context_get_server_info() */ +typedef void (*pa_server_info_cb_t) (pa_context *c, const pa_server_info*i, void *userdata); + +/** Get some information about the server */ +pa_operation* pa_context_get_server_info(pa_context *c, pa_server_info_cb_t cb, void *userdata); + +/** @} */ + +/** @{ \name Modules */ + +/** Stores information about modules. Please note that this structure + * can be extended as part of evolutionary API updates at any time in + * any new release. */ +typedef struct pa_module_info { + uint32_t index; /**< Index of the module */ + const char*name, /**< Name of the module */ + *argument; /**< Argument string of the module */ + uint32_t n_used; /**< Usage counter or PA_INVALID_INDEX */ +/** \cond fulldocs */ + int auto_unload; /**< \deprecated Non-zero if this is an autoloaded module. */ +/** \endcond */ + pa_proplist *proplist; /**< Property list \since 0.9.15 */ +} pa_module_info; + +/** Callback prototype for pa_context_get_module_info() and friends */ +typedef void (*pa_module_info_cb_t) (pa_context *c, const pa_module_info*i, int eol, void *userdata); + +/** Get some information about a module by its index */ +pa_operation* pa_context_get_module_info(pa_context *c, uint32_t idx, pa_module_info_cb_t cb, void *userdata); + +/** Get the complete list of currently loaded modules */ +pa_operation* pa_context_get_module_info_list(pa_context *c, pa_module_info_cb_t cb, void *userdata); + +/** Callback prototype for pa_context_load_module() */ +typedef void (*pa_context_index_cb_t)(pa_context *c, uint32_t idx, void *userdata); + +/** Load a module. */ +pa_operation* pa_context_load_module(pa_context *c, const char*name, const char *argument, pa_context_index_cb_t cb, void *userdata); + +/** Unload a module. */ +pa_operation* pa_context_unload_module(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void *userdata); + +/** @} */ + +/** @{ \name Clients */ + +/** Stores information about clients. Please note that this structure + * can be extended as part of evolutionary API updates at any time in + * any new release. */ +typedef struct pa_client_info { + uint32_t index; /**< Index of this client */ + const char *name; /**< Name of this client */ + uint32_t owner_module; /**< Index of the owning module, or PA_INVALID_INDEX. */ + const char *driver; /**< Driver name */ + pa_proplist *proplist; /**< Property list \since 0.9.11 */ +} pa_client_info; + +/** Callback prototype for pa_context_get_client_info() and friends */ +typedef void (*pa_client_info_cb_t) (pa_context *c, const pa_client_info*i, int eol, void *userdata); + +/** Get information about a client by its index */ +pa_operation* pa_context_get_client_info(pa_context *c, uint32_t idx, pa_client_info_cb_t cb, void *userdata); + +/** Get the complete client list */ +pa_operation* pa_context_get_client_info_list(pa_context *c, pa_client_info_cb_t cb, void *userdata); + +/** Kill a client. */ +pa_operation* pa_context_kill_client(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void *userdata); + +/** @} */ + +/** @{ \name Cards */ + +/** \deprecated Superseded by pa_card_profile_info2 \since 0.9.15 */ +typedef struct pa_card_profile_info { + const char *name; /**< Name of this profile */ + const char *description; /**< Description of this profile */ + uint32_t n_sinks; /**< Number of sinks this profile would create */ + uint32_t n_sources; /**< Number of sources this profile would create */ + uint32_t priority; /**< The higher this value is, the more useful this profile is as a default. */ +} pa_card_profile_info; + +/** Stores information about a specific profile of a card. Please + * note that this structure can be extended as part of evolutionary + * API updates at any time in any new release. \since 5.0 */ +typedef struct pa_card_profile_info2 { + const char *name; /**< Name of this profile */ + const char *description; /**< Description of this profile */ + uint32_t n_sinks; /**< Number of sinks this profile would create */ + uint32_t n_sources; /**< Number of sources this profile would create */ + uint32_t priority; /**< The higher this value is, the more useful this profile is as a default. */ + int available; + /**< Is this profile available? If this is zero, meaning "unavailable", + * then it makes no sense to try to activate this profile. If this is + * non-zero, it's still not a guarantee that activating the profile will + * result in anything useful, it just means that the server isn't aware of + * any reason why the profile would definitely be useless. \since 5.0 */ +} pa_card_profile_info2; + +/** Stores information about a specific port of a card. Please + * note that this structure can be extended as part of evolutionary + * API updates at any time in any new release. \since 2.0 */ +typedef struct pa_card_port_info { + const char *name; /**< Name of this port */ + const char *description; /**< Description of this port */ + uint32_t priority; /**< The higher this value is, the more useful this port is as a default. */ + int available; /**< A #pa_port_available enum, indicating availability status of this port. */ + int direction; /**< A #pa_direction enum, indicating the direction of this port. */ + uint32_t n_profiles; /**< Number of entries in profile array */ + pa_card_profile_info** profiles; /**< \deprecated Superseded by profiles2 */ + pa_proplist *proplist; /**< Property list */ + int64_t latency_offset; /**< Latency offset of the port that gets added to the sink/source latency when the port is active. \since 3.0 */ + pa_card_profile_info2** profiles2; /**< Array of pointers to available profiles, or NULL. Array is terminated by an entry set to NULL. \since 5.0 */ + const char *availability_group; /**< An indentifier for the group of ports that share their availability status with + * each other. This is meant especially for handling cases where one 3.5 mm connector + * is used for headphones, headsets and microphones, and the hardware can only tell + * that something was plugged in but not what exactly. In this situation the ports for + * all those devices share their availability status, and PulseAudio can't tell which + * one is actually plugged in, and some application may ask the user what was plugged + * in. Such applications should get a list of all card ports and compare their + * `availability_group` fields. Ports that have the same group are those that need + * input from the user to determine which device was plugged in. The application should + * then activate the user-chosen port. + * + * May be NULL, in which case the port is not part of any availability group (which is + * the same as having a group with only one member). + * + * The group identifier must be treated as an opaque identifier. The string may look + * like an ALSA control name, but applications must not assume any such relationship. + * The group naming scheme can change without a warning. + * + * \since 14.0 */ + uint32_t type; /**< Port type, see #pa_device_port_type. \since 14.0 */ +} pa_card_port_info; + +/** Stores information about cards. Please note that this structure + * can be extended as part of evolutionary API updates at any time in + * any new release. \since 0.9.15 */ +typedef struct pa_card_info { + uint32_t index; /**< Index of this card */ + const char *name; /**< Name of this card */ + uint32_t owner_module; /**< Index of the owning module, or PA_INVALID_INDEX. */ + const char *driver; /**< Driver name */ + uint32_t n_profiles; /**< Number of entries in profile array */ + pa_card_profile_info* profiles; /**< \deprecated Superseded by profiles2 */ + pa_card_profile_info* active_profile; /**< \deprecated Superseded by active_profile2 */ + pa_proplist *proplist; /**< Property list */ + uint32_t n_ports; /**< Number of entries in port array */ + pa_card_port_info **ports; /**< Array of pointers to ports, or NULL. Array is terminated by an entry set to NULL. */ + pa_card_profile_info2** profiles2; /**< Array of pointers to available profiles, or NULL. Array is terminated by an entry set to NULL. \since 5.0 */ + pa_card_profile_info2* active_profile2; /**< Pointer to active profile in the array, or NULL. \since 5.0 */ +} pa_card_info; + +/** Callback prototype for pa_context_get_card_info_...() \since 0.9.15 */ +typedef void (*pa_card_info_cb_t) (pa_context *c, const pa_card_info*i, int eol, void *userdata); + +/** Get information about a card by its index \since 0.9.15 */ +pa_operation* pa_context_get_card_info_by_index(pa_context *c, uint32_t idx, pa_card_info_cb_t cb, void *userdata); + +/** Get information about a card by its name \since 0.9.15 */ +pa_operation* pa_context_get_card_info_by_name(pa_context *c, const char *name, pa_card_info_cb_t cb, void *userdata); + +/** Get the complete card list \since 0.9.15 */ +pa_operation* pa_context_get_card_info_list(pa_context *c, pa_card_info_cb_t cb, void *userdata); + +/** Change the profile of a card. \since 0.9.15 */ +pa_operation* pa_context_set_card_profile_by_index(pa_context *c, uint32_t idx, const char*profile, pa_context_success_cb_t cb, void *userdata); + +/** Change the profile of a card. \since 0.9.15 */ +pa_operation* pa_context_set_card_profile_by_name(pa_context *c, const char*name, const char*profile, pa_context_success_cb_t cb, void *userdata); + +/** Set the latency offset of a port. \since 3.0 */ +pa_operation* pa_context_set_port_latency_offset(pa_context *c, const char *card_name, const char *port_name, int64_t offset, pa_context_success_cb_t cb, void *userdata); + +/** @} */ + +/** @{ \name Sink Inputs */ + +/** Stores information about sink inputs. Please note that this structure + * can be extended as part of evolutionary API updates at any time in + * any new release. */ +typedef struct pa_sink_input_info { + uint32_t index; /**< Index of the sink input */ + const char *name; /**< Name of the sink input */ + uint32_t owner_module; /**< Index of the module this sink input belongs to, or PA_INVALID_INDEX when it does not belong to any module. */ + uint32_t client; /**< Index of the client this sink input belongs to, or PA_INVALID_INDEX when it does not belong to any client. */ + uint32_t sink; /**< Index of the connected sink */ + pa_sample_spec sample_spec; /**< The sample specification of the sink input. */ + pa_channel_map channel_map; /**< Channel map */ + pa_cvolume volume; /**< The volume of this sink input. */ + pa_usec_t buffer_usec; /**< Latency due to buffering in sink input, see pa_timing_info for details. */ + pa_usec_t sink_usec; /**< Latency of the sink device, see pa_timing_info for details. */ + const char *resample_method; /**< The resampling method used by this sink input. */ + const char *driver; /**< Driver name */ + int mute; /**< Stream muted \since 0.9.7 */ + pa_proplist *proplist; /**< Property list \since 0.9.11 */ + int corked; /**< Stream corked \since 1.0 */ + int has_volume; /**< Stream has volume. If not set, then the meaning of this struct's volume member is unspecified. \since 1.0 */ + int volume_writable; /**< The volume can be set. If not set, the volume can still change even though clients can't control the volume. \since 1.0 */ + pa_format_info *format; /**< Stream format information. \since 1.0 */ +} pa_sink_input_info; + +/** Callback prototype for pa_context_get_sink_input_info() and friends */ +typedef void (*pa_sink_input_info_cb_t) (pa_context *c, const pa_sink_input_info *i, int eol, void *userdata); + +/** Get some information about a sink input by its index */ +pa_operation* pa_context_get_sink_input_info(pa_context *c, uint32_t idx, pa_sink_input_info_cb_t cb, void *userdata); + +/** Get the complete sink input list */ +pa_operation* pa_context_get_sink_input_info_list(pa_context *c, pa_sink_input_info_cb_t cb, void *userdata); + +/** Move the specified sink input to a different sink. \since 0.9.5 */ +pa_operation* pa_context_move_sink_input_by_name(pa_context *c, uint32_t idx, const char *sink_name, pa_context_success_cb_t cb, void* userdata); + +/** Move the specified sink input to a different sink. \since 0.9.5 */ +pa_operation* pa_context_move_sink_input_by_index(pa_context *c, uint32_t idx, uint32_t sink_idx, pa_context_success_cb_t cb, void* userdata); + +/** Set the volume of a sink input stream */ +pa_operation* pa_context_set_sink_input_volume(pa_context *c, uint32_t idx, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata); + +/** Set the mute switch of a sink input stream \since 0.9.7 */ +pa_operation* pa_context_set_sink_input_mute(pa_context *c, uint32_t idx, int mute, pa_context_success_cb_t cb, void *userdata); + +/** Kill a sink input. */ +pa_operation* pa_context_kill_sink_input(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void *userdata); + +/** @} */ + +/** @{ \name Source Outputs */ + +/** Stores information about source outputs. Please note that this structure + * can be extended as part of evolutionary API updates at any time in + * any new release. */ +typedef struct pa_source_output_info { + uint32_t index; /**< Index of the source output */ + const char *name; /**< Name of the source output */ + uint32_t owner_module; /**< Index of the module this source output belongs to, or PA_INVALID_INDEX when it does not belong to any module. */ + uint32_t client; /**< Index of the client this source output belongs to, or PA_INVALID_INDEX when it does not belong to any client. */ + uint32_t source; /**< Index of the connected source */ + pa_sample_spec sample_spec; /**< The sample specification of the source output */ + pa_channel_map channel_map; /**< Channel map */ + pa_usec_t buffer_usec; /**< Latency due to buffering in the source output, see pa_timing_info for details. */ + pa_usec_t source_usec; /**< Latency of the source device, see pa_timing_info for details. */ + const char *resample_method; /**< The resampling method used by this source output. */ + const char *driver; /**< Driver name */ + pa_proplist *proplist; /**< Property list \since 0.9.11 */ + int corked; /**< Stream corked \since 1.0 */ + pa_cvolume volume; /**< The volume of this source output \since 1.0 */ + int mute; /**< Stream muted \since 1.0 */ + int has_volume; /**< Stream has volume. If not set, then the meaning of this struct's volume member is unspecified. \since 1.0 */ + int volume_writable; /**< The volume can be set. If not set, the volume can still change even though clients can't control the volume. \since 1.0 */ + pa_format_info *format; /**< Stream format information. \since 1.0 */ +} pa_source_output_info; + +/** Callback prototype for pa_context_get_source_output_info() and friends */ +typedef void (*pa_source_output_info_cb_t) (pa_context *c, const pa_source_output_info *i, int eol, void *userdata); + +/** Get information about a source output by its index */ +pa_operation* pa_context_get_source_output_info(pa_context *c, uint32_t idx, pa_source_output_info_cb_t cb, void *userdata); + +/** Get the complete list of source outputs */ +pa_operation* pa_context_get_source_output_info_list(pa_context *c, pa_source_output_info_cb_t cb, void *userdata); + +/** Move the specified source output to a different source. \since 0.9.5 */ +pa_operation* pa_context_move_source_output_by_name(pa_context *c, uint32_t idx, const char *source_name, pa_context_success_cb_t cb, void* userdata); + +/** Move the specified source output to a different source. \since 0.9.5 */ +pa_operation* pa_context_move_source_output_by_index(pa_context *c, uint32_t idx, uint32_t source_idx, pa_context_success_cb_t cb, void* userdata); + +/** Set the volume of a source output stream \since 1.0 */ +pa_operation* pa_context_set_source_output_volume(pa_context *c, uint32_t idx, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata); + +/** Set the mute switch of a source output stream \since 1.0 */ +pa_operation* pa_context_set_source_output_mute(pa_context *c, uint32_t idx, int mute, pa_context_success_cb_t cb, void *userdata); + +/** Kill a source output. */ +pa_operation* pa_context_kill_source_output(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void *userdata); + +/** @} */ + +/** @{ \name Statistics */ + +/** Memory block statistics. Please note that this structure + * can be extended as part of evolutionary API updates at any time in + * any new release. */ +typedef struct pa_stat_info { + uint32_t memblock_total; /**< Currently allocated memory blocks */ + uint32_t memblock_total_size; /**< Current total size of allocated memory blocks */ + uint32_t memblock_allocated; /**< Allocated memory blocks during the whole lifetime of the daemon. */ + uint32_t memblock_allocated_size; /**< Total size of all memory blocks allocated during the whole lifetime of the daemon. */ + uint32_t scache_size; /**< Total size of all sample cache entries. */ +} pa_stat_info; + +/** Callback prototype for pa_context_stat() */ +typedef void (*pa_stat_info_cb_t) (pa_context *c, const pa_stat_info *i, void *userdata); + +/** Get daemon memory block statistics */ +pa_operation* pa_context_stat(pa_context *c, pa_stat_info_cb_t cb, void *userdata); + +/** @} */ + +/** @{ \name Cached Samples */ + +/** Stores information about sample cache entries. Please note that this structure + * can be extended as part of evolutionary API updates at any time in + * any new release. */ +typedef struct pa_sample_info { + uint32_t index; /**< Index of this entry */ + const char *name; /**< Name of this entry */ + pa_cvolume volume; /**< Default volume of this entry */ + pa_sample_spec sample_spec; /**< Sample specification of the sample */ + pa_channel_map channel_map; /**< The channel map */ + pa_usec_t duration; /**< Duration of this entry */ + uint32_t bytes; /**< Length of this sample in bytes. */ + int lazy; /**< Non-zero when this is a lazy cache entry. */ + const char *filename; /**< In case this is a lazy cache entry, the filename for the sound file to be loaded on demand. */ + pa_proplist *proplist; /**< Property list for this sample. \since 0.9.11 */ +} pa_sample_info; + +/** Callback prototype for pa_context_get_sample_info_by_name() and friends */ +typedef void (*pa_sample_info_cb_t)(pa_context *c, const pa_sample_info *i, int eol, void *userdata); + +/** Get information about a sample by its name */ +pa_operation* pa_context_get_sample_info_by_name(pa_context *c, const char *name, pa_sample_info_cb_t cb, void *userdata); + +/** Get information about a sample by its index */ +pa_operation* pa_context_get_sample_info_by_index(pa_context *c, uint32_t idx, pa_sample_info_cb_t cb, void *userdata); + +/** Get the complete list of samples stored in the daemon. */ +pa_operation* pa_context_get_sample_info_list(pa_context *c, pa_sample_info_cb_t cb, void *userdata); + +/** @} */ + +/** \cond fulldocs */ + +/** @{ \name Autoload Entries */ + +/** \deprecated Type of an autoload entry. */ +typedef enum pa_autoload_type { + PA_AUTOLOAD_SINK = 0, + PA_AUTOLOAD_SOURCE = 1 +} pa_autoload_type_t; + +/** \deprecated Stores information about autoload entries. Please note that this structure + * can be extended as part of evolutionary API updates at any time in + * any new release. */ +typedef struct pa_autoload_info { + uint32_t index; /**< Index of this autoload entry */ + const char *name; /**< Name of the sink or source */ + pa_autoload_type_t type; /**< Type of the autoload entry */ + const char *module; /**< Module name to load */ + const char *argument; /**< Argument string for module */ +} pa_autoload_info; + +/** \deprecated Callback prototype for pa_context_get_autoload_info_by_name() and friends */ +typedef void (*pa_autoload_info_cb_t)(pa_context *c, const pa_autoload_info *i, int eol, void *userdata); + +/** \deprecated Get info about a specific autoload entry. */ +pa_operation* pa_context_get_autoload_info_by_name(pa_context *c, const char *name, pa_autoload_type_t type, pa_autoload_info_cb_t cb, void *userdata) PA_GCC_DEPRECATED; + +/** \deprecated Get info about a specific autoload entry. */ +pa_operation* pa_context_get_autoload_info_by_index(pa_context *c, uint32_t idx, pa_autoload_info_cb_t cb, void *userdata) PA_GCC_DEPRECATED; + +/** \deprecated Get the complete list of autoload entries. */ +pa_operation* pa_context_get_autoload_info_list(pa_context *c, pa_autoload_info_cb_t cb, void *userdata) PA_GCC_DEPRECATED; + +/** \deprecated Add a new autoload entry. */ +pa_operation* pa_context_add_autoload(pa_context *c, const char *name, pa_autoload_type_t type, const char *module, const char*argument, pa_context_index_cb_t, void* userdata) PA_GCC_DEPRECATED; + +/** \deprecated Remove an autoload entry. */ +pa_operation* pa_context_remove_autoload_by_name(pa_context *c, const char *name, pa_autoload_type_t type, pa_context_success_cb_t cb, void* userdata) PA_GCC_DEPRECATED; + +/** \deprecated Remove an autoload entry. */ +pa_operation* pa_context_remove_autoload_by_index(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void* userdata) PA_GCC_DEPRECATED; + +/** @} */ + +/** \endcond */ + +PA_C_DECL_END + +#endif diff --git a/src/pulse/json.c b/src/pulse/json.c new file mode 100644 index 0000000..d126712 --- /dev/null +++ b/src/pulse/json.c @@ -0,0 +1,614 @@ +/*** + This file is part of PulseAudio. + + Copyright 2016 Arun Raghavan <mail@arunraghavan.net> + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <math.h> + +#include <pulse/json.h> +#include <pulse/xmalloc.h> +#include <pulsecore/core-util.h> +#include <pulsecore/hashmap.h> +#include <pulsecore/strbuf.h> + +#define MAX_NESTING_DEPTH 20 /* Arbitrary number to make sure we don't have a stack overflow */ + +struct pa_json_object { + pa_json_type type; + + union { + int int_value; + double double_value; + bool bool_value; + char *string_value; + pa_hashmap *object_values; /* name -> object */ + pa_idxset *array_values; /* objects */ + }; +}; + +static const char* parse_value(const char *str, const char *end, pa_json_object **obj, unsigned int depth); + +static pa_json_object* json_object_new(void) { + pa_json_object *obj; + + obj = pa_xnew0(pa_json_object, 1); + + return obj; +} + +static bool is_whitespace(char c) { + return c == '\t' || c == '\n' || c == '\r' || c == ' '; +} + +static bool is_digit(char c) { + return c >= '0' && c <= '9'; +} + +static bool is_end(const char c, const char *end) { + if (!end) + return c == '\0'; + else { + while (*end) { + if (c == *end) + return true; + end++; + } + } + + return false; +} + +static const char* consume_string(const char *str, const char *expect) { + while (*expect) { + if (*str != *expect) + return NULL; + + str++; + expect++; + } + + return str; +} + +static const char* parse_null(const char *str, pa_json_object *obj) { + str = consume_string(str, "null"); + + if (str) + obj->type = PA_JSON_TYPE_NULL; + + return str; +} + +static const char* parse_boolean(const char *str, pa_json_object *obj) { + const char *tmp; + + tmp = consume_string(str, "true"); + + if (tmp) { + obj->type = PA_JSON_TYPE_BOOL; + obj->bool_value = true; + } else { + tmp = consume_string(str, "false"); + + if (str) { + obj->type = PA_JSON_TYPE_BOOL; + obj->bool_value = false; + } + } + + return tmp; +} + +static const char* parse_string(const char *str, pa_json_object *obj) { + pa_strbuf *buf = pa_strbuf_new(); + + str++; /* Consume leading '"' */ + + while (*str && *str != '"') { + if (*str != '\\') { + /* We only accept ASCII printable characters. */ + if (*str < 0x20 || *str > 0x7E) { + pa_log("Invalid non-ASCII character: 0x%x", (unsigned int) *str); + goto error; + } + + /* Normal character, juts consume */ + pa_strbuf_putc(buf, *str); + } else { + /* Need to unescape */ + str++; + + switch (*str) { + case '"': + case '\\': + case '/': + pa_strbuf_putc(buf, *str); + break; + + case 'b': + pa_strbuf_putc(buf, '\b' /* backspace */); + break; + + case 'f': + pa_strbuf_putc(buf, '\f' /* form feed */); + break; + + case 'n': + pa_strbuf_putc(buf, '\n' /* new line */); + break; + + case 'r': + pa_strbuf_putc(buf, '\r' /* carriage return */); + break; + + case 't': + pa_strbuf_putc(buf, '\t' /* horizontal tab */); + break; + + case 'u': + pa_log("Unicode code points are currently unsupported"); + goto error; + + default: + pa_log("Unexepcted escape value: %c", *str); + goto error; + } + } + + str++; + } + + if (*str != '"') { + pa_log("Failed to parse remainder of string: %s", str); + goto error; + } + + str++; + + obj->type = PA_JSON_TYPE_STRING; + obj->string_value = pa_strbuf_to_string_free(buf); + + return str; + +error: + pa_strbuf_free(buf); + return NULL; +} + +static const char* parse_number(const char *str, pa_json_object *obj) { + bool negative = false, has_fraction = false, has_exponent = false, valid = false; + unsigned int integer = 0; + unsigned int fraction = 0; + unsigned int fraction_digits = 0; + int exponent = 0; + + if (*str == '-') { + negative = true; + str++; + } + + if (*str == '0') { + valid = true; + str++; + goto fraction; + } + + while (is_digit(*str)) { + valid = true; + + if (integer > ((negative ? INT_MAX : UINT_MAX) / 10)) { + pa_log("Integer overflow while parsing number"); + goto error; + } + + integer = (integer * 10) + (*str - '0'); + str++; + } + +fraction: + + if (!valid) { + pa_log("Missing digits while parsing number"); + goto error; + } + + if (*str == '.') { + has_fraction = true; + str++; + valid = false; + + while (is_digit(*str)) { + valid = true; + + if (fraction > (UINT_MAX / 10)) { + pa_log("Integer overflow while parsing fractional part of number"); + goto error; + } + + fraction = (fraction * 10) + (*str - '0'); + fraction_digits++; + str++; + } + + if (!valid) { + pa_log("No digit after '.' while parsing fraction"); + goto error; + } + } + + if (*str == 'e' || *str == 'E') { + bool exponent_negative = false; + + has_exponent = true; + str++; + valid = false; + + if (*str == '-') { + exponent_negative = true; + str++; + } else if (*str == '+') + str++; + + while (is_digit(*str)) { + valid = true; + + if (exponent > (INT_MAX / 10)) { + pa_log("Integer overflow while parsing exponent part of number"); + goto error; + } + + exponent = (exponent * 10) + (*str - '0'); + str++; + } + + if (!valid) { + pa_log("No digit in exponent while parsing fraction"); + goto error; + } + + if (exponent_negative) + exponent *= -1; + } + + if (has_fraction || has_exponent) { + obj->type = PA_JSON_TYPE_DOUBLE; + obj->double_value = + (negative ? -1.0 : 1.0) * (integer + (double) fraction / pow(10, fraction_digits)) * pow(10, exponent); + } else { + obj->type = PA_JSON_TYPE_INT; + obj->int_value = (negative ? -1 : 1) * integer; + } + + return str; + +error: + return NULL; +} + +static const char *parse_object(const char *str, pa_json_object *obj, unsigned int depth) { + pa_json_object *name = NULL, *value = NULL; + + obj->object_values = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, + pa_xfree, (pa_free_cb_t) pa_json_object_free); + + while (*str != '}') { + str++; /* Consume leading '{' or ',' */ + + str = parse_value(str, ":", &name, depth + 1); + if (!str || pa_json_object_get_type(name) != PA_JSON_TYPE_STRING) { + pa_log("Could not parse key for object"); + goto error; + } + + /* Consume the ':' */ + str++; + + str = parse_value(str, ",}", &value, depth + 1); + if (!str) { + pa_log("Could not parse value for object"); + goto error; + } + + pa_hashmap_put(obj->object_values, pa_xstrdup(pa_json_object_get_string(name)), value); + pa_json_object_free(name); + + name = NULL; + value = NULL; + } + + /* Drop trailing '}' */ + str++; + + /* We now know the value was correctly parsed */ + obj->type = PA_JSON_TYPE_OBJECT; + + return str; + +error: + pa_hashmap_free(obj->object_values); + obj->object_values = NULL; + + if (name) + pa_json_object_free(name); + if (value) + pa_json_object_free(value); + + return NULL; +} + +static const char *parse_array(const char *str, pa_json_object *obj, unsigned int depth) { + pa_json_object *value; + + obj->array_values = pa_idxset_new(NULL, NULL); + + while (*str != ']') { + str++; /* Consume leading '[' or ',' */ + + /* Need to chew up whitespaces as a special case to deal with the + * possibility of an empty array */ + while (is_whitespace(*str)) + str++; + + if (*str == ']') + break; + + str = parse_value(str, ",]", &value, depth + 1); + if (!str) { + pa_log("Could not parse value for array"); + goto error; + } + + pa_idxset_put(obj->array_values, value, NULL); + } + + /* Drop trailing ']' */ + str++; + + /* We now know the value was correctly parsed */ + obj->type = PA_JSON_TYPE_ARRAY; + + return str; + +error: + pa_idxset_free(obj->array_values, (pa_free_cb_t) pa_json_object_free); + obj->array_values = NULL; + return NULL; +} + +typedef enum { + JSON_PARSER_STATE_INIT, + JSON_PARSER_STATE_FINISH, +} json_parser_state; + +static const char* parse_value(const char *str, const char *end, pa_json_object **obj, unsigned int depth) { + json_parser_state state = JSON_PARSER_STATE_INIT; + pa_json_object *o; + + pa_assert(str != NULL); + + o = json_object_new(); + + if (depth > MAX_NESTING_DEPTH) { + pa_log("Exceeded maximum permitted nesting depth of objects (%u)", MAX_NESTING_DEPTH); + goto error; + } + + while (!is_end(*str, end)) { + switch (state) { + case JSON_PARSER_STATE_INIT: + if (is_whitespace(*str)) { + str++; + } else if (*str == 'n') { + str = parse_null(str, o); + state = JSON_PARSER_STATE_FINISH; + } else if (*str == 't' || *str == 'f') { + str = parse_boolean(str, o); + state = JSON_PARSER_STATE_FINISH; + } else if (*str == '"') { + str = parse_string(str, o); + state = JSON_PARSER_STATE_FINISH; + } else if (is_digit(*str) || *str == '-') { + str = parse_number(str, o); + state = JSON_PARSER_STATE_FINISH; + } else if (*str == '{') { + str = parse_object(str, o, depth); + state = JSON_PARSER_STATE_FINISH; + } else if (*str == '[') { + str = parse_array(str, o, depth); + state = JSON_PARSER_STATE_FINISH; + } else { + pa_log("Invalid JSON string: %s", str); + goto error; + } + + if (!str) + goto error; + + break; + + case JSON_PARSER_STATE_FINISH: + /* Consume trailing whitespaces */ + if (is_whitespace(*str)) { + str++; + } else { + goto error; + } + } + } + + if (pa_json_object_get_type(o) == PA_JSON_TYPE_INIT) { + /* We didn't actually get any data */ + pa_log("No data while parsing json string: '%s' till '%s'", str, pa_strnull(end)); + goto error; + } + + *obj = o; + + return str; + +error: + pa_json_object_free(o); + return NULL; +} + + +pa_json_object* pa_json_parse(const char *str) { + pa_json_object *obj; + + str = parse_value(str, NULL, &obj, 0); + + if (!str) { + pa_log("JSON parsing failed"); + return NULL; + } + + if (*str != '\0') { + pa_log("Unable to parse complete JSON string, remainder is: %s", str); + pa_json_object_free(obj); + return NULL; + } + + return obj; +} + +pa_json_type pa_json_object_get_type(const pa_json_object *obj) { + return obj->type; +} + +void pa_json_object_free(pa_json_object *obj) { + + switch (pa_json_object_get_type(obj)) { + case PA_JSON_TYPE_INIT: + case PA_JSON_TYPE_INT: + case PA_JSON_TYPE_DOUBLE: + case PA_JSON_TYPE_BOOL: + case PA_JSON_TYPE_NULL: + break; + + case PA_JSON_TYPE_STRING: + pa_xfree(obj->string_value); + break; + + case PA_JSON_TYPE_OBJECT: + pa_hashmap_free(obj->object_values); + break; + + case PA_JSON_TYPE_ARRAY: + pa_idxset_free(obj->array_values, (pa_free_cb_t) pa_json_object_free); + break; + + default: + pa_assert_not_reached(); + } + + pa_xfree(obj); +} + +int pa_json_object_get_int(const pa_json_object *o) { + pa_assert(pa_json_object_get_type(o) == PA_JSON_TYPE_INT); + return o->int_value; +} + +double pa_json_object_get_double(const pa_json_object *o) { + pa_assert(pa_json_object_get_type(o) == PA_JSON_TYPE_DOUBLE); + return o->double_value; +} + +bool pa_json_object_get_bool(const pa_json_object *o) { + pa_assert(pa_json_object_get_type(o) == PA_JSON_TYPE_BOOL); + return o->bool_value; +} + +const char* pa_json_object_get_string(const pa_json_object *o) { + pa_assert(pa_json_object_get_type(o) == PA_JSON_TYPE_STRING); + return o->string_value; +} + +const pa_json_object* pa_json_object_get_object_member(const pa_json_object *o, const char *name) { + pa_assert(pa_json_object_get_type(o) == PA_JSON_TYPE_OBJECT); + return pa_hashmap_get(o->object_values, name); +} + +int pa_json_object_get_array_length(const pa_json_object *o) { + pa_assert(pa_json_object_get_type(o) == PA_JSON_TYPE_ARRAY); + return pa_idxset_size(o->array_values); +} + +const pa_json_object* pa_json_object_get_array_member(const pa_json_object *o, int index) { + pa_assert(pa_json_object_get_type(o) == PA_JSON_TYPE_ARRAY); + return pa_idxset_get_by_index(o->array_values, index); +} + +bool pa_json_object_equal(const pa_json_object *o1, const pa_json_object *o2) { + int i; + + if (pa_json_object_get_type(o1) != pa_json_object_get_type(o2)) + return false; + + switch (pa_json_object_get_type(o1)) { + case PA_JSON_TYPE_NULL: + return true; + + case PA_JSON_TYPE_BOOL: + return o1->bool_value == o2->bool_value; + + case PA_JSON_TYPE_INT: + return o1->int_value == o2->int_value; + + case PA_JSON_TYPE_DOUBLE: + return PA_DOUBLE_IS_EQUAL(o1->double_value, o2->double_value); + + case PA_JSON_TYPE_STRING: + return pa_streq(o1->string_value, o2->string_value); + + case PA_JSON_TYPE_ARRAY: + if (pa_json_object_get_array_length(o1) != pa_json_object_get_array_length(o2)) + return false; + + for (i = 0; i < pa_json_object_get_array_length(o1); i++) { + if (!pa_json_object_equal(pa_json_object_get_array_member(o1, i), + pa_json_object_get_array_member(o2, i))) + return false; + } + + return true; + + case PA_JSON_TYPE_OBJECT: { + void *state; + const char *key; + const pa_json_object *v1, *v2; + + if (pa_hashmap_size(o1->object_values) != pa_hashmap_size(o2->object_values)) + return false; + + PA_HASHMAP_FOREACH_KV(key, v1, o1->object_values, state) { + v2 = pa_json_object_get_object_member(o2, key); + if (!v2 || !pa_json_object_equal(v1, v2)) + return false; + } + + return true; + } + + default: + pa_assert_not_reached(); + } +} diff --git a/src/pulse/json.h b/src/pulse/json.h new file mode 100644 index 0000000..7759bf2 --- /dev/null +++ b/src/pulse/json.h @@ -0,0 +1,53 @@ +/*** + This file is part of PulseAudio. + + Copyright 2016 Arun Raghavan <mail@arunraghavan.net> + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <stdbool.h> + +#define PA_DOUBLE_IS_EQUAL(x, y) (((x) - (y)) < 0.000001 && ((x) - (y)) > -0.000001) + +typedef enum { + PA_JSON_TYPE_INIT = 0, + PA_JSON_TYPE_NULL, + PA_JSON_TYPE_INT, + PA_JSON_TYPE_DOUBLE, + PA_JSON_TYPE_BOOL, + PA_JSON_TYPE_STRING, + PA_JSON_TYPE_ARRAY, + PA_JSON_TYPE_OBJECT, +} pa_json_type; + +typedef struct pa_json_object pa_json_object; + +pa_json_object* pa_json_parse(const char *str); +pa_json_type pa_json_object_get_type(const pa_json_object *obj); +void pa_json_object_free(pa_json_object *obj); + +/* All pointer members that are returned are valid while the corresponding object is valid */ + +int pa_json_object_get_int(const pa_json_object *o); +double pa_json_object_get_double(const pa_json_object *o); +bool pa_json_object_get_bool(const pa_json_object *o); +const char* pa_json_object_get_string(const pa_json_object *o); + +const pa_json_object* pa_json_object_get_object_member(const pa_json_object *o, const char *name); + +int pa_json_object_get_array_length(const pa_json_object *o); +const pa_json_object* pa_json_object_get_array_member(const pa_json_object *o, int index); + +bool pa_json_object_equal(const pa_json_object *o1, const pa_json_object *o2); diff --git a/src/pulse/mainloop-api.c b/src/pulse/mainloop-api.c new file mode 100644 index 0000000..4ea0061 --- /dev/null +++ b/src/pulse/mainloop-api.c @@ -0,0 +1,75 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/i18n.h> +#include <pulsecore/macro.h> + +#include "mainloop-api.h" + +struct once_info { + void (*callback)(pa_mainloop_api*m, void *userdata); + void *userdata; +}; + +static void once_callback(pa_mainloop_api *m, pa_defer_event *e, void *userdata) { + struct once_info *i = userdata; + + pa_assert(m); + pa_assert(i); + + pa_assert(i->callback); + i->callback(m, i->userdata); + + pa_assert(m->defer_free); + m->defer_free(e); +} + +static void free_callback(pa_mainloop_api *m, pa_defer_event *e, void *userdata) { + struct once_info *i = userdata; + + pa_assert(m); + pa_assert(i); + pa_xfree(i); +} + +void pa_mainloop_api_once(pa_mainloop_api* m, void (*callback)(pa_mainloop_api *m, void *userdata), void *userdata) { + struct once_info *i; + pa_defer_event *e; + + pa_assert(m); + pa_assert(callback); + + pa_init_i18n(); + + i = pa_xnew(struct once_info, 1); + i->callback = callback; + i->userdata = userdata; + + pa_assert(m->defer_new); + pa_assert_se(e = m->defer_new(m, once_callback, i)); + m->defer_set_destroy(e, free_callback); +} diff --git a/src/pulse/mainloop-api.h b/src/pulse/mainloop-api.h new file mode 100644 index 0000000..f04ba20 --- /dev/null +++ b/src/pulse/mainloop-api.h @@ -0,0 +1,131 @@ +#ifndef foomainloopapihfoo +#define foomainloopapihfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/time.h> + +#include <pulse/cdecl.h> +#include <pulse/version.h> + +/** \file + * + * Main loop abstraction layer. Both the PulseAudio core and the + * PulseAudio client library use a main loop abstraction layer. Due to + * this it is possible to embed PulseAudio into other + * applications easily. Three main loop implementations are + * currently available: + * \li A minimal implementation based on the C library's poll() function + * (See \ref mainloop.h). + * \li A special version of the previous implementation where all of + * PulseAudio's internal handling runs in a separate thread + * (See \ref thread-mainloop.h). + * \li A wrapper around the GLIB main loop. Use this to embed PulseAudio into + * your GLIB/GTK+/GNOME programs (See \ref glib-mainloop.h). + * + * The structure pa_mainloop_api is used as a vtable for the main loop abstraction. + * + * This mainloop abstraction layer has no direct support for UNIX signals. + * Generic, mainloop implementation agnostic support is available through + * \ref mainloop-signal.h. + * */ + +PA_C_DECL_BEGIN + +/** An abstract mainloop API vtable */ +typedef struct pa_mainloop_api pa_mainloop_api; + +/** A bitmask for IO events */ +typedef enum pa_io_event_flags { + PA_IO_EVENT_NULL = 0, /**< No event */ + PA_IO_EVENT_INPUT = 1, /**< Input event */ + PA_IO_EVENT_OUTPUT = 2, /**< Output event */ + PA_IO_EVENT_HANGUP = 4, /**< Hangup event */ + PA_IO_EVENT_ERROR = 8 /**< Error event */ +} pa_io_event_flags_t; + +/** An opaque IO event source object */ +typedef struct pa_io_event pa_io_event; +/** An IO event callback prototype \since 0.9.3 */ +typedef void (*pa_io_event_cb_t)(pa_mainloop_api*ea, pa_io_event* e, int fd, pa_io_event_flags_t events, void *userdata); +/** A IO event destroy callback prototype \since 0.9.3 */ +typedef void (*pa_io_event_destroy_cb_t)(pa_mainloop_api*a, pa_io_event *e, void *userdata); + +/** An opaque timer event source object */ +typedef struct pa_time_event pa_time_event; +/** A time event callback prototype \since 0.9.3 */ +typedef void (*pa_time_event_cb_t)(pa_mainloop_api*a, pa_time_event* e, const struct timeval *tv, void *userdata); +/** A time event destroy callback prototype \since 0.9.3 */ +typedef void (*pa_time_event_destroy_cb_t)(pa_mainloop_api*a, pa_time_event *e, void *userdata); + +/** An opaque deferred event source object. Events of this type are triggered once in every main loop iteration */ +typedef struct pa_defer_event pa_defer_event; +/** A defer event callback prototype \since 0.9.3 */ +typedef void (*pa_defer_event_cb_t)(pa_mainloop_api*a, pa_defer_event* e, void *userdata); +/** A defer event destroy callback prototype \since 0.9.3 */ +typedef void (*pa_defer_event_destroy_cb_t)(pa_mainloop_api*a, pa_defer_event *e, void *userdata); + +/** An abstract mainloop API vtable */ +struct pa_mainloop_api { + /** A pointer to some private, arbitrary data of the main loop implementation */ + void *userdata; + + /** Create a new IO event source object */ + pa_io_event* (*io_new)(pa_mainloop_api*a, int fd, pa_io_event_flags_t events, pa_io_event_cb_t cb, void *userdata); + /** Enable or disable IO events on this object */ + void (*io_enable)(pa_io_event* e, pa_io_event_flags_t events); + /** Free a IO event source object */ + void (*io_free)(pa_io_event* e); + /** Set a function that is called when the IO event source is destroyed. Use this to free the userdata argument if required */ + void (*io_set_destroy)(pa_io_event *e, pa_io_event_destroy_cb_t cb); + + /** Create a new timer event source object for the specified Unix time */ + pa_time_event* (*time_new)(pa_mainloop_api*a, const struct timeval *tv, pa_time_event_cb_t cb, void *userdata); + /** Restart a running or expired timer event source with a new Unix time */ + void (*time_restart)(pa_time_event* e, const struct timeval *tv); + /** Free a deferred timer event source object */ + void (*time_free)(pa_time_event* e); + /** Set a function that is called when the timer event source is destroyed. Use this to free the userdata argument if required */ + void (*time_set_destroy)(pa_time_event *e, pa_time_event_destroy_cb_t cb); + + /** Create a new deferred event source object */ + pa_defer_event* (*defer_new)(pa_mainloop_api*a, pa_defer_event_cb_t cb, void *userdata); + /** Enable or disable a deferred event source temporarily */ + void (*defer_enable)(pa_defer_event* e, int b); + /** Free a deferred event source object */ + void (*defer_free)(pa_defer_event* e); + /** Set a function that is called when the deferred event source is destroyed. Use this to free the userdata argument if required */ + void (*defer_set_destroy)(pa_defer_event *e, pa_defer_event_destroy_cb_t cb); + + /** Exit the main loop and return the specified retval*/ + void (*quit)(pa_mainloop_api*a, int retval); +}; + +/** Run the specified callback function once from the main loop using an + * anonymous defer event. If the mainloop runs in a different thread, you need + * to follow the mainloop implementation's rules regarding how to safely create + * defer events. In particular, if you're using \ref pa_threaded_mainloop, you + * must lock the mainloop before calling this function. */ +void pa_mainloop_api_once(pa_mainloop_api*m, void (*callback)(pa_mainloop_api*m, void *userdata), void *userdata); + +PA_C_DECL_END + +#endif diff --git a/src/pulse/mainloop-signal.c b/src/pulse/mainloop-signal.c new file mode 100644 index 0000000..f196ad3 --- /dev/null +++ b/src/pulse/mainloop-signal.c @@ -0,0 +1,224 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2008 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <signal.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#ifdef HAVE_WINDOWS_H +#include <windows.h> +#endif + +#include <pulse/xmalloc.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/core-util.h> +#include <pulsecore/i18n.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> + +#include "mainloop-signal.h" + +struct pa_signal_event { + int sig; +#ifdef HAVE_SIGACTION + struct sigaction saved_sigaction; +#else + void (*saved_handler)(int sig); +#endif + void *userdata; + pa_signal_cb_t callback; + pa_signal_destroy_cb_t destroy_callback; + pa_signal_event *previous, *next; +}; + +static pa_mainloop_api *api = NULL; +static int signal_pipe[2] = { -1, -1 }; +static pa_io_event* io_event = NULL; +static pa_signal_event *signals = NULL; + +static void signal_handler(int sig) { + int saved_errno; + + saved_errno = errno; + +#ifndef HAVE_SIGACTION + signal(sig, signal_handler); +#endif + + /* XXX: If writing fails, there's nothing we can do? */ + (void) pa_write(signal_pipe[1], &sig, sizeof(sig), NULL); + + errno = saved_errno; +} + +static void dispatch(pa_mainloop_api*a, int sig) { + pa_signal_event *s; + + for (s = signals; s; s = s->next) + if (s->sig == sig) { + pa_assert(s->callback); + s->callback(a, s, sig, s->userdata); + break; + } +} + +static void callback(pa_mainloop_api*a, pa_io_event*e, int fd, pa_io_event_flags_t f, void *userdata) { + ssize_t r; + int sig; + + pa_assert(a); + pa_assert(e); + pa_assert(f == PA_IO_EVENT_INPUT); + pa_assert(e == io_event); + pa_assert(fd == signal_pipe[0]); + + if ((r = pa_read(signal_pipe[0], &sig, sizeof(sig), NULL)) < 0) { + if (errno == EAGAIN) + return; + + pa_log("read(): %s", pa_cstrerror(errno)); + return; + } + + if (r != sizeof(sig)) { + pa_log("short read()"); + return; + } + + dispatch(a, sig); +} + +int pa_signal_init(pa_mainloop_api *a) { + + pa_assert(a); + pa_assert(!api); + pa_assert(signal_pipe[0] == -1); + pa_assert(signal_pipe[1] == -1); + pa_assert(!io_event); + + if (pa_pipe_cloexec(signal_pipe) < 0) { + pa_log("pipe(): %s", pa_cstrerror(errno)); + return -1; + } + + pa_make_fd_nonblock(signal_pipe[0]); + pa_make_fd_nonblock(signal_pipe[1]); + + api = a; + + pa_assert_se(io_event = api->io_new(api, signal_pipe[0], PA_IO_EVENT_INPUT, callback, NULL)); + + return 0; +} + +void pa_signal_done(void) { + while (signals) + pa_signal_free(signals); + + if (io_event) { + pa_assert(api); + api->io_free(io_event); + io_event = NULL; + } + + pa_close_pipe(signal_pipe); + + api = NULL; +} + +pa_signal_event* pa_signal_new(int sig, pa_signal_cb_t _callback, void *userdata) { + pa_signal_event *e = NULL; + +#ifdef HAVE_SIGACTION + struct sigaction sa; +#endif + + pa_assert(sig > 0); + pa_assert(_callback); + + pa_init_i18n(); + + for (e = signals; e; e = e->next) + if (e->sig == sig) + return NULL; + + e = pa_xnew(pa_signal_event, 1); + e->sig = sig; + e->callback = _callback; + e->userdata = userdata; + e->destroy_callback = NULL; + +#ifdef HAVE_SIGACTION + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = signal_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + + if (sigaction(sig, &sa, &e->saved_sigaction) < 0) +#else + if ((e->saved_handler = signal(sig, signal_handler)) == SIG_ERR) +#endif + goto fail; + + e->previous = NULL; + e->next = signals; + signals = e; + + return e; +fail: + pa_xfree(e); + return NULL; +} + +void pa_signal_free(pa_signal_event *e) { + pa_assert(e); + + if (e->next) + e->next->previous = e->previous; + if (e->previous) + e->previous->next = e->next; + else + signals = e->next; + +#ifdef HAVE_SIGACTION + pa_assert_se(sigaction(e->sig, &e->saved_sigaction, NULL) == 0); +#else + pa_assert_se(signal(e->sig, e->saved_handler) == signal_handler); +#endif + + if (e->destroy_callback) + e->destroy_callback(api, e, e->userdata); + + pa_xfree(e); +} + +void pa_signal_set_destroy(pa_signal_event *e, pa_signal_destroy_cb_t _callback) { + pa_assert(e); + + e->destroy_callback = _callback; +} diff --git a/src/pulse/mainloop-signal.h b/src/pulse/mainloop-signal.h new file mode 100644 index 0000000..0a7bdeb --- /dev/null +++ b/src/pulse/mainloop-signal.h @@ -0,0 +1,64 @@ +#ifndef foomainloopsignalhfoo +#define foomainloopsignalhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2008 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulse/mainloop-api.h> +#include <pulse/cdecl.h> + +PA_C_DECL_BEGIN + +/** \file + * UNIX signal support for main loops. In contrast to other + * main loop event sources such as timer and IO events, UNIX signal + * support requires modification of the global process + * environment. Due to this the generic main loop abstraction layer as + * defined in \ref mainloop-api.h doesn't have direct support for UNIX + * signals. However, you may hook signal support into an abstract main loop via the routines defined herein. + */ + +/** An opaque UNIX signal event source object */ +typedef struct pa_signal_event pa_signal_event; + +/** Callback prototype for signal events */ +typedef void (*pa_signal_cb_t) (pa_mainloop_api *api, pa_signal_event*e, int sig, void *userdata); + +/** Destroy callback prototype for signal events */ +typedef void (*pa_signal_destroy_cb_t) (pa_mainloop_api *api, pa_signal_event*e, void *userdata); + +/** Initialize the UNIX signal subsystem and bind it to the specified main loop */ +int pa_signal_init(pa_mainloop_api *api); + +/** Cleanup the signal subsystem */ +void pa_signal_done(void); + +/** Create a new UNIX signal event source object */ +pa_signal_event* pa_signal_new(int sig, pa_signal_cb_t callback, void *userdata); + +/** Free a UNIX signal event source object */ +void pa_signal_free(pa_signal_event *e); + +/** Set a function that is called when the signal event source is destroyed. Use this to free the userdata argument if required */ +void pa_signal_set_destroy(pa_signal_event *e, pa_signal_destroy_cb_t callback); + +PA_C_DECL_END + +#endif diff --git a/src/pulse/mainloop.c b/src/pulse/mainloop.c new file mode 100644 index 0000000..355935e --- /dev/null +++ b/src/pulse/mainloop.c @@ -0,0 +1,976 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <fcntl.h> +#include <errno.h> + +#ifndef HAVE_PIPE +#include <pulsecore/pipe.h> +#endif + +#include <pulse/rtclock.h> +#include <pulse/timeval.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/poll.h> +#include <pulsecore/core-rtclock.h> +#include <pulsecore/core-util.h> +#include <pulsecore/i18n.h> +#include <pulsecore/llist.h> +#include <pulsecore/log.h> +#include <pulsecore/core-error.h> +#include <pulsecore/socket.h> +#include <pulsecore/macro.h> + +#include "mainloop.h" +#include "internal.h" + +struct pa_io_event { + pa_mainloop *mainloop; + bool dead:1; + + int fd; + pa_io_event_flags_t events; + struct pollfd *pollfd; + + pa_io_event_cb_t callback; + void *userdata; + pa_io_event_destroy_cb_t destroy_callback; + + PA_LLIST_FIELDS(pa_io_event); +}; + +struct pa_time_event { + pa_mainloop *mainloop; + bool dead:1; + + bool enabled:1; + bool use_rtclock:1; + pa_usec_t time; + + pa_time_event_cb_t callback; + void *userdata; + pa_time_event_destroy_cb_t destroy_callback; + + PA_LLIST_FIELDS(pa_time_event); +}; + +struct pa_defer_event { + pa_mainloop *mainloop; + bool dead:1; + + bool enabled:1; + + pa_defer_event_cb_t callback; + void *userdata; + pa_defer_event_destroy_cb_t destroy_callback; + + PA_LLIST_FIELDS(pa_defer_event); +}; + +struct pa_mainloop { + PA_LLIST_HEAD(pa_io_event, io_events); + PA_LLIST_HEAD(pa_time_event, time_events); + PA_LLIST_HEAD(pa_defer_event, defer_events); + + unsigned n_enabled_defer_events, n_enabled_time_events, n_io_events; + unsigned io_events_please_scan, time_events_please_scan, defer_events_please_scan; + + bool rebuild_pollfds:1; + struct pollfd *pollfds; + unsigned max_pollfds, n_pollfds; + + pa_usec_t prepared_timeout; + pa_time_event *cached_next_time_event; + + pa_mainloop_api api; + + int retval; + bool quit:1; + + int wakeup_pipe[2]; + int wakeup_pipe_type; + + enum { + STATE_PASSIVE, + STATE_PREPARED, + STATE_POLLING, + STATE_POLLED, + STATE_QUIT + } state; + + pa_poll_func poll_func; + void *poll_func_userdata; + int poll_func_ret; +}; + +static short map_flags_to_libc(pa_io_event_flags_t flags) { + return (short) + ((flags & PA_IO_EVENT_INPUT ? POLLIN : 0) | + (flags & PA_IO_EVENT_OUTPUT ? POLLOUT : 0) | + (flags & PA_IO_EVENT_ERROR ? POLLERR : 0) | + (flags & PA_IO_EVENT_HANGUP ? POLLHUP : 0)); +} + +static pa_io_event_flags_t map_flags_from_libc(short flags) { + return + (flags & POLLIN ? PA_IO_EVENT_INPUT : 0) | + (flags & POLLOUT ? PA_IO_EVENT_OUTPUT : 0) | + (flags & POLLERR ? PA_IO_EVENT_ERROR : 0) | + (flags & POLLHUP ? PA_IO_EVENT_HANGUP : 0); +} + +/* IO events */ +static pa_io_event* mainloop_io_new( + pa_mainloop_api *a, + int fd, + pa_io_event_flags_t events, + pa_io_event_cb_t callback, + void *userdata) { + + pa_mainloop *m; + pa_io_event *e; + + pa_assert(a); + pa_assert(a->userdata); + pa_assert(fd >= 0); + pa_assert(callback); + + m = a->userdata; + pa_assert(a == &m->api); + + e = pa_xnew0(pa_io_event, 1); + e->mainloop = m; + + e->fd = fd; + e->events = events; + + e->callback = callback; + e->userdata = userdata; + + PA_LLIST_PREPEND(pa_io_event, m->io_events, e); + m->rebuild_pollfds = true; + m->n_io_events ++; + + pa_mainloop_wakeup(m); + + return e; +} + +static void mainloop_io_enable(pa_io_event *e, pa_io_event_flags_t events) { + pa_assert(e); + pa_assert(!e->dead); + + if (e->events == events) + return; + + e->events = events; + + if (e->pollfd) + e->pollfd->events = map_flags_to_libc(events); + else + e->mainloop->rebuild_pollfds = true; + + pa_mainloop_wakeup(e->mainloop); +} + +static void mainloop_io_free(pa_io_event *e) { + pa_assert(e); + pa_assert(!e->dead); + + e->dead = true; + e->mainloop->io_events_please_scan ++; + + e->mainloop->n_io_events --; + e->mainloop->rebuild_pollfds = true; + + pa_mainloop_wakeup(e->mainloop); +} + +static void mainloop_io_set_destroy(pa_io_event *e, pa_io_event_destroy_cb_t callback) { + pa_assert(e); + + e->destroy_callback = callback; +} + +/* Defer events */ +static pa_defer_event* mainloop_defer_new( + pa_mainloop_api *a, + pa_defer_event_cb_t callback, + void *userdata) { + + pa_mainloop *m; + pa_defer_event *e; + + pa_assert(a); + pa_assert(a->userdata); + pa_assert(callback); + + m = a->userdata; + pa_assert(a == &m->api); + + e = pa_xnew0(pa_defer_event, 1); + e->mainloop = m; + + e->enabled = true; + m->n_enabled_defer_events++; + + e->callback = callback; + e->userdata = userdata; + + PA_LLIST_PREPEND(pa_defer_event, m->defer_events, e); + + pa_mainloop_wakeup(e->mainloop); + + return e; +} + +static void mainloop_defer_enable(pa_defer_event *e, int b) { + pa_assert(e); + pa_assert(!e->dead); + + if (e->enabled && !b) { + pa_assert(e->mainloop->n_enabled_defer_events > 0); + e->mainloop->n_enabled_defer_events--; + } else if (!e->enabled && b) { + e->mainloop->n_enabled_defer_events++; + pa_mainloop_wakeup(e->mainloop); + } + + e->enabled = b; +} + +static void mainloop_defer_free(pa_defer_event *e) { + pa_assert(e); + pa_assert(!e->dead); + + e->dead = true; + e->mainloop->defer_events_please_scan ++; + + if (e->enabled) { + pa_assert(e->mainloop->n_enabled_defer_events > 0); + e->mainloop->n_enabled_defer_events--; + e->enabled = false; + } +} + +static void mainloop_defer_set_destroy(pa_defer_event *e, pa_defer_event_destroy_cb_t callback) { + pa_assert(e); + pa_assert(!e->dead); + + e->destroy_callback = callback; +} + +/* Time events */ +static pa_usec_t make_rt(const struct timeval *tv, bool *use_rtclock) { + struct timeval ttv; + + if (!tv) { + *use_rtclock = false; + return PA_USEC_INVALID; + } + + ttv = *tv; + *use_rtclock = !!(ttv.tv_usec & PA_TIMEVAL_RTCLOCK); + + if (*use_rtclock) + ttv.tv_usec &= ~PA_TIMEVAL_RTCLOCK; + else + pa_rtclock_from_wallclock(&ttv); + + return pa_timeval_load(&ttv); +} + +static pa_time_event* mainloop_time_new( + pa_mainloop_api *a, + const struct timeval *tv, + pa_time_event_cb_t callback, + void *userdata) { + + pa_mainloop *m; + pa_time_event *e; + pa_usec_t t; + bool use_rtclock = false; + + pa_assert(a); + pa_assert(a->userdata); + pa_assert(callback); + + t = make_rt(tv, &use_rtclock); + + m = a->userdata; + pa_assert(a == &m->api); + + e = pa_xnew0(pa_time_event, 1); + e->mainloop = m; + + if ((e->enabled = (t != PA_USEC_INVALID))) { + e->time = t; + e->use_rtclock = use_rtclock; + + m->n_enabled_time_events++; + + if (m->cached_next_time_event) { + pa_assert(m->cached_next_time_event->enabled); + + if (t < m->cached_next_time_event->time) + m->cached_next_time_event = e; + } + } + + e->callback = callback; + e->userdata = userdata; + + PA_LLIST_PREPEND(pa_time_event, m->time_events, e); + + if (e->enabled) + pa_mainloop_wakeup(m); + + return e; +} + +static void mainloop_time_restart(pa_time_event *e, const struct timeval *tv) { + bool valid; + pa_usec_t t; + bool use_rtclock = false; + + pa_assert(e); + pa_assert(!e->dead); + + t = make_rt(tv, &use_rtclock); + + valid = (t != PA_USEC_INVALID); + if (e->enabled && !valid) { + pa_assert(e->mainloop->n_enabled_time_events > 0); + e->mainloop->n_enabled_time_events--; + } else if (!e->enabled && valid) + e->mainloop->n_enabled_time_events++; + + if ((e->enabled = valid)) { + e->time = t; + e->use_rtclock = use_rtclock; + pa_mainloop_wakeup(e->mainloop); + } + + if (e->mainloop->cached_next_time_event == e) + e->mainloop->cached_next_time_event = NULL; + + if (e->mainloop->cached_next_time_event && e->enabled) { + pa_assert(e->mainloop->cached_next_time_event->enabled); + + if (t < e->mainloop->cached_next_time_event->time) + e->mainloop->cached_next_time_event = e; + } +} + +static void mainloop_time_free(pa_time_event *e) { + pa_assert(e); + pa_assert(!e->dead); + + e->dead = true; + e->mainloop->time_events_please_scan ++; + + if (e->enabled) { + pa_assert(e->mainloop->n_enabled_time_events > 0); + e->mainloop->n_enabled_time_events--; + e->enabled = false; + } + + if (e->mainloop->cached_next_time_event == e) + e->mainloop->cached_next_time_event = NULL; + + /* no wakeup needed here. Think about it! */ +} + +static void mainloop_time_set_destroy(pa_time_event *e, pa_time_event_destroy_cb_t callback) { + pa_assert(e); + pa_assert(!e->dead); + + e->destroy_callback = callback; +} + +/* quit() */ + +static void mainloop_quit(pa_mainloop_api *a, int retval) { + pa_mainloop *m; + + pa_assert(a); + pa_assert(a->userdata); + m = a->userdata; + pa_assert(a == &m->api); + + pa_mainloop_quit(m, retval); +} + +static const pa_mainloop_api vtable = { + .userdata = NULL, + + .io_new = mainloop_io_new, + .io_enable = mainloop_io_enable, + .io_free = mainloop_io_free, + .io_set_destroy = mainloop_io_set_destroy, + + .time_new = mainloop_time_new, + .time_restart = mainloop_time_restart, + .time_free = mainloop_time_free, + .time_set_destroy = mainloop_time_set_destroy, + + .defer_new = mainloop_defer_new, + .defer_enable = mainloop_defer_enable, + .defer_free = mainloop_defer_free, + .defer_set_destroy = mainloop_defer_set_destroy, + + .quit = mainloop_quit, +}; + +pa_mainloop *pa_mainloop_new(void) { + pa_mainloop *m; + + pa_init_i18n(); + + m = pa_xnew0(pa_mainloop, 1); + + if (pa_pipe_cloexec(m->wakeup_pipe) < 0) { + pa_log_error("ERROR: cannot create wakeup pipe"); + pa_xfree(m); + return NULL; + } + + pa_make_fd_nonblock(m->wakeup_pipe[0]); + pa_make_fd_nonblock(m->wakeup_pipe[1]); + + m->rebuild_pollfds = true; + + m->api = vtable; + m->api.userdata = m; + + m->state = STATE_PASSIVE; + + m->poll_func_ret = -1; + + return m; +} + +static void cleanup_io_events(pa_mainloop *m, bool force) { + pa_io_event *e, *n; + + PA_LLIST_FOREACH_SAFE(e, n, m->io_events) { + + if (!force && m->io_events_please_scan <= 0) + break; + + if (force || e->dead) { + PA_LLIST_REMOVE(pa_io_event, m->io_events, e); + + if (e->dead) { + pa_assert(m->io_events_please_scan > 0); + m->io_events_please_scan--; + } + + if (e->destroy_callback) + e->destroy_callback(&m->api, e, e->userdata); + + pa_xfree(e); + + m->rebuild_pollfds = true; + } + } + + pa_assert(m->io_events_please_scan == 0); +} + +static void cleanup_time_events(pa_mainloop *m, bool force) { + pa_time_event *e, *n; + + PA_LLIST_FOREACH_SAFE(e, n, m->time_events) { + + if (!force && m->time_events_please_scan <= 0) + break; + + if (force || e->dead) { + PA_LLIST_REMOVE(pa_time_event, m->time_events, e); + + if (e->dead) { + pa_assert(m->time_events_please_scan > 0); + m->time_events_please_scan--; + } + + if (!e->dead && e->enabled) { + pa_assert(m->n_enabled_time_events > 0); + m->n_enabled_time_events--; + e->enabled = false; + } + + if (e->destroy_callback) + e->destroy_callback(&m->api, e, e->userdata); + + pa_xfree(e); + } + } + + pa_assert(m->time_events_please_scan == 0); +} + +static void cleanup_defer_events(pa_mainloop *m, bool force) { + pa_defer_event *e, *n; + + PA_LLIST_FOREACH_SAFE(e, n, m->defer_events) { + + if (!force && m->defer_events_please_scan <= 0) + break; + + if (force || e->dead) { + PA_LLIST_REMOVE(pa_defer_event, m->defer_events, e); + + if (e->dead) { + pa_assert(m->defer_events_please_scan > 0); + m->defer_events_please_scan--; + } + + if (!e->dead && e->enabled) { + pa_assert(m->n_enabled_defer_events > 0); + m->n_enabled_defer_events--; + e->enabled = false; + } + + if (e->destroy_callback) + e->destroy_callback(&m->api, e, e->userdata); + + pa_xfree(e); + } + } + + pa_assert(m->defer_events_please_scan == 0); +} + +void pa_mainloop_free(pa_mainloop *m) { + pa_assert(m); + + cleanup_io_events(m, true); + cleanup_defer_events(m, true); + cleanup_time_events(m, true); + + pa_xfree(m->pollfds); + + pa_close_pipe(m->wakeup_pipe); + + pa_xfree(m); +} + +static void scan_dead(pa_mainloop *m) { + pa_assert(m); + + if (m->io_events_please_scan) + cleanup_io_events(m, false); + + if (m->time_events_please_scan) + cleanup_time_events(m, false); + + if (m->defer_events_please_scan) + cleanup_defer_events(m, false); +} + +static void rebuild_pollfds(pa_mainloop *m) { + pa_io_event*e; + struct pollfd *p; + unsigned l; + + l = m->n_io_events + 1; + if (m->max_pollfds < l) { + l *= 2; + m->pollfds = pa_xrealloc(m->pollfds, sizeof(struct pollfd)*l); + m->max_pollfds = l; + } + + m->n_pollfds = 0; + p = m->pollfds; + + m->pollfds[0].fd = m->wakeup_pipe[0]; + m->pollfds[0].events = POLLIN; + m->pollfds[0].revents = 0; + p++; + m->n_pollfds++; + + PA_LLIST_FOREACH(e, m->io_events) { + if (e->dead) { + e->pollfd = NULL; + continue; + } + + e->pollfd = p; + p->fd = e->fd; + p->events = map_flags_to_libc(e->events); + p->revents = 0; + + p++; + m->n_pollfds++; + } + + m->rebuild_pollfds = false; +} + +static unsigned dispatch_pollfds(pa_mainloop *m) { + pa_io_event *e; + unsigned r = 0, k; + + pa_assert(m->poll_func_ret > 0); + + k = m->poll_func_ret; + + PA_LLIST_FOREACH(e, m->io_events) { + + if (k <= 0 || m->quit) + break; + + if (e->dead || !e->pollfd || !e->pollfd->revents) + continue; + + pa_assert(e->pollfd->fd == e->fd); + pa_assert(e->callback); + + e->callback(&m->api, e, e->fd, map_flags_from_libc(e->pollfd->revents), e->userdata); + e->pollfd->revents = 0; + r++; + k--; + } + + return r; +} + +static unsigned dispatch_defer(pa_mainloop *m) { + pa_defer_event *e; + unsigned r = 0; + + if (m->n_enabled_defer_events <= 0) + return 0; + + PA_LLIST_FOREACH(e, m->defer_events) { + + if (m->quit) + break; + + if (e->dead || !e->enabled) + continue; + + pa_assert(e->callback); + e->callback(&m->api, e, e->userdata); + r++; + } + + return r; +} + +static pa_time_event* find_next_time_event(pa_mainloop *m) { + pa_time_event *t, *n = NULL; + pa_assert(m); + + if (m->cached_next_time_event) + return m->cached_next_time_event; + + PA_LLIST_FOREACH(t, m->time_events) { + + if (t->dead || !t->enabled) + continue; + + if (!n || t->time < n->time) { + n = t; + + /* Shortcut for time == 0 */ + if (n->time == 0) + break; + } + } + + m->cached_next_time_event = n; + return n; +} + +static pa_usec_t calc_next_timeout(pa_mainloop *m) { + pa_time_event *t; + pa_usec_t clock_now; + + if (m->n_enabled_time_events <= 0) + return PA_USEC_INVALID; + + pa_assert_se(t = find_next_time_event(m)); + + if (t->time <= 0) + return 0; + + clock_now = pa_rtclock_now(); + + if (t->time <= clock_now) + return 0; + + return t->time - clock_now; +} + +static unsigned dispatch_timeout(pa_mainloop *m) { + pa_time_event *e; + pa_usec_t now; + unsigned r = 0; + pa_assert(m); + + if (m->n_enabled_time_events <= 0) + return 0; + + now = pa_rtclock_now(); + + PA_LLIST_FOREACH(e, m->time_events) { + + if (m->quit) + break; + + if (e->dead || !e->enabled) + continue; + + if (e->time <= now) { + struct timeval tv; + pa_assert(e->callback); + + /* Disable time event */ + mainloop_time_restart(e, NULL); + + e->callback(&m->api, e, pa_timeval_rtstore(&tv, e->time, e->use_rtclock), e->userdata); + + r++; + } + } + + return r; +} + +void pa_mainloop_wakeup(pa_mainloop *m) { + char c = 'W'; + pa_assert(m); + + if (pa_write(m->wakeup_pipe[1], &c, sizeof(c), &m->wakeup_pipe_type) < 0) + /* Not many options for recovering from the error. Let's at least log something. */ + pa_log("pa_write() failed while trying to wake up the mainloop: %s", pa_cstrerror(errno)); +} + +static void clear_wakeup(pa_mainloop *m) { + char c[10]; + + pa_assert(m); + + while (pa_read(m->wakeup_pipe[0], &c, sizeof(c), &m->wakeup_pipe_type) == sizeof(c)) + ; +} + +int pa_mainloop_prepare(pa_mainloop *m, int timeout) { + pa_assert(m); + pa_assert(m->state == STATE_PASSIVE); + + clear_wakeup(m); + scan_dead(m); + + if (m->quit) + goto quit; + + if (m->n_enabled_defer_events <= 0) { + + if (m->rebuild_pollfds) + rebuild_pollfds(m); + + m->prepared_timeout = calc_next_timeout(m); + if (timeout >= 0) { + if (timeout < m->prepared_timeout || m->prepared_timeout == PA_USEC_INVALID) + m->prepared_timeout = timeout; + } + } + + m->state = STATE_PREPARED; + return 0; + +quit: + m->state = STATE_QUIT; + return -2; +} + +static int usec_to_timeout(pa_usec_t u) { + int timeout; + + if (u == PA_USEC_INVALID) + return -1; + + timeout = (u + PA_USEC_PER_MSEC - 1) / PA_USEC_PER_MSEC; + pa_assert(timeout >= 0); + + return timeout; +} + +int pa_mainloop_poll(pa_mainloop *m) { + pa_assert(m); + pa_assert(m->state == STATE_PREPARED); + + if (m->quit) + goto quit; + + m->state = STATE_POLLING; + + if (m->n_enabled_defer_events) + m->poll_func_ret = 0; + else { + pa_assert(!m->rebuild_pollfds); + + if (m->poll_func) + m->poll_func_ret = m->poll_func( + m->pollfds, m->n_pollfds, + usec_to_timeout(m->prepared_timeout), + m->poll_func_userdata); + else { +#ifdef HAVE_PPOLL + struct timespec ts; + + m->poll_func_ret = ppoll( + m->pollfds, m->n_pollfds, + m->prepared_timeout == PA_USEC_INVALID ? NULL : pa_timespec_store(&ts, m->prepared_timeout), + NULL); +#else + m->poll_func_ret = pa_poll( + m->pollfds, m->n_pollfds, + usec_to_timeout(m->prepared_timeout)); +#endif + } + + if (m->poll_func_ret < 0) { + if (errno == EINTR) + m->poll_func_ret = 0; + else + pa_log("poll(): %s", pa_cstrerror(errno)); + } + } + + m->state = m->poll_func_ret < 0 ? STATE_PASSIVE : STATE_POLLED; + return m->poll_func_ret; + +quit: + m->state = STATE_QUIT; + return -2; +} + +int pa_mainloop_dispatch(pa_mainloop *m) { + unsigned dispatched = 0; + + pa_assert(m); + pa_assert(m->state == STATE_POLLED); + + if (m->quit) + goto quit; + + if (m->n_enabled_defer_events) + dispatched += dispatch_defer(m); + else { + if (m->n_enabled_time_events) + dispatched += dispatch_timeout(m); + + if (m->quit) + goto quit; + + if (m->poll_func_ret > 0) + dispatched += dispatch_pollfds(m); + } + + if (m->quit) + goto quit; + + m->state = STATE_PASSIVE; + + return (int) dispatched; + +quit: + m->state = STATE_QUIT; + return -2; +} + +int pa_mainloop_get_retval(const pa_mainloop *m) { + pa_assert(m); + + return m->retval; +} + +int pa_mainloop_iterate(pa_mainloop *m, int block, int *retval) { + int r; + pa_assert(m); + + if ((r = pa_mainloop_prepare(m, block ? -1 : 0)) < 0) + goto quit; + + if ((r = pa_mainloop_poll(m)) < 0) + goto quit; + + if ((r = pa_mainloop_dispatch(m)) < 0) + goto quit; + + return r; + +quit: + + if ((r == -2) && retval) + *retval = pa_mainloop_get_retval(m); + return r; +} + +int pa_mainloop_run(pa_mainloop *m, int *retval) { + int r; + + while ((r = pa_mainloop_iterate(m, 1, retval)) >= 0) + ; + + if (r == -2) + return 1; + else + return -1; +} + +void pa_mainloop_quit(pa_mainloop *m, int retval) { + pa_assert(m); + + m->quit = true; + m->retval = retval; + pa_mainloop_wakeup(m); +} + +pa_mainloop_api* pa_mainloop_get_api(pa_mainloop *m) { + pa_assert(m); + + return &m->api; +} + +void pa_mainloop_set_poll_func(pa_mainloop *m, pa_poll_func poll_func, void *userdata) { + pa_assert(m); + + m->poll_func = poll_func; + m->poll_func_userdata = userdata; +} + +bool pa_mainloop_is_our_api(const pa_mainloop_api *m) { + pa_assert(m); + + return m->io_new == mainloop_io_new; +} diff --git a/src/pulse/mainloop.h b/src/pulse/mainloop.h new file mode 100644 index 0000000..f1069da --- /dev/null +++ b/src/pulse/mainloop.h @@ -0,0 +1,134 @@ +#ifndef foomainloophfoo +#define foomainloophfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulse/mainloop-api.h> +#include <pulse/cdecl.h> + +PA_C_DECL_BEGIN + +struct pollfd; + +/** \page mainloop Main Loop + * + * \section overv_sec Overview + * + * The built-in main loop implementation is based on the poll() system call. + * It supports the functions defined in the main loop abstraction and very + * little else. + * + * The main loop is created using pa_mainloop_new() and destroyed using + * pa_mainloop_free(). To get access to the main loop abstraction, + * pa_mainloop_get_api() is used. + * + * \section iter_sec Iteration + * + * The main loop is designed around the concept of iterations. Each iteration + * consists of three steps that repeat during the application's entire + * lifetime: + * + * -# Prepare - Build a list of file descriptors + * that need to be monitored and calculate the next timeout. + * -# Poll - Execute the actual poll() system call. + * -# Dispatch - Dispatch any events that have fired. + * + * When using the main loop, the application can either execute each + * iteration, one at a time, using pa_mainloop_iterate(), or let the library + * iterate automatically using pa_mainloop_run(). + * + * \section thread_sec Threads + * + * The main loop functions are designed to be thread safe, but the objects + * are not. What this means is that multiple main loops can be used, but only + * one object per thread. + * + */ + +/** \file + * + * A minimal main loop implementation based on the C library's poll() + * function. Using the routines defined herein you may create a simple + * main loop supporting the generic main loop abstraction layer as + * defined in \ref mainloop-api.h. This implementation is thread safe + * as long as you access the main loop object from a single thread only. + * + * See also \subpage mainloop + */ + +/** An opaque main loop object */ +typedef struct pa_mainloop pa_mainloop; + +/** Allocate a new main loop object. Free with pa_mainloop_free. */ +pa_mainloop *pa_mainloop_new(void); + +/** Free a main loop object */ +void pa_mainloop_free(pa_mainloop* m); + +/** Prepare for a single iteration of the main loop. Returns a negative value +on error or exit request. timeout specifies a maximum timeout for the subsequent +poll, or -1 for blocking behaviour. The timeout is specified in microseconds. */ +int pa_mainloop_prepare(pa_mainloop *m, int timeout); + +/** Execute the previously prepared poll. Returns a negative value on error.*/ +int pa_mainloop_poll(pa_mainloop *m); + +/** Dispatch timeout, io and deferred events from the previously executed poll. Returns +a negative value on error. On success returns the number of source dispatched. */ +int pa_mainloop_dispatch(pa_mainloop *m); + +/** Return the return value as specified with the main loop's quit() routine. */ +int pa_mainloop_get_retval(const pa_mainloop *m); + +/** Run a single iteration of the main loop. This is a convenience function +for pa_mainloop_prepare(), pa_mainloop_poll() and pa_mainloop_dispatch(). +Returns a negative value on error or exit request. If block is nonzero, +block for events if none are queued. Optionally return the return value as +specified with the main loop's quit() routine in the integer variable retval points +to. On success returns the number of sources dispatched in this iteration. */ +int pa_mainloop_iterate(pa_mainloop *m, int block, int *retval); + +/** Run unlimited iterations of the main loop object until the main loop's +quit() routine is called. Returns a negative value on error. Optionally return +the return value as specified with the main loop's quit() routine in the integer +variable retval points to. */ +int pa_mainloop_run(pa_mainloop *m, int *retval); + +/** Return the abstract main loop abstraction layer vtable for this + main loop. No need to free the API as it is owned by the loop + and is destroyed when the loop is freed. */ +pa_mainloop_api* pa_mainloop_get_api(pa_mainloop*m); + +/** Shutdown the main loop with the specified return value */ +void pa_mainloop_quit(pa_mainloop *m, int retval); + +/** Interrupt a running poll (for threaded systems) */ +void pa_mainloop_wakeup(pa_mainloop *m); + +/** Generic prototype of a poll() like function */ +typedef int (*pa_poll_func)(struct pollfd *ufds, unsigned long nfds, int timeout, void*userdata); + +/** Change the poll() implementation */ +void pa_mainloop_set_poll_func(pa_mainloop *m, pa_poll_func poll_func, void *userdata); + +PA_C_DECL_END + +#endif diff --git a/src/pulse/meson.build b/src/pulse/meson.build new file mode 100644 index 0000000..aaebff5 --- /dev/null +++ b/src/pulse/meson.build @@ -0,0 +1,134 @@ +configure_file( + input : 'version.h.in', + output : 'version.h', + configuration : cdata, + install_dir : join_paths(includedir, 'pulse'), +) + +libpulse_sources = [ + 'channelmap.c', + 'context.c', + 'direction.c', + 'error.c', + 'ext-device-manager.c', + 'ext-device-restore.c', + 'ext-stream-restore.c', + 'format.c', + 'internal.h', + 'introspect.c', + 'mainloop-api.c', + 'mainloop-signal.c', + 'mainloop.c', + 'operation.c', + 'proplist.c', + 'rtclock.c', + 'sample.c', + 'scache.c', + 'stream.c', + 'subscribe.c', + 'thread-mainloop.c', + 'timeval.c', + 'utf8.c', + 'util.c', + 'volume.c', + 'xmalloc.c', +] + +libpulse_headers = [ + 'cdecl.h', + 'channelmap.h', + 'context.h', + 'def.h', + 'direction.h', + 'error.h', + 'ext-device-manager.h', + 'ext-device-restore.h', + 'ext-stream-restore.h', + 'format.h', + 'gccmacro.h', + 'introspect.h', + 'mainloop-api.h', + 'mainloop-signal.h', + 'mainloop.h', + 'operation.h', + 'proplist.h', + 'pulseaudio.h', + 'rtclock.h', + 'sample.h', + 'scache.h', + 'stream.h', + 'subscribe.h', + 'thread-mainloop.h', + 'timeval.h', + 'utf8.h', + 'util.h', + 'volume.h', + 'xmalloc.h', +] + +if glib_dep.found() + libpulse_headers += 'glib-mainloop.h' +endif + +versioning_link_args = '-Wl,-version-script=' + join_paths(meson.source_root(), 'src', 'map-file') + +libpulse = shared_library('pulse', + libpulse_sources, + libpulse_headers, + version : libpulse_version, + include_directories : [configinc, topinc], + c_args : [pa_c_args], + link_args : [nodelete_link_args, versioning_link_args], + install : true, + install_rpath : privlibdir, + dependencies : [libm_dep, thread_dep, libpulsecommon_dep, dbus_dep, dl_dep, iconv_dep, libintl_dep], + implicit_include_directories : false) + +libpulse_dep = declare_dependency(link_with: libpulse) + +install_headers( + libpulse_headers, 'simple.h', + subdir : 'pulse' +) + +libpulse_simple = shared_library('pulse-simple', + 'simple.c', + 'simple.h', + version : libpulse_simple_version, + c_args : [pa_c_args], + link_args : [nodelete_link_args, versioning_link_args], + include_directories : [configinc, topinc], + dependencies : [libpulse_dep, libpulsecommon_dep], + install : true, + install_rpath : privlibdir, +) + +libpulse_simple_dep = declare_dependency(link_with: libpulse_simple) + +if glib_dep.found() + libpulse_mainloop_glib = shared_library('pulse-mainloop-glib', + 'glib-mainloop.c', + 'glib-mainloop.h', + version : libpulse_mainloop_glib_version, + c_args : [pa_c_args], + link_args : [nodelete_link_args, versioning_link_args], + include_directories : [configinc, topinc], + dependencies : [libpulse_dep, libpulsecommon_dep, glib_dep], + install : true, + install_rpath : privlibdir, + ) + + libpulse_mainloop_glib_dep = declare_dependency(link_with: libpulse_mainloop_glib) +endif + +# Configuration files + +client_conf = configuration_data() +client_conf.set('PA_BINARY', cdata.get_unquoted('PA_BINARY')) + +client_conf_file = configure_file( + input : 'client.conf.in', + output : 'client.conf', + configuration : client_conf, + install_dir : pulsesysconfdir, +) diff --git a/src/pulse/operation.c b/src/pulse/operation.c new file mode 100644 index 0000000..3f396f0 --- /dev/null +++ b/src/pulse/operation.c @@ -0,0 +1,154 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/xmalloc.h> +#include <pulsecore/macro.h> +#include <pulsecore/flist.h> +#include <pulse/fork-detect.h> + +#include "internal.h" +#include "operation.h" + +PA_STATIC_FLIST_DECLARE(operations, 0, pa_xfree); + +pa_operation *pa_operation_new(pa_context *c, pa_stream *s, pa_operation_cb_t cb, void *userdata) { + pa_operation *o; + pa_assert(c); + + if (!(o = pa_flist_pop(PA_STATIC_FLIST_GET(operations)))) + o = pa_xnew(pa_operation, 1); + + pa_zero(*o); + + PA_REFCNT_INIT(o); + o->context = c; + o->stream = s; + + o->state = PA_OPERATION_RUNNING; + o->callback = cb; + o->userdata = userdata; + + /* Refcounting is strictly one-way: from the "bigger" to the "smaller" object. */ + PA_LLIST_PREPEND(pa_operation, c->operations, o); + pa_operation_ref(o); + + return o; +} + +pa_operation *pa_operation_ref(pa_operation *o) { + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + PA_REFCNT_INC(o); + return o; +} + +void pa_operation_unref(pa_operation *o) { + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (PA_REFCNT_DEC(o) <= 0) { + pa_assert(!o->context); + pa_assert(!o->stream); + + if (pa_flist_push(PA_STATIC_FLIST_GET(operations), o) < 0) + pa_xfree(o); + } +} + +static void operation_unlink(pa_operation *o) { + pa_assert(o); + + if (o->context) { + pa_assert(PA_REFCNT_VALUE(o) >= 2); + + PA_LLIST_REMOVE(pa_operation, o->context->operations, o); + pa_operation_unref(o); + + o->context = NULL; + } + + o->stream = NULL; + o->callback = NULL; + o->userdata = NULL; + o->state_callback = NULL; + o->state_userdata = NULL; +} + +static void operation_set_state(pa_operation *o, pa_operation_state_t st) { + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (st == o->state) + return; + + if ((o->state == PA_OPERATION_DONE) || (o->state == PA_OPERATION_CANCELED)) + return; + + pa_operation_ref(o); + + o->state = st; + + if (o->state_callback) + o->state_callback(o, o->state_userdata); + + if ((o->state == PA_OPERATION_DONE) || (o->state == PA_OPERATION_CANCELED)) + operation_unlink(o); + + pa_operation_unref(o); +} + +void pa_operation_cancel(pa_operation *o) { + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + operation_set_state(o, PA_OPERATION_CANCELED); +} + +void pa_operation_done(pa_operation *o) { + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + operation_set_state(o, PA_OPERATION_DONE); +} + +pa_operation_state_t pa_operation_get_state(const pa_operation *o) { + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + return o->state; +} + +void pa_operation_set_state_callback(pa_operation *o, pa_operation_notify_cb_t cb, void *userdata) { + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (pa_detect_fork()) + return; + + if (o->state == PA_OPERATION_DONE || o->state == PA_OPERATION_CANCELED) + return; + + o->state_callback = cb; + o->state_userdata = userdata; +} diff --git a/src/pulse/operation.h b/src/pulse/operation.h new file mode 100644 index 0000000..302b85b --- /dev/null +++ b/src/pulse/operation.h @@ -0,0 +1,64 @@ +#ifndef foooperationhfoo +#define foooperationhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulse/cdecl.h> +#include <pulse/def.h> +#include <pulse/version.h> + +/** \file + * Asynchronous operations */ + +PA_C_DECL_BEGIN + +/** An asynchronous operation object */ +typedef struct pa_operation pa_operation; + +/** A callback for operation state changes */ +typedef void (*pa_operation_notify_cb_t) (pa_operation *o, void *userdata); + +/** Increase the reference count by one */ +pa_operation *pa_operation_ref(pa_operation *o); + +/** Decrease the reference count by one */ +void pa_operation_unref(pa_operation *o); + +/** Cancel the operation. Beware! This will not necessarily cancel the + * execution of the operation on the server side. However it will make + * sure that the callback associated with this operation will not be + * called anymore, effectively disabling the operation from the client + * side's view. */ +void pa_operation_cancel(pa_operation *o); + +/** Return the current status of the operation */ +pa_operation_state_t pa_operation_get_state(const pa_operation *o); + +/** Set the callback function that is called when the operation state + * changes. Usually this is not necessary, since the functions that + * create pa_operation objects already take a callback that is called + * when the operation finishes. Registering a state change callback is + * mainly useful, if you want to get called back also if the operation + * gets cancelled. \since 4.0 */ +void pa_operation_set_state_callback(pa_operation *o, pa_operation_notify_cb_t cb, void *userdata); + +PA_C_DECL_END + +#endif diff --git a/src/pulse/proplist.c b/src/pulse/proplist.c new file mode 100644 index 0000000..9135115 --- /dev/null +++ b/src/pulse/proplist.c @@ -0,0 +1,704 @@ +/*** + This file is part of PulseAudio. + + Copyright 2007 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <ctype.h> + +#include <pulse/xmalloc.h> +#include <pulse/utf8.h> + +#include <pulsecore/hashmap.h> +#include <pulsecore/strbuf.h> +#include <pulsecore/core-util.h> + +#include "proplist.h" + +struct property { + char *key; + void *value; + size_t nbytes; +}; + +#define MAKE_HASHMAP(p) ((pa_hashmap*) (p)) +#define MAKE_HASHMAP_CONST(p) ((const pa_hashmap*) (p)) +#define MAKE_PROPLIST(p) ((pa_proplist*) (p)) + +int pa_proplist_key_valid(const char *key) { + + if (!pa_ascii_valid(key)) + return 0; + + if (strlen(key) <= 0) + return 0; + + return 1; +} + +static void property_free(struct property *prop) { + pa_assert(prop); + + pa_xfree(prop->key); + pa_xfree(prop->value); + pa_xfree(prop); +} + +pa_proplist* pa_proplist_new(void) { + return MAKE_PROPLIST(pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, (pa_free_cb_t) property_free)); +} + +void pa_proplist_free(pa_proplist* p) { + pa_assert(p); + + pa_hashmap_free(MAKE_HASHMAP(p)); +} + +/** Will accept only valid UTF-8 */ +int pa_proplist_sets(pa_proplist *p, const char *key, const char *value) { + struct property *prop; + bool add = false; + + pa_assert(p); + pa_assert(key); + pa_assert(value); + + if (!pa_proplist_key_valid(key) || !pa_utf8_valid(value)) + return -1; + + if (!(prop = pa_hashmap_get(MAKE_HASHMAP_CONST(p), key))) { + prop = pa_xnew(struct property, 1); + prop->key = pa_xstrdup(key); + add = true; + } else + pa_xfree(prop->value); + + prop->value = pa_xstrdup(value); + prop->nbytes = strlen(value)+1; + + if (add) + pa_hashmap_put(MAKE_HASHMAP(p), prop->key, prop); + + return 0; +} + +/** Will accept only valid UTF-8 */ +static int proplist_setn(pa_proplist *p, const char *key, size_t key_length, const char *value, size_t value_length) { + struct property *prop; + bool add = false; + char *k, *v; + + pa_assert(p); + pa_assert(key); + pa_assert(value); + + k = pa_xstrndup(key, key_length); + v = pa_xstrndup(value, value_length); + + if (!pa_proplist_key_valid(k) || !pa_utf8_valid(v)) { + pa_xfree(k); + pa_xfree(v); + return -1; + } + + if (!(prop = pa_hashmap_get(MAKE_HASHMAP_CONST(p), k))) { + prop = pa_xnew(struct property, 1); + prop->key = k; + add = true; + } else { + pa_xfree(prop->value); + pa_xfree(k); + } + + prop->value = v; + prop->nbytes = strlen(v)+1; + + if (add) + pa_hashmap_put(MAKE_HASHMAP(p), prop->key, prop); + + return 0; +} + +/** Will accept only valid UTF-8 */ +int pa_proplist_setp(pa_proplist *p, const char *pair) { + const char *t; + + pa_assert(p); + pa_assert(pair); + + if (!(t = strchr(pair, '='))) + return -1; + + return proplist_setn(p, + pair, t - pair, + t + 1, strchr(pair, 0) - t - 1); +} + +static int proplist_sethex(pa_proplist *p, const char *key, size_t key_length, const char *value, size_t value_length) { + struct property *prop; + bool add = false; + char *k, *v; + uint8_t *d; + size_t dn; + + pa_assert(p); + pa_assert(key); + pa_assert(value); + + k = pa_xstrndup(key, key_length); + + if (!pa_proplist_key_valid(k)) { + pa_xfree(k); + return -1; + } + + v = pa_xstrndup(value, value_length); + d = pa_xmalloc(value_length*2+1); + + if ((dn = pa_parsehex(v, d, value_length*2)) == (size_t) -1) { + pa_xfree(k); + pa_xfree(v); + pa_xfree(d); + return -1; + } + + pa_xfree(v); + + if (!(prop = pa_hashmap_get(MAKE_HASHMAP_CONST(p), k))) { + prop = pa_xnew(struct property, 1); + prop->key = k; + add = true; + } else { + pa_xfree(prop->value); + pa_xfree(k); + } + + d[dn] = 0; + prop->value = d; + prop->nbytes = dn; + + if (add) + pa_hashmap_put(MAKE_HASHMAP(p), prop->key, prop); + + return 0; +} + +/** Will accept only valid UTF-8 */ +int pa_proplist_setf(pa_proplist *p, const char *key, const char *format, ...) { + struct property *prop; + bool add = false; + va_list ap; + char *v; + + pa_assert(p); + pa_assert(key); + pa_assert(format); + + if (!pa_proplist_key_valid(key) || !pa_utf8_valid(format)) + return -1; + + va_start(ap, format); + v = pa_vsprintf_malloc(format, ap); + va_end(ap); + + if (!pa_utf8_valid(v)) + goto fail; + + if (!(prop = pa_hashmap_get(MAKE_HASHMAP_CONST(p), key))) { + prop = pa_xnew(struct property, 1); + prop->key = pa_xstrdup(key); + add = true; + } else + pa_xfree(prop->value); + + prop->value = v; + prop->nbytes = strlen(v)+1; + + if (add) + pa_hashmap_put(MAKE_HASHMAP(p), prop->key, prop); + + return 0; + +fail: + pa_xfree(v); + return -1; +} + +int pa_proplist_set(pa_proplist *p, const char *key, const void *data, size_t nbytes) { + struct property *prop; + bool add = false; + + pa_assert(p); + pa_assert(key); + pa_assert(data || nbytes == 0); + + if (!pa_proplist_key_valid(key)) + return -1; + + if (!(prop = pa_hashmap_get(MAKE_HASHMAP_CONST(p), key))) { + prop = pa_xnew(struct property, 1); + prop->key = pa_xstrdup(key); + add = true; + } else + pa_xfree(prop->value); + + prop->value = pa_xmalloc(nbytes+1); + if (nbytes > 0) + memcpy(prop->value, data, nbytes); + ((char*) prop->value)[nbytes] = 0; + prop->nbytes = nbytes; + + if (add) + pa_hashmap_put(MAKE_HASHMAP(p), prop->key, prop); + + return 0; +} + +const char *pa_proplist_gets(const pa_proplist *p, const char *key) { + struct property *prop; + + pa_assert(p); + pa_assert(key); + + if (!pa_proplist_key_valid(key)) + return NULL; + + if (!(prop = pa_hashmap_get(MAKE_HASHMAP_CONST(p), key))) + return NULL; + + if (prop->nbytes <= 0) + return NULL; + + if (((char*) prop->value)[prop->nbytes-1] != 0) + return NULL; + + if (strlen((char*) prop->value) != prop->nbytes-1) + return NULL; + + if (!pa_utf8_valid((char*) prop->value)) + return NULL; + + return (char*) prop->value; +} + +int pa_proplist_get(const pa_proplist *p, const char *key, const void **data, size_t *nbytes) { + struct property *prop; + + pa_assert(p); + pa_assert(key); + pa_assert(data); + pa_assert(nbytes); + + if (!pa_proplist_key_valid(key)) + return -1; + + if (!(prop = pa_hashmap_get(MAKE_HASHMAP_CONST(p), key))) + return -1; + + *data = prop->value; + *nbytes = prop->nbytes; + + return 0; +} + +void pa_proplist_update(pa_proplist *p, pa_update_mode_t mode, const pa_proplist *other) { + struct property *prop; + void *state = NULL; + + pa_assert(p); + pa_assert(mode == PA_UPDATE_SET || mode == PA_UPDATE_MERGE || mode == PA_UPDATE_REPLACE); + pa_assert(other); + + if (mode == PA_UPDATE_SET) + pa_proplist_clear(p); + + while ((prop = pa_hashmap_iterate(MAKE_HASHMAP_CONST(other), &state, NULL))) { + + if (mode == PA_UPDATE_MERGE && pa_proplist_contains(p, prop->key)) + continue; + + pa_assert_se(pa_proplist_set(p, prop->key, prop->value, prop->nbytes) == 0); + } +} + +int pa_proplist_unset(pa_proplist *p, const char *key) { + pa_assert(p); + pa_assert(key); + + if (!pa_proplist_key_valid(key)) + return -1; + + if (pa_hashmap_remove_and_free(MAKE_HASHMAP(p), key) < 0) + return -2; + + return 0; +} + +int pa_proplist_unset_many(pa_proplist *p, const char * const keys[]) { + const char * const * k; + int n = 0; + + pa_assert(p); + pa_assert(keys); + + for (k = keys; *k; k++) + if (!pa_proplist_key_valid(*k)) + return -1; + + for (k = keys; *k; k++) + if (pa_proplist_unset(p, *k) >= 0) + n++; + + return n; +} + +const char *pa_proplist_iterate(const pa_proplist *p, void **state) { + struct property *prop; + + if (!(prop = pa_hashmap_iterate(MAKE_HASHMAP_CONST(p), state, NULL))) + return NULL; + + return prop->key; +} + +char *pa_proplist_to_string_sep(const pa_proplist *p, const char *sep) { + const char *key; + void *state = NULL; + pa_strbuf *buf; + + pa_assert(p); + pa_assert(sep); + + buf = pa_strbuf_new(); + + while ((key = pa_proplist_iterate(p, &state))) { + const char *v; + + if (!pa_strbuf_isempty(buf)) + pa_strbuf_puts(buf, sep); + + if ((v = pa_proplist_gets(p, key))) { + const char *t; + + pa_strbuf_printf(buf, "%s = \"", key); + + for (t = v;;) { + size_t h; + + h = strcspn(t, "\""); + + if (h > 0) + pa_strbuf_putsn(buf, t, h); + + t += h; + + if (*t == 0) + break; + + pa_assert(*t == '"'); + pa_strbuf_puts(buf, "\\\""); + + t++; + } + + pa_strbuf_puts(buf, "\""); + } else { + const void *value; + size_t nbytes; + char *c; + + pa_assert_se(pa_proplist_get(p, key, &value, &nbytes) == 0); + c = pa_xmalloc(nbytes*2+1); + pa_hexstr((const uint8_t*) value, nbytes, c, nbytes*2+1); + + pa_strbuf_printf(buf, "%s = hex:%s", key, c); + pa_xfree(c); + } + } + + return pa_strbuf_to_string_free(buf); +} + +char *pa_proplist_to_string(const pa_proplist *p) { + char *s, *t; + + s = pa_proplist_to_string_sep(p, "\n"); + t = pa_sprintf_malloc("%s\n", s); + pa_xfree(s); + + return t; +} + +pa_proplist *pa_proplist_from_string(const char *s) { + enum { + WHITESPACE, + KEY, + AFTER_KEY, + VALUE_START, + VALUE_SIMPLE, + VALUE_DOUBLE_QUOTES, + VALUE_DOUBLE_QUOTES_ESCAPE, + VALUE_TICKS, + VALUE_TICKS_ESCAPED, + VALUE_HEX + } state; + + pa_proplist *pl; + const char *p, *key = NULL, *value = NULL; + size_t key_len = 0, value_len = 0; + + pa_assert(s); + + pl = pa_proplist_new(); + + state = WHITESPACE; + + for (p = s;; p++) { + switch (state) { + + case WHITESPACE: + if (*p == 0) + goto success; + else if (*p == '=') + goto fail; + else if (!isspace((unsigned char)*p)) { + key = p; + state = KEY; + key_len = 1; + } + break; + + case KEY: + if (*p == 0) + goto fail; + else if (*p == '=') + state = VALUE_START; + else if (isspace((unsigned char)*p)) + state = AFTER_KEY; + else + key_len++; + break; + + case AFTER_KEY: + if (*p == 0) + goto fail; + else if (*p == '=') + state = VALUE_START; + else if (!isspace((unsigned char)*p)) + goto fail; + break; + + case VALUE_START: + if (*p == 0) + goto fail; + else if (strncmp(p, "hex:", 4) == 0) { + state = VALUE_HEX; + value = p+4; + value_len = 0; + p += 3; + } else if (*p == '\'') { + state = VALUE_TICKS; + value = p+1; + value_len = 0; + } else if (*p == '"') { + state = VALUE_DOUBLE_QUOTES; + value = p+1; + value_len = 0; + } else if (!isspace((unsigned char)*p)) { + state = VALUE_SIMPLE; + value = p; + value_len = 1; + } + break; + + case VALUE_SIMPLE: + if (*p == 0 || isspace((unsigned char)*p)) { + if (proplist_setn(pl, key, key_len, value, value_len) < 0) + goto fail; + + if (*p == 0) + goto success; + + state = WHITESPACE; + } else + value_len++; + break; + + case VALUE_DOUBLE_QUOTES: + if (*p == 0) + goto fail; + else if (*p == '"') { + char *v; + + v = pa_unescape(pa_xstrndup(value, value_len)); + + if (proplist_setn(pl, key, key_len, v, strlen(v)) < 0) { + pa_xfree(v); + goto fail; + } + + pa_xfree(v); + state = WHITESPACE; + } else if (*p == '\\') { + state = VALUE_DOUBLE_QUOTES_ESCAPE; + value_len++; + } else + value_len++; + break; + + case VALUE_DOUBLE_QUOTES_ESCAPE: + if (*p == 0) + goto fail; + else { + state = VALUE_DOUBLE_QUOTES; + value_len++; + } + break; + + case VALUE_TICKS: + if (*p == 0) + goto fail; + else if (*p == '\'') { + char *v; + + v = pa_unescape(pa_xstrndup(value, value_len)); + + if (proplist_setn(pl, key, key_len, v, strlen(v)) < 0) { + pa_xfree(v); + goto fail; + } + + pa_xfree(v); + state = WHITESPACE; + } else if (*p == '\\') { + state = VALUE_TICKS_ESCAPED; + value_len++; + } else + value_len++; + break; + + case VALUE_TICKS_ESCAPED: + if (*p == 0) + goto fail; + else { + state = VALUE_TICKS; + value_len++; + } + break; + + case VALUE_HEX: + if ((*p >= '0' && *p <= '9') || + (*p >= 'A' && *p <= 'F') || + (*p >= 'a' && *p <= 'f')) { + value_len++; + } else if (*p == 0 || isspace((unsigned char)*p)) { + + if (proplist_sethex(pl, key, key_len, value, value_len) < 0) + goto fail; + + if (*p == 0) + goto success; + + state = WHITESPACE; + } else + goto fail; + break; + } + } + +success: + return MAKE_PROPLIST(pl); + +fail: + pa_proplist_free(pl); + return NULL; +} + +int pa_proplist_contains(const pa_proplist *p, const char *key) { + pa_assert(p); + pa_assert(key); + + if (!pa_proplist_key_valid(key)) + return -1; + + if (!(pa_hashmap_get(MAKE_HASHMAP_CONST(p), key))) + return 0; + + return 1; +} + +void pa_proplist_clear(pa_proplist *p) { + pa_assert(p); + + pa_hashmap_remove_all(MAKE_HASHMAP(p)); +} + +pa_proplist* pa_proplist_copy(const pa_proplist *p) { + pa_proplist *copy; + + pa_assert_se(copy = pa_proplist_new()); + + if (p) + pa_proplist_update(copy, PA_UPDATE_REPLACE, p); + + return copy; +} + +unsigned pa_proplist_size(const pa_proplist *p) { + pa_assert(p); + + return pa_hashmap_size(MAKE_HASHMAP_CONST(p)); +} + +int pa_proplist_isempty(const pa_proplist *p) { + pa_assert(p); + + return pa_hashmap_isempty(MAKE_HASHMAP_CONST(p)); +} + +int pa_proplist_equal(const pa_proplist *a, const pa_proplist *b) { + const void *key = NULL; + struct property *a_prop = NULL; + struct property *b_prop = NULL; + void *state = NULL; + + pa_assert(a); + pa_assert(b); + + if (a == b) + return 1; + + if (pa_proplist_size(a) != pa_proplist_size(b)) + return 0; + + while ((a_prop = pa_hashmap_iterate(MAKE_HASHMAP_CONST(a), &state, &key))) { + if (!(b_prop = pa_hashmap_get(MAKE_HASHMAP_CONST(b), key))) + return 0; + + if (a_prop->nbytes != b_prop->nbytes) + return 0; + + if (memcmp(a_prop->value, b_prop->value, a_prop->nbytes) != 0) + return 0; + } + + return 1; +} diff --git a/src/pulse/proplist.h b/src/pulse/proplist.h new file mode 100644 index 0000000..e50518b --- /dev/null +++ b/src/pulse/proplist.h @@ -0,0 +1,411 @@ +#ifndef foopulseproplisthfoo +#define foopulseproplisthfoo + +/*** + This file is part of PulseAudio. + + Copyright 2007 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/types.h> + +#include <pulse/cdecl.h> +#include <pulse/gccmacro.h> +#include <pulse/version.h> + +/** \file + * Property list constants and functions */ + +PA_C_DECL_BEGIN + +/** For streams: localized media name, formatted as UTF-8. E.g. "Guns'N'Roses: Civil War".*/ +#define PA_PROP_MEDIA_NAME "media.name" + +/** For streams: localized media title if applicable, formatted as UTF-8. E.g. "Civil War" */ +#define PA_PROP_MEDIA_TITLE "media.title" + +/** For streams: localized media artist if applicable, formatted as UTF-8. E.g. "Guns'N'Roses" */ +#define PA_PROP_MEDIA_ARTIST "media.artist" + +/** For streams: localized media copyright string if applicable, formatted as UTF-8. E.g. "Evil Record Corp." */ +#define PA_PROP_MEDIA_COPYRIGHT "media.copyright" + +/** For streams: localized media generator software string if applicable, formatted as UTF-8. E.g. "Foocrop AudioFrobnicator" */ +#define PA_PROP_MEDIA_SOFTWARE "media.software" + +/** For streams: media language if applicable, in standard POSIX format. E.g. "de_DE" */ +#define PA_PROP_MEDIA_LANGUAGE "media.language" + +/** For streams: source filename if applicable, in URI format or local path. E.g. "/home/lennart/music/foobar.ogg" */ +#define PA_PROP_MEDIA_FILENAME "media.filename" + +/** \cond fulldocs */ +/** For streams: icon for the media. A binary blob containing PNG image data */ +#define PA_PROP_MEDIA_ICON "media.icon" +/** \endcond */ + +/** For streams: an XDG icon name for the media. E.g. "audio-x-mp3" */ +#define PA_PROP_MEDIA_ICON_NAME "media.icon_name" + +/** For streams: logic role of this media. One of the strings "video", "music", "game", "event", "phone", "animation", "production", "a11y", "test" */ +#define PA_PROP_MEDIA_ROLE "media.role" + +/** For streams: the name of a filter that is desired, e.g.\ "echo-cancel" or "equalizer-sink". PulseAudio may choose to not apply the filter if it does not make sense (for example, applying echo-cancellation on a Bluetooth headset probably does not make sense. \since 1.0 */ +#define PA_PROP_FILTER_WANT "filter.want" + +/** For streams: the name of a filter that is desired, e.g.\ "echo-cancel" or "equalizer-sink". Differs from PA_PROP_FILTER_WANT in that it forces PulseAudio to apply the filter, regardless of whether PulseAudio thinks it makes sense to do so or not. If this is set, PA_PROP_FILTER_WANT is ignored. In other words, you almost certainly do not want to use this. \since 1.0 */ +#define PA_PROP_FILTER_APPLY "filter.apply" + +/** For streams: the name of a filter that should specifically suppressed (i.e.\ overrides PA_PROP_FILTER_WANT). Useful for the times that PA_PROP_FILTER_WANT is automatically added (e.g. echo-cancellation for phone streams when $VOIP_APP does its own, internal AEC) \since 1.0 */ +#define PA_PROP_FILTER_SUPPRESS "filter.suppress" + +/** For event sound streams: XDG event sound name. e.g.\ "message-new-email" (Event sound streams are those with media.role set to "event") */ +#define PA_PROP_EVENT_ID "event.id" + +/** For event sound streams: localized human readable one-line description of the event, formatted as UTF-8. E.g. "Email from lennart@example.com received." */ +#define PA_PROP_EVENT_DESCRIPTION "event.description" + +/** For event sound streams: absolute horizontal mouse position on the screen if the event sound was triggered by a mouse click, integer formatted as text string. E.g. "865" */ +#define PA_PROP_EVENT_MOUSE_X "event.mouse.x" + +/** For event sound streams: absolute vertical mouse position on the screen if the event sound was triggered by a mouse click, integer formatted as text string. E.g. "432" */ +#define PA_PROP_EVENT_MOUSE_Y "event.mouse.y" + +/** For event sound streams: relative horizontal mouse position on the screen if the event sound was triggered by a mouse click, float formatted as text string, ranging from 0.0 (left side of the screen) to 1.0 (right side of the screen). E.g. "0.65" */ +#define PA_PROP_EVENT_MOUSE_HPOS "event.mouse.hpos" + +/** For event sound streams: relative vertical mouse position on the screen if the event sound was triggered by a mouse click, float formatted as text string, ranging from 0.0 (top of the screen) to 1.0 (bottom of the screen). E.g. "0.43" */ +#define PA_PROP_EVENT_MOUSE_VPOS "event.mouse.vpos" + +/** For event sound streams: mouse button that triggered the event if applicable, integer formatted as string with 0=left, 1=middle, 2=right. E.g. "0" */ +#define PA_PROP_EVENT_MOUSE_BUTTON "event.mouse.button" + +/** For streams that belong to a window on the screen: localized window title. E.g. "Totem Music Player" */ +#define PA_PROP_WINDOW_NAME "window.name" + +/** For streams that belong to a window on the screen: a textual id for identifying a window logically. E.g. "org.gnome.Totem.MainWindow" */ +#define PA_PROP_WINDOW_ID "window.id" + +/** \cond fulldocs */ +/** For streams that belong to a window on the screen: window icon. A binary blob containing PNG image data */ +#define PA_PROP_WINDOW_ICON "window.icon" +/** \endcond */ + +/** For streams that belong to a window on the screen: an XDG icon name for the window. E.g. "totem" */ +#define PA_PROP_WINDOW_ICON_NAME "window.icon_name" + +/** For streams that belong to a window on the screen: absolute horizontal window position on the screen, integer formatted as text string. E.g. "865". \since 0.9.17 */ +#define PA_PROP_WINDOW_X "window.x" + +/** For streams that belong to a window on the screen: absolute vertical window position on the screen, integer formatted as text string. E.g. "343". \since 0.9.17 */ +#define PA_PROP_WINDOW_Y "window.y" + +/** For streams that belong to a window on the screen: window width on the screen, integer formatted as text string. e.g. "365". \since 0.9.17 */ +#define PA_PROP_WINDOW_WIDTH "window.width" + +/** For streams that belong to a window on the screen: window height on the screen, integer formatted as text string. E.g. "643". \since 0.9.17 */ +#define PA_PROP_WINDOW_HEIGHT "window.height" + +/** For streams that belong to a window on the screen: relative position of the window center on the screen, float formatted as text string, ranging from 0.0 (left side of the screen) to 1.0 (right side of the screen). E.g. "0.65". \since 0.9.17 */ +#define PA_PROP_WINDOW_HPOS "window.hpos" + +/** For streams that belong to a window on the screen: relative position of the window center on the screen, float formatted as text string, ranging from 0.0 (top of the screen) to 1.0 (bottom of the screen). E.g. "0.43". \since 0.9.17 */ +#define PA_PROP_WINDOW_VPOS "window.vpos" + +/** For streams that belong to a window on the screen: if the windowing system supports multiple desktops, a comma separated list of indexes of the desktops this window is visible on. If this property is an empty string, it is visible on all desktops (i.e. 'sticky'). The first desktop is 0. E.g. "0,2,3" \since 0.9.18 */ +#define PA_PROP_WINDOW_DESKTOP "window.desktop" + +/** For streams that belong to an X11 window on the screen: the X11 display string. E.g. ":0.0" */ +#define PA_PROP_WINDOW_X11_DISPLAY "window.x11.display" + +/** For streams that belong to an X11 window on the screen: the X11 screen the window is on, an integer formatted as string. E.g. "0" */ +#define PA_PROP_WINDOW_X11_SCREEN "window.x11.screen" + +/** For streams that belong to an X11 window on the screen: the X11 monitor the window is on, an integer formatted as string. E.g. "0" */ +#define PA_PROP_WINDOW_X11_MONITOR "window.x11.monitor" + +/** For streams that belong to an X11 window on the screen: the window XID, an integer formatted as string. E.g. "25632" */ +#define PA_PROP_WINDOW_X11_XID "window.x11.xid" + +/** For clients/streams: localized human readable application name. E.g. "Totem Music Player" */ +#define PA_PROP_APPLICATION_NAME "application.name" + +/** For clients/streams: a textual id for identifying an application logically. E.g. "org.gnome.Totem" */ +#define PA_PROP_APPLICATION_ID "application.id" + +/** For clients/streams: a version string, e.g.\ "0.6.88" */ +#define PA_PROP_APPLICATION_VERSION "application.version" + +/** \cond fulldocs */ +/** For clients/streams: application icon. A binary blob containing PNG image data */ +#define PA_PROP_APPLICATION_ICON "application.icon" +/** \endcond */ + +/** For clients/streams: an XDG icon name for the application. E.g. "totem" */ +#define PA_PROP_APPLICATION_ICON_NAME "application.icon_name" + +/** For clients/streams: application language if applicable, in standard POSIX format. E.g. "de_DE" */ +#define PA_PROP_APPLICATION_LANGUAGE "application.language" + +/** For clients/streams on UNIX: application process PID, an integer formatted as string. E.g. "4711" */ +#define PA_PROP_APPLICATION_PROCESS_ID "application.process.id" + +/** For clients/streams: application process name. E.g. "totem" */ +#define PA_PROP_APPLICATION_PROCESS_BINARY "application.process.binary" + +/** For clients/streams: application user name. E.g. "lennart" */ +#define PA_PROP_APPLICATION_PROCESS_USER "application.process.user" + +/** For clients/streams: host name the application runs on. E.g. "omega" */ +#define PA_PROP_APPLICATION_PROCESS_HOST "application.process.host" + +/** For clients/streams: the D-Bus host id the application runs on. E.g. "543679e7b01393ed3e3e650047d78f6e" */ +#define PA_PROP_APPLICATION_PROCESS_MACHINE_ID "application.process.machine_id" + +/** For clients/streams: an id for the login session the application runs in. On Unix the value of $XDG_SESSION_ID. E.g. "5" */ +#define PA_PROP_APPLICATION_PROCESS_SESSION_ID "application.process.session_id" + +/** For devices: device string in the underlying audio layer's format. E.g. "surround51:0" */ +#define PA_PROP_DEVICE_STRING "device.string" + +/** For devices: API this device is access with. E.g. "alsa" */ +#define PA_PROP_DEVICE_API "device.api" + +/** For devices: localized human readable device one-line description. E.g. "Foobar Industries USB Headset 2000+ Ultra" */ +#define PA_PROP_DEVICE_DESCRIPTION "device.description" + +/** For devices: bus path to the device in the OS' format. E.g. "/sys/bus/pci/devices/0000:00:1f.2" */ +#define PA_PROP_DEVICE_BUS_PATH "device.bus_path" + +/** For devices: serial number if applicable. E.g. "4711-0815-1234" */ +#define PA_PROP_DEVICE_SERIAL "device.serial" + +/** For devices: vendor ID if applicable. E.g. 1274 */ +#define PA_PROP_DEVICE_VENDOR_ID "device.vendor.id" + +/** For devices: vendor name if applicable. E.g. "Foocorp Heavy Industries" */ +#define PA_PROP_DEVICE_VENDOR_NAME "device.vendor.name" + +/** For devices: product ID if applicable. E.g. 4565 */ +#define PA_PROP_DEVICE_PRODUCT_ID "device.product.id" + +/** For devices: product name if applicable. E.g. "SuperSpeakers 2000 Pro" */ +#define PA_PROP_DEVICE_PRODUCT_NAME "device.product.name" + +/** For devices: device class. One of "sound", "modem", "monitor", "filter" */ +#define PA_PROP_DEVICE_CLASS "device.class" + +/** For devices: form factor if applicable. One of "internal", "speaker", "handset", "tv", "webcam", "microphone", "headset", "headphone", "hands-free", "car", "hifi", "computer", "portable" */ +#define PA_PROP_DEVICE_FORM_FACTOR "device.form_factor" + +/** For devices: bus of the device if applicable. One of "isa", "pci", "usb", "firewire", "bluetooth" */ +#define PA_PROP_DEVICE_BUS "device.bus" + +/** \cond fulldocs */ +/** For devices: icon for the device. A binary blob containing PNG image data */ +#define PA_PROP_DEVICE_ICON "device.icon" +/** \endcond */ + +/** For devices: an XDG icon name for the device. E.g. "sound-card-speakers-usb" */ +#define PA_PROP_DEVICE_ICON_NAME "device.icon_name" + +/** For devices: access mode of the device if applicable. One of "mmap", "mmap_rewrite", "serial" */ +#define PA_PROP_DEVICE_ACCESS_MODE "device.access_mode" + +/** For filter devices: master device id if applicable. */ +#define PA_PROP_DEVICE_MASTER_DEVICE "device.master_device" + +/** For devices: buffer size in bytes, integer formatted as string. */ +#define PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE "device.buffering.buffer_size" + +/** For devices: fragment size in bytes, integer formatted as string. */ +#define PA_PROP_DEVICE_BUFFERING_FRAGMENT_SIZE "device.buffering.fragment_size" + +/** For devices: profile identifier for the profile this devices is in. E.g. "analog-stereo", "analog-surround-40", "iec958-stereo", ...*/ +#define PA_PROP_DEVICE_PROFILE_NAME "device.profile.name" + +/** For devices: intended use. A space separated list of roles (see PA_PROP_MEDIA_ROLE) this device is particularly well suited for, due to latency, quality or form factor. \since 0.9.16 */ +#define PA_PROP_DEVICE_INTENDED_ROLES "device.intended_roles" + +/** For devices: human readable one-line description of the profile this device is in. E.g. "Analog Stereo", ... */ +#define PA_PROP_DEVICE_PROFILE_DESCRIPTION "device.profile.description" + +/** For modules: the author's name, formatted as UTF-8 string. E.g. "Lennart Poettering" */ +#define PA_PROP_MODULE_AUTHOR "module.author" + +/** For modules: a human readable one-line description of the module's purpose formatted as UTF-8. E.g. "Frobnicate sounds with a flux compensator" */ +#define PA_PROP_MODULE_DESCRIPTION "module.description" + +/** For modules: a human readable usage description of the module's arguments formatted as UTF-8. */ +#define PA_PROP_MODULE_USAGE "module.usage" + +/** For modules: a version string for the module. E.g. "0.9.15" */ +#define PA_PROP_MODULE_VERSION "module.version" + +/** For PCM formats: the sample format used as returned by pa_sample_format_to_string() \since 1.0 */ +#define PA_PROP_FORMAT_SAMPLE_FORMAT "format.sample_format" + +/** For all formats: the sample rate (unsigned integer) \since 1.0 */ +#define PA_PROP_FORMAT_RATE "format.rate" + +/** For all formats: the number of channels (unsigned integer) \since 1.0 */ +#define PA_PROP_FORMAT_CHANNELS "format.channels" + +/** For PCM formats: the channel map of the stream as returned by pa_channel_map_snprint() \since 1.0 */ +#define PA_PROP_FORMAT_CHANNEL_MAP "format.channel_map" + +/** A property list object. Basically a dictionary with ASCII strings + * as keys and arbitrary data as values. \since 0.9.11 */ +typedef struct pa_proplist pa_proplist; + +/** Allocate a property list. Free with pa_proplist_free. \since 0.9.11 */ +pa_proplist* pa_proplist_new(void); + +/** Free the property list. \since 0.9.11 */ +void pa_proplist_free(pa_proplist* p); + +/** Returns a non-zero value if the key is valid. \since 3.0 */ +int pa_proplist_key_valid(const char *key); + +/** Append a new string entry to the property list, possibly + * overwriting an already existing entry with the same key. An + * internal copy of the data passed is made. Will accept only valid + * UTF-8. Returns zero on success. \since 0.9.11 */ +int pa_proplist_sets(pa_proplist *p, const char *key, const char *value); + +/** Append a new string entry to the property list, possibly + * overwriting an already existing entry with the same key. An + * internal copy of the data passed is made. Will accept only valid + * UTF-8. The string passed in must contain a '='. Left hand side of + * the '=' is used as key name, the right hand side as string + * data. Returns zero on success. \since 0.9.16 */ +int pa_proplist_setp(pa_proplist *p, const char *pair); + +/** Append a new string entry to the property list, possibly + * overwriting an already existing entry with the same key. An + * internal copy of the data passed is made. Will accept only valid + * UTF-8. The data can be passed as printf()-style format string with + * arguments. Returns zero on success. \since 0.9.11 */ +int pa_proplist_setf(pa_proplist *p, const char *key, const char *format, ...) PA_GCC_PRINTF_ATTR(3,4); + +/** Append a new arbitrary data entry to the property list, possibly + * overwriting an already existing entry with the same key. An + * internal copy of the data passed is made. + * Returns zero on success. \since 0.9.11 */ +int pa_proplist_set(pa_proplist *p, const char *key, const void *data, size_t nbytes); + +/** Return a string entry for the specified key. Will return NULL if + * the data is not valid UTF-8. Will return a NUL-terminated string in + * an internally allocated buffer. The caller should make a copy of + * the data before accessing the property list again. \since 0.9.11 */ +const char *pa_proplist_gets(const pa_proplist *p, const char *key); + +/** Store the value for the specified key in \a data. Will store a + * NUL-terminated string for string entries. The \a data pointer returned will + * point to an internally allocated buffer. The caller should make a + * copy of the data before the property list is accessed again. + * Returns zero on success, negative on error. \since 0.9.11 */ +int pa_proplist_get(const pa_proplist *p, const char *key, const void **data, size_t *nbytes); + +/** Update mode enum for pa_proplist_update(). \since 0.9.11 */ +typedef enum pa_update_mode { + PA_UPDATE_SET + /**< Replace the entire property list with the new one. Don't keep + * any of the old data around. */, + + PA_UPDATE_MERGE + /**< Merge new property list into the existing one, not replacing + * any old entries if they share a common key with the new + * property list. */, + + PA_UPDATE_REPLACE + /**< Merge new property list into the existing one, replacing all + * old entries that share a common key with the new property + * list. */ +} pa_update_mode_t; + +/** \cond fulldocs */ +#define PA_UPDATE_SET PA_UPDATE_SET +#define PA_UPDATE_MERGE PA_UPDATE_MERGE +#define PA_UPDATE_REPLACE PA_UPDATE_REPLACE +/** \endcond */ + +/** Merge property list "other" into "p", adhering the merge mode as + * specified in "mode". \since 0.9.11 */ +void pa_proplist_update(pa_proplist *p, pa_update_mode_t mode, const pa_proplist *other); + +/** Removes a single entry from the property list, identified be the + * specified key name. Returns zero on success, negative on error. + * \since 0.9.11 */ +int pa_proplist_unset(pa_proplist *p, const char *key); + +/** Similar to pa_proplist_unset() but takes an array of keys to + * remove. The array should be terminated by a NULL pointer. Returns -1 + * on failure, otherwise the number of entries actually removed (which + * might even be 0, if there were no matching entries to + * remove). \since 0.9.11 */ +int pa_proplist_unset_many(pa_proplist *p, const char * const keys[]); + +/** Iterate through the property list. The user should allocate a + * state variable of type void* and initialize it with NULL. A pointer + * to this variable should then be passed to pa_proplist_iterate() + * which should be called in a loop until it returns NULL which + * signifies EOL. The property list should not be modified during + * iteration through the list -- with the exception of deleting the + * current entry. On each invocation this function will return the + * key string for the next entry. The keys in the property list do not + * have any particular order. \since 0.9.11 */ +const char *pa_proplist_iterate(const pa_proplist *p, void **state); + +/** Format the property list nicely as a human readable string. This + * works very much like pa_proplist_to_string_sep() and uses a newline + * as separator and appends one final one. Call pa_xfree() on the + * result. \since 0.9.11 */ +char *pa_proplist_to_string(const pa_proplist *p); + +/** Format the property list nicely as a human readable string and + * choose the separator. Call pa_xfree() on the result. \since + * 0.9.15 */ +char *pa_proplist_to_string_sep(const pa_proplist *p, const char *sep); + +/** Allocate a new property list and assign key/value from a human + * readable string. \since 0.9.15 */ +pa_proplist *pa_proplist_from_string(const char *str); + +/** Returns 1 if an entry for the specified key exists in the + * property list. Returns negative on error. \since 0.9.11 */ +int pa_proplist_contains(const pa_proplist *p, const char *key); + +/** Remove all entries from the property list object. \since 0.9.11 */ +void pa_proplist_clear(pa_proplist *p); + +/** Allocate a new property list and copy over every single entry from + * the specified list. \since 0.9.11 */ +pa_proplist* pa_proplist_copy(const pa_proplist *p); + +/** Return the number of entries in the property list. \since 0.9.15 */ +unsigned pa_proplist_size(const pa_proplist *p); + +/** Returns 0 when the proplist is empty, positive otherwise \since 0.9.15 */ +int pa_proplist_isempty(const pa_proplist *p); + +/** Return non-zero when a and b have the same keys and values. + * \since 0.9.16 */ +int pa_proplist_equal(const pa_proplist *a, const pa_proplist *b); + +PA_C_DECL_END + +#endif diff --git a/src/pulse/pulseaudio.h b/src/pulse/pulseaudio.h new file mode 100644 index 0000000..e9fec55 --- /dev/null +++ b/src/pulse/pulseaudio.h @@ -0,0 +1,180 @@ +#ifndef foopulseaudiohfoo +#define foopulseaudiohfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulse/direction.h> +#include <pulse/mainloop-api.h> +#include <pulse/sample.h> +#include <pulse/format.h> +#include <pulse/def.h> +#include <pulse/context.h> +#include <pulse/stream.h> +#include <pulse/introspect.h> +#include <pulse/subscribe.h> +#include <pulse/scache.h> +#include <pulse/version.h> +#include <pulse/error.h> +#include <pulse/operation.h> +#include <pulse/channelmap.h> +#include <pulse/volume.h> +#include <pulse/xmalloc.h> +#include <pulse/utf8.h> +#include <pulse/thread-mainloop.h> +#include <pulse/mainloop.h> +#include <pulse/mainloop-signal.h> +#include <pulse/util.h> +#include <pulse/timeval.h> +#include <pulse/proplist.h> +#include <pulse/rtclock.h> + +/** \file + * Include all libpulse header files at once. The following files are + * included: \ref direction.h, \ref mainloop-api.h, \ref sample.h, \ref def.h, + * \ref context.h, \ref stream.h, \ref introspect.h, \ref subscribe.h, \ref + * scache.h, \ref version.h, \ref error.h, \ref channelmap.h, \ref + * operation.h,\ref volume.h, \ref xmalloc.h, \ref utf8.h, \ref + * thread-mainloop.h, \ref mainloop.h, \ref util.h, \ref proplist.h, + * \ref timeval.h, \ref rtclock.h and \ref mainloop-signal.h at + * once */ + +/** \mainpage + * + * \section intro_sec Introduction + * + * This document describes the client API for the PulseAudio sound + * server. The API comes in two flavours to accommodate different styles + * of applications and different needs in complexity: + * + * \li The complete but somewhat complicated to use asynchronous API + * \li The simplified, easy to use, but limited synchronous API + * + * All strings in PulseAudio are in the UTF-8 encoding, regardless of current + * locale. Some functions will filter invalid sequences from the string, some + * will simply fail. To ensure reliable behaviour, make sure everything you + * pass to the API is already in UTF-8. + + * \section simple_sec Simple API + * + * Use this if you develop your program in synchronous style and just + * need a way to play or record data on the sound server. See + * \subpage simple for more details. + * + * \section async_sec Asynchronous API + * + * Use this if you develop your programs in asynchronous, event loop + * based style or if you want to use the advanced features of the + * PulseAudio API. A guide can be found in \subpage async. + * + * By using the built-in threaded main loop, it is possible to achieve a + * pseudo-synchronous API, which can be useful in synchronous applications + * where the simple API is insufficient. See the \ref async page for + * details. + * + * \section thread_sec Threads + * + * The PulseAudio client libraries are not designed to be directly + * thread-safe. They are however designed to be reentrant and + * threads-aware. + * + * To use the libraries in a threaded environment, you must assure that + * all objects are only used in one thread at a time. Normally, this means + * that all objects belonging to a single context must be accessed from the + * same thread. + * + * The included main loop implementation is also not thread safe. Take care + * to make sure event objects are not manipulated when any other code is + * using the main loop. + * + * \section error_sec Error Handling + * + * Every function should explicitly document how errors are reported to + * the caller. Unfortunately, currently a lot of that documentation is + * missing. Here is an overview of the general conventions used. + * + * The PulseAudio API indicates error conditions by returning a negative + * integer value or a NULL pointer. On success, zero or a positive integer + * value or a valid pointer is returned. + * + * Functions of the \ref simple API generally return -1 or NULL on failure and + * can optionally store an error code (see ::pa_error_code) using a pointer + * argument. + * + * Functions of the \ref async API return an negative error code or NULL on + * failure (see ::pa_error_code). In the later case, pa_context_errno() + * can be used to obtain the error code of the last failed operation. + * + * An error code can be turned into a human readable message using + * pa_strerror(). + * + * \section logging_sec Logging + * + * You can configure different logging parameters for the PulseAudio client + * libraries. The following environment variables are recognized: + * + * - `PULSE_LOG`: Maximum log level required. Bigger values result in a + * more verbose logging output. The following values are recognized: + * + `0`: Error messages + * + `1`: Warning messages + * + `2`: Notice messages + * + `3`: Info messages + * + `4`: Debug messages + * - `PULSE_LOG_SYSLOG`: If defined, force all client libraries to log + * their output using the syslog(3) mechanism. Default behavior is to + * log all output to stderr. + * - `PULSE_LOG_JOURNAL`: If defined, force all client libraries to log + * their output using the systemd journal. If both `PULSE_LOG_JOURNAL` + * and `PULSE_LOG_SYSLOG` are defined, logging to the systemd journal + * takes a higher precedence. Each message originating library file name + * and function are included by default through the journal fields + * `CODE_FILE`, `CODE_FUNC`, and `CODE_LINE`. Any backtrace attached to + * the logging message is sent through the PulseAudio-specific journal + * field `PULSE_BACKTRACE`. This environment variable has no effect if + * PulseAudio was compiled without systemd journal support. + * - `PULSE_LOG_COLORS`: If defined, enables colored logging output. + * - `PULSE_LOG_TIME`: If defined, include timestamps with each message. + * - `PULSE_LOG_FILE`: If defined, include each message originating file + * name. + * - `PULSE_LOG_META`: If defined, include each message originating file + * name and path relative to the PulseAudio source tree root. + * - `PULSE_LOG_LEVEL`: If defined, include a log level prefix with each + * message. Respectively, the prefixes "E", "W", "N", "I", "D" stands + * for Error, Warning, Notice, Info, and Debugging. + * - `PULSE_LOG_BACKTRACE`: Number of functions to display in the backtrace. + * If this variable is not defined, or has a value of zero, no backtrace + * is shown. + * - `PULSE_LOG_BACKTRACE_SKIP`: Number of backtrace levels to skip, from + * the function printing the log message downwards. + * - `PULSE_LOG_NO_RATE_LIMIT`: If defined, do not rate limit the logging + * output. Rate limiting skips certain log messages when their frequency + * is considered too high. + * + * \section pkgconfig pkg-config + * + * The PulseAudio libraries provide pkg-config snippets for the different + * modules: + * + * \li libpulse - The asynchronous API and the internal main loop implementation. + * \li libpulse-mainloop-glib - GLIB 2.x main loop bindings. + * \li libpulse-simple - The simple PulseAudio API. + */ + +#endif diff --git a/src/pulse/rtclock.c b/src/pulse/rtclock.c new file mode 100644 index 0000000..56cacf7 --- /dev/null +++ b/src/pulse/rtclock.c @@ -0,0 +1,36 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <sys/time.h> + +#include <pulse/timeval.h> + +#include <pulsecore/core-rtclock.h> + +#include "rtclock.h" + +pa_usec_t pa_rtclock_now(void) { + struct timeval tv; + + return pa_timeval_load(pa_rtclock_get(&tv)); +} diff --git a/src/pulse/rtclock.h b/src/pulse/rtclock.h new file mode 100644 index 0000000..da65076 --- /dev/null +++ b/src/pulse/rtclock.h @@ -0,0 +1,38 @@ +#ifndef foortclockfoo +#define foortclockfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2009 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulse/cdecl.h> +#include <pulse/sample.h> + +/** \file + * Monotonic clock utilities. */ + +PA_C_DECL_BEGIN + +/** Return the current monotonic system time in usec, if such a clock + * is available. If it is not available this will return the + * wallclock time instead. \since 0.9.16 */ +pa_usec_t pa_rtclock_now(void); + +PA_C_DECL_END + +#endif diff --git a/src/pulse/sample.c b/src/pulse/sample.c new file mode 100644 index 0000000..ff77985 --- /dev/null +++ b/src/pulse/sample.c @@ -0,0 +1,285 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <string.h> + +#include <pulse/timeval.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/i18n.h> +#include <pulsecore/macro.h> + +#include "sample.h" + +static const size_t size_table[] = { + [PA_SAMPLE_U8] = 1, + [PA_SAMPLE_ULAW] = 1, + [PA_SAMPLE_ALAW] = 1, + [PA_SAMPLE_S16LE] = 2, + [PA_SAMPLE_S16BE] = 2, + [PA_SAMPLE_FLOAT32LE] = 4, + [PA_SAMPLE_FLOAT32BE] = 4, + [PA_SAMPLE_S32LE] = 4, + [PA_SAMPLE_S32BE] = 4, + [PA_SAMPLE_S24LE] = 3, + [PA_SAMPLE_S24BE] = 3, + [PA_SAMPLE_S24_32LE] = 4, + [PA_SAMPLE_S24_32BE] = 4 +}; + +size_t pa_sample_size_of_format(pa_sample_format_t f) { + pa_assert(pa_sample_format_valid(f)); + + return size_table[f]; +} + +size_t pa_sample_size(const pa_sample_spec *spec) { + pa_assert(spec); + pa_assert(pa_sample_spec_valid(spec)); + + return size_table[spec->format]; +} + +size_t pa_frame_size(const pa_sample_spec *spec) { + pa_assert(spec); + pa_assert(pa_sample_spec_valid(spec)); + + return size_table[spec->format] * spec->channels; +} + +size_t pa_bytes_per_second(const pa_sample_spec *spec) { + pa_assert(spec); + pa_assert(pa_sample_spec_valid(spec)); + + return spec->rate * size_table[spec->format] * spec->channels; +} + +pa_usec_t pa_bytes_to_usec(uint64_t length, const pa_sample_spec *spec) { + pa_assert(spec); + pa_assert(pa_sample_spec_valid(spec)); + + return (((pa_usec_t) (length / (size_table[spec->format] * spec->channels)) * PA_USEC_PER_SEC) / spec->rate); +} + +size_t pa_usec_to_bytes(pa_usec_t t, const pa_sample_spec *spec) { + pa_assert(spec); + pa_assert(pa_sample_spec_valid(spec)); + + return (size_t) (((t * spec->rate) / PA_USEC_PER_SEC)) * (size_table[spec->format] * spec->channels); +} + +pa_sample_spec* pa_sample_spec_init(pa_sample_spec *spec) { + pa_assert(spec); + + spec->format = PA_SAMPLE_INVALID; + spec->rate = 0; + spec->channels = 0; + + return spec; +} + +int pa_sample_format_valid(unsigned format) { + return format < PA_SAMPLE_MAX; +} + +int pa_sample_rate_valid(uint32_t rate) { + /* The extra 1% is due to module-loopback: it temporarily sets + * a higher-than-nominal rate to get rid of excessive buffer + * latency */ + return rate > 0 && rate <= PA_RATE_MAX * 101 / 100; +} + +int pa_channels_valid(uint8_t channels) { + return channels > 0 && channels <= PA_CHANNELS_MAX; +} + +int pa_sample_spec_valid(const pa_sample_spec *spec) { + pa_assert(spec); + + if (PA_UNLIKELY(!pa_sample_rate_valid(spec->rate) || + !pa_channels_valid(spec->channels) || + !pa_sample_format_valid(spec->format))) + return 0; + + return 1; +} + +int pa_sample_spec_equal(const pa_sample_spec*a, const pa_sample_spec*b) { + pa_assert(a); + pa_assert(b); + + pa_return_val_if_fail(pa_sample_spec_valid(a), 0); + + if (PA_UNLIKELY(a == b)) + return 1; + + pa_return_val_if_fail(pa_sample_spec_valid(b), 0); + + return + (a->format == b->format) && + (a->rate == b->rate) && + (a->channels == b->channels); +} + +const char *pa_sample_format_to_string(pa_sample_format_t f) { + static const char* const table[]= { + [PA_SAMPLE_U8] = "u8", + [PA_SAMPLE_ALAW] = "aLaw", + [PA_SAMPLE_ULAW] = "uLaw", + [PA_SAMPLE_S16LE] = "s16le", + [PA_SAMPLE_S16BE] = "s16be", + [PA_SAMPLE_FLOAT32LE] = "float32le", + [PA_SAMPLE_FLOAT32BE] = "float32be", + [PA_SAMPLE_S32LE] = "s32le", + [PA_SAMPLE_S32BE] = "s32be", + [PA_SAMPLE_S24LE] = "s24le", + [PA_SAMPLE_S24BE] = "s24be", + [PA_SAMPLE_S24_32LE] = "s24-32le", + [PA_SAMPLE_S24_32BE] = "s24-32be", + }; + + if (!pa_sample_format_valid(f)) + return NULL; + + return table[f]; +} + +char *pa_sample_spec_snprint(char *s, size_t l, const pa_sample_spec *spec) { + pa_assert(s); + pa_assert(l > 0); + pa_assert(spec); + + pa_init_i18n(); + + if (!pa_sample_spec_valid(spec)) + pa_snprintf(s, l, _("(invalid)")); + else + pa_snprintf(s, l, _("%s %uch %uHz"), pa_sample_format_to_string(spec->format), spec->channels, spec->rate); + + return s; +} + +char* pa_bytes_snprint(char *s, size_t l, unsigned v) { + pa_assert(s); + pa_assert(l > 0); + + pa_init_i18n(); + + if (v >= ((unsigned) 1024)*1024*1024) + pa_snprintf(s, l, _("%0.1f GiB"), ((double) v)/1024/1024/1024); + else if (v >= ((unsigned) 1024)*1024) + pa_snprintf(s, l, _("%0.1f MiB"), ((double) v)/1024/1024); + else if (v >= (unsigned) 1024) + pa_snprintf(s, l, _("%0.1f KiB"), ((double) v)/1024); + else + pa_snprintf(s, l, _("%u B"), (unsigned) v); + + return s; +} + +pa_sample_format_t pa_parse_sample_format(const char *format) { + pa_assert(format); + + if (strcasecmp(format, "s16le") == 0) + return PA_SAMPLE_S16LE; + else if (strcasecmp(format, "s16be") == 0) + return PA_SAMPLE_S16BE; + else if (strcasecmp(format, "s16ne") == 0 || strcasecmp(format, "s16") == 0 || strcasecmp(format, "16") == 0) + return PA_SAMPLE_S16NE; + else if (strcasecmp(format, "s16re") == 0) + return PA_SAMPLE_S16RE; + else if (strcasecmp(format, "u8") == 0 || strcasecmp(format, "8") == 0) + return PA_SAMPLE_U8; + else if (strcasecmp(format, "float32") == 0 || strcasecmp(format, "float32ne") == 0 || strcasecmp(format, "float") == 0) + return PA_SAMPLE_FLOAT32NE; + else if (strcasecmp(format, "float32re") == 0) + return PA_SAMPLE_FLOAT32RE; + else if (strcasecmp(format, "float32le") == 0) + return PA_SAMPLE_FLOAT32LE; + else if (strcasecmp(format, "float32be") == 0) + return PA_SAMPLE_FLOAT32BE; + else if (strcasecmp(format, "ulaw") == 0 || strcasecmp(format, "mulaw") == 0) + return PA_SAMPLE_ULAW; + else if (strcasecmp(format, "alaw") == 0) + return PA_SAMPLE_ALAW; + else if (strcasecmp(format, "s32le") == 0) + return PA_SAMPLE_S32LE; + else if (strcasecmp(format, "s32be") == 0) + return PA_SAMPLE_S32BE; + else if (strcasecmp(format, "s32ne") == 0 || strcasecmp(format, "s32") == 0 || strcasecmp(format, "32") == 0) + return PA_SAMPLE_S32NE; + else if (strcasecmp(format, "s32re") == 0) + return PA_SAMPLE_S24RE; + else if (strcasecmp(format, "s24le") == 0) + return PA_SAMPLE_S24LE; + else if (strcasecmp(format, "s24be") == 0) + return PA_SAMPLE_S24BE; + else if (strcasecmp(format, "s24ne") == 0 || strcasecmp(format, "s24") == 0 || strcasecmp(format, "24") == 0) + return PA_SAMPLE_S24NE; + else if (strcasecmp(format, "s24re") == 0) + return PA_SAMPLE_S24RE; + else if (strcasecmp(format, "s24-32le") == 0) + return PA_SAMPLE_S24_32LE; + else if (strcasecmp(format, "s24-32be") == 0) + return PA_SAMPLE_S24_32BE; + else if (strcasecmp(format, "s24-32ne") == 0 || strcasecmp(format, "s24-32") == 0) + return PA_SAMPLE_S24_32NE; + else if (strcasecmp(format, "s24-32re") == 0) + return PA_SAMPLE_S24_32RE; + + return PA_SAMPLE_INVALID; +} + +int pa_sample_format_is_le(pa_sample_format_t f) { + pa_assert(pa_sample_format_valid(f)); + + switch (f) { + case PA_SAMPLE_S16LE: + case PA_SAMPLE_S24LE: + case PA_SAMPLE_S32LE: + case PA_SAMPLE_S24_32LE: + case PA_SAMPLE_FLOAT32LE: + return 1; + + case PA_SAMPLE_S16BE: + case PA_SAMPLE_S24BE: + case PA_SAMPLE_S32BE: + case PA_SAMPLE_S24_32BE: + case PA_SAMPLE_FLOAT32BE: + return 0; + + default: + return -1; + } +} + +int pa_sample_format_is_be(pa_sample_format_t f) { + int r; + + if ((r = pa_sample_format_is_le(f)) < 0) + return r; + + return !r; +} diff --git a/src/pulse/sample.h b/src/pulse/sample.h new file mode 100644 index 0000000..35346a8 --- /dev/null +++ b/src/pulse/sample.h @@ -0,0 +1,364 @@ +#ifndef foosamplehfoo +#define foosamplehfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <inttypes.h> +#include <sys/types.h> +#include <sys/param.h> + +#include <pulse/gccmacro.h> +#include <pulse/cdecl.h> +#include <pulse/version.h> + +/** \page sample Sample Format Specifications + * + * \section overv_sec Overview + * + * PulseAudio is capable of handling a multitude of sample formats, rates + * and channels, transparently converting and mixing them as needed. + * + * \section format_sec Sample Format + * + * PulseAudio supports the following sample formats: + * + * \li PA_SAMPLE_U8 - Unsigned 8 bit integer PCM. + * \li PA_SAMPLE_S16LE - Signed 16 integer bit PCM, little endian. + * \li PA_SAMPLE_S16BE - Signed 16 integer bit PCM, big endian. + * \li PA_SAMPLE_FLOAT32LE - 32 bit IEEE floating point PCM, little endian. + * \li PA_SAMPLE_FLOAT32BE - 32 bit IEEE floating point PCM, big endian. + * \li PA_SAMPLE_ALAW - 8 bit a-Law. + * \li PA_SAMPLE_ULAW - 8 bit mu-Law. + * \li PA_SAMPLE_S32LE - Signed 32 bit integer PCM, little endian. + * \li PA_SAMPLE_S32BE - Signed 32 bit integer PCM, big endian. + * \li PA_SAMPLE_S24LE - Signed 24 bit integer PCM packed, little endian. + * \li PA_SAMPLE_S24BE - Signed 24 bit integer PCM packed, big endian. + * \li PA_SAMPLE_S24_32LE - Signed 24 bit integer PCM in LSB of 32 bit words, little endian. + * \li PA_SAMPLE_S24_32BE - Signed 24 bit integer PCM in LSB of 32 bit words, big endian. + * + * The floating point sample formats have the range from -1.0 to 1.0. + * + * The sample formats that are sensitive to endianness have convenience + * macros for native endian (NE), and reverse endian (RE). + * + * \section rate_sec Sample Rates + * + * PulseAudio supports any sample rate between 1 Hz and 192000 Hz. There is no + * point trying to exceed the sample rate of the output device though as the + * signal will only get downsampled, consuming CPU on the machine running the + * server. + * + * \section chan_sec Channels + * + * PulseAudio supports up to 32 individual channels. The order of the + * channels is up to the application, but they must be continuous. To map + * channels to speakers, see \ref channelmap. + * + * \section calc_sec Calculations + * + * The PulseAudio library contains a number of convenience functions to do + * calculations on sample formats: + * + * \li pa_bytes_per_second() - The number of bytes one second of audio will + * take given a sample format. + * \li pa_frame_size() - The size, in bytes, of one frame (i.e. one set of + * samples, one for each channel). + * \li pa_sample_size() - The size, in bytes, of one sample. + * \li pa_bytes_to_usec() - Calculate the time it would take to play a buffer + * of a certain size. + * + * \section util_sec Convenience Functions + * + * The library also contains a couple of other convenience functions: + * + * \li pa_sample_spec_valid() - Tests if a sample format specification is + * valid. + * \li pa_sample_spec_equal() - Tests if the sample format specifications are + * identical. + * \li pa_sample_format_to_string() - Return a textual description of a + * sample format. + * \li pa_parse_sample_format() - Parse a text string into a sample format. + * \li pa_sample_spec_snprint() - Create a textual description of a complete + * sample format specification. + * \li pa_bytes_snprint() - Pretty print a byte value (e.g. 2.5 MiB). + */ + +/** \file + * Constants and routines for sample type handling + * + * See also \subpage sample + */ + +PA_C_DECL_BEGIN + +#if !defined(WORDS_BIGENDIAN) + +#if defined(__BYTE_ORDER) +#if __BYTE_ORDER == __BIG_ENDIAN +#define WORDS_BIGENDIAN +#endif +#endif + +/* On Sparc, WORDS_BIGENDIAN needs to be set if _BIG_ENDIAN is defined. */ +#if defined(__sparc__) && defined(_BIG_ENDIAN) +#define WORDS_BIGENDIAN +#endif + +#endif + +/** Maximum number of allowed channels */ +#define PA_CHANNELS_MAX 32U + +/** Maximum allowed sample rate */ +#define PA_RATE_MAX (48000U*8U) + +/** Sample format */ +typedef enum pa_sample_format { + PA_SAMPLE_U8, + /**< Unsigned 8 Bit PCM */ + + PA_SAMPLE_ALAW, + /**< 8 Bit a-Law */ + + PA_SAMPLE_ULAW, + /**< 8 Bit mu-Law */ + + PA_SAMPLE_S16LE, + /**< Signed 16 Bit PCM, little endian (PC) */ + + PA_SAMPLE_S16BE, + /**< Signed 16 Bit PCM, big endian */ + + PA_SAMPLE_FLOAT32LE, + /**< 32 Bit IEEE floating point, little endian (PC), range -1.0 to 1.0 */ + + PA_SAMPLE_FLOAT32BE, + /**< 32 Bit IEEE floating point, big endian, range -1.0 to 1.0 */ + + PA_SAMPLE_S32LE, + /**< Signed 32 Bit PCM, little endian (PC) */ + + PA_SAMPLE_S32BE, + /**< Signed 32 Bit PCM, big endian */ + + PA_SAMPLE_S24LE, + /**< Signed 24 Bit PCM packed, little endian (PC). \since 0.9.15 */ + + PA_SAMPLE_S24BE, + /**< Signed 24 Bit PCM packed, big endian. \since 0.9.15 */ + + PA_SAMPLE_S24_32LE, + /**< Signed 24 Bit PCM in LSB of 32 Bit words, little endian (PC). \since 0.9.15 */ + + PA_SAMPLE_S24_32BE, + /**< Signed 24 Bit PCM in LSB of 32 Bit words, big endian. \since 0.9.15 */ + + /* Remeber to update + * https://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/User/SupportedAudioFormats/ + * when adding new formats! */ + + PA_SAMPLE_MAX, + /**< Upper limit of valid sample types */ + + PA_SAMPLE_INVALID = -1 + /**< An invalid value */ +} pa_sample_format_t; + +#ifdef WORDS_BIGENDIAN +/** Signed 16 Bit PCM, native endian */ +#define PA_SAMPLE_S16NE PA_SAMPLE_S16BE +/** 32 Bit IEEE floating point, native endian */ +#define PA_SAMPLE_FLOAT32NE PA_SAMPLE_FLOAT32BE +/** Signed 32 Bit PCM, native endian */ +#define PA_SAMPLE_S32NE PA_SAMPLE_S32BE +/** Signed 24 Bit PCM packed, native endian. \since 0.9.15 */ +#define PA_SAMPLE_S24NE PA_SAMPLE_S24BE +/** Signed 24 Bit PCM in LSB of 32 Bit words, native endian. \since 0.9.15 */ +#define PA_SAMPLE_S24_32NE PA_SAMPLE_S24_32BE + +/** Signed 16 Bit PCM reverse endian */ +#define PA_SAMPLE_S16RE PA_SAMPLE_S16LE +/** 32 Bit IEEE floating point, reverse endian */ +#define PA_SAMPLE_FLOAT32RE PA_SAMPLE_FLOAT32LE +/** Signed 32 Bit PCM, reverse endian */ +#define PA_SAMPLE_S32RE PA_SAMPLE_S32LE +/** Signed 24 Bit PCM, packed reverse endian. \since 0.9.15 */ +#define PA_SAMPLE_S24RE PA_SAMPLE_S24LE +/** Signed 24 Bit PCM, in LSB of 32 Bit words, reverse endian. \since 0.9.15 */ +#define PA_SAMPLE_S24_32RE PA_SAMPLE_S24_32LE +#else +/** Signed 16 Bit PCM, native endian */ +#define PA_SAMPLE_S16NE PA_SAMPLE_S16LE +/** 32 Bit IEEE floating point, native endian */ +#define PA_SAMPLE_FLOAT32NE PA_SAMPLE_FLOAT32LE +/** Signed 32 Bit PCM, native endian */ +#define PA_SAMPLE_S32NE PA_SAMPLE_S32LE +/** Signed 24 Bit PCM packed, native endian. \since 0.9.15 */ +#define PA_SAMPLE_S24NE PA_SAMPLE_S24LE +/** Signed 24 Bit PCM in LSB of 32 Bit words, native endian. \since 0.9.15 */ +#define PA_SAMPLE_S24_32NE PA_SAMPLE_S24_32LE + +/** Signed 16 Bit PCM, reverse endian */ +#define PA_SAMPLE_S16RE PA_SAMPLE_S16BE +/** 32 Bit IEEE floating point, reverse endian */ +#define PA_SAMPLE_FLOAT32RE PA_SAMPLE_FLOAT32BE +/** Signed 32 Bit PCM, reverse endian */ +#define PA_SAMPLE_S32RE PA_SAMPLE_S32BE +/** Signed 24 Bit PCM, packed reverse endian. \since 0.9.15 */ +#define PA_SAMPLE_S24RE PA_SAMPLE_S24BE +/** Signed 24 Bit PCM, in LSB of 32 Bit words, reverse endian. \since 0.9.15 */ +#define PA_SAMPLE_S24_32RE PA_SAMPLE_S24_32BE +#endif + +/** A Shortcut for PA_SAMPLE_FLOAT32NE */ +#define PA_SAMPLE_FLOAT32 PA_SAMPLE_FLOAT32NE + +/** \cond fulldocs */ +/* Allow clients to check with #ifdef for these sample formats */ +#define PA_SAMPLE_U8 PA_SAMPLE_U8 +#define PA_SAMPLE_ALAW PA_SAMPLE_ALAW +#define PA_SAMPLE_ULAW PA_SAMPLE_ULAW +#define PA_SAMPLE_S16LE PA_SAMPLE_S16LE +#define PA_SAMPLE_S16BE PA_SAMPLE_S16BE +#define PA_SAMPLE_FLOAT32LE PA_SAMPLE_FLOAT32LE +#define PA_SAMPLE_FLOAT32BE PA_SAMPLE_FLOAT32BE +#define PA_SAMPLE_S32LE PA_SAMPLE_S32LE +#define PA_SAMPLE_S32BE PA_SAMPLE_S32BE +#define PA_SAMPLE_S24LE PA_SAMPLE_S24LE +#define PA_SAMPLE_S24BE PA_SAMPLE_S24BE +#define PA_SAMPLE_S24_32LE PA_SAMPLE_S24_32LE +#define PA_SAMPLE_S24_32BE PA_SAMPLE_S24_32BE +/** \endcond */ + +/** A sample format and attribute specification */ +typedef struct pa_sample_spec { + pa_sample_format_t format; + /**< The sample format */ + + uint32_t rate; + /**< The sample rate. (e.g. 44100) */ + + uint8_t channels; + /**< Audio channels. (1 for mono, 2 for stereo, ...) */ +} pa_sample_spec; + +/** Type for usec specifications (unsigned). Always 64 bit. */ +typedef uint64_t pa_usec_t; + +/** Return the amount of bytes that constitute playback of one second of + * audio, with the specified sample spec. */ +size_t pa_bytes_per_second(const pa_sample_spec *spec) PA_GCC_PURE; + +/** Return the size of a frame with the specific sample type */ +size_t pa_frame_size(const pa_sample_spec *spec) PA_GCC_PURE; + +/** Return the size of a sample with the specific sample type */ +size_t pa_sample_size(const pa_sample_spec *spec) PA_GCC_PURE; + +/** Similar to pa_sample_size() but take a sample format instead of a + * full sample spec. \since 0.9.15 */ +size_t pa_sample_size_of_format(pa_sample_format_t f) PA_GCC_PURE; + +/** Calculate the time it would take to play a buffer of the specified + * size with the specified sample type. The return value will always + * be rounded down for non-integral return values. */ +pa_usec_t pa_bytes_to_usec(uint64_t length, const pa_sample_spec *spec) PA_GCC_PURE; + +/** Calculates the size of a buffer required, for playback duration + * of the time specified, with the specified sample type. The + * return value will always be rounded down for non-integral + * return values. \since 0.9 */ +size_t pa_usec_to_bytes(pa_usec_t t, const pa_sample_spec *spec) PA_GCC_PURE; + +/** Initialize the specified sample spec and return a pointer to + * it. The sample spec will have a defined state but + * pa_sample_spec_valid() will fail for it. \since 0.9.13 */ +pa_sample_spec* pa_sample_spec_init(pa_sample_spec *spec); + +/** Return non-zero if the given integer is a valid sample format. \since 5.0 */ +int pa_sample_format_valid(unsigned format) PA_GCC_PURE; + +/** Return non-zero if the rate is within the supported range. \since 5.0 */ +int pa_sample_rate_valid(uint32_t rate) PA_GCC_PURE; + +/** Return non-zero if the channel count is within the supported range. + * \since 5.0 */ +int pa_channels_valid(uint8_t channels) PA_GCC_PURE; + +/** Return non-zero when the sample type specification is valid */ +int pa_sample_spec_valid(const pa_sample_spec *spec) PA_GCC_PURE; + +/** Return non-zero when the two sample type specifications match */ +int pa_sample_spec_equal(const pa_sample_spec*a, const pa_sample_spec*b) PA_GCC_PURE; + +/** Return a descriptive string for the specified sample format. \since 0.8 */ +const char *pa_sample_format_to_string(pa_sample_format_t f) PA_GCC_PURE; + +/** Parse a sample format text. Inverse of pa_sample_format_to_string() */ +pa_sample_format_t pa_parse_sample_format(const char *format) PA_GCC_PURE; + +/** Maximum required string length for + * pa_sample_spec_snprint(). Please note that this value can change + * with any release without warning and without being considered API + * or ABI breakage. You should not use this definition anywhere where + * it might become part of an ABI. */ +#define PA_SAMPLE_SPEC_SNPRINT_MAX 32 + +/** Pretty print a sample type specification to a string. Returns \a s. */ +char* pa_sample_spec_snprint(char *s, size_t l, const pa_sample_spec *spec); + +/** Maximum required string length for pa_bytes_snprint(). Please note + * that this value can change with any release without warning and + * without being considered API or ABI breakage. You should not use + * this definition anywhere where it might become part of an + * ABI. \since 0.9.16 */ +#define PA_BYTES_SNPRINT_MAX 11 + +/** Pretty print a byte size value (i.e.\ "2.5 MiB"). Returns \a s. */ +char* pa_bytes_snprint(char *s, size_t l, unsigned v); + +/** Returns 1 when the specified format is little endian, 0 when + * big endian. Returns -1 when endianness does not apply to the + * specified format, or endianess is unknown. \since 0.9.16 */ +int pa_sample_format_is_le(pa_sample_format_t f) PA_GCC_PURE; + +/** Returns 1 when the specified format is big endian, 0 when + * little endian. Returns -1 when endianness does not apply to the + * specified format, or endianess is unknown. \since 0.9.16 */ +int pa_sample_format_is_be(pa_sample_format_t f) PA_GCC_PURE; + +#ifdef WORDS_BIGENDIAN +#define pa_sample_format_is_ne(f) pa_sample_format_is_be(f) +#define pa_sample_format_is_re(f) pa_sample_format_is_le(f) +#else +/** Returns 1 when the specified format is native endian, 0 when + * not. Returns -1 when endianness does not apply to the + * specified format, or endianess is unknown. \since 0.9.16 */ +#define pa_sample_format_is_ne(f) pa_sample_format_is_le(f) +/** Returns 1 when the specified format is reverse endian, 0 when + * native. Returns -1 when endianness does not apply to the + * specified format, or endianess is unknown. \since 0.9.16 */ +#define pa_sample_format_is_re(f) pa_sample_format_is_be(f) +#endif + +PA_C_DECL_END + +#endif diff --git a/src/pulse/scache.c b/src/pulse/scache.c new file mode 100644 index 0000000..d393229 --- /dev/null +++ b/src/pulse/scache.c @@ -0,0 +1,272 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <stdio.h> + +#include <pulse/utf8.h> +#include <pulse/fork-detect.h> + +#include <pulsecore/pstream-util.h> +#include <pulsecore/macro.h> +#include <pulsecore/proplist-util.h> + +#include "internal.h" +#include "scache.h" + +int pa_stream_connect_upload(pa_stream *s, size_t length) { + pa_tagstruct *t; + uint32_t tag; + const char *name; + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY(s->context, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_UNCONNECTED, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, length > 0, PA_ERR_INVALID); + PA_CHECK_VALIDITY(s->context, length == (size_t) (uint32_t) length, PA_ERR_INVALID); + PA_CHECK_VALIDITY(s->context, s->context->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + if (!(name = pa_proplist_gets(s->proplist, PA_PROP_EVENT_ID))) + name = pa_proplist_gets(s->proplist, PA_PROP_MEDIA_NAME); + + PA_CHECK_VALIDITY(s->context, name && *name && pa_utf8_valid(name), PA_ERR_INVALID); + + pa_stream_ref(s); + + s->direction = PA_STREAM_UPLOAD; + s->flags = 0; + + t = pa_tagstruct_command(s->context, PA_COMMAND_CREATE_UPLOAD_STREAM, &tag); + + pa_tagstruct_puts(t, name); + pa_tagstruct_put_sample_spec(t, &s->sample_spec); + pa_tagstruct_put_channel_map(t, &s->channel_map); + pa_tagstruct_putu32(t, (uint32_t) length); + + if (s->context->version >= 13) + pa_tagstruct_put_proplist(t, s->proplist); + + pa_pstream_send_tagstruct(s->context->pstream, t); + pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_create_stream_callback, s, NULL); + + pa_stream_set_state(s, PA_STREAM_CREATING); + + pa_stream_unref(s); + return 0; +} + +int pa_stream_finish_upload(pa_stream *s) { + pa_tagstruct *t; + uint32_t tag; + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY(s->context, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY(s->context, s->channel_valid, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, s->context->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + pa_stream_ref(s); + + t = pa_tagstruct_command(s->context, PA_COMMAND_FINISH_UPLOAD_STREAM, &tag); + pa_tagstruct_putu32(t, s->channel); + pa_pstream_send_tagstruct(s->context->pstream, t); + pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_stream_disconnect_callback, s, NULL); + + pa_stream_unref(s); + return 0; +} + +static void play_sample_ack_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_operation *o = userdata; + int success = 1; + uint32_t idx = PA_INVALID_INDEX; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (!o->context) + goto finish; + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(o->context, command, t, false) < 0) + goto finish; + + success = 0; + } else if ((o->context->version >= 13 && pa_tagstruct_getu32(t, &idx) < 0) || + !pa_tagstruct_eof(t)) { + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } else if (o->context->version >= 13 && idx == PA_INVALID_INDEX) + success = 0; + + if (o->callback) { + pa_context_success_cb_t cb = (pa_context_success_cb_t) o->callback; + cb(o->context, success, o->userdata); + } + +finish: + pa_operation_done(o); + pa_operation_unref(o); +} + +static void play_sample_with_proplist_ack_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_operation *o = userdata; + uint32_t idx; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (!o->context) + goto finish; + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(o->context, command, t, false) < 0) + goto finish; + + idx = PA_INVALID_INDEX; + } else if (pa_tagstruct_getu32(t, &idx) < 0 || + !pa_tagstruct_eof(t)) { + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + + if (o->callback) { + pa_context_play_sample_cb_t cb = (pa_context_play_sample_cb_t) o->callback; + cb(o->context, idx, o->userdata); + } + +finish: + pa_operation_done(o); + pa_operation_unref(o); +} + +pa_operation *pa_context_play_sample(pa_context *c, const char *name, const char *dev, pa_volume_t volume, pa_context_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, name && *name, PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, !dev || *dev, PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + if (!dev) + dev = c->conf->default_sink; + + t = pa_tagstruct_command(c, PA_COMMAND_PLAY_SAMPLE, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, dev); + + if (!PA_VOLUME_IS_VALID(volume) && c->version < 15) + volume = PA_VOLUME_NORM; + + pa_tagstruct_putu32(t, volume); + pa_tagstruct_puts(t, name); + + if (c->version >= 13) { + pa_proplist *p = pa_proplist_new(); + pa_tagstruct_put_proplist(t, p); + pa_proplist_free(p); + } + + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, play_sample_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation *pa_context_play_sample_with_proplist(pa_context *c, const char *name, const char *dev, pa_volume_t volume, const pa_proplist *p, pa_context_play_sample_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, name && *name, PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, !dev || *dev, PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 13, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + if (!dev) + dev = c->conf->default_sink; + + t = pa_tagstruct_command(c, PA_COMMAND_PLAY_SAMPLE, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, dev); + + if (!PA_VOLUME_IS_VALID(volume) && c->version < 15) + volume = PA_VOLUME_NORM; + + pa_tagstruct_putu32(t, volume); + pa_tagstruct_puts(t, name); + + if (p) + pa_tagstruct_put_proplist(t, p); + else { + pa_proplist *empty_proplist = pa_proplist_new(); + pa_tagstruct_put_proplist(t, empty_proplist); + pa_proplist_free(empty_proplist); + } + + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, play_sample_with_proplist_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_remove_sample(pa_context *c, const char *name, pa_context_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, name && *name, PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_REMOVE_SAMPLE, &tag); + pa_tagstruct_puts(t, name); + + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} diff --git a/src/pulse/scache.h b/src/pulse/scache.h new file mode 100644 index 0000000..1be7198 --- /dev/null +++ b/src/pulse/scache.h @@ -0,0 +1,125 @@ +#ifndef fooscachehfoo +#define fooscachehfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/types.h> + +#include <pulse/context.h> +#include <pulse/stream.h> +#include <pulse/cdecl.h> +#include <pulse/version.h> + +/** \page scache Sample Cache + * + * \section overv_sec Overview + * + * The sample cache provides a simple way of overcoming high network latencies + * and reducing bandwidth. Instead of streaming a sound precisely when it + * should be played, it is stored on the server and only the command to start + * playing it needs to be sent. + * + * \section create_sec Creation + * + * To create a sample, the normal stream API is used (see \ref streams). The + * function pa_stream_connect_upload() will make sure the stream is stored as + * a sample on the server. + * + * To complete the upload, pa_stream_finish_upload() is called and the sample + * will receive the same name as the stream. If the upload should be aborted, + * simply call pa_stream_disconnect(). + * + * \section play_sec Playing samples + * + * To play back a sample, simply call pa_context_play_sample(): + * + * \code + * pa_operation *o; + * + * o = pa_context_play_sample(my_context, + * "sample2", // Name of my sample + * NULL, // Use default sink + * PA_VOLUME_NORM, // Full volume + * NULL, // Don't need a callback + * NULL + * ); + * if (o) + * pa_operation_unref(o); + * \endcode + * + * \section rem_sec Removing samples + * + * When a sample is no longer needed, it should be removed on the server to + * save resources. The sample is deleted using pa_context_remove_sample(). + */ + +/** \file + * All sample cache related routines + * + * See also \subpage scache + */ + +PA_C_DECL_BEGIN + +/** Callback prototype for pa_context_play_sample_with_proplist(). The + * idx value is the index of the sink input object, or + * PA_INVALID_INDEX on failure. \since 0.9.11 */ +typedef void (*pa_context_play_sample_cb_t)(pa_context *c, uint32_t idx, void *userdata); + +/** Make this stream a sample upload stream. Returns zero on success. */ +int pa_stream_connect_upload(pa_stream *s, size_t length); + +/** Finish the sample upload, the stream name will become the sample + * name. You cancel a sample upload by issuing + * pa_stream_disconnect(). Returns zero on success. */ +int pa_stream_finish_upload(pa_stream *s); + +/** Remove a sample from the sample cache. Returns an operation object which + * may be used to cancel the operation while it is running. */ +pa_operation* pa_context_remove_sample(pa_context *c, const char *name, pa_context_success_cb_t cb, void *userdata); + +/** Play a sample from the sample cache to the specified device. If + * the latter is NULL use the default sink. Returns an operation + * object */ +pa_operation* pa_context_play_sample( + pa_context *c /**< Context */, + const char *name /**< Name of the sample to play */, + const char *dev /**< Sink to play this sample on */, + pa_volume_t volume /**< Volume to play this sample with. Starting with 0.9.15 you may pass here PA_VOLUME_INVALID which will leave the decision about the volume to the server side, which is a good idea. */ , + pa_context_success_cb_t cb /**< Call this function after successfully starting playback, or NULL */, + void *userdata /**< Userdata to pass to the callback */); + +/** Play a sample from the sample cache to the specified device, + * allowing specification of a property list for the playback + * stream. If the latter is NULL use the default sink. Returns an + * operation object. \since 0.9.11 */ +pa_operation* pa_context_play_sample_with_proplist( + pa_context *c /**< Context */, + const char *name /**< Name of the sample to play */, + const char *dev /**< Sink to play this sample on */, + pa_volume_t volume /**< Volume to play this sample with. Starting with 0.9.15 you may pass here PA_VOLUME_INVALID which will leave the decision about the volume to the server side, which is a good idea. */ , + const pa_proplist *proplist /**< Property list for this sound. The property list of the cached entry will have this merged into it. */, + pa_context_play_sample_cb_t cb /**< Call this function after successfully starting playback, or NULL */, + void *userdata /**< Userdata to pass to the callback */); + +PA_C_DECL_END + +#endif diff --git a/src/pulse/simple.c b/src/pulse/simple.c new file mode 100644 index 0000000..38881de --- /dev/null +++ b/src/pulse/simple.c @@ -0,0 +1,512 @@ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include <pulse/pulseaudio.h> +#include <pulse/thread-mainloop.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/log.h> +#include <pulsecore/macro.h> + +#include "simple.h" + +struct pa_simple { + pa_threaded_mainloop *mainloop; + pa_context *context; + pa_stream *stream; + pa_stream_direction_t direction; + + const void *read_data; + size_t read_index, read_length; + + int operation_success; +}; + +#define CHECK_VALIDITY_RETURN_ANY(rerror, expression, error, ret) \ + do { \ + if (!(expression)) { \ + if (rerror) \ + *(rerror) = error; \ + return (ret); \ + } \ + } while(false); + +#define CHECK_SUCCESS_GOTO(p, rerror, expression, label) \ + do { \ + if (!(expression)) { \ + if (rerror) \ + *(rerror) = pa_context_errno((p)->context); \ + goto label; \ + } \ + } while(false); + +#define CHECK_DEAD_GOTO(p, rerror, label) \ + do { \ + if (!(p)->context || !PA_CONTEXT_IS_GOOD(pa_context_get_state((p)->context)) || \ + !(p)->stream || !PA_STREAM_IS_GOOD(pa_stream_get_state((p)->stream))) { \ + if (((p)->context && pa_context_get_state((p)->context) == PA_CONTEXT_FAILED) || \ + ((p)->stream && pa_stream_get_state((p)->stream) == PA_STREAM_FAILED)) { \ + if (rerror) \ + *(rerror) = pa_context_errno((p)->context); \ + } else \ + if (rerror) \ + *(rerror) = PA_ERR_BADSTATE; \ + goto label; \ + } \ + } while(false); + +static void context_state_cb(pa_context *c, void *userdata) { + pa_simple *p = userdata; + pa_assert(c); + pa_assert(p); + + switch (pa_context_get_state(c)) { + case PA_CONTEXT_READY: + case PA_CONTEXT_TERMINATED: + case PA_CONTEXT_FAILED: + pa_threaded_mainloop_signal(p->mainloop, 0); + break; + + case PA_CONTEXT_UNCONNECTED: + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + break; + } +} + +static void stream_state_cb(pa_stream *s, void * userdata) { + pa_simple *p = userdata; + pa_assert(s); + pa_assert(p); + + switch (pa_stream_get_state(s)) { + + case PA_STREAM_READY: + case PA_STREAM_FAILED: + case PA_STREAM_TERMINATED: + pa_threaded_mainloop_signal(p->mainloop, 0); + break; + + case PA_STREAM_UNCONNECTED: + case PA_STREAM_CREATING: + break; + } +} + +static void stream_request_cb(pa_stream *s, size_t length, void *userdata) { + pa_simple *p = userdata; + pa_assert(p); + + pa_threaded_mainloop_signal(p->mainloop, 0); +} + +static void stream_latency_update_cb(pa_stream *s, void *userdata) { + pa_simple *p = userdata; + + pa_assert(p); + + pa_threaded_mainloop_signal(p->mainloop, 0); +} + +pa_simple* pa_simple_new( + const char *server, + const char *name, + pa_stream_direction_t dir, + const char *dev, + const char *stream_name, + const pa_sample_spec *ss, + const pa_channel_map *map, + const pa_buffer_attr *attr, + int *rerror) { + + pa_simple *p; + int error = PA_ERR_INTERNAL, r; + + CHECK_VALIDITY_RETURN_ANY(rerror, !server || *server, PA_ERR_INVALID, NULL); + CHECK_VALIDITY_RETURN_ANY(rerror, dir == PA_STREAM_PLAYBACK || dir == PA_STREAM_RECORD, PA_ERR_INVALID, NULL); + CHECK_VALIDITY_RETURN_ANY(rerror, !dev || *dev, PA_ERR_INVALID, NULL); + CHECK_VALIDITY_RETURN_ANY(rerror, ss && pa_sample_spec_valid(ss), PA_ERR_INVALID, NULL); + CHECK_VALIDITY_RETURN_ANY(rerror, !map || (pa_channel_map_valid(map) && map->channels == ss->channels), PA_ERR_INVALID, NULL) + + p = pa_xnew0(pa_simple, 1); + p->direction = dir; + + if (!(p->mainloop = pa_threaded_mainloop_new())) + goto fail; + + if (!(p->context = pa_context_new(pa_threaded_mainloop_get_api(p->mainloop), name))) + goto fail; + + pa_context_set_state_callback(p->context, context_state_cb, p); + + if (pa_context_connect(p->context, server, 0, NULL) < 0) { + error = pa_context_errno(p->context); + goto fail; + } + + pa_threaded_mainloop_lock(p->mainloop); + + if (pa_threaded_mainloop_start(p->mainloop) < 0) + goto unlock_and_fail; + + for (;;) { + pa_context_state_t state; + + state = pa_context_get_state(p->context); + + if (state == PA_CONTEXT_READY) + break; + + if (!PA_CONTEXT_IS_GOOD(state)) { + error = pa_context_errno(p->context); + goto unlock_and_fail; + } + + /* Wait until the context is ready */ + pa_threaded_mainloop_wait(p->mainloop); + } + + if (!(p->stream = pa_stream_new(p->context, stream_name, ss, map))) { + error = pa_context_errno(p->context); + goto unlock_and_fail; + } + + pa_stream_set_state_callback(p->stream, stream_state_cb, p); + pa_stream_set_read_callback(p->stream, stream_request_cb, p); + pa_stream_set_write_callback(p->stream, stream_request_cb, p); + pa_stream_set_latency_update_callback(p->stream, stream_latency_update_cb, p); + + if (dir == PA_STREAM_PLAYBACK) + r = pa_stream_connect_playback(p->stream, dev, attr, + PA_STREAM_INTERPOLATE_TIMING + |PA_STREAM_ADJUST_LATENCY + |PA_STREAM_AUTO_TIMING_UPDATE, NULL, NULL); + else + r = pa_stream_connect_record(p->stream, dev, attr, + PA_STREAM_INTERPOLATE_TIMING + |PA_STREAM_ADJUST_LATENCY + |PA_STREAM_AUTO_TIMING_UPDATE); + + if (r < 0) { + error = pa_context_errno(p->context); + goto unlock_and_fail; + } + + for (;;) { + pa_stream_state_t state; + + state = pa_stream_get_state(p->stream); + + if (state == PA_STREAM_READY) + break; + + if (!PA_STREAM_IS_GOOD(state)) { + error = pa_context_errno(p->context); + goto unlock_and_fail; + } + + /* Wait until the stream is ready */ + pa_threaded_mainloop_wait(p->mainloop); + } + + pa_threaded_mainloop_unlock(p->mainloop); + + return p; + +unlock_and_fail: + pa_threaded_mainloop_unlock(p->mainloop); + +fail: + if (rerror) + *rerror = error; + pa_simple_free(p); + return NULL; +} + +void pa_simple_free(pa_simple *s) { + pa_assert(s); + + if (s->mainloop) + pa_threaded_mainloop_stop(s->mainloop); + + if (s->stream) + pa_stream_unref(s->stream); + + if (s->context) { + pa_context_disconnect(s->context); + pa_context_unref(s->context); + } + + if (s->mainloop) + pa_threaded_mainloop_free(s->mainloop); + + pa_xfree(s); +} + +int pa_simple_write(pa_simple *p, const void*data, size_t length, int *rerror) { + pa_assert(p); + + CHECK_VALIDITY_RETURN_ANY(rerror, p->direction == PA_STREAM_PLAYBACK, PA_ERR_BADSTATE, -1); + CHECK_VALIDITY_RETURN_ANY(rerror, data, PA_ERR_INVALID, -1); + CHECK_VALIDITY_RETURN_ANY(rerror, length > 0, PA_ERR_INVALID, -1); + + pa_threaded_mainloop_lock(p->mainloop); + + CHECK_DEAD_GOTO(p, rerror, unlock_and_fail); + + while (length > 0) { + size_t l; + int r; + + while (!(l = pa_stream_writable_size(p->stream))) { + pa_threaded_mainloop_wait(p->mainloop); + CHECK_DEAD_GOTO(p, rerror, unlock_and_fail); + } + + CHECK_SUCCESS_GOTO(p, rerror, l != (size_t) -1, unlock_and_fail); + + if (l > length) + l = length; + + r = pa_stream_write(p->stream, data, l, NULL, 0LL, PA_SEEK_RELATIVE); + CHECK_SUCCESS_GOTO(p, rerror, r >= 0, unlock_and_fail); + + data = (const uint8_t*) data + l; + length -= l; + } + + pa_threaded_mainloop_unlock(p->mainloop); + return 0; + +unlock_and_fail: + pa_threaded_mainloop_unlock(p->mainloop); + return -1; +} + +int pa_simple_read(pa_simple *p, void*data, size_t length, int *rerror) { + pa_assert(p); + + CHECK_VALIDITY_RETURN_ANY(rerror, p->direction == PA_STREAM_RECORD, PA_ERR_BADSTATE, -1); + CHECK_VALIDITY_RETURN_ANY(rerror, data, PA_ERR_INVALID, -1); + CHECK_VALIDITY_RETURN_ANY(rerror, length > 0, PA_ERR_INVALID, -1); + + pa_threaded_mainloop_lock(p->mainloop); + + CHECK_DEAD_GOTO(p, rerror, unlock_and_fail); + + while (length > 0) { + size_t l; + + while (!p->read_data) { + int r; + + r = pa_stream_peek(p->stream, &p->read_data, &p->read_length); + CHECK_SUCCESS_GOTO(p, rerror, r == 0, unlock_and_fail); + + if (p->read_length <= 0) { + pa_threaded_mainloop_wait(p->mainloop); + CHECK_DEAD_GOTO(p, rerror, unlock_and_fail); + } else if (!p->read_data) { + /* There's a hole in the stream, skip it. We could generate + * silence, but that wouldn't work for compressed streams. */ + r = pa_stream_drop(p->stream); + CHECK_SUCCESS_GOTO(p, rerror, r == 0, unlock_and_fail); + } else + p->read_index = 0; + } + + l = p->read_length < length ? p->read_length : length; + memcpy(data, (const uint8_t*) p->read_data+p->read_index, l); + + data = (uint8_t*) data + l; + length -= l; + + p->read_index += l; + p->read_length -= l; + + if (!p->read_length) { + int r; + + r = pa_stream_drop(p->stream); + p->read_data = NULL; + p->read_length = 0; + p->read_index = 0; + + CHECK_SUCCESS_GOTO(p, rerror, r == 0, unlock_and_fail); + } + } + + pa_threaded_mainloop_unlock(p->mainloop); + return 0; + +unlock_and_fail: + pa_threaded_mainloop_unlock(p->mainloop); + return -1; +} + +static void success_cb(pa_stream *s, int success, void *userdata) { + pa_simple *p = userdata; + + pa_assert(s); + pa_assert(p); + + p->operation_success = success; + pa_threaded_mainloop_signal(p->mainloop, 0); +} + +int pa_simple_drain(pa_simple *p, int *rerror) { + pa_operation *o = NULL; + + pa_assert(p); + + CHECK_VALIDITY_RETURN_ANY(rerror, p->direction == PA_STREAM_PLAYBACK, PA_ERR_BADSTATE, -1); + + pa_threaded_mainloop_lock(p->mainloop); + CHECK_DEAD_GOTO(p, rerror, unlock_and_fail); + + o = pa_stream_drain(p->stream, success_cb, p); + CHECK_SUCCESS_GOTO(p, rerror, o, unlock_and_fail); + + p->operation_success = 0; + while (pa_operation_get_state(o) == PA_OPERATION_RUNNING) { + pa_threaded_mainloop_wait(p->mainloop); + CHECK_DEAD_GOTO(p, rerror, unlock_and_fail); + } + CHECK_SUCCESS_GOTO(p, rerror, p->operation_success, unlock_and_fail); + + pa_operation_unref(o); + pa_threaded_mainloop_unlock(p->mainloop); + + return 0; + +unlock_and_fail: + + if (o) { + pa_operation_cancel(o); + pa_operation_unref(o); + } + + pa_threaded_mainloop_unlock(p->mainloop); + return -1; +} + +int pa_simple_flush(pa_simple *p, int *rerror) { + pa_operation *o = NULL; + + pa_assert(p); + + pa_threaded_mainloop_lock(p->mainloop); + CHECK_DEAD_GOTO(p, rerror, unlock_and_fail); + + o = pa_stream_flush(p->stream, success_cb, p); + CHECK_SUCCESS_GOTO(p, rerror, o, unlock_and_fail); + + p->operation_success = 0; + while (pa_operation_get_state(o) == PA_OPERATION_RUNNING) { + pa_threaded_mainloop_wait(p->mainloop); + CHECK_DEAD_GOTO(p, rerror, unlock_and_fail); + } + CHECK_SUCCESS_GOTO(p, rerror, p->operation_success, unlock_and_fail); + + pa_operation_unref(o); + pa_threaded_mainloop_unlock(p->mainloop); + + return 0; + +unlock_and_fail: + + if (o) { + pa_operation_cancel(o); + pa_operation_unref(o); + } + + pa_threaded_mainloop_unlock(p->mainloop); + return -1; +} + +pa_usec_t pa_simple_get_latency(pa_simple *p, int *rerror) { + pa_usec_t t; + + pa_assert(p); + + pa_threaded_mainloop_lock(p->mainloop); + + for (;;) { + int negative; + + CHECK_DEAD_GOTO(p, rerror, unlock_and_fail); + + if (pa_stream_get_latency(p->stream, &t, &negative) >= 0) { + if (p->direction == PA_STREAM_RECORD) { + pa_usec_t already_read; + + /* pa_simple_read() calls pa_stream_peek() to get the next + * chunk of audio. If the next chunk is larger than what the + * pa_simple_read() caller wanted, the leftover data is stored + * in p->read_data until pa_simple_read() is called again. + * pa_stream_drop() won't be called until the whole chunk has + * been consumed, which means that pa_stream_get_latency() will + * return too large values, because the whole size of the + * partially read chunk is included in the latency. Therefore, + * we need to subtract the already-read amount from the + * latency. */ + already_read = pa_bytes_to_usec(p->read_index, pa_stream_get_sample_spec(p->stream)); + + if (!negative) { + if (t > already_read) + t -= already_read; + else + t = 0; + } + } + + /* We don't have a way to report negative latencies from + * pa_simple_get_latency(). If the latency is negative, let's + * report zero. */ + if (negative) + t = 0; + + break; + } + + CHECK_SUCCESS_GOTO(p, rerror, pa_context_errno(p->context) == PA_ERR_NODATA, unlock_and_fail); + + /* Wait until latency data is available again */ + pa_threaded_mainloop_wait(p->mainloop); + } + + pa_threaded_mainloop_unlock(p->mainloop); + + return t; + +unlock_and_fail: + + pa_threaded_mainloop_unlock(p->mainloop); + return (pa_usec_t) -1; +} diff --git a/src/pulse/simple.h b/src/pulse/simple.h new file mode 100644 index 0000000..44ae65d --- /dev/null +++ b/src/pulse/simple.h @@ -0,0 +1,161 @@ +#ifndef foosimplehfoo +#define foosimplehfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/types.h> + +#include <pulse/sample.h> +#include <pulse/channelmap.h> +#include <pulse/def.h> +#include <pulse/cdecl.h> +#include <pulse/version.h> + +/** \page simple Simple API + * + * \section overv_sec Overview + * + * The simple API is designed for applications with very basic sound + * playback or capture needs. It can only support a single stream per + * connection and has no support for handling of complex features like + * events, channel mappings and volume control. It is, however, very simple + * to use and quite sufficient for many programs. + * + * \section conn_sec Connecting + * + * The first step before using the sound system is to connect to the + * server. This is normally done this way: + * + * \code + * pa_simple *s; + * pa_sample_spec ss; + * + * ss.format = PA_SAMPLE_S16NE; + * ss.channels = 2; + * ss.rate = 44100; + * + * s = pa_simple_new(NULL, // Use the default server. + * "Fooapp", // Our application's name. + * PA_STREAM_PLAYBACK, + * NULL, // Use the default device. + * "Music", // Description of our stream. + * &ss, // Our sample format. + * NULL, // Use default channel map + * NULL, // Use default buffering attributes. + * NULL, // Ignore error code. + * ); + * \endcode + * + * At this point a connected object is returned, or NULL if there was a + * problem connecting. + * + * \section transfer_sec Transferring data + * + * Once the connection is established to the server, data can start flowing. + * Using the connection is very similar to the normal read() and write() + * system calls. The main difference is that they're called pa_simple_read() + * and pa_simple_write(). Note that these operations always block. + * + * \section ctrl_sec Buffer control + * + * \li pa_simple_get_latency() - Will return the total latency of + * the playback or record pipeline, respectively. + * \li pa_simple_flush() - Will throw away all data currently in buffers. + * + * If a playback stream is used then the following operation is available: + * + * \li pa_simple_drain() - Will wait for all sent data to finish playing. + * + * \section cleanup_sec Cleanup + * + * Once playback or capture is complete, the connection should be closed + * and resources freed. This is done through: + * + * \code + * pa_simple_free(s); + * \endcode + */ + +/** \file + * A simple but limited synchronous playback and recording + * API. This is a synchronous, simplified wrapper around the standard + * asynchronous API. + * + * See also \subpage simple + */ + +/** \example pacat-simple.c + * A simple playback tool using the simple API */ + +/** \example parec-simple.c + * A simple recording tool using the simple API */ + +PA_C_DECL_BEGIN + +/** \struct pa_simple + * An opaque simple connection object */ +typedef struct pa_simple pa_simple; + +/** Create a new connection to the server. */ +pa_simple* pa_simple_new( + const char *server, /**< Server name, or NULL for default */ + const char *name, /**< A descriptive name for this client (application name, ...) */ + pa_stream_direction_t dir, /**< Open this stream for recording or playback? */ + const char *dev, /**< Sink (resp. source) name, or NULL for default */ + const char *stream_name, /**< A descriptive name for this stream (application name, song title, ...) */ + const pa_sample_spec *ss, /**< The sample type to use */ + const pa_channel_map *map, /**< The channel map to use, or NULL for default */ + const pa_buffer_attr *attr, /**< Buffering attributes, or NULL for default */ + int *error /**< A pointer where the error code is stored when the routine returns NULL. It is OK to pass NULL here. */ + ); + +/** Close and free the connection to the server. The connection object becomes invalid when this is called. */ +void pa_simple_free(pa_simple *s); + +/** Write some data to the server. Returns zero on success, negative on error. */ +int pa_simple_write(pa_simple *s, const void *data, size_t bytes, int *error); + +/** Wait until all data already written is played by the daemon. + * Returns zero on success, negative on error. */ +int pa_simple_drain(pa_simple *s, int *error); + +/** Read some data from the server. This function blocks until \a bytes amount + * of data has been received from the server, or until an error occurs. + * Returns zero on success, negative on failure. */ +int pa_simple_read( + pa_simple *s, /**< The connection object. */ + void *data, /**< A pointer to a buffer. */ + size_t bytes, /**< The number of bytes to read. */ + int *error + /**< A pointer where the error code is stored when the function returns + * a negative value. It is OK to pass NULL here. */ + ); + +/** Return the playback or record latency. */ +pa_usec_t pa_simple_get_latency(pa_simple *s, int *error); + +/** Flush the playback or record buffer. This discards any audio in the buffer. + * Returns zero on success, negative on error. */ +int pa_simple_flush(pa_simple *s, int *error); + +PA_C_DECL_END + +#endif diff --git a/src/pulse/stream.c b/src/pulse/stream.c new file mode 100644 index 0000000..f0c8034 --- /dev/null +++ b/src/pulse/stream.c @@ -0,0 +1,2927 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <stdio.h> +#include <string.h> + +#include <pulse/def.h> +#include <pulse/timeval.h> +#include <pulse/rtclock.h> +#include <pulse/xmalloc.h> +#include <pulse/fork-detect.h> + +#include <pulsecore/pstream-util.h> +#include <pulsecore/sample-util.h> +#include <pulsecore/log.h> +#include <pulsecore/hashmap.h> +#include <pulsecore/macro.h> +#include <pulsecore/core-rtclock.h> +#include <pulsecore/core-util.h> + +#include "internal.h" +#include "stream.h" + +/* #define STREAM_DEBUG */ + +#define AUTO_TIMING_INTERVAL_START_USEC (10*PA_USEC_PER_MSEC) +#define AUTO_TIMING_INTERVAL_END_USEC (1500*PA_USEC_PER_MSEC) + +#define SMOOTHER_ADJUST_TIME (1000*PA_USEC_PER_MSEC) +#define SMOOTHER_HISTORY_TIME (5000*PA_USEC_PER_MSEC) +#define SMOOTHER_MIN_HISTORY (4) + +pa_stream *pa_stream_new(pa_context *c, const char *name, const pa_sample_spec *ss, const pa_channel_map *map) { + return pa_stream_new_with_proplist(c, name, ss, map, NULL); +} + +static void reset_callbacks(pa_stream *s) { + s->read_callback = NULL; + s->read_userdata = NULL; + s->write_callback = NULL; + s->write_userdata = NULL; + s->state_callback = NULL; + s->state_userdata = NULL; + s->overflow_callback = NULL; + s->overflow_userdata = NULL; + s->underflow_callback = NULL; + s->underflow_userdata = NULL; + s->latency_update_callback = NULL; + s->latency_update_userdata = NULL; + s->moved_callback = NULL; + s->moved_userdata = NULL; + s->suspended_callback = NULL; + s->suspended_userdata = NULL; + s->started_callback = NULL; + s->started_userdata = NULL; + s->event_callback = NULL; + s->event_userdata = NULL; + s->buffer_attr_callback = NULL; + s->buffer_attr_userdata = NULL; +} + +static pa_stream *pa_stream_new_with_proplist_internal( + pa_context *c, + const char *name, + const pa_sample_spec *ss, + const pa_channel_map *map, + pa_format_info * const *formats, + unsigned int n_formats, + pa_proplist *p) { + + pa_stream *s; + unsigned int i; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert((ss == NULL && map == NULL) || (formats == NULL && n_formats == 0)); + pa_assert(n_formats < PA_MAX_FORMATS); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, name || (p && pa_proplist_contains(p, PA_PROP_MEDIA_NAME)), PA_ERR_INVALID); + + s = pa_xnew(pa_stream, 1); + PA_REFCNT_INIT(s); + s->context = c; + s->mainloop = c->mainloop; + + s->direction = PA_STREAM_NODIRECTION; + s->state = PA_STREAM_UNCONNECTED; + s->flags = 0; + + if (ss) + s->sample_spec = *ss; + else + pa_sample_spec_init(&s->sample_spec); + + if (map) + s->channel_map = *map; + else + pa_channel_map_init(&s->channel_map); + + s->n_formats = 0; + if (formats) { + s->n_formats = n_formats; + for (i = 0; i < n_formats; i++) + s->req_formats[i] = pa_format_info_copy(formats[i]); + } + + /* We'll get the final negotiated format after connecting */ + s->format = NULL; + + s->direct_on_input = PA_INVALID_INDEX; + + s->proplist = p ? pa_proplist_copy(p) : pa_proplist_new(); + if (name) + pa_proplist_sets(s->proplist, PA_PROP_MEDIA_NAME, name); + + s->channel = 0; + s->channel_valid = false; + s->syncid = c->csyncid++; + s->stream_index = PA_INVALID_INDEX; + + s->requested_bytes = 0; + memset(&s->buffer_attr, 0, sizeof(s->buffer_attr)); + + /* We initialize the target length here, so that if the user + * passes no explicit buffering metrics the default is similar to + * what older PA versions provided. */ + + s->buffer_attr.maxlength = (uint32_t) -1; + if (ss) + s->buffer_attr.tlength = (uint32_t) pa_usec_to_bytes(250*PA_USEC_PER_MSEC, ss); /* 250ms of buffering */ + else { + /* FIXME: We assume a worst-case compressed format corresponding to + * 48000 Hz, 2 ch, S16 PCM, but this can very well be incorrect */ + pa_sample_spec tmp_ss = { + .format = PA_SAMPLE_S16NE, + .rate = 48000, + .channels = 2, + }; + s->buffer_attr.tlength = (uint32_t) pa_usec_to_bytes(250*PA_USEC_PER_MSEC, &tmp_ss); /* 250ms of buffering */ + } + s->buffer_attr.minreq = (uint32_t) -1; + s->buffer_attr.prebuf = (uint32_t) -1; + s->buffer_attr.fragsize = (uint32_t) -1; + + s->device_index = PA_INVALID_INDEX; + s->device_name = NULL; + s->suspended = false; + s->corked = false; + + s->write_memblock = NULL; + s->write_data = NULL; + + pa_memchunk_reset(&s->peek_memchunk); + s->peek_data = NULL; + s->record_memblockq = NULL; + + memset(&s->timing_info, 0, sizeof(s->timing_info)); + s->timing_info_valid = false; + + s->previous_time = 0; + s->latest_underrun_at_index = -1; + + s->read_index_not_before = 0; + s->write_index_not_before = 0; + for (i = 0; i < PA_MAX_WRITE_INDEX_CORRECTIONS; i++) + s->write_index_corrections[i].valid = 0; + s->current_write_index_correction = 0; + + s->auto_timing_update_event = NULL; + s->auto_timing_update_requested = false; + s->auto_timing_interval_usec = AUTO_TIMING_INTERVAL_START_USEC; + + reset_callbacks(s); + + s->smoother = NULL; + + /* Refcounting is strictly one-way: from the "bigger" to the "smaller" object. */ + PA_LLIST_PREPEND(pa_stream, c->streams, s); + pa_stream_ref(s); + + return s; +} + +pa_stream *pa_stream_new_with_proplist( + pa_context *c, + const char *name, + const pa_sample_spec *ss, + const pa_channel_map *map, + pa_proplist *p) { + + pa_channel_map tmap; + + PA_CHECK_VALIDITY_RETURN_NULL(c, ss && pa_sample_spec_valid(ss), PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 12 || (ss->format != PA_SAMPLE_S32LE && ss->format != PA_SAMPLE_S32BE), PA_ERR_NOTSUPPORTED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 15 || (ss->format != PA_SAMPLE_S24LE && ss->format != PA_SAMPLE_S24BE), PA_ERR_NOTSUPPORTED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 15 || (ss->format != PA_SAMPLE_S24_32LE && ss->format != PA_SAMPLE_S24_32BE), PA_ERR_NOTSUPPORTED); + PA_CHECK_VALIDITY_RETURN_NULL(c, !map || (pa_channel_map_valid(map) && map->channels == ss->channels), PA_ERR_INVALID); + + if (!map) + PA_CHECK_VALIDITY_RETURN_NULL(c, map = pa_channel_map_init_auto(&tmap, ss->channels, PA_CHANNEL_MAP_DEFAULT), PA_ERR_INVALID); + + return pa_stream_new_with_proplist_internal(c, name, ss, map, NULL, 0, p); +} + +pa_stream *pa_stream_new_extended( + pa_context *c, + const char *name, + pa_format_info * const *formats, + unsigned int n_formats, + pa_proplist *p) { + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 21, PA_ERR_NOTSUPPORTED); + + return pa_stream_new_with_proplist_internal(c, name, NULL, NULL, formats, n_formats, p); +} + +static void stream_unlink(pa_stream *s) { + pa_operation *o, *n; + pa_assert(s); + + if (!s->context) + return; + + /* Detach from context */ + + /* Unref all operation objects that point to us */ + for (o = s->context->operations; o; o = n) { + n = o->next; + + if (o->stream == s) + pa_operation_cancel(o); + } + + /* Drop all outstanding replies for this stream */ + if (s->context->pdispatch) + pa_pdispatch_unregister_reply(s->context->pdispatch, s); + + if (s->channel_valid) { + pa_hashmap_remove((s->direction == PA_STREAM_RECORD) ? s->context->record_streams : s->context->playback_streams, PA_UINT32_TO_PTR(s->channel)); + s->channel = 0; + s->channel_valid = false; + } + + PA_LLIST_REMOVE(pa_stream, s->context->streams, s); + pa_stream_unref(s); + + s->context = NULL; + + if (s->auto_timing_update_event) { + pa_assert(s->mainloop); + s->mainloop->time_free(s->auto_timing_update_event); + } + + reset_callbacks(s); +} + +static void stream_free(pa_stream *s) { + unsigned int i; + + pa_assert(s); + + stream_unlink(s); + + if (s->write_memblock) { + if (s->write_data) + pa_memblock_release(s->write_memblock); + pa_memblock_unref(s->write_memblock); + } + + if (s->peek_memchunk.memblock) { + if (s->peek_data) + pa_memblock_release(s->peek_memchunk.memblock); + pa_memblock_unref(s->peek_memchunk.memblock); + } + + if (s->record_memblockq) + pa_memblockq_free(s->record_memblockq); + + if (s->proplist) + pa_proplist_free(s->proplist); + + if (s->smoother) + pa_smoother_free(s->smoother); + + for (i = 0; i < s->n_formats; i++) + pa_format_info_free(s->req_formats[i]); + + if (s->format) + pa_format_info_free(s->format); + + pa_xfree(s->device_name); + pa_xfree(s); +} + +void pa_stream_unref(pa_stream *s) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + if (PA_REFCNT_DEC(s) <= 0) + stream_free(s); +} + +pa_stream* pa_stream_ref(pa_stream *s) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_REFCNT_INC(s); + return s; +} + +pa_stream_state_t pa_stream_get_state(const pa_stream *s) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + return s->state; +} + +pa_context* pa_stream_get_context(const pa_stream *s) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + return s->context; +} + +uint32_t pa_stream_get_index(const pa_stream *s) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY_RETURN_ANY(s->context, !pa_detect_fork(), PA_ERR_FORKED, PA_INVALID_INDEX); + PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE, PA_INVALID_INDEX); + + return s->stream_index; +} + +void pa_stream_set_state(pa_stream *s, pa_stream_state_t st) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + if (s->state == st) + return; + + pa_stream_ref(s); + + s->state = st; + + if (s->state_callback) + s->state_callback(s, s->state_userdata); + + if ((st == PA_STREAM_FAILED || st == PA_STREAM_TERMINATED)) + stream_unlink(s); + + pa_stream_unref(s); +} + +static void request_auto_timing_update(pa_stream *s, bool force) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + if (!(s->flags & PA_STREAM_AUTO_TIMING_UPDATE)) + return; + + if (s->state == PA_STREAM_READY && + (force || !s->auto_timing_update_requested)) { + pa_operation *o; + +#ifdef STREAM_DEBUG + pa_log_debug("Automatically requesting new timing data"); +#endif + + if ((o = pa_stream_update_timing_info(s, NULL, NULL))) { + pa_operation_unref(o); + s->auto_timing_update_requested = true; + } + } + + if (s->auto_timing_update_event) { + if (s->suspended && !force) { + pa_assert(s->mainloop); + s->mainloop->time_free(s->auto_timing_update_event); + s->auto_timing_update_event = NULL; + } else { + if (force) + s->auto_timing_interval_usec = AUTO_TIMING_INTERVAL_START_USEC; + + pa_context_rttime_restart(s->context, s->auto_timing_update_event, pa_rtclock_now() + s->auto_timing_interval_usec); + + s->auto_timing_interval_usec = PA_MIN(AUTO_TIMING_INTERVAL_END_USEC, s->auto_timing_interval_usec*2); + } + } +} + +void pa_command_stream_killed(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_context *c = userdata; + pa_stream *s; + uint32_t channel; + + pa_assert(pd); + pa_assert(command == PA_COMMAND_PLAYBACK_STREAM_KILLED || command == PA_COMMAND_RECORD_STREAM_KILLED); + pa_assert(t); + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + pa_context_ref(c); + + if (pa_tagstruct_getu32(t, &channel) < 0 || + !pa_tagstruct_eof(t)) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + + if (!(s = pa_hashmap_get(command == PA_COMMAND_PLAYBACK_STREAM_KILLED ? c->playback_streams : c->record_streams, PA_UINT32_TO_PTR(channel)))) + goto finish; + + if (s->state != PA_STREAM_READY) + goto finish; + + pa_context_set_error(c, PA_ERR_KILLED); + pa_stream_set_state(s, PA_STREAM_FAILED); + +finish: + pa_context_unref(c); +} + +static void check_smoother_status(pa_stream *s, bool aposteriori, bool force_start, bool force_stop) { + pa_usec_t x; + + pa_assert(s); + pa_assert(!force_start || !force_stop); + + if (!s->smoother) + return; + + x = pa_rtclock_now(); + + if (s->timing_info_valid) { + if (aposteriori) + x -= s->timing_info.transport_usec; + else + x += s->timing_info.transport_usec; + } + + if (s->suspended || s->corked || force_stop) + pa_smoother_pause(s->smoother, x); + else if (force_start || s->buffer_attr.prebuf == 0) { + + if (!s->timing_info_valid && + !aposteriori && + !force_start && + !force_stop && + s->context->version >= 13) { + + /* If the server supports STARTED events we take them as + * indications when audio really starts/stops playing, if + * we don't have any timing info yet -- instead of trying + * to be smart and guessing the server time. Otherwise the + * unknown transport delay adds too much noise to our time + * calculations. */ + + return; + } + + pa_smoother_resume(s->smoother, x, true); + } + + /* Please note that we have no idea if playback actually started + * if prebuf is non-zero! */ +} + +static void auto_timing_update_callback(pa_mainloop_api *m, pa_time_event *e, const struct timeval *t, void *userdata); + +void pa_command_stream_moved(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_context *c = userdata; + pa_stream *s; + uint32_t channel; + const char *dn; + bool suspended; + uint32_t di; + pa_usec_t usec = 0; + uint32_t maxlength = 0, fragsize = 0, minreq = 0, tlength = 0, prebuf = 0; + + pa_assert(pd); + pa_assert(command == PA_COMMAND_PLAYBACK_STREAM_MOVED || command == PA_COMMAND_RECORD_STREAM_MOVED); + pa_assert(t); + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + pa_context_ref(c); + + if (c->version < 12) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + + if (pa_tagstruct_getu32(t, &channel) < 0 || + pa_tagstruct_getu32(t, &di) < 0 || + pa_tagstruct_gets(t, &dn) < 0 || + pa_tagstruct_get_boolean(t, &suspended) < 0) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + + if (c->version >= 13) { + + if (command == PA_COMMAND_RECORD_STREAM_MOVED) { + if (pa_tagstruct_getu32(t, &maxlength) < 0 || + pa_tagstruct_getu32(t, &fragsize) < 0 || + pa_tagstruct_get_usec(t, &usec) < 0) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + } else { + if (pa_tagstruct_getu32(t, &maxlength) < 0 || + pa_tagstruct_getu32(t, &tlength) < 0 || + pa_tagstruct_getu32(t, &prebuf) < 0 || + pa_tagstruct_getu32(t, &minreq) < 0 || + pa_tagstruct_get_usec(t, &usec) < 0) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + } + } + + if (!pa_tagstruct_eof(t)) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + + if (!dn || di == PA_INVALID_INDEX) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + + if (!(s = pa_hashmap_get(command == PA_COMMAND_PLAYBACK_STREAM_MOVED ? c->playback_streams : c->record_streams, PA_UINT32_TO_PTR(channel)))) + goto finish; + + if (s->state != PA_STREAM_READY) + goto finish; + + if (c->version >= 13) { + if (s->direction == PA_STREAM_RECORD) + s->timing_info.configured_source_usec = usec; + else + s->timing_info.configured_sink_usec = usec; + + s->buffer_attr.maxlength = maxlength; + s->buffer_attr.fragsize = fragsize; + s->buffer_attr.tlength = tlength; + s->buffer_attr.prebuf = prebuf; + s->buffer_attr.minreq = minreq; + } + + pa_xfree(s->device_name); + s->device_name = pa_xstrdup(dn); + s->device_index = di; + + s->suspended = suspended; + + if ((s->flags & PA_STREAM_AUTO_TIMING_UPDATE) && !suspended && !s->auto_timing_update_event) { + s->auto_timing_interval_usec = AUTO_TIMING_INTERVAL_START_USEC; + s->auto_timing_update_event = pa_context_rttime_new(s->context, pa_rtclock_now() + s->auto_timing_interval_usec, &auto_timing_update_callback, s); + request_auto_timing_update(s, true); + } + + check_smoother_status(s, true, false, false); + request_auto_timing_update(s, true); + + if (s->moved_callback) + s->moved_callback(s, s->moved_userdata); + +finish: + pa_context_unref(c); +} + +void pa_command_stream_buffer_attr(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_context *c = userdata; + pa_stream *s; + uint32_t channel; + pa_usec_t usec = 0; + uint32_t maxlength = 0, fragsize = 0, minreq = 0, tlength = 0, prebuf = 0; + + pa_assert(pd); + pa_assert(command == PA_COMMAND_PLAYBACK_BUFFER_ATTR_CHANGED || command == PA_COMMAND_RECORD_BUFFER_ATTR_CHANGED); + pa_assert(t); + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + pa_context_ref(c); + + if (c->version < 15) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + + if (pa_tagstruct_getu32(t, &channel) < 0) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + + if (command == PA_COMMAND_RECORD_STREAM_MOVED) { + if (pa_tagstruct_getu32(t, &maxlength) < 0 || + pa_tagstruct_getu32(t, &fragsize) < 0 || + pa_tagstruct_get_usec(t, &usec) < 0) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + } else { + if (pa_tagstruct_getu32(t, &maxlength) < 0 || + pa_tagstruct_getu32(t, &tlength) < 0 || + pa_tagstruct_getu32(t, &prebuf) < 0 || + pa_tagstruct_getu32(t, &minreq) < 0 || + pa_tagstruct_get_usec(t, &usec) < 0) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + } + + if (!pa_tagstruct_eof(t)) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + + if (!(s = pa_hashmap_get(command == PA_COMMAND_PLAYBACK_BUFFER_ATTR_CHANGED ? c->playback_streams : c->record_streams, PA_UINT32_TO_PTR(channel)))) + goto finish; + + if (s->state != PA_STREAM_READY) + goto finish; + + if (s->direction == PA_STREAM_RECORD) + s->timing_info.configured_source_usec = usec; + else + s->timing_info.configured_sink_usec = usec; + + s->buffer_attr.maxlength = maxlength; + s->buffer_attr.fragsize = fragsize; + s->buffer_attr.tlength = tlength; + s->buffer_attr.prebuf = prebuf; + s->buffer_attr.minreq = minreq; + + request_auto_timing_update(s, true); + + if (s->buffer_attr_callback) + s->buffer_attr_callback(s, s->buffer_attr_userdata); + +finish: + pa_context_unref(c); +} + +void pa_command_stream_suspended(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_context *c = userdata; + pa_stream *s; + uint32_t channel; + bool suspended; + + pa_assert(pd); + pa_assert(command == PA_COMMAND_PLAYBACK_STREAM_SUSPENDED || command == PA_COMMAND_RECORD_STREAM_SUSPENDED); + pa_assert(t); + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + pa_context_ref(c); + + if (c->version < 12) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + + if (pa_tagstruct_getu32(t, &channel) < 0 || + pa_tagstruct_get_boolean(t, &suspended) < 0 || + !pa_tagstruct_eof(t)) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + + if (!(s = pa_hashmap_get(command == PA_COMMAND_PLAYBACK_STREAM_SUSPENDED ? c->playback_streams : c->record_streams, PA_UINT32_TO_PTR(channel)))) + goto finish; + + if (s->state != PA_STREAM_READY) + goto finish; + + s->suspended = suspended; + + if ((s->flags & PA_STREAM_AUTO_TIMING_UPDATE) && !suspended && !s->auto_timing_update_event) { + s->auto_timing_interval_usec = AUTO_TIMING_INTERVAL_START_USEC; + s->auto_timing_update_event = pa_context_rttime_new(s->context, pa_rtclock_now() + s->auto_timing_interval_usec, &auto_timing_update_callback, s); + request_auto_timing_update(s, true); + } + + check_smoother_status(s, true, false, false); + request_auto_timing_update(s, true); + + if (s->suspended_callback) + s->suspended_callback(s, s->suspended_userdata); + +finish: + pa_context_unref(c); +} + +void pa_command_stream_started(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_context *c = userdata; + pa_stream *s; + uint32_t channel; + + pa_assert(pd); + pa_assert(command == PA_COMMAND_STARTED); + pa_assert(t); + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + pa_context_ref(c); + + if (c->version < 13) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + + if (pa_tagstruct_getu32(t, &channel) < 0 || + !pa_tagstruct_eof(t)) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + + if (!(s = pa_hashmap_get(c->playback_streams, PA_UINT32_TO_PTR(channel)))) + goto finish; + + if (s->state != PA_STREAM_READY) + goto finish; + + check_smoother_status(s, true, true, false); + request_auto_timing_update(s, true); + + if (s->started_callback) + s->started_callback(s, s->started_userdata); + +finish: + pa_context_unref(c); +} + +void pa_command_stream_event(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_context *c = userdata; + pa_stream *s; + uint32_t channel; + pa_proplist *pl = NULL; + const char *event; + + pa_assert(pd); + pa_assert(command == PA_COMMAND_PLAYBACK_STREAM_EVENT || command == PA_COMMAND_RECORD_STREAM_EVENT); + pa_assert(t); + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + pa_context_ref(c); + + if (c->version < 15) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + + pl = pa_proplist_new(); + + if (pa_tagstruct_getu32(t, &channel) < 0 || + pa_tagstruct_gets(t, &event) < 0 || + pa_tagstruct_get_proplist(t, pl) < 0 || + !pa_tagstruct_eof(t) || !event) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + + if (!(s = pa_hashmap_get(command == PA_COMMAND_PLAYBACK_STREAM_EVENT ? c->playback_streams : c->record_streams, PA_UINT32_TO_PTR(channel)))) + goto finish; + + if (s->state != PA_STREAM_READY) + goto finish; + + if (pa_streq(event, PA_STREAM_EVENT_FORMAT_LOST)) { + /* Let client know what the running time was when the stream had to be killed */ + pa_usec_t stream_time; + if (pa_stream_get_time(s, &stream_time) == 0) + pa_proplist_setf(pl, "stream-time", "%llu", (unsigned long long) stream_time); + } + + if (s->event_callback) + s->event_callback(s, event, pl, s->event_userdata); + +finish: + pa_context_unref(c); + + if (pl) + pa_proplist_free(pl); +} + +void pa_command_request(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_stream *s; + pa_context *c = userdata; + uint32_t bytes, channel; + + pa_assert(pd); + pa_assert(command == PA_COMMAND_REQUEST); + pa_assert(t); + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + pa_context_ref(c); + + if (pa_tagstruct_getu32(t, &channel) < 0 || + pa_tagstruct_getu32(t, &bytes) < 0 || + !pa_tagstruct_eof(t)) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + + if (!(s = pa_hashmap_get(c->playback_streams, PA_UINT32_TO_PTR(channel)))) + goto finish; + + if (s->state != PA_STREAM_READY) + goto finish; + + s->requested_bytes += bytes; + +#ifdef STREAM_DEBUG + pa_log_debug("got request for %lli, now at %lli", (long long) bytes, (long long) s->requested_bytes); +#endif + + if (s->requested_bytes > 0 && s->write_callback) + s->write_callback(s, (size_t) s->requested_bytes, s->write_userdata); + +finish: + pa_context_unref(c); +} + +int64_t pa_stream_get_underflow_index(const pa_stream *p) { + pa_assert(p); + return p->latest_underrun_at_index; +} + +void pa_command_overflow_or_underflow(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_stream *s; + pa_context *c = userdata; + uint32_t channel; + int64_t offset = -1; + + pa_assert(pd); + pa_assert(command == PA_COMMAND_OVERFLOW || command == PA_COMMAND_UNDERFLOW); + pa_assert(t); + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + pa_context_ref(c); + + if (pa_tagstruct_getu32(t, &channel) < 0) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + + if (c->version >= 23 && command == PA_COMMAND_UNDERFLOW) { + if (pa_tagstruct_gets64(t, &offset) < 0) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + } + + if (!pa_tagstruct_eof(t)) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + + if (!(s = pa_hashmap_get(c->playback_streams, PA_UINT32_TO_PTR(channel)))) + goto finish; + + if (s->state != PA_STREAM_READY) + goto finish; + + if (offset != -1) + s->latest_underrun_at_index = offset; + + if (s->buffer_attr.prebuf > 0) + check_smoother_status(s, true, false, true); + + request_auto_timing_update(s, true); + + if (command == PA_COMMAND_OVERFLOW) { + if (s->overflow_callback) + s->overflow_callback(s, s->overflow_userdata); + } else if (command == PA_COMMAND_UNDERFLOW) { + if (s->underflow_callback) + s->underflow_callback(s, s->underflow_userdata); + } + +finish: + pa_context_unref(c); +} + +static void invalidate_indexes(pa_stream *s, bool r, bool w) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + +#ifdef STREAM_DEBUG + pa_log_debug("invalidate r:%u w:%u tag:%u", r, w, s->context->ctag); +#endif + + if (s->state != PA_STREAM_READY) + return; + + if (w) { + s->write_index_not_before = s->context->ctag; + + if (s->timing_info_valid) + s->timing_info.write_index_corrupt = true; + +#ifdef STREAM_DEBUG + pa_log_debug("write_index invalidated"); +#endif + } + + if (r) { + s->read_index_not_before = s->context->ctag; + + if (s->timing_info_valid) + s->timing_info.read_index_corrupt = true; + +#ifdef STREAM_DEBUG + pa_log_debug("read_index invalidated"); +#endif + } + + request_auto_timing_update(s, true); +} + +static void auto_timing_update_callback(pa_mainloop_api *m, pa_time_event *e, const struct timeval *t, void *userdata) { + pa_stream *s = userdata; + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + pa_stream_ref(s); + request_auto_timing_update(s, false); + pa_stream_unref(s); +} + +static void create_stream_complete(pa_stream *s) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + pa_assert(s->state == PA_STREAM_CREATING); + + pa_stream_set_state(s, PA_STREAM_READY); + + if (s->requested_bytes > 0 && s->write_callback) + s->write_callback(s, (size_t) s->requested_bytes, s->write_userdata); + + if (s->flags & PA_STREAM_AUTO_TIMING_UPDATE) { + s->auto_timing_interval_usec = AUTO_TIMING_INTERVAL_START_USEC; + pa_assert(!s->auto_timing_update_event); + s->auto_timing_update_event = pa_context_rttime_new(s->context, pa_rtclock_now() + s->auto_timing_interval_usec, &auto_timing_update_callback, s); + + request_auto_timing_update(s, true); + } + + check_smoother_status(s, true, false, false); +} + +static void patch_buffer_attr(pa_stream *s, pa_buffer_attr *attr, pa_stream_flags_t *flags) { + const char *e; + + pa_assert(s); + pa_assert(attr); + + if ((e = getenv("PULSE_LATENCY_MSEC"))) { + uint32_t ms; + pa_sample_spec ss; + + pa_sample_spec_init(&ss); + + if (pa_sample_spec_valid(&s->sample_spec)) + ss = s->sample_spec; + else if (s->n_formats == 1) + pa_format_info_to_sample_spec(s->req_formats[0], &ss, NULL); + + if (pa_atou(e, &ms) < 0 || ms <= 0) + pa_log_debug("Failed to parse $PULSE_LATENCY_MSEC: %s", e); + else if (!pa_sample_spec_valid(&s->sample_spec)) + pa_log_debug("Ignoring $PULSE_LATENCY_MSEC: %s (invalid sample spec)", e); + else { + attr->maxlength = (uint32_t) -1; + attr->tlength = pa_usec_to_bytes(ms * PA_USEC_PER_MSEC, &ss); + attr->minreq = (uint32_t) -1; + attr->prebuf = (uint32_t) -1; + attr->fragsize = attr->tlength; + + if (flags) + *flags |= PA_STREAM_ADJUST_LATENCY; + } + } + + if (s->context->version >= 13) + return; + + /* Version older than 0.9.10 didn't do server side buffer_attr + * selection, hence we have to fake it on the client side. */ + + /* We choose fairly conservative values here, to not confuse + * old clients with extremely large playback buffers */ + + if (attr->maxlength == (uint32_t) -1) + attr->maxlength = 4*1024*1024; /* 4MB is the maximum queue length PulseAudio <= 0.9.9 supported. */ + + if (attr->tlength == (uint32_t) -1) + attr->tlength = (uint32_t) pa_usec_to_bytes(250*PA_USEC_PER_MSEC, &s->sample_spec); /* 250ms of buffering */ + + if (attr->minreq == (uint32_t) -1) + attr->minreq = (attr->tlength)/5; /* Ask for more data when there are only 200ms left in the playback buffer */ + + if (attr->prebuf == (uint32_t) -1) + attr->prebuf = attr->tlength; /* Start to play only when the playback is fully filled up once */ + + if (attr->fragsize == (uint32_t) -1) + attr->fragsize = attr->tlength; /* Pass data to the app only when the buffer is filled up once */ +} + +void pa_create_stream_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_stream *s = userdata; + uint32_t requested_bytes = 0; + + pa_assert(pd); + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + pa_assert(s->state == PA_STREAM_CREATING); + + pa_stream_ref(s); + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(s->context, command, t, false) < 0) + goto finish; + + pa_stream_set_state(s, PA_STREAM_FAILED); + goto finish; + } + + if (pa_tagstruct_getu32(t, &s->channel) < 0 || + s->channel == PA_INVALID_INDEX || + ((s->direction != PA_STREAM_UPLOAD) && (pa_tagstruct_getu32(t, &s->stream_index) < 0 || s->stream_index == PA_INVALID_INDEX)) || + ((s->direction != PA_STREAM_RECORD) && pa_tagstruct_getu32(t, &requested_bytes) < 0)) { + pa_context_fail(s->context, PA_ERR_PROTOCOL); + goto finish; + } + + s->requested_bytes = (int64_t) requested_bytes; + + if (s->context->version >= 9) { + if (s->direction == PA_STREAM_PLAYBACK) { + if (pa_tagstruct_getu32(t, &s->buffer_attr.maxlength) < 0 || + pa_tagstruct_getu32(t, &s->buffer_attr.tlength) < 0 || + pa_tagstruct_getu32(t, &s->buffer_attr.prebuf) < 0 || + pa_tagstruct_getu32(t, &s->buffer_attr.minreq) < 0) { + pa_context_fail(s->context, PA_ERR_PROTOCOL); + goto finish; + } + } else if (s->direction == PA_STREAM_RECORD) { + if (pa_tagstruct_getu32(t, &s->buffer_attr.maxlength) < 0 || + pa_tagstruct_getu32(t, &s->buffer_attr.fragsize) < 0) { + pa_context_fail(s->context, PA_ERR_PROTOCOL); + goto finish; + } + } + } + + if (s->context->version >= 12 && s->direction != PA_STREAM_UPLOAD) { + pa_sample_spec ss; + pa_channel_map cm; + const char *dn = NULL; + bool suspended; + + if (pa_tagstruct_get_sample_spec(t, &ss) < 0 || + pa_tagstruct_get_channel_map(t, &cm) < 0 || + pa_tagstruct_getu32(t, &s->device_index) < 0 || + pa_tagstruct_gets(t, &dn) < 0 || + pa_tagstruct_get_boolean(t, &suspended) < 0) { + pa_context_fail(s->context, PA_ERR_PROTOCOL); + goto finish; + } + + if (!dn || s->device_index == PA_INVALID_INDEX || + ss.channels != cm.channels || + !pa_channel_map_valid(&cm) || + !pa_sample_spec_valid(&ss) || + (s->n_formats == 0 && ( + (!(s->flags & PA_STREAM_FIX_FORMAT) && ss.format != s->sample_spec.format) || + (!(s->flags & PA_STREAM_FIX_RATE) && ss.rate != s->sample_spec.rate) || + (!(s->flags & PA_STREAM_FIX_CHANNELS) && !pa_channel_map_equal(&cm, &s->channel_map))))) { + pa_context_fail(s->context, PA_ERR_PROTOCOL); + goto finish; + } + + pa_xfree(s->device_name); + s->device_name = pa_xstrdup(dn); + s->suspended = suspended; + + s->channel_map = cm; + s->sample_spec = ss; + } + + if (s->context->version >= 13 && s->direction != PA_STREAM_UPLOAD) { + pa_usec_t usec; + + if (pa_tagstruct_get_usec(t, &usec) < 0) { + pa_context_fail(s->context, PA_ERR_PROTOCOL); + goto finish; + } + + if (s->direction == PA_STREAM_RECORD) + s->timing_info.configured_source_usec = usec; + else + s->timing_info.configured_sink_usec = usec; + } + + if ((s->context->version >= 21 && s->direction == PA_STREAM_PLAYBACK) + || s->context->version >= 22) { + + pa_format_info *f = pa_format_info_new(); + + if (pa_tagstruct_get_format_info(t, f) < 0 || !pa_format_info_valid(f)) { + pa_format_info_free(f); + if (s->n_formats > 0) { + /* We used the extended API, so we should have got back a proper format */ + pa_context_fail(s->context, PA_ERR_PROTOCOL); + goto finish; + } + } else + s->format = f; + } + + if (!pa_tagstruct_eof(t)) { + pa_context_fail(s->context, PA_ERR_PROTOCOL); + goto finish; + } + + if (s->direction == PA_STREAM_RECORD) { + pa_assert(!s->record_memblockq); + + s->record_memblockq = pa_memblockq_new( + "client side record memblockq", + 0, + s->buffer_attr.maxlength, + 0, + &s->sample_spec, + 1, + 0, + 0, + NULL); + } + + s->channel_valid = true; + pa_hashmap_put((s->direction == PA_STREAM_RECORD) ? s->context->record_streams : s->context->playback_streams, PA_UINT32_TO_PTR(s->channel), s); + + create_stream_complete(s); + +finish: + pa_stream_unref(s); +} + +static int create_stream( + pa_stream_direction_t direction, + pa_stream *s, + const char *dev, + const pa_buffer_attr *attr, + pa_stream_flags_t flags, + const pa_cvolume *volume, + pa_stream *sync_stream) { + + pa_tagstruct *t; + uint32_t tag; + bool volume_set = !!volume; + pa_cvolume cv; + uint32_t i; + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + pa_assert(direction == PA_STREAM_PLAYBACK || direction == PA_STREAM_RECORD); + + PA_CHECK_VALIDITY(s->context, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_UNCONNECTED, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, s->direct_on_input == PA_INVALID_INDEX || direction == PA_STREAM_RECORD, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, !(flags & ~(PA_STREAM_START_CORKED| + PA_STREAM_INTERPOLATE_TIMING| + PA_STREAM_NOT_MONOTONIC| + PA_STREAM_AUTO_TIMING_UPDATE| + PA_STREAM_NO_REMAP_CHANNELS| + PA_STREAM_NO_REMIX_CHANNELS| + PA_STREAM_FIX_FORMAT| + PA_STREAM_FIX_RATE| + PA_STREAM_FIX_CHANNELS| + PA_STREAM_DONT_MOVE| + PA_STREAM_VARIABLE_RATE| + PA_STREAM_PEAK_DETECT| + PA_STREAM_START_MUTED| + PA_STREAM_ADJUST_LATENCY| + PA_STREAM_EARLY_REQUESTS| + PA_STREAM_DONT_INHIBIT_AUTO_SUSPEND| + PA_STREAM_START_UNMUTED| + PA_STREAM_FAIL_ON_SUSPEND| + PA_STREAM_RELATIVE_VOLUME| + PA_STREAM_PASSTHROUGH)), PA_ERR_INVALID); + + PA_CHECK_VALIDITY(s->context, s->context->version >= 12 || !(flags & PA_STREAM_VARIABLE_RATE), PA_ERR_NOTSUPPORTED); + PA_CHECK_VALIDITY(s->context, s->context->version >= 13 || !(flags & PA_STREAM_PEAK_DETECT), PA_ERR_NOTSUPPORTED); + PA_CHECK_VALIDITY(s->context, s->context->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + /* Although some of the other flags are not supported on older + * version, we don't check for them here, because it doesn't hurt + * when they are passed but actually not supported. This makes + * client development easier */ + + PA_CHECK_VALIDITY(s->context, direction == PA_STREAM_RECORD || !(flags & (PA_STREAM_PEAK_DETECT)), PA_ERR_INVALID); + PA_CHECK_VALIDITY(s->context, !sync_stream || (direction == PA_STREAM_PLAYBACK && sync_stream->direction == PA_STREAM_PLAYBACK), PA_ERR_INVALID); + PA_CHECK_VALIDITY(s->context, (flags & (PA_STREAM_ADJUST_LATENCY|PA_STREAM_EARLY_REQUESTS)) != (PA_STREAM_ADJUST_LATENCY|PA_STREAM_EARLY_REQUESTS), PA_ERR_INVALID); + + pa_stream_ref(s); + + s->direction = direction; + + if (sync_stream) + s->syncid = sync_stream->syncid; + + if (attr) + s->buffer_attr = *attr; + patch_buffer_attr(s, &s->buffer_attr, &flags); + + s->flags = flags; + s->corked = !!(flags & PA_STREAM_START_CORKED); + + if (flags & PA_STREAM_INTERPOLATE_TIMING) { + pa_usec_t x; + + x = pa_rtclock_now(); + + pa_assert(!s->smoother); + s->smoother = pa_smoother_new( + SMOOTHER_ADJUST_TIME, + SMOOTHER_HISTORY_TIME, + !(flags & PA_STREAM_NOT_MONOTONIC), + true, + SMOOTHER_MIN_HISTORY, + x, + true); + } + + if (!dev) + dev = s->direction == PA_STREAM_PLAYBACK ? s->context->conf->default_sink : s->context->conf->default_source; + + t = pa_tagstruct_command( + s->context, + (uint32_t) (s->direction == PA_STREAM_PLAYBACK ? PA_COMMAND_CREATE_PLAYBACK_STREAM : PA_COMMAND_CREATE_RECORD_STREAM), + &tag); + + if (s->context->version < 13) + pa_tagstruct_puts(t, pa_proplist_gets(s->proplist, PA_PROP_MEDIA_NAME)); + + pa_tagstruct_put( + t, + PA_TAG_SAMPLE_SPEC, &s->sample_spec, + PA_TAG_CHANNEL_MAP, &s->channel_map, + PA_TAG_U32, PA_INVALID_INDEX, + PA_TAG_STRING, dev, + PA_TAG_U32, s->buffer_attr.maxlength, + PA_TAG_BOOLEAN, s->corked, + PA_TAG_INVALID); + + if (!volume) { + if (pa_sample_spec_valid(&s->sample_spec)) + volume = pa_cvolume_reset(&cv, s->sample_spec.channels); + else { + /* This is not really relevant, since no volume was set, and + * the real number of channels is embedded in the format_info + * structure */ + volume = pa_cvolume_reset(&cv, PA_CHANNELS_MAX); + } + } + + if (s->direction == PA_STREAM_PLAYBACK) { + pa_tagstruct_put( + t, + PA_TAG_U32, s->buffer_attr.tlength, + PA_TAG_U32, s->buffer_attr.prebuf, + PA_TAG_U32, s->buffer_attr.minreq, + PA_TAG_U32, s->syncid, + PA_TAG_INVALID); + + pa_tagstruct_put_cvolume(t, volume); + } else + pa_tagstruct_putu32(t, s->buffer_attr.fragsize); + + if (s->context->version >= 12) { + pa_tagstruct_put( + t, + PA_TAG_BOOLEAN, flags & PA_STREAM_NO_REMAP_CHANNELS, + PA_TAG_BOOLEAN, flags & PA_STREAM_NO_REMIX_CHANNELS, + PA_TAG_BOOLEAN, flags & PA_STREAM_FIX_FORMAT, + PA_TAG_BOOLEAN, flags & PA_STREAM_FIX_RATE, + PA_TAG_BOOLEAN, flags & PA_STREAM_FIX_CHANNELS, + PA_TAG_BOOLEAN, flags & PA_STREAM_DONT_MOVE, + PA_TAG_BOOLEAN, flags & PA_STREAM_VARIABLE_RATE, + PA_TAG_INVALID); + } + + if (s->context->version >= 13) { + + if (s->direction == PA_STREAM_PLAYBACK) + pa_tagstruct_put_boolean(t, flags & PA_STREAM_START_MUTED); + else + pa_tagstruct_put_boolean(t, flags & PA_STREAM_PEAK_DETECT); + + pa_tagstruct_put( + t, + PA_TAG_BOOLEAN, flags & PA_STREAM_ADJUST_LATENCY, + PA_TAG_PROPLIST, s->proplist, + PA_TAG_INVALID); + + if (s->direction == PA_STREAM_RECORD) + pa_tagstruct_putu32(t, s->direct_on_input); + } + + if (s->context->version >= 14) { + + if (s->direction == PA_STREAM_PLAYBACK) + pa_tagstruct_put_boolean(t, volume_set); + + pa_tagstruct_put_boolean(t, flags & PA_STREAM_EARLY_REQUESTS); + } + + if (s->context->version >= 15) { + + if (s->direction == PA_STREAM_PLAYBACK) + pa_tagstruct_put_boolean(t, flags & (PA_STREAM_START_MUTED|PA_STREAM_START_UNMUTED)); + + pa_tagstruct_put_boolean(t, flags & PA_STREAM_DONT_INHIBIT_AUTO_SUSPEND); + pa_tagstruct_put_boolean(t, flags & PA_STREAM_FAIL_ON_SUSPEND); + } + + if (s->context->version >= 17 && s->direction == PA_STREAM_PLAYBACK) + pa_tagstruct_put_boolean(t, flags & PA_STREAM_RELATIVE_VOLUME); + + if (s->context->version >= 18 && s->direction == PA_STREAM_PLAYBACK) + pa_tagstruct_put_boolean(t, flags & (PA_STREAM_PASSTHROUGH)); + + if ((s->context->version >= 21 && s->direction == PA_STREAM_PLAYBACK) + || s->context->version >= 22) { + + pa_tagstruct_putu8(t, s->n_formats); + for (i = 0; i < s->n_formats; i++) + pa_tagstruct_put_format_info(t, s->req_formats[i]); + } + + if (s->context->version >= 22 && s->direction == PA_STREAM_RECORD) { + pa_tagstruct_put_cvolume(t, volume); + pa_tagstruct_put_boolean(t, flags & PA_STREAM_START_MUTED); + pa_tagstruct_put_boolean(t, volume_set); + pa_tagstruct_put_boolean(t, flags & (PA_STREAM_START_MUTED|PA_STREAM_START_UNMUTED)); + pa_tagstruct_put_boolean(t, flags & PA_STREAM_RELATIVE_VOLUME); + pa_tagstruct_put_boolean(t, flags & (PA_STREAM_PASSTHROUGH)); + } + + pa_pstream_send_tagstruct(s->context->pstream, t); + pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_create_stream_callback, s, NULL); + + pa_stream_set_state(s, PA_STREAM_CREATING); + + pa_stream_unref(s); + return 0; +} + +int pa_stream_connect_playback( + pa_stream *s, + const char *dev, + const pa_buffer_attr *attr, + pa_stream_flags_t flags, + const pa_cvolume *volume, + pa_stream *sync_stream) { + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + return create_stream(PA_STREAM_PLAYBACK, s, dev, attr, flags, volume, sync_stream); +} + +int pa_stream_connect_record( + pa_stream *s, + const char *dev, + const pa_buffer_attr *attr, + pa_stream_flags_t flags) { + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + return create_stream(PA_STREAM_RECORD, s, dev, attr, flags, NULL, NULL); +} + +int pa_stream_begin_write( + pa_stream *s, + void **data, + size_t *nbytes) { + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY(s->context, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, s->direction == PA_STREAM_PLAYBACK || s->direction == PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, data, PA_ERR_INVALID); + PA_CHECK_VALIDITY(s->context, nbytes && *nbytes != 0, PA_ERR_INVALID); + + if (*nbytes != (size_t) -1) { + size_t m, fs; + + m = pa_mempool_block_size_max(s->context->mempool); + fs = pa_frame_size(&s->sample_spec); + + m = (m / fs) * fs; + if (*nbytes > m) + *nbytes = m; + } + + if (!s->write_memblock) { + s->write_memblock = pa_memblock_new(s->context->mempool, *nbytes); + s->write_data = pa_memblock_acquire(s->write_memblock); + } + + *data = s->write_data; + *nbytes = pa_memblock_get_length(s->write_memblock); + + return 0; +} + +int pa_stream_cancel_write( + pa_stream *s) { + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY(s->context, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, s->direction == PA_STREAM_PLAYBACK || s->direction == PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, s->write_memblock, PA_ERR_BADSTATE); + + pa_assert(s->write_data); + + pa_memblock_release(s->write_memblock); + pa_memblock_unref(s->write_memblock); + s->write_memblock = NULL; + s->write_data = NULL; + + return 0; +} + +int pa_stream_write_ext_free( + pa_stream *s, + const void *data, + size_t length, + pa_free_cb_t free_cb, + void *free_cb_data, + int64_t offset, + pa_seek_mode_t seek) { + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + pa_assert(data); + + PA_CHECK_VALIDITY(s->context, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, s->direction == PA_STREAM_PLAYBACK || s->direction == PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, seek <= PA_SEEK_RELATIVE_END, PA_ERR_INVALID); + PA_CHECK_VALIDITY(s->context, s->direction == PA_STREAM_PLAYBACK || (seek == PA_SEEK_RELATIVE && offset == 0), PA_ERR_INVALID); + PA_CHECK_VALIDITY(s->context, + !s->write_memblock || + ((data >= s->write_data) && + ((const char*) data + length <= (const char*) s->write_data + pa_memblock_get_length(s->write_memblock))), + PA_ERR_INVALID); + PA_CHECK_VALIDITY(s->context, offset % pa_frame_size(&s->sample_spec) == 0, PA_ERR_INVALID); + PA_CHECK_VALIDITY(s->context, length % pa_frame_size(&s->sample_spec) == 0, PA_ERR_INVALID); + PA_CHECK_VALIDITY(s->context, !free_cb || !s->write_memblock, PA_ERR_INVALID); + + if (s->write_memblock) { + pa_memchunk chunk; + + /* pa_stream_write_begin() was called before */ + + pa_memblock_release(s->write_memblock); + + chunk.memblock = s->write_memblock; + chunk.index = (const char *) data - (const char *) s->write_data; + chunk.length = length; + + s->write_memblock = NULL; + s->write_data = NULL; + + pa_pstream_send_memblock(s->context->pstream, s->channel, offset, seek, &chunk); + pa_memblock_unref(chunk.memblock); + + } else { + pa_seek_mode_t t_seek = seek; + int64_t t_offset = offset; + size_t t_length = length; + const void *t_data = data; + + /* pa_stream_write_begin() was not called before */ + + while (t_length > 0) { + pa_memchunk chunk; + + chunk.index = 0; + + if (free_cb && !pa_pstream_get_shm(s->context->pstream)) { + chunk.memblock = pa_memblock_new_user(s->context->mempool, (void*) t_data, t_length, free_cb, free_cb_data, 1); + chunk.length = t_length; + } else { + void *d; + size_t blk_size_max; + + /* Break large audio streams into _aligned_ blocks or the + * other endpoint will happily discard them upon arrival. */ + blk_size_max = pa_frame_align(pa_mempool_block_size_max(s->context->mempool), &s->sample_spec); + chunk.length = PA_MIN(t_length, blk_size_max); + chunk.memblock = pa_memblock_new(s->context->mempool, chunk.length); + + d = pa_memblock_acquire(chunk.memblock); + memcpy(d, t_data, chunk.length); + pa_memblock_release(chunk.memblock); + } + + pa_pstream_send_memblock(s->context->pstream, s->channel, t_offset, t_seek, &chunk); + + t_offset = 0; + t_seek = PA_SEEK_RELATIVE; + + t_data = (const uint8_t*) t_data + chunk.length; + t_length -= chunk.length; + + pa_memblock_unref(chunk.memblock); + } + + if (free_cb && pa_pstream_get_shm(s->context->pstream)) + free_cb(free_cb_data); + } + + /* This is obviously wrong since we ignore the seeking index . But + * that's OK, the server side applies the same error */ + s->requested_bytes -= (seek == PA_SEEK_RELATIVE ? offset : 0) + (int64_t) length; + +#ifdef STREAM_DEBUG + pa_log_debug("wrote %lli, now at %lli", (long long) length, (long long) s->requested_bytes); +#endif + + if (s->direction == PA_STREAM_PLAYBACK) { + + /* Update latency request correction */ + if (s->write_index_corrections[s->current_write_index_correction].valid) { + + if (seek == PA_SEEK_ABSOLUTE) { + s->write_index_corrections[s->current_write_index_correction].corrupt = false; + s->write_index_corrections[s->current_write_index_correction].absolute = true; + s->write_index_corrections[s->current_write_index_correction].value = offset + (int64_t) length; + } else if (seek == PA_SEEK_RELATIVE) { + if (!s->write_index_corrections[s->current_write_index_correction].corrupt) + s->write_index_corrections[s->current_write_index_correction].value += offset + (int64_t) length; + } else + s->write_index_corrections[s->current_write_index_correction].corrupt = true; + } + + /* Update the write index in the already available latency data */ + if (s->timing_info_valid) { + + if (seek == PA_SEEK_ABSOLUTE) { + s->timing_info.write_index_corrupt = false; + s->timing_info.write_index = offset + (int64_t) length; + } else if (seek == PA_SEEK_RELATIVE) { + if (!s->timing_info.write_index_corrupt) + s->timing_info.write_index += offset + (int64_t) length; + } else + s->timing_info.write_index_corrupt = true; + } + + if (!s->timing_info_valid || s->timing_info.write_index_corrupt) + request_auto_timing_update(s, true); + } + + return 0; +} + +int pa_stream_write( + pa_stream *s, + const void *data, + size_t length, + pa_free_cb_t free_cb, + int64_t offset, + pa_seek_mode_t seek) { + + return pa_stream_write_ext_free(s, data, length, free_cb, (void*) data, offset, seek); +} + +int pa_stream_peek(pa_stream *s, const void **data, size_t *length) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + pa_assert(data); + pa_assert(length); + + PA_CHECK_VALIDITY(s->context, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, s->direction == PA_STREAM_RECORD, PA_ERR_BADSTATE); + + if (!s->peek_memchunk.memblock) { + + if (pa_memblockq_peek(s->record_memblockq, &s->peek_memchunk) < 0) { + /* record_memblockq is empty. */ + *data = NULL; + *length = 0; + return 0; + + } else if (!s->peek_memchunk.memblock) { + /* record_memblockq isn't empty, but it doesn't have any data at + * the current read index. */ + *data = NULL; + *length = s->peek_memchunk.length; + return 0; + } + + s->peek_data = pa_memblock_acquire(s->peek_memchunk.memblock); + } + + pa_assert(s->peek_data); + *data = (uint8_t*) s->peek_data + s->peek_memchunk.index; + *length = s->peek_memchunk.length; + return 0; +} + +int pa_stream_drop(pa_stream *s) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY(s->context, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, s->direction == PA_STREAM_RECORD, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, s->peek_memchunk.length > 0, PA_ERR_BADSTATE); + + pa_memblockq_drop(s->record_memblockq, s->peek_memchunk.length); + + /* Fix the simulated local read index */ + if (s->timing_info_valid && !s->timing_info.read_index_corrupt) + s->timing_info.read_index += (int64_t) s->peek_memchunk.length; + + if (s->peek_memchunk.memblock) { + pa_assert(s->peek_data); + s->peek_data = NULL; + pa_memblock_release(s->peek_memchunk.memblock); + pa_memblock_unref(s->peek_memchunk.memblock); + } + + pa_memchunk_reset(&s->peek_memchunk); + + return 0; +} + +size_t pa_stream_writable_size(const pa_stream *s) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY_RETURN_ANY(s->context, !pa_detect_fork(), PA_ERR_FORKED, (size_t) -1); + PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE, (size_t) -1); + PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->direction != PA_STREAM_RECORD, PA_ERR_BADSTATE, (size_t) -1); + + return s->requested_bytes > 0 ? (size_t) s->requested_bytes : 0; +} + +size_t pa_stream_readable_size(const pa_stream *s) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY_RETURN_ANY(s->context, !pa_detect_fork(), PA_ERR_FORKED, (size_t) -1); + PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE, (size_t) -1); + PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->direction == PA_STREAM_RECORD, PA_ERR_BADSTATE, (size_t) -1); + + return pa_memblockq_get_length(s->record_memblockq); +} + +pa_operation * pa_stream_drain(pa_stream *s, pa_stream_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(s->context, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction == PA_STREAM_PLAYBACK, PA_ERR_BADSTATE); + + /* Ask for a timing update before we cork/uncork to get the best + * accuracy for the transport latency suitable for the + * check_smoother_status() call in the started callback */ + request_auto_timing_update(s, true); + + o = pa_operation_new(s->context, s, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(s->context, PA_COMMAND_DRAIN_PLAYBACK_STREAM, &tag); + pa_tagstruct_putu32(t, s->channel); + pa_pstream_send_tagstruct(s->context->pstream, t); + pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_stream_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + /* This might cause the read index to continue again, hence + * let's request a timing update */ + request_auto_timing_update(s, true); + + return o; +} + +static pa_usec_t calc_time(const pa_stream *s, bool ignore_transport) { + pa_usec_t usec; + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + pa_assert(s->state == PA_STREAM_READY); + pa_assert(s->direction != PA_STREAM_UPLOAD); + pa_assert(s->timing_info_valid); + pa_assert(s->direction != PA_STREAM_PLAYBACK || !s->timing_info.read_index_corrupt); + pa_assert(s->direction != PA_STREAM_RECORD || !s->timing_info.write_index_corrupt); + + if (s->direction == PA_STREAM_PLAYBACK) { + /* The last byte that was written into the output device + * had this time value associated */ + usec = pa_bytes_to_usec(s->timing_info.read_index < 0 ? 0 : (uint64_t) s->timing_info.read_index, &s->sample_spec); + + if (!s->corked && !s->suspended) { + + if (!ignore_transport) + /* Because the latency info took a little time to come + * to us, we assume that the real output time is actually + * a little ahead */ + usec += s->timing_info.transport_usec; + + /* However, the output device usually maintains a buffer + too, hence the real sample currently played is a little + back */ + if (s->timing_info.sink_usec >= usec) + usec = 0; + else + usec -= s->timing_info.sink_usec; + } + + } else { + pa_assert(s->direction == PA_STREAM_RECORD); + + /* The last byte written into the server side queue had + * this time value associated */ + usec = pa_bytes_to_usec(s->timing_info.write_index < 0 ? 0 : (uint64_t) s->timing_info.write_index, &s->sample_spec); + + if (!s->corked && !s->suspended) { + + if (!ignore_transport) + /* Add transport latency */ + usec += s->timing_info.transport_usec; + + /* Add latency of data in device buffer */ + usec += s->timing_info.source_usec; + + /* If this is a monitor source, we need to correct the + * time by the playback device buffer */ + if (s->timing_info.sink_usec >= usec) + usec = 0; + else + usec -= s->timing_info.sink_usec; + } + } + + return usec; +} + +static void stream_get_timing_info_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_operation *o = userdata; + struct timeval local, remote, now; + pa_timing_info *i; + bool playing = false; + uint64_t underrun_for = 0, playing_for = 0; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (!o->context || !o->stream) + goto finish; + + i = &o->stream->timing_info; + + o->stream->timing_info_valid = false; + i->write_index_corrupt = true; + i->read_index_corrupt = true; + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(o->context, command, t, false) < 0) + goto finish; + + } else { + + if (pa_tagstruct_get_usec(t, &i->sink_usec) < 0 || + pa_tagstruct_get_usec(t, &i->source_usec) < 0 || + pa_tagstruct_get_boolean(t, &playing) < 0 || + pa_tagstruct_get_timeval(t, &local) < 0 || + pa_tagstruct_get_timeval(t, &remote) < 0 || + pa_tagstruct_gets64(t, &i->write_index) < 0 || + pa_tagstruct_gets64(t, &i->read_index) < 0) { + + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + + if (o->context->version >= 13 && + o->stream->direction == PA_STREAM_PLAYBACK) + if (pa_tagstruct_getu64(t, &underrun_for) < 0 || + pa_tagstruct_getu64(t, &playing_for) < 0) { + + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + + if (!pa_tagstruct_eof(t)) { + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + o->stream->timing_info_valid = true; + i->write_index_corrupt = false; + i->read_index_corrupt = false; + + i->playing = (int) playing; + i->since_underrun = (int64_t) (playing ? playing_for : underrun_for); + + pa_gettimeofday(&now); + + /* Calculate timestamps */ + if (pa_timeval_cmp(&local, &remote) <= 0 && pa_timeval_cmp(&remote, &now) <= 0) { + /* local and remote seem to have synchronized clocks */ + + if (o->stream->direction == PA_STREAM_PLAYBACK) + i->transport_usec = pa_timeval_diff(&remote, &local); + else + i->transport_usec = pa_timeval_diff(&now, &remote); + + i->synchronized_clocks = true; + i->timestamp = remote; + } else { + /* clocks are not synchronized, let's estimate latency then */ + i->transport_usec = pa_timeval_diff(&now, &local)/2; + i->synchronized_clocks = false; + i->timestamp = local; + pa_timeval_add(&i->timestamp, i->transport_usec); + } + + /* Invalidate read and write indexes if necessary */ + if (tag < o->stream->read_index_not_before) + i->read_index_corrupt = true; + + if (tag < o->stream->write_index_not_before) + i->write_index_corrupt = true; + + if (o->stream->direction == PA_STREAM_PLAYBACK) { + /* Write index correction */ + + int n, j; + uint32_t ctag = tag; + + /* Go through the saved correction values and add up the + * total correction.*/ + for (n = 0, j = o->stream->current_write_index_correction+1; + n < PA_MAX_WRITE_INDEX_CORRECTIONS; + n++, j = (j + 1) % PA_MAX_WRITE_INDEX_CORRECTIONS) { + + /* Step over invalid data or out-of-date data */ + if (!o->stream->write_index_corrections[j].valid || + o->stream->write_index_corrections[j].tag < ctag) + continue; + + /* Make sure that everything is in order */ + ctag = o->stream->write_index_corrections[j].tag+1; + + /* Now fix the write index */ + if (o->stream->write_index_corrections[j].corrupt) { + /* A corrupting seek was made */ + i->write_index_corrupt = true; + } else if (o->stream->write_index_corrections[j].absolute) { + /* An absolute seek was made */ + i->write_index = o->stream->write_index_corrections[j].value; + i->write_index_corrupt = false; + } else if (!i->write_index_corrupt) { + /* A relative seek was made */ + i->write_index += o->stream->write_index_corrections[j].value; + } + } + + /* Clear old correction entries */ + for (n = 0; n < PA_MAX_WRITE_INDEX_CORRECTIONS; n++) { + if (!o->stream->write_index_corrections[n].valid) + continue; + + if (o->stream->write_index_corrections[n].tag <= tag) + o->stream->write_index_corrections[n].valid = false; + } + } + + if (o->stream->direction == PA_STREAM_RECORD) { + /* Read index correction */ + + if (!i->read_index_corrupt) + i->read_index -= (int64_t) pa_memblockq_get_length(o->stream->record_memblockq); + } + + /* Update smoother if we're not corked */ + if (o->stream->smoother && !o->stream->corked) { + pa_usec_t u, x; + + u = x = pa_rtclock_now() - i->transport_usec; + + if (o->stream->direction == PA_STREAM_PLAYBACK && o->context->version >= 13) { + pa_usec_t su; + + /* If we weren't playing then it will take some time + * until the audio will actually come out through the + * speakers. Since we follow that timing here, we need + * to try to fix this up */ + + su = pa_bytes_to_usec((uint64_t) i->since_underrun, &o->stream->sample_spec); + + if (su < i->sink_usec) + x += i->sink_usec - su; + } + + if (!i->playing) + pa_smoother_pause(o->stream->smoother, x); + + /* Update the smoother */ + if ((o->stream->direction == PA_STREAM_PLAYBACK && !i->read_index_corrupt) || + (o->stream->direction == PA_STREAM_RECORD && !i->write_index_corrupt)) + pa_smoother_put(o->stream->smoother, u, calc_time(o->stream, true)); + + if (i->playing) + pa_smoother_resume(o->stream->smoother, x, true); + } + } + + o->stream->auto_timing_update_requested = false; + + if (o->stream->latency_update_callback) + o->stream->latency_update_callback(o->stream, o->stream->latency_update_userdata); + + if (o->callback && o->stream && o->stream->state == PA_STREAM_READY) { + pa_stream_success_cb_t cb = (pa_stream_success_cb_t) o->callback; + cb(o->stream, o->stream->timing_info_valid, o->userdata); + } + +finish: + + pa_operation_done(o); + pa_operation_unref(o); +} + +pa_operation* pa_stream_update_timing_info(pa_stream *s, pa_stream_success_cb_t cb, void *userdata) { + uint32_t tag; + pa_operation *o; + pa_tagstruct *t; + struct timeval now; + int cidx = 0; + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(s->context, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + + if (s->direction == PA_STREAM_PLAYBACK) { + /* Find a place to store the write_index correction data for this entry */ + cidx = (s->current_write_index_correction + 1) % PA_MAX_WRITE_INDEX_CORRECTIONS; + + /* Check if we could allocate a correction slot. If not, there are too many outstanding queries */ + PA_CHECK_VALIDITY_RETURN_NULL(s->context, !s->write_index_corrections[cidx].valid, PA_ERR_INTERNAL); + } + o = pa_operation_new(s->context, s, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command( + s->context, + (uint32_t) (s->direction == PA_STREAM_PLAYBACK ? PA_COMMAND_GET_PLAYBACK_LATENCY : PA_COMMAND_GET_RECORD_LATENCY), + &tag); + pa_tagstruct_putu32(t, s->channel); + pa_tagstruct_put_timeval(t, pa_gettimeofday(&now)); + + pa_pstream_send_tagstruct(s->context->pstream, t); + pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, stream_get_timing_info_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + if (s->direction == PA_STREAM_PLAYBACK) { + /* Fill in initial correction data */ + + s->current_write_index_correction = cidx; + + s->write_index_corrections[cidx].valid = true; + s->write_index_corrections[cidx].absolute = false; + s->write_index_corrections[cidx].corrupt = false; + s->write_index_corrections[cidx].tag = tag; + s->write_index_corrections[cidx].value = 0; + } + + return o; +} + +void pa_stream_disconnect_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_stream *s = userdata; + + pa_assert(pd); + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + pa_stream_ref(s); + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(s->context, command, t, false) < 0) + goto finish; + + pa_stream_set_state(s, PA_STREAM_FAILED); + goto finish; + } else if (!pa_tagstruct_eof(t)) { + pa_context_fail(s->context, PA_ERR_PROTOCOL); + goto finish; + } + + pa_stream_set_state(s, PA_STREAM_TERMINATED); + +finish: + pa_stream_unref(s); +} + +int pa_stream_disconnect(pa_stream *s) { + pa_tagstruct *t; + uint32_t tag; + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY(s->context, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY(s->context, s->channel_valid, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, s->context->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + pa_stream_ref(s); + + t = pa_tagstruct_command( + s->context, + (uint32_t) (s->direction == PA_STREAM_PLAYBACK ? PA_COMMAND_DELETE_PLAYBACK_STREAM : + (s->direction == PA_STREAM_RECORD ? PA_COMMAND_DELETE_RECORD_STREAM : PA_COMMAND_DELETE_UPLOAD_STREAM)), + &tag); + pa_tagstruct_putu32(t, s->channel); + pa_pstream_send_tagstruct(s->context->pstream, t); + pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_stream_disconnect_callback, s, NULL); + + pa_stream_unref(s); + return 0; +} + +void pa_stream_set_read_callback(pa_stream *s, pa_stream_request_cb_t cb, void *userdata) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + if (pa_detect_fork()) + return; + + if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED) + return; + + s->read_callback = cb; + s->read_userdata = userdata; +} + +void pa_stream_set_write_callback(pa_stream *s, pa_stream_request_cb_t cb, void *userdata) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + if (pa_detect_fork()) + return; + + if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED) + return; + + s->write_callback = cb; + s->write_userdata = userdata; +} + +void pa_stream_set_state_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + if (pa_detect_fork()) + return; + + if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED) + return; + + s->state_callback = cb; + s->state_userdata = userdata; +} + +void pa_stream_set_overflow_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + if (pa_detect_fork()) + return; + + if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED) + return; + + s->overflow_callback = cb; + s->overflow_userdata = userdata; +} + +void pa_stream_set_underflow_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + if (pa_detect_fork()) + return; + + if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED) + return; + + s->underflow_callback = cb; + s->underflow_userdata = userdata; +} + +void pa_stream_set_latency_update_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + if (pa_detect_fork()) + return; + + if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED) + return; + + s->latency_update_callback = cb; + s->latency_update_userdata = userdata; +} + +void pa_stream_set_moved_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + if (pa_detect_fork()) + return; + + if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED) + return; + + s->moved_callback = cb; + s->moved_userdata = userdata; +} + +void pa_stream_set_suspended_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + if (pa_detect_fork()) + return; + + if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED) + return; + + s->suspended_callback = cb; + s->suspended_userdata = userdata; +} + +void pa_stream_set_started_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + if (pa_detect_fork()) + return; + + if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED) + return; + + s->started_callback = cb; + s->started_userdata = userdata; +} + +void pa_stream_set_event_callback(pa_stream *s, pa_stream_event_cb_t cb, void *userdata) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + if (pa_detect_fork()) + return; + + if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED) + return; + + s->event_callback = cb; + s->event_userdata = userdata; +} + +void pa_stream_set_buffer_attr_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + if (pa_detect_fork()) + return; + + if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED) + return; + + s->buffer_attr_callback = cb; + s->buffer_attr_userdata = userdata; +} + +void pa_stream_simple_ack_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_operation *o = userdata; + int success = 1; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (!o->context) + goto finish; + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(o->context, command, t, false) < 0) + goto finish; + + success = 0; + } else if (!pa_tagstruct_eof(t)) { + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + + if (o->callback) { + pa_stream_success_cb_t cb = (pa_stream_success_cb_t) o->callback; + cb(o->stream, success, o->userdata); + } + +finish: + pa_operation_done(o); + pa_operation_unref(o); +} + +pa_operation* pa_stream_cork(pa_stream *s, int b, pa_stream_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(s->context, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + + /* Ask for a timing update before we cork/uncork to get the best + * accuracy for the transport latency suitable for the + * check_smoother_status() call in the started callback */ + request_auto_timing_update(s, true); + + s->corked = b; + + o = pa_operation_new(s->context, s, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command( + s->context, + (uint32_t) (s->direction == PA_STREAM_PLAYBACK ? PA_COMMAND_CORK_PLAYBACK_STREAM : PA_COMMAND_CORK_RECORD_STREAM), + &tag); + pa_tagstruct_putu32(t, s->channel); + pa_tagstruct_put_boolean(t, !!b); + pa_pstream_send_tagstruct(s->context->pstream, t); + pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_stream_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + check_smoother_status(s, false, false, false); + + /* This might cause the indexes to hang/start again, hence let's + * request a timing update, after the cork/uncork, too */ + request_auto_timing_update(s, true); + + return o; +} + +static pa_operation* stream_send_simple_command(pa_stream *s, uint32_t command, pa_stream_success_cb_t cb, void *userdata) { + pa_tagstruct *t; + pa_operation *o; + uint32_t tag; + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(s->context, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + + o = pa_operation_new(s->context, s, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(s->context, command, &tag); + pa_tagstruct_putu32(t, s->channel); + pa_pstream_send_tagstruct(s->context->pstream, t); + pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_stream_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_stream_flush(pa_stream *s, pa_stream_success_cb_t cb, void *userdata) { + pa_operation *o; + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(s->context, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + + /* Ask for a timing update *before* the flush, so that the + * transport usec is as up to date as possible when we get the + * underflow message and update the smoother status*/ + request_auto_timing_update(s, true); + + if (!(o = stream_send_simple_command(s, (uint32_t) (s->direction == PA_STREAM_PLAYBACK ? PA_COMMAND_FLUSH_PLAYBACK_STREAM : PA_COMMAND_FLUSH_RECORD_STREAM), cb, userdata))) + return NULL; + + if (s->direction == PA_STREAM_PLAYBACK) { + + if (s->write_index_corrections[s->current_write_index_correction].valid) + s->write_index_corrections[s->current_write_index_correction].corrupt = true; + + if (s->buffer_attr.prebuf > 0) + check_smoother_status(s, false, false, true); + + /* This will change the write index, but leave the + * read index untouched. */ + invalidate_indexes(s, false, true); + + } else + /* For record streams this has no influence on the write + * index, but the read index might jump. */ + invalidate_indexes(s, true, false); + + /* Note that we do not update requested_bytes here. This is + * because we cannot really know how data actually was dropped + * from the write index due to this. This 'error' will be applied + * by both client and server and hence we should be fine. */ + + return o; +} + +pa_operation* pa_stream_prebuf(pa_stream *s, pa_stream_success_cb_t cb, void *userdata) { + pa_operation *o; + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(s->context, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction == PA_STREAM_PLAYBACK, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->buffer_attr.prebuf > 0, PA_ERR_BADSTATE); + + /* Ask for a timing update before we cork/uncork to get the best + * accuracy for the transport latency suitable for the + * check_smoother_status() call in the started callback */ + request_auto_timing_update(s, true); + + if (!(o = stream_send_simple_command(s, PA_COMMAND_PREBUF_PLAYBACK_STREAM, cb, userdata))) + return NULL; + + /* This might cause the read index to hang again, hence + * let's request a timing update */ + request_auto_timing_update(s, true); + + return o; +} + +pa_operation* pa_stream_trigger(pa_stream *s, pa_stream_success_cb_t cb, void *userdata) { + pa_operation *o; + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(s->context, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction == PA_STREAM_PLAYBACK, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->buffer_attr.prebuf > 0, PA_ERR_BADSTATE); + + /* Ask for a timing update before we cork/uncork to get the best + * accuracy for the transport latency suitable for the + * check_smoother_status() call in the started callback */ + request_auto_timing_update(s, true); + + if (!(o = stream_send_simple_command(s, PA_COMMAND_TRIGGER_PLAYBACK_STREAM, cb, userdata))) + return NULL; + + /* This might cause the read index to start moving again, hence + * let's request a timing update */ + request_auto_timing_update(s, true); + + return o; +} + +pa_operation* pa_stream_set_name(pa_stream *s, const char *name, pa_stream_success_cb_t cb, void *userdata) { + pa_operation *o; + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + pa_assert(name); + + PA_CHECK_VALIDITY_RETURN_NULL(s->context, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + + if (s->context->version >= 13) { + pa_proplist *p = pa_proplist_new(); + + pa_proplist_sets(p, PA_PROP_MEDIA_NAME, name); + o = pa_stream_proplist_update(s, PA_UPDATE_REPLACE, p, cb, userdata); + pa_proplist_free(p); + } else { + pa_tagstruct *t; + uint32_t tag; + + o = pa_operation_new(s->context, s, (pa_operation_cb_t) cb, userdata); + t = pa_tagstruct_command( + s->context, + (uint32_t) (s->direction == PA_STREAM_RECORD ? PA_COMMAND_SET_RECORD_STREAM_NAME : PA_COMMAND_SET_PLAYBACK_STREAM_NAME), + &tag); + pa_tagstruct_putu32(t, s->channel); + pa_tagstruct_puts(t, name); + pa_pstream_send_tagstruct(s->context->pstream, t); + pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_stream_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + } + + return o; +} + +int pa_stream_get_time(pa_stream *s, pa_usec_t *r_usec) { + pa_usec_t usec; + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY(s->context, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, s->timing_info_valid, PA_ERR_NODATA); + PA_CHECK_VALIDITY(s->context, s->direction != PA_STREAM_PLAYBACK || !s->timing_info.read_index_corrupt, PA_ERR_NODATA); + PA_CHECK_VALIDITY(s->context, s->direction != PA_STREAM_RECORD || !s->timing_info.write_index_corrupt, PA_ERR_NODATA); + + if (s->smoother) + usec = pa_smoother_get(s->smoother, pa_rtclock_now()); + else + usec = calc_time(s, false); + + /* Make sure the time runs monotonically */ + if (!(s->flags & PA_STREAM_NOT_MONOTONIC)) { + if (usec < s->previous_time) + usec = s->previous_time; + else + s->previous_time = usec; + } + + if (r_usec) + *r_usec = usec; + + return 0; +} + +static pa_usec_t time_counter_diff(const pa_stream *s, pa_usec_t a, pa_usec_t b, int *negative) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + if (negative) + *negative = 0; + + if (a >= b) + return a-b; + else { + if (negative && s->direction == PA_STREAM_RECORD) { + *negative = 1; + return b-a; + } else + return 0; + } +} + +int pa_stream_get_latency(pa_stream *s, pa_usec_t *r_usec, int *negative) { + pa_usec_t t, c; + int r; + int64_t cindex; + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + pa_assert(r_usec); + + PA_CHECK_VALIDITY(s->context, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, s->timing_info_valid, PA_ERR_NODATA); + PA_CHECK_VALIDITY(s->context, s->direction != PA_STREAM_PLAYBACK || !s->timing_info.write_index_corrupt, PA_ERR_NODATA); + PA_CHECK_VALIDITY(s->context, s->direction != PA_STREAM_RECORD || !s->timing_info.read_index_corrupt, PA_ERR_NODATA); + + if ((r = pa_stream_get_time(s, &t)) < 0) + return r; + + if (s->direction == PA_STREAM_PLAYBACK) + cindex = s->timing_info.write_index; + else + cindex = s->timing_info.read_index; + + if (cindex < 0) + cindex = 0; + + c = pa_bytes_to_usec((uint64_t) cindex, &s->sample_spec); + + if (s->direction == PA_STREAM_PLAYBACK) + *r_usec = time_counter_diff(s, c, t, negative); + else + *r_usec = time_counter_diff(s, t, c, negative); + + return 0; +} + +const pa_timing_info* pa_stream_get_timing_info(pa_stream *s) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(s->context, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->timing_info_valid, PA_ERR_NODATA); + + return &s->timing_info; +} + +const pa_sample_spec* pa_stream_get_sample_spec(pa_stream *s) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(s->context, !pa_detect_fork(), PA_ERR_FORKED); + + return &s->sample_spec; +} + +const pa_channel_map* pa_stream_get_channel_map(pa_stream *s) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(s->context, !pa_detect_fork(), PA_ERR_FORKED); + + return &s->channel_map; +} + +const pa_format_info* pa_stream_get_format_info(const pa_stream *s) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + /* We don't have the format till routing is done */ + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, !pa_detect_fork(), PA_ERR_FORKED); + + return s->format; +} +const pa_buffer_attr* pa_stream_get_buffer_attr(pa_stream *s) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->context->version >= 9, PA_ERR_NOTSUPPORTED); + + return &s->buffer_attr; +} + +static void stream_set_buffer_attr_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_operation *o = userdata; + int success = 1; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (!o->context) + goto finish; + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(o->context, command, t, false) < 0) + goto finish; + + success = 0; + } else { + if (o->stream->direction == PA_STREAM_PLAYBACK) { + if (pa_tagstruct_getu32(t, &o->stream->buffer_attr.maxlength) < 0 || + pa_tagstruct_getu32(t, &o->stream->buffer_attr.tlength) < 0 || + pa_tagstruct_getu32(t, &o->stream->buffer_attr.prebuf) < 0 || + pa_tagstruct_getu32(t, &o->stream->buffer_attr.minreq) < 0) { + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + } else if (o->stream->direction == PA_STREAM_RECORD) { + if (pa_tagstruct_getu32(t, &o->stream->buffer_attr.maxlength) < 0 || + pa_tagstruct_getu32(t, &o->stream->buffer_attr.fragsize) < 0) { + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + } + + if (o->stream->context->version >= 13) { + pa_usec_t usec; + + if (pa_tagstruct_get_usec(t, &usec) < 0) { + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + + if (o->stream->direction == PA_STREAM_RECORD) + o->stream->timing_info.configured_source_usec = usec; + else + o->stream->timing_info.configured_sink_usec = usec; + } + + if (!pa_tagstruct_eof(t)) { + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + } + + if (o->callback) { + pa_stream_success_cb_t cb = (pa_stream_success_cb_t) o->callback; + cb(o->stream, success, o->userdata); + } + +finish: + pa_operation_done(o); + pa_operation_unref(o); +} + +pa_operation* pa_stream_set_buffer_attr(pa_stream *s, const pa_buffer_attr *attr, pa_stream_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + pa_buffer_attr copy; + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + pa_assert(attr); + + PA_CHECK_VALIDITY_RETURN_NULL(s->context, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->context->version >= 12, PA_ERR_NOTSUPPORTED); + + /* Ask for a timing update before we cork/uncork to get the best + * accuracy for the transport latency suitable for the + * check_smoother_status() call in the started callback */ + request_auto_timing_update(s, true); + + o = pa_operation_new(s->context, s, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command( + s->context, + (uint32_t) (s->direction == PA_STREAM_RECORD ? PA_COMMAND_SET_RECORD_STREAM_BUFFER_ATTR : PA_COMMAND_SET_PLAYBACK_STREAM_BUFFER_ATTR), + &tag); + pa_tagstruct_putu32(t, s->channel); + + copy = *attr; + patch_buffer_attr(s, ©, NULL); + attr = © + + pa_tagstruct_putu32(t, attr->maxlength); + + if (s->direction == PA_STREAM_PLAYBACK) + pa_tagstruct_put( + t, + PA_TAG_U32, attr->tlength, + PA_TAG_U32, attr->prebuf, + PA_TAG_U32, attr->minreq, + PA_TAG_INVALID); + else + pa_tagstruct_putu32(t, attr->fragsize); + + if (s->context->version >= 13) + pa_tagstruct_put_boolean(t, !!(s->flags & PA_STREAM_ADJUST_LATENCY)); + + if (s->context->version >= 14) + pa_tagstruct_put_boolean(t, !!(s->flags & PA_STREAM_EARLY_REQUESTS)); + + pa_pstream_send_tagstruct(s->context->pstream, t); + pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, stream_set_buffer_attr_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + /* This might cause changes in the read/write index, hence let's + * request a timing update */ + request_auto_timing_update(s, true); + + return o; +} + +uint32_t pa_stream_get_device_index(const pa_stream *s) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY_RETURN_ANY(s->context, !pa_detect_fork(), PA_ERR_FORKED, PA_INVALID_INDEX); + PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE, PA_INVALID_INDEX); + PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE, PA_INVALID_INDEX); + PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->context->version >= 12, PA_ERR_NOTSUPPORTED, PA_INVALID_INDEX); + PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->device_index != PA_INVALID_INDEX, PA_ERR_BADSTATE, PA_INVALID_INDEX); + + return s->device_index; +} + +const char *pa_stream_get_device_name(const pa_stream *s) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(s->context, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->context->version >= 12, PA_ERR_NOTSUPPORTED); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->device_name, PA_ERR_BADSTATE); + + return s->device_name; +} + +int pa_stream_is_suspended(const pa_stream *s) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY(s->context, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, s->context->version >= 12, PA_ERR_NOTSUPPORTED); + + return s->suspended; +} + +int pa_stream_is_corked(const pa_stream *s) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY(s->context, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + + return s->corked; +} + +static void stream_update_sample_rate_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_operation *o = userdata; + int success = 1; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (!o->context) + goto finish; + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(o->context, command, t, false) < 0) + goto finish; + + success = 0; + } else { + + if (!pa_tagstruct_eof(t)) { + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + } + + o->stream->sample_spec.rate = PA_PTR_TO_UINT(o->private); + pa_assert(pa_sample_spec_valid(&o->stream->sample_spec)); + + if (o->callback) { + pa_stream_success_cb_t cb = (pa_stream_success_cb_t) o->callback; + cb(o->stream, success, o->userdata); + } + +finish: + pa_operation_done(o); + pa_operation_unref(o); +} + +pa_operation *pa_stream_update_sample_rate(pa_stream *s, uint32_t rate, pa_stream_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(s->context, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, pa_sample_rate_valid(rate), PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->flags & PA_STREAM_VARIABLE_RATE, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->context->version >= 12, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(s->context, s, (pa_operation_cb_t) cb, userdata); + o->private = PA_UINT_TO_PTR(rate); + + t = pa_tagstruct_command( + s->context, + (uint32_t) (s->direction == PA_STREAM_RECORD ? PA_COMMAND_UPDATE_RECORD_STREAM_SAMPLE_RATE : PA_COMMAND_UPDATE_PLAYBACK_STREAM_SAMPLE_RATE), + &tag); + pa_tagstruct_putu32(t, s->channel); + pa_tagstruct_putu32(t, rate); + + pa_pstream_send_tagstruct(s->context->pstream, t); + pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, stream_update_sample_rate_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation *pa_stream_proplist_update(pa_stream *s, pa_update_mode_t mode, pa_proplist *p, pa_stream_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(s->context, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, mode == PA_UPDATE_SET || mode == PA_UPDATE_MERGE || mode == PA_UPDATE_REPLACE, PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->context->version >= 13, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(s->context, s, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command( + s->context, + (uint32_t) (s->direction == PA_STREAM_RECORD ? PA_COMMAND_UPDATE_RECORD_STREAM_PROPLIST : PA_COMMAND_UPDATE_PLAYBACK_STREAM_PROPLIST), + &tag); + pa_tagstruct_putu32(t, s->channel); + pa_tagstruct_putu32(t, (uint32_t) mode); + pa_tagstruct_put_proplist(t, p); + + pa_pstream_send_tagstruct(s->context->pstream, t); + pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_stream_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + /* Please note that we don't update s->proplist here, because we + * don't export that field */ + + return o; +} + +pa_operation *pa_stream_proplist_remove(pa_stream *s, const char *const keys[], pa_stream_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + const char * const*k; + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(s->context, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, keys && keys[0], PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->context->version >= 13, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(s->context, s, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command( + s->context, + (uint32_t) (s->direction == PA_STREAM_RECORD ? PA_COMMAND_REMOVE_RECORD_STREAM_PROPLIST : PA_COMMAND_REMOVE_PLAYBACK_STREAM_PROPLIST), + &tag); + pa_tagstruct_putu32(t, s->channel); + + for (k = keys; *k; k++) + pa_tagstruct_puts(t, *k); + + pa_tagstruct_puts(t, NULL); + + pa_pstream_send_tagstruct(s->context->pstream, t); + pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_stream_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + /* Please note that we don't update s->proplist here, because we + * don't export that field */ + + return o; +} + +int pa_stream_set_monitor_stream(pa_stream *s, uint32_t sink_input_idx) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY(s->context, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY(s->context, sink_input_idx != PA_INVALID_INDEX, PA_ERR_INVALID); + PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_UNCONNECTED, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, s->context->version >= 13, PA_ERR_NOTSUPPORTED); + + s->direct_on_input = sink_input_idx; + + return 0; +} + +uint32_t pa_stream_get_monitor_stream(const pa_stream *s) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY_RETURN_ANY(s->context, !pa_detect_fork(), PA_ERR_FORKED, PA_INVALID_INDEX); + PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->direct_on_input != PA_INVALID_INDEX, PA_ERR_BADSTATE, PA_INVALID_INDEX); + PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->context->version >= 13, PA_ERR_NOTSUPPORTED, PA_INVALID_INDEX); + + return s->direct_on_input; +} diff --git a/src/pulse/stream.h b/src/pulse/stream.h new file mode 100644 index 0000000..f9b52d4 --- /dev/null +++ b/src/pulse/stream.h @@ -0,0 +1,808 @@ +#ifndef foostreamhfoo +#define foostreamhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/types.h> + +#include <pulse/sample.h> +#include <pulse/format.h> +#include <pulse/channelmap.h> +#include <pulse/volume.h> +#include <pulse/def.h> +#include <pulse/cdecl.h> +#include <pulse/operation.h> +#include <pulse/context.h> +#include <pulse/proplist.h> + +/** \page streams Audio Streams + * + * \section overv_sec Overview + * + * Audio streams form the central functionality of the sound server. Data is + * routed, converted and mixed from several sources before it is passed along + * to a final output. Currently, there are three forms of audio streams: + * + * \li Playback streams - Data flows from the client to the server. + * \li Record streams - Data flows from the server to the client. + * \li Upload streams - Similar to playback streams, but the data is stored in + * the sample cache. See \ref scache for more information + * about controlling the sample cache. + * + * \section create_sec Creating + * + * To access a stream, a pa_stream object must be created using + * pa_stream_new() or pa_stream_new_extended(). pa_stream_new() is for PCM + * streams only, while pa_stream_new_extended() can be used for both PCM and + * compressed audio streams. At this point the application must specify what + * stream format(s) it supports. See \ref sample and \ref channelmap for more + * information on the stream format parameters. FIXME: Those references only + * talk about PCM parameters, we should also have an overview page for how the + * pa_format_info based stream format configuration works. Bug filed: + * https://bugs.freedesktop.org/show_bug.cgi?id=72265 + * + * This first step will only create a client-side object, representing the + * stream. To use the stream, a server-side object must be created and + * associated with the local object. Depending on which type of stream is + * desired, a different function is needed: + * + * \li Playback stream - pa_stream_connect_playback() + * \li Record stream - pa_stream_connect_record() + * \li Upload stream - pa_stream_connect_upload() (see \ref scache) + * + * Similar to how connections are done in contexts, connecting a stream will + * not generate a pa_operation object. Also like contexts, the application + * should register a state change callback, using + * pa_stream_set_state_callback(), and wait for the stream to enter an active + * state. + * + * Note: there is a user-controllable slider in mixer applications such as + * pavucontrol corresponding to each of the created streams. Multiple + * (especially identically named) volume sliders for the same application might + * confuse the user. Also, the server supports only a limited number of + * simultaneous streams. Because of this, it is not always appropriate to + * create multiple streams in one application that needs to output multiple + * sounds. The rough guideline is: if there is no use case that would require + * separate user-initiated volume changes for each stream, perform the mixing + * inside the application. + * + * \subsection bufattr_subsec Buffer Attributes + * + * Playback and record streams always have a server-side buffer as + * part of the data flow. The size of this buffer needs to be chosen + * in a compromise between low latency and sensitivity for buffer + * overflows/underruns. + * + * The buffer metrics may be controlled by the application. They are + * described with a pa_buffer_attr structure. + * + * If PA_STREAM_ADJUST_LATENCY is set, then the tlength/fragsize + * parameters of the pa_buffer_attr structure will be interpreted + * slightly differently than otherwise when passed to + * pa_stream_connect_record() and pa_stream_connect_playback(): the + * overall latency that is comprised of both the server side playback + * buffer length, the hardware playback buffer length and additional + * latencies will be adjusted in a way that it matches tlength resp. + * fragsize. Set PA_STREAM_ADJUST_LATENCY if you want to control the + * overall playback latency for your stream. Unset it if you want to + * control only the latency induced by the server-side, rewritable + * playback buffer. The server will try to fulfill the client's latency + * requests as good as possible. However if the underlying hardware cannot + * change the hardware buffer length or only in a limited range, the + * actually resulting latency might be different from what the client + * requested. Thus, for synchronization clients always need to check + * the actual measured latency via pa_stream_get_latency() or a + * similar call, and not make any assumptions about the latency + * available. The function pa_stream_get_buffer_attr() will always + * return the actual size of the server-side per-stream buffer in + * tlength/fragsize, regardless whether PA_STREAM_ADJUST_LATENCY is + * set or not. + * + * The server-side per-stream playback buffers are indexed by a write and + * a read index. The application writes to the write index and the sound + * device reads from the read index. The read index is increased + * monotonically, while the write index may be freely controlled by + * the application. Subtracting the read index from the write index + * will give you the current fill level of the buffer. The read/write + * indexes are 64bit values and measured in bytes, they will never + * wrap. The current read/write index may be queried using + * pa_stream_get_timing_info() (see below for more information). In + * case of a buffer underrun the read index is equal or larger than + * the write index. Unless the prebuf value is 0, PulseAudio will + * temporarily pause playback in such a case, and wait until the + * buffer is filled up to prebuf bytes again. If prebuf is 0, the + * read index may be larger than the write index, in which case + * silence is played. If the application writes data to indexes lower + * than the read index, the data is immediately lost. + * + * \section transfer_sec Transferring Data + * + * Once the stream is up, data can start flowing between the client and the + * server. Two different access models can be used to transfer the data: + * + * \li Asynchronous - The application registers a callback using + * pa_stream_set_write_callback() and + * pa_stream_set_read_callback() to receive notifications + * that data can either be written or read. + * \li Polled - Query the library for available data/space using + * pa_stream_writable_size() and pa_stream_readable_size() and + * transfer data as needed. The sizes are stored locally, in the + * client end, so there is no delay when reading them. + * + * It is also possible to mix the two models freely. + * + * Once there is data/space available, it can be transferred using either + * pa_stream_write() for playback, or pa_stream_peek() / pa_stream_drop() for + * record. Make sure you do not overflow the playback buffers as data will be + * dropped. + * + * \section bufctl_sec Buffer Control + * + * The transfer buffers can be controlled through a number of operations: + * + * \li pa_stream_cork() - Start or stop the playback or recording. + * \li pa_stream_trigger() - Start playback immediately and do not wait for + * the buffer to fill up to the set trigger level. + * \li pa_stream_prebuf() - Reenable the playback trigger level. + * \li pa_stream_drain() - Wait for the playback buffer to go empty. Will + * return a pa_operation object that will indicate when + * the buffer is completely drained. + * \li pa_stream_flush() - Drop all data from the playback or record buffer. Do not + * wait for it to finish playing. + * + * \section seek_modes Seeking in the Playback Buffer + * + * A client application may freely seek in the playback buffer. To + * accomplish that the pa_stream_write() function takes a seek mode + * and an offset argument. The seek mode is one of: + * + * \li PA_SEEK_RELATIVE - seek relative to the current write index. + * \li PA_SEEK_ABSOLUTE - seek relative to the beginning of the playback buffer, + * (i.e. the first that was ever played in the stream). + * \li PA_SEEK_RELATIVE_ON_READ - seek relative to the current read index. Use + * this to write data to the output buffer that should be played as soon as possible. + * \li PA_SEEK_RELATIVE_END - seek relative to the last byte ever written. + * + * If an application just wants to append some data to the output + * buffer, PA_SEEK_RELATIVE and an offset of 0 should be used. + * + * After a call to pa_stream_write() the write index will be left at + * the position right after the last byte of the written data. + * + * \section latency_sec Latency + * + * A major problem with networked audio is the increased latency caused by + * the network. To remedy this, PulseAudio supports an advanced system of + * monitoring the current latency. + * + * To get the raw data needed to calculate latencies, call + * pa_stream_get_timing_info(). This will give you a pa_timing_info + * structure that contains everything that is known about the server + * side buffer transport delays and the backend active in the + * server. (Besides other things it contains the write and read index + * values mentioned above.) + * + * This structure is updated every time a + * pa_stream_update_timing_info() operation is executed. (i.e. before + * the first call to this function the timing information structure is + * not available!) Since it is a lot of work to keep this structure + * up-to-date manually, PulseAudio can do that automatically for you: + * if PA_STREAM_AUTO_TIMING_UPDATE is passed when connecting the + * stream PulseAudio will automatically update the structure every + * 100ms and every time a function is called that might invalidate the + * previously known timing data (such as pa_stream_write() or + * pa_stream_flush()). Please note however, that there always is a + * short time window when the data in the timing information structure + * is out-of-date. PulseAudio tries to mark these situations by + * setting the write_index_corrupt and read_index_corrupt fields + * accordingly. + * + * The raw timing data in the pa_timing_info structure is usually hard + * to deal with. Therefore a simpler interface is available: + * you can call pa_stream_get_time() or pa_stream_get_latency(). The + * former will return the current playback time of the hardware since + * the stream has been started. The latter returns the overall time a sample + * that you write now takes to be played by the hardware. These two + * functions base their calculations on the same data that is returned + * by pa_stream_get_timing_info(). Hence the same rules for keeping + * the timing data up-to-date apply here. In case the write or read + * index is corrupted, these two functions will fail with + * -PA_ERR_NODATA set. + * + * Since updating the timing info structure usually requires a full + * network round trip and some applications monitor the timing very + * often PulseAudio offers a timing interpolation system. If + * PA_STREAM_INTERPOLATE_TIMING is passed when connecting the stream, + * pa_stream_get_time() and pa_stream_get_latency() will try to + * interpolate the current playback time/latency by estimating the + * number of samples that have been played back by the hardware since + * the last regular timing update. It is especially useful to combine + * this option with PA_STREAM_AUTO_TIMING_UPDATE, which will enable + * you to monitor the current playback time/latency very precisely and + * very frequently without requiring a network round trip every time. + * + * \section flow_sec Overflow and underflow + * + * Even with the best precautions, buffers will sometime over - or + * underflow. To handle this gracefully, the application can be + * notified when this happens. Callbacks are registered using + * pa_stream_set_overflow_callback() and + * pa_stream_set_underflow_callback(). + * + * \section sync_streams Synchronizing Multiple Playback Streams + * + * PulseAudio allows applications to fully synchronize multiple + * playback streams that are connected to the same output device. That + * means the streams will always be played back sample-by-sample + * synchronously. If stream operations like pa_stream_cork() are + * issued on one of the synchronized streams, they are simultaneously + * issued on the others. + * + * To synchronize a stream to another, just pass the "master" stream + * as last argument to pa_stream_connect_playback(). To make sure that + * the freshly created stream doesn't start playback right-away, make + * sure to pass PA_STREAM_START_CORKED and -- after all streams have + * been created -- uncork them all with a single call to + * pa_stream_cork() for the master stream. + * + * To make sure that a particular stream doesn't stop playing when a + * server side buffer underrun happens on it while the other + * synchronized streams continue playing and hence deviate, you need to + * pass a pa_buffer_attr with prebuf set to 0 when connecting. + * + * \section disc_sec Disconnecting + * + * When a stream has served is purpose it must be disconnected with + * pa_stream_disconnect(). If you only unreference it, then it will live on + * and eat resources both locally and on the server until you disconnect the + * context. + * + */ + +/** \file + * Audio streams for input, output and sample upload + * + * See also \subpage streams + */ + +PA_C_DECL_BEGIN + +/** An opaque stream for playback or recording */ +typedef struct pa_stream pa_stream; + +/** A generic callback for operation completion */ +typedef void (*pa_stream_success_cb_t) (pa_stream*s, int success, void *userdata); + +/** A generic request callback */ +typedef void (*pa_stream_request_cb_t)(pa_stream *p, size_t nbytes, void *userdata); + +/** A generic notification callback */ +typedef void (*pa_stream_notify_cb_t)(pa_stream *p, void *userdata); + +/** A callback for asynchronous meta/policy event messages. Well known + * event names are PA_STREAM_EVENT_REQUEST_CORK and + * PA_STREAM_EVENT_REQUEST_UNCORK. The set of defined events can be + * extended at any time. Also, server modules may introduce additional + * message types so make sure that your callback function ignores messages + * it doesn't know. \since 0.9.15 */ +typedef void (*pa_stream_event_cb_t)(pa_stream *p, const char *name, pa_proplist *pl, void *userdata); + +/** Create a new, unconnected stream with the specified name and + * sample type. It is recommended to use pa_stream_new_with_proplist() + * instead and specify some initial properties. */ +pa_stream* pa_stream_new( + pa_context *c /**< The context to create this stream in */, + const char *name /**< A name for this stream */, + const pa_sample_spec *ss /**< The desired sample format */, + const pa_channel_map *map /**< The desired channel map, or NULL for default */); + +/** Create a new, unconnected stream with the specified name and + * sample type, and specify the initial stream property + * list. \since 0.9.11 */ +pa_stream* pa_stream_new_with_proplist( + pa_context *c /**< The context to create this stream in */, + const char *name /**< A name for this stream */, + const pa_sample_spec *ss /**< The desired sample format */, + const pa_channel_map *map /**< The desired channel map, or NULL for default */, + pa_proplist *p /**< The initial property list */); + +/** Create a new, unconnected stream with the specified name, the set of formats + * this client can provide, and an initial list of properties. While + * connecting, the server will select the most appropriate format which the + * client must then provide. \since 1.0 */ +pa_stream *pa_stream_new_extended( + pa_context *c /**< The context to create this stream in */, + const char *name /**< A name for this stream */, + pa_format_info * const * formats /**< The list of formats that can be provided */, + unsigned int n_formats /**< The number of formats being passed in */, + pa_proplist *p /**< The initial property list */); + +/** Decrease the reference counter by one. */ +void pa_stream_unref(pa_stream *s); + +/** Increase the reference counter by one. */ +pa_stream *pa_stream_ref(pa_stream *s); + +/** Return the current state of the stream. */ +pa_stream_state_t pa_stream_get_state(const pa_stream *p); + +/** Return the context this stream is attached to. */ +pa_context* pa_stream_get_context(const pa_stream *p); + +/** Return the sink input resp.\ source output index this stream is + * identified in the server with. This is useful with the + * introspection functions such as pa_context_get_sink_input_info() + * or pa_context_get_source_output_info(). This returns PA_INVALID_INDEX + * on failure. */ +uint32_t pa_stream_get_index(const pa_stream *s); + +/** Return the index of the sink or source this stream is connected to + * in the server. This is useful with the introspection + * functions such as pa_context_get_sink_info_by_index() or + * pa_context_get_source_info_by_index(). + * + * Please note that streams may be moved between sinks/sources and thus + * it is recommended to use pa_stream_set_moved_callback() to be notified + * about this. This function will return with PA_INVALID_INDEX on failure, + * including the being server older than 0.9.8. \since 0.9.8 */ +uint32_t pa_stream_get_device_index(const pa_stream *s); + +/** Return the name of the sink or source this stream is connected to + * in the server. This is useful with the introspection + * functions such as pa_context_get_sink_info_by_name() + * or pa_context_get_source_info_by_name(). + * + * Please note that streams may be moved between sinks/sources and thus + * it is recommended to use pa_stream_set_moved_callback() to be notified + * about this. This function will fail when the server is older than + * 0.9.8. \since 0.9.8 */ +const char *pa_stream_get_device_name(const pa_stream *s); + +/** Return 1 if the sink or source this stream is connected to has + * been suspended. This will return 0 if not, and a negative value on + * error. This function will return with -PA_ERR_NOTSUPPORTED when the + * server is older than 0.9.8. \since 0.9.8 */ +int pa_stream_is_suspended(const pa_stream *s); + +/** Return 1 if the this stream has been corked. This will return 0 if + * not, and a negative value on error. \since 0.9.11 */ +int pa_stream_is_corked(const pa_stream *s); + +/** Connect the stream to a sink. It is strongly recommended to pass + * NULL in both \a dev and \a volume and to set neither + * PA_STREAM_START_MUTED nor PA_STREAM_START_UNMUTED -- unless these + * options are directly dependent on user input or configuration. + * + * If you follow this rule then the sound server will have the full + * flexibility to choose the device, volume and mute status + * automatically, based on server-side policies, heuristics and stored + * information from previous uses. Also the server may choose to + * reconfigure audio devices to make other sinks/sources or + * capabilities available to be able to accept the stream. + * + * Before 0.9.20 it was not defined whether the \a volume parameter was + * interpreted relative to the sink's current volume or treated as + * an absolute device volume. Since 0.9.20 it is an absolute volume when + * the sink is in flat volume mode, and relative otherwise, thus + * making sure the volume passed here has always the same semantics as + * the volume passed to pa_context_set_sink_input_volume(). It is possible + * to figure out whether flat volume mode is in effect for a given sink + * by calling pa_context_get_sink_info_by_name(). + * + * Since 5.0, it's possible to specify a single-channel volume even if the + * stream has multiple channels. In that case the same volume is applied to all + * channels. + * + * Returns zero on success. */ +int pa_stream_connect_playback( + pa_stream *s /**< The stream to connect to a sink */, + const char *dev /**< Name of the sink to connect to, or NULL to let the server decide */ , + const pa_buffer_attr *attr /**< Buffering attributes, or NULL for default */, + pa_stream_flags_t flags /**< Additional flags, or 0 for default */, + const pa_cvolume *volume /**< Initial volume, or NULL for default */, + pa_stream *sync_stream /**< Synchronize this stream with the specified one, or NULL for a standalone stream */); + +/** Connect the stream to a source. Returns zero on success. */ +int pa_stream_connect_record( + pa_stream *s /**< The stream to connect to a source */ , + const char *dev /**< Name of the source to connect to, or NULL to let the server decide */, + const pa_buffer_attr *attr /**< Buffer attributes, or NULL for default */, + pa_stream_flags_t flags /**< Additional flags, or 0 for default */); + +/** Disconnect a stream from a source/sink. Returns zero on success. */ +int pa_stream_disconnect(pa_stream *s); + +/** Prepare writing data to the server (for playback streams). This + * function may be used to optimize the number of memory copies when + * doing playback ("zero-copy"). It is recommended to call this + * function before each call to pa_stream_write(). + * + * Pass in the address to a pointer and an address of the number of + * bytes you want to write. On return the two values will contain a + * pointer where you can place the data to write and the maximum number + * of bytes you can write. \a *nbytes can be smaller or have the same + * value as you passed in. You need to be able to handle both cases. + * Accessing memory beyond the returned \a *nbytes value is invalid. + * Accessing the memory returned after the following pa_stream_write() + * or pa_stream_cancel_write() is invalid. + * + * On invocation only \a *nbytes needs to be initialized, on return both + * *data and *nbytes will be valid. If you place (size_t) -1 in *nbytes + * on invocation the memory size will be chosen automatically (which is + * recommended to do). After placing your data in the memory area + * returned, call pa_stream_write() with \a data set to an address + * within this memory area and an \a nbytes value that is smaller or + * equal to what was returned by this function to actually execute the + * write. + * + * An invocation of pa_stream_write() should follow "quickly" on + * pa_stream_begin_write(). It is not recommended letting an unbounded + * amount of time pass after calling pa_stream_begin_write() and + * before calling pa_stream_write(). If you want to cancel a + * previously called pa_stream_begin_write() without calling + * pa_stream_write() use pa_stream_cancel_write(). Calling + * pa_stream_begin_write() twice without calling pa_stream_write() or + * pa_stream_cancel_write() in between will return exactly the same + * \a data pointer and \a nbytes values. + * + * On success, will return zero and a valid (non-NULL) pointer. If the + * return value is non-zero, or the pointer is NULL, this indicates an + * error. Callers should also pay careful attention to the returned + * length, which may not be the same as that passed in, as mentioned above. + * + * \since 0.9.16 */ +int pa_stream_begin_write( + pa_stream *p, + void **data, + size_t *nbytes); + +/** Reverses the effect of pa_stream_begin_write() dropping all data + * that has already been placed in the memory area returned by + * pa_stream_begin_write(). Only valid to call if + * pa_stream_begin_write() was called before and neither + * pa_stream_cancel_write() nor pa_stream_write() have been called + * yet. Accessing the memory previously returned by + * pa_stream_begin_write() after this call is invalid. Any further + * explicit freeing of the memory area is not necessary. + * Returns zero on success. \since 0.9.16 */ +int pa_stream_cancel_write( + pa_stream *p); + +/** Write some data to the server (for playback streams). + * If \a free_cb is non-NULL this routine is called when all data has + * been written out. An internal reference to the specified data is + * kept, the data is not copied. If NULL, the data is copied into an + * internal buffer. + * + * The client may freely seek around in the output buffer. For + * most applications it is typical to pass 0 and PA_SEEK_RELATIVE + * as values for the arguments \a offset and \a seek respectively. + * After a successful write call the write index will be at the + * position after where this chunk of data has been written to. + * + * As an optimization for avoiding needless memory copies you may call + * pa_stream_begin_write() before this call and then place your audio + * data directly in the memory area returned by that call. Then, pass + * a pointer to that memory area to pa_stream_write(). After the + * invocation of pa_stream_write() the memory area may no longer be + * accessed. Any further explicit freeing of the memory area is not + * necessary. It is OK to write to the memory area returned by + * pa_stream_begin_write() only partially with this call, skipping + * bytes both at the end and at the beginning of the reserved memory + * area. + * + * Returns zero on success. */ +int pa_stream_write( + pa_stream *p /**< The stream to use */, + const void *data /**< The data to write */, + size_t nbytes /**< The length of the data to write in bytes, must be in multiples of the stream's sample spec frame size */, + pa_free_cb_t free_cb /**< A cleanup routine for the data or NULL to request an internal copy */, + int64_t offset /**< Offset for seeking, must be 0 for upload streams, must be in multiples of the stream's sample spec frame size */, + pa_seek_mode_t seek /**< Seek mode, must be PA_SEEK_RELATIVE for upload streams */); + +/** Function does exactly the same as pa_stream_write() with the difference + * that free_cb_data is passed to free_cb instead of data. \since 6.0 */ +int pa_stream_write_ext_free( + pa_stream *p /**< The stream to use */, + const void *data /**< The data to write */, + size_t nbytes /**< The length of the data to write in bytes */, + pa_free_cb_t free_cb /**< A cleanup routine for the data or NULL to request an internal copy */, + void *free_cb_data /**< Argument passed to free_cb function */, + int64_t offset /**< Offset for seeking, must be 0 for upload streams */, + pa_seek_mode_t seek /**< Seek mode, must be PA_SEEK_RELATIVE for upload streams */); + +/** Read the next fragment from the buffer (for recording streams). + * If there is data at the current read index, \a data will point to + * the actual data and \a nbytes will contain the size of the data in + * bytes (which can be less or more than a complete fragment). + * + * If there is no data at the current read index, it means that either + * the buffer is empty or it contains a hole (that is, the write index + * is ahead of the read index but there's no data where the read index + * points at). If the buffer is empty, \a data will be NULL and + * \a nbytes will be 0. If there is a hole, \a data will be NULL and + * \a nbytes will contain the length of the hole. + * + * Use pa_stream_drop() to actually remove the data from the buffer + * and move the read index forward. pa_stream_drop() should not be + * called if the buffer is empty, but it should be called if there is + * a hole. + * + * Returns zero on success, negative on error. */ +int pa_stream_peek( + pa_stream *p /**< The stream to use */, + const void **data /**< Pointer to pointer that will point to data */, + size_t *nbytes /**< The length of the data read in bytes */); + +/** Remove the current fragment on record streams. It is invalid to do this without first + * calling pa_stream_peek(). Returns zero on success. */ +int pa_stream_drop(pa_stream *p); + +/** Return the number of bytes requested by the server that have not yet + * been written. + * + * It is possible to write more than this amount, up to the stream's + * buffer_attr.maxlength bytes. This is usually not desirable, though, as + * it would increase stream latency to be higher than requested + * (buffer_attr.tlength). + * + * (size_t) -1 is returned on error. + */ +size_t pa_stream_writable_size(const pa_stream *p); + +/** Return the number of bytes that may be read using pa_stream_peek(). + * + * (size_t) -1 is returned on error. */ +size_t pa_stream_readable_size(const pa_stream *p); + +/** Drain a playback stream. Use this for notification when the + * playback buffer is empty after playing all the audio in the buffer. + * Please note that only one drain operation per stream may be issued + * at a time. */ +pa_operation* pa_stream_drain(pa_stream *s, pa_stream_success_cb_t cb, void *userdata); + +/** Request a timing info structure update for a stream. Use + * pa_stream_get_timing_info() to get access to the raw timing data, + * or pa_stream_get_time() or pa_stream_get_latency() to get cleaned + * up values. */ +pa_operation* pa_stream_update_timing_info(pa_stream *p, pa_stream_success_cb_t cb, void *userdata); + +/** Set the callback function that is called whenever the state of the stream changes. */ +void pa_stream_set_state_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata); + +/** Set the callback function that is called when new data may be + * written to the stream. */ +void pa_stream_set_write_callback(pa_stream *p, pa_stream_request_cb_t cb, void *userdata); + +/** Set the callback function that is called when new data is available from the stream. */ +void pa_stream_set_read_callback(pa_stream *p, pa_stream_request_cb_t cb, void *userdata); + +/** Set the callback function that is called when a buffer overflow happens. (Only for playback streams) */ +void pa_stream_set_overflow_callback(pa_stream *p, pa_stream_notify_cb_t cb, void *userdata); + +/** Return at what position the latest underflow occurred, or -1 if this information is not + * known (e.g.\ if no underflow has occurred, or server is older than 1.0). + * Can be used inside the underflow callback to get information about the current underflow. + * (Only for playback streams) \since 1.0 */ +int64_t pa_stream_get_underflow_index(const pa_stream *p); + +/** Set the callback function that is called when a buffer underflow happens. (Only for playback streams) */ +void pa_stream_set_underflow_callback(pa_stream *p, pa_stream_notify_cb_t cb, void *userdata); + +/** Set the callback function that is called when the server starts + * playback after an underrun or on initial startup. This only informs + * that audio is flowing again, it is no indication that audio started + * to reach the speakers already. (Only for playback streams) \since + * 0.9.11 */ +void pa_stream_set_started_callback(pa_stream *p, pa_stream_notify_cb_t cb, void *userdata); + +/** Set the callback function that is called whenever a latency + * information update happens. Useful on PA_STREAM_AUTO_TIMING_UPDATE + * streams only. */ +void pa_stream_set_latency_update_callback(pa_stream *p, pa_stream_notify_cb_t cb, void *userdata); + +/** Set the callback function that is called whenever the stream is + * moved to a different sink/source. Use pa_stream_get_device_name() or + * pa_stream_get_device_index() to query the new sink/source. This + * notification is only generated when the server is at least + * 0.9.8. \since 0.9.8 */ +void pa_stream_set_moved_callback(pa_stream *p, pa_stream_notify_cb_t cb, void *userdata); + +/** Set the callback function that is called whenever the sink/source + * this stream is connected to is suspended or resumed. Use + * pa_stream_is_suspended() to query the new suspend status. Please + * note that the suspend status might also change when the stream is + * moved between devices. Thus if you call this function you very + * likely want to call pa_stream_set_moved_callback() too. This + * notification is only generated when the server is at least + * 0.9.8. \since 0.9.8 */ +void pa_stream_set_suspended_callback(pa_stream *p, pa_stream_notify_cb_t cb, void *userdata); + +/** Set the callback function that is called whenever a meta/policy + * control event is received. \since 0.9.15 */ +void pa_stream_set_event_callback(pa_stream *p, pa_stream_event_cb_t cb, void *userdata); + +/** Set the callback function that is called whenever the buffer + * attributes on the server side change. Please note that the buffer + * attributes can change when moving a stream to a different + * sink/source too, hence if you use this callback you should use + * pa_stream_set_moved_callback() as well. \since 0.9.15 */ +void pa_stream_set_buffer_attr_callback(pa_stream *p, pa_stream_notify_cb_t cb, void *userdata); + +/** Pause (or resume) playback of this stream temporarily. Available + * on both playback and recording streams. If \a b is 1 the stream is + * paused. If \a b is 0 the stream is resumed. The pause/resume operation + * is executed as quickly as possible. If a cork is very quickly + * followed by an uncork or the other way round, this might not + * actually have any effect on the stream that is output. You can use + * pa_stream_is_corked() to find out whether the stream is currently + * paused or not. Normally a stream will be created in uncorked + * state. If you pass PA_STREAM_START_CORKED as a flag when connecting + * the stream, it will be created in corked state. */ +pa_operation* pa_stream_cork(pa_stream *s, int b, pa_stream_success_cb_t cb, void *userdata); + +/** Flush the playback or record buffer of this stream. This discards any audio data + * in the buffer. Most of the time you're better off using the parameter + * \a seek of pa_stream_write() instead of this function. */ +pa_operation* pa_stream_flush(pa_stream *s, pa_stream_success_cb_t cb, void *userdata); + +/** Reenable prebuffering if specified in the pa_buffer_attr + * structure. Available for playback streams only. */ +pa_operation* pa_stream_prebuf(pa_stream *s, pa_stream_success_cb_t cb, void *userdata); + +/** Request immediate start of playback on this stream. This disables + * prebuffering temporarily if specified in the pa_buffer_attr structure. + * Available for playback streams only. */ +pa_operation* pa_stream_trigger(pa_stream *s, pa_stream_success_cb_t cb, void *userdata); + +/** Rename the stream. */ +pa_operation* pa_stream_set_name(pa_stream *s, const char *name, pa_stream_success_cb_t cb, void *userdata); + +/** Return the current playback/recording time. This is based on the + * data in the timing info structure returned by + * pa_stream_get_timing_info(). The returned time is in the sound card + * clock domain, which usually runs at a slightly different rate than + * the system clock. + * + * This function will usually only return new data if a timing info + * update has been received. Only if timing interpolation has been + * requested (PA_STREAM_INTERPOLATE_TIMING) the data from the last + * timing update is used for an estimation of the current + * playback/recording time based on the local time that passed since + * the timing info structure has been acquired. + * + * The time value returned by this function is guaranteed to increase + * monotonically (the returned value is always greater + * or equal to the value returned by the last call). This behaviour + * can be disabled by using PA_STREAM_NOT_MONOTONIC. This may be + * desirable to better deal with bad estimations of transport + * latencies, but may have strange effects if the application is not + * able to deal with time going 'backwards'. + * + * The time interpolator activated by PA_STREAM_INTERPOLATE_TIMING + * favours 'smooth' time graphs over accurate ones to improve the + * smoothness of UI operations that are tied to the audio clock. If + * accuracy is more important to you, you might need to estimate your + * timing based on the data from pa_stream_get_timing_info() yourself + * or not work with interpolated timing at all and instead always + * query the server side for the most up to date timing with + * pa_stream_update_timing_info(). + * + * If no timing information has been + * received yet this call will return -PA_ERR_NODATA. For more details + * see pa_stream_get_timing_info(). + * + * Returns zero on success, negative on error. */ +int pa_stream_get_time(pa_stream *s, pa_usec_t *r_usec); + +/** Determine the total stream latency. This function is based on + * pa_stream_get_time(). The returned time is in the sound card clock + * domain, which usually runs at a slightly different rate than the + * system clock. + * + * The latency is stored in \a *r_usec. In case the stream is a + * monitoring stream the result can be negative, i.e. the captured + * samples are not yet played. In this case \a *negative is set to 1. + * + * If no timing information has been received yet, this call will + * return -PA_ERR_NODATA. On success, it will return 0. + * + * For more details see pa_stream_get_timing_info() and + * pa_stream_get_time(). */ +int pa_stream_get_latency(pa_stream *s, pa_usec_t *r_usec, int *negative); + +/** Return the latest raw timing data structure. The returned pointer + * refers to an internal read-only instance of the timing + * structure. The user should make a copy of this structure if + * wanting to modify it. An in-place update to this data structure + * may be requested using pa_stream_update_timing_info(). + * + * If no timing information has been received before (i.e. by + * requesting pa_stream_update_timing_info() or by using + * PA_STREAM_AUTO_TIMING_UPDATE), this function will return NULL. + * + * Please note that the write_index member field (and only this field) + * is updated on each pa_stream_write() call, not just when a timing + * update has been received. */ +const pa_timing_info* pa_stream_get_timing_info(pa_stream *s); + +/** Return a pointer to the stream's sample specification. */ +const pa_sample_spec* pa_stream_get_sample_spec(pa_stream *s); + +/** Return a pointer to the stream's channel map. */ +const pa_channel_map* pa_stream_get_channel_map(pa_stream *s); + +/** Return a pointer to the stream's format. \since 1.0 */ +const pa_format_info* pa_stream_get_format_info(const pa_stream *s); + +/** Return the per-stream server-side buffer metrics of the + * stream. Only valid after the stream has been connected successfully + * and if the server is at least PulseAudio 0.9. This will return the + * actual configured buffering metrics, which may differ from what was + * requested during pa_stream_connect_record() or + * pa_stream_connect_playback(). This call will always return the + * actual per-stream server-side buffer metrics, regardless whether + * PA_STREAM_ADJUST_LATENCY is set or not. \since 0.9.0 */ +const pa_buffer_attr* pa_stream_get_buffer_attr(pa_stream *s); + +/** Change the buffer metrics of the stream during playback. The + * server might have chosen different buffer metrics than + * requested. The selected metrics may be queried with + * pa_stream_get_buffer_attr() as soon as the callback is called. Only + * valid after the stream has been connected successfully and if the + * server is at least PulseAudio 0.9.8. Please be aware of the + * slightly different semantics of the call depending whether + * PA_STREAM_ADJUST_LATENCY is set or not. \since 0.9.8 */ +pa_operation *pa_stream_set_buffer_attr(pa_stream *s, const pa_buffer_attr *attr, pa_stream_success_cb_t cb, void *userdata); + +/** Change the stream sampling rate during playback. You need to pass + * PA_STREAM_VARIABLE_RATE in the flags parameter of + * pa_stream_connect_playback() if you plan to use this function. Only valid + * after the stream has been connected successfully and if the server + * is at least PulseAudio 0.9.8. \since 0.9.8 */ +pa_operation *pa_stream_update_sample_rate(pa_stream *s, uint32_t rate, pa_stream_success_cb_t cb, void *userdata); + +/** Update the property list of the sink input/source output of this + * stream, adding new entries. Please note that it is highly + * recommended to set as many properties initially via + * pa_stream_new_with_proplist() as possible instead a posteriori with + * this function, since that information may be used to route + * this stream to the right device. \since 0.9.11 */ +pa_operation *pa_stream_proplist_update(pa_stream *s, pa_update_mode_t mode, pa_proplist *p, pa_stream_success_cb_t cb, void *userdata); + +/** Update the property list of the sink input/source output of this + * stream, remove entries. \since 0.9.11 */ +pa_operation *pa_stream_proplist_remove(pa_stream *s, const char *const keys[], pa_stream_success_cb_t cb, void *userdata); + +/** For record streams connected to a monitor source: monitor only a + * very specific sink input of the sink. This function needs to be + * called before pa_stream_connect_record() is called. + * Returns zero on success, negative on error. \since 0.9.11 */ +int pa_stream_set_monitor_stream(pa_stream *s, uint32_t sink_input_idx); + +/** Return the sink input index previously set with + * pa_stream_set_monitor_stream(). Returns PA_INVALID_INDEX + * on failure. \since 0.9.11 */ +uint32_t pa_stream_get_monitor_stream(const pa_stream *s); + +PA_C_DECL_END + +#endif diff --git a/src/pulse/subscribe.c b/src/pulse/subscribe.c new file mode 100644 index 0000000..e7cce2e --- /dev/null +++ b/src/pulse/subscribe.c @@ -0,0 +1,88 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> + +#include <pulsecore/macro.h> +#include <pulsecore/pstream-util.h> + +#include "internal.h" +#include "subscribe.h" + +void pa_command_subscribe_event(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_context *c = userdata; + pa_subscription_event_type_t e; + uint32_t idx; + + pa_assert(pd); + pa_assert(command == PA_COMMAND_SUBSCRIBE_EVENT); + pa_assert(t); + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + pa_context_ref(c); + + if (pa_tagstruct_getu32(t, &e) < 0 || + pa_tagstruct_getu32(t, &idx) < 0 || + !pa_tagstruct_eof(t)) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + + if (c->subscribe_callback) + c->subscribe_callback(c, e, idx, c->subscribe_userdata); + +finish: + pa_context_unref(c); +} + +pa_operation* pa_context_subscribe(pa_context *c, pa_subscription_mask_t m, pa_context_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_SUBSCRIBE, &tag); + pa_tagstruct_putu32(t, m); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +void pa_context_set_subscribe_callback(pa_context *c, pa_context_subscribe_cb_t cb, void *userdata) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + if (c->state == PA_CONTEXT_TERMINATED || c->state == PA_CONTEXT_FAILED) + return; + + c->subscribe_callback = cb; + c->subscribe_userdata = userdata; +} diff --git a/src/pulse/subscribe.h b/src/pulse/subscribe.h new file mode 100644 index 0000000..b43c8ea --- /dev/null +++ b/src/pulse/subscribe.h @@ -0,0 +1,83 @@ +#ifndef foosubscribehfoo +#define foosubscribehfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <inttypes.h> + +#include <pulse/def.h> +#include <pulse/context.h> +#include <pulse/cdecl.h> +#include <pulse/version.h> + +/** \page subscribe Event Subscription + * + * \section overv_sec Overview + * + * The application can be notified, asynchronously, whenever the internal + * layout of the server changes. Possible notifications are described in the + * \ref pa_subscription_event_type and \ref pa_subscription_mask + * enumerations. + * + * The application sets the notification mask using pa_context_subscribe() + * and the function that will be called whenever a notification occurs using + * pa_context_set_subscribe_callback(). + * + * The callback will be called with a \ref pa_subscription_event_type_t + * representing the event that caused the callback. Clients can examine what + * object changed using \ref PA_SUBSCRIPTION_EVENT_FACILITY_MASK. The actual + * event type can then be extracted with \ref PA_SUBSCRIPTION_EVENT_TYPE_MASK. + * Please note that the masked values are integers, not flags (so you will + * check the object/event type using a comparison not a binary AND). For + * example, the callback might look something like: + * +@verbatim +void my_subscription_callback(pa_context *c, pa_subscription_event_type_t t, + uint32_t idx, void *userdata) { + if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE) { + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) { + ... a source was added, let's do stuff! ... + } + } +} +@endverbatim + */ + +/** \file + * Daemon introspection event subscription subsystem. + * + * See also \subpage subscribe + */ + +PA_C_DECL_BEGIN + +/** Subscription event callback prototype */ +typedef void (*pa_context_subscribe_cb_t)(pa_context *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata); + +/** Enable event notification */ +pa_operation* pa_context_subscribe(pa_context *c, pa_subscription_mask_t m, pa_context_success_cb_t cb, void *userdata); + +/** Set the context specific call back function that is called whenever the state of the daemon changes */ +void pa_context_set_subscribe_callback(pa_context *c, pa_context_subscribe_cb_t cb, void *userdata); + +PA_C_DECL_END + +#endif diff --git a/src/pulse/thread-mainloop.c b/src/pulse/thread-mainloop.c new file mode 100644 index 0000000..495cc1e --- /dev/null +++ b/src/pulse/thread-mainloop.c @@ -0,0 +1,301 @@ +/*** + This file is part of PulseAudio. + + Copyright 2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#ifndef OS_IS_WIN32 +#include <pthread.h> +#endif + +#include <signal.h> +#include <stdio.h> + +#include <pulse/xmalloc.h> +#include <pulse/mainloop.h> + +#include <pulsecore/i18n.h> +#include <pulsecore/log.h> +#include <pulsecore/thread.h> +#include <pulsecore/mutex.h> +#include <pulsecore/macro.h> +#include <pulsecore/poll.h> + +#include "thread-mainloop.h" + +struct pa_threaded_mainloop { + pa_mainloop *real_mainloop; + volatile int n_waiting, n_waiting_for_accept; + pa_atomic_t in_once_unlocked; + + pa_thread* thread; + pa_mutex* mutex; + pa_cond* cond, *accept_cond; + + char *name; +}; + +static inline int in_worker(pa_threaded_mainloop *m) { + return pa_thread_self() == m->thread; +} + +static int poll_func(struct pollfd *ufds, unsigned long nfds, int timeout, void *userdata) { + pa_mutex *mutex = userdata; + int r; + + pa_assert(mutex); + + /* Before entering poll() we unlock the mutex, so that + * avahi_simple_poll_quit() can succeed from another thread. */ + + pa_mutex_unlock(mutex); + r = pa_poll(ufds, nfds, timeout); + pa_mutex_lock(mutex); + + return r; +} + +static void thread(void *userdata) { + pa_threaded_mainloop *m = userdata; + +#ifndef OS_IS_WIN32 + sigset_t mask; + sigset_t prev_mask; + struct sigaction sa; + + sigfillset(&mask); + + /* If SIGSYS is currently unblocked and trapped then keep it unblocked. */ + if (!pthread_sigmask(SIG_SETMASK, NULL, &prev_mask) && + !sigismember(&prev_mask, SIGSYS) && + !sigaction(SIGSYS, NULL, &sa) + && sa.sa_handler != SIG_DFL) { + sigdelset(&mask, SIGSYS); + } + + /* Make sure that signals are delivered to the main thread. + * Use SIG_SETMASK because SIG_BLOCK does an union with current set.*/ + pthread_sigmask(SIG_SETMASK, &mask, NULL); +#endif + + pa_mutex_lock(m->mutex); + + (void) pa_mainloop_run(m->real_mainloop, NULL); + + pa_mutex_unlock(m->mutex); +} + +pa_threaded_mainloop *pa_threaded_mainloop_new(void) { + pa_threaded_mainloop *m; + + pa_init_i18n(); + + m = pa_xnew0(pa_threaded_mainloop, 1); + + if (!(m->real_mainloop = pa_mainloop_new())) { + pa_xfree(m); + return NULL; + } + + m->mutex = pa_mutex_new(true, true); + m->cond = pa_cond_new(); + m->accept_cond = pa_cond_new(); + + pa_mainloop_set_poll_func(m->real_mainloop, poll_func, m->mutex); + + return m; +} + +void pa_threaded_mainloop_free(pa_threaded_mainloop* m) { + pa_assert(m); + + /* Make sure that this function is not called from the helper thread */ + pa_assert((m->thread && !pa_thread_is_running(m->thread)) || !in_worker(m)); + + pa_threaded_mainloop_stop(m); + + if (m->thread) + pa_thread_free(m->thread); + + pa_mainloop_free(m->real_mainloop); + + pa_mutex_free(m->mutex); + pa_cond_free(m->cond); + pa_cond_free(m->accept_cond); + + pa_xfree(m->name); + pa_xfree(m); +} + +int pa_threaded_mainloop_start(pa_threaded_mainloop *m) { + pa_assert(m); + + pa_assert(!m->thread || !pa_thread_is_running(m->thread)); + + if (!(m->thread = pa_thread_new(m->name ? m->name : "threaded-ml", thread, m))) + return -1; + + return 0; +} + +void pa_threaded_mainloop_stop(pa_threaded_mainloop *m) { + pa_assert(m); + + if (!m->thread || !pa_thread_is_running(m->thread)) + return; + + /* Make sure that this function is not called from the helper thread */ + pa_assert(!in_worker(m)); + + pa_mutex_lock(m->mutex); + pa_mainloop_quit(m->real_mainloop, 0); + pa_mutex_unlock(m->mutex); + + pa_thread_join(m->thread); +} + +void pa_threaded_mainloop_lock(pa_threaded_mainloop *m) { + pa_assert(m); + + /* Make sure that this function is not called from the helper thread, unless it is unlocked */ + pa_assert(!m->thread || !pa_thread_is_running(m->thread) || !in_worker(m) || pa_atomic_load(&m->in_once_unlocked)); + + pa_mutex_lock(m->mutex); +} + +void pa_threaded_mainloop_unlock(pa_threaded_mainloop *m) { + pa_assert(m); + + /* Make sure that this function is not called from the helper thread, unless it is unlocked */ + pa_assert(!m->thread || !pa_thread_is_running(m->thread) || !in_worker(m) || pa_atomic_load(&m->in_once_unlocked)); + + pa_mutex_unlock(m->mutex); +} + +/* Called with the lock taken */ +void pa_threaded_mainloop_signal(pa_threaded_mainloop *m, int wait_for_accept) { + pa_assert(m); + + pa_cond_signal(m->cond, 1); + + if (wait_for_accept) { + m->n_waiting_for_accept ++; + + while (m->n_waiting_for_accept > 0) + pa_cond_wait(m->accept_cond, m->mutex); + } +} + +/* Called with the lock taken */ +void pa_threaded_mainloop_wait(pa_threaded_mainloop *m) { + pa_assert(m); + + /* Make sure that this function is not called from the helper thread */ + pa_assert(!m->thread || !pa_thread_is_running(m->thread) || !in_worker(m)); + + m->n_waiting ++; + + pa_cond_wait(m->cond, m->mutex); + + pa_assert(m->n_waiting > 0); + m->n_waiting --; +} + +/* Called with the lock taken */ +void pa_threaded_mainloop_accept(pa_threaded_mainloop *m) { + pa_assert(m); + + /* Make sure that this function is not called from the helper thread */ + pa_assert(!m->thread || !pa_thread_is_running(m->thread) || !in_worker(m)); + + pa_assert(m->n_waiting_for_accept > 0); + m->n_waiting_for_accept --; + + pa_cond_signal(m->accept_cond, 0); +} + +int pa_threaded_mainloop_get_retval(const pa_threaded_mainloop *m) { + pa_assert(m); + + return pa_mainloop_get_retval(m->real_mainloop); +} + +pa_mainloop_api* pa_threaded_mainloop_get_api(pa_threaded_mainloop*m) { + pa_assert(m); + + return pa_mainloop_get_api(m->real_mainloop); +} + +int pa_threaded_mainloop_in_thread(pa_threaded_mainloop *m) { + pa_assert(m); + + return m->thread && pa_thread_self() == m->thread; +} + +void pa_threaded_mainloop_set_name(pa_threaded_mainloop *m, const char *name) { + pa_assert(m); + pa_assert(name); + + m->name = pa_xstrdup(name); + + if (m->thread) + pa_thread_set_name(m->thread, m->name); +} + +typedef struct { + pa_threaded_mainloop *mainloop; + void (*callback)(pa_threaded_mainloop *m, void *userdata); + void *userdata; +} once_unlocked_data; + +static void once_unlocked_cb(pa_mainloop_api *api, void *userdata) { + once_unlocked_data *data = userdata; + + pa_assert(userdata); + + pa_atomic_store(&data->mainloop->in_once_unlocked, 1); + pa_mutex_unlock(data->mainloop->mutex); + + data->callback(data->mainloop, data->userdata); + + pa_mutex_lock(data->mainloop->mutex); + pa_atomic_store(&data->mainloop->in_once_unlocked, 0); +} + +void pa_threaded_mainloop_once_unlocked(pa_threaded_mainloop *m, void (*callback)(pa_threaded_mainloop *m, void *userdata), + void *userdata) { + pa_mainloop_api *api; + once_unlocked_data *data; + + pa_assert(m); + pa_assert(callback); + /* Make sure that this function is not called from the helper thread */ + pa_assert((m->thread && !pa_thread_is_running(m->thread)) || !in_worker(m)); + + api = pa_mainloop_get_api(m->real_mainloop); + data = pa_xnew0(once_unlocked_data, 1); + + data->mainloop = m; + data->callback = callback; + data->userdata = userdata; + + pa_mainloop_api_once(api, once_unlocked_cb, data); +} diff --git a/src/pulse/thread-mainloop.h b/src/pulse/thread-mainloop.h new file mode 100644 index 0000000..fd9ad3c --- /dev/null +++ b/src/pulse/thread-mainloop.h @@ -0,0 +1,329 @@ +#ifndef foothreadmainloophfoo +#define foothreadmainloophfoo + +/*** + This file is part of PulseAudio. + + Copyright 2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulse/mainloop-api.h> +#include <pulse/cdecl.h> +#include <pulse/version.h> + +PA_C_DECL_BEGIN + +/** \page threaded_mainloop Threaded Main Loop + * + * \section overv_sec Overview + * + * The threaded main loop implementation is a special version of the primary + * main loop implementation (see \ref mainloop). For the basic design, see + * its documentation. + * + * The added feature in the threaded main loop is that it spawns a new thread + * that runs the real main loop. This allows a synchronous application to use + * the asynchronous API without risking stalling the PulseAudio library. + * + * \section creat_sec Creation + * + * A pa_threaded_mainloop object is created using pa_threaded_mainloop_new(). + * This will only allocate the required structures though, so to use it the + * thread must also be started. This is done through + * pa_threaded_mainloop_start(), after which you can start using the main loop. + * + * \section destr_sec Destruction + * + * When the PulseAudio connection has been terminated, the thread must be + * stopped and the resources freed. Stopping the thread is done using + * pa_threaded_mainloop_stop(), which must be called without the lock (see + * below) held. When that function returns, the thread is stopped and the + * pa_threaded_mainloop object can be freed using pa_threaded_mainloop_free(). + * + * \section lock_sec Locking + * + * Since the PulseAudio API doesn't allow concurrent accesses to objects, + * a locking scheme must be used to guarantee safe usage. The threaded main + * loop API provides such a scheme through the functions + * pa_threaded_mainloop_lock() and pa_threaded_mainloop_unlock(). + * + * The lock is recursive, so it's safe to use it multiple times from the same + * thread. Just make sure you call pa_threaded_mainloop_unlock() the same + * number of times you called pa_threaded_mainloop_lock(). + * + * The lock needs to be held whenever you call any PulseAudio function that + * uses an object associated with this main loop. Those objects include + * pa_mainloop, pa_context, pa_stream and pa_operation, and the various event + * objects (pa_io_event, pa_time_event, pa_defer_event). Make sure you do not + * hold on to the lock more than necessary though, as the threaded main loop + * stops while the lock is held. + * + * Example: + * + * \code + * void my_check_stream_func(pa_threaded_mainloop *m, pa_stream *s) { + * pa_stream_state_t state; + * + * pa_threaded_mainloop_lock(m); + * + * state = pa_stream_get_state(s); + * + * pa_threaded_mainloop_unlock(m); + * + * if (state == PA_STREAM_READY) + * printf("Stream is ready!"); + * else + * printf("Stream is not ready!"); + * } + * \endcode + * + * \section cb_sec Callbacks + * + * Callbacks in PulseAudio are asynchronous, so they require extra care when + * using them together with a threaded main loop. + * + * The easiest way to turn the callback based operations into synchronous + * ones, is to simply wait for the callback to be called and continue from + * there. This is the approach chosen in PulseAudio's threaded API. + * + * \subsection basic_subsec Basic callbacks + * + * For the basic case, where all that is required is to wait for the callback + * to be invoked, the code should look something like this: + * + * Example: + * + * \code + * static void my_drain_callback(pa_stream *s, int success, void *userdata) { + * pa_threaded_mainloop *m; + * + * m = userdata; + * assert(m); + * + * pa_threaded_mainloop_signal(m, 0); + * } + * + * void my_drain_stream_func(pa_threaded_mainloop *m, pa_stream *s) { + * pa_operation *o; + * + * pa_threaded_mainloop_lock(m); + * + * o = pa_stream_drain(s, my_drain_callback, m); + * assert(o); + * + * while (pa_operation_get_state(o) == PA_OPERATION_RUNNING) + * pa_threaded_mainloop_wait(m); + * + * pa_operation_unref(o); + * + * pa_threaded_mainloop_unlock(m); + * } + * \endcode + * + * The main function, my_drain_stream_func(), will wait for the callback to + * be called using pa_threaded_mainloop_wait(). + * + * If your application is multi-threaded, then this waiting must be + * done inside a while loop. The reason for this is that multiple + * threads might be using pa_threaded_mainloop_wait() at the same + * time. Each thread must therefore verify that it was its callback + * that was invoked. Also the underlying OS synchronization primitives + * are usually not free of spurious wake-ups, so a + * pa_threaded_mainloop_wait() must be called within a loop even if + * you have only one thread waiting. + * + * The callback, my_drain_callback(), indicates to the main function that it + * has been called using pa_threaded_mainloop_signal(). + * + * As you can see, pa_threaded_mainloop_wait() may only be called with + * the lock held. The same thing is true for pa_threaded_mainloop_signal(), + * but as the lock is held before the callback is invoked, you do not have to + * deal with that. + * + * The functions will not dead lock because the wait function will release + * the lock before waiting and then regrab it once it has been signalled. + * For those of you familiar with threads, the behaviour is that of a + * condition variable. + * + * \subsection data_subsec Data callbacks + * + * For many callbacks, simply knowing that they have been called is + * insufficient. The callback also receives some data that is desired. To + * access this data safely, we must extend our example a bit: + * + * \code + * static int * volatile drain_result = NULL; + * + * static void my_drain_callback(pa_stream*s, int success, void *userdata) { + * pa_threaded_mainloop *m; + * + * m = userdata; + * assert(m); + * + * drain_result = &success; + * + * pa_threaded_mainloop_signal(m, 1); + * } + * + * void my_drain_stream_func(pa_threaded_mainloop *m, pa_stream *s) { + * pa_operation *o; + * + * pa_threaded_mainloop_lock(m); + * + * o = pa_stream_drain(s, my_drain_callback, m); + * assert(o); + * + * while (drain_result == NULL) + * pa_threaded_mainloop_wait(m); + * + * pa_operation_unref(o); + * + * if (*drain_result) + * printf("Success!"); + * else + * printf("Bitter defeat..."); + * + * pa_threaded_mainloop_accept(m); + * + * pa_threaded_mainloop_unlock(m); + * } + * \endcode + * + * The example is a bit silly as it would probably have been easier to just + * copy the contents of success, but for larger data structures this can be + * wasteful. + * + * The difference here compared to the basic callback is the value 1 passed + * to pa_threaded_mainloop_signal() and the call to + * pa_threaded_mainloop_accept(). What will happen is that + * pa_threaded_mainloop_signal() will signal the main function and then wait. + * The main function is then free to use the data in the callback until + * pa_threaded_mainloop_accept() is called, which will allow the callback + * to continue. + * + * Note that pa_threaded_mainloop_accept() must be called some time between + * exiting the while loop and unlocking the main loop! Failure to do so will + * result in a race condition. I.e. it is not ok to release the lock and + * regrab it before calling pa_threaded_mainloop_accept(). + * + * \subsection async_subsec Asynchronous callbacks + * + * PulseAudio also has callbacks that are completely asynchronous, meaning + * that they can be called at any time. The threaded main loop API provides + * the locking mechanism to handle concurrent accesses, but nothing else. + * Applications will have to handle communication from the callback to the + * main program through their own mechanisms. + * + * The callbacks that are completely asynchronous are: + * + * \li State callbacks for contexts, streams, etc. + * \li Subscription notifications + */ + +/** \file + * + * A thread based event loop implementation based on pa_mainloop. The + * event loop is run in a helper thread in the background. A few + * synchronization primitives are available to access the objects + * attached to the event loop safely. + * + * See also \subpage threaded_mainloop + */ + +/** An opaque threaded main loop object */ +typedef struct pa_threaded_mainloop pa_threaded_mainloop; + +/** Allocate a new threaded main loop object. You have to call + * pa_threaded_mainloop_start() before the event loop thread starts + * running. Free with pa_threaded_mainloop_free. */ +pa_threaded_mainloop *pa_threaded_mainloop_new(void); + +/** Free a threaded main loop object. If the event loop thread is + * still running, terminate it with pa_threaded_mainloop_stop() + * first. */ +void pa_threaded_mainloop_free(pa_threaded_mainloop* m); + +/** Start the event loop thread. Returns zero on success, negative on error. */ +int pa_threaded_mainloop_start(pa_threaded_mainloop *m); + +/** Terminate the event loop thread cleanly. Make sure to unlock the + * mainloop object before calling this function. */ +void pa_threaded_mainloop_stop(pa_threaded_mainloop *m); + +/** Lock the event loop object, effectively blocking the event loop + * thread from processing events. You can use this to enforce + * exclusive access to all objects attached to the event loop. This + * lock is recursive. This function may not be called inside the event + * loop thread. Events that are dispatched from the event loop thread + * are executed with this lock held. */ +void pa_threaded_mainloop_lock(pa_threaded_mainloop *m); + +/** Unlock the event loop object, inverse of pa_threaded_mainloop_lock(). */ +void pa_threaded_mainloop_unlock(pa_threaded_mainloop *m); + +/** Wait for an event to be signalled by the event loop thread. You + * can use this to pass data from the event loop thread to the main + * thread in a synchronized fashion. This function may not be called + * inside the event loop thread. Prior to this call the event loop + * object needs to be locked using pa_threaded_mainloop_lock(). While + * waiting the lock will be released. Immediately before returning it + * will be acquired again. This function may spuriously wake up even + * without pa_threaded_mainloop_signal() being called. You need to + * make sure to handle that! */ +void pa_threaded_mainloop_wait(pa_threaded_mainloop *m); + +/** Signal all threads waiting for a signalling event in + * pa_threaded_mainloop_wait(). If wait_for_accept is non-zero, do + * not return before the signal was accepted by a + * pa_threaded_mainloop_accept() call. While waiting for that condition + * the event loop object is unlocked. */ +void pa_threaded_mainloop_signal(pa_threaded_mainloop *m, int wait_for_accept); + +/** Accept a signal from the event thread issued with + * pa_threaded_mainloop_signal(). This call should only be used in + * conjunction with pa_threaded_mainloop_signal() with a non-zero + * wait_for_accept value. */ +void pa_threaded_mainloop_accept(pa_threaded_mainloop *m); + +/** Return the return value as specified with the main loop's + * pa_mainloop_quit() routine. */ +int pa_threaded_mainloop_get_retval(const pa_threaded_mainloop *m); + +/** Return the main loop abstraction layer vtable for this main loop. + * There is no need to free this object as it is owned by the loop + * and is destroyed when the loop is freed. */ +pa_mainloop_api* pa_threaded_mainloop_get_api(pa_threaded_mainloop*m); + +/** Returns non-zero when called from within the event loop thread. \since 0.9.7 */ +int pa_threaded_mainloop_in_thread(pa_threaded_mainloop *m); + +/** Sets the name of the thread. \since 5.0 */ +void pa_threaded_mainloop_set_name(pa_threaded_mainloop *m, const char *name); + +/** Runs the given callback in the mainloop thread without the lock held. The + * caller is responsible for ensuring that PulseAudio data structures are only + * accessed in a thread-safe way (that is, APIs that take pa_context and + * pa_stream are not thread-safe, and should not accessed without some + * synchronisation). This is the only situation in which + * pa_threaded_mainloop_lock() and pa_threaded_mainloop_unlock() may be used + * in the mainloop thread context. \since 13.0 */ +void pa_threaded_mainloop_once_unlocked(pa_threaded_mainloop *m, void (*callback)(pa_threaded_mainloop *m, void *userdata), + void *userdata); + +PA_C_DECL_END + +#endif diff --git a/src/pulse/timeval.c b/src/pulse/timeval.c new file mode 100644 index 0000000..e991c33 --- /dev/null +++ b/src/pulse/timeval.c @@ -0,0 +1,211 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stddef.h> +#include <sys/time.h> + +#ifdef HAVE_WINDOWS_H +#include <windows.h> +#endif + +#include <pulsecore/macro.h> +#include <pulsecore/core-util.h> + +#include "timeval.h" + +struct timeval *pa_gettimeofday(struct timeval *tv) { + pa_assert(tv); + +#if defined(OS_IS_WIN32) + /* + * Copied from implementation by Steven Edwards (LGPL). + * Found on wine mailing list. + */ +#if defined(_MSC_VER) || defined(__BORLANDC__) +#define EPOCHFILETIME (116444736000000000i64) +#else +#define EPOCHFILETIME (116444736000000000LL) +#endif +{ + FILETIME ft; + LARGE_INTEGER li; + int64_t t; + + GetSystemTimeAsFileTime(&ft); + li.LowPart = ft.dwLowDateTime; + li.HighPart = ft.dwHighDateTime; + t = li.QuadPart; /* In 100-nanosecond intervals */ + t -= EPOCHFILETIME; /* Offset to the Epoch time */ + t /= 10; /* In microseconds */ + tv->tv_sec = (time_t) (t / PA_USEC_PER_SEC); + tv->tv_usec = (suseconds_t) (t % PA_USEC_PER_SEC); +} +#elif defined(HAVE_GETTIMEOFDAY) + pa_assert_se(gettimeofday(tv, NULL) == 0); +#else +#error "Platform lacks gettimeofday() or equivalent function." +#endif + + return tv; +} + +pa_usec_t pa_timeval_diff(const struct timeval *a, const struct timeval *b) { + pa_usec_t r; + + pa_assert(a); + pa_assert(b); + + /* Check which is the earlier time and swap the two arguments if required. */ + if (PA_UNLIKELY(pa_timeval_cmp(a, b) < 0)) { + const struct timeval *c; + c = a; + a = b; + b = c; + } + + /* Calculate the second difference*/ + r = ((pa_usec_t) a->tv_sec - (pa_usec_t) b->tv_sec) * PA_USEC_PER_SEC; + + /* Calculate the microsecond difference */ + if (a->tv_usec > b->tv_usec) + r += (pa_usec_t) a->tv_usec - (pa_usec_t) b->tv_usec; + else if (a->tv_usec < b->tv_usec) + r -= (pa_usec_t) b->tv_usec - (pa_usec_t) a->tv_usec; + + return r; +} + +int pa_timeval_cmp(const struct timeval *a, const struct timeval *b) { + pa_assert(a); + pa_assert(b); + + if (a->tv_sec < b->tv_sec) + return -1; + + if (a->tv_sec > b->tv_sec) + return 1; + + if (a->tv_usec < b->tv_usec) + return -1; + + if (a->tv_usec > b->tv_usec) + return 1; + + return 0; +} + +pa_usec_t pa_timeval_age(const struct timeval *tv) { + struct timeval now; + pa_assert(tv); + + return pa_timeval_diff(pa_gettimeofday(&now), tv); +} + +struct timeval* pa_timeval_add(struct timeval *tv, pa_usec_t v) { + time_t secs; + pa_assert(tv); + + secs = (time_t) (v/PA_USEC_PER_SEC); + + if (PA_UNLIKELY(tv->tv_sec > PA_INT_TYPE_MAX(time_t) - secs)) + goto overflow; + + tv->tv_sec += secs; + v -= (pa_usec_t) secs * PA_USEC_PER_SEC; + tv->tv_usec += (suseconds_t) v; + + /* Normalize */ + while ((pa_usec_t) tv->tv_usec >= PA_USEC_PER_SEC) { + + if (PA_UNLIKELY(tv->tv_sec >= PA_INT_TYPE_MAX(time_t))) + goto overflow; + + tv->tv_sec++; + tv->tv_usec -= (suseconds_t) PA_USEC_PER_SEC; + } + + return tv; + +overflow: + tv->tv_sec = PA_INT_TYPE_MAX(time_t); + tv->tv_usec = (suseconds_t) (PA_USEC_PER_SEC-1); + return tv; +} + +struct timeval* pa_timeval_sub(struct timeval *tv, pa_usec_t v) { + time_t secs; + pa_assert(tv); + + secs = (time_t) (v/PA_USEC_PER_SEC); + + if (PA_UNLIKELY(tv->tv_sec < secs)) + goto underflow; + + tv->tv_sec -= secs; + v -= (pa_usec_t) secs * PA_USEC_PER_SEC; + + if (tv->tv_usec >= (suseconds_t) v) + tv->tv_usec -= (suseconds_t) v; + else { + + if (PA_UNLIKELY(tv->tv_sec <= 0)) + goto underflow; + + tv->tv_sec --; + tv->tv_usec += (suseconds_t) (PA_USEC_PER_SEC - v); + } + + return tv; + +underflow: + tv->tv_sec = 0; + tv->tv_usec = 0; + return tv; +} + +struct timeval* pa_timeval_store(struct timeval *tv, pa_usec_t v) { + pa_assert(tv); + + if (PA_UNLIKELY(v == PA_USEC_INVALID)) { + tv->tv_sec = PA_INT_TYPE_MAX(time_t); + tv->tv_usec = (suseconds_t) (PA_USEC_PER_SEC-1); + + return tv; + } + + tv->tv_sec = (time_t) (v / PA_USEC_PER_SEC); + tv->tv_usec = (suseconds_t) (v % PA_USEC_PER_SEC); + + return tv; +} + +pa_usec_t pa_timeval_load(const struct timeval *tv) { + + if (PA_UNLIKELY(!tv)) + return PA_USEC_INVALID; + + return + (pa_usec_t) tv->tv_sec * PA_USEC_PER_SEC + + (pa_usec_t) tv->tv_usec; +} diff --git a/src/pulse/timeval.h b/src/pulse/timeval.h new file mode 100644 index 0000000..9ecf791 --- /dev/null +++ b/src/pulse/timeval.h @@ -0,0 +1,87 @@ +#ifndef footimevalhfoo +#define footimevalhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulse/cdecl.h> +#include <pulse/gccmacro.h> +#include <pulse/sample.h> +#include <pulse/version.h> + +/** \file + * Utility functions for handling timeval calculations */ + +PA_C_DECL_BEGIN + +/** The number of milliseconds in a second */ +#define PA_MSEC_PER_SEC ((pa_usec_t) 1000ULL) + +/** The number of microseconds in a second */ +#define PA_USEC_PER_SEC ((pa_usec_t) 1000000ULL) + +/** The number of nanoseconds in a second */ +#define PA_NSEC_PER_SEC ((unsigned long long) 1000000000ULL) + +/** The number of microseconds in a millisecond */ +#define PA_USEC_PER_MSEC ((pa_usec_t) 1000ULL) + +/** The number of nanoseconds in a millisecond */ +#define PA_NSEC_PER_MSEC ((unsigned long long) 1000000ULL) + +/** The number of nanoseconds in a microsecond */ +#define PA_NSEC_PER_USEC ((unsigned long long) 1000ULL) + +/** Invalid time in usec. \since 0.9.15 */ +#define PA_USEC_INVALID ((pa_usec_t) -1) + +/** Biggest time in usec. \since 0.9.18 */ +#define PA_USEC_MAX ((pa_usec_t) -2) + +struct timeval; + +/** Return the current wallclock timestamp, just like UNIX gettimeofday(). */ +struct timeval *pa_gettimeofday(struct timeval *tv); + +/** Calculate the difference between the two specified timeval + * structs. */ +pa_usec_t pa_timeval_diff(const struct timeval *a, const struct timeval *b) PA_GCC_PURE; + +/** Compare the two timeval structs and return 0 when equal, negative when a < b, positive otherwise */ +int pa_timeval_cmp(const struct timeval *a, const struct timeval *b) PA_GCC_PURE; + +/** Return the time difference between now and the specified timestamp */ +pa_usec_t pa_timeval_age(const struct timeval *tv); + +/** Add the specified time in microseconds to the specified timeval structure */ +struct timeval* pa_timeval_add(struct timeval *tv, pa_usec_t v); + +/** Subtract the specified time in microseconds to the specified timeval structure. \since 0.9.11 */ +struct timeval* pa_timeval_sub(struct timeval *tv, pa_usec_t v); + +/** Store the specified usec value in the timeval struct. \since 0.9.7 */ +struct timeval* pa_timeval_store(struct timeval *tv, pa_usec_t v); + +/** Load the specified tv value and return it in usec. \since 0.9.7 */ +pa_usec_t pa_timeval_load(const struct timeval *tv); + +PA_C_DECL_END + +#endif diff --git a/src/pulse/utf8.c b/src/pulse/utf8.c new file mode 100644 index 0000000..3262eed --- /dev/null +++ b/src/pulse/utf8.c @@ -0,0 +1,287 @@ +/*** + This file is part of PulseAudio. + + Copyright 2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +/* This file is based on the GLIB utf8 validation functions. The + * original license text follows. */ + +/* gutf8.c - Operations on UTF-8 strings. + * + * Copyright (C) 1999 Tom Tromey + * Copyright (C) 2000 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <stdlib.h> +#include <inttypes.h> +#include <string.h> + +#ifdef HAVE_ICONV +#include <iconv.h> +#endif + +#include <pulse/xmalloc.h> +#include <pulsecore/macro.h> + +#include "utf8.h" + +#define FILTER_CHAR '_' + +static inline bool is_unicode_valid(uint32_t ch) { + + if (ch >= 0x110000) /* End of unicode space */ + return false; + if ((ch & 0xFFFFF800) == 0xD800) /* Reserved area for UTF-16 */ + return false; + if ((ch >= 0xFDD0) && (ch <= 0xFDEF)) /* Reserved */ + return false; + if ((ch & 0xFFFE) == 0xFFFE) /* BOM (Byte Order Mark) */ + return false; + + return true; +} + +static inline bool is_continuation_char(uint8_t ch) { + if ((ch & 0xc0) != 0x80) /* 10xxxxxx */ + return false; + return true; +} + +static inline void merge_continuation_char(uint32_t *u_ch, uint8_t ch) { + *u_ch <<= 6; + *u_ch |= ch & 0x3f; +} + +static char* utf8_validate(const char *str, char *output) { + uint32_t val = 0; + uint32_t min = 0; + const uint8_t *p, *last; + int size; + uint8_t *o; + + pa_assert(str); + + o = (uint8_t*) output; + for (p = (const uint8_t*) str; *p; p++) { + if (*p < 128) { + if (o) + *o = *p; + } else { + last = p; + + if ((*p & 0xe0) == 0xc0) { /* 110xxxxx two-char seq. */ + size = 2; + min = 128; + val = (uint32_t) (*p & 0x1e); + goto ONE_REMAINING; + } else if ((*p & 0xf0) == 0xe0) { /* 1110xxxx three-char seq.*/ + size = 3; + min = (1 << 11); + val = (uint32_t) (*p & 0x0f); + goto TWO_REMAINING; + } else if ((*p & 0xf8) == 0xf0) { /* 11110xxx four-char seq */ + size = 4; + min = (1 << 16); + val = (uint32_t) (*p & 0x07); + } else + goto error; + + p++; + if (!is_continuation_char(*p)) + goto error; + merge_continuation_char(&val, *p); + +TWO_REMAINING: + p++; + if (!is_continuation_char(*p)) + goto error; + merge_continuation_char(&val, *p); + +ONE_REMAINING: + p++; + if (!is_continuation_char(*p)) + goto error; + merge_continuation_char(&val, *p); + + if (val < min) + goto error; + + if (!is_unicode_valid(val)) + goto error; + + if (o) { + memcpy(o, last, (size_t) size); + o += size; + } + + continue; + +error: + if (o) { + *o = FILTER_CHAR; + p = last; /* We retry at the next character */ + } else + goto failure; + } + + if (o) + o++; + } + + if (o) { + *o = '\0'; + return output; + } + + return (char*) str; + +failure: + return NULL; +} + +char* pa_utf8_valid (const char *str) { + return utf8_validate(str, NULL); +} + +char* pa_utf8_filter (const char *str) { + char *new_str; + + pa_assert(str); + new_str = pa_xmalloc(strlen(str) + 1); + return utf8_validate(str, new_str); +} + +#ifdef HAVE_ICONV + +static char* iconv_simple(const char *str, const char *to, const char *from) { + char *new_str; + size_t len, inlen; + iconv_t cd; + ICONV_CONST char *inbuf; + char *outbuf; + size_t res, inbytes, outbytes; + + pa_assert(str); + pa_assert(to); + pa_assert(from); + + cd = iconv_open(to, from); + if (cd == (iconv_t)-1) + return NULL; + + inlen = len = strlen(str) + 1; + new_str = pa_xmalloc(len); + + for (;;) { + inbuf = (ICONV_CONST char*) str; /* Brain dead prototype for iconv() */ + inbytes = inlen; + outbuf = new_str; + outbytes = len; + + res = iconv(cd, &inbuf, &inbytes, &outbuf, &outbytes); + + if (res != (size_t)-1) + break; + + if (errno != E2BIG) { + pa_xfree(new_str); + new_str = NULL; + break; + } + + pa_assert(inbytes != 0); + + len += inbytes; + new_str = pa_xrealloc(new_str, len); + } + + iconv_close(cd); + + return new_str; +} + +char* pa_utf8_to_locale (const char *str) { + return iconv_simple(str, "", "UTF-8"); +} + +char* pa_locale_to_utf8 (const char *str) { + return iconv_simple(str, "UTF-8", ""); +} + +#else + +char* pa_utf8_to_locale (const char *str) { + pa_assert(str); + + return pa_ascii_filter(str); +} + +char* pa_locale_to_utf8 (const char *str) { + pa_assert(str); + + if (pa_utf8_valid(str)) + return pa_xstrdup(str); + + return NULL; +} + +#endif + +char *pa_ascii_valid(const char *str) { + const char *p; + pa_assert(str); + + for (p = str; *p; p++) + if ((unsigned char) *p >= 128) + return NULL; + + return (char*) str; +} + +char *pa_ascii_filter(const char *str) { + char *r, *s, *d; + pa_assert(str); + + r = pa_xstrdup(str); + + for (s = r, d = r; *s; s++) + if ((unsigned char) *s < 128) + *(d++) = *s; + + *d = 0; + + return r; +} diff --git a/src/pulse/utf8.h b/src/pulse/utf8.h new file mode 100644 index 0000000..a72097a --- /dev/null +++ b/src/pulse/utf8.h @@ -0,0 +1,54 @@ +#ifndef fooutf8hfoo +#define fooutf8hfoo + +/*** + This file is part of PulseAudio. + + Copyright 2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <pulse/cdecl.h> +#include <pulse/gccmacro.h> +#include <pulse/version.h> + +/** \file + * UTF-8 validation functions + */ + +PA_C_DECL_BEGIN + +/** Test if the specified strings qualifies as valid UTF8. Return the string if so, otherwise NULL */ +char *pa_utf8_valid(const char *str) PA_GCC_PURE; + +/** Test if the specified strings qualifies as valid 7-bit ASCII. Return the string if so, otherwise NULL. \since 0.9.15 */ +char *pa_ascii_valid(const char *str) PA_GCC_PURE; + +/** Filter all invalid UTF8 characters from the specified string, returning a new fully UTF8 valid string. Don't forget to free the returned string with pa_xfree() */ +char *pa_utf8_filter(const char *str); + +/** Filter all invalid ASCII characters from the specified string, returning a new fully ASCII valid string. Don't forget to free the returned string with pa_xfree(). \since 0.9.15 */ +char *pa_ascii_filter(const char *str); + +/** Convert a UTF-8 string to the current locale. Free the string using pa_xfree(). */ +char* pa_utf8_to_locale (const char *str); + +/** Convert a string in the current locale to UTF-8. Free the string using pa_xfree(). */ +char* pa_locale_to_utf8 (const char *str); + +PA_C_DECL_END + +#endif diff --git a/src/pulse/util.c b/src/pulse/util.c new file mode 100644 index 0000000..2be389b --- /dev/null +++ b/src/pulse/util.c @@ -0,0 +1,520 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <sys/types.h> + +#ifdef HAVE_PWD_H +#include <pwd.h> +#endif + +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif + +#ifdef HAVE_WINDOWS_H +#include <windows.h> +#endif + +#ifdef HAVE_SYS_PRCTL_H +#include <sys/prctl.h> +#endif + +#ifdef OS_IS_DARWIN +#include <libgen.h> +#include <sys/sysctl.h> +#endif + +#include <pulse/xmalloc.h> +#include <pulse/timeval.h> + +#include <pulsecore/socket.h> +#include <pulsecore/core-error.h> +#include <pulsecore/core-util.h> +#include <pulsecore/macro.h> +#include <pulsecore/usergroup.h> + +#include "util.h" + +#if defined(HAVE_DLADDR) && defined(PA_GCC_WEAKREF) +#ifndef _GNU_SOURCE +#define _GNU_SOURCE 1 +#endif +#include <dlfcn.h> + +static int _main() PA_GCC_WEAKREF(main); +#endif + +#ifdef HAVE_PTHREAD +#include <pthread.h> +#endif + +#ifdef HAVE_SCHED_H +#include <sched.h> + +#if defined(__linux__) && !defined(SCHED_RESET_ON_FORK) +#define SCHED_RESET_ON_FORK 0x40000000 +#endif +#endif + +#ifdef __APPLE__ +#include <mach/mach_init.h> +#include <mach/thread_act.h> +#include <mach/thread_policy.h> +#include <sys/sysctl.h> +#endif + +#ifdef HAVE_DBUS +#include <pulsecore/rtkit.h> +#endif + +char *pa_get_user_name(char *s, size_t l) { + const char *p; + char *name = NULL; +#ifdef OS_IS_WIN32 + char buf[1024]; +#endif + +#ifdef HAVE_PWD_H + struct passwd *r; +#endif + + pa_assert(s); + pa_assert(l > 0); + + p = NULL; +#ifdef HAVE_GETUID + p = getuid() == 0 ? "root" : NULL; +#endif + if (!p) p = getenv("USER"); + if (!p) p = getenv("LOGNAME"); + if (!p) p = getenv("USERNAME"); + + if (p) { + name = pa_strlcpy(s, p, l); + } else { +#ifdef HAVE_PWD_H + + if ((r = pa_getpwuid_malloc(getuid())) == NULL) { + pa_snprintf(s, l, "%lu", (unsigned long) getuid()); + return s; + } + + name = pa_strlcpy(s, r->pw_name, l); + pa_getpwuid_free(r); + +#elif defined(OS_IS_WIN32) /* HAVE_PWD_H */ + DWORD size = sizeof(buf); + + if (!GetUserName(buf, &size)) { + errno = ENOENT; + return NULL; + } + + name = pa_strlcpy(s, buf, l); + +#else /* HAVE_PWD_H */ + + return NULL; +#endif /* HAVE_PWD_H */ + } + + return name; +} + +char *pa_get_host_name(char *s, size_t l) { + + pa_assert(s); + pa_assert(l > 0); + + if (gethostname(s, l) < 0) + return NULL; + + s[l-1] = 0; + return s; +} + +char *pa_get_home_dir(char *s, size_t l) { + char *e; + char *dir; +#ifdef HAVE_PWD_H + struct passwd *r; +#endif + + pa_assert(s); + pa_assert(l > 0); + + if ((e = getenv("HOME"))) { + dir = pa_strlcpy(s, e, l); + goto finish; + } + + if ((e = getenv("USERPROFILE"))) { + dir = pa_strlcpy(s, e, l); + goto finish; + } + +#ifdef HAVE_PWD_H + errno = 0; + if ((r = pa_getpwuid_malloc(getuid())) == NULL) { + if (!errno) + errno = ENOENT; + + return NULL; + } + + dir = pa_strlcpy(s, r->pw_dir, l); + + pa_getpwuid_free(r); +#endif /* HAVE_PWD_H */ + +finish: + if (!dir) { + errno = ENOENT; + return NULL; + } + + if (!pa_is_path_absolute(dir)) { + pa_log("Failed to get the home directory, not an absolute path: %s", dir); + errno = ENOENT; + return NULL; + } + + return dir; +} + +char *pa_get_binary_name(char *s, size_t l) { + + pa_assert(s); + pa_assert(l > 0); + +#if defined(OS_IS_WIN32) + { + char path[PATH_MAX]; + + if (GetModuleFileName(NULL, path, PATH_MAX)) + return pa_strlcpy(s, pa_path_get_filename(path), l); + } +#endif + +#if defined(__linux__) || defined(__FreeBSD_kernel__) + { + char *rp; + /* This works on Linux and Debian/kFreeBSD */ + + if ((rp = pa_readlink("/proc/self/exe"))) { + pa_strlcpy(s, pa_path_get_filename(rp), l); + pa_xfree(rp); + return s; + } + } +#endif + +#ifdef __FreeBSD__ + { + char *rp; + + if ((rp = pa_readlink("/proc/curproc/file"))) { + pa_strlcpy(s, pa_path_get_filename(rp), l); + pa_xfree(rp); + return s; + } + } +#endif + +#if defined(HAVE_DLADDR) && defined(PA_GCC_WEAKREF) + { + Dl_info info; + if(_main) { + int err = dladdr(&_main, &info); + if (err != 0) { + char *p = pa_realpath(info.dli_fname); + if (p) + return p; + } + } + } +#endif + +#if defined(HAVE_SYS_PRCTL_H) && defined(PR_GET_NAME) + { + + #ifndef TASK_COMM_LEN + /* Actually defined in linux/sched.h */ + #define TASK_COMM_LEN 16 + #endif + + char tcomm[TASK_COMM_LEN+1]; + memset(tcomm, 0, sizeof(tcomm)); + + /* This works on Linux only */ + if (prctl(PR_GET_NAME, (unsigned long) tcomm, 0, 0, 0) == 0) + return pa_strlcpy(s, tcomm, l); + + } +#endif + +#ifdef OS_IS_DARWIN + { + int mib[] = { CTL_KERN, KERN_PROCARGS, getpid(), 0 }; + size_t len, nmib = (sizeof(mib) / sizeof(mib[0])) - 1; + char *buf; + + sysctl(mib, nmib, NULL, &len, NULL, 0); + buf = (char *) pa_xmalloc(len); + + if (sysctl(mib, nmib, buf, &len, NULL, 0) == 0) { + pa_strlcpy(s, basename(buf), l); + pa_xfree(buf); + return s; + } + + pa_xfree(buf); + + /* fall thru */ + } +#endif /* OS_IS_DARWIN */ + + errno = ENOENT; + return NULL; +} + +char *pa_path_get_filename(const char *p) { + char *fn; + + if (!p) + return NULL; + + if ((fn = strrchr(p, PA_PATH_SEP_CHAR))) + return fn+1; + + return (char*) p; +} + +char *pa_get_fqdn(char *s, size_t l) { + char hn[256]; +#ifdef HAVE_GETADDRINFO + struct addrinfo *a = NULL, hints; +#endif + + pa_assert(s); + pa_assert(l > 0); + + if (!pa_get_host_name(hn, sizeof(hn))) + return NULL; + +#ifdef HAVE_GETADDRINFO + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_flags = AI_CANONNAME; + + if (getaddrinfo(hn, NULL, &hints, &a)) + return pa_strlcpy(s, hn, l); + + if (!a->ai_canonname || !*a->ai_canonname) { + freeaddrinfo(a); + return pa_strlcpy(s, hn, l); + } + + pa_strlcpy(s, a->ai_canonname, l); + freeaddrinfo(a); + return s; +#else + return pa_strlcpy(s, hn, l); +#endif +} + +int pa_msleep(unsigned long t) { +#ifdef OS_IS_WIN32 + Sleep(t); + return 0; +#elif defined(HAVE_NANOSLEEP) + struct timespec ts; + + ts.tv_sec = (time_t) (t / PA_MSEC_PER_SEC); + ts.tv_nsec = (long) ((t % PA_MSEC_PER_SEC) * PA_NSEC_PER_MSEC); + + return nanosleep(&ts, NULL); +#else +#error "Platform lacks a sleep function." +#endif +} + +#ifdef _POSIX_PRIORITY_SCHEDULING +static int set_scheduler(int rtprio) { +#ifdef HAVE_SCHED_H + struct sched_param sp; +#ifdef HAVE_DBUS + int r; + long long rttime; +#ifdef RLIMIT_RTTIME + struct rlimit rl; +#endif + DBusError error; + DBusConnection *bus; + + dbus_error_init(&error); +#endif + + pa_zero(sp); + sp.sched_priority = rtprio; + +#ifdef SCHED_RESET_ON_FORK + if (pthread_setschedparam(pthread_self(), SCHED_RR|SCHED_RESET_ON_FORK, &sp) == 0) { + pa_log_debug("SCHED_RR|SCHED_RESET_ON_FORK worked."); + return 0; + } +#endif + + if (pthread_setschedparam(pthread_self(), SCHED_RR, &sp) == 0) { + pa_log_debug("SCHED_RR worked."); + return 0; + } +#endif /* HAVE_SCHED_H */ + +#ifdef HAVE_DBUS + /* Try to talk to RealtimeKit */ + + if (!(bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error))) { + pa_log("Failed to connect to system bus: %s", error.message); + dbus_error_free(&error); + errno = -EIO; + return -1; + } + + /* We need to disable exit on disconnect because otherwise + * dbus_shutdown will kill us. See + * https://bugs.freedesktop.org/show_bug.cgi?id=16924 */ + dbus_connection_set_exit_on_disconnect(bus, FALSE); + + rttime = rtkit_get_rttime_usec_max(bus); + if (rttime >= 0) { +#ifdef RLIMIT_RTTIME + r = getrlimit(RLIMIT_RTTIME, &rl); + + if (r >= 0 && (long long) rl.rlim_max > rttime) { + pa_log_info("Clamping rlimit-rttime to %lld for RealtimeKit", rttime); + rl.rlim_cur = rl.rlim_max = rttime; + r = setrlimit(RLIMIT_RTTIME, &rl); + + if (r < 0) + pa_log("setrlimit() failed: %s", pa_cstrerror(errno)); + } +#endif + r = rtkit_make_realtime(bus, 0, rtprio); + dbus_connection_close(bus); + dbus_connection_unref(bus); + + if (r >= 0) { + pa_log_debug("RealtimeKit worked."); + return 0; + } + + errno = -r; + } else { + dbus_connection_close(bus); + dbus_connection_unref(bus); + errno = -rttime; + } + +#else + errno = 0; +#endif + + return -1; +} +#endif + +/* Make the current thread a realtime thread, and acquire the highest + * rtprio we can get that is less or equal the specified parameter. If + * the thread is already realtime, don't do anything. */ +int pa_thread_make_realtime(int rtprio) { + +#if defined(OS_IS_DARWIN) + struct thread_time_constraint_policy ttcpolicy; + uint64_t freq = 0; + size_t size = sizeof(freq); + int ret; + + ret = sysctlbyname("hw.cpufrequency", &freq, &size, NULL, 0); + if (ret < 0) { + pa_log_info("Unable to read CPU frequency, acquisition of real-time scheduling failed."); + return -1; + } + + pa_log_debug("sysctl for hw.cpufrequency: %llu", freq); + + /* See http://developer.apple.com/library/mac/#documentation/Darwin/Conceptual/KernelProgramming/scheduler/scheduler.html */ + ttcpolicy.period = freq / 160; + ttcpolicy.computation = freq / 3300; + ttcpolicy.constraint = freq / 2200; + ttcpolicy.preemptible = 1; + + ret = thread_policy_set(mach_thread_self(), + THREAD_TIME_CONSTRAINT_POLICY, + (thread_policy_t) &ttcpolicy, + THREAD_TIME_CONSTRAINT_POLICY_COUNT); + if (ret) { + pa_log_info("Unable to set real-time thread priority (%08x).", ret); + return -1; + } + + pa_log_info("Successfully acquired real-time thread priority."); + return 0; + +#elif defined(_POSIX_PRIORITY_SCHEDULING) + int p; + + if (set_scheduler(rtprio) >= 0) { + pa_log_info("Successfully enabled SCHED_RR scheduling for thread, with priority %i.", rtprio); + return 0; + } + + for (p = rtprio-1; p >= 1; p--) + if (set_scheduler(p) >= 0) { + pa_log_info("Successfully enabled SCHED_RR scheduling for thread, with priority %i, which is lower than the requested %i.", p, rtprio); + return 0; + } +#elif defined(OS_IS_WIN32) + /* Windows only allows realtime scheduling to be set on a per process basis. + * Therefore, instead of making the thread realtime, just give it the highest non-realtime priority. */ + if (SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL)) { + pa_log_info("Successfully enabled THREAD_PRIORITY_TIME_CRITICAL scheduling for thread."); + return 0; + } + + pa_log_warn("SetThreadPriority() failed: 0x%08X", GetLastError()); + errno = EPERM; +#else + errno = ENOTSUP; +#endif + pa_log_info("Failed to acquire real-time scheduling: %s", pa_cstrerror(errno)); + return -1; +} diff --git a/src/pulse/util.h b/src/pulse/util.h new file mode 100644 index 0000000..0717a73 --- /dev/null +++ b/src/pulse/util.h @@ -0,0 +1,65 @@ +#ifndef fooutilhfoo +#define fooutilhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <stddef.h> + +#include <pulse/cdecl.h> +#include <pulse/version.h> + +/** \file + * Assorted utility functions */ + +PA_C_DECL_BEGIN + +/** Return the current username in the specified string buffer. */ +char *pa_get_user_name(char *s, size_t l); + +/** Return the current hostname in the specified buffer. */ +char *pa_get_host_name(char *s, size_t l); + +/** Return the fully qualified domain name in s */ +char *pa_get_fqdn(char *s, size_t l); + +/** Return the home directory of the current user */ +char *pa_get_home_dir(char *s, size_t l); + +/** Return the binary file name of the current process. This is not + * supported on all architectures, in which case NULL is returned. */ +char *pa_get_binary_name(char *s, size_t l); + +/** Return a pointer to the filename inside a path (which is the last + * component). If passed NULL will return NULL. */ +char *pa_path_get_filename(const char *p); + +/** Wait t milliseconds */ +int pa_msleep(unsigned long t); + +/** Make the calling thread realtime if we can. On Linux, this uses RealTimeKit + * if available and POSIX APIs otherwise (the latter applies to other UNIX + * variants as well). This is also implemented for macOS and Windows. + * \since 13.0 */ +int pa_thread_make_realtime(int rtprio); + +PA_C_DECL_END + +#endif diff --git a/src/pulse/version.h b/src/pulse/version.h new file mode 100644 index 0000000..48ef3b2 --- /dev/null +++ b/src/pulse/version.h @@ -0,0 +1,70 @@ +#ifndef fooversionhfoo /*-*-C-*-*/ +#define fooversionhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +/* WARNING: Make sure to edit the real source file version.h.in! */ + +#include <pulse/cdecl.h> + +/** \file + * Define header version */ + +PA_C_DECL_BEGIN + +/** Return the version of the header files. Keep in mind that this is +a macro and not a function, so it is impossible to get the pointer of +it. */ +#define pa_get_headers_version() ("14.2.0") + +/** Return the version of the library the current application is + * linked to. */ +const char* pa_get_library_version(void); + +/** The current API version. Version 6 relates to Polypaudio + * 0.6. Prior versions (i.e. Polypaudio 0.5.1 and older) have + * PA_API_VERSION undefined. Please note that this is only ever + * increased on incompatible API changes! */ +#define PA_API_VERSION 12 + +/** The current protocol version. Version 8 relates to Polypaudio + * 0.8/PulseAudio 0.9. */ +#define PA_PROTOCOL_VERSION 34 + +/** The major version of PA. \since 0.9.15 */ +#define PA_MAJOR 14 + +/** The minor version of PA. \since 0.9.15 */ +#define PA_MINOR 2 + +/** The micro version of PA (will always be 0 from v1.0 onwards). \since 0.9.15 */ +#define PA_MICRO 0 + +/** Evaluates to TRUE if the PulseAudio library version is equal or + * newer than the specified. \since 0.9.16 */ +#define PA_CHECK_VERSION(major,minor,micro) \ + ((PA_MAJOR > (major)) || \ + (PA_MAJOR == (major) && PA_MINOR > (minor)) || \ + (PA_MAJOR == (major) && PA_MINOR == (minor) && PA_MICRO >= (micro))) + +PA_C_DECL_END + +#endif diff --git a/src/pulse/version.h.in b/src/pulse/version.h.in new file mode 100644 index 0000000..e8c1e6d --- /dev/null +++ b/src/pulse/version.h.in @@ -0,0 +1,70 @@ +#ifndef fooversionhfoo /*-*-C-*-*/ +#define fooversionhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +/* WARNING: Make sure to edit the real source file version.h.in! */ + +#include <pulse/cdecl.h> + +/** \file + * Define header version */ + +PA_C_DECL_BEGIN + +/** Return the version of the header files. Keep in mind that this is +a macro and not a function, so it is impossible to get the pointer of +it. */ +#define pa_get_headers_version() ("@PA_MAJOR@.@PA_MINOR@.0") + +/** Return the version of the library the current application is + * linked to. */ +const char* pa_get_library_version(void); + +/** The current API version. Version 6 relates to Polypaudio + * 0.6. Prior versions (i.e. Polypaudio 0.5.1 and older) have + * PA_API_VERSION undefined. Please note that this is only ever + * increased on incompatible API changes! */ +#define PA_API_VERSION @PA_API_VERSION@ + +/** The current protocol version. Version 8 relates to Polypaudio + * 0.8/PulseAudio 0.9. */ +#define PA_PROTOCOL_VERSION @PA_PROTOCOL_VERSION@ + +/** The major version of PA. \since 0.9.15 */ +#define PA_MAJOR @PA_MAJOR@ + +/** The minor version of PA. \since 0.9.15 */ +#define PA_MINOR @PA_MINOR@ + +/** The micro version of PA (will always be 0 from v1.0 onwards). \since 0.9.15 */ +#define PA_MICRO 0 + +/** Evaluates to TRUE if the PulseAudio library version is equal or + * newer than the specified. \since 0.9.16 */ +#define PA_CHECK_VERSION(major,minor,micro) \ + ((PA_MAJOR > (major)) || \ + (PA_MAJOR == (major) && PA_MINOR > (minor)) || \ + (PA_MAJOR == (major) && PA_MINOR == (minor) && PA_MICRO >= (micro))) + +PA_C_DECL_END + +#endif diff --git a/src/pulse/volume.c b/src/pulse/volume.c new file mode 100644 index 0000000..fc6ac8d --- /dev/null +++ b/src/pulse/volume.c @@ -0,0 +1,991 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <string.h> +#include <math.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/i18n.h> +#include <pulsecore/macro.h> +#include <pulsecore/sample-util.h> + +#include "volume.h" + +int pa_cvolume_equal(const pa_cvolume *a, const pa_cvolume *b) { + int i; + pa_assert(a); + pa_assert(b); + + pa_return_val_if_fail(pa_cvolume_valid(a), 0); + + if (PA_UNLIKELY(a == b)) + return 1; + + pa_return_val_if_fail(pa_cvolume_valid(b), 0); + + if (a->channels != b->channels) + return 0; + + for (i = 0; i < a->channels; i++) + if (a->values[i] != b->values[i]) + return 0; + + return 1; +} + +pa_cvolume* pa_cvolume_init(pa_cvolume *a) { + unsigned c; + + pa_assert(a); + + a->channels = 0; + + for (c = 0; c < PA_CHANNELS_MAX; c++) + a->values[c] = PA_VOLUME_INVALID; + + return a; +} + +pa_cvolume* pa_cvolume_set(pa_cvolume *a, unsigned channels, pa_volume_t v) { + int i; + + pa_assert(a); + pa_assert(pa_channels_valid(channels)); + + a->channels = (uint8_t) channels; + + for (i = 0; i < a->channels; i++) + /* Clamp in case there is stale data that exceeds the current + * PA_VOLUME_MAX */ + a->values[i] = PA_CLAMP_VOLUME(v); + + return a; +} + +pa_volume_t pa_cvolume_avg(const pa_cvolume *a) { + uint64_t sum = 0; + unsigned c; + + pa_assert(a); + pa_return_val_if_fail(pa_cvolume_valid(a), PA_VOLUME_MUTED); + + for (c = 0; c < a->channels; c++) + sum += a->values[c]; + + sum /= a->channels; + + return (pa_volume_t) sum; +} + +pa_volume_t pa_cvolume_avg_mask(const pa_cvolume *a, const pa_channel_map *cm, pa_channel_position_mask_t mask) { + uint64_t sum = 0; + unsigned c, n; + + pa_assert(a); + + if (!cm) + return pa_cvolume_avg(a); + + pa_return_val_if_fail(pa_cvolume_compatible_with_channel_map(a, cm), PA_VOLUME_MUTED); + + for (c = n = 0; c < a->channels; c++) { + + if (!(PA_CHANNEL_POSITION_MASK(cm->map[c]) & mask)) + continue; + + sum += a->values[c]; + n ++; + } + + if (n > 0) + sum /= n; + + return (pa_volume_t) sum; +} + +pa_volume_t pa_cvolume_max(const pa_cvolume *a) { + pa_volume_t m = PA_VOLUME_MUTED; + unsigned c; + + pa_assert(a); + pa_return_val_if_fail(pa_cvolume_valid(a), PA_VOLUME_MUTED); + + for (c = 0; c < a->channels; c++) + if (a->values[c] > m) + m = a->values[c]; + + return m; +} + +pa_volume_t pa_cvolume_min(const pa_cvolume *a) { + pa_volume_t m = PA_VOLUME_MAX; + unsigned c; + + pa_assert(a); + pa_return_val_if_fail(pa_cvolume_valid(a), PA_VOLUME_MUTED); + + for (c = 0; c < a->channels; c++) + if (a->values[c] < m) + m = a->values[c]; + + return m; +} + +pa_volume_t pa_cvolume_max_mask(const pa_cvolume *a, const pa_channel_map *cm, pa_channel_position_mask_t mask) { + pa_volume_t m = PA_VOLUME_MUTED; + unsigned c; + + pa_assert(a); + + if (!cm) + return pa_cvolume_max(a); + + pa_return_val_if_fail(pa_cvolume_compatible_with_channel_map(a, cm), PA_VOLUME_MUTED); + + for (c = 0; c < a->channels; c++) { + + if (!(PA_CHANNEL_POSITION_MASK(cm->map[c]) & mask)) + continue; + + if (a->values[c] > m) + m = a->values[c]; + } + + return m; +} + +pa_volume_t pa_cvolume_min_mask(const pa_cvolume *a, const pa_channel_map *cm, pa_channel_position_mask_t mask) { + pa_volume_t m = PA_VOLUME_MAX; + unsigned c; + + pa_assert(a); + + if (!cm) + return pa_cvolume_min(a); + + pa_return_val_if_fail(pa_cvolume_compatible_with_channel_map(a, cm), PA_VOLUME_MUTED); + + for (c = 0; c < a->channels; c++) { + + if (!(PA_CHANNEL_POSITION_MASK(cm->map[c]) & mask)) + continue; + + if (a->values[c] < m) + m = a->values[c]; + } + + return m; +} + +pa_volume_t pa_sw_volume_multiply(pa_volume_t a, pa_volume_t b) { + uint64_t result; + + pa_return_val_if_fail(PA_VOLUME_IS_VALID(a), PA_VOLUME_INVALID); + pa_return_val_if_fail(PA_VOLUME_IS_VALID(b), PA_VOLUME_INVALID); + + /* cbrt((a/PA_VOLUME_NORM)^3*(b/PA_VOLUME_NORM)^3)*PA_VOLUME_NORM = a*b/PA_VOLUME_NORM */ + + result = ((uint64_t) a * (uint64_t) b + (uint64_t) PA_VOLUME_NORM / 2ULL) / (uint64_t) PA_VOLUME_NORM; + + if (result > (uint64_t)PA_VOLUME_MAX) + pa_log_warn("pa_sw_volume_multiply: Volume exceeds maximum allowed value and will be clipped. Please check your volume settings."); + + return (pa_volume_t) PA_CLAMP_VOLUME(result); +} + +pa_volume_t pa_sw_volume_divide(pa_volume_t a, pa_volume_t b) { + uint64_t result; + + pa_return_val_if_fail(PA_VOLUME_IS_VALID(a), PA_VOLUME_INVALID); + pa_return_val_if_fail(PA_VOLUME_IS_VALID(b), PA_VOLUME_INVALID); + + if (b <= PA_VOLUME_MUTED) + return 0; + + result = ((uint64_t) a * (uint64_t) PA_VOLUME_NORM + (uint64_t) b / 2ULL) / (uint64_t) b; + + if (result > (uint64_t)PA_VOLUME_MAX) + pa_log_warn("pa_sw_volume_divide: Volume exceeds maximum allowed value and will be clipped. Please check your volume settings."); + + return (pa_volume_t) PA_CLAMP_VOLUME(result); +} + +/* Amplitude, not power */ +static double linear_to_dB(double v) { + return 20.0 * log10(v); +} + +static double dB_to_linear(double v) { + return pow(10.0, v / 20.0); +} + +pa_volume_t pa_sw_volume_from_dB(double dB) { + if (isinf(dB) < 0 || dB <= PA_DECIBEL_MININFTY) + return PA_VOLUME_MUTED; + + return pa_sw_volume_from_linear(dB_to_linear(dB)); +} + +double pa_sw_volume_to_dB(pa_volume_t v) { + + pa_return_val_if_fail(PA_VOLUME_IS_VALID(v), PA_DECIBEL_MININFTY); + + if (v <= PA_VOLUME_MUTED) + return PA_DECIBEL_MININFTY; + + return linear_to_dB(pa_sw_volume_to_linear(v)); +} + +pa_volume_t pa_sw_volume_from_linear(double v) { + + if (v <= 0.0) + return PA_VOLUME_MUTED; + + /* + * We use a cubic mapping here, as suggested and discussed here: + * + * http://www.robotplanet.dk/audio/audio_gui_design/ + * http://lists.linuxaudio.org/pipermail/linux-audio-dev/2009-May/thread.html#23151 + * + * We make sure that the conversion to linear and back yields the + * same volume value! That's why we need the lround() below! + */ + + return (pa_volume_t) PA_CLAMP_VOLUME((uint64_t) lround(cbrt(v) * PA_VOLUME_NORM)); +} + +double pa_sw_volume_to_linear(pa_volume_t v) { + double f; + + pa_return_val_if_fail(PA_VOLUME_IS_VALID(v), 0.0); + + if (v <= PA_VOLUME_MUTED) + return 0.0; + + if (v == PA_VOLUME_NORM) + return 1.0; + + f = ((double) v / PA_VOLUME_NORM); + + return f*f*f; +} + +char *pa_cvolume_snprint(char *s, size_t l, const pa_cvolume *c) { + unsigned channel; + bool first = true; + char *e; + + pa_assert(s); + pa_assert(l > 0); + pa_assert(c); + + pa_init_i18n(); + + if (!pa_cvolume_valid(c)) { + pa_snprintf(s, l, _("(invalid)")); + return s; + } + + *(e = s) = 0; + + for (channel = 0; channel < c->channels && l > 1; channel++) { + l -= pa_snprintf(e, l, "%s%u: %3u%%", + first ? "" : " ", + channel, + (unsigned)(((uint64_t)c->values[channel] * 100 + (uint64_t)PA_VOLUME_NORM / 2) / (uint64_t)PA_VOLUME_NORM)); + + e = strchr(e, 0); + first = false; + } + + return s; +} + +char *pa_volume_snprint(char *s, size_t l, pa_volume_t v) { + pa_assert(s); + pa_assert(l > 0); + + pa_init_i18n(); + + if (!PA_VOLUME_IS_VALID(v)) { + pa_snprintf(s, l, _("(invalid)")); + return s; + } + + pa_snprintf(s, l, "%3u%%", (unsigned)(((uint64_t)v * 100 + (uint64_t)PA_VOLUME_NORM / 2) / (uint64_t)PA_VOLUME_NORM)); + return s; +} + +char *pa_sw_cvolume_snprint_dB(char *s, size_t l, const pa_cvolume *c) { + unsigned channel; + bool first = true; + char *e; + + pa_assert(s); + pa_assert(l > 0); + pa_assert(c); + + pa_init_i18n(); + + if (!pa_cvolume_valid(c)) { + pa_snprintf(s, l, _("(invalid)")); + return s; + } + + *(e = s) = 0; + + for (channel = 0; channel < c->channels && l > 1; channel++) { + double f = pa_sw_volume_to_dB(c->values[channel]); + + l -= pa_snprintf(e, l, "%s%u: %0.2f dB", + first ? "" : " ", + channel, + isinf(f) < 0 || f <= PA_DECIBEL_MININFTY ? -INFINITY : f); + + e = strchr(e, 0); + first = false; + } + + return s; +} + +char *pa_cvolume_snprint_verbose(char *s, size_t l, const pa_cvolume *c, const pa_channel_map *map, int print_dB) { + char *current = s; + bool first = true; + + pa_assert(s); + pa_assert(l > 0); + pa_assert(c); + + pa_init_i18n(); + + if (!pa_cvolume_valid(c)) { + pa_snprintf(s, l, _("(invalid)")); + return s; + } + + pa_assert(!map || (map->channels == c->channels)); + pa_assert(!map || pa_channel_map_valid(map)); + + current[0] = 0; + + for (unsigned channel = 0; channel < c->channels && l > 1; channel++) { + char channel_position[32]; + size_t bytes_printed; + char buf[PA_VOLUME_SNPRINT_VERBOSE_MAX]; + + if (map) + pa_snprintf(channel_position, sizeof(channel_position), "%s", pa_channel_position_to_string(map->map[channel])); + else + pa_snprintf(channel_position, sizeof(channel_position), "%u", channel); + + bytes_printed = pa_snprintf(current, l, "%s%s: %s", + first ? "" : ", ", + channel_position, + pa_volume_snprint_verbose(buf, sizeof(buf), c->values[channel], print_dB)); + l -= bytes_printed; + current += bytes_printed; + first = false; + } + + return s; +} + +char *pa_sw_volume_snprint_dB(char *s, size_t l, pa_volume_t v) { + double f; + + pa_assert(s); + pa_assert(l > 0); + + pa_init_i18n(); + + if (!PA_VOLUME_IS_VALID(v)) { + pa_snprintf(s, l, _("(invalid)")); + return s; + } + + f = pa_sw_volume_to_dB(v); + pa_snprintf(s, l, "%0.2f dB", isinf(f) < 0 || f <= PA_DECIBEL_MININFTY ? -INFINITY : f); + + return s; +} + +char *pa_volume_snprint_verbose(char *s, size_t l, pa_volume_t v, int print_dB) { + char dB[PA_SW_VOLUME_SNPRINT_DB_MAX]; + + pa_assert(s); + pa_assert(l > 0); + + pa_init_i18n(); + + if (!PA_VOLUME_IS_VALID(v)) { + pa_snprintf(s, l, _("(invalid)")); + return s; + } + + pa_snprintf(s, l, "%" PRIu32 " / %3u%%%s%s", + v, + (unsigned)(((uint64_t)v * 100 + (uint64_t)PA_VOLUME_NORM / 2) / (uint64_t)PA_VOLUME_NORM), + print_dB ? " / " : "", + print_dB ? pa_sw_volume_snprint_dB(dB, sizeof(dB), v) : ""); + + return s; +} + +int pa_cvolume_channels_equal_to(const pa_cvolume *a, pa_volume_t v) { + unsigned c; + pa_assert(a); + + pa_return_val_if_fail(pa_cvolume_valid(a), 0); + pa_return_val_if_fail(PA_VOLUME_IS_VALID(v), 0); + + for (c = 0; c < a->channels; c++) + if (a->values[c] != v) + return 0; + + return 1; +} + +pa_cvolume *pa_sw_cvolume_multiply(pa_cvolume *dest, const pa_cvolume *a, const pa_cvolume *b) { + unsigned i; + + pa_assert(dest); + pa_assert(a); + pa_assert(b); + + pa_return_val_if_fail(pa_cvolume_valid(a), NULL); + pa_return_val_if_fail(pa_cvolume_valid(b), NULL); + + dest->channels = PA_MIN(a->channels, b->channels); + + for (i = 0; i < dest->channels; i++) + dest->values[i] = pa_sw_volume_multiply(a->values[i], b->values[i]); + + return dest; +} + +pa_cvolume *pa_sw_cvolume_multiply_scalar(pa_cvolume *dest, const pa_cvolume *a, pa_volume_t b) { + unsigned i; + + pa_assert(dest); + pa_assert(a); + + pa_return_val_if_fail(pa_cvolume_valid(a), NULL); + pa_return_val_if_fail(PA_VOLUME_IS_VALID(b), NULL); + + for (i = 0; i < a->channels; i++) + dest->values[i] = pa_sw_volume_multiply(a->values[i], b); + + dest->channels = (uint8_t) i; + + return dest; +} + +pa_cvolume *pa_sw_cvolume_divide(pa_cvolume *dest, const pa_cvolume *a, const pa_cvolume *b) { + unsigned i; + + pa_assert(dest); + pa_assert(a); + pa_assert(b); + + pa_return_val_if_fail(pa_cvolume_valid(a), NULL); + pa_return_val_if_fail(pa_cvolume_valid(b), NULL); + + dest->channels = PA_MIN(a->channels, b->channels); + + for (i = 0; i < dest->channels; i++) + dest->values[i] = pa_sw_volume_divide(a->values[i], b->values[i]); + + return dest; +} + +pa_cvolume *pa_sw_cvolume_divide_scalar(pa_cvolume *dest, const pa_cvolume *a, pa_volume_t b) { + unsigned i; + + pa_assert(dest); + pa_assert(a); + + pa_return_val_if_fail(pa_cvolume_valid(a), NULL); + pa_return_val_if_fail(PA_VOLUME_IS_VALID(b), NULL); + + for (i = 0; i < a->channels; i++) + dest->values[i] = pa_sw_volume_divide(a->values[i], b); + + dest->channels = (uint8_t) i; + + return dest; +} + +int pa_cvolume_valid(const pa_cvolume *v) { + unsigned c; + + pa_assert(v); + + if (!pa_channels_valid(v->channels)) + return 0; + + for (c = 0; c < v->channels; c++) + if (!PA_VOLUME_IS_VALID(v->values[c])) + return 0; + + return 1; +} + +static bool on_left(pa_channel_position_t p) { + return !!(PA_CHANNEL_POSITION_MASK(p) & PA_CHANNEL_POSITION_MASK_LEFT); +} + +static bool on_right(pa_channel_position_t p) { + return !!(PA_CHANNEL_POSITION_MASK(p) & PA_CHANNEL_POSITION_MASK_RIGHT); +} + +static bool on_center(pa_channel_position_t p) { + return !!(PA_CHANNEL_POSITION_MASK(p) & PA_CHANNEL_POSITION_MASK_CENTER); +} + +static bool on_hfe(pa_channel_position_t p) { + return !!(PA_CHANNEL_POSITION_MASK(p) & PA_CHANNEL_POSITION_MASK_HFE); +} + +static bool on_lfe(pa_channel_position_t p) { + return !!(PA_CHANNEL_POSITION_MASK(p) & PA_CHANNEL_POSITION_MASK_LFE); +} + +static bool on_front(pa_channel_position_t p) { + return !!(PA_CHANNEL_POSITION_MASK(p) & PA_CHANNEL_POSITION_MASK_FRONT); +} + +static bool on_rear(pa_channel_position_t p) { + return !!(PA_CHANNEL_POSITION_MASK(p) & PA_CHANNEL_POSITION_MASK_REAR); +} + +pa_cvolume *pa_cvolume_remap(pa_cvolume *v, const pa_channel_map *from, const pa_channel_map *to) { + int a, b; + pa_cvolume result; + + pa_assert(v); + pa_assert(from); + pa_assert(to); + + pa_return_val_if_fail(pa_channel_map_valid(to), NULL); + pa_return_val_if_fail(pa_cvolume_compatible_with_channel_map(v, from), NULL); + + if (pa_channel_map_equal(from, to)) + return v; + + result.channels = to->channels; + + for (b = 0; b < to->channels; b++) { + pa_volume_t k = 0; + int n = 0; + + for (a = 0; a < from->channels; a++) + if (from->map[a] == to->map[b]) { + k += v->values[a]; + n ++; + } + + if (n <= 0) { + for (a = 0; a < from->channels; a++) + if ((on_left(from->map[a]) && on_left(to->map[b])) || + (on_right(from->map[a]) && on_right(to->map[b])) || + (on_center(from->map[a]) && on_center(to->map[b])) || + (on_lfe(from->map[a]) && on_lfe(to->map[b]))) { + + k += v->values[a]; + n ++; + } + } + + if (n <= 0) + k = pa_cvolume_avg(v); + else + k /= n; + + result.values[b] = k; + } + + *v = result; + return v; +} + +int pa_cvolume_compatible(const pa_cvolume *v, const pa_sample_spec *ss) { + + pa_assert(v); + pa_assert(ss); + + pa_return_val_if_fail(pa_cvolume_valid(v), 0); + pa_return_val_if_fail(pa_sample_spec_valid(ss), 0); + + return v->channels == ss->channels; +} + +int pa_cvolume_compatible_with_channel_map(const pa_cvolume *v, const pa_channel_map *cm) { + pa_assert(v); + pa_assert(cm); + + pa_return_val_if_fail(pa_cvolume_valid(v), 0); + pa_return_val_if_fail(pa_channel_map_valid(cm), 0); + + return v->channels == cm->channels; +} + +/* + * Returns the average volume of l and r, where l and r are two disjoint sets of channels + * (e g left and right, or front and rear). + */ +static void get_avg(const pa_channel_map *map, const pa_cvolume *v, pa_volume_t *l, pa_volume_t *r, + bool (*on_l)(pa_channel_position_t), bool (*on_r)(pa_channel_position_t)) { + int c; + pa_volume_t left = 0, right = 0; + unsigned n_left = 0, n_right = 0; + + pa_assert(v); + pa_assert(map); + pa_assert(map->channels == v->channels); + pa_assert(l); + pa_assert(r); + + for (c = 0; c < map->channels; c++) { + if (on_l(map->map[c])) { + left += v->values[c]; + n_left++; + } else if (on_r(map->map[c])) { + right += v->values[c]; + n_right++; + } + } + + if (n_left <= 0) + *l = PA_VOLUME_NORM; + else + *l = left / n_left; + + if (n_right <= 0) + *r = PA_VOLUME_NORM; + else + *r = right / n_right; +} + +float pa_cvolume_get_balance(const pa_cvolume *v, const pa_channel_map *map) { + pa_volume_t left, right; + + pa_assert(v); + pa_assert(map); + + pa_return_val_if_fail(pa_cvolume_compatible_with_channel_map(v, map), 0.0f); + + if (!pa_channel_map_can_balance(map)) + return 0.0f; + + get_avg(map, v, &left, &right, on_left, on_right); + + if (left == right) + return 0.0f; + + /* 1.0, 0.0 => -1.0 + 0.0, 1.0 => 1.0 + 0.0, 0.0 => 0.0 + 0.5, 0.5 => 0.0 + 1.0, 0.5 => -0.5 + 1.0, 0.25 => -0.75 + 0.75, 0.25 => -0.66 + 0.5, 0.25 => -0.5 */ + + if (left > right) + return -1.0f + ((float) right / (float) left); + else + return 1.0f - ((float) left / (float) right); +} + +static pa_cvolume* set_balance(pa_cvolume *v, const pa_channel_map *map, float new_balance, + bool (*on_l)(pa_channel_position_t), bool (*on_r)(pa_channel_position_t)) { + + pa_volume_t left, nleft, right, nright, m; + unsigned c; + + get_avg(map, v, &left, &right, on_l, on_r); + + m = PA_MAX(left, right); + + if (new_balance <= 0) { + nright = (new_balance + 1.0f) * m; + nleft = m; + } else { + nleft = (1.0f - new_balance) * m; + nright = m; + } + + for (c = 0; c < map->channels; c++) { + if (on_l(map->map[c])) { + if (left == 0) + v->values[c] = nleft; + else + v->values[c] = (pa_volume_t) PA_CLAMP_VOLUME(((uint64_t) v->values[c] * (uint64_t) nleft) / (uint64_t) left); + } else if (on_r(map->map[c])) { + if (right == 0) + v->values[c] = nright; + else + v->values[c] = (pa_volume_t) PA_CLAMP_VOLUME(((uint64_t) v->values[c] * (uint64_t) nright) / (uint64_t) right); + } + } + + return v; +} + + +pa_cvolume* pa_cvolume_set_balance(pa_cvolume *v, const pa_channel_map *map, float new_balance) { + pa_assert(map); + pa_assert(v); + + pa_return_val_if_fail(pa_cvolume_compatible_with_channel_map(v, map), NULL); + pa_return_val_if_fail(new_balance >= -1.0f, NULL); + pa_return_val_if_fail(new_balance <= 1.0f, NULL); + + if (!pa_channel_map_can_balance(map)) + return v; + + return set_balance(v, map, new_balance, on_left, on_right); +} + +pa_cvolume* pa_cvolume_scale(pa_cvolume *v, pa_volume_t max) { + unsigned c; + pa_volume_t t = 0; + + pa_assert(v); + + pa_return_val_if_fail(pa_cvolume_valid(v), NULL); + pa_return_val_if_fail(PA_VOLUME_IS_VALID(max), NULL); + + t = pa_cvolume_max(v); + + if (t <= PA_VOLUME_MUTED) + return pa_cvolume_set(v, v->channels, max); + + for (c = 0; c < v->channels; c++) + v->values[c] = (pa_volume_t) PA_CLAMP_VOLUME(((uint64_t) v->values[c] * (uint64_t) max) / (uint64_t) t); + + return v; +} + +pa_cvolume* pa_cvolume_scale_mask(pa_cvolume *v, pa_volume_t max, const pa_channel_map *cm, pa_channel_position_mask_t mask) { + unsigned c; + pa_volume_t t = 0; + + pa_assert(v); + + pa_return_val_if_fail(PA_VOLUME_IS_VALID(max), NULL); + + if (!cm) + return pa_cvolume_scale(v, max); + + pa_return_val_if_fail(pa_cvolume_compatible_with_channel_map(v, cm), NULL); + + t = pa_cvolume_max_mask(v, cm, mask); + + if (t <= PA_VOLUME_MUTED) + return pa_cvolume_set(v, v->channels, max); + + for (c = 0; c < v->channels; c++) + v->values[c] = (pa_volume_t) PA_CLAMP_VOLUME(((uint64_t) v->values[c] * (uint64_t) max) / (uint64_t) t); + + return v; +} + +float pa_cvolume_get_fade(const pa_cvolume *v, const pa_channel_map *map) { + pa_volume_t rear, front; + + pa_assert(v); + pa_assert(map); + + pa_return_val_if_fail(pa_cvolume_compatible_with_channel_map(v, map), 0.0f); + + if (!pa_channel_map_can_fade(map)) + return 0.0f; + + get_avg(map, v, &rear, &front, on_rear, on_front); + + if (front == rear) + return 0.0f; + + if (rear > front) + return -1.0f + ((float) front / (float) rear); + else + return 1.0f - ((float) rear / (float) front); +} + +pa_cvolume* pa_cvolume_set_fade(pa_cvolume *v, const pa_channel_map *map, float new_fade) { + pa_assert(map); + pa_assert(v); + + pa_return_val_if_fail(pa_cvolume_compatible_with_channel_map(v, map), NULL); + pa_return_val_if_fail(new_fade >= -1.0f, NULL); + pa_return_val_if_fail(new_fade <= 1.0f, NULL); + + if (!pa_channel_map_can_fade(map)) + return v; + + return set_balance(v, map, new_fade, on_rear, on_front); +} + +float pa_cvolume_get_lfe_balance(const pa_cvolume *v, const pa_channel_map *map) { + pa_volume_t hfe, lfe; + + pa_assert(v); + pa_assert(map); + + pa_return_val_if_fail(pa_cvolume_compatible_with_channel_map(v, map), 0.0f); + + if (!pa_channel_map_can_lfe_balance(map)) + return 0.0f; + + get_avg(map, v, &hfe, &lfe, on_hfe, on_lfe); + + if (hfe == lfe) + return 0.0f; + + if (hfe > lfe) + return -1.0f + ((float) lfe / (float) hfe); + else + return 1.0f - ((float) hfe / (float) lfe); +} + +pa_cvolume* pa_cvolume_set_lfe_balance(pa_cvolume *v, const pa_channel_map *map, float new_balance) { + pa_assert(map); + pa_assert(v); + + pa_return_val_if_fail(pa_cvolume_compatible_with_channel_map(v, map), NULL); + pa_return_val_if_fail(new_balance >= -1.0f, NULL); + pa_return_val_if_fail(new_balance <= 1.0f, NULL); + + if (!pa_channel_map_can_lfe_balance(map)) + return v; + + return set_balance(v, map, new_balance, on_hfe, on_lfe); +} + +pa_cvolume* pa_cvolume_set_position( + pa_cvolume *cv, + const pa_channel_map *map, + pa_channel_position_t t, + pa_volume_t v) { + + unsigned c; + bool good = false; + + pa_assert(cv); + pa_assert(map); + + pa_return_val_if_fail(pa_cvolume_compatible_with_channel_map(cv, map), NULL); + pa_return_val_if_fail(t < PA_CHANNEL_POSITION_MAX, NULL); + pa_return_val_if_fail(PA_VOLUME_IS_VALID(v), NULL); + + for (c = 0; c < map->channels; c++) + if (map->map[c] == t) { + cv->values[c] = v; + good = true; + } + + return good ? cv : NULL; +} + +pa_volume_t pa_cvolume_get_position( + const pa_cvolume *cv, + const pa_channel_map *map, + pa_channel_position_t t) { + + unsigned c; + pa_volume_t v = PA_VOLUME_MUTED; + + pa_assert(cv); + pa_assert(map); + + pa_return_val_if_fail(pa_cvolume_compatible_with_channel_map(cv, map), PA_VOLUME_MUTED); + pa_return_val_if_fail(t < PA_CHANNEL_POSITION_MAX, PA_VOLUME_MUTED); + + for (c = 0; c < map->channels; c++) + if (map->map[c] == t) + if (cv->values[c] > v) + v = cv->values[c]; + + return v; +} + +pa_cvolume* pa_cvolume_merge(pa_cvolume *dest, const pa_cvolume *a, const pa_cvolume *b) { + unsigned i; + + pa_assert(dest); + pa_assert(a); + pa_assert(b); + + pa_return_val_if_fail(pa_cvolume_valid(a), NULL); + pa_return_val_if_fail(pa_cvolume_valid(b), NULL); + + dest->channels = PA_MIN(a->channels, b->channels); + + for (i = 0; i < dest->channels; i++) + dest->values[i] = PA_MAX(a->values[i], b->values[i]); + + return dest; +} + +pa_cvolume* pa_cvolume_inc_clamp(pa_cvolume *v, pa_volume_t inc, pa_volume_t limit) { + pa_volume_t m; + + pa_assert(v); + + pa_return_val_if_fail(pa_cvolume_valid(v), NULL); + pa_return_val_if_fail(PA_VOLUME_IS_VALID(inc), NULL); + + m = pa_cvolume_max(v); + + if (m >= limit - inc) + m = limit; + else + m += inc; + + return pa_cvolume_scale(v, m); +} + +pa_cvolume* pa_cvolume_inc(pa_cvolume *v, pa_volume_t inc) { + return pa_cvolume_inc_clamp(v, inc, PA_VOLUME_MAX); +} + +pa_cvolume* pa_cvolume_dec(pa_cvolume *v, pa_volume_t dec) { + pa_volume_t m; + + pa_assert(v); + + pa_return_val_if_fail(pa_cvolume_valid(v), NULL); + pa_return_val_if_fail(PA_VOLUME_IS_VALID(dec), NULL); + + m = pa_cvolume_max(v); + + if (m <= PA_VOLUME_MUTED + dec) + m = PA_VOLUME_MUTED; + else + m -= dec; + + return pa_cvolume_scale(v, m); +} diff --git a/src/pulse/volume.h b/src/pulse/volume.h new file mode 100644 index 0000000..fe44b0b --- /dev/null +++ b/src/pulse/volume.h @@ -0,0 +1,442 @@ +#ifndef foovolumehfoo +#define foovolumehfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <inttypes.h> +#include <limits.h> + +#include <pulse/cdecl.h> +#include <pulse/gccmacro.h> +#include <pulse/sample.h> +#include <pulse/channelmap.h> +#include <pulse/version.h> + +/** \page volume Volume Control + * + * \section overv_sec Overview + * + * Sinks, sources, sink inputs, source outputs and samples can all have their + * own volumes. To deal with these, The PulseAudio library contains a number of + * functions that ease handling. + * + * The basic volume type in PulseAudio is the \ref pa_volume_t type. Most of + * the time, applications will use the aggregated pa_cvolume structure that + * can store the volume of all channels at once. + * + * Volumes commonly span between muted (0%), and normal (100%). It is possible + * to set volumes to higher than 100%, but clipping might occur. + * + * There is no single well-defined meaning attached to the 100% volume for a + * sink input. In fact, it depends on the server configuration. With flat + * volumes enabled, it means the maximum volume that the sound hardware is + * capable of, which is usually so high that you absolutely must not set sink + * input volume to 100% unless the the user explicitly requests that (note that + * usually you shouldn't set the volume anyway if the user doesn't explicitly + * request it, instead, let PulseAudio decide the volume for the sink input). + * With flat volumes disabled the sink input volume is relative to the sink + * volume, so 100% sink input volume means that the sink input is played at the + * current sink volume level. In this case 100% is often a good default volume + * for a sink input, although you still should let PulseAudio decide the + * default volume. It is possible to figure out whether flat volume mode is in + * effect for a given sink by calling pa_context_get_sink_info_by_name(). + * + * \section calc_sec Calculations + * + * The volumes in PulseAudio are cubic in nature and applications shouldn't + * perform calculations with them directly. Instead, they should be converted + * to and from either dB or a linear scale: + * + * \li dB - pa_sw_volume_from_dB() / pa_sw_volume_to_dB() + * \li Linear - pa_sw_volume_from_linear() / pa_sw_volume_to_linear() + * + * For simple multiplication, pa_sw_volume_multiply() and + * pa_sw_cvolume_multiply() can be used. + * + * It's often unknown what scale hardware volumes relate to. Don't use the + * above functions on sink and source volumes, unless the sink or source in + * question has the PA_SINK_DECIBEL_VOLUME or PA_SOURCE_DECIBEL_VOLUME flag + * set. The conversion functions are rarely needed anyway, most of the time + * it's sufficient to treat all volumes as opaque with a range from + * PA_VOLUME_MUTED (0%) to PA_VOLUME_NORM (100%). + * + * \section conv_sec Convenience Functions + * + * To handle the pa_cvolume structure, the PulseAudio library provides a + * number of convenience functions: + * + * \li pa_cvolume_valid() - Tests if a pa_cvolume structure is valid. + * \li pa_cvolume_equal() - Tests if two pa_cvolume structures are identical. + * \li pa_cvolume_channels_equal_to() - Tests if all channels of a pa_cvolume + * structure have a given volume. + * \li pa_cvolume_is_muted() - Tests if all channels of a pa_cvolume + * structure are muted. + * \li pa_cvolume_is_norm() - Tests if all channels of a pa_cvolume structure + * are at a normal volume. + * \li pa_cvolume_set() - Set the first n channels of a pa_cvolume structure to + * a certain volume. + * \li pa_cvolume_reset() - Set the first n channels of a pa_cvolume structure + * to a normal volume. + * \li pa_cvolume_mute() - Set the first n channels of a pa_cvolume structure + * to a muted volume. + * \li pa_cvolume_avg() - Return the average volume of all channels. + * \li pa_cvolume_snprint() - Pretty print a pa_cvolume structure. + */ + +/** \file + * Constants and routines for volume handling + * + * See also \subpage volume + */ + +PA_C_DECL_BEGIN + +/** Volume specification: + * PA_VOLUME_MUTED: silence; + * < PA_VOLUME_NORM: decreased volume; + * PA_VOLUME_NORM: normal volume; + * > PA_VOLUME_NORM: increased volume */ +typedef uint32_t pa_volume_t; + +/** Normal volume (100%, 0 dB) */ +#define PA_VOLUME_NORM ((pa_volume_t) 0x10000U) + +/** Muted (minimal valid) volume (0%, -inf dB) */ +#define PA_VOLUME_MUTED ((pa_volume_t) 0U) + +/** Maximum valid volume we can store. \since 0.9.15 */ +#define PA_VOLUME_MAX ((pa_volume_t) UINT32_MAX/2) + +/** Recommended maximum volume to show in user facing UIs. + * Note: UIs should deal gracefully with volumes greater than this value + * and not cause feedback loops etc. - i.e. if the volume is more than + * this, the UI should not limit it and push the limited value back to + * the server. \since 0.9.23 */ +#define PA_VOLUME_UI_MAX (pa_sw_volume_from_dB(+11.0)) + +/** Special 'invalid' volume. \since 0.9.16 */ +#define PA_VOLUME_INVALID ((pa_volume_t) UINT32_MAX) + +/** Check if volume is valid. \since 1.0 */ +#define PA_VOLUME_IS_VALID(v) ((v) <= PA_VOLUME_MAX) + +/** Clamp volume to the permitted range. \since 1.0 */ +#define PA_CLAMP_VOLUME(v) (PA_CLAMP_UNLIKELY((v), PA_VOLUME_MUTED, PA_VOLUME_MAX)) + +/** A structure encapsulating a per-channel volume */ +typedef struct pa_cvolume { + uint8_t channels; /**< Number of channels */ + pa_volume_t values[PA_CHANNELS_MAX]; /**< Per-channel volume */ +} pa_cvolume; + +/** Return non-zero when *a == *b, checking that both a and b + * have the same number of channels and that the volumes of + * channels in a equal those in b. */ +int pa_cvolume_equal(const pa_cvolume *a, const pa_cvolume *b) PA_GCC_PURE; + +/** Initialize the specified volume and return a pointer to + * it. The sample spec will have a defined state but + * pa_cvolume_valid() will fail for it. \since 0.9.13 */ +pa_cvolume* pa_cvolume_init(pa_cvolume *a); + +/** Set the volume of the first n channels to PA_VOLUME_NORM */ +#define pa_cvolume_reset(a, n) pa_cvolume_set((a), (n), PA_VOLUME_NORM) + +/** Set the volume of the first n channels to PA_VOLUME_MUTED */ +#define pa_cvolume_mute(a, n) pa_cvolume_set((a), (n), PA_VOLUME_MUTED) + +/** Set the volume of the specified number of channels to the volume v */ +pa_cvolume* pa_cvolume_set(pa_cvolume *a, unsigned channels, pa_volume_t v); + +/** Maximum length of the strings returned by + * pa_cvolume_snprint(). Please note that this value can change with + * any release without warning and without being considered API or ABI + * breakage. You should not use this definition anywhere where it + * might become part of an ABI.*/ +#define PA_CVOLUME_SNPRINT_MAX 320 + +/** Pretty print a volume structure. Returns \a s. */ +char *pa_cvolume_snprint(char *s, size_t l, const pa_cvolume *c); + +/** Maximum length of the strings returned by + * pa_sw_cvolume_snprint_dB(). Please note that this value can change with + * any release without warning and without being considered API or ABI + * breakage. You should not use this definition anywhere where it + * might become part of an ABI. \since 0.9.13 */ +#define PA_SW_CVOLUME_SNPRINT_DB_MAX 448 + +/** Pretty print a volume structure, showing dB values. Returns \a s. \since 0.9.13 */ +char *pa_sw_cvolume_snprint_dB(char *s, size_t l, const pa_cvolume *c); + +/** Maximum length of the strings returned by pa_cvolume_snprint_verbose(). + * Please note that this value can change with any release without warning and + * without being considered API or ABI breakage. You should not use this + * definition anywhere where it might become part of an ABI. \since 5.0 */ +#define PA_CVOLUME_SNPRINT_VERBOSE_MAX 1984 + +/** Pretty print a volume structure in a verbose way. The volume for each + * channel is printed in several formats: the raw pa_volume_t value, + * percentage, and if print_dB is non-zero, also the dB value. If map is not + * NULL, the channel names will be printed. Returns \a s. \since 5.0 */ +char *pa_cvolume_snprint_verbose(char *s, size_t l, const pa_cvolume *c, const pa_channel_map *map, int print_dB); + +/** Maximum length of the strings returned by + * pa_volume_snprint(). Please note that this value can change with + * any release without warning and without being considered API or ABI + * breakage. You should not use this definition anywhere where it + * might become part of an ABI. \since 0.9.15 */ +#define PA_VOLUME_SNPRINT_MAX 10 + +/** Pretty print a volume. Returns \a s. \since 0.9.15 */ +char *pa_volume_snprint(char *s, size_t l, pa_volume_t v); + +/** Maximum length of the strings returned by + * pa_sw_volume_snprint_dB(). Please note that this value can change with + * any release without warning and without being considered API or ABI + * breakage. You should not use this definition anywhere where it + * might become part of an ABI. \since 0.9.15 */ +#define PA_SW_VOLUME_SNPRINT_DB_MAX 11 + +/** Pretty print a volume but show dB values. Returns \a s. \since 0.9.15 */ +char *pa_sw_volume_snprint_dB(char *s, size_t l, pa_volume_t v); + +/** Maximum length of the strings returned by pa_volume_snprint_verbose(). + * Please note that this value can change with any release without warning and + * withou being considered API or ABI breakage. You should not use this + * definition anywhere where it might become part of an ABI. \since 5.0 */ +#define PA_VOLUME_SNPRINT_VERBOSE_MAX 35 + +/** Pretty print a volume in a verbose way. The volume is printed in several + * formats: the raw pa_volume_t value, percentage, and if print_dB is non-zero, + * also the dB value. Returns \a s. \since 5.0 */ +char *pa_volume_snprint_verbose(char *s, size_t l, pa_volume_t v, int print_dB); + +/** Return the average volume of all channels */ +pa_volume_t pa_cvolume_avg(const pa_cvolume *a) PA_GCC_PURE; + +/** Return the average volume of all channels that are included in the + * specified channel map with the specified channel position mask. If + * cm is NULL this call is identical to pa_cvolume_avg(). If no + * channel is selected the returned value will be + * PA_VOLUME_MUTED. \since 0.9.16 */ +pa_volume_t pa_cvolume_avg_mask(const pa_cvolume *a, const pa_channel_map *cm, pa_channel_position_mask_t mask) PA_GCC_PURE; + +/** Return the maximum volume of all channels. \since 0.9.12 */ +pa_volume_t pa_cvolume_max(const pa_cvolume *a) PA_GCC_PURE; + +/** Return the maximum volume of all channels that are included in the + * specified channel map with the specified channel position mask. If + * cm is NULL this call is identical to pa_cvolume_max(). If no + * channel is selected the returned value will be PA_VOLUME_MUTED. + * \since 0.9.16 */ +pa_volume_t pa_cvolume_max_mask(const pa_cvolume *a, const pa_channel_map *cm, pa_channel_position_mask_t mask) PA_GCC_PURE; + +/** Return the minimum volume of all channels. \since 0.9.16 */ +pa_volume_t pa_cvolume_min(const pa_cvolume *a) PA_GCC_PURE; + +/** Return the minimum volume of all channels that are included in the + * specified channel map with the specified channel position mask. If + * cm is NULL this call is identical to pa_cvolume_min(). If no + * channel is selected the returned value will be PA_VOLUME_MUTED. + * \since 0.9.16 */ +pa_volume_t pa_cvolume_min_mask(const pa_cvolume *a, const pa_channel_map *cm, pa_channel_position_mask_t mask) PA_GCC_PURE; + +/** Return non-zero when the passed cvolume structure is valid */ +int pa_cvolume_valid(const pa_cvolume *v) PA_GCC_PURE; + +/** Return non-zero if the volume of all channels is equal to the specified value */ +int pa_cvolume_channels_equal_to(const pa_cvolume *a, pa_volume_t v) PA_GCC_PURE; + +/** Return 1 if the specified volume has all channels muted */ +#define pa_cvolume_is_muted(a) pa_cvolume_channels_equal_to((a), PA_VOLUME_MUTED) + +/** Return 1 if the specified volume has all channels on normal level */ +#define pa_cvolume_is_norm(a) pa_cvolume_channels_equal_to((a), PA_VOLUME_NORM) + +/** Multiply two volume specifications, return the result. This uses + * PA_VOLUME_NORM as neutral element of multiplication. This is only + * valid for software volumes! */ +pa_volume_t pa_sw_volume_multiply(pa_volume_t a, pa_volume_t b) PA_GCC_CONST; + +/** Multiply two per-channel volumes and return the result in + * *dest. This is only valid for software volumes! a, b and dest may + * point to the same structure. Returns dest, or NULL on error. */ +pa_cvolume *pa_sw_cvolume_multiply(pa_cvolume *dest, const pa_cvolume *a, const pa_cvolume *b); + +/** Multiply a per-channel volume with a scalar volume and return the + * result in *dest. This is only valid for software volumes! a + * and dest may point to the same structure. Returns dest, or NULL on error. + * \since 0.9.16 */ +pa_cvolume *pa_sw_cvolume_multiply_scalar(pa_cvolume *dest, const pa_cvolume *a, pa_volume_t b); + +/** Divide two volume specifications, return the result. This uses + * PA_VOLUME_NORM as neutral element of division. This is only valid + * for software volumes! If a division by zero is tried the result + * will be 0. \since 0.9.13 */ +pa_volume_t pa_sw_volume_divide(pa_volume_t a, pa_volume_t b) PA_GCC_CONST; + +/** Divide two per-channel volumes and return the result in + * *dest. This is only valid for software volumes! a, b + * and dest may point to the same structure. Returns dest, + * or NULL on error. \since 0.9.13 */ +pa_cvolume *pa_sw_cvolume_divide(pa_cvolume *dest, const pa_cvolume *a, const pa_cvolume *b); + +/** Divide a per-channel volume by a scalar volume and return the + * result in *dest. This is only valid for software volumes! a + * and dest may point to the same structure. Returns dest, + * or NULL on error. \since 0.9.16 */ +pa_cvolume *pa_sw_cvolume_divide_scalar(pa_cvolume *dest, const pa_cvolume *a, pa_volume_t b); + +/** Convert a decibel value to a volume (amplitude, not power). This is only valid for software volumes! */ +pa_volume_t pa_sw_volume_from_dB(double f) PA_GCC_CONST; + +/** Convert a volume to a decibel value (amplitude, not power). This is only valid for software volumes! */ +double pa_sw_volume_to_dB(pa_volume_t v) PA_GCC_CONST; + +/** Convert a linear factor to a volume. 0.0 and less is muted while + * 1.0 is PA_VOLUME_NORM. This is only valid for software volumes! */ +pa_volume_t pa_sw_volume_from_linear(double v) PA_GCC_CONST; + +/** Convert a volume to a linear factor. This is only valid for software volumes! */ +double pa_sw_volume_to_linear(pa_volume_t v) PA_GCC_CONST; + +#ifdef INFINITY +#define PA_DECIBEL_MININFTY ((double) -INFINITY) +#else +/** This floor value is used as minus infinity when using pa_sw_volume_to_dB() / pa_sw_volume_from_dB(). */ +#define PA_DECIBEL_MININFTY ((double) -200.0) +#endif + +/** Remap a volume from one channel mapping to a different channel mapping. + * Returns \a v. \since 0.9.12 */ +pa_cvolume *pa_cvolume_remap(pa_cvolume *v, const pa_channel_map *from, const pa_channel_map *to); + +/** Return non-zero if the specified volume is compatible with the + * specified sample spec. \since 0.9.13 */ +int pa_cvolume_compatible(const pa_cvolume *v, const pa_sample_spec *ss) PA_GCC_PURE; + +/** Return non-zero if the specified volume is compatible with the + * specified sample spec. \since 0.9.15 */ +int pa_cvolume_compatible_with_channel_map(const pa_cvolume *v, const pa_channel_map *cm) PA_GCC_PURE; + +/** Calculate a 'balance' value for the specified volume with the + * specified channel map. The return value will range from -1.0f + * (left) to +1.0f (right). If no balance value is applicable to this + * channel map the return value will always be 0.0f. See + * pa_channel_map_can_balance(). \since 0.9.15 */ +float pa_cvolume_get_balance(const pa_cvolume *v, const pa_channel_map *map) PA_GCC_PURE; + +/** Adjust the 'balance' value for the specified volume with the + * specified channel map. v will be modified in place and + * returned. The balance is a value between -1.0f and +1.0f. This + * operation might not be reversible! Also, after this call + * pa_cvolume_get_balance() is not guaranteed to actually return the + * requested balance value (e.g. when the input volume was zero anyway for + * all channels). If no balance value is applicable to + * this channel map the volume will not be modified. See + * pa_channel_map_can_balance(). Will return NULL on error. \since 0.9.15 */ +pa_cvolume* pa_cvolume_set_balance(pa_cvolume *v, const pa_channel_map *map, float new_balance); + +/** Calculate a 'fade' value (i.e.\ 'balance' between front and rear) + * for the specified volume with the specified channel map. The return + * value will range from -1.0f (rear) to +1.0f (left). If no fade + * value is applicable to this channel map the return value will + * always be 0.0f. See pa_channel_map_can_fade(). \since 0.9.15 */ +float pa_cvolume_get_fade(const pa_cvolume *v, const pa_channel_map *map) PA_GCC_PURE; + +/** Adjust the 'fade' value (i.e.\ 'balance' between front and rear) + * for the specified volume with the specified channel map. v will be + * modified in place and returned. The balance is a value between + * -1.0f and +1.0f. This operation might not be reversible! Also, + * after this call pa_cvolume_get_fade() is not guaranteed to actually + * return the requested fade value (e.g. when the input volume was + * zero anyway for all channels). If no fade value is applicable to + * this channel map the volume will not be modified. See + * pa_channel_map_can_fade(). Will return NULL on error. \since 0.9.15 */ +pa_cvolume* pa_cvolume_set_fade(pa_cvolume *v, const pa_channel_map *map, float new_fade); + +/** Calculate a 'lfe balance' value for the specified volume with + * the specified channel map. The return value will range from + * -1.0f (no lfe) to +1.0f (only lfe), where 0.0f is balanced. + * If no value is applicable to this channel map the return value + * will always be 0.0f. See pa_channel_map_can_lfe_balance(). \since 8.0 */ +float pa_cvolume_get_lfe_balance(const pa_cvolume *v, const pa_channel_map *map) PA_GCC_PURE; + +/** Adjust the 'lfe balance' value for the specified volume with + * the specified channel map. v will be modified in place and returned. + * The balance is a value between -1.0f (no lfe) and +1.0f (only lfe). + * This operation might not be reversible! Also, after this call + * pa_cvolume_get_lfe_balance() is not guaranteed to actually + * return the requested value (e.g. when the input volume was + * zero anyway for all channels). If no lfe balance value is applicable to + * this channel map the volume will not be modified. See + * pa_channel_map_can_lfe_balance(). Will return NULL on error. \since 8.0 */ +pa_cvolume* pa_cvolume_set_lfe_balance(pa_cvolume *v, const pa_channel_map *map, float new_balance); + +/** Scale the passed pa_cvolume structure so that the maximum volume + * of all channels equals max. The proportions between the channel + * volumes are kept. Returns \a v, or NULL on error. \since 0.9.15 */ +pa_cvolume* pa_cvolume_scale(pa_cvolume *v, pa_volume_t max); + +/** Scale the passed pa_cvolume structure so that the maximum volume + * of all channels selected via cm/mask equals max. This also modifies + * the volume of those channels that are unmasked. The proportions + * between the channel volumes are kept. If cm is NULL this call is + * identical to pa_cvolume_scale(). Returns \a v, or NULL on error. + * \since 0.9.16 */ +pa_cvolume* pa_cvolume_scale_mask(pa_cvolume *v, pa_volume_t max, const pa_channel_map *cm, pa_channel_position_mask_t mask); + +/** Set the passed volume to all channels at the specified channel + * position. Will return the updated volume struct, or NULL if there + * is no channel at the position specified. You can check if a channel + * map includes a specific position by calling + * pa_channel_map_has_position(). Returns \a cv, or NULL on error. + * \since 0.9.16 */ +pa_cvolume* pa_cvolume_set_position(pa_cvolume *cv, const pa_channel_map *map, pa_channel_position_t t, pa_volume_t v); + +/** Get the maximum volume of all channels at the specified channel + * position. Will return 0 if there is no channel at the position + * specified. You can check if a channel map includes a specific + * position by calling pa_channel_map_has_position(). \since 0.9.16 */ +pa_volume_t pa_cvolume_get_position(const pa_cvolume *cv, const pa_channel_map *map, pa_channel_position_t t) PA_GCC_PURE; + +/** This goes through all channels in a and b and sets the + * corresponding channel in dest to the greater volume of both. a, b + * and dest may point to the same structure. Returns \a dest, or NULL + * on error. \since 0.9.16 */ +pa_cvolume* pa_cvolume_merge(pa_cvolume *dest, const pa_cvolume *a, const pa_cvolume *b); + +/** Increase the volume passed in by 'inc', but not exceeding 'limit'. + * The proportions between the channels are kept. + * Returns \a v, or NULL on error. \since 0.9.19 */ +pa_cvolume* pa_cvolume_inc_clamp(pa_cvolume *v, pa_volume_t inc, pa_volume_t limit); + +/** Increase the volume passed in by 'inc'. The proportions between + * the channels are kept. Returns \a v, or NULL on error. \since 0.9.16 */ +pa_cvolume* pa_cvolume_inc(pa_cvolume *v, pa_volume_t inc); + +/** Decrease the volume passed in by 'dec'. The proportions between + * the channels are kept. Returns \a v, or NULL on error. \since 0.9.16 */ +pa_cvolume* pa_cvolume_dec(pa_cvolume *v, pa_volume_t dec); + +PA_C_DECL_END + +#endif diff --git a/src/pulse/xmalloc.c b/src/pulse/xmalloc.c new file mode 100644 index 0000000..1a535b3 --- /dev/null +++ b/src/pulse/xmalloc.c @@ -0,0 +1,131 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <signal.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> + +#include <pulse/gccmacro.h> +#include <pulsecore/core-util.h> +#include <pulsecore/macro.h> + +#include "xmalloc.h" + +/* Make sure not to allocate more than this much memory. */ +#define MAX_ALLOC_SIZE (1024*1024*96) /* 96MB */ + +/* #undef malloc */ +/* #undef free */ +/* #undef realloc */ +/* #undef strndup */ +/* #undef strdup */ + +static void oom(void) PA_GCC_NORETURN; + +/* called in case of an OOM situation. Prints an error message and + * exits */ +static void oom(void) { + static const char e[] = "Not enough memory\n"; + pa_loop_write(STDERR_FILENO, e, sizeof(e)-1, NULL); +#ifdef SIGQUIT + raise(SIGQUIT); +#endif + _exit(1); +} + +void* pa_xmalloc(size_t size) { + void *p; + pa_assert(size > 0); + pa_assert(size < MAX_ALLOC_SIZE); + + if (!(p = malloc(size))) + oom(); + + return p; +} + +void* pa_xmalloc0(size_t size) { + void *p; + pa_assert(size > 0); + pa_assert(size < MAX_ALLOC_SIZE); + + if (!(p = calloc(1, size))) + oom(); + + return p; +} + +void *pa_xrealloc(void *ptr, size_t size) { + void *p; + pa_assert(size > 0); + pa_assert(size < MAX_ALLOC_SIZE); + + if (!(p = realloc(ptr, size))) + oom(); + return p; +} + +void* pa_xmemdup(const void *p, size_t l) { + if (!p) + return NULL; + else { + char *r = pa_xmalloc(l); + memcpy(r, p, l); + return r; + } +} + +char *pa_xstrdup(const char *s) { + if (!s) + return NULL; + + return pa_xmemdup(s, strlen(s)+1); +} + +char *pa_xstrndup(const char *s, size_t l) { + char *e, *r; + + if (!s) + return NULL; + + if ((e = memchr(s, 0, l))) + return pa_xmemdup(s, (size_t) (e-s+1)); + + r = pa_xmalloc(l+1); + memcpy(r, s, l); + r[l] = 0; + return r; +} + +void pa_xfree(void *p) { + int saved_errno; + + if (!p) + return; + + saved_errno = errno; + free(p); + errno = saved_errno; +} diff --git a/src/pulse/xmalloc.h b/src/pulse/xmalloc.h new file mode 100644 index 0000000..d1d6968 --- /dev/null +++ b/src/pulse/xmalloc.h @@ -0,0 +1,105 @@ +#ifndef foomemoryhfoo +#define foomemoryhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/types.h> +#include <stdlib.h> +#include <limits.h> +#include <assert.h> + +#include <pulse/cdecl.h> +#include <pulse/gccmacro.h> +#include <pulse/version.h> + +/** \file + * Memory allocation functions. + */ + +PA_C_DECL_BEGIN + +/** Allocate the specified number of bytes, just like malloc() does. However, in case of OOM, terminate */ +void* pa_xmalloc(size_t l) PA_GCC_MALLOC PA_GCC_ALLOC_SIZE(1); + +/** Same as pa_xmalloc(), but initialize allocated memory to 0 */ +void *pa_xmalloc0(size_t l) PA_GCC_MALLOC PA_GCC_ALLOC_SIZE(1); + +/** The combination of pa_xmalloc() and realloc() */ +void *pa_xrealloc(void *ptr, size_t size) PA_GCC_ALLOC_SIZE(2); + +/** Free allocated memory */ +void pa_xfree(void *p); + +/** Duplicate the specified string, allocating memory with pa_xmalloc() */ +char *pa_xstrdup(const char *s) PA_GCC_MALLOC; + +/** Duplicate the specified string, but truncate after l characters */ +char *pa_xstrndup(const char *s, size_t l) PA_GCC_MALLOC; + +/** Duplicate the specified memory block */ +void* pa_xmemdup(const void *p, size_t l) PA_GCC_MALLOC PA_GCC_ALLOC_SIZE(2); + +/** Internal helper for pa_xnew() */ +static void* _pa_xnew_internal(size_t n, size_t k) PA_GCC_MALLOC PA_GCC_ALLOC_SIZE2(1,2); + +static inline void* _pa_xnew_internal(size_t n, size_t k) { + assert(n < INT_MAX/k); + return pa_xmalloc(n*k); +} + +/** Allocate n new structures of the specified type. */ +#define pa_xnew(type, n) ((type*) _pa_xnew_internal((n), sizeof(type))) + +/** Internal helper for pa_xnew0() */ +static void* _pa_xnew0_internal(size_t n, size_t k) PA_GCC_MALLOC PA_GCC_ALLOC_SIZE2(1,2); + +static inline void* _pa_xnew0_internal(size_t n, size_t k) { + assert(n < INT_MAX/k); + return pa_xmalloc0(n*k); +} + +/** Same as pa_xnew() but set the memory to zero */ +#define pa_xnew0(type, n) ((type*) _pa_xnew0_internal((n), sizeof(type))) + +/** Internal helper for pa_xnew0() */ +static void* _pa_xnewdup_internal(const void *p, size_t n, size_t k) PA_GCC_MALLOC PA_GCC_ALLOC_SIZE2(2,3); + +static inline void* _pa_xnewdup_internal(const void *p, size_t n, size_t k) { + assert(n < INT_MAX/k); + return pa_xmemdup(p, n*k); +} + +/** Same as pa_xnew() but duplicate the specified data */ +#define pa_xnewdup(type, p, n) ((type*) _pa_xnewdup_internal((p), (n), sizeof(type))) + +/** Internal helper for pa_xrenew() */ +static void* _pa_xrenew_internal(void *p, size_t n, size_t k) PA_GCC_MALLOC PA_GCC_ALLOC_SIZE2(2,3); + +static inline void* _pa_xrenew_internal(void *p, size_t n, size_t k) { + assert(n < INT_MAX/k); + return pa_xrealloc(p, n*k); +} + +/** Reallocate n new structures of the specified type. */ +#define pa_xrenew(type, p, n) ((type*) _pa_xrenew_internal(p, (n), sizeof(type))) + +PA_C_DECL_END + +#endif |