summaryrefslogtreecommitdiffstats
path: root/src/pulsecore
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:03:18 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:03:18 +0000
commit2dd5bc6a074165ddfbd57c4bd52c2d2dac8f47a1 (patch)
tree465b29cb405d3af0b0ad50c78e1dccc636594fec /src/pulsecore
parentInitial commit. (diff)
downloadpulseaudio-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/pulsecore')
-rw-r--r--src/pulsecore/arpa-inet.c106
-rw-r--r--src/pulsecore/arpa-inet.h21
-rw-r--r--src/pulsecore/asyncmsgq.c358
-rw-r--r--src/pulsecore/asyncmsgq.h81
-rw-r--r--src/pulsecore/asyncq.c320
-rw-r--r--src/pulsecore/asyncq.h64
-rw-r--r--src/pulsecore/atomic.h683
-rw-r--r--src/pulsecore/aupdate.c135
-rw-r--r--src/pulsecore/aupdate.h100
-rw-r--r--src/pulsecore/auth-cookie.c139
-rw-r--r--src/pulsecore/auth-cookie.h34
-rw-r--r--src/pulsecore/authkey.c208
-rw-r--r--src/pulsecore/authkey.h29
-rw-r--r--src/pulsecore/avahi-wrap.c193
-rw-r--r--src/pulsecore/avahi-wrap.h30
-rw-r--r--src/pulsecore/bitset.c65
-rw-r--r--src/pulsecore/bitset.h35
-rw-r--r--src/pulsecore/card.c425
-rw-r--r--src/pulsecore/card.h147
-rw-r--r--src/pulsecore/cli-command.c2249
-rw-r--r--src/pulsecore/cli-command.h44
-rw-r--r--src/pulsecore/cli-text.c704
-rw-r--r--src/pulsecore/cli-text.h39
-rw-r--r--src/pulsecore/cli.c176
-rw-r--r--src/pulsecore/cli.h40
-rw-r--r--src/pulsecore/client.c168
-rw-r--r--src/pulsecore/client.h83
-rw-r--r--src/pulsecore/conf-parser.c394
-rw-r--r--src/pulsecore/conf-parser.h87
-rw-r--r--src/pulsecore/core-error.c78
-rw-r--r--src/pulsecore/core-error.h39
-rw-r--r--src/pulsecore/core-format.c164
-rw-r--r--src/pulsecore/core-format.h59
-rw-r--r--src/pulsecore/core-rtclock.c267
-rw-r--r--src/pulsecore/core-rtclock.h53
-rw-r--r--src/pulsecore/core-scache.c521
-rw-r--r--src/pulsecore/core-scache.h68
-rw-r--r--src/pulsecore/core-subscribe.c262
-rw-r--r--src/pulsecore/core-subscribe.h37
-rw-r--r--src/pulsecore/core-util.c3637
-rw-r--r--src/pulsecore/core-util.h323
-rw-r--r--src/pulsecore/core.c632
-rw-r--r--src/pulsecore/core.h287
-rw-r--r--src/pulsecore/cpu-arm.c170
-rw-r--r--src/pulsecore/cpu-arm.h53
-rw-r--r--src/pulsecore/cpu-orc.c39
-rw-r--r--src/pulsecore/cpu-orc.h31
-rw-r--r--src/pulsecore/cpu-x86.c132
-rw-r--r--src/pulsecore/cpu-x86.h59
-rw-r--r--src/pulsecore/cpu.c38
-rw-r--r--src/pulsecore/cpu.h49
-rw-r--r--src/pulsecore/creds.h64
-rw-r--r--src/pulsecore/database-gdbm.c244
-rw-r--r--src/pulsecore/database-simple.c495
-rw-r--r--src/pulsecore/database-tdb.c250
-rw-r--r--src/pulsecore/database.c107
-rw-r--r--src/pulsecore/database.h80
-rw-r--r--src/pulsecore/dbus-shared.c102
-rw-r--r--src/pulsecore/dbus-shared.h40
-rw-r--r--src/pulsecore/dbus-util.c778
-rw-r--r--src/pulsecore/dbus-util.h114
-rw-r--r--src/pulsecore/device-port.c303
-rw-r--r--src/pulsecore/device-port.h100
-rw-r--r--src/pulsecore/dllmain.c53
-rw-r--r--src/pulsecore/dynarray.c165
-rw-r--r--src/pulsecore/dynarray.h73
-rw-r--r--src/pulsecore/endianmacros.h160
-rw-r--r--src/pulsecore/esound.h204
-rw-r--r--src/pulsecore/fdsem.c320
-rw-r--r--src/pulsecore/fdsem.h53
-rw-r--r--src/pulsecore/ffmpeg/avcodec.h81
-rw-r--r--src/pulsecore/ffmpeg/dsputil.h1
-rw-r--r--src/pulsecore/ffmpeg/resample2.c298
-rw-r--r--src/pulsecore/filter/LICENSE.WEBKIT27
-rw-r--r--src/pulsecore/filter/biquad.c117
-rw-r--r--src/pulsecore/filter/biquad.h45
-rw-r--r--src/pulsecore/filter/crossover.c97
-rw-r--r--src/pulsecore/filter/crossover.h29
-rw-r--r--src/pulsecore/filter/lfe-filter.c201
-rw-r--r--src/pulsecore/filter/lfe-filter.h39
-rw-r--r--src/pulsecore/flist.c177
-rw-r--r--src/pulsecore/flist.h70
-rw-r--r--src/pulsecore/g711.c2531
-rw-r--r--src/pulsecore/g711.h40
-rw-r--r--src/pulsecore/hashmap.c342
-rw-r--r--src/pulsecore/hashmap.h101
-rw-r--r--src/pulsecore/hook-list.c129
-rw-r--r--src/pulsecore/hook-list.h71
-rw-r--r--src/pulsecore/i18n.c37
-rw-r--r--src/pulsecore/i18n.h62
-rw-r--r--src/pulsecore/idxset.c471
-rw-r--r--src/pulsecore/idxset.h116
-rw-r--r--src/pulsecore/iochannel.c541
-rw-r--r--src/pulsecore/iochannel.h89
-rw-r--r--src/pulsecore/ioline.c463
-rw-r--r--src/pulsecore/ioline.h63
-rw-r--r--src/pulsecore/ipacl.c238
-rw-r--r--src/pulsecore/ipacl.h30
-rw-r--r--src/pulsecore/llist.h111
-rw-r--r--src/pulsecore/lock-autospawn.c362
-rw-r--r--src/pulsecore/lock-autospawn.h30
-rw-r--r--src/pulsecore/log.c685
-rw-r--r--src/pulsecore/log.h155
-rw-r--r--src/pulsecore/ltdl-helper.c63
-rw-r--r--src/pulsecore/ltdl-helper.h29
-rw-r--r--src/pulsecore/macro.h272
-rw-r--r--src/pulsecore/mcalign.c216
-rw-r--r--src/pulsecore/mcalign.h81
-rw-r--r--src/pulsecore/mem.h60
-rw-r--r--src/pulsecore/memblock.c1518
-rw-r--r--src/pulsecore/memblock.h159
-rw-r--r--src/pulsecore/memblockq.c1019
-rw-r--r--src/pulsecore/memblockq.h187
-rw-r--r--src/pulsecore/memchunk.c121
-rw-r--r--src/pulsecore/memchunk.h56
-rw-r--r--src/pulsecore/memfd-wrappers.h69
-rw-r--r--src/pulsecore/memtrap.c242
-rw-r--r--src/pulsecore/memtrap.h49
-rw-r--r--src/pulsecore/meson.build286
-rw-r--r--src/pulsecore/message-handler.c104
-rw-r--r--src/pulsecore/message-handler.h50
-rw-r--r--src/pulsecore/mime-type.c179
-rw-r--r--src/pulsecore/mime-type.h31
-rw-r--r--src/pulsecore/mix.c726
-rw-r--r--src/pulsecore/mix.h62
-rw-r--r--src/pulsecore/mix_neon.c223
-rw-r--r--src/pulsecore/modargs.c546
-rw-r--r--src/pulsecore/modargs.h98
-rw-r--r--src/pulsecore/modinfo.c97
-rw-r--r--src/pulsecore/modinfo.h44
-rw-r--r--src/pulsecore/module.c415
-rw-r--r--src/pulsecore/module.h129
-rw-r--r--src/pulsecore/msgobject.c45
-rw-r--r--src/pulsecore/msgobject.h46
-rw-r--r--src/pulsecore/mutex-posix.c162
-rw-r--r--src/pulsecore/mutex-win32.c154
-rw-r--r--src/pulsecore/mutex.h57
-rw-r--r--src/pulsecore/namereg.c227
-rw-r--r--src/pulsecore/namereg.h43
-rw-r--r--src/pulsecore/native-common.c78
-rw-r--r--src/pulsecore/native-common.h209
-rw-r--r--src/pulsecore/object.c70
-rw-r--r--src/pulsecore/object.h117
-rw-r--r--src/pulsecore/once.c75
-rw-r--r--src/pulsecore/once.h71
-rw-r--r--src/pulsecore/packet.c122
-rw-r--r--src/pulsecore/packet.h45
-rw-r--r--src/pulsecore/parseaddr.c156
-rw-r--r--src/pulsecore/parseaddr.h46
-rw-r--r--src/pulsecore/pdispatch.c475
-rw-r--r--src/pulsecore/pdispatch.h56
-rw-r--r--src/pulsecore/pid.c398
-rw-r--r--src/pulsecore/pid.h28
-rw-r--r--src/pulsecore/pipe.c155
-rw-r--r--src/pulsecore/pipe.h29
-rw-r--r--src/pulsecore/play-memblockq.c283
-rw-r--r--src/pulsecore/play-memblockq.h47
-rw-r--r--src/pulsecore/play-memchunk.c62
-rw-r--r--src/pulsecore/play-memchunk.h36
-rw-r--r--src/pulsecore/poll-posix.c235
-rw-r--r--src/pulsecore/poll-win32.c646
-rw-r--r--src/pulsecore/poll.h62
-rw-r--r--src/pulsecore/proplist-util.c276
-rw-r--r--src/pulsecore/proplist-util.h28
-rw-r--r--src/pulsecore/protocol-cli.c140
-rw-r--r--src/pulsecore/protocol-cli.h36
-rw-r--r--src/pulsecore/protocol-dbus.c1140
-rw-r--r--src/pulsecore/protocol-dbus.h215
-rw-r--r--src/pulsecore/protocol-esound.c1734
-rw-r--r--src/pulsecore/protocol-esound.h56
-rw-r--r--src/pulsecore/protocol-http.c817
-rw-r--r--src/pulsecore/protocol-http.h41
-rw-r--r--src/pulsecore/protocol-native.c5514
-rw-r--r--src/pulsecore/protocol-native.h88
-rw-r--r--src/pulsecore/protocol-simple.c770
-rw-r--r--src/pulsecore/protocol-simple.h55
-rw-r--r--src/pulsecore/pstream-util.c198
-rw-r--r--src/pulsecore/pstream-util.h39
-rw-r--r--src/pulsecore/pstream.c1290
-rw-r--r--src/pulsecore/pstream.h76
-rw-r--r--src/pulsecore/queue.c122
-rw-r--r--src/pulsecore/queue.h41
-rw-r--r--src/pulsecore/random.c129
-rw-r--r--src/pulsecore/random.h29
-rw-r--r--src/pulsecore/ratelimit.c74
-rw-r--r--src/pulsecore/ratelimit.h55
-rw-r--r--src/pulsecore/refcnt.h79
-rw-r--r--src/pulsecore/remap.c626
-rw-r--r--src/pulsecore/remap.h60
-rw-r--r--src/pulsecore/remap_mmx.c155
-rw-r--r--src/pulsecore/remap_neon.c545
-rw-r--r--src/pulsecore/remap_sse.c153
-rw-r--r--src/pulsecore/resampler.c1507
-rw-r--r--src/pulsecore/resampler.h181
-rw-r--r--src/pulsecore/resampler/ffmpeg.c132
-rw-r--r--src/pulsecore/resampler/libsamplerate.c100
-rw-r--r--src/pulsecore/resampler/peaks.c161
-rw-r--r--src/pulsecore/resampler/soxr.c168
-rw-r--r--src/pulsecore/resampler/speex.c178
-rw-r--r--src/pulsecore/resampler/trivial.c100
-rw-r--r--src/pulsecore/rtkit.c313
-rw-r--r--src/pulsecore/rtkit.h79
-rw-r--r--src/pulsecore/rtpoll.c631
-rw-r--r--src/pulsecore/rtpoll.h100
-rw-r--r--src/pulsecore/sample-util.c405
-rw-r--r--src/pulsecore/sample-util.h154
-rw-r--r--src/pulsecore/sconv-s16be.c81
-rw-r--r--src/pulsecore/sconv-s16be.h67
-rw-r--r--src/pulsecore/sconv-s16le.c439
-rw-r--r--src/pulsecore/sconv-s16le.h67
-rw-r--r--src/pulsecore/sconv.c296
-rw-r--r--src/pulsecore/sconv.h41
-rw-r--r--src/pulsecore/sconv_neon.c96
-rw-r--r--src/pulsecore/sconv_sse.c177
-rw-r--r--src/pulsecore/semaphore-osx.c92
-rw-r--r--src/pulsecore/semaphore-posix.c87
-rw-r--r--src/pulsecore/semaphore-win32.c60
-rw-r--r--src/pulsecore/semaphore.h46
-rw-r--r--src/pulsecore/shared.c116
-rw-r--r--src/pulsecore/shared.h55
-rw-r--r--src/pulsecore/shm.c495
-rw-r--r--src/pulsecore/shm.h61
-rw-r--r--src/pulsecore/sink-input.c2449
-rw-r--r--src/pulsecore/sink-input.h469
-rw-r--r--src/pulsecore/sink.c3996
-rw-r--r--src/pulsecore/sink.h575
-rw-r--r--src/pulsecore/sioman.c37
-rw-r--r--src/pulsecore/sioman.h26
-rw-r--r--src/pulsecore/sndfile-util.c461
-rw-r--r--src/pulsecore/sndfile-util.h50
-rw-r--r--src/pulsecore/socket-client.c563
-rw-r--r--src/pulsecore/socket-client.h48
-rw-r--r--src/pulsecore/socket-server.c613
-rw-r--r--src/pulsecore/socket-server.h53
-rw-r--r--src/pulsecore/socket-util.c338
-rw-r--r--src/pulsecore/socket-util.h44
-rw-r--r--src/pulsecore/socket.h20
-rw-r--r--src/pulsecore/sound-file-stream.c339
-rw-r--r--src/pulsecore/sound-file-stream.h27
-rw-r--r--src/pulsecore/sound-file.c163
-rw-r--r--src/pulsecore/sound-file.h31
-rw-r--r--src/pulsecore/source-output.c1912
-rw-r--r--src/pulsecore/source-output.h410
-rw-r--r--src/pulsecore/source.c3052
-rw-r--r--src/pulsecore/source.h493
-rw-r--r--src/pulsecore/srbchannel.c379
-rw-r--r--src/pulsecore/srbchannel.h57
-rw-r--r--src/pulsecore/start-child.c113
-rw-r--r--src/pulsecore/start-child.h28
-rw-r--r--src/pulsecore/strbuf.c193
-rw-r--r--src/pulsecore/strbuf.h40
-rw-r--r--src/pulsecore/stream-util.c84
-rw-r--r--src/pulsecore/stream-util.h48
-rw-r--r--src/pulsecore/strlist.c171
-rw-r--r--src/pulsecore/strlist.h54
-rw-r--r--src/pulsecore/svolume.orc84
-rw-r--r--src/pulsecore/svolume_arm.c167
-rw-r--r--src/pulsecore/svolume_c.c271
-rw-r--r--src/pulsecore/svolume_mmx.c252
-rw-r--r--src/pulsecore/svolume_orc.c50
-rw-r--r--src/pulsecore/svolume_sse.c263
-rw-r--r--src/pulsecore/tagstruct.c800
-rw-r--r--src/pulsecore/tagstruct.h106
-rw-r--r--src/pulsecore/thread-mq.c223
-rw-r--r--src/pulsecore/thread-mq.h57
-rw-r--r--src/pulsecore/thread-posix.c254
-rw-r--r--src/pulsecore/thread-win32.c240
-rw-r--r--src/pulsecore/thread.h117
-rw-r--r--src/pulsecore/time-smoother.c524
-rw-r--r--src/pulsecore/time-smoother.h57
-rw-r--r--src/pulsecore/tokenizer.c82
-rw-r--r--src/pulsecore/tokenizer.h30
-rw-r--r--src/pulsecore/typedefs.h37
-rw-r--r--src/pulsecore/usergroup.c362
-rw-r--r--src/pulsecore/usergroup.h49
-rw-r--r--src/pulsecore/winerrno.h89
-rw-r--r--src/pulsecore/x11prop.c145
-rw-r--r--src/pulsecore/x11prop.h32
-rw-r--r--src/pulsecore/x11wrap.c305
-rw-r--r--src/pulsecore/x11wrap.h60
280 files changed, 80133 insertions, 0 deletions
diff --git a/src/pulsecore/arpa-inet.c b/src/pulsecore/arpa-inet.c
new file mode 100644
index 0000000..afea397
--- /dev/null
+++ b/src/pulsecore/arpa-inet.c
@@ -0,0 +1,106 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#if !defined(HAVE_ARPA_INET_H) && defined(OS_IS_WIN32)
+
+#include <errno.h>
+
+#include <pulsecore/macro.h>
+#include <pulsecore/socket.h>
+#include <pulsecore/core-util.h>
+
+#include "arpa-inet.h"
+
+const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt) {
+ struct in_addr *in = (struct in_addr*)src;
+#ifdef HAVE_IPV6
+ struct in6_addr *in6 = (struct in6_addr*)src;
+#endif
+
+ pa_assert(src);
+ pa_assert(dst);
+
+ switch (af) {
+ case AF_INET:
+ pa_snprintf(dst, cnt, "%d.%d.%d.%d",
+#ifdef WORDS_BIGENDIAN
+ (int)(in->s_addr >> 24) & 0xff,
+ (int)(in->s_addr >> 16) & 0xff,
+ (int)(in->s_addr >> 8) & 0xff,
+ (int)(in->s_addr >> 0) & 0xff);
+#else
+ (int)(in->s_addr >> 0) & 0xff,
+ (int)(in->s_addr >> 8) & 0xff,
+ (int)(in->s_addr >> 16) & 0xff,
+ (int)(in->s_addr >> 24) & 0xff);
+#endif
+ break;
+#ifdef HAVE_IPV6
+ case AF_INET6:
+ pa_snprintf(dst, cnt, "%x:%x:%x:%x:%x:%x:%x:%x",
+ in6->s6_addr[ 0] << 8 | in6->s6_addr[ 1],
+ in6->s6_addr[ 2] << 8 | in6->s6_addr[ 3],
+ in6->s6_addr[ 4] << 8 | in6->s6_addr[ 5],
+ in6->s6_addr[ 6] << 8 | in6->s6_addr[ 7],
+ in6->s6_addr[ 8] << 8 | in6->s6_addr[ 9],
+ in6->s6_addr[10] << 8 | in6->s6_addr[11],
+ in6->s6_addr[12] << 8 | in6->s6_addr[13],
+ in6->s6_addr[14] << 8 | in6->s6_addr[15]);
+ break;
+#endif
+ default:
+ errno = EAFNOSUPPORT;
+ return NULL;
+ }
+
+ return dst;
+}
+
+int inet_pton(int af, const char *src, void *dst) {
+ struct in_addr *in = (struct in_addr*)dst;
+#ifdef HAVE_IPV6
+ struct in6_addr *in6 = (struct in6_addr*)dst;
+#endif
+
+ pa_assert(src);
+ pa_assert(dst);
+
+ switch (af) {
+ case AF_INET:
+ in->s_addr = inet_addr(src);
+ if (in->s_addr == INADDR_NONE)
+ return 0;
+ break;
+#ifdef HAVE_IPV6
+ case AF_INET6:
+ /* FIXME */
+#endif
+ default:
+ errno = EAFNOSUPPORT;
+ return -1;
+ }
+
+ return 1;
+}
+
+#endif
diff --git a/src/pulsecore/arpa-inet.h b/src/pulsecore/arpa-inet.h
new file mode 100644
index 0000000..d940f70
--- /dev/null
+++ b/src/pulsecore/arpa-inet.h
@@ -0,0 +1,21 @@
+#ifndef fooarpa_inethfoo
+#define fooarpa_inethfoo
+
+#if defined(HAVE_ARPA_INET_H)
+
+#include <arpa/inet.h>
+
+#elif defined(OS_IS_WIN32)
+
+/* On Windows winsock2.h (here included via pulsecore/socket.h) provides most of the functionality of arpa/inet.h, except for
+ * the inet_ntop and inet_pton functions, which are implemented here. */
+
+#include <pulsecore/socket.h>
+
+const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt);
+
+int inet_pton(int af, const char *src, void *dst);
+
+#endif
+
+#endif
diff --git a/src/pulsecore/asyncmsgq.c b/src/pulsecore/asyncmsgq.c
new file mode 100644
index 0000000..47371ae
--- /dev/null
+++ b/src/pulsecore/asyncmsgq.c
@@ -0,0 +1,358 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <unistd.h>
+#include <errno.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/macro.h>
+#include <pulsecore/log.h>
+#include <pulsecore/semaphore.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/mutex.h>
+#include <pulsecore/flist.h>
+
+#include "asyncmsgq.h"
+
+PA_STATIC_FLIST_DECLARE(asyncmsgq, 0, pa_xfree);
+PA_STATIC_FLIST_DECLARE(semaphores, 0, (void(*)(void*)) pa_semaphore_free);
+
+struct asyncmsgq_item {
+ int code;
+ pa_msgobject *object;
+ void *userdata;
+ pa_free_cb_t free_cb;
+ int64_t offset;
+ pa_memchunk memchunk;
+ pa_semaphore *semaphore;
+ int ret;
+};
+
+struct pa_asyncmsgq {
+ PA_REFCNT_DECLARE;
+ pa_asyncq *asyncq;
+ pa_mutex *mutex; /* only for the writer side */
+
+ struct asyncmsgq_item *current;
+};
+
+pa_asyncmsgq *pa_asyncmsgq_new(unsigned size) {
+ pa_asyncq *asyncq;
+ pa_asyncmsgq *a;
+
+ asyncq = pa_asyncq_new(size);
+ if (!asyncq)
+ return NULL;
+
+ a = pa_xnew(pa_asyncmsgq, 1);
+
+ PA_REFCNT_INIT(a);
+ a->asyncq = asyncq;
+ pa_assert_se(a->mutex = pa_mutex_new(false, true));
+ a->current = NULL;
+
+ return a;
+}
+
+static void asyncmsgq_free(pa_asyncmsgq *a) {
+ struct asyncmsgq_item *i;
+ pa_assert(a);
+
+ while ((i = pa_asyncq_pop(a->asyncq, false))) {
+
+ pa_assert(!i->semaphore);
+
+ if (i->object)
+ pa_msgobject_unref(i->object);
+
+ if (i->memchunk.memblock)
+ pa_memblock_unref(i->memchunk.memblock);
+
+ if (i->free_cb)
+ i->free_cb(i->userdata);
+
+ if (pa_flist_push(PA_STATIC_FLIST_GET(asyncmsgq), i) < 0)
+ pa_xfree(i);
+ }
+
+ pa_asyncq_free(a->asyncq, NULL);
+ pa_mutex_free(a->mutex);
+ pa_xfree(a);
+}
+
+pa_asyncmsgq* pa_asyncmsgq_ref(pa_asyncmsgq *q) {
+ pa_assert(PA_REFCNT_VALUE(q) > 0);
+
+ PA_REFCNT_INC(q);
+ return q;
+}
+
+void pa_asyncmsgq_unref(pa_asyncmsgq* q) {
+ pa_assert(PA_REFCNT_VALUE(q) > 0);
+
+ if (PA_REFCNT_DEC(q) <= 0)
+ asyncmsgq_free(q);
+}
+
+void pa_asyncmsgq_post(pa_asyncmsgq *a, pa_msgobject *object, int code, const void *userdata, int64_t offset, const pa_memchunk *chunk, pa_free_cb_t free_cb) {
+ struct asyncmsgq_item *i;
+ pa_assert(PA_REFCNT_VALUE(a) > 0);
+
+ if (!(i = pa_flist_pop(PA_STATIC_FLIST_GET(asyncmsgq))))
+ i = pa_xnew(struct asyncmsgq_item, 1);
+
+ i->code = code;
+ i->object = object ? pa_msgobject_ref(object) : NULL;
+ i->userdata = (void*) userdata;
+ i->free_cb = free_cb;
+ i->offset = offset;
+ if (chunk) {
+ pa_assert(chunk->memblock);
+ i->memchunk = *chunk;
+ pa_memblock_ref(i->memchunk.memblock);
+ } else
+ pa_memchunk_reset(&i->memchunk);
+ i->semaphore = NULL;
+
+ /* This mutex makes the queue multiple-writer safe. This lock is only used on the writing side */
+ pa_mutex_lock(a->mutex);
+ pa_asyncq_post(a->asyncq, i);
+ pa_mutex_unlock(a->mutex);
+}
+
+int pa_asyncmsgq_send(pa_asyncmsgq *a, pa_msgobject *object, int code, const void *userdata, int64_t offset, const pa_memchunk *chunk) {
+ struct asyncmsgq_item i;
+ pa_assert(PA_REFCNT_VALUE(a) > 0);
+
+ i.code = code;
+ i.object = object;
+ i.userdata = (void*) userdata;
+ i.free_cb = NULL;
+ i.ret = -1;
+ i.offset = offset;
+ if (chunk) {
+ pa_assert(chunk->memblock);
+ i.memchunk = *chunk;
+ } else
+ pa_memchunk_reset(&i.memchunk);
+
+ if (!(i.semaphore = pa_flist_pop(PA_STATIC_FLIST_GET(semaphores))))
+ i.semaphore = pa_semaphore_new(0);
+
+ /* This mutex makes the queue multiple-writer safe. This lock is only used on the writing side */
+ pa_mutex_lock(a->mutex);
+ pa_assert_se(pa_asyncq_push(a->asyncq, &i, true) == 0);
+ pa_mutex_unlock(a->mutex);
+
+ pa_semaphore_wait(i.semaphore);
+
+ if (pa_flist_push(PA_STATIC_FLIST_GET(semaphores), i.semaphore) < 0)
+ pa_semaphore_free(i.semaphore);
+
+ return i.ret;
+}
+
+int pa_asyncmsgq_get(pa_asyncmsgq *a, pa_msgobject **object, int *code, void **userdata, int64_t *offset, pa_memchunk *chunk, bool wait_op) {
+ pa_assert(PA_REFCNT_VALUE(a) > 0);
+ pa_assert(!a->current);
+
+ if (!(a->current = pa_asyncq_pop(a->asyncq, wait_op))) {
+/* pa_log("failure"); */
+ return -1;
+ }
+
+/* pa_log("success"); */
+
+ if (code)
+ *code = a->current->code;
+ if (userdata)
+ *userdata = a->current->userdata;
+ if (offset)
+ *offset = a->current->offset;
+ if (object) {
+ if ((*object = a->current->object))
+ pa_msgobject_assert_ref(*object);
+ }
+ if (chunk)
+ *chunk = a->current->memchunk;
+
+/* pa_log_debug("Get q=%p object=%p (%s) code=%i data=%p chunk.length=%lu", */
+/* (void*) a, */
+/* (void*) a->current->object, */
+/* a->current->object ? a->current->object->parent.type_name : NULL, */
+/* a->current->code, */
+/* (void*) a->current->userdata, */
+/* (unsigned long) a->current->memchunk.length); */
+
+ return 0;
+}
+
+void pa_asyncmsgq_done(pa_asyncmsgq *a, int ret) {
+ pa_assert(PA_REFCNT_VALUE(a) > 0);
+ pa_assert(a);
+ pa_assert(a->current);
+
+ if (a->current->semaphore) {
+ a->current->ret = ret;
+ pa_semaphore_post(a->current->semaphore);
+ } else {
+
+ if (a->current->free_cb)
+ a->current->free_cb(a->current->userdata);
+
+ if (a->current->object)
+ pa_msgobject_unref(a->current->object);
+
+ if (a->current->memchunk.memblock)
+ pa_memblock_unref(a->current->memchunk.memblock);
+
+ if (pa_flist_push(PA_STATIC_FLIST_GET(asyncmsgq), a->current) < 0)
+ pa_xfree(a->current);
+ }
+
+ a->current = NULL;
+}
+
+int pa_asyncmsgq_wait_for(pa_asyncmsgq *a, int code) {
+ int c;
+ pa_assert(PA_REFCNT_VALUE(a) > 0);
+
+ pa_asyncmsgq_ref(a);
+
+ do {
+ pa_msgobject *o;
+ void *data;
+ int64_t offset;
+ pa_memchunk chunk;
+ int ret;
+
+ if (pa_asyncmsgq_get(a, &o, &c, &data, &offset, &chunk, true) < 0)
+ return -1;
+
+ ret = pa_asyncmsgq_dispatch(o, c, data, offset, &chunk);
+ pa_asyncmsgq_done(a, ret);
+
+ } while (c != code);
+
+ pa_asyncmsgq_unref(a);
+
+ return 0;
+}
+
+int pa_asyncmsgq_process_one(pa_asyncmsgq *a) {
+ pa_msgobject *object;
+ int code;
+ void *data;
+ pa_memchunk chunk;
+ int64_t offset;
+ int ret;
+
+ pa_assert(PA_REFCNT_VALUE(a) > 0);
+
+ if (pa_asyncmsgq_get(a, &object, &code, &data, &offset, &chunk, false) < 0)
+ return 0;
+
+ pa_asyncmsgq_ref(a);
+ ret = pa_asyncmsgq_dispatch(object, code, data, offset, &chunk);
+ pa_asyncmsgq_done(a, ret);
+ pa_asyncmsgq_unref(a);
+
+ return 1;
+}
+
+int pa_asyncmsgq_read_fd(pa_asyncmsgq *a) {
+ pa_assert(PA_REFCNT_VALUE(a) > 0);
+
+ return pa_asyncq_read_fd(a->asyncq);
+}
+
+int pa_asyncmsgq_read_before_poll(pa_asyncmsgq *a) {
+ pa_assert(PA_REFCNT_VALUE(a) > 0);
+
+ return pa_asyncq_read_before_poll(a->asyncq);
+}
+
+void pa_asyncmsgq_read_after_poll(pa_asyncmsgq *a) {
+ pa_assert(PA_REFCNT_VALUE(a) > 0);
+
+ pa_asyncq_read_after_poll(a->asyncq);
+}
+
+int pa_asyncmsgq_write_fd(pa_asyncmsgq *a) {
+ pa_assert(PA_REFCNT_VALUE(a) > 0);
+
+ return pa_asyncq_write_fd(a->asyncq);
+}
+
+void pa_asyncmsgq_write_before_poll(pa_asyncmsgq *a) {
+ pa_assert(PA_REFCNT_VALUE(a) > 0);
+
+ pa_asyncq_write_before_poll(a->asyncq);
+}
+
+void pa_asyncmsgq_write_after_poll(pa_asyncmsgq *a) {
+ pa_assert(PA_REFCNT_VALUE(a) > 0);
+
+ pa_asyncq_write_after_poll(a->asyncq);
+}
+
+int pa_asyncmsgq_dispatch(pa_msgobject *object, int code, void *userdata, int64_t offset, pa_memchunk *memchunk) {
+
+ if (object)
+ return object->process_msg(object, code, userdata, offset, pa_memchunk_isset(memchunk) ? memchunk : NULL);
+
+ return 0;
+}
+
+void pa_asyncmsgq_flush(pa_asyncmsgq *a, bool run) {
+ pa_assert(PA_REFCNT_VALUE(a) > 0);
+
+ for (;;) {
+ pa_msgobject *object;
+ int code;
+ void *data;
+ int64_t offset;
+ pa_memchunk chunk;
+ int ret;
+
+ if (pa_asyncmsgq_get(a, &object, &code, &data, &offset, &chunk, false) < 0)
+ return;
+
+ if (!run) {
+ pa_asyncmsgq_done(a, -1);
+ continue;
+ }
+
+ pa_asyncmsgq_ref(a);
+ ret = pa_asyncmsgq_dispatch(object, code, data, offset, &chunk);
+ pa_asyncmsgq_done(a, ret);
+ pa_asyncmsgq_unref(a);
+ }
+}
+
+bool pa_asyncmsgq_dispatching(pa_asyncmsgq *a) {
+ pa_assert(PA_REFCNT_VALUE(a) > 0);
+
+ return !!a->current;
+}
diff --git a/src/pulsecore/asyncmsgq.h b/src/pulsecore/asyncmsgq.h
new file mode 100644
index 0000000..367ccac
--- /dev/null
+++ b/src/pulsecore/asyncmsgq.h
@@ -0,0 +1,81 @@
+#ifndef foopulseasyncmsgqhfoo
+#define foopulseasyncmsgqhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/types.h>
+
+#include <pulsecore/asyncq.h>
+#include <pulsecore/memchunk.h>
+#include <pulsecore/msgobject.h>
+
+/* A simple asynchronous message queue, based on pa_asyncq. In
+ * contrast to pa_asyncq this one is multiple-writer safe, though
+ * still not multiple-reader safe. This queue is intended to be used
+ * for controlling real-time threads from normal-priority
+ * threads. Multiple-writer-safety is accomplished by using a mutex on
+ * the writer side. This queue is thus not useful for communication
+ * between several real-time threads.
+ *
+ * The queue takes messages consisting of:
+ * "Object" for which this messages is intended (may be NULL)
+ * A numeric message code
+ * Arbitrary userdata pointer (may be NULL)
+ * A memchunk (may be NULL)
+ *
+ * There are two functions for submitting messages: _post and
+ * _send. The former just enqueues the message asynchronously, the
+ * latter waits for completion, synchronously. */
+
+enum {
+ PA_MESSAGE_SHUTDOWN = -1/* A generic message to inform the handler of this queue to quit */
+};
+
+typedef struct pa_asyncmsgq pa_asyncmsgq;
+
+pa_asyncmsgq* pa_asyncmsgq_new(unsigned size);
+pa_asyncmsgq* pa_asyncmsgq_ref(pa_asyncmsgq *q);
+
+void pa_asyncmsgq_unref(pa_asyncmsgq* q);
+
+void pa_asyncmsgq_post(pa_asyncmsgq *q, pa_msgobject *object, int code, const void *userdata, int64_t offset, const pa_memchunk *memchunk, pa_free_cb_t userdata_free_cb);
+int pa_asyncmsgq_send(pa_asyncmsgq *q, pa_msgobject *object, int code, const void *userdata, int64_t offset, const pa_memchunk *memchunk);
+
+int pa_asyncmsgq_get(pa_asyncmsgq *q, pa_msgobject **object, int *code, void **userdata, int64_t *offset, pa_memchunk *memchunk, bool wait);
+int pa_asyncmsgq_dispatch(pa_msgobject *object, int code, void *userdata, int64_t offset, pa_memchunk *memchunk);
+void pa_asyncmsgq_done(pa_asyncmsgq *q, int ret);
+int pa_asyncmsgq_wait_for(pa_asyncmsgq *a, int code);
+int pa_asyncmsgq_process_one(pa_asyncmsgq *a);
+
+void pa_asyncmsgq_flush(pa_asyncmsgq *a, bool run);
+
+/* For the reading side */
+int pa_asyncmsgq_read_fd(pa_asyncmsgq *q);
+int pa_asyncmsgq_read_before_poll(pa_asyncmsgq *a);
+void pa_asyncmsgq_read_after_poll(pa_asyncmsgq *a);
+
+/* For the write side */
+int pa_asyncmsgq_write_fd(pa_asyncmsgq *q);
+void pa_asyncmsgq_write_before_poll(pa_asyncmsgq *a);
+void pa_asyncmsgq_write_after_poll(pa_asyncmsgq *a);
+
+bool pa_asyncmsgq_dispatching(pa_asyncmsgq *a);
+
+#endif
diff --git a/src/pulsecore/asyncq.c b/src/pulsecore/asyncq.c
new file mode 100644
index 0000000..53bfe4f
--- /dev/null
+++ b/src/pulsecore/asyncq.c
@@ -0,0 +1,320 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2006-2008 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <unistd.h>
+#include <errno.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/atomic.h>
+#include <pulsecore/log.h>
+#include <pulsecore/thread.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/llist.h>
+#include <pulsecore/flist.h>
+#include <pulsecore/fdsem.h>
+
+#include "asyncq.h"
+
+#define ASYNCQ_SIZE 256
+
+/* For debugging purposes we can define _Y to put an extra thread
+ * yield between each operation. */
+
+/* #define PROFILE */
+
+#ifdef PROFILE
+#define _Y pa_thread_yield()
+#else
+#define _Y do { } while(0)
+#endif
+
+struct localq {
+ void *data;
+ PA_LLIST_FIELDS(struct localq);
+};
+
+struct pa_asyncq {
+ unsigned size;
+ unsigned read_idx;
+ unsigned write_idx;
+ pa_fdsem *read_fdsem, *write_fdsem;
+
+ PA_LLIST_HEAD(struct localq, localq);
+ struct localq *last_localq;
+ bool waiting_for_post;
+};
+
+PA_STATIC_FLIST_DECLARE(localq, 0, pa_xfree);
+
+#define PA_ASYNCQ_CELLS(x) ((pa_atomic_ptr_t*) ((uint8_t*) (x) + PA_ALIGN(sizeof(struct pa_asyncq))))
+
+static unsigned reduce(pa_asyncq *l, unsigned value) {
+ return value & (unsigned) (l->size - 1);
+}
+
+pa_asyncq *pa_asyncq_new(unsigned size) {
+ pa_asyncq *l;
+
+ if (!size)
+ size = ASYNCQ_SIZE;
+
+ pa_assert(pa_is_power_of_two(size));
+
+ l = pa_xmalloc0(PA_ALIGN(sizeof(pa_asyncq)) + (sizeof(pa_atomic_ptr_t) * size));
+
+ l->size = size;
+
+ PA_LLIST_HEAD_INIT(struct localq, l->localq);
+ l->last_localq = NULL;
+ l->waiting_for_post = false;
+
+ if (!(l->read_fdsem = pa_fdsem_new())) {
+ pa_xfree(l);
+ return NULL;
+ }
+
+ if (!(l->write_fdsem = pa_fdsem_new())) {
+ pa_fdsem_free(l->read_fdsem);
+ pa_xfree(l);
+ return NULL;
+ }
+
+ return l;
+}
+
+void pa_asyncq_free(pa_asyncq *l, pa_free_cb_t free_cb) {
+ struct localq *q;
+ pa_assert(l);
+
+ if (free_cb) {
+ void *p;
+
+ while ((p = pa_asyncq_pop(l, 0)))
+ free_cb(p);
+ }
+
+ while ((q = l->localq)) {
+ if (free_cb)
+ free_cb(q->data);
+
+ PA_LLIST_REMOVE(struct localq, l->localq, q);
+
+ if (pa_flist_push(PA_STATIC_FLIST_GET(localq), q) < 0)
+ pa_xfree(q);
+ }
+
+ pa_fdsem_free(l->read_fdsem);
+ pa_fdsem_free(l->write_fdsem);
+ pa_xfree(l);
+}
+
+static int push(pa_asyncq*l, void *p, bool wait_op) {
+ unsigned idx;
+ pa_atomic_ptr_t *cells;
+
+ pa_assert(l);
+ pa_assert(p);
+
+ cells = PA_ASYNCQ_CELLS(l);
+
+ _Y;
+ idx = reduce(l, l->write_idx);
+
+ if (!pa_atomic_ptr_cmpxchg(&cells[idx], NULL, p)) {
+
+ if (!wait_op)
+ return -1;
+
+/* pa_log("sleeping on push"); */
+
+ do {
+ pa_fdsem_wait(l->read_fdsem);
+ } while (!pa_atomic_ptr_cmpxchg(&cells[idx], NULL, p));
+ }
+
+ _Y;
+ l->write_idx++;
+
+ pa_fdsem_post(l->write_fdsem);
+
+ return 0;
+}
+
+static bool flush_postq(pa_asyncq *l, bool wait_op) {
+ struct localq *q;
+
+ pa_assert(l);
+
+ while ((q = l->last_localq)) {
+
+ if (push(l, q->data, wait_op) < 0)
+ return false;
+
+ l->last_localq = q->prev;
+
+ PA_LLIST_REMOVE(struct localq, l->localq, q);
+
+ if (pa_flist_push(PA_STATIC_FLIST_GET(localq), q) < 0)
+ pa_xfree(q);
+ }
+
+ return true;
+}
+
+int pa_asyncq_push(pa_asyncq*l, void *p, bool wait_op) {
+ pa_assert(l);
+
+ if (!flush_postq(l, wait_op))
+ return -1;
+
+ return push(l, p, wait_op);
+}
+
+void pa_asyncq_post(pa_asyncq*l, void *p) {
+ struct localq *q;
+
+ pa_assert(l);
+ pa_assert(p);
+
+ if (flush_postq(l, false))
+ if (pa_asyncq_push(l, p, false) >= 0)
+ return;
+
+ /* OK, we couldn't push anything in the queue. So let's queue it
+ * locally and push it later */
+
+ if (pa_log_ratelimit(PA_LOG_WARN))
+ pa_log_warn("q overrun, queuing locally");
+
+ if (!(q = pa_flist_pop(PA_STATIC_FLIST_GET(localq))))
+ q = pa_xnew(struct localq, 1);
+
+ q->data = p;
+ PA_LLIST_PREPEND(struct localq, l->localq, q);
+
+ if (!l->last_localq)
+ l->last_localq = q;
+
+ return;
+}
+
+void* pa_asyncq_pop(pa_asyncq*l, bool wait_op) {
+ unsigned idx;
+ void *ret;
+ pa_atomic_ptr_t *cells;
+
+ pa_assert(l);
+
+ cells = PA_ASYNCQ_CELLS(l);
+
+ _Y;
+ idx = reduce(l, l->read_idx);
+
+ if (!(ret = pa_atomic_ptr_load(&cells[idx]))) {
+
+ if (!wait_op)
+ return NULL;
+
+/* pa_log("sleeping on pop"); */
+
+ do {
+ pa_fdsem_wait(l->write_fdsem);
+ } while (!(ret = pa_atomic_ptr_load(&cells[idx])));
+ }
+
+ pa_assert(ret);
+
+ /* Guaranteed to succeed if we only have a single reader */
+ pa_assert_se(pa_atomic_ptr_cmpxchg(&cells[idx], ret, NULL));
+
+ _Y;
+ l->read_idx++;
+
+ pa_fdsem_post(l->read_fdsem);
+
+ return ret;
+}
+
+int pa_asyncq_read_fd(pa_asyncq *q) {
+ pa_assert(q);
+
+ return pa_fdsem_get(q->write_fdsem);
+}
+
+int pa_asyncq_read_before_poll(pa_asyncq *l) {
+ unsigned idx;
+ pa_atomic_ptr_t *cells;
+
+ pa_assert(l);
+
+ cells = PA_ASYNCQ_CELLS(l);
+
+ _Y;
+ idx = reduce(l, l->read_idx);
+
+ for (;;) {
+ if (pa_atomic_ptr_load(&cells[idx]))
+ return -1;
+
+ if (pa_fdsem_before_poll(l->write_fdsem) >= 0)
+ return 0;
+ }
+}
+
+void pa_asyncq_read_after_poll(pa_asyncq *l) {
+ pa_assert(l);
+
+ pa_fdsem_after_poll(l->write_fdsem);
+}
+
+int pa_asyncq_write_fd(pa_asyncq *q) {
+ pa_assert(q);
+
+ return pa_fdsem_get(q->read_fdsem);
+}
+
+void pa_asyncq_write_before_poll(pa_asyncq *l) {
+ pa_assert(l);
+
+ for (;;) {
+
+ if (flush_postq(l, false))
+ break;
+
+ if (pa_fdsem_before_poll(l->read_fdsem) >= 0) {
+ l->waiting_for_post = true;
+ break;
+ }
+ }
+}
+
+void pa_asyncq_write_after_poll(pa_asyncq *l) {
+ pa_assert(l);
+
+ if (l->waiting_for_post) {
+ pa_fdsem_after_poll(l->read_fdsem);
+ l->waiting_for_post = false;
+ }
+}
diff --git a/src/pulsecore/asyncq.h b/src/pulsecore/asyncq.h
new file mode 100644
index 0000000..8c86762
--- /dev/null
+++ b/src/pulsecore/asyncq.h
@@ -0,0 +1,64 @@
+#ifndef foopulseasyncqhfoo
+#define foopulseasyncqhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/types.h>
+#include <pulse/def.h>
+#include <pulsecore/macro.h>
+
+/* A simple, asynchronous, lock-free (if requested also wait-free)
+ * queue. Not multiple-reader/multiple-writer safe. If that is
+ * required both sides can be protected by a mutex each. --- Which is
+ * not a bad thing in most cases, since this queue is intended for
+ * communication between a normal thread and a single real-time
+ * thread. Only the real-time side needs to be lock-free/wait-free.
+ *
+ * If the queue is full and another entry shall be pushed, or when the
+ * queue is empty and another entry shall be popped and the "wait"
+ * argument is non-zero, the queue will block on a UNIX FIFO object --
+ * that will probably require locking on the kernel side -- which
+ * however is probably not problematic, because we do it only on
+ * starvation or overload in which case we have to block anyway. */
+
+typedef struct pa_asyncq pa_asyncq;
+
+pa_asyncq* pa_asyncq_new(unsigned size);
+void pa_asyncq_free(pa_asyncq* q, pa_free_cb_t free_cb);
+
+void* pa_asyncq_pop(pa_asyncq *q, bool wait);
+int pa_asyncq_push(pa_asyncq *q, void *p, bool wait);
+
+/* Similar to pa_asyncq_push(), but if the queue is full, postpone the
+ * appending of the item locally and delay until
+ * pa_asyncq_before_poll_post() is called. */
+void pa_asyncq_post(pa_asyncq*l, void *p);
+
+/* For the reading side */
+int pa_asyncq_read_fd(pa_asyncq *q);
+int pa_asyncq_read_before_poll(pa_asyncq *a);
+void pa_asyncq_read_after_poll(pa_asyncq *a);
+
+/* For the writing side */
+int pa_asyncq_write_fd(pa_asyncq *q);
+void pa_asyncq_write_before_poll(pa_asyncq *a);
+void pa_asyncq_write_after_poll(pa_asyncq *a);
+
+#endif
diff --git a/src/pulsecore/atomic.h b/src/pulsecore/atomic.h
new file mode 100644
index 0000000..e5c1401
--- /dev/null
+++ b/src/pulsecore/atomic.h
@@ -0,0 +1,683 @@
+#ifndef foopulseatomichfoo
+#define foopulseatomichfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2006-2008 Lennart Poettering
+ Copyright 2008 Nokia Corporation
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulsecore/macro.h>
+
+/*
+ * atomic_ops guarantees us that sizeof(AO_t) == sizeof(void*). It is
+ * not guaranteed however, that sizeof(AO_t) == sizeof(size_t).
+ * however very likely.
+ *
+ * For now we do only full memory barriers. Eventually we might want
+ * to support more elaborate memory barriers, in which case we will add
+ * suffixes to the function names.
+ *
+ * On gcc >= 4.1 we use the builtin atomic functions. otherwise we use
+ * libatomic_ops
+ */
+
+#ifndef PACKAGE
+#error "Please include config.h before including this file!"
+#endif
+
+#ifdef HAVE_ATOMIC_BUILTINS
+
+/* __sync based implementation */
+
+typedef struct pa_atomic {
+ volatile int value;
+} pa_atomic_t;
+
+#define PA_ATOMIC_INIT(v) { .value = (v) }
+
+#ifdef HAVE_ATOMIC_BUILTINS_MEMORY_MODEL
+
+/* __atomic based implementation */
+
+static inline int pa_atomic_load(const pa_atomic_t *a) {
+ return __atomic_load_n(&a->value, __ATOMIC_SEQ_CST);
+}
+
+static inline void pa_atomic_store(pa_atomic_t *a, int i) {
+ __atomic_store_n(&a->value, i, __ATOMIC_SEQ_CST);
+}
+
+#else
+
+static inline int pa_atomic_load(const pa_atomic_t *a) {
+ __sync_synchronize();
+ return a->value;
+}
+
+static inline void pa_atomic_store(pa_atomic_t *a, int i) {
+ a->value = i;
+ __sync_synchronize();
+}
+
+#endif
+
+
+/* Returns the previously set value */
+static inline int pa_atomic_add(pa_atomic_t *a, int i) {
+ return __sync_fetch_and_add(&a->value, i);
+}
+
+/* Returns the previously set value */
+static inline int pa_atomic_sub(pa_atomic_t *a, int i) {
+ return __sync_fetch_and_sub(&a->value, i);
+}
+
+/* Returns the previously set value */
+static inline int pa_atomic_inc(pa_atomic_t *a) {
+ return pa_atomic_add(a, 1);
+}
+
+/* Returns the previously set value */
+static inline int pa_atomic_dec(pa_atomic_t *a) {
+ return pa_atomic_sub(a, 1);
+}
+
+/* Returns true when the operation was successful. */
+static inline bool pa_atomic_cmpxchg(pa_atomic_t *a, int old_i, int new_i) {
+ return __sync_bool_compare_and_swap(&a->value, old_i, new_i);
+}
+
+typedef struct pa_atomic_ptr {
+ volatile unsigned long value;
+} pa_atomic_ptr_t;
+
+#define PA_ATOMIC_PTR_INIT(v) { .value = (long) (v) }
+
+#ifdef HAVE_ATOMIC_BUILTINS_MEMORY_MODEL
+
+/* __atomic based implementation */
+
+static inline void* pa_atomic_ptr_load(const pa_atomic_ptr_t *a) {
+ return (void*) __atomic_load_n(&a->value, __ATOMIC_SEQ_CST);
+}
+
+static inline void pa_atomic_ptr_store(pa_atomic_ptr_t *a, void* p) {
+ __atomic_store_n(&a->value, (unsigned long) p, __ATOMIC_SEQ_CST);
+}
+
+#else
+
+static inline void* pa_atomic_ptr_load(const pa_atomic_ptr_t *a) {
+ __sync_synchronize();
+ return (void*) a->value;
+}
+
+static inline void pa_atomic_ptr_store(pa_atomic_ptr_t *a, void *p) {
+ a->value = (unsigned long) p;
+ __sync_synchronize();
+}
+
+#endif
+
+static inline bool pa_atomic_ptr_cmpxchg(pa_atomic_ptr_t *a, void *old_p, void* new_p) {
+ return __sync_bool_compare_and_swap(&a->value, (long) old_p, (long) new_p);
+}
+
+#elif defined(__NetBSD__) && defined(HAVE_SYS_ATOMIC_H)
+
+/* NetBSD 5.0+ atomic_ops(3) implementation */
+
+#include <sys/atomic.h>
+
+typedef struct pa_atomic {
+ volatile unsigned int value;
+} pa_atomic_t;
+
+#define PA_ATOMIC_INIT(v) { .value = (unsigned int) (v) }
+
+static inline int pa_atomic_load(const pa_atomic_t *a) {
+ membar_sync();
+ return (int) a->value;
+}
+
+static inline void pa_atomic_store(pa_atomic_t *a, int i) {
+ a->value = (unsigned int) i;
+ membar_sync();
+}
+
+/* Returns the previously set value */
+static inline int pa_atomic_add(pa_atomic_t *a, int i) {
+ int nv = (int) atomic_add_int_nv(&a->value, i);
+ return nv - i;
+}
+
+/* Returns the previously set value */
+static inline int pa_atomic_sub(pa_atomic_t *a, int i) {
+ int nv = (int) atomic_add_int_nv(&a->value, -i);
+ return nv + i;
+}
+
+/* Returns the previously set value */
+static inline int pa_atomic_inc(pa_atomic_t *a) {
+ int nv = (int) atomic_inc_uint_nv(&a->value);
+ return nv - 1;
+}
+
+/* Returns the previously set value */
+static inline int pa_atomic_dec(pa_atomic_t *a) {
+ int nv = (int) atomic_dec_uint_nv(&a->value);
+ return nv + 1;
+}
+
+/* Returns true when the operation was successful. */
+static inline bool pa_atomic_cmpxchg(pa_atomic_t *a, int old_i, int new_i) {
+ unsigned int r = atomic_cas_uint(&a->value, (unsigned int) old_i, (unsigned int) new_i);
+ return (int) r == old_i;
+}
+
+typedef struct pa_atomic_ptr {
+ volatile void *value;
+} pa_atomic_ptr_t;
+
+#define PA_ATOMIC_PTR_INIT(v) { .value = (v) }
+
+static inline void* pa_atomic_ptr_load(const pa_atomic_ptr_t *a) {
+ membar_sync();
+ return (void *) a->value;
+}
+
+static inline void pa_atomic_ptr_store(pa_atomic_ptr_t *a, void *p) {
+ a->value = p;
+ membar_sync();
+}
+
+static inline bool pa_atomic_ptr_cmpxchg(pa_atomic_ptr_t *a, void *old_p, void* new_p) {
+ void *r = atomic_cas_ptr(&a->value, old_p, new_p);
+ return r == old_p;
+}
+
+#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
+
+#include <sys/cdefs.h>
+#include <sys/types.h>
+#include <sys/param.h>
+#include <machine/atomic.h>
+
+#if __FreeBSD_version < 600000
+#if defined(__i386__) || defined(__amd64__)
+#if defined(__amd64__)
+#define atomic_load_acq_64 atomic_load_acq_long
+#endif
+static inline u_int atomic_fetchadd_int(volatile u_int *p, u_int v) {
+ __asm __volatile(
+ " " __XSTRING(MPLOCKED) " "
+ " xaddl %0, %1 ; "
+ "# atomic_fetchadd_int"
+ : "+r" (v),
+ "=m" (*p)
+ : "m" (*p));
+
+ return (v);
+}
+#elif defined(__sparc__) && defined(__arch64__)
+#define atomic_load_acq_64 atomic_load_acq_long
+#define atomic_fetchadd_int atomic_add_int
+#elif defined(__ia64__)
+#define atomic_load_acq_64 atomic_load_acq_long
+static inline uint32_t
+atomic_fetchadd_int(volatile uint32_t *p, uint32_t v) {
+ uint32_t value;
+
+ do {
+ value = *p;
+ } while (!atomic_cmpset_32(p, value, value + v));
+ return (value);
+}
+#endif
+#endif
+
+typedef struct pa_atomic {
+ volatile unsigned long value;
+} pa_atomic_t;
+
+#define PA_ATOMIC_INIT(v) { .value = (v) }
+
+static inline int pa_atomic_load(const pa_atomic_t *a) {
+ return (int) atomic_load_acq_int((unsigned int *) &a->value);
+}
+
+static inline void pa_atomic_store(pa_atomic_t *a, int i) {
+ atomic_store_rel_int((unsigned int *) &a->value, i);
+}
+
+static inline int pa_atomic_add(pa_atomic_t *a, int i) {
+ return atomic_fetchadd_int((unsigned int *) &a->value, i);
+}
+
+static inline int pa_atomic_sub(pa_atomic_t *a, int i) {
+ return atomic_fetchadd_int((unsigned int *) &a->value, -(i));
+}
+
+static inline int pa_atomic_inc(pa_atomic_t *a) {
+ return atomic_fetchadd_int((unsigned int *) &a->value, 1);
+}
+
+static inline int pa_atomic_dec(pa_atomic_t *a) {
+ return atomic_fetchadd_int((unsigned int *) &a->value, -1);
+}
+
+static inline int pa_atomic_cmpxchg(pa_atomic_t *a, int old_i, int new_i) {
+ return atomic_cmpset_int((unsigned int *) &a->value, old_i, new_i);
+}
+
+typedef struct pa_atomic_ptr {
+ volatile unsigned long value;
+} pa_atomic_ptr_t;
+
+#define PA_ATOMIC_PTR_INIT(v) { .value = (unsigned long) (v) }
+
+static inline void* pa_atomic_ptr_load(const pa_atomic_ptr_t *a) {
+#ifdef atomic_load_acq_64
+ return (void*) atomic_load_acq_ptr((unsigned long *) &a->value);
+#else
+ return (void*) atomic_load_acq_ptr((unsigned int *) &a->value);
+#endif
+}
+
+static inline void pa_atomic_ptr_store(pa_atomic_ptr_t *a, void *p) {
+#ifdef atomic_load_acq_64
+ atomic_store_rel_ptr(&a->value, (unsigned long) p);
+#else
+ atomic_store_rel_ptr((unsigned int *) &a->value, (unsigned int) p);
+#endif
+}
+
+static inline int pa_atomic_ptr_cmpxchg(pa_atomic_ptr_t *a, void *old_p, void* new_p) {
+#ifdef atomic_load_acq_64
+ return atomic_cmpset_ptr(&a->value, (unsigned long) old_p, (unsigned long) new_p);
+#else
+ return atomic_cmpset_ptr((unsigned int *) &a->value, (unsigned int) old_p, (unsigned int) new_p);
+#endif
+}
+
+#elif defined(__GNUC__) && (defined(__amd64__) || defined(__x86_64__))
+
+#warn "The native atomic operations implementation for AMD64 has not been tested thoroughly. libatomic_ops is known to not work properly on AMD64 and your gcc version is too old for the gcc-builtin atomic ops support. You have three options now: test the native atomic operations implementation for AMD64, fix libatomic_ops, or upgrade your GCC."
+
+/* Adapted from glibc */
+
+typedef struct pa_atomic {
+ volatile int value;
+} pa_atomic_t;
+
+#define PA_ATOMIC_INIT(v) { .value = (v) }
+
+static inline int pa_atomic_load(const pa_atomic_t *a) {
+ return a->value;
+}
+
+static inline void pa_atomic_store(pa_atomic_t *a, int i) {
+ a->value = i;
+}
+
+static inline int pa_atomic_add(pa_atomic_t *a, int i) {
+ int result;
+
+ __asm __volatile ("lock; xaddl %0, %1"
+ : "=r" (result), "=m" (a->value)
+ : "0" (i), "m" (a->value));
+
+ return result;
+}
+
+static inline int pa_atomic_sub(pa_atomic_t *a, int i) {
+ return pa_atomic_add(a, -i);
+}
+
+static inline int pa_atomic_inc(pa_atomic_t *a) {
+ return pa_atomic_add(a, 1);
+}
+
+static inline int pa_atomic_dec(pa_atomic_t *a) {
+ return pa_atomic_sub(a, 1);
+}
+
+static inline bool pa_atomic_cmpxchg(pa_atomic_t *a, int old_i, int new_i) {
+ int result;
+
+ __asm__ __volatile__ ("lock; cmpxchgl %2, %1"
+ : "=a" (result), "=m" (a->value)
+ : "r" (new_i), "m" (a->value), "0" (old_i));
+
+ return result == old_i;
+}
+
+typedef struct pa_atomic_ptr {
+ volatile unsigned long value;
+} pa_atomic_ptr_t;
+
+#define PA_ATOMIC_PTR_INIT(v) { .value = (long) (v) }
+
+static inline void* pa_atomic_ptr_load(const pa_atomic_ptr_t *a) {
+ return (void*) a->value;
+}
+
+static inline void pa_atomic_ptr_store(pa_atomic_ptr_t *a, void *p) {
+ a->value = (unsigned long) p;
+}
+
+static inline bool pa_atomic_ptr_cmpxchg(pa_atomic_ptr_t *a, void *old_p, void* new_p) {
+ void *result;
+
+ __asm__ __volatile__ ("lock; cmpxchgq %q2, %1"
+ : "=a" (result), "=m" (a->value)
+ : "r" (new_p), "m" (a->value), "0" (old_p));
+
+ return result == old_p;
+}
+
+#elif defined(ATOMIC_ARM_INLINE_ASM)
+
+/*
+ These should only be enabled if we have ARMv6 or better.
+*/
+
+typedef struct pa_atomic {
+ volatile int value;
+} pa_atomic_t;
+
+#define PA_ATOMIC_INIT(v) { .value = (v) }
+
+static inline void pa_memory_barrier(void) {
+#ifdef ATOMIC_ARM_MEMORY_BARRIER_ENABLED
+ asm volatile ("mcr p15, 0, r0, c7, c10, 5 @ dmb");
+#endif
+}
+
+static inline int pa_atomic_load(const pa_atomic_t *a) {
+ pa_memory_barrier();
+ return a->value;
+}
+
+static inline void pa_atomic_store(pa_atomic_t *a, int i) {
+ a->value = i;
+ pa_memory_barrier();
+}
+
+/* Returns the previously set value */
+static inline int pa_atomic_add(pa_atomic_t *a, int i) {
+ unsigned long not_exclusive;
+ int new_val, old_val;
+
+ pa_memory_barrier();
+ do {
+ asm volatile ("ldrex %0, [%3]\n"
+ "add %2, %0, %4\n"
+ "strex %1, %2, [%3]\n"
+ : "=&r" (old_val), "=&r" (not_exclusive), "=&r" (new_val)
+ : "r" (&a->value), "Ir" (i)
+ : "cc");
+ } while(not_exclusive);
+ pa_memory_barrier();
+
+ return old_val;
+}
+
+/* Returns the previously set value */
+static inline int pa_atomic_sub(pa_atomic_t *a, int i) {
+ unsigned long not_exclusive;
+ int new_val, old_val;
+
+ pa_memory_barrier();
+ do {
+ asm volatile ("ldrex %0, [%3]\n"
+ "sub %2, %0, %4\n"
+ "strex %1, %2, [%3]\n"
+ : "=&r" (old_val), "=&r" (not_exclusive), "=&r" (new_val)
+ : "r" (&a->value), "Ir" (i)
+ : "cc");
+ } while(not_exclusive);
+ pa_memory_barrier();
+
+ return old_val;
+}
+
+static inline int pa_atomic_inc(pa_atomic_t *a) {
+ return pa_atomic_add(a, 1);
+}
+
+static inline int pa_atomic_dec(pa_atomic_t *a) {
+ return pa_atomic_sub(a, 1);
+}
+
+static inline bool pa_atomic_cmpxchg(pa_atomic_t *a, int old_i, int new_i) {
+ unsigned long not_equal, not_exclusive;
+
+ pa_memory_barrier();
+ do {
+ asm volatile ("ldrex %0, [%2]\n"
+ "subs %0, %0, %3\n"
+ "mov %1, %0\n"
+ "strexeq %0, %4, [%2]\n"
+ : "=&r" (not_exclusive), "=&r" (not_equal)
+ : "r" (&a->value), "Ir" (old_i), "r" (new_i)
+ : "cc");
+ } while(not_exclusive && !not_equal);
+ pa_memory_barrier();
+
+ return !not_equal;
+}
+
+typedef struct pa_atomic_ptr {
+ volatile unsigned long value;
+} pa_atomic_ptr_t;
+
+#define PA_ATOMIC_PTR_INIT(v) { .value = (long) (v) }
+
+static inline void* pa_atomic_ptr_load(const pa_atomic_ptr_t *a) {
+ pa_memory_barrier();
+ return (void*) a->value;
+}
+
+static inline void pa_atomic_ptr_store(pa_atomic_ptr_t *a, void *p) {
+ a->value = (unsigned long) p;
+ pa_memory_barrier();
+}
+
+static inline bool pa_atomic_ptr_cmpxchg(pa_atomic_ptr_t *a, void *old_p, void* new_p) {
+ unsigned long not_equal, not_exclusive;
+
+ pa_memory_barrier();
+ do {
+ asm volatile ("ldrex %0, [%2]\n"
+ "subs %0, %0, %3\n"
+ "mov %1, %0\n"
+ "strexeq %0, %4, [%2]\n"
+ : "=&r" (not_exclusive), "=&r" (not_equal)
+ : "r" (&a->value), "Ir" (old_p), "r" (new_p)
+ : "cc");
+ } while(not_exclusive && !not_equal);
+ pa_memory_barrier();
+
+ return !not_equal;
+}
+
+#elif defined(ATOMIC_ARM_LINUX_HELPERS)
+
+/* See file arch/arm/kernel/entry-armv.S in your kernel sources for more
+ information about these functions. The arm kernel helper functions first
+ appeared in 2.6.16.
+ Apply --disable-atomic-arm-linux-helpers flag to configure if you prefer
+ inline asm implementation or you have an obsolete Linux kernel.
+*/
+/* Memory barrier */
+typedef void (__kernel_dmb_t)(void);
+#define __kernel_dmb (*(__kernel_dmb_t *)0xffff0fa0)
+
+static inline void pa_memory_barrier(void) {
+#ifndef ATOMIC_ARM_MEMORY_BARRIER_ENABLED
+ __kernel_dmb();
+#endif
+}
+
+/* Atomic exchange (__kernel_cmpxchg_t contains memory barriers if needed) */
+typedef int (__kernel_cmpxchg_t)(int oldval, int newval, volatile int *ptr);
+#define __kernel_cmpxchg (*(__kernel_cmpxchg_t *)0xffff0fc0)
+
+/* This is just to get rid of all warnings */
+typedef int (__kernel_cmpxchg_u_t)(unsigned long oldval, unsigned long newval, volatile unsigned long *ptr);
+#define __kernel_cmpxchg_u (*(__kernel_cmpxchg_u_t *)0xffff0fc0)
+
+typedef struct pa_atomic {
+ volatile int value;
+} pa_atomic_t;
+
+#define PA_ATOMIC_INIT(v) { .value = (v) }
+
+static inline int pa_atomic_load(const pa_atomic_t *a) {
+ pa_memory_barrier();
+ return a->value;
+}
+
+static inline void pa_atomic_store(pa_atomic_t *a, int i) {
+ a->value = i;
+ pa_memory_barrier();
+}
+
+/* Returns the previously set value */
+static inline int pa_atomic_add(pa_atomic_t *a, int i) {
+ int old_val;
+ do {
+ old_val = a->value;
+ } while(__kernel_cmpxchg(old_val, old_val + i, &a->value));
+ return old_val;
+}
+
+/* Returns the previously set value */
+static inline int pa_atomic_sub(pa_atomic_t *a, int i) {
+ int old_val;
+ do {
+ old_val = a->value;
+ } while(__kernel_cmpxchg(old_val, old_val - i, &a->value));
+ return old_val;
+}
+
+/* Returns the previously set value */
+static inline int pa_atomic_inc(pa_atomic_t *a) {
+ return pa_atomic_add(a, 1);
+}
+
+/* Returns the previously set value */
+static inline int pa_atomic_dec(pa_atomic_t *a) {
+ return pa_atomic_sub(a, 1);
+}
+
+/* Returns true when the operation was successful. */
+static inline bool pa_atomic_cmpxchg(pa_atomic_t *a, int old_i, int new_i) {
+ bool failed;
+ do {
+ failed = !!__kernel_cmpxchg(old_i, new_i, &a->value);
+ } while(failed && a->value == old_i);
+ return !failed;
+}
+
+typedef struct pa_atomic_ptr {
+ volatile unsigned long value;
+} pa_atomic_ptr_t;
+
+#define PA_ATOMIC_PTR_INIT(v) { .value = (unsigned long) (v) }
+
+static inline void* pa_atomic_ptr_load(const pa_atomic_ptr_t *a) {
+ pa_memory_barrier();
+ return (void*) a->value;
+}
+
+static inline void pa_atomic_ptr_store(pa_atomic_ptr_t *a, void *p) {
+ a->value = (unsigned long) p;
+ pa_memory_barrier();
+}
+
+static inline bool pa_atomic_ptr_cmpxchg(pa_atomic_ptr_t *a, void *old_p, void* new_p) {
+ bool failed;
+ do {
+ failed = !!__kernel_cmpxchg_u((unsigned long) old_p, (unsigned long) new_p, &a->value);
+ } while(failed && a->value == (unsigned long) old_p);
+ return !failed;
+}
+
+#else
+
+/* libatomic_ops based implementation */
+
+#include <atomic_ops.h>
+
+typedef struct pa_atomic {
+ volatile AO_t value;
+} pa_atomic_t;
+
+#define PA_ATOMIC_INIT(v) { .value = (AO_t) (v) }
+
+static inline int pa_atomic_load(const pa_atomic_t *a) {
+ return (int) AO_load_full((AO_t*) &a->value);
+}
+
+static inline void pa_atomic_store(pa_atomic_t *a, int i) {
+ AO_store_full(&a->value, (AO_t) i);
+}
+
+static inline int pa_atomic_add(pa_atomic_t *a, int i) {
+ return (int) AO_fetch_and_add_full(&a->value, (AO_t) i);
+}
+
+static inline int pa_atomic_sub(pa_atomic_t *a, int i) {
+ return (int) AO_fetch_and_add_full(&a->value, (AO_t) -i);
+}
+
+static inline int pa_atomic_inc(pa_atomic_t *a) {
+ return (int) AO_fetch_and_add1_full(&a->value);
+}
+
+static inline int pa_atomic_dec(pa_atomic_t *a) {
+ return (int) AO_fetch_and_sub1_full(&a->value);
+}
+
+static inline bool pa_atomic_cmpxchg(pa_atomic_t *a, int old_i, int new_i) {
+ return AO_compare_and_swap_full(&a->value, (unsigned long) old_i, (unsigned long) new_i);
+}
+
+typedef struct pa_atomic_ptr {
+ volatile AO_t value;
+} pa_atomic_ptr_t;
+
+#define PA_ATOMIC_PTR_INIT(v) { .value = (AO_t) (v) }
+
+static inline void* pa_atomic_ptr_load(const pa_atomic_ptr_t *a) {
+ return (void*) AO_load_full((AO_t*) &a->value);
+}
+
+static inline void pa_atomic_ptr_store(pa_atomic_ptr_t *a, void *p) {
+ AO_store_full(&a->value, (AO_t) p);
+}
+
+static inline bool pa_atomic_ptr_cmpxchg(pa_atomic_ptr_t *a, void *old_p, void* new_p) {
+ return AO_compare_and_swap_full(&a->value, (AO_t) old_p, (AO_t) new_p);
+}
+
+#endif
+
+#endif
diff --git a/src/pulsecore/aupdate.c b/src/pulsecore/aupdate.c
new file mode 100644
index 0000000..04d5a57
--- /dev/null
+++ b/src/pulsecore/aupdate.c
@@ -0,0 +1,135 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2009 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/semaphore.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/mutex.h>
+
+#include "aupdate.h"
+
+#define MSB (1U << (sizeof(unsigned)*8U-1))
+#define WHICH(n) (!!((n) & MSB))
+#define COUNTER(n) ((n) & ~MSB)
+
+struct pa_aupdate {
+ pa_atomic_t read_lock;
+ pa_mutex *write_lock;
+ pa_semaphore *semaphore;
+ bool swapped;
+};
+
+pa_aupdate *pa_aupdate_new(void) {
+ pa_aupdate *a;
+
+ a = pa_xnew(pa_aupdate, 1);
+ pa_atomic_store(&a->read_lock, 0);
+ a->write_lock = pa_mutex_new(false, false);
+ a->semaphore = pa_semaphore_new(0);
+
+ return a;
+}
+
+void pa_aupdate_free(pa_aupdate *a) {
+ pa_assert(a);
+
+ pa_mutex_free(a->write_lock);
+ pa_semaphore_free(a->semaphore);
+
+ pa_xfree(a);
+}
+
+unsigned pa_aupdate_read_begin(pa_aupdate *a) {
+ unsigned n;
+
+ pa_assert(a);
+
+ /* Increase the lock counter */
+ n = (unsigned) pa_atomic_inc(&a->read_lock);
+
+ /* When n is 0 we have about 2^31 threads running that all try to
+ * access the data at the same time, oh my! */
+ pa_assert(COUNTER(n)+1 > 0);
+
+ /* The uppermost bit tells us which data to look at */
+ return WHICH(n);
+}
+
+void pa_aupdate_read_end(pa_aupdate *a) {
+ unsigned PA_UNUSED n;
+
+ pa_assert(a);
+
+ /* Decrease the lock counter */
+ n = (unsigned) pa_atomic_dec(&a->read_lock);
+
+ /* Make sure the counter was valid */
+ pa_assert(COUNTER(n) > 0);
+
+ /* Post the semaphore */
+ pa_semaphore_post(a->semaphore);
+}
+
+unsigned pa_aupdate_write_begin(pa_aupdate *a) {
+ unsigned n;
+
+ pa_assert(a);
+
+ pa_mutex_lock(a->write_lock);
+
+ n = (unsigned) pa_atomic_load(&a->read_lock);
+
+ a->swapped = false;
+
+ return !WHICH(n);
+}
+
+unsigned pa_aupdate_write_swap(pa_aupdate *a) {
+ unsigned n;
+
+ pa_assert(a);
+
+ for (;;) {
+ n = (unsigned) pa_atomic_load(&a->read_lock);
+
+ /* If the read counter is > 0 wait; if it is 0 try to swap the lists */
+ if (COUNTER(n) > 0)
+ pa_semaphore_wait(a->semaphore);
+ else if (pa_atomic_cmpxchg(&a->read_lock, (int) n, (int) (n ^ MSB)))
+ break;
+ }
+
+ a->swapped = true;
+
+ return WHICH(n);
+}
+
+void pa_aupdate_write_end(pa_aupdate *a) {
+ pa_assert(a);
+
+ if (!a->swapped)
+ pa_aupdate_write_swap(a);
+
+ pa_mutex_unlock(a->write_lock);
+}
diff --git a/src/pulsecore/aupdate.h b/src/pulsecore/aupdate.h
new file mode 100644
index 0000000..347b853
--- /dev/null
+++ b/src/pulsecore/aupdate.h
@@ -0,0 +1,100 @@
+#ifndef foopulsecoreaupdatehfoo
+#define foopulsecoreaupdatehfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2009 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+typedef struct pa_aupdate pa_aupdate;
+
+pa_aupdate *pa_aupdate_new(void);
+void pa_aupdate_free(pa_aupdate *a);
+
+/* Will return 0, or 1, depending on which copy of the data the caller
+ * should look at */
+unsigned pa_aupdate_read_begin(pa_aupdate *a);
+void pa_aupdate_read_end(pa_aupdate *a);
+
+/* Will return 0, or 1, depending which copy of the data the caller
+ * should modify */
+unsigned pa_aupdate_write_begin(pa_aupdate *a);
+void pa_aupdate_write_end(pa_aupdate *a);
+
+/* Will return 0, or 1, depending which copy of the data the caller
+ * should modify. Each time called this will return the opposite of
+ * the previous pa_aupdate_write_begin() / pa_aupdate_write_swap()
+ * call. Should only be called between pa_aupdate_write_begin() and
+ * pa_aupdate_write_end() */
+unsigned pa_aupdate_write_swap(pa_aupdate *a);
+
+/*
+ * This infrastructure allows lock-free updates of arbitrary data
+ * structures in an rcu'ish way: two copies of the data structure
+ * should be existing. One side ('the reader') has read access to one
+ * of the two data structure at a time. It does not have to lock it,
+ * however it needs to signal that it is using it/stopped using
+ * it. The other side ('the writer') modifies the second data structure,
+ * and then atomically swaps the two data structures, followed by a
+ * modification of the other one.
+ *
+ * This is intended to be used for cases where the reader side needs
+ * to be fast while the writer side can be slow.
+ *
+ * The reader side is signal handler safe.
+ *
+ * The writer side lock is not recursive. The reader side is.
+ *
+ * There may be multiple readers and multiple writers at the same
+ * time.
+ *
+ * Usage is like this:
+ *
+ * static struct foo bar[2];
+ * static pa_aupdate *a;
+ *
+ * reader() {
+ * unsigned j;
+ *
+ * j = pa_update_read_begin(a);
+ *
+ * ... read the data structure bar[j] ...
+ *
+ * pa_update_read_end(a);
+ * }
+ *
+ * writer() {
+ * unsigned j;
+ *
+ * j = pa_update_write_begin(a);
+ *
+ * ... update the data structure bar[j] ...
+ *
+ * j = pa_update_write_swap(a);
+ *
+ * ... update the data structure bar[j], the same way as above ...
+ *
+ * pa_update_write_end(a)
+ * }
+ *
+ * In some cases keeping both structures up-to-date might not be
+ * necessary, since they are fully rebuilt on each iteration
+ * anyway. In that case you may leave the _write_swap() call out, it
+ * will then be done implicitly in the _write_end() invocation.
+ */
+
+#endif
diff --git a/src/pulsecore/auth-cookie.c b/src/pulsecore/auth-cookie.c
new file mode 100644
index 0000000..248d9bf
--- /dev/null
+++ b/src/pulsecore/auth-cookie.c
@@ -0,0 +1,139 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2008 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sys/types.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/refcnt.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/shared.h>
+#include <pulsecore/authkey.h>
+
+#include "auth-cookie.h"
+
+struct pa_auth_cookie {
+ PA_REFCNT_DECLARE;
+ pa_core *core;
+ char *name;
+ size_t size;
+};
+
+pa_auth_cookie* pa_auth_cookie_get(pa_core *core, const char *cn, bool create, size_t size) {
+ pa_auth_cookie *c;
+ char *t;
+
+ pa_assert(core);
+ pa_assert(size > 0);
+
+ t = pa_sprintf_malloc("auth-cookie%s%s", cn ? "@" : "", cn ? cn : "");
+
+ if ((c = pa_shared_get(core, t))) {
+
+ pa_xfree(t);
+
+ if (c->size != size)
+ return NULL;
+
+ return pa_auth_cookie_ref(c);
+ }
+
+ c = pa_xmalloc(PA_ALIGN(sizeof(pa_auth_cookie)) + size);
+ PA_REFCNT_INIT(c);
+ c->core = core;
+ c->name = t;
+ c->size = size;
+
+ pa_assert_se(pa_shared_set(core, t, c) >= 0);
+
+ if (pa_authkey_load(cn, create, (uint8_t*) c + PA_ALIGN(sizeof(pa_auth_cookie)), size) < 0) {
+ pa_auth_cookie_unref(c);
+ return NULL;
+ }
+
+ return c;
+}
+
+pa_auth_cookie *pa_auth_cookie_create(pa_core *core, const void *data, size_t size) {
+ pa_auth_cookie *c;
+ char *t;
+
+ pa_assert(core);
+ pa_assert(data);
+ pa_assert(size > 0);
+
+ t = pa_xstrdup("auth-cookie");
+
+ if ((c = pa_shared_get(core, t))) {
+
+ pa_xfree(t);
+
+ if (c->size != size)
+ return NULL;
+
+ return pa_auth_cookie_ref(c);
+ }
+
+ c = pa_xmalloc(PA_ALIGN(sizeof(pa_auth_cookie)) + size);
+ PA_REFCNT_INIT(c);
+ c->core = core;
+ c->name = t;
+ c->size = size;
+
+ pa_assert_se(pa_shared_set(core, t, c) >= 0);
+
+ memcpy((uint8_t *) c + PA_ALIGN(sizeof(pa_auth_cookie)), data, size);
+
+ return c;
+}
+
+pa_auth_cookie* pa_auth_cookie_ref(pa_auth_cookie *c) {
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ PA_REFCNT_INC(c);
+
+ return c;
+}
+
+void pa_auth_cookie_unref(pa_auth_cookie *c) {
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ if (PA_REFCNT_DEC(c) > 0)
+ return;
+
+ pa_assert_se(pa_shared_remove(c->core, c->name) >= 0);
+
+ pa_xfree(c->name);
+ pa_xfree(c);
+}
+
+const uint8_t* pa_auth_cookie_read(pa_auth_cookie *c, size_t size) {
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+ pa_assert(c->size == size);
+
+ return (const uint8_t*) c + PA_ALIGN(sizeof(pa_auth_cookie));
+}
diff --git a/src/pulsecore/auth-cookie.h b/src/pulsecore/auth-cookie.h
new file mode 100644
index 0000000..01f5244
--- /dev/null
+++ b/src/pulsecore/auth-cookie.h
@@ -0,0 +1,34 @@
+#ifndef fooauthcookiehfoo
+#define fooauthcookiehfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2008 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulsecore/core.h>
+
+typedef struct pa_auth_cookie pa_auth_cookie;
+
+pa_auth_cookie* pa_auth_cookie_get(pa_core *c, const char *cn, bool create, size_t size);
+pa_auth_cookie* pa_auth_cookie_create(pa_core *c, const void *data, size_t size);
+pa_auth_cookie* pa_auth_cookie_ref(pa_auth_cookie *c);
+void pa_auth_cookie_unref(pa_auth_cookie *c);
+
+const uint8_t* pa_auth_cookie_read(pa_auth_cookie *, size_t size);
+
+#endif
diff --git a/src/pulsecore/authkey.c b/src/pulsecore/authkey.c
new file mode 100644
index 0000000..71a9833
--- /dev/null
+++ b/src/pulsecore/authkey.c
@@ -0,0 +1,208 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+
+#include <pulse/util.h>
+#include <pulse/xmalloc.h>
+#include <pulsecore/core-error.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/log.h>
+#include <pulsecore/random.h>
+#include <pulsecore/macro.h>
+
+#include "authkey.h"
+
+/* Generate a new authentication key, store it in file fd and return it in *data */
+static int generate(int fd, void *ret_data, size_t length) {
+ ssize_t r;
+
+ pa_assert(fd >= 0);
+ pa_assert(ret_data);
+ pa_assert(length > 0);
+
+ pa_random(ret_data, length);
+
+ lseek(fd, (off_t) 0, SEEK_SET);
+ if (ftruncate(fd, (off_t) 0) < 0) {
+ pa_log("Failed to truncate cookie file: %s", pa_cstrerror(errno));
+ return -1;
+ }
+
+ if ((r = pa_loop_write(fd, ret_data, length, NULL)) < 0 || (size_t) r != length) {
+ pa_log("Failed to write cookie file: %s", pa_cstrerror(errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+#ifndef O_BINARY
+#define O_BINARY 0
+#endif
+
+/* Load an authentication cookie from file fn and store it in data. If
+ * the cookie file doesn't exist, create it */
+static int load(const char *fn, bool create, void *data, size_t length) {
+ int fd = -1;
+ int writable = 1;
+ int unlock = 0, ret = -1;
+ ssize_t r;
+
+ pa_assert(fn);
+ pa_assert(data);
+ pa_assert(length > 0);
+
+ if (create)
+ pa_make_secure_parent_dir(fn, pa_in_system_mode() ? 0755U : 0700U, -1, -1, false);
+
+ if ((fd = pa_open_cloexec(fn, (create ? O_RDWR|O_CREAT : O_RDONLY)|O_BINARY, S_IRUSR|S_IWUSR)) < 0) {
+
+ if (!create || errno != EACCES || (fd = open(fn, O_RDONLY|O_BINARY)) < 0) {
+ pa_log_warn("Failed to open cookie file '%s': %s", fn, pa_cstrerror(errno));
+ goto finish;
+ } else
+ writable = 0;
+ }
+
+ unlock = pa_lock_fd(fd, 1) >= 0;
+
+ if ((r = pa_loop_read(fd, data, length, NULL)) < 0) {
+ pa_log("Failed to read cookie file '%s': %s", fn, pa_cstrerror(errno));
+ goto finish;
+ }
+
+ if ((size_t) r != length) {
+ pa_log_debug("Got %d bytes from cookie file '%s', expected %d", (int) r, fn, (int) length);
+
+ if (!writable) {
+ pa_log_warn("Unable to write cookie to read-only file");
+ goto finish;
+ }
+
+ if (generate(fd, data, length) < 0)
+ goto finish;
+ }
+
+ ret = 0;
+
+finish:
+
+ if (fd >= 0) {
+
+ if (unlock)
+ pa_lock_fd(fd, 0);
+
+ if (pa_close(fd) < 0) {
+ pa_log_warn("Failed to close cookie file: %s", pa_cstrerror(errno));
+ ret = -1;
+ }
+ }
+
+ return ret;
+}
+
+/* If the specified file path starts with / return it, otherwise
+ * return path prepended with the config home directory. */
+static int normalize_path(const char *fn, char **_r) {
+ pa_assert(fn);
+ pa_assert(_r);
+
+ if (!pa_is_path_absolute(fn))
+ return pa_append_to_config_home_dir(fn, _r);
+
+ *_r = pa_xstrdup(fn);
+ return 0;
+}
+
+int pa_authkey_load(const char *fn, bool create, void *data, size_t length) {
+ char *p;
+ int ret;
+
+ pa_assert(fn);
+ pa_assert(data);
+ pa_assert(length > 0);
+
+ if ((ret = normalize_path(fn, &p)) < 0)
+ return ret;
+
+ if ((ret = load(p, create, data, length)) < 0)
+ pa_log_warn("Failed to load authentication key '%s': %s", p, (ret < 0) ? pa_cstrerror(errno) : "File corrupt");
+
+ pa_xfree(p);
+
+ return ret;
+}
+
+/* Store the specified cookie in the specified cookie file */
+int pa_authkey_save(const char *fn, const void *data, size_t length) {
+ int fd = -1;
+ int unlock = 0, ret;
+ ssize_t r;
+ char *p;
+
+ pa_assert(fn);
+ pa_assert(data);
+ pa_assert(length > 0);
+
+ if ((ret = normalize_path(fn, &p)) < 0)
+ return ret;
+
+ if ((fd = pa_open_cloexec(p, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR)) < 0) {
+ pa_log_warn("Failed to open cookie file '%s': %s", fn, pa_cstrerror(errno));
+ ret = -1;
+ goto finish;
+ }
+
+ unlock = pa_lock_fd(fd, 1) >= 0;
+
+ if ((r = pa_loop_write(fd, data, length, NULL)) < 0 || (size_t) r != length) {
+ pa_log("Failed to read cookie file '%s': %s", fn, pa_cstrerror(errno));
+ ret = -1;
+ goto finish;
+ }
+
+finish:
+
+ if (fd >= 0) {
+
+ if (unlock)
+ pa_lock_fd(fd, 0);
+
+ if (pa_close(fd) < 0) {
+ pa_log_warn("Failed to close cookie file: %s", pa_cstrerror(errno));
+ ret = -1;
+ }
+ }
+
+ pa_xfree(p);
+
+ return ret;
+}
diff --git a/src/pulsecore/authkey.h b/src/pulsecore/authkey.h
new file mode 100644
index 0000000..7af9253
--- /dev/null
+++ b/src/pulsecore/authkey.h
@@ -0,0 +1,29 @@
+#ifndef fooauthkeyhfoo
+#define fooauthkeyhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/types.h>
+
+int pa_authkey_load(const char *fn, bool create, void *data, size_t length);
+
+int pa_authkey_save(const char *path, const void *data, size_t length);
+
+#endif
diff --git a/src/pulsecore/avahi-wrap.c b/src/pulsecore/avahi-wrap.c
new file mode 100644
index 0000000..dc586cb
--- /dev/null
+++ b/src/pulsecore/avahi-wrap.c
@@ -0,0 +1,193 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulse/timeval.h>
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/macro.h>
+
+#include "avahi-wrap.h"
+
+typedef struct {
+ AvahiPoll api;
+ pa_mainloop_api *mainloop;
+} pa_avahi_poll;
+
+struct AvahiWatch {
+ pa_io_event *io_event;
+ pa_avahi_poll *avahi_poll;
+ AvahiWatchEvent current_event;
+ AvahiWatchCallback callback;
+ void *userdata;
+};
+
+static AvahiWatchEvent translate_io_flags_back(pa_io_event_flags_t e) {
+ return
+ (e & PA_IO_EVENT_INPUT ? AVAHI_WATCH_IN : 0) |
+ (e & PA_IO_EVENT_OUTPUT ? AVAHI_WATCH_OUT : 0) |
+ (e & PA_IO_EVENT_ERROR ? AVAHI_WATCH_ERR : 0) |
+ (e & PA_IO_EVENT_HANGUP ? AVAHI_WATCH_HUP : 0);
+}
+
+static pa_io_event_flags_t translate_io_flags(AvahiWatchEvent e) {
+ return
+ (e & AVAHI_WATCH_IN ? PA_IO_EVENT_INPUT : 0) |
+ (e & AVAHI_WATCH_OUT ? PA_IO_EVENT_OUTPUT : 0) |
+ (e & AVAHI_WATCH_ERR ? PA_IO_EVENT_ERROR : 0) |
+ (e & AVAHI_WATCH_HUP ? PA_IO_EVENT_HANGUP : 0);
+}
+
+static void watch_callback(pa_mainloop_api*a, pa_io_event* e, int fd, pa_io_event_flags_t events, void *userdata) {
+ AvahiWatch *w = userdata;
+
+ pa_assert(a);
+ pa_assert(e);
+ pa_assert(w);
+
+ w->current_event = translate_io_flags_back(events);
+ w->callback(w, fd, w->current_event, w->userdata);
+ w->current_event = 0;
+}
+
+static AvahiWatch* watch_new(const AvahiPoll *api, int fd, AvahiWatchEvent event, AvahiWatchCallback callback, void *userdata) {
+ pa_avahi_poll *p;
+ AvahiWatch *w;
+
+ pa_assert(api);
+ pa_assert(fd >= 0);
+ pa_assert(callback);
+ pa_assert_se(p = api->userdata);
+
+ w = pa_xnew(AvahiWatch, 1);
+ w->avahi_poll = p;
+ w->current_event = 0;
+ w->callback = callback;
+ w->userdata = userdata;
+ w->io_event = p->mainloop->io_new(p->mainloop, fd, translate_io_flags(event), watch_callback, w);
+
+ return w;
+}
+
+static void watch_update(AvahiWatch *w, AvahiWatchEvent event) {
+ pa_assert(w);
+
+ w->avahi_poll->mainloop->io_enable(w->io_event, translate_io_flags(event));
+}
+
+static AvahiWatchEvent watch_get_events(AvahiWatch *w) {
+ pa_assert(w);
+
+ return w->current_event;
+}
+
+static void watch_free(AvahiWatch *w) {
+ pa_assert(w);
+
+ w->avahi_poll->mainloop->io_free(w->io_event);
+ pa_xfree(w);
+}
+
+struct AvahiTimeout {
+ pa_time_event *time_event;
+ pa_avahi_poll *avahi_poll;
+ AvahiTimeoutCallback callback;
+ void *userdata;
+};
+
+static void timeout_callback(pa_mainloop_api*a, pa_time_event* e, const struct timeval *t, void *userdata) {
+ AvahiTimeout *to = userdata;
+
+ pa_assert(a);
+ pa_assert(e);
+
+ to->callback(to, to->userdata);
+}
+
+static AvahiTimeout* timeout_new(const AvahiPoll *api, const struct timeval *tv, AvahiTimeoutCallback callback, void *userdata) {
+ pa_avahi_poll *p;
+ AvahiTimeout *t;
+
+ pa_assert(api);
+ pa_assert(callback);
+ pa_assert_se(p = api->userdata);
+
+ t = pa_xnew(AvahiTimeout, 1);
+ t->avahi_poll = p;
+ t->callback = callback;
+ t->userdata = userdata;
+
+ t->time_event = tv ? p->mainloop->time_new(p->mainloop, tv, timeout_callback, t) : NULL;
+
+ return t;
+}
+
+static void timeout_update(AvahiTimeout *t, const struct timeval *tv) {
+
+ pa_assert(t);
+
+ if (t->time_event && tv)
+ t->avahi_poll->mainloop->time_restart(t->time_event, tv);
+ else if (!t->time_event && tv)
+ t->time_event = t->avahi_poll->mainloop->time_new(t->avahi_poll->mainloop, tv, timeout_callback, t);
+ else if (t->time_event && !tv) {
+ t->avahi_poll->mainloop->time_free(t->time_event);
+ t->time_event = NULL;
+ }
+}
+
+static void timeout_free(AvahiTimeout *t) {
+ pa_assert(t);
+
+ if (t->time_event)
+ t->avahi_poll->mainloop->time_free(t->time_event);
+ pa_xfree(t);
+}
+
+AvahiPoll* pa_avahi_poll_new(pa_mainloop_api *m) {
+ pa_avahi_poll *p;
+
+ pa_assert(m);
+
+ p = pa_xnew(pa_avahi_poll, 1);
+
+ p->api.userdata = p;
+ p->api.watch_new = watch_new;
+ p->api.watch_update = watch_update;
+ p->api.watch_get_events = watch_get_events;
+ p->api.watch_free = watch_free;
+ p->api.timeout_new = timeout_new;
+ p->api.timeout_update = timeout_update;
+ p->api.timeout_free = timeout_free;
+ p->mainloop = m;
+
+ return &p->api;
+}
+
+void pa_avahi_poll_free(AvahiPoll *api) {
+ pa_avahi_poll *p;
+ pa_assert(api);
+ pa_assert_se(p = api->userdata);
+
+ pa_xfree(p);
+}
+
diff --git a/src/pulsecore/avahi-wrap.h b/src/pulsecore/avahi-wrap.h
new file mode 100644
index 0000000..c7a9719
--- /dev/null
+++ b/src/pulsecore/avahi-wrap.h
@@ -0,0 +1,30 @@
+#ifndef fooavahiwrapperhfoo
+#define fooavahiwrapperhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <avahi-client/client.h>
+
+#include <pulse/mainloop-api.h>
+
+AvahiPoll* pa_avahi_poll_new(pa_mainloop_api *api);
+void pa_avahi_poll_free(AvahiPoll *p);
+
+#endif
diff --git a/src/pulsecore/bitset.c b/src/pulsecore/bitset.c
new file mode 100644
index 0000000..5c146ca
--- /dev/null
+++ b/src/pulsecore/bitset.c
@@ -0,0 +1,65 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2009 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include <pulse/xmalloc.h>
+
+#include "bitset.h"
+
+void pa_bitset_set(pa_bitset_t *b, unsigned k, bool v) {
+ pa_assert(b);
+
+ if (v)
+ b[k >> 5] |= 1 << (k & 31);
+ else
+ b[k >> 5] &= ~((uint32_t) (1 << (k & 31)));
+}
+
+bool pa_bitset_get(const pa_bitset_t *b, unsigned k) {
+ return !!(b[k >> 5] & (1 << (k & 31)));
+}
+
+bool pa_bitset_equals(const pa_bitset_t *b, unsigned n, ...) {
+ va_list ap;
+ pa_bitset_t *a;
+ bool equal;
+
+ a = pa_xnew0(pa_bitset_t, PA_BITSET_ELEMENTS(n));
+
+ va_start(ap, n);
+ for (;;) {
+ int j = va_arg(ap, int);
+
+ if (j < 0)
+ break;
+
+ pa_bitset_set(a, j, true);
+ }
+ va_end(ap);
+
+ equal = memcmp(a, b, PA_BITSET_SIZE(n)) == 0;
+ pa_xfree(a);
+
+ return equal;
+}
diff --git a/src/pulsecore/bitset.h b/src/pulsecore/bitset.h
new file mode 100644
index 0000000..32fa82d
--- /dev/null
+++ b/src/pulsecore/bitset.h
@@ -0,0 +1,35 @@
+#ifndef foopulsecorebitsethfoo
+#define foopulsecorebitsethfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2009 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <inttypes.h>
+#include <pulsecore/macro.h>
+
+#define PA_BITSET_ELEMENTS(n) (((n)+31)/32)
+#define PA_BITSET_SIZE(n) (PA_BITSET_ELEMENTS(n)*4)
+
+typedef uint32_t pa_bitset_t;
+
+void pa_bitset_set(pa_bitset_t *b, unsigned k, bool v);
+bool pa_bitset_get(const pa_bitset_t *b, unsigned k);
+bool pa_bitset_equals(const pa_bitset_t *b, unsigned n, ...);
+
+#endif
diff --git a/src/pulsecore/card.c b/src/pulsecore/card.c
new file mode 100644
index 0000000..6989596
--- /dev/null
+++ b/src/pulsecore/card.c
@@ -0,0 +1,425 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2009 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <pulse/xmalloc.h>
+#include <pulse/util.h>
+
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/namereg.h>
+#include <pulsecore/device-port.h>
+
+#include "card.h"
+
+const char *pa_available_to_string(pa_available_t available) {
+ switch (available) {
+ case PA_AVAILABLE_UNKNOWN:
+ return "unknown";
+ case PA_AVAILABLE_NO:
+ return "no";
+ case PA_AVAILABLE_YES:
+ return "yes";
+ default:
+ pa_assert_not_reached();
+ }
+}
+
+pa_card_profile *pa_card_profile_new(const char *name, const char *description, size_t extra) {
+ pa_card_profile *c;
+
+ pa_assert(name);
+
+ c = pa_xmalloc0(PA_ALIGN(sizeof(pa_card_profile)) + extra);
+ c->name = pa_xstrdup(name);
+ c->description = pa_xstrdup(description);
+ c->available = PA_AVAILABLE_UNKNOWN;
+
+ return c;
+}
+
+void pa_card_profile_free(pa_card_profile *c) {
+ pa_assert(c);
+
+ pa_xfree(c->input_name);
+ pa_xfree(c->output_name);
+ pa_xfree(c->name);
+ pa_xfree(c->description);
+ pa_xfree(c);
+}
+
+void pa_card_profile_set_available(pa_card_profile *c, pa_available_t available) {
+ pa_core *core;
+
+ pa_assert(c);
+ pa_assert(c->card); /* Modify member variable directly during creation instead of using this function */
+
+ if (c->available == available)
+ return;
+
+ c->available = available;
+ pa_log_debug("Setting card %s profile %s to availability status %s", c->card->name, c->name,
+ pa_available_to_string(available));
+
+ /* Post subscriptions to the card which owns us */
+ pa_assert_se(core = c->card->core);
+ pa_subscription_post(core, PA_SUBSCRIPTION_EVENT_CARD|PA_SUBSCRIPTION_EVENT_CHANGE, c->card->index);
+
+ if (c->card->linked)
+ pa_hook_fire(&core->hooks[PA_CORE_HOOK_CARD_PROFILE_AVAILABLE_CHANGED], c);
+}
+
+pa_card_new_data* pa_card_new_data_init(pa_card_new_data *data) {
+ pa_assert(data);
+
+ memset(data, 0, sizeof(*data));
+ data->proplist = pa_proplist_new();
+ data->profiles = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, (pa_free_cb_t) pa_card_profile_free);
+ data->ports = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, (pa_free_cb_t) pa_device_port_unref);
+ return data;
+}
+
+void pa_card_new_data_set_name(pa_card_new_data *data, const char *name) {
+ pa_assert(data);
+
+ pa_xfree(data->name);
+ data->name = pa_xstrdup(name);
+}
+
+void pa_card_new_data_set_preferred_port(pa_card_new_data *data, pa_direction_t direction, pa_device_port *port) {
+ pa_assert(data);
+
+ if (direction == PA_DIRECTION_INPUT)
+ data->preferred_input_port = port;
+ else
+ data->preferred_output_port = port;
+}
+
+void pa_card_new_data_done(pa_card_new_data *data) {
+
+ pa_assert(data);
+
+ pa_proplist_free(data->proplist);
+
+ if (data->profiles)
+ pa_hashmap_free(data->profiles);
+
+ if (data->ports)
+ pa_hashmap_free(data->ports);
+
+ pa_xfree(data->name);
+}
+
+pa_card *pa_card_new(pa_core *core, pa_card_new_data *data) {
+ pa_card *c;
+ const char *name;
+ void *state;
+ pa_card_profile *profile;
+ pa_device_port *port;
+
+ pa_core_assert_ref(core);
+ pa_assert(data);
+ pa_assert(data->name);
+ pa_assert(data->profiles);
+ pa_assert(!pa_hashmap_isempty(data->profiles));
+
+ c = pa_xnew0(pa_card, 1);
+
+ if (!(name = pa_namereg_register(core, data->name, PA_NAMEREG_CARD, c, data->namereg_fail))) {
+ pa_xfree(c);
+ return NULL;
+ }
+
+ pa_card_new_data_set_name(data, name);
+ pa_hook_fire(&core->hooks[PA_CORE_HOOK_CARD_NEW], data);
+
+ c->core = core;
+ c->name = pa_xstrdup(data->name);
+ c->proplist = pa_proplist_copy(data->proplist);
+ c->driver = pa_xstrdup(pa_path_get_filename(data->driver));
+ c->module = data->module;
+
+ c->sinks = pa_idxset_new(NULL, NULL);
+ c->sources = pa_idxset_new(NULL, NULL);
+
+ /* As a minor optimization we just steal the list instead of
+ * copying it here */
+ pa_assert_se(c->profiles = data->profiles);
+ data->profiles = NULL;
+ pa_assert_se(c->ports = data->ports);
+ data->ports = NULL;
+
+ PA_HASHMAP_FOREACH(profile, c->profiles, state)
+ profile->card = c;
+
+ PA_HASHMAP_FOREACH(port, c->ports, state)
+ port->card = c;
+
+ c->preferred_input_port = data->preferred_input_port;
+ c->preferred_output_port = data->preferred_output_port;
+
+ pa_device_init_description(c->proplist, c);
+ pa_device_init_icon(c->proplist, true);
+ pa_device_init_intended_roles(c->proplist);
+
+ return c;
+}
+
+void pa_card_choose_initial_profile(pa_card *card) {
+ pa_card_profile *profile;
+ void *state;
+ pa_card_profile *best = NULL;
+
+ pa_assert(card);
+
+ /* By default, pick the highest priority profile that is not unavailable,
+ * or if all profiles are unavailable, pick the profile with the highest
+ * priority regardless of its availability. */
+
+ pa_log_debug("Looking for initial profile for card %s", card->name);
+ PA_HASHMAP_FOREACH(profile, card->profiles, state) {
+ pa_log_debug("%s availability %s", profile->name, pa_available_to_string(profile->available));
+ if (profile->available == PA_AVAILABLE_NO)
+ continue;
+
+ if (!best || profile->priority > best->priority)
+ best = profile;
+ }
+
+ if (!best) {
+ PA_HASHMAP_FOREACH(profile, card->profiles, state) {
+ if (!best || profile->priority > best->priority)
+ best = profile;
+ }
+ }
+ pa_assert(best);
+
+ card->active_profile = best;
+ card->save_profile = false;
+ pa_log_info("%s: active_profile: %s", card->name, card->active_profile->name);
+
+ /* Let policy modules override the default. */
+ pa_hook_fire(&card->core->hooks[PA_CORE_HOOK_CARD_CHOOSE_INITIAL_PROFILE], card);
+}
+
+void pa_card_put(pa_card *card) {
+ pa_assert(card);
+
+ pa_assert_se(pa_idxset_put(card->core->cards, card, &card->index) >= 0);
+ card->linked = true;
+
+ pa_log_info("Created %u \"%s\"", card->index, card->name);
+ pa_hook_fire(&card->core->hooks[PA_CORE_HOOK_CARD_PUT], card);
+ pa_subscription_post(card->core, PA_SUBSCRIPTION_EVENT_CARD|PA_SUBSCRIPTION_EVENT_NEW, card->index);
+}
+
+void pa_card_free(pa_card *c) {
+ pa_core *core;
+
+ pa_assert(c);
+ pa_assert(c->core);
+
+ core = c->core;
+
+ if (c->linked) {
+ pa_hook_fire(&core->hooks[PA_CORE_HOOK_CARD_UNLINK], c);
+
+ pa_idxset_remove_by_data(c->core->cards, c, NULL);
+ pa_log_info("Freed %u \"%s\"", c->index, c->name);
+ pa_subscription_post(c->core, PA_SUBSCRIPTION_EVENT_CARD|PA_SUBSCRIPTION_EVENT_REMOVE, c->index);
+ }
+
+ pa_namereg_unregister(core, c->name);
+
+ pa_assert(pa_idxset_isempty(c->sinks));
+ pa_idxset_free(c->sinks, NULL);
+ pa_assert(pa_idxset_isempty(c->sources));
+ pa_idxset_free(c->sources, NULL);
+
+ pa_hashmap_free(c->ports);
+
+ if (c->profiles)
+ pa_hashmap_free(c->profiles);
+
+ pa_proplist_free(c->proplist);
+ pa_xfree(c->driver);
+ pa_xfree(c->name);
+ pa_xfree(c);
+}
+
+void pa_card_add_profile(pa_card *c, pa_card_profile *profile) {
+ pa_assert(c);
+ pa_assert(profile);
+
+ /* take ownership of the profile */
+ pa_assert_se(pa_hashmap_put(c->profiles, profile->name, profile) >= 0);
+ profile->card = c;
+
+ pa_subscription_post(c->core, PA_SUBSCRIPTION_EVENT_CARD|PA_SUBSCRIPTION_EVENT_CHANGE, c->index);
+
+ pa_hook_fire(&c->core->hooks[PA_CORE_HOOK_CARD_PROFILE_ADDED], profile);
+}
+
+static const char* profile_name_for_dir(pa_card_profile *cp, pa_direction_t dir) {
+ if (dir == PA_DIRECTION_OUTPUT && cp->output_name)
+ return cp->output_name;
+ if (dir == PA_DIRECTION_INPUT && cp->input_name)
+ return cp->input_name;
+ return cp->name;
+}
+
+static void update_port_preferred_profile(pa_card *c) {
+ pa_sink *sink;
+ pa_source *source;
+ uint32_t state;
+
+ PA_IDXSET_FOREACH(sink, c->sinks, state)
+ if (sink->active_port)
+ pa_device_port_set_preferred_profile(sink->active_port, profile_name_for_dir(c->active_profile, PA_DIRECTION_OUTPUT));
+ PA_IDXSET_FOREACH(source, c->sources, state)
+ if (source->active_port)
+ pa_device_port_set_preferred_profile(source->active_port, profile_name_for_dir(c->active_profile, PA_DIRECTION_INPUT));
+}
+
+int pa_card_set_profile(pa_card *c, pa_card_profile *profile, bool save) {
+ int r;
+
+ pa_assert(c);
+ pa_assert(profile);
+ pa_assert(profile->card == c);
+
+ if (!c->set_profile) {
+ pa_log_debug("set_profile() operation not implemented for card %u \"%s\"", c->index, c->name);
+ return -PA_ERR_NOTIMPLEMENTED;
+ }
+
+ if (c->active_profile == profile) {
+ if (save && !c->save_profile) {
+ update_port_preferred_profile(c);
+ c->save_profile = true;
+ }
+ return 0;
+ }
+
+ /* If we're setting the initial profile, we shouldn't call set_profile(),
+ * because the implementations don't expect that (for historical reasons).
+ * We should just set c->active_profile, and the implementations will
+ * properly set up that profile after pa_card_put() has returned. It would
+ * be probably good to change this so that also the initial profile can be
+ * set up in set_profile(), but if set_profile() fails, that would need
+ * some better handling than what we do here currently. */
+ if (c->linked && (r = c->set_profile(c, profile)) < 0)
+ return r;
+
+ pa_log_debug("%s: active_profile: %s -> %s", c->name, c->active_profile->name, profile->name);
+ c->active_profile = profile;
+ c->save_profile = save;
+
+ if (save)
+ update_port_preferred_profile(c);
+
+ if (c->linked) {
+ pa_hook_fire(&c->core->hooks[PA_CORE_HOOK_CARD_PROFILE_CHANGED], c);
+ pa_subscription_post(c->core, PA_SUBSCRIPTION_EVENT_CARD|PA_SUBSCRIPTION_EVENT_CHANGE, c->index);
+ }
+
+ return 0;
+}
+
+void pa_card_set_preferred_port(pa_card *c, pa_direction_t direction, pa_device_port *port) {
+ pa_device_port *old_port;
+ const char *old_port_str;
+ const char *new_port_str;
+ pa_card_preferred_port_changed_hook_data data;
+
+ pa_assert(c);
+
+ if (direction == PA_DIRECTION_INPUT) {
+ old_port = c->preferred_input_port;
+ old_port_str = c->preferred_input_port ? c->preferred_input_port->name : "(unset)";
+ } else {
+ old_port = c->preferred_output_port;
+ old_port_str = c->preferred_output_port ? c->preferred_output_port->name : "(unset)";
+ }
+
+ if (port == old_port)
+ return;
+
+ new_port_str = port ? port->name : "(unset)";
+
+ if (direction == PA_DIRECTION_INPUT) {
+ c->preferred_input_port = port;
+ pa_log_debug("%s: preferred_input_port: %s -> %s", c->name, old_port_str, new_port_str);
+ } else {
+ c->preferred_output_port = port;
+ pa_log_debug("%s: preferred_output_port: %s -> %s", c->name, old_port_str, new_port_str);
+ }
+
+ data.card = c;
+ data.direction = direction;
+ pa_hook_fire(&c->core->hooks[PA_CORE_HOOK_CARD_PREFERRED_PORT_CHANGED], &data);
+}
+
+int pa_card_suspend(pa_card *c, bool suspend, pa_suspend_cause_t cause) {
+ pa_sink *sink;
+ pa_source *source;
+ pa_suspend_cause_t suspend_cause;
+ uint32_t idx;
+ int ret = 0;
+
+ pa_assert(c);
+ pa_assert(cause != 0);
+
+ suspend_cause = c->suspend_cause;
+
+ if (suspend)
+ suspend_cause |= cause;
+ else
+ suspend_cause &= ~cause;
+
+ if (c->suspend_cause != suspend_cause) {
+ pa_log_debug("Card suspend causes/state changed");
+ c->suspend_cause = suspend_cause;
+ pa_hook_fire(&c->core->hooks[PA_CORE_HOOK_CARD_SUSPEND_CHANGED], c);
+ }
+
+ PA_IDXSET_FOREACH(sink, c->sinks, idx) {
+ int r;
+
+ if ((r = pa_sink_suspend(sink, suspend, cause)) < 0)
+ ret = r;
+ }
+
+ PA_IDXSET_FOREACH(source, c->sources, idx) {
+ int r;
+
+ if ((r = pa_source_suspend(source, suspend, cause)) < 0)
+ ret = r;
+ }
+
+ return ret;
+}
diff --git a/src/pulsecore/card.h b/src/pulsecore/card.h
new file mode 100644
index 0000000..a11e33d
--- /dev/null
+++ b/src/pulsecore/card.h
@@ -0,0 +1,147 @@
+#ifndef foopulsecardhfoo
+#define foopulsecardhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2009 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulsecore/typedefs.h>
+#include <pulse/proplist.h>
+#include <pulsecore/core.h>
+#include <pulsecore/module.h>
+#include <pulsecore/idxset.h>
+
+/* This enum replaces pa_port_available_t (defined in pulse/def.h) for
+ * internal use, so make sure both enum types stay in sync. */
+typedef enum pa_available {
+ PA_AVAILABLE_UNKNOWN = 0,
+ PA_AVAILABLE_NO = 1,
+ PA_AVAILABLE_YES = 2,
+} pa_available_t;
+
+struct pa_card_profile {
+ pa_card *card;
+ char *name;
+ char *description;
+
+ /* Identifiers for the profile's input and output parts, i e, if two different profiles
+ have the same input_name string, they have the same source(s).
+ Same for output_name and sink(s).
+ Can be NULL (and in case of an input- or output- only profile, the other direction
+ will be NULL). */
+ char *input_name;
+ char *output_name;
+
+ unsigned priority;
+ pa_available_t available; /* PA_AVAILABLE_UNKNOWN, PA_AVAILABLE_NO or PA_AVAILABLE_YES */
+
+ /* We probably want to have different properties later on here */
+ unsigned n_sinks;
+ unsigned n_sources;
+
+ unsigned max_sink_channels;
+ unsigned max_source_channels;
+
+ /* .. followed by some implementation specific data */
+};
+
+#define PA_CARD_PROFILE_DATA(d) ((void*) ((uint8_t*) d + PA_ALIGN(sizeof(pa_card_profile))))
+
+struct pa_card {
+ uint32_t index;
+ pa_core *core;
+
+ char *name;
+
+ pa_proplist *proplist;
+ pa_module *module;
+ char *driver;
+
+ pa_idxset *sinks;
+ pa_idxset *sources;
+
+ pa_hashmap *profiles;
+ pa_card_profile *active_profile;
+
+ pa_hashmap *ports;
+ pa_device_port *preferred_input_port;
+ pa_device_port *preferred_output_port;
+
+ bool save_profile:1;
+
+ pa_suspend_cause_t suspend_cause;
+
+ bool linked;
+
+ void *userdata;
+
+ int (*set_profile)(pa_card *c, pa_card_profile *profile);
+};
+
+typedef struct pa_card_new_data {
+ char *name;
+ pa_proplist *proplist;
+
+ const char *driver;
+ pa_module *module;
+
+ pa_hashmap *profiles;
+ pa_hashmap *ports;
+ pa_device_port *preferred_input_port;
+ pa_device_port *preferred_output_port;
+
+ bool namereg_fail:1;
+} pa_card_new_data;
+
+typedef struct {
+ pa_card *card;
+ pa_direction_t direction;
+} pa_card_preferred_port_changed_hook_data;
+
+const char *pa_available_to_string(pa_available_t available);
+
+pa_card_profile *pa_card_profile_new(const char *name, const char *description, size_t extra);
+void pa_card_profile_free(pa_card_profile *c);
+
+/* The profile's available status has changed */
+void pa_card_profile_set_available(pa_card_profile *c, pa_available_t available);
+
+pa_card_new_data *pa_card_new_data_init(pa_card_new_data *data);
+void pa_card_new_data_set_name(pa_card_new_data *data, const char *name);
+void pa_card_new_data_set_preferred_port(pa_card_new_data *data, pa_direction_t direction, pa_device_port *port);
+void pa_card_new_data_done(pa_card_new_data *data);
+
+pa_card *pa_card_new(pa_core *c, pa_card_new_data *data);
+
+/* Select the initial card profile according to the configured policies. This
+ * must be called between pa_card_new() and pa_card_put(), after the port and
+ * profile availabilities have been initialized. */
+void pa_card_choose_initial_profile(pa_card *card);
+
+void pa_card_put(pa_card *c);
+void pa_card_free(pa_card *c);
+
+void pa_card_add_profile(pa_card *c, pa_card_profile *profile);
+
+int pa_card_set_profile(pa_card *c, pa_card_profile *profile, bool save);
+
+void pa_card_set_preferred_port(pa_card *c, pa_direction_t direction, pa_device_port *port);
+
+int pa_card_suspend(pa_card *c, bool suspend, pa_suspend_cause_t cause);
+
+#endif
diff --git a/src/pulsecore/cli-command.c b/src/pulsecore/cli-command.c
new file mode 100644
index 0000000..58f3d1e
--- /dev/null
+++ b/src/pulsecore/cli-command.c
@@ -0,0 +1,2249 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <ltdl.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <time.h>
+#include <fcntl.h>
+#include <ctype.h>
+
+#include <pulse/xmalloc.h>
+#include <pulse/error.h>
+
+#include <pulsecore/module.h>
+#include <pulsecore/sink.h>
+#include <pulsecore/source.h>
+#include <pulsecore/client.h>
+#include <pulsecore/sink-input.h>
+#include <pulsecore/source-output.h>
+#include <pulsecore/tokenizer.h>
+#include <pulsecore/strbuf.h>
+#include <pulsecore/namereg.h>
+#include <pulsecore/cli-text.h>
+#include <pulsecore/core-scache.h>
+#include <pulsecore/sound-file.h>
+#include <pulsecore/play-memchunk.h>
+#include <pulsecore/sound-file-stream.h>
+#include <pulsecore/shared.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/core-error.h>
+#include <pulsecore/modinfo.h>
+#include <pulsecore/dynarray.h>
+
+#include "cli-command.h"
+
+struct command {
+ const char *name;
+ int (*proc) (pa_core *c, pa_tokenizer*t, pa_strbuf *buf, bool *fail);
+ const char *help;
+ unsigned args;
+};
+
+#define META_INCLUDE ".include"
+#define META_FAIL ".fail"
+#define META_NOFAIL ".nofail"
+#define META_IFEXISTS ".ifexists"
+#define META_ELSE ".else"
+#define META_ENDIF ".endif"
+
+enum {
+ IFSTATE_NONE = -1,
+ IFSTATE_FALSE = 0,
+ IFSTATE_TRUE = 1,
+};
+
+/* Prototypes for all available commands */
+static int pa_cli_command_exit(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_help(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_modules(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_clients(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_cards(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_sinks(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_sources(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_sink_inputs(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_source_outputs(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_stat(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_info(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_load(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_unload(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_describe(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_sink_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_sink_input_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_source_output_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_source_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_sink_mute(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_source_mute(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_sink_input_mute(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_source_output_mute(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_sink_default(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_source_default(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_kill_client(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_kill_sink_input(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_kill_source_output(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_scache_play(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_scache_remove(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_scache_list(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_scache_load(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_scache_load_dir(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_play_file(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_dump(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_list_shared_props(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_move_sink_input(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_move_source_output(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_vacuum(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_suspend_sink(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_suspend_source(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_suspend(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_log_target(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_log_level(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_log_meta(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_log_time(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_log_backtrace(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_update_sink_proplist(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_update_source_proplist(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_update_sink_input_proplist(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_update_source_output_proplist(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_card_profile(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_sink_port(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_source_port(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_port_offset(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+static int pa_cli_command_dump_volumes(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail);
+
+/* A method table for all available commands */
+
+static const struct command commands[] = {
+ { "help", pa_cli_command_help, "Show this help", 1 },
+ { "list-modules", pa_cli_command_modules, "List loaded modules", 1 },
+ { "list-cards", pa_cli_command_cards, "List cards", 1 },
+ { "list-sinks", pa_cli_command_sinks, "List loaded sinks", 1 },
+ { "list-sources", pa_cli_command_sources, "List loaded sources", 1 },
+ { "list-clients", pa_cli_command_clients, "List loaded clients", 1 },
+ { "list-sink-inputs", pa_cli_command_sink_inputs, "List sink inputs", 1 },
+ { "list-source-outputs", pa_cli_command_source_outputs, "List source outputs", 1 },
+ { "stat", pa_cli_command_stat, "Show memory block statistics", 1 },
+ { "info", pa_cli_command_info, "Show comprehensive status", 1 },
+ { "ls", pa_cli_command_info, NULL, 1 },
+ { "list", pa_cli_command_info, NULL, 1 },
+ { "load-module", pa_cli_command_load, "Load a module (args: name, arguments)", 3},
+ { "unload-module", pa_cli_command_unload, "Unload a module (args: index|name)", 2},
+ { "describe-module", pa_cli_command_describe, "Describe a module (arg: name)", 2},
+ { "set-sink-volume", pa_cli_command_sink_volume, "Set the volume of a sink (args: index|name, volume)", 3},
+ { "set-source-volume", pa_cli_command_source_volume, "Set the volume of a source (args: index|name, volume)", 3},
+ { "set-sink-mute", pa_cli_command_sink_mute, "Set the mute switch of a sink (args: index|name, bool)", 3},
+ { "set-source-mute", pa_cli_command_source_mute, "Set the mute switch of a source (args: index|name, bool)", 3},
+ { "set-sink-input-volume", pa_cli_command_sink_input_volume, "Set the volume of a sink input (args: index, volume)", 3},
+ { "set-source-output-volume",pa_cli_command_source_output_volume,"Set the volume of a source output (args: index, volume)", 3},
+ { "set-sink-input-mute", pa_cli_command_sink_input_mute, "Set the mute switch of a sink input (args: index, bool)", 3},
+ { "set-source-output-mute", pa_cli_command_source_output_mute, "Set the mute switch of a source output (args: index, bool)", 3},
+ { "set-default-sink", pa_cli_command_sink_default, "Set the default sink (args: index|name)", 2},
+ { "set-default-source", pa_cli_command_source_default, "Set the default source (args: index|name)", 2},
+ { "set-card-profile", pa_cli_command_card_profile, "Change the profile of a card (args: index|name, profile-name)", 3},
+ { "set-sink-port", pa_cli_command_sink_port, "Change the port of a sink (args: index|name, port-name)", 3},
+ { "set-source-port", pa_cli_command_source_port, "Change the port of a source (args: index|name, port-name)", 3},
+ { "set-port-latency-offset", pa_cli_command_port_offset, "Change the latency of a port (args: card-index|card-name, port-name, latency-offset)", 4},
+ { "suspend-sink", pa_cli_command_suspend_sink, "Suspend sink (args: index|name, bool)", 3},
+ { "suspend-source", pa_cli_command_suspend_source, "Suspend source (args: index|name, bool)", 3},
+ { "suspend", pa_cli_command_suspend, "Suspend all sinks and all sources (args: bool)", 2},
+ { "move-sink-input", pa_cli_command_move_sink_input, "Move sink input to another sink (args: index, sink)", 3},
+ { "move-source-output", pa_cli_command_move_source_output, "Move source output to another source (args: index, source)", 3},
+ { "update-sink-proplist", pa_cli_command_update_sink_proplist, "Update the properties of a sink (args: index|name, properties)", 3},
+ { "update-source-proplist", pa_cli_command_update_source_proplist, "Update the properties of a source (args: index|name, properties)", 3},
+ { "update-sink-input-proplist", pa_cli_command_update_sink_input_proplist, "Update the properties of a sink input (args: index, properties)", 3},
+ { "update-source-output-proplist", pa_cli_command_update_source_output_proplist, "Update the properties of a source output (args: index, properties)", 3},
+ { "list-samples", pa_cli_command_scache_list, "List all entries in the sample cache", 1},
+ { "play-sample", pa_cli_command_scache_play, "Play a sample from the sample cache (args: name, sink|index)", 3},
+ { "remove-sample", pa_cli_command_scache_remove, "Remove a sample from the sample cache (args: name)", 2},
+ { "load-sample", pa_cli_command_scache_load, "Load a sound file into the sample cache (args: name, filename)", 3},
+ { "load-sample-lazy", pa_cli_command_scache_load, "Lazily load a sound file into the sample cache (args: name, filename)", 3},
+ { "load-sample-dir-lazy", pa_cli_command_scache_load_dir, "Lazily load all files in a directory into the sample cache (args: pathname)", 2},
+ { "kill-client", pa_cli_command_kill_client, "Kill a client (args: index)", 2},
+ { "kill-sink-input", pa_cli_command_kill_sink_input, "Kill a sink input (args: index)", 2},
+ { "kill-source-output", pa_cli_command_kill_source_output, "Kill a source output (args: index)", 2},
+ { "set-log-target", pa_cli_command_log_target, "Change the log target (args: null|auto|syslog|stderr|file:PATH|newfile:PATH)", 2},
+ { "set-log-level", pa_cli_command_log_level, "Change the log level (args: numeric level)", 2},
+ { "set-log-meta", pa_cli_command_log_meta, "Show source code location in log messages (args: bool)", 2},
+ { "set-log-time", pa_cli_command_log_time, "Show timestamps in log messages (args: bool)", 2},
+ { "set-log-backtrace", pa_cli_command_log_backtrace, "Show backtrace in log messages (args: frames)", 2},
+ { "play-file", pa_cli_command_play_file, "Play a sound file (args: filename, sink|index)", 3},
+ { "dump", pa_cli_command_dump, "Dump daemon configuration", 1},
+ { "dump-volumes", pa_cli_command_dump_volumes, "Debug: Show the state of all volumes", 1 },
+ { "shared", pa_cli_command_list_shared_props, "Debug: Show shared properties", 1},
+ { "exit", pa_cli_command_exit, "Terminate the daemon", 1 },
+ { "vacuum", pa_cli_command_vacuum, NULL, 1},
+ { NULL, NULL, NULL, 0 }
+};
+
+static const char whitespace[] = " \t\n\r";
+static const char linebreak[] = "\n\r";
+
+static uint32_t parse_index(const char *n) {
+ uint32_t idx;
+
+ if (pa_atou(n, &idx) < 0)
+ return (uint32_t) PA_IDXSET_INVALID;
+
+ return idx;
+}
+
+static int pa_cli_command_exit(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ if (pa_core_exit(c, false, 0) < 0)
+ pa_strbuf_puts(buf, "Not allowed to terminate daemon.\n");
+
+ return 0;
+}
+
+static int pa_cli_command_help(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ const struct command*command;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ pa_strbuf_puts(buf, "Available commands:\n");
+
+ for (command = commands; command->name; command++)
+ if (command->help)
+ pa_strbuf_printf(buf, " %-25s %s\n", command->name, command->help);
+ return 0;
+}
+
+static int pa_cli_command_modules(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ char *s;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ pa_assert_se(s = pa_module_list_to_string(c));
+ pa_strbuf_puts(buf, s);
+ pa_xfree(s);
+ return 0;
+}
+
+static int pa_cli_command_clients(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ char *s;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ pa_assert_se(s = pa_client_list_to_string(c));
+ pa_strbuf_puts(buf, s);
+ pa_xfree(s);
+ return 0;
+}
+
+static int pa_cli_command_cards(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ char *s;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ pa_assert_se(s = pa_card_list_to_string(c));
+ pa_strbuf_puts(buf, s);
+ pa_xfree(s);
+ return 0;
+}
+
+static int pa_cli_command_sinks(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ char *s;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ pa_assert_se(s = pa_sink_list_to_string(c));
+ pa_strbuf_puts(buf, s);
+ pa_xfree(s);
+ return 0;
+}
+
+static int pa_cli_command_sources(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ char *s;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ pa_assert_se(s = pa_source_list_to_string(c));
+ pa_strbuf_puts(buf, s);
+ pa_xfree(s);
+ return 0;
+}
+
+static int pa_cli_command_sink_inputs(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ char *s;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ pa_assert_se(s = pa_sink_input_list_to_string(c));
+ pa_strbuf_puts(buf, s);
+ pa_xfree(s);
+ return 0;
+}
+
+static int pa_cli_command_source_outputs(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ char *s;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ pa_assert_se(s = pa_source_output_list_to_string(c));
+ pa_strbuf_puts(buf, s);
+ pa_xfree(s);
+ return 0;
+}
+
+static int pa_cli_command_stat(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ char ss[PA_SAMPLE_SPEC_SNPRINT_MAX];
+ char cm[PA_CHANNEL_MAP_SNPRINT_MAX];
+ char bytes[PA_BYTES_SNPRINT_MAX];
+ const pa_mempool_stat *mstat;
+ unsigned k;
+
+ static const char* const type_table[PA_MEMBLOCK_TYPE_MAX] = {
+ [PA_MEMBLOCK_POOL] = "POOL",
+ [PA_MEMBLOCK_POOL_EXTERNAL] = "POOL_EXTERNAL",
+ [PA_MEMBLOCK_APPENDED] = "APPENDED",
+ [PA_MEMBLOCK_USER] = "USER",
+ [PA_MEMBLOCK_FIXED] = "FIXED",
+ [PA_MEMBLOCK_IMPORTED] = "IMPORTED",
+ };
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ mstat = pa_mempool_get_stat(c->mempool);
+
+ pa_strbuf_printf(buf, "Memory blocks currently allocated: %u, size: %s.\n",
+ (unsigned) pa_atomic_load(&mstat->n_allocated),
+ pa_bytes_snprint(bytes, sizeof(bytes), (unsigned) pa_atomic_load(&mstat->allocated_size)));
+
+ pa_strbuf_printf(buf, "Memory blocks allocated during the whole lifetime: %u, size: %s.\n",
+ (unsigned) pa_atomic_load(&mstat->n_accumulated),
+ pa_bytes_snprint(bytes, sizeof(bytes), (unsigned) pa_atomic_load(&mstat->accumulated_size)));
+
+ pa_strbuf_printf(buf, "Memory blocks imported from other processes: %u, size: %s.\n",
+ (unsigned) pa_atomic_load(&mstat->n_imported),
+ pa_bytes_snprint(bytes, sizeof(bytes), (unsigned) pa_atomic_load(&mstat->imported_size)));
+
+ pa_strbuf_printf(buf, "Memory blocks exported to other processes: %u, size: %s.\n",
+ (unsigned) pa_atomic_load(&mstat->n_exported),
+ pa_bytes_snprint(bytes, sizeof(bytes), (unsigned) pa_atomic_load(&mstat->exported_size)));
+
+ pa_strbuf_printf(buf, "Total sample cache size: %s.\n",
+ pa_bytes_snprint(bytes, sizeof(bytes), (unsigned) pa_scache_total_size(c)));
+
+ pa_strbuf_printf(buf, "Default sample spec: %s\n",
+ pa_sample_spec_snprint(ss, sizeof(ss), &c->default_sample_spec));
+
+ pa_strbuf_printf(buf, "Default channel map: %s\n",
+ pa_channel_map_snprint(cm, sizeof(cm), &c->default_channel_map));
+
+ pa_strbuf_printf(buf, "Default sink name: %s\n"
+ "Default source name: %s\n",
+ c->default_sink ? c->default_sink->name : "none",
+ c->default_source ? c->default_source->name : "none");
+
+ for (k = 0; k < PA_MEMBLOCK_TYPE_MAX; k++)
+ pa_strbuf_printf(buf,
+ "Memory blocks of type %s: %u allocated/%u accumulated.\n",
+ type_table[k],
+ (unsigned) pa_atomic_load(&mstat->n_allocated_by_type[k]),
+ (unsigned) pa_atomic_load(&mstat->n_accumulated_by_type[k]));
+
+ return 0;
+}
+
+static int pa_cli_command_info(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ pa_cli_command_stat(c, t, buf, fail);
+ pa_cli_command_modules(c, t, buf, fail);
+ pa_cli_command_sinks(c, t, buf, fail);
+ pa_cli_command_sources(c, t, buf, fail);
+ pa_cli_command_clients(c, t, buf, fail);
+ pa_cli_command_cards(c, t, buf, fail);
+ pa_cli_command_sink_inputs(c, t, buf, fail);
+ pa_cli_command_source_outputs(c, t, buf, fail);
+ pa_cli_command_scache_list(c, t, buf, fail);
+ return 0;
+}
+
+static int pa_cli_command_load(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ const char *name;
+ pa_error_code_t err;
+ pa_module *m = NULL;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ if (!(name = pa_tokenizer_get(t, 1))) {
+ pa_strbuf_puts(buf, "You need to specify the module name and optionally arguments.\n");
+ return -1;
+ }
+
+ if ((err = pa_module_load(&m, c, name, pa_tokenizer_get(t, 2))) < 0) {
+ if (err == PA_ERR_EXIST) {
+ pa_strbuf_puts(buf, "Module already loaded; ignoring.\n");
+ } else {
+ pa_strbuf_puts(buf, "Module load failed.\n");
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int pa_cli_command_unload(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ pa_module *m;
+ uint32_t idx;
+ const char *i;
+ bool unloaded = false;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ if (!(i = pa_tokenizer_get(t, 1))) {
+ pa_strbuf_puts(buf, "You need to specify the module index or name.\n");
+ return -1;
+ }
+
+ if (pa_atou(i, &idx) >= 0) {
+ if (!(m = pa_idxset_get_by_index(c->modules, idx))) {
+ pa_strbuf_puts(buf, "Invalid module index.\n");
+ return -1;
+ }
+
+ pa_module_unload(m, false);
+
+ } else {
+ PA_IDXSET_FOREACH(m, c->modules, idx)
+ if (pa_streq(i, m->name)) {
+ unloaded = true;
+ pa_module_unload(m, false);
+ }
+
+ if (unloaded == false) {
+ pa_strbuf_printf(buf, "Module %s not loaded.\n", i);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int pa_cli_command_describe(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ const char *name;
+ pa_modinfo *i;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ if (!(name = pa_tokenizer_get(t, 1))) {
+ pa_strbuf_puts(buf, "You need to specify the module name.\n");
+ return -1;
+ }
+
+ if ((i = pa_modinfo_get_by_name(name))) {
+
+ pa_strbuf_printf(buf, "Name: %s\n", name);
+
+ if (!i->description && !i->version && !i->author && !i->usage)
+ pa_strbuf_printf(buf, "No module information available\n");
+ else {
+ if (i->version)
+ pa_strbuf_printf(buf, "Version: %s\n", i->version);
+ if (i->description)
+ pa_strbuf_printf(buf, "Description: %s\n", i->description);
+ if (i->author)
+ pa_strbuf_printf(buf, "Author: %s\n", i->author);
+ if (i->usage)
+ pa_strbuf_printf(buf, "Usage: %s\n", i->usage);
+ pa_strbuf_printf(buf, "Load Once: %s\n", pa_yes_no(i->load_once));
+ if (i->deprecated)
+ pa_strbuf_printf(buf, "Warning, deprecated: %s\n", i->deprecated);
+ }
+
+ pa_modinfo_free(i);
+ } else
+ pa_strbuf_puts(buf, "Failed to open module.\n");
+
+ return 0;
+}
+
+static int pa_cli_command_sink_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ const char *n, *v;
+ pa_sink *sink;
+ uint32_t volume;
+ pa_cvolume cvolume;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ if (!(n = pa_tokenizer_get(t, 1))) {
+ pa_strbuf_puts(buf, "You need to specify a sink either by its name or its index.\n");
+ return -1;
+ }
+
+ if (!(v = pa_tokenizer_get(t, 2))) {
+ pa_strbuf_puts(buf, "You need to specify a volume >= 0. (0 is muted, 0x10000 is normal volume)\n");
+ return -1;
+ }
+
+ if (pa_atou(v, &volume) < 0) {
+ pa_strbuf_puts(buf, "Failed to parse volume.\n");
+ return -1;
+ }
+
+ if (!PA_VOLUME_IS_VALID(volume)) {
+ pa_strbuf_puts(buf, "Volume outside permissible range.\n");
+ return -1;
+ }
+
+ if (!(sink = pa_namereg_get(c, n, PA_NAMEREG_SINK))) {
+ pa_strbuf_puts(buf, "No sink found by this name or index.\n");
+ return -1;
+ }
+
+ pa_cvolume_set(&cvolume, 1, volume);
+ pa_sink_set_volume(sink, &cvolume, true, true);
+ return 0;
+}
+
+static int pa_cli_command_sink_input_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ const char *n, *v;
+ pa_sink_input *si;
+ pa_volume_t volume;
+ pa_cvolume cvolume;
+ uint32_t idx;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ if (!(n = pa_tokenizer_get(t, 1))) {
+ pa_strbuf_puts(buf, "You need to specify a sink input by its index.\n");
+ return -1;
+ }
+
+ if ((idx = parse_index(n)) == PA_IDXSET_INVALID) {
+ pa_strbuf_puts(buf, "Failed to parse index.\n");
+ return -1;
+ }
+
+ if (!(v = pa_tokenizer_get(t, 2))) {
+ pa_strbuf_puts(buf, "You need to specify a volume >= 0. (0 is muted, 0x10000 is normal volume)\n");
+ return -1;
+ }
+
+ if (pa_atou(v, &volume) < 0) {
+ pa_strbuf_puts(buf, "Failed to parse volume.\n");
+ return -1;
+ }
+
+ if (!PA_VOLUME_IS_VALID(volume)) {
+ pa_strbuf_puts(buf, "Volume outside permissible range.\n");
+ return -1;
+ }
+
+ if (!(si = pa_idxset_get_by_index(c->sink_inputs, idx))) {
+ pa_strbuf_puts(buf, "No sink input found with this index.\n");
+ return -1;
+ }
+
+ if (!si->volume_writable) {
+ pa_strbuf_puts(buf, "This sink input's volume can't be changed.\n");
+ return -1;
+ }
+
+ pa_cvolume_set(&cvolume, 1, volume);
+ pa_sink_input_set_volume(si, &cvolume, true, true);
+ return 0;
+}
+
+static int pa_cli_command_source_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ const char *n, *v;
+ pa_source *source;
+ uint32_t volume;
+ pa_cvolume cvolume;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ if (!(n = pa_tokenizer_get(t, 1))) {
+ pa_strbuf_puts(buf, "You need to specify a source either by its name or its index.\n");
+ return -1;
+ }
+
+ if (!(v = pa_tokenizer_get(t, 2))) {
+ pa_strbuf_puts(buf, "You need to specify a volume >= 0. (0 is muted, 0x10000 is normal volume)\n");
+ return -1;
+ }
+
+ if (pa_atou(v, &volume) < 0) {
+ pa_strbuf_puts(buf, "Failed to parse volume.\n");
+ return -1;
+ }
+
+ if (!PA_VOLUME_IS_VALID(volume)) {
+ pa_strbuf_puts(buf, "Volume outside permissible range.\n");
+ return -1;
+ }
+
+ if (!(source = pa_namereg_get(c, n, PA_NAMEREG_SOURCE))) {
+ pa_strbuf_puts(buf, "No source found by this name or index.\n");
+ return -1;
+ }
+
+ pa_cvolume_set(&cvolume, 1, volume);
+ pa_source_set_volume(source, &cvolume, true, true);
+ return 0;
+}
+
+static int pa_cli_command_source_output_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ const char *n, *v;
+ pa_source_output *so;
+ pa_volume_t volume;
+ pa_cvolume cvolume;
+ uint32_t idx;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ if (!(n = pa_tokenizer_get(t, 1))) {
+ pa_strbuf_puts(buf, "You need to specify a source output by its index.\n");
+ return -1;
+ }
+
+ if ((idx = parse_index(n)) == PA_IDXSET_INVALID) {
+ pa_strbuf_puts(buf, "Failed to parse index.\n");
+ return -1;
+ }
+
+ if (!(v = pa_tokenizer_get(t, 2))) {
+ pa_strbuf_puts(buf, "You need to specify a volume >= 0. (0 is muted, 0x10000 is normal volume)\n");
+ return -1;
+ }
+
+ if (pa_atou(v, &volume) < 0) {
+ pa_strbuf_puts(buf, "Failed to parse volume.\n");
+ return -1;
+ }
+
+ if (!PA_VOLUME_IS_VALID(volume)) {
+ pa_strbuf_puts(buf, "Volume outside permissible range.\n");
+ return -1;
+ }
+
+ if (!(so = pa_idxset_get_by_index(c->source_outputs, idx))) {
+ pa_strbuf_puts(buf, "No source output found with this index.\n");
+ return -1;
+ }
+
+ if (!so->volume_writable) {
+ pa_strbuf_puts(buf, "This source output's volume can't be changed.\n");
+ return -1;
+ }
+
+ pa_cvolume_set(&cvolume, 1, volume);
+ pa_source_output_set_volume(so, &cvolume, true, true);
+ return 0;
+}
+
+static int pa_cli_command_sink_mute(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ const char *n, *m;
+ pa_sink *sink;
+ int mute;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ if (!(n = pa_tokenizer_get(t, 1))) {
+ pa_strbuf_puts(buf, "You need to specify a sink either by its name or its index.\n");
+ return -1;
+ }
+
+ if (!(m = pa_tokenizer_get(t, 2))) {
+ pa_strbuf_puts(buf, "You need to specify a mute switch setting (0/1).\n");
+ return -1;
+ }
+
+ if ((mute = pa_parse_boolean(m)) < 0) {
+ pa_strbuf_puts(buf, "Failed to parse mute switch.\n");
+ return -1;
+ }
+
+ if (!(sink = pa_namereg_get(c, n, PA_NAMEREG_SINK))) {
+ pa_strbuf_puts(buf, "No sink found by this name or index.\n");
+ return -1;
+ }
+
+ pa_sink_set_mute(sink, mute, true);
+ return 0;
+}
+
+static int pa_cli_command_source_mute(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ const char *n, *m;
+ pa_source *source;
+ int mute;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ if (!(n = pa_tokenizer_get(t, 1))) {
+ pa_strbuf_puts(buf, "You need to specify a source either by its name or its index.\n");
+ return -1;
+ }
+
+ if (!(m = pa_tokenizer_get(t, 2))) {
+ pa_strbuf_puts(buf, "You need to specify a mute switch setting (0/1).\n");
+ return -1;
+ }
+
+ if ((mute = pa_parse_boolean(m)) < 0) {
+ pa_strbuf_puts(buf, "Failed to parse mute switch.\n");
+ return -1;
+ }
+
+ if (!(source = pa_namereg_get(c, n, PA_NAMEREG_SOURCE))) {
+ pa_strbuf_puts(buf, "No sink found by this name or index.\n");
+ return -1;
+ }
+
+ pa_source_set_mute(source, mute, true);
+ return 0;
+}
+
+static int pa_cli_command_update_sink_proplist(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ const char *n, *s;
+ pa_sink *sink;
+ pa_proplist *p;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ if (!(n = pa_tokenizer_get(t, 1))) {
+ pa_strbuf_puts(buf, "You need to specify a sink either by its name or its index.\n");
+ return -1;
+ }
+
+ if (!(s = pa_tokenizer_get(t, 2))) {
+ pa_strbuf_puts(buf, "You need to specify a \"key=value\" argument.\n");
+ return -1;
+ }
+
+ if (!(sink = pa_namereg_get(c, n, PA_NAMEREG_SINK))) {
+ pa_strbuf_puts(buf, "No sink found by this name or index.\n");
+ return -1;
+ }
+
+ if (!(p = pa_proplist_from_string(s))) {
+ pa_strbuf_puts(buf, "Failed to parse proplist.\n");
+ return -1;
+ }
+
+ pa_sink_update_proplist(sink, PA_UPDATE_REPLACE, p);
+
+ pa_proplist_free(p);
+
+ return 0;
+}
+
+static int pa_cli_command_update_source_proplist(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ const char *n, *s;
+ pa_source *source;
+ pa_proplist *p;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ if (!(n = pa_tokenizer_get(t, 1))) {
+ pa_strbuf_puts(buf, "You need to specify a source either by its name or its index.\n");
+ return -1;
+ }
+
+ if (!(s = pa_tokenizer_get(t, 2))) {
+ pa_strbuf_puts(buf, "You need to specify a \"key=value\" argument.\n");
+ return -1;
+ }
+
+ if (!(source = pa_namereg_get(c, n, PA_NAMEREG_SOURCE))) {
+ pa_strbuf_puts(buf, "No source found by this name or index.\n");
+ return -1;
+ }
+
+ if (!(p = pa_proplist_from_string(s))) {
+ pa_strbuf_puts(buf, "Failed to parse proplist.\n");
+ return -1;
+ }
+
+ pa_source_update_proplist(source, PA_UPDATE_REPLACE, p);
+
+ pa_proplist_free(p);
+
+ return 0;
+}
+
+static int pa_cli_command_update_sink_input_proplist(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ const char *n, *s;
+ pa_sink_input *si;
+ uint32_t idx;
+ pa_proplist *p;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ if (!(n = pa_tokenizer_get(t, 1))) {
+ pa_strbuf_puts(buf, "You need to specify a sink input either by index.\n");
+ return -1;
+ }
+
+ if ((idx = parse_index(n)) == PA_IDXSET_INVALID) {
+ pa_strbuf_puts(buf, "Failed to parse index.\n");
+ return -1;
+ }
+
+ if (!(s = pa_tokenizer_get(t, 2))) {
+ pa_strbuf_puts(buf, "You need to specify a \"key=value\" argument.\n");
+ return -1;
+ }
+
+ if (!(si = pa_idxset_get_by_index(c->sink_inputs, (uint32_t) idx))) {
+ pa_strbuf_puts(buf, "No sink input found with this index.\n");
+ return -1;
+ }
+
+ if (!(p = pa_proplist_from_string(s))) {
+ pa_strbuf_puts(buf, "Failed to parse proplist.\n");
+ return -1;
+ }
+
+ pa_sink_input_update_proplist(si, PA_UPDATE_REPLACE, p);
+
+ pa_proplist_free(p);
+
+ return 0;
+}
+
+static int pa_cli_command_update_source_output_proplist(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ const char *n, *s;
+ pa_source_output *so;
+ uint32_t idx;
+ pa_proplist *p;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ if (!(n = pa_tokenizer_get(t, 1))) {
+ pa_strbuf_puts(buf, "You need to specify a source output by its index.\n");
+ return -1;
+ }
+
+ if ((idx = parse_index(n)) == PA_IDXSET_INVALID) {
+ pa_strbuf_puts(buf, "Failed to parse index.\n");
+ return -1;
+ }
+
+ if (!(s = pa_tokenizer_get(t, 2))) {
+ pa_strbuf_puts(buf, "You need to specify a \"key=value\" argument.\n");
+ return -1;
+ }
+
+ if (!(so = pa_idxset_get_by_index(c->source_outputs, (uint32_t) idx))) {
+ pa_strbuf_puts(buf, "No source output found with this index.\n");
+ return -1;
+ }
+
+ if (!(p = pa_proplist_from_string(s))) {
+ pa_strbuf_puts(buf, "Failed to parse proplist.\n");
+ return -1;
+ }
+
+ pa_source_output_update_proplist(so, PA_UPDATE_REPLACE, p);
+
+ pa_proplist_free(p);
+
+ return 0;
+}
+
+static int pa_cli_command_sink_input_mute(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ const char *n, *v;
+ pa_sink_input *si;
+ uint32_t idx;
+ int mute;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ if (!(n = pa_tokenizer_get(t, 1))) {
+ pa_strbuf_puts(buf, "You need to specify a sink input by its index.\n");
+ return -1;
+ }
+
+ if ((idx = parse_index(n)) == PA_IDXSET_INVALID) {
+ pa_strbuf_puts(buf, "Failed to parse index.\n");
+ return -1;
+ }
+
+ if (!(v = pa_tokenizer_get(t, 2))) {
+ pa_strbuf_puts(buf, "You need to specify a mute switch setting (0/1).\n");
+ return -1;
+ }
+
+ if ((mute = pa_parse_boolean(v)) < 0) {
+ pa_strbuf_puts(buf, "Failed to parse mute switch.\n");
+ return -1;
+ }
+
+ if (!(si = pa_idxset_get_by_index(c->sink_inputs, (uint32_t) idx))) {
+ pa_strbuf_puts(buf, "No sink input found with this index.\n");
+ return -1;
+ }
+
+ pa_sink_input_set_mute(si, mute, true);
+ return 0;
+}
+
+static int pa_cli_command_source_output_mute(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ const char *n, *v;
+ pa_source_output *so;
+ uint32_t idx;
+ int mute;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ if (!(n = pa_tokenizer_get(t, 1))) {
+ pa_strbuf_puts(buf, "You need to specify a source output by its index.\n");
+ return -1;
+ }
+
+ if ((idx = parse_index(n)) == PA_IDXSET_INVALID) {
+ pa_strbuf_puts(buf, "Failed to parse index.\n");
+ return -1;
+ }
+
+ if (!(v = pa_tokenizer_get(t, 2))) {
+ pa_strbuf_puts(buf, "You need to specify a mute switch setting (0/1).\n");
+ return -1;
+ }
+
+ if ((mute = pa_parse_boolean(v)) < 0) {
+ pa_strbuf_puts(buf, "Failed to parse mute switch.\n");
+ return -1;
+ }
+
+ if (!(so = pa_idxset_get_by_index(c->source_outputs, (uint32_t) idx))) {
+ pa_strbuf_puts(buf, "No source output found with this index.\n");
+ return -1;
+ }
+
+ pa_source_output_set_mute(so, mute, true);
+ return 0;
+}
+
+static int pa_cli_command_sink_default(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ const char *n;
+ pa_sink *s;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ if (!(n = pa_tokenizer_get(t, 1))) {
+ pa_strbuf_puts(buf, "You need to specify a sink either by its name or its index.\n");
+ return -1;
+ }
+
+ if ((s = pa_namereg_get(c, n, PA_NAMEREG_SINK)))
+ pa_core_set_configured_default_sink(c, s->name);
+ else
+ pa_strbuf_printf(buf, "Sink %s does not exist.\n", n);
+
+ return 0;
+}
+
+static int pa_cli_command_source_default(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ const char *n;
+ pa_source *s;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ if (!(n = pa_tokenizer_get(t, 1))) {
+ pa_strbuf_puts(buf, "You need to specify a source either by its name or its index.\n");
+ return -1;
+ }
+
+ if ((s = pa_namereg_get(c, n, PA_NAMEREG_SOURCE)))
+ pa_core_set_configured_default_source(c, s->name);
+ else
+ pa_strbuf_printf(buf, "Source %s does not exist.\n", n);
+ return 0;
+}
+
+static int pa_cli_command_kill_client(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ const char *n;
+ pa_client *client;
+ uint32_t idx;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ if (!(n = pa_tokenizer_get(t, 1))) {
+ pa_strbuf_puts(buf, "You need to specify a client by its index.\n");
+ return -1;
+ }
+
+ if ((idx = parse_index(n)) == PA_IDXSET_INVALID) {
+ pa_strbuf_puts(buf, "Failed to parse index.\n");
+ return -1;
+ }
+
+ if (!(client = pa_idxset_get_by_index(c->clients, idx))) {
+ pa_strbuf_puts(buf, "No client found by this index.\n");
+ return -1;
+ }
+
+ pa_client_kill(client);
+ return 0;
+}
+
+static int pa_cli_command_kill_sink_input(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ const char *n;
+ pa_sink_input *sink_input;
+ uint32_t idx;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ if (!(n = pa_tokenizer_get(t, 1))) {
+ pa_strbuf_puts(buf, "You need to specify a sink input by its index.\n");
+ return -1;
+ }
+
+ if ((idx = parse_index(n)) == PA_IDXSET_INVALID) {
+ pa_strbuf_puts(buf, "Failed to parse index.\n");
+ return -1;
+ }
+
+ if (!(sink_input = pa_idxset_get_by_index(c->sink_inputs, idx))) {
+ pa_strbuf_puts(buf, "No sink input found by this index.\n");
+ return -1;
+ }
+
+ pa_sink_input_kill(sink_input);
+ return 0;
+}
+
+static int pa_cli_command_kill_source_output(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ const char *n;
+ pa_source_output *source_output;
+ uint32_t idx;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ if (!(n = pa_tokenizer_get(t, 1))) {
+ pa_strbuf_puts(buf, "You need to specify a source output by its index.\n");
+ return -1;
+ }
+
+ if ((idx = parse_index(n)) == PA_IDXSET_INVALID) {
+ pa_strbuf_puts(buf, "Failed to parse index.\n");
+ return -1;
+ }
+
+ if (!(source_output = pa_idxset_get_by_index(c->source_outputs, idx))) {
+ pa_strbuf_puts(buf, "No source output found by this index.\n");
+ return -1;
+ }
+
+ pa_source_output_kill(source_output);
+ return 0;
+}
+
+static int pa_cli_command_scache_list(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ char *s;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ pa_assert_se(s = pa_scache_list_to_string(c));
+ pa_strbuf_puts(buf, s);
+ pa_xfree(s);
+
+ return 0;
+}
+
+static int pa_cli_command_scache_play(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ const char *n, *sink_name;
+ pa_sink *sink;
+ uint32_t idx;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ if (!(n = pa_tokenizer_get(t, 1)) || !(sink_name = pa_tokenizer_get(t, 2))) {
+ pa_strbuf_puts(buf, "You need to specify a sample name and a sink name.\n");
+ return -1;
+ }
+
+ if (!(sink = pa_namereg_get(c, sink_name, PA_NAMEREG_SINK))) {
+ pa_strbuf_puts(buf, "No sink by that name.\n");
+ return -1;
+ }
+
+ if (pa_scache_play_item(c, n, sink, PA_VOLUME_NORM, NULL, &idx) < 0) {
+ pa_strbuf_puts(buf, "Failed to play sample.\n");
+ return -1;
+ }
+
+ pa_strbuf_printf(buf, "Playing on sink input #%i\n", idx);
+
+ return 0;
+}
+
+static int pa_cli_command_scache_remove(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ const char *n;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ if (!(n = pa_tokenizer_get(t, 1))) {
+ pa_strbuf_puts(buf, "You need to specify a sample name.\n");
+ return -1;
+ }
+
+ if (pa_scache_remove_item(c, n) < 0) {
+ pa_strbuf_puts(buf, "Failed to remove sample.\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int pa_cli_command_scache_load(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ const char *fname, *n;
+ int r;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ if (!(fname = pa_tokenizer_get(t, 2)) || !(n = pa_tokenizer_get(t, 1))) {
+ pa_strbuf_puts(buf, "You need to specify a file name and a sample name.\n");
+ return -1;
+ }
+
+ if (strstr(pa_tokenizer_get(t, 0), "lazy"))
+ r = pa_scache_add_file_lazy(c, n, fname, NULL);
+ else
+ r = pa_scache_add_file(c, n, fname, NULL);
+
+ if (r < 0)
+ pa_strbuf_puts(buf, "Failed to load sound file.\n");
+
+ return 0;
+}
+
+static int pa_cli_command_scache_load_dir(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ const char *pname;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ if (!(pname = pa_tokenizer_get(t, 1))) {
+ pa_strbuf_puts(buf, "You need to specify a path name.\n");
+ return -1;
+ }
+
+ if (pa_scache_add_directory_lazy(c, pname) < 0) {
+ pa_strbuf_puts(buf, "Failed to load directory.\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int pa_cli_command_play_file(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ const char *fname, *sink_name;
+ pa_sink *sink;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ if (!(fname = pa_tokenizer_get(t, 1)) || !(sink_name = pa_tokenizer_get(t, 2))) {
+ pa_strbuf_puts(buf, "You need to specify a file name and a sink name.\n");
+ return -1;
+ }
+
+ if (!(sink = pa_namereg_get(c, sink_name, PA_NAMEREG_SINK))) {
+ pa_strbuf_puts(buf, "No sink by that name.\n");
+ return -1;
+ }
+
+ if (pa_play_file(sink, fname, NULL) < 0) {
+ pa_strbuf_puts(buf, "Failed to play sound file.\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int pa_cli_command_list_shared_props(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ pa_shared_dump(c, buf);
+ return 0;
+}
+
+static int pa_cli_command_vacuum(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ pa_mempool_vacuum(c->mempool);
+
+ return 0;
+}
+
+static int pa_cli_command_move_sink_input(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ const char *n, *k;
+ pa_sink_input *si;
+ pa_sink *sink;
+ uint32_t idx;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ if (!(n = pa_tokenizer_get(t, 1))) {
+ pa_strbuf_puts(buf, "You need to specify a sink input by its index.\n");
+ return -1;
+ }
+
+ if ((idx = parse_index(n)) == PA_IDXSET_INVALID) {
+ pa_strbuf_puts(buf, "Failed to parse index.\n");
+ return -1;
+ }
+
+ if (!(k = pa_tokenizer_get(t, 2))) {
+ pa_strbuf_puts(buf, "You need to specify a sink.\n");
+ return -1;
+ }
+
+ if (!(si = pa_idxset_get_by_index(c->sink_inputs, (uint32_t) idx))) {
+ pa_strbuf_puts(buf, "No sink input found with this index.\n");
+ return -1;
+ }
+
+ if (!(sink = pa_namereg_get(c, k, PA_NAMEREG_SINK))) {
+ pa_strbuf_puts(buf, "No sink found by this name or index.\n");
+ return -1;
+ }
+
+ if (pa_sink_input_move_to(si, sink, true) < 0) {
+ pa_strbuf_puts(buf, "Moved failed.\n");
+ return -1;
+ }
+ return 0;
+}
+
+static int pa_cli_command_move_source_output(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ const char *n, *k;
+ pa_source_output *so;
+ pa_source *source;
+ uint32_t idx;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ if (!(n = pa_tokenizer_get(t, 1))) {
+ pa_strbuf_puts(buf, "You need to specify a source output by its index.\n");
+ return -1;
+ }
+
+ if ((idx = parse_index(n)) == PA_IDXSET_INVALID) {
+ pa_strbuf_puts(buf, "Failed to parse index.\n");
+ return -1;
+ }
+
+ if (!(k = pa_tokenizer_get(t, 2))) {
+ pa_strbuf_puts(buf, "You need to specify a source.\n");
+ return -1;
+ }
+
+ if (!(so = pa_idxset_get_by_index(c->source_outputs, (uint32_t) idx))) {
+ pa_strbuf_puts(buf, "No source output found with this index.\n");
+ return -1;
+ }
+
+ if (!(source = pa_namereg_get(c, k, PA_NAMEREG_SOURCE))) {
+ pa_strbuf_puts(buf, "No source found by this name or index.\n");
+ return -1;
+ }
+
+ if (pa_source_output_move_to(so, source, true) < 0) {
+ pa_strbuf_puts(buf, "Moved failed.\n");
+ return -1;
+ }
+ return 0;
+}
+
+static int pa_cli_command_suspend_sink(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ const char *n, *m;
+ pa_sink *sink;
+ int suspend, r;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ if (!(n = pa_tokenizer_get(t, 1))) {
+ pa_strbuf_puts(buf, "You need to specify a sink either by its name or its index.\n");
+ return -1;
+ }
+
+ if (!(m = pa_tokenizer_get(t, 2))) {
+ pa_strbuf_puts(buf, "You need to specify a suspend switch setting (0/1).\n");
+ return -1;
+ }
+
+ if ((suspend = pa_parse_boolean(m)) < 0) {
+ pa_strbuf_puts(buf, "Failed to parse suspend switch.\n");
+ return -1;
+ }
+
+ if (!(sink = pa_namereg_get(c, n, PA_NAMEREG_SINK))) {
+ pa_strbuf_puts(buf, "No sink found by this name or index.\n");
+ return -1;
+ }
+
+ pa_log_debug("%s of sink %s requested via CLI.", suspend ? "Suspending" : "Resuming", sink->name);
+
+ if ((r = pa_sink_suspend(sink, suspend, PA_SUSPEND_USER)) < 0)
+ pa_strbuf_printf(buf, "Failed to resume/suspend sink: %s\n", pa_strerror(r));
+
+ return 0;
+}
+
+static int pa_cli_command_suspend_source(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ const char *n, *m;
+ pa_source *source;
+ int suspend, r;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ if (!(n = pa_tokenizer_get(t, 1))) {
+ pa_strbuf_puts(buf, "You need to specify a source either by its name or its index.\n");
+ return -1;
+ }
+
+ if (!(m = pa_tokenizer_get(t, 2))) {
+ pa_strbuf_puts(buf, "You need to specify a suspend switch setting (0/1).\n");
+ return -1;
+ }
+
+ if ((suspend = pa_parse_boolean(m)) < 0) {
+ pa_strbuf_puts(buf, "Failed to parse suspend switch.\n");
+ return -1;
+ }
+
+ if (!(source = pa_namereg_get(c, n, PA_NAMEREG_SOURCE))) {
+ pa_strbuf_puts(buf, "No source found by this name or index.\n");
+ return -1;
+ }
+
+ pa_log_debug("%s of source %s requested via CLI.", suspend ? "Suspending" : "Resuming", source->name);
+
+ if ((r = pa_source_suspend(source, suspend, PA_SUSPEND_USER)) < 0)
+ pa_strbuf_printf(buf, "Failed to resume/suspend source: %s\n", pa_strerror(r));
+
+ return 0;
+}
+
+static int pa_cli_command_suspend(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ const char *m;
+ int suspend, r;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ if (!(m = pa_tokenizer_get(t, 1))) {
+ pa_strbuf_puts(buf, "You need to specify a suspend switch setting (0/1).\n");
+ return -1;
+ }
+
+ if ((suspend = pa_parse_boolean(m)) < 0) {
+ pa_strbuf_puts(buf, "Failed to parse suspend switch.\n");
+ return -1;
+ }
+
+ pa_log_debug("%s of all sinks and sources requested via CLI.", suspend ? "Suspending" : "Resuming");
+
+ if ((r = pa_sink_suspend_all(c, suspend, PA_SUSPEND_USER)) < 0)
+ pa_strbuf_printf(buf, "Failed to resume/suspend all sinks: %s\n", pa_strerror(r));
+
+ if ((r = pa_source_suspend_all(c, suspend, PA_SUSPEND_USER)) < 0)
+ pa_strbuf_printf(buf, "Failed to resume/suspend all sources: %s\n", pa_strerror(r));
+
+ return 0;
+}
+
+static int pa_cli_command_log_target(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ const char *m;
+ pa_log_target *log_target = NULL;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ if (!(m = pa_tokenizer_get(t, 1))) {
+ pa_strbuf_puts(buf, "You need to specify a log target (null|auto|syslog|stderr|file:PATH|newfile:PATH).\n");
+ return -1;
+ }
+
+ /* 'auto' is actually the effect with 'stderr' */
+ if (pa_streq(m, "auto"))
+ log_target = pa_log_target_new(PA_LOG_STDERR, NULL);
+ else {
+ log_target = pa_log_parse_target(m);
+
+ if (!log_target) {
+ pa_strbuf_puts(buf, "Invalid log target.\n");
+ return -1;
+ }
+ }
+
+ if (pa_log_set_target(log_target) < 0) {
+ pa_strbuf_puts(buf, "Failed to set log target.\n");
+ pa_log_target_free(log_target);
+ return -1;
+ }
+
+ pa_log_target_free(log_target);
+
+ return 0;
+}
+
+static int pa_cli_command_log_level(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ const char *m;
+ uint32_t level;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ if (!(m = pa_tokenizer_get(t, 1))) {
+ pa_strbuf_puts(buf, "You need to specify a log level (0..4).\n");
+ return -1;
+ }
+
+ if (pa_atou(m, &level) < 0 || level >= PA_LOG_LEVEL_MAX) {
+ pa_strbuf_puts(buf, "Failed to parse log level.\n");
+ return -1;
+ }
+
+ pa_log_set_level(level);
+
+ return 0;
+}
+
+static int pa_cli_command_log_meta(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ const char *m;
+ int b;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ if (!(m = pa_tokenizer_get(t, 1))) {
+ pa_strbuf_puts(buf, "You need to specify a boolean.\n");
+ return -1;
+ }
+
+ if ((b = pa_parse_boolean(m)) < 0) {
+ pa_strbuf_puts(buf, "Failed to parse log meta switch.\n");
+ return -1;
+ }
+
+ pa_log_set_flags(PA_LOG_PRINT_META, b ? PA_LOG_SET : PA_LOG_UNSET);
+
+ return 0;
+}
+
+static int pa_cli_command_log_time(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ const char *m;
+ int b;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ if (!(m = pa_tokenizer_get(t, 1))) {
+ pa_strbuf_puts(buf, "You need to specify a boolean.\n");
+ return -1;
+ }
+
+ if ((b = pa_parse_boolean(m)) < 0) {
+ pa_strbuf_puts(buf, "Failed to parse log meta switch.\n");
+ return -1;
+ }
+
+ pa_log_set_flags(PA_LOG_PRINT_TIME, b ? PA_LOG_SET : PA_LOG_UNSET);
+
+ return 0;
+}
+
+static int pa_cli_command_log_backtrace(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ const char *m;
+ uint32_t nframes;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ if (!(m = pa_tokenizer_get(t, 1))) {
+ pa_strbuf_puts(buf, "You need to specify a backtrace level.\n");
+ return -1;
+ }
+
+ if (pa_atou(m, &nframes) < 0 || nframes >= 1000) {
+ pa_strbuf_puts(buf, "Failed to parse backtrace level.\n");
+ return -1;
+ }
+
+ pa_log_set_show_backtrace(nframes);
+
+ return 0;
+}
+
+static int pa_cli_command_card_profile(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ const char *n, *p;
+ pa_card *card;
+ pa_card_profile *profile;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ if (!(n = pa_tokenizer_get(t, 1))) {
+ pa_strbuf_puts(buf, "You need to specify a card either by its name or its index.\n");
+ return -1;
+ }
+
+ if (!(p = pa_tokenizer_get(t, 2))) {
+ pa_strbuf_puts(buf, "You need to specify a profile by its name.\n");
+ return -1;
+ }
+
+ if (!(card = pa_namereg_get(c, n, PA_NAMEREG_CARD))) {
+ pa_strbuf_puts(buf, "No card found by this name or index.\n");
+ return -1;
+ }
+
+ if (!(profile = pa_hashmap_get(card->profiles, p))) {
+ pa_strbuf_printf(buf, "No such profile: %s\n", p);
+ return -1;
+ }
+
+ if (pa_card_set_profile(card, profile, true) < 0) {
+ pa_strbuf_printf(buf, "Failed to set card profile to '%s'.\n", p);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int pa_cli_command_sink_port(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ const char *n, *p;
+ pa_sink *sink;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ if (!(n = pa_tokenizer_get(t, 1))) {
+ pa_strbuf_puts(buf, "You need to specify a sink either by its name or its index.\n");
+ return -1;
+ }
+
+ if (!(p = pa_tokenizer_get(t, 2))) {
+ pa_strbuf_puts(buf, "You need to specify a profile by its name.\n");
+ return -1;
+ }
+
+ if (!(sink = pa_namereg_get(c, n, PA_NAMEREG_SINK))) {
+ pa_strbuf_puts(buf, "No sink found by this name or index.\n");
+ return -1;
+ }
+
+ if (pa_sink_set_port(sink, p, true) < 0) {
+ pa_strbuf_printf(buf, "Failed to set sink port to '%s'.\n", p);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int pa_cli_command_source_port(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ const char *n, *p;
+ pa_source *source;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ if (!(n = pa_tokenizer_get(t, 1))) {
+ pa_strbuf_puts(buf, "You need to specify a source either by its name or its index.\n");
+ return -1;
+ }
+
+ if (!(p = pa_tokenizer_get(t, 2))) {
+ pa_strbuf_puts(buf, "You need to specify a profile by its name.\n");
+ return -1;
+ }
+
+ if (!(source = pa_namereg_get(c, n, PA_NAMEREG_SOURCE))) {
+ pa_strbuf_puts(buf, "No source found by this name or index.\n");
+ return -1;
+ }
+
+ if (pa_source_set_port(source, p, true) < 0) {
+ pa_strbuf_printf(buf, "Failed to set source port to '%s'.\n", p);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int pa_cli_command_port_offset(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ const char *n, *p, *l;
+ pa_device_port *port;
+ pa_card *card;
+ int32_t offset;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ if (!(n = pa_tokenizer_get(t, 1))) {
+ pa_strbuf_puts(buf, "You need to specify a card either by its name or its index.\n");
+ return -1;
+ }
+
+ if (!(p = pa_tokenizer_get(t, 2))) {
+ pa_strbuf_puts(buf, "You need to specify a port by its name.\n");
+ return -1;
+ }
+
+ if (!(l = pa_tokenizer_get(t, 3))) {
+ pa_strbuf_puts(buf, "You need to specify a latency offset.\n");
+ return -1;
+ }
+
+ if (pa_atoi(l, &offset) < 0) {
+ pa_strbuf_puts(buf, "Failed to parse the latency offset.\n");
+ return -1;
+ }
+
+ if (!(card = pa_namereg_get(c, n, PA_NAMEREG_CARD))) {
+ pa_strbuf_puts(buf, "No card found by this name or index.\n");
+ return -1;
+ }
+
+ if (!(port = pa_hashmap_get(card->ports, p))) {
+ pa_strbuf_puts(buf, "No port found by this name.\n");
+ return -1;
+ }
+
+ pa_device_port_set_latency_offset(port, offset);
+
+ return 0;
+}
+
+static int pa_cli_command_dump(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ pa_module *m;
+ pa_sink *sink;
+ pa_source *source;
+ pa_card *card;
+ bool nl;
+ uint32_t idx;
+ time_t now;
+#ifdef HAVE_CTIME_R
+ char txt[256];
+#endif
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ time(&now);
+
+#ifdef HAVE_CTIME_R
+ pa_strbuf_printf(buf, "### Configuration dump generated at %s\n", ctime_r(&now, txt));
+#else
+ pa_strbuf_printf(buf, "### Configuration dump generated at %s\n", ctime(&now));
+#endif
+
+ PA_IDXSET_FOREACH(m, c->modules, idx) {
+
+ pa_strbuf_printf(buf, "load-module %s", m->name);
+
+ if (m->argument)
+ pa_strbuf_printf(buf, " %s", m->argument);
+
+ pa_strbuf_puts(buf, "\n");
+ }
+
+ nl = false;
+ PA_IDXSET_FOREACH(sink, c->sinks, idx) {
+
+ if (!nl) {
+ pa_strbuf_puts(buf, "\n");
+ nl = true;
+ }
+
+ pa_strbuf_printf(buf, "set-sink-volume %s 0x%03x\n", sink->name, pa_cvolume_max(pa_sink_get_volume(sink, false)));
+ pa_strbuf_printf(buf, "set-sink-mute %s %s\n", sink->name, pa_yes_no(pa_sink_get_mute(sink, false)));
+ pa_strbuf_printf(buf, "suspend-sink %s %s\n", sink->name, pa_yes_no(sink->state == PA_SINK_SUSPENDED));
+ }
+
+ nl = false;
+ PA_IDXSET_FOREACH(source, c->sources, idx) {
+
+ if (!nl) {
+ pa_strbuf_puts(buf, "\n");
+ nl = true;
+ }
+
+ pa_strbuf_printf(buf, "set-source-volume %s 0x%03x\n", source->name, pa_cvolume_max(pa_source_get_volume(source, false)));
+ pa_strbuf_printf(buf, "set-source-mute %s %s\n", source->name, pa_yes_no(pa_source_get_mute(source, false)));
+ pa_strbuf_printf(buf, "suspend-source %s %s\n", source->name, pa_yes_no(source->state == PA_SOURCE_SUSPENDED));
+ }
+
+ nl = false;
+ PA_IDXSET_FOREACH(card, c->cards, idx) {
+
+ if (!nl) {
+ pa_strbuf_puts(buf, "\n");
+ nl = true;
+ }
+
+ pa_strbuf_printf(buf, "set-card-profile %s %s\n", card->name, card->active_profile->name);
+ }
+
+ nl = false;
+ if (c->default_sink) {
+ if (!nl) {
+ pa_strbuf_puts(buf, "\n");
+ nl = true;
+ }
+
+ pa_strbuf_printf(buf, "set-default-sink %s\n", c->default_sink->name);
+ }
+
+ if (c->default_source) {
+ if (!nl)
+ pa_strbuf_puts(buf, "\n");
+
+ pa_strbuf_printf(buf, "set-default-source %s\n", c->default_source->name);
+ }
+
+ pa_strbuf_puts(buf, "\n### EOF\n");
+
+ return 0;
+}
+
+static int pa_cli_command_dump_volumes(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
+ pa_sink *s;
+ pa_source *so;
+ pa_sink_input *i;
+ pa_source_output *o;
+ uint32_t s_idx, i_idx;
+ char v_str[PA_CVOLUME_SNPRINT_VERBOSE_MAX];
+ pa_channel_map *map;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ PA_IDXSET_FOREACH(s, c->sinks, s_idx) {
+ map = &s->channel_map;
+ pa_strbuf_printf(buf, "Sink %d: ", s_idx);
+ pa_strbuf_printf(buf,
+ "reference = %s, ",
+ pa_cvolume_snprint_verbose(v_str,
+ sizeof(v_str),
+ &s->reference_volume,
+ map,
+ s->flags & PA_SINK_DECIBEL_VOLUME));
+ pa_strbuf_printf(buf,
+ "real = %s, ",
+ pa_cvolume_snprint_verbose(v_str,
+ sizeof(v_str),
+ &s->real_volume,
+ &s->channel_map,
+ s->flags & PA_SINK_DECIBEL_VOLUME));
+ pa_strbuf_printf(buf, "soft = %s, ", pa_cvolume_snprint_verbose(v_str, sizeof(v_str), &s->soft_volume, map, true));
+ pa_strbuf_printf(buf,
+ "current_hw = %s, ",
+ pa_cvolume_snprint_verbose(v_str,
+ sizeof(v_str),
+ &s->thread_info.current_hw_volume,
+ map,
+ s->flags & PA_SINK_DECIBEL_VOLUME));
+ pa_strbuf_printf(buf, "save = %s\n", pa_yes_no(s->save_volume));
+
+ PA_IDXSET_FOREACH(i, s->inputs, i_idx) {
+ map = &i->channel_map;
+ pa_strbuf_printf(buf, "\tInput %d: ", i_idx);
+ pa_strbuf_printf(buf, "volume = %s, ", pa_cvolume_snprint_verbose(v_str, sizeof(v_str), &i->volume, map, true));
+ pa_strbuf_printf(buf,
+ "reference_ratio = %s, ",
+ pa_cvolume_snprint_verbose(v_str, sizeof(v_str), &i->reference_ratio, map, true));
+ pa_strbuf_printf(buf,
+ "real_ratio = %s, ",
+ pa_cvolume_snprint_verbose(v_str, sizeof(v_str), &i->real_ratio, map, true));
+ pa_strbuf_printf(buf, "soft = %s, ", pa_cvolume_snprint_verbose(v_str, sizeof(v_str), &i->soft_volume, map, true));
+ pa_strbuf_printf(buf,
+ "volume_factor = %s, ",
+ pa_cvolume_snprint_verbose(v_str, sizeof(v_str), &i->volume_factor, map, true));
+ pa_strbuf_printf(buf,
+ "volume_factor_sink = %s, ",
+ pa_cvolume_snprint_verbose(v_str,
+ sizeof(v_str),
+ &i->volume_factor_sink,
+ &i->sink->channel_map,
+ true));
+ pa_strbuf_printf(buf, "save = %s\n", pa_yes_no(i->save_volume));
+ }
+ }
+
+ PA_IDXSET_FOREACH(so, c->sources, s_idx) {
+ map = &so->channel_map;
+ pa_strbuf_printf(buf, "Source %d: ", s_idx);
+ pa_strbuf_printf(buf,
+ "reference = %s, ",
+ pa_cvolume_snprint_verbose(v_str,
+ sizeof(v_str),
+ &so->reference_volume,
+ map,
+ so->flags & PA_SOURCE_DECIBEL_VOLUME));
+ pa_strbuf_printf(buf,
+ "real = %s, ",
+ pa_cvolume_snprint_verbose(v_str,
+ sizeof(v_str),
+ &so->real_volume,
+ map,
+ so->flags & PA_SOURCE_DECIBEL_VOLUME));
+ pa_strbuf_printf(buf, "soft = %s, ", pa_cvolume_snprint_verbose(v_str, sizeof(v_str), &so->soft_volume, map, true));
+ pa_strbuf_printf(buf,
+ "current_hw = %s, ",
+ pa_cvolume_snprint_verbose(v_str,
+ sizeof(v_str),
+ &so->thread_info.current_hw_volume,
+ map,
+ so->flags & PA_SOURCE_DECIBEL_VOLUME));
+ pa_strbuf_printf(buf, "save = %s\n", pa_yes_no(so->save_volume));
+
+ PA_IDXSET_FOREACH(o, so->outputs, i_idx) {
+ map = &o->channel_map;
+ pa_strbuf_printf(buf, "\tOutput %d: ", i_idx);
+ pa_strbuf_printf(buf, "volume = %s, ", pa_cvolume_snprint_verbose(v_str, sizeof(v_str), &o->volume, map, true));
+ pa_strbuf_printf(buf,
+ "reference_ratio = %s, ",
+ pa_cvolume_snprint_verbose(v_str, sizeof(v_str), &o->reference_ratio, map, true));
+ pa_strbuf_printf(buf,
+ "real_ratio = %s, ",
+ pa_cvolume_snprint_verbose(v_str, sizeof(v_str), &o->real_ratio, map, true));
+ pa_strbuf_printf(buf, "soft = %s, ", pa_cvolume_snprint_verbose(v_str, sizeof(v_str), &o->soft_volume, map, true));
+ pa_strbuf_printf(buf,
+ "volume_factor = %s, ",
+ pa_cvolume_snprint_verbose(v_str, sizeof(v_str), &o->volume_factor, map, true));
+ pa_strbuf_printf(buf,
+ "volume_factor_source = %s, ",
+ pa_cvolume_snprint_verbose(v_str,
+ sizeof(v_str),
+ &o->volume_factor_source,
+ &o->source->channel_map,
+ true));
+ pa_strbuf_printf(buf, "save = %s\n", pa_yes_no(o->save_volume));
+ }
+ }
+
+ return 0;
+}
+
+int pa_cli_command_execute_line_stateful(pa_core *c, const char *s, pa_strbuf *buf, bool *fail, int *ifstate) {
+ const char *cs;
+
+ pa_assert(c);
+ pa_assert(s);
+ pa_assert(buf);
+
+ cs = s+strspn(s, whitespace);
+
+ if (*cs == '#' || !*cs)
+ return 0;
+ else if (*cs == '.') {
+ if (!strcmp(cs, META_ELSE)) {
+ if (!ifstate || *ifstate == IFSTATE_NONE) {
+ pa_strbuf_printf(buf, "Meta command %s is not valid in this context\n", cs);
+ return -1;
+ } else if (*ifstate == IFSTATE_TRUE)
+ *ifstate = IFSTATE_FALSE;
+ else
+ *ifstate = IFSTATE_TRUE;
+ return 0;
+ } else if (!strcmp(cs, META_ENDIF)) {
+ if (!ifstate || *ifstate == IFSTATE_NONE) {
+ pa_strbuf_printf(buf, "Meta command %s is not valid in this context\n", cs);
+ return -1;
+ } else
+ *ifstate = IFSTATE_NONE;
+ return 0;
+ }
+ if (ifstate && *ifstate == IFSTATE_FALSE)
+ return 0;
+ if (!strcmp(cs, META_FAIL))
+ *fail = true;
+ else if (!strcmp(cs, META_NOFAIL))
+ *fail = false;
+ else {
+ size_t l;
+ l = strcspn(cs, whitespace);
+
+ if (l == sizeof(META_INCLUDE)-1 && !strncmp(cs, META_INCLUDE, l)) {
+ struct stat st;
+ const char *filename = cs+l+strspn(cs+l, whitespace);
+
+ if (stat(filename, &st) < 0) {
+ pa_log_warn("stat('%s'): %s", filename, pa_cstrerror(errno));
+ if (*fail)
+ return -1;
+ } else {
+ if (S_ISDIR(st.st_mode)) {
+ DIR *d;
+
+ if (!(d = opendir(filename))) {
+ pa_log_warn("Failed to read '%s': %s", filename, pa_cstrerror(errno));
+ if (*fail)
+ return -1;
+ } else {
+ unsigned i, count;
+ char **sorted_files;
+ struct dirent *de;
+ bool failed = false;
+ pa_dynarray *files = pa_dynarray_new(NULL);
+
+ while ((de = readdir(d))) {
+ char *extn;
+ size_t flen = strlen(de->d_name);
+
+ if (flen < 4)
+ continue;
+
+ extn = &de->d_name[flen-3];
+ if (strncmp(extn, ".pa", 3) == 0)
+ pa_dynarray_append(files, pa_sprintf_malloc("%s" PA_PATH_SEP "%s", filename, de->d_name));
+ }
+
+ closedir(d);
+ if ((count = pa_dynarray_size(files))) {
+ sorted_files = pa_xnew(char*, count);
+ for (i = 0; i < count; ++i)
+ sorted_files[i] = pa_dynarray_get(files, i);
+ pa_dynarray_free(files);
+
+ for (i = 0; i < count; ++i) {
+ for (unsigned j = 0; j < count; ++j) {
+ if (strcmp(sorted_files[i], sorted_files[j]) < 0) {
+ char *tmp = sorted_files[i];
+ sorted_files[i] = sorted_files[j];
+ sorted_files[j] = tmp;
+ }
+ }
+ }
+
+ for (i = 0; i < count; ++i) {
+ if (!failed) {
+ if (pa_cli_command_execute_file(c, sorted_files[i], buf, fail) < 0 && *fail)
+ failed = true;
+ }
+
+ pa_xfree(sorted_files[i]);
+ }
+ pa_xfree(sorted_files);
+ if (failed)
+ return -1;
+ }
+ }
+ } else if (pa_cli_command_execute_file(c, filename, buf, fail) < 0 && *fail) {
+ return -1;
+ }
+ }
+ } else if (l == sizeof(META_IFEXISTS)-1 && !strncmp(cs, META_IFEXISTS, l)) {
+ if (!ifstate) {
+ pa_strbuf_printf(buf, "Meta command %s is not valid in this context\n", cs);
+ return -1;
+ } else if (*ifstate != IFSTATE_NONE) {
+ pa_strbuf_printf(buf, "Nested %s commands not supported\n", cs);
+ return -1;
+ } else {
+ const char *filename = cs+l+strspn(cs+l, whitespace);
+ *ifstate = pa_module_exists(filename) ? IFSTATE_TRUE : IFSTATE_FALSE;
+ }
+ } else {
+ pa_strbuf_printf(buf, "Invalid meta command: %s\n", cs);
+ if (*fail) return -1;
+ }
+ }
+ } else {
+ const struct command*command;
+ int unknown = 1;
+ size_t l;
+
+ if (ifstate && *ifstate == IFSTATE_FALSE)
+ return 0;
+
+ l = strcspn(cs, whitespace);
+
+ for (command = commands; command->name; command++)
+ if (strlen(command->name) == l && !strncmp(cs, command->name, l)) {
+ int ret;
+ pa_tokenizer *t = pa_tokenizer_new(cs, command->args);
+ pa_assert(t);
+ ret = command->proc(c, t, buf, fail);
+ pa_tokenizer_free(t);
+ unknown = 0;
+
+ if (ret < 0 && *fail)
+ return -1;
+
+ break;
+ }
+
+ if (unknown) {
+ pa_strbuf_printf(buf, "Unknown command: %s\n", cs);
+ if (*fail)
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int pa_cli_command_execute_line(pa_core *c, const char *s, pa_strbuf *buf, bool *fail) {
+ return pa_cli_command_execute_line_stateful(c, s, buf, fail, NULL);
+}
+
+int pa_cli_command_execute_file_stream(pa_core *c, FILE *f, pa_strbuf *buf, bool *fail) {
+ char line[2048];
+ int ifstate = IFSTATE_NONE;
+ int ret = -1;
+ bool _fail = true;
+
+ pa_assert(c);
+ pa_assert(f);
+ pa_assert(buf);
+
+ if (!fail)
+ fail = &_fail;
+
+ while (fgets(line, sizeof(line), f)) {
+ pa_strip_nl(line);
+
+ if (pa_cli_command_execute_line_stateful(c, line, buf, fail, &ifstate) < 0 && *fail)
+ goto fail;
+ }
+
+ ret = 0;
+
+fail:
+
+ return ret;
+}
+
+int pa_cli_command_execute_file(pa_core *c, const char *fn, pa_strbuf *buf, bool *fail) {
+ FILE *f = NULL;
+ int ret = -1;
+ bool _fail = true;
+
+ pa_assert(c);
+ pa_assert(fn);
+ pa_assert(buf);
+
+ if (!fail)
+ fail = &_fail;
+
+ if (!(f = pa_fopen_cloexec(fn, "r"))) {
+ pa_strbuf_printf(buf, "open('%s') failed: %s\n", fn, pa_cstrerror(errno));
+ if (!*fail)
+ ret = 0;
+ goto fail;
+ }
+
+ pa_log_debug("Parsing script '%s'", fn);
+ ret = pa_cli_command_execute_file_stream(c, f, buf, fail);
+
+fail:
+ if (f)
+ fclose(f);
+
+ return ret;
+}
+
+int pa_cli_command_execute(pa_core *c, const char *s, pa_strbuf *buf, bool *fail) {
+ const char *p;
+ int ifstate = IFSTATE_NONE;
+ bool _fail = true;
+
+ pa_assert(c);
+ pa_assert(s);
+ pa_assert(buf);
+
+ if (!fail)
+ fail = &_fail;
+
+ p = s;
+ while (*p) {
+ size_t l = strcspn(p, linebreak);
+ char *line = pa_xstrndup(p, l);
+
+ if (pa_cli_command_execute_line_stateful(c, line, buf, fail, &ifstate) < 0 && *fail) {
+ pa_xfree(line);
+ return -1;
+ }
+ pa_xfree(line);
+
+ p += l;
+ p += strspn(p, linebreak);
+ }
+
+ return 0;
+}
diff --git a/src/pulsecore/cli-command.h b/src/pulsecore/cli-command.h
new file mode 100644
index 0000000..3513341
--- /dev/null
+++ b/src/pulsecore/cli-command.h
@@ -0,0 +1,44 @@
+#ifndef fooclicommandhfoo
+#define fooclicommandhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulsecore/strbuf.h>
+#include <pulsecore/core.h>
+
+/* Execute a single CLI command. Write the results to the string
+ * buffer *buf. If *fail is non-zero the function will return -1 when
+ * one or more of the executed commands failed. *fail
+ * may be modified by the function call. */
+int pa_cli_command_execute_line(pa_core *c, const char *s, pa_strbuf *buf, bool *fail);
+
+/* Execute a whole file of CLI commands */
+int pa_cli_command_execute_file(pa_core *c, const char *fn, pa_strbuf *buf, bool *fail);
+
+/* Execute a whole file of CLI commands */
+int pa_cli_command_execute_file_stream(pa_core *c, FILE *f, pa_strbuf *buf, bool *fail);
+
+/* Split the specified string into lines and run pa_cli_command_execute_line() for each. */
+int pa_cli_command_execute(pa_core *c, const char *s, pa_strbuf *buf, bool *fail);
+
+/* Same as pa_cli_command_execute_line() but also take ifstate var. */
+int pa_cli_command_execute_line_stateful(pa_core *c, const char *s, pa_strbuf *buf, bool *fail, int *ifstate);
+
+#endif
diff --git a/src/pulsecore/cli-text.c b/src/pulsecore/cli-text.c
new file mode 100644
index 0000000..8f35f36
--- /dev/null
+++ b/src/pulsecore/cli-text.c
@@ -0,0 +1,704 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulse/volume.h>
+#include <pulse/xmalloc.h>
+#include <pulse/timeval.h>
+
+#include <pulsecore/module.h>
+#include <pulsecore/client.h>
+#include <pulsecore/card.h>
+#include <pulsecore/sink.h>
+#include <pulsecore/source.h>
+#include <pulsecore/sink-input.h>
+#include <pulsecore/source-output.h>
+#include <pulsecore/strbuf.h>
+#include <pulsecore/core-scache.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/namereg.h>
+
+#include "cli-text.h"
+
+char *pa_module_list_to_string(pa_core *c) {
+ pa_strbuf *s;
+ pa_module *m;
+ uint32_t idx = PA_IDXSET_INVALID;
+ pa_assert(c);
+
+ s = pa_strbuf_new();
+
+ pa_strbuf_printf(s, "%u module(s) loaded.\n", pa_idxset_size(c->modules));
+
+ PA_IDXSET_FOREACH(m, c->modules, idx) {
+ char *t;
+
+ pa_strbuf_printf(s, " index: %u\n"
+ "\tname: <%s>\n"
+ "\targument: <%s>\n"
+ "\tused: %i\n"
+ "\tload once: %s\n",
+ m->index,
+ m->name,
+ pa_strempty(m->argument),
+ pa_module_get_n_used(m),
+ pa_yes_no(m->load_once));
+
+ t = pa_proplist_to_string_sep(m->proplist, "\n\t\t");
+ pa_strbuf_printf(s, "\tproperties:\n\t\t%s\n", t);
+ pa_xfree(t);
+ }
+
+ return pa_strbuf_to_string_free(s);
+}
+
+char *pa_client_list_to_string(pa_core *c) {
+ pa_strbuf *s;
+ pa_client *client;
+ uint32_t idx = PA_IDXSET_INVALID;
+ pa_assert(c);
+
+ s = pa_strbuf_new();
+
+ pa_strbuf_printf(s, "%u client(s) logged in.\n", pa_idxset_size(c->clients));
+
+ PA_IDXSET_FOREACH(client, c->clients, idx) {
+ char *t;
+ pa_strbuf_printf(
+ s,
+ " index: %u\n"
+ "\tdriver: <%s>\n",
+ client->index,
+ client->driver);
+
+ if (client->module)
+ pa_strbuf_printf(s, "\towner module: %u\n", client->module->index);
+
+ t = pa_proplist_to_string_sep(client->proplist, "\n\t\t");
+ pa_strbuf_printf(s, "\tproperties:\n\t\t%s\n", t);
+ pa_xfree(t);
+ }
+
+ return pa_strbuf_to_string_free(s);
+}
+
+static void append_port_list(pa_strbuf *s, pa_hashmap *ports) {
+ pa_device_port *p;
+ void *state;
+
+ pa_assert(ports);
+
+ if (pa_hashmap_isempty(ports))
+ return;
+
+ pa_strbuf_puts(s, "\tports:\n");
+ PA_HASHMAP_FOREACH(p, ports, state) {
+ char *t = pa_proplist_to_string_sep(p->proplist, "\n\t\t\t\t");
+ pa_strbuf_printf(s, "\t\t%s: %s (priority %u, latency offset %" PRId64 " usec, available: %s)\n",
+ p->name, p->description, p->priority, p->latency_offset,
+ pa_available_to_string(p->available));
+ pa_strbuf_printf(s, "\t\t\tproperties:\n\t\t\t\t%s\n", t);
+ pa_xfree(t);
+ }
+}
+
+char *pa_card_list_to_string(pa_core *c) {
+ pa_strbuf *s;
+ pa_card *card;
+ uint32_t idx = PA_IDXSET_INVALID;
+ pa_assert(c);
+
+ s = pa_strbuf_new();
+
+ pa_strbuf_printf(s, "%u card(s) available.\n", pa_idxset_size(c->cards));
+
+ PA_IDXSET_FOREACH(card, c->cards, idx) {
+ char *t;
+ pa_sink *sink;
+ pa_source *source;
+ uint32_t sidx;
+ pa_card_profile *profile;
+ void *state;
+
+ pa_strbuf_printf(
+ s,
+ " index: %u\n"
+ "\tname: <%s>\n"
+ "\tdriver: <%s>\n",
+ card->index,
+ card->name,
+ card->driver);
+
+ if (card->module)
+ pa_strbuf_printf(s, "\towner module: %u\n", card->module->index);
+
+ t = pa_proplist_to_string_sep(card->proplist, "\n\t\t");
+ pa_strbuf_printf(s, "\tproperties:\n\t\t%s\n", t);
+ pa_xfree(t);
+
+ pa_strbuf_puts(s, "\tprofiles:\n");
+ PA_HASHMAP_FOREACH(profile, card->profiles, state)
+ pa_strbuf_printf(s, "\t\t%s: %s (priority %u, available: %s)\n", profile->name, profile->description,
+ profile->priority, pa_available_to_string(profile->available));
+
+ pa_strbuf_printf(
+ s,
+ "\tactive profile: <%s>\n",
+ card->active_profile->name);
+
+ if (!pa_idxset_isempty(card->sinks)) {
+ pa_strbuf_puts(s, "\tsinks:\n");
+ PA_IDXSET_FOREACH(sink, card->sinks, sidx)
+ pa_strbuf_printf(s, "\t\t%s/#%u: %s\n", sink->name, sink->index, pa_strna(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_DESCRIPTION)));
+ }
+
+ if (!pa_idxset_isempty(card->sources)) {
+ pa_strbuf_puts(s, "\tsources:\n");
+ PA_IDXSET_FOREACH(source, card->sources, sidx)
+ pa_strbuf_printf(s, "\t\t%s/#%u: %s\n", source->name, source->index, pa_strna(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_DESCRIPTION)));
+ }
+
+ append_port_list(s, card->ports);
+ }
+
+ return pa_strbuf_to_string_free(s);
+}
+
+char *pa_sink_list_to_string(pa_core *c) {
+ pa_strbuf *s;
+ pa_sink *sink;
+ uint32_t idx = PA_IDXSET_INVALID;
+ pa_assert(c);
+
+ s = pa_strbuf_new();
+
+ pa_strbuf_printf(s, "%u sink(s) available.\n", pa_idxset_size(c->sinks));
+
+ PA_IDXSET_FOREACH(sink, c->sinks, idx) {
+ char ss[PA_SAMPLE_SPEC_SNPRINT_MAX],
+ cv[PA_CVOLUME_SNPRINT_VERBOSE_MAX],
+ v[PA_VOLUME_SNPRINT_VERBOSE_MAX],
+ cm[PA_CHANNEL_MAP_SNPRINT_MAX], *t;
+ const char *cmn;
+ char suspend_cause_buf[PA_SUSPEND_CAUSE_TO_STRING_BUF_SIZE];
+
+ cmn = pa_channel_map_to_pretty_name(&sink->channel_map);
+
+ pa_strbuf_printf(
+ s,
+ " %c index: %u\n"
+ "\tname: <%s>\n"
+ "\tdriver: <%s>\n"
+ "\tflags: %s%s%s%s%s%s%s%s\n"
+ "\tstate: %s\n"
+ "\tsuspend cause: %s\n"
+ "\tpriority: %u\n"
+ "\tvolume: %s\n"
+ "\t balance %0.2f\n"
+ "\tbase volume: %s\n"
+ "\tvolume steps: %u\n"
+ "\tmuted: %s\n"
+ "\tcurrent latency: %0.2f ms\n"
+ "\tmax request: %lu KiB\n"
+ "\tmax rewind: %lu KiB\n"
+ "\tmonitor source: %u\n"
+ "\tsample spec: %s\n"
+ "\tchannel map: %s%s%s\n"
+ "\tused by: %u\n"
+ "\tlinked by: %u\n",
+ sink == c->default_sink ? '*' : ' ',
+ sink->index,
+ sink->name,
+ sink->driver,
+ sink->flags & PA_SINK_HARDWARE ? "HARDWARE " : "",
+ sink->flags & PA_SINK_NETWORK ? "NETWORK " : "",
+ sink->flags & PA_SINK_HW_MUTE_CTRL ? "HW_MUTE_CTRL " : "",
+ sink->flags & PA_SINK_HW_VOLUME_CTRL ? "HW_VOLUME_CTRL " : "",
+ sink->flags & PA_SINK_DECIBEL_VOLUME ? "DECIBEL_VOLUME " : "",
+ sink->flags & PA_SINK_LATENCY ? "LATENCY " : "",
+ sink->flags & PA_SINK_FLAT_VOLUME ? "FLAT_VOLUME " : "",
+ sink->flags & PA_SINK_DYNAMIC_LATENCY ? "DYNAMIC_LATENCY" : "",
+ pa_sink_state_to_string(sink->state),
+ pa_suspend_cause_to_string(sink->suspend_cause, suspend_cause_buf),
+ sink->priority,
+ pa_cvolume_snprint_verbose(cv,
+ sizeof(cv),
+ pa_sink_get_volume(sink, false),
+ &sink->channel_map,
+ sink->flags & PA_SINK_DECIBEL_VOLUME),
+ pa_cvolume_get_balance(pa_sink_get_volume(sink, false), &sink->channel_map),
+ pa_volume_snprint_verbose(v, sizeof(v), sink->base_volume, sink->flags & PA_SINK_DECIBEL_VOLUME),
+ sink->n_volume_steps,
+ pa_yes_no(pa_sink_get_mute(sink, false)),
+ (double) pa_sink_get_latency(sink) / (double) PA_USEC_PER_MSEC,
+ (unsigned long) pa_sink_get_max_request(sink) / 1024,
+ (unsigned long) pa_sink_get_max_rewind(sink) / 1024,
+ sink->monitor_source ? sink->monitor_source->index : PA_INVALID_INDEX,
+ pa_sample_spec_snprint(ss, sizeof(ss), &sink->sample_spec),
+ pa_channel_map_snprint(cm, sizeof(cm), &sink->channel_map),
+ cmn ? "\n\t " : "",
+ cmn ? cmn : "",
+ pa_sink_used_by(sink),
+ pa_sink_linked_by(sink));
+
+ if (sink->flags & PA_SINK_DYNAMIC_LATENCY) {
+ pa_usec_t min_latency, max_latency;
+ pa_sink_get_latency_range(sink, &min_latency, &max_latency);
+
+ pa_strbuf_printf(
+ s,
+ "\tconfigured latency: %0.2f ms; range is %0.2f .. %0.2f ms\n",
+ (double) pa_sink_get_requested_latency(sink) / (double) PA_USEC_PER_MSEC,
+ (double) min_latency / PA_USEC_PER_MSEC,
+ (double) max_latency / PA_USEC_PER_MSEC);
+ } else
+ pa_strbuf_printf(
+ s,
+ "\tfixed latency: %0.2f ms\n",
+ (double) pa_sink_get_fixed_latency(sink) / PA_USEC_PER_MSEC);
+
+ if (sink->card)
+ pa_strbuf_printf(s, "\tcard: %u <%s>\n", sink->card->index, sink->card->name);
+ if (sink->module)
+ pa_strbuf_printf(s, "\tmodule: %u\n", sink->module->index);
+
+ t = pa_proplist_to_string_sep(sink->proplist, "\n\t\t");
+ pa_strbuf_printf(s, "\tproperties:\n\t\t%s\n", t);
+ pa_xfree(t);
+
+ append_port_list(s, sink->ports);
+
+ if (sink->active_port)
+ pa_strbuf_printf(
+ s,
+ "\tactive port: <%s>\n",
+ sink->active_port->name);
+ }
+
+ return pa_strbuf_to_string_free(s);
+}
+
+char *pa_source_list_to_string(pa_core *c) {
+ pa_strbuf *s;
+ pa_source *source;
+ uint32_t idx = PA_IDXSET_INVALID;
+ pa_assert(c);
+
+ s = pa_strbuf_new();
+
+ pa_strbuf_printf(s, "%u source(s) available.\n", pa_idxset_size(c->sources));
+
+ PA_IDXSET_FOREACH(source, c->sources, idx) {
+ char ss[PA_SAMPLE_SPEC_SNPRINT_MAX],
+ cv[PA_CVOLUME_SNPRINT_VERBOSE_MAX],
+ v[PA_VOLUME_SNPRINT_VERBOSE_MAX],
+ cm[PA_CHANNEL_MAP_SNPRINT_MAX], *t;
+ const char *cmn;
+ char suspend_cause_buf[PA_SUSPEND_CAUSE_TO_STRING_BUF_SIZE];
+
+ cmn = pa_channel_map_to_pretty_name(&source->channel_map);
+
+ pa_strbuf_printf(
+ s,
+ " %c index: %u\n"
+ "\tname: <%s>\n"
+ "\tdriver: <%s>\n"
+ "\tflags: %s%s%s%s%s%s%s\n"
+ "\tstate: %s\n"
+ "\tsuspend cause: %s\n"
+ "\tpriority: %u\n"
+ "\tvolume: %s\n"
+ "\t balance %0.2f\n"
+ "\tbase volume: %s\n"
+ "\tvolume steps: %u\n"
+ "\tmuted: %s\n"
+ "\tcurrent latency: %0.2f ms\n"
+ "\tmax rewind: %lu KiB\n"
+ "\tsample spec: %s\n"
+ "\tchannel map: %s%s%s\n"
+ "\tused by: %u\n"
+ "\tlinked by: %u\n",
+ source == c->default_source ? '*' : ' ',
+ source->index,
+ source->name,
+ source->driver,
+ source->flags & PA_SOURCE_HARDWARE ? "HARDWARE " : "",
+ source->flags & PA_SOURCE_NETWORK ? "NETWORK " : "",
+ source->flags & PA_SOURCE_HW_MUTE_CTRL ? "HW_MUTE_CTRL " : "",
+ source->flags & PA_SOURCE_HW_VOLUME_CTRL ? "HW_VOLUME_CTRL " : "",
+ source->flags & PA_SOURCE_DECIBEL_VOLUME ? "DECIBEL_VOLUME " : "",
+ source->flags & PA_SOURCE_LATENCY ? "LATENCY " : "",
+ source->flags & PA_SOURCE_DYNAMIC_LATENCY ? "DYNAMIC_LATENCY" : "",
+ pa_source_state_to_string(source->state),
+ pa_suspend_cause_to_string(source->suspend_cause, suspend_cause_buf),
+ source->priority,
+ pa_cvolume_snprint_verbose(cv,
+ sizeof(cv),
+ pa_source_get_volume(source, false),
+ &source->channel_map,
+ source->flags & PA_SOURCE_DECIBEL_VOLUME),
+ pa_cvolume_get_balance(pa_source_get_volume(source, false), &source->channel_map),
+ pa_volume_snprint_verbose(v, sizeof(v), source->base_volume, source->flags & PA_SOURCE_DECIBEL_VOLUME),
+ source->n_volume_steps,
+ pa_yes_no(pa_source_get_mute(source, false)),
+ (double) pa_source_get_latency(source) / PA_USEC_PER_MSEC,
+ (unsigned long) pa_source_get_max_rewind(source) / 1024,
+ pa_sample_spec_snprint(ss, sizeof(ss), &source->sample_spec),
+ pa_channel_map_snprint(cm, sizeof(cm), &source->channel_map),
+ cmn ? "\n\t " : "",
+ cmn ? cmn : "",
+ pa_source_used_by(source),
+ pa_source_linked_by(source));
+
+ if (source->flags & PA_SOURCE_DYNAMIC_LATENCY) {
+ pa_usec_t min_latency, max_latency;
+ pa_source_get_latency_range(source, &min_latency, &max_latency);
+
+ pa_strbuf_printf(
+ s,
+ "\tconfigured latency: %0.2f ms; range is %0.2f .. %0.2f ms\n",
+ (double) pa_source_get_requested_latency(source) / PA_USEC_PER_MSEC,
+ (double) min_latency / PA_USEC_PER_MSEC,
+ (double) max_latency / PA_USEC_PER_MSEC);
+ } else
+ pa_strbuf_printf(
+ s,
+ "\tfixed latency: %0.2f ms\n",
+ (double) pa_source_get_fixed_latency(source) / PA_USEC_PER_MSEC);
+
+ if (source->monitor_of)
+ pa_strbuf_printf(s, "\tmonitor_of: %u\n", source->monitor_of->index);
+ if (source->card)
+ pa_strbuf_printf(s, "\tcard: %u <%s>\n", source->card->index, source->card->name);
+ if (source->module)
+ pa_strbuf_printf(s, "\tmodule: %u\n", source->module->index);
+
+ t = pa_proplist_to_string_sep(source->proplist, "\n\t\t");
+ pa_strbuf_printf(s, "\tproperties:\n\t\t%s\n", t);
+ pa_xfree(t);
+
+ append_port_list(s, source->ports);
+
+ if (source->active_port)
+ pa_strbuf_printf(
+ s,
+ "\tactive port: <%s>\n",
+ source->active_port->name);
+ }
+
+ return pa_strbuf_to_string_free(s);
+}
+
+char *pa_source_output_list_to_string(pa_core *c) {
+ pa_strbuf *s;
+ pa_source_output *o;
+ uint32_t idx = PA_IDXSET_INVALID;
+ static const char* const state_table[] = {
+ [PA_SOURCE_OUTPUT_INIT] = "INIT",
+ [PA_SOURCE_OUTPUT_RUNNING] = "RUNNING",
+ [PA_SOURCE_OUTPUT_CORKED] = "CORKED",
+ [PA_SOURCE_OUTPUT_UNLINKED] = "UNLINKED"
+ };
+ pa_assert(c);
+
+ s = pa_strbuf_new();
+
+ pa_strbuf_printf(s, "%u source output(s) available.\n", pa_idxset_size(c->source_outputs));
+
+ PA_IDXSET_FOREACH(o, c->source_outputs, idx) {
+ char ss[PA_SAMPLE_SPEC_SNPRINT_MAX], cv[PA_CVOLUME_SNPRINT_VERBOSE_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX], *t, clt[28];
+ pa_usec_t cl;
+ const char *cmn;
+ pa_cvolume v;
+ char *volume_str = NULL;
+
+ cmn = pa_channel_map_to_pretty_name(&o->channel_map);
+
+ if ((cl = pa_source_output_get_requested_latency(o)) == (pa_usec_t) -1)
+ pa_snprintf(clt, sizeof(clt), "n/a");
+ else
+ pa_snprintf(clt, sizeof(clt), "%0.2f ms", (double) cl / PA_USEC_PER_MSEC);
+
+ pa_assert(o->source);
+
+ if (pa_source_output_is_volume_readable(o)) {
+ pa_source_output_get_volume(o, &v, true);
+ volume_str = pa_sprintf_malloc("%s\n\t balance %0.2f",
+ pa_cvolume_snprint_verbose(cv, sizeof(cv), &v, &o->channel_map, true),
+ pa_cvolume_get_balance(&v, &o->channel_map));
+ } else
+ volume_str = pa_xstrdup("n/a");
+
+ pa_strbuf_printf(
+ s,
+ " index: %u\n"
+ "\tdriver: <%s>\n"
+ "\tflags: %s%s%s%s%s%s%s%s%s%s%s%s\n"
+ "\tstate: %s\n"
+ "\tsource: %u <%s>\n"
+ "\tvolume: %s\n"
+ "\tmuted: %s\n"
+ "\tcurrent latency: %0.2f ms\n"
+ "\trequested latency: %s\n"
+ "\tsample spec: %s\n"
+ "\tchannel map: %s%s%s\n"
+ "\tresample method: %s\n",
+ o->index,
+ o->driver,
+ o->flags & PA_SOURCE_OUTPUT_VARIABLE_RATE ? "VARIABLE_RATE " : "",
+ o->flags & PA_SOURCE_OUTPUT_DONT_MOVE ? "DONT_MOVE " : "",
+ o->flags & PA_SOURCE_OUTPUT_START_CORKED ? "START_CORKED " : "",
+ o->flags & PA_SOURCE_OUTPUT_NO_REMAP ? "NO_REMAP " : "",
+ o->flags & PA_SOURCE_OUTPUT_NO_REMIX ? "NO_REMIX " : "",
+ o->flags & PA_SOURCE_OUTPUT_FIX_FORMAT ? "FIX_FORMAT " : "",
+ o->flags & PA_SOURCE_OUTPUT_FIX_RATE ? "FIX_RATE " : "",
+ o->flags & PA_SOURCE_OUTPUT_FIX_CHANNELS ? "FIX_CHANNELS " : "",
+ o->flags & PA_SOURCE_OUTPUT_DONT_INHIBIT_AUTO_SUSPEND ? "DONT_INHIBIT_AUTO_SUSPEND " : "",
+ o->flags & PA_SOURCE_OUTPUT_NO_CREATE_ON_SUSPEND ? "NO_CREATE_ON_SUSPEND " : "",
+ o->flags & PA_SOURCE_OUTPUT_KILL_ON_SUSPEND ? "KILL_ON_SUSPEND " : "",
+ o->flags & PA_SOURCE_OUTPUT_PASSTHROUGH ? "PASSTHROUGH " : "",
+ state_table[o->state],
+ o->source->index, o->source->name,
+ volume_str,
+ pa_yes_no(o->muted),
+ (double) pa_source_output_get_latency(o, NULL) / PA_USEC_PER_MSEC,
+ clt,
+ pa_sample_spec_snprint(ss, sizeof(ss), &o->sample_spec),
+ pa_channel_map_snprint(cm, sizeof(cm), &o->channel_map),
+ cmn ? "\n\t " : "",
+ cmn ? cmn : "",
+ pa_resample_method_to_string(pa_source_output_get_resample_method(o)));
+
+ pa_xfree(volume_str);
+
+ if (o->module)
+ pa_strbuf_printf(s, "\towner module: %u\n", o->module->index);
+ if (o->client)
+ pa_strbuf_printf(s, "\tclient: %u <%s>\n", o->client->index, pa_strnull(pa_proplist_gets(o->client->proplist, PA_PROP_APPLICATION_NAME)));
+ if (o->direct_on_input)
+ pa_strbuf_printf(s, "\tdirect on input: %u\n", o->direct_on_input->index);
+
+ t = pa_proplist_to_string_sep(o->proplist, "\n\t\t");
+ pa_strbuf_printf(s, "\tproperties:\n\t\t%s\n", t);
+ pa_xfree(t);
+ }
+
+ return pa_strbuf_to_string_free(s);
+}
+
+char *pa_sink_input_list_to_string(pa_core *c) {
+ pa_strbuf *s;
+ pa_sink_input *i;
+ uint32_t idx = PA_IDXSET_INVALID;
+ static const char* const state_table[] = {
+ [PA_SINK_INPUT_INIT] = "INIT",
+ [PA_SINK_INPUT_RUNNING] = "RUNNING",
+ [PA_SINK_INPUT_CORKED] = "CORKED",
+ [PA_SINK_INPUT_UNLINKED] = "UNLINKED"
+ };
+
+ pa_assert(c);
+ s = pa_strbuf_new();
+
+ pa_strbuf_printf(s, "%u sink input(s) available.\n", pa_idxset_size(c->sink_inputs));
+
+ PA_IDXSET_FOREACH(i, c->sink_inputs, idx) {
+ char ss[PA_SAMPLE_SPEC_SNPRINT_MAX], cv[PA_CVOLUME_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX], *t, clt[28];
+ pa_usec_t cl;
+ const char *cmn;
+ pa_cvolume v;
+ char *volume_str = NULL;
+
+ cmn = pa_channel_map_to_pretty_name(&i->channel_map);
+
+ if ((cl = pa_sink_input_get_requested_latency(i)) == (pa_usec_t) -1)
+ pa_snprintf(clt, sizeof(clt), "n/a");
+ else
+ pa_snprintf(clt, sizeof(clt), "%0.2f ms", (double) cl / PA_USEC_PER_MSEC);
+
+ pa_assert(i->sink);
+
+ if (pa_sink_input_is_volume_readable(i)) {
+ pa_sink_input_get_volume(i, &v, true);
+ volume_str = pa_sprintf_malloc("%s\n\t balance %0.2f",
+ pa_cvolume_snprint_verbose(cv, sizeof(cv), &v, &i->channel_map, true),
+ pa_cvolume_get_balance(&v, &i->channel_map));
+ } else
+ volume_str = pa_xstrdup("n/a");
+
+ pa_strbuf_printf(
+ s,
+ " index: %u\n"
+ "\tdriver: <%s>\n"
+ "\tflags: %s%s%s%s%s%s%s%s%s%s%s%s\n"
+ "\tstate: %s\n"
+ "\tsink: %u <%s>\n"
+ "\tvolume: %s\n"
+ "\tmuted: %s\n"
+ "\tcurrent latency: %0.2f ms\n"
+ "\trequested latency: %s\n"
+ "\tsample spec: %s\n"
+ "\tchannel map: %s%s%s\n"
+ "\tresample method: %s\n",
+ i->index,
+ i->driver,
+ i->flags & PA_SINK_INPUT_VARIABLE_RATE ? "VARIABLE_RATE " : "",
+ i->flags & PA_SINK_INPUT_DONT_MOVE ? "DONT_MOVE " : "",
+ i->flags & PA_SINK_INPUT_START_CORKED ? "START_CORKED " : "",
+ i->flags & PA_SINK_INPUT_NO_REMAP ? "NO_REMAP " : "",
+ i->flags & PA_SINK_INPUT_NO_REMIX ? "NO_REMIX " : "",
+ i->flags & PA_SINK_INPUT_FIX_FORMAT ? "FIX_FORMAT " : "",
+ i->flags & PA_SINK_INPUT_FIX_RATE ? "FIX_RATE " : "",
+ i->flags & PA_SINK_INPUT_FIX_CHANNELS ? "FIX_CHANNELS " : "",
+ i->flags & PA_SINK_INPUT_DONT_INHIBIT_AUTO_SUSPEND ? "DONT_INHIBIT_AUTO_SUSPEND " : "",
+ i->flags & PA_SINK_INPUT_NO_CREATE_ON_SUSPEND ? "NO_CREATE_SUSPEND " : "",
+ i->flags & PA_SINK_INPUT_KILL_ON_SUSPEND ? "KILL_ON_SUSPEND " : "",
+ i->flags & PA_SINK_INPUT_PASSTHROUGH ? "PASSTHROUGH " : "",
+ state_table[i->state],
+ i->sink->index, i->sink->name,
+ volume_str,
+ pa_yes_no(i->muted),
+ (double) pa_sink_input_get_latency(i, NULL) / PA_USEC_PER_MSEC,
+ clt,
+ pa_sample_spec_snprint(ss, sizeof(ss), &i->sample_spec),
+ pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map),
+ cmn ? "\n\t " : "",
+ cmn ? cmn : "",
+ pa_resample_method_to_string(pa_sink_input_get_resample_method(i)));
+
+ pa_xfree(volume_str);
+
+ if (i->module)
+ pa_strbuf_printf(s, "\tmodule: %u\n", i->module->index);
+ if (i->client)
+ pa_strbuf_printf(s, "\tclient: %u <%s>\n", i->client->index, pa_strnull(pa_proplist_gets(i->client->proplist, PA_PROP_APPLICATION_NAME)));
+
+ t = pa_proplist_to_string_sep(i->proplist, "\n\t\t");
+ pa_strbuf_printf(s, "\tproperties:\n\t\t%s\n", t);
+ pa_xfree(t);
+ }
+
+ return pa_strbuf_to_string_free(s);
+}
+
+char *pa_scache_list_to_string(pa_core *c) {
+ pa_strbuf *s;
+ pa_assert(c);
+
+ s = pa_strbuf_new();
+
+ pa_strbuf_printf(s, "%u cache entrie(s) available.\n", c->scache ? pa_idxset_size(c->scache) : 0);
+
+ if (c->scache) {
+ pa_scache_entry *e;
+ uint32_t idx = PA_IDXSET_INVALID;
+
+ PA_IDXSET_FOREACH(e, c->scache, idx) {
+ double l = 0;
+ char ss[PA_SAMPLE_SPEC_SNPRINT_MAX] = "n/a", cv[PA_CVOLUME_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX] = "n/a", *t;
+ const char *cmn;
+
+ cmn = pa_channel_map_to_pretty_name(&e->channel_map);
+
+ if (e->memchunk.memblock) {
+ pa_sample_spec_snprint(ss, sizeof(ss), &e->sample_spec);
+ pa_channel_map_snprint(cm, sizeof(cm), &e->channel_map);
+ l = (double) e->memchunk.length / (double) pa_bytes_per_second(&e->sample_spec);
+ }
+
+ pa_strbuf_printf(
+ s,
+ " name: <%s>\n"
+ "\tindex: %u\n"
+ "\tsample spec: %s\n"
+ "\tchannel map: %s%s%s\n"
+ "\tlength: %lu\n"
+ "\tduration: %0.1f s\n"
+ "\tvolume: %s\n"
+ "\t balance %0.2f\n"
+ "\tlazy: %s\n"
+ "\tfilename: <%s>\n",
+ e->name,
+ e->index,
+ ss,
+ cm,
+ cmn ? "\n\t " : "",
+ cmn ? cmn : "",
+ (long unsigned)(e->memchunk.memblock ? e->memchunk.length : 0),
+ l,
+ e->volume_is_set ? pa_cvolume_snprint_verbose(cv, sizeof(cv), &e->volume, &e->channel_map, true) : "n/a",
+ (e->memchunk.memblock && e->volume_is_set) ? pa_cvolume_get_balance(&e->volume, &e->channel_map) : 0.0f,
+ pa_yes_no(e->lazy),
+ e->filename ? e->filename : "n/a");
+
+ t = pa_proplist_to_string_sep(e->proplist, "\n\t\t");
+ pa_strbuf_printf(s, "\tproperties:\n\t\t%s\n", t);
+ pa_xfree(t);
+ }
+ }
+
+ return pa_strbuf_to_string_free(s);
+}
+
+char *pa_full_status_string(pa_core *c) {
+ pa_strbuf *s;
+ int i;
+
+ s = pa_strbuf_new();
+
+ for (i = 0; i < 8; i++) {
+ char *t = NULL;
+
+ switch (i) {
+ case 0:
+ t = pa_sink_list_to_string(c);
+ break;
+ case 1:
+ t = pa_source_list_to_string(c);
+ break;
+ case 2:
+ t = pa_sink_input_list_to_string(c);
+ break;
+ case 3:
+ t = pa_source_output_list_to_string(c);
+ break;
+ case 4:
+ t = pa_client_list_to_string(c);
+ break;
+ case 5:
+ t = pa_card_list_to_string(c);
+ break;
+ case 6:
+ t = pa_module_list_to_string(c);
+ break;
+ case 7:
+ t = pa_scache_list_to_string(c);
+ break;
+ }
+
+ pa_strbuf_puts(s, t);
+ pa_xfree(t);
+ }
+
+ return pa_strbuf_to_string_free(s);
+}
diff --git a/src/pulsecore/cli-text.h b/src/pulsecore/cli-text.h
new file mode 100644
index 0000000..b89be50
--- /dev/null
+++ b/src/pulsecore/cli-text.h
@@ -0,0 +1,39 @@
+#ifndef fooclitexthfoo
+#define fooclitexthfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulsecore/core.h>
+
+/* Some functions to generate pretty formatted listings of
+ * entities. The returned strings have to be freed manually. */
+
+char *pa_sink_input_list_to_string(pa_core *c);
+char *pa_source_output_list_to_string(pa_core *c);
+char *pa_sink_list_to_string(pa_core *core);
+char *pa_source_list_to_string(pa_core *c);
+char *pa_card_list_to_string(pa_core *c);
+char *pa_client_list_to_string(pa_core *c);
+char *pa_module_list_to_string(pa_core *c);
+char *pa_scache_list_to_string(pa_core *c);
+
+char *pa_full_status_string(pa_core *c);
+
+#endif
diff --git a/src/pulsecore/cli.c b/src/pulsecore/cli.c
new file mode 100644
index 0000000..f942629
--- /dev/null
+++ b/src/pulsecore/cli.c
@@ -0,0 +1,176 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/ioline.h>
+#include <pulsecore/module.h>
+#include <pulsecore/client.h>
+#include <pulsecore/tokenizer.h>
+#include <pulsecore/strbuf.h>
+#include <pulsecore/cli-text.h>
+#include <pulsecore/cli-command.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+
+#include "cli.h"
+
+#define PROMPT ">>> "
+
+struct pa_cli {
+ pa_core *core;
+ pa_ioline *line;
+
+ pa_cli_eof_cb_t eof_callback;
+ void *userdata;
+
+ pa_client *client;
+
+ bool fail, kill_requested;
+ int defer_kill;
+
+ bool interactive;
+ char *last_line;
+};
+
+static void line_callback(pa_ioline *line, const char *s, void *userdata);
+static void client_kill(pa_client *c);
+
+pa_cli* pa_cli_new(pa_core *core, pa_iochannel *io, pa_module *m) {
+ char cname[256];
+ pa_cli *c;
+ pa_client_new_data data;
+ pa_client *client;
+
+ pa_assert(io);
+
+ pa_iochannel_socket_peer_to_string(io, cname, sizeof(cname));
+
+ pa_client_new_data_init(&data);
+ data.driver = __FILE__;
+ data.module = m;
+ pa_proplist_sets(data.proplist, PA_PROP_APPLICATION_NAME, cname);
+ client = pa_client_new(core, &data);
+ pa_client_new_data_done(&data);
+
+ if (!client)
+ return NULL;
+
+ c = pa_xnew0(pa_cli, 1);
+ c->core = core;
+ c->client = client;
+ pa_assert_se(c->line = pa_ioline_new(io));
+
+ c->client->kill = client_kill;
+ c->client->userdata = c;
+
+ pa_ioline_set_callback(c->line, line_callback, c);
+
+ return c;
+}
+
+void pa_cli_free(pa_cli *c) {
+ pa_assert(c);
+
+ pa_ioline_close(c->line);
+ pa_ioline_unref(c->line);
+ pa_client_free(c->client);
+ pa_xfree(c->last_line);
+ pa_xfree(c);
+}
+
+static void client_kill(pa_client *client) {
+ pa_cli *c;
+
+ pa_assert(client);
+ pa_assert_se(c = client->userdata);
+
+ pa_log_debug("CLI client killed.");
+
+ if (c->defer_kill)
+ c->kill_requested = true;
+ else if (c->eof_callback)
+ c->eof_callback(c, c->userdata);
+}
+
+static void line_callback(pa_ioline *line, const char *s, void *userdata) {
+ pa_strbuf *buf;
+ pa_cli *c = userdata;
+ char *p;
+
+ pa_assert(line);
+ pa_assert(c);
+
+ if (!s) {
+ pa_log_debug("CLI got EOF from user.");
+
+ if (c->eof_callback)
+ c->eof_callback(c, c->userdata);
+
+ return;
+ }
+
+ /* Magic command, like they had in AT Hayes Modems! Those were the good days! */
+ if (pa_streq(s, "/"))
+ s = c->last_line;
+ else if (s[0]) {
+ pa_xfree(c->last_line);
+ c->last_line = pa_xstrdup(s);
+ }
+
+ pa_assert_se(buf = pa_strbuf_new());
+ c->defer_kill++;
+ if (pa_streq(s, "hello")) {
+ pa_strbuf_printf(buf, "Welcome to PulseAudio %s! "
+ "Use \"help\" for usage information.\n", PACKAGE_VERSION);
+ c->interactive = true;
+ }
+ else
+ pa_cli_command_execute_line(c->core, s, buf, &c->fail);
+ c->defer_kill--;
+ pa_ioline_puts(line, p = pa_strbuf_to_string_free(buf));
+ pa_xfree(p);
+
+ if (c->kill_requested) {
+ if (c->eof_callback)
+ c->eof_callback(c, c->userdata);
+ } else if (c->interactive)
+ pa_ioline_puts(line, PROMPT);
+}
+
+void pa_cli_set_eof_callback(pa_cli *c, pa_cli_eof_cb_t cb, void *userdata) {
+ pa_assert(c);
+
+ c->eof_callback = cb;
+ c->userdata = userdata;
+}
+
+pa_module *pa_cli_get_module(pa_cli *c) {
+ pa_assert(c);
+
+ return c->client->module;
+}
diff --git a/src/pulsecore/cli.h b/src/pulsecore/cli.h
new file mode 100644
index 0000000..37d5ff9
--- /dev/null
+++ b/src/pulsecore/cli.h
@@ -0,0 +1,40 @@
+#ifndef fooclihfoo
+#define fooclihfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulsecore/iochannel.h>
+#include <pulsecore/core.h>
+#include <pulsecore/module.h>
+
+typedef struct pa_cli pa_cli;
+
+typedef void (*pa_cli_eof_cb_t)(pa_cli *c, void *userdata);
+
+/* Create a new command line session on the specified io channel owned by the specified module */
+pa_cli* pa_cli_new(pa_core *core, pa_iochannel *io, pa_module *m);
+void pa_cli_free(pa_cli *cli);
+
+/* Set a callback function that is called whenever the command line session is terminated */
+void pa_cli_set_eof_callback(pa_cli *cli, pa_cli_eof_cb_t cb, void *userdata);
+
+pa_module *pa_cli_get_module(pa_cli *c);
+
+#endif
diff --git a/src/pulsecore/client.c b/src/pulsecore/client.c
new file mode 100644
index 0000000..2e6af47
--- /dev/null
+++ b/src/pulsecore/client.c
@@ -0,0 +1,168 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <pulse/xmalloc.h>
+#include <pulse/util.h>
+
+#include <pulsecore/core-subscribe.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/core-util.h>
+
+#include "client.h"
+
+pa_client_new_data* pa_client_new_data_init(pa_client_new_data *data) {
+ pa_assert(data);
+
+ memset(data, 0, sizeof(*data));
+ data->proplist = pa_proplist_new();
+
+ return data;
+}
+
+void pa_client_new_data_done(pa_client_new_data *data) {
+ pa_assert(data);
+
+ pa_proplist_free(data->proplist);
+}
+
+pa_client *pa_client_new(pa_core *core, pa_client_new_data *data) {
+ pa_client *c;
+
+ pa_core_assert_ref(core);
+ pa_assert(data);
+
+ if (pa_hook_fire(&core->hooks[PA_CORE_HOOK_CLIENT_NEW], data) < 0)
+ return NULL;
+
+ c = pa_xnew0(pa_client, 1);
+ c->core = core;
+ c->proplist = pa_proplist_copy(data->proplist);
+ c->driver = pa_xstrdup(pa_path_get_filename(data->driver));
+ c->module = data->module;
+
+ c->sink_inputs = pa_idxset_new(NULL, NULL);
+ c->source_outputs = pa_idxset_new(NULL, NULL);
+
+ pa_assert_se(pa_idxset_put(core->clients, c, &c->index) >= 0);
+
+ pa_log_info("Created %u \"%s\"", c->index, pa_strnull(pa_proplist_gets(c->proplist, PA_PROP_APPLICATION_NAME)));
+ pa_subscription_post(core, PA_SUBSCRIPTION_EVENT_CLIENT|PA_SUBSCRIPTION_EVENT_NEW, c->index);
+
+ pa_hook_fire(&core->hooks[PA_CORE_HOOK_CLIENT_PUT], c);
+
+ pa_core_check_idle(core);
+
+ return c;
+}
+
+void pa_client_free(pa_client *c) {
+ pa_core *core;
+
+ pa_assert(c);
+ pa_assert(c->core);
+
+ core = c->core;
+
+ pa_hook_fire(&core->hooks[PA_CORE_HOOK_CLIENT_UNLINK], c);
+
+ pa_idxset_remove_by_data(c->core->clients, c, NULL);
+
+ pa_log_info("Freed %u \"%s\"", c->index, pa_strnull(pa_proplist_gets(c->proplist, PA_PROP_APPLICATION_NAME)));
+ pa_subscription_post(c->core, PA_SUBSCRIPTION_EVENT_CLIENT|PA_SUBSCRIPTION_EVENT_REMOVE, c->index);
+
+ pa_assert(pa_idxset_isempty(c->sink_inputs));
+ pa_idxset_free(c->sink_inputs, NULL);
+ pa_assert(pa_idxset_isempty(c->source_outputs));
+ pa_idxset_free(c->source_outputs, NULL);
+
+ pa_proplist_free(c->proplist);
+ pa_xfree(c->driver);
+ pa_xfree(c);
+
+ pa_core_check_idle(core);
+}
+
+void pa_client_kill(pa_client *c) {
+ pa_assert(c);
+
+ if (!c->kill) {
+ pa_log_warn("kill() operation not implemented for client %u", c->index);
+ return;
+ }
+
+ c->kill(c);
+}
+
+void pa_client_set_name(pa_client *c, const char *name) {
+ pa_assert(c);
+ pa_assert(name);
+
+ pa_log_info("Client %u changed name from \"%s\" to \"%s\"", c->index, pa_strnull(pa_proplist_gets(c->proplist, PA_PROP_APPLICATION_NAME)), name);
+ pa_proplist_sets(c->proplist, PA_PROP_APPLICATION_NAME, name);
+
+ pa_client_update_proplist(c, 0, NULL);
+}
+
+void pa_client_update_proplist(pa_client *c, pa_update_mode_t mode, pa_proplist *p) {
+ pa_assert(c);
+
+ if (p)
+ pa_proplist_update(c->proplist, mode, p);
+
+ pa_hook_fire(&c->core->hooks[PA_CORE_HOOK_CLIENT_PROPLIST_CHANGED], c);
+ pa_subscription_post(c->core, PA_SUBSCRIPTION_EVENT_CLIENT|PA_SUBSCRIPTION_EVENT_CHANGE, c->index);
+}
+
+void pa_client_send_event(pa_client *c, const char *event, pa_proplist *data) {
+ pa_proplist *pl = NULL;
+ pa_client_send_event_hook_data hook_data;
+
+ pa_assert(c);
+ pa_assert(event);
+
+ if (!c->send_event)
+ return;
+
+ if (!data)
+ data = pl = pa_proplist_new();
+
+ hook_data.client = c;
+ hook_data.data = data;
+ hook_data.event = event;
+
+ if (pa_hook_fire(&c->core->hooks[PA_CORE_HOOK_CLIENT_SEND_EVENT], &hook_data) < 0)
+ goto finish;
+
+ c->send_event(c, event, data);
+
+finish:
+
+ if (pl)
+ pa_proplist_free(pl);
+}
diff --git a/src/pulsecore/client.h b/src/pulsecore/client.h
new file mode 100644
index 0000000..c6952e3
--- /dev/null
+++ b/src/pulsecore/client.h
@@ -0,0 +1,83 @@
+#ifndef foopulseclienthfoo
+#define foopulseclienthfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <inttypes.h>
+
+#include <pulsecore/typedefs.h>
+#include <pulse/proplist.h>
+#include <pulsecore/core.h>
+#include <pulsecore/module.h>
+
+/* Every connection to the server should have a pa_client
+ * attached. That way the user may generate a listing of all connected
+ * clients easily and kill them if they want.*/
+
+struct pa_client {
+ uint32_t index;
+ pa_core *core;
+
+ pa_proplist *proplist;
+ pa_module *module;
+ char *driver;
+
+ pa_idxset *sink_inputs;
+ pa_idxset *source_outputs;
+
+ void *userdata;
+
+ void (*kill)(pa_client *c);
+
+ void (*send_event)(pa_client *c, const char *name, pa_proplist *data);
+};
+
+typedef struct pa_client_new_data {
+ pa_proplist *proplist;
+ const char *driver;
+ pa_module *module;
+} pa_client_new_data;
+
+pa_client_new_data *pa_client_new_data_init(pa_client_new_data *data);
+void pa_client_new_data_done(pa_client_new_data *data);
+
+pa_client *pa_client_new(pa_core *c, pa_client_new_data *data);
+
+/* This function should be called only by the code that created the client */
+void pa_client_free(pa_client *c);
+
+/* Code that didn't create the client should call this function to
+ * request destruction of the client */
+void pa_client_kill(pa_client *c);
+
+/* Rename the client */
+void pa_client_set_name(pa_client *c, const char *name);
+
+void pa_client_update_proplist(pa_client *c, pa_update_mode_t mode, pa_proplist *p);
+
+void pa_client_send_event(pa_client *c, const char *event, pa_proplist *data);
+
+typedef struct pa_client_send_event_hook_data {
+ pa_client *client;
+ const char *event;
+ pa_proplist *data;
+} pa_client_send_event_hook_data;
+
+#endif
diff --git a/src/pulsecore/conf-parser.c b/src/pulsecore/conf-parser.c
new file mode 100644
index 0000000..73b7061
--- /dev/null
+++ b/src/pulsecore/conf-parser.c
@@ -0,0 +1,394 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <dirent.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/core-error.h>
+#include <pulsecore/log.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/macro.h>
+
+#include "conf-parser.h"
+
+#define WHITESPACE " \t\n"
+#define COMMENTS "#;\n"
+
+/* Run the user supplied parser for an assignment */
+static int normal_assignment(pa_config_parser_state *state) {
+ const pa_config_item *item;
+
+ pa_assert(state);
+
+ for (item = state->item_table; item->parse; item++) {
+
+ if (item->lvalue && !pa_streq(state->lvalue, item->lvalue))
+ continue;
+
+ if (item->section && !state->section)
+ continue;
+
+ if (item->section && !pa_streq(state->section, item->section))
+ continue;
+
+ state->data = item->data;
+
+ return item->parse(state);
+ }
+
+ pa_log("[%s:%u] Unknown lvalue '%s' in section '%s'.", state->filename, state->lineno, state->lvalue, pa_strna(state->section));
+
+ return -1;
+}
+
+/* Parse a proplist entry. */
+static int proplist_assignment(pa_config_parser_state *state) {
+ pa_assert(state);
+ pa_assert(state->proplist);
+
+ if (pa_proplist_sets(state->proplist, state->lvalue, state->rvalue) < 0) {
+ pa_log("[%s:%u] Failed to parse a proplist entry: %s = %s", state->filename, state->lineno, state->lvalue, state->rvalue);
+ return -1;
+ }
+
+ return 0;
+}
+
+/* Parse a variable assignment line */
+static int parse_line(pa_config_parser_state *state) {
+ char *c;
+
+ state->lvalue = state->buf + strspn(state->buf, WHITESPACE);
+
+ if ((c = strpbrk(state->lvalue, COMMENTS)))
+ *c = 0;
+
+ if (!*state->lvalue)
+ return 0;
+
+ if (pa_startswith(state->lvalue, ".include ")) {
+ char *path = NULL, *fn;
+ int r;
+
+ fn = pa_strip(state->lvalue + 9);
+ if (!pa_is_path_absolute(fn)) {
+ const char *k;
+ if ((k = strrchr(state->filename, '/'))) {
+ char *dir = pa_xstrndup(state->filename, k - state->filename);
+ fn = path = pa_sprintf_malloc("%s" PA_PATH_SEP "%s", dir, fn);
+ pa_xfree(dir);
+ }
+ }
+
+ r = pa_config_parse(fn, NULL, state->item_table, state->proplist, false, state->userdata);
+ pa_xfree(path);
+ return r;
+ }
+
+ if (*state->lvalue == '[') {
+ size_t k;
+
+ k = strlen(state->lvalue);
+ pa_assert(k > 0);
+
+ if (state->lvalue[k-1] != ']') {
+ pa_log("[%s:%u] Invalid section header.", state->filename, state->lineno);
+ return -1;
+ }
+
+ pa_xfree(state->section);
+ state->section = pa_xstrndup(state->lvalue + 1, k-2);
+
+ if (pa_streq(state->section, "Properties")) {
+ if (!state->proplist) {
+ pa_log("[%s:%u] \"Properties\" section is not allowed in this file.", state->filename, state->lineno);
+ return -1;
+ }
+
+ state->in_proplist = true;
+ } else
+ state->in_proplist = false;
+
+ return 0;
+ }
+
+ if (!(state->rvalue = strchr(state->lvalue, '='))) {
+ pa_log("[%s:%u] Missing '='.", state->filename, state->lineno);
+ return -1;
+ }
+
+ *state->rvalue = 0;
+ state->rvalue++;
+
+ state->lvalue = pa_strip(state->lvalue);
+ state->rvalue = pa_strip(state->rvalue);
+
+ if (state->in_proplist)
+ return proplist_assignment(state);
+ else
+ return normal_assignment(state);
+}
+
+#ifndef OS_IS_WIN32
+static int conf_filter(const struct dirent *entry) {
+ return pa_endswith(entry->d_name, ".conf");
+}
+#endif
+
+/* Go through the file and parse each line */
+int pa_config_parse(const char *filename, FILE *f, const pa_config_item *t, pa_proplist *proplist, bool use_dot_d,
+ void *userdata) {
+ int r = -1;
+ bool do_close = !f;
+ pa_config_parser_state state;
+
+ pa_assert(filename);
+ pa_assert(t);
+
+ pa_zero(state);
+
+ if (!f && !(f = pa_fopen_cloexec(filename, "r"))) {
+ if (errno == ENOENT) {
+ pa_log_debug("Failed to open configuration file '%s': %s", filename, pa_cstrerror(errno));
+ r = 0;
+ goto finish;
+ }
+
+ pa_log_warn("Failed to open configuration file '%s': %s", filename, pa_cstrerror(errno));
+ goto finish;
+ }
+ pa_log_debug("Parsing configuration file '%s'", filename);
+
+ state.filename = filename;
+ state.item_table = t;
+ state.userdata = userdata;
+
+ if (proplist)
+ state.proplist = pa_proplist_new();
+
+ while (!feof(f)) {
+ if (!fgets(state.buf, sizeof(state.buf), f)) {
+ if (feof(f))
+ break;
+
+ pa_log_warn("Failed to read configuration file '%s': %s", filename, pa_cstrerror(errno));
+ goto finish;
+ }
+
+ state.lineno++;
+
+ if (parse_line(&state) < 0)
+ goto finish;
+ }
+
+ if (proplist)
+ pa_proplist_update(proplist, PA_UPDATE_REPLACE, state.proplist);
+
+ r = 0;
+
+finish:
+ if (state.proplist)
+ pa_proplist_free(state.proplist);
+
+ pa_xfree(state.section);
+
+ if (do_close && f)
+ fclose(f);
+
+ if (use_dot_d) {
+#ifdef OS_IS_WIN32
+ char *dir_name = pa_sprintf_malloc("%s.d", filename);
+ char *pattern = pa_sprintf_malloc("%s\\*.conf", dir_name);
+ HANDLE fh;
+ WIN32_FIND_DATA wfd;
+
+ fh = FindFirstFile(pattern, &wfd);
+ if (fh != INVALID_HANDLE_VALUE) {
+ do {
+ if (!(wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
+ char *filename2 = pa_sprintf_malloc("%s\\%s", dir_name, wfd.cFileName);
+ pa_config_parse(filename2, NULL, t, proplist, false, userdata);
+ pa_xfree(filename2);
+ }
+ } while (FindNextFile(fh, &wfd));
+ FindClose(fh);
+ } else {
+ DWORD err = GetLastError();
+
+ if (err == ERROR_PATH_NOT_FOUND) {
+ pa_log_debug("Pattern %s did not match any files, ignoring.", pattern);
+ } else {
+ LPVOID msgbuf;
+ DWORD fret = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&msgbuf, 0, NULL);
+
+ if (fret != 0) {
+ pa_log_warn("FindFirstFile(%s) failed with error %ld (%s), ignoring.", pattern, err, (char*)msgbuf);
+ LocalFree(msgbuf);
+ } else {
+ pa_log_warn("FindFirstFile(%s) failed with error %ld, ignoring.", pattern, err);
+ pa_log_warn("FormatMessage failed with error %ld", GetLastError());
+ }
+ }
+ }
+
+ pa_xfree(pattern);
+ pa_xfree(dir_name);
+#else
+ char *dir_name;
+ int n;
+ struct dirent **entries = NULL;
+
+ dir_name = pa_sprintf_malloc("%s.d", filename);
+
+ n = scandir(dir_name, &entries, conf_filter, alphasort);
+ if (n >= 0) {
+ int i;
+
+ for (i = 0; i < n; i++) {
+ char *filename2;
+
+ filename2 = pa_sprintf_malloc("%s" PA_PATH_SEP "%s", dir_name, entries[i]->d_name);
+ pa_config_parse(filename2, NULL, t, proplist, false, userdata);
+ pa_xfree(filename2);
+
+ free(entries[i]);
+ }
+
+ free(entries);
+ } else {
+ if (errno == ENOENT)
+ pa_log_debug("%s does not exist, ignoring.", dir_name);
+ else
+ pa_log_warn("scandir(\"%s\") failed: %s", dir_name, pa_cstrerror(errno));
+ }
+
+ pa_xfree(dir_name);
+#endif
+ }
+
+ return r;
+}
+
+int pa_config_parse_int(pa_config_parser_state *state) {
+ int *i;
+ int32_t k;
+
+ pa_assert(state);
+
+ i = state->data;
+
+ if (pa_atoi(state->rvalue, &k) < 0) {
+ pa_log("[%s:%u] Failed to parse numeric value: %s", state->filename, state->lineno, state->rvalue);
+ return -1;
+ }
+
+ *i = (int) k;
+ return 0;
+}
+
+int pa_config_parse_unsigned(pa_config_parser_state *state) {
+ unsigned *u;
+ uint32_t k;
+
+ pa_assert(state);
+
+ u = state->data;
+
+ if (pa_atou(state->rvalue, &k) < 0) {
+ pa_log("[%s:%u] Failed to parse numeric value: %s", state->filename, state->lineno, state->rvalue);
+ return -1;
+ }
+
+ *u = (unsigned) k;
+ return 0;
+}
+
+int pa_config_parse_size(pa_config_parser_state *state) {
+ size_t *i;
+ uint32_t k;
+
+ pa_assert(state);
+
+ i = state->data;
+
+ if (pa_atou(state->rvalue, &k) < 0) {
+ pa_log("[%s:%u] Failed to parse numeric value: %s", state->filename, state->lineno, state->rvalue);
+ return -1;
+ }
+
+ *i = (size_t) k;
+ return 0;
+}
+
+int pa_config_parse_bool(pa_config_parser_state *state) {
+ int k;
+ bool *b;
+
+ pa_assert(state);
+
+ b = state->data;
+
+ if ((k = pa_parse_boolean(state->rvalue)) < 0) {
+ pa_log("[%s:%u] Failed to parse boolean value: %s", state->filename, state->lineno, state->rvalue);
+ return -1;
+ }
+
+ *b = !!k;
+
+ return 0;
+}
+
+int pa_config_parse_not_bool(pa_config_parser_state *state) {
+ int k;
+ bool *b;
+
+ pa_assert(state);
+
+ b = state->data;
+
+ if ((k = pa_parse_boolean(state->rvalue)) < 0) {
+ pa_log("[%s:%u] Failed to parse boolean value: %s", state->filename, state->lineno, state->rvalue);
+ return -1;
+ }
+
+ *b = !k;
+
+ return 0;
+}
+
+int pa_config_parse_string(pa_config_parser_state *state) {
+ char **s;
+
+ pa_assert(state);
+
+ s = state->data;
+
+ pa_xfree(*s);
+ *s = *state->rvalue ? pa_xstrdup(state->rvalue) : NULL;
+ return 0;
+}
diff --git a/src/pulsecore/conf-parser.h b/src/pulsecore/conf-parser.h
new file mode 100644
index 0000000..7dc0ff9
--- /dev/null
+++ b/src/pulsecore/conf-parser.h
@@ -0,0 +1,87 @@
+#ifndef fooconfparserhfoo
+#define fooconfparserhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdio.h>
+
+#include <pulse/proplist.h>
+
+/* An abstract parser for simple, line based, shallow configuration
+ * files consisting of variable assignments only. */
+
+typedef struct pa_config_parser_state pa_config_parser_state;
+
+typedef int (*pa_config_parser_cb_t)(pa_config_parser_state *state);
+
+/* Wraps info for parsing a specific configuration variable */
+typedef struct pa_config_item {
+ const char *lvalue; /* name of the variable */
+ pa_config_parser_cb_t parse; /* Function that is called to parse the variable's value */
+ void *data; /* Where to store the variable's data */
+ const char *section;
+} pa_config_item;
+
+struct pa_config_parser_state {
+ const char *filename;
+ unsigned lineno;
+ char *section;
+ char *lvalue;
+ char *rvalue;
+ void *data; /* The data pointer of the current pa_config_item. */
+ void *userdata; /* The pointer that was given to pa_config_parse(). */
+
+ /* Private data to be used only by conf-parser.c. */
+ const pa_config_item *item_table;
+ char buf[4096];
+ pa_proplist *proplist;
+ bool in_proplist;
+};
+
+/* The configuration file parsing routine. Expects a table of
+ * pa_config_items in *t that is terminated by an item where lvalue is
+ * NULL.
+ *
+ * If use_dot_d is true, then after parsing the file named by the filename
+ * argument, the function will parse all files ending with ".conf" in
+ * alphabetical order from a directory whose name is filename + ".d", if such
+ * directory exists.
+ *
+ * Some configuration files may contain a Properties section, which
+ * is a bit special. Normally all accepted lvalues must be predefined
+ * in the pa_config_item table, but in the Properties section the
+ * pa_config_item table is ignored, and all lvalues are accepted (as
+ * long as they are valid proplist keys). If the proplist pointer is
+ * non-NULL, the parser will parse any section named "Properties" as
+ * properties, and those properties will be merged into the given
+ * proplist. If proplist is NULL, then sections named "Properties"
+ * are not allowed at all in the configuration file. */
+int pa_config_parse(const char *filename, FILE *f, const pa_config_item *t, pa_proplist *proplist, bool use_dot_d,
+ void *userdata);
+
+/* Generic parsers for integers, size_t, booleans and strings */
+int pa_config_parse_int(pa_config_parser_state *state);
+int pa_config_parse_unsigned(pa_config_parser_state *state);
+int pa_config_parse_size(pa_config_parser_state *state);
+int pa_config_parse_bool(pa_config_parser_state *state);
+int pa_config_parse_not_bool(pa_config_parser_state *state);
+int pa_config_parse_string(pa_config_parser_state *state);
+
+#endif
diff --git a/src/pulsecore/core-error.c b/src/pulsecore/core-error.c
new file mode 100644
index 0000000..446de6c
--- /dev/null
+++ b/src/pulsecore/core-error.c
@@ -0,0 +1,78 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <pulse/utf8.h>
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/thread.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/log.h>
+
+#include "core-error.h"
+
+PA_STATIC_TLS_DECLARE(cstrerror, pa_xfree);
+
+const char* pa_cstrerror(int errnum) {
+ const char *original = NULL;
+ char *translated, *t;
+ char errbuf[128];
+
+ if (errnum < 0)
+ errnum = -errnum;
+
+ if ((t = PA_STATIC_TLS_GET(cstrerror)))
+ pa_xfree(t);
+
+#if defined(HAVE_STRERROR_R) && defined(__GLIBC__)
+ original = strerror_r(errnum, errbuf, sizeof(errbuf));
+#elif defined(HAVE_STRERROR_R)
+ if (strerror_r(errnum, errbuf, sizeof(errbuf)) == 0) {
+ errbuf[sizeof(errbuf) - 1] = 0;
+ original = errbuf;
+ }
+#else
+ /* This might not be thread safe, but we hope for the best */
+ original = strerror(errnum);
+#endif
+
+ /* The second condition is a Windows-ism */
+ if (!original || !strcasecmp(original, "Unknown error")) {
+ pa_snprintf(errbuf, sizeof(errbuf), "Unknown error %d", errnum);
+ original = errbuf;
+ }
+
+ if (!(translated = pa_locale_to_utf8(original))) {
+ pa_log_warn("Unable to convert error string to locale, filtering.");
+ translated = pa_utf8_filter(original);
+ }
+
+ PA_STATIC_TLS_SET(cstrerror, translated);
+
+ return translated;
+}
diff --git a/src/pulsecore/core-error.h b/src/pulsecore/core-error.h
new file mode 100644
index 0000000..f4af5d5
--- /dev/null
+++ b/src/pulsecore/core-error.h
@@ -0,0 +1,39 @@
+#ifndef foocoreerrorhfoo
+#define foocoreerrorhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulse/cdecl.h>
+
+/** \file
+ * Error management */
+
+PA_C_DECL_BEGIN
+
+/** A wrapper around the standard strerror() function that converts the
+ * string to UTF-8. The function is thread safe but the returned string is
+ * only guaranteed to exist until the thread exits or pa_cstrerror() is
+ * called again from the same thread. */
+const char* pa_cstrerror(int errnum);
+
+PA_C_DECL_END
+
+#endif
diff --git a/src/pulsecore/core-format.c b/src/pulsecore/core-format.c
new file mode 100644
index 0000000..1a21864
--- /dev/null
+++ b/src/pulsecore/core-format.c
@@ -0,0 +1,164 @@
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "core-format.h"
+
+#include <pulse/def.h>
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/macro.h>
+
+pa_format_info *pa_format_info_from_sample_spec2(const pa_sample_spec *ss, const pa_channel_map *map, bool set_format,
+ bool set_rate, bool set_channels) {
+ pa_format_info *format = NULL;
+
+ pa_assert(ss);
+
+ format = pa_format_info_new();
+ format->encoding = PA_ENCODING_PCM;
+
+ if (set_format)
+ pa_format_info_set_sample_format(format, ss->format);
+
+ if (set_rate)
+ pa_format_info_set_rate(format, ss->rate);
+
+ if (set_channels) {
+ pa_format_info_set_channels(format, ss->channels);
+
+ if (map) {
+ if (map->channels != ss->channels) {
+ pa_log_debug("Channel map is incompatible with the sample spec.");
+ goto fail;
+ }
+
+ pa_format_info_set_channel_map(format, map);
+ }
+ }
+
+ return format;
+
+fail:
+ if (format)
+ pa_format_info_free(format);
+
+ return NULL;
+}
+
+int pa_format_info_to_sample_spec2(const pa_format_info *f, pa_sample_spec *ss, pa_channel_map *map,
+ const pa_sample_spec *fallback_ss, const pa_channel_map *fallback_map) {
+ int r, r2;
+ pa_sample_spec ss_local;
+ pa_channel_map map_local;
+
+ pa_assert(f);
+ pa_assert(ss);
+ pa_assert(map);
+ pa_assert(fallback_ss);
+ pa_assert(fallback_map);
+
+ if (!pa_format_info_is_pcm(f))
+ return pa_format_info_to_sample_spec_fake(f, ss, map);
+
+ r = pa_format_info_get_sample_format(f, &ss_local.format);
+ if (r == -PA_ERR_NOENTITY)
+ ss_local.format = fallback_ss->format;
+ else if (r < 0)
+ return r;
+
+ pa_assert(pa_sample_format_valid(ss_local.format));
+
+ r = pa_format_info_get_rate(f, &ss_local.rate);
+ if (r == -PA_ERR_NOENTITY)
+ ss_local.rate = fallback_ss->rate;
+ else if (r < 0)
+ return r;
+
+ pa_assert(pa_sample_rate_valid(ss_local.rate));
+
+ r = pa_format_info_get_channels(f, &ss_local.channels);
+ r2 = pa_format_info_get_channel_map(f, &map_local);
+ if (r == -PA_ERR_NOENTITY && r2 >= 0)
+ ss_local.channels = map_local.channels;
+ else if (r == -PA_ERR_NOENTITY)
+ ss_local.channels = fallback_ss->channels;
+ else if (r < 0)
+ return r;
+
+ pa_assert(pa_channels_valid(ss_local.channels));
+
+ if (r2 >= 0 && map_local.channels != ss_local.channels) {
+ pa_log_debug("Channel map is not compatible with the sample spec.");
+ return -PA_ERR_INVALID;
+ }
+
+ if (r2 == -PA_ERR_NOENTITY) {
+ if (fallback_map->channels == ss_local.channels)
+ map_local = *fallback_map;
+ else
+ pa_channel_map_init_extend(&map_local, ss_local.channels, PA_CHANNEL_MAP_DEFAULT);
+ } else if (r2 < 0)
+ return r2;
+
+ pa_assert(pa_channel_map_valid(&map_local));
+ pa_assert(ss_local.channels == map_local.channels);
+
+ *ss = ss_local;
+ *map = map_local;
+
+ return 0;
+}
+
+int pa_format_info_to_sample_spec_fake(const pa_format_info *f, pa_sample_spec *ss, pa_channel_map *map) {
+ int rate;
+
+ pa_assert(f);
+ pa_assert(ss);
+
+ /* Note: When we add support for non-IEC61937 encapsulated compressed
+ * formats, this function should return a non-zero values for these. */
+
+ ss->format = PA_SAMPLE_S16LE;
+ if ((f->encoding == PA_ENCODING_TRUEHD_IEC61937) ||
+ (f->encoding == PA_ENCODING_DTSHD_IEC61937)) {
+ ss->channels = 8;
+ if (map) {
+ /* We use the ALSA mapping, because most likely we will be using an
+ * ALSA sink. This doesn't really matter anyway, though, because
+ * the channel map doesn't affect anything with passthrough
+ * streams. The channel map just needs to be consistent with the
+ * sample spec's channel count. */
+ pa_channel_map_init_auto(map, 8, PA_CHANNEL_MAP_ALSA);
+ }
+ } else {
+ ss->channels = 2;
+ if (map)
+ pa_channel_map_init_stereo(map);
+ }
+
+ pa_return_val_if_fail(pa_format_info_get_prop_int(f, PA_PROP_FORMAT_RATE, &rate) == 0, -PA_ERR_INVALID);
+ ss->rate = (uint32_t) rate;
+
+ if (f->encoding == PA_ENCODING_EAC3_IEC61937)
+ ss->rate *= 4;
+
+ return 0;
+}
diff --git a/src/pulsecore/core-format.h b/src/pulsecore/core-format.h
new file mode 100644
index 0000000..e2e02fe
--- /dev/null
+++ b/src/pulsecore/core-format.h
@@ -0,0 +1,59 @@
+#ifndef foocoreformathfoo
+#define foocoreformathfoo
+
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulse/format.h>
+
+#include <stdbool.h>
+
+/* Convert a sample spec and an optional channel map to a new PCM format info
+ * object (remember to free it). If map is NULL, then the channel map will be
+ * left unspecified. If some fields of the sample spec should be ignored, pass
+ * false for set_format, set_rate and set_channels as appropriate, then those
+ * fields will be left unspecified. This function returns NULL if the input is
+ * invalid (for example, setting the sample rate was requested, but the rate
+ * in ss is invalid).
+ *
+ * pa_format_info_from_sample_spec() exists too. This "version 2" was created,
+ * because the original function doesn't provide the possibility of ignoring
+ * some of the sample spec fields. That functionality can't be added to the
+ * original function, because the function is a part of the public API and
+ * adding parameters to it would break the API. */
+pa_format_info *pa_format_info_from_sample_spec2(const pa_sample_spec *ss, const pa_channel_map *map, bool set_format,
+ bool set_rate, bool set_channels);
+
+/* Convert the format info into a sample spec and a channel map. If the format
+ * info doesn't contain some information, the fallback sample spec and channel
+ * map are used to populate the output.
+ *
+ * pa_format_info_to_sample_spec() exists too. This "version 2" was created,
+ * because the original function doesn't provide the possibility of specifying
+ * a fallback sample spec and channel map. That functionality can't be added to
+ * the original function, because the function is part of the public API and
+ * adding parameters to it would break the API. */
+int pa_format_info_to_sample_spec2(const pa_format_info *f, pa_sample_spec *ss, pa_channel_map *map,
+ const pa_sample_spec *fallback_ss, const pa_channel_map *fallback_map);
+
+/* For compressed formats. Converts the format info into a sample spec and a
+ * channel map that an ALSA device can use as its configuration parameters when
+ * playing back the compressed data. That is, the returned sample spec doesn't
+ * describe the audio content, but the device parameters. */
+int pa_format_info_to_sample_spec_fake(const pa_format_info *f, pa_sample_spec *ss, pa_channel_map *map);
+
+#endif
diff --git a/src/pulsecore/core-rtclock.c b/src/pulsecore/core-rtclock.c
new file mode 100644
index 0000000..2c2e286
--- /dev/null
+++ b/src/pulsecore/core-rtclock.c
@@ -0,0 +1,267 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef OS_IS_DARWIN
+#define _POSIX_C_SOURCE 1
+#endif
+
+#include <stddef.h>
+#include <time.h>
+#include <sys/time.h>
+#include <errno.h>
+
+#ifdef HAVE_SYS_PRCTL_H
+#include <sys/prctl.h>
+#endif
+
+#ifdef OS_IS_DARWIN
+#include <CoreServices/CoreServices.h>
+#include <mach/mach.h>
+#include <mach/mach_time.h>
+#include <unistd.h>
+#endif
+
+#ifdef HAVE_WINDOWS_H
+#include <windows.h>
+#endif
+
+#include <pulse/timeval.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/core-error.h>
+
+#include "core-rtclock.h"
+
+#ifdef OS_IS_WIN32
+static int64_t counter_freq = 0;
+#endif
+
+pa_usec_t pa_rtclock_age(const struct timeval *tv) {
+ struct timeval now;
+ pa_assert(tv);
+
+ return pa_timeval_diff(pa_rtclock_get(&now), tv);
+}
+
+struct timeval *pa_rtclock_get(struct timeval *tv) {
+
+#if defined(OS_IS_DARWIN)
+ uint64_t val, abs_time = mach_absolute_time();
+ Nanoseconds nanos;
+
+ nanos = AbsoluteToNanoseconds(*(AbsoluteTime *) &abs_time);
+ val = *(uint64_t *) &nanos;
+
+ tv->tv_sec = val / PA_NSEC_PER_SEC;
+ tv->tv_usec = (val % PA_NSEC_PER_SEC) / PA_NSEC_PER_USEC;
+
+ return tv;
+
+#elif defined(HAVE_CLOCK_GETTIME)
+ struct timespec ts;
+
+#ifdef CLOCK_MONOTONIC
+ /* No locking or atomic ops for no_monotonic here */
+ static bool no_monotonic = false;
+
+ if (!no_monotonic)
+ if (clock_gettime(CLOCK_MONOTONIC, &ts) < 0)
+ no_monotonic = true;
+
+ if (no_monotonic)
+#endif /* CLOCK_MONOTONIC */
+ pa_assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
+
+ pa_assert(tv);
+
+ tv->tv_sec = ts.tv_sec;
+ tv->tv_usec = ts.tv_nsec / PA_NSEC_PER_USEC;
+
+ return tv;
+#elif defined(OS_IS_WIN32)
+ if (counter_freq > 0) {
+ LARGE_INTEGER count;
+
+ pa_assert_se(QueryPerformanceCounter(&count));
+
+ tv->tv_sec = count.QuadPart / counter_freq;
+ tv->tv_usec = (count.QuadPart % counter_freq) * PA_USEC_PER_SEC / counter_freq;
+
+ return tv;
+ }
+#endif /* HAVE_CLOCK_GETTIME */
+
+ return pa_gettimeofday(tv);
+}
+
+bool pa_rtclock_hrtimer(void) {
+
+#if defined (OS_IS_DARWIN)
+ mach_timebase_info_data_t tbi;
+ uint64_t time_nsec;
+
+ mach_timebase_info(&tbi);
+
+ /* nsec = nticks * (N/D) - we want 1 tick == resolution !? */
+ time_nsec = tbi.numer / tbi.denom;
+ return time_nsec <= (long) (PA_HRTIMER_THRESHOLD_USEC*PA_NSEC_PER_USEC);
+
+#elif defined(HAVE_CLOCK_GETTIME)
+ struct timespec ts;
+
+#ifdef CLOCK_MONOTONIC
+
+ if (clock_getres(CLOCK_MONOTONIC, &ts) >= 0)
+ return ts.tv_sec == 0 && ts.tv_nsec <= (long) (PA_HRTIMER_THRESHOLD_USEC*PA_NSEC_PER_USEC);
+
+#endif /* CLOCK_MONOTONIC */
+
+ pa_assert_se(clock_getres(CLOCK_REALTIME, &ts) == 0);
+ return ts.tv_sec == 0 && ts.tv_nsec <= (long) (PA_HRTIMER_THRESHOLD_USEC*PA_NSEC_PER_USEC);
+
+#elif defined(OS_IS_WIN32)
+
+ if (counter_freq > 0)
+ return counter_freq >= (int64_t) (PA_USEC_PER_SEC/PA_HRTIMER_THRESHOLD_USEC);
+
+#endif /* HAVE_CLOCK_GETTIME */
+
+ return false;
+}
+
+#define TIMER_SLACK_NS (int) ((500 * PA_NSEC_PER_USEC))
+
+void pa_rtclock_hrtimer_enable(void) {
+
+#ifdef PR_SET_TIMERSLACK
+ int slack_ns;
+
+ if ((slack_ns = prctl(PR_GET_TIMERSLACK, 0, 0, 0, 0)) < 0) {
+ pa_log_info("PR_GET_TIMERSLACK/PR_SET_TIMERSLACK not supported.");
+ return;
+ }
+
+ pa_log_debug("Timer slack is set to %i us.", (int) (slack_ns/PA_NSEC_PER_USEC));
+
+ if (slack_ns > TIMER_SLACK_NS) {
+ slack_ns = TIMER_SLACK_NS;
+
+ pa_log_debug("Setting timer slack to %i us.", (int) (slack_ns/PA_NSEC_PER_USEC));
+
+ if (prctl(PR_SET_TIMERSLACK, slack_ns, 0, 0, 0) < 0) {
+ pa_log_warn("PR_SET_TIMERSLACK failed: %s", pa_cstrerror(errno));
+ return;
+ }
+ }
+
+#elif defined(OS_IS_WIN32)
+ LARGE_INTEGER freq;
+
+ pa_assert_se(QueryPerformanceFrequency(&freq));
+ counter_freq = freq.QuadPart;
+
+#endif
+}
+
+struct timeval* pa_rtclock_from_wallclock(struct timeval *tv) {
+ struct timeval wc_now, rt_now;
+
+ pa_assert(tv);
+
+ pa_gettimeofday(&wc_now);
+ pa_rtclock_get(&rt_now);
+
+ /* pa_timeval_sub() saturates on underflow! */
+
+ if (pa_timeval_cmp(&wc_now, tv) < 0)
+ pa_timeval_add(&rt_now, pa_timeval_diff(tv, &wc_now));
+ else
+ pa_timeval_sub(&rt_now, pa_timeval_diff(&wc_now, tv));
+
+ *tv = rt_now;
+
+ return tv;
+}
+
+#ifdef HAVE_CLOCK_GETTIME
+pa_usec_t pa_timespec_load(const struct timespec *ts) {
+
+ if (PA_UNLIKELY(!ts))
+ return PA_USEC_INVALID;
+
+ return
+ (pa_usec_t) ts->tv_sec * PA_USEC_PER_SEC +
+ (pa_usec_t) ts->tv_nsec / PA_NSEC_PER_USEC;
+}
+
+struct timespec* pa_timespec_store(struct timespec *ts, pa_usec_t v) {
+ pa_assert(ts);
+
+ if (PA_UNLIKELY(v == PA_USEC_INVALID)) {
+ ts->tv_sec = PA_INT_TYPE_MAX(time_t);
+ ts->tv_nsec = (long) (PA_NSEC_PER_SEC-1);
+ return NULL;
+ }
+
+ ts->tv_sec = (time_t) (v / PA_USEC_PER_SEC);
+ ts->tv_nsec = (long) ((v % PA_USEC_PER_SEC) * PA_NSEC_PER_USEC);
+
+ return ts;
+}
+#endif
+
+static struct timeval* wallclock_from_rtclock(struct timeval *tv) {
+ struct timeval wc_now, rt_now;
+
+ pa_assert(tv);
+
+ pa_gettimeofday(&wc_now);
+ pa_rtclock_get(&rt_now);
+
+ /* pa_timeval_sub() saturates on underflow! */
+
+ if (pa_timeval_cmp(&rt_now, tv) < 0)
+ pa_timeval_add(&wc_now, pa_timeval_diff(tv, &rt_now));
+ else
+ pa_timeval_sub(&wc_now, pa_timeval_diff(&rt_now, tv));
+
+ *tv = wc_now;
+
+ return tv;
+}
+
+struct timeval* pa_timeval_rtstore(struct timeval *tv, pa_usec_t v, bool rtclock) {
+ pa_assert(tv);
+
+ if (v == PA_USEC_INVALID)
+ return NULL;
+
+ pa_timeval_store(tv, v);
+
+ if (rtclock)
+ tv->tv_usec |= PA_TIMEVAL_RTCLOCK;
+ else
+ wallclock_from_rtclock(tv);
+
+ return tv;
+}
diff --git a/src/pulsecore/core-rtclock.h b/src/pulsecore/core-rtclock.h
new file mode 100644
index 0000000..89ad237
--- /dev/null
+++ b/src/pulsecore/core-rtclock.h
@@ -0,0 +1,53 @@
+#ifndef foopulsertclockhfoo
+#define foopulsertclockhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulsecore/macro.h>
+#include <pulse/sample.h>
+
+struct timeval;
+
+/* Something like pulse/timeval.h but based on CLOCK_MONOTONIC */
+
+struct timeval *pa_rtclock_get(struct timeval *ts);
+
+pa_usec_t pa_rtclock_age(const struct timeval *tv);
+bool pa_rtclock_hrtimer(void);
+void pa_rtclock_hrtimer_enable(void);
+
+/* timer with a resolution better than this are considered high-resolution */
+#define PA_HRTIMER_THRESHOLD_USEC 10
+
+/* bit to set in tv.tv_usec to mark that the timeval is in monotonic time */
+#define PA_TIMEVAL_RTCLOCK ((time_t) (1LU << 30))
+
+struct timeval* pa_rtclock_from_wallclock(struct timeval *tv);
+
+#ifdef HAVE_CLOCK_GETTIME
+struct timespec;
+
+pa_usec_t pa_timespec_load(const struct timespec *ts);
+struct timespec* pa_timespec_store(struct timespec *ts, pa_usec_t v);
+#endif
+
+struct timeval* pa_timeval_rtstore(struct timeval *tv, pa_usec_t v, bool rtclock);
+
+#endif
diff --git a/src/pulsecore/core-scache.c b/src/pulsecore/core-scache.c
new file mode 100644
index 0000000..026eeca
--- /dev/null
+++ b/src/pulsecore/core-scache.c
@@ -0,0 +1,521 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2008 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <limits.h>
+#include <time.h>
+
+#ifdef HAVE_GLOB_H
+#include <glob.h>
+#endif
+
+#ifdef HAVE_WINDOWS_H
+#include <windows.h>
+#endif
+
+#include <pulse/mainloop.h>
+#include <pulse/channelmap.h>
+#include <pulse/timeval.h>
+#include <pulse/util.h>
+#include <pulse/volume.h>
+#include <pulse/xmalloc.h>
+#include <pulse/rtclock.h>
+
+#include <pulsecore/sink-input.h>
+#include <pulsecore/play-memchunk.h>
+#include <pulsecore/core-subscribe.h>
+#include <pulsecore/namereg.h>
+#include <pulsecore/sound-file.h>
+#include <pulsecore/core-rtclock.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/log.h>
+#include <pulsecore/core-error.h>
+#include <pulsecore/macro.h>
+
+#include "core-scache.h"
+
+#define UNLOAD_POLL_TIME (60 * PA_USEC_PER_SEC)
+
+static void timeout_callback(pa_mainloop_api *m, pa_time_event *e, const struct timeval *t, void *userdata) {
+ pa_core *c = userdata;
+
+ pa_assert(c);
+ pa_assert(c->mainloop == m);
+ pa_assert(c->scache_auto_unload_event == e);
+
+ pa_scache_unload_unused(c);
+
+ pa_core_rttime_restart(c, e, pa_rtclock_now() + UNLOAD_POLL_TIME);
+}
+
+static void free_entry(pa_scache_entry *e) {
+ pa_assert(e);
+
+ pa_namereg_unregister(e->core, e->name);
+ pa_subscription_post(e->core, PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE|PA_SUBSCRIPTION_EVENT_REMOVE, e->index);
+ pa_hook_fire(&e->core->hooks[PA_CORE_HOOK_SAMPLE_CACHE_UNLINK], e);
+ pa_xfree(e->name);
+ pa_xfree(e->filename);
+ if (e->memchunk.memblock)
+ pa_memblock_unref(e->memchunk.memblock);
+ if (e->proplist)
+ pa_proplist_free(e->proplist);
+ pa_xfree(e);
+}
+
+static pa_scache_entry* scache_add_item(pa_core *c, const char *name, bool *new_sample) {
+ pa_scache_entry *e;
+
+ pa_assert(c);
+ pa_assert(name);
+ pa_assert(new_sample);
+
+ if ((e = pa_namereg_get(c, name, PA_NAMEREG_SAMPLE))) {
+ if (e->memchunk.memblock)
+ pa_memblock_unref(e->memchunk.memblock);
+
+ pa_xfree(e->filename);
+ pa_proplist_clear(e->proplist);
+
+ pa_assert(e->core == c);
+
+ pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE|PA_SUBSCRIPTION_EVENT_CHANGE, e->index);
+ *new_sample = false;
+ } else {
+ e = pa_xnew(pa_scache_entry, 1);
+
+ if (!pa_namereg_register(c, name, PA_NAMEREG_SAMPLE, e, true)) {
+ pa_xfree(e);
+ return NULL;
+ }
+
+ e->name = pa_xstrdup(name);
+ e->core = c;
+ e->proplist = pa_proplist_new();
+
+ pa_idxset_put(c->scache, e, &e->index);
+
+ pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE|PA_SUBSCRIPTION_EVENT_NEW, e->index);
+ *new_sample = true;
+ }
+
+ e->last_used_time = 0;
+ pa_memchunk_reset(&e->memchunk);
+ e->filename = NULL;
+ e->lazy = false;
+ e->last_used_time = 0;
+
+ pa_sample_spec_init(&e->sample_spec);
+ pa_channel_map_init(&e->channel_map);
+ pa_cvolume_init(&e->volume);
+ e->volume_is_set = false;
+
+ pa_proplist_sets(e->proplist, PA_PROP_MEDIA_ROLE, "event");
+
+ return e;
+}
+
+int pa_scache_add_item(
+ pa_core *c,
+ const char *name,
+ const pa_sample_spec *ss,
+ const pa_channel_map *map,
+ const pa_memchunk *chunk,
+ pa_proplist *p,
+ uint32_t *idx) {
+
+ pa_scache_entry *e;
+ char st[PA_SAMPLE_SPEC_SNPRINT_MAX];
+ pa_channel_map tmap;
+ bool new_sample;
+
+ pa_assert(c);
+ pa_assert(name);
+ pa_assert(!ss || pa_sample_spec_valid(ss));
+ pa_assert(!map || (pa_channel_map_valid(map) && ss && pa_channel_map_compatible(map, ss)));
+
+ if (ss && !map) {
+ pa_channel_map_init_extend(&tmap, ss->channels, PA_CHANNEL_MAP_DEFAULT);
+ map = &tmap;
+ }
+
+ if (chunk && chunk->length > PA_SCACHE_ENTRY_SIZE_MAX)
+ return -1;
+
+ if (!(e = scache_add_item(c, name, &new_sample)))
+ return -1;
+
+ pa_sample_spec_init(&e->sample_spec);
+ pa_channel_map_init(&e->channel_map);
+ pa_cvolume_init(&e->volume);
+ e->volume_is_set = false;
+
+ if (ss) {
+ e->sample_spec = *ss;
+ pa_cvolume_reset(&e->volume, ss->channels);
+ }
+
+ if (map)
+ e->channel_map = *map;
+
+ if (chunk) {
+ e->memchunk = *chunk;
+ pa_memblock_ref(e->memchunk.memblock);
+ }
+
+ if (p)
+ pa_proplist_update(e->proplist, PA_UPDATE_REPLACE, p);
+
+ if (idx)
+ *idx = e->index;
+
+ pa_log_debug("Created sample \"%s\" (#%d), %lu bytes with sample spec %s",
+ name, e->index, (unsigned long) e->memchunk.length,
+ pa_sample_spec_snprint(st, sizeof(st), &e->sample_spec));
+
+ pa_hook_fire(&e->core->hooks[new_sample ? PA_CORE_HOOK_SAMPLE_CACHE_NEW : PA_CORE_HOOK_SAMPLE_CACHE_CHANGED], e);
+
+ return 0;
+}
+
+int pa_scache_add_file(pa_core *c, const char *name, const char *filename, uint32_t *idx) {
+ pa_sample_spec ss;
+ pa_channel_map map;
+ pa_memchunk chunk;
+ int r;
+ pa_proplist *p;
+
+#ifdef OS_IS_WIN32
+ char buf[MAX_PATH];
+
+ if (ExpandEnvironmentStrings(filename, buf, MAX_PATH))
+ filename = buf;
+#endif
+
+ pa_assert(c);
+ pa_assert(name);
+ pa_assert(filename);
+
+ p = pa_proplist_new();
+ pa_proplist_sets(p, PA_PROP_MEDIA_FILENAME, filename);
+
+ if (pa_sound_file_load(c->mempool, filename, &ss, &map, &chunk, p) < 0) {
+ pa_proplist_free(p);
+ return -1;
+ }
+
+ r = pa_scache_add_item(c, name, &ss, &map, &chunk, p, idx);
+ pa_memblock_unref(chunk.memblock);
+ pa_proplist_free(p);
+
+ return r;
+}
+
+int pa_scache_add_file_lazy(pa_core *c, const char *name, const char *filename, uint32_t *idx) {
+ pa_scache_entry *e;
+ bool new_sample;
+
+#ifdef OS_IS_WIN32
+ char buf[MAX_PATH];
+
+ if (ExpandEnvironmentStrings(filename, buf, MAX_PATH))
+ filename = buf;
+#endif
+
+ pa_assert(c);
+ pa_assert(name);
+ pa_assert(filename);
+
+ if (!(e = scache_add_item(c, name, &new_sample)))
+ return -1;
+
+ e->lazy = true;
+ e->filename = pa_xstrdup(filename);
+
+ pa_proplist_sets(e->proplist, PA_PROP_MEDIA_FILENAME, filename);
+
+ if (!c->scache_auto_unload_event)
+ c->scache_auto_unload_event = pa_core_rttime_new(c, pa_rtclock_now() + UNLOAD_POLL_TIME, timeout_callback, c);
+
+ if (idx)
+ *idx = e->index;
+
+ pa_hook_fire(&e->core->hooks[new_sample ? PA_CORE_HOOK_SAMPLE_CACHE_NEW : PA_CORE_HOOK_SAMPLE_CACHE_CHANGED], e);
+
+ return 0;
+}
+
+int pa_scache_remove_item(pa_core *c, const char *name) {
+ pa_scache_entry *e;
+
+ pa_assert(c);
+ pa_assert(name);
+
+ if (!(e = pa_namereg_get(c, name, PA_NAMEREG_SAMPLE)))
+ return -1;
+
+ pa_assert_se(pa_idxset_remove_by_data(c->scache, e, NULL) == e);
+
+ pa_log_debug("Removed sample \"%s\"", name);
+
+ free_entry(e);
+
+ return 0;
+}
+
+void pa_scache_free_all(pa_core *c) {
+ pa_assert(c);
+
+ pa_idxset_remove_all(c->scache, (pa_free_cb_t) free_entry);
+
+ if (c->scache_auto_unload_event) {
+ c->mainloop->time_free(c->scache_auto_unload_event);
+ c->scache_auto_unload_event = NULL;
+ }
+}
+
+int pa_scache_play_item(pa_core *c, const char *name, pa_sink *sink, pa_volume_t volume, pa_proplist *p, uint32_t *sink_input_idx) {
+ pa_scache_entry *e;
+ pa_cvolume r;
+ pa_proplist *merged;
+ bool pass_volume;
+
+ pa_assert(c);
+ pa_assert(name);
+ pa_assert(sink);
+
+ if (!(e = pa_namereg_get(c, name, PA_NAMEREG_SAMPLE)))
+ return -1;
+
+ merged = pa_proplist_new();
+ pa_proplist_sets(merged, PA_PROP_MEDIA_NAME, name);
+ pa_proplist_sets(merged, PA_PROP_EVENT_ID, name);
+
+ if (e->lazy && !e->memchunk.memblock) {
+ pa_channel_map old_channel_map = e->channel_map;
+
+ if (pa_sound_file_load(c->mempool, e->filename, &e->sample_spec, &e->channel_map, &e->memchunk, merged) < 0)
+ goto fail;
+
+ pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE|PA_SUBSCRIPTION_EVENT_CHANGE, e->index);
+
+ if (e->volume_is_set) {
+ if (pa_cvolume_valid(&e->volume))
+ pa_cvolume_remap(&e->volume, &old_channel_map, &e->channel_map);
+ else
+ pa_cvolume_reset(&e->volume, e->sample_spec.channels);
+ }
+ }
+
+ if (!e->memchunk.memblock)
+ goto fail;
+
+ pa_log_debug("Playing sample \"%s\" on \"%s\"", name, sink->name);
+
+ pass_volume = true;
+
+ if (e->volume_is_set && PA_VOLUME_IS_VALID(volume)) {
+ pa_cvolume_set(&r, e->sample_spec.channels, volume);
+ pa_sw_cvolume_multiply(&r, &r, &e->volume);
+ } else if (e->volume_is_set)
+ r = e->volume;
+ else if (PA_VOLUME_IS_VALID(volume))
+ pa_cvolume_set(&r, e->sample_spec.channels, volume);
+ else
+ pass_volume = false;
+
+ pa_proplist_update(merged, PA_UPDATE_REPLACE, e->proplist);
+
+ if (p)
+ pa_proplist_update(merged, PA_UPDATE_REPLACE, p);
+
+ if (pa_play_memchunk(sink,
+ &e->sample_spec, &e->channel_map,
+ &e->memchunk,
+ pass_volume ? &r : NULL,
+ merged,
+ PA_SINK_INPUT_NO_CREATE_ON_SUSPEND|PA_SINK_INPUT_KILL_ON_SUSPEND, sink_input_idx) < 0)
+ goto fail;
+
+ pa_proplist_free(merged);
+
+ if (e->lazy)
+ time(&e->last_used_time);
+
+ return 0;
+
+fail:
+ pa_proplist_free(merged);
+ return -1;
+}
+
+int pa_scache_play_item_by_name(pa_core *c, const char *name, const char*sink_name, pa_volume_t volume, pa_proplist *p, uint32_t *sink_input_idx) {
+ pa_sink *sink;
+
+ pa_assert(c);
+ pa_assert(name);
+
+ if (!(sink = pa_namereg_get(c, sink_name, PA_NAMEREG_SINK)))
+ return -1;
+
+ return pa_scache_play_item(c, name, sink, volume, p, sink_input_idx);
+}
+
+const char *pa_scache_get_name_by_id(pa_core *c, uint32_t id) {
+ pa_scache_entry *e;
+
+ pa_assert(c);
+ pa_assert(id != PA_IDXSET_INVALID);
+
+ if (!c->scache || !(e = pa_idxset_get_by_index(c->scache, id)))
+ return NULL;
+
+ return e->name;
+}
+
+uint32_t pa_scache_get_id_by_name(pa_core *c, const char *name) {
+ pa_scache_entry *e;
+
+ pa_assert(c);
+ pa_assert(name);
+
+ if (!(e = pa_namereg_get(c, name, PA_NAMEREG_SAMPLE)))
+ return PA_IDXSET_INVALID;
+
+ return e->index;
+}
+
+size_t pa_scache_total_size(pa_core *c) {
+ pa_scache_entry *e;
+ uint32_t idx;
+ size_t sum = 0;
+
+ pa_assert(c);
+
+ if (!c->scache || !pa_idxset_size(c->scache))
+ return 0;
+
+ PA_IDXSET_FOREACH(e, c->scache, idx)
+ if (e->memchunk.memblock)
+ sum += e->memchunk.length;
+
+ return sum;
+}
+
+void pa_scache_unload_unused(pa_core *c) {
+ pa_scache_entry *e;
+ time_t now;
+ uint32_t idx;
+
+ pa_assert(c);
+
+ if (!c->scache || !pa_idxset_size(c->scache))
+ return;
+
+ time(&now);
+
+ PA_IDXSET_FOREACH(e, c->scache, idx) {
+
+ if (!e->lazy || !e->memchunk.memblock)
+ continue;
+
+ if (e->last_used_time + c->scache_idle_time > now)
+ continue;
+
+ pa_memblock_unref(e->memchunk.memblock);
+ pa_memchunk_reset(&e->memchunk);
+
+ pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE|PA_SUBSCRIPTION_EVENT_CHANGE, e->index);
+ }
+}
+
+static void add_file(pa_core *c, const char *pathname) {
+ struct stat st;
+ const char *e;
+
+ pa_core_assert_ref(c);
+ pa_assert(pathname);
+
+ e = pa_path_get_filename(pathname);
+
+ if (stat(pathname, &st) < 0) {
+ pa_log("stat('%s'): %s", pathname, pa_cstrerror(errno));
+ return;
+ }
+
+#if defined(S_ISREG) && defined(S_ISLNK)
+ if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))
+#endif
+ pa_scache_add_file_lazy(c, e, pathname, NULL);
+}
+
+int pa_scache_add_directory_lazy(pa_core *c, const char *pathname) {
+ DIR *dir;
+
+ pa_core_assert_ref(c);
+ pa_assert(pathname);
+
+ /* First try to open this as directory */
+ if (!(dir = opendir(pathname))) {
+#ifdef HAVE_GLOB_H
+ glob_t p;
+ unsigned int i;
+ /* If that fails, try to open it as shell glob */
+
+ if (glob(pathname, GLOB_ERR|GLOB_NOSORT, NULL, &p) < 0) {
+ pa_log("failed to open directory '%s': %s", pathname, pa_cstrerror(errno));
+ return -1;
+ }
+
+ for (i = 0; i < p.gl_pathc; i++)
+ add_file(c, p.gl_pathv[i]);
+
+ globfree(&p);
+#else
+ return -1;
+#endif
+ } else {
+ struct dirent *e;
+
+ while ((e = readdir(dir))) {
+ char *p;
+
+ if (e->d_name[0] == '.')
+ continue;
+
+ p = pa_sprintf_malloc("%s" PA_PATH_SEP "%s", pathname, e->d_name);
+ add_file(c, p);
+ pa_xfree(p);
+ }
+
+ closedir(dir);
+ }
+
+ return 0;
+}
diff --git a/src/pulsecore/core-scache.h b/src/pulsecore/core-scache.h
new file mode 100644
index 0000000..f0eacaa
--- /dev/null
+++ b/src/pulsecore/core-scache.h
@@ -0,0 +1,68 @@
+#ifndef foocorescachehfoo
+#define foocorescachehfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulsecore/core.h>
+#include <pulsecore/memchunk.h>
+#include <pulsecore/sink.h>
+
+#define PA_SCACHE_ENTRY_SIZE_MAX (1024*1024*16)
+
+typedef struct pa_scache_entry {
+ uint32_t index;
+ pa_core *core;
+
+ char *name;
+
+ pa_cvolume volume;
+ bool volume_is_set;
+ pa_sample_spec sample_spec;
+ pa_channel_map channel_map;
+ pa_memchunk memchunk;
+
+ char *filename;
+
+ bool lazy;
+ time_t last_used_time;
+
+ pa_proplist *proplist;
+} pa_scache_entry;
+
+int pa_scache_add_item(pa_core *c, const char *name, const pa_sample_spec *ss, const pa_channel_map *map, const pa_memchunk *chunk, pa_proplist *p, uint32_t *idx);
+int pa_scache_add_file(pa_core *c, const char *name, const char *filename, uint32_t *idx);
+int pa_scache_add_file_lazy(pa_core *c, const char *name, const char *filename, uint32_t *idx);
+
+int pa_scache_add_directory_lazy(pa_core *c, const char *pathname);
+
+int pa_scache_remove_item(pa_core *c, const char *name);
+int pa_scache_play_item(pa_core *c, const char *name, pa_sink *sink, pa_volume_t volume, pa_proplist *p, uint32_t *sink_input_idx);
+int pa_scache_play_item_by_name(pa_core *c, const char *name, const char*sink_name, pa_volume_t volume, pa_proplist *p, uint32_t *sink_input_idx);
+void pa_scache_free_all(pa_core *c);
+
+const char *pa_scache_get_name_by_id(pa_core *c, uint32_t id);
+uint32_t pa_scache_get_id_by_name(pa_core *c, const char *name);
+
+size_t pa_scache_total_size(pa_core *c);
+
+void pa_scache_unload_unused(pa_core *c);
+
+#endif
diff --git a/src/pulsecore/core-subscribe.c b/src/pulsecore/core-subscribe.c
new file mode 100644
index 0000000..8c91059
--- /dev/null
+++ b/src/pulsecore/core-subscribe.c
@@ -0,0 +1,262 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+
+#include "core-subscribe.h"
+
+/* The subscription subsystem may be used to be notified whenever an
+ * entity (sink, source, ...) is created or deleted. Modules may
+ * register a callback function that is called whenever an event
+ * matching a subscription mask happens. The execution of the callback
+ * function is postponed to the next main loop iteration, i.e. is not
+ * called from within the stack frame the entity was created in. */
+
+struct pa_subscription {
+ pa_core *core;
+ bool dead;
+
+ pa_subscription_cb_t callback;
+ void *userdata;
+ pa_subscription_mask_t mask;
+
+ PA_LLIST_FIELDS(pa_subscription);
+};
+
+struct pa_subscription_event {
+ pa_core *core;
+
+ pa_subscription_event_type_t type;
+ uint32_t index;
+
+ PA_LLIST_FIELDS(pa_subscription_event);
+};
+
+static void sched_event(pa_core *c);
+
+/* Allocate a new subscription object for the given subscription mask. Use the specified callback function and user data */
+pa_subscription* pa_subscription_new(pa_core *c, pa_subscription_mask_t m, pa_subscription_cb_t callback, void *userdata) {
+ pa_subscription *s;
+
+ pa_assert(c);
+ pa_assert(m);
+ pa_assert(callback);
+
+ s = pa_xnew(pa_subscription, 1);
+ s->core = c;
+ s->dead = false;
+ s->callback = callback;
+ s->userdata = userdata;
+ s->mask = m;
+
+ PA_LLIST_PREPEND(pa_subscription, c->subscriptions, s);
+ return s;
+}
+
+/* Free a subscription object, effectively marking it for deletion */
+void pa_subscription_free(pa_subscription*s) {
+ pa_assert(s);
+ pa_assert(!s->dead);
+
+ s->dead = true;
+ sched_event(s->core);
+}
+
+static void free_subscription(pa_subscription *s) {
+ pa_assert(s);
+ pa_assert(s->core);
+
+ PA_LLIST_REMOVE(pa_subscription, s->core->subscriptions, s);
+ pa_xfree(s);
+}
+
+static void free_event(pa_subscription_event *s) {
+ pa_assert(s);
+ pa_assert(s->core);
+
+ if (!s->next)
+ s->core->subscription_event_last = s->prev;
+
+ PA_LLIST_REMOVE(pa_subscription_event, s->core->subscription_event_queue, s);
+ pa_xfree(s);
+}
+
+/* Free all subscription objects */
+void pa_subscription_free_all(pa_core *c) {
+ pa_assert(c);
+
+ while (c->subscriptions)
+ free_subscription(c->subscriptions);
+
+ while (c->subscription_event_queue)
+ free_event(c->subscription_event_queue);
+
+ if (c->subscription_defer_event) {
+ c->mainloop->defer_free(c->subscription_defer_event);
+ c->subscription_defer_event = NULL;
+ }
+}
+
+#ifdef DEBUG
+static void dump_event(const char * prefix, pa_subscription_event*e) {
+ const char * const fac_table[] = {
+ [PA_SUBSCRIPTION_EVENT_SINK] = "SINK",
+ [PA_SUBSCRIPTION_EVENT_SOURCE] = "SOURCE",
+ [PA_SUBSCRIPTION_EVENT_SINK_INPUT] = "SINK_INPUT",
+ [PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT] = "SOURCE_OUTPUT",
+ [PA_SUBSCRIPTION_EVENT_MODULE] = "MODULE",
+ [PA_SUBSCRIPTION_EVENT_CLIENT] = "CLIENT",
+ [PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE] = "SAMPLE_CACHE",
+ [PA_SUBSCRIPTION_EVENT_SERVER] = "SERVER",
+ [PA_SUBSCRIPTION_EVENT_AUTOLOAD] = "AUTOLOAD",
+ [PA_SUBSCRIPTION_EVENT_CARD] = "CARD"
+ };
+
+ const char * const type_table[] = {
+ [PA_SUBSCRIPTION_EVENT_NEW] = "NEW",
+ [PA_SUBSCRIPTION_EVENT_CHANGE] = "CHANGE",
+ [PA_SUBSCRIPTION_EVENT_REMOVE] = "REMOVE"
+ };
+
+ pa_log_debug("%s event (%s|%s|%u)",
+ prefix,
+ fac_table[e->type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK],
+ type_table[e->type & PA_SUBSCRIPTION_EVENT_TYPE_MASK],
+ e->index);
+}
+#endif
+
+/* Deferred callback for dispatching subscription events */
+static void defer_cb(pa_mainloop_api *m, pa_defer_event *de, void *userdata) {
+ pa_core *c = userdata;
+ pa_subscription *s;
+
+ pa_assert(c->mainloop == m);
+ pa_assert(c);
+ pa_assert(c->subscription_defer_event == de);
+
+ c->mainloop->defer_enable(c->subscription_defer_event, 0);
+
+ /* Dispatch queued events */
+
+ while (c->subscription_event_queue) {
+ pa_subscription_event *e = c->subscription_event_queue;
+
+ for (s = c->subscriptions; s; s = s->next) {
+
+ if (!s->dead && pa_subscription_match_flags(s->mask, e->type))
+ s->callback(c, e->type, e->index, s->userdata);
+ }
+
+#ifdef DEBUG
+ dump_event("Dispatched", e);
+#endif
+ free_event(e);
+ }
+
+ /* Remove dead subscriptions */
+
+ s = c->subscriptions;
+ while (s) {
+ pa_subscription *n = s->next;
+ if (s->dead)
+ free_subscription(s);
+ s = n;
+ }
+}
+
+/* Schedule an mainloop event so that a pending subscription event is dispatched */
+static void sched_event(pa_core *c) {
+ pa_assert(c);
+
+ if (!c->subscription_defer_event) {
+ c->subscription_defer_event = c->mainloop->defer_new(c->mainloop, defer_cb, c);
+ pa_assert(c->subscription_defer_event);
+ }
+
+ c->mainloop->defer_enable(c->subscription_defer_event, 1);
+}
+
+/* Append a new subscription event to the subscription event queue and schedule a main loop event */
+void pa_subscription_post(pa_core *c, pa_subscription_event_type_t t, uint32_t idx) {
+ pa_subscription_event *e;
+ pa_assert(c);
+
+ /* No need for queuing subscriptions of no one is listening */
+ if (!c->subscriptions)
+ return;
+
+ if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_NEW) {
+ pa_subscription_event *i, *n;
+
+ /* Check for duplicates */
+ for (i = c->subscription_event_last; i; i = n) {
+ n = i->prev;
+
+ /* not the same object type */
+ if (((t ^ i->type) & PA_SUBSCRIPTION_EVENT_FACILITY_MASK))
+ continue;
+
+ /* not the same object */
+ if (i->index != idx)
+ continue;
+
+ if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
+ /* This object is being removed, hence there is no
+ * point in keeping the old events regarding this
+ * entry in the queue. */
+
+ free_event(i);
+ pa_log_debug("Dropped redundant event due to remove event.");
+ continue;
+ }
+
+ if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_CHANGE) {
+ /* This object has changed. If a "new" or "change" event for
+ * this object is still in the queue we can exit. */
+
+ pa_log_debug("Dropped redundant event due to change event.");
+ return;
+ }
+ }
+ }
+
+ e = pa_xnew(pa_subscription_event, 1);
+ e->core = c;
+ e->type = t;
+ e->index = idx;
+
+ PA_LLIST_INSERT_AFTER(pa_subscription_event, c->subscription_event_queue, c->subscription_event_last, e);
+ c->subscription_event_last = e;
+
+#ifdef DEBUG
+ dump_event("Queued", e);
+#endif
+
+ sched_event(c);
+}
diff --git a/src/pulsecore/core-subscribe.h b/src/pulsecore/core-subscribe.h
new file mode 100644
index 0000000..6032dc3
--- /dev/null
+++ b/src/pulsecore/core-subscribe.h
@@ -0,0 +1,37 @@
+#ifndef foocoresubscribehfoo
+#define foocoresubscribehfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+typedef struct pa_subscription pa_subscription;
+typedef struct pa_subscription_event pa_subscription_event;
+
+#include <pulsecore/core.h>
+#include <pulsecore/native-common.h>
+
+typedef void (*pa_subscription_cb_t)(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata);
+
+pa_subscription* pa_subscription_new(pa_core *c, pa_subscription_mask_t m, pa_subscription_cb_t cb, void *userdata);
+void pa_subscription_free(pa_subscription*s);
+void pa_subscription_free_all(pa_core *c);
+
+void pa_subscription_post(pa_core *c, pa_subscription_event_type_t t, uint32_t idx);
+
+#endif
diff --git a/src/pulsecore/core-util.c b/src/pulsecore/core-util.c
new file mode 100644
index 0000000..601b1d1
--- /dev/null
+++ b/src/pulsecore/core-util.c
@@ -0,0 +1,3637 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2004 Joe Marcus Clarke
+ Copyright 2006-2007 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <math.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+
+#ifdef HAVE_LANGINFO_H
+#include <langinfo.h>
+#endif
+
+#ifdef HAVE_UNAME
+#include <sys/utsname.h>
+#endif
+
+#if defined(HAVE_REGEX_H)
+#include <regex.h>
+#elif defined(HAVE_PCREPOSIX_H)
+#include <pcreposix.h>
+#endif
+
+#ifdef HAVE_STRTOD_L
+#ifdef HAVE_LOCALE_H
+#include <locale.h>
+#endif
+#ifdef HAVE_XLOCALE_H
+#include <xlocale.h>
+#endif
+#endif
+
+#ifdef HAVE_SYS_RESOURCE_H
+#include <sys/resource.h>
+#endif
+
+#ifdef HAVE_SYS_CAPABILITY_H
+#include <sys/capability.h>
+#endif
+
+#ifdef HAVE_SYS_MMAN_H
+#include <sys/mman.h>
+#endif
+
+#ifdef HAVE_PTHREAD
+#include <pthread.h>
+#endif
+
+#ifdef HAVE_NETDB_H
+#include <netdb.h>
+#endif
+
+#ifdef HAVE_WINDOWS_H
+#include <windows.h>
+#endif
+
+#ifndef ENOTSUP
+#define ENOTSUP 135
+#endif
+
+#ifdef HAVE_PWD_H
+#include <pwd.h>
+#endif
+
+#ifdef HAVE_GRP_H
+#include <grp.h>
+#endif
+
+#ifdef HAVE_LIBSAMPLERATE
+#include <samplerate.h>
+#endif
+
+#ifdef HAVE_DBUS
+#include <pulsecore/rtkit.h>
+#endif
+
+#if defined(__linux__) && !defined(__ANDROID__)
+#include <sys/personality.h>
+#endif
+
+#ifdef HAVE_CPUID_H
+#include <cpuid.h>
+#endif
+
+#include <pulse/xmalloc.h>
+#include <pulse/util.h>
+#include <pulse/utf8.h>
+
+#include <pulsecore/core-error.h>
+#include <pulsecore/socket.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/thread.h>
+#include <pulsecore/strbuf.h>
+#include <pulsecore/usergroup.h>
+#include <pulsecore/strlist.h>
+#include <pulsecore/pipe.h>
+#include <pulsecore/once.h>
+
+#include "core-util.h"
+
+/* Not all platforms have this */
+#ifndef MSG_NOSIGNAL
+#define MSG_NOSIGNAL 0
+#endif
+
+#define NEWLINE "\r\n"
+#define WHITESPACE "\n\r \t"
+
+static pa_strlist *recorded_env = NULL;
+
+#ifdef OS_IS_WIN32
+static fd_set nonblocking_fds;
+#endif
+
+#ifdef OS_IS_WIN32
+
+/* Returns the directory of the current DLL, with '/bin/' removed if it is the last component */
+char *pa_win32_get_toplevel(HANDLE handle) {
+ static char *toplevel = NULL;
+
+ if (!toplevel) {
+ char library_path[MAX_PATH];
+ char *p;
+
+ if (!GetModuleFileName(handle, library_path, MAX_PATH))
+ return NULL;
+
+ toplevel = pa_xstrdup(library_path);
+
+ p = strrchr(toplevel, PA_PATH_SEP_CHAR);
+ if (p)
+ *p = '\0';
+
+ p = strrchr(toplevel, PA_PATH_SEP_CHAR);
+ if (p && pa_streq(p + 1, "bin"))
+ *p = '\0';
+ }
+
+ return toplevel;
+}
+
+#endif
+
+static void set_nonblock(int fd, bool nonblock) {
+
+#ifdef O_NONBLOCK
+ int v, nv;
+ pa_assert(fd >= 0);
+
+ pa_assert_se((v = fcntl(fd, F_GETFL)) >= 0);
+
+ if (nonblock)
+ nv = v | O_NONBLOCK;
+ else
+ nv = v & ~O_NONBLOCK;
+
+ if (v != nv)
+ pa_assert_se(fcntl(fd, F_SETFL, nv) >= 0);
+
+#elif defined(OS_IS_WIN32)
+ u_long arg;
+
+ if (nonblock)
+ arg = 1;
+ else
+ arg = 0;
+
+ if (ioctlsocket(fd, FIONBIO, &arg) < 0) {
+ pa_assert_se(WSAGetLastError() == WSAENOTSOCK);
+ pa_log_warn("Only sockets can be made non-blocking!");
+ return;
+ }
+
+ /* There is no method to query status, so we remember all fds */
+ if (nonblock)
+ FD_SET(fd, &nonblocking_fds);
+ else
+ FD_CLR(fd, &nonblocking_fds);
+#else
+ pa_log_warn("Non-blocking I/O not supported.!");
+#endif
+
+}
+
+/** Make a file descriptor nonblock. Doesn't do any error checking */
+void pa_make_fd_nonblock(int fd) {
+ set_nonblock(fd, true);
+}
+
+/** Make a file descriptor blocking. Doesn't do any error checking */
+void pa_make_fd_block(int fd) {
+ set_nonblock(fd, false);
+}
+
+/** Query if a file descriptor is non-blocking */
+bool pa_is_fd_nonblock(int fd) {
+
+#ifdef O_NONBLOCK
+ int v;
+ pa_assert(fd >= 0);
+
+ pa_assert_se((v = fcntl(fd, F_GETFL)) >= 0);
+
+ return !!(v & O_NONBLOCK);
+
+#elif defined(OS_IS_WIN32)
+ return !!FD_ISSET(fd, &nonblocking_fds);
+#else
+ return false;
+#endif
+
+}
+
+/* Set the FD_CLOEXEC flag for a fd */
+void pa_make_fd_cloexec(int fd) {
+
+#ifdef FD_CLOEXEC
+ int v;
+ pa_assert(fd >= 0);
+
+ pa_assert_se((v = fcntl(fd, F_GETFD, 0)) >= 0);
+
+ if (!(v & FD_CLOEXEC))
+ pa_assert_se(fcntl(fd, F_SETFD, v|FD_CLOEXEC) >= 0);
+#endif
+
+}
+
+/** Creates a directory securely. Will create parent directories recursively if
+ * required. This will not update permissions on parent directories if they
+ * already exist, however. */
+int pa_make_secure_dir(const char* dir, mode_t m, uid_t uid, gid_t gid, bool update_perms) {
+ struct stat st;
+ int r, saved_errno;
+ bool retry = true;
+
+ pa_assert(dir);
+
+again:
+#ifdef OS_IS_WIN32
+ r = mkdir(dir);
+#else
+{
+ mode_t u;
+ u = umask((~m) & 0777);
+ r = mkdir(dir, m);
+ umask(u);
+}
+#endif
+
+ if (r < 0 && errno == ENOENT && retry) {
+ /* If a parent directory in the path doesn't exist, try to create that
+ * first, then try again. */
+ pa_make_secure_parent_dir(dir, m, uid, gid, false);
+ retry = false;
+ goto again;
+ }
+
+ if (r < 0 && errno != EEXIST)
+ return -1;
+
+#if defined(HAVE_FSTAT) && !defined(OS_IS_WIN32)
+{
+ int fd;
+ if ((fd = open(dir,
+#ifdef O_CLOEXEC
+ O_CLOEXEC|
+#endif
+#ifdef O_NOCTTY
+ O_NOCTTY|
+#endif
+#ifdef O_NOFOLLOW
+ O_NOFOLLOW|
+#endif
+ O_RDONLY)) < 0)
+ goto fail;
+
+ if (fstat(fd, &st) < 0) {
+ pa_assert_se(pa_close(fd) >= 0);
+ goto fail;
+ }
+
+ if (!S_ISDIR(st.st_mode)) {
+ pa_assert_se(pa_close(fd) >= 0);
+ errno = EEXIST;
+ goto fail;
+ }
+
+ if (!update_perms) {
+ pa_assert_se(pa_close(fd) >= 0);
+ return 0;
+ }
+
+#ifdef HAVE_FCHOWN
+ if (uid == (uid_t) -1)
+ uid = getuid();
+ if (gid == (gid_t) -1)
+ gid = getgid();
+ if (((st.st_uid != uid) || (st.st_gid != gid)) && fchown(fd, uid, gid) < 0) {
+ pa_assert_se(pa_close(fd) >= 0);
+ goto fail;
+ }
+#endif
+
+#ifdef HAVE_FCHMOD
+ if ((st.st_mode & 07777) != m && fchmod(fd, m) < 0) {
+ pa_assert_se(pa_close(fd) >= 0);
+ goto fail;
+ };
+#endif
+
+ pa_assert_se(pa_close(fd) >= 0);
+}
+#else
+ pa_log_warn("Secure directory creation not supported on this platform.");
+#endif
+
+ return 0;
+
+fail:
+ saved_errno = errno;
+ rmdir(dir);
+ errno = saved_errno;
+
+ return -1;
+}
+
+/* Return a newly allocated sting containing the parent directory of the specified file */
+char *pa_parent_dir(const char *fn) {
+ char *slash, *dir = pa_xstrdup(fn);
+
+ if ((slash = (char*) pa_path_get_filename(dir)) == dir) {
+ pa_xfree(dir);
+ errno = ENOENT;
+ return NULL;
+ }
+
+ *(slash-1) = 0;
+ return dir;
+}
+
+/* Creates a the parent directory of the specified path securely */
+int pa_make_secure_parent_dir(const char *fn, mode_t m, uid_t uid, gid_t gid, bool update_perms) {
+ int ret = -1;
+ char *dir;
+
+ if (!(dir = pa_parent_dir(fn)))
+ goto finish;
+
+ if (pa_make_secure_dir(dir, m, uid, gid, update_perms) < 0)
+ goto finish;
+
+ ret = 0;
+
+finish:
+ pa_xfree(dir);
+ return ret;
+}
+
+/** Platform independent read function. Necessary since not all
+ * systems treat all file descriptors equal. If type is
+ * non-NULL it is used to cache the type of the fd. This is
+ * useful for making sure that only a single syscall is executed per
+ * function call. The variable pointed to should be initialized to 0
+ * by the caller. */
+ssize_t pa_read(int fd, void *buf, size_t count, int *type) {
+
+#ifdef OS_IS_WIN32
+
+ if (!type || *type == 0) {
+ ssize_t r;
+
+ if ((r = recv(fd, buf, count, 0)) >= 0)
+ return r;
+
+ if (WSAGetLastError() != WSAENOTSOCK) {
+ errno = WSAGetLastError();
+ return r;
+ }
+
+ if (type)
+ *type = 1;
+ }
+
+#endif
+
+ for (;;) {
+ ssize_t r;
+
+ if ((r = read(fd, buf, count)) < 0)
+ if (errno == EINTR)
+ continue;
+
+ return r;
+ }
+}
+
+/** Similar to pa_read(), but handles writes */
+ssize_t pa_write(int fd, const void *buf, size_t count, int *type) {
+
+ if (!type || *type == 0) {
+ ssize_t r;
+
+ for (;;) {
+ if ((r = send(fd, buf, count, MSG_NOSIGNAL)) < 0) {
+
+ if (errno == EINTR)
+ continue;
+
+ break;
+ }
+
+ return r;
+ }
+
+#ifdef OS_IS_WIN32
+ if (WSAGetLastError() != WSAENOTSOCK) {
+ errno = WSAGetLastError();
+ return r;
+ }
+#else
+ if (errno != ENOTSOCK)
+ return r;
+#endif
+
+ if (type)
+ *type = 1;
+ }
+
+ for (;;) {
+ ssize_t r;
+
+ if ((r = write(fd, buf, count)) < 0)
+ if (errno == EINTR)
+ continue;
+
+ return r;
+ }
+}
+
+/** Calls read() in a loop. Makes sure that as much as 'size' bytes,
+ * unless EOF is reached or an error occurred */
+ssize_t pa_loop_read(int fd, void*data, size_t size, int *type) {
+ ssize_t ret = 0;
+ int _type;
+
+ pa_assert(fd >= 0);
+ pa_assert(data);
+ pa_assert(size);
+
+ if (!type) {
+ _type = 0;
+ type = &_type;
+ }
+
+ while (size > 0) {
+ ssize_t r;
+
+ if ((r = pa_read(fd, data, size, type)) < 0)
+ return r;
+
+ if (r == 0)
+ break;
+
+ ret += r;
+ data = (uint8_t*) data + r;
+ size -= (size_t) r;
+ }
+
+ return ret;
+}
+
+/** Similar to pa_loop_read(), but wraps write() */
+ssize_t pa_loop_write(int fd, const void*data, size_t size, int *type) {
+ ssize_t ret = 0;
+ int _type;
+
+ pa_assert(fd >= 0);
+ pa_assert(data);
+ pa_assert(size);
+
+ if (!type) {
+ _type = 0;
+ type = &_type;
+ }
+
+ while (size > 0) {
+ ssize_t r;
+
+ if ((r = pa_write(fd, data, size, type)) < 0)
+ return r;
+
+ if (r == 0)
+ break;
+
+ ret += r;
+ data = (const uint8_t*) data + r;
+ size -= (size_t) r;
+ }
+
+ return ret;
+}
+
+/** Platform independent close function. Necessary since not all
+ * systems treat all file descriptors equal. */
+int pa_close(int fd) {
+
+#ifdef OS_IS_WIN32
+ int ret;
+
+ FD_CLR(fd, &nonblocking_fds);
+
+ if ((ret = closesocket(fd)) == 0)
+ return 0;
+
+ if (WSAGetLastError() != WSAENOTSOCK) {
+ errno = WSAGetLastError();
+ return ret;
+ }
+#endif
+
+ for (;;) {
+ int r;
+
+ if ((r = close(fd)) < 0)
+ if (errno == EINTR)
+ continue;
+
+ return r;
+ }
+}
+
+/* Print a warning messages in case that the given signal is not
+ * blocked or trapped */
+void pa_check_signal_is_blocked(int sig) {
+#ifdef HAVE_SIGACTION
+ struct sigaction sa;
+ sigset_t set;
+
+ /* If POSIX threads are supported use thread-aware
+ * pthread_sigmask() function, to check if the signal is
+ * blocked. Otherwise fall back to sigprocmask() */
+
+#ifdef HAVE_PTHREAD
+ if (pthread_sigmask(SIG_SETMASK, NULL, &set) < 0) {
+#endif
+ if (sigprocmask(SIG_SETMASK, NULL, &set) < 0) {
+ pa_log("sigprocmask(): %s", pa_cstrerror(errno));
+ return;
+ }
+#ifdef HAVE_PTHREAD
+ }
+#endif
+
+ if (sigismember(&set, sig))
+ return;
+
+ /* Check whether the signal is trapped */
+
+ if (sigaction(sig, NULL, &sa) < 0) {
+ pa_log("sigaction(): %s", pa_cstrerror(errno));
+ return;
+ }
+
+ if (sa.sa_handler != SIG_DFL)
+ return;
+
+ pa_log_warn("%s is not trapped. This might cause malfunction!", pa_sig2str(sig));
+#else /* HAVE_SIGACTION */
+ pa_log_warn("%s might not be trapped. This might cause malfunction!", pa_sig2str(sig));
+#endif
+}
+
+/* The following function is based on an example from the GNU libc
+ * documentation. This function is similar to GNU's asprintf(). */
+char *pa_sprintf_malloc(const char *format, ...) {
+ size_t size = 100;
+ char *c = NULL;
+
+ pa_assert(format);
+
+ for(;;) {
+ int r;
+ va_list ap;
+
+ c = pa_xrealloc(c, size);
+
+ va_start(ap, format);
+ r = vsnprintf(c, size, format, ap);
+ va_end(ap);
+
+ c[size-1] = 0;
+
+ if (r > -1 && (size_t) r < size)
+ return c;
+
+ if (r > -1) /* glibc 2.1 */
+ size = (size_t) r+1;
+ else /* glibc 2.0 */
+ size *= 2;
+ }
+}
+
+/* Same as the previous function, but use a va_list instead of an
+ * ellipsis */
+char *pa_vsprintf_malloc(const char *format, va_list ap) {
+ size_t size = 100;
+ char *c = NULL;
+
+ pa_assert(format);
+
+ for(;;) {
+ int r;
+ va_list aq;
+
+ c = pa_xrealloc(c, size);
+
+ va_copy(aq, ap);
+ r = vsnprintf(c, size, format, aq);
+ va_end(aq);
+
+ c[size-1] = 0;
+
+ if (r > -1 && (size_t) r < size)
+ return c;
+
+ if (r > -1) /* glibc 2.1 */
+ size = (size_t) r+1;
+ else /* glibc 2.0 */
+ size *= 2;
+ }
+}
+
+/* Similar to OpenBSD's strlcpy() function */
+char *pa_strlcpy(char *b, const char *s, size_t l) {
+ size_t k;
+
+ pa_assert(b);
+ pa_assert(s);
+ pa_assert(l > 0);
+
+ k = strlen(s);
+
+ if (k > l-1)
+ k = l-1;
+
+ memcpy(b, s, k);
+ b[k] = 0;
+
+ return b;
+}
+
+#ifdef HAVE_SYS_RESOURCE_H
+static int set_nice(int nice_level) {
+#ifdef HAVE_DBUS
+ DBusError error;
+ DBusConnection *bus;
+ int r;
+
+ dbus_error_init(&error);
+#endif
+
+#ifdef HAVE_SYS_RESOURCE_H
+ if (setpriority(PRIO_PROCESS, 0, nice_level) >= 0) {
+ pa_log_debug("setpriority() worked.");
+ return 0;
+ }
+#endif
+
+#ifdef HAVE_DBUS
+ /* Try to talk to RealtimeKit */
+
+ if (!(bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error))) {
+ pa_log("Failed to connect to system bus: %s", error.message);
+ dbus_error_free(&error);
+ errno = -EIO;
+ return -1;
+ }
+
+ /* We need to disable exit on disconnect because otherwise
+ * dbus_shutdown will kill us. See
+ * https://bugs.freedesktop.org/show_bug.cgi?id=16924 */
+ dbus_connection_set_exit_on_disconnect(bus, FALSE);
+
+ r = rtkit_make_high_priority(bus, 0, nice_level);
+ dbus_connection_close(bus);
+ dbus_connection_unref(bus);
+
+ if (r >= 0) {
+ pa_log_debug("RealtimeKit worked.");
+ return 0;
+ }
+
+ errno = -r;
+#endif
+
+ return -1;
+}
+#endif
+
+/* Raise the priority of the current process as much as possible that
+ * is <= the specified nice level..*/
+int pa_raise_priority(int nice_level) {
+
+#ifdef HAVE_SYS_RESOURCE_H
+ int n;
+
+ if (set_nice(nice_level) >= 0) {
+ pa_log_info("Successfully gained nice level %i.", nice_level);
+ return 0;
+ }
+
+ for (n = nice_level+1; n < 0; n++)
+ if (set_nice(n) >= 0) {
+ pa_log_info("Successfully acquired nice level %i, which is lower than the requested %i.", n, nice_level);
+ return 0;
+ }
+
+ pa_log_info("Failed to acquire high-priority scheduling: %s", pa_cstrerror(errno));
+ return -1;
+#endif
+
+#ifdef OS_IS_WIN32
+ if (nice_level < 0) {
+ if (!SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS)) {
+ pa_log_warn("SetPriorityClass() failed: 0x%08X", GetLastError());
+ errno = EPERM;
+ return -1;
+ }
+
+ pa_log_info("Successfully gained high priority class.");
+ }
+#endif
+
+ return 0;
+}
+
+/* Reset the priority to normal, inverting the changes made by
+ * pa_raise_priority() and pa_thread_make_realtime()*/
+void pa_reset_priority(void) {
+#ifdef HAVE_SYS_RESOURCE_H
+ struct sched_param sp;
+
+ setpriority(PRIO_PROCESS, 0, 0);
+
+ pa_zero(sp);
+ pthread_setschedparam(pthread_self(), SCHED_OTHER, &sp);
+#endif
+
+#ifdef OS_IS_WIN32
+ SetPriorityClass(GetCurrentProcess(), NORMAL_PRIORITY_CLASS);
+#endif
+}
+
+/* Check whenever any substring in v matches the provided regex. */
+int pa_match(const char *expr, const char *v) {
+#if defined(HAVE_REGEX_H) || defined(HAVE_PCREPOSIX_H)
+ int k;
+ regex_t re;
+ int r;
+
+ pa_assert(expr);
+ pa_assert(v);
+
+ if (regcomp(&re, expr, REG_NOSUB|REG_EXTENDED) != 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if ((k = regexec(&re, v, 0, NULL, 0)) == 0)
+ r = 1;
+ else if (k == REG_NOMATCH)
+ r = 0;
+ else
+ r = -1;
+
+ regfree(&re);
+
+ if (r < 0)
+ errno = EINVAL;
+
+ return r;
+#else
+ errno = ENOSYS;
+ return -1;
+#endif
+}
+
+/* Check whenever the provided regex pattern is valid. */
+bool pa_is_regex_valid(const char *expr) {
+#if defined(HAVE_REGEX_H) || defined(HAVE_PCREPOSIX_H)
+ regex_t re;
+
+ if (expr == NULL || regcomp(&re, expr, REG_NOSUB|REG_EXTENDED) != 0) {
+ return false;
+ }
+
+ regfree(&re);
+ return true;
+#else
+ return false;
+#endif
+}
+
+/* Try to parse a boolean string value.*/
+int pa_parse_boolean(const char *v) {
+ pa_assert(v);
+
+ /* First we check language independent */
+ if (pa_streq(v, "1") || !strcasecmp(v, "y") || !strcasecmp(v, "t")
+ || !strcasecmp(v, "yes") || !strcasecmp(v, "true") || !strcasecmp(v, "on"))
+ return 1;
+ else if (pa_streq(v, "0") || !strcasecmp(v, "n") || !strcasecmp(v, "f")
+ || !strcasecmp(v, "no") || !strcasecmp(v, "false") || !strcasecmp(v, "off"))
+ return 0;
+
+#ifdef HAVE_LANGINFO_H
+{
+ const char *expr;
+ /* And then we check language dependent */
+ if ((expr = nl_langinfo(YESEXPR)))
+ if (expr[0])
+ if (pa_match(expr, v) > 0)
+ return 1;
+
+ if ((expr = nl_langinfo(NOEXPR)))
+ if (expr[0])
+ if (pa_match(expr, v) > 0)
+ return 0;
+}
+#endif
+
+ errno = EINVAL;
+ return -1;
+}
+
+/* Try to parse a volume string to pa_volume_t. The allowed formats are:
+ * db, % and unsigned integer */
+int pa_parse_volume(const char *v, pa_volume_t *volume) {
+ int len;
+ uint32_t i;
+ double d;
+ char str[64];
+
+ pa_assert(v);
+ pa_assert(volume);
+
+ len = strlen(v);
+
+ if (len <= 0 || len >= 64)
+ return -1;
+
+ memcpy(str, v, len + 1);
+
+ if (str[len - 1] == '%') {
+ str[len - 1] = '\0';
+ if (pa_atod(str, &d) < 0)
+ return -1;
+
+ d = d / 100 * PA_VOLUME_NORM;
+
+ if (d < 0 || d > PA_VOLUME_MAX)
+ return -1;
+
+ *volume = d;
+ return 0;
+ }
+
+ if (len > 2 && (str[len - 1] == 'b' || str[len - 1] == 'B') &&
+ (str[len - 2] == 'd' || str[len - 2] == 'D')) {
+ str[len - 2] = '\0';
+ if (pa_atod(str, &d) < 0)
+ return -1;
+
+ if (d > pa_sw_volume_to_dB(PA_VOLUME_MAX))
+ return -1;
+
+ *volume = pa_sw_volume_from_dB(d);
+ return 0;
+ }
+
+ if (pa_atou(v, &i) < 0 || !PA_VOLUME_IS_VALID(i))
+ return -1;
+
+ *volume = i;
+ return 0;
+}
+
+/* Split the specified string wherever one of the characters in delimiter
+ * occurs. Each time it is called returns a newly allocated string
+ * with pa_xmalloc(). The variable state points to, should be
+ * initialized to NULL before the first call. */
+char *pa_split(const char *c, const char *delimiter, const char**state) {
+ const char *current = *state ? *state : c;
+ size_t l;
+
+ if (!*current)
+ return NULL;
+
+ l = strcspn(current, delimiter);
+ *state = current+l;
+
+ if (**state)
+ (*state)++;
+
+ return pa_xstrndup(current, l);
+}
+
+/* Split the specified string wherever one of the characters in delimiter
+ * occurs. Each time it is called returns a pointer to the substring within the
+ * string and the length in 'n'. Note that the resultant string cannot be used
+ * as-is without the length parameter, since it is merely pointing to a point
+ * within the original string. The variable state points to, should be
+ * initialized to NULL before the first call. */
+const char *pa_split_in_place(const char *c, const char *delimiter, size_t *n, const char**state) {
+ const char *current = *state ? *state : c;
+ size_t l;
+
+ if (!*current)
+ return NULL;
+
+ l = strcspn(current, delimiter);
+ *state = current+l;
+
+ if (**state)
+ (*state)++;
+
+ *n = l;
+ return current;
+}
+
+/* Split a string into words. Otherwise similar to pa_split(). */
+char *pa_split_spaces(const char *c, const char **state) {
+ const char *current = *state ? *state : c;
+ size_t l;
+
+ if (!*current || *c == 0)
+ return NULL;
+
+ current += strspn(current, WHITESPACE);
+ l = strcspn(current, WHITESPACE);
+
+ *state = current+l;
+
+ return pa_xstrndup(current, l);
+}
+
+/* Similar to pa_split_spaces, except this returns a string in-place.
+ Returned string is generally not NULL-terminated.
+ See pa_split_in_place(). */
+const char *pa_split_spaces_in_place(const char *c, size_t *n, const char **state) {
+ const char *current = *state ? *state : c;
+ size_t l;
+
+ if (!*current || *c == 0)
+ return NULL;
+
+ current += strspn(current, WHITESPACE);
+ l = strcspn(current, WHITESPACE);
+
+ *state = current+l;
+
+ *n = l;
+ return current;
+}
+
+PA_STATIC_TLS_DECLARE(signame, pa_xfree);
+
+/* Return the name of an UNIX signal. Similar to Solaris sig2str() */
+const char *pa_sig2str(int sig) {
+ char *t;
+
+ if (sig <= 0)
+ goto fail;
+
+#ifdef NSIG
+ if (sig >= NSIG)
+ goto fail;
+#endif
+
+#ifdef HAVE_SIG2STR
+ {
+ char buf[SIG2STR_MAX];
+
+ if (sig2str(sig, buf) == 0) {
+ pa_xfree(PA_STATIC_TLS_GET(signame));
+ t = pa_sprintf_malloc("SIG%s", buf);
+ PA_STATIC_TLS_SET(signame, t);
+ return t;
+ }
+ }
+#else
+
+ switch (sig) {
+#ifdef SIGHUP
+ case SIGHUP: return "SIGHUP";
+#endif
+ case SIGINT: return "SIGINT";
+#ifdef SIGQUIT
+ case SIGQUIT: return "SIGQUIT";
+#endif
+ case SIGILL: return "SIGULL";
+#ifdef SIGTRAP
+ case SIGTRAP: return "SIGTRAP";
+#endif
+ case SIGABRT: return "SIGABRT";
+#ifdef SIGBUS
+ case SIGBUS: return "SIGBUS";
+#endif
+ case SIGFPE: return "SIGFPE";
+#ifdef SIGKILL
+ case SIGKILL: return "SIGKILL";
+#endif
+#ifdef SIGUSR1
+ case SIGUSR1: return "SIGUSR1";
+#endif
+ case SIGSEGV: return "SIGSEGV";
+#ifdef SIGUSR2
+ case SIGUSR2: return "SIGUSR2";
+#endif
+#ifdef SIGPIPE
+ case SIGPIPE: return "SIGPIPE";
+#endif
+#ifdef SIGALRM
+ case SIGALRM: return "SIGALRM";
+#endif
+ case SIGTERM: return "SIGTERM";
+#ifdef SIGSTKFLT
+ case SIGSTKFLT: return "SIGSTKFLT";
+#endif
+#ifdef SIGCHLD
+ case SIGCHLD: return "SIGCHLD";
+#endif
+#ifdef SIGCONT
+ case SIGCONT: return "SIGCONT";
+#endif
+#ifdef SIGSTOP
+ case SIGSTOP: return "SIGSTOP";
+#endif
+#ifdef SIGTSTP
+ case SIGTSTP: return "SIGTSTP";
+#endif
+#ifdef SIGTTIN
+ case SIGTTIN: return "SIGTTIN";
+#endif
+#ifdef SIGTTOU
+ case SIGTTOU: return "SIGTTOU";
+#endif
+#ifdef SIGURG
+ case SIGURG: return "SIGURG";
+#endif
+#ifdef SIGXCPU
+ case SIGXCPU: return "SIGXCPU";
+#endif
+#ifdef SIGXFSZ
+ case SIGXFSZ: return "SIGXFSZ";
+#endif
+#ifdef SIGVTALRM
+ case SIGVTALRM: return "SIGVTALRM";
+#endif
+#ifdef SIGPROF
+ case SIGPROF: return "SIGPROF";
+#endif
+#ifdef SIGWINCH
+ case SIGWINCH: return "SIGWINCH";
+#endif
+#ifdef SIGIO
+ case SIGIO: return "SIGIO";
+#endif
+#ifdef SIGPWR
+ case SIGPWR: return "SIGPWR";
+#endif
+#ifdef SIGSYS
+ case SIGSYS: return "SIGSYS";
+#endif
+ }
+
+#ifdef SIGRTMIN
+ if (sig >= SIGRTMIN && sig <= SIGRTMAX) {
+ pa_xfree(PA_STATIC_TLS_GET(signame));
+ t = pa_sprintf_malloc("SIGRTMIN+%i", sig - SIGRTMIN);
+ PA_STATIC_TLS_SET(signame, t);
+ return t;
+ }
+#endif
+
+#endif
+
+fail:
+
+ pa_xfree(PA_STATIC_TLS_GET(signame));
+ t = pa_sprintf_malloc("SIG%i", sig);
+ PA_STATIC_TLS_SET(signame, t);
+ return t;
+}
+
+#ifdef HAVE_GRP_H
+
+/* Check whether the specified GID and the group name match */
+static int is_group(gid_t gid, const char *name) {
+ struct group *group = NULL;
+ int r = -1;
+
+ errno = 0;
+ if (!(group = pa_getgrgid_malloc(gid))) {
+ if (!errno)
+ errno = ENOENT;
+
+ pa_log("pa_getgrgid_malloc(%u): %s", gid, pa_cstrerror(errno));
+
+ goto finish;
+ }
+
+ r = pa_streq(name, group->gr_name);
+
+finish:
+ pa_getgrgid_free(group);
+
+ return r;
+}
+
+/* Check the current user is member of the specified group */
+int pa_own_uid_in_group(const char *name, gid_t *gid) {
+ GETGROUPS_T *gids, tgid;
+ long n = sysconf(_SC_NGROUPS_MAX);
+ int r = -1, i, k;
+
+ pa_assert(n > 0);
+
+ gids = pa_xmalloc(sizeof(GETGROUPS_T) * (size_t) n);
+
+ if ((n = getgroups((int) n, gids)) < 0) {
+ pa_log("getgroups(): %s", pa_cstrerror(errno));
+ goto finish;
+ }
+
+ for (i = 0; i < n; i++) {
+
+ if ((k = is_group(gids[i], name)) < 0)
+ goto finish;
+ else if (k > 0) {
+ *gid = gids[i];
+ r = 1;
+ goto finish;
+ }
+ }
+
+ if ((k = is_group(tgid = getgid(), name)) < 0)
+ goto finish;
+ else if (k > 0) {
+ *gid = tgid;
+ r = 1;
+ goto finish;
+ }
+
+ r = 0;
+
+finish:
+
+ pa_xfree(gids);
+ return r;
+}
+
+/* Check whether the specific user id is a member of the specified group */
+int pa_uid_in_group(uid_t uid, const char *name) {
+ struct group *group = NULL;
+ char **i;
+ int r = -1;
+
+ errno = 0;
+ if (!(group = pa_getgrnam_malloc(name))) {
+ if (!errno)
+ errno = ENOENT;
+ goto finish;
+ }
+
+ r = 0;
+ for (i = group->gr_mem; *i; i++) {
+ struct passwd *pw = NULL;
+
+ errno = 0;
+ if (!(pw = pa_getpwnam_malloc(*i)))
+ continue;
+
+ if (pw->pw_uid == uid)
+ r = 1;
+
+ pa_getpwnam_free(pw);
+
+ if (r == 1)
+ break;
+ }
+
+finish:
+ pa_getgrnam_free(group);
+
+ return r;
+}
+
+/* Get the GID of a given group, return (gid_t) -1 on failure. */
+gid_t pa_get_gid_of_group(const char *name) {
+ gid_t ret = (gid_t) -1;
+ struct group *gr = NULL;
+
+ errno = 0;
+ if (!(gr = pa_getgrnam_malloc(name))) {
+ if (!errno)
+ errno = ENOENT;
+ goto finish;
+ }
+
+ ret = gr->gr_gid;
+
+finish:
+ pa_getgrnam_free(gr);
+ return ret;
+}
+
+int pa_check_in_group(gid_t g) {
+ gid_t gids[NGROUPS_MAX];
+ int r;
+
+ if ((r = getgroups(NGROUPS_MAX, gids)) < 0)
+ return -1;
+
+ for (; r > 0; r--)
+ if (gids[r-1] == g)
+ return 1;
+
+ return 0;
+}
+
+#else /* HAVE_GRP_H */
+
+int pa_own_uid_in_group(const char *name, gid_t *gid) {
+ errno = ENOTSUP;
+ return -1;
+
+}
+
+int pa_uid_in_group(uid_t uid, const char *name) {
+ errno = ENOTSUP;
+ return -1;
+}
+
+gid_t pa_get_gid_of_group(const char *name) {
+ errno = ENOTSUP;
+ return (gid_t) -1;
+}
+
+int pa_check_in_group(gid_t g) {
+ errno = ENOTSUP;
+ return -1;
+}
+
+#endif
+
+/* Lock or unlock a file entirely.
+ (advisory on UNIX, mandatory on Windows) */
+int pa_lock_fd(int fd, int b) {
+#ifdef F_SETLKW
+ struct flock f_lock;
+
+ /* Try a R/W lock first */
+
+ f_lock.l_type = (short) (b ? F_WRLCK : F_UNLCK);
+ f_lock.l_whence = SEEK_SET;
+ f_lock.l_start = 0;
+ f_lock.l_len = 0;
+
+ if (fcntl(fd, F_SETLKW, &f_lock) >= 0)
+ return 0;
+
+ /* Perhaps the file descriptor was opened for read only, than try again with a read lock. */
+ if (b && errno == EBADF) {
+ f_lock.l_type = F_RDLCK;
+ if (fcntl(fd, F_SETLKW, &f_lock) >= 0)
+ return 0;
+ }
+
+ pa_log("%slock: %s", !b ? "un" : "", pa_cstrerror(errno));
+#endif
+
+#ifdef OS_IS_WIN32
+ HANDLE h = (HANDLE) _get_osfhandle(fd);
+
+ if (b && LockFile(h, 0, 0, 0xFFFFFFFF, 0xFFFFFFFF))
+ return 0;
+ if (!b && UnlockFile(h, 0, 0, 0xFFFFFFFF, 0xFFFFFFFF))
+ return 0;
+
+ pa_log("%slock failed: 0x%08X", !b ? "un" : "", GetLastError());
+
+ /* FIXME: Needs to set errno! */
+#endif
+
+ return -1;
+}
+
+/* Remove trailing newlines from a string */
+char* pa_strip_nl(char *s) {
+ pa_assert(s);
+
+ s[strcspn(s, NEWLINE)] = 0;
+ return s;
+}
+
+char *pa_strip(char *s) {
+ char *e, *l = NULL;
+
+ /* Drops trailing whitespace. Modifies the string in
+ * place. Returns pointer to first non-space character */
+
+ s += strspn(s, WHITESPACE);
+
+ for (e = s; *e; e++)
+ if (!strchr(WHITESPACE, *e))
+ l = e;
+
+ if (l)
+ *(l+1) = 0;
+ else
+ *s = 0;
+
+ return s;
+}
+
+/* Create a temporary lock file and lock it. */
+int pa_lock_lockfile(const char *fn) {
+ int fd;
+ pa_assert(fn);
+
+ for (;;) {
+ struct stat st;
+
+ if ((fd = pa_open_cloexec(fn, O_CREAT|O_RDWR
+#ifdef O_NOFOLLOW
+ |O_NOFOLLOW
+#endif
+ , S_IRUSR|S_IWUSR)) < 0) {
+ pa_log_warn("Failed to create lock file '%s': %s", fn, pa_cstrerror(errno));
+ goto fail;
+ }
+
+ if (pa_lock_fd(fd, 1) < 0) {
+ pa_log_warn("Failed to lock file '%s'.", fn);
+ goto fail;
+ }
+
+ if (fstat(fd, &st) < 0) {
+ pa_log_warn("Failed to fstat() file '%s': %s", fn, pa_cstrerror(errno));
+ goto fail;
+ }
+
+ /* Check whether the file has been removed meanwhile. When yes,
+ * restart this loop, otherwise, we're done */
+ if (st.st_nlink >= 1)
+ break;
+
+ if (pa_lock_fd(fd, 0) < 0) {
+ pa_log_warn("Failed to unlock file '%s'.", fn);
+ goto fail;
+ }
+
+ if (pa_close(fd) < 0) {
+ pa_log_warn("Failed to close file '%s': %s", fn, pa_cstrerror(errno));
+ fd = -1;
+ goto fail;
+ }
+ }
+
+ return fd;
+
+fail:
+
+ if (fd >= 0) {
+ int saved_errno = errno;
+ pa_close(fd);
+ errno = saved_errno;
+ }
+
+ return -1;
+}
+
+/* Unlock a temporary lock file */
+int pa_unlock_lockfile(const char *fn, int fd) {
+ int r = 0;
+ pa_assert(fd >= 0);
+
+ if (fn) {
+ if (unlink(fn) < 0) {
+ pa_log_warn("Unable to remove lock file '%s': %s", fn, pa_cstrerror(errno));
+ r = -1;
+ }
+ }
+
+ if (pa_lock_fd(fd, 0) < 0) {
+ pa_log_warn("Failed to unlock file '%s'.", fn);
+ r = -1;
+ }
+
+ if (pa_close(fd) < 0) {
+ pa_log_warn("Failed to close '%s': %s", fn, pa_cstrerror(errno));
+ r = -1;
+ }
+
+ return r;
+}
+
+static int check_ours(const char *p) {
+ struct stat st;
+
+ pa_assert(p);
+
+ if (stat(p, &st) < 0)
+ return -errno;
+
+#ifdef HAVE_GETUID
+ if (st.st_uid != getuid() && st.st_uid != 0)
+ return -EACCES;
+#endif
+
+ return 0;
+}
+
+static char *get_pulse_home(void) {
+ char *h, *ret;
+ int t;
+
+ h = pa_get_home_dir_malloc();
+ if (!h) {
+ pa_log_error("Failed to get home directory.");
+ return NULL;
+ }
+
+ t = check_ours(h);
+ if (t < 0 && t != -ENOENT) {
+ pa_log_error("Home directory not accessible: %s", pa_cstrerror(-t));
+ pa_xfree(h);
+ return NULL;
+ }
+
+ /* If the old directory exists, use it. */
+ ret = pa_sprintf_malloc("%s" PA_PATH_SEP ".pulse", h);
+ pa_xfree(h);
+ if (access(ret, F_OK) >= 0)
+ return ret;
+ free(ret);
+
+ /* Otherwise go for the XDG compliant directory. */
+ if (pa_get_config_home_dir(&ret) < 0)
+ return NULL;
+
+ return ret;
+}
+
+char *pa_get_state_dir(void) {
+ char *d;
+
+ /* The state directory shall contain dynamic data that should be
+ * kept across reboots, and is private to this user */
+
+ if (!(d = pa_xstrdup(getenv("PULSE_STATE_PATH"))))
+ if (!(d = get_pulse_home()))
+ return NULL;
+
+ /* If PULSE_STATE_PATH and PULSE_RUNTIME_PATH point to the same
+ * dir then this will break. */
+
+ if (pa_make_secure_dir(d, 0700U, (uid_t) -1, (gid_t) -1, true) < 0) {
+ pa_log_error("Failed to create secure directory (%s): %s", d, pa_cstrerror(errno));
+ pa_xfree(d);
+ return NULL;
+ }
+
+ return d;
+}
+
+char *pa_get_home_dir_malloc(void) {
+ char *homedir;
+ size_t allocated = 128;
+
+ for (;;) {
+ homedir = pa_xmalloc(allocated);
+
+ if (!pa_get_home_dir(homedir, allocated)) {
+ pa_xfree(homedir);
+ return NULL;
+ }
+
+ if (strlen(homedir) < allocated - 1)
+ break;
+
+ pa_xfree(homedir);
+ allocated *= 2;
+ }
+
+ return homedir;
+}
+
+int pa_append_to_home_dir(const char *path, char **_r) {
+ char *home_dir;
+
+ pa_assert(path);
+ pa_assert(_r);
+
+ home_dir = pa_get_home_dir_malloc();
+ if (!home_dir) {
+ pa_log("Failed to get home directory.");
+ return -PA_ERR_NOENTITY;
+ }
+
+ *_r = pa_sprintf_malloc("%s" PA_PATH_SEP "%s", home_dir, path);
+ pa_xfree(home_dir);
+ return 0;
+}
+
+int pa_get_config_home_dir(char **_r) {
+ const char *e;
+ char *home_dir;
+
+ pa_assert(_r);
+
+ e = getenv("XDG_CONFIG_HOME");
+ if (e && *e) {
+ *_r = pa_sprintf_malloc("%s" PA_PATH_SEP "pulse", e);
+ return 0;
+ }
+
+ home_dir = pa_get_home_dir_malloc();
+ if (!home_dir)
+ return -PA_ERR_NOENTITY;
+
+ *_r = pa_sprintf_malloc("%s" PA_PATH_SEP ".config" PA_PATH_SEP "pulse", home_dir);
+ pa_xfree(home_dir);
+ return 0;
+}
+
+int pa_append_to_config_home_dir(const char *path, char **_r) {
+ int r;
+ char *config_home_dir;
+
+ pa_assert(path);
+ pa_assert(_r);
+
+ r = pa_get_config_home_dir(&config_home_dir);
+ if (r < 0)
+ return r;
+
+ *_r = pa_sprintf_malloc("%s" PA_PATH_SEP "%s", config_home_dir, path);
+ pa_xfree(config_home_dir);
+ return 0;
+}
+
+char *pa_get_binary_name_malloc(void) {
+ char *t;
+ size_t allocated = 128;
+
+ for (;;) {
+ t = pa_xmalloc(allocated);
+
+ if (!pa_get_binary_name(t, allocated)) {
+ pa_xfree(t);
+ return NULL;
+ }
+
+ if (strlen(t) < allocated - 1)
+ break;
+
+ pa_xfree(t);
+ allocated *= 2;
+ }
+
+ return t;
+}
+
+static char* make_random_dir(mode_t m) {
+ static const char table[] =
+ "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "0123456789";
+
+ char *fn;
+ size_t pathlen;
+
+ fn = pa_sprintf_malloc("%s" PA_PATH_SEP "pulse-XXXXXXXXXXXX", pa_get_temp_dir());
+ pathlen = strlen(fn);
+
+ for (;;) {
+ size_t i;
+ int r;
+ mode_t u;
+ int saved_errno;
+
+ for (i = pathlen - 12; i < pathlen; i++)
+ fn[i] = table[rand() % (sizeof(table)-1)];
+
+ u = umask((~m) & 0777);
+#ifndef OS_IS_WIN32
+ r = mkdir(fn, m);
+#else
+ r = mkdir(fn);
+#endif
+
+ saved_errno = errno;
+ umask(u);
+ errno = saved_errno;
+
+ if (r >= 0)
+ return fn;
+
+ if (errno != EEXIST) {
+ pa_log_error("Failed to create random directory %s: %s", fn, pa_cstrerror(errno));
+ pa_xfree(fn);
+ return NULL;
+ }
+ }
+}
+
+static int make_random_dir_and_link(mode_t m, const char *k) {
+ char *p;
+
+ if (!(p = make_random_dir(m)))
+ return -1;
+
+#ifdef HAVE_SYMLINK
+ if (symlink(p, k) < 0) {
+ int saved_errno = errno;
+
+ if (errno != EEXIST)
+ pa_log_error("Failed to symlink %s to %s: %s", k, p, pa_cstrerror(errno));
+
+ rmdir(p);
+ pa_xfree(p);
+
+ errno = saved_errno;
+ return -1;
+ }
+#else
+ pa_xfree(p);
+ return -1;
+#endif
+
+ pa_xfree(p);
+ return 0;
+}
+
+char *pa_get_runtime_dir(void) {
+ char *d, *k = NULL, *p = NULL, *t = NULL, *mid;
+ mode_t m;
+
+ /* The runtime directory shall contain dynamic data that needs NOT
+ * to be kept across reboots and is usually private to the user,
+ * except in system mode, where it might be accessible by other
+ * users, too. Since we need POSIX locking and UNIX sockets in
+ * this directory, we try XDG_RUNTIME_DIR first, and if that isn't
+ * set create a directory in $HOME and link it to a random subdir
+ * in /tmp, if it was not explicitly configured. */
+
+ m = pa_in_system_mode() ? 0755U : 0700U;
+
+ /* Use the explicitly configured value if it is set */
+ d = getenv("PULSE_RUNTIME_PATH");
+ if (d) {
+
+ if (pa_make_secure_dir(d, m, (uid_t) -1, (gid_t) -1, true) < 0) {
+ pa_log_error("Failed to create secure directory (%s): %s", d, pa_cstrerror(errno));
+ goto fail;
+ }
+
+ return pa_xstrdup(d);
+ }
+
+ /* Use the XDG standard for the runtime directory. */
+ d = getenv("XDG_RUNTIME_DIR");
+ if (d) {
+#ifdef HAVE_GETUID
+ struct stat st;
+ if (stat(d, &st) == 0 && st.st_uid != getuid()) {
+ pa_log(_("XDG_RUNTIME_DIR (%s) is not owned by us (uid %d), but by uid %d! "
+ "(This could e.g. happen if you try to connect to a non-root PulseAudio as a root user, over the native protocol. Don't do that.)"),
+ d, getuid(), st.st_uid);
+ goto fail;
+ }
+#endif
+
+ k = pa_sprintf_malloc("%s" PA_PATH_SEP "pulse", d);
+
+ if (pa_make_secure_dir(k, m, (uid_t) -1, (gid_t) -1, true) < 0) {
+ pa_log_error("Failed to create secure directory (%s): %s", k, pa_cstrerror(errno));
+ goto fail;
+ }
+
+ return k;
+ }
+
+ /* XDG_RUNTIME_DIR wasn't set, use the old legacy fallback */
+ d = get_pulse_home();
+ if (!d)
+ goto fail;
+
+ if (pa_make_secure_dir(d, m, (uid_t) -1, (gid_t) -1, true) < 0) {
+ pa_log_error("Failed to create secure directory (%s): %s", d, pa_cstrerror(errno));
+ pa_xfree(d);
+ goto fail;
+ }
+
+ mid = pa_machine_id();
+ if (!mid) {
+ pa_xfree(d);
+ goto fail;
+ }
+
+ k = pa_sprintf_malloc("%s" PA_PATH_SEP "%s-runtime", d, mid);
+ pa_xfree(d);
+ pa_xfree(mid);
+
+ for (;;) {
+ /* OK, first let's check if the "runtime" symlink already exists */
+
+ p = pa_readlink(k);
+ if (!p) {
+
+ if (errno != ENOENT) {
+ pa_log_error("Failed to stat runtime directory %s: %s", k, pa_cstrerror(errno));
+ goto fail;
+ }
+
+#ifdef HAVE_SYMLINK
+ /* Hmm, so the runtime directory didn't exist yet, so let's
+ * create one in /tmp and symlink that to it */
+
+ if (make_random_dir_and_link(0700, k) < 0) {
+
+ /* Mhmm, maybe another process was quicker than us,
+ * let's check if that was valid */
+ if (errno == EEXIST)
+ continue;
+
+ goto fail;
+ }
+#else
+ /* No symlink possible, so let's just create the runtime directly
+ * Do not check again if it exists since it cannot be a symlink */
+ if (mkdir(k) < 0 && errno != EEXIST)
+ goto fail;
+#endif
+
+ return k;
+ }
+
+ /* Make sure that this actually makes sense */
+ if (!pa_is_path_absolute(p)) {
+ pa_log_error("Path %s in link %s is not absolute.", p, k);
+ errno = ENOENT;
+ goto fail;
+ }
+
+ /* Hmm, so this symlink is still around, make sure nobody fools us */
+#ifdef HAVE_LSTAT
+{
+ struct stat st;
+ if (lstat(p, &st) < 0) {
+
+ if (errno != ENOENT) {
+ pa_log_error("Failed to stat runtime directory %s: %s", p, pa_cstrerror(errno));
+ goto fail;
+ }
+
+ } else {
+
+ if (S_ISDIR(st.st_mode) &&
+ (st.st_uid == getuid()) &&
+ ((st.st_mode & 0777) == 0700)) {
+
+ pa_xfree(p);
+ return k;
+ }
+
+ pa_log_info("Hmm, runtime path exists, but points to an invalid directory. Changing runtime directory.");
+ }
+}
+#endif
+
+ pa_xfree(p);
+ p = NULL;
+
+ /* Hmm, so the link points to some nonexisting or invalid
+ * dir. Let's replace it by a new link. We first create a
+ * temporary link and then rename that to allow concurrent
+ * execution of this function. */
+
+ t = pa_sprintf_malloc("%s.tmp", k);
+
+ if (make_random_dir_and_link(0700, t) < 0) {
+
+ if (errno != EEXIST) {
+ pa_log_error("Failed to symlink %s: %s", t, pa_cstrerror(errno));
+ goto fail;
+ }
+
+ pa_xfree(t);
+ t = NULL;
+
+ /* Hmm, someone else was quicker then us. Let's give
+ * him some time to finish, and retry. */
+ pa_msleep(10);
+ continue;
+ }
+
+ /* OK, we succeeded in creating the temporary symlink, so
+ * let's rename it */
+ if (rename(t, k) < 0) {
+ pa_log_error("Failed to rename %s to %s: %s", t, k, pa_cstrerror(errno));
+ goto fail;
+ }
+
+ pa_xfree(t);
+ return k;
+ }
+
+fail:
+ pa_xfree(p);
+ pa_xfree(k);
+ pa_xfree(t);
+
+ return NULL;
+}
+
+/* Try to open a configuration file. If "env" is specified, open the
+ * value of the specified environment variable. Otherwise look for a
+ * file "local" in the home directory or a file "global" in global
+ * file system. If "result" is non-NULL, a pointer to a newly
+ * allocated buffer containing the used configuration file is
+ * stored there.*/
+FILE *pa_open_config_file(const char *global, const char *local, const char *env, char **result) {
+ const char *fn;
+ FILE *f;
+
+ if (env && (fn = getenv(env))) {
+ if ((f = pa_fopen_cloexec(fn, "r"))) {
+ if (result)
+ *result = pa_xstrdup(fn);
+
+ return f;
+ }
+
+ pa_log_warn("Failed to open configuration file '%s': %s", fn, pa_cstrerror(errno));
+ return NULL;
+ }
+
+ if (local) {
+ const char *e;
+ char *lfn;
+ char *h;
+
+ if ((e = getenv("PULSE_CONFIG_PATH"))) {
+ fn = lfn = pa_sprintf_malloc("%s" PA_PATH_SEP "%s", e, local);
+ f = pa_fopen_cloexec(fn, "r");
+ } else if ((h = pa_get_home_dir_malloc())) {
+ fn = lfn = pa_sprintf_malloc("%s" PA_PATH_SEP ".pulse" PA_PATH_SEP "%s", h, local);
+ f = pa_fopen_cloexec(fn, "r");
+ if (!f) {
+ free(lfn);
+ fn = lfn = pa_sprintf_malloc("%s" PA_PATH_SEP ".config/pulse" PA_PATH_SEP "%s", h, local);
+ f = pa_fopen_cloexec(fn, "r");
+ }
+ pa_xfree(h);
+ } else
+ return NULL;
+
+ if (f) {
+ if (result)
+ *result = pa_xstrdup(fn);
+
+ pa_xfree(lfn);
+ return f;
+ }
+
+ if (errno != ENOENT) {
+ pa_log_warn("Failed to open configuration file '%s': %s", fn, pa_cstrerror(errno));
+ pa_xfree(lfn);
+ return NULL;
+ }
+
+ pa_xfree(lfn);
+ }
+
+ if (global) {
+ char *gfn;
+
+#ifdef OS_IS_WIN32
+ if (strncmp(global, PA_DEFAULT_CONFIG_DIR, strlen(PA_DEFAULT_CONFIG_DIR)) == 0)
+ gfn = pa_sprintf_malloc("%s" PA_PATH_SEP "etc" PA_PATH_SEP "pulse%s",
+ pa_win32_get_toplevel(NULL),
+ global + strlen(PA_DEFAULT_CONFIG_DIR));
+ else
+#endif
+ gfn = pa_xstrdup(global);
+
+ if ((f = pa_fopen_cloexec(gfn, "r"))) {
+ if (result)
+ *result = gfn;
+ else
+ pa_xfree(gfn);
+
+ return f;
+ }
+ pa_xfree(gfn);
+ }
+
+ errno = ENOENT;
+ return NULL;
+}
+
+char *pa_find_config_file(const char *global, const char *local, const char *env) {
+ const char *fn;
+
+ if (env && (fn = getenv(env))) {
+ if (access(fn, R_OK) == 0)
+ return pa_xstrdup(fn);
+
+ pa_log_warn("Failed to access configuration file '%s': %s", fn, pa_cstrerror(errno));
+ return NULL;
+ }
+
+ if (local) {
+ const char *e;
+ char *lfn;
+ char *h;
+
+ if ((e = getenv("PULSE_CONFIG_PATH")))
+ fn = lfn = pa_sprintf_malloc("%s" PA_PATH_SEP "%s", e, local);
+ else if ((h = pa_get_home_dir_malloc())) {
+ fn = lfn = pa_sprintf_malloc("%s" PA_PATH_SEP ".pulse" PA_PATH_SEP "%s", h, local);
+ pa_xfree(h);
+ } else
+ return NULL;
+
+ if (access(fn, R_OK) == 0) {
+ char *r = pa_xstrdup(fn);
+ pa_xfree(lfn);
+ return r;
+ }
+
+ if (errno != ENOENT) {
+ pa_log_warn("Failed to access configuration file '%s': %s", fn, pa_cstrerror(errno));
+ pa_xfree(lfn);
+ return NULL;
+ }
+
+ pa_xfree(lfn);
+ }
+
+ if (global) {
+ char *gfn;
+
+#ifdef OS_IS_WIN32
+ if (strncmp(global, PA_DEFAULT_CONFIG_DIR, strlen(PA_DEFAULT_CONFIG_DIR)) == 0)
+ gfn = pa_sprintf_malloc("%s" PA_PATH_SEP "etc" PA_PATH_SEP "pulse%s",
+ pa_win32_get_toplevel(NULL),
+ global + strlen(PA_DEFAULT_CONFIG_DIR));
+ else
+#endif
+ gfn = pa_xstrdup(global);
+
+ if (access(gfn, R_OK) == 0)
+ return gfn;
+ pa_xfree(gfn);
+ }
+
+ errno = ENOENT;
+
+ return NULL;
+}
+
+/* Format the specified data as a hexademical string */
+char *pa_hexstr(const uint8_t* d, size_t dlength, char *s, size_t slength) {
+ size_t i = 0, j = 0;
+ const char hex[] = "0123456789abcdef";
+
+ pa_assert(d);
+ pa_assert(s);
+ pa_assert(slength > 0);
+
+ while (j+2 < slength && i < dlength) {
+ s[j++] = hex[*d >> 4];
+ s[j++] = hex[*d & 0xF];
+
+ d++;
+ i++;
+ }
+
+ s[j < slength ? j : slength] = 0;
+ return s;
+}
+
+/* Convert a hexadecimal digit to a number or -1 if invalid */
+static int hexc(char c) {
+ if (c >= '0' && c <= '9')
+ return c - '0';
+
+ if (c >= 'A' && c <= 'F')
+ return c - 'A' + 10;
+
+ if (c >= 'a' && c <= 'f')
+ return c - 'a' + 10;
+
+ errno = EINVAL;
+ return -1;
+}
+
+/* Parse a hexadecimal string as created by pa_hexstr() to a BLOB */
+size_t pa_parsehex(const char *p, uint8_t *d, size_t dlength) {
+ size_t j = 0;
+
+ pa_assert(p);
+ pa_assert(d);
+
+ while (j < dlength && *p) {
+ int b;
+
+ if ((b = hexc(*(p++))) < 0)
+ return (size_t) -1;
+
+ d[j] = (uint8_t) (b << 4);
+
+ if (!*p)
+ return (size_t) -1;
+
+ if ((b = hexc(*(p++))) < 0)
+ return (size_t) -1;
+
+ d[j] |= (uint8_t) b;
+ j++;
+ }
+
+ return j;
+}
+
+/* Returns nonzero when *s starts with *pfx */
+bool pa_startswith(const char *s, const char *pfx) {
+ size_t l;
+
+ pa_assert(s);
+ pa_assert(pfx);
+
+ l = strlen(pfx);
+
+ return strlen(s) >= l && strncmp(s, pfx, l) == 0;
+}
+
+/* Returns nonzero when *s ends with *sfx */
+bool pa_endswith(const char *s, const char *sfx) {
+ size_t l1, l2;
+
+ pa_assert(s);
+ pa_assert(sfx);
+
+ l1 = strlen(s);
+ l2 = strlen(sfx);
+
+ return l1 >= l2 && pa_streq(s + l1 - l2, sfx);
+}
+
+bool pa_is_path_absolute(const char *fn) {
+ pa_assert(fn);
+
+#ifndef OS_IS_WIN32
+ return *fn == '/';
+#else
+ return strlen(fn) >= 3 && isalpha(fn[0]) && fn[1] == ':' && fn[2] == '\\';
+#endif
+}
+
+char *pa_make_path_absolute(const char *p) {
+ char *r;
+ char *cwd;
+
+ pa_assert(p);
+
+ if (pa_is_path_absolute(p))
+ return pa_xstrdup(p);
+
+ if (!(cwd = pa_getcwd()))
+ return pa_xstrdup(p);
+
+ r = pa_sprintf_malloc("%s" PA_PATH_SEP "%s", cwd, p);
+ pa_xfree(cwd);
+ return r;
+}
+
+/* If fn is NULL, return the PulseAudio runtime or state dir (depending on the
+ * rt parameter). If fn is non-NULL and starts with /, return fn. Otherwise,
+ * append fn to the runtime/state dir and return it. */
+static char *get_path(const char *fn, bool prependmid, bool rt) {
+ char *rtp;
+
+ rtp = rt ? pa_get_runtime_dir() : pa_get_state_dir();
+
+ if (fn) {
+ char *r, *canonical_rtp;
+
+ if (pa_is_path_absolute(fn)) {
+ pa_xfree(rtp);
+ return pa_xstrdup(fn);
+ }
+
+ if (!rtp)
+ return NULL;
+
+ /* Hopefully make the path smaller to avoid 108 char limit (fdo#44680) */
+ if ((canonical_rtp = pa_realpath(rtp))) {
+ if (strlen(rtp) >= strlen(canonical_rtp))
+ pa_xfree(rtp);
+ else {
+ pa_xfree(canonical_rtp);
+ canonical_rtp = rtp;
+ }
+ } else
+ canonical_rtp = rtp;
+
+ if (prependmid) {
+ char *mid;
+
+ if (!(mid = pa_machine_id())) {
+ pa_xfree(canonical_rtp);
+ return NULL;
+ }
+
+ r = pa_sprintf_malloc("%s" PA_PATH_SEP "%s-%s", canonical_rtp, mid, fn);
+ pa_xfree(mid);
+ } else
+ r = pa_sprintf_malloc("%s" PA_PATH_SEP "%s", canonical_rtp, fn);
+
+ pa_xfree(canonical_rtp);
+ return r;
+ } else
+ return rtp;
+}
+
+char *pa_runtime_path(const char *fn) {
+ return get_path(fn, false, true);
+}
+
+char *pa_state_path(const char *fn, bool appendmid) {
+ return get_path(fn, appendmid, false);
+}
+
+/* Convert the string s to a signed integer in *ret_i */
+int pa_atoi(const char *s, int32_t *ret_i) {
+ long l;
+
+ pa_assert(s);
+ pa_assert(ret_i);
+
+ if (pa_atol(s, &l) < 0)
+ return -1;
+
+ if ((int32_t) l != l) {
+ errno = ERANGE;
+ return -1;
+ }
+
+ *ret_i = (int32_t) l;
+
+ return 0;
+}
+
+/* Convert the string s to an unsigned integer in *ret_u */
+int pa_atou(const char *s, uint32_t *ret_u) {
+ char *x = NULL;
+ unsigned long l;
+
+ pa_assert(s);
+ pa_assert(ret_u);
+
+ /* strtoul() ignores leading spaces. We don't. */
+ if (isspace((unsigned char)*s)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* strtoul() accepts strings that start with a minus sign. In that case the
+ * original negative number gets negated, and strtoul() returns the negated
+ * result. We don't want that kind of behaviour. strtoul() also allows a
+ * leading plus sign, which is also a thing that we don't want. */
+ if (*s == '-' || *s == '+') {
+ errno = EINVAL;
+ return -1;
+ }
+
+ errno = 0;
+ l = strtoul(s, &x, 0);
+
+ /* If x doesn't point to the end of s, there was some trailing garbage in
+ * the string. If x points to s, no conversion was done (empty string). */
+ if (!x || *x || x == s || errno) {
+ if (!errno)
+ errno = EINVAL;
+ return -1;
+ }
+
+ if ((uint32_t) l != l) {
+ errno = ERANGE;
+ return -1;
+ }
+
+ *ret_u = (uint32_t) l;
+
+ return 0;
+}
+
+/* Convert the string s to a signed long integer in *ret_l. */
+int pa_atol(const char *s, long *ret_l) {
+ char *x = NULL;
+ long l;
+
+ pa_assert(s);
+ pa_assert(ret_l);
+
+ /* strtol() ignores leading spaces. We don't. */
+ if (isspace((unsigned char)*s)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* strtol() accepts leading plus signs, but that's ugly, so we don't allow
+ * that. */
+ if (*s == '+') {
+ errno = EINVAL;
+ return -1;
+ }
+
+ errno = 0;
+ l = strtol(s, &x, 0);
+
+ /* If x doesn't point to the end of s, there was some trailing garbage in
+ * the string. If x points to s, no conversion was done (at least an empty
+ * string can trigger this). */
+ if (!x || *x || x == s || errno) {
+ if (!errno)
+ errno = EINVAL;
+ return -1;
+ }
+
+ *ret_l = l;
+
+ return 0;
+}
+
+#ifdef HAVE_STRTOD_L
+static locale_t c_locale = NULL;
+
+static void c_locale_destroy(void) {
+ freelocale(c_locale);
+}
+#endif
+
+int pa_atod(const char *s, double *ret_d) {
+ char *x = NULL;
+ double f;
+
+ pa_assert(s);
+ pa_assert(ret_d);
+
+ /* strtod() ignores leading spaces. We don't. */
+ if (isspace((unsigned char)*s)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* strtod() accepts leading plus signs, but that's ugly, so we don't allow
+ * that. */
+ if (*s == '+') {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* This should be locale independent */
+
+#ifdef HAVE_STRTOD_L
+
+ PA_ONCE_BEGIN {
+
+ if ((c_locale = newlocale(LC_ALL_MASK, "C", NULL)))
+ atexit(c_locale_destroy);
+
+ } PA_ONCE_END;
+
+ if (c_locale) {
+ errno = 0;
+ f = strtod_l(s, &x, c_locale);
+ } else
+#endif
+ {
+ errno = 0;
+ f = strtod(s, &x);
+ }
+
+ /* If x doesn't point to the end of s, there was some trailing garbage in
+ * the string. If x points to s, no conversion was done (at least an empty
+ * string can trigger this). */
+ if (!x || *x || x == s || errno) {
+ if (!errno)
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (isnan(f)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ *ret_d = f;
+
+ return 0;
+}
+
+/* Same as snprintf, but guarantees NUL-termination on every platform */
+size_t pa_snprintf(char *str, size_t size, const char *format, ...) {
+ size_t ret;
+ va_list ap;
+
+ pa_assert(str);
+ pa_assert(size > 0);
+ pa_assert(format);
+
+ va_start(ap, format);
+ ret = pa_vsnprintf(str, size, format, ap);
+ va_end(ap);
+
+ return ret;
+}
+
+/* Same as vsnprintf, but guarantees NUL-termination on every platform */
+size_t pa_vsnprintf(char *str, size_t size, const char *format, va_list ap) {
+ int ret;
+
+ pa_assert(str);
+ pa_assert(size > 0);
+ pa_assert(format);
+
+ ret = vsnprintf(str, size, format, ap);
+
+ str[size-1] = 0;
+
+ if (ret < 0)
+ return strlen(str);
+
+ if ((size_t) ret > size-1)
+ return size-1;
+
+ return (size_t) ret;
+}
+
+/* Truncate the specified string, but guarantee that the string
+ * returned still validates as UTF8 */
+char *pa_truncate_utf8(char *c, size_t l) {
+ pa_assert(c);
+ pa_assert(pa_utf8_valid(c));
+
+ if (strlen(c) <= l)
+ return c;
+
+ c[l] = 0;
+
+ while (l > 0 && !pa_utf8_valid(c))
+ c[--l] = 0;
+
+ return c;
+}
+
+char *pa_getcwd(void) {
+ size_t l = 128;
+
+ for (;;) {
+ char *p = pa_xmalloc(l);
+ if (getcwd(p, l))
+ return p;
+
+ if (errno != ERANGE) {
+ pa_xfree(p);
+ return NULL;
+ }
+
+ pa_xfree(p);
+ l *= 2;
+ }
+}
+
+void *pa_will_need(const void *p, size_t l) {
+#ifdef RLIMIT_MEMLOCK
+ struct rlimit rlim;
+#endif
+ const void *a;
+ size_t size;
+ int r = ENOTSUP;
+ size_t bs;
+ const size_t page_size = pa_page_size();
+
+ pa_assert(p);
+ pa_assert(l > 0);
+
+ a = PA_PAGE_ALIGN_PTR(p);
+ size = (size_t) ((const uint8_t*) p + l - (const uint8_t*) a);
+
+#ifdef HAVE_POSIX_MADVISE
+ if ((r = posix_madvise((void*) a, size, POSIX_MADV_WILLNEED)) == 0) {
+ pa_log_debug("posix_madvise() worked fine!");
+ return (void*) p;
+ }
+#endif
+
+ /* Most likely the memory was not mmap()ed from a file and thus
+ * madvise() didn't work, so let's misuse mlock() do page this
+ * stuff back into RAM. Yeah, let's fuck with the MM! It's so
+ * inviting, the man page of mlock() tells us: "All pages that
+ * contain a part of the specified address range are guaranteed to
+ * be resident in RAM when the call returns successfully." */
+
+#ifdef RLIMIT_MEMLOCK
+ pa_assert_se(getrlimit(RLIMIT_MEMLOCK, &rlim) == 0);
+
+ if (rlim.rlim_cur < page_size) {
+ pa_log_debug("posix_madvise() failed (or doesn't exist), resource limits don't allow mlock(), can't page in data: %s", pa_cstrerror(r));
+ errno = EPERM;
+ return (void*) p;
+ }
+
+ bs = PA_PAGE_ALIGN((size_t) rlim.rlim_cur);
+#else
+ bs = page_size*4;
+#endif
+
+ pa_log_debug("posix_madvise() failed (or doesn't exist), trying mlock(): %s", pa_cstrerror(r));
+
+#ifdef HAVE_MLOCK
+ while (size > 0 && bs > 0) {
+
+ if (bs > size)
+ bs = size;
+
+ if (mlock(a, bs) < 0) {
+ bs = PA_PAGE_ALIGN(bs / 2);
+ continue;
+ }
+
+ pa_assert_se(munlock(a, bs) == 0);
+
+ a = (const uint8_t*) a + bs;
+ size -= bs;
+ }
+#endif
+
+ if (bs <= 0)
+ pa_log_debug("mlock() failed too (or doesn't exist), giving up: %s", pa_cstrerror(errno));
+ else
+ pa_log_debug("mlock() worked fine!");
+
+ return (void*) p;
+}
+
+void pa_close_pipe(int fds[2]) {
+ pa_assert(fds);
+
+ if (fds[0] >= 0)
+ pa_assert_se(pa_close(fds[0]) == 0);
+
+ if (fds[1] >= 0)
+ pa_assert_se(pa_close(fds[1]) == 0);
+
+ fds[0] = fds[1] = -1;
+}
+
+char *pa_readlink(const char *p) {
+#ifdef HAVE_READLINK
+ size_t l = 100;
+
+ for (;;) {
+ char *c;
+ ssize_t n;
+
+ c = pa_xmalloc(l);
+
+ if ((n = readlink(p, c, l-1)) < 0) {
+ pa_xfree(c);
+ return NULL;
+ }
+
+ if ((size_t) n < l-1) {
+ c[n] = 0;
+ return c;
+ }
+
+ pa_xfree(c);
+ l *= 2;
+ }
+#else
+ return NULL;
+#endif
+}
+
+int pa_close_all(int except_fd, ...) {
+ va_list ap;
+ unsigned n = 0, i;
+ int r, *p;
+
+ va_start(ap, except_fd);
+
+ if (except_fd >= 0)
+ for (n = 1; va_arg(ap, int) >= 0; n++)
+ ;
+
+ va_end(ap);
+
+ p = pa_xnew(int, n+1);
+
+ va_start(ap, except_fd);
+
+ i = 0;
+ if (except_fd >= 0) {
+ int fd;
+ p[i++] = except_fd;
+
+ while ((fd = va_arg(ap, int)) >= 0)
+ p[i++] = fd;
+ }
+ p[i] = -1;
+
+ va_end(ap);
+
+ r = pa_close_allv(p);
+ pa_xfree(p);
+
+ return r;
+}
+
+int pa_close_allv(const int except_fds[]) {
+#ifndef OS_IS_WIN32
+ struct rlimit rl;
+ int maxfd, fd;
+
+#if defined(__linux__) || defined(__sun)
+ int saved_errno;
+ DIR *d;
+
+ if ((d = opendir("/proc/self/fd"))) {
+
+ struct dirent *de;
+
+ while ((de = readdir(d))) {
+ bool found;
+ long l;
+ char *e = NULL;
+ int i;
+
+ if (de->d_name[0] == '.')
+ continue;
+
+ errno = 0;
+ l = strtol(de->d_name, &e, 10);
+ if (errno != 0 || !e || *e) {
+ closedir(d);
+ errno = EINVAL;
+ return -1;
+ }
+
+ fd = (int) l;
+
+ if ((long) fd != l) {
+ closedir(d);
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (fd < 3)
+ continue;
+
+ if (fd == dirfd(d))
+ continue;
+
+ found = false;
+ for (i = 0; except_fds[i] >= 0; i++)
+ if (except_fds[i] == fd) {
+ found = true;
+ break;
+ }
+
+ if (found)
+ continue;
+
+ if (pa_close(fd) < 0) {
+ saved_errno = errno;
+ closedir(d);
+ errno = saved_errno;
+
+ return -1;
+ }
+ }
+
+ closedir(d);
+ return 0;
+ }
+
+#endif
+
+ if (getrlimit(RLIMIT_NOFILE, &rl) >= 0)
+ maxfd = (int) rl.rlim_max;
+ else
+ maxfd = sysconf(_SC_OPEN_MAX);
+
+ for (fd = 3; fd < maxfd; fd++) {
+ int i;
+ bool found;
+
+ found = false;
+ for (i = 0; except_fds[i] >= 0; i++)
+ if (except_fds[i] == fd) {
+ found = true;
+ break;
+ }
+
+ if (found)
+ continue;
+
+ if (pa_close(fd) < 0 && errno != EBADF)
+ return -1;
+ }
+#endif /* !OS_IS_WIN32 */
+
+ return 0;
+}
+
+int pa_unblock_sigs(int except, ...) {
+ va_list ap;
+ unsigned n = 0, i;
+ int r, *p;
+
+ va_start(ap, except);
+
+ if (except >= 1)
+ for (n = 1; va_arg(ap, int) >= 0; n++)
+ ;
+
+ va_end(ap);
+
+ p = pa_xnew(int, n+1);
+
+ va_start(ap, except);
+
+ i = 0;
+ if (except >= 1) {
+ int sig;
+ p[i++] = except;
+
+ while ((sig = va_arg(ap, int)) >= 0)
+ p[i++] = sig;
+ }
+ p[i] = -1;
+
+ va_end(ap);
+
+ r = pa_unblock_sigsv(p);
+ pa_xfree(p);
+
+ return r;
+}
+
+int pa_unblock_sigsv(const int except[]) {
+#ifndef OS_IS_WIN32
+ int i;
+ sigset_t ss;
+
+ if (sigemptyset(&ss) < 0)
+ return -1;
+
+ for (i = 0; except[i] > 0; i++)
+ if (sigaddset(&ss, except[i]) < 0)
+ return -1;
+
+ return sigprocmask(SIG_SETMASK, &ss, NULL);
+#else
+ return 0;
+#endif
+}
+
+int pa_reset_sigs(int except, ...) {
+ va_list ap;
+ unsigned n = 0, i;
+ int *p, r;
+
+ va_start(ap, except);
+
+ if (except >= 1)
+ for (n = 1; va_arg(ap, int) >= 0; n++)
+ ;
+
+ va_end(ap);
+
+ p = pa_xnew(int, n+1);
+
+ va_start(ap, except);
+
+ i = 0;
+ if (except >= 1) {
+ int sig;
+ p[i++] = except;
+
+ while ((sig = va_arg(ap, int)) >= 0)
+ p[i++] = sig;
+ }
+ p[i] = -1;
+
+ va_end(ap);
+
+ r = pa_reset_sigsv(p);
+ pa_xfree(p);
+
+ return r;
+}
+
+int pa_reset_sigsv(const int except[]) {
+#ifndef OS_IS_WIN32
+ int sig;
+
+ for (sig = 1; sig < NSIG; sig++) {
+ bool reset = true;
+
+ switch (sig) {
+ case SIGKILL:
+ case SIGSTOP:
+ reset = false;
+ break;
+
+ default: {
+ int i;
+
+ for (i = 0; except[i] > 0; i++) {
+ if (sig == except[i]) {
+ reset = false;
+ break;
+ }
+ }
+ }
+ }
+
+ if (reset) {
+ struct sigaction sa;
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = SIG_DFL;
+
+ /* On Linux the first two RT signals are reserved by
+ * glibc, and sigaction() will return EINVAL for them. */
+ if ((sigaction(sig, &sa, NULL) < 0))
+ if (errno != EINVAL)
+ return -1;
+ }
+ }
+#endif
+
+ return 0;
+}
+
+void pa_set_env(const char *key, const char *value) {
+ pa_assert(key);
+ pa_assert(value);
+
+ /* This is not thread-safe */
+
+#ifdef OS_IS_WIN32
+ SetEnvironmentVariable(key, value);
+#else
+ setenv(key, value, 1);
+#endif
+}
+
+void pa_unset_env(const char *key) {
+ pa_assert(key);
+
+ /* This is not thread-safe */
+
+#ifdef OS_IS_WIN32
+ SetEnvironmentVariable(key, NULL);
+#else
+ unsetenv(key);
+#endif
+}
+
+void pa_set_env_and_record(const char *key, const char *value) {
+ pa_assert(key);
+ pa_assert(value);
+
+ /* This is not thread-safe */
+
+ pa_set_env(key, value);
+ recorded_env = pa_strlist_prepend(recorded_env, key);
+}
+
+void pa_unset_env_recorded(void) {
+
+ /* This is not thread-safe */
+
+ for (;;) {
+ char *s;
+
+ recorded_env = pa_strlist_pop(recorded_env, &s);
+
+ if (!s)
+ break;
+
+ pa_unset_env(s);
+ pa_xfree(s);
+ }
+}
+
+bool pa_in_system_mode(void) {
+ const char *e;
+
+ if (!(e = getenv("PULSE_SYSTEM")))
+ return false;
+
+ return !!atoi(e);
+}
+
+/* Checks a delimiters-separated list of words in haystack for needle */
+bool pa_str_in_list(const char *haystack, const char *delimiters, const char *needle) {
+ char *s;
+ const char *state = NULL;
+
+ if (!haystack || !needle)
+ return false;
+
+ while ((s = pa_split(haystack, delimiters, &state))) {
+ if (pa_streq(needle, s)) {
+ pa_xfree(s);
+ return true;
+ }
+
+ pa_xfree(s);
+ }
+
+ return false;
+}
+
+/* Checks a whitespace-separated list of words in haystack for needle */
+bool pa_str_in_list_spaces(const char *haystack, const char *needle) {
+ const char *s;
+ size_t n;
+ const char *state = NULL;
+
+ if (!haystack || !needle)
+ return false;
+
+ while ((s = pa_split_spaces_in_place(haystack, &n, &state))) {
+ if (pa_strneq(needle, s, n))
+ return true;
+ }
+
+ return false;
+}
+
+char* pa_str_strip_suffix(const char *str, const char *suffix) {
+ size_t str_l, suf_l, prefix;
+ char *ret;
+
+ pa_assert(str);
+ pa_assert(suffix);
+
+ str_l = strlen(str);
+ suf_l = strlen(suffix);
+
+ if (str_l < suf_l)
+ return NULL;
+
+ prefix = str_l - suf_l;
+
+ if (!pa_streq(&str[prefix], suffix))
+ return NULL;
+
+ ret = pa_xmalloc(prefix + 1);
+
+ strncpy(ret, str, prefix);
+ ret[prefix] = '\0';
+
+ return ret;
+}
+
+char *pa_get_user_name_malloc(void) {
+ ssize_t k;
+ char *u;
+
+#ifdef _SC_LOGIN_NAME_MAX
+ k = (ssize_t) sysconf(_SC_LOGIN_NAME_MAX);
+
+ if (k <= 0)
+#endif
+ k = 32;
+
+ u = pa_xnew(char, k+1);
+
+ if (!(pa_get_user_name(u, k))) {
+ pa_xfree(u);
+ return NULL;
+ }
+
+ return u;
+}
+
+char *pa_get_host_name_malloc(void) {
+ size_t l;
+
+ l = 100;
+ for (;;) {
+ char *c;
+
+ c = pa_xmalloc(l);
+
+ if (!pa_get_host_name(c, l)) {
+
+ if (errno != EINVAL && errno != ENAMETOOLONG)
+ break;
+
+ } else if (strlen(c) < l-1) {
+ char *u;
+
+ if (*c == 0) {
+ pa_xfree(c);
+ break;
+ }
+
+ u = pa_utf8_filter(c);
+ pa_xfree(c);
+ return u;
+ }
+
+ /* Hmm, the hostname is as long the space we offered the
+ * function, we cannot know if it fully fit in, so let's play
+ * safe and retry. */
+
+ pa_xfree(c);
+ l *= 2;
+ }
+
+ return NULL;
+}
+
+char *pa_machine_id(void) {
+ FILE *f;
+ char *h;
+
+ /* The returned value is supposed be some kind of ascii identifier
+ * that is unique and stable across reboots. First we try if the machine-id
+ * file is available. If it's available, that's great, since it provides an
+ * identifier that suits our needs perfectly. If it's not, we fall back to
+ * the hostname, which is not as good, since it can change over time. */
+
+ /* We search for the machine-id file from four locations. The first two are
+ * relative to the configured installation prefix, but if we're installed
+ * under /usr/local, for example, it's likely that the machine-id won't be
+ * found there, so we also try the hardcoded paths.
+ *
+ * PA_MACHINE_ID or PA_MACHINE_ID_FALLBACK might exist on a Windows system,
+ * but the last two hardcoded paths certainly don't, hence we don't try
+ * them on Windows. */
+ if ((f = pa_fopen_cloexec(PA_MACHINE_ID, "r")) ||
+ (f = pa_fopen_cloexec(PA_MACHINE_ID_FALLBACK, "r")) ||
+#if !defined(OS_IS_WIN32)
+ (f = pa_fopen_cloexec("/etc/machine-id", "r")) ||
+ (f = pa_fopen_cloexec("/var/lib/dbus/machine-id", "r"))
+#else
+ false
+#endif
+ ) {
+ char ln[34] = "", *r;
+
+ r = fgets(ln, sizeof(ln)-1, f);
+ fclose(f);
+
+ pa_strip_nl(ln);
+
+ if (r && ln[0])
+ return pa_utf8_filter(ln);
+ }
+
+ if ((h = pa_get_host_name_malloc()))
+ return h;
+
+#if !defined(OS_IS_WIN32) && !defined(__ANDROID__)
+ /* If no hostname was set we use the POSIX hostid. It's usually
+ * the IPv4 address. Might not be that stable. */
+ return pa_sprintf_malloc("%08lx", (unsigned long) gethostid());
+#else
+ return NULL;
+#endif
+}
+
+char *pa_session_id(void) {
+ const char *e;
+
+ e = getenv("XDG_SESSION_ID");
+ if (!e)
+ return NULL;
+
+ return pa_utf8_filter(e);
+}
+
+char *pa_uname_string(void) {
+#ifdef HAVE_UNAME
+ struct utsname u;
+
+ pa_assert_se(uname(&u) >= 0);
+
+ return pa_sprintf_malloc("%s %s %s %s", u.sysname, u.machine, u.release, u.version);
+#endif
+#ifdef OS_IS_WIN32
+ OSVERSIONINFO i;
+
+ pa_zero(i);
+ i.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
+ pa_assert_se(GetVersionEx(&i));
+
+ return pa_sprintf_malloc("Windows %d.%d (%d) %s", i.dwMajorVersion, i.dwMinorVersion, i.dwBuildNumber, i.szCSDVersion);
+#endif
+}
+
+#ifdef HAVE_VALGRIND_MEMCHECK_H
+bool pa_in_valgrind(void) {
+ static int b = 0;
+
+ /* To make heisenbugs a bit simpler to find we check for $VALGRIND
+ * here instead of really checking whether we run in valgrind or
+ * not. */
+
+ if (b < 1)
+ b = getenv("VALGRIND") ? 2 : 1;
+
+ return b > 1;
+}
+#endif
+
+unsigned pa_gcd(unsigned a, unsigned b) {
+
+ while (b > 0) {
+ unsigned t = b;
+ b = a % b;
+ a = t;
+ }
+
+ return a;
+}
+
+void pa_reduce(unsigned *num, unsigned *den) {
+
+ unsigned gcd = pa_gcd(*num, *den);
+
+ if (gcd <= 0)
+ return;
+
+ *num /= gcd;
+ *den /= gcd;
+
+ pa_assert(pa_gcd(*num, *den) == 1);
+}
+
+unsigned pa_ncpus(void) {
+ long ncpus;
+
+#ifdef _SC_NPROCESSORS_ONLN
+ ncpus = sysconf(_SC_NPROCESSORS_ONLN);
+#else
+ ncpus = 1;
+#endif
+
+ return ncpus <= 0 ? 1 : (unsigned) ncpus;
+}
+
+char *pa_replace(const char*s, const char*a, const char *b) {
+ pa_strbuf *sb;
+ size_t an;
+
+ pa_assert(s);
+ pa_assert(a);
+ pa_assert(*a);
+ pa_assert(b);
+
+ an = strlen(a);
+ sb = pa_strbuf_new();
+
+ for (;;) {
+ const char *p;
+
+ if (!(p = strstr(s, a)))
+ break;
+
+ pa_strbuf_putsn(sb, s, p-s);
+ pa_strbuf_puts(sb, b);
+ s = p + an;
+ }
+
+ pa_strbuf_puts(sb, s);
+
+ return pa_strbuf_to_string_free(sb);
+}
+
+char *pa_escape(const char *p, const char *chars) {
+ const char *s;
+ const char *c;
+ char *out_string, *output;
+ int char_count = strlen(p);
+
+ /* Maximum number of characters in output string
+ * including trailing 0. */
+ char_count = 2 * char_count + 1;
+
+ /* allocate output string */
+ out_string = pa_xmalloc(char_count);
+ output = out_string;
+
+ /* write output string */
+ for (s = p; *s; ++s) {
+ if (*s == '\\')
+ *output++ = '\\';
+ else if (chars) {
+ for (c = chars; *c; ++c) {
+ if (*s == *c) {
+ *output++ = '\\';
+ break;
+ }
+ }
+ }
+ *output++ = *s;
+ }
+
+ *output = 0;
+
+ /* Remove trailing garbage */
+ output = pa_xstrdup(out_string);
+
+ pa_xfree(out_string);
+ return output;
+}
+
+char *pa_unescape(char *p) {
+ char *s, *d;
+ bool escaped = false;
+
+ for (s = p, d = p; *s; s++) {
+ if (!escaped && *s == '\\') {
+ escaped = true;
+ continue;
+ }
+
+ *(d++) = *s;
+ escaped = false;
+ }
+
+ *d = 0;
+
+ return p;
+}
+
+char *pa_realpath(const char *path) {
+ char *t;
+ pa_assert(path);
+
+ /* We want only absolute paths */
+ if (path[0] != '/') {
+ errno = EINVAL;
+ return NULL;
+ }
+
+#if defined(__GLIBC__)
+ {
+ char *r;
+
+ if (!(r = realpath(path, NULL)))
+ return NULL;
+
+ /* We copy this here in case our pa_xmalloc() is not
+ * implemented on top of libc malloc() */
+ t = pa_xstrdup(r);
+ pa_xfree(r);
+ }
+#elif defined(PATH_MAX)
+ {
+ char *path_buf;
+ path_buf = pa_xmalloc(PATH_MAX);
+
+#if defined(OS_IS_WIN32)
+ if (!(t = _fullpath(path_buf, path, _MAX_PATH))) {
+ pa_xfree(path_buf);
+ return NULL;
+ }
+#else
+ if (!(t = realpath(path, path_buf))) {
+ pa_xfree(path_buf);
+ return NULL;
+ }
+#endif
+ }
+#else
+#error "It's not clear whether this system supports realpath(..., NULL) like GNU libc does. If it doesn't we need a private version of realpath() here."
+#endif
+
+ return t;
+}
+
+void pa_disable_sigpipe(void) {
+
+#ifdef SIGPIPE
+ struct sigaction sa;
+
+ pa_zero(sa);
+
+ if (sigaction(SIGPIPE, NULL, &sa) < 0) {
+ pa_log("sigaction(): %s", pa_cstrerror(errno));
+ return;
+ }
+
+ sa.sa_handler = SIG_IGN;
+
+ if (sigaction(SIGPIPE, &sa, NULL) < 0) {
+ pa_log("sigaction(): %s", pa_cstrerror(errno));
+ return;
+ }
+#endif
+}
+
+void pa_xfreev(void**a) {
+ void **p;
+
+ if (!a)
+ return;
+
+ for (p = a; *p; p++)
+ pa_xfree(*p);
+
+ pa_xfree(a);
+}
+
+char **pa_split_spaces_strv(const char *s) {
+ char **t, *e;
+ unsigned i = 0, n = 8;
+ const char *state = NULL;
+
+ t = pa_xnew(char*, n);
+ while ((e = pa_split_spaces(s, &state))) {
+ t[i++] = e;
+
+ if (i >= n) {
+ n *= 2;
+ t = pa_xrenew(char*, t, n);
+ }
+ }
+
+ if (i <= 0) {
+ pa_xfree(t);
+ return NULL;
+ }
+
+ t[i] = NULL;
+ return t;
+}
+
+char* pa_maybe_prefix_path(const char *path, const char *prefix) {
+ pa_assert(path);
+
+ if (pa_is_path_absolute(path))
+ return pa_xstrdup(path);
+
+ return pa_sprintf_malloc("%s" PA_PATH_SEP "%s", prefix, path);
+}
+
+size_t pa_pipe_buf(int fd) {
+
+#ifdef _PC_PIPE_BUF
+ long n;
+
+ if ((n = fpathconf(fd, _PC_PIPE_BUF)) >= 0)
+ return (size_t) n;
+#endif
+
+#ifdef PIPE_BUF
+ return PIPE_BUF;
+#else
+ return 4096;
+#endif
+}
+
+void pa_reset_personality(void) {
+
+#if defined(__linux__) && !defined(__ANDROID__)
+ if (personality(PER_LINUX) < 0)
+ pa_log_warn("Uh, personality() failed: %s", pa_cstrerror(errno));
+#endif
+
+}
+
+bool pa_run_from_build_tree(void) {
+ static bool b = false;
+
+#ifdef HAVE_RUNNING_FROM_BUILD_TREE
+ char *rp;
+ PA_ONCE_BEGIN {
+ if ((rp = pa_readlink("/proc/self/exe"))) {
+ b = pa_startswith(rp, PA_BUILDDIR);
+ pa_xfree(rp);
+ }
+ } PA_ONCE_END;
+#endif
+
+ return b;
+}
+
+const char *pa_get_temp_dir(void) {
+ const char *t;
+
+ if ((t = getenv("TMPDIR")) &&
+ pa_is_path_absolute(t))
+ return t;
+
+ if ((t = getenv("TMP")) &&
+ pa_is_path_absolute(t))
+ return t;
+
+ if ((t = getenv("TEMP")) &&
+ pa_is_path_absolute(t))
+ return t;
+
+ if ((t = getenv("TEMPDIR")) &&
+ pa_is_path_absolute(t))
+ return t;
+
+ return "/tmp";
+}
+
+int pa_open_cloexec(const char *fn, int flags, mode_t mode) {
+ int fd;
+
+#ifdef O_NOCTTY
+ flags |= O_NOCTTY;
+#endif
+
+#ifdef O_CLOEXEC
+ if ((fd = open(fn, flags|O_CLOEXEC, mode)) >= 0)
+ goto finish;
+
+ if (errno != EINVAL)
+ return fd;
+#endif
+
+ if ((fd = open(fn, flags, mode)) >= 0)
+ goto finish;
+
+ /* return error */
+ return fd;
+
+finish:
+ /* Some implementations might simply ignore O_CLOEXEC if it is not
+ * understood, make sure FD_CLOEXEC is enabled anyway */
+
+ pa_make_fd_cloexec(fd);
+ return fd;
+}
+
+int pa_socket_cloexec(int domain, int type, int protocol) {
+ int fd;
+
+#ifdef SOCK_CLOEXEC
+ if ((fd = socket(domain, type | SOCK_CLOEXEC, protocol)) >= 0)
+ goto finish;
+
+ if (errno != EINVAL)
+ return fd;
+#endif
+
+ if ((fd = socket(domain, type, protocol)) >= 0)
+ goto finish;
+
+ /* return error */
+ return fd;
+
+finish:
+ /* Some implementations might simply ignore SOCK_CLOEXEC if it is
+ * not understood, make sure FD_CLOEXEC is enabled anyway */
+
+ pa_make_fd_cloexec(fd);
+ return fd;
+}
+
+int pa_pipe_cloexec(int pipefd[2]) {
+ int r;
+
+#ifdef HAVE_PIPE2
+ if ((r = pipe2(pipefd, O_CLOEXEC)) >= 0)
+ goto finish;
+
+ if (errno == EMFILE) {
+ pa_log_error("The per-process limit on the number of open file descriptors has been reached.");
+ return r;
+ }
+
+ if (errno == ENFILE) {
+ pa_log_error("The system-wide limit on the total number of open files has been reached.");
+ return r;
+ }
+
+ if (errno != EINVAL && errno != ENOSYS)
+ return r;
+
+#endif
+
+ if ((r = pipe(pipefd)) >= 0)
+ goto finish;
+
+ if (errno == EMFILE) {
+ pa_log_error("The per-process limit on the number of open file descriptors has been reached.");
+ return r;
+ }
+
+ if (errno == ENFILE) {
+ pa_log_error("The system-wide limit on the total number of open files has been reached.");
+ return r;
+ }
+
+ /* return error */
+ return r;
+
+finish:
+ pa_make_fd_cloexec(pipefd[0]);
+ pa_make_fd_cloexec(pipefd[1]);
+
+ return 0;
+}
+
+int pa_accept_cloexec(int sockfd, struct sockaddr *addr, socklen_t *addrlen) {
+ int fd;
+
+ errno = 0;
+
+#ifdef HAVE_ACCEPT4
+ if ((fd = accept4(sockfd, addr, addrlen, SOCK_CLOEXEC)) >= 0)
+ goto finish;
+
+ if (errno != EINVAL && errno != ENOSYS)
+ return fd;
+
+#endif
+
+#ifdef HAVE_PACCEPT
+ if ((fd = paccept(sockfd, addr, addrlen, NULL, SOCK_CLOEXEC)) >= 0)
+ goto finish;
+#endif
+
+ if ((fd = accept(sockfd, addr, addrlen)) >= 0)
+ goto finish;
+
+ /* return error */
+ return fd;
+
+finish:
+ pa_make_fd_cloexec(fd);
+ return fd;
+}
+
+FILE* pa_fopen_cloexec(const char *path, const char *mode) {
+ FILE *f;
+ char *m;
+
+ m = pa_sprintf_malloc("%se", mode);
+
+ errno = 0;
+ if ((f = fopen(path, m))) {
+ pa_xfree(m);
+ goto finish;
+ }
+
+ pa_xfree(m);
+
+ if (errno != EINVAL)
+ return NULL;
+
+ if (!(f = fopen(path, mode)))
+ return NULL;
+
+finish:
+ pa_make_fd_cloexec(fileno(f));
+ return f;
+}
+
+void pa_nullify_stdfds(void) {
+
+#ifndef OS_IS_WIN32
+ pa_close(STDIN_FILENO);
+ pa_close(STDOUT_FILENO);
+ pa_close(STDERR_FILENO);
+
+ pa_assert_se(open("/dev/null", O_RDONLY) == STDIN_FILENO);
+ pa_assert_se(open("/dev/null", O_WRONLY) == STDOUT_FILENO);
+ pa_assert_se(open("/dev/null", O_WRONLY) == STDERR_FILENO);
+#else
+ FreeConsole();
+#endif
+
+}
+
+char *pa_read_line_from_file(const char *fn) {
+ FILE *f;
+ char ln[256] = "", *r;
+
+ if (!(f = pa_fopen_cloexec(fn, "r")))
+ return NULL;
+
+ r = fgets(ln, sizeof(ln)-1, f);
+ fclose(f);
+
+ if (!r) {
+ errno = EIO;
+ return NULL;
+ }
+
+ pa_strip_nl(ln);
+ return pa_xstrdup(ln);
+}
+
+bool pa_running_in_vm(void) {
+
+#if defined(__i386__) || defined(__x86_64__)
+
+ /* Both CPUID and DMI are x86 specific interfaces... */
+
+#ifdef HAVE_CPUID_H
+ unsigned int eax, ebx, ecx, edx;
+#endif
+
+#ifdef __linux__
+ const char *const dmi_vendors[] = {
+ "/sys/class/dmi/id/sys_vendor",
+ "/sys/class/dmi/id/board_vendor",
+ "/sys/class/dmi/id/bios_vendor"
+ };
+
+ unsigned i;
+
+ for (i = 0; i < PA_ELEMENTSOF(dmi_vendors); i++) {
+ char *s;
+
+ if ((s = pa_read_line_from_file(dmi_vendors[i]))) {
+
+ if (pa_startswith(s, "QEMU") ||
+ /* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */
+ pa_startswith(s, "VMware") ||
+ pa_startswith(s, "VMW") ||
+ pa_startswith(s, "Microsoft Corporation") ||
+ pa_startswith(s, "innotek GmbH") ||
+ pa_startswith(s, "Xen")) {
+
+ pa_xfree(s);
+ return true;
+ }
+
+ pa_xfree(s);
+ }
+ }
+
+#endif
+
+#ifdef HAVE_CPUID_H
+
+ /* Hypervisors provide presence on 0x1 cpuid leaf.
+ * http://lwn.net/Articles/301888/ */
+ if (__get_cpuid(1, &eax, &ebx, &ecx, &edx) == 0)
+ return false;
+
+ if (ecx & 0x80000000)
+ return true;
+
+#endif /* HAVE_CPUID_H */
+
+#endif /* defined(__i386__) || defined(__x86_64__) */
+
+ return false;
+}
+
+size_t pa_page_size(void) {
+#if defined(PAGE_SIZE)
+ return PAGE_SIZE;
+#elif defined(PAGESIZE)
+ return PAGESIZE;
+#elif defined(HAVE_SYSCONF)
+ static size_t page_size = 4096; /* Let's hope it's like x86. */
+
+ PA_ONCE_BEGIN {
+ long ret = sysconf(_SC_PAGE_SIZE);
+ if (ret > 0)
+ page_size = ret;
+ } PA_ONCE_END;
+
+ return page_size;
+#else
+ return 4096;
+#endif
+}
diff --git a/src/pulsecore/core-util.h b/src/pulsecore/core-util.h
new file mode 100644
index 0000000..9440af9
--- /dev/null
+++ b/src/pulsecore/core-util.h
@@ -0,0 +1,323 @@
+#ifndef foocoreutilhfoo
+#define foocoreutilhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006-2007 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+
+#ifdef HAVE_SYS_RESOURCE_H
+#include <sys/resource.h>
+#endif
+
+#include <pulse/gccmacro.h>
+#include <pulse/volume.h>
+
+#include <pulsecore/i18n.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/socket.h>
+
+#ifndef PACKAGE
+#error "Please include config.h before including this file!"
+#endif
+
+struct timeval;
+
+/* These resource limits are pretty new on Linux, let's define them
+ * here manually, in case the kernel is newer than the glibc */
+#if !defined(RLIMIT_NICE) && defined(__linux__)
+#define RLIMIT_NICE 13
+#endif
+#if !defined(RLIMIT_RTPRIO) && defined(__linux__)
+#define RLIMIT_RTPRIO 14
+#endif
+#if !defined(RLIMIT_RTTIME) && defined(__linux__)
+#define RLIMIT_RTTIME 15
+#endif
+
+void pa_make_fd_nonblock(int fd);
+void pa_make_fd_block(int fd);
+bool pa_is_fd_nonblock(int fd);
+
+void pa_make_fd_cloexec(int fd);
+
+int pa_make_secure_dir(const char* dir, mode_t m, uid_t uid, gid_t gid, bool update_perms);
+int pa_make_secure_parent_dir(const char *fn, mode_t, uid_t uid, gid_t gid, bool update_perms);
+
+ssize_t pa_read(int fd, void *buf, size_t count, int *type);
+ssize_t pa_write(int fd, const void *buf, size_t count, int *type);
+ssize_t pa_loop_read(int fd, void*data, size_t size, int *type);
+ssize_t pa_loop_write(int fd, const void*data, size_t size, int *type);
+
+int pa_close(int fd);
+
+void pa_check_signal_is_blocked(int sig);
+
+char *pa_sprintf_malloc(const char *format, ...) PA_GCC_PRINTF_ATTR(1,2);
+char *pa_vsprintf_malloc(const char *format, va_list ap);
+
+char *pa_strlcpy(char *b, const char *s, size_t l);
+
+char *pa_parent_dir(const char *fn);
+
+int pa_raise_priority(int nice_level);
+void pa_reset_priority(void);
+
+int pa_parse_boolean(const char *s) PA_GCC_PURE;
+
+int pa_parse_volume(const char *s, pa_volume_t *volume);
+
+static inline const char *pa_yes_no(bool b) {
+ return b ? "yes" : "no";
+}
+
+static inline const char *pa_yes_no_localised(bool b) {
+ return b ? _("yes") : _("no");
+}
+
+static inline const char *pa_strnull(const char *x) {
+ return x ? x : "(null)";
+}
+
+static inline const char *pa_strempty(const char *x) {
+ return x ? x : "";
+}
+
+static inline const char *pa_strna(const char *x) {
+ return x ? x : "n/a";
+}
+
+char *pa_split(const char *c, const char *delimiters, const char **state);
+const char *pa_split_in_place(const char *c, const char *delimiters, size_t *n, const char **state);
+char *pa_split_spaces(const char *c, const char **state);
+const char *pa_split_spaces_in_place(const char *c, size_t *n, const char **state);
+
+char *pa_strip_nl(char *s);
+char *pa_strip(char *s);
+
+const char *pa_sig2str(int sig) PA_GCC_PURE;
+
+int pa_own_uid_in_group(const char *name, gid_t *gid);
+int pa_uid_in_group(uid_t uid, const char *name);
+gid_t pa_get_gid_of_group(const char *name);
+int pa_check_in_group(gid_t g);
+
+int pa_lock_fd(int fd, int b);
+
+int pa_lock_lockfile(const char *fn);
+int pa_unlock_lockfile(const char *fn, int fd);
+
+char *pa_hexstr(const uint8_t* d, size_t dlength, char *s, size_t slength);
+size_t pa_parsehex(const char *p, uint8_t *d, size_t dlength);
+
+bool pa_startswith(const char *s, const char *pfx) PA_GCC_PURE;
+bool pa_endswith(const char *s, const char *sfx) PA_GCC_PURE;
+
+FILE *pa_open_config_file(const char *global, const char *local, const char *env, char **result);
+char* pa_find_config_file(const char *global, const char *local, const char *env);
+
+char *pa_get_runtime_dir(void);
+char *pa_get_state_dir(void);
+char *pa_get_home_dir_malloc(void);
+int pa_append_to_home_dir(const char *path, char **_r);
+int pa_get_config_home_dir(char **_r);
+int pa_append_to_config_home_dir(const char *path, char **_r);
+char *pa_get_binary_name_malloc(void);
+char *pa_runtime_path(const char *fn);
+char *pa_state_path(const char *fn, bool prepend_machine_id);
+
+int pa_atoi(const char *s, int32_t *ret_i);
+int pa_atou(const char *s, uint32_t *ret_u);
+int pa_atol(const char *s, long *ret_l);
+int pa_atod(const char *s, double *ret_d);
+
+size_t pa_snprintf(char *str, size_t size, const char *format, ...);
+size_t pa_vsnprintf(char *str, size_t size, const char *format, va_list ap);
+
+char *pa_truncate_utf8(char *c, size_t l);
+
+int pa_match(const char *expr, const char *v);
+bool pa_is_regex_valid(const char *expr);
+
+char *pa_getcwd(void);
+char *pa_make_path_absolute(const char *p);
+bool pa_is_path_absolute(const char *p);
+
+void *pa_will_need(const void *p, size_t l);
+
+static inline int pa_is_power_of_two(unsigned n) {
+ return !(n & (n - 1));
+}
+
+static inline unsigned pa_ulog2(unsigned n) {
+
+ if (n <= 1)
+ return 0;
+
+#if __GNUC__ >= 4 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4)
+ return 8U * (unsigned) sizeof(unsigned) - (unsigned) __builtin_clz(n) - 1;
+#else
+{
+ unsigned r = 0;
+
+ for (;;) {
+ n = n >> 1;
+
+ if (!n)
+ return r;
+
+ r++;
+ }
+}
+#endif
+}
+
+static inline unsigned pa_make_power_of_two(unsigned n) {
+
+ if (pa_is_power_of_two(n))
+ return n;
+
+ return 1U << (pa_ulog2(n) + 1);
+}
+
+void pa_close_pipe(int fds[2]);
+
+char *pa_readlink(const char *p);
+
+int pa_close_all(int except_fd, ...);
+int pa_close_allv(const int except_fds[]);
+int pa_unblock_sigs(int except, ...);
+int pa_unblock_sigsv(const int except[]);
+int pa_reset_sigs(int except, ...);
+int pa_reset_sigsv(const int except[]);
+
+void pa_set_env(const char *key, const char *value);
+void pa_unset_env(const char *key);
+void pa_set_env_and_record(const char *key, const char *value);
+void pa_unset_env_recorded(void);
+
+bool pa_in_system_mode(void);
+
+#define pa_streq(a,b) (!strcmp((a),(b)))
+#define pa_strneq(a,b,n) (!strncmp((a),(b),(n)))
+
+/* Like pa_streq, but does not blow up on NULL pointers. */
+static inline bool pa_safe_streq(const char *a, const char *b) {
+ if (a == NULL || b == NULL)
+ return a == b;
+ return pa_streq(a, b);
+}
+
+bool pa_str_in_list_spaces(const char *needle, const char *haystack);
+bool pa_str_in_list(const char *haystack, const char *delimiters, const char *needle);
+
+char* pa_str_strip_suffix(const char *str, const char *suffix);
+
+char *pa_get_host_name_malloc(void);
+char *pa_get_user_name_malloc(void);
+
+char *pa_machine_id(void);
+char *pa_session_id(void);
+char *pa_uname_string(void);
+
+#ifdef HAVE_VALGRIND_MEMCHECK_H
+bool pa_in_valgrind(void);
+#else
+static inline bool pa_in_valgrind(void) {
+ return false;
+}
+#endif
+
+unsigned pa_gcd(unsigned a, unsigned b);
+void pa_reduce(unsigned *num, unsigned *den);
+
+unsigned pa_ncpus(void);
+
+/* Replaces all occurrences of `a' in `s' with `b'. The caller has to free the
+ * returned string. All parameters must be non-NULL and additionally `a' must
+ * not be a zero-length string.
+ */
+char *pa_replace(const char*s, const char*a, const char *b);
+
+/* Escapes p by inserting backslashes in front of backslashes. chars is a
+ * regular (i.e. NULL-terminated) string containing additional characters that
+ * should be escaped. chars can be NULL. The caller has to free the returned
+ * string. */
+char *pa_escape(const char *p, const char *chars);
+
+/* Does regular backslash unescaping. Returns the argument p. */
+char *pa_unescape(char *p);
+
+char *pa_realpath(const char *path);
+
+void pa_disable_sigpipe(void);
+
+void pa_xfreev(void**a);
+
+static inline void pa_xstrfreev(char **a) {
+ pa_xfreev((void**) a);
+}
+
+char **pa_split_spaces_strv(const char *s);
+
+char* pa_maybe_prefix_path(const char *path, const char *prefix);
+
+/* Returns size of the specified pipe or 4096 on failure */
+size_t pa_pipe_buf(int fd);
+
+void pa_reset_personality(void);
+
+bool pa_run_from_build_tree(void) PA_GCC_CONST;
+
+const char *pa_get_temp_dir(void);
+
+int pa_open_cloexec(const char *fn, int flags, mode_t mode);
+int pa_socket_cloexec(int domain, int type, int protocol);
+int pa_pipe_cloexec(int pipefd[2]);
+int pa_accept_cloexec(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
+FILE* pa_fopen_cloexec(const char *path, const char *mode);
+
+void pa_nullify_stdfds(void);
+
+char *pa_read_line_from_file(const char *fn);
+bool pa_running_in_vm(void);
+
+#ifdef OS_IS_WIN32
+char *pa_win32_get_toplevel(HANDLE handle);
+#endif
+
+size_t pa_page_size(void);
+
+/* Rounds down */
+static inline void* PA_PAGE_ALIGN_PTR(const void *p) {
+ return (void*) (((size_t) p) & ~(pa_page_size() - 1));
+}
+
+/* Rounds up */
+static inline size_t PA_PAGE_ALIGN(size_t l) {
+ size_t page_size = pa_page_size();
+ return (l + page_size - 1) & ~(page_size - 1);
+}
+
+#endif
diff --git a/src/pulsecore/core.c b/src/pulsecore/core.c
new file mode 100644
index 0000000..c28c531
--- /dev/null
+++ b/src/pulsecore/core.c
@@ -0,0 +1,632 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <signal.h>
+
+#include <pulse/rtclock.h>
+#include <pulse/timeval.h>
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/module.h>
+#include <pulsecore/core-rtclock.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/core-scache.h>
+#include <pulsecore/core-subscribe.h>
+#include <pulsecore/random.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+
+#include "core.h"
+
+PA_DEFINE_PUBLIC_CLASS(pa_core, pa_msgobject);
+
+static int core_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk) {
+ pa_core *c = PA_CORE(o);
+
+ pa_core_assert_ref(c);
+
+ switch (code) {
+
+ case PA_CORE_MESSAGE_UNLOAD_MODULE:
+ pa_module_unload(userdata, true);
+ return 0;
+
+ default:
+ return -1;
+ }
+}
+
+static void core_free(pa_object *o);
+
+pa_core* pa_core_new(pa_mainloop_api *m, bool shared, bool enable_memfd, size_t shm_size) {
+ pa_core* c;
+ pa_mempool *pool;
+ pa_mem_type_t type;
+ int j;
+
+ pa_assert(m);
+
+ if (shared) {
+ type = (enable_memfd) ? PA_MEM_TYPE_SHARED_MEMFD : PA_MEM_TYPE_SHARED_POSIX;
+ if (!(pool = pa_mempool_new(type, shm_size, false))) {
+ pa_log_warn("Failed to allocate %s memory pool. Falling back to a normal memory pool.",
+ pa_mem_type_to_string(type));
+ shared = false;
+ }
+ }
+
+ if (!shared) {
+ if (!(pool = pa_mempool_new(PA_MEM_TYPE_PRIVATE, shm_size, false))) {
+ pa_log("pa_mempool_new() failed.");
+ return NULL;
+ }
+ }
+
+ c = pa_msgobject_new(pa_core);
+ c->parent.parent.free = core_free;
+ c->parent.process_msg = core_process_msg;
+
+ c->state = PA_CORE_STARTUP;
+ c->mainloop = m;
+
+ c->clients = pa_idxset_new(NULL, NULL);
+ c->cards = pa_idxset_new(NULL, NULL);
+ c->sinks = pa_idxset_new(NULL, NULL);
+ c->sources = pa_idxset_new(NULL, NULL);
+ c->sink_inputs = pa_idxset_new(NULL, NULL);
+ c->source_outputs = pa_idxset_new(NULL, NULL);
+ c->modules = pa_idxset_new(NULL, NULL);
+ c->scache = pa_idxset_new(NULL, NULL);
+
+ c->namereg = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+ c->shared = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+ c->message_handlers = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+
+ c->default_source = NULL;
+ c->default_sink = NULL;
+
+ c->default_sample_spec.format = PA_SAMPLE_S16NE;
+ c->default_sample_spec.rate = 44100;
+ c->default_sample_spec.channels = 2;
+ pa_channel_map_init_extend(&c->default_channel_map, c->default_sample_spec.channels, PA_CHANNEL_MAP_DEFAULT);
+ c->default_n_fragments = 4;
+ c->default_fragment_size_msec = 25;
+
+ c->deferred_volume_safety_margin_usec = 8000;
+ c->deferred_volume_extra_delay_usec = 0;
+
+ c->module_defer_unload_event = NULL;
+ c->modules_pending_unload = pa_hashmap_new(NULL, NULL);
+
+ c->subscription_defer_event = NULL;
+ PA_LLIST_HEAD_INIT(pa_subscription, c->subscriptions);
+ PA_LLIST_HEAD_INIT(pa_subscription_event, c->subscription_event_queue);
+ c->subscription_event_last = NULL;
+
+ c->mempool = pool;
+ c->shm_size = shm_size;
+ pa_silence_cache_init(&c->silence_cache);
+
+ c->exit_event = NULL;
+ c->scache_auto_unload_event = NULL;
+
+ c->exit_idle_time = -1;
+ c->scache_idle_time = 20;
+
+ c->flat_volumes = true;
+ c->disallow_module_loading = false;
+ c->disallow_exit = false;
+ c->running_as_daemon = false;
+ c->realtime_scheduling = false;
+ c->realtime_priority = 5;
+ c->disable_remixing = false;
+ c->remixing_use_all_sink_channels = true;
+ c->remixing_produce_lfe = false;
+ c->remixing_consume_lfe = false;
+ c->lfe_crossover_freq = 0;
+ c->deferred_volume = true;
+ c->resample_method = PA_RESAMPLER_SPEEX_FLOAT_BASE + 1;
+
+ for (j = 0; j < PA_CORE_HOOK_MAX; j++)
+ pa_hook_init(&c->hooks[j], c);
+
+ pa_random(&c->cookie, sizeof(c->cookie));
+
+#ifdef SIGPIPE
+ pa_check_signal_is_blocked(SIGPIPE);
+#endif
+
+ return c;
+}
+
+static void core_free(pa_object *o) {
+ pa_core *c = PA_CORE(o);
+ int j;
+ pa_assert(c);
+
+ c->state = PA_CORE_SHUTDOWN;
+
+ /* Note: All modules and samples in the cache should be unloaded before
+ * we get here */
+
+ pa_assert(pa_idxset_isempty(c->scache));
+ pa_idxset_free(c->scache, NULL);
+
+ pa_assert(pa_idxset_isempty(c->modules));
+ pa_idxset_free(c->modules, NULL);
+
+ pa_assert(pa_idxset_isempty(c->clients));
+ pa_idxset_free(c->clients, NULL);
+
+ pa_assert(pa_idxset_isempty(c->cards));
+ pa_idxset_free(c->cards, NULL);
+
+ pa_assert(pa_idxset_isempty(c->sinks));
+ pa_idxset_free(c->sinks, NULL);
+
+ pa_assert(pa_idxset_isempty(c->sources));
+ pa_idxset_free(c->sources, NULL);
+
+ pa_assert(pa_idxset_isempty(c->source_outputs));
+ pa_idxset_free(c->source_outputs, NULL);
+
+ pa_assert(pa_idxset_isempty(c->sink_inputs));
+ pa_idxset_free(c->sink_inputs, NULL);
+
+ pa_assert(pa_hashmap_isempty(c->namereg));
+ pa_hashmap_free(c->namereg);
+
+ pa_assert(pa_hashmap_isempty(c->shared));
+ pa_hashmap_free(c->shared);
+
+ pa_assert(pa_hashmap_isempty(c->message_handlers));
+ pa_hashmap_free(c->message_handlers);
+
+ pa_assert(pa_hashmap_isempty(c->modules_pending_unload));
+ pa_hashmap_free(c->modules_pending_unload);
+
+ pa_subscription_free_all(c);
+
+ if (c->exit_event)
+ c->mainloop->time_free(c->exit_event);
+
+ pa_assert(!c->default_source);
+ pa_assert(!c->default_sink);
+ pa_xfree(c->configured_default_source);
+ pa_xfree(c->configured_default_sink);
+
+ pa_silence_cache_done(&c->silence_cache);
+ pa_mempool_unref(c->mempool);
+
+ for (j = 0; j < PA_CORE_HOOK_MAX; j++)
+ pa_hook_done(&c->hooks[j]);
+
+ pa_xfree(c);
+}
+
+void pa_core_set_configured_default_sink(pa_core *core, const char *sink) {
+ char *old_sink;
+
+ pa_assert(core);
+
+ old_sink = pa_xstrdup(core->configured_default_sink);
+
+ if (pa_safe_streq(sink, old_sink))
+ goto finish;
+
+ pa_xfree(core->configured_default_sink);
+ core->configured_default_sink = pa_xstrdup(sink);
+ pa_log_info("configured_default_sink: %s -> %s",
+ old_sink ? old_sink : "(unset)", sink ? sink : "(unset)");
+ pa_subscription_post(core, PA_SUBSCRIPTION_EVENT_SERVER | PA_SUBSCRIPTION_EVENT_CHANGE, PA_INVALID_INDEX);
+
+ pa_core_update_default_sink(core);
+
+finish:
+ pa_xfree(old_sink);
+}
+
+void pa_core_set_configured_default_source(pa_core *core, const char *source) {
+ char *old_source;
+
+ pa_assert(core);
+
+ old_source = pa_xstrdup(core->configured_default_source);
+
+ if (pa_safe_streq(source, old_source))
+ goto finish;
+
+ pa_xfree(core->configured_default_source);
+ core->configured_default_source = pa_xstrdup(source);
+ pa_log_info("configured_default_source: %s -> %s",
+ old_source ? old_source : "(unset)", source ? source : "(unset)");
+ pa_subscription_post(core, PA_SUBSCRIPTION_EVENT_SERVER | PA_SUBSCRIPTION_EVENT_CHANGE, PA_INVALID_INDEX);
+
+ pa_core_update_default_source(core);
+
+finish:
+ pa_xfree(old_source);
+}
+
+/* a < b -> return -1
+ * a == b -> return 0
+ * a > b -> return 1 */
+static int compare_sinks(pa_sink *a, pa_sink *b) {
+ pa_core *core;
+
+ core = a->core;
+
+ /* Available sinks always beat unavailable sinks. */
+ if (a->active_port && a->active_port->available == PA_AVAILABLE_NO
+ && (!b->active_port || b->active_port->available != PA_AVAILABLE_NO))
+ return -1;
+ if (b->active_port && b->active_port->available == PA_AVAILABLE_NO
+ && (!a->active_port || a->active_port->available != PA_AVAILABLE_NO))
+ return 1;
+
+ /* The configured default sink is preferred over any other sink. */
+ if (pa_safe_streq(b->name, core->configured_default_sink))
+ return -1;
+ if (pa_safe_streq(a->name, core->configured_default_sink))
+ return 1;
+
+ if (a->priority < b->priority)
+ return -1;
+ if (a->priority > b->priority)
+ return 1;
+
+ /* It's hard to find any difference between these sinks, but maybe one of
+ * them is already the default sink? If so, it's best to keep it as the
+ * default to avoid changing the routing for no good reason. */
+ if (b == core->default_sink)
+ return -1;
+ if (a == core->default_sink)
+ return 1;
+
+ return 0;
+}
+
+void pa_core_update_default_sink(pa_core *core) {
+ pa_sink *best = NULL;
+ pa_sink *sink;
+ uint32_t idx;
+ pa_sink *old_default_sink;
+
+ pa_assert(core);
+
+ PA_IDXSET_FOREACH(sink, core->sinks, idx) {
+ if (!PA_SINK_IS_LINKED(sink->state))
+ continue;
+
+ if (!best) {
+ best = sink;
+ continue;
+ }
+
+ if (compare_sinks(sink, best) > 0)
+ best = sink;
+ }
+
+ old_default_sink = core->default_sink;
+
+ if (best == old_default_sink)
+ return;
+
+ core->default_sink = best;
+ pa_log_info("default_sink: %s -> %s",
+ old_default_sink ? old_default_sink->name : "(unset)", best ? best->name : "(unset)");
+
+ /* If the default sink changed, it may be that the default source has to be
+ * changed too, because monitor sources are prioritized partly based on the
+ * priorities of the monitored sinks. */
+ pa_core_update_default_source(core);
+
+ pa_subscription_post(core, PA_SUBSCRIPTION_EVENT_SERVER | PA_SUBSCRIPTION_EVENT_CHANGE, PA_INVALID_INDEX);
+ pa_hook_fire(&core->hooks[PA_CORE_HOOK_DEFAULT_SINK_CHANGED], core->default_sink);
+
+ /* try to move the streams from old_default_sink to the new default_sink conditionally */
+ if (old_default_sink)
+ pa_sink_move_streams_to_default_sink(core, old_default_sink, true);
+}
+
+/* a < b -> return -1
+ * a == b -> return 0
+ * a > b -> return 1 */
+static int compare_sources(pa_source *a, pa_source *b) {
+ pa_core *core;
+
+ core = a->core;
+
+ /* Available sources always beat unavailable sources. */
+ if (a->active_port && a->active_port->available == PA_AVAILABLE_NO
+ && (!b->active_port || b->active_port->available != PA_AVAILABLE_NO))
+ return -1;
+ if (b->active_port && b->active_port->available == PA_AVAILABLE_NO
+ && (!a->active_port || a->active_port->available != PA_AVAILABLE_NO))
+ return 1;
+
+ /* The configured default source is preferred over any other source. */
+ if (pa_safe_streq(b->name, core->configured_default_source))
+ return -1;
+ if (pa_safe_streq(a->name, core->configured_default_source))
+ return 1;
+
+ /* Monitor sources lose to non-monitor sources. */
+ if (a->monitor_of && !b->monitor_of)
+ return -1;
+ if (!a->monitor_of && b->monitor_of)
+ return 1;
+
+ if (a->priority < b->priority)
+ return -1;
+ if (a->priority > b->priority)
+ return 1;
+
+ /* If the sources are monitors, we can compare the monitored sinks. */
+ if (a->monitor_of)
+ return compare_sinks(a->monitor_of, b->monitor_of);
+
+ /* It's hard to find any difference between these sources, but maybe one of
+ * them is already the default source? If so, it's best to keep it as the
+ * default to avoid changing the routing for no good reason. */
+ if (b == core->default_source)
+ return -1;
+ if (a == core->default_source)
+ return 1;
+
+ return 0;
+}
+
+void pa_core_update_default_source(pa_core *core) {
+ pa_source *best = NULL;
+ pa_source *source;
+ uint32_t idx;
+ pa_source *old_default_source;
+
+ pa_assert(core);
+
+ PA_IDXSET_FOREACH(source, core->sources, idx) {
+ if (!PA_SOURCE_IS_LINKED(source->state))
+ continue;
+
+ if (!best) {
+ best = source;
+ continue;
+ }
+
+ if (compare_sources(source, best) > 0)
+ best = source;
+ }
+
+ old_default_source = core->default_source;
+
+ if (best == old_default_source)
+ return;
+
+ core->default_source = best;
+ pa_log_info("default_source: %s -> %s",
+ old_default_source ? old_default_source->name : "(unset)", best ? best->name : "(unset)");
+ pa_subscription_post(core, PA_SUBSCRIPTION_EVENT_SERVER | PA_SUBSCRIPTION_EVENT_CHANGE, PA_INVALID_INDEX);
+ pa_hook_fire(&core->hooks[PA_CORE_HOOK_DEFAULT_SOURCE_CHANGED], core->default_source);
+
+ /* try to move the streams from old_default_source to the new default_source conditionally */
+ if (old_default_source)
+ pa_source_move_streams_to_default_source(core, old_default_source, true);
+}
+
+void pa_core_set_exit_idle_time(pa_core *core, int time) {
+ pa_assert(core);
+
+ if (time == core->exit_idle_time)
+ return;
+
+ pa_log_info("exit_idle_time: %i -> %i", core->exit_idle_time, time);
+ core->exit_idle_time = time;
+}
+
+static void exit_callback(pa_mainloop_api *m, pa_time_event *e, const struct timeval *t, void *userdata) {
+ pa_core *c = userdata;
+ pa_assert(c->exit_event == e);
+
+ pa_log_info("We are idle, quitting...");
+ pa_core_exit(c, true, 0);
+}
+
+void pa_core_check_idle(pa_core *c) {
+ pa_assert(c);
+
+ if (!c->exit_event &&
+ c->exit_idle_time >= 0 &&
+ pa_idxset_size(c->clients) == 0) {
+
+ c->exit_event = pa_core_rttime_new(c, pa_rtclock_now() + c->exit_idle_time * PA_USEC_PER_SEC, exit_callback, c);
+
+ } else if (c->exit_event && pa_idxset_size(c->clients) > 0) {
+ c->mainloop->time_free(c->exit_event);
+ c->exit_event = NULL;
+ }
+}
+
+int pa_core_exit(pa_core *c, bool force, int retval) {
+ pa_assert(c);
+
+ if (c->disallow_exit && !force)
+ return -1;
+
+ c->mainloop->quit(c->mainloop, retval);
+ return 0;
+}
+
+void pa_core_maybe_vacuum(pa_core *c) {
+ pa_assert(c);
+
+ if (pa_idxset_isempty(c->sink_inputs) && pa_idxset_isempty(c->source_outputs)) {
+ pa_log_debug("Hmm, no streams around, trying to vacuum.");
+ } else {
+ pa_sink *si;
+ pa_source *so;
+ uint32_t idx;
+
+ idx = 0;
+ PA_IDXSET_FOREACH(si, c->sinks, idx)
+ if (si->state != PA_SINK_SUSPENDED)
+ return;
+
+ idx = 0;
+ PA_IDXSET_FOREACH(so, c->sources, idx)
+ if (so->state != PA_SOURCE_SUSPENDED)
+ return;
+
+ pa_log_info("All sinks and sources are suspended, vacuuming memory");
+ }
+
+ pa_mempool_vacuum(c->mempool);
+}
+
+pa_time_event* pa_core_rttime_new(pa_core *c, pa_usec_t usec, pa_time_event_cb_t cb, void *userdata) {
+ struct timeval tv;
+
+ pa_assert(c);
+ pa_assert(c->mainloop);
+
+ return c->mainloop->time_new(c->mainloop, pa_timeval_rtstore(&tv, usec, true), cb, userdata);
+}
+
+void pa_core_rttime_restart(pa_core *c, pa_time_event *e, pa_usec_t usec) {
+ struct timeval tv;
+
+ pa_assert(c);
+ pa_assert(c->mainloop);
+
+ c->mainloop->time_restart(e, pa_timeval_rtstore(&tv, usec, true));
+}
+
+void pa_core_move_streams_to_newly_available_preferred_sink(pa_core *c, pa_sink *s) {
+ pa_sink_input *si;
+ uint32_t idx;
+
+ pa_assert(c);
+ pa_assert(s);
+
+ PA_IDXSET_FOREACH(si, c->sink_inputs, idx) {
+ if (si->sink == s)
+ continue;
+
+ if (!si->sink)
+ continue;
+
+ /* Skip this sink input if it is connecting a filter sink to
+ * the master */
+ if (si->origin_sink)
+ continue;
+
+ /* It might happen that a stream and a sink are set up at the
+ same time, in which case we want to make sure we don't
+ interfere with that */
+ if (!PA_SINK_INPUT_IS_LINKED(si->state))
+ continue;
+
+ if (pa_safe_streq(si->preferred_sink, s->name))
+ pa_sink_input_move_to(si, s, false);
+ }
+
+}
+
+void pa_core_move_streams_to_newly_available_preferred_source(pa_core *c, pa_source *s) {
+ pa_source_output *so;
+ uint32_t idx;
+
+ pa_assert(c);
+ pa_assert(s);
+
+ PA_IDXSET_FOREACH(so, c->source_outputs, idx) {
+ if (so->source == s)
+ continue;
+
+ if (so->direct_on_input)
+ continue;
+
+ if (!so->source)
+ continue;
+
+ /* Skip this source output if it is connecting a filter source to
+ * the master */
+ if (so->destination_source)
+ continue;
+
+ /* It might happen that a stream and a source are set up at the
+ same time, in which case we want to make sure we don't
+ interfere with that */
+ if (!PA_SOURCE_OUTPUT_IS_LINKED(so->state))
+ continue;
+
+ if (pa_safe_streq(so->preferred_source, s->name))
+ pa_source_output_move_to(so, s, false);
+ }
+
+}
+
+
+/* Helper macro to reduce repetition in pa_suspend_cause_to_string().
+ * Parameters:
+ * char *p: the current position in the write buffer
+ * bool first: is cause_to_check the first cause to be written?
+ * pa_suspend_cause_t cause_bitfield: the causes given to pa_suspend_cause_to_string()
+ * pa_suspend_cause_t cause_to_check: the cause whose presence in cause_bitfield is to be checked
+ */
+#define CHECK_CAUSE(p, first, cause_bitfield, cause_to_check) \
+ if (cause_bitfield & PA_SUSPEND_##cause_to_check) { \
+ size_t len = sizeof(#cause_to_check) - 1; \
+ if (!first) { \
+ *p = '|'; \
+ p++; \
+ } \
+ first = false; \
+ memcpy(p, #cause_to_check, len); \
+ p += len; \
+ }
+
+const char *pa_suspend_cause_to_string(pa_suspend_cause_t cause_bitfield, char buf[PA_SUSPEND_CAUSE_TO_STRING_BUF_SIZE]) {
+ char *p = buf;
+ bool first = true;
+
+ CHECK_CAUSE(p, first, cause_bitfield, USER);
+ CHECK_CAUSE(p, first, cause_bitfield, APPLICATION);
+ CHECK_CAUSE(p, first, cause_bitfield, IDLE);
+ CHECK_CAUSE(p, first, cause_bitfield, SESSION);
+ CHECK_CAUSE(p, first, cause_bitfield, PASSTHROUGH);
+ CHECK_CAUSE(p, first, cause_bitfield, INTERNAL);
+ CHECK_CAUSE(p, first, cause_bitfield, UNAVAILABLE);
+
+ if (p == buf) {
+ memcpy(p, "(none)", 6);
+ p += 6;
+ }
+
+ *p = 0;
+
+ return buf;
+}
diff --git a/src/pulsecore/core.h b/src/pulsecore/core.h
new file mode 100644
index 0000000..57924b9
--- /dev/null
+++ b/src/pulsecore/core.h
@@ -0,0 +1,287 @@
+#ifndef foocorehfoo
+#define foocorehfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulsecore/typedefs.h>
+#include <pulse/mainloop-api.h>
+#include <pulse/sample.h>
+#include <pulsecore/cpu.h>
+
+/* This is a bitmask that encodes the cause why a sink/source is
+ * suspended.
+ *
+ * When adding new causes, remember to update pa_suspend_cause_to_string() and
+ * PA_SUSPEND_CAUSE_TO_STRING_BUF_SIZE! */
+typedef enum pa_suspend_cause {
+ PA_SUSPEND_USER = 1, /* Exposed to the user via some protocol */
+ PA_SUSPEND_APPLICATION = 2, /* Used by the device reservation logic */
+ PA_SUSPEND_IDLE = 4, /* Used by module-suspend-on-idle */
+ PA_SUSPEND_SESSION = 8, /* Used by module-hal for mark inactive sessions */
+ PA_SUSPEND_PASSTHROUGH = 16, /* Used to suspend monitor sources when the sink is in passthrough mode */
+ PA_SUSPEND_INTERNAL = 32, /* This is used for short period server-internal suspends, such as for sample rate updates */
+ PA_SUSPEND_UNAVAILABLE = 64, /* Used by device implementations that have to suspend when the device is unavailable */
+ PA_SUSPEND_ALL = 0xFFFF /* Magic cause that can be used to resume forcibly */
+} pa_suspend_cause_t;
+
+#include <pulsecore/idxset.h>
+#include <pulsecore/hashmap.h>
+#include <pulsecore/memblock.h>
+#include <pulsecore/resampler.h>
+#include <pulsecore/llist.h>
+#include <pulsecore/hook-list.h>
+#include <pulsecore/asyncmsgq.h>
+#include <pulsecore/sample-util.h>
+#include <pulsecore/sink.h>
+#include <pulsecore/source.h>
+#include <pulsecore/core-subscribe.h>
+#include <pulsecore/msgobject.h>
+
+typedef enum pa_server_type {
+ PA_SERVER_TYPE_UNSET,
+ PA_SERVER_TYPE_USER,
+ PA_SERVER_TYPE_SYSTEM,
+ PA_SERVER_TYPE_NONE
+} pa_server_type_t;
+
+typedef enum pa_core_state {
+ PA_CORE_STARTUP,
+ PA_CORE_RUNNING,
+ PA_CORE_SHUTDOWN
+} pa_core_state_t;
+
+typedef enum pa_core_hook {
+ PA_CORE_HOOK_SINK_NEW,
+ PA_CORE_HOOK_SINK_FIXATE,
+ PA_CORE_HOOK_SINK_PUT,
+ PA_CORE_HOOK_SINK_UNLINK,
+ PA_CORE_HOOK_SINK_UNLINK_POST,
+ PA_CORE_HOOK_SINK_STATE_CHANGED,
+ PA_CORE_HOOK_SINK_PROPLIST_CHANGED,
+ PA_CORE_HOOK_SINK_PORT_CHANGED,
+ PA_CORE_HOOK_SINK_FLAGS_CHANGED,
+ PA_CORE_HOOK_SINK_VOLUME_CHANGED,
+ PA_CORE_HOOK_SINK_MUTE_CHANGED,
+ PA_CORE_HOOK_SINK_PORT_LATENCY_OFFSET_CHANGED,
+ PA_CORE_HOOK_SOURCE_NEW,
+ PA_CORE_HOOK_SOURCE_FIXATE,
+ PA_CORE_HOOK_SOURCE_PUT,
+ PA_CORE_HOOK_SOURCE_UNLINK,
+ PA_CORE_HOOK_SOURCE_UNLINK_POST,
+ PA_CORE_HOOK_SOURCE_STATE_CHANGED,
+ PA_CORE_HOOK_SOURCE_PROPLIST_CHANGED,
+ PA_CORE_HOOK_SOURCE_PORT_CHANGED,
+ PA_CORE_HOOK_SOURCE_FLAGS_CHANGED,
+ PA_CORE_HOOK_SOURCE_VOLUME_CHANGED,
+ PA_CORE_HOOK_SOURCE_MUTE_CHANGED,
+ PA_CORE_HOOK_SOURCE_PORT_LATENCY_OFFSET_CHANGED,
+ PA_CORE_HOOK_SINK_INPUT_NEW,
+ PA_CORE_HOOK_SINK_INPUT_FIXATE,
+ PA_CORE_HOOK_SINK_INPUT_PUT,
+ PA_CORE_HOOK_SINK_INPUT_UNLINK,
+ PA_CORE_HOOK_SINK_INPUT_UNLINK_POST,
+ PA_CORE_HOOK_SINK_INPUT_MOVE_START,
+ PA_CORE_HOOK_SINK_INPUT_MOVE_FINISH,
+ PA_CORE_HOOK_SINK_INPUT_MOVE_FAIL,
+ PA_CORE_HOOK_SINK_INPUT_STATE_CHANGED,
+ PA_CORE_HOOK_SINK_INPUT_PROPLIST_CHANGED,
+ PA_CORE_HOOK_SINK_INPUT_VOLUME_CHANGED,
+ PA_CORE_HOOK_SINK_INPUT_MUTE_CHANGED,
+ PA_CORE_HOOK_SINK_INPUT_SEND_EVENT,
+ PA_CORE_HOOK_SOURCE_OUTPUT_NEW,
+ PA_CORE_HOOK_SOURCE_OUTPUT_FIXATE,
+ PA_CORE_HOOK_SOURCE_OUTPUT_PUT,
+ PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK,
+ PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK_POST,
+ PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_START,
+ PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_FINISH,
+ PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_FAIL,
+ PA_CORE_HOOK_SOURCE_OUTPUT_STATE_CHANGED,
+ PA_CORE_HOOK_SOURCE_OUTPUT_PROPLIST_CHANGED,
+ PA_CORE_HOOK_SOURCE_OUTPUT_VOLUME_CHANGED,
+ PA_CORE_HOOK_SOURCE_OUTPUT_MUTE_CHANGED,
+ PA_CORE_HOOK_SOURCE_OUTPUT_SEND_EVENT,
+ PA_CORE_HOOK_CLIENT_NEW,
+ PA_CORE_HOOK_CLIENT_PUT,
+ PA_CORE_HOOK_CLIENT_UNLINK,
+ PA_CORE_HOOK_CLIENT_PROPLIST_CHANGED,
+ PA_CORE_HOOK_CLIENT_SEND_EVENT,
+ PA_CORE_HOOK_CARD_NEW,
+ PA_CORE_HOOK_CARD_CHOOSE_INITIAL_PROFILE,
+ PA_CORE_HOOK_CARD_PUT,
+ PA_CORE_HOOK_CARD_UNLINK,
+ PA_CORE_HOOK_CARD_PREFERRED_PORT_CHANGED,
+ PA_CORE_HOOK_CARD_PROFILE_CHANGED,
+ PA_CORE_HOOK_CARD_PROFILE_ADDED,
+ PA_CORE_HOOK_CARD_PROFILE_AVAILABLE_CHANGED,
+ PA_CORE_HOOK_CARD_SUSPEND_CHANGED,
+ PA_CORE_HOOK_PORT_AVAILABLE_CHANGED,
+ PA_CORE_HOOK_PORT_LATENCY_OFFSET_CHANGED,
+ PA_CORE_HOOK_DEFAULT_SINK_CHANGED,
+ PA_CORE_HOOK_DEFAULT_SOURCE_CHANGED,
+ PA_CORE_HOOK_MODULE_NEW,
+ PA_CORE_HOOK_MODULE_PROPLIST_CHANGED,
+ PA_CORE_HOOK_MODULE_UNLINK,
+ PA_CORE_HOOK_SAMPLE_CACHE_NEW,
+ PA_CORE_HOOK_SAMPLE_CACHE_CHANGED,
+ PA_CORE_HOOK_SAMPLE_CACHE_UNLINK,
+ PA_CORE_HOOK_MAX
+} pa_core_hook_t;
+
+/* The core structure of PulseAudio. Every PulseAudio daemon contains
+ * exactly one of these. It is used for storing kind of global
+ * variables for the daemon. */
+
+struct pa_core {
+ pa_msgobject parent;
+
+ pa_core_state_t state;
+
+ /* A random value which may be used to identify this instance of
+ * PulseAudio. Not cryptographically secure in any way. */
+ uint32_t cookie;
+
+ pa_mainloop_api *mainloop;
+
+ /* idxset of all kinds of entities */
+ pa_idxset *clients, *cards, *sinks, *sources, *sink_inputs, *source_outputs, *modules, *scache;
+
+ /* Some hashmaps for all sorts of entities */
+ pa_hashmap *namereg, *shared, *message_handlers;
+
+ /* The default sink/source as configured by the user. If the user hasn't
+ * explicitly configured anything, these are set to NULL. These are strings
+ * instead of sink/source pointers, because that allows us to reference
+ * devices that don't currently exist. That's useful for remembering that
+ * a hotplugged USB sink was previously set as the default sink. */
+ char *configured_default_sink;
+ char *configured_default_source;
+
+ /* The effective default sink/source. If no sink or source is explicitly
+ * configured as the default, we pick the device that ranks highest
+ * according to the compare_sinks() and compare_sources() functions in
+ * core.c. pa_core_update_default_sink/source() has to be called whenever
+ * anything changes that might change the comparison results. */
+ pa_sink *default_sink;
+ pa_source *default_source;
+
+ pa_channel_map default_channel_map;
+ pa_sample_spec default_sample_spec;
+ uint32_t alternate_sample_rate;
+ unsigned default_n_fragments, default_fragment_size_msec;
+ unsigned deferred_volume_safety_margin_usec;
+ int deferred_volume_extra_delay_usec;
+ unsigned lfe_crossover_freq;
+
+ pa_defer_event *module_defer_unload_event;
+ pa_hashmap *modules_pending_unload; /* pa_module -> pa_module (hashmap-as-a-set) */
+
+ pa_defer_event *subscription_defer_event;
+ PA_LLIST_HEAD(pa_subscription, subscriptions);
+ PA_LLIST_HEAD(pa_subscription_event, subscription_event_queue);
+ pa_subscription_event *subscription_event_last;
+
+ /* The mempool is used for data we write to, it's readonly for the client. */
+ pa_mempool *mempool;
+
+ /* Shared memory size, as specified either by daemon configuration
+ * or PA daemon defaults (~ 64 MiB). */
+ size_t shm_size;
+
+ pa_silence_cache silence_cache;
+
+ pa_time_event *exit_event;
+ pa_time_event *scache_auto_unload_event;
+
+ int exit_idle_time, scache_idle_time;
+
+ bool flat_volumes:1;
+ bool rescue_streams:1;
+ bool disallow_module_loading:1;
+ bool disallow_exit:1;
+ bool running_as_daemon:1;
+ bool realtime_scheduling:1;
+ bool avoid_resampling:1;
+ bool disable_remixing:1;
+ bool remixing_use_all_sink_channels:1;
+ bool remixing_produce_lfe:1;
+ bool remixing_consume_lfe:1;
+ bool deferred_volume:1;
+
+ pa_resample_method_t resample_method;
+ int realtime_priority;
+
+ pa_server_type_t server_type;
+ pa_cpu_info cpu_info;
+
+ /* hooks */
+ pa_hook hooks[PA_CORE_HOOK_MAX];
+};
+
+PA_DECLARE_PUBLIC_CLASS(pa_core);
+#define PA_CORE(o) pa_core_cast(o)
+
+enum {
+ PA_CORE_MESSAGE_UNLOAD_MODULE,
+ PA_CORE_MESSAGE_MAX
+};
+
+pa_core* pa_core_new(pa_mainloop_api *m, bool shared, bool enable_memfd, size_t shm_size);
+
+void pa_core_set_configured_default_sink(pa_core *core, const char *sink);
+void pa_core_set_configured_default_source(pa_core *core, const char *source);
+
+/* These should be called whenever something changes that may affect the
+ * default sink or source choice.
+ *
+ * If the default source choice happens between two monitor sources, the
+ * monitored sinks are compared, so if the default sink changes, the default
+ * source may change too. However, pa_core_update_default_sink() calls
+ * pa_core_update_default_source() internally, so it's sufficient to only call
+ * pa_core_update_default_sink() when something happens that affects the sink
+ * ordering. */
+void pa_core_update_default_sink(pa_core *core);
+void pa_core_update_default_source(pa_core *core);
+
+void pa_core_set_exit_idle_time(pa_core *core, int time);
+
+/* Check whether no one is connected to this core */
+void pa_core_check_idle(pa_core *c);
+
+int pa_core_exit(pa_core *c, bool force, int retval);
+
+void pa_core_maybe_vacuum(pa_core *c);
+
+/* wrapper for c->mainloop->time_*() RT time events */
+pa_time_event* pa_core_rttime_new(pa_core *c, pa_usec_t usec, pa_time_event_cb_t cb, void *userdata);
+void pa_core_rttime_restart(pa_core *c, pa_time_event *e, pa_usec_t usec);
+
+static const size_t PA_SUSPEND_CAUSE_TO_STRING_BUF_SIZE =
+ sizeof("USER|APPLICATION|IDLE|SESSION|PASSTHROUGH|INTERNAL|UNAVAILABLE");
+
+/* Converts the given suspend cause to a string. The string is written to the
+ * provided buffer. The same buffer is the return value of this function. */
+const char *pa_suspend_cause_to_string(pa_suspend_cause_t cause, char buf[PA_SUSPEND_CAUSE_TO_STRING_BUF_SIZE]);
+
+void pa_core_move_streams_to_newly_available_preferred_sink(pa_core *c, pa_sink *s);
+
+void pa_core_move_streams_to_newly_available_preferred_source(pa_core *c, pa_source *s);
+
+#endif
diff --git a/src/pulsecore/cpu-arm.c b/src/pulsecore/cpu-arm.c
new file mode 100644
index 0000000..122a72b
--- /dev/null
+++ b/src/pulsecore/cpu-arm.c
@@ -0,0 +1,170 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2009 Wim Taymans <wim.taymans@collabora.co.uk>
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdint.h>
+#include <sys/types.h>
+#include <fcntl.h>
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/log.h>
+
+#include "cpu-arm.h"
+
+#if defined (__arm__) && defined (__linux__)
+
+#define MAX_BUFFER 4096
+static char *
+get_cpuinfo_line(char *cpuinfo, const char *tag) {
+ char *line, *end, *colon;
+
+ if (!(line = strstr(cpuinfo, tag)))
+ return NULL;
+
+ if (!(end = strchr(line, '\n')))
+ return NULL;
+
+ if (!(colon = strchr(line, ':')))
+ return NULL;
+
+ if (++colon >= end)
+ return NULL;
+
+ return pa_xstrndup(colon, end - colon);
+}
+
+static char *get_cpuinfo(void) {
+ char *cpuinfo;
+ int n, fd;
+
+ cpuinfo = pa_xmalloc(MAX_BUFFER);
+
+ if ((fd = pa_open_cloexec("/proc/cpuinfo", O_RDONLY, 0)) < 0) {
+ pa_xfree(cpuinfo);
+ return NULL;
+ }
+
+ if ((n = pa_read(fd, cpuinfo, MAX_BUFFER-1, NULL)) < 0) {
+ pa_xfree(cpuinfo);
+ pa_close(fd);
+ return NULL;
+ }
+ cpuinfo[n] = 0;
+ pa_close(fd);
+
+ return cpuinfo;
+}
+#endif /* defined (__arm__) && defined (__linux__) */
+
+void pa_cpu_get_arm_flags(pa_cpu_arm_flag_t *flags) {
+#if defined (__arm__) && defined (__linux__)
+ char *cpuinfo, *line;
+ int arch, part;
+
+ /* We need to read the CPU flags from /proc/cpuinfo because there is no user
+ * space support to get the CPU features. This only works on linux AFAIK. */
+ if (!(cpuinfo = get_cpuinfo())) {
+ pa_log("Can't read cpuinfo");
+ return;
+ }
+
+ *flags = 0;
+
+ /* get the CPU architecture */
+ if ((line = get_cpuinfo_line(cpuinfo, "CPU architecture"))) {
+ arch = strtoul(line, NULL, 0);
+ if (arch >= 6)
+ *flags |= PA_CPU_ARM_V6;
+ if (arch >= 7)
+ *flags |= PA_CPU_ARM_V7;
+
+ pa_xfree(line);
+ }
+
+ /* get the CPU features */
+ if ((line = get_cpuinfo_line(cpuinfo, "Features"))) {
+ const char *state = NULL;
+ char *current;
+
+ while ((current = pa_split_spaces(line, &state))) {
+ if (pa_streq(current, "vfp"))
+ *flags |= PA_CPU_ARM_VFP;
+ else if (pa_streq(current, "edsp"))
+ *flags |= PA_CPU_ARM_EDSP;
+ else if (pa_streq(current, "neon"))
+ *flags |= PA_CPU_ARM_NEON;
+ else if (pa_streq(current, "vfpv3"))
+ *flags |= PA_CPU_ARM_VFPV3;
+
+ pa_xfree(current);
+ }
+ pa_xfree(line);
+ }
+
+ /* get the CPU part number */
+ if ((line = get_cpuinfo_line(cpuinfo, "CPU part"))) {
+ part = strtoul(line, NULL, 0);
+ if (part == 0xc08)
+ *flags |= PA_CPU_ARM_CORTEX_A8;
+ pa_xfree(line);
+ }
+ pa_xfree(cpuinfo);
+
+ pa_log_info("CPU flags: %s%s%s%s%s%s%s",
+ (*flags & PA_CPU_ARM_V6) ? "V6 " : "",
+ (*flags & PA_CPU_ARM_V7) ? "V7 " : "",
+ (*flags & PA_CPU_ARM_VFP) ? "VFP " : "",
+ (*flags & PA_CPU_ARM_EDSP) ? "EDSP " : "",
+ (*flags & PA_CPU_ARM_NEON) ? "NEON " : "",
+ (*flags & PA_CPU_ARM_VFPV3) ? "VFPV3 " : "",
+ (*flags & PA_CPU_ARM_CORTEX_A8) ? "Cortex-A8 " : "");
+#endif
+}
+
+bool pa_cpu_init_arm(pa_cpu_arm_flag_t *flags) {
+#if defined (__arm__)
+#if defined (__linux__)
+ pa_cpu_get_arm_flags(flags);
+
+ if (*flags & PA_CPU_ARM_V6)
+ pa_volume_func_init_arm(*flags);
+
+#ifdef HAVE_NEON
+ if (*flags & PA_CPU_ARM_NEON) {
+ pa_convert_func_init_neon(*flags);
+ pa_mix_func_init_neon(*flags);
+ pa_remap_func_init_neon(*flags);
+ }
+#endif
+
+ return true;
+
+#else /* defined (__linux__) */
+ pa_log("Reading ARM CPU features not yet supported on this OS");
+#endif /* defined (__linux__) */
+
+#else /* defined (__arm__) */
+ return false;
+#endif /* defined (__arm__) */
+}
diff --git a/src/pulsecore/cpu-arm.h b/src/pulsecore/cpu-arm.h
new file mode 100644
index 0000000..dfdda32
--- /dev/null
+++ b/src/pulsecore/cpu-arm.h
@@ -0,0 +1,53 @@
+#ifndef foocpuarmhfoo
+#define foocpuarmhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2009 Wim Taymans <wim.taymans@collabora.co.uk>
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdint.h>
+#include <pulsecore/macro.h>
+
+#ifndef PACKAGE
+#error "Please include config.h before including this file!"
+#endif
+
+typedef enum pa_cpu_arm_flag {
+ PA_CPU_ARM_V6 = (1 << 0),
+ PA_CPU_ARM_V7 = (1 << 1),
+ PA_CPU_ARM_VFP = (1 << 2),
+ PA_CPU_ARM_EDSP = (1 << 3),
+ PA_CPU_ARM_NEON = (1 << 4),
+ PA_CPU_ARM_VFPV3 = (1 << 5),
+ PA_CPU_ARM_CORTEX_A8 = (1 << 6),
+} pa_cpu_arm_flag_t;
+
+void pa_cpu_get_arm_flags(pa_cpu_arm_flag_t *flags);
+bool pa_cpu_init_arm(pa_cpu_arm_flag_t *flags);
+
+/* some optimized functions */
+void pa_volume_func_init_arm(pa_cpu_arm_flag_t flags);
+
+#ifdef HAVE_NEON
+void pa_convert_func_init_neon(pa_cpu_arm_flag_t flags);
+void pa_mix_func_init_neon(pa_cpu_arm_flag_t flags);
+void pa_remap_func_init_neon(pa_cpu_arm_flag_t flags);
+#endif
+
+#endif /* foocpuarmhfoo */
diff --git a/src/pulsecore/cpu-orc.c b/src/pulsecore/cpu-orc.c
new file mode 100644
index 0000000..5caf6b6
--- /dev/null
+++ b/src/pulsecore/cpu-orc.c
@@ -0,0 +1,39 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2010 Arun Raghavan <arun.raghavan@collabora.co.uk>
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "cpu-orc.h"
+
+bool pa_cpu_init_orc(pa_cpu_info cpu_info) {
+#ifndef DISABLE_ORC
+ /* Update these as we test on more architectures */
+ pa_cpu_x86_flag_t x86_want_flags = PA_CPU_X86_MMX | PA_CPU_X86_SSE | PA_CPU_X86_SSE2 | PA_CPU_X86_SSE3 | PA_CPU_X86_SSSE3 | PA_CPU_X86_SSE4_1 | PA_CPU_X86_SSE4_2;
+
+ /* Enable Orc svolume optimizations */
+ if ((cpu_info.cpu_type == PA_CPU_X86) && (cpu_info.flags.x86 & x86_want_flags)) {
+ pa_volume_func_init_orc();
+ return true;
+ }
+#endif
+
+ return false;
+}
diff --git a/src/pulsecore/cpu-orc.h b/src/pulsecore/cpu-orc.h
new file mode 100644
index 0000000..edfacea
--- /dev/null
+++ b/src/pulsecore/cpu-orc.h
@@ -0,0 +1,31 @@
+#ifndef foocpuorchfoo
+#define foocpuorchfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2010 Arun Raghavan <arun.raghavan@collabora.co.uk>
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulsecore/cpu.h>
+
+/* Orc-optimised bits */
+
+bool pa_cpu_init_orc(pa_cpu_info cpu_info);
+
+void pa_volume_func_init_orc(void);
+
+#endif /* foocpuorchfoo */
diff --git a/src/pulsecore/cpu-x86.c b/src/pulsecore/cpu-x86.c
new file mode 100644
index 0000000..4e59e14
--- /dev/null
+++ b/src/pulsecore/cpu-x86.c
@@ -0,0 +1,132 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2009 Wim Taymans <wim.taymans@collabora.co.uk>
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdint.h>
+
+#ifdef HAVE_CPUID_H
+#include <cpuid.h>
+#endif
+
+#include <pulsecore/log.h>
+
+#include "cpu-x86.h"
+
+void pa_cpu_get_x86_flags(pa_cpu_x86_flag_t *flags) {
+#if (defined(__i386__) || defined(__amd64__)) && defined(HAVE_CPUID_H)
+ uint32_t eax, ebx, ecx, edx;
+ uint32_t level;
+
+ *flags = 0;
+
+ /* get standard level */
+ if (__get_cpuid(0x00000000, &level, &ebx, &ecx, &edx) == 0)
+ goto finish;
+
+ if (level >= 1) {
+ if (__get_cpuid(0x00000001, &eax, &ebx, &ecx, &edx) == 0)
+ goto finish;
+
+ if (edx & (1<<15))
+ *flags |= PA_CPU_X86_CMOV;
+
+ if (edx & (1<<23))
+ *flags |= PA_CPU_X86_MMX;
+
+ if (edx & (1<<25))
+ *flags |= PA_CPU_X86_SSE;
+
+ if (edx & (1<<26))
+ *flags |= PA_CPU_X86_SSE2;
+
+ if (ecx & (1<<0))
+ *flags |= PA_CPU_X86_SSE3;
+
+ if (ecx & (1<<9))
+ *flags |= PA_CPU_X86_SSSE3;
+
+ if (ecx & (1<<19))
+ *flags |= PA_CPU_X86_SSE4_1;
+
+ if (ecx & (1<<20))
+ *flags |= PA_CPU_X86_SSE4_2;
+ }
+
+ /* get extended level */
+ if (__get_cpuid(0x80000000, &level, &ebx, &ecx, &edx) == 0)
+ goto finish;
+
+ if (level >= 0x80000001) {
+ if (__get_cpuid(0x80000001, &eax, &ebx, &ecx, &edx) == 0)
+ goto finish;
+
+ if (edx & (1<<22))
+ *flags |= PA_CPU_X86_MMXEXT;
+
+ if (edx & (1<<23))
+ *flags |= PA_CPU_X86_MMX;
+
+ if (edx & (1<<30))
+ *flags |= PA_CPU_X86_3DNOWEXT;
+
+ if (edx & (1<<31))
+ *flags |= PA_CPU_X86_3DNOW;
+ }
+
+finish:
+ pa_log_info("CPU flags: %s%s%s%s%s%s%s%s%s%s%s",
+ (*flags & PA_CPU_X86_CMOV) ? "CMOV " : "",
+ (*flags & PA_CPU_X86_MMX) ? "MMX " : "",
+ (*flags & PA_CPU_X86_SSE) ? "SSE " : "",
+ (*flags & PA_CPU_X86_SSE2) ? "SSE2 " : "",
+ (*flags & PA_CPU_X86_SSE3) ? "SSE3 " : "",
+ (*flags & PA_CPU_X86_SSSE3) ? "SSSE3 " : "",
+ (*flags & PA_CPU_X86_SSE4_1) ? "SSE4_1 " : "",
+ (*flags & PA_CPU_X86_SSE4_2) ? "SSE4_2 " : "",
+ (*flags & PA_CPU_X86_MMXEXT) ? "MMXEXT " : "",
+ (*flags & PA_CPU_X86_3DNOW) ? "3DNOW " : "",
+ (*flags & PA_CPU_X86_3DNOWEXT) ? "3DNOWEXT " : "");
+#endif /* (defined(__i386__) || defined(__amd64__)) && defined(HAVE_CPUID_H) */
+}
+
+bool pa_cpu_init_x86(pa_cpu_x86_flag_t *flags) {
+#if defined (__i386__) || defined (__amd64__)
+ pa_cpu_get_x86_flags(flags);
+
+ /* activate various optimisations */
+ if (*flags & PA_CPU_X86_MMX) {
+ pa_volume_func_init_mmx(*flags);
+ pa_remap_func_init_mmx(*flags);
+ }
+
+ if (*flags & (PA_CPU_X86_SSE | PA_CPU_X86_SSE2)) {
+ pa_volume_func_init_sse(*flags);
+ pa_remap_func_init_sse(*flags);
+ pa_convert_func_init_sse(*flags);
+ }
+
+ return true;
+#else /* defined (__i386__) || defined (__amd64__) */
+ return false;
+#endif /* defined (__i386__) || defined (__amd64__) */
+}
diff --git a/src/pulsecore/cpu-x86.h b/src/pulsecore/cpu-x86.h
new file mode 100644
index 0000000..eff2014
--- /dev/null
+++ b/src/pulsecore/cpu-x86.h
@@ -0,0 +1,59 @@
+#ifndef foocpux86hfoo
+#define foocpux86hfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2009 Wim Taymans <wim.taymans@collabora.co.uk>
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdint.h>
+#include <pulsecore/macro.h>
+
+typedef enum pa_cpu_x86_flag {
+ PA_CPU_X86_MMX = (1 << 0),
+ PA_CPU_X86_MMXEXT = (1 << 1),
+ PA_CPU_X86_SSE = (1 << 2),
+ PA_CPU_X86_SSE2 = (1 << 3),
+ PA_CPU_X86_SSE3 = (1 << 4),
+ PA_CPU_X86_SSSE3 = (1 << 5),
+ PA_CPU_X86_SSE4_1 = (1 << 6),
+ PA_CPU_X86_SSE4_2 = (1 << 7),
+ PA_CPU_X86_3DNOW = (1 << 8),
+ PA_CPU_X86_3DNOWEXT = (1 << 9),
+ PA_CPU_X86_CMOV = (1 << 10)
+} pa_cpu_x86_flag_t;
+
+void pa_cpu_get_x86_flags(pa_cpu_x86_flag_t *flags);
+bool pa_cpu_init_x86 (pa_cpu_x86_flag_t *flags);
+
+#if defined (__i386__)
+typedef int32_t pa_reg_x86;
+#elif defined (__amd64__)
+typedef int64_t pa_reg_x86;
+#endif
+
+/* some optimized functions */
+void pa_volume_func_init_mmx(pa_cpu_x86_flag_t flags);
+void pa_volume_func_init_sse(pa_cpu_x86_flag_t flags);
+
+void pa_remap_func_init_mmx(pa_cpu_x86_flag_t flags);
+void pa_remap_func_init_sse(pa_cpu_x86_flag_t flags);
+
+void pa_convert_func_init_sse (pa_cpu_x86_flag_t flags);
+
+#endif /* foocpux86hfoo */
diff --git a/src/pulsecore/cpu.c b/src/pulsecore/cpu.c
new file mode 100644
index 0000000..e0c110e
--- /dev/null
+++ b/src/pulsecore/cpu.c
@@ -0,0 +1,38 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2014 Peter Meerwald <pmeerw@pmeerw.net>
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "cpu.h"
+#include "cpu-orc.h"
+
+void pa_cpu_init(pa_cpu_info *cpu_info) {
+ cpu_info->cpu_type = PA_CPU_UNDEFINED;
+ /* don't force generic code, used for testing only */
+ cpu_info->force_generic_code = false;
+ if (!getenv("PULSE_NO_SIMD")) {
+ if (pa_cpu_init_x86(&cpu_info->flags.x86))
+ cpu_info->cpu_type = PA_CPU_X86;
+ else if (pa_cpu_init_arm(&cpu_info->flags.arm))
+ cpu_info->cpu_type = PA_CPU_ARM;
+ pa_cpu_init_orc(*cpu_info);
+ }
+
+ pa_remap_func_init(cpu_info);
+ pa_mix_func_init(cpu_info);
+}
diff --git a/src/pulsecore/cpu.h b/src/pulsecore/cpu.h
new file mode 100644
index 0000000..e65c4fb
--- /dev/null
+++ b/src/pulsecore/cpu.h
@@ -0,0 +1,49 @@
+#ifndef foocpuhfoo
+#define foocpuhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2010 Arun Raghavan
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulsecore/cpu-x86.h>
+#include <pulsecore/cpu-arm.h>
+
+typedef enum {
+ PA_CPU_UNDEFINED = 0,
+ PA_CPU_X86,
+ PA_CPU_ARM,
+} pa_cpu_type_t;
+
+typedef struct pa_cpu_info pa_cpu_info;
+
+struct pa_cpu_info {
+ pa_cpu_type_t cpu_type;
+
+ union {
+ pa_cpu_x86_flag_t x86;
+ pa_cpu_arm_flag_t arm;
+ } flags;
+ bool force_generic_code;
+};
+
+void pa_cpu_init(pa_cpu_info *cpu_info);
+
+void pa_remap_func_init(const pa_cpu_info *cpu_info);
+void pa_mix_func_init(const pa_cpu_info *cpu_info);
+
+#endif /* foocpuhfoo */
diff --git a/src/pulsecore/creds.h b/src/pulsecore/creds.h
new file mode 100644
index 0000000..9fdbb4f
--- /dev/null
+++ b/src/pulsecore/creds.h
@@ -0,0 +1,64 @@
+#ifndef foocredshfoo
+#define foocredshfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/types.h>
+
+#ifndef PACKAGE
+#error "Please include config.h before including this file!"
+#endif
+
+#include <pulsecore/socket.h>
+#include <stdbool.h>
+
+#define MAX_ANCIL_DATA_FDS 2
+
+typedef struct pa_creds pa_creds;
+typedef struct pa_cmsg_ancil_data pa_cmsg_ancil_data;
+
+#if defined(SCM_CREDENTIALS)
+
+#define HAVE_CREDS 1
+
+struct pa_creds {
+ gid_t gid;
+ uid_t uid;
+};
+
+/* Struct for handling ancillary data, i e, extra data that can be sent together with a message
+ * over unix pipes. Supports sending and receiving credentials and file descriptors. */
+struct pa_cmsg_ancil_data {
+ pa_creds creds;
+ bool creds_valid;
+ int nfd;
+
+ /* Don't close these fds by your own. Check pa_cmsg_ancil_data_close_fds() */
+ int fds[MAX_ANCIL_DATA_FDS];
+ bool close_fds_on_cleanup;
+};
+
+void pa_cmsg_ancil_data_close_fds(struct pa_cmsg_ancil_data *ancil);
+
+#else
+#undef HAVE_CREDS
+#endif
+
+#endif
diff --git a/src/pulsecore/database-gdbm.c b/src/pulsecore/database-gdbm.c
new file mode 100644
index 0000000..b39f7de
--- /dev/null
+++ b/src/pulsecore/database-gdbm.c
@@ -0,0 +1,244 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2009 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <gdbm.h>
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/log.h>
+
+#include "database.h"
+
+#define MAKE_GDBM_FILE(x) ((GDBM_FILE) (x))
+
+static inline datum* datum_to_gdbm(datum *to, const pa_datum *from) {
+ pa_assert(from);
+ pa_assert(to);
+
+ to->dptr = from->data;
+ to->dsize = from->size;
+
+ return to;
+}
+
+static inline pa_datum* datum_from_gdbm(pa_datum *to, const datum *from) {
+ pa_assert(from);
+ pa_assert(to);
+
+ to->data = from->dptr;
+ to->size = from->dsize;
+
+ return to;
+}
+
+void pa_datum_free(pa_datum *d) {
+ pa_assert(d);
+
+ free(d->data); /* gdbm uses raw malloc/free hence we should do that here, too */
+ pa_zero(d);
+}
+
+const char* pa_database_get_filename_suffix(void) {
+ return ".gdbm";
+}
+
+pa_database* pa_database_open_internal(const char *path, bool for_write) {
+ GDBM_FILE f;
+ int gdbm_cache_size;
+
+ pa_assert(path);
+
+ errno = 0;
+
+ /* We need to set the block size explicitly here, since otherwise
+ * gdbm takes the native block size of the underlying file system
+ * which might be incredibly large. */
+ f = gdbm_open((char*) path, 1024, GDBM_NOLOCK | (for_write ? GDBM_WRCREAT : GDBM_READER), 0644, NULL);
+
+ if (f)
+ pa_log_debug("Opened GDBM database '%s'", path);
+
+ if (!f) {
+ if (errno == 0)
+ errno = EIO;
+ return NULL;
+ }
+
+ /* By default the cache of gdbm is rather large, let's reduce it a bit to save memory */
+ gdbm_cache_size = 10;
+ gdbm_setopt(f, GDBM_CACHESIZE, &gdbm_cache_size, sizeof(gdbm_cache_size));
+
+ return (pa_database*) f;
+}
+
+void pa_database_close(pa_database *db) {
+ pa_assert(db);
+
+ gdbm_close(MAKE_GDBM_FILE(db));
+}
+
+pa_datum* pa_database_get(pa_database *db, const pa_datum *key, pa_datum* data) {
+ datum gdbm_key, gdbm_data;
+
+ pa_assert(db);
+ pa_assert(key);
+ pa_assert(data);
+
+ gdbm_data = gdbm_fetch(MAKE_GDBM_FILE(db), *datum_to_gdbm(&gdbm_key, key));
+
+ return gdbm_data.dptr ?
+ datum_from_gdbm(data, &gdbm_data) :
+ NULL;
+}
+
+int pa_database_set(pa_database *db, const pa_datum *key, const pa_datum* data, bool overwrite) {
+ datum gdbm_key, gdbm_data;
+
+ pa_assert(db);
+ pa_assert(key);
+ pa_assert(data);
+
+ return gdbm_store(MAKE_GDBM_FILE(db),
+ *datum_to_gdbm(&gdbm_key, key),
+ *datum_to_gdbm(&gdbm_data, data),
+ overwrite ? GDBM_REPLACE : GDBM_INSERT) != 0 ? -1 : 0;
+}
+
+int pa_database_unset(pa_database *db, const pa_datum *key) {
+ datum gdbm_key;
+
+ pa_assert(db);
+ pa_assert(key);
+
+ return gdbm_delete(MAKE_GDBM_FILE(db), *datum_to_gdbm(&gdbm_key, key)) != 0 ? -1 : 0;
+}
+
+int pa_database_clear(pa_database *db) {
+ datum gdbm_key;
+
+ pa_assert(db);
+
+ gdbm_key = gdbm_firstkey(MAKE_GDBM_FILE(db));
+
+ while (gdbm_key.dptr) {
+ datum next;
+
+ next = gdbm_nextkey(MAKE_GDBM_FILE(db), gdbm_key);
+
+ gdbm_delete(MAKE_GDBM_FILE(db), gdbm_key);
+
+ free(gdbm_key.dptr);
+ gdbm_key = next;
+ }
+
+ return gdbm_reorganize(MAKE_GDBM_FILE(db)) == 0 ? 0 : -1;
+}
+
+signed pa_database_size(pa_database *db) {
+ datum gdbm_key;
+ unsigned n = 0;
+
+ pa_assert(db);
+
+ /* This sucks */
+
+ gdbm_key = gdbm_firstkey(MAKE_GDBM_FILE(db));
+
+ while (gdbm_key.dptr) {
+ datum next;
+
+ n++;
+
+ next = gdbm_nextkey(MAKE_GDBM_FILE(db), gdbm_key);
+ free(gdbm_key.dptr);
+ gdbm_key = next;
+ }
+
+ return (signed) n;
+}
+
+pa_datum* pa_database_first(pa_database *db, pa_datum *key, pa_datum *data) {
+ datum gdbm_key, gdbm_data;
+
+ pa_assert(db);
+ pa_assert(key);
+
+ gdbm_key = gdbm_firstkey(MAKE_GDBM_FILE(db));
+
+ if (!gdbm_key.dptr)
+ return NULL;
+
+ if (data) {
+ gdbm_data = gdbm_fetch(MAKE_GDBM_FILE(db), gdbm_key);
+
+ if (!gdbm_data.dptr) {
+ free(gdbm_key.dptr);
+ return NULL;
+ }
+
+ datum_from_gdbm(data, &gdbm_data);
+ }
+
+ datum_from_gdbm(key, &gdbm_key);
+
+ return key;
+}
+
+pa_datum* pa_database_next(pa_database *db, const pa_datum *key, pa_datum *next, pa_datum *data) {
+ datum gdbm_key, gdbm_data;
+
+ pa_assert(db);
+ pa_assert(key);
+ pa_assert(next);
+
+ if (!key)
+ return pa_database_first(db, next, data);
+
+ gdbm_key = gdbm_nextkey(MAKE_GDBM_FILE(db), *datum_to_gdbm(&gdbm_key, key));
+
+ if (!gdbm_key.dptr)
+ return NULL;
+
+ if (data) {
+ gdbm_data = gdbm_fetch(MAKE_GDBM_FILE(db), gdbm_key);
+
+ if (!gdbm_data.dptr) {
+ free(gdbm_key.dptr);
+ return NULL;
+ }
+
+ datum_from_gdbm(data, &gdbm_data);
+ }
+
+ datum_from_gdbm(next, &gdbm_key);
+
+ return next;
+}
+
+int pa_database_sync(pa_database *db) {
+ pa_assert(db);
+
+ gdbm_sync(MAKE_GDBM_FILE(db));
+ return 0;
+}
diff --git a/src/pulsecore/database-simple.c b/src/pulsecore/database-simple.c
new file mode 100644
index 0000000..96af8e0
--- /dev/null
+++ b/src/pulsecore/database-simple.c
@@ -0,0 +1,495 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2009 Nokia Corporation
+ Contact: Maemo Multimedia <multimedia@maemo.org>
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdio.h>
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/log.h>
+#include <pulsecore/core-error.h>
+#include <pulsecore/hashmap.h>
+
+#include "database.h"
+
+typedef struct simple_data {
+ char *filename;
+ char *tmp_filename;
+ pa_hashmap *map;
+ bool read_only;
+} simple_data;
+
+typedef struct entry {
+ pa_datum key;
+ pa_datum data;
+} entry;
+
+void pa_datum_free(pa_datum *d) {
+ pa_assert(d);
+
+ pa_xfree(d->data);
+ d->data = NULL;
+ d->size = 0;
+}
+
+static int compare_func(const void *a, const void *b) {
+ const pa_datum *aa, *bb;
+
+ aa = (const pa_datum*)a;
+ bb = (const pa_datum*)b;
+
+ if (aa->size != bb->size)
+ return aa->size > bb->size ? 1 : -1;
+
+ return memcmp(aa->data, bb->data, aa->size);
+}
+
+/* pa_idxset_string_hash_func modified for our use */
+static unsigned hash_func(const void *p) {
+ const pa_datum *d;
+ unsigned hash = 0;
+ const char *c;
+ unsigned i;
+
+ d = (const pa_datum*)p;
+ c = d->data;
+
+ for (i = 0; i < d->size; i++) {
+ hash = 31 * hash + (unsigned) *c;
+ c++;
+ }
+
+ return hash;
+}
+
+static entry* new_entry(const pa_datum *key, const pa_datum *data) {
+ entry *e;
+
+ pa_assert(key);
+ pa_assert(data);
+
+ e = pa_xnew0(entry, 1);
+ e->key.data = key->size > 0 ? pa_xmemdup(key->data, key->size) : NULL;
+ e->key.size = key->size;
+ e->data.data = data->size > 0 ? pa_xmemdup(data->data, data->size) : NULL;
+ e->data.size = data->size;
+ return e;
+}
+
+static void free_entry(entry *e) {
+ if (e) {
+ if (e->key.data)
+ pa_xfree(e->key.data);
+ if (e->data.data)
+ pa_xfree(e->data.data);
+ pa_xfree(e);
+ }
+}
+
+static int read_uint(FILE *f, uint32_t *res) {
+ size_t items = 0;
+ uint8_t values[4];
+ uint32_t tmp;
+ int i;
+
+ items = fread(&values, sizeof(values), sizeof(uint8_t), f);
+
+ if (feof(f)) /* EOF */
+ return 0;
+
+ if (ferror(f))
+ return -1;
+
+ for (i = 0; i < 4; ++i) {
+ tmp = values[i];
+ *res += (tmp << (i*8));
+ }
+
+ return items;
+}
+
+static int read_data(FILE *f, void **data, ssize_t *length) {
+ size_t items = 0;
+ uint32_t data_len = 0;
+
+ pa_assert(f);
+
+ *data = NULL;
+ *length = 0;
+
+ if ((items = read_uint(f, &data_len)) <= 0)
+ return -1;
+
+ if (data_len > 0) {
+ *data = pa_xmalloc0(data_len);
+ items = fread(*data, data_len, 1, f);
+
+ if (feof(f)) /* EOF */
+ goto reset;
+
+ if (ferror(f))
+ goto reset;
+
+ *length = data_len;
+
+ } else { /* no data? */
+ return -1;
+ }
+
+ return 0;
+
+reset:
+ pa_xfree(*data);
+ *data = NULL;
+ *length = 0;
+ return -1;
+}
+
+static int fill_data(simple_data *db, FILE *f) {
+ pa_datum key;
+ pa_datum data;
+ void *d = NULL;
+ ssize_t l = 0;
+ bool append = false;
+ enum { FIELD_KEY = 0, FIELD_DATA } field = FIELD_KEY;
+
+ pa_assert(db);
+ pa_assert(db->map);
+
+ errno = 0;
+
+ key.size = 0;
+ key.data = NULL;
+
+ while (!read_data(f, &d, &l)) {
+
+ switch (field) {
+ case FIELD_KEY:
+ key.data = d;
+ key.size = l;
+ field = FIELD_DATA;
+ break;
+ case FIELD_DATA:
+ data.data = d;
+ data.size = l;
+ append = true;
+ break;
+ }
+
+ if (append) {
+ entry *e = pa_xnew0(entry, 1);
+ e->key.data = key.data;
+ e->key.size = key.size;
+ e->data.data = data.data;
+ e->data.size = data.size;
+ pa_hashmap_put(db->map, &e->key, e);
+ append = false;
+ field = FIELD_KEY;
+ }
+ }
+
+ if (ferror(f)) {
+ pa_log_warn("read error. %s", pa_cstrerror(errno));
+ pa_database_clear((pa_database*)db);
+ }
+
+ if (field == FIELD_DATA && d)
+ pa_xfree(d);
+
+ return pa_hashmap_size(db->map);
+}
+
+const char* pa_database_get_filename_suffix(void) {
+ return ".simple";
+}
+
+pa_database* pa_database_open_internal(const char *path, bool for_write) {
+ FILE *f;
+ simple_data *db;
+
+ pa_assert(path);
+
+ errno = 0;
+
+ f = pa_fopen_cloexec(path, "r");
+
+ if (f || errno == ENOENT) { /* file not found is ok */
+ db = pa_xnew0(simple_data, 1);
+ db->map = pa_hashmap_new_full(hash_func, compare_func, NULL, (pa_free_cb_t) free_entry);
+ db->filename = pa_xstrdup(path);
+ db->tmp_filename = pa_sprintf_malloc(".%s.tmp", db->filename);
+ db->read_only = !for_write;
+
+ if (f) {
+ fill_data(db, f);
+ fclose(f);
+ }
+ } else {
+ if (errno == 0)
+ errno = EIO;
+ db = NULL;
+ }
+
+ return (pa_database*) db;
+}
+
+void pa_database_close(pa_database *database) {
+ simple_data *db = (simple_data*)database;
+ pa_assert(db);
+
+ pa_database_sync(database);
+ pa_xfree(db->filename);
+ pa_xfree(db->tmp_filename);
+ pa_hashmap_free(db->map);
+ pa_xfree(db);
+}
+
+pa_datum* pa_database_get(pa_database *database, const pa_datum *key, pa_datum* data) {
+ simple_data *db = (simple_data*)database;
+ entry *e;
+
+ pa_assert(db);
+ pa_assert(key);
+ pa_assert(data);
+
+ e = pa_hashmap_get(db->map, key);
+
+ if (!e)
+ return NULL;
+
+ data->data = e->data.size > 0 ? pa_xmemdup(e->data.data, e->data.size) : NULL;
+ data->size = e->data.size;
+
+ return data;
+}
+
+int pa_database_set(pa_database *database, const pa_datum *key, const pa_datum* data, bool overwrite) {
+ simple_data *db = (simple_data*)database;
+ entry *e;
+ int ret = 0;
+
+ pa_assert(db);
+ pa_assert(key);
+ pa_assert(data);
+
+ if (db->read_only)
+ return -1;
+
+ e = new_entry(key, data);
+
+ if (pa_hashmap_put(db->map, &e->key, e) < 0) {
+ /* entry with same key exists in hashmap */
+ entry *r;
+ if (overwrite) {
+ r = pa_hashmap_remove(db->map, key);
+ pa_hashmap_put(db->map, &e->key, e);
+ } else {
+ /* won't overwrite, so clean new entry */
+ r = e;
+ ret = -1;
+ }
+
+ free_entry(r);
+ }
+
+ return ret;
+}
+
+int pa_database_unset(pa_database *database, const pa_datum *key) {
+ simple_data *db = (simple_data*)database;
+
+ pa_assert(db);
+ pa_assert(key);
+
+ return pa_hashmap_remove_and_free(db->map, key);
+}
+
+int pa_database_clear(pa_database *database) {
+ simple_data *db = (simple_data*)database;
+
+ pa_assert(db);
+
+ pa_hashmap_remove_all(db->map);
+
+ return 0;
+}
+
+signed pa_database_size(pa_database *database) {
+ simple_data *db = (simple_data*)database;
+ pa_assert(db);
+
+ return (signed) pa_hashmap_size(db->map);
+}
+
+pa_datum* pa_database_first(pa_database *database, pa_datum *key, pa_datum *data) {
+ simple_data *db = (simple_data*)database;
+ entry *e;
+
+ pa_assert(db);
+ pa_assert(key);
+
+ e = pa_hashmap_first(db->map);
+
+ if (!e)
+ return NULL;
+
+ key->data = e->key.size > 0 ? pa_xmemdup(e->key.data, e->key.size) : NULL;
+ key->size = e->key.size;
+
+ if (data) {
+ data->data = e->data.size > 0 ? pa_xmemdup(e->data.data, e->data.size) : NULL;
+ data->size = e->data.size;
+ }
+
+ return key;
+}
+
+pa_datum* pa_database_next(pa_database *database, const pa_datum *key, pa_datum *next, pa_datum *data) {
+ simple_data *db = (simple_data*)database;
+ entry *e;
+ entry *search;
+ void *state;
+ bool pick_now;
+
+ pa_assert(db);
+ pa_assert(next);
+
+ if (!key)
+ return pa_database_first(database, next, data);
+
+ search = pa_hashmap_get(db->map, key);
+
+ state = NULL;
+ pick_now = false;
+
+ while ((e = pa_hashmap_iterate(db->map, &state, NULL))) {
+ if (pick_now)
+ break;
+
+ if (search == e)
+ pick_now = true;
+ }
+
+ if (!pick_now || !e)
+ return NULL;
+
+ next->data = e->key.size > 0 ? pa_xmemdup(e->key.data, e->key.size) : NULL;
+ next->size = e->key.size;
+
+ if (data) {
+ data->data = e->data.size > 0 ? pa_xmemdup(e->data.data, e->data.size) : NULL;
+ data->size = e->data.size;
+ }
+
+ return next;
+}
+
+static int write_uint(FILE *f, const uint32_t num) {
+ size_t items;
+ uint8_t values[4];
+ int i;
+ errno = 0;
+
+ for (i = 0; i < 4; i++)
+ values[i] = (num >> (i*8)) & 0xFF;
+
+ items = fwrite(&values, sizeof(values), sizeof(uint8_t), f);
+
+ if (ferror(f))
+ return -1;
+
+ return items;
+}
+
+static int write_data(FILE *f, void *data, const size_t length) {
+ size_t items;
+ uint32_t len;
+
+ len = length;
+ if ((items = write_uint(f, len)) <= 0)
+ return -1;
+
+ items = fwrite(data, length, 1, f);
+
+ if (ferror(f) || items != 1)
+ return -1;
+
+ return 0;
+}
+
+static int write_entry(FILE *f, const entry *e) {
+ pa_assert(f);
+ pa_assert(e);
+
+ if (write_data(f, e->key.data, e->key.size) < 0)
+ return -1;
+ if (write_data(f, e->data.data, e->data.size) < 0)
+ return -1;
+
+ return 0;
+}
+
+int pa_database_sync(pa_database *database) {
+ simple_data *db = (simple_data*)database;
+ FILE *f;
+ void *state;
+ entry *e;
+
+ pa_assert(db);
+
+ if (db->read_only)
+ return 0;
+
+ errno = 0;
+
+ f = pa_fopen_cloexec(db->tmp_filename, "w");
+
+ if (!f)
+ goto fail;
+
+ state = NULL;
+ while((e = pa_hashmap_iterate(db->map, &state, NULL))) {
+ if (write_entry(f, e) < 0) {
+ pa_log_warn("error while writing to file. %s", pa_cstrerror(errno));
+ goto fail;
+ }
+ }
+
+ fclose(f);
+ f = NULL;
+
+ if (rename(db->tmp_filename, db->filename) < 0) {
+ pa_log_warn("error while renaming file. %s", pa_cstrerror(errno));
+ goto fail;
+ }
+
+ return 0;
+
+fail:
+ if (f)
+ fclose(f);
+ return -1;
+}
diff --git a/src/pulsecore/database-tdb.c b/src/pulsecore/database-tdb.c
new file mode 100644
index 0000000..6605ed8
--- /dev/null
+++ b/src/pulsecore/database-tdb.c
@@ -0,0 +1,250 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2009 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+
+/* Some versions of tdb lack inclusion of signal.h in the header files but use sigatomic_t */
+#include <signal.h>
+#include <tdb.h>
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/log.h>
+
+#include "database.h"
+
+#define MAKE_TDB_CONTEXT(x) ((struct tdb_context*) (x))
+
+static inline TDB_DATA* datum_to_tdb(TDB_DATA *to, const pa_datum *from) {
+ pa_assert(from);
+ pa_assert(to);
+
+ to->dptr = from->data;
+ to->dsize = from->size;
+
+ return to;
+}
+
+static inline pa_datum* datum_from_tdb(pa_datum *to, const TDB_DATA *from) {
+ pa_assert(from);
+ pa_assert(to);
+
+ to->data = from->dptr;
+ to->size = from->dsize;
+
+ return to;
+}
+
+void pa_datum_free(pa_datum *d) {
+ pa_assert(d);
+
+ free(d->data); /* tdb uses raw malloc/free hence we should do that here, too */
+ pa_zero(d);
+}
+
+static struct tdb_context *tdb_open_cloexec(
+ const char *name,
+ int hash_size,
+ int tdb_flags,
+ int open_flags,
+ mode_t mode) {
+
+ /* Mimics pa_open_cloexec() */
+
+ struct tdb_context *c;
+
+#ifdef O_NOCTTY
+ open_flags |= O_NOCTTY;
+#endif
+
+#ifdef O_CLOEXEC
+ errno = 0;
+ if ((c = tdb_open(name, hash_size, tdb_flags, open_flags | O_CLOEXEC, mode)))
+ goto finish;
+
+ if (errno != EINVAL)
+ return NULL;
+#endif
+
+ errno = 0;
+ if (!(c = tdb_open(name, hash_size, tdb_flags, open_flags, mode)))
+ return NULL;
+
+finish:
+ pa_make_fd_cloexec(tdb_fd(c));
+ return c;
+}
+
+const char* pa_database_get_filename_suffix(void) {
+ return ".tdb";
+}
+
+pa_database* pa_database_open_internal(const char *path, bool for_write) {
+ struct tdb_context *c;
+
+ pa_assert(path);
+
+ if ((c = tdb_open_cloexec(path, 0, TDB_NOSYNC|TDB_NOLOCK, (for_write ? O_RDWR|O_CREAT : O_RDONLY), 0644)))
+ pa_log_debug("Opened TDB database '%s'", path);
+
+ if (!c) {
+ if (errno == 0)
+ errno = EIO;
+ return NULL;
+ }
+
+ return (pa_database*) c;
+}
+
+void pa_database_close(pa_database *db) {
+ pa_assert(db);
+
+ tdb_close(MAKE_TDB_CONTEXT(db));
+}
+
+pa_datum* pa_database_get(pa_database *db, const pa_datum *key, pa_datum* data) {
+ TDB_DATA tdb_key, tdb_data;
+
+ pa_assert(db);
+ pa_assert(key);
+ pa_assert(data);
+
+ tdb_data = tdb_fetch(MAKE_TDB_CONTEXT(db), *datum_to_tdb(&tdb_key, key));
+
+ return tdb_data.dptr ?
+ datum_from_tdb(data, &tdb_data) :
+ NULL;
+}
+
+int pa_database_set(pa_database *db, const pa_datum *key, const pa_datum* data, bool overwrite) {
+ TDB_DATA tdb_key, tdb_data;
+
+ pa_assert(db);
+ pa_assert(key);
+ pa_assert(data);
+
+ return tdb_store(MAKE_TDB_CONTEXT(db),
+ *datum_to_tdb(&tdb_key, key),
+ *datum_to_tdb(&tdb_data, data),
+ overwrite ? TDB_REPLACE : TDB_INSERT) != 0 ? -1 : 0;
+}
+
+int pa_database_unset(pa_database *db, const pa_datum *key) {
+ TDB_DATA tdb_key;
+
+ pa_assert(db);
+ pa_assert(key);
+
+ return tdb_delete(MAKE_TDB_CONTEXT(db), *datum_to_tdb(&tdb_key, key)) != 0 ? -1 : 0;
+}
+
+int pa_database_clear(pa_database *db) {
+ pa_assert(db);
+
+ return tdb_wipe_all(MAKE_TDB_CONTEXT(db)) != 0 ? -1 : 0;
+}
+
+signed pa_database_size(pa_database *db) {
+ TDB_DATA tdb_key;
+ unsigned n = 0;
+
+ pa_assert(db);
+
+ /* This sucks */
+
+ tdb_key = tdb_firstkey(MAKE_TDB_CONTEXT(db));
+
+ while (tdb_key.dptr) {
+ TDB_DATA next;
+
+ n++;
+
+ next = tdb_nextkey(MAKE_TDB_CONTEXT(db), tdb_key);
+ free(tdb_key.dptr);
+ tdb_key = next;
+ }
+
+ return (signed) n;
+}
+
+pa_datum* pa_database_first(pa_database *db, pa_datum *key, pa_datum *data) {
+ TDB_DATA tdb_key, tdb_data;
+
+ pa_assert(db);
+ pa_assert(key);
+
+ tdb_key = tdb_firstkey(MAKE_TDB_CONTEXT(db));
+
+ if (!tdb_key.dptr)
+ return NULL;
+
+ if (data) {
+ tdb_data = tdb_fetch(MAKE_TDB_CONTEXT(db), tdb_key);
+
+ if (!tdb_data.dptr) {
+ free(tdb_key.dptr);
+ return NULL;
+ }
+
+ datum_from_tdb(data, &tdb_data);
+ }
+
+ datum_from_tdb(key, &tdb_key);
+
+ return key;
+}
+
+pa_datum* pa_database_next(pa_database *db, const pa_datum *key, pa_datum *next, pa_datum *data) {
+ TDB_DATA tdb_key, tdb_data;
+
+ pa_assert(db);
+ pa_assert(key);
+
+ tdb_key = tdb_nextkey(MAKE_TDB_CONTEXT(db), *datum_to_tdb(&tdb_key, key));
+
+ if (!tdb_key.dptr)
+ return NULL;
+
+ if (data) {
+ tdb_data = tdb_fetch(MAKE_TDB_CONTEXT(db), tdb_key);
+
+ if (!tdb_data.dptr) {
+ free(tdb_key.dptr);
+ return NULL;
+ }
+
+ datum_from_tdb(data, &tdb_data);
+ }
+
+ datum_from_tdb(next, &tdb_key);
+
+ return next;
+}
+
+int pa_database_sync(pa_database *db) {
+ pa_assert(db);
+
+ return 0;
+}
diff --git a/src/pulsecore/database.c b/src/pulsecore/database.c
new file mode 100644
index 0000000..8b3b89c
--- /dev/null
+++ b/src/pulsecore/database.c
@@ -0,0 +1,107 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2020 Igor V. Kovalenko <igor.v.kovalenko@gmail.com>
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <dirent.h>
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/log.h>
+
+#include "database.h"
+#include "core-error.h"
+
+pa_database* pa_database_open(const char *path, const char *fn, bool prependmid, bool for_write) {
+
+ const char *filename_suffix = pa_database_get_filename_suffix();
+
+ char *machine_id = NULL, *filename_prefix, *full_path;
+
+ DIR *database_dir = NULL;
+ struct dirent *de;
+
+ pa_database *f;
+
+ pa_assert(filename_suffix && filename_suffix[0]);
+
+ if (prependmid && !(machine_id = pa_machine_id())) {
+ return NULL;
+ }
+
+ /* Database file name starts with ${machine_id}-${fn} */
+ if (machine_id)
+ filename_prefix = pa_sprintf_malloc("%s-%s", machine_id, fn);
+ else
+ filename_prefix = pa_xstrdup(fn);
+
+ /* Search for existing database directory entry name matching architecture suffix and filename suffix. */
+ database_dir = opendir(path);
+
+ if (database_dir) {
+ for (;;) {
+ errno = 0;
+ de = readdir(database_dir);
+ if (!de) {
+ if (errno) {
+ pa_log_warn("Unable to search for existing database file, readdir() failed: %s", pa_cstrerror(errno));
+ /* can continue as if there is no matching database file candidate */
+ }
+
+ break;
+ }
+
+ if (pa_startswith(de->d_name, filename_prefix)
+ && de->d_name[strlen(filename_prefix)] == '.'
+ && pa_endswith(de->d_name + strlen(filename_prefix) + 1, filename_suffix)) {
+
+ /* candidate filename found, replace filename_prefix with this one */
+
+ pa_log_debug("Found existing database file '%s/%s', using it", path, de->d_name);
+ pa_xfree(filename_prefix);
+ filename_prefix = pa_xstrndup(de->d_name, strlen(de->d_name) - strlen(filename_suffix));
+ break;
+ }
+ }
+
+ closedir(database_dir);
+ } else {
+ pa_log_warn("Unable to search for existing database file, failed to open directory %s: %s", path, pa_cstrerror(errno));
+ }
+
+ full_path = pa_sprintf_malloc("%s" PA_PATH_SEP "%s%s", path, filename_prefix, filename_suffix);
+
+ f = pa_database_open_internal(full_path, for_write);
+
+ if (f)
+ pa_log_info("Successfully opened '%s' database file '%s'.", fn, full_path);
+ else
+ pa_log("Failed to open '%s' database file '%s': %s", fn, full_path, pa_cstrerror(errno));
+
+ pa_xfree(full_path);
+ pa_xfree(filename_prefix);
+
+ /* deallocate machine_id if it was used to construct file name */
+ pa_xfree(machine_id);
+
+ return f;
+}
diff --git a/src/pulsecore/database.h b/src/pulsecore/database.h
new file mode 100644
index 0000000..7fa489c
--- /dev/null
+++ b/src/pulsecore/database.h
@@ -0,0 +1,80 @@
+#ifndef foopulsecoredatabasehfoo
+#define foopulsecoredatabasehfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2009 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/types.h>
+
+#include <pulsecore/macro.h>
+
+/* A little abstraction over simple databases, such as gdbm, tdb, and
+ * so on. We only make minimal assumptions about the supported
+ * backend: it does not need to support locking, it does not have to
+ * be arch independent. */
+
+typedef struct pa_database pa_database;
+
+typedef struct pa_datum {
+ void *data;
+ size_t size;
+} pa_datum;
+
+void pa_datum_free(pa_datum *d);
+
+/* Database implementation; returns non-empty database filename extension string */
+const char* pa_database_get_filename_suffix(void);
+
+/* Opens a database file. The file is loaded from the directory indicated by
+ * path. The file name is constructed by using fn as the base and then adding
+ * several parts:
+ * 1) If prependmid is true, the machine id is prepended to the file name.
+ * 2) The database implementation specific suffix is added.
+ * 3) Older versions of PulseAudio in some cases added the CPU architecture
+ * to the file name, which was later deemed unnecessary, but for
+ * compatibility reasons we still need to look for those files, so we scan
+ * the directory for files that match the prefix (possible machine id plus
+ * fn) and the suffix, and if any matches are found, we use the first one.
+ *
+ * When no existing file is found, we create a new file for the database
+ * (without the CPU architecture part in the name).
+ *
+ * For a read-only database, set for_write to false. */
+
+pa_database* pa_database_open(const char *path, const char *fn, bool prependmid, bool for_write);
+
+/* Database implementation; opens specified database file using provided path. */
+pa_database* pa_database_open_internal(const char *path, bool for_write);
+void pa_database_close(pa_database *db);
+
+pa_datum* pa_database_get(pa_database *db, const pa_datum *key, pa_datum* data);
+
+int pa_database_set(pa_database *db, const pa_datum *key, const pa_datum* data, bool overwrite);
+int pa_database_unset(pa_database *db, const pa_datum *key);
+
+int pa_database_clear(pa_database *db);
+
+signed pa_database_size(pa_database *db);
+
+pa_datum* pa_database_first(pa_database *db, pa_datum *key, pa_datum *data /* may be NULL */);
+pa_datum* pa_database_next(pa_database *db, const pa_datum *key, pa_datum *next, pa_datum *data /* may be NULL */);
+
+int pa_database_sync(pa_database *db);
+
+#endif
diff --git a/src/pulsecore/dbus-shared.c b/src/pulsecore/dbus-shared.c
new file mode 100644
index 0000000..3422c29
--- /dev/null
+++ b/src/pulsecore/dbus-shared.c
@@ -0,0 +1,102 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006, 2009 Lennart Poettering
+ Copyright 2006 Shams E. King
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/shared.h>
+
+#include "dbus-shared.h"
+
+struct pa_dbus_connection {
+ PA_REFCNT_DECLARE;
+
+ pa_dbus_wrap_connection *connection;
+ pa_core *core;
+ const char *property_name;
+};
+
+static pa_dbus_connection* dbus_connection_new(pa_core *c, pa_dbus_wrap_connection *conn, const char *name) {
+ pa_dbus_connection *pconn;
+
+ pconn = pa_xnew(pa_dbus_connection, 1);
+ PA_REFCNT_INIT(pconn);
+ pconn->core = c;
+ pconn->property_name = name;
+ pconn->connection = conn;
+
+ pa_assert_se(pa_shared_set(c, name, pconn) >= 0);
+
+ return pconn;
+}
+
+pa_dbus_connection* pa_dbus_bus_get(pa_core *c, DBusBusType type, DBusError *error) {
+
+ static const char *const prop_name[] = {
+ [DBUS_BUS_SESSION] = "dbus-connection-session",
+ [DBUS_BUS_SYSTEM] = "dbus-connection-system",
+ [DBUS_BUS_STARTER] = "dbus-connection-starter"
+ };
+ pa_dbus_wrap_connection *conn;
+ pa_dbus_connection *pconn;
+
+ pa_assert(type == DBUS_BUS_SYSTEM || type == DBUS_BUS_SESSION || type == DBUS_BUS_STARTER);
+
+ if ((pconn = pa_shared_get(c, prop_name[type])))
+ return pa_dbus_connection_ref(pconn);
+
+ if (!(conn = pa_dbus_wrap_connection_new(c->mainloop, true, type, error)))
+ return NULL;
+
+ return dbus_connection_new(c, conn, prop_name[type]);
+}
+
+DBusConnection* pa_dbus_connection_get(pa_dbus_connection *c) {
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) > 0);
+ pa_assert(c->connection);
+
+ return pa_dbus_wrap_connection_get(c->connection);
+}
+
+void pa_dbus_connection_unref(pa_dbus_connection *c) {
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) > 0);
+
+ if (PA_REFCNT_DEC(c) > 0)
+ return;
+
+ pa_dbus_wrap_connection_free(c->connection);
+
+ pa_assert_se(pa_shared_remove(c->core, c->property_name) >= 0);
+ pa_xfree(c);
+}
+
+pa_dbus_connection* pa_dbus_connection_ref(pa_dbus_connection *c) {
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) > 0);
+
+ PA_REFCNT_INC(c);
+
+ return c;
+}
diff --git a/src/pulsecore/dbus-shared.h b/src/pulsecore/dbus-shared.h
new file mode 100644
index 0000000..3d552e9
--- /dev/null
+++ b/src/pulsecore/dbus-shared.h
@@ -0,0 +1,40 @@
+#ifndef foodbussharedhfoo
+#define foodbussharedhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006, 2009 Lennart Poettering
+ Copyright 2006 Shams E. King
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <dbus/dbus.h>
+
+#include <pulsecore/core.h>
+#include <pulsecore/dbus-util.h>
+
+typedef struct pa_dbus_connection pa_dbus_connection;
+
+/* return a pa_dbus_connection of the specified type for the given core,
+ * like dbus_bus_get(), but integrates the connection with the pa_core */
+pa_dbus_connection* pa_dbus_bus_get(pa_core *c, DBusBusType type, DBusError *error);
+
+DBusConnection* pa_dbus_connection_get(pa_dbus_connection *conn);
+
+pa_dbus_connection* pa_dbus_connection_ref(pa_dbus_connection *conn);
+void pa_dbus_connection_unref(pa_dbus_connection *conn);
+
+#endif
diff --git a/src/pulsecore/dbus-util.c b/src/pulsecore/dbus-util.c
new file mode 100644
index 0000000..7d55020
--- /dev/null
+++ b/src/pulsecore/dbus-util.c
@@ -0,0 +1,778 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2006 Lennart Poettering
+ Copyright 2006 Shams E. King
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdarg.h>
+
+#include <pulse/rtclock.h>
+#include <pulse/timeval.h>
+#include <pulse/utf8.h>
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/core-rtclock.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/log.h>
+
+#include "dbus-util.h"
+
+struct pa_dbus_wrap_connection {
+ pa_mainloop_api *mainloop;
+ DBusConnection *connection;
+ pa_defer_event* dispatch_event;
+ bool use_rtclock:1;
+};
+
+struct timeout_data {
+ pa_dbus_wrap_connection *connection;
+ DBusTimeout *timeout;
+};
+
+static void dispatch_cb(pa_mainloop_api *ea, pa_defer_event *ev, void *userdata) {
+ DBusConnection *conn = userdata;
+
+ if (dbus_connection_dispatch(conn) == DBUS_DISPATCH_COMPLETE)
+ /* no more data to process, disable the deferred */
+ ea->defer_enable(ev, 0);
+}
+
+/* DBusDispatchStatusFunction callback for the pa mainloop */
+static void dispatch_status(DBusConnection *conn, DBusDispatchStatus status, void *userdata) {
+ pa_dbus_wrap_connection *c = userdata;
+
+ pa_assert(c);
+
+ switch(status) {
+
+ case DBUS_DISPATCH_COMPLETE:
+ c->mainloop->defer_enable(c->dispatch_event, 0);
+ break;
+
+ case DBUS_DISPATCH_DATA_REMAINS:
+ case DBUS_DISPATCH_NEED_MEMORY:
+ default:
+ c->mainloop->defer_enable(c->dispatch_event, 1);
+ break;
+ }
+}
+
+static pa_io_event_flags_t get_watch_flags(DBusWatch *watch) {
+ unsigned int flags;
+ pa_io_event_flags_t events = 0;
+
+ pa_assert(watch);
+
+ flags = dbus_watch_get_flags(watch);
+
+ /* no watch flags for disabled watches */
+ if (!dbus_watch_get_enabled(watch))
+ return PA_IO_EVENT_NULL;
+
+ if (flags & DBUS_WATCH_READABLE)
+ events |= PA_IO_EVENT_INPUT;
+ if (flags & DBUS_WATCH_WRITABLE)
+ events |= PA_IO_EVENT_OUTPUT;
+
+ return events | PA_IO_EVENT_HANGUP | PA_IO_EVENT_ERROR;
+}
+
+/* pa_io_event_cb_t IO event handler */
+static void handle_io_event(pa_mainloop_api *ea, pa_io_event *e, int fd, pa_io_event_flags_t events, void *userdata) {
+ unsigned int flags = 0;
+ DBusWatch *watch = userdata;
+
+ pa_assert(fd == dbus_watch_get_unix_fd(watch));
+
+ if (!dbus_watch_get_enabled(watch)) {
+ pa_log_warn("Asked to handle disabled watch: %p %i", (void*) watch, fd);
+ return;
+ }
+
+ if (events & PA_IO_EVENT_INPUT)
+ flags |= DBUS_WATCH_READABLE;
+ if (events & PA_IO_EVENT_OUTPUT)
+ flags |= DBUS_WATCH_WRITABLE;
+ if (events & PA_IO_EVENT_HANGUP)
+ flags |= DBUS_WATCH_HANGUP;
+ if (events & PA_IO_EVENT_ERROR)
+ flags |= DBUS_WATCH_ERROR;
+
+ dbus_watch_handle(watch, flags);
+}
+
+/* pa_time_event_cb_t timer event handler */
+static void handle_time_event(pa_mainloop_api *ea, pa_time_event* e, const struct timeval *t, void *userdata) {
+ struct timeval tv;
+ struct timeout_data *d = userdata;
+
+ pa_assert(d);
+ pa_assert(d->connection);
+
+ if (dbus_timeout_get_enabled(d->timeout)) {
+ /* Restart it for the next scheduled time. We do this before
+ * calling dbus_timeout_handle() to make sure that the time
+ * event is still around. */
+ ea->time_restart(e, pa_timeval_rtstore(&tv,
+ pa_timeval_load(t) + dbus_timeout_get_interval(d->timeout) * PA_USEC_PER_MSEC,
+ d->connection->use_rtclock));
+
+ dbus_timeout_handle(d->timeout);
+ }
+}
+
+/* DBusAddWatchFunction callback for pa mainloop */
+static dbus_bool_t add_watch(DBusWatch *watch, void *data) {
+ pa_dbus_wrap_connection *c = data;
+ pa_io_event *ev;
+
+ pa_assert(watch);
+ pa_assert(c);
+
+ ev = c->mainloop->io_new(
+ c->mainloop,
+ dbus_watch_get_unix_fd(watch),
+ get_watch_flags(watch), handle_io_event, watch);
+
+ dbus_watch_set_data(watch, ev, NULL);
+
+ return TRUE;
+}
+
+/* DBusRemoveWatchFunction callback for pa mainloop */
+static void remove_watch(DBusWatch *watch, void *data) {
+ pa_dbus_wrap_connection *c = data;
+ pa_io_event *ev;
+
+ pa_assert(watch);
+ pa_assert(c);
+
+ if ((ev = dbus_watch_get_data(watch)))
+ c->mainloop->io_free(ev);
+}
+
+/* DBusWatchToggledFunction callback for pa mainloop */
+static void toggle_watch(DBusWatch *watch, void *data) {
+ pa_dbus_wrap_connection *c = data;
+ pa_io_event *ev;
+
+ pa_assert(watch);
+ pa_assert(c);
+
+ pa_assert_se(ev = dbus_watch_get_data(watch));
+
+ /* get_watch_flags() checks if the watch is enabled */
+ c->mainloop->io_enable(ev, get_watch_flags(watch));
+}
+
+static void time_event_destroy_cb(pa_mainloop_api *a, pa_time_event *e, void *userdata) {
+ pa_xfree(userdata);
+}
+
+/* DBusAddTimeoutFunction callback for pa mainloop */
+static dbus_bool_t add_timeout(DBusTimeout *timeout, void *data) {
+ pa_dbus_wrap_connection *c = data;
+ pa_time_event *ev;
+ struct timeval tv;
+ struct timeout_data *d;
+
+ pa_assert(timeout);
+ pa_assert(c);
+
+ if (!dbus_timeout_get_enabled(timeout))
+ return FALSE;
+
+ d = pa_xnew(struct timeout_data, 1);
+ d->connection = c;
+ d->timeout = timeout;
+ ev = c->mainloop->time_new(c->mainloop, pa_timeval_rtstore(&tv, pa_rtclock_now() + dbus_timeout_get_interval(timeout) * PA_USEC_PER_MSEC, c->use_rtclock), handle_time_event, d);
+ c->mainloop->time_set_destroy(ev, time_event_destroy_cb);
+
+ dbus_timeout_set_data(timeout, ev, NULL);
+
+ return TRUE;
+}
+
+/* DBusRemoveTimeoutFunction callback for pa mainloop */
+static void remove_timeout(DBusTimeout *timeout, void *data) {
+ pa_dbus_wrap_connection *c = data;
+ pa_time_event *ev;
+
+ pa_assert(timeout);
+ pa_assert(c);
+
+ if ((ev = dbus_timeout_get_data(timeout)))
+ c->mainloop->time_free(ev);
+}
+
+/* DBusTimeoutToggledFunction callback for pa mainloop */
+static void toggle_timeout(DBusTimeout *timeout, void *data) {
+ struct timeout_data *d = data;
+ pa_time_event *ev;
+ struct timeval tv;
+
+ pa_assert(d);
+ pa_assert(d->connection);
+ pa_assert(timeout);
+
+ pa_assert_se(ev = dbus_timeout_get_data(timeout));
+
+ if (dbus_timeout_get_enabled(timeout))
+ d->connection->mainloop->time_restart(ev, pa_timeval_rtstore(&tv, pa_rtclock_now() + dbus_timeout_get_interval(timeout) * PA_USEC_PER_MSEC, d->connection->use_rtclock));
+ else
+ d->connection->mainloop->time_restart(ev, pa_timeval_rtstore(&tv, PA_USEC_INVALID, d->connection->use_rtclock));
+}
+
+static void wakeup_main(void *userdata) {
+ pa_dbus_wrap_connection *c = userdata;
+
+ pa_assert(c);
+
+ /* this will wakeup the mainloop and dispatch events, although
+ * it may not be the cleanest way of accomplishing it */
+ c->mainloop->defer_enable(c->dispatch_event, 1);
+}
+
+pa_dbus_wrap_connection* pa_dbus_wrap_connection_new(pa_mainloop_api *m, bool use_rtclock, DBusBusType type, DBusError *error) {
+ DBusConnection *conn;
+ pa_dbus_wrap_connection *pconn;
+ char *id;
+
+ pa_assert(type == DBUS_BUS_SYSTEM || type == DBUS_BUS_SESSION || type == DBUS_BUS_STARTER);
+
+ if (!(conn = dbus_bus_get_private(type, error)))
+ return NULL;
+
+ pconn = pa_xnew(pa_dbus_wrap_connection, 1);
+ pconn->mainloop = m;
+ pconn->connection = conn;
+ pconn->use_rtclock = use_rtclock;
+
+ dbus_connection_set_exit_on_disconnect(conn, FALSE);
+ dbus_connection_set_dispatch_status_function(conn, dispatch_status, pconn, NULL);
+ dbus_connection_set_watch_functions(conn, add_watch, remove_watch, toggle_watch, pconn, NULL);
+ dbus_connection_set_timeout_functions(conn, add_timeout, remove_timeout, toggle_timeout, pconn, NULL);
+ dbus_connection_set_wakeup_main_function(conn, wakeup_main, pconn, NULL);
+
+ pconn->dispatch_event = pconn->mainloop->defer_new(pconn->mainloop, dispatch_cb, conn);
+
+ pa_log_debug("Successfully connected to D-Bus %s bus %s as %s",
+ type == DBUS_BUS_SYSTEM ? "system" : (type == DBUS_BUS_SESSION ? "session" : "starter"),
+ pa_strnull((id = dbus_connection_get_server_id(conn))),
+ pa_strnull(dbus_bus_get_unique_name(conn)));
+
+ dbus_free(id);
+
+ return pconn;
+}
+
+pa_dbus_wrap_connection* pa_dbus_wrap_connection_new_from_existing(
+ pa_mainloop_api *m,
+ bool use_rtclock,
+ DBusConnection *conn) {
+ pa_dbus_wrap_connection *pconn;
+
+ pa_assert(m);
+ pa_assert(conn);
+
+ pconn = pa_xnew(pa_dbus_wrap_connection, 1);
+ pconn->mainloop = m;
+ pconn->connection = dbus_connection_ref(conn);
+ pconn->use_rtclock = use_rtclock;
+
+ dbus_connection_set_exit_on_disconnect(conn, FALSE);
+ dbus_connection_set_dispatch_status_function(conn, dispatch_status, pconn, NULL);
+ dbus_connection_set_watch_functions(conn, add_watch, remove_watch, toggle_watch, pconn, NULL);
+ dbus_connection_set_timeout_functions(conn, add_timeout, remove_timeout, toggle_timeout, pconn, NULL);
+ dbus_connection_set_wakeup_main_function(conn, wakeup_main, pconn, NULL);
+
+ pconn->dispatch_event = pconn->mainloop->defer_new(pconn->mainloop, dispatch_cb, conn);
+
+ return pconn;
+}
+
+void pa_dbus_wrap_connection_free(pa_dbus_wrap_connection* c) {
+ pa_assert(c);
+
+ if (dbus_connection_get_is_connected(c->connection)) {
+ dbus_connection_close(c->connection);
+ /* must process remaining messages, bit of a kludge to handle
+ * both unload and shutdown */
+ while (dbus_connection_read_write_dispatch(c->connection, -1))
+ ;
+ }
+
+ c->mainloop->defer_free(c->dispatch_event);
+ dbus_connection_unref(c->connection);
+ pa_xfree(c);
+}
+
+DBusConnection* pa_dbus_wrap_connection_get(pa_dbus_wrap_connection *c) {
+ pa_assert(c);
+ pa_assert(c->connection);
+
+ return c->connection;
+}
+
+int pa_dbus_add_matches(DBusConnection *c, DBusError *error, ...) {
+ const char *t;
+ va_list ap;
+ unsigned k = 0;
+
+ pa_assert(c);
+ pa_assert(error);
+
+ va_start(ap, error);
+ while ((t = va_arg(ap, const char*))) {
+ dbus_bus_add_match(c, t, error);
+
+ if (dbus_error_is_set(error))
+ goto fail;
+
+ k++;
+ }
+ va_end(ap);
+ return 0;
+
+fail:
+
+ va_end(ap);
+ va_start(ap, error);
+ for (; k > 0; k--) {
+ pa_assert_se(t = va_arg(ap, const char*));
+ dbus_bus_remove_match(c, t, NULL);
+ }
+ va_end(ap);
+
+ return -1;
+}
+
+void pa_dbus_remove_matches(DBusConnection *c, ...) {
+ const char *t;
+ va_list ap;
+
+ pa_assert(c);
+
+ va_start(ap, c);
+ while ((t = va_arg(ap, const char*)))
+ dbus_bus_remove_match(c, t, NULL);
+ va_end(ap);
+}
+
+pa_dbus_pending *pa_dbus_pending_new(
+ DBusConnection *c,
+ DBusMessage *m,
+ DBusPendingCall *pending,
+ void *context_data,
+ void *call_data) {
+
+ pa_dbus_pending *p;
+
+ pa_assert(pending);
+
+ p = pa_xnew(pa_dbus_pending, 1);
+ p->connection = c;
+ p->message = m;
+ p->pending = pending;
+ p->context_data = context_data;
+ p->call_data = call_data;
+
+ PA_LLIST_INIT(pa_dbus_pending, p);
+
+ return p;
+}
+
+void pa_dbus_pending_free(pa_dbus_pending *p) {
+ pa_assert(p);
+
+ if (p->pending) {
+ dbus_pending_call_cancel(p->pending);
+ dbus_pending_call_unref(p->pending);
+ }
+
+ if (p->message)
+ dbus_message_unref(p->message);
+
+ pa_xfree(p);
+}
+
+void pa_dbus_sync_pending_list(pa_dbus_pending **p) {
+ pa_assert(p);
+
+ while (*p && dbus_connection_read_write_dispatch((*p)->connection, -1))
+ ;
+}
+
+void pa_dbus_free_pending_list(pa_dbus_pending **p) {
+ pa_dbus_pending *i;
+
+ pa_assert(p);
+
+ while ((i = *p)) {
+ PA_LLIST_REMOVE(pa_dbus_pending, *p, i);
+ pa_dbus_pending_free(i);
+ }
+}
+
+const char *pa_dbus_get_error_message(DBusMessage *m) {
+ const char *message;
+
+ pa_assert(m);
+ pa_assert(dbus_message_get_type(m) == DBUS_MESSAGE_TYPE_ERROR);
+
+ if (dbus_message_get_signature(m)[0] != 's')
+ return "<no explanation>";
+
+ pa_assert_se(dbus_message_get_args(m, NULL, DBUS_TYPE_STRING, &message, DBUS_TYPE_INVALID));
+
+ return message;
+}
+
+void pa_dbus_send_error(DBusConnection *c, DBusMessage *in_reply_to, const char *name, const char *format, ...) {
+ va_list ap;
+ char *message;
+ DBusMessage *reply = NULL;
+
+ pa_assert(c);
+ pa_assert(in_reply_to);
+ pa_assert(name);
+ pa_assert(format);
+
+ va_start(ap, format);
+ message = pa_vsprintf_malloc(format, ap);
+ va_end(ap);
+ pa_assert_se((reply = dbus_message_new_error(in_reply_to, name, message)));
+ pa_assert_se(dbus_connection_send(c, reply, NULL));
+
+ dbus_message_unref(reply);
+
+ pa_xfree(message);
+}
+
+void pa_dbus_send_empty_reply(DBusConnection *c, DBusMessage *in_reply_to) {
+ DBusMessage *reply = NULL;
+
+ pa_assert(c);
+ pa_assert(in_reply_to);
+
+ pa_assert_se((reply = dbus_message_new_method_return(in_reply_to)));
+ pa_assert_se(dbus_connection_send(c, reply, NULL));
+ dbus_message_unref(reply);
+}
+
+void pa_dbus_send_basic_value_reply(DBusConnection *c, DBusMessage *in_reply_to, int type, void *data) {
+ DBusMessage *reply = NULL;
+
+ pa_assert(c);
+ pa_assert(in_reply_to);
+ pa_assert(dbus_type_is_basic(type));
+ pa_assert(data);
+
+ pa_assert_se((reply = dbus_message_new_method_return(in_reply_to)));
+ pa_assert_se(dbus_message_append_args(reply, type, data, DBUS_TYPE_INVALID));
+ pa_assert_se(dbus_connection_send(c, reply, NULL));
+ dbus_message_unref(reply);
+}
+
+static const char *signature_from_basic_type(int type) {
+ switch (type) {
+ case DBUS_TYPE_BOOLEAN: return DBUS_TYPE_BOOLEAN_AS_STRING;
+ case DBUS_TYPE_BYTE: return DBUS_TYPE_BYTE_AS_STRING;
+ case DBUS_TYPE_INT16: return DBUS_TYPE_INT16_AS_STRING;
+ case DBUS_TYPE_UINT16: return DBUS_TYPE_UINT16_AS_STRING;
+ case DBUS_TYPE_INT32: return DBUS_TYPE_INT32_AS_STRING;
+ case DBUS_TYPE_UINT32: return DBUS_TYPE_UINT32_AS_STRING;
+ case DBUS_TYPE_INT64: return DBUS_TYPE_INT64_AS_STRING;
+ case DBUS_TYPE_UINT64: return DBUS_TYPE_UINT64_AS_STRING;
+ case DBUS_TYPE_DOUBLE: return DBUS_TYPE_DOUBLE_AS_STRING;
+ case DBUS_TYPE_STRING: return DBUS_TYPE_STRING_AS_STRING;
+ case DBUS_TYPE_OBJECT_PATH: return DBUS_TYPE_OBJECT_PATH_AS_STRING;
+ case DBUS_TYPE_SIGNATURE: return DBUS_TYPE_SIGNATURE_AS_STRING;
+ default: pa_assert_not_reached();
+ }
+}
+
+void pa_dbus_send_basic_variant_reply(DBusConnection *c, DBusMessage *in_reply_to, int type, void *data) {
+ DBusMessage *reply = NULL;
+ DBusMessageIter msg_iter;
+ DBusMessageIter variant_iter;
+
+ pa_assert(c);
+ pa_assert(in_reply_to);
+ pa_assert(dbus_type_is_basic(type));
+ pa_assert(data);
+
+ pa_assert_se((reply = dbus_message_new_method_return(in_reply_to)));
+ dbus_message_iter_init_append(reply, &msg_iter);
+ pa_assert_se(dbus_message_iter_open_container(&msg_iter,
+ DBUS_TYPE_VARIANT,
+ signature_from_basic_type(type),
+ &variant_iter));
+ pa_assert_se(dbus_message_iter_append_basic(&variant_iter, type, data));
+ pa_assert_se(dbus_message_iter_close_container(&msg_iter, &variant_iter));
+ pa_assert_se(dbus_connection_send(c, reply, NULL));
+ dbus_message_unref(reply);
+}
+
+/* Note: returns sizeof(char*) for strings, object paths and signatures. */
+static unsigned basic_type_size(int type) {
+ switch (type) {
+ case DBUS_TYPE_BOOLEAN: return sizeof(dbus_bool_t);
+ case DBUS_TYPE_BYTE: return 1;
+ case DBUS_TYPE_INT16: return sizeof(dbus_int16_t);
+ case DBUS_TYPE_UINT16: return sizeof(dbus_uint16_t);
+ case DBUS_TYPE_INT32: return sizeof(dbus_int32_t);
+ case DBUS_TYPE_UINT32: return sizeof(dbus_uint32_t);
+ case DBUS_TYPE_INT64: return sizeof(dbus_int64_t);
+ case DBUS_TYPE_UINT64: return sizeof(dbus_uint64_t);
+ case DBUS_TYPE_DOUBLE: return sizeof(double);
+ case DBUS_TYPE_STRING:
+ case DBUS_TYPE_OBJECT_PATH:
+ case DBUS_TYPE_SIGNATURE: return sizeof(char*);
+ default: pa_assert_not_reached();
+ }
+}
+
+void pa_dbus_send_basic_array_variant_reply(
+ DBusConnection *c,
+ DBusMessage *in_reply_to,
+ int item_type,
+ void *array,
+ unsigned n) {
+ DBusMessage *reply = NULL;
+ DBusMessageIter msg_iter;
+
+ pa_assert(c);
+ pa_assert(in_reply_to);
+ pa_assert(dbus_type_is_basic(item_type));
+ pa_assert(array || n == 0);
+
+ pa_assert_se((reply = dbus_message_new_method_return(in_reply_to)));
+ dbus_message_iter_init_append(reply, &msg_iter);
+ pa_dbus_append_basic_array_variant(&msg_iter, item_type, array, n);
+ pa_assert_se(dbus_connection_send(c, reply, NULL));
+ dbus_message_unref(reply);
+}
+
+void pa_dbus_send_proplist_variant_reply(DBusConnection *c, DBusMessage *in_reply_to, pa_proplist *proplist) {
+ DBusMessage *reply = NULL;
+ DBusMessageIter msg_iter;
+
+ pa_assert(c);
+ pa_assert(in_reply_to);
+ pa_assert(proplist);
+
+ pa_assert_se((reply = dbus_message_new_method_return(in_reply_to)));
+ dbus_message_iter_init_append(reply, &msg_iter);
+ pa_dbus_append_proplist_variant(&msg_iter, proplist);
+ pa_assert_se(dbus_connection_send(c, reply, NULL));
+ dbus_message_unref(reply);
+}
+
+void pa_dbus_append_basic_array(DBusMessageIter *iter, int item_type, const void *array, unsigned n) {
+ DBusMessageIter array_iter;
+ unsigned i;
+ unsigned item_size;
+
+ pa_assert(iter);
+ pa_assert(dbus_type_is_basic(item_type));
+ pa_assert(array || n == 0);
+
+ item_size = basic_type_size(item_type);
+
+ pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, signature_from_basic_type(item_type), &array_iter));
+
+ for (i = 0; i < n; ++i)
+ pa_assert_se(dbus_message_iter_append_basic(&array_iter, item_type, &((uint8_t*) array)[i * item_size]));
+
+ pa_assert_se(dbus_message_iter_close_container(iter, &array_iter));
+}
+
+void pa_dbus_append_basic_variant(DBusMessageIter *iter, int type, void *data) {
+ DBusMessageIter variant_iter;
+
+ pa_assert(iter);
+ pa_assert(dbus_type_is_basic(type));
+ pa_assert(data);
+
+ pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, signature_from_basic_type(type), &variant_iter));
+ pa_assert_se(dbus_message_iter_append_basic(&variant_iter, type, data));
+ pa_assert_se(dbus_message_iter_close_container(iter, &variant_iter));
+}
+
+void pa_dbus_append_basic_array_variant(DBusMessageIter *iter, int item_type, const void *array, unsigned n) {
+ DBusMessageIter variant_iter;
+ char *array_signature;
+
+ pa_assert(iter);
+ pa_assert(dbus_type_is_basic(item_type));
+ pa_assert(array || n == 0);
+
+ array_signature = pa_sprintf_malloc("a%c", *signature_from_basic_type(item_type));
+
+ pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, array_signature, &variant_iter));
+ pa_dbus_append_basic_array(&variant_iter, item_type, array, n);
+ pa_assert_se(dbus_message_iter_close_container(iter, &variant_iter));
+
+ pa_xfree(array_signature);
+}
+
+void pa_dbus_append_basic_variant_dict_entry(DBusMessageIter *dict_iter, const char *key, int type, void *data) {
+ DBusMessageIter dict_entry_iter;
+
+ pa_assert(dict_iter);
+ pa_assert(key);
+ pa_assert(dbus_type_is_basic(type));
+ pa_assert(data);
+
+ pa_assert_se(dbus_message_iter_open_container(dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_iter));
+ pa_assert_se(dbus_message_iter_append_basic(&dict_entry_iter, DBUS_TYPE_STRING, &key));
+ pa_dbus_append_basic_variant(&dict_entry_iter, type, data);
+ pa_assert_se(dbus_message_iter_close_container(dict_iter, &dict_entry_iter));
+}
+
+void pa_dbus_append_basic_array_variant_dict_entry(
+ DBusMessageIter *dict_iter,
+ const char *key,
+ int item_type,
+ const void *array,
+ unsigned n) {
+ DBusMessageIter dict_entry_iter;
+
+ pa_assert(dict_iter);
+ pa_assert(key);
+ pa_assert(dbus_type_is_basic(item_type));
+ pa_assert(array || n == 0);
+
+ pa_assert_se(dbus_message_iter_open_container(dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_iter));
+ pa_assert_se(dbus_message_iter_append_basic(&dict_entry_iter, DBUS_TYPE_STRING, &key));
+ pa_dbus_append_basic_array_variant(&dict_entry_iter, item_type, array, n);
+ pa_assert_se(dbus_message_iter_close_container(dict_iter, &dict_entry_iter));
+}
+
+void pa_dbus_append_proplist(DBusMessageIter *iter, pa_proplist *proplist) {
+ DBusMessageIter dict_iter;
+ DBusMessageIter dict_entry_iter;
+ DBusMessageIter array_iter;
+ void *state = NULL;
+ const char *key;
+
+ pa_assert(iter);
+ pa_assert(proplist);
+
+ pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "{say}", &dict_iter));
+
+ while ((key = pa_proplist_iterate(proplist, &state))) {
+ const void *value = NULL;
+ size_t nbytes;
+
+ pa_assert_se(pa_proplist_get(proplist, key, &value, &nbytes) >= 0);
+
+ pa_assert_se(dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_iter));
+
+ pa_assert_se(dbus_message_iter_append_basic(&dict_entry_iter, DBUS_TYPE_STRING, &key));
+
+ pa_assert_se(dbus_message_iter_open_container(&dict_entry_iter, DBUS_TYPE_ARRAY, "y", &array_iter));
+ pa_assert_se(dbus_message_iter_append_fixed_array(&array_iter, DBUS_TYPE_BYTE, &value, nbytes));
+ pa_assert_se(dbus_message_iter_close_container(&dict_entry_iter, &array_iter));
+
+ pa_assert_se(dbus_message_iter_close_container(&dict_iter, &dict_entry_iter));
+ }
+
+ pa_assert_se(dbus_message_iter_close_container(iter, &dict_iter));
+}
+
+void pa_dbus_append_proplist_variant(DBusMessageIter *iter, pa_proplist *proplist) {
+ DBusMessageIter variant_iter;
+
+ pa_assert(iter);
+ pa_assert(proplist);
+
+ pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "a{say}", &variant_iter));
+ pa_dbus_append_proplist(&variant_iter, proplist);
+ pa_assert_se(dbus_message_iter_close_container(iter, &variant_iter));
+}
+
+void pa_dbus_append_proplist_variant_dict_entry(DBusMessageIter *dict_iter, const char *key, pa_proplist *proplist) {
+ DBusMessageIter dict_entry_iter;
+
+ pa_assert(dict_iter);
+ pa_assert(key);
+ pa_assert(proplist);
+
+ pa_assert_se(dbus_message_iter_open_container(dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_iter));
+ pa_assert_se(dbus_message_iter_append_basic(&dict_entry_iter, DBUS_TYPE_STRING, &key));
+ pa_dbus_append_proplist_variant(&dict_entry_iter, proplist);
+ pa_assert_se(dbus_message_iter_close_container(dict_iter, &dict_entry_iter));
+}
+
+pa_proplist *pa_dbus_get_proplist_arg(DBusConnection *c, DBusMessage *msg, DBusMessageIter *iter) {
+ DBusMessageIter dict_iter;
+ DBusMessageIter dict_entry_iter;
+ char *signature;
+ pa_proplist *proplist = NULL;
+ const char *key = NULL;
+ const uint8_t *value = NULL;
+ int value_length = 0;
+
+ pa_assert(c);
+ pa_assert(msg);
+ pa_assert(iter);
+
+ pa_assert(signature = dbus_message_iter_get_signature(iter));
+ pa_assert_se(pa_streq(signature, "a{say}"));
+
+ dbus_free(signature);
+
+ proplist = pa_proplist_new();
+
+ dbus_message_iter_recurse(iter, &dict_iter);
+
+ while (dbus_message_iter_get_arg_type(&dict_iter) != DBUS_TYPE_INVALID) {
+ dbus_message_iter_recurse(&dict_iter, &dict_entry_iter);
+
+ dbus_message_iter_get_basic(&dict_entry_iter, &key);
+ dbus_message_iter_next(&dict_entry_iter);
+
+ if (strlen(key) <= 0 || !pa_ascii_valid(key)) {
+ pa_dbus_send_error(c, msg, DBUS_ERROR_INVALID_ARGS, "Invalid property list key: '%s'.", key);
+ goto fail;
+ }
+
+ dbus_message_iter_get_fixed_array(&dict_entry_iter, &value, &value_length);
+
+ pa_assert(value_length >= 0);
+
+ pa_assert_se(pa_proplist_set(proplist, key, value, value_length) >= 0);
+
+ dbus_message_iter_next(&dict_iter);
+ }
+
+ dbus_message_iter_next(iter);
+
+ return proplist;
+
+fail:
+ if (proplist)
+ pa_proplist_free(proplist);
+
+ return NULL;
+}
diff --git a/src/pulsecore/dbus-util.h b/src/pulsecore/dbus-util.h
new file mode 100644
index 0000000..cc3abda
--- /dev/null
+++ b/src/pulsecore/dbus-util.h
@@ -0,0 +1,114 @@
+#ifndef foodbusutilhfoo
+#define foodbusutilhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2006 Shams E. King
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <dbus/dbus.h>
+
+#include <pulse/gccmacro.h>
+#include <pulse/mainloop-api.h>
+#include <pulse/proplist.h>
+
+#include <pulsecore/llist.h>
+
+/* A wrap connection is not shared or refcounted, it is available in client side */
+typedef struct pa_dbus_wrap_connection pa_dbus_wrap_connection;
+
+pa_dbus_wrap_connection* pa_dbus_wrap_connection_new(pa_mainloop_api *mainloop, bool use_rtclock, DBusBusType type, DBusError *error);
+pa_dbus_wrap_connection* pa_dbus_wrap_connection_new_from_existing(
+ pa_mainloop_api *mainloop,
+ bool use_rtclock,
+ DBusConnection *conn);
+void pa_dbus_wrap_connection_free(pa_dbus_wrap_connection* conn);
+
+DBusConnection* pa_dbus_wrap_connection_get(pa_dbus_wrap_connection *conn);
+
+int pa_dbus_add_matches(DBusConnection *c, DBusError *error, ...) PA_GCC_SENTINEL;
+void pa_dbus_remove_matches(DBusConnection *c, ...) PA_GCC_SENTINEL;
+
+typedef struct pa_dbus_pending pa_dbus_pending;
+
+struct pa_dbus_pending {
+ DBusConnection *connection;
+ DBusMessage *message;
+ DBusPendingCall *pending;
+
+ void *context_data;
+ void *call_data;
+
+ PA_LLIST_FIELDS(pa_dbus_pending);
+};
+
+pa_dbus_pending *pa_dbus_pending_new(DBusConnection *c, DBusMessage *m, DBusPendingCall *pending, void *context_data, void *call_data);
+void pa_dbus_pending_free(pa_dbus_pending *p);
+
+/* Sync up a list of pa_dbus_pending_call objects */
+void pa_dbus_sync_pending_list(pa_dbus_pending **p);
+
+/* Free up a list of pa_dbus_pending_call objects */
+void pa_dbus_free_pending_list(pa_dbus_pending **p);
+
+/* When receiving a DBusMessage with type DBUS_MESSAGE_TYPE_ERROR, the
+ * DBusMessage may or may not contain an error message (a human-readable
+ * explanation of what went wrong). Extracting the error message from the
+ * DBusMessage object is a bit tedious, so here's a helper function that does
+ * that. If the DBusMessage doesn't contain any error message,
+ * "<no explanation>" is returned. */
+const char *pa_dbus_get_error_message(DBusMessage *m);
+
+/* Sends an error message as the reply to the given message. */
+void pa_dbus_send_error(
+ DBusConnection *c,
+ DBusMessage *in_reply_to,
+ const char *name,
+ const char *format, ...) PA_GCC_PRINTF_ATTR(4, 5);
+
+void pa_dbus_send_empty_reply(DBusConnection *c, DBusMessage *in_reply_to);
+void pa_dbus_send_basic_value_reply(DBusConnection *c, DBusMessage *in_reply_to, int type, void *data);
+void pa_dbus_send_basic_variant_reply(DBusConnection *c, DBusMessage *in_reply_to, int type, void *data);
+void pa_dbus_send_basic_array_variant_reply(
+ DBusConnection *c,
+ DBusMessage *in_reply_to,
+ int item_type,
+ void *array,
+ unsigned n);
+void pa_dbus_send_proplist_variant_reply(DBusConnection *c, DBusMessage *in_reply_to, pa_proplist *proplist);
+
+void pa_dbus_append_basic_array(DBusMessageIter *iter, int item_type, const void *array, unsigned n);
+void pa_dbus_append_basic_array_variant(DBusMessageIter *iter, int item_type, const void *array, unsigned n);
+void pa_dbus_append_basic_variant(DBusMessageIter *iter, int type, void *data);
+void pa_dbus_append_basic_variant_dict_entry(DBusMessageIter *dict_iter, const char *key, int type, void *data);
+void pa_dbus_append_basic_array_variant_dict_entry(
+ DBusMessageIter *dict_iter,
+ const char *key,
+ int item_type,
+ const void *array,
+ unsigned n);
+void pa_dbus_append_proplist(DBusMessageIter *iter, pa_proplist *proplist);
+void pa_dbus_append_proplist_variant(DBusMessageIter *iter, pa_proplist *proplist);
+void pa_dbus_append_proplist_variant_dict_entry(DBusMessageIter *dict_iter, const char *key, pa_proplist *proplist);
+
+/* Returns a new proplist that the caller has to free. If the proplist contains
+ * invalid keys, an error reply is sent and NULL is returned. The iterator must
+ * point to "a{say}" element. This function calls dbus_message_iter_next(iter)
+ * before returning. */
+pa_proplist *pa_dbus_get_proplist_arg(DBusConnection *c, DBusMessage *msg, DBusMessageIter *iter);
+
+#endif
diff --git a/src/pulsecore/device-port.c b/src/pulsecore/device-port.c
new file mode 100644
index 0000000..4f9235e
--- /dev/null
+++ b/src/pulsecore/device-port.c
@@ -0,0 +1,303 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+ Copyright 2011 David Henningsson, Canonical Ltd.
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "device-port.h"
+#include <pulsecore/card.h>
+#include <pulsecore/core-util.h>
+
+PA_DEFINE_PUBLIC_CLASS(pa_device_port, pa_object);
+
+pa_device_port_new_data *pa_device_port_new_data_init(pa_device_port_new_data *data) {
+ pa_assert(data);
+
+ pa_zero(*data);
+ data->type = PA_DEVICE_PORT_TYPE_UNKNOWN;
+ data->available = PA_AVAILABLE_UNKNOWN;
+ return data;
+}
+
+void pa_device_port_new_data_set_name(pa_device_port_new_data *data, const char *name) {
+ pa_assert(data);
+
+ pa_xfree(data->name);
+ data->name = pa_xstrdup(name);
+}
+
+void pa_device_port_new_data_set_description(pa_device_port_new_data *data, const char *description) {
+ pa_assert(data);
+
+ pa_xfree(data->description);
+ data->description = pa_xstrdup(description);
+}
+
+void pa_device_port_new_data_set_available(pa_device_port_new_data *data, pa_available_t available) {
+ pa_assert(data);
+
+ data->available = available;
+}
+
+void pa_device_port_new_data_set_availability_group(pa_device_port_new_data *data, const char *group) {
+ pa_assert(data);
+
+ pa_xfree(data->availability_group);
+ data->availability_group = pa_xstrdup(group);
+}
+
+void pa_device_port_new_data_set_direction(pa_device_port_new_data *data, pa_direction_t direction) {
+ pa_assert(data);
+
+ data->direction = direction;
+}
+
+void pa_device_port_new_data_set_type(pa_device_port_new_data *data, pa_device_port_type_t type) {
+ pa_assert(data);
+
+ data->type = type;
+}
+
+void pa_device_port_new_data_done(pa_device_port_new_data *data) {
+ pa_assert(data);
+
+ pa_xfree(data->name);
+ pa_xfree(data->description);
+ pa_xfree(data->availability_group);
+}
+
+void pa_device_port_set_preferred_profile(pa_device_port *p, const char *new_pp) {
+ pa_assert(p);
+
+ if (!pa_safe_streq(p->preferred_profile, new_pp)) {
+ pa_xfree(p->preferred_profile);
+ p->preferred_profile = pa_xstrdup(new_pp);
+ }
+}
+
+void pa_device_port_set_available(pa_device_port *p, pa_available_t status) {
+ pa_assert(p);
+
+ if (p->available == status)
+ return;
+
+/* pa_assert(status != PA_AVAILABLE_UNKNOWN); */
+
+ p->available = status;
+ pa_log_debug("Setting port %s to status %s", p->name, pa_available_to_string(status));
+
+ /* Post subscriptions to the card which owns us */
+ /* XXX: We need to check p->card, because this function may be called
+ * before the card object has been created. The card object should probably
+ * be created before port objects, and then p->card could be non-NULL for
+ * the whole lifecycle of pa_device_port. */
+ if (p->card && p->card->linked) {
+ pa_sink *sink;
+ pa_source *source;
+
+ pa_subscription_post(p->core, PA_SUBSCRIPTION_EVENT_CARD|PA_SUBSCRIPTION_EVENT_CHANGE, p->card->index);
+
+ sink = pa_device_port_get_sink(p);
+ source = pa_device_port_get_source(p);
+ if (sink)
+ pa_subscription_post(p->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, sink->index);
+ if (source)
+ pa_subscription_post(p->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, source->index);
+
+ /* A sink or source whose active port is unavailable can't be the
+ * default sink/source, so port availability changes may affect the
+ * default sink/source choice. */
+ if (p->direction == PA_DIRECTION_OUTPUT)
+ pa_core_update_default_sink(p->core);
+ else
+ pa_core_update_default_source(p->core);
+
+ if (p->direction == PA_DIRECTION_OUTPUT) {
+ if (sink && p == sink->active_port) {
+ if (sink->active_port->available == PA_AVAILABLE_NO) {
+ if (p->core->rescue_streams)
+ pa_sink_move_streams_to_default_sink(p->core, sink, false);
+ } else
+ pa_core_move_streams_to_newly_available_preferred_sink(p->core, sink);
+ }
+ } else {
+ if (source && p == source->active_port) {
+ if (source->active_port->available == PA_AVAILABLE_NO) {
+ if (p->core->rescue_streams)
+ pa_source_move_streams_to_default_source(p->core, source, false);
+ } else
+ pa_core_move_streams_to_newly_available_preferred_source(p->core, source);
+ }
+ }
+
+ /* This may cause the sink and source pointers to become invalid, if
+ * the availability change causes the card profile to get switched. If
+ * you add code after this line, remember to take that into account. */
+ pa_hook_fire(&p->core->hooks[PA_CORE_HOOK_PORT_AVAILABLE_CHANGED], p);
+ }
+}
+
+static void device_port_free(pa_object *o) {
+ pa_device_port *p = PA_DEVICE_PORT(o);
+
+ pa_assert(p);
+ pa_assert(pa_device_port_refcnt(p) == 0);
+
+ if (p->impl_free)
+ p->impl_free(p);
+
+ if (p->proplist)
+ pa_proplist_free(p->proplist);
+
+ if (p->profiles)
+ pa_hashmap_free(p->profiles);
+
+ pa_xfree(p->availability_group);
+ pa_xfree(p->preferred_profile);
+ pa_xfree(p->name);
+ pa_xfree(p->description);
+ pa_xfree(p);
+}
+
+pa_device_port *pa_device_port_new(pa_core *c, pa_device_port_new_data *data, size_t extra) {
+ pa_device_port *p;
+
+ pa_assert(data);
+ pa_assert(data->name);
+ pa_assert(data->description);
+ pa_assert(data->direction == PA_DIRECTION_OUTPUT || data->direction == PA_DIRECTION_INPUT);
+
+ p = PA_DEVICE_PORT(pa_object_new_internal(PA_ALIGN(sizeof(pa_device_port)) + extra, pa_device_port_type_id, pa_device_port_check_type));
+ p->parent.free = device_port_free;
+
+ p->name = data->name;
+ data->name = NULL;
+ p->description = data->description;
+ data->description = NULL;
+ p->core = c;
+ p->card = NULL;
+ p->priority = 0;
+ p->available = data->available;
+ p->availability_group = data->availability_group;
+ data->availability_group = NULL;
+ p->profiles = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+ p->direction = data->direction;
+ p->type = data->type;
+
+ p->latency_offset = 0;
+ p->proplist = pa_proplist_new();
+
+ return p;
+}
+
+void pa_device_port_set_latency_offset(pa_device_port *p, int64_t offset) {
+ uint32_t state;
+ pa_core *core;
+
+ pa_assert(p);
+
+ if (offset == p->latency_offset)
+ return;
+
+ p->latency_offset = offset;
+
+ switch (p->direction) {
+ case PA_DIRECTION_OUTPUT: {
+ pa_sink *sink;
+
+ PA_IDXSET_FOREACH(sink, p->core->sinks, state) {
+ if (sink->active_port == p) {
+ pa_sink_set_port_latency_offset(sink, p->latency_offset);
+ break;
+ }
+ }
+
+ break;
+ }
+
+ case PA_DIRECTION_INPUT: {
+ pa_source *source;
+
+ PA_IDXSET_FOREACH(source, p->core->sources, state) {
+ if (source->active_port == p) {
+ pa_source_set_port_latency_offset(source, p->latency_offset);
+ break;
+ }
+ }
+
+ break;
+ }
+ }
+
+ pa_assert_se(core = p->core);
+ pa_subscription_post(core, PA_SUBSCRIPTION_EVENT_CARD|PA_SUBSCRIPTION_EVENT_CHANGE, p->card->index);
+ pa_hook_fire(&core->hooks[PA_CORE_HOOK_PORT_LATENCY_OFFSET_CHANGED], p);
+}
+
+pa_device_port *pa_device_port_find_best(pa_hashmap *ports)
+{
+ void *state;
+ pa_device_port *p, *best = NULL;
+
+ if (!ports)
+ return NULL;
+
+ /* First run: skip unavailable ports */
+ PA_HASHMAP_FOREACH(p, ports, state) {
+ if (p->available == PA_AVAILABLE_NO)
+ continue;
+
+ if (!best || p->priority > best->priority)
+ best = p;
+ }
+
+ /* Second run: if only unavailable ports exist, still suggest a port */
+ if (!best) {
+ PA_HASHMAP_FOREACH(p, ports, state)
+ if (!best || p->priority > best->priority)
+ best = p;
+ }
+
+ return best;
+}
+
+pa_sink *pa_device_port_get_sink(pa_device_port *p) {
+ pa_sink *rs = NULL;
+ pa_sink *sink;
+ uint32_t state;
+
+ PA_IDXSET_FOREACH(sink, p->card->sinks, state)
+ if (p == pa_hashmap_get(sink->ports, p->name)) {
+ rs = sink;
+ break;
+ }
+ return rs;
+}
+
+pa_source *pa_device_port_get_source(pa_device_port *p) {
+ pa_source *rs = NULL;
+ pa_source *source;
+ uint32_t state;
+
+ PA_IDXSET_FOREACH(source, p->card->sources, state)
+ if (p == pa_hashmap_get(source->ports, p->name)) {
+ rs = source;
+ break;
+ }
+ return rs;
+}
diff --git a/src/pulsecore/device-port.h b/src/pulsecore/device-port.h
new file mode 100644
index 0000000..7178ff2
--- /dev/null
+++ b/src/pulsecore/device-port.h
@@ -0,0 +1,100 @@
+#ifndef foopulsedeviceporthfoo
+#define foopulsedeviceporthfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+ Copyright 2011 David Henningsson, Canonical Ltd.
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <inttypes.h>
+
+#include <pulsecore/typedefs.h>
+#include <pulse/def.h>
+#include <pulsecore/object.h>
+#include <pulsecore/hashmap.h>
+#include <pulsecore/core.h>
+#include <pulsecore/card.h>
+
+struct pa_device_port {
+ pa_object parent; /* Needed for reference counting */
+ pa_core *core;
+ pa_card *card;
+
+ char *name;
+ char *description;
+ char *preferred_profile;
+ pa_device_port_type_t type;
+
+ unsigned priority;
+ pa_available_t available; /* PA_AVAILABLE_UNKNOWN, PA_AVAILABLE_NO or PA_AVAILABLE_YES */
+ char *availability_group; /* a string indentifier which determine the group of devices handling the available state simulteneously */
+
+ pa_proplist *proplist;
+ pa_hashmap *profiles; /* Does not own the profiles */
+ pa_direction_t direction;
+ int64_t latency_offset;
+
+ /* Free the extra implementation specific data. Called before other members are freed. */
+ void (*impl_free)(pa_device_port *port);
+
+ /* .. followed by some implementation specific data */
+};
+
+PA_DECLARE_PUBLIC_CLASS(pa_device_port);
+#define PA_DEVICE_PORT(s) (pa_device_port_cast(s))
+
+#define PA_DEVICE_PORT_DATA(d) ((void*) ((uint8_t*) d + PA_ALIGN(sizeof(pa_device_port))))
+
+typedef struct pa_device_port_new_data {
+ char *name;
+ char *description;
+ pa_available_t available;
+ char *availability_group;
+ pa_direction_t direction;
+ pa_device_port_type_t type;
+} pa_device_port_new_data;
+
+pa_device_port_new_data *pa_device_port_new_data_init(pa_device_port_new_data *data);
+void pa_device_port_new_data_set_name(pa_device_port_new_data *data, const char *name);
+void pa_device_port_new_data_set_description(pa_device_port_new_data *data, const char *description);
+void pa_device_port_new_data_set_available(pa_device_port_new_data *data, pa_available_t available);
+void pa_device_port_new_data_set_availability_group(pa_device_port_new_data *data, const char *group);
+void pa_device_port_new_data_set_direction(pa_device_port_new_data *data, pa_direction_t direction);
+void pa_device_port_new_data_set_type(pa_device_port_new_data *data, pa_device_port_type_t type);
+void pa_device_port_new_data_done(pa_device_port_new_data *data);
+
+pa_device_port *pa_device_port_new(pa_core *c, pa_device_port_new_data *data, size_t extra);
+
+/* The port's available status has changed */
+void pa_device_port_set_available(pa_device_port *p, pa_available_t available);
+
+void pa_device_port_set_latency_offset(pa_device_port *p, int64_t offset);
+void pa_device_port_set_preferred_profile(pa_device_port *p, const char *new_pp);
+
+pa_device_port *pa_device_port_find_best(pa_hashmap *ports);
+
+pa_sink *pa_device_port_get_sink(pa_device_port *p);
+
+pa_source *pa_device_port_get_source(pa_device_port *p);
+
+#endif
diff --git a/src/pulsecore/dllmain.c b/src/pulsecore/dllmain.c
new file mode 100644
index 0000000..e594ae5
--- /dev/null
+++ b/src/pulsecore/dllmain.c
@@ -0,0 +1,53 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef OS_IS_WIN32
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <windows.h>
+#include <winsock2.h>
+
+extern char *pa_win32_get_toplevel(HANDLE handle);
+
+BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
+ WSADATA data;
+
+ switch (fdwReason) {
+
+ case DLL_PROCESS_ATTACH:
+ if (!pa_win32_get_toplevel(hinstDLL))
+ return FALSE;
+ WSAStartup(MAKEWORD(2, 0), &data);
+ break;
+
+ case DLL_PROCESS_DETACH:
+ WSACleanup();
+ break;
+
+ }
+ return TRUE;
+}
+
+#endif /* OS_IS_WIN32 */
diff --git a/src/pulsecore/dynarray.c b/src/pulsecore/dynarray.c
new file mode 100644
index 0000000..a948f26
--- /dev/null
+++ b/src/pulsecore/dynarray.c
@@ -0,0 +1,165 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2008 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/macro.h>
+
+#include "dynarray.h"
+
+struct pa_dynarray {
+ void **data;
+ unsigned n_allocated, n_entries;
+ pa_free_cb_t free_cb;
+};
+
+pa_dynarray* pa_dynarray_new(pa_free_cb_t free_cb) {
+ pa_dynarray *array;
+
+ array = pa_xnew0(pa_dynarray, 1);
+ array->free_cb = free_cb;
+
+ return array;
+}
+
+void pa_dynarray_free(pa_dynarray *array) {
+ unsigned i;
+ pa_assert(array);
+
+ if (array->free_cb)
+ for (i = 0; i < array->n_entries; i++)
+ array->free_cb(array->data[i]);
+
+ pa_xfree(array->data);
+ pa_xfree(array);
+}
+
+void pa_dynarray_append(pa_dynarray *array, void *p) {
+ pa_assert(array);
+ pa_assert(p);
+
+ if (array->n_entries == array->n_allocated) {
+ unsigned n = PA_MAX(array->n_allocated * 2, 25U);
+
+ array->data = pa_xrealloc(array->data, sizeof(void *) * n);
+ array->n_allocated = n;
+ }
+
+ array->data[array->n_entries++] = p;
+}
+
+void *pa_dynarray_get(pa_dynarray *array, unsigned i) {
+ pa_assert(array);
+
+ if (i >= array->n_entries)
+ return NULL;
+
+ return array->data[i];
+}
+
+void *pa_dynarray_last(pa_dynarray *array) {
+ pa_assert(array);
+
+ if (array->n_entries == 0)
+ return NULL;
+
+ return array->data[array->n_entries - 1];
+}
+
+int pa_dynarray_remove_by_index(pa_dynarray *array, unsigned i) {
+ void *entry;
+
+ pa_assert(array);
+
+ if (i >= array->n_entries)
+ return -PA_ERR_NOENTITY;
+
+ entry = array->data[i];
+ array->data[i] = array->data[array->n_entries - 1];
+ array->n_entries--;
+
+ if (array->free_cb)
+ array->free_cb(entry);
+
+ return 0;
+}
+
+int pa_dynarray_remove_by_data(pa_dynarray *array, void *p) {
+ unsigned i;
+
+ pa_assert(array);
+ pa_assert(p);
+
+ /* Iterate backwards, with the assumption that recently appended entries
+ * are likely to be removed first. */
+ i = array->n_entries;
+ while (i > 0) {
+ i--;
+ if (array->data[i] == p) {
+ pa_dynarray_remove_by_index(array, i);
+ return 0;
+ }
+ }
+
+ return -PA_ERR_NOENTITY;
+}
+
+void *pa_dynarray_steal_last(pa_dynarray *array) {
+ pa_assert(array);
+
+ if (array->n_entries > 0)
+ return array->data[--array->n_entries];
+ else
+ return NULL;
+}
+
+unsigned pa_dynarray_size(pa_dynarray *array) {
+ pa_assert(array);
+
+ return array->n_entries;
+}
+
+int pa_dynarray_insert_by_index(pa_dynarray *array, void *p, unsigned i) {
+ void *entry;
+ unsigned j;
+
+ pa_assert(array);
+
+ if (i > array->n_entries)
+ return -PA_ERR_NOENTITY;
+
+ if (i == array->n_entries)
+ pa_dynarray_append(array, p);
+ else {
+ entry = pa_dynarray_last(array);
+ pa_dynarray_append(array, entry);
+ j = array->n_entries - 2;
+ for (;j > i; j--)
+ array->data[j] = array->data[j-1];
+ array->data[i] = p;
+ }
+
+ return 0;
+}
diff --git a/src/pulsecore/dynarray.h b/src/pulsecore/dynarray.h
new file mode 100644
index 0000000..4c0925e
--- /dev/null
+++ b/src/pulsecore/dynarray.h
@@ -0,0 +1,73 @@
+#ifndef foopulsecoredynarrayhfoo
+#define foopulsecoredynarrayhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2008 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulse/def.h>
+
+typedef struct pa_dynarray pa_dynarray;
+
+/* Implementation of a simple dynamically sized array for storing pointers.
+ *
+ * When the array is created, a free callback can be provided, which will be
+ * then used when removing items from the array and when freeing the array. If
+ * the free callback is not provided, the memory management of the stored items
+ * is the responsibility of the array user. If there is need to remove items
+ * from the array without freeing them, while also having the free callback
+ * set, the functions with "steal" in their name can be used.
+ *
+ * Removing items from the middle of the array causes the last item to be
+ * moved to the place of the removed item. That is, array ordering is not
+ * preserved.
+ *
+ * The array doesn't support storing NULL pointers. */
+
+pa_dynarray* pa_dynarray_new(pa_free_cb_t free_cb);
+void pa_dynarray_free(pa_dynarray *array);
+
+void pa_dynarray_append(pa_dynarray *array, void *p);
+
+/* Returns the element at index i, or NULL if i is out of bounds. */
+void *pa_dynarray_get(pa_dynarray *array, unsigned i);
+
+/* Returns the last element, or NULL if the array is empty. */
+void *pa_dynarray_last(pa_dynarray *array);
+
+/* Returns -PA_ERR_NOENTITY if i is out of bounds, and zero otherwise. */
+int pa_dynarray_remove_by_index(pa_dynarray *array, unsigned i);
+
+/* Returns -PA_ERR_NOENTITY if p is not found in the array, and zero
+ * otherwise. If the array contains multiple occurrences of p, only one of
+ * them is removed (and it's unspecified which one). */
+int pa_dynarray_remove_by_data(pa_dynarray *array, void *p);
+
+/* Returns the removed item, or NULL if the array is empty. */
+void *pa_dynarray_steal_last(pa_dynarray *array);
+
+unsigned pa_dynarray_size(pa_dynarray *array);
+
+/* Returns -PA_ERR_NOENTITY if i is out of bounds, and zero otherwise.
+ * Here i is the location index in the array like 0, ..., array->entries */
+int pa_dynarray_insert_by_index(pa_dynarray *array, void *p, unsigned i);
+
+#define PA_DYNARRAY_FOREACH(elem, array, idx) \
+ for ((idx) = 0; ((elem) = pa_dynarray_get(array, idx)); (idx)++)
+
+#endif
diff --git a/src/pulsecore/endianmacros.h b/src/pulsecore/endianmacros.h
new file mode 100644
index 0000000..c4cb8a2
--- /dev/null
+++ b/src/pulsecore/endianmacros.h
@@ -0,0 +1,160 @@
+#ifndef fooendianmacroshfoo
+#define fooendianmacroshfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <inttypes.h>
+
+#ifndef PACKAGE
+#error "Please include config.h before including this file!"
+#endif
+
+#ifdef HAVE_BYTESWAP_H
+#include <byteswap.h>
+#endif
+
+#ifdef HAVE_BYTESWAP_H
+#define PA_INT16_SWAP(x) ((int16_t) bswap_16((uint16_t) x))
+#define PA_UINT16_SWAP(x) ((uint16_t) bswap_16((uint16_t) x))
+#define PA_INT32_SWAP(x) ((int32_t) bswap_32((uint32_t) x))
+#define PA_UINT32_SWAP(x) ((uint32_t) bswap_32((uint32_t) x))
+#else
+#define PA_INT16_SWAP(x) ( (int16_t) ( ((uint16_t) (x) >> 8) | ((uint16_t) (x) << 8) ) )
+#define PA_UINT16_SWAP(x) ( (uint16_t) ( ((uint16_t) (x) >> 8) | ((uint16_t) (x) << 8) ) )
+#define PA_INT32_SWAP(x) ( (int32_t) ( ((uint32_t) (x) >> 24) | ((uint32_t) (x) << 24) | (((uint32_t) (x) & 0xFF00) << 8) | ((((uint32_t) (x)) >> 8) & 0xFF00) ) )
+#define PA_UINT32_SWAP(x) ( (uint32_t) ( ((uint32_t) (x) >> 24) | ((uint32_t) (x) << 24) | (((uint32_t) (x) & 0xFF00) << 8) | ((((uint32_t) (x)) >> 8) & 0xFF00) ) )
+#endif
+
+static inline uint32_t PA_READ24BE(const uint8_t *p) {
+ return
+ ((uint32_t) p[0] << 16) |
+ ((uint32_t) p[1] << 8) |
+ ((uint32_t) p[2]);
+}
+
+static inline uint32_t PA_READ24LE(const uint8_t *p) {
+ return
+ ((uint32_t) p[2] << 16) |
+ ((uint32_t) p[1] << 8) |
+ ((uint32_t) p[0]);
+}
+
+static inline void PA_WRITE24BE(uint8_t *p, uint32_t u) {
+ p[0] = (uint8_t) (u >> 16);
+ p[1] = (uint8_t) (u >> 8);
+ p[2] = (uint8_t) u;
+}
+
+static inline void PA_WRITE24LE(uint8_t *p, uint32_t u) {
+ p[2] = (uint8_t) (u >> 16);
+ p[1] = (uint8_t) (u >> 8);
+ p[0] = (uint8_t) u;
+}
+
+static inline float PA_READ_FLOAT32RE(const void *p) {
+ union {
+ float f;
+ uint32_t u;
+ } t;
+
+ t.u = PA_UINT32_SWAP(*(uint32_t *) p);
+ return t.f;
+}
+
+static inline void PA_WRITE_FLOAT32RE(void *p, float x) {
+ union {
+ float f;
+ uint32_t u;
+ } t;
+
+ t.f = x;
+ *(uint32_t *) p = PA_UINT32_SWAP(t.u);
+}
+
+#define PA_MAYBE_INT16_SWAP(c,x) ((c) ? PA_INT16_SWAP(x) : (x))
+#define PA_MAYBE_UINT16_SWAP(c,x) ((c) ? PA_UINT16_SWAP(x) : (x))
+
+#define PA_MAYBE_INT32_SWAP(c,x) ((c) ? PA_INT32_SWAP(x) : (x))
+#define PA_MAYBE_UINT32_SWAP(c,x) ((c) ? PA_UINT32_SWAP(x) : (x))
+
+#ifdef WORDS_BIGENDIAN
+ #define PA_INT16_FROM_LE(x) PA_INT16_SWAP(x)
+ #define PA_INT16_FROM_BE(x) ((int16_t)(x))
+
+ #define PA_INT16_TO_LE(x) PA_INT16_SWAP(x)
+ #define PA_INT16_TO_BE(x) ((int16_t)(x))
+
+ #define PA_UINT16_FROM_LE(x) PA_UINT16_SWAP(x)
+ #define PA_UINT16_FROM_BE(x) ((uint16_t)(x))
+
+ #define PA_UINT16_TO_LE(x) PA_UINT16_SWAP(x)
+ #define PA_UINT16_TO_BE(x) ((uint16_t)(x))
+
+ #define PA_INT32_FROM_LE(x) PA_INT32_SWAP(x)
+ #define PA_INT32_FROM_BE(x) ((int32_t)(x))
+
+ #define PA_INT32_TO_LE(x) PA_INT32_SWAP(x)
+ #define PA_INT32_TO_BE(x) ((int32_t)(x))
+
+ #define PA_UINT32_FROM_LE(x) PA_UINT32_SWAP(x)
+ #define PA_UINT32_FROM_BE(x) ((uint32_t)(x))
+
+ #define PA_UINT32_TO_LE(x) PA_UINT32_SWAP(x)
+ #define PA_UINT32_TO_BE(x) ((uint32_t)(x))
+
+ #define PA_READ24NE(x) PA_READ24BE(x)
+ #define PA_WRITE24NE(x,y) PA_WRITE24BE((x),(y))
+
+ #define PA_READ24RE(x) PA_READ24LE(x)
+ #define PA_WRITE24RE(x,y) PA_WRITE24LE((x),(y))
+#else
+ #define PA_INT16_FROM_LE(x) ((int16_t)(x))
+ #define PA_INT16_FROM_BE(x) PA_INT16_SWAP(x)
+
+ #define PA_INT16_TO_LE(x) ((int16_t)(x))
+ #define PA_INT16_TO_BE(x) PA_INT16_SWAP(x)
+
+ #define PA_UINT16_FROM_LE(x) ((uint16_t)(x))
+ #define PA_UINT16_FROM_BE(x) PA_UINT16_SWAP(x)
+
+ #define PA_UINT16_TO_LE(x) ((uint16_t)(x))
+ #define PA_UINT16_TO_BE(x) PA_UINT16_SWAP(x)
+
+ #define PA_INT32_FROM_LE(x) ((int32_t)(x))
+ #define PA_INT32_FROM_BE(x) PA_INT32_SWAP(x)
+
+ #define PA_INT32_TO_LE(x) ((int32_t)(x))
+ #define PA_INT32_TO_BE(x) PA_INT32_SWAP(x)
+
+ #define PA_UINT32_FROM_LE(x) ((uint32_t)(x))
+ #define PA_UINT32_FROM_BE(x) PA_UINT32_SWAP(x)
+
+ #define PA_UINT32_TO_LE(x) ((uint32_t)(x))
+ #define PA_UINT32_TO_BE(x) PA_UINT32_SWAP(x)
+
+ #define PA_READ24NE(x) PA_READ24LE(x)
+ #define PA_WRITE24NE(x,y) PA_WRITE24LE((x),(y))
+
+ #define PA_READ24RE(x) PA_READ24BE(x)
+ #define PA_WRITE24RE(x,y) PA_WRITE24BE((x),(y))
+#endif
+
+#endif
diff --git a/src/pulsecore/esound.h b/src/pulsecore/esound.h
new file mode 100644
index 0000000..4ca0e04
--- /dev/null
+++ b/src/pulsecore/esound.h
@@ -0,0 +1,204 @@
+#ifndef fooesoundhfoo
+#define fooesoundhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+/* Most of the following is blatantly stolen from esound. */
+
+/* path and name of the default EsounD domain socket */
+#define ESD_UNIX_SOCKET_DIR "/tmp/.esd"
+#define ESD_UNIX_SOCKET_NAME "/tmp/.esd/socket"
+
+/* length of the audio buffer size */
+#define ESD_BUF_SIZE (4 * 1024)
+/* maximum size we can write(). Otherwise we might overflow */
+#define ESD_MAX_WRITE_SIZE (21 * 4096)
+
+/* length of the authentication key, octets */
+#define ESD_KEY_LEN (16)
+
+/* default port for the EsounD server */
+#define ESD_DEFAULT_PORT (16001)
+
+/* default sample rate for the EsounD server */
+#define ESD_DEFAULT_RATE (44100)
+
+/* maximum length of a stream/sample name */
+#define ESD_NAME_MAX (128)
+
+/* a magic number to identify the relative endianness of a client */
+#define ESD_ENDIAN_KEY ((uint32_t) (('E' << 24) + ('N' << 16) + ('D' << 8) + ('N')))
+
+#define ESD_VOLUME_BASE (256)
+
+/*************************************/
+/* what can we do to/with the EsounD */
+enum esd_proto {
+ ESD_PROTO_CONNECT, /* implied on initial client connection */
+
+ /* pseudo "security" functionality */
+ ESD_PROTO_LOCK, /* disable "foreign" client connections */
+ ESD_PROTO_UNLOCK, /* enable "foreign" client connections */
+
+ /* stream functionality: play, record, monitor */
+ ESD_PROTO_STREAM_PLAY, /* play all following data as a stream */
+ ESD_PROTO_STREAM_REC, /* record data from card as a stream */
+ ESD_PROTO_STREAM_MON, /* send mixed buffer output as a stream */
+
+ /* sample functionality: cache, free, play, loop, EOL, kill */
+ ESD_PROTO_SAMPLE_CACHE, /* cache a sample in the server */
+ ESD_PROTO_SAMPLE_FREE, /* release a sample in the server */
+ ESD_PROTO_SAMPLE_PLAY, /* play a cached sample */
+ ESD_PROTO_SAMPLE_LOOP, /* loop a cached sample, til eoloop */
+ ESD_PROTO_SAMPLE_STOP, /* stop a looping sample when done */
+ ESD_PROTO_SAMPLE_KILL, /* stop the looping sample immediately */
+
+ /* free and reclaim /dev/dsp functionality */
+ ESD_PROTO_STANDBY, /* release /dev/dsp and ignore all data */
+ ESD_PROTO_RESUME, /* reclaim /dev/dsp and play sounds again */
+
+ /* TODO: move these to a more logical place. NOTE: will break the protocol */
+ ESD_PROTO_SAMPLE_GETID, /* get the ID for an already-cached sample */
+ ESD_PROTO_STREAM_FILT, /* filter mixed buffer output as a stream */
+
+ /* esd remote management */
+ ESD_PROTO_SERVER_INFO, /* get server info (ver, sample rate, format) */
+ ESD_PROTO_ALL_INFO, /* get all info (server info, players, samples) */
+ ESD_PROTO_SUBSCRIBE, /* track new and removed players and samples */
+ ESD_PROTO_UNSUBSCRIBE, /* stop tracking updates */
+
+ /* esd remote control */
+ ESD_PROTO_STREAM_PAN, /* set stream panning */
+ ESD_PROTO_SAMPLE_PAN, /* set default sample panning */
+
+ /* esd status */
+ ESD_PROTO_STANDBY_MODE, /* see if server is in standby, autostandby, etc */
+
+ /* esd latency */
+ ESD_PROTO_LATENCY, /* retrieve latency between write()'s and output */
+
+ ESD_PROTO_MAX /* for bounds checking */
+};
+
+/******************/
+/* The EsounD api */
+
+/* the properties of a sound buffer are logically or'd */
+
+/* bits of stream/sample data */
+#define ESD_MASK_BITS ( 0x000F )
+#define ESD_BITS8 ( 0x0000 )
+#define ESD_BITS16 ( 0x0001 )
+
+/* how many interleaved channels of data */
+#define ESD_MASK_CHAN ( 0x00F0 )
+#define ESD_MONO ( 0x0010 )
+#define ESD_STEREO ( 0x0020 )
+
+/* whether it's a stream or a sample */
+#define ESD_MASK_MODE ( 0x0F00 )
+#define ESD_STREAM ( 0x0000 )
+#define ESD_SAMPLE ( 0x0100 )
+#define ESD_ADPCM ( 0x0200 ) /* TODO: anyone up for this? =P */
+
+/* the function of the stream/sample, and common functions */
+#define ESD_MASK_FUNC ( 0xF000 )
+#define ESD_PLAY ( 0x1000 )
+/* functions for streams only */
+#define ESD_MONITOR ( 0x0000 )
+#define ESD_RECORD ( 0x2000 )
+/* functions for samples only */
+#define ESD_STOP ( 0x0000 )
+#define ESD_LOOP ( 0x2000 )
+
+typedef int esd_format_t;
+typedef int esd_proto_t;
+
+/*******************************************************************/
+/* esdmgr.c - functions to implement a "sound manager" for esd */
+
+/* structures to retrieve information about streams/samples from the server */
+typedef struct esd_server_info {
+
+ int version; /* server version encoded as an int */
+ esd_format_t format; /* magic int with the format info */
+ int rate; /* sample rate */
+
+} esd_server_info_t;
+
+typedef struct esd_player_info {
+
+ struct esd_player_info *next; /* point to next entry in list */
+ esd_server_info_t *server; /* the server that contains this stream */
+
+ int source_id; /* either a stream fd or sample id */
+ char name[ ESD_NAME_MAX ]; /* name of stream for remote control */
+ int rate; /* sample rate */
+ int left_vol_scale; /* volume scaling */
+ int right_vol_scale;
+
+ esd_format_t format; /* magic int with the format info */
+
+} esd_player_info_t;
+
+typedef struct esd_sample_info {
+
+ struct esd_sample_info *next; /* point to next entry in list */
+ esd_server_info_t *server; /* the server that contains this sample */
+
+ int sample_id; /* either a stream fd or sample id */
+ char name[ ESD_NAME_MAX ]; /* name of stream for remote control */
+ int rate; /* sample rate */
+ int left_vol_scale; /* volume scaling */
+ int right_vol_scale;
+
+ esd_format_t format; /* magic int with the format info */
+ int length; /* total buffer length */
+
+} esd_sample_info_t;
+
+typedef struct esd_info {
+
+ esd_server_info_t *server;
+ esd_player_info_t *player_list;
+ esd_sample_info_t *sample_list;
+
+} esd_info_t;
+
+enum esd_standby_mode {
+ ESM_ERROR, ESM_ON_STANDBY, ESM_ON_AUTOSTANDBY, ESM_RUNNING
+};
+typedef int esd_standby_mode_t;
+
+enum esd_client_state {
+ ESD_STREAMING_DATA, /* data from here on is streamed data */
+ ESD_CACHING_SAMPLE, /* midway through caching a sample */
+ ESD_NEEDS_REQDATA, /* more data needed to complete request */
+ ESD_NEXT_REQUEST, /* proceed to next request */
+ ESD_CLIENT_STATE_MAX /* place holder */
+};
+typedef int esd_client_state_t;
+
+/* the endian key is transferred in binary, if it's read into int, */
+/* and matches ESD_ENDIAN_KEY (ENDN), then the endianness of the */
+/* server and the client match; if it's SWAP_ENDIAN_KEY, swap data */
+#define ESD_SWAP_ENDIAN_KEY (PA_UINT32_SWAP(ESD_ENDIAN_KEY))
+
+#endif
diff --git a/src/pulsecore/fdsem.c b/src/pulsecore/fdsem.c
new file mode 100644
index 0000000..a7fbf95
--- /dev/null
+++ b/src/pulsecore/fdsem.c
@@ -0,0 +1,320 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef HAVE_SYS_SYSCALL_H
+#include <sys/syscall.h>
+#endif
+
+#include <unistd.h>
+#include <errno.h>
+
+#include <pulsecore/atomic.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/core-error.h>
+#include <pulse/xmalloc.h>
+
+#ifndef HAVE_PIPE
+#include <pulsecore/pipe.h>
+#endif
+
+#ifdef HAVE_SYS_EVENTFD_H
+#include <sys/eventfd.h>
+#endif
+
+#include "fdsem.h"
+
+struct pa_fdsem {
+ int fds[2];
+#ifdef HAVE_SYS_EVENTFD_H
+ int efd;
+#endif
+ int write_type;
+ pa_fdsem_data *data;
+};
+
+pa_fdsem *pa_fdsem_new(void) {
+ pa_fdsem *f;
+
+ f = pa_xmalloc0(PA_ALIGN(sizeof(pa_fdsem)) + PA_ALIGN(sizeof(pa_fdsem_data)));
+
+#ifdef HAVE_SYS_EVENTFD_H
+ if ((f->efd = eventfd(0, EFD_CLOEXEC)) >= 0)
+ f->fds[0] = f->fds[1] = -1;
+ else
+#endif
+ {
+ if (pa_pipe_cloexec(f->fds) < 0) {
+ pa_xfree(f);
+ return NULL;
+ }
+ }
+
+ f->data = (pa_fdsem_data*) ((uint8_t*) f + PA_ALIGN(sizeof(pa_fdsem)));
+
+ pa_atomic_store(&f->data->waiting, 0);
+ pa_atomic_store(&f->data->signalled, 0);
+ pa_atomic_store(&f->data->in_pipe, 0);
+
+ return f;
+}
+
+pa_fdsem *pa_fdsem_open_shm(pa_fdsem_data *data, int event_fd) {
+ pa_fdsem *f = NULL;
+
+ pa_assert(data);
+ pa_assert(event_fd >= 0);
+
+#ifdef HAVE_SYS_EVENTFD_H
+ f = pa_xnew0(pa_fdsem, 1);
+
+ f->efd = event_fd;
+ pa_make_fd_cloexec(f->efd);
+ f->fds[0] = f->fds[1] = -1;
+ f->data = data;
+#endif
+
+ return f;
+}
+
+pa_fdsem *pa_fdsem_new_shm(pa_fdsem_data *data) {
+ pa_fdsem *f = NULL;
+
+ pa_assert(data);
+
+#ifdef HAVE_SYS_EVENTFD_H
+
+ f = pa_xnew0(pa_fdsem, 1);
+
+ if ((f->efd = eventfd(0, EFD_CLOEXEC)) < 0) {
+ pa_xfree(f);
+ return NULL;
+ }
+
+ f->fds[0] = f->fds[1] = -1;
+ f->data = data;
+
+ pa_atomic_store(&f->data->waiting, 0);
+ pa_atomic_store(&f->data->signalled, 0);
+ pa_atomic_store(&f->data->in_pipe, 0);
+
+#endif
+
+ return f;
+}
+
+void pa_fdsem_free(pa_fdsem *f) {
+ pa_assert(f);
+
+#ifdef HAVE_SYS_EVENTFD_H
+ if (f->efd >= 0)
+ pa_close(f->efd);
+#endif
+ pa_close_pipe(f->fds);
+
+ pa_xfree(f);
+}
+
+static void flush(pa_fdsem *f) {
+ ssize_t r;
+ pa_assert(f);
+
+ if (pa_atomic_load(&f->data->in_pipe) <= 0)
+ return;
+
+ do {
+ char x[10];
+
+#ifdef HAVE_SYS_EVENTFD_H
+ if (f->efd >= 0) {
+ uint64_t u;
+
+ if ((r = pa_read(f->efd, &u, sizeof(u), NULL)) != sizeof(u)) {
+
+ if (r >= 0 || errno != EINTR) {
+ pa_log_error("Invalid read from eventfd: %s", r < 0 ? pa_cstrerror(errno) : "EOF");
+ pa_assert_not_reached();
+ }
+
+ continue;
+ }
+ r = (ssize_t) u;
+ } else
+#endif
+
+ if ((r = pa_read(f->fds[0], &x, sizeof(x), NULL)) <= 0) {
+
+ if (r >= 0 || errno != EINTR) {
+ pa_log_error("Invalid read from pipe: %s", r < 0 ? pa_cstrerror(errno) : "EOF");
+ pa_assert_not_reached();
+ }
+
+ continue;
+ }
+
+ } while (pa_atomic_sub(&f->data->in_pipe, (int) r) > (int) r);
+}
+
+void pa_fdsem_post(pa_fdsem *f) {
+ pa_assert(f);
+
+ if (pa_atomic_cmpxchg(&f->data->signalled, 0, 1)) {
+
+ if (pa_atomic_load(&f->data->waiting)) {
+ ssize_t r;
+ char x = 'x';
+
+ pa_atomic_inc(&f->data->in_pipe);
+
+ for (;;) {
+
+#ifdef HAVE_SYS_EVENTFD_H
+ if (f->efd >= 0) {
+ uint64_t u = 1;
+
+ if ((r = pa_write(f->efd, &u, sizeof(u), &f->write_type)) != sizeof(u)) {
+ if (r >= 0 || errno != EINTR) {
+ pa_log_error("Invalid write to eventfd: %s", r < 0 ? pa_cstrerror(errno) : "EOF");
+ pa_assert_not_reached();
+ }
+
+ continue;
+ }
+ } else
+#endif
+
+ if ((r = pa_write(f->fds[1], &x, 1, &f->write_type)) != 1) {
+ if (r >= 0 || errno != EINTR) {
+ pa_log_error("Invalid write to pipe: %s", r < 0 ? pa_cstrerror(errno) : "EOF");
+ pa_assert_not_reached();
+ }
+
+ continue;
+ }
+
+ break;
+ }
+ }
+ }
+}
+
+void pa_fdsem_wait(pa_fdsem *f) {
+ pa_assert(f);
+
+ flush(f);
+
+ if (pa_atomic_cmpxchg(&f->data->signalled, 1, 0))
+ return;
+
+ pa_atomic_inc(&f->data->waiting);
+
+ while (!pa_atomic_cmpxchg(&f->data->signalled, 1, 0)) {
+ char x[10];
+ ssize_t r;
+
+#ifdef HAVE_SYS_EVENTFD_H
+ if (f->efd >= 0) {
+ uint64_t u;
+
+ if ((r = pa_read(f->efd, &u, sizeof(u), NULL)) != sizeof(u)) {
+
+ if (r >= 0 || errno != EINTR) {
+ pa_log_error("Invalid read from eventfd: %s", r < 0 ? pa_cstrerror(errno) : "EOF");
+ pa_assert_not_reached();
+ }
+
+ continue;
+ }
+
+ r = (ssize_t) u;
+ } else
+#endif
+
+ if ((r = pa_read(f->fds[0], &x, sizeof(x), NULL)) <= 0) {
+
+ if (r >= 0 || errno != EINTR) {
+ pa_log_error("Invalid read from pipe: %s", r < 0 ? pa_cstrerror(errno) : "EOF");
+ pa_assert_not_reached();
+ }
+
+ continue;
+ }
+
+ pa_atomic_sub(&f->data->in_pipe, (int) r);
+ }
+
+ pa_assert_se(pa_atomic_dec(&f->data->waiting) >= 1);
+}
+
+int pa_fdsem_try(pa_fdsem *f) {
+ pa_assert(f);
+
+ flush(f);
+
+ if (pa_atomic_cmpxchg(&f->data->signalled, 1, 0))
+ return 1;
+
+ return 0;
+}
+
+int pa_fdsem_get(pa_fdsem *f) {
+ pa_assert(f);
+
+#ifdef HAVE_SYS_EVENTFD_H
+ if (f->efd >= 0)
+ return f->efd;
+#endif
+
+ return f->fds[0];
+}
+
+int pa_fdsem_before_poll(pa_fdsem *f) {
+ pa_assert(f);
+
+ flush(f);
+
+ if (pa_atomic_cmpxchg(&f->data->signalled, 1, 0))
+ return -1;
+
+ pa_atomic_inc(&f->data->waiting);
+
+ if (pa_atomic_cmpxchg(&f->data->signalled, 1, 0)) {
+ pa_assert_se(pa_atomic_dec(&f->data->waiting) >= 1);
+ return -1;
+ }
+ return 0;
+}
+
+int pa_fdsem_after_poll(pa_fdsem *f) {
+ pa_assert(f);
+
+ pa_assert_se(pa_atomic_dec(&f->data->waiting) >= 1);
+
+ flush(f);
+
+ if (pa_atomic_cmpxchg(&f->data->signalled, 1, 0))
+ return 1;
+
+ return 0;
+}
diff --git a/src/pulsecore/fdsem.h b/src/pulsecore/fdsem.h
new file mode 100644
index 0000000..ae1bbe9
--- /dev/null
+++ b/src/pulsecore/fdsem.h
@@ -0,0 +1,53 @@
+#ifndef foopulsefdsemhfoo
+#define foopulsefdsemhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/types.h>
+
+/* A simple, asynchronous semaphore which uses fds for sleeping. In
+ * the best case all functions are lock-free unless sleeping is
+ * required. */
+
+#include <pulsecore/atomic.h>
+
+typedef struct pa_fdsem pa_fdsem;
+
+typedef struct pa_fdsem_data {
+ pa_atomic_t waiting;
+ pa_atomic_t signalled;
+ pa_atomic_t in_pipe;
+} pa_fdsem_data;
+
+pa_fdsem *pa_fdsem_new(void);
+pa_fdsem *pa_fdsem_open_shm(pa_fdsem_data *data, int event_fd);
+pa_fdsem *pa_fdsem_new_shm(pa_fdsem_data *data);
+void pa_fdsem_free(pa_fdsem *f);
+
+void pa_fdsem_post(pa_fdsem *f);
+void pa_fdsem_wait(pa_fdsem *f);
+int pa_fdsem_try(pa_fdsem *f);
+
+int pa_fdsem_get(pa_fdsem *f);
+
+int pa_fdsem_before_poll(pa_fdsem *f);
+int pa_fdsem_after_poll(pa_fdsem *f);
+
+#endif
diff --git a/src/pulsecore/ffmpeg/avcodec.h b/src/pulsecore/ffmpeg/avcodec.h
new file mode 100644
index 0000000..079c252
--- /dev/null
+++ b/src/pulsecore/ffmpeg/avcodec.h
@@ -0,0 +1,81 @@
+/*
+ * copyright (c) 2001 Fabrice Bellard
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef AVCODEC_H
+#define AVCODEC_H
+
+/* Just a heavily bastardized version of the original file from
+ * ffmpeg, just enough to get resample2.c to compile without
+ * modification -- Lennart */
+
+#if !defined(PACKAGE) && defined(HAVE_CONFIG_H)
+#include <config.h>
+#endif
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <math.h>
+#include <string.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#define av_mallocz(l) calloc(1, (l))
+#define av_malloc(l) malloc(l)
+#define av_realloc(p,l) realloc((p),(l))
+#define av_free(p) free(p)
+
+static inline void av_freep(void *k) {
+ void **p = k;
+
+ if (p) {
+ free(*p);
+ *p = NULL;
+ }
+}
+
+static inline int av_clip(int a, int amin, int amax)
+{
+ if (a < amin) return amin;
+ else if (a > amax) return amax;
+ else return a;
+}
+
+#define av_log(a,b,c)
+
+#define FFABS(a) ((a) >= 0 ? (a) : (-(a)))
+#define FFSIGN(a) ((a) > 0 ? 1 : -1)
+
+#define FFMAX(a,b) ((a) > (b) ? (a) : (b))
+#define FFMIN(a,b) ((a) > (b) ? (b) : (a))
+
+struct AVResampleContext;
+struct AVResampleContext *av_resample_init(int out_rate, int in_rate, int filter_length, int log2_phase_count, int linear, double cutoff);
+int av_resample(struct AVResampleContext *c, short *dst, short *src, int *consumed, int src_size, int dst_size, int update_ctx);
+void av_resample_compensate(struct AVResampleContext *c, int sample_delta, int compensation_distance);
+void av_resample_close(struct AVResampleContext *c);
+void av_build_filter(int16_t *filter, double factor, int tap_count, int phase_count, int scale, int type);
+
+/*
+ * crude lrintf for non-C99 systems.
+ */
+#ifndef HAVE_LRINTF
+#define lrintf(x) ((long int)(x))
+#endif
+
+#endif /* AVCODEC_H */
diff --git a/src/pulsecore/ffmpeg/dsputil.h b/src/pulsecore/ffmpeg/dsputil.h
new file mode 100644
index 0000000..8da742d
--- /dev/null
+++ b/src/pulsecore/ffmpeg/dsputil.h
@@ -0,0 +1 @@
+/* empty file, just here to allow us to compile an unmodified resampler2.c */
diff --git a/src/pulsecore/ffmpeg/resample2.c b/src/pulsecore/ffmpeg/resample2.c
new file mode 100644
index 0000000..a18f96e
--- /dev/null
+++ b/src/pulsecore/ffmpeg/resample2.c
@@ -0,0 +1,298 @@
+/*
+ * audio resampling
+ * Copyright (c) 2004 Michael Niedermayer <michaelni@gmx.at>
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @file libavcodec/resample2.c
+ * audio resampling
+ * @author Michael Niedermayer <michaelni@gmx.at>
+ */
+
+#include "avcodec.h"
+#include "dsputil.h"
+
+#ifndef CONFIG_RESAMPLE_HP
+#define FILTER_SHIFT 15
+
+#define FELEM int16_t
+#define FELEM2 int32_t
+#define FELEML int64_t
+#define FELEM_MAX INT16_MAX
+#define FELEM_MIN INT16_MIN
+#define WINDOW_TYPE 9
+#elif !defined(CONFIG_RESAMPLE_AUDIOPHILE_KIDDY_MODE)
+#define FILTER_SHIFT 30
+
+#define FELEM int32_t
+#define FELEM2 int64_t
+#define FELEML int64_t
+#define FELEM_MAX INT32_MAX
+#define FELEM_MIN INT32_MIN
+#define WINDOW_TYPE 12
+#else
+#define FILTER_SHIFT 0
+
+#define FELEM double
+#define FELEM2 double
+#define FELEML double
+#define WINDOW_TYPE 24
+#endif
+
+
+typedef struct AVResampleContext{
+ FELEM *filter_bank;
+ int filter_length;
+ int ideal_dst_incr;
+ int dst_incr;
+ int index;
+ int frac;
+ int src_incr;
+ int compensation_distance;
+ int phase_shift;
+ int phase_mask;
+ int linear;
+}AVResampleContext;
+
+/**
+ * 0th order modified bessel function of the first kind.
+ */
+static double bessel(double x){
+ double v=1;
+ double t=1;
+ int i;
+
+ x= x*x/4;
+ for(i=1; i<50; i++){
+ t *= x/(i*i);
+ v += t;
+ }
+ return v;
+}
+
+/**
+ * builds a polyphase filterbank.
+ * @param factor resampling factor
+ * @param scale wanted sum of coefficients for each filter
+ * @param type 0->cubic, 1->blackman nuttall windowed sinc, 2..16->kaiser windowed sinc beta=2..16
+ */
+void av_build_filter(FELEM *filter, double factor, int tap_count, int phase_count, int scale, int type){
+ int ph, i;
+ double x, y, w, tab[tap_count];
+ const int center= (tap_count-1)/2;
+
+ /* if upsampling, only need to interpolate, no filter */
+ if (factor > 1.0)
+ factor = 1.0;
+
+ for(ph=0;ph<phase_count;ph++) {
+ double norm = 0;
+ for(i=0;i<tap_count;i++) {
+ x = M_PI * ((double)(i - center) - (double)ph / phase_count) * factor;
+ if (x == 0) y = 1.0;
+ else y = sin(x) / x;
+ switch(type){
+ case 0:{
+ const float d= -0.5; //first order derivative = -0.5
+ x = fabs(((double)(i - center) - (double)ph / phase_count) * factor);
+ if(x<1.0) y= 1 - 3*x*x + 2*x*x*x + d*( -x*x + x*x*x);
+ else y= d*(-4 + 8*x - 5*x*x + x*x*x);
+ break;}
+ case 1:
+ w = 2.0*x / (factor*tap_count) + M_PI;
+ y *= 0.3635819 - 0.4891775 * cos(w) + 0.1365995 * cos(2*w) - 0.0106411 * cos(3*w);
+ break;
+ default:
+ w = 2.0*x / (factor*tap_count*M_PI);
+ y *= bessel(type*sqrt(FFMAX(1-w*w, 0)));
+ break;
+ }
+
+ tab[i] = y;
+ norm += y;
+ }
+
+ /* normalize so that an uniform color remains the same */
+ for(i=0;i<tap_count;i++) {
+#ifdef CONFIG_RESAMPLE_AUDIOPHILE_KIDDY_MODE
+ filter[ph * tap_count + i] = tab[i] / norm;
+#else
+ filter[ph * tap_count + i] = av_clip(lrintf(tab[i] * scale / norm), FELEM_MIN, FELEM_MAX);
+#endif
+ }
+ }
+#if 0
+ {
+#define LEN 1024
+ int j,k;
+ double sine[LEN + tap_count];
+ double filtered[LEN];
+ double maxff=-2, minff=2, maxsf=-2, minsf=2;
+ for(i=0; i<LEN; i++){
+ double ss=0, sf=0, ff=0;
+ for(j=0; j<LEN+tap_count; j++)
+ sine[j]= cos(i*j*M_PI/LEN);
+ for(j=0; j<LEN; j++){
+ double sum=0;
+ ph=0;
+ for(k=0; k<tap_count; k++)
+ sum += filter[ph * tap_count + k] * sine[k+j];
+ filtered[j]= sum / (1<<FILTER_SHIFT);
+ ss+= sine[j + center] * sine[j + center];
+ ff+= filtered[j] * filtered[j];
+ sf+= sine[j + center] * filtered[j];
+ }
+ ss= sqrt(2*ss/LEN);
+ ff= sqrt(2*ff/LEN);
+ sf= 2*sf/LEN;
+ maxff= FFMAX(maxff, ff);
+ minff= FFMIN(minff, ff);
+ maxsf= FFMAX(maxsf, sf);
+ minsf= FFMIN(minsf, sf);
+ if(i%11==0){
+ av_log(NULL, AV_LOG_ERROR, "i:%4d ss:%f ff:%13.6e-%13.6e sf:%13.6e-%13.6e\n", i, ss, maxff, minff, maxsf, minsf);
+ minff=minsf= 2;
+ maxff=maxsf= -2;
+ }
+ }
+ }
+#endif
+}
+
+AVResampleContext *av_resample_init(int out_rate, int in_rate, int filter_size, int phase_shift, int linear, double cutoff){
+ AVResampleContext *c= av_mallocz(sizeof(AVResampleContext));
+ double factor= FFMIN(out_rate * cutoff / in_rate, 1.0);
+ int phase_count= 1<<phase_shift;
+
+ c->phase_shift= phase_shift;
+ c->phase_mask= phase_count-1;
+ c->linear= linear;
+
+ c->filter_length= FFMAX((int)ceil(filter_size/factor), 1);
+ c->filter_bank= av_mallocz(c->filter_length*(phase_count+1)*sizeof(FELEM));
+ av_build_filter(c->filter_bank, factor, c->filter_length, phase_count, 1<<FILTER_SHIFT, WINDOW_TYPE);
+ memcpy(&c->filter_bank[c->filter_length*phase_count+1], c->filter_bank, (c->filter_length-1)*sizeof(FELEM));
+ c->filter_bank[c->filter_length*phase_count]= c->filter_bank[c->filter_length - 1];
+
+ c->src_incr= out_rate;
+ c->ideal_dst_incr= c->dst_incr= in_rate * phase_count;
+ c->index= -phase_count*((c->filter_length-1)/2);
+
+ return c;
+}
+
+void av_resample_close(AVResampleContext *c){
+ av_freep(&c->filter_bank);
+ av_freep(&c);
+}
+
+void av_resample_compensate(AVResampleContext *c, int sample_delta, int compensation_distance){
+// sample_delta += (c->ideal_dst_incr - c->dst_incr)*(int64_t)c->compensation_distance / c->ideal_dst_incr;
+ c->compensation_distance= compensation_distance;
+ c->dst_incr = c->ideal_dst_incr - c->ideal_dst_incr * (int64_t)sample_delta / compensation_distance;
+}
+
+int av_resample(AVResampleContext *c, short *dst, short *src, int *consumed, int src_size, int dst_size, int update_ctx){
+ int dst_index, i;
+ int index= c->index;
+ int frac= c->frac;
+ int dst_incr_frac= c->dst_incr % c->src_incr;
+ int dst_incr= c->dst_incr / c->src_incr;
+ int compensation_distance= c->compensation_distance;
+
+ if(compensation_distance == 0 && c->filter_length == 1 && c->phase_shift==0){
+ int64_t index2= ((int64_t)index)<<32;
+ int64_t incr= (1LL<<32) * c->dst_incr / c->src_incr;
+ dst_size= FFMIN(dst_size, (src_size-1-index) * (int64_t)c->src_incr / c->dst_incr);
+
+ for(dst_index=0; dst_index < dst_size; dst_index++){
+ dst[dst_index] = src[index2>>32];
+ index2 += incr;
+ }
+ frac += dst_index * dst_incr_frac;
+ index += dst_index * dst_incr;
+ index += frac / c->src_incr;
+ frac %= c->src_incr;
+ }else{
+ for(dst_index=0; dst_index < dst_size; dst_index++){
+ FELEM *filter= c->filter_bank + c->filter_length*(index & c->phase_mask);
+ int sample_index= index >> c->phase_shift;
+ FELEM2 val=0;
+
+ if(sample_index < 0){
+ for(i=0; i<c->filter_length; i++)
+ val += src[FFABS(sample_index + i) % src_size] * filter[i];
+ }else if(sample_index + c->filter_length > src_size){
+ break;
+ }else if(c->linear){
+ FELEM2 v2=0;
+ for(i=0; i<c->filter_length; i++){
+ val += src[sample_index + i] * (FELEM2)filter[i];
+ v2 += src[sample_index + i] * (FELEM2)filter[i + c->filter_length];
+ }
+ val+=(v2-val)*(FELEML)frac / c->src_incr;
+ }else{
+ for(i=0; i<c->filter_length; i++){
+ val += src[sample_index + i] * (FELEM2)filter[i];
+ }
+ }
+
+#ifdef CONFIG_RESAMPLE_AUDIOPHILE_KIDDY_MODE
+ dst[dst_index] = av_clip_int16(lrintf(val));
+#else
+ val = (val + (1<<(FILTER_SHIFT-1)))>>FILTER_SHIFT;
+ dst[dst_index] = (unsigned)(val + 32768) > 65535 ? (val>>31) ^ 32767 : val;
+#endif
+
+ frac += dst_incr_frac;
+ index += dst_incr;
+ if(frac >= c->src_incr){
+ frac -= c->src_incr;
+ index++;
+ }
+
+ if(dst_index + 1 == compensation_distance){
+ compensation_distance= 0;
+ dst_incr_frac= c->ideal_dst_incr % c->src_incr;
+ dst_incr= c->ideal_dst_incr / c->src_incr;
+ }
+ }
+ }
+ *consumed= FFMAX(index, 0) >> c->phase_shift;
+ if(index>=0) index &= c->phase_mask;
+
+ if(compensation_distance){
+ compensation_distance -= dst_index;
+ assert(compensation_distance > 0);
+ }
+ if(update_ctx){
+ c->frac= frac;
+ c->index= index;
+ c->dst_incr= dst_incr_frac + c->src_incr*dst_incr;
+ c->compensation_distance= compensation_distance;
+ }
+#if 0
+ if(update_ctx && !c->compensation_distance){
+#undef rand
+ av_resample_compensate(c, rand() % (8000*2) - 8000, 8000*2);
+av_log(NULL, AV_LOG_DEBUG, "%d %d %d\n", c->dst_incr, c->ideal_dst_incr, c->compensation_distance);
+ }
+#endif
+
+ return dst_index;
+}
diff --git a/src/pulsecore/filter/LICENSE.WEBKIT b/src/pulsecore/filter/LICENSE.WEBKIT
new file mode 100644
index 0000000..2f69d9f
--- /dev/null
+++ b/src/pulsecore/filter/LICENSE.WEBKIT
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
diff --git a/src/pulsecore/filter/biquad.c b/src/pulsecore/filter/biquad.c
new file mode 100644
index 0000000..3205e7c
--- /dev/null
+++ b/src/pulsecore/filter/biquad.c
@@ -0,0 +1,117 @@
+/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/* Copyright (C) 2010 Google Inc. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE.WEBKIT file.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulsecore/macro.h>
+
+#include <math.h>
+#include "biquad.h"
+
+#ifndef M_PI
+#define M_PI 3.14159265358979323846
+#endif
+
+#ifndef M_SQRT2
+#define M_SQRT2 1.41421356237309504880
+#endif
+
+static void set_coefficient(struct biquad *bq, double b0, double b1, double b2,
+ double a0, double a1, double a2)
+{
+ double a0_inv = 1 / a0;
+ bq->b0 = b0 * a0_inv;
+ bq->b1 = b1 * a0_inv;
+ bq->b2 = b2 * a0_inv;
+ bq->a1 = a1 * a0_inv;
+ bq->a2 = a2 * a0_inv;
+}
+
+static void biquad_lowpass(struct biquad *bq, double cutoff)
+{
+ /* Limit cutoff to 0 to 1. */
+ cutoff = PA_MIN(cutoff, 1.0);
+ cutoff = PA_MAX(0.0, cutoff);
+
+ if (cutoff >= 1.0) {
+ /* When cutoff is 1, the z-transform is 1. */
+ set_coefficient(bq, 1, 0, 0, 1, 0, 0);
+ } else if (cutoff > 0) {
+ /* Compute biquad coefficients for lowpass filter */
+ double theta = M_PI * cutoff;
+ double sn = 0.5 * M_SQRT2 * sin(theta);
+ double beta = 0.5 * (1 - sn) / (1 + sn);
+ double gamma_coeff = (0.5 + beta) * cos(theta);
+ double alpha = 0.25 * (0.5 + beta - gamma_coeff);
+
+ double b0 = 2 * alpha;
+ double b1 = 2 * 2 * alpha;
+ double b2 = 2 * alpha;
+ double a1 = 2 * -gamma_coeff;
+ double a2 = 2 * beta;
+
+ set_coefficient(bq, b0, b1, b2, 1, a1, a2);
+ } else {
+ /* When cutoff is zero, nothing gets through the filter, so set
+ * coefficients up correctly.
+ */
+ set_coefficient(bq, 0, 0, 0, 1, 0, 0);
+ }
+}
+
+static void biquad_highpass(struct biquad *bq, double cutoff)
+{
+ /* Limit cutoff to 0 to 1. */
+ cutoff = PA_MIN(cutoff, 1.0);
+ cutoff = PA_MAX(0.0, cutoff);
+
+ if (cutoff >= 1.0) {
+ /* The z-transform is 0. */
+ set_coefficient(bq, 0, 0, 0, 1, 0, 0);
+ } else if (cutoff > 0) {
+ /* Compute biquad coefficients for highpass filter */
+ double theta = M_PI * cutoff;
+ double sn = 0.5 * M_SQRT2 * sin(theta);
+ double beta = 0.5 * (1 - sn) / (1 + sn);
+ double gamma_coeff = (0.5 + beta) * cos(theta);
+ double alpha = 0.25 * (0.5 + beta + gamma_coeff);
+
+ double b0 = 2 * alpha;
+ double b1 = 2 * -2 * alpha;
+ double b2 = 2 * alpha;
+ double a1 = 2 * -gamma_coeff;
+ double a2 = 2 * beta;
+
+ set_coefficient(bq, b0, b1, b2, 1, a1, a2);
+ } else {
+ /* When cutoff is zero, we need to be careful because the above
+ * gives a quadratic divided by the same quadratic, with poles
+ * and zeros on the unit circle in the same place. When cutoff
+ * is zero, the z-transform is 1.
+ */
+ set_coefficient(bq, 1, 0, 0, 1, 0, 0);
+ }
+}
+
+void biquad_set(struct biquad *bq, enum biquad_type type, double freq)
+{
+
+ switch (type) {
+ case BQ_LOWPASS:
+ biquad_lowpass(bq, freq);
+ break;
+ case BQ_HIGHPASS:
+ biquad_highpass(bq, freq);
+ break;
+ }
+}
diff --git a/src/pulsecore/filter/biquad.h b/src/pulsecore/filter/biquad.h
new file mode 100644
index 0000000..bb8f2fb
--- /dev/null
+++ b/src/pulsecore/filter/biquad.h
@@ -0,0 +1,45 @@
+/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef BIQUAD_H_
+#define BIQUAD_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* The biquad filter parameters. The transfer function H(z) is (b0 + b1 * z^(-1)
+ * + b2 * z^(-2)) / (1 + a1 * z^(-1) + a2 * z^(-2)). The previous two inputs
+ * are stored in x1 and x2, and the previous two outputs are stored in y1 and
+ * y2.
+ *
+ * We use double during the coefficients calculation for better accurary, but
+ * float is used during the actual filtering for faster computation.
+ */
+struct biquad {
+ float b0, b1, b2;
+ float a1, a2;
+};
+
+/* The type of the biquad filters */
+enum biquad_type {
+ BQ_LOWPASS,
+ BQ_HIGHPASS,
+};
+
+/* Initialize a biquad filter parameters from its type and parameters.
+ * Args:
+ * bq - The biquad filter we want to set.
+ * type - The type of the biquad filter.
+ * frequency - The value should be in the range [0, 1]. It is relative to
+ * half of the sampling rate.
+ */
+void biquad_set(struct biquad *bq, enum biquad_type type, double freq);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* BIQUAD_H_ */
diff --git a/src/pulsecore/filter/crossover.c b/src/pulsecore/filter/crossover.c
new file mode 100644
index 0000000..dab34af
--- /dev/null
+++ b/src/pulsecore/filter/crossover.c
@@ -0,0 +1,97 @@
+/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulsecore/macro.h>
+
+#include "crossover.h"
+
+void lr4_set(struct lr4 *lr4, enum biquad_type type, float freq)
+{
+ biquad_set(&lr4->bq, type, freq);
+ lr4->x1 = 0;
+ lr4->x2 = 0;
+ lr4->y1 = 0;
+ lr4->y2 = 0;
+ lr4->z1 = 0;
+ lr4->z2 = 0;
+}
+
+void lr4_process_float32(struct lr4 *lr4, int samples, int channels, float *src, float *dest)
+{
+ float lx1 = lr4->x1;
+ float lx2 = lr4->x2;
+ float ly1 = lr4->y1;
+ float ly2 = lr4->y2;
+ float lz1 = lr4->z1;
+ float lz2 = lr4->z2;
+ float lb0 = lr4->bq.b0;
+ float lb1 = lr4->bq.b1;
+ float lb2 = lr4->bq.b2;
+ float la1 = lr4->bq.a1;
+ float la2 = lr4->bq.a2;
+
+ int i;
+ for (i = 0; i < samples * channels; i += channels) {
+ float x, y, z;
+ x = src[i];
+ y = lb0*x + lb1*lx1 + lb2*lx2 - la1*ly1 - la2*ly2;
+ z = lb0*y + lb1*ly1 + lb2*ly2 - la1*lz1 - la2*lz2;
+ lx2 = lx1;
+ lx1 = x;
+ ly2 = ly1;
+ ly1 = y;
+ lz2 = lz1;
+ lz1 = z;
+ dest[i] = z;
+ }
+
+ lr4->x1 = lx1;
+ lr4->x2 = lx2;
+ lr4->y1 = ly1;
+ lr4->y2 = ly2;
+ lr4->z1 = lz1;
+ lr4->z2 = lz2;
+}
+
+void lr4_process_s16(struct lr4 *lr4, int samples, int channels, short *src, short *dest)
+{
+ float lx1 = lr4->x1;
+ float lx2 = lr4->x2;
+ float ly1 = lr4->y1;
+ float ly2 = lr4->y2;
+ float lz1 = lr4->z1;
+ float lz2 = lr4->z2;
+ float lb0 = lr4->bq.b0;
+ float lb1 = lr4->bq.b1;
+ float lb2 = lr4->bq.b2;
+ float la1 = lr4->bq.a1;
+ float la2 = lr4->bq.a2;
+
+ int i;
+ for (i = 0; i < samples * channels; i += channels) {
+ float x, y, z;
+ x = src[i];
+ y = lb0*x + lb1*lx1 + lb2*lx2 - la1*ly1 - la2*ly2;
+ z = lb0*y + lb1*ly1 + lb2*ly2 - la1*lz1 - la2*lz2;
+ lx2 = lx1;
+ lx1 = x;
+ ly2 = ly1;
+ ly1 = y;
+ lz2 = lz1;
+ lz1 = z;
+ dest[i] = PA_CLAMP_UNLIKELY((int) z, -0x8000, 0x7fff);
+ }
+
+ lr4->x1 = lx1;
+ lr4->x2 = lx2;
+ lr4->y1 = ly1;
+ lr4->y2 = ly2;
+ lr4->z1 = lz1;
+ lr4->z2 = lz2;
+}
diff --git a/src/pulsecore/filter/crossover.h b/src/pulsecore/filter/crossover.h
new file mode 100644
index 0000000..c5c9765
--- /dev/null
+++ b/src/pulsecore/filter/crossover.h
@@ -0,0 +1,29 @@
+/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef CROSSOVER_H_
+#define CROSSOVER_H_
+
+#include "biquad.h"
+/* An LR4 filter is two biquads with the same parameters connected in series:
+ *
+ * x -- [BIQUAD] -- y -- [BIQUAD] -- z
+ *
+ * Both biquad filter has the same parameter b[012] and a[12],
+ * The variable [xyz][12] keep the history values.
+ */
+struct lr4 {
+ struct biquad bq;
+ float x1, x2;
+ float y1, y2;
+ float z1, z2;
+};
+
+void lr4_set(struct lr4 *lr4, enum biquad_type type, float freq);
+
+void lr4_process_float32(struct lr4 *lr4, int samples, int channels, float *src, float *dest);
+void lr4_process_s16(struct lr4 *lr4, int samples, int channels, short *src, short *dest);
+
+#endif /* CROSSOVER_H_ */
diff --git a/src/pulsecore/filter/lfe-filter.c b/src/pulsecore/filter/lfe-filter.c
new file mode 100644
index 0000000..c0b1eb0
--- /dev/null
+++ b/src/pulsecore/filter/lfe-filter.c
@@ -0,0 +1,201 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2014 David Henningsson, Canonical Ltd.
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "lfe-filter.h"
+#include <pulse/xmalloc.h>
+#include <pulsecore/flist.h>
+#include <pulsecore/llist.h>
+#include <pulsecore/filter/biquad.h>
+#include <pulsecore/filter/crossover.h>
+
+struct saved_state {
+ PA_LLIST_FIELDS(struct saved_state);
+ pa_memchunk chunk;
+ int64_t index;
+ struct lr4 lr4[PA_CHANNELS_MAX];
+};
+
+PA_STATIC_FLIST_DECLARE(lfe_state, 0, pa_xfree);
+
+/* An LR4 filter, implemented as a chain of two Butterworth filters.
+
+ Currently the channel map is fixed so that a highpass filter is applied to all
+ channels except for the LFE channel, where a lowpass filter is applied.
+ This works well for e g stereo to 2.1/5.1/7.1 scenarios, where the remap engine
+ has calculated the LFE channel to be the average of all source channels.
+*/
+
+struct pa_lfe_filter {
+ int64_t index;
+ PA_LLIST_HEAD(struct saved_state, saved);
+ float crossover;
+ pa_channel_map cm;
+ pa_sample_spec ss;
+ size_t maxrewind;
+ bool active;
+ struct lr4 lr4[PA_CHANNELS_MAX];
+};
+
+static void remove_state(pa_lfe_filter_t *f, struct saved_state *s) {
+ PA_LLIST_REMOVE(struct saved_state, f->saved, s);
+ pa_memblock_unref(s->chunk.memblock);
+ pa_xfree(s);
+}
+
+pa_lfe_filter_t * pa_lfe_filter_new(const pa_sample_spec* ss, const pa_channel_map* cm, float crossover_freq, size_t maxrewind) {
+
+ pa_lfe_filter_t *f = pa_xnew0(struct pa_lfe_filter, 1);
+ f->crossover = crossover_freq;
+ f->cm = *cm;
+ f->ss = *ss;
+ f->maxrewind = maxrewind;
+ pa_lfe_filter_update_rate(f, ss->rate);
+ return f;
+}
+
+void pa_lfe_filter_free(pa_lfe_filter_t *f) {
+ while (f->saved)
+ remove_state(f, f->saved);
+
+ pa_xfree(f);
+}
+
+void pa_lfe_filter_reset(pa_lfe_filter_t *f) {
+ pa_lfe_filter_update_rate(f, f->ss.rate);
+}
+
+static void process_block(pa_lfe_filter_t *f, pa_memchunk *buf, bool store_result) {
+ int samples = buf->length / pa_frame_size(&f->ss);
+
+ void *garbage = store_result ? NULL : pa_xmalloc(buf->length);
+
+ if (f->ss.format == PA_SAMPLE_FLOAT32NE) {
+ int i;
+ float *data = pa_memblock_acquire_chunk(buf);
+ for (i = 0; i < f->cm.channels; i++)
+ lr4_process_float32(&f->lr4[i], samples, f->cm.channels, &data[i], garbage ? garbage : &data[i]);
+ pa_memblock_release(buf->memblock);
+ }
+ else if (f->ss.format == PA_SAMPLE_S16NE) {
+ int i;
+ short *data = pa_memblock_acquire_chunk(buf);
+ for (i = 0; i < f->cm.channels; i++)
+ lr4_process_s16(&f->lr4[i], samples, f->cm.channels, &data[i], garbage ? garbage : &data[i]);
+ pa_memblock_release(buf->memblock);
+ }
+ else pa_assert_not_reached();
+
+ pa_xfree(garbage);
+ f->index += samples;
+}
+
+pa_memchunk * pa_lfe_filter_process(pa_lfe_filter_t *f, pa_memchunk *buf) {
+ pa_mempool *pool;
+ struct saved_state *s, *s2;
+ void *data;
+
+ if (!f->active || !buf->length)
+ return buf;
+
+ /* Remove old states (FIXME: we could do better than searching the entire array here?) */
+ PA_LLIST_FOREACH_SAFE(s, s2, f->saved)
+ if (s->index + (int64_t) (s->chunk.length / pa_frame_size(&f->ss) + f->maxrewind) < f->index)
+ remove_state(f, s);
+
+ /* Insert our existing state into the flist */
+ if ((s = pa_flist_pop(PA_STATIC_FLIST_GET(lfe_state))) == NULL)
+ s = pa_xnew(struct saved_state, 1);
+ PA_LLIST_INIT(struct saved_state, s);
+
+ /* TODO: This actually memcpys the entire chunk into a new allocation, because we need to retain the original
+ in case of rewinding. Investigate whether this can be avoided. */
+ data = pa_memblock_acquire_chunk(buf);
+ pool = pa_memblock_get_pool(buf->memblock);
+ s->chunk.memblock = pa_memblock_new_malloced(pool, pa_xmemdup(data, buf->length), buf->length);
+ s->chunk.length = buf->length;
+ s->chunk.index = 0;
+ pa_memblock_release(buf->memblock);
+ pa_mempool_unref(pool), pool = NULL;
+
+ s->index = f->index;
+ memcpy(s->lr4, f->lr4, sizeof(struct lr4) * f->cm.channels);
+ PA_LLIST_PREPEND(struct saved_state, f->saved, s);
+
+ process_block(f, buf, true);
+ return buf;
+}
+
+void pa_lfe_filter_update_rate(pa_lfe_filter_t *f, uint32_t new_rate) {
+ int i;
+ float biquad_freq = f->crossover / (new_rate / 2);
+
+ while (f->saved)
+ remove_state(f, f->saved);
+
+ f->index = 0;
+ f->ss.rate = new_rate;
+ if (biquad_freq <= 0 || biquad_freq >= 1) {
+ pa_log_warn("Crossover frequency (%f) outside range for sample rate %d", f->crossover, new_rate);
+ f->active = false;
+ return;
+ }
+
+ for (i = 0; i < f->cm.channels; i++)
+ lr4_set(&f->lr4[i], f->cm.map[i] == PA_CHANNEL_POSITION_LFE ? BQ_LOWPASS : BQ_HIGHPASS, biquad_freq);
+
+ f->active = true;
+}
+
+void pa_lfe_filter_rewind(pa_lfe_filter_t *f, size_t amount) {
+ struct saved_state *i, *s = NULL;
+ size_t samples = amount / pa_frame_size(&f->ss);
+ f->index -= samples;
+
+ /* Find the closest saved position */
+ PA_LLIST_FOREACH(i, f->saved) {
+ if (i->index > f->index)
+ continue;
+ if (s == NULL || i->index > s->index)
+ s = i;
+ }
+ if (s == NULL) {
+ pa_log_debug("Rewinding LFE filter %zu samples to position %lli. No saved state found", samples, (long long) f->index);
+ pa_lfe_filter_update_rate(f, f->ss.rate);
+ return;
+ }
+ pa_log_debug("Rewinding LFE filter %zu samples to position %lli. Found saved state at position %lli",
+ samples, (long long) f->index, (long long) s->index);
+ memcpy(f->lr4, s->lr4, sizeof(struct lr4) * f->cm.channels);
+
+ /* now fast forward to the actual position */
+ if (f->index > s->index) {
+ pa_memchunk x = s->chunk;
+ x.length = (f->index - s->index) * pa_frame_size(&f->ss);
+ if (x.length > s->chunk.length) {
+ pa_log_error("Hole in stream, cannot fast forward LFE filter");
+ return;
+ }
+ f->index = s->index;
+ process_block(f, &x, false);
+ }
+}
diff --git a/src/pulsecore/filter/lfe-filter.h b/src/pulsecore/filter/lfe-filter.h
new file mode 100644
index 0000000..54d695b
--- /dev/null
+++ b/src/pulsecore/filter/lfe-filter.h
@@ -0,0 +1,39 @@
+#ifndef foolfefilterhfoo
+#define foolfefilterhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2014 David Henningsson, Canonical Ltd.
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <pulse/sample.h>
+#include <pulse/channelmap.h>
+#include <pulsecore/memchunk.h>
+#include <pulsecore/memblockq.h>
+
+typedef struct pa_lfe_filter pa_lfe_filter_t;
+
+pa_lfe_filter_t * pa_lfe_filter_new(const pa_sample_spec* ss, const pa_channel_map* cm, float crossover_freq, size_t maxrewind);
+void pa_lfe_filter_free(pa_lfe_filter_t *);
+void pa_lfe_filter_reset(pa_lfe_filter_t *);
+void pa_lfe_filter_rewind(pa_lfe_filter_t *, size_t amount);
+pa_memchunk * pa_lfe_filter_process(pa_lfe_filter_t *filter, pa_memchunk *buf);
+void pa_lfe_filter_update_rate(pa_lfe_filter_t *, uint32_t new_rate);
+
+#endif
diff --git a/src/pulsecore/flist.c b/src/pulsecore/flist.c
new file mode 100644
index 0000000..8d2e643
--- /dev/null
+++ b/src/pulsecore/flist.c
@@ -0,0 +1,177 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2006-2008 Lennart Poettering
+ Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+ Copyright (C) 2012 Canonical Ltd.
+
+ Contact: Jyri Sarha <Jyri.Sarha@nokia.com>
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/atomic.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/macro.h>
+
+#include "flist.h"
+
+#define FLIST_SIZE 256
+
+/* Atomic table indices contain
+ sign bit = if set, indicates empty/NULL value
+ tag bits (to avoid the ABA problem)
+ actual index bits
+*/
+
+/* Lock free single linked list element. */
+struct pa_flist_elem {
+ pa_atomic_t next;
+ pa_atomic_ptr_t ptr;
+};
+
+typedef struct pa_flist_elem pa_flist_elem;
+
+struct pa_flist {
+ char *name;
+ unsigned size;
+
+ pa_atomic_t current_tag;
+ int index_mask;
+ int tag_shift;
+ int tag_mask;
+
+ /* Stack that contains pointers stored into free list */
+ pa_atomic_t stored;
+ /* Stack that contains empty list elements */
+ pa_atomic_t empty;
+ pa_flist_elem table[];
+};
+
+/* Lock free pop from linked list stack */
+static pa_flist_elem *stack_pop(pa_flist *flist, pa_atomic_t *list) {
+ pa_flist_elem *popped;
+ int idx;
+ pa_assert(list);
+
+ do {
+ idx = pa_atomic_load(list);
+ if (idx < 0)
+ return NULL;
+ popped = &flist->table[idx & flist->index_mask];
+ } while (!pa_atomic_cmpxchg(list, idx, pa_atomic_load(&popped->next)));
+
+ return popped;
+}
+
+/* Lock free push to linked list stack */
+static void stack_push(pa_flist *flist, pa_atomic_t *list, pa_flist_elem *new_elem) {
+ int tag, newindex, next;
+ pa_assert(list);
+
+ tag = pa_atomic_inc(&flist->current_tag);
+ newindex = new_elem - flist->table;
+ pa_assert(newindex >= 0 && newindex < (int) flist->size);
+ newindex |= (tag << flist->tag_shift) & flist->tag_mask;
+
+ do {
+ next = pa_atomic_load(list);
+ pa_atomic_store(&new_elem->next, next);
+ } while (!pa_atomic_cmpxchg(list, next, newindex));
+}
+
+pa_flist *pa_flist_new_with_name(unsigned size, const char *name) {
+ pa_flist *l;
+ unsigned i;
+ pa_assert(name);
+
+ if (!size)
+ size = FLIST_SIZE;
+
+ l = pa_xmalloc0(sizeof(pa_flist) + sizeof(pa_flist_elem) * size);
+
+ l->name = pa_xstrdup(name);
+ l->size = size;
+
+ while (1 << l->tag_shift < (int) size)
+ l->tag_shift++;
+ l->index_mask = (1 << l->tag_shift) - 1;
+ l->tag_mask = INT_MAX - l->index_mask;
+
+ pa_atomic_store(&l->stored, -1);
+ pa_atomic_store(&l->empty, -1);
+ for (i=0; i < size; i++) {
+ stack_push(l, &l->empty, &l->table[i]);
+ }
+ return l;
+}
+
+pa_flist *pa_flist_new(unsigned size) {
+ return pa_flist_new_with_name(size, "unknown");
+}
+
+void pa_flist_free(pa_flist *l, pa_free_cb_t free_cb) {
+ pa_assert(l);
+ pa_assert(l->name);
+
+ if (free_cb) {
+ pa_flist_elem *elem;
+ while((elem = stack_pop(l, &l->stored)))
+ free_cb(pa_atomic_ptr_load(&elem->ptr));
+ }
+
+ pa_xfree(l->name);
+ pa_xfree(l);
+}
+
+int pa_flist_push(pa_flist *l, void *p) {
+ pa_flist_elem *elem;
+ pa_assert(l);
+ pa_assert(p);
+
+ elem = stack_pop(l, &l->empty);
+ if (elem == NULL) {
+ if (pa_log_ratelimit(PA_LOG_DEBUG))
+ pa_log_debug("%s flist is full (don't worry)", l->name);
+ return -1;
+ }
+ pa_atomic_ptr_store(&elem->ptr, p);
+ stack_push(l, &l->stored, elem);
+
+ return 0;
+}
+
+void* pa_flist_pop(pa_flist *l) {
+ pa_flist_elem *elem;
+ void *ptr;
+ pa_assert(l);
+
+ elem = stack_pop(l, &l->stored);
+ if (elem == NULL)
+ return NULL;
+
+ ptr = pa_atomic_ptr_load(&elem->ptr);
+
+ stack_push(l, &l->empty, elem);
+
+ return ptr;
+}
diff --git a/src/pulsecore/flist.h b/src/pulsecore/flist.h
new file mode 100644
index 0000000..0341208
--- /dev/null
+++ b/src/pulsecore/flist.h
@@ -0,0 +1,70 @@
+#ifndef foopulseflisthfoo
+#define foopulseflisthfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2006-2008 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulse/def.h>
+#include <pulse/gccmacro.h>
+
+#include <pulsecore/once.h>
+#include <pulsecore/core-util.h>
+
+/* A multiple-reader multipler-write lock-free free list implementation */
+
+typedef struct pa_flist pa_flist;
+
+pa_flist * pa_flist_new(unsigned size);
+/* Name string is copied and added to flist structure. The original is
+ * responsibility of the caller. The name is only used for debug printing. */
+pa_flist * pa_flist_new_with_name(unsigned size, const char *name);
+void pa_flist_free(pa_flist *l, pa_free_cb_t free_cb);
+
+/* Please note that this routine might fail! */
+int pa_flist_push(pa_flist*l, void *p);
+void* pa_flist_pop(pa_flist*l);
+
+/* Please note that the destructor stuff is not really necessary, we do
+ * this just to make valgrind output more useful. */
+
+#define PA_STATIC_FLIST_DECLARE(name, size, free_cb) \
+ static struct { \
+ pa_flist *volatile flist; \
+ pa_once once; \
+ } name##_flist = { NULL, PA_ONCE_INIT }; \
+ static void name##_flist_init(void) { \
+ name##_flist.flist = \
+ pa_flist_new_with_name(size, __FILE__ ": " #name); \
+ } \
+ static inline pa_flist* name##_flist_get(void) { \
+ pa_run_once(&name##_flist.once, name##_flist_init); \
+ return name##_flist.flist; \
+ } \
+ static void name##_flist_destructor(void) PA_GCC_DESTRUCTOR; \
+ static void name##_flist_destructor(void) { \
+ if (!pa_in_valgrind()) \
+ return; \
+ if (name##_flist.flist) \
+ pa_flist_free(name##_flist.flist, (free_cb)); \
+ } \
+ struct __stupid_useless_struct_to_allow_trailing_semicolon
+
+#define PA_STATIC_FLIST_GET(name) (name##_flist_get())
+
+#endif
diff --git a/src/pulsecore/g711.c b/src/pulsecore/g711.c
new file mode 100644
index 0000000..aa2d703
--- /dev/null
+++ b/src/pulsecore/g711.c
@@ -0,0 +1,2531 @@
+/*
+ * This source code is a product of Sun Microsystems, Inc. and is provided
+ * for unrestricted use. Users may copy or modify this source code without
+ * charge.
+ *
+ * SUN SOURCE CODE IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING
+ * THE WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE.
+ *
+ * Sun source code is provided with no support and without any obligation on
+ * the part of Sun Microsystems, Inc. to assist in its use, correction,
+ * modification or enhancement.
+ *
+ * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE
+ * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY THIS SOFTWARE
+ * OR ANY PART THEREOF.
+ *
+ * In no event will Sun Microsystems, Inc. be liable for any lost revenue
+ * or profits or other special, indirect and consequential damages, even if
+ * Sun has been advised of the possibility of such damages.
+ *
+ * Sun Microsystems, Inc.
+ * 2550 Garcia Avenue
+ * Mountain View, California 94043
+ */
+
+/*
+ * g711.c
+ *
+ * u-law, A-law and linear PCM conversions.
+ */
+
+/*
+ * December 30, 1994:
+ * Functions linear2alaw, linear2ulaw have been updated to correctly
+ * convert unquantized 16 bit values.
+ * Tables for direct u- to A-law and A- to u-law conversions have been
+ * corrected.
+ * Borge Lindberg, Center for PersonKommunikation, Aalborg University.
+ * bli@cpk.auc.dk
+ *
+ */
+
+#include "g711.h"
+
+#define SIGN_BIT (0x80) /* Sign bit for a A-law byte. */
+#define QUANT_MASK (0xf) /* Quantization field mask. */
+#define NSEGS (8) /* Number of A-law segments. */
+#define SEG_SHIFT (4) /* Left shift for segment number. */
+#define SEG_MASK (0x70) /* Segment field mask. */
+
+#if !defined(FAST_ALAW_CONVERSION) || !defined(FAST_ULAW_CONVERSION)
+static int16_t seg_aend[8] = {0x1F, 0x3F, 0x7F, 0xFF,
+ 0x1FF, 0x3FF, 0x7FF, 0xFFF};
+static int16_t seg_uend[8] = {0x3F, 0x7F, 0xFF, 0x1FF,
+ 0x3FF, 0x7FF, 0xFFF, 0x1FFF};
+
+static int16_t search(
+ int16_t val,
+ int16_t *table,
+ int size)
+{
+ int i;
+
+ for (i = 0; i < size; i++) {
+ if (val <= *table++)
+ return (i);
+ }
+ return (size);
+}
+#endif /* !FAST_*_CONVERSION */
+
+#ifndef FAST_ALAW_CONVERSION
+/*
+ * linear2alaw() accepts an 13-bit signed integer and encodes it as A-law data
+ * stored in a unsigned char. This function should only be called with
+ * the data shifted such that it only contains information in the lower
+ * 13-bits.
+ *
+ * Linear Input Code Compressed Code
+ * ------------------------ ---------------
+ * 0000000wxyza 000wxyz
+ * 0000001wxyza 001wxyz
+ * 000001wxyzab 010wxyz
+ * 00001wxyzabc 011wxyz
+ * 0001wxyzabcd 100wxyz
+ * 001wxyzabcde 101wxyz
+ * 01wxyzabcdef 110wxyz
+ * 1wxyzabcdefg 111wxyz
+ *
+ * For further information see John C. Bellamy's Digital Telephony, 1982,
+ * John Wiley & Sons, pps 98-111 and 472-476.
+ */
+unsigned char st_13linear2alaw(
+ int16_t pcm_val) /* 2's complement (13-bit range) */
+{
+ int16_t mask;
+ short seg;
+ unsigned char aval;
+
+ /* Have calling software do it since its already doing a shift
+ * from 32-bits down to 16-bits.
+ */
+ /* pcm_val = pcm_val >> 3; */
+
+ /* A-law using even bit inversion */
+ if (pcm_val >= 0) {
+ mask = 0xD5; /* sign (7th) bit = 1 */
+ } else {
+ mask = 0x55; /* sign bit = 0 */
+ pcm_val = -pcm_val - 1;
+ }
+
+ /* Convert the scaled magnitude to segment number. */
+ seg = search(pcm_val, seg_aend, 8);
+
+ /* Combine the sign, segment, and quantization bits. */
+
+ if (seg >= 8) /* out of range, return maximum value. */
+ return (unsigned char) (0x7F ^ mask);
+ else {
+ aval = (unsigned char) seg << SEG_SHIFT;
+ if (seg < 2)
+ aval |= (pcm_val >> 1) & QUANT_MASK;
+ else
+ aval |= (pcm_val >> seg) & QUANT_MASK;
+ return (aval ^ mask);
+ }
+}
+
+/*
+ * alaw2linear() - Convert an A-law value to 16-bit signed linear PCM
+ *
+ */
+int16_t st_alaw2linear16(
+ unsigned char a_val)
+{
+ int16_t t;
+ int16_t seg;
+
+ a_val ^= 0x55;
+
+ t = (a_val & QUANT_MASK) << 4;
+ seg = ((unsigned)a_val & SEG_MASK) >> SEG_SHIFT;
+ switch (seg) {
+ case 0:
+ t += 8;
+ break;
+ case 1:
+ t += 0x108;
+ break;
+ default:
+ t += 0x108;
+ t <<= seg - 1;
+ }
+ return ((a_val & SIGN_BIT) ? t : -t);
+}
+#endif /* !FAST_ALAW_CONVERSION */
+
+#define BIAS (0x84) /* Bias for linear code. */
+#define CLIP 8159
+
+#ifndef FAST_ULAW_CONVERSION
+/*
+ * linear2ulaw() accepts a 14-bit signed integer and encodes it as u-law data
+ * stored in a unsigned char. This function should only be called with
+ * the data shifted such that it only contains information in the lower
+ * 14-bits.
+ *
+ * In order to simplify the encoding process, the original linear magnitude
+ * is biased by adding 33 which shifts the encoding range from (0 - 8158) to
+ * (33 - 8191). The result can be seen in the following encoding table:
+ *
+ * Biased Linear Input Code Compressed Code
+ * ------------------------ ---------------
+ * 00000001wxyza 000wxyz
+ * 0000001wxyzab 001wxyz
+ * 000001wxyzabc 010wxyz
+ * 00001wxyzabcd 011wxyz
+ * 0001wxyzabcde 100wxyz
+ * 001wxyzabcdef 101wxyz
+ * 01wxyzabcdefg 110wxyz
+ * 1wxyzabcdefgh 111wxyz
+ *
+ * Each biased linear code has a leading 1 which identifies the segment
+ * number. The value of the segment number is equal to 7 minus the number
+ * of leading 0's. The quantization interval is directly available as the
+ * four bits wxyz. * The trailing bits (a - h) are ignored.
+ *
+ * Ordinarily the complement of the resulting code word is used for
+ * transmission, and so the code word is complemented before it is returned.
+ *
+ * For further information see John C. Bellamy's Digital Telephony, 1982,
+ * John Wiley & Sons, pps 98-111 and 472-476.
+ */
+unsigned char st_14linear2ulaw(
+ int16_t pcm_val) /* 2's complement (14-bit range) */
+{
+ int16_t mask;
+ int16_t seg;
+ unsigned char uval;
+
+ /* Have calling software do it since its already doing a shift
+ * from 32-bits down to 16-bits.
+ */
+ /* pcm_val = pcm_val >> 2; */
+
+ /* u-law inverts all bits */
+ /* Get the sign and the magnitude of the value. */
+ if (pcm_val < 0) {
+ pcm_val = -pcm_val;
+ mask = 0x7F;
+ } else {
+ mask = 0xFF;
+ }
+ if ( pcm_val > CLIP ) pcm_val = CLIP; /* clip the magnitude */
+ pcm_val += (BIAS >> 2);
+
+ /* Convert the scaled magnitude to segment number. */
+ seg = search(pcm_val, seg_uend, 8);
+
+ /*
+ * Combine the sign, segment, quantization bits;
+ * and complement the code word.
+ */
+ if (seg >= 8) /* out of range, return maximum value. */
+ return (unsigned char) (0x7F ^ mask);
+ else {
+ uval = (unsigned char) (seg << 4) | ((pcm_val >> (seg + 1)) & 0xF);
+ return (uval ^ mask);
+ }
+
+}
+
+/*
+ * ulaw2linear() - Convert a u-law value to 16-bit linear PCM
+ *
+ * First, a biased linear code is derived from the code word. An unbiased
+ * output can then be obtained by subtracting 33 from the biased code.
+ *
+ * Note that this function expects to be passed the complement of the
+ * original code word. This is in keeping with ISDN conventions.
+ */
+int16_t st_ulaw2linear16(
+ unsigned char u_val)
+{
+ int16_t t;
+
+ /* Complement to obtain normal u-law value. */
+ u_val = ~u_val;
+
+ /*
+ * Extract and bias the quantization bits. Then
+ * shift up by the segment number and subtract out the bias.
+ */
+ t = ((u_val & QUANT_MASK) << 3) + BIAS;
+ t <<= ((unsigned)u_val & SEG_MASK) >> SEG_SHIFT;
+
+ return ((u_val & SIGN_BIT) ? (BIAS - t) : (t - BIAS));
+}
+#endif /* !FAST_ULAW_CONVERSION */
+
+#ifdef FAST_ALAW_CONVERSION
+
+int16_t _st_alaw2linear16[256] = {
+ -5504, -5248, -6016, -5760, -4480, -4224, -4992,
+ -4736, -7552, -7296, -8064, -7808, -6528, -6272,
+ -7040, -6784, -2752, -2624, -3008, -2880, -2240,
+ -2112, -2496, -2368, -3776, -3648, -4032, -3904,
+ -3264, -3136, -3520, -3392, -22016, -20992, -24064,
+ -23040, -17920, -16896, -19968, -18944, -30208, -29184,
+ -32256, -31232, -26112, -25088, -28160, -27136, -11008,
+ -10496, -12032, -11520, -8960, -8448, -9984, -9472,
+ -15104, -14592, -16128, -15616, -13056, -12544, -14080,
+ -13568, -344, -328, -376, -360, -280, -264,
+ -312, -296, -472, -456, -504, -488, -408,
+ -392, -440, -424, -88, -72, -120, -104,
+ -24, -8, -56, -40, -216, -200, -248,
+ -232, -152, -136, -184, -168, -1376, -1312,
+ -1504, -1440, -1120, -1056, -1248, -1184, -1888,
+ -1824, -2016, -1952, -1632, -1568, -1760, -1696,
+ -688, -656, -752, -720, -560, -528, -624,
+ -592, -944, -912, -1008, -976, -816, -784,
+ -880, -848, 5504, 5248, 6016, 5760, 4480,
+ 4224, 4992, 4736, 7552, 7296, 8064, 7808,
+ 6528, 6272, 7040, 6784, 2752, 2624, 3008,
+ 2880, 2240, 2112, 2496, 2368, 3776, 3648,
+ 4032, 3904, 3264, 3136, 3520, 3392, 22016,
+ 20992, 24064, 23040, 17920, 16896, 19968, 18944,
+ 30208, 29184, 32256, 31232, 26112, 25088, 28160,
+ 27136, 11008, 10496, 12032, 11520, 8960, 8448,
+ 9984, 9472, 15104, 14592, 16128, 15616, 13056,
+ 12544, 14080, 13568, 344, 328, 376, 360,
+ 280, 264, 312, 296, 472, 456, 504,
+ 488, 408, 392, 440, 424, 88, 72,
+ 120, 104, 24, 8, 56, 40, 216,
+ 200, 248, 232, 152, 136, 184, 168,
+ 1376, 1312, 1504, 1440, 1120, 1056, 1248,
+ 1184, 1888, 1824, 2016, 1952, 1632, 1568,
+ 1760, 1696, 688, 656, 752, 720, 560,
+ 528, 624, 592, 944, 912, 1008, 976,
+ 816, 784, 880, 848
+};
+
+uint8_t _st_13linear2alaw[0x2000] = {
+ 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a,
+ 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a,
+ 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a,
+ 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a,
+ 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a,
+ 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a,
+ 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a,
+ 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a,
+ 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a,
+ 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a,
+ 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2b, 0x2b, 0x2b, 0x2b,
+ 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b,
+ 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b,
+ 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b,
+ 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b,
+ 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b,
+ 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b,
+ 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b,
+ 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b,
+ 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b,
+ 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b,
+ 0x2b, 0x2b, 0x2b, 0x2b, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28,
+ 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28,
+ 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28,
+ 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28,
+ 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28,
+ 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28,
+ 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28,
+ 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28,
+ 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28,
+ 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28,
+ 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28,
+ 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29,
+ 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29,
+ 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29,
+ 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29,
+ 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29,
+ 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29,
+ 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29,
+ 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29,
+ 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29,
+ 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29,
+ 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x2e, 0x2e, 0x2e, 0x2e,
+ 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e,
+ 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e,
+ 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e,
+ 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e,
+ 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e,
+ 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e,
+ 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e,
+ 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e,
+ 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e,
+ 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e,
+ 0x2e, 0x2e, 0x2e, 0x2e, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f,
+ 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f,
+ 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f,
+ 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f,
+ 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f,
+ 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f,
+ 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f,
+ 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f,
+ 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f,
+ 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f,
+ 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f,
+ 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c,
+ 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c,
+ 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c,
+ 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c,
+ 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c,
+ 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c,
+ 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c,
+ 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c,
+ 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c,
+ 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c,
+ 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2d, 0x2d, 0x2d, 0x2d,
+ 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+ 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+ 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+ 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+ 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+ 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+ 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+ 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+ 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+ 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+ 0x2d, 0x2d, 0x2d, 0x2d, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23,
+ 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23,
+ 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23,
+ 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23,
+ 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23,
+ 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23,
+ 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23,
+ 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23,
+ 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23,
+ 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23,
+ 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x20, 0x20, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21,
+ 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21,
+ 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21,
+ 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21,
+ 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21,
+ 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21,
+ 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21,
+ 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21,
+ 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21,
+ 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21,
+ 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21,
+ 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26,
+ 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26,
+ 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26,
+ 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26,
+ 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26,
+ 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26,
+ 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26,
+ 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26,
+ 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26,
+ 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26,
+ 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x27, 0x27, 0x27, 0x27,
+ 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27,
+ 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27,
+ 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27,
+ 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27,
+ 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27,
+ 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27,
+ 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27,
+ 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27,
+ 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27,
+ 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27,
+ 0x27, 0x27, 0x27, 0x27, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24,
+ 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24,
+ 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24,
+ 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24,
+ 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24,
+ 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24,
+ 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24,
+ 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24,
+ 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24,
+ 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24,
+ 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24,
+ 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25,
+ 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25,
+ 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25,
+ 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25,
+ 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25,
+ 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25,
+ 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25,
+ 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25,
+ 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25,
+ 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25,
+ 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x3a, 0x3a, 0x3a, 0x3a,
+ 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a,
+ 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a,
+ 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a,
+ 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a,
+ 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a,
+ 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b,
+ 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b,
+ 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b,
+ 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b,
+ 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b,
+ 0x3b, 0x3b, 0x3b, 0x3b, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38,
+ 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38,
+ 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38,
+ 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38,
+ 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38,
+ 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x39, 0x39, 0x39, 0x39,
+ 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39,
+ 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39,
+ 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39,
+ 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39,
+ 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39,
+ 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e,
+ 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e,
+ 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e,
+ 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e,
+ 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e,
+ 0x3e, 0x3e, 0x3e, 0x3e, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f,
+ 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f,
+ 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f,
+ 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f,
+ 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f,
+ 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3c, 0x3c, 0x3c, 0x3c,
+ 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c,
+ 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c,
+ 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c,
+ 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c,
+ 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c,
+ 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d,
+ 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d,
+ 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d,
+ 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d,
+ 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d,
+ 0x3d, 0x3d, 0x3d, 0x3d, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32,
+ 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32,
+ 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32,
+ 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32,
+ 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32,
+ 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x33, 0x33, 0x33, 0x33,
+ 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33,
+ 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33,
+ 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33,
+ 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33,
+ 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31,
+ 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31,
+ 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31,
+ 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31,
+ 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31,
+ 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x36, 0x36, 0x36, 0x36,
+ 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
+ 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
+ 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
+ 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
+ 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
+ 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37,
+ 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37,
+ 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37,
+ 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37,
+ 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37,
+ 0x37, 0x37, 0x37, 0x37, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x35, 0x35, 0x35, 0x35,
+ 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35,
+ 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35,
+ 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35,
+ 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35,
+ 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35,
+ 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
+ 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
+ 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09,
+ 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09,
+ 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x0e, 0x0e, 0x0e, 0x0e,
+ 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e,
+ 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e,
+ 0x0e, 0x0e, 0x0e, 0x0e, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+ 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+ 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
+ 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
+ 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x07, 0x07, 0x07, 0x07,
+ 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
+ 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
+ 0x07, 0x07, 0x07, 0x07, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
+ 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
+ 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x1a, 0x1a, 0x1a, 0x1a,
+ 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a,
+ 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b,
+ 0x1b, 0x1b, 0x1b, 0x1b, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+ 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x19, 0x19, 0x19, 0x19,
+ 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19,
+ 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e,
+ 0x1e, 0x1e, 0x1e, 0x1e, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f,
+ 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1c, 0x1c, 0x1c, 0x1c,
+ 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c,
+ 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d,
+ 0x1d, 0x1d, 0x1d, 0x1d, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
+ 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x13, 0x13, 0x13, 0x13,
+ 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
+ 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
+ 0x10, 0x10, 0x10, 0x10, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x16, 0x16, 0x16, 0x16,
+ 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16,
+ 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17,
+ 0x17, 0x17, 0x17, 0x17, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,
+ 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x15, 0x15, 0x15, 0x15,
+ 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15,
+ 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6b, 0x6b, 0x6b, 0x6b,
+ 0x6b, 0x6b, 0x6b, 0x6b, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68,
+ 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x6e, 0x6e, 0x6e, 0x6e,
+ 0x6e, 0x6e, 0x6e, 0x6e, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f,
+ 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6d, 0x6d, 0x6d, 0x6d,
+ 0x6d, 0x6d, 0x6d, 0x6d, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62,
+ 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x60, 0x60, 0x60, 0x60,
+ 0x60, 0x60, 0x60, 0x60, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
+ 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x67, 0x67, 0x67, 0x67,
+ 0x67, 0x67, 0x67, 0x67, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64,
+ 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x7a, 0x7a, 0x7a, 0x7a,
+ 0x7b, 0x7b, 0x7b, 0x7b, 0x78, 0x78, 0x78, 0x78, 0x79, 0x79, 0x79, 0x79,
+ 0x7e, 0x7e, 0x7e, 0x7e, 0x7f, 0x7f, 0x7f, 0x7f, 0x7c, 0x7c, 0x7c, 0x7c,
+ 0x7d, 0x7d, 0x7d, 0x7d, 0x72, 0x72, 0x72, 0x72, 0x73, 0x73, 0x73, 0x73,
+ 0x70, 0x70, 0x70, 0x70, 0x71, 0x71, 0x71, 0x71, 0x76, 0x76, 0x76, 0x76,
+ 0x77, 0x77, 0x77, 0x77, 0x74, 0x74, 0x74, 0x74, 0x75, 0x75, 0x75, 0x75,
+ 0x4a, 0x4a, 0x4b, 0x4b, 0x48, 0x48, 0x49, 0x49, 0x4e, 0x4e, 0x4f, 0x4f,
+ 0x4c, 0x4c, 0x4d, 0x4d, 0x42, 0x42, 0x43, 0x43, 0x40, 0x40, 0x41, 0x41,
+ 0x46, 0x46, 0x47, 0x47, 0x44, 0x44, 0x45, 0x45, 0x5a, 0x5a, 0x5b, 0x5b,
+ 0x58, 0x58, 0x59, 0x59, 0x5e, 0x5e, 0x5f, 0x5f, 0x5c, 0x5c, 0x5d, 0x5d,
+ 0x52, 0x52, 0x53, 0x53, 0x50, 0x50, 0x51, 0x51, 0x56, 0x56, 0x57, 0x57,
+ 0x54, 0x54, 0x55, 0x55, 0xd5, 0xd5, 0xd4, 0xd4, 0xd7, 0xd7, 0xd6, 0xd6,
+ 0xd1, 0xd1, 0xd0, 0xd0, 0xd3, 0xd3, 0xd2, 0xd2, 0xdd, 0xdd, 0xdc, 0xdc,
+ 0xdf, 0xdf, 0xde, 0xde, 0xd9, 0xd9, 0xd8, 0xd8, 0xdb, 0xdb, 0xda, 0xda,
+ 0xc5, 0xc5, 0xc4, 0xc4, 0xc7, 0xc7, 0xc6, 0xc6, 0xc1, 0xc1, 0xc0, 0xc0,
+ 0xc3, 0xc3, 0xc2, 0xc2, 0xcd, 0xcd, 0xcc, 0xcc, 0xcf, 0xcf, 0xce, 0xce,
+ 0xc9, 0xc9, 0xc8, 0xc8, 0xcb, 0xcb, 0xca, 0xca, 0xf5, 0xf5, 0xf5, 0xf5,
+ 0xf4, 0xf4, 0xf4, 0xf4, 0xf7, 0xf7, 0xf7, 0xf7, 0xf6, 0xf6, 0xf6, 0xf6,
+ 0xf1, 0xf1, 0xf1, 0xf1, 0xf0, 0xf0, 0xf0, 0xf0, 0xf3, 0xf3, 0xf3, 0xf3,
+ 0xf2, 0xf2, 0xf2, 0xf2, 0xfd, 0xfd, 0xfd, 0xfd, 0xfc, 0xfc, 0xfc, 0xfc,
+ 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xfe, 0xf9, 0xf9, 0xf9, 0xf9,
+ 0xf8, 0xf8, 0xf8, 0xf8, 0xfb, 0xfb, 0xfb, 0xfb, 0xfa, 0xfa, 0xfa, 0xfa,
+ 0xe5, 0xe5, 0xe5, 0xe5, 0xe5, 0xe5, 0xe5, 0xe5, 0xe4, 0xe4, 0xe4, 0xe4,
+ 0xe4, 0xe4, 0xe4, 0xe4, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7,
+ 0xe6, 0xe6, 0xe6, 0xe6, 0xe6, 0xe6, 0xe6, 0xe6, 0xe1, 0xe1, 0xe1, 0xe1,
+ 0xe1, 0xe1, 0xe1, 0xe1, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0,
+ 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe2, 0xe2, 0xe2, 0xe2,
+ 0xe2, 0xe2, 0xe2, 0xe2, 0xed, 0xed, 0xed, 0xed, 0xed, 0xed, 0xed, 0xed,
+ 0xec, 0xec, 0xec, 0xec, 0xec, 0xec, 0xec, 0xec, 0xef, 0xef, 0xef, 0xef,
+ 0xef, 0xef, 0xef, 0xef, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee,
+ 0xe9, 0xe9, 0xe9, 0xe9, 0xe9, 0xe9, 0xe9, 0xe9, 0xe8, 0xe8, 0xe8, 0xe8,
+ 0xe8, 0xe8, 0xe8, 0xe8, 0xeb, 0xeb, 0xeb, 0xeb, 0xeb, 0xeb, 0xeb, 0xeb,
+ 0xea, 0xea, 0xea, 0xea, 0xea, 0xea, 0xea, 0xea, 0x95, 0x95, 0x95, 0x95,
+ 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95,
+ 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94,
+ 0x94, 0x94, 0x94, 0x94, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97,
+ 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x96, 0x96, 0x96, 0x96,
+ 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96,
+ 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91,
+ 0x91, 0x91, 0x91, 0x91, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
+ 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x93, 0x93, 0x93, 0x93,
+ 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93,
+ 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92,
+ 0x92, 0x92, 0x92, 0x92, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d,
+ 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9c, 0x9c, 0x9c, 0x9c,
+ 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c,
+ 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f,
+ 0x9f, 0x9f, 0x9f, 0x9f, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e,
+ 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x99, 0x99, 0x99, 0x99,
+ 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
+ 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98,
+ 0x98, 0x98, 0x98, 0x98, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b,
+ 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9a, 0x9a, 0x9a, 0x9a,
+ 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a,
+ 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
+ 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
+ 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x84, 0x84, 0x84, 0x84,
+ 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84,
+ 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84,
+ 0x84, 0x84, 0x84, 0x84, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87,
+ 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87,
+ 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87,
+ 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86,
+ 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86,
+ 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x81, 0x81, 0x81, 0x81,
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
+ 0x81, 0x81, 0x81, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
+ 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
+ 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x82, 0x82, 0x82, 0x82,
+ 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82,
+ 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82,
+ 0x82, 0x82, 0x82, 0x82, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d,
+ 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d,
+ 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d,
+ 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c,
+ 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c,
+ 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8f, 0x8f, 0x8f, 0x8f,
+ 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f,
+ 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f,
+ 0x8f, 0x8f, 0x8f, 0x8f, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e,
+ 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e,
+ 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e,
+ 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89,
+ 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89,
+ 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b,
+ 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b,
+ 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b,
+ 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a,
+ 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a,
+ 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0xb5, 0xb5, 0xb5, 0xb5,
+ 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5,
+ 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5,
+ 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5,
+ 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5,
+ 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5,
+ 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4,
+ 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4,
+ 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4,
+ 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4,
+ 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4,
+ 0xb4, 0xb4, 0xb4, 0xb4, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7,
+ 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7,
+ 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7,
+ 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7,
+ 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7,
+ 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb6, 0xb6, 0xb6, 0xb6,
+ 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6,
+ 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6,
+ 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6,
+ 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6,
+ 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6,
+ 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1,
+ 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1,
+ 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1,
+ 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1,
+ 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1,
+ 0xb1, 0xb1, 0xb1, 0xb1, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0,
+ 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0,
+ 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0,
+ 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0,
+ 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0,
+ 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb3, 0xb3, 0xb3, 0xb3,
+ 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3,
+ 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3,
+ 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3,
+ 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3,
+ 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3,
+ 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2,
+ 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2,
+ 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2,
+ 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2,
+ 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2,
+ 0xb2, 0xb2, 0xb2, 0xb2, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd,
+ 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd,
+ 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd,
+ 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd,
+ 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd,
+ 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbc, 0xbc, 0xbc, 0xbc,
+ 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc,
+ 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc,
+ 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc,
+ 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc,
+ 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc,
+ 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf,
+ 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf,
+ 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf,
+ 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf,
+ 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf,
+ 0xbf, 0xbf, 0xbf, 0xbf, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe,
+ 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe,
+ 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe,
+ 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe,
+ 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe,
+ 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xb9, 0xb9, 0xb9, 0xb9,
+ 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9,
+ 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9,
+ 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9,
+ 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9,
+ 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9,
+ 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8,
+ 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8,
+ 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8,
+ 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8,
+ 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8,
+ 0xb8, 0xb8, 0xb8, 0xb8, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb,
+ 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb,
+ 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb,
+ 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb,
+ 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb,
+ 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xba, 0xba, 0xba, 0xba,
+ 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba,
+ 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba,
+ 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba,
+ 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba,
+ 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba,
+ 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5,
+ 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5,
+ 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5,
+ 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5,
+ 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5,
+ 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5,
+ 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5,
+ 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5,
+ 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5,
+ 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5,
+ 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa4, 0xa4, 0xa4, 0xa4,
+ 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4,
+ 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4,
+ 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4,
+ 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4,
+ 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4,
+ 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4,
+ 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4,
+ 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4,
+ 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4,
+ 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4,
+ 0xa4, 0xa4, 0xa4, 0xa4, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7,
+ 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7,
+ 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7,
+ 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7,
+ 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7,
+ 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7,
+ 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7,
+ 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7,
+ 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7,
+ 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7,
+ 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7,
+ 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6,
+ 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6,
+ 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6,
+ 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6,
+ 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6,
+ 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6,
+ 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6,
+ 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6,
+ 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6,
+ 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6,
+ 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa1, 0xa1, 0xa1, 0xa1,
+ 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1,
+ 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1,
+ 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1,
+ 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1,
+ 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1,
+ 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1,
+ 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1,
+ 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1,
+ 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1,
+ 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1,
+ 0xa1, 0xa1, 0xa1, 0xa1, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0,
+ 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0,
+ 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0,
+ 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0,
+ 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0,
+ 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0,
+ 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0,
+ 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0,
+ 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0,
+ 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0,
+ 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0,
+ 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3,
+ 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3,
+ 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3,
+ 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3,
+ 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3,
+ 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3,
+ 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3,
+ 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3,
+ 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3,
+ 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3,
+ 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa2, 0xa2, 0xa2, 0xa2,
+ 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2,
+ 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2,
+ 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2,
+ 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2,
+ 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2,
+ 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2,
+ 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2,
+ 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2,
+ 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2,
+ 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2,
+ 0xa2, 0xa2, 0xa2, 0xa2, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad,
+ 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad,
+ 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad,
+ 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad,
+ 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad,
+ 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad,
+ 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad,
+ 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad,
+ 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad,
+ 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad,
+ 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad,
+ 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac,
+ 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac,
+ 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac,
+ 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac,
+ 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac,
+ 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac,
+ 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac,
+ 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac,
+ 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac,
+ 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac,
+ 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xaf, 0xaf, 0xaf, 0xaf,
+ 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf,
+ 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf,
+ 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf,
+ 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf,
+ 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf,
+ 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf,
+ 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf,
+ 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf,
+ 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf,
+ 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf,
+ 0xaf, 0xaf, 0xaf, 0xaf, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae,
+ 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae,
+ 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae,
+ 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae,
+ 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae,
+ 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae,
+ 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae,
+ 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae,
+ 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae,
+ 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae,
+ 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae,
+ 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9,
+ 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9,
+ 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9,
+ 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9,
+ 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9,
+ 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9,
+ 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9,
+ 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9,
+ 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9,
+ 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9,
+ 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa8, 0xa8, 0xa8, 0xa8,
+ 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8,
+ 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8,
+ 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8,
+ 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8,
+ 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8,
+ 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8,
+ 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8,
+ 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8,
+ 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8,
+ 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8,
+ 0xa8, 0xa8, 0xa8, 0xa8, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
+ 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
+ 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
+ 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
+ 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
+ 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
+ 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
+ 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
+ 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
+ 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
+ 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa
+};
+
+#endif /* FAST_ALAW_CONVERSION */
+
+#ifdef FAST_ULAW_CONVERSION
+
+int16_t _st_ulaw2linear16[256] = {
+ -32124, -31100, -30076, -29052, -28028, -27004, -25980,
+ -24956, -23932, -22908, -21884, -20860, -19836, -18812,
+ -17788, -16764, -15996, -15484, -14972, -14460, -13948,
+ -13436, -12924, -12412, -11900, -11388, -10876, -10364,
+ -9852, -9340, -8828, -8316, -7932, -7676, -7420,
+ -7164, -6908, -6652, -6396, -6140, -5884, -5628,
+ -5372, -5116, -4860, -4604, -4348, -4092, -3900,
+ -3772, -3644, -3516, -3388, -3260, -3132, -3004,
+ -2876, -2748, -2620, -2492, -2364, -2236, -2108,
+ -1980, -1884, -1820, -1756, -1692, -1628, -1564,
+ -1500, -1436, -1372, -1308, -1244, -1180, -1116,
+ -1052, -988, -924, -876, -844, -812, -780,
+ -748, -716, -684, -652, -620, -588, -556,
+ -524, -492, -460, -428, -396, -372, -356,
+ -340, -324, -308, -292, -276, -260, -244,
+ -228, -212, -196, -180, -164, -148, -132,
+ -120, -112, -104, -96, -88, -80, -72,
+ -64, -56, -48, -40, -32, -24, -16,
+ -8, 0, 32124, 31100, 30076, 29052, 28028,
+ 27004, 25980, 24956, 23932, 22908, 21884, 20860,
+ 19836, 18812, 17788, 16764, 15996, 15484, 14972,
+ 14460, 13948, 13436, 12924, 12412, 11900, 11388,
+ 10876, 10364, 9852, 9340, 8828, 8316, 7932,
+ 7676, 7420, 7164, 6908, 6652, 6396, 6140,
+ 5884, 5628, 5372, 5116, 4860, 4604, 4348,
+ 4092, 3900, 3772, 3644, 3516, 3388, 3260,
+ 3132, 3004, 2876, 2748, 2620, 2492, 2364,
+ 2236, 2108, 1980, 1884, 1820, 1756, 1692,
+ 1628, 1564, 1500, 1436, 1372, 1308, 1244,
+ 1180, 1116, 1052, 988, 924, 876, 844,
+ 812, 780, 748, 716, 684, 652, 620,
+ 588, 556, 524, 492, 460, 428, 396,
+ 372, 356, 340, 324, 308, 292, 276,
+ 260, 244, 228, 212, 196, 180, 164,
+ 148, 132, 120, 112, 104, 96, 88,
+ 80, 72, 64, 56, 48, 40, 32,
+ 24, 16, 8, 0
+};
+
+uint8_t _st_14linear2ulaw[0x4000] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03,
+ 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+ 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+ 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+ 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+ 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+ 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+ 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+ 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+ 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+ 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+ 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+ 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+ 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+ 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+ 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+ 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+ 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+ 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+ 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+ 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+ 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+ 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
+ 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
+ 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
+ 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
+ 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
+ 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
+ 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
+ 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
+ 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
+ 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
+ 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
+ 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
+ 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
+ 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
+ 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
+ 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
+ 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
+ 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
+ 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
+ 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
+ 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
+ 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x06, 0x06,
+ 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
+ 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
+ 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
+ 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
+ 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
+ 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
+ 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
+ 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
+ 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
+ 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
+ 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
+ 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
+ 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
+ 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
+ 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
+ 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
+ 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
+ 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
+ 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
+ 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
+ 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
+ 0x06, 0x06, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
+ 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
+ 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
+ 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
+ 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
+ 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
+ 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
+ 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
+ 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
+ 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
+ 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
+ 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
+ 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
+ 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
+ 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
+ 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
+ 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
+ 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
+ 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
+ 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
+ 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
+ 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x09, 0x09,
+ 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09,
+ 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09,
+ 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09,
+ 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09,
+ 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09,
+ 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09,
+ 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09,
+ 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09,
+ 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09,
+ 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09,
+ 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09,
+ 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09,
+ 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09,
+ 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09,
+ 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09,
+ 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09,
+ 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09,
+ 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09,
+ 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09,
+ 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09,
+ 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09,
+ 0x09, 0x09, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
+ 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
+ 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
+ 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
+ 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
+ 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
+ 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
+ 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
+ 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
+ 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
+ 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
+ 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
+ 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
+ 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
+ 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
+ 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
+ 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
+ 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
+ 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
+ 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
+ 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
+ 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
+ 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e,
+ 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e,
+ 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e,
+ 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e,
+ 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e,
+ 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e,
+ 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e,
+ 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e,
+ 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e,
+ 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e,
+ 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e,
+ 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e,
+ 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e,
+ 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e,
+ 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e,
+ 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e,
+ 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e,
+ 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e,
+ 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e,
+ 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e,
+ 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e,
+ 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x0f, 0x0f, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
+ 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
+ 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
+ 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
+ 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
+ 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
+ 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
+ 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
+ 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
+ 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
+ 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
+ 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
+ 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
+ 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
+ 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
+ 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
+ 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
+ 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
+ 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
+ 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
+ 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
+ 0x12, 0x12, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
+ 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
+ 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
+ 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
+ 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
+ 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
+ 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
+ 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
+ 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
+ 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
+ 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x14, 0x14,
+ 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,
+ 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,
+ 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,
+ 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,
+ 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,
+ 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,
+ 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,
+ 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,
+ 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,
+ 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,
+ 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15,
+ 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15,
+ 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15,
+ 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15,
+ 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15,
+ 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15,
+ 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15,
+ 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15,
+ 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15,
+ 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15,
+ 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15,
+ 0x15, 0x15, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16,
+ 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16,
+ 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16,
+ 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16,
+ 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16,
+ 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16,
+ 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16,
+ 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16,
+ 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16,
+ 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16,
+ 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x17, 0x17,
+ 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17,
+ 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17,
+ 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17,
+ 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17,
+ 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17,
+ 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17,
+ 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17,
+ 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17,
+ 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17,
+ 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17,
+ 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+ 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+ 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+ 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+ 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+ 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+ 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+ 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+ 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+ 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+ 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+ 0x18, 0x18, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19,
+ 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19,
+ 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19,
+ 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19,
+ 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19,
+ 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19,
+ 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19,
+ 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19,
+ 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19,
+ 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19,
+ 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x1a, 0x1a,
+ 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a,
+ 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a,
+ 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a,
+ 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a,
+ 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a,
+ 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a,
+ 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a,
+ 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a,
+ 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a,
+ 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a,
+ 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b,
+ 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b,
+ 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b,
+ 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b,
+ 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b,
+ 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b,
+ 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b,
+ 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b,
+ 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b,
+ 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b,
+ 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b,
+ 0x1b, 0x1b, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c,
+ 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c,
+ 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c,
+ 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c,
+ 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c,
+ 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c,
+ 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c,
+ 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c,
+ 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c,
+ 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c,
+ 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1d, 0x1d,
+ 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d,
+ 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d,
+ 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d,
+ 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d,
+ 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d,
+ 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d,
+ 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d,
+ 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d,
+ 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d,
+ 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d,
+ 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e,
+ 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e,
+ 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e,
+ 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e,
+ 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e,
+ 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e,
+ 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e,
+ 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e,
+ 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e,
+ 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e,
+ 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e,
+ 0x1e, 0x1e, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f,
+ 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f,
+ 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f,
+ 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f,
+ 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f,
+ 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f,
+ 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f,
+ 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f,
+ 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f,
+ 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f,
+ 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x20, 0x20, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21,
+ 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21,
+ 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21,
+ 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21,
+ 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21,
+ 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x23, 0x23,
+ 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23,
+ 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23,
+ 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23,
+ 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23,
+ 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23,
+ 0x23, 0x23, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24,
+ 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24,
+ 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24,
+ 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24,
+ 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24,
+ 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25,
+ 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25,
+ 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25,
+ 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25,
+ 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25,
+ 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x26, 0x26,
+ 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26,
+ 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26,
+ 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26,
+ 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26,
+ 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26,
+ 0x26, 0x26, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27,
+ 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27,
+ 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27,
+ 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27,
+ 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27,
+ 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28,
+ 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28,
+ 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28,
+ 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28,
+ 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28,
+ 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x29, 0x29,
+ 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29,
+ 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29,
+ 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29,
+ 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29,
+ 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29,
+ 0x29, 0x29, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a,
+ 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a,
+ 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a,
+ 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a,
+ 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a,
+ 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b,
+ 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b,
+ 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b,
+ 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b,
+ 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b,
+ 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2c, 0x2c,
+ 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c,
+ 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c,
+ 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c,
+ 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c,
+ 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c,
+ 0x2c, 0x2c, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+ 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+ 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+ 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+ 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d,
+ 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e,
+ 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e,
+ 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e,
+ 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e,
+ 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e,
+ 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2f, 0x2f,
+ 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f,
+ 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f,
+ 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f,
+ 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f,
+ 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f,
+ 0x2f, 0x2f, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x31,
+ 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31,
+ 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31,
+ 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32,
+ 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32,
+ 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32,
+ 0x32, 0x32, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33,
+ 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33,
+ 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x34, 0x34,
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35,
+ 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35,
+ 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35,
+ 0x35, 0x35, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
+ 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
+ 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x37, 0x37,
+ 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37,
+ 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37,
+ 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38,
+ 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38,
+ 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38,
+ 0x38, 0x38, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39,
+ 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39,
+ 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x3a, 0x3a,
+ 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a,
+ 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a,
+ 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b,
+ 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b,
+ 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b,
+ 0x3b, 0x3b, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c,
+ 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c,
+ 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3d, 0x3d,
+ 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d,
+ 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d,
+ 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e,
+ 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e,
+ 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e,
+ 0x3e, 0x3e, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f,
+ 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f,
+ 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x40, 0x40,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x40, 0x40, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
+ 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42,
+ 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x43, 0x43,
+ 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43,
+ 0x43, 0x43, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45,
+ 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x46, 0x46,
+ 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46,
+ 0x46, 0x46, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47,
+ 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48,
+ 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x49, 0x49,
+ 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49,
+ 0x49, 0x49, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a,
+ 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b,
+ 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4c, 0x4c,
+ 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c,
+ 0x4c, 0x4c, 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, 0x4d,
+ 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e,
+ 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4f, 0x4f,
+ 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f,
+ 0x4f, 0x4f, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x51, 0x51,
+ 0x51, 0x51, 0x51, 0x51, 0x51, 0x51, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52,
+ 0x52, 0x52, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x54, 0x54,
+ 0x54, 0x54, 0x54, 0x54, 0x54, 0x54, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+ 0x55, 0x55, 0x56, 0x56, 0x56, 0x56, 0x56, 0x56, 0x56, 0x56, 0x57, 0x57,
+ 0x57, 0x57, 0x57, 0x57, 0x57, 0x57, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58,
+ 0x58, 0x58, 0x59, 0x59, 0x59, 0x59, 0x59, 0x59, 0x59, 0x59, 0x5a, 0x5a,
+ 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5b, 0x5b, 0x5b, 0x5b, 0x5b, 0x5b,
+ 0x5b, 0x5b, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5d, 0x5d,
+ 0x5d, 0x5d, 0x5d, 0x5d, 0x5d, 0x5d, 0x5e, 0x5e, 0x5e, 0x5e, 0x5e, 0x5e,
+ 0x5e, 0x5e, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x60, 0x60,
+ 0x60, 0x60, 0x61, 0x61, 0x61, 0x61, 0x62, 0x62, 0x62, 0x62, 0x63, 0x63,
+ 0x63, 0x63, 0x64, 0x64, 0x64, 0x64, 0x65, 0x65, 0x65, 0x65, 0x66, 0x66,
+ 0x66, 0x66, 0x67, 0x67, 0x67, 0x67, 0x68, 0x68, 0x68, 0x68, 0x69, 0x69,
+ 0x69, 0x69, 0x6a, 0x6a, 0x6a, 0x6a, 0x6b, 0x6b, 0x6b, 0x6b, 0x6c, 0x6c,
+ 0x6c, 0x6c, 0x6d, 0x6d, 0x6d, 0x6d, 0x6e, 0x6e, 0x6e, 0x6e, 0x6f, 0x6f,
+ 0x6f, 0x6f, 0x70, 0x70, 0x71, 0x71, 0x72, 0x72, 0x73, 0x73, 0x74, 0x74,
+ 0x75, 0x75, 0x76, 0x76, 0x77, 0x77, 0x78, 0x78, 0x79, 0x79, 0x7a, 0x7a,
+ 0x7b, 0x7b, 0x7c, 0x7c, 0x7d, 0x7d, 0x7e, 0x7e, 0xff, 0xfe, 0xfe, 0xfd,
+ 0xfd, 0xfc, 0xfc, 0xfb, 0xfb, 0xfa, 0xfa, 0xf9, 0xf9, 0xf8, 0xf8, 0xf7,
+ 0xf7, 0xf6, 0xf6, 0xf5, 0xf5, 0xf4, 0xf4, 0xf3, 0xf3, 0xf2, 0xf2, 0xf1,
+ 0xf1, 0xf0, 0xf0, 0xef, 0xef, 0xef, 0xef, 0xee, 0xee, 0xee, 0xee, 0xed,
+ 0xed, 0xed, 0xed, 0xec, 0xec, 0xec, 0xec, 0xeb, 0xeb, 0xeb, 0xeb, 0xea,
+ 0xea, 0xea, 0xea, 0xe9, 0xe9, 0xe9, 0xe9, 0xe8, 0xe8, 0xe8, 0xe8, 0xe7,
+ 0xe7, 0xe7, 0xe7, 0xe6, 0xe6, 0xe6, 0xe6, 0xe5, 0xe5, 0xe5, 0xe5, 0xe4,
+ 0xe4, 0xe4, 0xe4, 0xe3, 0xe3, 0xe3, 0xe3, 0xe2, 0xe2, 0xe2, 0xe2, 0xe1,
+ 0xe1, 0xe1, 0xe1, 0xe0, 0xe0, 0xe0, 0xe0, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf,
+ 0xdf, 0xdf, 0xdf, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xdd,
+ 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdc, 0xdc, 0xdc, 0xdc, 0xdc,
+ 0xdc, 0xdc, 0xdc, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xda,
+ 0xda, 0xda, 0xda, 0xda, 0xda, 0xda, 0xda, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9,
+ 0xd9, 0xd9, 0xd9, 0xd8, 0xd8, 0xd8, 0xd8, 0xd8, 0xd8, 0xd8, 0xd8, 0xd7,
+ 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6,
+ 0xd6, 0xd6, 0xd6, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd4,
+ 0xd4, 0xd4, 0xd4, 0xd4, 0xd4, 0xd4, 0xd4, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3,
+ 0xd3, 0xd3, 0xd3, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd1,
+ 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0,
+ 0xd0, 0xd0, 0xd0, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf,
+ 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xce, 0xce, 0xce, 0xce, 0xce,
+ 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xcd,
+ 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd,
+ 0xcd, 0xcd, 0xcd, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc,
+ 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb,
+ 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xca,
+ 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca,
+ 0xca, 0xca, 0xca, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9,
+ 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8,
+ 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc7,
+ 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7,
+ 0xc7, 0xc7, 0xc7, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
+ 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5,
+ 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc4,
+ 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4,
+ 0xc4, 0xc4, 0xc4, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3,
+ 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2,
+ 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc1,
+ 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1,
+ 0xc1, 0xc1, 0xc1, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf,
+ 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf,
+ 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf,
+ 0xbf, 0xbf, 0xbf, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe,
+ 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe,
+ 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbd,
+ 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd,
+ 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd,
+ 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc,
+ 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc,
+ 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc,
+ 0xbc, 0xbc, 0xbc, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb,
+ 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb,
+ 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xba,
+ 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba,
+ 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba,
+ 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9,
+ 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9,
+ 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9,
+ 0xb9, 0xb9, 0xb9, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8,
+ 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8,
+ 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb7,
+ 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7,
+ 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7,
+ 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6,
+ 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6,
+ 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6,
+ 0xb6, 0xb6, 0xb6, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5,
+ 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5,
+ 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb4,
+ 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4,
+ 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4,
+ 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3,
+ 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3,
+ 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3,
+ 0xb3, 0xb3, 0xb3, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2,
+ 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2,
+ 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb1,
+ 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1,
+ 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1,
+ 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0,
+ 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0,
+ 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0,
+ 0xb0, 0xb0, 0xb0, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf,
+ 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf,
+ 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf,
+ 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf,
+ 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf,
+ 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xae, 0xae, 0xae, 0xae, 0xae,
+ 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae,
+ 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae,
+ 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae,
+ 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae,
+ 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xad,
+ 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad,
+ 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad,
+ 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad,
+ 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad,
+ 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad,
+ 0xad, 0xad, 0xad, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac,
+ 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac,
+ 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac,
+ 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac,
+ 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac,
+ 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xab, 0xab, 0xab, 0xab, 0xab,
+ 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
+ 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
+ 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
+ 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
+ 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9,
+ 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9,
+ 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9,
+ 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9,
+ 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9,
+ 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8,
+ 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8,
+ 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8,
+ 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8,
+ 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8,
+ 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa7,
+ 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7,
+ 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7,
+ 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7,
+ 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7,
+ 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7,
+ 0xa7, 0xa7, 0xa7, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6,
+ 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6,
+ 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6,
+ 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6,
+ 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6,
+ 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5,
+ 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5,
+ 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5,
+ 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5,
+ 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5,
+ 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa4,
+ 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4,
+ 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4,
+ 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4,
+ 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4,
+ 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4,
+ 0xa4, 0xa4, 0xa4, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3,
+ 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3,
+ 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3,
+ 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3,
+ 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3,
+ 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2,
+ 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2,
+ 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2,
+ 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2,
+ 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2,
+ 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa1,
+ 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1,
+ 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1,
+ 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1,
+ 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1,
+ 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1,
+ 0xa1, 0xa1, 0xa1, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0,
+ 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0,
+ 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0,
+ 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0,
+ 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0,
+ 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f,
+ 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f,
+ 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f,
+ 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f,
+ 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f,
+ 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f,
+ 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f,
+ 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f,
+ 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f,
+ 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f,
+ 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f,
+ 0x9f, 0x9f, 0x9f, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e,
+ 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e,
+ 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e,
+ 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e,
+ 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e,
+ 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e,
+ 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e,
+ 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e,
+ 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e,
+ 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e,
+ 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9d,
+ 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d,
+ 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d,
+ 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d,
+ 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d,
+ 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d,
+ 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d,
+ 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d,
+ 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d,
+ 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d,
+ 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d,
+ 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c,
+ 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c,
+ 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c,
+ 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c,
+ 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c,
+ 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c,
+ 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c,
+ 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c,
+ 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c,
+ 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c,
+ 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c,
+ 0x9c, 0x9c, 0x9c, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b,
+ 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b,
+ 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b,
+ 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b,
+ 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b,
+ 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b,
+ 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b,
+ 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b,
+ 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b,
+ 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b,
+ 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9a,
+ 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a,
+ 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a,
+ 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a,
+ 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a,
+ 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a,
+ 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a,
+ 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a,
+ 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a,
+ 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a,
+ 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a,
+ 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x99, 0x99, 0x99, 0x99, 0x99,
+ 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
+ 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
+ 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
+ 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
+ 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
+ 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
+ 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
+ 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
+ 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
+ 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
+ 0x99, 0x99, 0x99, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98,
+ 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98,
+ 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98,
+ 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98,
+ 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98,
+ 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98,
+ 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98,
+ 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98,
+ 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98,
+ 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98,
+ 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x97,
+ 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97,
+ 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97,
+ 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97,
+ 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97,
+ 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97,
+ 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97,
+ 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97,
+ 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97,
+ 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97,
+ 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97,
+ 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x96, 0x96, 0x96, 0x96, 0x96,
+ 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96,
+ 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96,
+ 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96,
+ 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96,
+ 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96,
+ 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96,
+ 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96,
+ 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96,
+ 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96,
+ 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96,
+ 0x96, 0x96, 0x96, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95,
+ 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95,
+ 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95,
+ 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95,
+ 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95,
+ 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95,
+ 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95,
+ 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95,
+ 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95,
+ 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95,
+ 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x94,
+ 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94,
+ 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94,
+ 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94,
+ 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94,
+ 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94,
+ 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94,
+ 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94,
+ 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94,
+ 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94,
+ 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94,
+ 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x93, 0x93, 0x93, 0x93, 0x93,
+ 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93,
+ 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93,
+ 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93,
+ 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93,
+ 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93,
+ 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93,
+ 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93,
+ 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93,
+ 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93,
+ 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93,
+ 0x93, 0x93, 0x93, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92,
+ 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92,
+ 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92,
+ 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92,
+ 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92,
+ 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92,
+ 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92,
+ 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92,
+ 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92,
+ 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92,
+ 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x91,
+ 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91,
+ 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91,
+ 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91,
+ 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91,
+ 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91,
+ 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91,
+ 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91,
+ 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91,
+ 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91,
+ 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91,
+ 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x90, 0x90, 0x90, 0x90, 0x90,
+ 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
+ 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
+ 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
+ 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
+ 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
+ 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
+ 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
+ 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
+ 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
+ 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
+ 0x90, 0x90, 0x90, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f,
+ 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f,
+ 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f,
+ 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f,
+ 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f,
+ 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f,
+ 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f,
+ 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f,
+ 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f,
+ 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f,
+ 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f,
+ 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f,
+ 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f,
+ 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f,
+ 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f,
+ 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f,
+ 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f,
+ 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f,
+ 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f,
+ 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f,
+ 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f,
+ 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e,
+ 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e,
+ 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e,
+ 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e,
+ 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e,
+ 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e,
+ 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e,
+ 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e,
+ 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e,
+ 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e,
+ 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e,
+ 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e,
+ 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e,
+ 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e,
+ 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e,
+ 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e,
+ 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e,
+ 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e,
+ 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e,
+ 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e,
+ 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e,
+ 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8d,
+ 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d,
+ 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d,
+ 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d,
+ 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d,
+ 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d,
+ 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d,
+ 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d,
+ 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d,
+ 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d,
+ 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d,
+ 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d,
+ 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d,
+ 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d,
+ 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d,
+ 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d,
+ 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d,
+ 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d,
+ 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d,
+ 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d,
+ 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d,
+ 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d,
+ 0x8d, 0x8d, 0x8d, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c,
+ 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c,
+ 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c,
+ 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c,
+ 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c,
+ 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c,
+ 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c,
+ 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c,
+ 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c,
+ 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c,
+ 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c,
+ 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c,
+ 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c,
+ 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c,
+ 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c,
+ 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c,
+ 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c,
+ 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c,
+ 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c,
+ 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c,
+ 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c,
+ 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b,
+ 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b,
+ 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b,
+ 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b,
+ 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b,
+ 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b,
+ 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b,
+ 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b,
+ 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b,
+ 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b,
+ 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b,
+ 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b,
+ 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b,
+ 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b,
+ 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b,
+ 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b,
+ 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b,
+ 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b,
+ 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b,
+ 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b,
+ 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b,
+ 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8a,
+ 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a,
+ 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a,
+ 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a,
+ 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a,
+ 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a,
+ 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a,
+ 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a,
+ 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a,
+ 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a,
+ 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a,
+ 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a,
+ 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a,
+ 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a,
+ 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a,
+ 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a,
+ 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a,
+ 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a,
+ 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a,
+ 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a,
+ 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a,
+ 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a,
+ 0x8a, 0x8a, 0x8a, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89,
+ 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89,
+ 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89,
+ 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89,
+ 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89,
+ 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89,
+ 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89,
+ 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89,
+ 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89,
+ 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89,
+ 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89,
+ 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89,
+ 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89,
+ 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89,
+ 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89,
+ 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89,
+ 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89,
+ 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89,
+ 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89,
+ 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89,
+ 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89,
+ 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x87,
+ 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87,
+ 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87,
+ 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87,
+ 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87,
+ 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87,
+ 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87,
+ 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87,
+ 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87,
+ 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87,
+ 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87,
+ 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87,
+ 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87,
+ 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87,
+ 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87,
+ 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87,
+ 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87,
+ 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87,
+ 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87,
+ 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87,
+ 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87,
+ 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87,
+ 0x87, 0x87, 0x87, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86,
+ 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86,
+ 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86,
+ 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86,
+ 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86,
+ 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86,
+ 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86,
+ 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86,
+ 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86,
+ 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86,
+ 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86,
+ 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86,
+ 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86,
+ 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86,
+ 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86,
+ 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86,
+ 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86,
+ 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86,
+ 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86,
+ 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86,
+ 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86,
+ 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x85, 0x85, 0x85, 0x85, 0x85,
+ 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
+ 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
+ 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
+ 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
+ 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
+ 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
+ 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
+ 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
+ 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
+ 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
+ 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
+ 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
+ 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
+ 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
+ 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
+ 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
+ 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
+ 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
+ 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
+ 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
+ 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x84,
+ 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84,
+ 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84,
+ 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84,
+ 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84,
+ 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84,
+ 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84,
+ 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84,
+ 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84,
+ 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84,
+ 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84,
+ 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84,
+ 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84,
+ 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84,
+ 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84,
+ 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84,
+ 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84,
+ 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84,
+ 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84,
+ 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84,
+ 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84,
+ 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84,
+ 0x84, 0x84, 0x84, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
+ 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
+ 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
+ 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
+ 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
+ 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
+ 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
+ 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
+ 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
+ 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
+ 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
+ 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
+ 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
+ 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
+ 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
+ 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
+ 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
+ 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
+ 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
+ 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
+ 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
+ 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x82, 0x82, 0x82, 0x82, 0x82,
+ 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82,
+ 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82,
+ 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82,
+ 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82,
+ 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82,
+ 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82,
+ 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82,
+ 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82,
+ 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82,
+ 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82,
+ 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82,
+ 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82,
+ 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82,
+ 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82,
+ 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82,
+ 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82,
+ 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82,
+ 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82,
+ 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82,
+ 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82,
+ 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x81,
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
+ 0x81, 0x81, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80
+};
+
+#endif /* FAST_ULAW_CONVERSION */
+
+/* The following code was used to generate the lookup tables */
+#if 0
+int main()
+{
+ int x, y, find2a = 0;
+
+ y = 0;
+ printf("int16_t _st_alaw2linear16[256] = {\n ");
+ for (x = 0; x < 256; x++)
+ {
+ printf("%8d,", st_alaw2linear16(x));
+ y++;
+ if (y == 7)
+ {
+ y = 0;
+ printf("\n ");
+ }
+ }
+
+ printf("\n};\n\nuint8_t _st_13linear2alaw[0x2000] = {\n ");
+ y = 0;
+ for (x = 0; x < 0x2000; x++)
+ {
+ printf(" 0x%02x,", st_13linear2alaw((-0x1000)+x));
+ y++;
+ if (y == 12)
+ {
+ y = 0;
+ printf("\n ");
+ }
+ }
+
+ printf("\n};\n\nint16_t _st_ulaw2linear16[256] = {\n ");
+ y = 0;
+ for (x = 0; x < 256; x++)
+ {
+ printf("%8d,", st_ulaw2linear16(x));
+ y++;
+ if (y == 7)
+ {
+ y = 0;
+ printf("\n ");
+ }
+ }
+
+ printf("\n};\n\nuint8_t _st_14linear2ulaw[0x4000] = {\n ");
+ y = 0;
+ for (x = 0; x < 0x4000; x++)
+ {
+ printf(" 0x%02x,", st_14linear2ulaw((-0x2000)+x));
+ y++;
+ if (y == 12)
+ {
+ y = 0;
+ printf("\n ");
+ }
+ }
+ printf("\n};\n");
+
+}
+#endif
+
+/* The following is not used by SoX but kept for reference */
+#if 0
+/* copy from CCITT G.711 specifications */
+unsigned char _u2a[128] = { /* u- to A-law conversions */
+ 1, 1, 2, 2, 3, 3, 4, 4,
+ 5, 5, 6, 6, 7, 7, 8, 8,
+ 9, 10, 11, 12, 13, 14, 15, 16,
+ 17, 18, 19, 20, 21, 22, 23, 24,
+ 25, 27, 29, 31, 33, 34, 35, 36,
+ 37, 38, 39, 40, 41, 42, 43, 44,
+ 46, 48, 49, 50, 51, 52, 53, 54,
+ 55, 56, 57, 58, 59, 60, 61, 62,
+ 64, 65, 66, 67, 68, 69, 70, 71,
+ 72, 73, 74, 75, 76, 77, 78, 79,
+/* corrected:
+ 81, 82, 83, 84, 85, 86, 87, 88,
+ should be: */
+ 80, 82, 83, 84, 85, 86, 87, 88,
+ 89, 90, 91, 92, 93, 94, 95, 96,
+ 97, 98, 99, 100, 101, 102, 103, 104,
+ 105, 106, 107, 108, 109, 110, 111, 112,
+ 113, 114, 115, 116, 117, 118, 119, 120,
+ 121, 122, 123, 124, 125, 126, 127, 128};
+
+unsigned char _a2u[128] = { /* A- to u-law conversions */
+ 1, 3, 5, 7, 9, 11, 13, 15,
+ 16, 17, 18, 19, 20, 21, 22, 23,
+ 24, 25, 26, 27, 28, 29, 30, 31,
+ 32, 32, 33, 33, 34, 34, 35, 35,
+ 36, 37, 38, 39, 40, 41, 42, 43,
+ 44, 45, 46, 47, 48, 48, 49, 49,
+ 50, 51, 52, 53, 54, 55, 56, 57,
+ 58, 59, 60, 61, 62, 63, 64, 64,
+ 65, 66, 67, 68, 69, 70, 71, 72,
+/* corrected:
+ 73, 74, 75, 76, 77, 78, 79, 79,
+ should be: */
+ 73, 74, 75, 76, 77, 78, 79, 80,
+
+ 80, 81, 82, 83, 84, 85, 86, 87,
+ 88, 89, 90, 91, 92, 93, 94, 95,
+ 96, 97, 98, 99, 100, 101, 102, 103,
+ 104, 105, 106, 107, 108, 109, 110, 111,
+ 112, 113, 114, 115, 116, 117, 118, 119,
+ 120, 121, 122, 123, 124, 125, 126, 127};
+
+/* A-law to u-law conversion */
+unsigned char st_alaw2ulaw(
+ unsigned char aval)
+{
+ aval &= 0xff;
+ return (unsigned char) ((aval & 0x80) ? (0xFF ^ _a2u[aval ^ 0xD5]) :
+ (0x7F ^ _a2u[aval ^ 0x55]));
+}
+
+/* u-law to A-law conversion */
+unsigned char st_ulaw2alaw(
+ unsigned char uval)
+{
+ uval &= 0xff;
+ return (unsigned char) ((uval & 0x80) ? (0xD5 ^ (_u2a[0xFF ^ uval] - 1)) :
+ (unsigned char) (0x55 ^ (_u2a[0x7F ^ uval] - 1)));
+}
+#endif
diff --git a/src/pulsecore/g711.h b/src/pulsecore/g711.h
new file mode 100644
index 0000000..37ebcf7
--- /dev/null
+++ b/src/pulsecore/g711.h
@@ -0,0 +1,40 @@
+#ifndef foog711hfoo
+#define foog711hfoo
+
+/* g711.h - include for G711 u-law and a-law conversion routines
+**
+** Copyright (C) 2001 Chris Bagwell
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation. This software is provided "as is" without express or
+** implied warranty.
+*/
+
+/** Copied from sox -- Lennart Poettering */
+
+#include <inttypes.h>
+
+#ifdef FAST_ALAW_CONVERSION
+extern uint8_t _st_13linear2alaw[0x2000];
+extern int16_t _st_alaw2linear16[256];
+#define st_13linear2alaw(sw) (_st_13linear2alaw[(sw + 0x1000)])
+#define st_alaw2linear16(uc) (_st_alaw2linear16[uc])
+#else
+unsigned char st_13linear2alaw(int16_t pcm_val);
+int16_t st_alaw2linear16(unsigned char);
+#endif
+
+#ifdef FAST_ULAW_CONVERSION
+extern uint8_t _st_14linear2ulaw[0x4000];
+extern int16_t _st_ulaw2linear16[256];
+#define st_14linear2ulaw(sw) (_st_14linear2ulaw[(sw + 0x2000)])
+#define st_ulaw2linear16(uc) (_st_ulaw2linear16[uc])
+#else
+unsigned char st_14linear2ulaw(int16_t pcm_val);
+int16_t st_ulaw2linear16(unsigned char);
+#endif
+
+#endif
diff --git a/src/pulsecore/hashmap.c b/src/pulsecore/hashmap.c
new file mode 100644
index 0000000..c2fc3f5
--- /dev/null
+++ b/src/pulsecore/hashmap.c
@@ -0,0 +1,342 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2008 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/idxset.h>
+#include <pulsecore/flist.h>
+#include <pulsecore/macro.h>
+
+#include "hashmap.h"
+
+#define NBUCKETS 127
+
+struct hashmap_entry {
+ void *key;
+ void *value;
+
+ struct hashmap_entry *bucket_next, *bucket_previous;
+ struct hashmap_entry *iterate_next, *iterate_previous;
+};
+
+struct pa_hashmap {
+ pa_hash_func_t hash_func;
+ pa_compare_func_t compare_func;
+
+ pa_free_cb_t key_free_func;
+ pa_free_cb_t value_free_func;
+
+ struct hashmap_entry *iterate_list_head, *iterate_list_tail;
+ unsigned n_entries;
+};
+
+#define BY_HASH(h) ((struct hashmap_entry**) ((uint8_t*) (h) + PA_ALIGN(sizeof(pa_hashmap))))
+
+PA_STATIC_FLIST_DECLARE(entries, 0, pa_xfree);
+
+pa_hashmap *pa_hashmap_new_full(pa_hash_func_t hash_func, pa_compare_func_t compare_func, pa_free_cb_t key_free_func, pa_free_cb_t value_free_func) {
+ pa_hashmap *h;
+
+ h = pa_xmalloc0(PA_ALIGN(sizeof(pa_hashmap)) + NBUCKETS*sizeof(struct hashmap_entry*));
+
+ h->hash_func = hash_func ? hash_func : pa_idxset_trivial_hash_func;
+ h->compare_func = compare_func ? compare_func : pa_idxset_trivial_compare_func;
+
+ h->key_free_func = key_free_func;
+ h->value_free_func = value_free_func;
+
+ h->n_entries = 0;
+ h->iterate_list_head = h->iterate_list_tail = NULL;
+
+ return h;
+}
+
+pa_hashmap *pa_hashmap_new(pa_hash_func_t hash_func, pa_compare_func_t compare_func) {
+ return pa_hashmap_new_full(hash_func, compare_func, NULL, NULL);
+}
+
+static void remove_entry(pa_hashmap *h, struct hashmap_entry *e) {
+ pa_assert(h);
+ pa_assert(e);
+
+ /* Remove from iteration list */
+ if (e->iterate_next)
+ e->iterate_next->iterate_previous = e->iterate_previous;
+ else
+ h->iterate_list_tail = e->iterate_previous;
+
+ if (e->iterate_previous)
+ e->iterate_previous->iterate_next = e->iterate_next;
+ else
+ h->iterate_list_head = e->iterate_next;
+
+ /* Remove from hash table bucket list */
+ if (e->bucket_next)
+ e->bucket_next->bucket_previous = e->bucket_previous;
+
+ if (e->bucket_previous)
+ e->bucket_previous->bucket_next = e->bucket_next;
+ else {
+ unsigned hash = h->hash_func(e->key) % NBUCKETS;
+ BY_HASH(h)[hash] = e->bucket_next;
+ }
+
+ if (h->key_free_func)
+ h->key_free_func(e->key);
+
+ if (pa_flist_push(PA_STATIC_FLIST_GET(entries), e) < 0)
+ pa_xfree(e);
+
+ pa_assert(h->n_entries >= 1);
+ h->n_entries--;
+}
+
+void pa_hashmap_free(pa_hashmap *h) {
+ pa_assert(h);
+
+ pa_hashmap_remove_all(h);
+ pa_xfree(h);
+}
+
+static struct hashmap_entry *hash_scan(const pa_hashmap *h, unsigned hash, const void *key) {
+ struct hashmap_entry *e;
+ pa_assert(h);
+ pa_assert(hash < NBUCKETS);
+
+ for (e = BY_HASH(h)[hash]; e; e = e->bucket_next)
+ if (h->compare_func(e->key, key) == 0)
+ return e;
+
+ return NULL;
+}
+
+int pa_hashmap_put(pa_hashmap *h, void *key, void *value) {
+ struct hashmap_entry *e;
+ unsigned hash;
+
+ pa_assert(h);
+
+ hash = h->hash_func(key) % NBUCKETS;
+
+ if (hash_scan(h, hash, key))
+ return -1;
+
+ if (!(e = pa_flist_pop(PA_STATIC_FLIST_GET(entries))))
+ e = pa_xnew(struct hashmap_entry, 1);
+
+ e->key = key;
+ e->value = value;
+
+ /* Insert into hash table */
+ e->bucket_next = BY_HASH(h)[hash];
+ e->bucket_previous = NULL;
+ if (BY_HASH(h)[hash])
+ BY_HASH(h)[hash]->bucket_previous = e;
+ BY_HASH(h)[hash] = e;
+
+ /* Insert into iteration list */
+ e->iterate_previous = h->iterate_list_tail;
+ e->iterate_next = NULL;
+ if (h->iterate_list_tail) {
+ pa_assert(h->iterate_list_head);
+ h->iterate_list_tail->iterate_next = e;
+ } else {
+ pa_assert(!h->iterate_list_head);
+ h->iterate_list_head = e;
+ }
+ h->iterate_list_tail = e;
+
+ h->n_entries++;
+ pa_assert(h->n_entries >= 1);
+
+ return 0;
+}
+
+void* pa_hashmap_get(const pa_hashmap *h, const void *key) {
+ unsigned hash;
+ struct hashmap_entry *e;
+
+ pa_assert(h);
+
+ hash = h->hash_func(key) % NBUCKETS;
+
+ if (!(e = hash_scan(h, hash, key)))
+ return NULL;
+
+ return e->value;
+}
+
+void* pa_hashmap_remove(pa_hashmap *h, const void *key) {
+ struct hashmap_entry *e;
+ unsigned hash;
+ void *data;
+
+ pa_assert(h);
+
+ hash = h->hash_func(key) % NBUCKETS;
+
+ if (!(e = hash_scan(h, hash, key)))
+ return NULL;
+
+ data = e->value;
+ remove_entry(h, e);
+
+ return data;
+}
+
+int pa_hashmap_remove_and_free(pa_hashmap *h, const void *key) {
+ void *data;
+
+ pa_assert(h);
+
+ data = pa_hashmap_remove(h, key);
+
+ if (data && h->value_free_func)
+ h->value_free_func(data);
+
+ return data ? 0 : -1;
+}
+
+void pa_hashmap_remove_all(pa_hashmap *h) {
+ pa_assert(h);
+
+ while (h->iterate_list_head) {
+ void *data;
+ data = h->iterate_list_head->value;
+ remove_entry(h, h->iterate_list_head);
+
+ if (h->value_free_func)
+ h->value_free_func(data);
+ }
+}
+
+void *pa_hashmap_iterate(const pa_hashmap *h, void **state, const void **key) {
+ struct hashmap_entry *e;
+
+ pa_assert(h);
+ pa_assert(state);
+
+ if (*state == (void*) -1)
+ goto at_end;
+
+ if (!*state && !h->iterate_list_head)
+ goto at_end;
+
+ e = *state ? *state : h->iterate_list_head;
+
+ if (e->iterate_next)
+ *state = e->iterate_next;
+ else
+ *state = (void*) -1;
+
+ if (key)
+ *key = e->key;
+
+ return e->value;
+
+at_end:
+ *state = (void *) -1;
+
+ if (key)
+ *key = NULL;
+
+ return NULL;
+}
+
+void *pa_hashmap_iterate_backwards(const pa_hashmap *h, void **state, const void **key) {
+ struct hashmap_entry *e;
+
+ pa_assert(h);
+ pa_assert(state);
+
+ if (*state == (void*) -1)
+ goto at_beginning;
+
+ if (!*state && !h->iterate_list_tail)
+ goto at_beginning;
+
+ e = *state ? *state : h->iterate_list_tail;
+
+ if (e->iterate_previous)
+ *state = e->iterate_previous;
+ else
+ *state = (void*) -1;
+
+ if (key)
+ *key = e->key;
+
+ return e->value;
+
+at_beginning:
+ *state = (void *) -1;
+
+ if (key)
+ *key = NULL;
+
+ return NULL;
+}
+
+void* pa_hashmap_first(const pa_hashmap *h) {
+ pa_assert(h);
+
+ if (!h->iterate_list_head)
+ return NULL;
+
+ return h->iterate_list_head->value;
+}
+
+void* pa_hashmap_last(const pa_hashmap *h) {
+ pa_assert(h);
+
+ if (!h->iterate_list_tail)
+ return NULL;
+
+ return h->iterate_list_tail->value;
+}
+
+void* pa_hashmap_steal_first(pa_hashmap *h) {
+ void *data;
+
+ pa_assert(h);
+
+ if (!h->iterate_list_head)
+ return NULL;
+
+ data = h->iterate_list_head->value;
+ remove_entry(h, h->iterate_list_head);
+
+ return data;
+}
+
+unsigned pa_hashmap_size(const pa_hashmap *h) {
+ pa_assert(h);
+
+ return h->n_entries;
+}
+
+bool pa_hashmap_isempty(const pa_hashmap *h) {
+ pa_assert(h);
+
+ return h->n_entries == 0;
+}
diff --git a/src/pulsecore/hashmap.h b/src/pulsecore/hashmap.h
new file mode 100644
index 0000000..c18a564
--- /dev/null
+++ b/src/pulsecore/hashmap.h
@@ -0,0 +1,101 @@
+#ifndef foopulsecorehashmaphfoo
+#define foopulsecorehashmaphfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2008 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulse/def.h>
+
+#include <pulsecore/idxset.h>
+
+/* Simple Implementation of a hash table. Memory management is the
+ * user's job. It's a good idea to have the key pointer point to a
+ * string in the value data. The insertion order is preserved when
+ * iterating. */
+
+typedef struct pa_hashmap pa_hashmap;
+
+/* Create a new hashmap. Use the specified functions for hashing and comparing objects in the map */
+pa_hashmap *pa_hashmap_new(pa_hash_func_t hash_func, pa_compare_func_t compare_func);
+
+/* Create a new hashmap. Use the specified functions for hashing and comparing objects in the map, and functions to free the key
+ * and value (either or both can be NULL). */
+pa_hashmap *pa_hashmap_new_full(pa_hash_func_t hash_func, pa_compare_func_t compare_func, pa_free_cb_t key_free_func, pa_free_cb_t value_free_func);
+
+/* Free the hash table. */
+void pa_hashmap_free(pa_hashmap*);
+
+/* Add an entry to the hashmap. Returns non-zero when the entry already exists */
+int pa_hashmap_put(pa_hashmap *h, void *key, void *value);
+
+/* Return an entry from the hashmap */
+void* pa_hashmap_get(const pa_hashmap *h, const void *key);
+
+/* Returns the data of the entry while removing */
+void* pa_hashmap_remove(pa_hashmap *h, const void *key);
+
+/* Removes the entry and frees the entry data. Returns a negative value if the
+ * entry is not found. FIXME: This function shouldn't be needed.
+ * pa_hashmap_remove() should free the entry data, and the current semantics of
+ * pa_hashmap_remove() should be implemented by a function called
+ * pa_hashmap_steal(). */
+int pa_hashmap_remove_and_free(pa_hashmap *h, const void *key);
+
+/* Remove all entries but don't free the hashmap */
+void pa_hashmap_remove_all(pa_hashmap *h);
+
+/* Return the current number of entries of the hashmap */
+unsigned pa_hashmap_size(const pa_hashmap *h);
+
+/* Return true if the hashmap is empty */
+bool pa_hashmap_isempty(const pa_hashmap *h);
+
+/* May be used to iterate through the hashmap. Initially the opaque
+ pointer *state has to be set to NULL. The hashmap may not be
+ modified during iteration -- except for deleting the current entry
+ via pa_hashmap_remove(). The key of the entry is returned in *key,
+ if key is non-NULL. After the last entry in the hashmap NULL is
+ returned. */
+void *pa_hashmap_iterate(const pa_hashmap *h, void **state, const void**key);
+
+/* Same as pa_hashmap_iterate() but goes backwards */
+void *pa_hashmap_iterate_backwards(const pa_hashmap *h, void **state, const void**key);
+
+/* Remove the oldest entry in the hashmap and return it */
+void *pa_hashmap_steal_first(pa_hashmap *h);
+
+/* Return the oldest entry in the hashmap */
+void* pa_hashmap_first(const pa_hashmap *h);
+
+/* Return the newest entry in the hashmap */
+void* pa_hashmap_last(const pa_hashmap *h);
+
+/* A macro to ease iteration through all entries */
+#define PA_HASHMAP_FOREACH(e, h, state) \
+ for ((state) = NULL, (e) = pa_hashmap_iterate((h), &(state), NULL); (e); (e) = pa_hashmap_iterate((h), &(state), NULL))
+
+/* A macro to ease itration through all key, value pairs */
+#define PA_HASHMAP_FOREACH_KV(k, e, h, state) \
+ for ((state) = NULL, (e) = pa_hashmap_iterate((h), &(state), (const void **) &(k)); (e); (e) = pa_hashmap_iterate((h), &(state), (const void **) &(k)))
+
+/* A macro to ease iteration through all entries, backwards */
+#define PA_HASHMAP_FOREACH_BACKWARDS(e, h, state) \
+ for ((state) = NULL, (e) = pa_hashmap_iterate_backwards((h), &(state), NULL); (e); (e) = pa_hashmap_iterate_backwards((h), &(state), NULL))
+
+#endif
diff --git a/src/pulsecore/hook-list.c b/src/pulsecore/hook-list.c
new file mode 100644
index 0000000..e59dcba
--- /dev/null
+++ b/src/pulsecore/hook-list.c
@@ -0,0 +1,129 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/macro.h>
+
+#include "hook-list.h"
+
+void pa_hook_init(pa_hook *hook, void *data) {
+ pa_assert(hook);
+
+ PA_LLIST_HEAD_INIT(pa_hook_slot, hook->slots);
+ hook->n_dead = hook->n_firing = 0;
+ hook->data = data;
+}
+
+static void slot_free(pa_hook *hook, pa_hook_slot *slot) {
+ pa_assert(hook);
+ pa_assert(slot);
+
+ PA_LLIST_REMOVE(pa_hook_slot, hook->slots, slot);
+
+ pa_xfree(slot);
+}
+
+void pa_hook_done(pa_hook *hook) {
+ pa_assert(hook);
+ pa_assert(hook->n_firing == 0);
+
+ while (hook->slots)
+ slot_free(hook, hook->slots);
+
+ pa_hook_init(hook, NULL);
+}
+
+pa_hook_slot* pa_hook_connect(pa_hook *hook, pa_hook_priority_t prio, pa_hook_cb_t cb, void *data) {
+ pa_hook_slot *slot, *where, *prev;
+
+ pa_assert(cb);
+
+ slot = pa_xnew(pa_hook_slot, 1);
+ slot->hook = hook;
+ slot->dead = false;
+ slot->callback = cb;
+ slot->data = data;
+ slot->priority = prio;
+
+ prev = NULL;
+ for (where = hook->slots; where; where = where->next) {
+ if (prio < where->priority)
+ break;
+ prev = where;
+ }
+
+ PA_LLIST_INSERT_AFTER(pa_hook_slot, hook->slots, prev, slot);
+
+ return slot;
+}
+
+void pa_hook_slot_free(pa_hook_slot *slot) {
+ pa_assert(slot);
+ pa_assert(!slot->dead);
+
+ if (slot->hook->n_firing > 0) {
+ slot->dead = true;
+ slot->hook->n_dead++;
+ } else
+ slot_free(slot->hook, slot);
+}
+
+pa_hook_result_t pa_hook_fire(pa_hook *hook, void *data) {
+ pa_hook_slot *slot, *next;
+ pa_hook_result_t result = PA_HOOK_OK;
+
+ pa_assert(hook);
+
+ hook->n_firing ++;
+
+ PA_LLIST_FOREACH(slot, hook->slots) {
+ if (slot->dead)
+ continue;
+
+ if ((result = slot->callback(hook->data, data, slot->data)) != PA_HOOK_OK)
+ break;
+ }
+
+ hook->n_firing --;
+ pa_assert(hook->n_firing >= 0);
+
+ for (slot = hook->slots; hook->n_dead > 0 && slot; slot = next) {
+ next = slot->next;
+
+ if (slot->dead) {
+ slot_free(hook, slot);
+ hook->n_dead--;
+ }
+ }
+
+ pa_assert(hook->n_dead == 0);
+
+ return result;
+}
+
+bool pa_hook_is_firing(pa_hook *hook) {
+ pa_assert(hook);
+
+ return hook->n_firing > 0;
+}
diff --git a/src/pulsecore/hook-list.h b/src/pulsecore/hook-list.h
new file mode 100644
index 0000000..63037e7
--- /dev/null
+++ b/src/pulsecore/hook-list.h
@@ -0,0 +1,71 @@
+#ifndef foohooklistfoo
+#define foohooklistfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulsecore/llist.h>
+
+typedef struct pa_hook_slot pa_hook_slot;
+typedef struct pa_hook pa_hook;
+
+typedef enum pa_hook_result {
+ PA_HOOK_OK = 0,
+ PA_HOOK_STOP = 1,
+ PA_HOOK_CANCEL = -1
+} pa_hook_result_t;
+
+typedef enum pa_hook_priority {
+ PA_HOOK_EARLY = -100,
+ PA_HOOK_NORMAL = 0,
+ PA_HOOK_LATE = 100
+} pa_hook_priority_t;
+
+typedef pa_hook_result_t (*pa_hook_cb_t)(
+ void *hook_data,
+ void *call_data,
+ void *slot_data);
+
+struct pa_hook_slot {
+ bool dead;
+ pa_hook *hook;
+ pa_hook_priority_t priority;
+ pa_hook_cb_t callback;
+ void *data;
+ PA_LLIST_FIELDS(pa_hook_slot);
+};
+
+struct pa_hook {
+ PA_LLIST_HEAD(pa_hook_slot, slots);
+ int n_firing, n_dead;
+
+ void *data;
+};
+
+void pa_hook_init(pa_hook *hook, void *data);
+void pa_hook_done(pa_hook *hook);
+
+pa_hook_slot* pa_hook_connect(pa_hook *hook, pa_hook_priority_t prio, pa_hook_cb_t cb, void *data);
+void pa_hook_slot_free(pa_hook_slot *slot);
+
+pa_hook_result_t pa_hook_fire(pa_hook *hook, void *data);
+
+bool pa_hook_is_firing(pa_hook *hook);
+
+#endif
diff --git a/src/pulsecore/i18n.c b/src/pulsecore/i18n.c
new file mode 100644
index 0000000..1cd74c0
--- /dev/null
+++ b/src/pulsecore/i18n.c
@@ -0,0 +1,37 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2008 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulsecore/once.h>
+
+#include "i18n.h"
+
+void pa_init_i18n(void) {
+#ifdef ENABLE_NLS
+ PA_ONCE_BEGIN {
+
+ bindtextdomain(GETTEXT_PACKAGE, PULSE_LOCALEDIR);
+ bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
+
+ } PA_ONCE_END;
+#endif
+}
diff --git a/src/pulsecore/i18n.h b/src/pulsecore/i18n.h
new file mode 100644
index 0000000..edf894f
--- /dev/null
+++ b/src/pulsecore/i18n.h
@@ -0,0 +1,62 @@
+#ifndef foopulsei18nhfoo
+#define foopulsei18nhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2008 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulse/cdecl.h>
+
+PA_C_DECL_BEGIN
+
+#ifdef ENABLE_NLS
+
+#if !defined(GETTEXT_PACKAGE)
+#error "Something is very wrong here, config.h needs to be included first"
+#endif
+
+#include <libintl.h>
+
+#define _(String) dgettext(GETTEXT_PACKAGE, String)
+#ifdef gettext_noop
+#define N_(String) gettext_noop(String)
+#else
+#define N_(String) (String)
+#endif
+
+#else /* NLS is disabled */
+
+#define _(String) (String)
+#define N_(String) (String)
+#define textdomain(String) (String)
+#define gettext(String) (String)
+#define dgettext(Domain,String) (String)
+#define dcgettext(Domain,String,Type) (String)
+#define ngettext(SingularString,PluralString,N) (PluralString)
+#define dngettext(Domain,SingularString,PluralString,N) (PluralString)
+#define dcngettext(Domain,SingularString,PluralString,N,Type) (PluralString)
+#define bindtextdomain(Domain,Directory) (Domain)
+#define bind_textdomain_codeset(Domain,Codeset) (Codeset)
+
+#endif /* ENABLE_NLS */
+
+void pa_init_i18n(void);
+
+PA_C_DECL_END
+
+#endif
diff --git a/src/pulsecore/idxset.c b/src/pulsecore/idxset.c
new file mode 100644
index 0000000..5175ca2
--- /dev/null
+++ b/src/pulsecore/idxset.c
@@ -0,0 +1,471 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2008 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/flist.h>
+#include <pulsecore/macro.h>
+
+#include "idxset.h"
+
+#define NBUCKETS 127
+
+struct idxset_entry {
+ uint32_t idx;
+ void *data;
+
+ struct idxset_entry *data_next, *data_previous;
+ struct idxset_entry *index_next, *index_previous;
+ struct idxset_entry *iterate_next, *iterate_previous;
+};
+
+struct pa_idxset {
+ pa_hash_func_t hash_func;
+ pa_compare_func_t compare_func;
+
+ uint32_t current_index;
+
+ struct idxset_entry *iterate_list_head, *iterate_list_tail;
+ unsigned n_entries;
+};
+
+#define BY_DATA(i) ((struct idxset_entry**) ((uint8_t*) (i) + PA_ALIGN(sizeof(pa_idxset))))
+#define BY_INDEX(i) (BY_DATA(i) + NBUCKETS)
+
+PA_STATIC_FLIST_DECLARE(entries, 0, pa_xfree);
+
+unsigned pa_idxset_string_hash_func(const void *p) {
+ unsigned hash = 0;
+ const char *c;
+
+ for (c = p; *c; c++)
+ hash = 31 * hash + (unsigned) *c;
+
+ return hash;
+}
+
+int pa_idxset_string_compare_func(const void *a, const void *b) {
+ return strcmp(a, b);
+}
+
+unsigned pa_idxset_trivial_hash_func(const void *p) {
+ return PA_PTR_TO_UINT(p);
+}
+
+int pa_idxset_trivial_compare_func(const void *a, const void *b) {
+ return a < b ? -1 : (a > b ? 1 : 0);
+}
+
+pa_idxset* pa_idxset_new(pa_hash_func_t hash_func, pa_compare_func_t compare_func) {
+ pa_idxset *s;
+
+ s = pa_xmalloc0(PA_ALIGN(sizeof(pa_idxset)) + NBUCKETS*2*sizeof(struct idxset_entry*));
+
+ s->hash_func = hash_func ? hash_func : pa_idxset_trivial_hash_func;
+ s->compare_func = compare_func ? compare_func : pa_idxset_trivial_compare_func;
+
+ s->current_index = 0;
+ s->n_entries = 0;
+ s->iterate_list_head = s->iterate_list_tail = NULL;
+
+ return s;
+}
+
+static void remove_entry(pa_idxset *s, struct idxset_entry *e) {
+ pa_assert(s);
+ pa_assert(e);
+
+ /* Remove from iteration linked list */
+ if (e->iterate_next)
+ e->iterate_next->iterate_previous = e->iterate_previous;
+ else
+ s->iterate_list_tail = e->iterate_previous;
+
+ if (e->iterate_previous)
+ e->iterate_previous->iterate_next = e->iterate_next;
+ else
+ s->iterate_list_head = e->iterate_next;
+
+ /* Remove from data hash table */
+ if (e->data_next)
+ e->data_next->data_previous = e->data_previous;
+
+ if (e->data_previous)
+ e->data_previous->data_next = e->data_next;
+ else {
+ unsigned hash = s->hash_func(e->data) % NBUCKETS;
+ BY_DATA(s)[hash] = e->data_next;
+ }
+
+ /* Remove from index hash table */
+ if (e->index_next)
+ e->index_next->index_previous = e->index_previous;
+
+ if (e->index_previous)
+ e->index_previous->index_next = e->index_next;
+ else
+ BY_INDEX(s)[e->idx % NBUCKETS] = e->index_next;
+
+ if (pa_flist_push(PA_STATIC_FLIST_GET(entries), e) < 0)
+ pa_xfree(e);
+
+ pa_assert(s->n_entries >= 1);
+ s->n_entries--;
+}
+
+void pa_idxset_free(pa_idxset *s, pa_free_cb_t free_cb) {
+ pa_assert(s);
+
+ pa_idxset_remove_all(s, free_cb);
+ pa_xfree(s);
+}
+
+static struct idxset_entry* data_scan(pa_idxset *s, unsigned hash, const void *p) {
+ struct idxset_entry *e;
+ pa_assert(s);
+ pa_assert(hash < NBUCKETS);
+ pa_assert(p);
+
+ for (e = BY_DATA(s)[hash]; e; e = e->data_next)
+ if (s->compare_func(e->data, p) == 0)
+ return e;
+
+ return NULL;
+}
+
+static struct idxset_entry* index_scan(pa_idxset *s, unsigned hash, uint32_t idx) {
+ struct idxset_entry *e;
+ pa_assert(s);
+ pa_assert(hash < NBUCKETS);
+
+ for (e = BY_INDEX(s)[hash]; e; e = e->index_next)
+ if (e->idx == idx)
+ return e;
+
+ return NULL;
+}
+
+int pa_idxset_put(pa_idxset*s, void *p, uint32_t *idx) {
+ unsigned hash;
+ struct idxset_entry *e;
+
+ pa_assert(s);
+
+ hash = s->hash_func(p) % NBUCKETS;
+
+ if ((e = data_scan(s, hash, p))) {
+ if (idx)
+ *idx = e->idx;
+
+ return -1;
+ }
+
+ if (!(e = pa_flist_pop(PA_STATIC_FLIST_GET(entries))))
+ e = pa_xnew(struct idxset_entry, 1);
+
+ e->data = p;
+ e->idx = s->current_index++;
+
+ /* Insert into data hash table */
+ e->data_next = BY_DATA(s)[hash];
+ e->data_previous = NULL;
+ if (BY_DATA(s)[hash])
+ BY_DATA(s)[hash]->data_previous = e;
+ BY_DATA(s)[hash] = e;
+
+ hash = e->idx % NBUCKETS;
+
+ /* Insert into index hash table */
+ e->index_next = BY_INDEX(s)[hash];
+ e->index_previous = NULL;
+ if (BY_INDEX(s)[hash])
+ BY_INDEX(s)[hash]->index_previous = e;
+ BY_INDEX(s)[hash] = e;
+
+ /* Insert into iteration list */
+ e->iterate_previous = s->iterate_list_tail;
+ e->iterate_next = NULL;
+ if (s->iterate_list_tail) {
+ pa_assert(s->iterate_list_head);
+ s->iterate_list_tail->iterate_next = e;
+ } else {
+ pa_assert(!s->iterate_list_head);
+ s->iterate_list_head = e;
+ }
+ s->iterate_list_tail = e;
+
+ s->n_entries++;
+ pa_assert(s->n_entries >= 1);
+
+ if (idx)
+ *idx = e->idx;
+
+ return 0;
+}
+
+void* pa_idxset_get_by_index(pa_idxset*s, uint32_t idx) {
+ unsigned hash;
+ struct idxset_entry *e;
+
+ pa_assert(s);
+
+ hash = idx % NBUCKETS;
+
+ if (!(e = index_scan(s, hash, idx)))
+ return NULL;
+
+ return e->data;
+}
+
+void* pa_idxset_get_by_data(pa_idxset*s, const void *p, uint32_t *idx) {
+ unsigned hash;
+ struct idxset_entry *e;
+
+ pa_assert(s);
+
+ hash = s->hash_func(p) % NBUCKETS;
+
+ if (!(e = data_scan(s, hash, p)))
+ return NULL;
+
+ if (idx)
+ *idx = e->idx;
+
+ return e->data;
+}
+
+void* pa_idxset_remove_by_index(pa_idxset*s, uint32_t idx) {
+ struct idxset_entry *e;
+ unsigned hash;
+ void *data;
+
+ pa_assert(s);
+
+ hash = idx % NBUCKETS;
+
+ if (!(e = index_scan(s, hash, idx)))
+ return NULL;
+
+ data = e->data;
+ remove_entry(s, e);
+
+ return data;
+}
+
+void* pa_idxset_remove_by_data(pa_idxset*s, const void *data, uint32_t *idx) {
+ struct idxset_entry *e;
+ unsigned hash;
+ void *r;
+
+ pa_assert(s);
+
+ hash = s->hash_func(data) % NBUCKETS;
+
+ if (!(e = data_scan(s, hash, data)))
+ return NULL;
+
+ r = e->data;
+
+ if (idx)
+ *idx = e->idx;
+
+ remove_entry(s, e);
+
+ return r;
+}
+
+void pa_idxset_remove_all(pa_idxset *s, pa_free_cb_t free_cb) {
+ pa_assert(s);
+
+ while (s->iterate_list_head) {
+ void *data = s->iterate_list_head->data;
+
+ remove_entry(s, s->iterate_list_head);
+
+ if (free_cb)
+ free_cb(data);
+ }
+}
+
+void* pa_idxset_rrobin(pa_idxset *s, uint32_t *idx) {
+ unsigned hash;
+ struct idxset_entry *e;
+
+ pa_assert(s);
+ pa_assert(idx);
+
+ hash = *idx % NBUCKETS;
+
+ e = index_scan(s, hash, *idx);
+
+ if (e && e->iterate_next)
+ e = e->iterate_next;
+ else
+ e = s->iterate_list_head;
+
+ if (!e)
+ return NULL;
+
+ *idx = e->idx;
+ return e->data;
+}
+
+void *pa_idxset_iterate(pa_idxset *s, void **state, uint32_t *idx) {
+ struct idxset_entry *e;
+
+ pa_assert(s);
+ pa_assert(state);
+
+ if (*state == (void*) -1)
+ goto at_end;
+
+ if ((!*state && !s->iterate_list_head))
+ goto at_end;
+
+ e = *state ? *state : s->iterate_list_head;
+
+ if (e->iterate_next)
+ *state = e->iterate_next;
+ else
+ *state = (void*) -1;
+
+ if (idx)
+ *idx = e->idx;
+
+ return e->data;
+
+at_end:
+ *state = (void *) -1;
+
+ if (idx)
+ *idx = PA_IDXSET_INVALID;
+
+ return NULL;
+}
+
+void* pa_idxset_steal_first(pa_idxset *s, uint32_t *idx) {
+ void *data;
+
+ pa_assert(s);
+
+ if (!s->iterate_list_head)
+ return NULL;
+
+ data = s->iterate_list_head->data;
+
+ if (idx)
+ *idx = s->iterate_list_head->idx;
+
+ remove_entry(s, s->iterate_list_head);
+
+ return data;
+}
+
+void* pa_idxset_first(pa_idxset *s, uint32_t *idx) {
+ pa_assert(s);
+
+ if (!s->iterate_list_head) {
+ if (idx)
+ *idx = PA_IDXSET_INVALID;
+ return NULL;
+ }
+
+ if (idx)
+ *idx = s->iterate_list_head->idx;
+
+ return s->iterate_list_head->data;
+}
+
+void *pa_idxset_next(pa_idxset *s, uint32_t *idx) {
+ struct idxset_entry *e;
+ unsigned hash;
+
+ pa_assert(s);
+ pa_assert(idx);
+
+ if (*idx == PA_IDXSET_INVALID)
+ return NULL;
+
+ hash = *idx % NBUCKETS;
+
+ if ((e = index_scan(s, hash, *idx))) {
+
+ e = e->iterate_next;
+
+ if (e) {
+ *idx = e->idx;
+ return e->data;
+ } else {
+ *idx = PA_IDXSET_INVALID;
+ return NULL;
+ }
+
+ } else {
+
+ /* If the entry passed doesn't exist anymore we try to find
+ * the next following */
+
+ for ((*idx)++; *idx < s->current_index; (*idx)++) {
+
+ hash = *idx % NBUCKETS;
+
+ if ((e = index_scan(s, hash, *idx))) {
+ *idx = e->idx;
+ return e->data;
+ }
+ }
+
+ *idx = PA_IDXSET_INVALID;
+ return NULL;
+ }
+}
+
+unsigned pa_idxset_size(pa_idxset*s) {
+ pa_assert(s);
+
+ return s->n_entries;
+}
+
+bool pa_idxset_isempty(pa_idxset *s) {
+ pa_assert(s);
+
+ return s->n_entries == 0;
+}
+
+pa_idxset *pa_idxset_copy(pa_idxset *s, pa_copy_func_t copy_func) {
+ pa_idxset *copy;
+ struct idxset_entry *i;
+
+ pa_assert(s);
+
+ copy = pa_idxset_new(s->hash_func, s->compare_func);
+
+ for (i = s->iterate_list_head; i; i = i->iterate_next)
+ pa_idxset_put(copy, copy_func ? copy_func(i->data) : i->data, NULL);
+
+ return copy;
+}
diff --git a/src/pulsecore/idxset.h b/src/pulsecore/idxset.h
new file mode 100644
index 0000000..7acb202
--- /dev/null
+++ b/src/pulsecore/idxset.h
@@ -0,0 +1,116 @@
+#ifndef foopulsecoreidxsethfoo
+#define foopulsecoreidxsethfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2008 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <inttypes.h>
+
+#include <pulse/def.h>
+
+#include <pulsecore/macro.h>
+
+/* A combination of a set and a dynamic array. Entries are indexable
+ * both through an automatically generated numeric index and the
+ * entry's data pointer. As usual, memory management is the user's
+ * job. */
+
+/* A special index value denoting the invalid index. */
+#define PA_IDXSET_INVALID ((uint32_t) -1)
+
+/* Generic implementations for hash and comparison functions. Just
+ * compares the pointer or calculates the hash value directly from the
+ * pointer value. */
+unsigned pa_idxset_trivial_hash_func(const void *p);
+int pa_idxset_trivial_compare_func(const void *a, const void *b);
+
+/* Generic implementations for hash and comparison functions for strings. */
+unsigned pa_idxset_string_hash_func(const void *p);
+int pa_idxset_string_compare_func(const void *a, const void *b);
+
+typedef unsigned (*pa_hash_func_t)(const void *p);
+typedef int (*pa_compare_func_t)(const void *a, const void *b);
+typedef void *(*pa_copy_func_t)(const void *p);
+
+typedef struct pa_idxset pa_idxset;
+
+/* Instantiate a new idxset with the specified hash and comparison functions */
+pa_idxset* pa_idxset_new(pa_hash_func_t hash_func, pa_compare_func_t compare_func);
+
+/* Free the idxset. When the idxset is not empty the specified function is called for every entry contained */
+void pa_idxset_free(pa_idxset *s, pa_free_cb_t free_cb);
+
+/* Store a new item in the idxset. The index of the item is returned in *idx */
+int pa_idxset_put(pa_idxset*s, void *p, uint32_t *idx);
+
+/* Get the entry by its idx */
+void* pa_idxset_get_by_index(pa_idxset*s, uint32_t idx);
+
+/* Get the entry by its data. The index is returned in *idx */
+void* pa_idxset_get_by_data(pa_idxset*s, const void *p, uint32_t *idx);
+
+/* Similar to pa_idxset_get_by_index(), but removes the entry from the idxset. */
+void* pa_idxset_remove_by_index(pa_idxset*s, uint32_t idx);
+
+/* Similar to pa_idxset_get_by_data(), but removes the entry from the idxset */
+void* pa_idxset_remove_by_data(pa_idxset*s, const void *p, uint32_t *idx);
+
+/* If free_cb is not NULL, it's called for each entry. */
+void pa_idxset_remove_all(pa_idxset *s, pa_free_cb_t free_cb);
+
+/* This may be used to iterate through all entries. When called with
+ an invalid index value it returns the first entry, otherwise the
+ next following. The function is best called with *idx =
+ PA_IDXSET_VALID first. It is safe to manipulate the idxset between
+ the calls. It is not guaranteed that all entries have already been
+ returned before the an entry is returned the second time.*/
+void* pa_idxset_rrobin(pa_idxset *s, uint32_t *idx);
+
+/* Iterate through the idxset. At first iteration state should be NULL */
+void *pa_idxset_iterate(pa_idxset *s, void **state, uint32_t *idx);
+
+/* Return the oldest entry in the idxset and remove it. If idx is not NULL fill in its index in *idx */
+void* pa_idxset_steal_first(pa_idxset *s, uint32_t *idx);
+
+/* Return the oldest entry in the idxset. Fill in its index in *idx. */
+void* pa_idxset_first(pa_idxset *s, uint32_t *idx);
+
+/* Return the entry following the entry indexed by *idx. After the
+ * call *index contains the index of the returned
+ * object. pa_idxset_first() and pa_idxset_next() may be used to
+ * iterate through the set.*/
+void *pa_idxset_next(pa_idxset *s, uint32_t *idx);
+
+/* Return the current number of entries in the idxset */
+unsigned pa_idxset_size(pa_idxset*s);
+
+/* Return true of the idxset is empty */
+bool pa_idxset_isempty(pa_idxset *s);
+
+/* Duplicate the idxset. This will not copy the actual indexes. If copy_func is
+ * set, each entry is copied using the provided function, otherwise a shallow
+ * copy will be made. */
+pa_idxset *pa_idxset_copy(pa_idxset *s, pa_copy_func_t copy_func);
+
+/* A macro to ease iteration through all entries */
+#define PA_IDXSET_FOREACH(e, s, idx) \
+ for ((e) = pa_idxset_first((s), &(idx)); (e); (e) = pa_idxset_next((s), &(idx)))
+
+#endif
diff --git a/src/pulsecore/iochannel.c b/src/pulsecore/iochannel.c
new file mode 100644
index 0000000..e25824b
--- /dev/null
+++ b/src/pulsecore/iochannel.c
@@ -0,0 +1,541 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006-2007 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+
+#ifdef HAVE_SYS_UN_H
+#include <sys/un.h>
+#endif
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/core-error.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/socket.h>
+#include <pulsecore/socket-util.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+
+#include "iochannel.h"
+
+struct pa_iochannel {
+ int ifd, ofd;
+ int ifd_type, ofd_type;
+ pa_mainloop_api* mainloop;
+
+ pa_iochannel_cb_t callback;
+ void*userdata;
+
+ bool readable:1;
+ bool writable:1;
+ bool hungup:1;
+ bool no_close:1;
+
+ pa_io_event* input_event, *output_event;
+};
+
+static void callback(pa_mainloop_api* m, pa_io_event *e, int fd, pa_io_event_flags_t f, void *userdata);
+
+static void delete_events(pa_iochannel *io) {
+ pa_assert(io);
+
+ if (io->input_event)
+ io->mainloop->io_free(io->input_event);
+
+ if (io->output_event && io->output_event != io->input_event)
+ io->mainloop->io_free(io->output_event);
+
+ io->input_event = io->output_event = NULL;
+}
+
+static void enable_events(pa_iochannel *io) {
+ pa_assert(io);
+
+ if (io->hungup) {
+ delete_events(io);
+ return;
+ }
+
+ if (io->ifd == io->ofd && io->ifd >= 0) {
+ pa_io_event_flags_t f = PA_IO_EVENT_NULL;
+
+ if (!io->readable)
+ f |= PA_IO_EVENT_INPUT;
+ if (!io->writable)
+ f |= PA_IO_EVENT_OUTPUT;
+
+ pa_assert(io->input_event == io->output_event);
+
+ if (f != PA_IO_EVENT_NULL) {
+ if (io->input_event)
+ io->mainloop->io_enable(io->input_event, f);
+ else
+ io->input_event = io->output_event = io->mainloop->io_new(io->mainloop, io->ifd, f, callback, io);
+ } else
+ delete_events(io);
+
+ } else {
+
+ if (io->ifd >= 0) {
+ if (!io->readable) {
+ if (io->input_event)
+ io->mainloop->io_enable(io->input_event, PA_IO_EVENT_INPUT);
+ else
+ io->input_event = io->mainloop->io_new(io->mainloop, io->ifd, PA_IO_EVENT_INPUT, callback, io);
+ } else if (io->input_event) {
+ io->mainloop->io_free(io->input_event);
+ io->input_event = NULL;
+ }
+ }
+
+ if (io->ofd >= 0) {
+ if (!io->writable) {
+ if (io->output_event)
+ io->mainloop->io_enable(io->output_event, PA_IO_EVENT_OUTPUT);
+ else
+ io->output_event = io->mainloop->io_new(io->mainloop, io->ofd, PA_IO_EVENT_OUTPUT, callback, io);
+ } else if (io->output_event) {
+ io->mainloop->io_free(io->output_event);
+ io->output_event = NULL;
+ }
+ }
+ }
+}
+
+static void callback(pa_mainloop_api* m, pa_io_event *e, int fd, pa_io_event_flags_t f, void *userdata) {
+ pa_iochannel *io = userdata;
+ bool changed = false;
+
+ pa_assert(m);
+ pa_assert(e);
+ pa_assert(fd >= 0);
+ pa_assert(userdata);
+
+ if ((f & (PA_IO_EVENT_HANGUP|PA_IO_EVENT_ERROR)) && !io->hungup) {
+ io->hungup = true;
+ changed = true;
+ }
+
+ if ((f & PA_IO_EVENT_INPUT) && !io->readable) {
+ io->readable = true;
+ changed = true;
+ pa_assert(e == io->input_event);
+ }
+
+ if ((f & PA_IO_EVENT_OUTPUT) && !io->writable) {
+ io->writable = true;
+ changed = true;
+ pa_assert(e == io->output_event);
+ }
+
+ if (changed) {
+ enable_events(io);
+
+ if (io->callback)
+ io->callback(io, io->userdata);
+ }
+}
+
+pa_iochannel* pa_iochannel_new(pa_mainloop_api*m, int ifd, int ofd) {
+ pa_iochannel *io;
+
+ pa_assert(m);
+ pa_assert(ifd >= 0 || ofd >= 0);
+
+ io = pa_xnew0(pa_iochannel, 1);
+ io->ifd = ifd;
+ io->ofd = ofd;
+ io->mainloop = m;
+
+ if (io->ifd >= 0)
+ pa_make_fd_nonblock(io->ifd);
+
+ if (io->ofd >= 0 && io->ofd != io->ifd)
+ pa_make_fd_nonblock(io->ofd);
+
+ enable_events(io);
+ return io;
+}
+
+void pa_iochannel_free(pa_iochannel*io) {
+ pa_assert(io);
+
+ delete_events(io);
+
+ if (!io->no_close) {
+ if (io->ifd >= 0)
+ pa_close(io->ifd);
+ if (io->ofd >= 0 && io->ofd != io->ifd)
+ pa_close(io->ofd);
+ }
+
+ pa_xfree(io);
+}
+
+bool pa_iochannel_is_readable(pa_iochannel*io) {
+ pa_assert(io);
+
+ return io->readable || io->hungup;
+}
+
+bool pa_iochannel_is_writable(pa_iochannel*io) {
+ pa_assert(io);
+
+ return io->writable && !io->hungup;
+}
+
+bool pa_iochannel_is_hungup(pa_iochannel*io) {
+ pa_assert(io);
+
+ return io->hungup;
+}
+
+ssize_t pa_iochannel_write(pa_iochannel*io, const void*data, size_t l) {
+ ssize_t r;
+
+ pa_assert(io);
+ pa_assert(data);
+ pa_assert(l);
+ pa_assert(io->ofd >= 0);
+
+ r = pa_write(io->ofd, data, l, &io->ofd_type);
+
+ if ((size_t) r == l)
+ return r; /* Fast path - we almost always successfully write everything */
+
+ if (r < 0) {
+ if (errno == EINTR || errno == EAGAIN)
+ r = 0;
+ else
+ return r;
+ }
+
+ /* Partial write - let's get a notification when we can write more */
+ io->writable = io->hungup = false;
+ enable_events(io);
+
+ return r;
+}
+
+ssize_t pa_iochannel_read(pa_iochannel*io, void*data, size_t l) {
+ ssize_t r;
+
+ pa_assert(io);
+ pa_assert(data);
+ pa_assert(io->ifd >= 0);
+
+ if ((r = pa_read(io->ifd, data, l, &io->ifd_type)) >= 0) {
+
+ /* We also reset the hangup flag here to ensure that another
+ * IO callback is triggered so that we will again call into
+ * user code */
+ io->readable = io->hungup = false;
+ enable_events(io);
+ }
+
+ return r;
+}
+
+#ifdef HAVE_CREDS
+
+bool pa_iochannel_creds_supported(pa_iochannel *io) {
+ struct {
+ struct sockaddr sa;
+#ifdef HAVE_SYS_UN_H
+ struct sockaddr_un un;
+#endif
+ struct sockaddr_storage storage;
+ } sa;
+
+ socklen_t l;
+
+ pa_assert(io);
+ pa_assert(io->ifd >= 0);
+ pa_assert(io->ofd == io->ifd);
+
+ l = sizeof(sa);
+ if (getsockname(io->ifd, &sa.sa, &l) < 0)
+ return false;
+
+ return sa.sa.sa_family == AF_UNIX;
+}
+
+int pa_iochannel_creds_enable(pa_iochannel *io) {
+ int t = 1;
+
+ pa_assert(io);
+ pa_assert(io->ifd >= 0);
+
+ if (setsockopt(io->ifd, SOL_SOCKET, SO_PASSCRED, &t, sizeof(t)) < 0) {
+ pa_log_error("setsockopt(SOL_SOCKET, SO_PASSCRED): %s", pa_cstrerror(errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+ssize_t pa_iochannel_write_with_creds(pa_iochannel*io, const void*data, size_t l, const pa_creds *ucred) {
+ ssize_t r;
+ struct msghdr mh;
+ struct iovec iov;
+ union {
+ struct cmsghdr hdr;
+ uint8_t data[CMSG_SPACE(sizeof(struct ucred))];
+ } cmsg;
+ struct ucred *u;
+
+ pa_assert(io);
+ pa_assert(data);
+ pa_assert(l);
+ pa_assert(io->ofd >= 0);
+
+ pa_zero(iov);
+ iov.iov_base = (void*) data;
+ iov.iov_len = l;
+
+ pa_zero(cmsg);
+ cmsg.hdr.cmsg_len = CMSG_LEN(sizeof(struct ucred));
+ cmsg.hdr.cmsg_level = SOL_SOCKET;
+ cmsg.hdr.cmsg_type = SCM_CREDENTIALS;
+
+ u = (struct ucred*) CMSG_DATA(&cmsg.hdr);
+
+ u->pid = getpid();
+ if (ucred) {
+ u->uid = ucred->uid;
+ u->gid = ucred->gid;
+ } else {
+ u->uid = getuid();
+ u->gid = getgid();
+ }
+
+ pa_zero(mh);
+ mh.msg_iov = &iov;
+ mh.msg_iovlen = 1;
+ mh.msg_control = &cmsg;
+ mh.msg_controllen = sizeof(cmsg);
+
+ if ((r = sendmsg(io->ofd, &mh, MSG_NOSIGNAL)) >= 0) {
+ io->writable = io->hungup = false;
+ enable_events(io);
+ }
+
+ return r;
+}
+
+/* For more details on FD passing, check the cmsg(3) manpage
+ * and IETF RFC #2292: "Advanced Sockets API for IPv6" */
+ssize_t pa_iochannel_write_with_fds(pa_iochannel*io, const void*data, size_t l, int nfd, const int *fds) {
+ ssize_t r;
+ int *msgdata;
+ struct msghdr mh;
+ struct iovec iov;
+ union {
+ struct cmsghdr hdr;
+ uint8_t data[CMSG_SPACE(sizeof(int) * MAX_ANCIL_DATA_FDS)];
+ } cmsg;
+
+ pa_assert(io);
+ pa_assert(data);
+ pa_assert(l);
+ pa_assert(io->ofd >= 0);
+ pa_assert(fds);
+ pa_assert(nfd > 0);
+ pa_assert(nfd <= MAX_ANCIL_DATA_FDS);
+
+ pa_zero(iov);
+ iov.iov_base = (void*) data;
+ iov.iov_len = l;
+
+ pa_zero(cmsg);
+ cmsg.hdr.cmsg_level = SOL_SOCKET;
+ cmsg.hdr.cmsg_type = SCM_RIGHTS;
+
+ msgdata = (int*) CMSG_DATA(&cmsg.hdr);
+ memcpy(msgdata, fds, nfd * sizeof(int));
+ cmsg.hdr.cmsg_len = CMSG_LEN(sizeof(int) * nfd);
+
+ pa_zero(mh);
+ mh.msg_iov = &iov;
+ mh.msg_iovlen = 1;
+ mh.msg_control = &cmsg;
+
+ /* If we followed the example on the cmsg man page, we'd use
+ * sizeof(cmsg.data) here, but if nfd < MAX_ANCIL_DATA_FDS, then the data
+ * buffer is larger than needed, and the kernel doesn't like it if we set
+ * msg_controllen to a larger than necessary value. The commit message for
+ * commit 451d1d6762 contains a longer explanation. */
+ mh.msg_controllen = CMSG_SPACE(sizeof(int) * nfd);
+
+ if ((r = sendmsg(io->ofd, &mh, MSG_NOSIGNAL)) >= 0) {
+ io->writable = io->hungup = false;
+ enable_events(io);
+ }
+ return r;
+}
+
+ssize_t pa_iochannel_read_with_ancil_data(pa_iochannel*io, void*data, size_t l, pa_cmsg_ancil_data *ancil_data) {
+ ssize_t r;
+ struct msghdr mh;
+ struct iovec iov;
+ union {
+ struct cmsghdr hdr;
+ uint8_t data[CMSG_SPACE(sizeof(struct ucred)) + CMSG_SPACE(sizeof(int) * MAX_ANCIL_DATA_FDS)];
+ } cmsg;
+
+ pa_assert(io);
+ pa_assert(data);
+ pa_assert(l);
+ pa_assert(io->ifd >= 0);
+ pa_assert(ancil_data);
+
+ if (io->ifd_type > 0) {
+ ancil_data->creds_valid = false;
+ ancil_data->nfd = 0;
+ return pa_iochannel_read(io, data, l);
+ }
+
+ iov.iov_base = data;
+ iov.iov_len = l;
+
+ pa_zero(mh);
+ mh.msg_iov = &iov;
+ mh.msg_iovlen = 1;
+ mh.msg_control = &cmsg;
+ mh.msg_controllen = sizeof(cmsg);
+
+ if ((r = recvmsg(io->ifd, &mh, 0)) >= 0) {
+ struct cmsghdr *cmh;
+
+ ancil_data->creds_valid = false;
+ ancil_data->nfd = 0;
+
+ for (cmh = CMSG_FIRSTHDR(&mh); cmh; cmh = CMSG_NXTHDR(&mh, cmh)) {
+
+ if (cmh->cmsg_level != SOL_SOCKET)
+ continue;
+
+ if (cmh->cmsg_type == SCM_CREDENTIALS) {
+ struct ucred u;
+ pa_assert(cmh->cmsg_len == CMSG_LEN(sizeof(struct ucred)));
+ memcpy(&u, CMSG_DATA(cmh), sizeof(struct ucred));
+
+ ancil_data->creds.gid = u.gid;
+ ancil_data->creds.uid = u.uid;
+ ancil_data->creds_valid = true;
+ }
+ else if (cmh->cmsg_type == SCM_RIGHTS) {
+ int nfd = (cmh->cmsg_len - CMSG_LEN(0)) / sizeof(int);
+ if (nfd > MAX_ANCIL_DATA_FDS) {
+ int i;
+ pa_log("Trying to receive too many file descriptors!");
+ for (i = 0; i < nfd; i++)
+ pa_close(((int*) CMSG_DATA(cmh))[i]);
+ continue;
+ }
+ memcpy(ancil_data->fds, CMSG_DATA(cmh), nfd * sizeof(int));
+ ancil_data->nfd = nfd;
+ ancil_data->close_fds_on_cleanup = true;
+ }
+ }
+
+ io->readable = io->hungup = false;
+ enable_events(io);
+ }
+
+ if (r == -1 && errno == ENOTSOCK) {
+ io->ifd_type = 1;
+ return pa_iochannel_read_with_ancil_data(io, data, l, ancil_data);
+ }
+
+ return r;
+}
+
+#endif /* HAVE_CREDS */
+
+void pa_iochannel_set_callback(pa_iochannel*io, pa_iochannel_cb_t _callback, void *userdata) {
+ pa_assert(io);
+
+ io->callback = _callback;
+ io->userdata = userdata;
+}
+
+void pa_iochannel_set_noclose(pa_iochannel*io, bool b) {
+ pa_assert(io);
+
+ io->no_close = b;
+}
+
+void pa_iochannel_socket_peer_to_string(pa_iochannel*io, char*s, size_t l) {
+ pa_assert(io);
+ pa_assert(s);
+ pa_assert(l);
+
+ pa_socket_peer_to_string(io->ifd, s, l);
+}
+
+int pa_iochannel_socket_set_rcvbuf(pa_iochannel *io, size_t l) {
+ pa_assert(io);
+
+ return pa_socket_set_rcvbuf(io->ifd, l);
+}
+
+int pa_iochannel_socket_set_sndbuf(pa_iochannel *io, size_t l) {
+ pa_assert(io);
+
+ return pa_socket_set_sndbuf(io->ofd, l);
+}
+
+pa_mainloop_api* pa_iochannel_get_mainloop_api(pa_iochannel *io) {
+ pa_assert(io);
+
+ return io->mainloop;
+}
+
+int pa_iochannel_get_recv_fd(pa_iochannel *io) {
+ pa_assert(io);
+
+ return io->ifd;
+}
+
+int pa_iochannel_get_send_fd(pa_iochannel *io) {
+ pa_assert(io);
+
+ return io->ofd;
+}
+
+bool pa_iochannel_socket_is_local(pa_iochannel *io) {
+ pa_assert(io);
+
+ if (pa_socket_is_local(io->ifd))
+ return true;
+
+ if (io->ifd != io->ofd)
+ if (pa_socket_is_local(io->ofd))
+ return true;
+
+ return false;
+}
diff --git a/src/pulsecore/iochannel.h b/src/pulsecore/iochannel.h
new file mode 100644
index 0000000..c81c5e3
--- /dev/null
+++ b/src/pulsecore/iochannel.h
@@ -0,0 +1,89 @@
+#ifndef fooiochannelhfoo
+#define fooiochannelhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifndef PACKAGE
+#error "Please include config.h before including this file!"
+#endif
+
+#include <sys/types.h>
+
+#include <pulse/mainloop-api.h>
+#include <pulsecore/creds.h>
+#include <pulsecore/macro.h>
+
+/* A wrapper around UNIX file descriptors for attaching them to the a
+ main event loop. Every time new data may be read or be written to
+ the channel a callback function is called. It is safe to destroy
+ the calling iochannel object from the callback */
+
+typedef struct pa_iochannel pa_iochannel;
+
+/* Create a new IO channel for the specified file descriptors for
+input resp. output. It is safe to pass the same file descriptor for
+both parameters (in case of full-duplex channels). For a simplex
+channel specify -1 for the other direction. */
+
+pa_iochannel* pa_iochannel_new(pa_mainloop_api*m, int ifd, int ofd);
+void pa_iochannel_free(pa_iochannel*io);
+
+/* Returns: length written on success, 0 if a retry is needed, negative value
+ * on error. */
+ssize_t pa_iochannel_write(pa_iochannel*io, const void*data, size_t l);
+ssize_t pa_iochannel_read(pa_iochannel*io, void*data, size_t l);
+
+#ifdef HAVE_CREDS
+bool pa_iochannel_creds_supported(pa_iochannel *io);
+int pa_iochannel_creds_enable(pa_iochannel *io);
+
+ssize_t pa_iochannel_write_with_fds(pa_iochannel*io, const void*data, size_t l, int nfd, const int *fds);
+ssize_t pa_iochannel_write_with_creds(pa_iochannel*io, const void*data, size_t l, const pa_creds *ucred);
+ssize_t pa_iochannel_read_with_ancil_data(pa_iochannel*io, void*data, size_t l, pa_cmsg_ancil_data *ancil_data);
+#endif
+
+bool pa_iochannel_is_readable(pa_iochannel*io);
+bool pa_iochannel_is_writable(pa_iochannel*io);
+bool pa_iochannel_is_hungup(pa_iochannel*io);
+
+/* Don't close the file descriptors when the io channel is freed. By
+ * default the file descriptors are closed. */
+void pa_iochannel_set_noclose(pa_iochannel*io, bool b);
+
+/* Set the callback function that is called whenever data becomes available for read or write */
+typedef void (*pa_iochannel_cb_t)(pa_iochannel*io, void *userdata);
+void pa_iochannel_set_callback(pa_iochannel*io, pa_iochannel_cb_t callback, void *userdata);
+
+/* In case the file descriptor is a socket, return a pretty-printed string in *s which describes the peer connected */
+void pa_iochannel_socket_peer_to_string(pa_iochannel*io, char*s, size_t l);
+
+/* Use setsockopt() to tune the receive and send buffers of TCP sockets */
+int pa_iochannel_socket_set_rcvbuf(pa_iochannel*io, size_t l);
+int pa_iochannel_socket_set_sndbuf(pa_iochannel*io, size_t l);
+
+bool pa_iochannel_socket_is_local(pa_iochannel *io);
+
+pa_mainloop_api* pa_iochannel_get_mainloop_api(pa_iochannel *io);
+
+int pa_iochannel_get_recv_fd(pa_iochannel *io);
+int pa_iochannel_get_send_fd(pa_iochannel *io);
+
+#endif
diff --git a/src/pulsecore/ioline.c b/src/pulsecore/ioline.c
new file mode 100644
index 0000000..dfc5a73
--- /dev/null
+++ b/src/pulsecore/ioline.c
@@ -0,0 +1,463 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/socket.h>
+#include <pulsecore/core-error.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/refcnt.h>
+
+#include "ioline.h"
+
+#define BUFFER_LIMIT (64*1024)
+#define READ_SIZE (1024)
+
+struct pa_ioline {
+ PA_REFCNT_DECLARE;
+
+ pa_iochannel *io;
+ pa_defer_event *defer_event;
+ pa_mainloop_api *mainloop;
+
+ char *wbuf;
+ size_t wbuf_length, wbuf_index, wbuf_valid_length;
+
+ char *rbuf;
+ size_t rbuf_length, rbuf_index, rbuf_valid_length;
+
+ pa_ioline_cb_t callback;
+ void *userdata;
+
+ pa_ioline_drain_cb_t drain_callback;
+ void *drain_userdata;
+
+ bool dead:1;
+ bool defer_close:1;
+};
+
+static void io_callback(pa_iochannel*io, void *userdata);
+static void defer_callback(pa_mainloop_api*m, pa_defer_event*e, void *userdata);
+
+pa_ioline* pa_ioline_new(pa_iochannel *io) {
+ pa_ioline *l;
+ pa_assert(io);
+
+ l = pa_xnew(pa_ioline, 1);
+ PA_REFCNT_INIT(l);
+ l->io = io;
+
+ l->wbuf = NULL;
+ l->wbuf_length = l->wbuf_index = l->wbuf_valid_length = 0;
+
+ l->rbuf = NULL;
+ l->rbuf_length = l->rbuf_index = l->rbuf_valid_length = 0;
+
+ l->callback = NULL;
+ l->userdata = NULL;
+
+ l->drain_callback = NULL;
+ l->drain_userdata = NULL;
+
+ l->mainloop = pa_iochannel_get_mainloop_api(io);
+
+ l->defer_event = l->mainloop->defer_new(l->mainloop, defer_callback, l);
+ l->mainloop->defer_enable(l->defer_event, 0);
+
+ l->dead = false;
+ l->defer_close = false;
+
+ pa_iochannel_set_callback(io, io_callback, l);
+
+ return l;
+}
+
+static void ioline_free(pa_ioline *l) {
+ pa_assert(l);
+
+ if (l->io)
+ pa_iochannel_free(l->io);
+
+ if (l->defer_event)
+ l->mainloop->defer_free(l->defer_event);
+
+ pa_xfree(l->wbuf);
+ pa_xfree(l->rbuf);
+ pa_xfree(l);
+}
+
+void pa_ioline_unref(pa_ioline *l) {
+ pa_assert(l);
+ pa_assert(PA_REFCNT_VALUE(l) >= 1);
+
+ if (PA_REFCNT_DEC(l) <= 0)
+ ioline_free(l);
+}
+
+pa_ioline* pa_ioline_ref(pa_ioline *l) {
+ pa_assert(l);
+ pa_assert(PA_REFCNT_VALUE(l) >= 1);
+
+ PA_REFCNT_INC(l);
+ return l;
+}
+
+void pa_ioline_close(pa_ioline *l) {
+ pa_assert(l);
+ pa_assert(PA_REFCNT_VALUE(l) >= 1);
+
+ l->dead = true;
+
+ if (l->io) {
+ pa_iochannel_free(l->io);
+ l->io = NULL;
+ }
+
+ if (l->defer_event) {
+ l->mainloop->defer_free(l->defer_event);
+ l->defer_event = NULL;
+ }
+
+ if (l->callback)
+ l->callback = NULL;
+}
+
+void pa_ioline_puts(pa_ioline *l, const char *c) {
+ size_t len;
+
+ pa_assert(l);
+ pa_assert(PA_REFCNT_VALUE(l) >= 1);
+ pa_assert(c);
+
+ if (l->dead)
+ return;
+
+ len = strlen(c);
+ if (len > BUFFER_LIMIT - l->wbuf_valid_length)
+ len = BUFFER_LIMIT - l->wbuf_valid_length;
+
+ if (len) {
+ pa_assert(l->wbuf_length >= l->wbuf_valid_length);
+
+ /* In case the allocated buffer is too small, enlarge it. */
+ if (l->wbuf_valid_length + len > l->wbuf_length) {
+ size_t n = l->wbuf_valid_length+len;
+ char *new = pa_xnew(char, (unsigned) n);
+
+ if (l->wbuf) {
+ memcpy(new, l->wbuf+l->wbuf_index, l->wbuf_valid_length);
+ pa_xfree(l->wbuf);
+ }
+
+ l->wbuf = new;
+ l->wbuf_length = n;
+ l->wbuf_index = 0;
+ } else if (l->wbuf_index + l->wbuf_valid_length + len > l->wbuf_length) {
+
+ /* In case the allocated buffer fits, but the current index is too far from the start, move it to the front. */
+ memmove(l->wbuf, l->wbuf+l->wbuf_index, l->wbuf_valid_length);
+ l->wbuf_index = 0;
+ }
+
+ pa_assert(l->wbuf_index + l->wbuf_valid_length + len <= l->wbuf_length);
+
+ /* Append the new string */
+ memcpy(l->wbuf + l->wbuf_index + l->wbuf_valid_length, c, len);
+ l->wbuf_valid_length += len;
+
+ l->mainloop->defer_enable(l->defer_event, 1);
+ }
+}
+
+void pa_ioline_set_callback(pa_ioline*l, pa_ioline_cb_t callback, void *userdata) {
+ pa_assert(l);
+ pa_assert(PA_REFCNT_VALUE(l) >= 1);
+
+ if (l->dead)
+ return;
+
+ l->callback = callback;
+ l->userdata = userdata;
+}
+
+void pa_ioline_set_drain_callback(pa_ioline*l, pa_ioline_drain_cb_t callback, void *userdata) {
+ pa_assert(l);
+ pa_assert(PA_REFCNT_VALUE(l) >= 1);
+
+ if (l->dead)
+ return;
+
+ l->drain_callback = callback;
+ l->drain_userdata = userdata;
+}
+
+static void failure(pa_ioline *l, bool process_leftover) {
+ pa_assert(l);
+ pa_assert(PA_REFCNT_VALUE(l) >= 1);
+ pa_assert(!l->dead);
+
+ if (process_leftover && l->rbuf_valid_length > 0) {
+ /* Pass the last missing bit to the client */
+
+ if (l->callback) {
+ char *p = pa_xstrndup(l->rbuf+l->rbuf_index, l->rbuf_valid_length);
+ l->callback(l, p, l->userdata);
+ pa_xfree(p);
+ }
+ }
+
+ if (l->callback) {
+ l->callback(l, NULL, l->userdata);
+ l->callback = NULL;
+ }
+
+ pa_ioline_close(l);
+}
+
+static void scan_for_lines(pa_ioline *l, size_t skip) {
+ pa_assert(l);
+ pa_assert(PA_REFCNT_VALUE(l) >= 1);
+ pa_assert(skip < l->rbuf_valid_length);
+
+ while (!l->dead && l->rbuf_valid_length > skip) {
+ char *e, *p;
+ size_t m;
+
+ if (!(e = memchr(l->rbuf + l->rbuf_index + skip, '\n', l->rbuf_valid_length - skip)))
+ break;
+
+ *e = 0;
+
+ p = l->rbuf + l->rbuf_index;
+ m = strlen(p);
+
+ l->rbuf_index += m+1;
+ l->rbuf_valid_length -= m+1;
+
+ /* A shortcut for the next time */
+ if (l->rbuf_valid_length == 0)
+ l->rbuf_index = 0;
+
+ if (l->callback)
+ l->callback(l, pa_strip_nl(p), l->userdata);
+
+ skip = 0;
+ }
+
+ /* If the buffer became too large and still no newline was found, drop it. */
+ if (l->rbuf_valid_length >= BUFFER_LIMIT)
+ l->rbuf_index = l->rbuf_valid_length = 0;
+}
+
+static int do_write(pa_ioline *l);
+
+static int do_read(pa_ioline *l) {
+ pa_assert(l);
+ pa_assert(PA_REFCNT_VALUE(l) >= 1);
+
+ while (l->io && !l->dead && pa_iochannel_is_readable(l->io)) {
+ ssize_t r;
+ size_t len;
+
+ len = l->rbuf_length - l->rbuf_index - l->rbuf_valid_length;
+
+ /* Check if we have to enlarge the read buffer */
+ if (len < READ_SIZE) {
+ size_t n = l->rbuf_valid_length+READ_SIZE;
+
+ if (n >= BUFFER_LIMIT)
+ n = BUFFER_LIMIT;
+
+ if (l->rbuf_length >= n) {
+ /* The current buffer is large enough, let's just move the data to the front */
+ if (l->rbuf_valid_length)
+ memmove(l->rbuf, l->rbuf+l->rbuf_index, l->rbuf_valid_length);
+ } else {
+ /* Enlarge the buffer */
+ char *new = pa_xnew(char, (unsigned) n);
+ if (l->rbuf_valid_length)
+ memcpy(new, l->rbuf+l->rbuf_index, l->rbuf_valid_length);
+ pa_xfree(l->rbuf);
+ l->rbuf = new;
+ l->rbuf_length = n;
+ }
+
+ l->rbuf_index = 0;
+ }
+
+ len = l->rbuf_length - l->rbuf_index - l->rbuf_valid_length;
+
+ pa_assert(len >= READ_SIZE);
+
+ /* Read some data */
+ if ((r = pa_iochannel_read(l->io, l->rbuf+l->rbuf_index+l->rbuf_valid_length, len)) <= 0) {
+
+ if (r < 0 && errno == EAGAIN)
+ return 0;
+
+ if (r < 0 && errno != ECONNRESET) {
+ pa_log("read(): %s", pa_cstrerror(errno));
+ failure(l, false);
+ } else
+ failure(l, true);
+
+ return -1;
+ }
+
+ l->rbuf_valid_length += (size_t) r;
+
+ /* Look if a line has been terminated in the newly read data */
+ scan_for_lines(l, l->rbuf_valid_length - (size_t) r);
+ }
+
+ return 0;
+}
+
+/* Try to flush the buffer */
+static int do_write(pa_ioline *l) {
+ ssize_t r;
+
+ pa_assert(l);
+ pa_assert(PA_REFCNT_VALUE(l) >= 1);
+
+ while (l->io && !l->dead && pa_iochannel_is_writable(l->io) && l->wbuf_valid_length > 0) {
+
+ if ((r = pa_iochannel_write(l->io, l->wbuf+l->wbuf_index, l->wbuf_valid_length)) < 0) {
+
+ if (errno != EPIPE)
+ pa_log("write(): %s", pa_cstrerror(errno));
+
+ failure(l, false);
+
+ return -1;
+ }
+
+ l->wbuf_index += (size_t) r;
+ l->wbuf_valid_length -= (size_t) r;
+
+ /* A shortcut for the next time */
+ if (l->wbuf_valid_length == 0)
+ l->wbuf_index = 0;
+ }
+
+ if (l->wbuf_valid_length <= 0 && l->drain_callback)
+ l->drain_callback(l, l->drain_userdata);
+
+ return 0;
+}
+
+/* Try to flush read/write data */
+static void do_work(pa_ioline *l) {
+ pa_assert(l);
+ pa_assert(PA_REFCNT_VALUE(l) >= 1);
+
+ pa_ioline_ref(l);
+
+ l->mainloop->defer_enable(l->defer_event, 0);
+
+ if (!l->dead)
+ do_read(l);
+
+ if (!l->dead)
+ do_write(l);
+
+ if (l->defer_close && !l->wbuf_valid_length)
+ failure(l, true);
+
+ pa_ioline_unref(l);
+}
+
+static void io_callback(pa_iochannel*io, void *userdata) {
+ pa_ioline *l = userdata;
+
+ pa_assert(io);
+ pa_assert(l);
+ pa_assert(PA_REFCNT_VALUE(l) >= 1);
+
+ do_work(l);
+}
+
+static void defer_callback(pa_mainloop_api*m, pa_defer_event*e, void *userdata) {
+ pa_ioline *l = userdata;
+
+ pa_assert(l);
+ pa_assert(PA_REFCNT_VALUE(l) >= 1);
+ pa_assert(l->mainloop == m);
+ pa_assert(l->defer_event == e);
+
+ do_work(l);
+}
+
+void pa_ioline_defer_close(pa_ioline *l) {
+ pa_assert(l);
+ pa_assert(PA_REFCNT_VALUE(l) >= 1);
+
+ l->defer_close = true;
+
+ if (!l->wbuf_valid_length)
+ l->mainloop->defer_enable(l->defer_event, 1);
+}
+
+void pa_ioline_printf(pa_ioline *l, const char *format, ...) {
+ char *t;
+ va_list ap;
+
+ pa_assert(l);
+ pa_assert(PA_REFCNT_VALUE(l) >= 1);
+
+ va_start(ap, format);
+ t = pa_vsprintf_malloc(format, ap);
+ va_end(ap);
+
+ pa_ioline_puts(l, t);
+ pa_xfree(t);
+}
+
+pa_iochannel* pa_ioline_detach_iochannel(pa_ioline *l) {
+ pa_iochannel *r;
+
+ pa_assert(l);
+
+ if (!l->io)
+ return NULL;
+
+ r = l->io;
+ l->io = NULL;
+
+ pa_iochannel_set_callback(r, NULL, NULL);
+
+ return r;
+}
+
+bool pa_ioline_is_drained(pa_ioline *l) {
+ pa_assert(l);
+
+ return l->wbuf_valid_length <= 0;
+}
diff --git a/src/pulsecore/ioline.h b/src/pulsecore/ioline.h
new file mode 100644
index 0000000..7b6dff3
--- /dev/null
+++ b/src/pulsecore/ioline.h
@@ -0,0 +1,63 @@
+#ifndef fooiolinehfoo
+#define fooiolinehfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulse/gccmacro.h>
+
+#include <pulsecore/iochannel.h>
+
+/* An ioline wraps an iochannel for line based communication. A
+ * callback function is called whenever a new line has been received
+ * from the client */
+
+typedef struct pa_ioline pa_ioline;
+
+typedef void (*pa_ioline_cb_t)(pa_ioline*io, const char *s, void *userdata);
+typedef void (*pa_ioline_drain_cb_t)(pa_ioline *io, void *userdata);
+
+pa_ioline* pa_ioline_new(pa_iochannel *io);
+void pa_ioline_unref(pa_ioline *l);
+pa_ioline* pa_ioline_ref(pa_ioline *l);
+void pa_ioline_close(pa_ioline *l);
+
+/* Write a string to the channel */
+void pa_ioline_puts(pa_ioline *s, const char *c);
+
+/* Write a string to the channel */
+void pa_ioline_printf(pa_ioline *s, const char *format, ...) PA_GCC_PRINTF_ATTR(2,3);
+
+/* Set the callback function that is called for every received line */
+void pa_ioline_set_callback(pa_ioline*io, pa_ioline_cb_t callback, void *userdata);
+
+/* Set the callback function that is called when everything has been written */
+void pa_ioline_set_drain_callback(pa_ioline*io, pa_ioline_drain_cb_t callback, void *userdata);
+
+/* Make sure to close the ioline object as soon as the send buffer is emptied */
+void pa_ioline_defer_close(pa_ioline *io);
+
+/* Returns true when everything was written */
+bool pa_ioline_is_drained(pa_ioline *io);
+
+/* Detaches from the iochannel and returns it. Data that has already
+ * been read will not be available in the detached iochannel */
+pa_iochannel* pa_ioline_detach_iochannel(pa_ioline *l);
+
+#endif
diff --git a/src/pulsecore/ipacl.c b/src/pulsecore/ipacl.c
new file mode 100644
index 0000000..e83d8a0
--- /dev/null
+++ b/src/pulsecore/ipacl.c
@@ -0,0 +1,238 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sys/types.h>
+#include <sys/types.h>
+#include <string.h>
+
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+#ifdef HAVE_NETINET_IN_SYSTM_H
+#include <netinet/in_systm.h>
+#endif
+#ifdef HAVE_NETINET_IP_H
+#include <netinet/ip.h>
+#endif
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/llist.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/socket.h>
+#include <pulsecore/arpa-inet.h>
+
+#include "ipacl.h"
+
+struct acl_entry {
+ PA_LLIST_FIELDS(struct acl_entry);
+ int family;
+ struct in_addr address_ipv4;
+#ifdef HAVE_IPV6
+ struct in6_addr address_ipv6;
+#endif
+ int bits;
+};
+
+struct pa_ip_acl {
+ PA_LLIST_HEAD(struct acl_entry, entries);
+};
+
+pa_ip_acl* pa_ip_acl_new(const char *s) {
+ const char *state = NULL;
+ char *a;
+ pa_ip_acl *acl;
+
+ pa_assert(s);
+
+ acl = pa_xnew(pa_ip_acl, 1);
+ PA_LLIST_HEAD_INIT(struct acl_entry, acl->entries);
+
+ while ((a = pa_split(s, ";", &state))) {
+ char *slash;
+ struct acl_entry e, *n;
+ uint32_t bits;
+
+ if ((slash = strchr(a, '/'))) {
+ *slash = 0;
+ slash++;
+ if (pa_atou(slash, &bits) < 0) {
+ pa_log_warn("Failed to parse number of bits: %s", slash);
+ goto fail;
+ }
+ } else
+ bits = (uint32_t) -1;
+
+ if (inet_pton(AF_INET, a, &e.address_ipv4) > 0) {
+
+ e.bits = bits == (uint32_t) -1 ? 32 : (int) bits;
+
+ if (e.bits > 32) {
+ pa_log_warn("Number of bits out of range: %i", e.bits);
+ goto fail;
+ }
+
+ e.family = AF_INET;
+
+ if (e.bits < 32 && (uint32_t) (ntohl(e.address_ipv4.s_addr) << e.bits) != 0)
+ pa_log_warn("Host part of ACL entry '%s/%u' is not zero!", a, e.bits);
+
+#ifdef HAVE_IPV6
+ } else if (inet_pton(AF_INET6, a, &e.address_ipv6) > 0) {
+
+ e.bits = bits == (uint32_t) -1 ? 128 : (int) bits;
+
+ if (e.bits > 128) {
+ pa_log_warn("Number of bits out of range: %i", e.bits);
+ goto fail;
+ }
+ e.family = AF_INET6;
+
+ if (e.bits < 128) {
+ int t = 0, i;
+
+ for (i = 0, bits = (uint32_t) e.bits; i < 16; i++) {
+
+ if (bits >= 8)
+ bits -= 8;
+ else {
+ if ((uint8_t) ((e.address_ipv6.s6_addr[i]) << bits) != 0) {
+ t = 1;
+ break;
+ }
+ bits = 0;
+ }
+ }
+
+ if (t)
+ pa_log_warn("Host part of ACL entry '%s/%u' is not zero!", a, e.bits);
+ }
+#endif
+
+ } else {
+ pa_log_warn("Failed to parse address: %s", a);
+ goto fail;
+ }
+
+ n = pa_xmemdup(&e, sizeof(struct acl_entry));
+ PA_LLIST_PREPEND(struct acl_entry, acl->entries, n);
+
+ pa_xfree(a);
+ }
+
+ return acl;
+
+fail:
+ pa_xfree(a);
+ pa_ip_acl_free(acl);
+
+ return NULL;
+}
+
+void pa_ip_acl_free(pa_ip_acl *acl) {
+ pa_assert(acl);
+
+ while (acl->entries) {
+ struct acl_entry *e = acl->entries;
+ PA_LLIST_REMOVE(struct acl_entry, acl->entries, e);
+ pa_xfree(e);
+ }
+
+ pa_xfree(acl);
+}
+
+int pa_ip_acl_check(pa_ip_acl *acl, int fd) {
+ struct sockaddr_storage sa;
+ struct acl_entry *e;
+ socklen_t salen;
+
+ pa_assert(acl);
+ pa_assert(fd >= 0);
+
+ salen = sizeof(sa);
+ if (getpeername(fd, (struct sockaddr*) &sa, &salen) < 0)
+ return -1;
+
+#ifdef HAVE_IPV6
+ if (sa.ss_family != AF_INET && sa.ss_family != AF_INET6)
+#else
+ if (sa.ss_family != AF_INET)
+#endif
+ return -1;
+
+ if (sa.ss_family == AF_INET && salen != sizeof(struct sockaddr_in))
+ return -1;
+
+#ifdef HAVE_IPV6
+ if (sa.ss_family == AF_INET6 && salen != sizeof(struct sockaddr_in6))
+ return -1;
+#endif
+
+ for (e = acl->entries; e; e = e->next) {
+
+ if (e->family != sa.ss_family)
+ continue;
+
+ if (e->family == AF_INET) {
+ struct sockaddr_in *sai = (struct sockaddr_in*) &sa;
+
+ if (e->bits == 0 || /* this needs special handling because >> takes the right-hand side modulo 32 */
+ (ntohl(sai->sin_addr.s_addr ^ e->address_ipv4.s_addr) >> (32 - e->bits)) == 0)
+ return 1;
+#ifdef HAVE_IPV6
+ } else if (e->family == AF_INET6) {
+ int i, bits;
+ struct sockaddr_in6 *sai = (struct sockaddr_in6*) &sa;
+
+ if (e->bits == 128)
+ return memcmp(&sai->sin6_addr, &e->address_ipv6, 16) == 0;
+
+ if (e->bits == 0)
+ return 1;
+
+ for (i = 0, bits = e->bits; i < 16; i++) {
+
+ if (bits >= 8) {
+ if (sai->sin6_addr.s6_addr[i] != e->address_ipv6.s6_addr[i])
+ break;
+
+ bits -= 8;
+ } else {
+ if ((sai->sin6_addr.s6_addr[i] ^ e->address_ipv6.s6_addr[i]) >> (8 - bits) != 0)
+ break;
+
+ bits = 0;
+ }
+
+ if (bits == 0)
+ return 1;
+ }
+#endif
+ }
+ }
+
+ return 0;
+}
diff --git a/src/pulsecore/ipacl.h b/src/pulsecore/ipacl.h
new file mode 100644
index 0000000..034248a
--- /dev/null
+++ b/src/pulsecore/ipacl.h
@@ -0,0 +1,30 @@
+#ifndef foopulsecoreipaclhfoo
+#define foopulsecoreipaclhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+typedef struct pa_ip_acl pa_ip_acl;
+
+pa_ip_acl* pa_ip_acl_new(const char *s);
+void pa_ip_acl_free(pa_ip_acl *acl);
+int pa_ip_acl_check(pa_ip_acl *acl, int fd);
+
+#endif
diff --git a/src/pulsecore/llist.h b/src/pulsecore/llist.h
new file mode 100644
index 0000000..a9412fa
--- /dev/null
+++ b/src/pulsecore/llist.h
@@ -0,0 +1,111 @@
+#ifndef foollistfoo
+#define foollistfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulsecore/macro.h>
+
+/* Some macros for maintaining doubly linked lists */
+
+/* The head of the linked list. Use this in the structure that shall
+ * contain the head of the linked list */
+#define PA_LLIST_HEAD(t,name) \
+ t *name
+
+/* The pointers in the linked list's items. Use this in the item structure */
+#define PA_LLIST_FIELDS(t) \
+ t *next, *prev
+
+/* Initialize the list's head */
+#define PA_LLIST_HEAD_INIT(t,item) \
+ do { \
+ (item) = (t*) NULL; } \
+ while(0)
+
+/* Initialize a list item */
+#define PA_LLIST_INIT(t,item) \
+ do { \
+ t *_item = (item); \
+ pa_assert(_item); \
+ _item->prev = _item->next = NULL; \
+ } while(0)
+
+/* Prepend an item to the list */
+#define PA_LLIST_PREPEND(t,head,item) \
+ do { \
+ t **_head = &(head), *_item = (item); \
+ pa_assert(_item); \
+ if ((_item->next = *_head)) \
+ _item->next->prev = _item; \
+ _item->prev = NULL; \
+ *_head = _item; \
+ } while (0)
+
+/* Remove an item from the list */
+#define PA_LLIST_REMOVE(t,head,item) \
+ do { \
+ t **_head = &(head), *_item = (item); \
+ pa_assert(_item); \
+ if (_item->next) \
+ _item->next->prev = _item->prev; \
+ if (_item->prev) \
+ _item->prev->next = _item->next; \
+ else { \
+ pa_assert(*_head == _item); \
+ *_head = _item->next; \
+ } \
+ _item->next = _item->prev = NULL; \
+ } while(0)
+
+/* Find the head of the list */
+#define PA_LLIST_FIND_HEAD(t,item,head) \
+ do { \
+ t **_head = (head), *_item = (item); \
+ *_head = _item; \
+ pa_assert(_head); \
+ while ((*_head)->prev) \
+ *_head = (*_head)->prev; \
+ } while (0)
+
+/* Insert an item after another one (a = where, b = what) */
+#define PA_LLIST_INSERT_AFTER(t,head,a,b) \
+ do { \
+ t **_head = &(head), *_a = (a), *_b = (b); \
+ pa_assert(_b); \
+ if (!_a) { \
+ if ((_b->next = *_head)) \
+ _b->next->prev = _b; \
+ _b->prev = NULL; \
+ *_head = _b; \
+ } else { \
+ if ((_b->next = _a->next)) \
+ _b->next->prev = _b; \
+ _b->prev = _a; \
+ _a->next = _b; \
+ } \
+ } while (0)
+
+#define PA_LLIST_FOREACH(i,head) \
+ for (i = (head); i; i = i->next)
+
+#define PA_LLIST_FOREACH_SAFE(i,n,head) \
+ for (i = (head); i && ((n = i->next), 1); i = n)
+
+#endif
diff --git a/src/pulsecore/lock-autospawn.c b/src/pulsecore/lock-autospawn.c
new file mode 100644
index 0000000..955fd23
--- /dev/null
+++ b/src/pulsecore/lock-autospawn.c
@@ -0,0 +1,362 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2008 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <string.h>
+#include <signal.h>
+
+#ifdef HAVE_PTHREAD
+#include <pthread.h>
+#endif
+
+#include <pulse/gccmacro.h>
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/i18n.h>
+#include <pulsecore/poll.h>
+#include <pulsecore/mutex.h>
+#include <pulsecore/thread.h>
+#include <pulsecore/core-util.h>
+
+#include "lock-autospawn.h"
+
+/* So, why do we have this complex code here with threads and pipes
+ * and stuff? For two reasons: POSIX file locks are per-process, not
+ * per-file descriptor. That means that two contexts within the same
+ * process that try to create the autospawn lock might end up assuming
+ * they both managed to lock the file. And then, POSIX locking
+ * operations are synchronous. If two contexts run from the same event
+ * loop it must be made sure that they do not block each other, but
+ * that the locking operation can happen asynchronously. */
+
+#define AUTOSPAWN_LOCK "autospawn.lock"
+
+static pa_mutex *mutex;
+
+static unsigned n_ref = 0;
+static int lock_fd = -1;
+static pa_mutex *lock_fd_mutex = NULL;
+static pa_thread *thread = NULL;
+static int pipe_fd[2] = { -1, -1 };
+
+static enum {
+ STATE_IDLE,
+ STATE_OWNING,
+ STATE_TAKEN,
+ STATE_FAILED
+} state = STATE_IDLE;
+
+static void destroy_mutex(void) PA_GCC_DESTRUCTOR;
+
+static int ref(void) {
+
+ if (n_ref > 0) {
+
+ pa_assert(pipe_fd[0] >= 0);
+ pa_assert(pipe_fd[1] >= 0);
+ pa_assert(lock_fd_mutex);
+
+ n_ref++;
+
+ return 0;
+ }
+
+ pa_assert(!lock_fd_mutex);
+ pa_assert(state == STATE_IDLE);
+ pa_assert(lock_fd < 0);
+ pa_assert(!thread);
+ pa_assert(pipe_fd[0] < 0);
+ pa_assert(pipe_fd[1] < 0);
+
+ if (pa_pipe_cloexec(pipe_fd) < 0)
+ return -1;
+
+ pa_make_fd_nonblock(pipe_fd[1]);
+ pa_make_fd_nonblock(pipe_fd[0]);
+
+ lock_fd_mutex = pa_mutex_new(false, false);
+
+ n_ref = 1;
+ return 0;
+}
+
+static void unref(bool after_fork) {
+
+ pa_assert(n_ref > 0);
+ pa_assert(pipe_fd[0] >= 0);
+ pa_assert(pipe_fd[1] >= 0);
+ pa_assert(lock_fd_mutex);
+
+ n_ref--;
+
+ if (n_ref > 0)
+ return;
+
+ /* Join threads only in the process the new thread was created in
+ * to avoid undefined behaviour.
+ * POSIX.1-2008 XSH 2.9.2 Thread IDs: "applications should only assume
+ * that thread IDs are usable and unique within a single process." */
+ if (thread) {
+ if (after_fork)
+ pa_thread_free_nojoin(thread);
+ else
+ pa_thread_free(thread);
+ thread = NULL;
+ }
+
+ pa_mutex_lock(lock_fd_mutex);
+
+ pa_assert(state != STATE_TAKEN);
+
+ if (state == STATE_OWNING) {
+
+ pa_assert(lock_fd >= 0);
+
+ if (after_fork)
+ pa_close(lock_fd);
+ else {
+ char *lf;
+
+ if (!(lf = pa_runtime_path(AUTOSPAWN_LOCK)))
+ pa_log_warn(_("Cannot access autospawn lock."));
+
+ pa_unlock_lockfile(lf, lock_fd);
+ pa_xfree(lf);
+ }
+ }
+
+ lock_fd = -1;
+ state = STATE_IDLE;
+
+ pa_mutex_unlock(lock_fd_mutex);
+
+ pa_mutex_free(lock_fd_mutex);
+ lock_fd_mutex = NULL;
+
+ pa_close(pipe_fd[0]);
+ pa_close(pipe_fd[1]);
+ pipe_fd[0] = pipe_fd[1] = -1;
+}
+
+static void ping(void) {
+ ssize_t s;
+
+ pa_assert(pipe_fd[1] >= 0);
+
+ for (;;) {
+ char x = 'x';
+
+ if ((s = pa_write(pipe_fd[1], &x, 1, NULL)) == 1)
+ break;
+
+ pa_assert(s < 0);
+
+ if (errno == EAGAIN)
+ break;
+
+ pa_assert(errno == EINTR);
+ }
+}
+
+static void wait_for_ping(void) {
+ ssize_t s;
+ char x;
+ struct pollfd pfd;
+ int k;
+
+ pa_assert(pipe_fd[0] >= 0);
+
+ memset(&pfd, 0, sizeof(pfd));
+ pfd.fd = pipe_fd[0];
+ pfd.events = POLLIN;
+
+ if ((k = pa_poll(&pfd, 1, -1)) != 1) {
+ pa_assert(k < 0);
+ pa_assert(errno == EINTR);
+ } else if ((s = pa_read(pipe_fd[0], &x, 1, NULL)) != 1) {
+ pa_assert(s < 0);
+ pa_assert(errno == EAGAIN);
+ }
+}
+
+static void empty_pipe(void) {
+ char x[16];
+ ssize_t s;
+
+ pa_assert(pipe_fd[0] >= 0);
+
+ if ((s = pa_read(pipe_fd[0], &x, sizeof(x), NULL)) < 1) {
+ pa_assert(s < 0);
+ pa_assert(errno == EAGAIN);
+ }
+}
+
+static void thread_func(void *u) {
+ int fd;
+ char *lf;
+
+#ifdef HAVE_PTHREAD
+ sigset_t fullset;
+
+ /* No signals in this thread please */
+ sigfillset(&fullset);
+ pthread_sigmask(SIG_BLOCK, &fullset, NULL);
+#endif
+
+ if (!(lf = pa_runtime_path(AUTOSPAWN_LOCK))) {
+ pa_log_warn(_("Cannot access autospawn lock."));
+ goto fail;
+ }
+
+ if ((fd = pa_lock_lockfile(lf)) < 0)
+ goto fail;
+
+ pa_mutex_lock(lock_fd_mutex);
+ pa_assert(state == STATE_IDLE);
+ lock_fd = fd;
+ state = STATE_OWNING;
+ pa_mutex_unlock(lock_fd_mutex);
+
+ goto finish;
+
+fail:
+ pa_mutex_lock(lock_fd_mutex);
+ pa_assert(state == STATE_IDLE);
+ state = STATE_FAILED;
+ pa_mutex_unlock(lock_fd_mutex);
+
+finish:
+ pa_xfree(lf);
+
+ ping();
+}
+
+static int start_thread(void) {
+
+ if (!thread)
+ if (!(thread = pa_thread_new("autospawn", thread_func, NULL)))
+ return -1;
+
+ return 0;
+}
+
+static void create_mutex(void) {
+ PA_ONCE_BEGIN {
+ mutex = pa_mutex_new(false, false);
+ } PA_ONCE_END;
+}
+
+static void destroy_mutex(void) {
+ if (mutex)
+ pa_mutex_free(mutex);
+}
+
+int pa_autospawn_lock_init(void) {
+ int ret = -1;
+
+ create_mutex();
+ pa_mutex_lock(mutex);
+
+ if (ref() < 0)
+ ret = -1;
+ else
+ ret = pipe_fd[0];
+
+ pa_mutex_unlock(mutex);
+
+ return ret;
+}
+
+int pa_autospawn_lock_acquire(bool block) {
+ int ret = -1;
+
+ create_mutex();
+ pa_mutex_lock(mutex);
+ pa_assert(n_ref >= 1);
+
+ pa_mutex_lock(lock_fd_mutex);
+
+ for (;;) {
+
+ empty_pipe();
+
+ if (state == STATE_OWNING) {
+ state = STATE_TAKEN;
+ ret = 1;
+ break;
+ }
+
+ if (state == STATE_FAILED) {
+ ret = -1;
+ break;
+ }
+
+ if (state == STATE_IDLE)
+ if (start_thread() < 0)
+ break;
+
+ if (!block) {
+ ret = 0;
+ break;
+ }
+
+ pa_mutex_unlock(lock_fd_mutex);
+ pa_mutex_unlock(mutex);
+
+ wait_for_ping();
+
+ pa_mutex_lock(mutex);
+ pa_mutex_lock(lock_fd_mutex);
+ }
+
+ pa_mutex_unlock(lock_fd_mutex);
+
+ pa_mutex_unlock(mutex);
+
+ return ret;
+}
+
+void pa_autospawn_lock_release(void) {
+
+ create_mutex();
+ pa_mutex_lock(mutex);
+ pa_assert(n_ref >= 1);
+
+ pa_assert(state == STATE_TAKEN);
+ state = STATE_OWNING;
+
+ ping();
+
+ pa_mutex_unlock(mutex);
+}
+
+void pa_autospawn_lock_done(bool after_fork) {
+
+ create_mutex();
+ pa_mutex_lock(mutex);
+ pa_assert(n_ref >= 1);
+
+ unref(after_fork);
+
+ pa_mutex_unlock(mutex);
+}
diff --git a/src/pulsecore/lock-autospawn.h b/src/pulsecore/lock-autospawn.h
new file mode 100644
index 0000000..9fc0ab1
--- /dev/null
+++ b/src/pulsecore/lock-autospawn.h
@@ -0,0 +1,30 @@
+#ifndef foopulselockautospawnhfoo
+#define foopulselockautospawnhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2008 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulsecore/macro.h>
+
+int pa_autospawn_lock_init(void);
+int pa_autospawn_lock_acquire(bool block);
+void pa_autospawn_lock_release(void);
+void pa_autospawn_lock_done(bool after_fork);
+
+#endif
diff --git a/src/pulsecore/log.c b/src/pulsecore/log.c
new file mode 100644
index 0000000..2ea8862
--- /dev/null
+++ b/src/pulsecore/log.c
@@ -0,0 +1,685 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#ifdef HAVE_EXECINFO_H
+#include <execinfo.h>
+#endif
+
+#ifdef HAVE_SYSLOG_H
+#include <syslog.h>
+#endif
+
+#ifdef HAVE_SYSTEMD_JOURNAL
+
+/* sd_journal_send() implicitly add fields for the source file,
+ * function name and code line from where it's invoked. As the
+ * correct code location fields CODE_FILE, CODE_LINE and
+ * CODE_FUNC are already handled by this module, we do not want
+ * the automatic values supplied by the systemd journal API.
+ *
+ * Without suppressing these, both the actual log event source
+ * and the call to sd_journal_send() will be logged. */
+#define SD_JOURNAL_SUPPRESS_LOCATION
+
+#include <systemd/sd-journal.h>
+#endif
+
+#include <pulse/gccmacro.h>
+#include <pulse/rtclock.h>
+#include <pulse/utf8.h>
+#include <pulse/xmalloc.h>
+#include <pulse/util.h>
+#include <pulse/timeval.h>
+
+#include <pulsecore/macro.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/core-error.h>
+#include <pulsecore/once.h>
+#include <pulsecore/ratelimit.h>
+#include <pulsecore/thread.h>
+#include <pulsecore/i18n.h>
+
+#include "log.h"
+
+#define ENV_LOG_SYSLOG "PULSE_LOG_SYSLOG"
+#define ENV_LOG_JOURNAL "PULSE_LOG_JOURNAL"
+#define ENV_LOG_LEVEL "PULSE_LOG"
+#define ENV_LOG_COLORS "PULSE_LOG_COLORS"
+#define ENV_LOG_PRINT_TIME "PULSE_LOG_TIME"
+#define ENV_LOG_PRINT_FILE "PULSE_LOG_FILE"
+#define ENV_LOG_PRINT_META "PULSE_LOG_META"
+#define ENV_LOG_PRINT_LEVEL "PULSE_LOG_LEVEL"
+#define ENV_LOG_BACKTRACE "PULSE_LOG_BACKTRACE"
+#define ENV_LOG_BACKTRACE_SKIP "PULSE_LOG_BACKTRACE_SKIP"
+#define ENV_LOG_NO_RATELIMIT "PULSE_LOG_NO_RATE_LIMIT"
+#define LOG_MAX_SUFFIX_NUMBER 99
+
+static char *ident = NULL; /* in local charset format */
+static pa_log_target target = { PA_LOG_STDERR, NULL };
+static pa_log_target_type_t target_override;
+static bool target_override_set = false;
+static pa_log_level_t maximum_level = PA_LOG_ERROR, maximum_level_override = PA_LOG_ERROR;
+static unsigned show_backtrace = 0, show_backtrace_override = 0, skip_backtrace = 0;
+static pa_log_flags_t flags = 0, flags_override = 0;
+static bool no_rate_limit = false;
+static int log_fd = -1;
+static int write_type = 0;
+
+#ifdef HAVE_SYSLOG_H
+static const int level_to_syslog[] = {
+ [PA_LOG_ERROR] = LOG_ERR,
+ [PA_LOG_WARN] = LOG_WARNING,
+ [PA_LOG_NOTICE] = LOG_NOTICE,
+ [PA_LOG_INFO] = LOG_INFO,
+ [PA_LOG_DEBUG] = LOG_DEBUG
+};
+#endif
+
+/* These are actually equivalent to the syslog ones
+ * but we don't want to depend on syslog.h */
+#ifdef HAVE_SYSTEMD_JOURNAL
+static const int level_to_journal[] = {
+ [PA_LOG_ERROR] = 3,
+ [PA_LOG_WARN] = 4,
+ [PA_LOG_NOTICE] = 5,
+ [PA_LOG_INFO] = 6,
+ [PA_LOG_DEBUG] = 7
+};
+#endif
+
+static const char level_to_char[] = {
+ [PA_LOG_ERROR] = 'E',
+ [PA_LOG_WARN] = 'W',
+ [PA_LOG_NOTICE] = 'N',
+ [PA_LOG_INFO] = 'I',
+ [PA_LOG_DEBUG] = 'D'
+};
+
+void pa_log_set_ident(const char *p) {
+ pa_xfree(ident);
+
+ if (!(ident = pa_utf8_to_locale(p)))
+ ident = pa_ascii_filter(p);
+}
+
+/* To make valgrind shut up. */
+static void ident_destructor(void) PA_GCC_DESTRUCTOR;
+static void ident_destructor(void) {
+ if (!pa_in_valgrind())
+ return;
+
+ pa_xfree(ident);
+}
+
+void pa_log_set_level(pa_log_level_t l) {
+ pa_assert(l < PA_LOG_LEVEL_MAX);
+
+ maximum_level = l;
+}
+
+int pa_log_set_target(pa_log_target *t) {
+ int fd = -1;
+ int old_fd;
+
+ pa_assert(t);
+
+ switch (t->type) {
+ case PA_LOG_STDERR:
+ case PA_LOG_SYSLOG:
+#ifdef HAVE_SYSTEMD_JOURNAL
+ case PA_LOG_JOURNAL:
+#endif
+ case PA_LOG_NULL:
+ break;
+ case PA_LOG_FILE:
+ if ((fd = pa_open_cloexec(t->file, O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR)) < 0) {
+ pa_log(_("Failed to open target file '%s'."), t->file);
+ return -1;
+ }
+ break;
+ case PA_LOG_NEWFILE: {
+ char *file_path;
+ char *p;
+ unsigned version;
+
+ file_path = pa_sprintf_malloc("%s.xx", t->file);
+ p = file_path + strlen(t->file);
+
+ for (version = 0; version <= LOG_MAX_SUFFIX_NUMBER; version++) {
+ memset(p, 0, 3); /* Overwrite the ".xx" part in file_path with zero bytes. */
+
+ if (version > 0)
+ pa_snprintf(p, 4, ".%u", version); /* Why 4? ".xx" + termitating zero byte. */
+
+ if ((fd = pa_open_cloexec(file_path, O_WRONLY | O_TRUNC | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR)) >= 0)
+ break;
+ }
+
+ if (version > LOG_MAX_SUFFIX_NUMBER) {
+ pa_log(_("Tried to open target file '%s', '%s.1', '%s.2' ... '%s.%d', but all failed."),
+ t->file, t->file, t->file, t->file, LOG_MAX_SUFFIX_NUMBER);
+ pa_xfree(file_path);
+ return -1;
+ } else
+ pa_log_debug("Opened target file %s\n", file_path);
+
+ pa_xfree(file_path);
+ break;
+ }
+ }
+
+ target.type = t->type;
+ pa_xfree(target.file);
+ target.file = pa_xstrdup(t->file);
+
+ old_fd = log_fd;
+ log_fd = fd;
+
+ if (old_fd >= 0)
+ pa_close(old_fd);
+
+ return 0;
+}
+
+void pa_log_set_flags(pa_log_flags_t _flags, pa_log_merge_t merge) {
+ pa_assert(!(_flags & ~(PA_LOG_COLORS|PA_LOG_PRINT_TIME|PA_LOG_PRINT_FILE|PA_LOG_PRINT_META|PA_LOG_PRINT_LEVEL)));
+
+ if (merge == PA_LOG_SET)
+ flags |= _flags;
+ else if (merge == PA_LOG_UNSET)
+ flags &= ~_flags;
+ else
+ flags = _flags;
+}
+
+void pa_log_set_show_backtrace(unsigned nlevels) {
+ show_backtrace = nlevels;
+}
+
+void pa_log_set_skip_backtrace(unsigned nlevels) {
+ skip_backtrace = nlevels;
+}
+
+#ifdef HAVE_EXECINFO_H
+
+static char* get_backtrace(unsigned show_nframes) {
+ void* trace[32];
+ int n_frames;
+ char **symbols, *e, *r;
+ unsigned j, n, s;
+ size_t a;
+
+ pa_assert(show_nframes > 0);
+
+ n_frames = backtrace(trace, PA_ELEMENTSOF(trace));
+
+ if (n_frames <= 0)
+ return NULL;
+
+ symbols = backtrace_symbols(trace, n_frames);
+
+ if (!symbols)
+ return NULL;
+
+ s = skip_backtrace;
+ n = PA_MIN((unsigned) n_frames, s + show_nframes);
+
+ a = 4;
+
+ for (j = s; j < n; j++) {
+ if (j > s)
+ a += 2;
+ a += strlen(pa_path_get_filename(symbols[j]));
+ }
+
+ r = pa_xnew(char, a);
+
+ strcpy(r, " (");
+ e = r + 2;
+
+ for (j = s; j < n; j++) {
+ const char *sym;
+
+ if (j > s) {
+ strcpy(e, "<<");
+ e += 2;
+ }
+
+ sym = pa_path_get_filename(symbols[j]);
+
+ strcpy(e, sym);
+ e += strlen(sym);
+ }
+
+ strcpy(e, ")");
+
+ free(symbols);
+
+ return r;
+}
+
+#endif
+
+static void init_defaults(void) {
+ PA_ONCE_BEGIN {
+
+ const char *e;
+
+ if (!ident) {
+ char binary[256];
+ if (pa_get_binary_name(binary, sizeof(binary)))
+ pa_log_set_ident(binary);
+ }
+
+ if (getenv(ENV_LOG_SYSLOG)) {
+ target_override = PA_LOG_SYSLOG;
+ target_override_set = true;
+ }
+
+#ifdef HAVE_SYSTEMD_JOURNAL
+ if (getenv(ENV_LOG_JOURNAL)) {
+ target_override = PA_LOG_JOURNAL;
+ target_override_set = true;
+ }
+#endif
+
+ if ((e = getenv(ENV_LOG_LEVEL))) {
+ maximum_level_override = (pa_log_level_t) atoi(e);
+
+ if (maximum_level_override >= PA_LOG_LEVEL_MAX)
+ maximum_level_override = PA_LOG_LEVEL_MAX-1;
+ }
+
+ if (getenv(ENV_LOG_COLORS))
+ flags_override |= PA_LOG_COLORS;
+
+ if (getenv(ENV_LOG_PRINT_TIME))
+ flags_override |= PA_LOG_PRINT_TIME;
+
+ if (getenv(ENV_LOG_PRINT_FILE))
+ flags_override |= PA_LOG_PRINT_FILE;
+
+ if (getenv(ENV_LOG_PRINT_META))
+ flags_override |= PA_LOG_PRINT_META;
+
+ if (getenv(ENV_LOG_PRINT_LEVEL))
+ flags_override |= PA_LOG_PRINT_LEVEL;
+
+ if ((e = getenv(ENV_LOG_BACKTRACE))) {
+ show_backtrace_override = (unsigned) atoi(e);
+
+ if (show_backtrace_override <= 0)
+ show_backtrace_override = 0;
+ }
+
+ if ((e = getenv(ENV_LOG_BACKTRACE_SKIP))) {
+ skip_backtrace = (unsigned) atoi(e);
+
+ if (skip_backtrace <= 0)
+ skip_backtrace = 0;
+ }
+
+ if (getenv(ENV_LOG_NO_RATELIMIT))
+ no_rate_limit = true;
+
+ } PA_ONCE_END;
+}
+
+#ifdef HAVE_SYSLOG_H
+static void log_syslog(pa_log_level_t level, char *t, char *timestamp, char *location, char *bt) {
+ char *local_t;
+
+ openlog(ident, LOG_PID, LOG_USER);
+
+ if ((local_t = pa_utf8_to_locale(t)))
+ t = local_t;
+
+ syslog(level_to_syslog[level], "%s%s%s%s", timestamp, location, t, pa_strempty(bt));
+ pa_xfree(local_t);
+}
+#endif
+
+void pa_log_levelv_meta(
+ pa_log_level_t level,
+ const char*file,
+ int line,
+ const char *func,
+ const char *format,
+ va_list ap) {
+
+ char *t, *n;
+ int saved_errno = errno;
+ char *bt = NULL;
+ pa_log_target_type_t _target;
+ pa_log_level_t _maximum_level;
+ unsigned _show_backtrace;
+ pa_log_flags_t _flags;
+
+ /* We don't use dynamic memory allocation here to minimize the hit
+ * in RT threads */
+ char text[16*1024], location[128], timestamp[32];
+
+ pa_assert(level < PA_LOG_LEVEL_MAX);
+ pa_assert(format);
+
+ init_defaults();
+
+ _target = target_override_set ? target_override : target.type;
+ _maximum_level = PA_MAX(maximum_level, maximum_level_override);
+ _show_backtrace = PA_MAX(show_backtrace, show_backtrace_override);
+ _flags = flags | flags_override;
+
+ if (PA_LIKELY(level > _maximum_level)) {
+ errno = saved_errno;
+ return;
+ }
+
+ pa_vsnprintf(text, sizeof(text), format, ap);
+
+ if ((_flags & PA_LOG_PRINT_META) && file && line > 0 && func)
+ pa_snprintf(location, sizeof(location), "[%s][%s:%i %s()] ",
+ pa_strnull(pa_thread_get_name(pa_thread_self())), file, line, func);
+ else if ((_flags & (PA_LOG_PRINT_META|PA_LOG_PRINT_FILE)) && file)
+ pa_snprintf(location, sizeof(location), "[%s] %s: ",
+ pa_strnull(pa_thread_get_name(pa_thread_self())), pa_path_get_filename(file));
+ else
+ location[0] = 0;
+
+ if (_flags & PA_LOG_PRINT_TIME) {
+ static pa_usec_t start, last;
+ pa_usec_t u, a, r;
+
+ u = pa_rtclock_now();
+
+ PA_ONCE_BEGIN {
+ start = u;
+ last = u;
+ } PA_ONCE_END;
+
+ r = u - last;
+ a = u - start;
+
+ /* This is not thread safe, but this is a debugging tool only
+ * anyway. */
+ last = u;
+
+ pa_snprintf(timestamp, sizeof(timestamp), "(%4llu.%03llu|%4llu.%03llu) ",
+ (unsigned long long) (a / PA_USEC_PER_SEC),
+ (unsigned long long) (((a / PA_USEC_PER_MSEC)) % 1000),
+ (unsigned long long) (r / PA_USEC_PER_SEC),
+ (unsigned long long) (((r / PA_USEC_PER_MSEC)) % 1000));
+
+ } else
+ timestamp[0] = 0;
+
+#ifdef HAVE_EXECINFO_H
+ if (_show_backtrace > 0)
+ bt = get_backtrace(_show_backtrace);
+#endif
+
+ if (!pa_utf8_valid(text))
+ pa_logl(level, "Invalid UTF-8 string following below:");
+
+ for (t = text; t; t = n) {
+ if ((n = strchr(t, '\n'))) {
+ *n = 0;
+ n++;
+ }
+
+ /* We ignore strings only made out of whitespace */
+ if (t[strspn(t, "\t ")] == 0)
+ continue;
+
+ switch (_target) {
+
+ case PA_LOG_STDERR: {
+ const char *prefix = "", *suffix = "", *grey = "";
+ char *local_t;
+
+#ifndef OS_IS_WIN32
+ /* Yes indeed. Useless, but fun! */
+ if ((_flags & PA_LOG_COLORS) && isatty(STDERR_FILENO)) {
+ if (level <= PA_LOG_ERROR)
+ prefix = "\x1B[1;31m";
+ else if (level <= PA_LOG_WARN)
+ prefix = "\x1B[1m";
+
+ if (bt)
+ grey = "\x1B[2m";
+
+ if (grey[0] || prefix[0])
+ suffix = "\x1B[0m";
+ }
+#endif
+
+ /* We shouldn't be using dynamic allocation here to
+ * minimize the hit in RT threads */
+ if ((local_t = pa_utf8_to_locale(t)))
+ t = local_t;
+
+ if (_flags & PA_LOG_PRINT_LEVEL)
+ fprintf(stderr, "%s%c: %s%s%s%s%s%s\n", timestamp, level_to_char[level], location, prefix, t, grey, pa_strempty(bt), suffix);
+ else
+ fprintf(stderr, "%s%s%s%s%s%s%s\n", timestamp, location, prefix, t, grey, pa_strempty(bt), suffix);
+#ifdef OS_IS_WIN32
+ fflush(stderr);
+#endif
+
+ pa_xfree(local_t);
+
+ break;
+ }
+
+#ifdef HAVE_SYSLOG_H
+ case PA_LOG_SYSLOG:
+ log_syslog(level, t, timestamp, location, bt);
+ break;
+#endif
+
+#ifdef HAVE_SYSTEMD_JOURNAL
+ case PA_LOG_JOURNAL:
+ if (sd_journal_send("MESSAGE=%s", t,
+ "PRIORITY=%i", level_to_journal[level],
+ "CODE_FILE=%s", file,
+ "CODE_FUNC=%s", func,
+ "CODE_LINE=%d", line,
+ "PULSE_BACKTRACE=%s", pa_strempty(bt),
+ NULL) < 0) {
+#ifdef HAVE_SYSLOG_H
+ pa_log_target new_target = { .type = PA_LOG_SYSLOG, .file = NULL };
+
+ syslog(level_to_syslog[PA_LOG_ERROR], "%s%s%s", timestamp, __FILE__,
+ "Error writing logs to the journal. Redirect log messages to syslog.");
+ log_syslog(level, t, timestamp, location, bt);
+#else
+ pa_log_target new_target = { .type = PA_LOG_STDERR, .file = NULL };
+
+ saved_errno = errno;
+ fprintf(stderr, "%s\n", "Error writing logs to the journal. Redirect log messages to console.");
+ fprintf(stderr, "%s\n", t);
+#endif
+ pa_log_set_target(&new_target);
+ }
+ break;
+#endif
+
+ case PA_LOG_FILE:
+ case PA_LOG_NEWFILE: {
+ char *local_t;
+
+ if ((local_t = pa_utf8_to_locale(t)))
+ t = local_t;
+
+ if (log_fd >= 0) {
+ char metadata[256];
+
+ if (_flags & PA_LOG_PRINT_LEVEL)
+ pa_snprintf(metadata, sizeof(metadata), "%s%c: %s", timestamp, level_to_char[level], location);
+ else
+ pa_snprintf(metadata, sizeof(metadata), "%s%s", timestamp, location);
+
+ if ((pa_write(log_fd, metadata, strlen(metadata), &write_type) < 0)
+ || (pa_write(log_fd, t, strlen(t), &write_type) < 0)
+ || (bt && pa_write(log_fd, bt, strlen(bt), &write_type) < 0)
+ || (pa_write(log_fd, "\n", 1, &write_type) < 0)) {
+ pa_log_target new_target = { .type = PA_LOG_STDERR, .file = NULL };
+ saved_errno = errno;
+ fprintf(stderr, "%s\n", "Error writing logs to a file descriptor. Redirect log messages to console.");
+ fprintf(stderr, "%s %s\n", metadata, t);
+ pa_log_set_target(&new_target);
+ }
+ }
+
+ pa_xfree(local_t);
+
+ break;
+ }
+ case PA_LOG_NULL:
+ default:
+ break;
+ }
+ }
+
+ pa_xfree(bt);
+ errno = saved_errno;
+}
+
+void pa_log_level_meta(
+ pa_log_level_t level,
+ const char*file,
+ int line,
+ const char *func,
+ const char *format, ...) {
+
+ va_list ap;
+ va_start(ap, format);
+ pa_log_levelv_meta(level, file, line, func, format, ap);
+ va_end(ap);
+}
+
+void pa_log_levelv(pa_log_level_t level, const char *format, va_list ap) {
+ pa_log_levelv_meta(level, NULL, 0, NULL, format, ap);
+}
+
+void pa_log_level(pa_log_level_t level, const char *format, ...) {
+ va_list ap;
+
+ va_start(ap, format);
+ pa_log_levelv_meta(level, NULL, 0, NULL, format, ap);
+ va_end(ap);
+}
+
+bool pa_log_ratelimit(pa_log_level_t level) {
+ /* Not more than 10 messages every 5s */
+ static PA_DEFINE_RATELIMIT(ratelimit, 5 * PA_USEC_PER_SEC, 10);
+
+ init_defaults();
+
+ if (no_rate_limit)
+ return true;
+
+ return pa_ratelimit_test(&ratelimit, level);
+}
+
+pa_log_target *pa_log_target_new(pa_log_target_type_t type, const char *file) {
+ pa_log_target *t = NULL;
+
+ t = pa_xnew(pa_log_target, 1);
+
+ t->type = type;
+ t->file = pa_xstrdup(file);
+
+ return t;
+}
+
+void pa_log_target_free(pa_log_target *t) {
+ pa_assert(t);
+
+ pa_xfree(t->file);
+ pa_xfree(t);
+}
+
+pa_log_target *pa_log_parse_target(const char *string) {
+ pa_log_target *t = NULL;
+
+ pa_assert(string);
+
+ if (pa_streq(string, "stderr"))
+ t = pa_log_target_new(PA_LOG_STDERR, NULL);
+ else if (pa_streq(string, "syslog"))
+ t = pa_log_target_new(PA_LOG_SYSLOG, NULL);
+#ifdef HAVE_SYSTEMD_JOURNAL
+ else if (pa_streq(string, "journal"))
+ t = pa_log_target_new(PA_LOG_JOURNAL, NULL);
+#endif
+ else if (pa_streq(string, "null"))
+ t = pa_log_target_new(PA_LOG_NULL, NULL);
+ else if (pa_startswith(string, "file:"))
+ t = pa_log_target_new(PA_LOG_FILE, string + 5);
+ else if (pa_startswith(string, "newfile:"))
+ t = pa_log_target_new(PA_LOG_NEWFILE, string + 8);
+ else
+ pa_log(_("Invalid log target."));
+
+ return t;
+}
+
+char *pa_log_target_to_string(const pa_log_target *t) {
+ char *string = NULL;
+
+ pa_assert(t);
+
+ switch (t->type) {
+ case PA_LOG_STDERR:
+ string = pa_xstrdup("stderr");
+ break;
+ case PA_LOG_SYSLOG:
+ string = pa_xstrdup("syslog");
+ break;
+#ifdef HAVE_SYSTEMD_JOURNAL
+ case PA_LOG_JOURNAL:
+ string = pa_xstrdup("journal");
+ break;
+#endif
+ case PA_LOG_NULL:
+ string = pa_xstrdup("null");
+ break;
+ case PA_LOG_FILE:
+ string = pa_sprintf_malloc("file:%s", t->file);
+ break;
+ case PA_LOG_NEWFILE:
+ string = pa_sprintf_malloc("newfile:%s", t->file);
+ break;
+ }
+
+ return string;
+}
diff --git a/src/pulsecore/log.h b/src/pulsecore/log.h
new file mode 100644
index 0000000..803ed5a
--- /dev/null
+++ b/src/pulsecore/log.h
@@ -0,0 +1,155 @@
+#ifndef foologhfoo
+#define foologhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdarg.h>
+#include <stdlib.h>
+
+#include <pulsecore/macro.h>
+#include <pulse/gccmacro.h>
+
+/* A simple logging subsystem */
+
+/* Where to log to */
+typedef enum pa_log_target_type {
+ PA_LOG_STDERR, /* default */
+ PA_LOG_SYSLOG,
+#ifdef HAVE_SYSTEMD_JOURNAL
+ PA_LOG_JOURNAL, /* systemd journal */
+#endif
+ PA_LOG_NULL, /* to /dev/null */
+ PA_LOG_FILE, /* to a user specified file */
+ PA_LOG_NEWFILE, /* with an automatic suffix to avoid overwriting anything */
+} pa_log_target_type_t;
+
+typedef enum pa_log_level {
+ PA_LOG_ERROR = 0, /* Error messages */
+ PA_LOG_WARN = 1, /* Warning messages */
+ PA_LOG_NOTICE = 2, /* Notice messages */
+ PA_LOG_INFO = 3, /* Info messages */
+ PA_LOG_DEBUG = 4, /* Debug messages */
+ PA_LOG_LEVEL_MAX
+} pa_log_level_t;
+
+typedef enum pa_log_flags {
+ PA_LOG_COLORS = 0x01, /* Show colorful output */
+ PA_LOG_PRINT_TIME = 0x02, /* Show time */
+ PA_LOG_PRINT_FILE = 0x04, /* Show source file */
+ PA_LOG_PRINT_META = 0x08, /* Show extended location information */
+ PA_LOG_PRINT_LEVEL = 0x10, /* Show log level prefix */
+} pa_log_flags_t;
+
+typedef enum pa_log_merge {
+ PA_LOG_SET,
+ PA_LOG_UNSET,
+ PA_LOG_RESET
+} pa_log_merge_t;
+
+typedef struct {
+ pa_log_target_type_t type;
+ char *file;
+} pa_log_target;
+
+/* Set an identification for the current daemon. Used when logging to syslog. */
+void pa_log_set_ident(const char *p);
+
+/* Set a log target. */
+int pa_log_set_target(pa_log_target *t);
+
+/* Maximal log level */
+void pa_log_set_level(pa_log_level_t l);
+
+/* Set flags */
+void pa_log_set_flags(pa_log_flags_t flags, pa_log_merge_t merge);
+
+/* Enable backtrace */
+void pa_log_set_show_backtrace(unsigned nlevels);
+
+/* Skip the first backtrace frames */
+void pa_log_set_skip_backtrace(unsigned nlevels);
+
+void pa_log_level_meta(
+ pa_log_level_t level,
+ const char*file,
+ int line,
+ const char *func,
+ const char *format, ...) PA_GCC_PRINTF_ATTR(5,6);
+
+void pa_log_levelv_meta(
+ pa_log_level_t level,
+ const char*file,
+ int line,
+ const char *func,
+ const char *format,
+ va_list ap);
+
+void pa_log_level(
+ pa_log_level_t level,
+ const char *format, ...) PA_GCC_PRINTF_ATTR(2,3);
+
+void pa_log_levelv(
+ pa_log_level_t level,
+ const char *format,
+ va_list ap);
+
+pa_log_target *pa_log_target_new(pa_log_target_type_t type, const char *file);
+
+void pa_log_target_free(pa_log_target *t);
+
+pa_log_target *pa_log_parse_target(const char *string);
+
+char *pa_log_target_to_string(const pa_log_target *t);
+
+#if __STDC_VERSION__ >= 199901L
+
+/* ISO varargs available */
+
+#define pa_log_debug(...) pa_log_level_meta(PA_LOG_DEBUG, __FILE__, __LINE__, __func__, __VA_ARGS__)
+#define pa_log_info(...) pa_log_level_meta(PA_LOG_INFO, __FILE__, __LINE__, __func__, __VA_ARGS__)
+#define pa_log_notice(...) pa_log_level_meta(PA_LOG_NOTICE, __FILE__, __LINE__, __func__, __VA_ARGS__)
+#define pa_log_warn(...) pa_log_level_meta(PA_LOG_WARN, __FILE__, __LINE__, __func__, __VA_ARGS__)
+#define pa_log_error(...) pa_log_level_meta(PA_LOG_ERROR, __FILE__, __LINE__, __func__, __VA_ARGS__)
+#define pa_logl(level, ...) pa_log_level_meta(level, __FILE__, __LINE__, __func__, __VA_ARGS__)
+
+#else
+
+#define LOG_FUNC(suffix, level) \
+PA_GCC_UNUSED static void pa_log_##suffix(const char *format, ...) { \
+ va_list ap; \
+ va_start(ap, format); \
+ pa_log_levelv_meta(level, NULL, 0, NULL, format, ap); \
+ va_end(ap); \
+}
+
+LOG_FUNC(debug, PA_LOG_DEBUG)
+LOG_FUNC(info, PA_LOG_INFO)
+LOG_FUNC(notice, PA_LOG_NOTICE)
+LOG_FUNC(warn, PA_LOG_WARN)
+LOG_FUNC(error, PA_LOG_ERROR)
+
+#endif
+
+#define pa_log pa_log_error
+
+bool pa_log_ratelimit(pa_log_level_t level);
+
+#endif
diff --git a/src/pulsecore/ltdl-helper.c b/src/pulsecore/ltdl-helper.c
new file mode 100644
index 0000000..cfdde26
--- /dev/null
+++ b/src/pulsecore/ltdl-helper.c
@@ -0,0 +1,63 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <ctype.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/macro.h>
+
+#include "ltdl-helper.h"
+
+pa_void_func_t pa_load_sym(lt_dlhandle handle, const char *module, const char *symbol) {
+ char *sn, *c;
+ pa_void_func_t f;
+
+ pa_assert(handle);
+ pa_assert(symbol);
+
+ f = (pa_void_func_t) lt_dlsym(handle, symbol);
+
+ if (f)
+ return f;
+
+ if (!module)
+ return NULL;
+
+ /* As the .la files might have been cleansed from the system, we should
+ * try with the ltdl prefix as well. */
+
+ sn = pa_sprintf_malloc("%s_LTX_%s", module, symbol);
+
+ for (c = sn; *c; c++)
+ if (!isalnum((unsigned char)*c))
+ *c = '_';
+
+ f = (pa_void_func_t) lt_dlsym(handle, sn);
+ pa_xfree(sn);
+
+ return f;
+}
diff --git a/src/pulsecore/ltdl-helper.h b/src/pulsecore/ltdl-helper.h
new file mode 100644
index 0000000..d3219ba
--- /dev/null
+++ b/src/pulsecore/ltdl-helper.h
@@ -0,0 +1,29 @@
+#ifndef foopulsecoreltdlhelperhfoo
+#define foopulsecoreltdlhelperhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <ltdl.h>
+
+typedef void (*pa_void_func_t)(void);
+
+pa_void_func_t pa_load_sym(lt_dlhandle handle, const char*module, const char *symbol);
+
+#endif
diff --git a/src/pulsecore/macro.h b/src/pulsecore/macro.h
new file mode 100644
index 0000000..ec8d31c
--- /dev/null
+++ b/src/pulsecore/macro.h
@@ -0,0 +1,272 @@
+#ifndef foopulsemacrohfoo
+#define foopulsemacrohfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <assert.h>
+#include <limits.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+
+#ifndef PACKAGE
+#error "Please include config.h before including this file!"
+#endif
+
+/* Rounds down */
+static inline void* PA_ALIGN_PTR(const void *p) {
+ return (void*) (((size_t) p) & ~(sizeof(void*) - 1));
+}
+
+/* Rounds up */
+static inline size_t PA_ALIGN(size_t l) {
+ return ((l + sizeof(void*) - 1) & ~(sizeof(void*) - 1));
+}
+
+#if defined(__GNUC__)
+ #define PA_UNUSED __attribute__ ((unused))
+#else
+ #define PA_UNUSED
+#endif
+
+#define PA_ELEMENTSOF(x) (sizeof(x)/sizeof((x)[0]))
+
+#if defined(__GNUC__)
+ #define PA_DECLARE_ALIGNED(n,t,v) t v __attribute__ ((aligned (n)))
+#else
+ #define PA_DECLARE_ALIGNED(n,t,v) t v
+#endif
+
+#ifdef __GNUC__
+#define typeof __typeof__
+#endif
+
+/* The users of PA_MIN and PA_MAX, PA_CLAMP, PA_ROUND_UP should be
+ * aware that these macros on non-GCC executed code with side effects
+ * twice. It is thus considered misuse to use code with side effects
+ * as arguments to MIN and MAX. */
+
+#ifdef __GNUC__
+#define PA_MAX(a,b) \
+ __extension__ ({ \
+ typeof(a) _a = (a); \
+ typeof(b) _b = (b); \
+ _a > _b ? _a : _b; \
+ })
+#else
+#define PA_MAX(a, b) ((a) > (b) ? (a) : (b))
+#endif
+
+#ifdef __GNUC__
+#define PA_MIN(a,b) \
+ __extension__ ({ \
+ typeof(a) _a = (a); \
+ typeof(b) _b = (b); \
+ _a < _b ? _a : _b; \
+ })
+#else
+#define PA_MIN(a, b) ((a) < (b) ? (a) : (b))
+#endif
+
+#ifdef __GNUC__
+#define PA_ROUND_UP(a, b) \
+ __extension__ ({ \
+ typeof(a) _a = (a); \
+ typeof(b) _b = (b); \
+ ((_a + _b - 1) / _b) * _b; \
+ })
+#else
+#define PA_ROUND_UP(a, b) ((((a) + (b) - 1) / (b)) * (b))
+#endif
+
+#ifdef __GNUC__
+#define PA_ROUND_DOWN(a, b) \
+ __extension__ ({ \
+ typeof(a) _a = (a); \
+ typeof(b) _b = (b); \
+ (_a / _b) * _b; \
+ })
+#else
+#define PA_ROUND_DOWN(a, b) (((a) / (b)) * (b))
+#endif
+
+#ifdef __GNUC__
+#define PA_CLIP_SUB(a, b) \
+ __extension__ ({ \
+ typeof(a) _a = (a); \
+ typeof(b) _b = (b); \
+ _a > _b ? _a - _b : 0; \
+ })
+#else
+#define PA_CLIP_SUB(a, b) ((a) > (b) ? (a) - (b) : 0)
+#endif
+
+#ifdef __GNUC__
+#define PA_PRETTY_FUNCTION __PRETTY_FUNCTION__
+#else
+#define PA_PRETTY_FUNCTION ""
+#endif
+
+#define pa_return_if_fail(expr) \
+ do { \
+ if (PA_UNLIKELY(!(expr))) { \
+ pa_log_debug("Assertion '%s' failed at %s:%u, function %s.", #expr , __FILE__, __LINE__, PA_PRETTY_FUNCTION); \
+ return; \
+ } \
+ } while(false)
+
+#define pa_return_val_if_fail(expr, val) \
+ do { \
+ if (PA_UNLIKELY(!(expr))) { \
+ pa_log_debug("Assertion '%s' failed at %s:%u, function %s.", #expr , __FILE__, __LINE__, PA_PRETTY_FUNCTION); \
+ return (val); \
+ } \
+ } while(false)
+
+#define pa_return_null_if_fail(expr) pa_return_val_if_fail(expr, NULL)
+
+/* pa_assert_se() is an assert which guarantees side effects of x,
+ * i.e. is never optimized away, regardless of NDEBUG or FASTPATH. */
+#ifndef __COVERITY__
+#define pa_assert_se(expr) \
+ do { \
+ if (PA_UNLIKELY(!(expr))) { \
+ pa_log_error("Assertion '%s' failed at %s:%u, function %s(). Aborting.", #expr , __FILE__, __LINE__, PA_PRETTY_FUNCTION); \
+ abort(); \
+ } \
+ } while (false)
+#else
+#define pa_assert_se(expr) \
+ do { \
+ int _unique_var = (expr); \
+ if (!_unique_var) \
+ abort(); \
+ } while (false)
+#endif
+
+/* Does exactly nothing */
+#define pa_nop() do {} while (false)
+
+/* pa_assert() is an assert that may be optimized away by defining
+ * NDEBUG. pa_assert_fp() is an assert that may be optimized away by
+ * defining FASTPATH. It is supposed to be used in inner loops. It's
+ * there for extra paranoia checking and should probably be removed in
+ * production builds. */
+#ifdef NDEBUG
+#define pa_assert(expr) pa_nop()
+#define pa_assert_fp(expr) pa_nop()
+#elif defined (FASTPATH)
+#define pa_assert(expr) pa_assert_se(expr)
+#define pa_assert_fp(expr) pa_nop()
+#else
+#define pa_assert(expr) pa_assert_se(expr)
+#define pa_assert_fp(expr) pa_assert_se(expr)
+#endif
+
+#ifdef NDEBUG
+#define pa_assert_not_reached() abort()
+#else
+#define pa_assert_not_reached() \
+ do { \
+ pa_log_error("Code should not be reached at %s:%u, function %s(). Aborting.", __FILE__, __LINE__, PA_PRETTY_FUNCTION); \
+ abort(); \
+ } while (false)
+#endif
+
+/* A compile time assertion */
+#define pa_assert_cc(expr) \
+ do { \
+ switch (0) { \
+ case 0: \
+ case !!(expr): \
+ ; \
+ } \
+ } while (false)
+
+#define PA_PTR_TO_UINT(p) ((unsigned int) ((uintptr_t) (p)))
+#define PA_UINT_TO_PTR(u) ((void*) ((uintptr_t) (u)))
+
+#define PA_PTR_TO_UINT32(p) ((uint32_t) ((uintptr_t) (p)))
+#define PA_UINT32_TO_PTR(u) ((void*) ((uintptr_t) (u)))
+
+#define PA_PTR_TO_INT(p) ((int) ((intptr_t) (p)))
+#define PA_INT_TO_PTR(u) ((void*) ((intptr_t) (u)))
+
+#define PA_PTR_TO_INT32(p) ((int32_t) ((intptr_t) (p)))
+#define PA_INT32_TO_PTR(u) ((void*) ((intptr_t) (u)))
+
+#ifdef OS_IS_WIN32
+#define PA_PATH_SEP "\\"
+#define PA_PATH_SEP_CHAR '\\'
+#else
+#define PA_PATH_SEP "/"
+#define PA_PATH_SEP_CHAR '/'
+#endif
+
+#if defined(__GNUC__) && defined(__ELF__)
+
+#define PA_WARN_REFERENCE(sym, msg) \
+ __asm__(".section .gnu.warning." #sym); \
+ __asm__(".asciz \"" msg "\""); \
+ __asm__(".previous")
+
+#else
+
+#define PA_WARN_REFERENCE(sym, msg)
+
+#endif
+
+#if defined(__i386__) || defined(__x86_64__)
+#define PA_DEBUG_TRAP __asm__("int $3")
+#else
+#define PA_DEBUG_TRAP raise(SIGTRAP)
+#endif
+
+#define pa_memzero(x,l) (memset((x), 0, (l)))
+#define pa_zero(x) (pa_memzero(&(x), sizeof(x)))
+
+#define PA_INT_TYPE_SIGNED(type) (!!((type) 0 > (type) -1))
+
+#define PA_INT_TYPE_HALF(type) ((type) 1 << (sizeof(type)*8 - 2))
+
+#define PA_INT_TYPE_MAX(type) \
+ ((type) (PA_INT_TYPE_SIGNED(type) \
+ ? (PA_INT_TYPE_HALF(type) - 1 + PA_INT_TYPE_HALF(type)) \
+ : (type) -1))
+
+#define PA_INT_TYPE_MIN(type) \
+ ((type) (PA_INT_TYPE_SIGNED(type) \
+ ? (-1 - PA_INT_TYPE_MAX(type)) \
+ : (type) 0))
+
+/* The '#' preprocessor operator doesn't expand any macros that are in the
+ * parameter, which is why we need a separate macro for those cases where the
+ * parameter contains a macro that needs expanding. */
+#define PA_STRINGIZE(x) #x
+#define PA_EXPAND_AND_STRINGIZE(x) PA_STRINGIZE(x)
+
+/* We include this at the very last place */
+#include <pulsecore/log.h>
+
+#endif
diff --git a/src/pulsecore/mcalign.c b/src/pulsecore/mcalign.c
new file mode 100644
index 0000000..df885c2
--- /dev/null
+++ b/src/pulsecore/mcalign.c
@@ -0,0 +1,216 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/macro.h>
+
+#include "mcalign.h"
+
+struct pa_mcalign {
+ size_t base;
+ pa_memchunk leftover, current;
+};
+
+pa_mcalign *pa_mcalign_new(size_t base) {
+ pa_mcalign *m;
+ pa_assert(base);
+
+ m = pa_xnew(pa_mcalign, 1);
+
+ m->base = base;
+ pa_memchunk_reset(&m->leftover);
+ pa_memchunk_reset(&m->current);
+
+ return m;
+}
+
+void pa_mcalign_free(pa_mcalign *m) {
+ pa_assert(m);
+
+ if (m->leftover.memblock)
+ pa_memblock_unref(m->leftover.memblock);
+
+ if (m->current.memblock)
+ pa_memblock_unref(m->current.memblock);
+
+ pa_xfree(m);
+}
+
+void pa_mcalign_push(pa_mcalign *m, const pa_memchunk *c) {
+ pa_assert(m);
+ pa_assert(c);
+
+ pa_assert(c->memblock);
+ pa_assert(c->length > 0);
+
+ pa_assert(!m->current.memblock);
+
+ /* Append to the leftover memory block */
+ if (m->leftover.memblock) {
+
+ /* Try to merge */
+ if (m->leftover.memblock == c->memblock &&
+ m->leftover.index + m->leftover.length == c->index) {
+
+ /* Merge */
+ m->leftover.length += c->length;
+
+ /* If the new chunk is larger than m->base, move it to current */
+ if (m->leftover.length >= m->base) {
+ m->current = m->leftover;
+ pa_memchunk_reset(&m->leftover);
+ }
+
+ } else {
+ size_t l;
+ void *lo_data, *m_data;
+
+ /* We have to copy */
+ pa_assert(m->leftover.length < m->base);
+ l = m->base - m->leftover.length;
+
+ if (l > c->length)
+ l = c->length;
+
+ /* Can we use the current block? */
+ pa_memchunk_make_writable(&m->leftover, m->base);
+
+ lo_data = pa_memblock_acquire(m->leftover.memblock);
+ m_data = pa_memblock_acquire(c->memblock);
+ memcpy((uint8_t*) lo_data + m->leftover.index + m->leftover.length, (uint8_t*) m_data + c->index, l);
+ pa_memblock_release(m->leftover.memblock);
+ pa_memblock_release(c->memblock);
+ m->leftover.length += l;
+
+ pa_assert(m->leftover.length <= m->base);
+ pa_assert(m->leftover.length <= pa_memblock_get_length(m->leftover.memblock));
+
+ if (c->length > l) {
+ /* Save the remainder of the memory block */
+ m->current = *c;
+ m->current.index += l;
+ m->current.length -= l;
+ pa_memblock_ref(m->current.memblock);
+ }
+ }
+ } else {
+ /* Nothing to merge or copy, just store it */
+
+ if (c->length >= m->base)
+ m->current = *c;
+ else
+ m->leftover = *c;
+
+ pa_memblock_ref(c->memblock);
+ }
+}
+
+int pa_mcalign_pop(pa_mcalign *m, pa_memchunk *c) {
+ pa_assert(m);
+ pa_assert(c);
+
+ /* First test if there's a leftover memory block available */
+ if (m->leftover.memblock) {
+ pa_assert(m->leftover.length > 0);
+ pa_assert(m->leftover.length <= m->base);
+
+ /* The leftover memory block is not yet complete */
+ if (m->leftover.length < m->base)
+ return -1;
+
+ /* Return the leftover memory block */
+ *c = m->leftover;
+ pa_memchunk_reset(&m->leftover);
+
+ /* If the current memblock is too small move it the leftover */
+ if (m->current.memblock && m->current.length < m->base) {
+ m->leftover = m->current;
+ pa_memchunk_reset(&m->current);
+ }
+
+ return 0;
+ }
+
+ /* Now let's see if there is other data available */
+ if (m->current.memblock) {
+ size_t l;
+ pa_assert(m->current.length >= m->base);
+
+ /* The length of the returned memory block */
+ l = m->current.length;
+ l /= m->base;
+ l *= m->base;
+ pa_assert(l > 0);
+
+ /* Prepare the returned block */
+ *c = m->current;
+ pa_memblock_ref(c->memblock);
+ c->length = l;
+
+ /* Drop that from the current memory block */
+ pa_assert(l <= m->current.length);
+ m->current.index += l;
+ m->current.length -= l;
+
+ /* In case the whole block was dropped ... */
+ if (m->current.length == 0)
+ pa_memblock_unref(m->current.memblock);
+ else {
+ /* Move the remainder to leftover */
+ pa_assert(m->current.length < m->base && !m->leftover.memblock);
+
+ m->leftover = m->current;
+ }
+
+ pa_memchunk_reset(&m->current);
+
+ return 0;
+ }
+
+ /* There's simply nothing */
+ return -1;
+}
+
+size_t pa_mcalign_csize(pa_mcalign *m, size_t l) {
+ pa_assert(m);
+ pa_assert(l > 0);
+
+ pa_assert(!m->current.memblock);
+
+ if (m->leftover.memblock)
+ l += m->leftover.length;
+
+ return (l/m->base)*m->base;
+}
+
+void pa_mcalign_flush(pa_mcalign *m) {
+ pa_memchunk chunk;
+ pa_assert(m);
+
+ while (pa_mcalign_pop(m, &chunk) >= 0)
+ pa_memblock_unref(chunk.memblock);
+}
diff --git a/src/pulsecore/mcalign.h b/src/pulsecore/mcalign.h
new file mode 100644
index 0000000..353e66a
--- /dev/null
+++ b/src/pulsecore/mcalign.h
@@ -0,0 +1,81 @@
+#ifndef foomcalignhfoo
+#define foomcalignhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulsecore/memblock.h>
+#include <pulsecore/memchunk.h>
+
+/* An alignment object, used for aligning memchunks to multiples of
+ * the frame size. */
+
+/* Method of operation: the user creates a new mcalign object by
+ * calling pa_mcalign_new() with the appropriate aligning
+ * granularity. After that they may call pa_mcalign_push() for an input
+ * memchunk. After exactly one memchunk the user has to call
+ * pa_mcalign_pop() until it returns -1. If pa_mcalign_pop() returns
+ * 0, the memchunk *c is valid and aligned to the granularity. Some
+ * pseudocode illustrating this:
+ *
+ * pa_mcalign *a = pa_mcalign_new(4, NULL);
+ *
+ * for (;;) {
+ * pa_memchunk input;
+ *
+ * ... fill input ...
+ *
+ * pa_mcalign_push(m, &input);
+ * pa_memblock_unref(input.memblock);
+ *
+ * for (;;) {
+ * pa_memchunk output;
+ *
+ * if (pa_mcalign_pop(m, &output) < 0)
+ * break;
+ *
+ * ... consume output ...
+ *
+ * pa_memblock_unref(output.memblock);
+ * }
+ * }
+ *
+ * pa_memchunk_free(a);
+ * */
+
+typedef struct pa_mcalign pa_mcalign;
+
+pa_mcalign *pa_mcalign_new(size_t base);
+void pa_mcalign_free(pa_mcalign *m);
+
+/* Push a new memchunk into the aligner. The caller of this routine
+ * has to free the memchunk by himself. */
+void pa_mcalign_push(pa_mcalign *m, const pa_memchunk *c);
+
+/* Pop a new memchunk from the aligner. Returns 0 when successful,
+ * nonzero otherwise. */
+int pa_mcalign_pop(pa_mcalign *m, pa_memchunk *c);
+
+/* If we pass l bytes in now, how many bytes would we get out? */
+size_t pa_mcalign_csize(pa_mcalign *m, size_t l);
+
+/* Flush what's still stored in the aligner */
+void pa_mcalign_flush(pa_mcalign *m);
+
+#endif
diff --git a/src/pulsecore/mem.h b/src/pulsecore/mem.h
new file mode 100644
index 0000000..cba1410
--- /dev/null
+++ b/src/pulsecore/mem.h
@@ -0,0 +1,60 @@
+#ifndef foopulsememhfoo
+#define foopulsememhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2016 Ahmed S. Darwish <darwish.07@gmail.com>
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+
+#include <pulsecore/creds.h>
+#include <pulsecore/macro.h>
+
+typedef enum pa_mem_type {
+ PA_MEM_TYPE_SHARED_POSIX, /* Data is shared and created using POSIX shm_open() */
+ PA_MEM_TYPE_SHARED_MEMFD, /* Data is shared and created using Linux memfd_create() */
+ PA_MEM_TYPE_PRIVATE, /* Data is private and created using classic memory allocation
+ (posix_memalign(), malloc() or anonymous mmap()) */
+} pa_mem_type_t;
+
+static inline const char *pa_mem_type_to_string(pa_mem_type_t type) {
+ switch (type) {
+ case PA_MEM_TYPE_SHARED_POSIX:
+ return "shared posix-shm";
+ case PA_MEM_TYPE_SHARED_MEMFD:
+ return "shared memfd";
+ case PA_MEM_TYPE_PRIVATE:
+ return "private";
+ }
+
+ pa_assert_not_reached();
+}
+
+static inline bool pa_mem_type_is_shared(pa_mem_type_t t) {
+ return (t == PA_MEM_TYPE_SHARED_POSIX) || (t == PA_MEM_TYPE_SHARED_MEMFD);
+}
+
+static inline bool pa_memfd_is_locally_supported() {
+#if defined(HAVE_CREDS) && defined(HAVE_MEMFD)
+ return true;
+#else
+ return false;
+#endif
+}
+
+#endif
diff --git a/src/pulsecore/memblock.c b/src/pulsecore/memblock.c
new file mode 100644
index 0000000..fb83dac
--- /dev/null
+++ b/src/pulsecore/memblock.c
@@ -0,0 +1,1518 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <signal.h>
+#include <errno.h>
+
+#ifdef HAVE_VALGRIND_MEMCHECK_H
+#include <valgrind/memcheck.h>
+#endif
+
+#include <pulse/xmalloc.h>
+#include <pulse/def.h>
+
+#include <pulsecore/shm.h>
+#include <pulsecore/log.h>
+#include <pulsecore/hashmap.h>
+#include <pulsecore/semaphore.h>
+#include <pulsecore/mutex.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/refcnt.h>
+#include <pulsecore/llist.h>
+#include <pulsecore/flist.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/memtrap.h>
+
+#include "memblock.h"
+
+/* We can allocate 64*1024*1024 bytes at maximum. That's 64MB. Please
+ * note that the footprint is usually much smaller, since the data is
+ * stored in SHM and our OS does not commit the memory before we use
+ * it for the first time. */
+#define PA_MEMPOOL_SLOTS_MAX 1024
+#define PA_MEMPOOL_SLOT_SIZE (64*1024)
+
+#define PA_MEMEXPORT_SLOTS_MAX 128
+
+#define PA_MEMIMPORT_SLOTS_MAX 160
+#define PA_MEMIMPORT_SEGMENTS_MAX 16
+
+struct pa_memblock {
+ PA_REFCNT_DECLARE; /* the reference counter */
+ pa_mempool *pool;
+
+ pa_memblock_type_t type;
+
+ bool read_only:1;
+ bool is_silence:1;
+
+ pa_atomic_ptr_t data;
+ size_t length;
+
+ pa_atomic_t n_acquired;
+ pa_atomic_t please_signal;
+
+ union {
+ struct {
+ /* If type == PA_MEMBLOCK_USER this points to a function for freeing this memory block */
+ pa_free_cb_t free_cb;
+ /* If type == PA_MEMBLOCK_USER this is passed as free_cb argument */
+ void *free_cb_data;
+ } user;
+
+ struct {
+ uint32_t id;
+ pa_memimport_segment *segment;
+ } imported;
+ } per_type;
+};
+
+struct pa_memimport_segment {
+ pa_memimport *import;
+ pa_shm memory;
+ pa_memtrap *trap;
+ unsigned n_blocks;
+ bool writable;
+};
+
+/*
+ * If true, this segment's lifetime will not be limited by the
+ * number of active blocks (seg->n_blocks) using its shared memory.
+ * Rather, it will exist for the full lifetime of the memimport it
+ * is attached to.
+ *
+ * This is done to support memfd blocks transport.
+ *
+ * To transfer memfd-backed blocks without passing their fd every
+ * time, thus minimizing overhead and avoiding fd leaks, a command
+ * is sent with the memfd fd as ancil data very early on.
+ *
+ * This command has an ID that identifies the memfd region. Further
+ * block references are then exclusively done using this ID. On the
+ * receiving end, such logic is enabled by the memimport's segment
+ * hash and 'permanent' segments below.
+ */
+static bool segment_is_permanent(pa_memimport_segment *seg) {
+ pa_assert(seg);
+ return seg->memory.type == PA_MEM_TYPE_SHARED_MEMFD;
+}
+
+/* A collection of multiple segments */
+struct pa_memimport {
+ pa_mutex *mutex;
+
+ pa_mempool *pool;
+ pa_hashmap *segments;
+ pa_hashmap *blocks;
+
+ /* Called whenever an imported memory block is no longer
+ * needed. */
+ pa_memimport_release_cb_t release_cb;
+ void *userdata;
+
+ PA_LLIST_FIELDS(pa_memimport);
+};
+
+struct memexport_slot {
+ PA_LLIST_FIELDS(struct memexport_slot);
+ pa_memblock *block;
+};
+
+struct pa_memexport {
+ pa_mutex *mutex;
+ pa_mempool *pool;
+
+ struct memexport_slot slots[PA_MEMEXPORT_SLOTS_MAX];
+
+ PA_LLIST_HEAD(struct memexport_slot, free_slots);
+ PA_LLIST_HEAD(struct memexport_slot, used_slots);
+ unsigned n_init;
+ unsigned baseidx;
+
+ /* Called whenever a client from which we imported a memory block
+ which we in turn exported to another client dies and we need to
+ revoke the memory block accordingly */
+ pa_memexport_revoke_cb_t revoke_cb;
+ void *userdata;
+
+ PA_LLIST_FIELDS(pa_memexport);
+};
+
+struct pa_mempool {
+ /* Reference count the mempool
+ *
+ * Any block allocation from the pool itself, or even just imported from
+ * another process through SHM and attached to it (PA_MEMBLOCK_IMPORTED),
+ * shall increase the refcount.
+ *
+ * This is done for per-client mempools: global references to blocks in
+ * the pool, or just to attached ones, can still be lingering around when
+ * the client connection dies and all per-client objects are to be freed.
+ * That is, current PulseAudio design does not guarantee that the client
+ * mempool blocks are referenced only by client-specific objects.
+ *
+ * For further details, please check:
+ * https://lists.freedesktop.org/archives/pulseaudio-discuss/2016-February/025587.html
+ */
+ PA_REFCNT_DECLARE;
+
+ pa_semaphore *semaphore;
+ pa_mutex *mutex;
+
+ pa_shm memory;
+
+ bool global;
+
+ size_t block_size;
+ unsigned n_blocks;
+ bool is_remote_writable;
+
+ pa_atomic_t n_init;
+
+ PA_LLIST_HEAD(pa_memimport, imports);
+ PA_LLIST_HEAD(pa_memexport, exports);
+
+ /* A list of free slots that may be reused */
+ pa_flist *free_slots;
+
+ pa_mempool_stat stat;
+};
+
+static void segment_detach(pa_memimport_segment *seg);
+
+PA_STATIC_FLIST_DECLARE(unused_memblocks, 0, pa_xfree);
+
+/* No lock necessary */
+static void stat_add(pa_memblock*b) {
+ pa_assert(b);
+ pa_assert(b->pool);
+
+ pa_atomic_inc(&b->pool->stat.n_allocated);
+ pa_atomic_add(&b->pool->stat.allocated_size, (int) b->length);
+
+ pa_atomic_inc(&b->pool->stat.n_accumulated);
+ pa_atomic_add(&b->pool->stat.accumulated_size, (int) b->length);
+
+ if (b->type == PA_MEMBLOCK_IMPORTED) {
+ pa_atomic_inc(&b->pool->stat.n_imported);
+ pa_atomic_add(&b->pool->stat.imported_size, (int) b->length);
+ }
+
+ pa_atomic_inc(&b->pool->stat.n_allocated_by_type[b->type]);
+ pa_atomic_inc(&b->pool->stat.n_accumulated_by_type[b->type]);
+}
+
+/* No lock necessary */
+static void stat_remove(pa_memblock *b) {
+ pa_assert(b);
+ pa_assert(b->pool);
+
+ pa_assert(pa_atomic_load(&b->pool->stat.n_allocated) > 0);
+ pa_assert(pa_atomic_load(&b->pool->stat.allocated_size) >= (int) b->length);
+
+ pa_atomic_dec(&b->pool->stat.n_allocated);
+ pa_atomic_sub(&b->pool->stat.allocated_size, (int) b->length);
+
+ if (b->type == PA_MEMBLOCK_IMPORTED) {
+ pa_assert(pa_atomic_load(&b->pool->stat.n_imported) > 0);
+ pa_assert(pa_atomic_load(&b->pool->stat.imported_size) >= (int) b->length);
+
+ pa_atomic_dec(&b->pool->stat.n_imported);
+ pa_atomic_sub(&b->pool->stat.imported_size, (int) b->length);
+ }
+
+ pa_atomic_dec(&b->pool->stat.n_allocated_by_type[b->type]);
+}
+
+static pa_memblock *memblock_new_appended(pa_mempool *p, size_t length);
+
+/* No lock necessary */
+pa_memblock *pa_memblock_new(pa_mempool *p, size_t length) {
+ pa_memblock *b;
+
+ pa_assert(p);
+ pa_assert(length);
+
+ if (!(b = pa_memblock_new_pool(p, length)))
+ b = memblock_new_appended(p, length);
+
+ return b;
+}
+
+/* No lock necessary */
+static pa_memblock *memblock_new_appended(pa_mempool *p, size_t length) {
+ pa_memblock *b;
+
+ pa_assert(p);
+ pa_assert(length);
+
+ /* If -1 is passed as length we choose the size for the caller. */
+
+ if (length == (size_t) -1)
+ length = pa_mempool_block_size_max(p);
+
+ b = pa_xmalloc(PA_ALIGN(sizeof(pa_memblock)) + length);
+ PA_REFCNT_INIT(b);
+ b->pool = p;
+ pa_mempool_ref(b->pool);
+ b->type = PA_MEMBLOCK_APPENDED;
+ b->read_only = b->is_silence = false;
+ pa_atomic_ptr_store(&b->data, (uint8_t*) b + PA_ALIGN(sizeof(pa_memblock)));
+ b->length = length;
+ pa_atomic_store(&b->n_acquired, 0);
+ pa_atomic_store(&b->please_signal, 0);
+
+ stat_add(b);
+ return b;
+}
+
+/* No lock necessary */
+static struct mempool_slot* mempool_allocate_slot(pa_mempool *p) {
+ struct mempool_slot *slot;
+ pa_assert(p);
+
+ if (!(slot = pa_flist_pop(p->free_slots))) {
+ int idx;
+
+ /* The free list was empty, we have to allocate a new entry */
+
+ if ((unsigned) (idx = pa_atomic_inc(&p->n_init)) >= p->n_blocks)
+ pa_atomic_dec(&p->n_init);
+ else
+ slot = (struct mempool_slot*) ((uint8_t*) p->memory.ptr + (p->block_size * (size_t) idx));
+
+ if (!slot) {
+ if (pa_log_ratelimit(PA_LOG_DEBUG))
+ pa_log_debug("Pool full");
+ pa_atomic_inc(&p->stat.n_pool_full);
+ return NULL;
+ }
+ }
+
+/* #ifdef HAVE_VALGRIND_MEMCHECK_H */
+/* if (PA_UNLIKELY(pa_in_valgrind())) { */
+/* VALGRIND_MALLOCLIKE_BLOCK(slot, p->block_size, 0, 0); */
+/* } */
+/* #endif */
+
+ return slot;
+}
+
+/* No lock necessary, totally redundant anyway */
+static inline void* mempool_slot_data(struct mempool_slot *slot) {
+ return slot;
+}
+
+/* No lock necessary */
+static unsigned mempool_slot_idx(pa_mempool *p, void *ptr) {
+ pa_assert(p);
+
+ pa_assert((uint8_t*) ptr >= (uint8_t*) p->memory.ptr);
+ pa_assert((uint8_t*) ptr < (uint8_t*) p->memory.ptr + p->memory.size);
+
+ return (unsigned) ((size_t) ((uint8_t*) ptr - (uint8_t*) p->memory.ptr) / p->block_size);
+}
+
+/* No lock necessary */
+static struct mempool_slot* mempool_slot_by_ptr(pa_mempool *p, void *ptr) {
+ unsigned idx;
+
+ if ((idx = mempool_slot_idx(p, ptr)) == (unsigned) -1)
+ return NULL;
+
+ return (struct mempool_slot*) ((uint8_t*) p->memory.ptr + (idx * p->block_size));
+}
+
+/* No lock necessary */
+bool pa_mempool_is_remote_writable(pa_mempool *p) {
+ pa_assert(p);
+ return p->is_remote_writable;
+}
+
+/* No lock necessary */
+void pa_mempool_set_is_remote_writable(pa_mempool *p, bool writable) {
+ pa_assert(p);
+ pa_assert(!writable || pa_mempool_is_shared(p));
+ p->is_remote_writable = writable;
+}
+
+/* No lock necessary */
+pa_memblock *pa_memblock_new_pool(pa_mempool *p, size_t length) {
+ pa_memblock *b = NULL;
+ struct mempool_slot *slot;
+ static int mempool_disable = 0;
+
+ pa_assert(p);
+ pa_assert(length);
+
+ if (mempool_disable == 0)
+ mempool_disable = getenv("PULSE_MEMPOOL_DISABLE") ? 1 : -1;
+
+ if (mempool_disable > 0)
+ return NULL;
+
+ /* If -1 is passed as length we choose the size for the caller: we
+ * take the largest size that fits in one of our slots. */
+
+ if (length == (size_t) -1)
+ length = pa_mempool_block_size_max(p);
+
+ if (p->block_size >= PA_ALIGN(sizeof(pa_memblock)) + length) {
+
+ if (!(slot = mempool_allocate_slot(p)))
+ return NULL;
+
+ b = mempool_slot_data(slot);
+ b->type = PA_MEMBLOCK_POOL;
+ pa_atomic_ptr_store(&b->data, (uint8_t*) b + PA_ALIGN(sizeof(pa_memblock)));
+
+ } else if (p->block_size >= length) {
+
+ if (!(slot = mempool_allocate_slot(p)))
+ return NULL;
+
+ if (!(b = pa_flist_pop(PA_STATIC_FLIST_GET(unused_memblocks))))
+ b = pa_xnew(pa_memblock, 1);
+
+ b->type = PA_MEMBLOCK_POOL_EXTERNAL;
+ pa_atomic_ptr_store(&b->data, mempool_slot_data(slot));
+
+ } else {
+ pa_log_debug("Memory block too large for pool: %lu > %lu", (unsigned long) length, (unsigned long) p->block_size);
+ pa_atomic_inc(&p->stat.n_too_large_for_pool);
+ return NULL;
+ }
+
+ PA_REFCNT_INIT(b);
+ b->pool = p;
+ pa_mempool_ref(b->pool);
+ b->read_only = b->is_silence = false;
+ b->length = length;
+ pa_atomic_store(&b->n_acquired, 0);
+ pa_atomic_store(&b->please_signal, 0);
+
+ stat_add(b);
+ return b;
+}
+
+/* No lock necessary */
+pa_memblock *pa_memblock_new_fixed(pa_mempool *p, void *d, size_t length, bool read_only) {
+ pa_memblock *b;
+
+ pa_assert(p);
+ pa_assert(d);
+ pa_assert(length != (size_t) -1);
+ pa_assert(length);
+
+ if (!(b = pa_flist_pop(PA_STATIC_FLIST_GET(unused_memblocks))))
+ b = pa_xnew(pa_memblock, 1);
+
+ PA_REFCNT_INIT(b);
+ b->pool = p;
+ pa_mempool_ref(b->pool);
+ b->type = PA_MEMBLOCK_FIXED;
+ b->read_only = read_only;
+ b->is_silence = false;
+ pa_atomic_ptr_store(&b->data, d);
+ b->length = length;
+ pa_atomic_store(&b->n_acquired, 0);
+ pa_atomic_store(&b->please_signal, 0);
+
+ stat_add(b);
+ return b;
+}
+
+/* No lock necessary */
+pa_memblock *pa_memblock_new_user(
+ pa_mempool *p,
+ void *d,
+ size_t length,
+ pa_free_cb_t free_cb,
+ void *free_cb_data,
+ bool read_only) {
+ pa_memblock *b;
+
+ pa_assert(p);
+ pa_assert(d);
+ pa_assert(length);
+ pa_assert(length != (size_t) -1);
+ pa_assert(free_cb);
+
+ if (!(b = pa_flist_pop(PA_STATIC_FLIST_GET(unused_memblocks))))
+ b = pa_xnew(pa_memblock, 1);
+
+ PA_REFCNT_INIT(b);
+ b->pool = p;
+ pa_mempool_ref(b->pool);
+ b->type = PA_MEMBLOCK_USER;
+ b->read_only = read_only;
+ b->is_silence = false;
+ pa_atomic_ptr_store(&b->data, d);
+ b->length = length;
+ pa_atomic_store(&b->n_acquired, 0);
+ pa_atomic_store(&b->please_signal, 0);
+
+ b->per_type.user.free_cb = free_cb;
+ b->per_type.user.free_cb_data = free_cb_data;
+
+ stat_add(b);
+ return b;
+}
+
+/* No lock necessary */
+bool pa_memblock_is_ours(pa_memblock *b) {
+ pa_assert(b);
+ pa_assert(PA_REFCNT_VALUE(b) > 0);
+
+ return b->type != PA_MEMBLOCK_IMPORTED;
+}
+
+/* No lock necessary */
+bool pa_memblock_is_read_only(pa_memblock *b) {
+ pa_assert(b);
+ pa_assert(PA_REFCNT_VALUE(b) > 0);
+
+ return b->read_only || PA_REFCNT_VALUE(b) > 1;
+}
+
+/* No lock necessary */
+bool pa_memblock_is_silence(pa_memblock *b) {
+ pa_assert(b);
+ pa_assert(PA_REFCNT_VALUE(b) > 0);
+
+ return b->is_silence;
+}
+
+/* No lock necessary */
+void pa_memblock_set_is_silence(pa_memblock *b, bool v) {
+ pa_assert(b);
+ pa_assert(PA_REFCNT_VALUE(b) > 0);
+
+ b->is_silence = v;
+}
+
+/* No lock necessary */
+bool pa_memblock_ref_is_one(pa_memblock *b) {
+ int r;
+ pa_assert(b);
+
+ pa_assert_se((r = PA_REFCNT_VALUE(b)) > 0);
+
+ return r == 1;
+}
+
+/* No lock necessary */
+void* pa_memblock_acquire(pa_memblock *b) {
+ pa_assert(b);
+ pa_assert(PA_REFCNT_VALUE(b) > 0);
+
+ pa_atomic_inc(&b->n_acquired);
+
+ return pa_atomic_ptr_load(&b->data);
+}
+
+/* No lock necessary */
+void *pa_memblock_acquire_chunk(const pa_memchunk *c) {
+ pa_assert(c);
+
+ return (uint8_t *) pa_memblock_acquire(c->memblock) + c->index;
+}
+
+/* No lock necessary, in corner cases locks by its own */
+void pa_memblock_release(pa_memblock *b) {
+ int r;
+ pa_assert(b);
+ pa_assert(PA_REFCNT_VALUE(b) > 0);
+
+ r = pa_atomic_dec(&b->n_acquired);
+ pa_assert(r >= 1);
+
+ /* Signal a waiting thread that this memblock is no longer used */
+ if (r == 1 && pa_atomic_load(&b->please_signal))
+ pa_semaphore_post(b->pool->semaphore);
+}
+
+size_t pa_memblock_get_length(pa_memblock *b) {
+ pa_assert(b);
+ pa_assert(PA_REFCNT_VALUE(b) > 0);
+
+ return b->length;
+}
+
+/* Note! Always unref the returned pool after use */
+pa_mempool* pa_memblock_get_pool(pa_memblock *b) {
+ pa_assert(b);
+ pa_assert(PA_REFCNT_VALUE(b) > 0);
+ pa_assert(b->pool);
+
+ pa_mempool_ref(b->pool);
+ return b->pool;
+}
+
+/* No lock necessary */
+pa_memblock* pa_memblock_ref(pa_memblock*b) {
+ pa_assert(b);
+ pa_assert(PA_REFCNT_VALUE(b) > 0);
+
+ PA_REFCNT_INC(b);
+ return b;
+}
+
+static void memblock_free(pa_memblock *b) {
+ pa_mempool *pool;
+
+ pa_assert(b);
+ pa_assert(b->pool);
+ pa_assert(pa_atomic_load(&b->n_acquired) == 0);
+
+ pool = b->pool;
+ stat_remove(b);
+
+ switch (b->type) {
+ case PA_MEMBLOCK_USER :
+ pa_assert(b->per_type.user.free_cb);
+ b->per_type.user.free_cb(b->per_type.user.free_cb_data);
+
+ /* Fall through */
+
+ case PA_MEMBLOCK_FIXED:
+ if (pa_flist_push(PA_STATIC_FLIST_GET(unused_memblocks), b) < 0)
+ pa_xfree(b);
+
+ break;
+
+ case PA_MEMBLOCK_APPENDED:
+
+ /* We could attach it to unused_memblocks, but that would
+ * probably waste some considerable amount of memory */
+ pa_xfree(b);
+ break;
+
+ case PA_MEMBLOCK_IMPORTED: {
+ pa_memimport_segment *segment;
+ pa_memimport *import;
+
+ /* FIXME! This should be implemented lock-free */
+
+ pa_assert_se(segment = b->per_type.imported.segment);
+ pa_assert_se(import = segment->import);
+
+ pa_mutex_lock(import->mutex);
+
+ pa_assert_se(pa_hashmap_remove(import->blocks, PA_UINT32_TO_PTR(b->per_type.imported.id)));
+
+ pa_assert(segment->n_blocks >= 1);
+ if (-- segment->n_blocks <= 0)
+ segment_detach(segment);
+
+ pa_mutex_unlock(import->mutex);
+
+ import->release_cb(import, b->per_type.imported.id, import->userdata);
+
+ if (pa_flist_push(PA_STATIC_FLIST_GET(unused_memblocks), b) < 0)
+ pa_xfree(b);
+
+ break;
+ }
+
+ case PA_MEMBLOCK_POOL_EXTERNAL:
+ case PA_MEMBLOCK_POOL: {
+ struct mempool_slot *slot;
+ bool call_free;
+
+ pa_assert_se(slot = mempool_slot_by_ptr(b->pool, pa_atomic_ptr_load(&b->data)));
+
+ call_free = b->type == PA_MEMBLOCK_POOL_EXTERNAL;
+
+/* #ifdef HAVE_VALGRIND_MEMCHECK_H */
+/* if (PA_UNLIKELY(pa_in_valgrind())) { */
+/* VALGRIND_FREELIKE_BLOCK(slot, b->pool->block_size); */
+/* } */
+/* #endif */
+
+ /* The free list dimensions should easily allow all slots
+ * to fit in, hence try harder if pushing this slot into
+ * the free list fails */
+ while (pa_flist_push(b->pool->free_slots, slot) < 0)
+ ;
+
+ if (call_free)
+ if (pa_flist_push(PA_STATIC_FLIST_GET(unused_memblocks), b) < 0)
+ pa_xfree(b);
+
+ break;
+ }
+
+ case PA_MEMBLOCK_TYPE_MAX:
+ default:
+ pa_assert_not_reached();
+ }
+
+ pa_mempool_unref(pool);
+}
+
+/* No lock necessary */
+void pa_memblock_unref(pa_memblock*b) {
+ pa_assert(b);
+ pa_assert(PA_REFCNT_VALUE(b) > 0);
+
+ if (PA_REFCNT_DEC(b) > 0)
+ return;
+
+ memblock_free(b);
+}
+
+/* Self locked */
+static void memblock_wait(pa_memblock *b) {
+ pa_assert(b);
+
+ if (pa_atomic_load(&b->n_acquired) > 0) {
+ /* We need to wait until all threads gave up access to the
+ * memory block before we can go on. Unfortunately this means
+ * that we have to lock and wait here. Sniff! */
+
+ pa_atomic_inc(&b->please_signal);
+
+ while (pa_atomic_load(&b->n_acquired) > 0)
+ pa_semaphore_wait(b->pool->semaphore);
+
+ pa_atomic_dec(&b->please_signal);
+ }
+}
+
+/* No lock necessary. This function is not multiple caller safe! */
+static void memblock_make_local(pa_memblock *b) {
+ pa_assert(b);
+
+ pa_atomic_dec(&b->pool->stat.n_allocated_by_type[b->type]);
+
+ if (b->length <= b->pool->block_size) {
+ struct mempool_slot *slot;
+
+ if ((slot = mempool_allocate_slot(b->pool))) {
+ void *new_data;
+ /* We can move it into a local pool, perfect! */
+
+ new_data = mempool_slot_data(slot);
+ memcpy(new_data, pa_atomic_ptr_load(&b->data), b->length);
+ pa_atomic_ptr_store(&b->data, new_data);
+
+ b->type = PA_MEMBLOCK_POOL_EXTERNAL;
+ b->read_only = false;
+
+ goto finish;
+ }
+ }
+
+ /* Humm, not enough space in the pool, so lets allocate the memory with malloc() */
+ b->per_type.user.free_cb = pa_xfree;
+ pa_atomic_ptr_store(&b->data, pa_xmemdup(pa_atomic_ptr_load(&b->data), b->length));
+ b->per_type.user.free_cb_data = pa_atomic_ptr_load(&b->data);
+
+ b->type = PA_MEMBLOCK_USER;
+ b->read_only = false;
+
+finish:
+ pa_atomic_inc(&b->pool->stat.n_allocated_by_type[b->type]);
+ pa_atomic_inc(&b->pool->stat.n_accumulated_by_type[b->type]);
+ memblock_wait(b);
+}
+
+/* No lock necessary. This function is not multiple caller safe */
+void pa_memblock_unref_fixed(pa_memblock *b) {
+ pa_assert(b);
+ pa_assert(PA_REFCNT_VALUE(b) > 0);
+ pa_assert(b->type == PA_MEMBLOCK_FIXED);
+
+ if (PA_REFCNT_VALUE(b) > 1)
+ memblock_make_local(b);
+
+ pa_memblock_unref(b);
+}
+
+/* No lock necessary. */
+pa_memblock *pa_memblock_will_need(pa_memblock *b) {
+ void *p;
+
+ pa_assert(b);
+ pa_assert(PA_REFCNT_VALUE(b) > 0);
+
+ p = pa_memblock_acquire(b);
+ pa_will_need(p, b->length);
+ pa_memblock_release(b);
+
+ return b;
+}
+
+/* Self-locked. This function is not multiple-caller safe */
+static void memblock_replace_import(pa_memblock *b) {
+ pa_memimport_segment *segment;
+ pa_memimport *import;
+
+ pa_assert(b);
+ pa_assert(b->type == PA_MEMBLOCK_IMPORTED);
+
+ pa_assert(pa_atomic_load(&b->pool->stat.n_imported) > 0);
+ pa_assert(pa_atomic_load(&b->pool->stat.imported_size) >= (int) b->length);
+ pa_atomic_dec(&b->pool->stat.n_imported);
+ pa_atomic_sub(&b->pool->stat.imported_size, (int) b->length);
+
+ pa_assert_se(segment = b->per_type.imported.segment);
+ pa_assert_se(import = segment->import);
+
+ pa_mutex_lock(import->mutex);
+
+ pa_assert_se(pa_hashmap_remove(import->blocks, PA_UINT32_TO_PTR(b->per_type.imported.id)));
+
+ memblock_make_local(b);
+
+ pa_assert(segment->n_blocks >= 1);
+ if (-- segment->n_blocks <= 0)
+ segment_detach(segment);
+
+ pa_mutex_unlock(import->mutex);
+}
+
+/*@per_client: This is a security measure. By default this should
+ * be set to true where the created mempool is never shared with more
+ * than one client in the system. Set this to false if a global
+ * mempool, shared with all existing and future clients, is required.
+ *
+ * NOTE-1: Do not create any further global mempools! They allow data
+ * leaks between clients and thus conflict with the xdg-app containers
+ * model. They also complicate the handling of memfd-based pools.
+ *
+ * NOTE-2: Almost all mempools are now created on a per client basis.
+ * The only exception is the pa_core's mempool which is still shared
+ * between all clients of the system.
+ *
+ * Beside security issues, special marking for global mempools is
+ * required for memfd communication. To avoid fd leaks, memfd pools
+ * are registered with the connection pstream to create an ID<->memfd
+ * mapping on both PA endpoints. Such memory regions are then always
+ * referenced by their IDs and never by their fds and thus their fds
+ * can be quickly closed later.
+ *
+ * Unfortunately this scheme cannot work with global pools since the
+ * ID registration mechanism needs to happen for each newly connected
+ * client, and thus the need for a more special handling. That is,
+ * for the pool's fd to be always open :-(
+ *
+ * TODO-1: Transform the global core mempool to a per-client one
+ * TODO-2: Remove global mempools support */
+pa_mempool *pa_mempool_new(pa_mem_type_t type, size_t size, bool per_client) {
+ pa_mempool *p;
+ char t1[PA_BYTES_SNPRINT_MAX], t2[PA_BYTES_SNPRINT_MAX];
+ const size_t page_size = pa_page_size();
+
+ p = pa_xnew0(pa_mempool, 1);
+ PA_REFCNT_INIT(p);
+
+ p->block_size = PA_PAGE_ALIGN(PA_MEMPOOL_SLOT_SIZE);
+ if (p->block_size < page_size)
+ p->block_size = page_size;
+
+ if (size <= 0)
+ p->n_blocks = PA_MEMPOOL_SLOTS_MAX;
+ else {
+ p->n_blocks = (unsigned) (size / p->block_size);
+
+ if (p->n_blocks < 2)
+ p->n_blocks = 2;
+ }
+
+ if (pa_shm_create_rw(&p->memory, type, p->n_blocks * p->block_size, 0700) < 0) {
+ pa_xfree(p);
+ return NULL;
+ }
+
+ pa_log_debug("Using %s memory pool with %u slots of size %s each, total size is %s, maximum usable slot size is %lu",
+ pa_mem_type_to_string(type),
+ p->n_blocks,
+ pa_bytes_snprint(t1, sizeof(t1), (unsigned) p->block_size),
+ pa_bytes_snprint(t2, sizeof(t2), (unsigned) (p->n_blocks * p->block_size)),
+ (unsigned long) pa_mempool_block_size_max(p));
+
+ p->global = !per_client;
+
+ pa_atomic_store(&p->n_init, 0);
+
+ PA_LLIST_HEAD_INIT(pa_memimport, p->imports);
+ PA_LLIST_HEAD_INIT(pa_memexport, p->exports);
+
+ p->mutex = pa_mutex_new(true, true);
+ p->semaphore = pa_semaphore_new(0);
+
+ p->free_slots = pa_flist_new(p->n_blocks);
+
+ return p;
+}
+
+static void mempool_free(pa_mempool *p) {
+ pa_assert(p);
+
+ pa_mutex_lock(p->mutex);
+
+ while (p->imports)
+ pa_memimport_free(p->imports);
+
+ while (p->exports)
+ pa_memexport_free(p->exports);
+
+ pa_mutex_unlock(p->mutex);
+
+ pa_flist_free(p->free_slots, NULL);
+
+ if (pa_atomic_load(&p->stat.n_allocated) > 0) {
+
+ /* Ouch, somebody is retaining a memory block reference! */
+
+#ifdef DEBUG_REF
+ unsigned i;
+ pa_flist *list;
+
+ /* Let's try to find at least one of those leaked memory blocks */
+
+ list = pa_flist_new(p->n_blocks);
+
+ for (i = 0; i < (unsigned) pa_atomic_load(&p->n_init); i++) {
+ struct mempool_slot *slot;
+ pa_memblock *b, *k;
+
+ slot = (struct mempool_slot*) ((uint8_t*) p->memory.ptr + (p->block_size * (size_t) i));
+ b = mempool_slot_data(slot);
+
+ while ((k = pa_flist_pop(p->free_slots))) {
+ while (pa_flist_push(list, k) < 0)
+ ;
+
+ if (b == k)
+ break;
+ }
+
+ if (!k)
+ pa_log("REF: Leaked memory block %p", b);
+
+ while ((k = pa_flist_pop(list)))
+ while (pa_flist_push(p->free_slots, k) < 0)
+ ;
+ }
+
+ pa_flist_free(list, NULL);
+
+#endif
+
+ pa_log_error("Memory pool destroyed but not all memory blocks freed! %u remain.", pa_atomic_load(&p->stat.n_allocated));
+
+/* PA_DEBUG_TRAP; */
+ }
+
+ pa_shm_free(&p->memory);
+
+ pa_mutex_free(p->mutex);
+ pa_semaphore_free(p->semaphore);
+
+ pa_xfree(p);
+}
+
+/* No lock necessary */
+const pa_mempool_stat* pa_mempool_get_stat(pa_mempool *p) {
+ pa_assert(p);
+
+ return &p->stat;
+}
+
+/* No lock necessary */
+size_t pa_mempool_block_size_max(pa_mempool *p) {
+ pa_assert(p);
+
+ return p->block_size - PA_ALIGN(sizeof(pa_memblock));
+}
+
+/* No lock necessary */
+void pa_mempool_vacuum(pa_mempool *p) {
+ struct mempool_slot *slot;
+ pa_flist *list;
+
+ pa_assert(p);
+
+ list = pa_flist_new(p->n_blocks);
+
+ while ((slot = pa_flist_pop(p->free_slots)))
+ while (pa_flist_push(list, slot) < 0)
+ ;
+
+ while ((slot = pa_flist_pop(list))) {
+ pa_shm_punch(&p->memory, (size_t) ((uint8_t*) slot - (uint8_t*) p->memory.ptr), p->block_size);
+
+ while (pa_flist_push(p->free_slots, slot))
+ ;
+ }
+
+ pa_flist_free(list, NULL);
+}
+
+/* No lock necessary */
+bool pa_mempool_is_shared(pa_mempool *p) {
+ pa_assert(p);
+
+ return pa_mem_type_is_shared(p->memory.type);
+}
+
+/* No lock necessary */
+bool pa_mempool_is_memfd_backed(const pa_mempool *p) {
+ pa_assert(p);
+
+ return (p->memory.type == PA_MEM_TYPE_SHARED_MEMFD);
+}
+
+/* No lock necessary */
+int pa_mempool_get_shm_id(pa_mempool *p, uint32_t *id) {
+ pa_assert(p);
+
+ if (!pa_mempool_is_shared(p))
+ return -1;
+
+ *id = p->memory.id;
+
+ return 0;
+}
+
+pa_mempool* pa_mempool_ref(pa_mempool *p) {
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) > 0);
+
+ PA_REFCNT_INC(p);
+ return p;
+}
+
+void pa_mempool_unref(pa_mempool *p) {
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) > 0);
+
+ if (PA_REFCNT_DEC(p) <= 0)
+ mempool_free(p);
+}
+
+/* No lock necessary
+ * Check pa_mempool_new() for per-client vs. global mempools */
+bool pa_mempool_is_global(pa_mempool *p) {
+ pa_assert(p);
+
+ return p->global;
+}
+
+/* No lock necessary
+ * Check pa_mempool_new() for per-client vs. global mempools */
+bool pa_mempool_is_per_client(pa_mempool *p) {
+ return !pa_mempool_is_global(p);
+}
+
+/* Self-locked
+ *
+ * This is only for per-client mempools!
+ *
+ * After this method's return, the caller owns the file descriptor
+ * and is responsible for closing it in the appropriate time. This
+ * should only be called once during during a mempool's lifetime.
+ *
+ * Check pa_shm->fd and pa_mempool_new() for further context. */
+int pa_mempool_take_memfd_fd(pa_mempool *p) {
+ int memfd_fd;
+
+ pa_assert(p);
+ pa_assert(pa_mempool_is_shared(p));
+ pa_assert(pa_mempool_is_memfd_backed(p));
+ pa_assert(pa_mempool_is_per_client(p));
+
+ pa_mutex_lock(p->mutex);
+
+ memfd_fd = p->memory.fd;
+ p->memory.fd = -1;
+
+ pa_mutex_unlock(p->mutex);
+
+ pa_assert(memfd_fd != -1);
+ return memfd_fd;
+}
+
+/* No lock necessary
+ *
+ * This is only for global mempools!
+ *
+ * Global mempools have their memfd descriptor always open. DO NOT
+ * close the returned descriptor by your own.
+ *
+ * Check pa_mempool_new() for further context. */
+int pa_mempool_get_memfd_fd(pa_mempool *p) {
+ int memfd_fd;
+
+ pa_assert(p);
+ pa_assert(pa_mempool_is_shared(p));
+ pa_assert(pa_mempool_is_memfd_backed(p));
+ pa_assert(pa_mempool_is_global(p));
+
+ memfd_fd = p->memory.fd;
+ pa_assert(memfd_fd != -1);
+
+ return memfd_fd;
+}
+
+/* For receiving blocks from other nodes */
+pa_memimport* pa_memimport_new(pa_mempool *p, pa_memimport_release_cb_t cb, void *userdata) {
+ pa_memimport *i;
+
+ pa_assert(p);
+ pa_assert(cb);
+
+ i = pa_xnew(pa_memimport, 1);
+ i->mutex = pa_mutex_new(true, true);
+ i->pool = p;
+ pa_mempool_ref(i->pool);
+ i->segments = pa_hashmap_new(NULL, NULL);
+ i->blocks = pa_hashmap_new(NULL, NULL);
+ i->release_cb = cb;
+ i->userdata = userdata;
+
+ pa_mutex_lock(p->mutex);
+ PA_LLIST_PREPEND(pa_memimport, p->imports, i);
+ pa_mutex_unlock(p->mutex);
+
+ return i;
+}
+
+static void memexport_revoke_blocks(pa_memexport *e, pa_memimport *i);
+
+/* Should be called locked
+ * Caller owns passed @memfd_fd and must close it down when appropriate. */
+static pa_memimport_segment* segment_attach(pa_memimport *i, pa_mem_type_t type, uint32_t shm_id,
+ int memfd_fd, bool writable) {
+ pa_memimport_segment* seg;
+ pa_assert(pa_mem_type_is_shared(type));
+
+ if (pa_hashmap_size(i->segments) >= PA_MEMIMPORT_SEGMENTS_MAX)
+ return NULL;
+
+ seg = pa_xnew0(pa_memimport_segment, 1);
+
+ if (pa_shm_attach(&seg->memory, type, shm_id, memfd_fd, writable) < 0) {
+ pa_xfree(seg);
+ return NULL;
+ }
+
+ seg->writable = writable;
+ seg->import = i;
+ seg->trap = pa_memtrap_add(seg->memory.ptr, seg->memory.size);
+
+ pa_hashmap_put(i->segments, PA_UINT32_TO_PTR(seg->memory.id), seg);
+ return seg;
+}
+
+/* Should be called locked */
+static void segment_detach(pa_memimport_segment *seg) {
+ pa_assert(seg);
+ pa_assert(seg->n_blocks == (segment_is_permanent(seg) ? 1u : 0u));
+
+ pa_hashmap_remove(seg->import->segments, PA_UINT32_TO_PTR(seg->memory.id));
+ pa_shm_free(&seg->memory);
+
+ if (seg->trap)
+ pa_memtrap_remove(seg->trap);
+
+ pa_xfree(seg);
+}
+
+/* Self-locked. Not multiple-caller safe */
+void pa_memimport_free(pa_memimport *i) {
+ pa_memexport *e;
+ pa_memblock *b;
+ pa_memimport_segment *seg;
+ void *state = NULL;
+
+ pa_assert(i);
+
+ pa_mutex_lock(i->mutex);
+
+ while ((b = pa_hashmap_first(i->blocks)))
+ memblock_replace_import(b);
+
+ /* Permanent segments exist for the lifetime of the memimport. Now
+ * that we're freeing the memimport itself, clear them all up.
+ *
+ * Careful! segment_detach() internally removes itself from the
+ * memimport's hash; the same hash we're now using for iteration. */
+ PA_HASHMAP_FOREACH(seg, i->segments, state) {
+ if (segment_is_permanent(seg))
+ segment_detach(seg);
+ }
+ pa_assert(pa_hashmap_size(i->segments) == 0);
+
+ pa_mutex_unlock(i->mutex);
+
+ pa_mutex_lock(i->pool->mutex);
+
+ /* If we've exported this block further we need to revoke that export */
+ for (e = i->pool->exports; e; e = e->next)
+ memexport_revoke_blocks(e, i);
+
+ PA_LLIST_REMOVE(pa_memimport, i->pool->imports, i);
+
+ pa_mutex_unlock(i->pool->mutex);
+
+ pa_mempool_unref(i->pool);
+ pa_hashmap_free(i->blocks);
+ pa_hashmap_free(i->segments);
+
+ pa_mutex_free(i->mutex);
+
+ pa_xfree(i);
+}
+
+/* Create a new memimport's memfd segment entry, with passed SHM ID
+ * as key and the newly-created segment (with its mmap()-ed memfd
+ * memory region) as its value.
+ *
+ * Note! check comments at 'pa_shm->fd', 'segment_is_permanent()',
+ * and 'pa_pstream_register_memfd_mempool()' for further details.
+ *
+ * Caller owns passed @memfd_fd and must close it down when appropriate. */
+int pa_memimport_attach_memfd(pa_memimport *i, uint32_t shm_id, int memfd_fd, bool writable) {
+ pa_memimport_segment *seg;
+ int ret = -1;
+
+ pa_assert(i);
+ pa_assert(memfd_fd != -1);
+
+ pa_mutex_lock(i->mutex);
+
+ if (!(seg = segment_attach(i, PA_MEM_TYPE_SHARED_MEMFD, shm_id, memfd_fd, writable)))
+ goto finish;
+
+ /* n_blocks acts as a segment reference count. To avoid the segment
+ * being deleted when receiving silent memchunks, etc., mark our
+ * permanent presence by incrementing that refcount. */
+ seg->n_blocks++;
+
+ pa_assert(segment_is_permanent(seg));
+ ret = 0;
+
+finish:
+ pa_mutex_unlock(i->mutex);
+ return ret;
+}
+
+/* Self-locked */
+pa_memblock* pa_memimport_get(pa_memimport *i, pa_mem_type_t type, uint32_t block_id, uint32_t shm_id,
+ size_t offset, size_t size, bool writable) {
+ pa_memblock *b = NULL;
+ pa_memimport_segment *seg;
+
+ pa_assert(i);
+ pa_assert(pa_mem_type_is_shared(type));
+
+ pa_mutex_lock(i->mutex);
+
+ if ((b = pa_hashmap_get(i->blocks, PA_UINT32_TO_PTR(block_id)))) {
+ pa_memblock_ref(b);
+ goto finish;
+ }
+
+ if (pa_hashmap_size(i->blocks) >= PA_MEMIMPORT_SLOTS_MAX)
+ goto finish;
+
+ if (!(seg = pa_hashmap_get(i->segments, PA_UINT32_TO_PTR(shm_id)))) {
+ if (type == PA_MEM_TYPE_SHARED_MEMFD) {
+ pa_log("Bailing out! No cached memimport segment for memfd ID %u", shm_id);
+ pa_log("Did the other PA endpoint forget registering its memfd pool?");
+ goto finish;
+ }
+
+ pa_assert(type == PA_MEM_TYPE_SHARED_POSIX);
+ if (!(seg = segment_attach(i, type, shm_id, -1, writable)))
+ goto finish;
+ }
+
+ if (writable && !seg->writable) {
+ pa_log("Cannot import cached segment in write mode - previously mapped as read-only");
+ goto finish;
+ }
+
+ if (offset+size > seg->memory.size)
+ goto finish;
+
+ if (!(b = pa_flist_pop(PA_STATIC_FLIST_GET(unused_memblocks))))
+ b = pa_xnew(pa_memblock, 1);
+
+ PA_REFCNT_INIT(b);
+ b->pool = i->pool;
+ pa_mempool_ref(b->pool);
+ b->type = PA_MEMBLOCK_IMPORTED;
+ b->read_only = !writable;
+ b->is_silence = false;
+ pa_atomic_ptr_store(&b->data, (uint8_t*) seg->memory.ptr + offset);
+ b->length = size;
+ pa_atomic_store(&b->n_acquired, 0);
+ pa_atomic_store(&b->please_signal, 0);
+ b->per_type.imported.id = block_id;
+ b->per_type.imported.segment = seg;
+
+ pa_hashmap_put(i->blocks, PA_UINT32_TO_PTR(block_id), b);
+
+ seg->n_blocks++;
+
+ stat_add(b);
+
+finish:
+ pa_mutex_unlock(i->mutex);
+
+ return b;
+}
+
+int pa_memimport_process_revoke(pa_memimport *i, uint32_t id) {
+ pa_memblock *b;
+ int ret = 0;
+ pa_assert(i);
+
+ pa_mutex_lock(i->mutex);
+
+ if (!(b = pa_hashmap_get(i->blocks, PA_UINT32_TO_PTR(id)))) {
+ ret = -1;
+ goto finish;
+ }
+
+ memblock_replace_import(b);
+
+finish:
+ pa_mutex_unlock(i->mutex);
+
+ return ret;
+}
+
+/* For sending blocks to other nodes */
+pa_memexport* pa_memexport_new(pa_mempool *p, pa_memexport_revoke_cb_t cb, void *userdata) {
+ pa_memexport *e;
+
+ static pa_atomic_t export_baseidx = PA_ATOMIC_INIT(0);
+
+ pa_assert(p);
+ pa_assert(cb);
+
+ if (!pa_mempool_is_shared(p))
+ return NULL;
+
+ e = pa_xnew(pa_memexport, 1);
+ e->mutex = pa_mutex_new(true, true);
+ e->pool = p;
+ pa_mempool_ref(e->pool);
+ PA_LLIST_HEAD_INIT(struct memexport_slot, e->free_slots);
+ PA_LLIST_HEAD_INIT(struct memexport_slot, e->used_slots);
+ e->n_init = 0;
+ e->revoke_cb = cb;
+ e->userdata = userdata;
+
+ pa_mutex_lock(p->mutex);
+
+ PA_LLIST_PREPEND(pa_memexport, p->exports, e);
+ e->baseidx = (uint32_t) pa_atomic_add(&export_baseidx, PA_MEMEXPORT_SLOTS_MAX);
+
+ pa_mutex_unlock(p->mutex);
+ return e;
+}
+
+void pa_memexport_free(pa_memexport *e) {
+ pa_assert(e);
+
+ pa_mutex_lock(e->mutex);
+ while (e->used_slots)
+ pa_memexport_process_release(e, (uint32_t) (e->used_slots - e->slots + e->baseidx));
+ pa_mutex_unlock(e->mutex);
+
+ pa_mutex_lock(e->pool->mutex);
+ PA_LLIST_REMOVE(pa_memexport, e->pool->exports, e);
+ pa_mutex_unlock(e->pool->mutex);
+
+ pa_mempool_unref(e->pool);
+ pa_mutex_free(e->mutex);
+ pa_xfree(e);
+}
+
+/* Self-locked */
+int pa_memexport_process_release(pa_memexport *e, uint32_t id) {
+ pa_memblock *b;
+
+ pa_assert(e);
+
+ pa_mutex_lock(e->mutex);
+
+ if (id < e->baseidx)
+ goto fail;
+ id -= e->baseidx;
+
+ if (id >= e->n_init)
+ goto fail;
+
+ if (!e->slots[id].block)
+ goto fail;
+
+ b = e->slots[id].block;
+ e->slots[id].block = NULL;
+
+ PA_LLIST_REMOVE(struct memexport_slot, e->used_slots, &e->slots[id]);
+ PA_LLIST_PREPEND(struct memexport_slot, e->free_slots, &e->slots[id]);
+
+ pa_mutex_unlock(e->mutex);
+
+/* pa_log("Processing release for %u", id); */
+
+ pa_assert(pa_atomic_load(&e->pool->stat.n_exported) > 0);
+ pa_assert(pa_atomic_load(&e->pool->stat.exported_size) >= (int) b->length);
+
+ pa_atomic_dec(&e->pool->stat.n_exported);
+ pa_atomic_sub(&e->pool->stat.exported_size, (int) b->length);
+
+ pa_memblock_unref(b);
+
+ return 0;
+
+fail:
+ pa_mutex_unlock(e->mutex);
+
+ return -1;
+}
+
+/* Self-locked */
+static void memexport_revoke_blocks(pa_memexport *e, pa_memimport *i) {
+ struct memexport_slot *slot, *next;
+ pa_assert(e);
+ pa_assert(i);
+
+ pa_mutex_lock(e->mutex);
+
+ for (slot = e->used_slots; slot; slot = next) {
+ uint32_t idx;
+ next = slot->next;
+
+ if (slot->block->type != PA_MEMBLOCK_IMPORTED ||
+ slot->block->per_type.imported.segment->import != i)
+ continue;
+
+ idx = (uint32_t) (slot - e->slots + e->baseidx);
+ e->revoke_cb(e, idx, e->userdata);
+ pa_memexport_process_release(e, idx);
+ }
+
+ pa_mutex_unlock(e->mutex);
+}
+
+/* No lock necessary */
+static pa_memblock *memblock_shared_copy(pa_mempool *p, pa_memblock *b) {
+ pa_memblock *n;
+
+ pa_assert(p);
+ pa_assert(b);
+
+ if (b->type == PA_MEMBLOCK_IMPORTED ||
+ b->type == PA_MEMBLOCK_POOL ||
+ b->type == PA_MEMBLOCK_POOL_EXTERNAL) {
+ pa_assert(b->pool == p);
+ return pa_memblock_ref(b);
+ }
+
+ if (!(n = pa_memblock_new_pool(p, b->length)))
+ return NULL;
+
+ memcpy(pa_atomic_ptr_load(&n->data), pa_atomic_ptr_load(&b->data), b->length);
+ return n;
+}
+
+/* Self-locked */
+int pa_memexport_put(pa_memexport *e, pa_memblock *b, pa_mem_type_t *type, uint32_t *block_id,
+ uint32_t *shm_id, size_t *offset, size_t * size) {
+ pa_shm *memory;
+ struct memexport_slot *slot;
+ void *data;
+
+ pa_assert(e);
+ pa_assert(b);
+ pa_assert(type);
+ pa_assert(block_id);
+ pa_assert(shm_id);
+ pa_assert(offset);
+ pa_assert(size);
+ pa_assert(b->pool == e->pool);
+
+ if (!(b = memblock_shared_copy(e->pool, b)))
+ return -1;
+
+ pa_mutex_lock(e->mutex);
+
+ if (e->free_slots) {
+ slot = e->free_slots;
+ PA_LLIST_REMOVE(struct memexport_slot, e->free_slots, slot);
+ } else if (e->n_init < PA_MEMEXPORT_SLOTS_MAX)
+ slot = &e->slots[e->n_init++];
+ else {
+ pa_mutex_unlock(e->mutex);
+ pa_memblock_unref(b);
+ return -1;
+ }
+
+ PA_LLIST_PREPEND(struct memexport_slot, e->used_slots, slot);
+ slot->block = b;
+ *block_id = (uint32_t) (slot - e->slots + e->baseidx);
+
+ pa_mutex_unlock(e->mutex);
+/* pa_log("Got block id %u", *block_id); */
+
+ data = pa_memblock_acquire(b);
+
+ if (b->type == PA_MEMBLOCK_IMPORTED) {
+ pa_assert(b->per_type.imported.segment);
+ memory = &b->per_type.imported.segment->memory;
+ } else {
+ pa_assert(b->type == PA_MEMBLOCK_POOL || b->type == PA_MEMBLOCK_POOL_EXTERNAL);
+ pa_assert(b->pool);
+ pa_assert(pa_mempool_is_shared(b->pool));
+ memory = &b->pool->memory;
+ }
+
+ pa_assert(data >= memory->ptr);
+ pa_assert((uint8_t*) data + b->length <= (uint8_t*) memory->ptr + memory->size);
+
+ *type = memory->type;
+ *shm_id = memory->id;
+ *offset = (size_t) ((uint8_t*) data - (uint8_t*) memory->ptr);
+ *size = b->length;
+
+ pa_memblock_release(b);
+
+ pa_atomic_inc(&e->pool->stat.n_exported);
+ pa_atomic_add(&e->pool->stat.exported_size, (int) b->length);
+
+ return 0;
+}
diff --git a/src/pulsecore/memblock.h b/src/pulsecore/memblock.h
new file mode 100644
index 0000000..4273c09
--- /dev/null
+++ b/src/pulsecore/memblock.h
@@ -0,0 +1,159 @@
+#ifndef foopulsememblockhfoo
+#define foopulsememblockhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+typedef struct pa_memblock pa_memblock;
+
+#include <sys/types.h>
+#include <inttypes.h>
+
+#include <pulse/def.h>
+#include <pulse/xmalloc.h>
+#include <pulsecore/atomic.h>
+#include <pulsecore/memchunk.h>
+#include <pulsecore/mem.h>
+
+/* A pa_memblock is a reference counted memory block. PulseAudio
+ * passes references to pa_memblocks around instead of copying
+ * data. See pa_memchunk for a structure that describes parts of
+ * memory blocks. */
+
+/* The type of memory this block points to */
+typedef enum pa_memblock_type {
+ PA_MEMBLOCK_POOL, /* Memory is part of the memory pool */
+ PA_MEMBLOCK_POOL_EXTERNAL, /* Data memory is part of the memory pool but the pa_memblock structure itself is not */
+ PA_MEMBLOCK_APPENDED, /* The data is appended to the memory block */
+ PA_MEMBLOCK_USER, /* User supplied memory, to be freed with free_cb */
+ PA_MEMBLOCK_FIXED, /* Data is a pointer to fixed memory that needs not to be freed */
+ PA_MEMBLOCK_IMPORTED, /* Memory is imported from another process via shm */
+ PA_MEMBLOCK_TYPE_MAX
+} pa_memblock_type_t;
+
+typedef struct pa_mempool pa_mempool;
+typedef struct pa_mempool_stat pa_mempool_stat;
+typedef struct pa_memimport_segment pa_memimport_segment;
+typedef struct pa_memimport pa_memimport;
+typedef struct pa_memexport pa_memexport;
+
+typedef void (*pa_memimport_release_cb_t)(pa_memimport *i, uint32_t block_id, void *userdata);
+typedef void (*pa_memexport_revoke_cb_t)(pa_memexport *e, uint32_t block_id, void *userdata);
+
+/* Please note that updates to this structure are not locked,
+ * i.e. n_allocated might be updated at a point in time where
+ * n_accumulated is not yet. Take these values with a grain of salt,
+ * they are here for purely statistical reasons.*/
+struct pa_mempool_stat {
+ pa_atomic_t n_allocated;
+ pa_atomic_t n_accumulated;
+ pa_atomic_t n_imported;
+ pa_atomic_t n_exported;
+ pa_atomic_t allocated_size;
+ pa_atomic_t accumulated_size;
+ pa_atomic_t imported_size;
+ pa_atomic_t exported_size;
+
+ pa_atomic_t n_too_large_for_pool;
+ pa_atomic_t n_pool_full;
+
+ pa_atomic_t n_allocated_by_type[PA_MEMBLOCK_TYPE_MAX];
+ pa_atomic_t n_accumulated_by_type[PA_MEMBLOCK_TYPE_MAX];
+};
+
+/* Allocate a new memory block of type PA_MEMBLOCK_MEMPOOL or PA_MEMBLOCK_APPENDED, depending on the size */
+pa_memblock *pa_memblock_new(pa_mempool *, size_t length);
+
+/* Allocate a new memory block of type PA_MEMBLOCK_MEMPOOL. If the requested size is too large, return NULL */
+pa_memblock *pa_memblock_new_pool(pa_mempool *, size_t length);
+
+/* Allocate a new memory block of type PA_MEMBLOCK_USER */
+pa_memblock *pa_memblock_new_user(pa_mempool *, void *data, size_t length, pa_free_cb_t free_cb, void *free_cb_data, bool read_only);
+
+/* A special case of pa_memblock_new_user: take a memory buffer previously allocated with pa_xmalloc() */
+static inline pa_memblock *pa_memblock_new_malloced(pa_mempool *p, void *data, size_t length) {
+ return pa_memblock_new_user(p, data, length, pa_xfree, data, 0);
+}
+
+/* Allocate a new memory block of type PA_MEMBLOCK_FIXED */
+pa_memblock *pa_memblock_new_fixed(pa_mempool *, void *data, size_t length, bool read_only);
+
+void pa_memblock_unref(pa_memblock*b);
+pa_memblock* pa_memblock_ref(pa_memblock*b);
+
+/* This special unref function has to be called by the owner of the
+memory of a static memory block when they want to release all
+references to the memory. This causes the memory to be copied and
+converted into a pool of malloc'ed memory block. Please note that this
+function is not multiple caller safe, i.e. needs to be locked
+manually if called from more than one thread at the same time. */
+void pa_memblock_unref_fixed(pa_memblock*b);
+
+bool pa_memblock_is_ours(pa_memblock *b);
+bool pa_memblock_is_read_only(pa_memblock *b);
+bool pa_memblock_is_silence(pa_memblock *b);
+bool pa_memblock_ref_is_one(pa_memblock *b);
+void pa_memblock_set_is_silence(pa_memblock *b, bool v);
+
+void* pa_memblock_acquire(pa_memblock *b);
+void *pa_memblock_acquire_chunk(const pa_memchunk *c);
+void pa_memblock_release(pa_memblock *b);
+
+size_t pa_memblock_get_length(pa_memblock *b);
+
+/* Note! Always unref the returned pool after use */
+pa_mempool * pa_memblock_get_pool(pa_memblock *b);
+
+pa_memblock *pa_memblock_will_need(pa_memblock *b);
+
+/* The memory block manager */
+pa_mempool *pa_mempool_new(pa_mem_type_t type, size_t size, bool per_client);
+void pa_mempool_unref(pa_mempool *p);
+pa_mempool* pa_mempool_ref(pa_mempool *p);
+const pa_mempool_stat* pa_mempool_get_stat(pa_mempool *p);
+void pa_mempool_vacuum(pa_mempool *p);
+int pa_mempool_get_shm_id(pa_mempool *p, uint32_t *id);
+bool pa_mempool_is_shared(pa_mempool *p);
+bool pa_mempool_is_memfd_backed(const pa_mempool *p);
+bool pa_mempool_is_global(pa_mempool *p);
+bool pa_mempool_is_per_client(pa_mempool *p);
+bool pa_mempool_is_remote_writable(pa_mempool *p);
+void pa_mempool_set_is_remote_writable(pa_mempool *p, bool writable);
+size_t pa_mempool_block_size_max(pa_mempool *p);
+
+int pa_mempool_take_memfd_fd(pa_mempool *p);
+int pa_mempool_get_memfd_fd(pa_mempool *p);
+
+/* For receiving blocks from other nodes */
+pa_memimport* pa_memimport_new(pa_mempool *p, pa_memimport_release_cb_t cb, void *userdata);
+void pa_memimport_free(pa_memimport *i);
+int pa_memimport_attach_memfd(pa_memimport *i, uint32_t shm_id, int memfd_fd, bool writable);
+pa_memblock* pa_memimport_get(pa_memimport *i, pa_mem_type_t type, uint32_t block_id,
+ uint32_t shm_id, size_t offset, size_t size, bool writable);
+int pa_memimport_process_revoke(pa_memimport *i, uint32_t block_id);
+
+/* For sending blocks to other nodes */
+pa_memexport* pa_memexport_new(pa_mempool *p, pa_memexport_revoke_cb_t cb, void *userdata);
+void pa_memexport_free(pa_memexport *e);
+int pa_memexport_put(pa_memexport *e, pa_memblock *b, pa_mem_type_t *type, uint32_t *block_id,
+ uint32_t *shm_id, size_t *offset, size_t * size);
+int pa_memexport_process_release(pa_memexport *e, uint32_t id);
+
+#endif
diff --git a/src/pulsecore/memblockq.c b/src/pulsecore/memblockq.c
new file mode 100644
index 0000000..b132dd3
--- /dev/null
+++ b/src/pulsecore/memblockq.c
@@ -0,0 +1,1019 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/log.h>
+#include <pulsecore/mcalign.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/flist.h>
+
+#include "memblockq.h"
+
+/* #define MEMBLOCKQ_DEBUG */
+
+struct list_item {
+ struct list_item *next, *prev;
+ int64_t index;
+ pa_memchunk chunk;
+};
+
+PA_STATIC_FLIST_DECLARE(list_items, 0, pa_xfree);
+
+struct pa_memblockq {
+ struct list_item *blocks, *blocks_tail;
+ struct list_item *current_read, *current_write;
+ unsigned n_blocks;
+ size_t maxlength, tlength, base, prebuf, minreq, maxrewind;
+ int64_t read_index, write_index;
+ bool in_prebuf;
+ pa_memchunk silence;
+ pa_mcalign *mcalign;
+ int64_t missing, requested;
+ char *name;
+ pa_sample_spec sample_spec;
+};
+
+pa_memblockq* pa_memblockq_new(
+ const char *name,
+ int64_t idx,
+ size_t maxlength,
+ size_t tlength,
+ const pa_sample_spec *sample_spec,
+ size_t prebuf,
+ size_t minreq,
+ size_t maxrewind,
+ pa_memchunk *silence) {
+
+ pa_memblockq* bq;
+
+ pa_assert(sample_spec);
+ pa_assert(name);
+
+ bq = pa_xnew0(pa_memblockq, 1);
+ bq->name = pa_xstrdup(name);
+
+ bq->sample_spec = *sample_spec;
+ bq->base = pa_frame_size(sample_spec);
+ bq->read_index = bq->write_index = idx;
+
+ pa_log_debug("memblockq requested: maxlength=%lu, tlength=%lu, base=%lu, prebuf=%lu, minreq=%lu maxrewind=%lu",
+ (unsigned long) maxlength, (unsigned long) tlength, (unsigned long) bq->base, (unsigned long) prebuf, (unsigned long) minreq, (unsigned long) maxrewind);
+
+ bq->in_prebuf = true;
+
+ pa_memblockq_set_maxlength(bq, maxlength);
+ pa_memblockq_set_tlength(bq, tlength);
+ pa_memblockq_set_minreq(bq, minreq);
+ pa_memblockq_set_prebuf(bq, prebuf);
+ pa_memblockq_set_maxrewind(bq, maxrewind);
+
+ pa_log_debug("memblockq sanitized: maxlength=%lu, tlength=%lu, base=%lu, prebuf=%lu, minreq=%lu maxrewind=%lu",
+ (unsigned long) bq->maxlength, (unsigned long) bq->tlength, (unsigned long) bq->base, (unsigned long) bq->prebuf, (unsigned long) bq->minreq, (unsigned long) bq->maxrewind);
+
+ if (silence) {
+ bq->silence = *silence;
+ pa_memblock_ref(bq->silence.memblock);
+ }
+
+ bq->mcalign = pa_mcalign_new(bq->base);
+
+ return bq;
+}
+
+void pa_memblockq_free(pa_memblockq* bq) {
+ pa_assert(bq);
+
+ pa_memblockq_silence(bq);
+
+ if (bq->silence.memblock)
+ pa_memblock_unref(bq->silence.memblock);
+
+ if (bq->mcalign)
+ pa_mcalign_free(bq->mcalign);
+
+ pa_xfree(bq->name);
+ pa_xfree(bq);
+}
+
+static void fix_current_read(pa_memblockq *bq) {
+ pa_assert(bq);
+
+ if (PA_UNLIKELY(!bq->blocks)) {
+ bq->current_read = NULL;
+ return;
+ }
+
+ if (PA_UNLIKELY(!bq->current_read))
+ bq->current_read = bq->blocks;
+
+ /* Scan left */
+ while (PA_UNLIKELY(bq->current_read->index > bq->read_index))
+
+ if (bq->current_read->prev)
+ bq->current_read = bq->current_read->prev;
+ else
+ break;
+
+ /* Scan right */
+ while (PA_LIKELY(bq->current_read != NULL) && PA_UNLIKELY(bq->current_read->index + (int64_t) bq->current_read->chunk.length <= bq->read_index))
+ bq->current_read = bq->current_read->next;
+
+ /* At this point current_read will either point at or left of the
+ next block to play. It may be NULL in case everything in
+ the queue was already played */
+}
+
+static void fix_current_write(pa_memblockq *bq) {
+ pa_assert(bq);
+
+ if (PA_UNLIKELY(!bq->blocks)) {
+ bq->current_write = NULL;
+ return;
+ }
+
+ if (PA_UNLIKELY(!bq->current_write))
+ bq->current_write = bq->blocks_tail;
+
+ /* Scan right */
+ while (PA_UNLIKELY(bq->current_write->index + (int64_t) bq->current_write->chunk.length <= bq->write_index))
+
+ if (bq->current_write->next)
+ bq->current_write = bq->current_write->next;
+ else
+ break;
+
+ /* Scan left */
+ while (PA_LIKELY(bq->current_write != NULL) && PA_UNLIKELY(bq->current_write->index > bq->write_index))
+ bq->current_write = bq->current_write->prev;
+
+ /* At this point current_write will either point at or right of
+ the next block to write data to. It may be NULL in case
+ everything in the queue is still to be played */
+}
+
+static void drop_block(pa_memblockq *bq, struct list_item *q) {
+ pa_assert(bq);
+ pa_assert(q);
+
+ pa_assert(bq->n_blocks >= 1);
+
+ if (q->prev)
+ q->prev->next = q->next;
+ else {
+ pa_assert(bq->blocks == q);
+ bq->blocks = q->next;
+ }
+
+ if (q->next)
+ q->next->prev = q->prev;
+ else {
+ pa_assert(bq->blocks_tail == q);
+ bq->blocks_tail = q->prev;
+ }
+
+ if (bq->current_write == q)
+ bq->current_write = q->prev;
+
+ if (bq->current_read == q)
+ bq->current_read = q->next;
+
+ pa_memblock_unref(q->chunk.memblock);
+
+ if (pa_flist_push(PA_STATIC_FLIST_GET(list_items), q) < 0)
+ pa_xfree(q);
+
+ bq->n_blocks--;
+}
+
+static void drop_backlog(pa_memblockq *bq) {
+ int64_t boundary;
+ pa_assert(bq);
+
+ boundary = bq->read_index - (int64_t) bq->maxrewind;
+
+ while (bq->blocks && (bq->blocks->index + (int64_t) bq->blocks->chunk.length <= boundary))
+ drop_block(bq, bq->blocks);
+}
+
+static bool can_push(pa_memblockq *bq, size_t l) {
+ int64_t end;
+
+ pa_assert(bq);
+
+ if (bq->read_index > bq->write_index) {
+ int64_t d = bq->read_index - bq->write_index;
+
+ if ((int64_t) l > d)
+ l -= (size_t) d;
+ else
+ return true;
+ }
+
+ end = bq->blocks_tail ? bq->blocks_tail->index + (int64_t) bq->blocks_tail->chunk.length : bq->write_index;
+
+ /* Make sure that the list doesn't get too long */
+ if (bq->write_index + (int64_t) l > end)
+ if (bq->write_index + (int64_t) l - bq->read_index > (int64_t) bq->maxlength)
+ return false;
+
+ return true;
+}
+
+static void write_index_changed(pa_memblockq *bq, int64_t old_write_index, bool account) {
+ int64_t delta;
+
+ pa_assert(bq);
+
+ delta = bq->write_index - old_write_index;
+
+ if (account)
+ bq->requested -= delta;
+ else
+ bq->missing -= delta;
+
+#ifdef MEMBLOCKQ_DEBUG
+ pa_log_debug("[%s] pushed/seeked %lli: requested counter at %lli, account=%i", bq->name, (long long) delta, (long long) bq->requested, account);
+#endif
+}
+
+static void read_index_changed(pa_memblockq *bq, int64_t old_read_index) {
+ int64_t delta;
+
+ pa_assert(bq);
+
+ delta = bq->read_index - old_read_index;
+ bq->missing += delta;
+
+#ifdef MEMBLOCKQ_DEBUG
+ pa_log_debug("[%s] popped %lli: missing counter at %lli", bq->name, (long long) delta, (long long) bq->missing);
+#endif
+}
+
+int pa_memblockq_push(pa_memblockq* bq, const pa_memchunk *uchunk) {
+ struct list_item *q, *n;
+ pa_memchunk chunk;
+ int64_t old;
+
+ pa_assert(bq);
+ pa_assert(uchunk);
+ pa_assert(uchunk->memblock);
+ pa_assert(uchunk->length > 0);
+ pa_assert(uchunk->index + uchunk->length <= pa_memblock_get_length(uchunk->memblock));
+
+ pa_assert(uchunk->length % bq->base == 0);
+ pa_assert(uchunk->index % bq->base == 0);
+
+ if (!can_push(bq, uchunk->length))
+ return -1;
+
+ old = bq->write_index;
+ chunk = *uchunk;
+
+ fix_current_write(bq);
+ q = bq->current_write;
+
+ /* First we advance the q pointer right of where we want to
+ * write to */
+
+ if (q) {
+ while (bq->write_index + (int64_t) chunk.length > q->index)
+ if (q->next)
+ q = q->next;
+ else
+ break;
+ }
+
+ if (!q)
+ q = bq->blocks_tail;
+
+ /* We go from back to front to look for the right place to add
+ * this new entry. Drop data we will overwrite on the way */
+
+ while (q) {
+
+ if (bq->write_index >= q->index + (int64_t) q->chunk.length)
+ /* We found the entry where we need to place the new entry immediately after */
+ break;
+ else if (bq->write_index + (int64_t) chunk.length <= q->index) {
+ /* This entry isn't touched at all, let's skip it */
+ q = q->prev;
+ } else if (bq->write_index <= q->index &&
+ bq->write_index + (int64_t) chunk.length >= q->index + (int64_t) q->chunk.length) {
+
+ /* This entry is fully replaced by the new entry, so let's drop it */
+
+ struct list_item *p;
+ p = q;
+ q = q->prev;
+ drop_block(bq, p);
+ } else if (bq->write_index >= q->index) {
+ /* The write index points into this memblock, so let's
+ * truncate or split it */
+
+ if (bq->write_index + (int64_t) chunk.length < q->index + (int64_t) q->chunk.length) {
+
+ /* We need to save the end of this memchunk */
+ struct list_item *p;
+ size_t d;
+
+ /* Create a new list entry for the end of the memchunk */
+ if (!(p = pa_flist_pop(PA_STATIC_FLIST_GET(list_items))))
+ p = pa_xnew(struct list_item, 1);
+
+ p->chunk = q->chunk;
+ pa_memblock_ref(p->chunk.memblock);
+
+ /* Calculate offset */
+ d = (size_t) (bq->write_index + (int64_t) chunk.length - q->index);
+ pa_assert(d > 0);
+
+ /* Drop it from the new entry */
+ p->index = q->index + (int64_t) d;
+ p->chunk.length -= d;
+
+ /* Add it to the list */
+ p->prev = q;
+ if ((p->next = q->next))
+ q->next->prev = p;
+ else
+ bq->blocks_tail = p;
+ q->next = p;
+
+ bq->n_blocks++;
+ }
+
+ /* Truncate the chunk */
+ if (!(q->chunk.length = (size_t) (bq->write_index - q->index))) {
+ struct list_item *p;
+ p = q;
+ q = q->prev;
+ drop_block(bq, p);
+ }
+
+ /* We had to truncate this block, hence we're now at the right position */
+ break;
+ } else {
+ size_t d;
+
+ pa_assert(bq->write_index + (int64_t)chunk.length > q->index &&
+ bq->write_index + (int64_t)chunk.length < q->index + (int64_t)q->chunk.length &&
+ bq->write_index < q->index);
+
+ /* The job overwrites the current entry at the end, so let's drop the beginning of this entry */
+
+ d = (size_t) (bq->write_index + (int64_t) chunk.length - q->index);
+ q->index += (int64_t) d;
+ q->chunk.index += d;
+ q->chunk.length -= d;
+
+ q = q->prev;
+ }
+ }
+
+ if (q) {
+ pa_assert(bq->write_index >= q->index + (int64_t)q->chunk.length);
+ pa_assert(!q->next || (bq->write_index + (int64_t)chunk.length <= q->next->index));
+
+ /* Try to merge memory blocks */
+
+ if (q->chunk.memblock == chunk.memblock &&
+ q->chunk.index + q->chunk.length == chunk.index &&
+ bq->write_index == q->index + (int64_t) q->chunk.length) {
+
+ q->chunk.length += chunk.length;
+ bq->write_index += (int64_t) chunk.length;
+ goto finish;
+ }
+ } else
+ pa_assert(!bq->blocks || (bq->write_index + (int64_t)chunk.length <= bq->blocks->index));
+
+ if (!(n = pa_flist_pop(PA_STATIC_FLIST_GET(list_items))))
+ n = pa_xnew(struct list_item, 1);
+
+ n->chunk = chunk;
+ pa_memblock_ref(n->chunk.memblock);
+ n->index = bq->write_index;
+ bq->write_index += (int64_t) n->chunk.length;
+
+ n->next = q ? q->next : bq->blocks;
+ n->prev = q;
+
+ if (n->next)
+ n->next->prev = n;
+ else
+ bq->blocks_tail = n;
+
+ if (n->prev)
+ n->prev->next = n;
+ else
+ bq->blocks = n;
+
+ bq->n_blocks++;
+
+finish:
+
+ write_index_changed(bq, old, true);
+ return 0;
+}
+
+bool pa_memblockq_prebuf_active(pa_memblockq *bq) {
+ pa_assert(bq);
+
+ if (bq->in_prebuf)
+ return pa_memblockq_get_length(bq) < bq->prebuf;
+ else
+ return bq->prebuf > 0 && bq->read_index >= bq->write_index;
+}
+
+static bool update_prebuf(pa_memblockq *bq) {
+ pa_assert(bq);
+
+ if (bq->in_prebuf) {
+
+ if (pa_memblockq_get_length(bq) < bq->prebuf)
+ return true;
+
+ bq->in_prebuf = false;
+ return false;
+ } else {
+
+ if (bq->prebuf > 0 && bq->read_index >= bq->write_index) {
+ bq->in_prebuf = true;
+ return true;
+ }
+
+ return false;
+ }
+}
+
+int pa_memblockq_peek(pa_memblockq* bq, pa_memchunk *chunk) {
+ int64_t d;
+ pa_assert(bq);
+ pa_assert(chunk);
+
+ /* We need to pre-buffer */
+ if (update_prebuf(bq))
+ return -1;
+
+ fix_current_read(bq);
+
+ /* Do we need to spit out silence? */
+ if (!bq->current_read || bq->current_read->index > bq->read_index) {
+ size_t length;
+
+ /* How much silence shall we return? */
+ if (bq->current_read)
+ length = (size_t) (bq->current_read->index - bq->read_index);
+ else if (bq->write_index > bq->read_index)
+ length = (size_t) (bq->write_index - bq->read_index);
+ else
+ length = 0;
+
+ /* We need to return silence, since no data is yet available */
+ if (bq->silence.memblock) {
+ *chunk = bq->silence;
+ pa_memblock_ref(chunk->memblock);
+
+ if (length > 0 && length < chunk->length)
+ chunk->length = length;
+
+ } else {
+
+ /* If the memblockq is empty, return -1, otherwise return
+ * the time to sleep */
+ if (length <= 0)
+ return -1;
+
+ chunk->memblock = NULL;
+ chunk->length = length;
+ }
+
+ chunk->index = 0;
+ return 0;
+ }
+
+ /* Ok, let's pass real data to the caller */
+ *chunk = bq->current_read->chunk;
+ pa_memblock_ref(chunk->memblock);
+
+ pa_assert(bq->read_index >= bq->current_read->index);
+ d = bq->read_index - bq->current_read->index;
+ chunk->index += (size_t) d;
+ chunk->length -= (size_t) d;
+
+ return 0;
+}
+
+int pa_memblockq_peek_fixed_size(pa_memblockq *bq, size_t block_size, pa_memchunk *chunk) {
+ pa_mempool *pool;
+ pa_memchunk tchunk, rchunk;
+ int64_t ri;
+ struct list_item *item;
+
+ pa_assert(bq);
+ pa_assert(block_size > 0);
+ pa_assert(chunk);
+ pa_assert(bq->silence.memblock);
+
+ if (pa_memblockq_peek(bq, &tchunk) < 0)
+ return -1;
+
+ if (tchunk.length >= block_size) {
+ *chunk = tchunk;
+ chunk->length = block_size;
+ return 0;
+ }
+
+ pool = pa_memblock_get_pool(tchunk.memblock);
+ rchunk.memblock = pa_memblock_new(pool, block_size);
+ rchunk.index = 0;
+ rchunk.length = tchunk.length;
+ pa_mempool_unref(pool), pool = NULL;
+
+ pa_memchunk_memcpy(&rchunk, &tchunk);
+ pa_memblock_unref(tchunk.memblock);
+
+ rchunk.index += tchunk.length;
+
+ /* We don't need to call fix_current_read() here, since
+ * pa_memblock_peek() already did that */
+ item = bq->current_read;
+ ri = bq->read_index + tchunk.length;
+
+ while (rchunk.index < block_size) {
+
+ if (!item || item->index > ri) {
+ /* Do we need to append silence? */
+ tchunk = bq->silence;
+
+ if (item)
+ tchunk.length = PA_MIN(tchunk.length, (size_t) (item->index - ri));
+
+ } else {
+ int64_t d;
+
+ /* We can append real data! */
+ tchunk = item->chunk;
+
+ d = ri - item->index;
+ tchunk.index += (size_t) d;
+ tchunk.length -= (size_t) d;
+
+ /* Go to next item for the next iteration */
+ item = item->next;
+ }
+
+ rchunk.length = tchunk.length = PA_MIN(tchunk.length, block_size - rchunk.index);
+ pa_memchunk_memcpy(&rchunk, &tchunk);
+
+ rchunk.index += rchunk.length;
+ ri += rchunk.length;
+ }
+
+ rchunk.index = 0;
+ rchunk.length = block_size;
+
+ *chunk = rchunk;
+ return 0;
+}
+
+void pa_memblockq_drop(pa_memblockq *bq, size_t length) {
+ int64_t old;
+ pa_assert(bq);
+ pa_assert(length % bq->base == 0);
+
+ old = bq->read_index;
+
+ while (length > 0) {
+
+ /* Do not drop any data when we are in prebuffering mode */
+ if (update_prebuf(bq))
+ break;
+
+ fix_current_read(bq);
+
+ if (bq->current_read) {
+ int64_t p, d;
+
+ /* We go through this piece by piece to make sure we don't
+ * drop more than allowed by prebuf */
+
+ p = bq->current_read->index + (int64_t) bq->current_read->chunk.length;
+ pa_assert(p >= bq->read_index);
+ d = p - bq->read_index;
+
+ if (d > (int64_t) length)
+ d = (int64_t) length;
+
+ bq->read_index += d;
+ length -= (size_t) d;
+
+ } else {
+
+ /* The list is empty, there's nothing we could drop */
+ bq->read_index += (int64_t) length;
+ break;
+ }
+ }
+
+ drop_backlog(bq);
+ read_index_changed(bq, old);
+}
+
+void pa_memblockq_rewind(pa_memblockq *bq, size_t length) {
+ int64_t old;
+ pa_assert(bq);
+ pa_assert(length % bq->base == 0);
+
+ old = bq->read_index;
+
+ /* This is kind of the inverse of pa_memblockq_drop() */
+
+ bq->read_index -= (int64_t) length;
+
+ read_index_changed(bq, old);
+}
+
+bool pa_memblockq_is_readable(pa_memblockq *bq) {
+ pa_assert(bq);
+
+ if (pa_memblockq_prebuf_active(bq))
+ return false;
+
+ if (pa_memblockq_get_length(bq) <= 0)
+ return false;
+
+ return true;
+}
+
+size_t pa_memblockq_get_length(pa_memblockq *bq) {
+ pa_assert(bq);
+
+ if (bq->write_index <= bq->read_index)
+ return 0;
+
+ return (size_t) (bq->write_index - bq->read_index);
+}
+
+void pa_memblockq_seek(pa_memblockq *bq, int64_t offset, pa_seek_mode_t seek, bool account) {
+ int64_t old;
+ pa_assert(bq);
+
+ old = bq->write_index;
+
+ switch (seek) {
+ case PA_SEEK_RELATIVE:
+ bq->write_index += offset;
+ break;
+ case PA_SEEK_ABSOLUTE:
+ bq->write_index = offset;
+ break;
+ case PA_SEEK_RELATIVE_ON_READ:
+ bq->write_index = bq->read_index + offset;
+ break;
+ case PA_SEEK_RELATIVE_END:
+ bq->write_index = (bq->blocks_tail ? bq->blocks_tail->index + (int64_t) bq->blocks_tail->chunk.length : bq->read_index) + offset;
+ break;
+ default:
+ pa_assert_not_reached();
+ }
+
+ drop_backlog(bq);
+ write_index_changed(bq, old, account);
+}
+
+void pa_memblockq_flush_write(pa_memblockq *bq, bool account) {
+ int64_t old;
+ pa_assert(bq);
+
+ pa_memblockq_silence(bq);
+
+ old = bq->write_index;
+ bq->write_index = bq->read_index;
+
+ pa_memblockq_prebuf_force(bq);
+ write_index_changed(bq, old, account);
+}
+
+void pa_memblockq_flush_read(pa_memblockq *bq) {
+ int64_t old;
+ pa_assert(bq);
+
+ pa_memblockq_silence(bq);
+
+ old = bq->read_index;
+ bq->read_index = bq->write_index;
+
+ pa_memblockq_prebuf_force(bq);
+ read_index_changed(bq, old);
+}
+
+size_t pa_memblockq_get_tlength(pa_memblockq *bq) {
+ pa_assert(bq);
+
+ return bq->tlength;
+}
+
+size_t pa_memblockq_get_minreq(pa_memblockq *bq) {
+ pa_assert(bq);
+
+ return bq->minreq;
+}
+
+size_t pa_memblockq_get_maxrewind(pa_memblockq *bq) {
+ pa_assert(bq);
+
+ return bq->maxrewind;
+}
+
+int64_t pa_memblockq_get_read_index(pa_memblockq *bq) {
+ pa_assert(bq);
+
+ return bq->read_index;
+}
+
+int64_t pa_memblockq_get_write_index(pa_memblockq *bq) {
+ pa_assert(bq);
+
+ return bq->write_index;
+}
+
+int pa_memblockq_push_align(pa_memblockq* bq, const pa_memchunk *chunk) {
+ pa_memchunk rchunk;
+
+ pa_assert(bq);
+ pa_assert(chunk);
+
+ if (bq->base == 1)
+ return pa_memblockq_push(bq, chunk);
+
+ if (!can_push(bq, pa_mcalign_csize(bq->mcalign, chunk->length)))
+ return -1;
+
+ pa_mcalign_push(bq->mcalign, chunk);
+
+ while (pa_mcalign_pop(bq->mcalign, &rchunk) >= 0) {
+ int r;
+ r = pa_memblockq_push(bq, &rchunk);
+ pa_memblock_unref(rchunk.memblock);
+
+ if (r < 0) {
+ pa_mcalign_flush(bq->mcalign);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+void pa_memblockq_prebuf_disable(pa_memblockq *bq) {
+ pa_assert(bq);
+
+ bq->in_prebuf = false;
+}
+
+void pa_memblockq_prebuf_force(pa_memblockq *bq) {
+ pa_assert(bq);
+
+ if (bq->prebuf > 0)
+ bq->in_prebuf = true;
+}
+
+size_t pa_memblockq_get_maxlength(pa_memblockq *bq) {
+ pa_assert(bq);
+
+ return bq->maxlength;
+}
+
+size_t pa_memblockq_get_prebuf(pa_memblockq *bq) {
+ pa_assert(bq);
+
+ return bq->prebuf;
+}
+
+size_t pa_memblockq_pop_missing(pa_memblockq *bq) {
+ size_t l;
+
+ pa_assert(bq);
+
+#ifdef MEMBLOCKQ_DEBUG
+ pa_log_debug("[%s] pop: %lli", bq->name, (long long) bq->missing);
+#endif
+
+ if (bq->missing <= 0)
+ return 0;
+
+ if (((size_t) bq->missing < bq->minreq) &&
+ !pa_memblockq_prebuf_active(bq))
+ return 0;
+
+ l = (size_t) bq->missing;
+
+ bq->requested += bq->missing;
+ bq->missing = 0;
+
+#ifdef MEMBLOCKQ_DEBUG
+ pa_log_debug("[%s] sent %lli: request counter is at %lli", bq->name, (long long) l, (long long) bq->requested);
+#endif
+
+ return l;
+}
+
+void pa_memblockq_set_maxlength(pa_memblockq *bq, size_t maxlength) {
+ pa_assert(bq);
+
+ bq->maxlength = ((maxlength+bq->base-1)/bq->base)*bq->base;
+
+ if (bq->maxlength < bq->base)
+ bq->maxlength = bq->base;
+
+ if (bq->tlength > bq->maxlength)
+ pa_memblockq_set_tlength(bq, bq->maxlength);
+}
+
+void pa_memblockq_set_tlength(pa_memblockq *bq, size_t tlength) {
+ size_t old_tlength;
+ pa_assert(bq);
+
+ if (tlength <= 0 || tlength == (size_t) -1)
+ tlength = bq->maxlength;
+
+ old_tlength = bq->tlength;
+ bq->tlength = ((tlength+bq->base-1)/bq->base)*bq->base;
+
+ if (bq->tlength > bq->maxlength)
+ bq->tlength = bq->maxlength;
+
+ if (bq->minreq > bq->tlength)
+ pa_memblockq_set_minreq(bq, bq->tlength);
+
+ if (bq->prebuf > bq->tlength+bq->base-bq->minreq)
+ pa_memblockq_set_prebuf(bq, bq->tlength+bq->base-bq->minreq);
+
+ bq->missing += (int64_t) bq->tlength - (int64_t) old_tlength;
+}
+
+void pa_memblockq_set_minreq(pa_memblockq *bq, size_t minreq) {
+ pa_assert(bq);
+
+ bq->minreq = (minreq/bq->base)*bq->base;
+
+ if (bq->minreq > bq->tlength)
+ bq->minreq = bq->tlength;
+
+ if (bq->minreq < bq->base)
+ bq->minreq = bq->base;
+
+ if (bq->prebuf > bq->tlength+bq->base-bq->minreq)
+ pa_memblockq_set_prebuf(bq, bq->tlength+bq->base-bq->minreq);
+}
+
+void pa_memblockq_set_prebuf(pa_memblockq *bq, size_t prebuf) {
+ pa_assert(bq);
+
+ if (prebuf == (size_t) -1)
+ prebuf = bq->tlength+bq->base-bq->minreq;
+
+ bq->prebuf = ((prebuf+bq->base-1)/bq->base)*bq->base;
+
+ if (prebuf > 0 && bq->prebuf < bq->base)
+ bq->prebuf = bq->base;
+
+ if (bq->prebuf > bq->tlength+bq->base-bq->minreq)
+ bq->prebuf = bq->tlength+bq->base-bq->minreq;
+
+ if (bq->prebuf <= 0 || pa_memblockq_get_length(bq) >= bq->prebuf)
+ bq->in_prebuf = false;
+}
+
+void pa_memblockq_set_maxrewind(pa_memblockq *bq, size_t maxrewind) {
+ pa_assert(bq);
+
+ bq->maxrewind = (maxrewind/bq->base)*bq->base;
+}
+
+void pa_memblockq_apply_attr(pa_memblockq *bq, const pa_buffer_attr *a) {
+ pa_assert(bq);
+ pa_assert(a);
+
+ pa_memblockq_set_maxlength(bq, a->maxlength);
+ pa_memblockq_set_tlength(bq, a->tlength);
+ pa_memblockq_set_minreq(bq, a->minreq);
+ pa_memblockq_set_prebuf(bq, a->prebuf);
+}
+
+void pa_memblockq_get_attr(pa_memblockq *bq, pa_buffer_attr *a) {
+ pa_assert(bq);
+ pa_assert(a);
+
+ a->maxlength = (uint32_t) pa_memblockq_get_maxlength(bq);
+ a->tlength = (uint32_t) pa_memblockq_get_tlength(bq);
+ a->prebuf = (uint32_t) pa_memblockq_get_prebuf(bq);
+ a->minreq = (uint32_t) pa_memblockq_get_minreq(bq);
+}
+
+int pa_memblockq_splice(pa_memblockq *bq, pa_memblockq *source) {
+
+ pa_assert(bq);
+ pa_assert(source);
+
+ pa_memblockq_prebuf_disable(bq);
+
+ for (;;) {
+ pa_memchunk chunk;
+
+ if (pa_memblockq_peek(source, &chunk) < 0)
+ return 0;
+
+ pa_assert(chunk.length > 0);
+
+ if (chunk.memblock) {
+
+ if (pa_memblockq_push_align(bq, &chunk) < 0) {
+ pa_memblock_unref(chunk.memblock);
+ return -1;
+ }
+
+ pa_memblock_unref(chunk.memblock);
+ } else
+ pa_memblockq_seek(bq, (int64_t) chunk.length, PA_SEEK_RELATIVE, true);
+
+ pa_memblockq_drop(bq, chunk.length);
+ }
+}
+
+void pa_memblockq_willneed(pa_memblockq *bq) {
+ struct list_item *q;
+
+ pa_assert(bq);
+
+ fix_current_read(bq);
+
+ for (q = bq->current_read; q; q = q->next)
+ pa_memchunk_will_need(&q->chunk);
+}
+
+void pa_memblockq_set_silence(pa_memblockq *bq, pa_memchunk *silence) {
+ pa_assert(bq);
+
+ if (bq->silence.memblock)
+ pa_memblock_unref(bq->silence.memblock);
+
+ if (silence) {
+ bq->silence = *silence;
+ pa_memblock_ref(bq->silence.memblock);
+ } else
+ pa_memchunk_reset(&bq->silence);
+}
+
+bool pa_memblockq_is_empty(pa_memblockq *bq) {
+ pa_assert(bq);
+
+ return !bq->blocks;
+}
+
+void pa_memblockq_silence(pa_memblockq *bq) {
+ pa_assert(bq);
+
+ while (bq->blocks)
+ drop_block(bq, bq->blocks);
+
+ pa_assert(bq->n_blocks == 0);
+}
+
+unsigned pa_memblockq_get_nblocks(pa_memblockq *bq) {
+ pa_assert(bq);
+
+ return bq->n_blocks;
+}
+
+size_t pa_memblockq_get_base(pa_memblockq *bq) {
+ pa_assert(bq);
+
+ return bq->base;
+}
diff --git a/src/pulsecore/memblockq.h b/src/pulsecore/memblockq.h
new file mode 100644
index 0000000..96e41cc
--- /dev/null
+++ b/src/pulsecore/memblockq.h
@@ -0,0 +1,187 @@
+#ifndef foomemblockqhfoo
+#define foomemblockqhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/types.h>
+#include <inttypes.h>
+
+#include <pulsecore/memblock.h>
+#include <pulsecore/memchunk.h>
+#include <pulse/def.h>
+
+/* A memblockq is a queue of pa_memchunks (yep, the name is not
+ * perfect). It is similar to the ring buffers used by most other
+ * audio software. In contrast to a ring buffer this memblockq data
+ * type doesn't need to copy any data around, it just maintains
+ * references to reference counted memory blocks. */
+
+typedef struct pa_memblockq pa_memblockq;
+
+/* Parameters:
+
+ - name: name for debugging purposes
+
+ - idx: start value for both read and write index
+
+ - maxlength: maximum length of queue. If more data is pushed into
+ the queue, the operation will fail. Must not be 0.
+
+ - tlength: the target length of the queue. Pass 0 for the default.
+
+ - ss: Sample spec describing the queue contents. Only multiples
+ of the frame size as implied by the sample spec are
+ allowed into the queue or can be popped from it.
+
+ - prebuf: If the queue runs empty wait until this many bytes are in
+ queue again before passing the first byte out. If set
+ to 0 pa_memblockq_pop() will return a silence memblock
+ if no data is in the queue and will never fail. Pass
+ (size_t) -1 for the default.
+
+ - minreq: pa_memblockq_pop_missing() will only return values greater
+ than this value. Pass 0 for the default.
+
+ - maxrewind: how many bytes of history to keep in the queue
+
+ - silence: return this memchunk when reading uninitialized data
+*/
+pa_memblockq* pa_memblockq_new(
+ const char *name,
+ int64_t idx,
+ size_t maxlength,
+ size_t tlength,
+ const pa_sample_spec *sample_spec,
+ size_t prebuf,
+ size_t minreq,
+ size_t maxrewind,
+ pa_memchunk *silence);
+
+void pa_memblockq_free(pa_memblockq*bq);
+
+/* Push a new memory chunk into the queue. */
+int pa_memblockq_push(pa_memblockq* bq, const pa_memchunk *chunk);
+
+/* Push a new memory chunk into the queue, but filter it through a
+ * pa_mcalign object. Don't mix this with pa_memblockq_seek() unless
+ * you know what you do. */
+int pa_memblockq_push_align(pa_memblockq* bq, const pa_memchunk *chunk);
+
+/* Manipulate the write pointer */
+void pa_memblockq_seek(pa_memblockq *bq, int64_t offset, pa_seek_mode_t seek, bool account);
+
+/* Return a copy of the next memory chunk in the queue. It is not
+ * removed from the queue. There are two reasons this function might
+ * fail: 1. prebuffering is active, 2. queue is empty and no silence
+ * memblock was passed at initialization. If the queue is not empty,
+ * but we're currently at a hole in the queue and no silence memblock
+ * was passed we return the length of the hole in chunk->length. */
+int pa_memblockq_peek(pa_memblockq* bq, pa_memchunk *chunk);
+
+/* Much like pa_memblockq_peek, but guarantees that the returned chunk
+ * will have a length of the block size passed. You must configure a
+ * silence memchunk for this memblockq if you use this call. */
+int pa_memblockq_peek_fixed_size(pa_memblockq *bq, size_t block_size, pa_memchunk *chunk);
+
+/* Drop the specified bytes from the queue. */
+void pa_memblockq_drop(pa_memblockq *bq, size_t length);
+
+/* Rewind the read index. If the history is shorter than the specified length we'll point to silence afterwards. */
+void pa_memblockq_rewind(pa_memblockq *bq, size_t length);
+
+/* Test if the pa_memblockq is currently readable, that is, more data than base */
+bool pa_memblockq_is_readable(pa_memblockq *bq);
+
+/* Return the length of the queue in bytes */
+size_t pa_memblockq_get_length(pa_memblockq *bq);
+
+/* Return the number of bytes that are missing since the last call to
+ * this function, reset the internal counter to 0. */
+size_t pa_memblockq_pop_missing(pa_memblockq *bq);
+
+/* Directly moves the data from the source memblockq into bq */
+int pa_memblockq_splice(pa_memblockq *bq, pa_memblockq *source);
+
+/* Set the queue to silence, set write index to read index */
+void pa_memblockq_flush_write(pa_memblockq *bq, bool account);
+
+/* Set the queue to silence, set write read index to write index*/
+void pa_memblockq_flush_read(pa_memblockq *bq);
+
+/* Ignore prebuf for now */
+void pa_memblockq_prebuf_disable(pa_memblockq *bq);
+
+/* Force prebuf */
+void pa_memblockq_prebuf_force(pa_memblockq *bq);
+
+/* Return the maximum length of the queue in bytes */
+size_t pa_memblockq_get_maxlength(pa_memblockq *bq);
+
+/* Get Target length */
+size_t pa_memblockq_get_tlength(pa_memblockq *bq);
+
+/* Return the prebuffer length in bytes */
+size_t pa_memblockq_get_prebuf(pa_memblockq *bq);
+
+/* Returns the minimal request value */
+size_t pa_memblockq_get_minreq(pa_memblockq *bq);
+
+/* Returns the maximal rewind value */
+size_t pa_memblockq_get_maxrewind(pa_memblockq *bq);
+
+/* Return the base unit in bytes */
+size_t pa_memblockq_get_base(pa_memblockq *bq);
+
+/* Return the current read index */
+int64_t pa_memblockq_get_read_index(pa_memblockq *bq);
+
+/* Return the current write index */
+int64_t pa_memblockq_get_write_index(pa_memblockq *bq);
+
+/* Change metrics. Always call in order. */
+void pa_memblockq_set_maxlength(pa_memblockq *memblockq, size_t maxlength); /* might modify tlength, prebuf, minreq too */
+void pa_memblockq_set_tlength(pa_memblockq *memblockq, size_t tlength); /* might modify minreq, too */
+void pa_memblockq_set_minreq(pa_memblockq *memblockq, size_t minreq); /* might modify prebuf, too */
+void pa_memblockq_set_prebuf(pa_memblockq *memblockq, size_t prebuf);
+void pa_memblockq_set_maxrewind(pa_memblockq *memblockq, size_t maxrewind); /* Set the maximum history size */
+void pa_memblockq_set_silence(pa_memblockq *memblockq, pa_memchunk *silence);
+
+/* Apply the data from pa_buffer_attr */
+void pa_memblockq_apply_attr(pa_memblockq *memblockq, const pa_buffer_attr *a);
+void pa_memblockq_get_attr(pa_memblockq *bq, pa_buffer_attr *a);
+
+/* Call pa_memchunk_will_need() for every chunk in the queue from the current read pointer to the end */
+void pa_memblockq_willneed(pa_memblockq *bq);
+
+/* Check whether the memblockq is completely empty, i.e. no data
+ * neither left nor right of the read pointer, and hence no buffered
+ * data for the future nor data in the backlog. */
+bool pa_memblockq_is_empty(pa_memblockq *bq);
+
+/* Drop everything in the queue, but don't modify the indexes */
+void pa_memblockq_silence(pa_memblockq *bq);
+
+/* Check whether we currently are in prebuf state */
+bool pa_memblockq_prebuf_active(pa_memblockq *bq);
+
+/* Return how many items are currently stored in the queue */
+unsigned pa_memblockq_get_nblocks(pa_memblockq *bq);
+
+#endif
diff --git a/src/pulsecore/memchunk.c b/src/pulsecore/memchunk.c
new file mode 100644
index 0000000..8822134
--- /dev/null
+++ b/src/pulsecore/memchunk.c
@@ -0,0 +1,121 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <pulsecore/macro.h>
+#include <pulsecore/core-util.h>
+
+#include "memchunk.h"
+
+pa_memchunk* pa_memchunk_make_writable(pa_memchunk *c, size_t min) {
+ pa_mempool *pool;
+ pa_memblock *n;
+ size_t l;
+ void *tdata, *sdata;
+
+ pa_assert(c);
+ pa_assert(c->memblock);
+
+ if (pa_memblock_ref_is_one(c->memblock) &&
+ !pa_memblock_is_read_only(c->memblock) &&
+ pa_memblock_get_length(c->memblock) >= c->index+min)
+ return c;
+
+ l = PA_MAX(c->length, min);
+
+ pool = pa_memblock_get_pool(c->memblock);
+ n = pa_memblock_new(pool, l);
+ pa_mempool_unref(pool), pool = NULL;
+
+ sdata = pa_memblock_acquire(c->memblock);
+ tdata = pa_memblock_acquire(n);
+
+ memcpy(tdata, (uint8_t*) sdata + c->index, c->length);
+
+ pa_memblock_release(c->memblock);
+ pa_memblock_release(n);
+
+ pa_memblock_unref(c->memblock);
+
+ c->memblock = n;
+ c->index = 0;
+
+ return c;
+}
+
+pa_memchunk* pa_memchunk_reset(pa_memchunk *c) {
+ pa_assert(c);
+
+ memset(c, 0, sizeof(*c));
+
+ return c;
+}
+
+pa_memchunk *pa_memchunk_will_need(const pa_memchunk *c) {
+ void *p;
+
+ pa_assert(c);
+ pa_assert(c->memblock);
+
+ /* A version of pa_memblock_will_need() that works on memchunks
+ * instead of memblocks */
+
+ p = pa_memblock_acquire_chunk(c);
+ pa_will_need(p, c->length);
+ pa_memblock_release(c->memblock);
+
+ return (pa_memchunk*) c;
+}
+
+pa_memchunk* pa_memchunk_memcpy(pa_memchunk *dst, pa_memchunk *src) {
+ void *p, *q;
+
+ pa_assert(dst);
+ pa_assert(src);
+ pa_assert(dst->length == src->length);
+
+ p = pa_memblock_acquire(dst->memblock);
+ q = pa_memblock_acquire(src->memblock);
+
+ memmove((uint8_t*) p + dst->index,
+ (uint8_t*) q + src->index,
+ dst->length);
+
+ pa_memblock_release(dst->memblock);
+ pa_memblock_release(src->memblock);
+
+ return dst;
+}
+
+bool pa_memchunk_isset(pa_memchunk *chunk) {
+ pa_assert(chunk);
+
+ return
+ chunk->memblock ||
+ chunk->index > 0 ||
+ chunk->length > 0;
+}
diff --git a/src/pulsecore/memchunk.h b/src/pulsecore/memchunk.h
new file mode 100644
index 0000000..2b19712
--- /dev/null
+++ b/src/pulsecore/memchunk.h
@@ -0,0 +1,56 @@
+#ifndef foomemchunkhfoo
+#define foomemchunkhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+typedef struct pa_memchunk pa_memchunk;
+
+#include <pulsecore/memblock.h>
+
+/* A memchunk describes a part of a memblock. In contrast to the memblock, a
+ * memchunk is not allocated dynamically or reference counted, instead
+ * it is usually stored on the stack and copied around */
+
+struct pa_memchunk {
+ pa_memblock *memblock;
+ size_t index, length;
+};
+
+/* Make a memchunk writable, i.e. make sure that the caller may have
+ * exclusive access to the memblock and it is not read-only. If needed
+ * the memblock in the structure is replaced by a copy. If min is not
+ * 0 it is made sure that the returned memblock is at least of the
+ * specified size, i.e. is enlarged if necessary. */
+pa_memchunk* pa_memchunk_make_writable(pa_memchunk *c, size_t min);
+
+/* Invalidate a memchunk. This does not free the containing memblock,
+ * but sets all members to zero. */
+pa_memchunk* pa_memchunk_reset(pa_memchunk *c);
+
+/* Map a memory chunk back into memory if it was swapped out */
+pa_memchunk *pa_memchunk_will_need(const pa_memchunk *c);
+
+/* Copy the data in the src memchunk to the dst memchunk */
+pa_memchunk* pa_memchunk_memcpy(pa_memchunk *dst, pa_memchunk *src);
+
+/* Return true if any field is set != 0 */
+bool pa_memchunk_isset(pa_memchunk *c);
+
+#endif
diff --git a/src/pulsecore/memfd-wrappers.h b/src/pulsecore/memfd-wrappers.h
new file mode 100644
index 0000000..c7aadfd
--- /dev/null
+++ b/src/pulsecore/memfd-wrappers.h
@@ -0,0 +1,69 @@
+#ifndef foopulsememfdwrappershfoo
+#define foopulsememfdwrappershfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2016 Ahmed S. Darwish <darwish.07@gmail.com>
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#if defined(HAVE_MEMFD) && !defined(HAVE_MEMFD_CREATE)
+
+#include <sys/syscall.h>
+#include <fcntl.h>
+
+/*
+ * Before glibc version 2.27 there was no wrapper for memfd_create(2),
+ * so we have to provide our own.
+ *
+ * Also define memfd fcntl sealing macros. While they are already
+ * defined in the kernel header file <linux/fcntl.h>, that file as
+ * a whole conflicts with the original glibc header <fnctl.h>.
+ */
+
+static inline int memfd_create(const char *name, unsigned int flags) {
+ return syscall(SYS_memfd_create, name, flags);
+}
+
+/* memfd_create(2) flags */
+
+#ifndef MFD_CLOEXEC
+#define MFD_CLOEXEC 0x0001U
+#endif
+
+#ifndef MFD_ALLOW_SEALING
+#define MFD_ALLOW_SEALING 0x0002U
+#endif
+
+/* fcntl() seals-related flags */
+
+#ifndef F_LINUX_SPECIFIC_BASE
+#define F_LINUX_SPECIFIC_BASE 1024
+#endif
+
+#ifndef F_ADD_SEALS
+#define F_ADD_SEALS (F_LINUX_SPECIFIC_BASE + 9)
+#define F_GET_SEALS (F_LINUX_SPECIFIC_BASE + 10)
+
+#define F_SEAL_SEAL 0x0001 /* prevent further seals from being set */
+#define F_SEAL_SHRINK 0x0002 /* prevent file from shrinking */
+#define F_SEAL_GROW 0x0004 /* prevent file from growing */
+#define F_SEAL_WRITE 0x0008 /* prevent writes */
+#endif
+
+#endif /* HAVE_MEMFD && !HAVE_MEMFD_CREATE */
+
+#endif
diff --git a/src/pulsecore/memtrap.c b/src/pulsecore/memtrap.c
new file mode 100644
index 0000000..e7b511c
--- /dev/null
+++ b/src/pulsecore/memtrap.c
@@ -0,0 +1,242 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2009 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <signal.h>
+
+#ifdef HAVE_SYS_MMAN_H
+#include <sys/mman.h>
+#endif
+
+/* This is deprecated on glibc but is still used by FreeBSD */
+#if !defined(MAP_ANONYMOUS) && defined(MAP_ANON)
+# define MAP_ANONYMOUS MAP_ANON
+#endif
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/aupdate.h>
+#include <pulsecore/atomic.h>
+#include <pulsecore/once.h>
+#include <pulsecore/mutex.h>
+
+#include "memtrap.h"
+
+struct pa_memtrap {
+ void *start;
+ size_t size;
+ pa_atomic_t bad;
+ pa_memtrap *next[2], *prev[2];
+};
+
+static pa_memtrap *memtraps[2] = { NULL, NULL };
+static pa_aupdate *aupdate;
+static pa_static_mutex mutex = PA_STATIC_MUTEX_INIT; /* only required to serialize access to the write side */
+
+static void allocate_aupdate(void) {
+ PA_ONCE_BEGIN {
+ aupdate = pa_aupdate_new();
+ } PA_ONCE_END;
+}
+
+bool pa_memtrap_is_good(pa_memtrap *m) {
+ pa_assert(m);
+
+ return !pa_atomic_load(&m->bad);
+}
+
+#ifdef HAVE_SIGACTION
+static void sigsafe_error(const char *s) {
+ size_t ret PA_GCC_UNUSED;
+ ret = write(STDERR_FILENO, s, strlen(s));
+}
+
+static void signal_handler(int sig, siginfo_t* si, void *data) {
+ unsigned j;
+ pa_memtrap *m;
+ void *r;
+
+ j = pa_aupdate_read_begin(aupdate);
+
+ for (m = memtraps[j]; m; m = m->next[j])
+ if (si->si_addr >= m->start &&
+ (uint8_t*) si->si_addr < (uint8_t*) m->start + m->size)
+ break;
+
+ if (!m)
+ goto fail;
+
+ pa_atomic_store(&m->bad, 1);
+
+ /* Remap anonymous memory into the bad segment */
+ if ((r = mmap(m->start, m->size, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_FIXED|MAP_PRIVATE, -1, 0)) == MAP_FAILED) {
+ sigsafe_error("mmap() failed.\n");
+ goto fail;
+ }
+
+ pa_assert(r == m->start);
+
+ pa_aupdate_read_end(aupdate);
+ return;
+
+fail:
+ pa_aupdate_read_end(aupdate);
+
+ sigsafe_error("Failed to handle SIGBUS.\n");
+ abort();
+}
+#endif
+
+static void memtrap_link(pa_memtrap *m, unsigned j) {
+ pa_assert(m);
+
+ m->prev[j] = NULL;
+
+ if ((m->next[j] = memtraps[j]))
+ m->next[j]->prev[j] = m;
+
+ memtraps[j] = m;
+}
+
+static void memtrap_unlink(pa_memtrap *m, unsigned j) {
+ pa_assert(m);
+
+ if (m->next[j])
+ m->next[j]->prev[j] = m->prev[j];
+
+ if (m->prev[j])
+ m->prev[j]->next[j] = m->next[j];
+ else
+ memtraps[j] = m->next[j];
+}
+
+pa_memtrap* pa_memtrap_add(const void *start, size_t size) {
+ pa_memtrap *m = NULL;
+ unsigned j;
+ pa_mutex *mx;
+
+ pa_assert(start);
+ pa_assert(size > 0);
+
+ start = PA_PAGE_ALIGN_PTR(start);
+ size = PA_PAGE_ALIGN(size);
+
+ m = pa_xnew(pa_memtrap, 1);
+ m->start = (void*) start;
+ m->size = size;
+ pa_atomic_store(&m->bad, 0);
+
+ allocate_aupdate();
+
+ mx = pa_static_mutex_get(&mutex, false, true);
+ pa_mutex_lock(mx);
+
+ j = pa_aupdate_write_begin(aupdate);
+ memtrap_link(m, j);
+ j = pa_aupdate_write_swap(aupdate);
+ memtrap_link(m, j);
+ pa_aupdate_write_end(aupdate);
+
+ pa_mutex_unlock(mx);
+
+ return m;
+}
+
+void pa_memtrap_remove(pa_memtrap *m) {
+ unsigned j;
+ pa_mutex *mx;
+
+ pa_assert(m);
+
+ allocate_aupdate();
+
+ mx = pa_static_mutex_get(&mutex, false, true);
+ pa_mutex_lock(mx);
+
+ j = pa_aupdate_write_begin(aupdate);
+ memtrap_unlink(m, j);
+ j = pa_aupdate_write_swap(aupdate);
+ memtrap_unlink(m, j);
+ pa_aupdate_write_end(aupdate);
+
+ pa_mutex_unlock(mx);
+
+ pa_xfree(m);
+}
+
+pa_memtrap *pa_memtrap_update(pa_memtrap *m, const void *start, size_t size) {
+ unsigned j;
+ pa_mutex *mx;
+
+ pa_assert(m);
+
+ pa_assert(start);
+ pa_assert(size > 0);
+
+ start = PA_PAGE_ALIGN_PTR(start);
+ size = PA_PAGE_ALIGN(size);
+
+ allocate_aupdate();
+
+ mx = pa_static_mutex_get(&mutex, false, true);
+ pa_mutex_lock(mx);
+
+ j = pa_aupdate_write_begin(aupdate);
+
+ if (m->start == start && m->size == size)
+ goto unlock;
+
+ memtrap_unlink(m, j);
+ pa_aupdate_write_swap(aupdate);
+
+ m->start = (void*) start;
+ m->size = size;
+ pa_atomic_store(&m->bad, 0);
+
+ pa_assert_se(pa_aupdate_write_swap(aupdate) == j);
+ memtrap_link(m, j);
+
+unlock:
+ pa_aupdate_write_end(aupdate);
+
+ pa_mutex_unlock(mx);
+
+ return m;
+}
+
+void pa_memtrap_install(void) {
+#ifdef HAVE_SIGACTION
+ struct sigaction sa;
+
+ allocate_aupdate();
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_sigaction = signal_handler;
+ sa.sa_flags = SA_RESTART|SA_SIGINFO;
+
+ pa_assert_se(sigaction(SIGBUS, &sa, NULL) == 0);
+#ifdef __FreeBSD_kernel__
+ pa_assert_se(sigaction(SIGSEGV, &sa, NULL) == 0);
+#endif
+#endif
+}
diff --git a/src/pulsecore/memtrap.h b/src/pulsecore/memtrap.h
new file mode 100644
index 0000000..cf3e99e
--- /dev/null
+++ b/src/pulsecore/memtrap.h
@@ -0,0 +1,49 @@
+#ifndef foopulsecorememtraphfoo
+#define foopulsecorememtraphfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2009 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/types.h>
+
+#include <pulsecore/macro.h>
+
+/* This subsystem will trap SIGBUS on specific memory regions. The
+ * regions will be remapped to anonymous memory (i.e. writable NUL
+ * bytes) on SIGBUS, so that execution of the main program can
+ * continue though with memory having changed beneath its hands. With
+ * pa_memtrap_is_good() it is possible to query if a memory region is
+ * still 'good' i.e. no SIGBUS has happened yet for it.
+ *
+ * Intended usage is to handle memory mapped in which is controlled by
+ * other processes that might execute ftruncate() or when mapping inb
+ * hardware resources that might get invalidated when unplugged. */
+
+typedef struct pa_memtrap pa_memtrap;
+
+pa_memtrap* pa_memtrap_add(const void *start, size_t size);
+pa_memtrap *pa_memtrap_update(pa_memtrap *m, const void *start, size_t size);
+
+void pa_memtrap_remove(pa_memtrap *m);
+
+bool pa_memtrap_is_good(pa_memtrap *m);
+
+void pa_memtrap_install(void);
+
+#endif
diff --git a/src/pulsecore/meson.build b/src/pulsecore/meson.build
new file mode 100644
index 0000000..99a702e
--- /dev/null
+++ b/src/pulsecore/meson.build
@@ -0,0 +1,286 @@
+libpulsecore_sources = [
+ 'asyncmsgq.c',
+ 'asyncq.c',
+ 'auth-cookie.c',
+ 'card.c',
+ 'cli-command.c',
+ 'cli-text.c',
+ 'client.c',
+ 'core-scache.c',
+ 'core-subscribe.c',
+ 'core.c',
+ 'cpu.c',
+ 'cpu-arm.c',
+ 'cpu-orc.c',
+ 'cpu-x86.c',
+ 'device-port.c',
+ 'database.c',
+ 'ffmpeg/resample2.c',
+ 'filter/biquad.c',
+ 'filter/crossover.c',
+ 'filter/lfe-filter.c',
+ 'hook-list.c',
+ 'ltdl-helper.c',
+ 'message-handler.c',
+ 'mix.c',
+ 'modargs.c',
+ 'modinfo.c',
+ 'module.c',
+ 'msgobject.c',
+ 'namereg.c',
+ 'object.c',
+ 'play-memblockq.c',
+ 'play-memchunk.c',
+ 'remap.c',
+ 'resampler.c',
+ 'resampler/ffmpeg.c',
+ 'resampler/peaks.c',
+ 'resampler/trivial.c',
+ 'rtpoll.c',
+ 'sconv-s16be.c',
+ 'sconv-s16le.c',
+ 'sconv.c',
+ 'shared.c',
+ 'sink.c',
+ 'sink-input.c',
+ 'sioman.c',
+ 'sound-file-stream.c',
+ 'sound-file.c',
+ 'source.c',
+ 'source-output.c',
+ 'start-child.c',
+ 'stream-util.c',
+ 'svolume_arm.c',
+ 'svolume_c.c',
+ 'svolume_mmx.c',
+ 'svolume_sse.c',
+ 'thread-mq.c',
+]
+
+libpulsecore_headers = [
+ 'asyncmsgq.h',
+ 'asyncq.h',
+ 'auth-cookie.h',
+ 'card.h',
+ 'cli-command.h',
+ 'cli-text.h',
+ 'client.h',
+ 'core.h',
+ 'core-scache.h',
+ 'core-subscribe.h',
+ 'cpu.h',
+ 'cpu-arm.h',
+ 'cpu-orc.h',
+ 'cpu-x86.h',
+ 'database.h',
+ 'device-port.h',
+ 'ffmpeg/avcodec.h',
+ 'ffmpeg/dsputil.h',
+ 'filter/biquad.h',
+ 'filter/crossover.h',
+ 'filter/lfe-filter.h',
+ 'hook-list.h',
+ 'ltdl-helper.h',
+ 'message-handler.h',
+ 'mix.h',
+ 'modargs.h',
+ 'modinfo.h',
+ 'module.h',
+ 'msgobject.h',
+ 'namereg.h',
+ 'object.h',
+ 'play-memblockq.h',
+ 'play-memchunk.h',
+ 'remap.h',
+ 'resampler.h',
+ 'rtpoll.h',
+ 'sconv.h',
+ 'sconv-s16be.h',
+ 'sconv-s16le.h',
+ 'shared.h',
+ 'sink-input.h',
+ 'sink.h',
+ 'sioman.h',
+ 'sound-file-stream.h',
+ 'sound-file.h',
+ 'source-output.h',
+ 'source.h',
+ 'start-child.h',
+ 'stream-util.h',
+ 'thread-mq.h',
+ 'typedefs.h',
+]
+
+if get_option('database') == 'tdb'
+ libpulsecore_sources += 'database-tdb.c'
+ database_c_args = '-DHAVE_TDB'
+elif get_option('database') == 'gdbm'
+ libpulsecore_sources += 'database-gdbm.c'
+ database_c_args = '-DHAVE_GDBM'
+else
+ libpulsecore_sources += 'database-simple.c'
+ database_c_args = '-DHAVE_SIMPLEDB'
+endif
+
+if dbus_dep.found()
+ libpulsecore_sources += [
+ 'dbus-shared.c',
+ 'protocol-dbus.c',
+ ]
+ libpulsecore_headers += [
+ 'dbus-shared.h',
+ 'protocol-dbus.h',
+ ]
+endif
+
+if samplerate_dep.found()
+ libpulsecore_sources += ['resampler/libsamplerate.c']
+endif
+
+if soxr_dep.found()
+ libpulsecore_sources += ['resampler/soxr.c']
+endif
+
+if speex_dep.found()
+ libpulsecore_sources += ['resampler/speex.c']
+endif
+
+if x11_dep.found()
+ libpulsecore_sources += ['x11wrap.c']
+ libpulsecore_headers += ['x11wrap.h']
+endif
+
+orc_sources = []
+orc_headers = []
+if have_orcc
+ orcsrc = 'svolume'
+ orc_h = custom_target(orcsrc + '-orc-gen.h',
+ input : orcsrc + '.orc',
+ output : orcsrc + '-orc-gen.h',
+ command : orcc_args + ['--header', '-o', '@OUTPUT@', '@INPUT@']
+ )
+ orc_c = custom_target(orcsrc + '-orc-gen.c',
+ input : orcsrc + '.orc',
+ output : orcsrc + '-orc-gen.c',
+ command : orcc_args + ['--implementation', '-o', '@OUTPUT@', '@INPUT@']
+ )
+ orc_sources = [orc_c, 'svolume_orc.c']
+ orc_headers = [orc_h]
+endif
+
+# FIXME: walk through dependencies and add files
+
+# FIXME: SIMD support (ORC)
+simd = import('unstable-simd')
+libpulsecore_simd = simd.check('libpulsecore_simd',
+ mmx : ['remap_mmx.c', 'svolume_mmx.c'],
+ sse : ['remap_sse.c', 'sconv_sse.c', 'svolume_sse.c'],
+ neon : ['remap_neon.c', 'sconv_neon.c', 'mix_neon.c'],
+ c_args : [pa_c_args],
+ include_directories : [configinc, topinc],
+ implicit_include_directories : false,
+ compiler : cc)
+libpulsecore_simd_lib = libpulsecore_simd[0]
+cdata.merge_from(libpulsecore_simd[1])
+
+# FIXME: Implement Windows support
+#'mutex-win32.c',
+#'poll-win32.c',
+#'semaphore-win32.c',
+#'thread-win32.c',
+
+libpulsecore = shared_library('pulsecore-' + pa_version_major_minor,
+ libpulsecore_sources, libpulsecore_headers,
+ orc_sources, orc_headers,
+ include_directories : [configinc, topinc],
+ c_args : [pa_c_args, server_c_args],
+ link_args : [nodelete_link_args],
+ install : true,
+ install_rpath : privlibdir,
+ install_dir : privlibdir,
+ link_with : libpulsecore_simd_lib,
+ dependencies : [libm_dep, libpulsecommon_dep, ltdl_dep, shm_dep, sndfile_dep, database_dep, dbus_dep, libatomic_ops_dep, orc_dep, samplerate_dep, soxr_dep, speex_dep, x11_dep, libintl_dep],
+ implicit_include_directories : false)
+
+libpulsecore_dep = declare_dependency(link_with: libpulsecore)
+
+# Internal libraries for modules
+# TODO: understand 'c_args' and 'dependencies' better, maybe we can remove some
+
+libavahi_wrap = shared_library('avahi-wrap',
+ 'avahi-wrap.c',
+ 'avahi-wrap.h',
+ c_args : [pa_c_args, server_c_args, database_c_args],
+ link_args : [nodelete_link_args],
+ include_directories : [configinc, topinc],
+ dependencies : [libpulse_dep, libpulsecommon_dep, libpulsecore_dep, avahi_dep],
+ implicit_include_directories : false, # pulsecore/poll.h <vs> /usr/include/poll.h
+ install : true,
+ install_rpath : privlibdir,
+ install_dir : modlibexecdir,
+)
+
+libcli = shared_library('cli',
+ 'cli.c',
+ 'cli.h',
+ c_args : [pa_c_args, server_c_args, database_c_args],
+ link_args : [nodelete_link_args],
+ include_directories : [configinc, topinc],
+ dependencies : [libpulse_dep, libpulsecommon_dep, libpulsecore_dep],
+ install : true,
+ install_rpath : privlibdir,
+ install_dir : modlibexecdir,
+)
+
+libcli_dep = declare_dependency(link_with: libcli)
+
+# FIXME: meson doesn't support multiple RPATH arguments currently
+rpath_dirs = join_paths(privlibdir) + ':' + join_paths(modlibexecdir)
+
+libprotocol_cli = shared_library('protocol-cli',
+ 'protocol-cli.c',
+ 'protocol-cli.h',
+ c_args : [pa_c_args, server_c_args, database_c_args],
+ link_args : [nodelete_link_args],
+ include_directories : [configinc, topinc],
+ dependencies : [libpulse_dep, libpulsecommon_dep, libpulsecore_dep, libcli_dep],
+ install : true,
+ install_rpath : rpath_dirs,
+ install_dir : modlibexecdir,
+)
+
+libprotocol_http = shared_library('protocol-http',
+ ['protocol-http.c', 'mime-type.c'],
+ ['protocol-http.h', 'mime-type.h'],
+ c_args : [pa_c_args, server_c_args, database_c_args],
+ link_args : [nodelete_link_args],
+ include_directories : [configinc, topinc],
+ dependencies : [libpulse_dep, libpulsecommon_dep, libpulsecore_dep],
+ install : true,
+ install_rpath : privlibdir,
+ install_dir : modlibexecdir,
+)
+
+libprotocol_native = shared_library('protocol-native',
+ 'protocol-native.c',
+ ['protocol-native.h', 'native-common.h'],
+ c_args : [pa_c_args, server_c_args, database_c_args],
+ link_args : [nodelete_link_args],
+ include_directories : [configinc, topinc],
+ dependencies : [libpulse_dep, libpulsecommon_dep, libpulsecore_dep, dbus_dep],
+ install : true,
+ install_rpath : privlibdir,
+ install_dir : modlibexecdir,
+)
+
+libprotocol_simple = shared_library('protocol-simple',
+ 'protocol-simple.c',
+ 'protocol-simple.h',
+ c_args : [pa_c_args, server_c_args, database_c_args],
+ link_args : [nodelete_link_args],
+ include_directories : [configinc, topinc],
+ dependencies : [libpulse_dep, libpulsecommon_dep, libpulsecore_dep],
+ install : true,
+ install_rpath : privlibdir,
+ install_dir : modlibexecdir,
+)
diff --git a/src/pulsecore/message-handler.c b/src/pulsecore/message-handler.c
new file mode 100644
index 0000000..7555a18
--- /dev/null
+++ b/src/pulsecore/message-handler.c
@@ -0,0 +1,104 @@
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/core.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+
+#include "message-handler.h"
+
+/* Message handler functions */
+
+/* Register message handler for the specified object. object_path must be a unique name starting with "/". */
+void pa_message_handler_register(pa_core *c, const char *object_path, const char *description, pa_message_handler_cb_t cb, void *userdata) {
+ struct pa_message_handler *handler;
+
+ pa_assert(c);
+ pa_assert(object_path);
+ pa_assert(cb);
+ pa_assert(userdata);
+
+ /* Ensure that the object path is not empty and starts with "/". */
+ pa_assert(object_path[0] == '/');
+
+ handler = pa_xnew0(struct pa_message_handler, 1);
+ handler->userdata = userdata;
+ handler->callback = cb;
+ handler->object_path = pa_xstrdup(object_path);
+ handler->description = pa_xstrdup(description);
+
+ pa_assert_se(pa_hashmap_put(c->message_handlers, handler->object_path, handler) == 0);
+}
+
+/* Unregister a message handler */
+void pa_message_handler_unregister(pa_core *c, const char *object_path) {
+ struct pa_message_handler *handler;
+
+ pa_assert(c);
+ pa_assert(object_path);
+
+ pa_assert_se(handler = pa_hashmap_remove(c->message_handlers, object_path));
+
+ pa_xfree(handler->object_path);
+ pa_xfree(handler->description);
+ pa_xfree(handler);
+}
+
+/* Send a message to an object identified by object_path */
+int pa_message_handler_send_message(pa_core *c, const char *object_path, const char *message, const char *message_parameters, char **response) {
+ struct pa_message_handler *handler;
+
+ pa_assert(c);
+ pa_assert(object_path);
+ pa_assert(message);
+ pa_assert(response);
+
+ *response = NULL;
+
+ if (!(handler = pa_hashmap_get(c->message_handlers, object_path)))
+ return -PA_ERR_NOENTITY;
+
+ /* The handler is expected to return an error code and may also
+ return an error string in response */
+ return handler->callback(handler->object_path, message, message_parameters, response, handler->userdata);
+}
+
+/* Set handler description */
+int pa_message_handler_set_description(pa_core *c, const char *object_path, const char *description) {
+ struct pa_message_handler *handler;
+
+ pa_assert(c);
+ pa_assert(object_path);
+
+ if (!(handler = pa_hashmap_get(c->message_handlers, object_path)))
+ return -PA_ERR_NOENTITY;
+
+ pa_xfree(handler->description);
+ handler->description = pa_xstrdup(description);
+
+ return PA_OK;
+}
diff --git a/src/pulsecore/message-handler.h b/src/pulsecore/message-handler.h
new file mode 100644
index 0000000..be94510
--- /dev/null
+++ b/src/pulsecore/message-handler.h
@@ -0,0 +1,50 @@
+#ifndef foocoremessageshfoo
+#define foocoremessageshfoo
+
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulsecore/core.h>
+
+/* Message handler types and functions */
+
+/* Prototype for message callback */
+typedef int (*pa_message_handler_cb_t)(
+ const char *object_path,
+ const char *message,
+ const char *message_parameters,
+ char **response,
+ void *userdata);
+
+/* Message handler object */
+struct pa_message_handler {
+ char *object_path;
+ char *description;
+ pa_message_handler_cb_t callback;
+ void *userdata;
+};
+
+/* Handler registration */
+void pa_message_handler_register(pa_core *c, const char *object_path, const char *description, pa_message_handler_cb_t cb, void *userdata);
+void pa_message_handler_unregister(pa_core *c, const char *object_path);
+
+/* Send message to the specified object path */
+int pa_message_handler_send_message(pa_core *c, const char *object_path, const char *message, const char *message_parameters, char **response);
+
+/* Set handler description */
+int pa_message_handler_set_description(pa_core *c, const char *object_path, const char *description);
+#endif
diff --git a/src/pulsecore/mime-type.c b/src/pulsecore/mime-type.c
new file mode 100644
index 0000000..b7157b4
--- /dev/null
+++ b/src/pulsecore/mime-type.c
@@ -0,0 +1,179 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2005-2009 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/core-util.h>
+
+#include "mime-type.h"
+
+bool pa_sample_spec_is_mime(const pa_sample_spec *ss, const pa_channel_map *cm) {
+
+ pa_assert(pa_channel_map_compatible(cm, ss));
+
+ switch (ss->format) {
+ case PA_SAMPLE_S16BE:
+ case PA_SAMPLE_S24BE:
+ case PA_SAMPLE_U8:
+
+ if (ss->rate != 8000 &&
+ ss->rate != 11025 &&
+ ss->rate != 16000 &&
+ ss->rate != 22050 &&
+ ss->rate != 24000 &&
+ ss->rate != 32000 &&
+ ss->rate != 44100 &&
+ ss->rate != 48000)
+ return false;
+
+ if (ss->channels != 1 &&
+ ss->channels != 2)
+ return false;
+
+ if ((cm->channels == 1 && cm->map[0] != PA_CHANNEL_POSITION_MONO) ||
+ (cm->channels == 2 && (cm->map[0] != PA_CHANNEL_POSITION_LEFT || cm->map[1] != PA_CHANNEL_POSITION_RIGHT)))
+ return false;
+
+ return true;
+
+ case PA_SAMPLE_ULAW:
+
+ if (ss->rate != 8000)
+ return false;
+
+ if (ss->channels != 1)
+ return false;
+
+ if (cm->map[0] != PA_CHANNEL_POSITION_MONO)
+ return false;
+
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+void pa_sample_spec_mimefy(pa_sample_spec *ss, pa_channel_map *cm) {
+
+ pa_assert(pa_channel_map_compatible(cm, ss));
+
+ /* Turns the sample type passed in into the next 'better' one that
+ * can be encoded for HTTP. If there is no 'better' one we pick
+ * the 'best' one that is 'worse'. */
+
+ if (ss->channels > 2)
+ ss->channels = 2;
+
+ if (ss->rate > 44100)
+ ss->rate = 48000;
+ else if (ss->rate > 32000)
+ ss->rate = 44100;
+ else if (ss->rate > 24000)
+ ss->rate = 32000;
+ else if (ss->rate > 22050)
+ ss->rate = 24000;
+ else if (ss->rate > 16000)
+ ss->rate = 22050;
+ else if (ss->rate > 11025)
+ ss->rate = 16000;
+ else if (ss->rate > 8000)
+ ss->rate = 11025;
+ else
+ ss->rate = 8000;
+
+ switch (ss->format) {
+ case PA_SAMPLE_S24BE:
+ case PA_SAMPLE_S24LE:
+ case PA_SAMPLE_S24_32LE:
+ case PA_SAMPLE_S24_32BE:
+ case PA_SAMPLE_S32LE:
+ case PA_SAMPLE_S32BE:
+ case PA_SAMPLE_FLOAT32LE:
+ case PA_SAMPLE_FLOAT32BE:
+ ss->format = PA_SAMPLE_S24BE;
+ break;
+
+ case PA_SAMPLE_S16BE:
+ case PA_SAMPLE_S16LE:
+ ss->format = PA_SAMPLE_S16BE;
+ break;
+
+ case PA_SAMPLE_ULAW:
+ case PA_SAMPLE_ALAW:
+
+ if (ss->rate == 8000 && ss->channels == 1)
+ ss->format = PA_SAMPLE_ULAW;
+ else
+ ss->format = PA_SAMPLE_S16BE;
+ break;
+
+ case PA_SAMPLE_U8:
+ ss->format = PA_SAMPLE_U8;
+ break;
+
+ case PA_SAMPLE_MAX:
+ case PA_SAMPLE_INVALID:
+ pa_assert_not_reached();
+ }
+
+ pa_channel_map_init_auto(cm, ss->channels, PA_CHANNEL_MAP_DEFAULT);
+
+ pa_assert(pa_sample_spec_is_mime(ss, cm));
+}
+
+char *pa_sample_spec_to_mime_type(const pa_sample_spec *ss, const pa_channel_map *cm) {
+ pa_assert(pa_channel_map_compatible(cm, ss));
+ pa_assert(pa_sample_spec_valid(ss));
+
+ if (!pa_sample_spec_is_mime(ss, cm))
+ return NULL;
+
+ switch (ss->format) {
+
+ case PA_SAMPLE_S16BE:
+ case PA_SAMPLE_S24BE:
+ case PA_SAMPLE_U8:
+ /* Stupid UPnP implementations (PS3...) choke on spaces in
+ * the mime type, that's why we write only ';' here,
+ * instead of '; '. */
+ return pa_sprintf_malloc("audio/%s;rate=%u;channels=%u",
+ ss->format == PA_SAMPLE_S16BE ? "L16" :
+ (ss->format == PA_SAMPLE_S24BE ? "L24" : "L8"),
+ ss->rate, ss->channels);
+
+ case PA_SAMPLE_ULAW:
+ return pa_xstrdup("audio/basic");
+
+ default:
+ pa_assert_not_reached();
+ }
+}
+
+char *pa_sample_spec_to_mime_type_mimefy(const pa_sample_spec *_ss, const pa_channel_map *_cm) {
+ pa_sample_spec ss = *_ss;
+ pa_channel_map cm = *_cm;
+
+ pa_sample_spec_mimefy(&ss, &cm);
+
+ return pa_sample_spec_to_mime_type(&ss, &cm);
+}
diff --git a/src/pulsecore/mime-type.h b/src/pulsecore/mime-type.h
new file mode 100644
index 0000000..f07f455
--- /dev/null
+++ b/src/pulsecore/mime-type.h
@@ -0,0 +1,31 @@
+#ifndef foopulsecoremimetypehfoo
+#define foopulsecoremimetypehfoo
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2009 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulsecore/macro.h>
+#include <pulse/sample.h>
+#include <pulse/channelmap.h>
+
+bool pa_sample_spec_is_mime(const pa_sample_spec *ss, const pa_channel_map *cm);
+void pa_sample_spec_mimefy(pa_sample_spec *ss, pa_channel_map *cm);
+char *pa_sample_spec_to_mime_type(const pa_sample_spec *ss, const pa_channel_map *cm);
+char *pa_sample_spec_to_mime_type_mimefy(const pa_sample_spec *_ss, const pa_channel_map *_cm);
+
+#endif
diff --git a/src/pulsecore/mix.c b/src/pulsecore/mix.c
new file mode 100644
index 0000000..59622d7
--- /dev/null
+++ b/src/pulsecore/mix.c
@@ -0,0 +1,726 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+ Copyright 2013 Peter Meerwald <pmeerw@pmeerw.net>
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <math.h>
+
+#include <pulsecore/sample-util.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/g711.h>
+#include <pulsecore/endianmacros.h>
+
+#include "cpu.h"
+#include "mix.h"
+
+#define VOLUME_PADDING 32
+
+static void calc_linear_integer_volume(int32_t linear[], const pa_cvolume *volume) {
+ unsigned channel, nchannels, padding;
+
+ pa_assert(linear);
+ pa_assert(volume);
+
+ nchannels = volume->channels;
+
+ for (channel = 0; channel < nchannels; channel++)
+ linear[channel] = (int32_t) lrint(pa_sw_volume_to_linear(volume->values[channel]) * 0x10000);
+
+ for (padding = 0; padding < VOLUME_PADDING; padding++, channel++)
+ linear[channel] = linear[padding];
+}
+
+static void calc_linear_float_volume(float linear[], const pa_cvolume *volume) {
+ unsigned channel, nchannels, padding;
+
+ pa_assert(linear);
+ pa_assert(volume);
+
+ nchannels = volume->channels;
+
+ for (channel = 0; channel < nchannels; channel++)
+ linear[channel] = (float) pa_sw_volume_to_linear(volume->values[channel]);
+
+ for (padding = 0; padding < VOLUME_PADDING; padding++, channel++)
+ linear[channel] = linear[padding];
+}
+
+static void calc_linear_integer_stream_volumes(pa_mix_info streams[], unsigned nstreams, const pa_cvolume *volume, const pa_sample_spec *spec) {
+ unsigned k, channel;
+ float linear[PA_CHANNELS_MAX + VOLUME_PADDING];
+
+ pa_assert(streams);
+ pa_assert(spec);
+ pa_assert(volume);
+
+ calc_linear_float_volume(linear, volume);
+
+ for (k = 0; k < nstreams; k++) {
+
+ for (channel = 0; channel < spec->channels; channel++) {
+ pa_mix_info *m = streams + k;
+ m->linear[channel].i = (int32_t) lrint(pa_sw_volume_to_linear(m->volume.values[channel]) * linear[channel] * 0x10000);
+ }
+ }
+}
+
+static void calc_linear_float_stream_volumes(pa_mix_info streams[], unsigned nstreams, const pa_cvolume *volume, const pa_sample_spec *spec) {
+ unsigned k, channel;
+ float linear[PA_CHANNELS_MAX + VOLUME_PADDING];
+
+ pa_assert(streams);
+ pa_assert(spec);
+ pa_assert(volume);
+
+ calc_linear_float_volume(linear, volume);
+
+ for (k = 0; k < nstreams; k++) {
+
+ for (channel = 0; channel < spec->channels; channel++) {
+ pa_mix_info *m = streams + k;
+ m->linear[channel].f = (float) (pa_sw_volume_to_linear(m->volume.values[channel]) * linear[channel]);
+ }
+ }
+}
+
+typedef void (*pa_calc_stream_volumes_func_t) (pa_mix_info streams[], unsigned nstreams, const pa_cvolume *volume, const pa_sample_spec *spec);
+
+static const pa_calc_stream_volumes_func_t calc_stream_volumes_table[] = {
+ [PA_SAMPLE_U8] = (pa_calc_stream_volumes_func_t) calc_linear_integer_stream_volumes,
+ [PA_SAMPLE_ALAW] = (pa_calc_stream_volumes_func_t) calc_linear_integer_stream_volumes,
+ [PA_SAMPLE_ULAW] = (pa_calc_stream_volumes_func_t) calc_linear_integer_stream_volumes,
+ [PA_SAMPLE_S16LE] = (pa_calc_stream_volumes_func_t) calc_linear_integer_stream_volumes,
+ [PA_SAMPLE_S16BE] = (pa_calc_stream_volumes_func_t) calc_linear_integer_stream_volumes,
+ [PA_SAMPLE_FLOAT32LE] = (pa_calc_stream_volumes_func_t) calc_linear_float_stream_volumes,
+ [PA_SAMPLE_FLOAT32BE] = (pa_calc_stream_volumes_func_t) calc_linear_float_stream_volumes,
+ [PA_SAMPLE_S32LE] = (pa_calc_stream_volumes_func_t) calc_linear_integer_stream_volumes,
+ [PA_SAMPLE_S32BE] = (pa_calc_stream_volumes_func_t) calc_linear_integer_stream_volumes,
+ [PA_SAMPLE_S24LE] = (pa_calc_stream_volumes_func_t) calc_linear_integer_stream_volumes,
+ [PA_SAMPLE_S24BE] = (pa_calc_stream_volumes_func_t) calc_linear_integer_stream_volumes,
+ [PA_SAMPLE_S24_32LE] = (pa_calc_stream_volumes_func_t) calc_linear_integer_stream_volumes,
+ [PA_SAMPLE_S24_32BE] = (pa_calc_stream_volumes_func_t) calc_linear_integer_stream_volumes
+};
+
+/* special case: mix 2 s16ne streams, 1 channel each */
+static void pa_mix2_ch1_s16ne(pa_mix_info streams[], int16_t *data, unsigned length) {
+ const int16_t *ptr0 = streams[0].ptr;
+ const int16_t *ptr1 = streams[1].ptr;
+
+ const int32_t cv0 = streams[0].linear[0].i;
+ const int32_t cv1 = streams[1].linear[0].i;
+
+ length /= sizeof(int16_t);
+
+ for (; length > 0; length--) {
+ int32_t sum;
+
+ sum = pa_mult_s16_volume(*ptr0++, cv0);
+ sum += pa_mult_s16_volume(*ptr1++, cv1);
+
+ sum = PA_CLAMP_UNLIKELY(sum, -0x8000, 0x7FFF);
+ *data++ = sum;
+ }
+}
+
+/* special case: mix 2 s16ne streams, 2 channels each */
+static void pa_mix2_ch2_s16ne(pa_mix_info streams[], int16_t *data, unsigned length) {
+ const int16_t *ptr0 = streams[0].ptr;
+ const int16_t *ptr1 = streams[1].ptr;
+
+ length /= sizeof(int16_t) * 2;
+
+ for (; length > 0; length--) {
+ int32_t sum;
+
+ sum = pa_mult_s16_volume(*ptr0++, streams[0].linear[0].i);
+ sum += pa_mult_s16_volume(*ptr1++, streams[1].linear[0].i);
+
+ sum = PA_CLAMP_UNLIKELY(sum, -0x8000, 0x7FFF);
+ *data++ = sum;
+
+ sum = pa_mult_s16_volume(*ptr0++, streams[0].linear[1].i);
+ sum += pa_mult_s16_volume(*ptr1++, streams[1].linear[1].i);
+
+ sum = PA_CLAMP_UNLIKELY(sum, -0x8000, 0x7FFF);
+ *data++ = sum;
+ }
+}
+
+/* special case: mix 2 s16ne streams */
+static void pa_mix2_s16ne(pa_mix_info streams[], unsigned channels, int16_t *data, unsigned length) {
+ const int16_t *ptr0 = streams[0].ptr;
+ const int16_t *ptr1 = streams[1].ptr;
+ unsigned channel = 0;
+
+ length /= sizeof(int16_t);
+
+ for (; length > 0; length--) {
+ int32_t sum;
+
+ sum = pa_mult_s16_volume(*ptr0++, streams[0].linear[channel].i);
+ sum += pa_mult_s16_volume(*ptr1++, streams[1].linear[channel].i);
+
+ sum = PA_CLAMP_UNLIKELY(sum, -0x8000, 0x7FFF);
+ *data++ = sum;
+
+ if (PA_UNLIKELY(++channel >= channels))
+ channel = 0;
+ }
+}
+
+/* special case: mix s16ne streams, 2 channels each */
+static void pa_mix_ch2_s16ne(pa_mix_info streams[], unsigned nstreams, int16_t *data, unsigned length) {
+
+ length /= sizeof(int16_t) * 2;
+
+ for (; length > 0; length--) {
+ int32_t sum0 = 0, sum1 = 0;
+ unsigned i;
+
+ for (i = 0; i < nstreams; i++) {
+ pa_mix_info *m = streams + i;
+ int32_t cv0 = m->linear[0].i;
+ int32_t cv1 = m->linear[1].i;
+
+ sum0 += pa_mult_s16_volume(*((int16_t*) m->ptr), cv0);
+ m->ptr = (uint8_t*) m->ptr + sizeof(int16_t);
+
+ sum1 += pa_mult_s16_volume(*((int16_t*) m->ptr), cv1);
+ m->ptr = (uint8_t*) m->ptr + sizeof(int16_t);
+ }
+
+ *data++ = PA_CLAMP_UNLIKELY(sum0, -0x8000, 0x7FFF);
+ *data++ = PA_CLAMP_UNLIKELY(sum1, -0x8000, 0x7FFF);
+ }
+}
+
+static void pa_mix_generic_s16ne(pa_mix_info streams[], unsigned nstreams, unsigned channels, int16_t *data, unsigned length) {
+ unsigned channel = 0;
+
+ length /= sizeof(int16_t);
+
+ for (; length > 0; length--) {
+ int32_t sum = 0;
+ unsigned i;
+
+ for (i = 0; i < nstreams; i++) {
+ pa_mix_info *m = streams + i;
+ int32_t cv = m->linear[channel].i;
+
+ if (PA_LIKELY(cv > 0))
+ sum += pa_mult_s16_volume(*((int16_t*) m->ptr), cv);
+ m->ptr = (uint8_t*) m->ptr + sizeof(int16_t);
+ }
+
+ sum = PA_CLAMP_UNLIKELY(sum, -0x8000, 0x7FFF);
+ *data++ = sum;
+
+ if (PA_UNLIKELY(++channel >= channels))
+ channel = 0;
+ }
+}
+
+static void pa_mix_s16ne_c(pa_mix_info streams[], unsigned nstreams, unsigned channels, int16_t *data, unsigned length) {
+ if (nstreams == 2 && channels == 1)
+ pa_mix2_ch1_s16ne(streams, data, length);
+ else if (nstreams == 2 && channels == 2)
+ pa_mix2_ch2_s16ne(streams, data, length);
+ else if (nstreams == 2)
+ pa_mix2_s16ne(streams, channels, data, length);
+ else if (channels == 2)
+ pa_mix_ch2_s16ne(streams, nstreams, data, length);
+ else
+ pa_mix_generic_s16ne(streams, nstreams, channels, data, length);
+}
+
+static void pa_mix_s16re_c(pa_mix_info streams[], unsigned nstreams, unsigned channels, int16_t *data, unsigned length) {
+ unsigned channel = 0;
+
+ length /= sizeof(int16_t);
+
+ for (; length > 0; length--, data++) {
+ int32_t sum = 0;
+ unsigned i;
+
+ for (i = 0; i < nstreams; i++) {
+ pa_mix_info *m = streams + i;
+ int32_t cv = m->linear[channel].i;
+
+ if (PA_LIKELY(cv > 0))
+ sum += pa_mult_s16_volume(PA_INT16_SWAP(*((int16_t*) m->ptr)), cv);
+ m->ptr = (uint8_t*) m->ptr + sizeof(int16_t);
+ }
+
+ sum = PA_CLAMP_UNLIKELY(sum, -0x8000, 0x7FFF);
+ *data = PA_INT16_SWAP((int16_t) sum);
+
+ if (PA_UNLIKELY(++channel >= channels))
+ channel = 0;
+ }
+}
+
+static void pa_mix_s32ne_c(pa_mix_info streams[], unsigned nstreams, unsigned channels, int32_t *data, unsigned length) {
+ unsigned channel = 0;
+
+ length /= sizeof(int32_t);
+
+ for (; length > 0; length--, data++) {
+ int64_t sum = 0;
+ unsigned i;
+
+ for (i = 0; i < nstreams; i++) {
+ pa_mix_info *m = streams + i;
+ int32_t cv = m->linear[channel].i;
+ int64_t v;
+
+ if (PA_LIKELY(cv > 0)) {
+ v = *((int32_t*) m->ptr);
+ v = (v * cv) >> 16;
+ sum += v;
+ }
+ m->ptr = (uint8_t*) m->ptr + sizeof(int32_t);
+ }
+
+ sum = PA_CLAMP_UNLIKELY(sum, -0x80000000LL, 0x7FFFFFFFLL);
+ *data = (int32_t) sum;
+
+ if (PA_UNLIKELY(++channel >= channels))
+ channel = 0;
+ }
+}
+
+static void pa_mix_s32re_c(pa_mix_info streams[], unsigned nstreams, unsigned channels, int32_t *data, unsigned length) {
+ unsigned channel = 0;
+
+ length /= sizeof(int32_t);
+
+ for (; length > 0; length--, data++) {
+ int64_t sum = 0;
+ unsigned i;
+
+ for (i = 0; i < nstreams; i++) {
+ pa_mix_info *m = streams + i;
+ int32_t cv = m->linear[channel].i;
+ int64_t v;
+
+ if (PA_LIKELY(cv > 0)) {
+ v = PA_INT32_SWAP(*((int32_t*) m->ptr));
+ v = (v * cv) >> 16;
+ sum += v;
+ }
+ m->ptr = (uint8_t*) m->ptr + sizeof(int32_t);
+ }
+
+ sum = PA_CLAMP_UNLIKELY(sum, -0x80000000LL, 0x7FFFFFFFLL);
+ *data = PA_INT32_SWAP((int32_t) sum);
+
+ if (PA_UNLIKELY(++channel >= channels))
+ channel = 0;
+ }
+}
+
+static void pa_mix_s24ne_c(pa_mix_info streams[], unsigned nstreams, unsigned channels, uint8_t *data, unsigned length) {
+ unsigned channel = 0;
+
+ for (; length > 0; length -= 3, data += 3) {
+ int64_t sum = 0;
+ unsigned i;
+
+ for (i = 0; i < nstreams; i++) {
+ pa_mix_info *m = streams + i;
+ int32_t cv = m->linear[channel].i;
+ int64_t v;
+
+ if (PA_LIKELY(cv > 0)) {
+ v = (int32_t) (PA_READ24NE(m->ptr) << 8);
+ v = (v * cv) >> 16;
+ sum += v;
+ }
+ m->ptr = (uint8_t*) m->ptr + 3;
+ }
+
+ sum = PA_CLAMP_UNLIKELY(sum, -0x80000000LL, 0x7FFFFFFFLL);
+ PA_WRITE24NE(data, ((uint32_t) sum) >> 8);
+
+ if (PA_UNLIKELY(++channel >= channels))
+ channel = 0;
+ }
+}
+
+static void pa_mix_s24re_c(pa_mix_info streams[], unsigned nstreams, unsigned channels, uint8_t *data, unsigned length) {
+ unsigned channel = 0;
+
+ for (; length > 0; length -= 3, data += 3) {
+ int64_t sum = 0;
+ unsigned i;
+
+ for (i = 0; i < nstreams; i++) {
+ pa_mix_info *m = streams + i;
+ int32_t cv = m->linear[channel].i;
+ int64_t v;
+
+ if (PA_LIKELY(cv > 0)) {
+ v = (int32_t) (PA_READ24RE(m->ptr) << 8);
+ v = (v * cv) >> 16;
+ sum += v;
+ }
+ m->ptr = (uint8_t*) m->ptr + 3;
+ }
+
+ sum = PA_CLAMP_UNLIKELY(sum, -0x80000000LL, 0x7FFFFFFFLL);
+ PA_WRITE24RE(data, ((uint32_t) sum) >> 8);
+
+ if (PA_UNLIKELY(++channel >= channels))
+ channel = 0;
+ }
+}
+
+static void pa_mix_s24_32ne_c(pa_mix_info streams[], unsigned nstreams, unsigned channels, uint32_t *data, unsigned length) {
+ unsigned channel = 0;
+
+ length /= sizeof(uint32_t);
+
+ for (; length > 0; length--, data++) {
+ int64_t sum = 0;
+ unsigned i;
+
+ for (i = 0; i < nstreams; i++) {
+ pa_mix_info *m = streams + i;
+ int32_t cv = m->linear[channel].i;
+ int64_t v;
+
+ if (PA_LIKELY(cv > 0)) {
+ v = (int32_t) (*((uint32_t*)m->ptr) << 8);
+ v = (v * cv) >> 16;
+ sum += v;
+ }
+ m->ptr = (uint8_t*) m->ptr + sizeof(int32_t);
+ }
+
+ sum = PA_CLAMP_UNLIKELY(sum, -0x80000000LL, 0x7FFFFFFFLL);
+ *data = ((uint32_t) (int32_t) sum) >> 8;
+
+ if (PA_UNLIKELY(++channel >= channels))
+ channel = 0;
+ }
+}
+
+static void pa_mix_s24_32re_c(pa_mix_info streams[], unsigned nstreams, unsigned channels, uint32_t *data, unsigned length) {
+ unsigned channel = 0;
+
+ length /= sizeof(uint32_t);
+
+ for (; length > 0; length--, data++) {
+ int64_t sum = 0;
+ unsigned i;
+
+ for (i = 0; i < nstreams; i++) {
+ pa_mix_info *m = streams + i;
+ int32_t cv = m->linear[channel].i;
+ int64_t v;
+
+ if (PA_LIKELY(cv > 0)) {
+ v = (int32_t) (PA_UINT32_SWAP(*((uint32_t*) m->ptr)) << 8);
+ v = (v * cv) >> 16;
+ sum += v;
+ }
+ m->ptr = (uint8_t*) m->ptr + sizeof(int32_t);
+ }
+
+ sum = PA_CLAMP_UNLIKELY(sum, -0x80000000LL, 0x7FFFFFFFLL);
+ *data = PA_INT32_SWAP(((uint32_t) (int32_t) sum) >> 8);
+
+ if (PA_UNLIKELY(++channel >= channels))
+ channel = 0;
+ }
+}
+
+static void pa_mix_u8_c(pa_mix_info streams[], unsigned nstreams, unsigned channels, uint8_t *data, unsigned length) {
+ unsigned channel = 0;
+
+ length /= sizeof(uint8_t);
+
+ for (; length > 0; length--, data++) {
+ int32_t sum = 0;
+ unsigned i;
+
+ for (i = 0; i < nstreams; i++) {
+ pa_mix_info *m = streams + i;
+ int32_t v, cv = m->linear[channel].i;
+
+ if (PA_LIKELY(cv > 0)) {
+ v = (int32_t) *((uint8_t*) m->ptr) - 0x80;
+ v = (v * cv) >> 16;
+ sum += v;
+ }
+ m->ptr = (uint8_t*) m->ptr + 1;
+ }
+
+ sum = PA_CLAMP_UNLIKELY(sum, -0x80, 0x7F);
+ *data = (uint8_t) (sum + 0x80);
+
+ if (PA_UNLIKELY(++channel >= channels))
+ channel = 0;
+ }
+}
+
+static void pa_mix_ulaw_c(pa_mix_info streams[], unsigned nstreams, unsigned channels, uint8_t *data, unsigned length) {
+ unsigned channel = 0;
+
+ length /= sizeof(uint8_t);
+
+ for (; length > 0; length--, data++) {
+ int32_t sum = 0;
+ unsigned i;
+
+ for (i = 0; i < nstreams; i++) {
+ pa_mix_info *m = streams + i;
+ int32_t cv = m->linear[channel].i;
+
+ if (PA_LIKELY(cv > 0))
+ sum += pa_mult_s16_volume(st_ulaw2linear16(*((uint8_t*) m->ptr)), cv);
+ m->ptr = (uint8_t*) m->ptr + 1;
+ }
+
+ sum = PA_CLAMP_UNLIKELY(sum, -0x8000, 0x7FFF);
+ *data = (uint8_t) st_14linear2ulaw((int16_t) sum >> 2);
+
+ if (PA_UNLIKELY(++channel >= channels))
+ channel = 0;
+ }
+}
+
+static void pa_mix_alaw_c(pa_mix_info streams[], unsigned nstreams, unsigned channels, uint8_t *data, unsigned length) {
+ unsigned channel = 0;
+
+ length /= sizeof(uint8_t);
+
+ for (; length > 0; length--, data++) {
+ int32_t sum = 0;
+ unsigned i;
+
+ for (i = 0; i < nstreams; i++) {
+ pa_mix_info *m = streams + i;
+ int32_t cv = m->linear[channel].i;
+
+ if (PA_LIKELY(cv > 0))
+ sum += pa_mult_s16_volume(st_alaw2linear16(*((uint8_t*) m->ptr)), cv);
+ m->ptr = (uint8_t*) m->ptr + 1;
+ }
+
+ sum = PA_CLAMP_UNLIKELY(sum, -0x8000, 0x7FFF);
+ *data = (uint8_t) st_13linear2alaw((int16_t) sum >> 3);
+
+ if (PA_UNLIKELY(++channel >= channels))
+ channel = 0;
+ }
+}
+
+static void pa_mix_float32ne_c(pa_mix_info streams[], unsigned nstreams, unsigned channels, float *data, unsigned length) {
+ unsigned channel = 0;
+
+ length /= sizeof(float);
+
+ for (; length > 0; length--, data++) {
+ float sum = 0;
+ unsigned i;
+
+ for (i = 0; i < nstreams; i++) {
+ pa_mix_info *m = streams + i;
+ float v, cv = m->linear[channel].f;
+
+ if (PA_LIKELY(cv > 0)) {
+ v = *((float*) m->ptr);
+ v *= cv;
+ sum += v;
+ }
+ m->ptr = (uint8_t*) m->ptr + sizeof(float);
+ }
+
+ *data = sum;
+
+ if (PA_UNLIKELY(++channel >= channels))
+ channel = 0;
+ }
+}
+
+static void pa_mix_float32re_c(pa_mix_info streams[], unsigned nstreams, unsigned channels, float *data, unsigned length) {
+ unsigned channel = 0;
+
+ length /= sizeof(float);
+
+ for (; length > 0; length--, data++) {
+ float sum = 0;
+ unsigned i;
+
+ for (i = 0; i < nstreams; i++) {
+ pa_mix_info *m = streams + i;
+ float cv = m->linear[channel].f;
+
+ if (PA_LIKELY(cv > 0))
+ sum += PA_READ_FLOAT32RE(m->ptr) * cv;
+ m->ptr = (uint8_t*) m->ptr + sizeof(float);
+ }
+
+ PA_WRITE_FLOAT32RE(data, sum);
+
+ if (PA_UNLIKELY(++channel >= channels))
+ channel = 0;
+ }
+}
+
+static pa_do_mix_func_t do_mix_table[] = {
+ [PA_SAMPLE_U8] = (pa_do_mix_func_t) pa_mix_u8_c,
+ [PA_SAMPLE_ALAW] = (pa_do_mix_func_t) pa_mix_alaw_c,
+ [PA_SAMPLE_ULAW] = (pa_do_mix_func_t) pa_mix_ulaw_c,
+ [PA_SAMPLE_S16NE] = (pa_do_mix_func_t) pa_mix_s16ne_c,
+ [PA_SAMPLE_S16RE] = (pa_do_mix_func_t) pa_mix_s16re_c,
+ [PA_SAMPLE_FLOAT32NE] = (pa_do_mix_func_t) pa_mix_float32ne_c,
+ [PA_SAMPLE_FLOAT32RE] = (pa_do_mix_func_t) pa_mix_float32re_c,
+ [PA_SAMPLE_S32NE] = (pa_do_mix_func_t) pa_mix_s32ne_c,
+ [PA_SAMPLE_S32RE] = (pa_do_mix_func_t) pa_mix_s32re_c,
+ [PA_SAMPLE_S24NE] = (pa_do_mix_func_t) pa_mix_s24ne_c,
+ [PA_SAMPLE_S24RE] = (pa_do_mix_func_t) pa_mix_s24re_c,
+ [PA_SAMPLE_S24_32NE] = (pa_do_mix_func_t) pa_mix_s24_32ne_c,
+ [PA_SAMPLE_S24_32RE] = (pa_do_mix_func_t) pa_mix_s24_32re_c
+};
+
+void pa_mix_func_init(const pa_cpu_info *cpu_info) {
+ if (cpu_info->force_generic_code)
+ do_mix_table[PA_SAMPLE_S16NE] = (pa_do_mix_func_t) pa_mix_generic_s16ne;
+ else
+ do_mix_table[PA_SAMPLE_S16NE] = (pa_do_mix_func_t) pa_mix_s16ne_c;
+}
+
+size_t pa_mix(
+ pa_mix_info streams[],
+ unsigned nstreams,
+ void *data,
+ size_t length,
+ const pa_sample_spec *spec,
+ const pa_cvolume *volume,
+ bool mute) {
+
+ pa_cvolume full_volume;
+ unsigned k;
+
+ pa_assert(streams);
+ pa_assert(data);
+ pa_assert(length);
+ pa_assert(spec);
+ pa_assert(nstreams > 1);
+
+ if (!volume)
+ volume = pa_cvolume_reset(&full_volume, spec->channels);
+
+ if (mute || pa_cvolume_is_muted(volume)) {
+ pa_silence_memory(data, length, spec);
+ return length;
+ }
+
+ for (k = 0; k < nstreams; k++) {
+ pa_assert(length <= streams[k].chunk.length);
+ streams[k].ptr = pa_memblock_acquire_chunk(&streams[k].chunk);
+ }
+
+ calc_stream_volumes_table[spec->format](streams, nstreams, volume, spec);
+ do_mix_table[spec->format](streams, nstreams, spec->channels, data, length);
+
+ for (k = 0; k < nstreams; k++)
+ pa_memblock_release(streams[k].chunk.memblock);
+
+ return length;
+}
+
+pa_do_mix_func_t pa_get_mix_func(pa_sample_format_t f) {
+ pa_assert(pa_sample_format_valid(f));
+
+ return do_mix_table[f];
+}
+
+void pa_set_mix_func(pa_sample_format_t f, pa_do_mix_func_t func) {
+ pa_assert(pa_sample_format_valid(f));
+
+ do_mix_table[f] = func;
+}
+
+typedef union {
+ float f;
+ uint32_t i;
+} volume_val;
+
+typedef void (*pa_calc_volume_func_t) (void *volumes, const pa_cvolume *volume);
+
+static const pa_calc_volume_func_t calc_volume_table[] = {
+ [PA_SAMPLE_U8] = (pa_calc_volume_func_t) calc_linear_integer_volume,
+ [PA_SAMPLE_ALAW] = (pa_calc_volume_func_t) calc_linear_integer_volume,
+ [PA_SAMPLE_ULAW] = (pa_calc_volume_func_t) calc_linear_integer_volume,
+ [PA_SAMPLE_S16LE] = (pa_calc_volume_func_t) calc_linear_integer_volume,
+ [PA_SAMPLE_S16BE] = (pa_calc_volume_func_t) calc_linear_integer_volume,
+ [PA_SAMPLE_FLOAT32LE] = (pa_calc_volume_func_t) calc_linear_float_volume,
+ [PA_SAMPLE_FLOAT32BE] = (pa_calc_volume_func_t) calc_linear_float_volume,
+ [PA_SAMPLE_S32LE] = (pa_calc_volume_func_t) calc_linear_integer_volume,
+ [PA_SAMPLE_S32BE] = (pa_calc_volume_func_t) calc_linear_integer_volume,
+ [PA_SAMPLE_S24LE] = (pa_calc_volume_func_t) calc_linear_integer_volume,
+ [PA_SAMPLE_S24BE] = (pa_calc_volume_func_t) calc_linear_integer_volume,
+ [PA_SAMPLE_S24_32LE] = (pa_calc_volume_func_t) calc_linear_integer_volume,
+ [PA_SAMPLE_S24_32BE] = (pa_calc_volume_func_t) calc_linear_integer_volume
+};
+
+void pa_volume_memchunk(
+ pa_memchunk*c,
+ const pa_sample_spec *spec,
+ const pa_cvolume *volume) {
+
+ void *ptr;
+ volume_val linear[PA_CHANNELS_MAX + VOLUME_PADDING];
+ pa_do_volume_func_t do_volume;
+
+ pa_assert(c);
+ pa_assert(spec);
+ pa_assert(pa_sample_spec_valid(spec));
+ pa_assert(pa_frame_aligned(c->length, spec));
+ pa_assert(volume);
+
+ if (pa_memblock_is_silence(c->memblock))
+ return;
+
+ if (pa_cvolume_is_norm(volume))
+ return;
+
+ if (pa_cvolume_is_muted(volume)) {
+ pa_silence_memchunk(c, spec);
+ return;
+ }
+
+ do_volume = pa_get_volume_func(spec->format);
+ pa_assert(do_volume);
+
+ calc_volume_table[spec->format] ((void *)linear, volume);
+
+ ptr = pa_memblock_acquire_chunk(c);
+
+ do_volume(ptr, (void *)linear, spec->channels, c->length);
+
+ pa_memblock_release(c->memblock);
+}
diff --git a/src/pulsecore/mix.h b/src/pulsecore/mix.h
new file mode 100644
index 0000000..8102bcd
--- /dev/null
+++ b/src/pulsecore/mix.h
@@ -0,0 +1,62 @@
+#ifndef foomixhfoo
+#define foomixhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+ Copyright 2013 Peter Meerwald <pmeerw@pmeerw.net>
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulse/sample.h>
+#include <pulse/volume.h>
+#include <pulsecore/memchunk.h>
+
+typedef struct pa_mix_info {
+ pa_memchunk chunk;
+ pa_cvolume volume;
+ void *userdata;
+
+ /* The following fields are used internally by pa_mix(), should
+ * not be initialised by the caller of pa_mix(). */
+ void *ptr;
+ union {
+ int32_t i;
+ float f;
+ } linear[PA_CHANNELS_MAX];
+} pa_mix_info;
+
+size_t pa_mix(
+ pa_mix_info channels[],
+ unsigned nchannels,
+ void *data,
+ size_t length,
+ const pa_sample_spec *spec,
+ const pa_cvolume *volume,
+ bool mute);
+
+typedef void (*pa_do_mix_func_t) (pa_mix_info streams[], unsigned nstreams, unsigned channels, void *data, unsigned length);
+
+pa_do_mix_func_t pa_get_mix_func(pa_sample_format_t f);
+void pa_set_mix_func(pa_sample_format_t f, pa_do_mix_func_t func);
+
+void pa_volume_memchunk(
+ pa_memchunk*c,
+ const pa_sample_spec *spec,
+ const pa_cvolume *volume);
+
+#endif
diff --git a/src/pulsecore/mix_neon.c b/src/pulsecore/mix_neon.c
new file mode 100644
index 0000000..eb02d81
--- /dev/null
+++ b/src/pulsecore/mix_neon.c
@@ -0,0 +1,223 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2013 Peter Meerwald <pmeerw@pmeerw.net>
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulsecore/macro.h>
+#include <pulsecore/endianmacros.h>
+#include <pulsecore/sample-util.h>
+
+#include "cpu-arm.h"
+#include "mix.h"
+
+#include <arm_neon.h>
+
+static pa_do_mix_func_t fallback;
+
+/* special case: mix s16ne streams, 2 channels each */
+static void pa_mix_ch2_s16ne_neon(pa_mix_info streams[], unsigned nstreams, uint8_t *data, unsigned length) {
+ const unsigned mask = sizeof(int16_t) * 8 - 1;
+ const uint8_t *end = data + (length & ~mask);
+
+ while (data < end) {
+ int32x4_t sum0, sum1;
+ unsigned i;
+
+ __asm__ __volatile__ (
+ "veor.s32 %q[sum0], %q[sum0] \n\t"
+ "veor.s32 %q[sum1], %q[sum1] \n\t"
+ : [sum0] "=w" (sum0), [sum1] "=w" (sum1)
+ :
+ : "cc" /* clobber list */
+ );
+
+ for (i = 0; i < nstreams; i++) {
+ pa_mix_info *m = streams + i;
+ int32_t cv0 = m->linear[0].i;
+ int32_t cv1 = m->linear[1].i;
+
+ __asm__ __volatile__ (
+ "vld2.s16 {d0,d2}, [%[ptr]]! \n\t"
+ "vmov.s32 d4[0], %[cv0] \n\t"
+ "vmov.s32 d4[1], %[cv1] \n\t"
+ "vshll.s16 q0, d0, #15 \n\t"
+ "vshll.s16 q1, d2, #15 \n\t"
+ "vqdmulh.s32 q0, q0, d4[0] \n\t"
+ "vqdmulh.s32 q1, q1, d4[1] \n\t"
+ "vqadd.s32 %q[sum0], %q[sum0], q0 \n\t"
+ "vqadd.s32 %q[sum1], %q[sum1], q1 \n\t"
+ : [ptr] "+r" (m->ptr), [sum0] "+w" (sum0), [sum1] "+w" (sum1)
+ : [cv0] "r" (cv0), [cv1] "r" (cv1)
+ : "memory", "cc", "q0", "q1", "d4" /* clobber list */
+ );
+ }
+
+ __asm__ __volatile__ (
+ "vqmovn.s32 d0, %q[sum0] \n\t"
+ "vqmovn.s32 d1, %q[sum1] \n\t"
+ "vst2.s16 {d0,d1}, [%[data]]! \n\t"
+ : [data] "+r" (data)
+ : [sum0] "w" (sum0), [sum1] "w" (sum1)
+ : "memory", "cc", "q0" /* clobber list */
+ );
+ }
+
+ fallback(streams, nstreams, 2, data, length & mask);
+}
+
+/* special case: mix 2 s16ne streams, 1 channel each */
+static void pa_mix2_ch1_s16ne_neon(pa_mix_info streams[], int16_t *data, unsigned length) {
+ const int16_t *ptr0 = streams[0].ptr;
+ const int16_t *ptr1 = streams[1].ptr;
+
+ int32x4_t sv0, sv1;
+ __asm__ __volatile__ (
+ "vdup.s32 %q[sv0], %[lin0] \n\t"
+ "vdup.s32 %q[sv1], %[lin1] \n\t"
+ : [sv0] "=w" (sv0), [sv1] "=w" (sv1)
+ : [lin0] "r" (streams[0].linear[0]), [lin1] "r" (streams[1].linear[0])
+ : /* clobber list */
+ );
+
+ length /= sizeof(int16_t);
+ for (; length >= 4; length -= 4) {
+ __asm__ __volatile__ (
+ "vld1.s16 d0, [%[ptr0]]! \n\t"
+ "vld1.s16 d2, [%[ptr1]]! \n\t"
+ "vshll.s16 q0, d0, #15 \n\t"
+ "vshll.s16 q1, d2, #15 \n\t"
+ "vqdmulh.s32 q0, q0, %q[sv0] \n\t"
+ "vqdmulh.s32 q1, q1, %q[sv1] \n\t"
+ "vqadd.s32 q0, q0, q1 \n\t"
+ "vqmovn.s32 d0, q0 \n\t"
+ "vst1.s16 d0, [%[data]]! \n\t"
+ : [ptr0] "+r" (ptr0), [ptr1] "+r" (ptr1), [data] "+r" (data)
+ : [sv0] "w" (sv0), [sv1] "w" (sv1)
+ : "memory", "cc", "q0", "q1" /* clobber list */
+ );
+ }
+
+ for (; length > 0; length--) {
+ int32_t sum = pa_mult_s16_volume(*ptr0++, streams[0].linear[0].i);
+ sum += pa_mult_s16_volume(*ptr1++, streams[1].linear[0].i);
+ *data++ = PA_CLAMP_UNLIKELY(sum, -0x8000, 0x7FFF);
+ }
+}
+
+/* special case: mix 2 s16ne streams, 2 channel each */
+static void pa_mix2_ch2_s16ne_neon(pa_mix_info streams[], int16_t *data, unsigned length) {
+ const int16_t *ptr0 = streams[0].ptr;
+ const int16_t *ptr1 = streams[1].ptr;
+
+ int32x4_t sv0, sv1;
+ __asm__ __volatile__ (
+ "vld1.s32 d0, [%[lin0]] \n\t"
+ "vmov.s32 d1, d0 \n\t"
+ "vmov.s32 %q[sv0], q0 \n\t"
+ "vld1.s32 d0, [%[lin1]] \n\t"
+ "vmov.s32 d1, d0 \n\t"
+ "vmov.s32 %q[sv1], q0 \n\t"
+ : [sv0] "=w" (sv0), [sv1] "=w" (sv1)
+ : [lin0] "r" (streams[0].linear), [lin1] "r" (streams[1].linear)
+ : "q0" /* clobber list */
+ );
+
+ length /= sizeof(int16_t);
+ for (; length >= 4; length -= 4) {
+ __asm__ __volatile__ (
+ "vld1.s16 d0, [%[ptr0]]! \n\t"
+ "vld1.s16 d2, [%[ptr1]]! \n\t"
+ "vshll.s16 q0, d0, #15 \n\t"
+ "vshll.s16 q1, d2, #15 \n\t"
+ "vqdmulh.s32 q0, q0, %q[sv0] \n\t"
+ "vqdmulh.s32 q1, q1, %q[sv1] \n\t"
+ "vqadd.s32 q0, q0, q1 \n\t"
+ "vqmovn.s32 d0, q0 \n\t"
+ "vst1.s16 d0, [%[data]]! \n\t"
+ : [ptr0] "+r" (ptr0), [ptr1] "+r" (ptr1), [data] "+r" (data)
+ : [sv0] "w" (sv0), [sv1] "w" (sv1)
+ : "memory", "cc", "q0", "q1" /* clobber list */
+ );
+ }
+
+ if (length > 0) {
+ int32_t sum;
+
+ sum = pa_mult_s16_volume(*ptr0++, streams[0].linear[0].i);
+ sum += pa_mult_s16_volume(*ptr1++, streams[1].linear[0].i);
+ *data++ = PA_CLAMP_UNLIKELY(sum, -0x8000, 0x7FFF);
+
+ sum = pa_mult_s16_volume(*ptr0++, streams[0].linear[1].i);
+ sum += pa_mult_s16_volume(*ptr1++, streams[1].linear[1].i);
+ *data++ = PA_CLAMP_UNLIKELY(sum, -0x8000, 0x7FFF);
+ }
+}
+
+/* special case: mix 2 s16ne streams, 4 channels each */
+static void pa_mix2_ch4_s16ne_neon(pa_mix_info streams[], int16_t *data, unsigned length) {
+ const int16_t *ptr0 = streams[0].ptr;
+ const int16_t *ptr1 = streams[1].ptr;
+
+ int32x4_t sv0, sv1;
+
+ __asm__ __volatile__ (
+ "vld1.s32 %h[sv0], [%[lin0]] \n\t"
+ "vld1.s32 %h[sv1], [%[lin1]] \n\t"
+ : [sv0] "=w" (sv0), [sv1] "=w" (sv1)
+ : [lin0] "r" (streams[0].linear), [lin1] "r" (streams[1].linear)
+ : /* clobber list */
+ );
+
+ length /= sizeof(int16_t);
+ for (; length >= 4; length -= 4) {
+ __asm__ __volatile__ (
+ "vld1.s16 d0, [%[ptr0]]! \n\t"
+ "vld1.s16 d2, [%[ptr1]]! \n\t"
+ "vshll.s16 q0, d0, #15 \n\t"
+ "vshll.s16 q1, d2, #15 \n\t"
+ "vqdmulh.s32 q0, q0, %q[sv0] \n\t"
+ "vqdmulh.s32 q1, q1, %q[sv1] \n\t"
+ "vqadd.s32 q0, q0, q1 \n\t"
+ "vqmovn.s32 d0, q0 \n\t"
+ "vst1.s16 d0, [%[data]]! \n\t"
+ : [ptr0] "+r" (ptr0), [ptr1] "+r" (ptr1), [data] "+r" (data)
+ : [sv0] "w" (sv0), [sv1] "w" (sv1)
+ : "memory", "cc", "q0", "q1" /* clobber list */
+ );
+ }
+}
+
+static void pa_mix_s16ne_neon(pa_mix_info streams[], unsigned nstreams, unsigned nchannels, void *data, unsigned length) {
+ if (nstreams == 2 && nchannels == 2)
+ pa_mix2_ch2_s16ne_neon(streams, data, length);
+ else if (nstreams == 2 && nchannels == 4)
+ pa_mix2_ch4_s16ne_neon(streams, data, length);
+ else if (nstreams == 2 && nchannels == 1)
+ pa_mix2_ch1_s16ne_neon(streams, data, length);
+ else if (nchannels == 2)
+ pa_mix_ch2_s16ne_neon(streams, nstreams, data, length);
+ else
+ fallback(streams, nstreams, nchannels, data, length);
+}
+
+void pa_mix_func_init_neon(pa_cpu_arm_flag_t flags) {
+ pa_log_info("Initialising ARM NEON optimized mixing functions.");
+
+ fallback = pa_get_mix_func(PA_SAMPLE_S16NE);
+ pa_set_mix_func(PA_SAMPLE_S16NE, (pa_do_mix_func_t) pa_mix_s16ne_neon);
+}
diff --git a/src/pulsecore/modargs.c b/src/pulsecore/modargs.c
new file mode 100644
index 0000000..bce5891
--- /dev/null
+++ b/src/pulsecore/modargs.c
@@ -0,0 +1,546 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/hashmap.h>
+#include <pulsecore/idxset.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/macro.h>
+
+#include "modargs.h"
+
+struct pa_modargs {
+ pa_hashmap *raw;
+ pa_hashmap *unescaped;
+};
+
+struct entry {
+ char *key, *value;
+};
+
+static int add_key_value(pa_modargs *ma, char *key, char *value, const char* const valid_keys[], bool ignore_dupes) {
+ struct entry *e;
+ char *raw;
+
+ pa_assert(ma);
+ pa_assert(ma->raw);
+ pa_assert(ma->unescaped);
+ pa_assert(key);
+ pa_assert(value);
+
+ if (pa_hashmap_get(ma->unescaped, key)) {
+ pa_xfree(key);
+ pa_xfree(value);
+
+ if (ignore_dupes)
+ return 0;
+ else
+ return -1;
+ }
+
+ if (valid_keys) {
+ const char*const* v;
+ for (v = valid_keys; *v; v++)
+ if (pa_streq(*v, key))
+ break;
+
+ if (!*v) {
+ pa_xfree(key);
+ pa_xfree(value);
+ return -1;
+ }
+ }
+
+ raw = pa_xstrdup(value);
+
+ e = pa_xnew(struct entry, 1);
+ e->key = key;
+ e->value = pa_unescape(value);
+ pa_hashmap_put(ma->unescaped, key, e);
+
+ if (pa_streq(raw, value))
+ pa_xfree(raw);
+ else {
+ e = pa_xnew(struct entry, 1);
+ e->key = pa_xstrdup(key);
+ e->value = raw;
+ pa_hashmap_put(ma->raw, key, e);
+ }
+
+ return 0;
+}
+
+static void free_func(void *p) {
+ struct entry *e = p;
+ pa_assert(e);
+
+ pa_xfree(e->key);
+ pa_xfree(e->value);
+ pa_xfree(e);
+}
+
+static int parse(pa_modargs *ma, const char *args, const char* const* valid_keys, bool ignore_dupes) {
+ enum {
+ WHITESPACE,
+ KEY,
+ VALUE_START,
+ VALUE_SIMPLE,
+ VALUE_SIMPLE_ESCAPED,
+ VALUE_DOUBLE_QUOTES,
+ VALUE_DOUBLE_QUOTES_ESCAPED,
+ VALUE_TICKS,
+ VALUE_TICKS_ESCAPED
+ } state;
+
+ const char *p, *key = NULL, *value = NULL;
+ size_t key_len = 0, value_len = 0;
+
+ state = WHITESPACE;
+
+ for (p = args; *p; p++) {
+ switch (state) {
+
+ case WHITESPACE:
+ if (*p == '=')
+ goto fail;
+ else if (!isspace((unsigned char)*p)) {
+ key = p;
+ state = KEY;
+ key_len = 1;
+ }
+ break;
+
+ case KEY:
+ if (*p == '=')
+ state = VALUE_START;
+ else if (isspace((unsigned char)*p))
+ goto fail;
+ else
+ key_len++;
+ break;
+
+ case VALUE_START:
+ if (*p == '\'') {
+ state = VALUE_TICKS;
+ value = p+1;
+ value_len = 0;
+ } else if (*p == '"') {
+ state = VALUE_DOUBLE_QUOTES;
+ value = p+1;
+ value_len = 0;
+ } else if (isspace((unsigned char)*p)) {
+ if (add_key_value(ma,
+ pa_xstrndup(key, key_len),
+ pa_xstrdup(""),
+ valid_keys,
+ ignore_dupes) < 0)
+ goto fail;
+ state = WHITESPACE;
+ } else if (*p == '\\') {
+ state = VALUE_SIMPLE_ESCAPED;
+ value = p;
+ value_len = 1;
+ } else {
+ state = VALUE_SIMPLE;
+ value = p;
+ value_len = 1;
+ }
+ break;
+
+ case VALUE_SIMPLE:
+ if (isspace((unsigned char)*p)) {
+ if (add_key_value(ma,
+ pa_xstrndup(key, key_len),
+ pa_xstrndup(value, value_len),
+ valid_keys,
+ ignore_dupes) < 0)
+ goto fail;
+ state = WHITESPACE;
+ } else if (*p == '\\') {
+ state = VALUE_SIMPLE_ESCAPED;
+ value_len++;
+ } else
+ value_len++;
+ break;
+
+ case VALUE_SIMPLE_ESCAPED:
+ state = VALUE_SIMPLE;
+ value_len++;
+ break;
+
+ case VALUE_DOUBLE_QUOTES:
+ if (*p == '"') {
+ if (add_key_value(ma,
+ pa_xstrndup(key, key_len),
+ pa_xstrndup(value, value_len),
+ valid_keys,
+ ignore_dupes) < 0)
+ goto fail;
+ state = WHITESPACE;
+ } else if (*p == '\\') {
+ state = VALUE_DOUBLE_QUOTES_ESCAPED;
+ value_len++;
+ } else
+ value_len++;
+ break;
+
+ case VALUE_DOUBLE_QUOTES_ESCAPED:
+ state = VALUE_DOUBLE_QUOTES;
+ value_len++;
+ break;
+
+ case VALUE_TICKS:
+ if (*p == '\'') {
+ if (add_key_value(ma,
+ pa_xstrndup(key, key_len),
+ pa_xstrndup(value, value_len),
+ valid_keys,
+ ignore_dupes) < 0)
+ goto fail;
+ state = WHITESPACE;
+ } else if (*p == '\\') {
+ state = VALUE_TICKS_ESCAPED;
+ value_len++;
+ } else
+ value_len++;
+ break;
+
+ case VALUE_TICKS_ESCAPED:
+ state = VALUE_TICKS;
+ value_len++;
+ break;
+ }
+ }
+
+ if (state == VALUE_START) {
+ if (add_key_value(ma, pa_xstrndup(key, key_len), pa_xstrdup(""), valid_keys, ignore_dupes) < 0)
+ goto fail;
+ } else if (state == VALUE_SIMPLE) {
+ if (add_key_value(ma, pa_xstrndup(key, key_len), pa_xstrdup(value), valid_keys, ignore_dupes) < 0)
+ goto fail;
+ } else if (state != WHITESPACE)
+ goto fail;
+
+ return 0;
+
+fail:
+ return -1;
+}
+
+pa_modargs *pa_modargs_new(const char *args, const char* const* valid_keys) {
+ pa_modargs *ma = pa_xnew(pa_modargs, 1);
+
+ ma->raw = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, free_func);
+ ma->unescaped = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, free_func);
+
+ if (args && parse(ma, args, valid_keys, false) < 0)
+ goto fail;
+
+ return ma;
+
+fail:
+ pa_modargs_free(ma);
+ return NULL;
+}
+
+int pa_modargs_append(pa_modargs *ma, const char *args, const char* const* valid_keys) {
+ return parse(ma, args, valid_keys, true);
+}
+
+void pa_modargs_free(pa_modargs*ma) {
+ pa_assert(ma);
+
+ pa_hashmap_free(ma->raw);
+ pa_hashmap_free(ma->unescaped);
+ pa_xfree(ma);
+}
+
+const char *pa_modargs_get_value(pa_modargs *ma, const char *key, const char *def) {
+ struct entry*e;
+
+ pa_assert(ma);
+ pa_assert(key);
+
+ if (!(e = pa_hashmap_get(ma->unescaped, key)))
+ return def;
+
+ return e->value;
+}
+
+static const char *modargs_get_value_raw(pa_modargs *ma, const char *key, const char *def) {
+ struct entry*e;
+
+ pa_assert(ma);
+ pa_assert(key);
+
+ if (!(e = pa_hashmap_get(ma->raw, key)))
+ if (!(e = pa_hashmap_get(ma->unescaped, key)))
+ return def;
+
+ return e->value;
+}
+
+int pa_modargs_get_value_u32(pa_modargs *ma, const char *key, uint32_t *value) {
+ const char *v;
+
+ pa_assert(value);
+
+ if (!(v = pa_modargs_get_value(ma, key, NULL)))
+ return 0;
+
+ if (pa_atou(v, value) < 0)
+ return -1;
+
+ return 0;
+}
+
+int pa_modargs_get_value_s32(pa_modargs *ma, const char *key, int32_t *value) {
+ const char *v;
+
+ pa_assert(value);
+
+ if (!(v = pa_modargs_get_value(ma, key, NULL)))
+ return 0;
+
+ if (pa_atoi(v, value) < 0)
+ return -1;
+
+ return 0;
+}
+
+int pa_modargs_get_value_boolean(pa_modargs *ma, const char *key, bool *value) {
+ const char *v;
+ int r;
+
+ pa_assert(value);
+
+ if (!(v = pa_modargs_get_value(ma, key, NULL)))
+ return 0;
+
+ if (!*v)
+ return -1;
+
+ if ((r = pa_parse_boolean(v)) < 0)
+ return -1;
+
+ *value = r;
+ return 0;
+}
+
+int pa_modargs_get_value_double(pa_modargs *ma, const char *key, double *value) {
+ const char *v;
+
+ pa_assert(value);
+
+ if (!(v = pa_modargs_get_value(ma, key, NULL)))
+ return 0;
+
+ if (pa_atod(v, value) < 0)
+ return -1;
+
+ return 0;
+}
+
+int pa_modargs_get_value_volume(pa_modargs *ma, const char *key, pa_volume_t *value) {
+ const char *v;
+
+ pa_assert(value);
+
+ if (!(v = pa_modargs_get_value(ma, key, NULL)))
+ return 0;
+
+ if (pa_parse_volume(v, value) < 0)
+ return -1;
+
+ return 0;
+}
+
+int pa_modargs_get_sample_rate(pa_modargs *ma, uint32_t *rate) {
+ uint32_t rate_local;
+
+ pa_assert(rate);
+
+ rate_local = *rate;
+ if ((pa_modargs_get_value_u32(ma, "rate", &rate_local)) < 0 ||
+ !pa_sample_rate_valid(rate_local))
+ return -1;
+
+ *rate = rate_local;
+
+ return 0;
+}
+
+int pa_modargs_get_sample_spec(pa_modargs *ma, pa_sample_spec *rss) {
+ const char *format;
+ uint32_t channels;
+ pa_sample_spec ss;
+
+ pa_assert(rss);
+
+ ss = *rss;
+ if ((pa_modargs_get_sample_rate(ma, &ss.rate)) < 0)
+ return -1;
+
+ channels = ss.channels;
+ if ((pa_modargs_get_value_u32(ma, "channels", &channels)) < 0 ||
+ !pa_channels_valid(channels))
+ return -1;
+ ss.channels = (uint8_t) channels;
+
+ if ((format = pa_modargs_get_value(ma, "format", NULL)))
+ if ((ss.format = pa_parse_sample_format(format)) < 0)
+ return -1;
+
+ if (!pa_sample_spec_valid(&ss))
+ return -1;
+
+ *rss = ss;
+
+ return 0;
+}
+
+int pa_modargs_get_alternate_sample_rate(pa_modargs *ma, uint32_t *alternate_rate) {
+ uint32_t rate_local;
+
+ pa_assert(alternate_rate);
+
+ rate_local = *alternate_rate;
+ if ((pa_modargs_get_value_u32(ma, "alternate_rate", &rate_local)) < 0 ||
+ !pa_sample_rate_valid(*alternate_rate))
+ return -1;
+
+ *alternate_rate = rate_local;
+
+ return 0;
+}
+
+int pa_modargs_get_channel_map(pa_modargs *ma, const char *name, pa_channel_map *rmap) {
+ pa_channel_map map;
+ const char *cm;
+
+ pa_assert(rmap);
+
+ map = *rmap;
+
+ if ((cm = pa_modargs_get_value(ma, name ? name : "channel_map", NULL)))
+ if (!pa_channel_map_parse(&map, cm))
+ return -1;
+
+ if (!pa_channel_map_valid(&map))
+ return -1;
+
+ *rmap = map;
+ return 0;
+}
+
+int pa_modargs_get_resample_method(pa_modargs *ma, pa_resample_method_t *rmethod) {
+ const char *m;
+
+ pa_assert(ma);
+ pa_assert(rmethod);
+
+ if ((m = pa_modargs_get_value(ma, "resample_method", NULL))) {
+ pa_resample_method_t method = pa_parse_resample_method(m);
+
+ if (method == PA_RESAMPLER_INVALID)
+ return -1;
+
+ *rmethod = method;
+ }
+
+ return 0;
+}
+
+int pa_modargs_get_sample_spec_and_channel_map(
+ pa_modargs *ma,
+ pa_sample_spec *rss,
+ pa_channel_map *rmap,
+ pa_channel_map_def_t def) {
+
+ pa_sample_spec ss;
+ pa_channel_map map;
+
+ pa_assert(rss);
+ pa_assert(rmap);
+
+ ss = *rss;
+
+ if (pa_modargs_get_sample_spec(ma, &ss) < 0)
+ return -1;
+
+ map = *rmap;
+
+ if (ss.channels != map.channels)
+ pa_channel_map_init_extend(&map, ss.channels, def);
+
+ if (pa_modargs_get_channel_map(ma, NULL, &map) < 0)
+ return -1;
+
+ if (map.channels != ss.channels) {
+ if (!pa_modargs_get_value(ma, "channels", NULL))
+ ss.channels = map.channels;
+ else
+ return -1;
+ }
+
+ *rmap = map;
+ *rss = ss;
+
+ return 0;
+}
+
+int pa_modargs_get_proplist(pa_modargs *ma, const char *name, pa_proplist *p, pa_update_mode_t m) {
+ const char *v;
+ pa_proplist *n;
+
+ pa_assert(ma);
+ pa_assert(name);
+ pa_assert(p);
+
+ if (!(v = modargs_get_value_raw(ma, name, NULL)))
+ return 0;
+
+ if (!(n = pa_proplist_from_string(v)))
+ return -1;
+
+ pa_proplist_update(p, m, n);
+ pa_proplist_free(n);
+
+ return 0;
+}
+
+const char *pa_modargs_iterate(pa_modargs *ma, void **state) {
+ struct entry *e;
+
+ pa_assert(ma);
+
+ if (!(e = pa_hashmap_iterate(ma->unescaped, state, NULL)))
+ return NULL;
+
+ return e->key;
+}
diff --git a/src/pulsecore/modargs.h b/src/pulsecore/modargs.h
new file mode 100644
index 0000000..96132a3
--- /dev/null
+++ b/src/pulsecore/modargs.h
@@ -0,0 +1,98 @@
+#ifndef foomodargshfoo
+#define foomodargshfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <inttypes.h>
+#include <pulse/sample.h>
+#include <pulse/channelmap.h>
+#include <pulse/proplist.h>
+#include <pulse/volume.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/resampler.h>
+
+typedef struct pa_modargs pa_modargs;
+
+/* A generic parser for module arguments */
+
+/* Parse the string args. The NULL-terminated array keys contains all valid arguments. */
+pa_modargs *pa_modargs_new(const char *args, const char* const keys[]);
+/* Parse the string args, and add any keys that are not already present. */
+int pa_modargs_append(pa_modargs *ma, const char *args, const char* const* valid_keys);
+void pa_modargs_free(pa_modargs*ma);
+
+/* Return the module argument for the specified name as a string. If
+ * the argument was not specified, return def instead.*/
+const char *pa_modargs_get_value(pa_modargs *ma, const char *key, const char *def);
+
+/* Return a module argument as unsigned 32bit value in *value. If the argument
+ * was not specified, *value remains unchanged. */
+int pa_modargs_get_value_u32(pa_modargs *ma, const char *key, uint32_t *value);
+int pa_modargs_get_value_s32(pa_modargs *ma, const char *key, int32_t *value);
+int pa_modargs_get_value_boolean(pa_modargs *ma, const char *key, bool *value);
+
+/* Return a module argument as double value in *value. If the argument was not
+ * specified, *value remains unchanged. */
+int pa_modargs_get_value_double(pa_modargs *ma, const char *key, double *value);
+
+/* Return a module argument as pa_volume_t value in *value. If the argument
+ * was not specified, *value remains unchanged. */
+int pa_modargs_get_value_volume(pa_modargs *ma, const char *key, pa_volume_t *value);
+
+/* Return sample rate from the "rate" argument. If the argument was not
+ * specified, *rate remains unchanged. */
+int pa_modargs_get_sample_rate(pa_modargs *ma, uint32_t *rate);
+
+/* Return sample spec data from the three arguments "rate", "format" and
+ * "channels". If the argument was not specified, *ss remains unchanged. */
+int pa_modargs_get_sample_spec(pa_modargs *ma, pa_sample_spec *ss);
+
+/* Return channel map data from the argument "channel_map" if name is NULL,
+ * otherwise read from the specified argument. If the argument was not
+ * specified, *map remains unchanged. */
+int pa_modargs_get_channel_map(pa_modargs *ma, const char *name, pa_channel_map *map);
+
+/* Return resample method from the argument "resample_method". If the argument
+ * was not specified, *method remains unchanged. */
+int pa_modargs_get_resample_method(pa_modargs *ma, pa_resample_method_t *method);
+
+/* Combination of pa_modargs_get_sample_spec() and
+pa_modargs_get_channel_map(). Not always suitable, since this routine
+initializes the map parameter based on the channels field of the ss
+structure if no channel_map is found, using pa_channel_map_init_auto() */
+
+int pa_modargs_get_sample_spec_and_channel_map(pa_modargs *ma, pa_sample_spec *ss, pa_channel_map *map, pa_channel_map_def_t def);
+
+/* Return alternate sample rate from "alternate_sample_rate" parameter. If the
+ * argument was not specified, *alternate_rate remains unchanged. */
+int pa_modargs_get_alternate_sample_rate(pa_modargs *ma, uint32_t *alternate_rate);
+
+int pa_modargs_get_proplist(pa_modargs *ma, const char *name, pa_proplist *p, pa_update_mode_t m);
+
+/* Iterate through the module argument list. The user should allocate a
+ * state variable of type void* and initialize it with NULL. A pointer
+ * to this variable should then be passed to pa_modargs_iterate()
+ * which should be called in a loop until it returns NULL which
+ * signifies EOL. On each invocation this function will return the
+ * key string for the next entry. The keys in the argument list do not
+ * have any particular order. */
+const char *pa_modargs_iterate(pa_modargs *ma, void **state);
+
+#endif
diff --git a/src/pulsecore/modinfo.c b/src/pulsecore/modinfo.c
new file mode 100644
index 0000000..e1a814f
--- /dev/null
+++ b/src/pulsecore/modinfo.c
@@ -0,0 +1,97 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <ltdl.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/ltdl-helper.h>
+
+#include "modinfo.h"
+
+#define PA_SYMBOL_AUTHOR "pa__get_author"
+#define PA_SYMBOL_DESCRIPTION "pa__get_description"
+#define PA_SYMBOL_USAGE "pa__get_usage"
+#define PA_SYMBOL_VERSION "pa__get_version"
+#define PA_SYMBOL_DEPRECATED "pa__get_deprecated"
+#define PA_SYMBOL_LOAD_ONCE "pa__load_once"
+
+pa_modinfo *pa_modinfo_get_by_handle(lt_dlhandle dl, const char *module_name) {
+ pa_modinfo *i;
+ const char* (*func)(void);
+ bool (*func2) (void);
+
+ pa_assert(dl);
+
+ i = pa_xnew0(pa_modinfo, 1);
+
+ if ((func = (const char* (*)(void)) pa_load_sym(dl, module_name, PA_SYMBOL_AUTHOR)))
+ i->author = pa_xstrdup(func());
+
+ if ((func = (const char* (*)(void)) pa_load_sym(dl, module_name, PA_SYMBOL_DESCRIPTION)))
+ i->description = pa_xstrdup(func());
+
+ if ((func = (const char* (*)(void)) pa_load_sym(dl, module_name, PA_SYMBOL_USAGE)))
+ i->usage = pa_xstrdup(func());
+
+ if ((func = (const char* (*)(void)) pa_load_sym(dl, module_name, PA_SYMBOL_VERSION)))
+ i->version = pa_xstrdup(func());
+
+ if ((func = (const char* (*)(void)) pa_load_sym(dl, module_name, PA_SYMBOL_DEPRECATED)))
+ i->deprecated = pa_xstrdup(func());
+
+ if ((func2 = (bool (*)(void)) pa_load_sym(dl, module_name, PA_SYMBOL_LOAD_ONCE)))
+ i->load_once = func2();
+
+ return i;
+}
+
+pa_modinfo *pa_modinfo_get_by_name(const char *name) {
+ lt_dlhandle dl;
+ pa_modinfo *i;
+
+ pa_assert(name);
+
+ if (!(dl = lt_dlopenext(name))) {
+ pa_log("Failed to open module \"%s\": %s", name, lt_dlerror());
+ return NULL;
+ }
+
+ i = pa_modinfo_get_by_handle(dl, name);
+ lt_dlclose(dl);
+
+ return i;
+}
+
+void pa_modinfo_free(pa_modinfo *i) {
+ pa_assert(i);
+
+ pa_xfree(i->author);
+ pa_xfree(i->description);
+ pa_xfree(i->usage);
+ pa_xfree(i->version);
+ pa_xfree(i->deprecated);
+ pa_xfree(i);
+}
diff --git a/src/pulsecore/modinfo.h b/src/pulsecore/modinfo.h
new file mode 100644
index 0000000..5b99b77
--- /dev/null
+++ b/src/pulsecore/modinfo.h
@@ -0,0 +1,44 @@
+#ifndef foomodinfohfoo
+#define foomodinfohfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+/* Some functions for reading module meta data from PulseAudio modules */
+#include <pulsecore/macro.h>
+
+typedef struct pa_modinfo {
+ char *author;
+ char *description;
+ char *usage;
+ char *version;
+ char *deprecated;
+ bool load_once;
+} pa_modinfo;
+
+/* Read meta data from an libtool handle */
+pa_modinfo *pa_modinfo_get_by_handle(lt_dlhandle dl, const char *module_name);
+
+/* Read meta data from a module file */
+pa_modinfo *pa_modinfo_get_by_name(const char *name);
+
+/* Free meta data */
+void pa_modinfo_free(pa_modinfo *i);
+
+#endif
diff --git a/src/pulsecore/module.c b/src/pulsecore/module.c
new file mode 100644
index 0000000..15a54b6
--- /dev/null
+++ b/src/pulsecore/module.c
@@ -0,0 +1,415 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <ltdl.h>
+
+#include <pulse/xmalloc.h>
+#include <pulse/proplist.h>
+
+#include <pulsecore/core-subscribe.h>
+#include <pulsecore/log.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/ltdl-helper.h>
+#include <pulsecore/modinfo.h>
+
+#include "module.h"
+
+#define PA_SYMBOL_INIT "pa__init"
+#define PA_SYMBOL_DONE "pa__done"
+#define PA_SYMBOL_LOAD_ONCE "pa__load_once"
+#define PA_SYMBOL_GET_N_USED "pa__get_n_used"
+#define PA_SYMBOL_GET_DEPRECATE "pa__get_deprecated"
+
+bool pa_module_exists(const char *name) {
+ const char *paths, *state = NULL;
+ char *n, *p, *pathname;
+ bool result;
+
+ pa_assert(name);
+
+ if (name[0] == PA_PATH_SEP_CHAR) {
+ result = access(name, F_OK) == 0 ? true : false;
+ pa_log_debug("Checking for existence of '%s': %s", name, result ? "success" : "failure");
+ if (result)
+ return true;
+ }
+
+ if (!(paths = lt_dlgetsearchpath()))
+ return false;
+
+ /* strip .so from the end of name, if present */
+ n = pa_xstrdup(name);
+ p = strrchr(n, '.');
+ if (p && pa_streq(p, PA_SOEXT))
+ p[0] = 0;
+
+ while ((p = pa_split(paths, ":", &state))) {
+ pathname = pa_sprintf_malloc("%s" PA_PATH_SEP "%s" PA_SOEXT, p, n);
+ result = access(pathname, F_OK) == 0 ? true : false;
+ pa_log_debug("Checking for existence of '%s': %s", pathname, result ? "success" : "failure");
+ pa_xfree(pathname);
+ pa_xfree(p);
+ if (result) {
+ pa_xfree(n);
+ return true;
+ }
+ }
+
+ state = NULL;
+ if (PA_UNLIKELY(pa_run_from_build_tree())) {
+ while ((p = pa_split(paths, ":", &state))) {
+#ifdef MESON_BUILD
+ pathname = pa_sprintf_malloc("%s" PA_PATH_SEP "src" PA_PATH_SEP "modules" PA_PATH_SEP "%s" PA_SOEXT, p, n);
+#else
+ pathname = pa_sprintf_malloc("%s" PA_PATH_SEP ".libs" PA_PATH_SEP "%s" PA_SOEXT, p, n);
+#endif
+ result = access(pathname, F_OK) == 0 ? true : false;
+ pa_log_debug("Checking for existence of '%s': %s", pathname, result ? "success" : "failure");
+ pa_xfree(pathname);
+ pa_xfree(p);
+ if (result) {
+ pa_xfree(n);
+ return true;
+ }
+ }
+ }
+
+ pa_xfree(n);
+ return false;
+}
+
+void pa_module_hook_connect(pa_module *m, pa_hook *hook, pa_hook_priority_t prio, pa_hook_cb_t cb, void *data) {
+ pa_assert(m);
+ pa_assert(hook);
+ pa_assert(m->hooks);
+ pa_dynarray_append(m->hooks, pa_hook_connect(hook, prio, cb, data));
+}
+
+int pa_module_load(pa_module** module, pa_core *c, const char *name, const char *argument) {
+ pa_module *m = NULL;
+ bool (*load_once)(void);
+ const char* (*get_deprecated)(void);
+ pa_modinfo *mi;
+ int errcode, rval;
+
+ pa_assert(module);
+ pa_assert(c);
+ pa_assert(name);
+
+ if (c->disallow_module_loading) {
+ errcode = -PA_ERR_ACCESS;
+ goto fail;
+ }
+
+ m = pa_xnew(pa_module, 1);
+ m->name = pa_xstrdup(name);
+ m->argument = pa_xstrdup(argument);
+ m->load_once = false;
+ m->proplist = pa_proplist_new();
+ m->hooks = pa_dynarray_new((pa_free_cb_t) pa_hook_slot_free);
+ m->index = PA_IDXSET_INVALID;
+
+ if (!(m->dl = lt_dlopenext(name))) {
+ /* We used to print the error that is returned by lt_dlerror(), but
+ * lt_dlerror() is useless. It returns pretty much always "file not
+ * found". That's because if there are any problems with loading the
+ * module with normal loaders, libltdl falls back to the "preload"
+ * loader, which never finds anything, and therefore says "file not
+ * found". */
+ pa_log("Failed to open module \"%s\".", name);
+ errcode = -PA_ERR_IO;
+ goto fail;
+ }
+
+ if ((load_once = (bool (*)(void)) pa_load_sym(m->dl, name, PA_SYMBOL_LOAD_ONCE))) {
+
+ m->load_once = load_once();
+
+ if (m->load_once) {
+ pa_module *i;
+ uint32_t idx;
+ /* OK, the module only wants to be loaded once, let's make sure it is */
+
+ PA_IDXSET_FOREACH(i, c->modules, idx) {
+ if (pa_streq(name, i->name)) {
+ pa_log("Module \"%s\" should be loaded once at most. Refusing to load.", name);
+ errcode = -PA_ERR_EXIST;
+ goto fail;
+ }
+ }
+ }
+ }
+
+ if ((get_deprecated = (const char* (*) (void)) pa_load_sym(m->dl, name, PA_SYMBOL_GET_DEPRECATE))) {
+ const char *t;
+
+ if ((t = get_deprecated()))
+ pa_log_warn("%s is deprecated: %s", name, t);
+ }
+
+ if (!(m->init = (int (*)(pa_module*_m)) pa_load_sym(m->dl, name, PA_SYMBOL_INIT))) {
+ pa_log("Failed to load module \"%s\": symbol \""PA_SYMBOL_INIT"\" not found.", name);
+ errcode = -PA_ERR_IO;
+ goto fail;
+ }
+
+ m->done = (void (*)(pa_module*_m)) pa_load_sym(m->dl, name, PA_SYMBOL_DONE);
+ m->get_n_used = (int (*)(pa_module*_m)) pa_load_sym(m->dl, name, PA_SYMBOL_GET_N_USED);
+ m->userdata = NULL;
+ m->core = c;
+ m->unload_requested = false;
+
+ pa_assert_se(pa_idxset_put(c->modules, m, &m->index) >= 0);
+ pa_assert(m->index != PA_IDXSET_INVALID);
+
+ if ((rval = m->init(m)) < 0) {
+ if (rval == -PA_MODULE_ERR_SKIP) {
+ errcode = -PA_ERR_NOENTITY;
+ goto fail;
+ }
+ pa_log_error("Failed to load module \"%s\" (argument: \"%s\"): initialization failed.", name, argument ? argument : "");
+ errcode = -PA_ERR_IO;
+ goto fail;
+ }
+
+ pa_log_info("Loaded \"%s\" (index: #%u; argument: \"%s\").", m->name, m->index, m->argument ? m->argument : "");
+
+ pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_MODULE|PA_SUBSCRIPTION_EVENT_NEW, m->index);
+
+ if ((mi = pa_modinfo_get_by_handle(m->dl, name))) {
+
+ if (mi->author && !pa_proplist_contains(m->proplist, PA_PROP_MODULE_AUTHOR))
+ pa_proplist_sets(m->proplist, PA_PROP_MODULE_AUTHOR, mi->author);
+
+ if (mi->description && !pa_proplist_contains(m->proplist, PA_PROP_MODULE_DESCRIPTION))
+ pa_proplist_sets(m->proplist, PA_PROP_MODULE_DESCRIPTION, mi->description);
+
+ if (mi->version && !pa_proplist_contains(m->proplist, PA_PROP_MODULE_VERSION))
+ pa_proplist_sets(m->proplist, PA_PROP_MODULE_VERSION, mi->version);
+
+ pa_modinfo_free(mi);
+ }
+
+ pa_hook_fire(&m->core->hooks[PA_CORE_HOOK_MODULE_NEW], m);
+
+ *module = m;
+
+ return 0;
+
+fail:
+
+ if (m) {
+ if (m->index != PA_IDXSET_INVALID)
+ pa_idxset_remove_by_index(c->modules, m->index);
+
+ if (m->hooks)
+ pa_dynarray_free(m->hooks);
+
+ if (m->proplist)
+ pa_proplist_free(m->proplist);
+
+ pa_xfree(m->argument);
+ pa_xfree(m->name);
+
+ if (m->dl)
+ lt_dlclose(m->dl);
+
+ pa_xfree(m);
+ }
+
+ *module = NULL;
+
+ return errcode;
+}
+
+static void postponed_dlclose(pa_mainloop_api *api, void *userdata) {
+ lt_dlhandle dl = userdata;
+
+ lt_dlclose(dl);
+}
+
+static void pa_module_free(pa_module *m) {
+ pa_assert(m);
+ pa_assert(m->core);
+
+ pa_log_info("Unloading \"%s\" (index: #%u).", m->name, m->index);
+ pa_hook_fire(&m->core->hooks[PA_CORE_HOOK_MODULE_UNLINK], m);
+
+ if (m->hooks) {
+ pa_dynarray_free(m->hooks);
+ m->hooks = NULL;
+ }
+
+ if (m->done)
+ m->done(m);
+
+ if (m->proplist)
+ pa_proplist_free(m->proplist);
+
+ /* If a module unloads itself with pa_module_unload(), we can't call
+ * lt_dlclose() here, because otherwise pa_module_unload() may return to a
+ * code location that has been removed from memory. Therefore, let's
+ * postpone the lt_dlclose() call a bit.
+ *
+ * Apparently lt_dlclose() doesn't always remove the module from memory,
+ * but it can happen, as can be seen here:
+ * https://bugs.freedesktop.org/show_bug.cgi?id=96831 */
+ pa_mainloop_api_once(m->core->mainloop, postponed_dlclose, m->dl);
+
+ pa_hashmap_remove(m->core->modules_pending_unload, m);
+
+ pa_log_info("Unloaded \"%s\" (index: #%u).", m->name, m->index);
+
+ pa_subscription_post(m->core, PA_SUBSCRIPTION_EVENT_MODULE|PA_SUBSCRIPTION_EVENT_REMOVE, m->index);
+
+ pa_xfree(m->name);
+ pa_xfree(m->argument);
+ pa_xfree(m);
+}
+
+void pa_module_unload(pa_module *m, bool force) {
+ pa_assert(m);
+
+ if (m->core->disallow_module_loading && !force)
+ return;
+
+ if (!(m = pa_idxset_remove_by_data(m->core->modules, m, NULL)))
+ return;
+
+ pa_module_free(m);
+}
+
+void pa_module_unload_by_index(pa_core *c, uint32_t idx, bool force) {
+ pa_module *m;
+ pa_assert(c);
+ pa_assert(idx != PA_IDXSET_INVALID);
+
+ if (c->disallow_module_loading && !force)
+ return;
+
+ if (!(m = pa_idxset_remove_by_index(c->modules, idx)))
+ return;
+
+ pa_module_free(m);
+}
+
+void pa_module_unload_all(pa_core *c) {
+ pa_module *m;
+ uint32_t *indices;
+ uint32_t state;
+ int i;
+
+ pa_assert(c);
+ pa_assert(c->modules);
+
+ if (pa_idxset_isempty(c->modules))
+ return;
+
+ /* Unload modules in reverse order by default */
+ indices = pa_xnew(uint32_t, pa_idxset_size(c->modules));
+ i = 0;
+ PA_IDXSET_FOREACH(m, c->modules, state)
+ indices[i++] = state;
+ pa_assert(i == (int) pa_idxset_size(c->modules));
+ i--;
+ for (; i >= 0; i--) {
+ m = pa_idxset_remove_by_index(c->modules, indices[i]);
+ if (m)
+ pa_module_free(m);
+ }
+ pa_xfree(indices);
+
+ /* Just in case module unloading caused more modules to load */
+ PA_IDXSET_FOREACH(m, c->modules, state)
+ pa_log_warn("After module unload, module '%s' was still loaded!", m->name);
+ c->disallow_module_loading = 1;
+ pa_idxset_remove_all(c->modules, (pa_free_cb_t) pa_module_free);
+ pa_assert(pa_idxset_isempty(c->modules));
+
+ if (c->module_defer_unload_event) {
+ c->mainloop->defer_free(c->module_defer_unload_event);
+ c->module_defer_unload_event = NULL;
+ }
+ pa_assert(pa_hashmap_isempty(c->modules_pending_unload));
+}
+
+static void defer_cb(pa_mainloop_api*api, pa_defer_event *e, void *userdata) {
+ pa_core *c = PA_CORE(userdata);
+ pa_module *m;
+
+ pa_core_assert_ref(c);
+ api->defer_enable(e, 0);
+
+ while ((m = pa_hashmap_first(c->modules_pending_unload)))
+ pa_module_unload(m, true);
+}
+
+void pa_module_unload_request(pa_module *m, bool force) {
+ pa_assert(m);
+
+ if (m->core->disallow_module_loading && !force)
+ return;
+
+ m->unload_requested = true;
+ pa_hashmap_put(m->core->modules_pending_unload, m, m);
+
+ if (!m->core->module_defer_unload_event)
+ m->core->module_defer_unload_event = m->core->mainloop->defer_new(m->core->mainloop, defer_cb, m->core);
+
+ m->core->mainloop->defer_enable(m->core->module_defer_unload_event, 1);
+}
+
+void pa_module_unload_request_by_index(pa_core *c, uint32_t idx, bool force) {
+ pa_module *m;
+ pa_assert(c);
+
+ if (!(m = pa_idxset_get_by_index(c->modules, idx)))
+ return;
+
+ pa_module_unload_request(m, force);
+}
+
+int pa_module_get_n_used(pa_module*m) {
+ pa_assert(m);
+
+ if (!m->get_n_used)
+ return -1;
+
+ return m->get_n_used(m);
+}
+
+void pa_module_update_proplist(pa_module *m, pa_update_mode_t mode, pa_proplist *p) {
+ pa_assert(m);
+
+ if (p)
+ pa_proplist_update(m->proplist, mode, p);
+
+ pa_subscription_post(m->core, PA_SUBSCRIPTION_EVENT_MODULE|PA_SUBSCRIPTION_EVENT_CHANGE, m->index);
+ pa_hook_fire(&m->core->hooks[PA_CORE_HOOK_MODULE_PROPLIST_CHANGED], m);
+}
diff --git a/src/pulsecore/module.h b/src/pulsecore/module.h
new file mode 100644
index 0000000..f918ea6
--- /dev/null
+++ b/src/pulsecore/module.h
@@ -0,0 +1,129 @@
+#ifndef foomodulehfoo
+#define foomodulehfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <inttypes.h>
+#include <ltdl.h>
+
+typedef struct pa_module pa_module;
+
+#include <pulse/proplist.h>
+#include <pulsecore/dynarray.h>
+
+#include <pulsecore/core.h>
+
+enum {
+ PA_MODULE_ERR_UNSPECIFIED = 1,
+ PA_MODULE_ERR_SKIP = 2
+};
+
+struct pa_module {
+ pa_core *core;
+ char *name, *argument;
+ uint32_t index;
+
+ lt_dlhandle dl;
+
+ int (*init)(pa_module*m);
+ void (*done)(pa_module*m);
+ int (*get_n_used)(pa_module *m);
+
+ void *userdata;
+
+ bool load_once:1;
+ bool unload_requested:1;
+
+ pa_proplist *proplist;
+ pa_dynarray *hooks;
+};
+
+bool pa_module_exists(const char *name);
+
+int pa_module_load(pa_module** m, pa_core *c, const char *name, const char *argument);
+
+void pa_module_unload(pa_module *m, bool force);
+void pa_module_unload_by_index(pa_core *c, uint32_t idx, bool force);
+
+void pa_module_unload_request(pa_module *m, bool force);
+void pa_module_unload_request_by_index(pa_core *c, uint32_t idx, bool force);
+
+void pa_module_unload_all(pa_core *c);
+
+int pa_module_get_n_used(pa_module*m);
+
+void pa_module_update_proplist(pa_module *m, pa_update_mode_t mode, pa_proplist *p);
+
+void pa_module_hook_connect(pa_module *m, pa_hook *hook, pa_hook_priority_t prio, pa_hook_cb_t cb, void *data);
+
+#define PA_MODULE_AUTHOR(s) \
+ const char *pa__get_author(void) { return s; } \
+ struct __stupid_useless_struct_to_allow_trailing_semicolon
+
+#define PA_MODULE_DESCRIPTION(s) \
+ const char *pa__get_description(void) { return s; } \
+ struct __stupid_useless_struct_to_allow_trailing_semicolon
+
+#define PA_MODULE_USAGE(s) \
+ const char *pa__get_usage(void) { return s; } \
+ struct __stupid_useless_struct_to_allow_trailing_semicolon
+
+#define PA_MODULE_VERSION(s) \
+ const char * pa__get_version(void) { return s; } \
+ struct __stupid_useless_struct_to_allow_trailing_semicolon
+
+#define PA_MODULE_DEPRECATED(s) \
+ const char * pa__get_deprecated(void) { return s; } \
+ struct __stupid_useless_struct_to_allow_trailing_semicolon
+
+#define PA_MODULE_LOAD_ONCE(b) \
+ bool pa__load_once(void) { return b; } \
+ struct __stupid_useless_struct_to_allow_trailing_semicolon
+
+/* Check if we're defining a module (usually defined via compiler flags) */
+#ifdef PA_MODULE_NAME
+
+/* Jump through some double-indirection hoops to get PA_MODULE_NAME substituted before the concatenation */
+#define _MACRO_CONCAT1(a, b) a ## b
+#define _MACRO_CONCAT(a, b) _MACRO_CONCAT1(a, b)
+
+#define pa__init _MACRO_CONCAT(PA_MODULE_NAME, _LTX_pa__init)
+#define pa__done _MACRO_CONCAT(PA_MODULE_NAME, _LTX_pa__done)
+#define pa__get_author _MACRO_CONCAT(PA_MODULE_NAME, _LTX_pa__get_author)
+#define pa__get_description _MACRO_CONCAT(PA_MODULE_NAME, _LTX_pa__get_description)
+#define pa__get_usage _MACRO_CONCAT(PA_MODULE_NAME, _LTX_pa__get_usage)
+#define pa__get_version _MACRO_CONCAT(PA_MODULE_NAME, _LTX_pa__get_version)
+#define pa__get_deprecated _MACRO_CONCAT(PA_MODULE_NAME, _LTX_pa__get_deprecated)
+#define pa__load_once _MACRO_CONCAT(PA_MODULE_NAME, _LTX_pa__load_once)
+#define pa__get_n_used _MACRO_CONCAT(PA_MODULE_NAME, _LTX_pa__get_n_used)
+
+int pa__init(pa_module*m);
+void pa__done(pa_module*m);
+int pa__get_n_used(pa_module*m);
+
+const char* pa__get_author(void);
+const char* pa__get_description(void);
+const char* pa__get_usage(void);
+const char* pa__get_version(void);
+const char* pa__get_deprecated(void);
+bool pa__load_once(void);
+#endif /* PA_MODULE_NAME */
+
+#endif
diff --git a/src/pulsecore/msgobject.c b/src/pulsecore/msgobject.c
new file mode 100644
index 0000000..e1d0fff
--- /dev/null
+++ b/src/pulsecore/msgobject.c
@@ -0,0 +1,45 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "msgobject.h"
+
+PA_DEFINE_PUBLIC_CLASS(pa_msgobject, pa_object);
+
+pa_msgobject *pa_msgobject_new_internal(size_t size, const char *type_id, bool (*check_type)(const char *type_name)) {
+ pa_msgobject *o;
+
+ pa_assert(size >= sizeof(pa_msgobject));
+ pa_assert(type_id);
+
+ if (!check_type)
+ check_type = pa_msgobject_check_type;
+
+ pa_assert(check_type(type_id));
+ pa_assert(check_type(pa_object_type_id));
+ pa_assert(check_type(pa_msgobject_type_id));
+
+ o = PA_MSGOBJECT(pa_object_new_internal(size, type_id, check_type));
+ o->process_msg = NULL;
+ return o;
+}
diff --git a/src/pulsecore/msgobject.h b/src/pulsecore/msgobject.h
new file mode 100644
index 0000000..dfb519d
--- /dev/null
+++ b/src/pulsecore/msgobject.h
@@ -0,0 +1,46 @@
+#ifndef foopulsemsgobjecthfoo
+#define foopulsemsgobjecthfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/types.h>
+
+#include <pulsecore/macro.h>
+#include <pulsecore/object.h>
+#include <pulsecore/memchunk.h>
+
+typedef struct pa_msgobject pa_msgobject;
+
+struct pa_msgobject {
+ pa_object parent;
+ int (*process_msg)(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk);
+};
+
+pa_msgobject *pa_msgobject_new_internal(size_t size, const char *type_id, bool (*check_type)(const char *type_name));
+
+#define pa_msgobject_new(type) ((type*) pa_msgobject_new_internal(sizeof(type), type##_type_id, type##_check_type))
+#define pa_msgobject_free ((void (*) (pa_msgobject* o)) pa_object_free)
+
+#define PA_MSGOBJECT(o) pa_msgobject_cast(o)
+
+PA_DECLARE_PUBLIC_CLASS(pa_msgobject);
+
+#endif
diff --git a/src/pulsecore/mutex-posix.c b/src/pulsecore/mutex-posix.c
new file mode 100644
index 0000000..a835be1
--- /dev/null
+++ b/src/pulsecore/mutex-posix.c
@@ -0,0 +1,162 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pthread.h>
+#include <errno.h>
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/macro.h>
+
+#include "mutex.h"
+
+struct pa_mutex {
+ pthread_mutex_t mutex;
+};
+
+struct pa_cond {
+ pthread_cond_t cond;
+};
+
+pa_mutex* pa_mutex_new(bool recursive, bool inherit_priority) {
+ pa_mutex *m;
+ pthread_mutexattr_t attr;
+#ifdef HAVE_PTHREAD_PRIO_INHERIT
+ int r;
+#endif
+
+ pa_assert_se(pthread_mutexattr_init(&attr) == 0);
+
+ if (recursive)
+ pa_assert_se(pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) == 0);
+
+#ifdef HAVE_PTHREAD_PRIO_INHERIT
+ if (inherit_priority) {
+ r = pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT);
+ pa_assert(r == 0 || r == ENOTSUP);
+ }
+#endif
+
+ m = pa_xnew(pa_mutex, 1);
+
+#ifndef HAVE_PTHREAD_PRIO_INHERIT
+ pa_assert_se(pthread_mutex_init(&m->mutex, &attr) == 0);
+
+#else
+ if ((r = pthread_mutex_init(&m->mutex, &attr))) {
+
+ /* If this failed, then this was probably due to non-available
+ * priority inheritance. In which case we fall back to normal
+ * mutexes. */
+ pa_assert(r == ENOTSUP && inherit_priority);
+
+ pa_assert_se(pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_NONE) == 0);
+ pa_assert_se(pthread_mutex_init(&m->mutex, &attr) == 0);
+ }
+#endif
+
+ return m;
+}
+
+void pa_mutex_free(pa_mutex *m) {
+ pa_assert(m);
+
+ pa_assert_se(pthread_mutex_destroy(&m->mutex) == 0);
+ pa_xfree(m);
+}
+
+void pa_mutex_lock(pa_mutex *m) {
+ pa_assert(m);
+
+ pa_assert_se(pthread_mutex_lock(&m->mutex) == 0);
+}
+
+bool pa_mutex_try_lock(pa_mutex *m) {
+ int r;
+ pa_assert(m);
+
+ if ((r = pthread_mutex_trylock(&m->mutex)) != 0) {
+ pa_assert(r == EBUSY);
+ return false;
+ }
+
+ return true;
+}
+
+void pa_mutex_unlock(pa_mutex *m) {
+ pa_assert(m);
+
+ pa_assert_se(pthread_mutex_unlock(&m->mutex) == 0);
+}
+
+pa_cond *pa_cond_new(void) {
+ pa_cond *c;
+
+ c = pa_xnew(pa_cond, 1);
+ pa_assert_se(pthread_cond_init(&c->cond, NULL) == 0);
+ return c;
+}
+
+void pa_cond_free(pa_cond *c) {
+ pa_assert(c);
+
+ pa_assert_se(pthread_cond_destroy(&c->cond) == 0);
+ pa_xfree(c);
+}
+
+void pa_cond_signal(pa_cond *c, int broadcast) {
+ pa_assert(c);
+
+ if (broadcast)
+ pa_assert_se(pthread_cond_broadcast(&c->cond) == 0);
+ else
+ pa_assert_se(pthread_cond_signal(&c->cond) == 0);
+}
+
+int pa_cond_wait(pa_cond *c, pa_mutex *m) {
+ pa_assert(c);
+ pa_assert(m);
+
+ return pthread_cond_wait(&c->cond, &m->mutex);
+}
+
+pa_mutex* pa_static_mutex_get(pa_static_mutex *s, bool recursive, bool inherit_priority) {
+ pa_mutex *m;
+
+ pa_assert(s);
+
+ /* First, check if already initialized and short cut */
+ if ((m = pa_atomic_ptr_load(&s->ptr)))
+ return m;
+
+ /* OK, not initialized, so let's allocate, and fill in */
+ m = pa_mutex_new(recursive, inherit_priority);
+ if ((pa_atomic_ptr_cmpxchg(&s->ptr, NULL, m)))
+ return m;
+
+ pa_mutex_free(m);
+
+ /* Him, filling in failed, so someone else must have filled in
+ * already */
+ pa_assert_se(m = pa_atomic_ptr_load(&s->ptr));
+ return m;
+}
diff --git a/src/pulsecore/mutex-win32.c b/src/pulsecore/mutex-win32.c
new file mode 100644
index 0000000..370f443
--- /dev/null
+++ b/src/pulsecore/mutex-win32.c
@@ -0,0 +1,154 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <windows.h>
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/hashmap.h>
+
+#include "mutex.h"
+
+struct pa_mutex {
+ CRITICAL_SECTION mutex;
+};
+
+struct pa_cond {
+ pa_hashmap *wait_events;
+};
+
+pa_mutex* pa_mutex_new(bool recursive, bool inherit_priority) {
+ pa_mutex *m;
+
+ m = pa_xnew(pa_mutex, 1);
+
+ InitializeCriticalSection(&m->mutex);
+
+ return m;
+}
+
+void pa_mutex_free(pa_mutex *m) {
+ assert(m);
+
+ DeleteCriticalSection(&m->mutex);
+ pa_xfree(m);
+}
+
+void pa_mutex_lock(pa_mutex *m) {
+ assert(m);
+
+ EnterCriticalSection(&m->mutex);
+}
+
+void pa_mutex_unlock(pa_mutex *m) {
+ assert(m);
+
+ LeaveCriticalSection(&m->mutex);
+}
+
+pa_cond *pa_cond_new(void) {
+ pa_cond *c;
+
+ c = pa_xnew(pa_cond, 1);
+ c->wait_events = pa_hashmap_new(NULL, NULL);
+ assert(c->wait_events);
+
+ return c;
+}
+
+void pa_cond_free(pa_cond *c) {
+ assert(c);
+
+ pa_hashmap_free(c->wait_events);
+ pa_xfree(c);
+}
+
+void pa_cond_signal(pa_cond *c, int broadcast) {
+ assert(c);
+
+ if (pa_hashmap_size(c->wait_events) == 0)
+ return;
+
+ if (broadcast)
+ SetEvent(pa_hashmap_first(c->wait_events));
+ else {
+ void *iter;
+ const void *key;
+ HANDLE event;
+
+ iter = NULL;
+ while (1) {
+ pa_hashmap_iterate(c->wait_events, &iter, &key);
+ if (key == NULL)
+ break;
+ event = (HANDLE)pa_hashmap_get(c->wait_events, key);
+ SetEvent(event);
+ }
+ }
+}
+
+int pa_cond_wait(pa_cond *c, pa_mutex *m) {
+ HANDLE event;
+
+ assert(c);
+ assert(m);
+
+ event = CreateEvent(NULL, FALSE, FALSE, NULL);
+ assert(event);
+
+ pa_hashmap_put(c->wait_events, event, event);
+
+ pa_mutex_unlock(m);
+
+ WaitForSingleObject(event, INFINITE);
+
+ pa_mutex_lock(m);
+
+ pa_hashmap_remove(c->wait_events, event);
+
+ CloseHandle(event);
+
+ return 0;
+}
+
+/* This is a copy of the function in mutex-posix.c */
+pa_mutex* pa_static_mutex_get(pa_static_mutex *s, bool recursive, bool inherit_priority) {
+ pa_mutex *m;
+
+ pa_assert(s);
+
+ /* First, check if already initialized and short cut */
+ if ((m = pa_atomic_ptr_load(&s->ptr)))
+ return m;
+
+ /* OK, not initialized, so let's allocate, and fill in */
+ m = pa_mutex_new(recursive, inherit_priority);
+ if ((pa_atomic_ptr_cmpxchg(&s->ptr, NULL, m)))
+ return m;
+
+ pa_mutex_free(m);
+
+ /* Him, filling in failed, so someone else must have filled in
+ * already */
+ pa_assert_se(m = pa_atomic_ptr_load(&s->ptr));
+ return m;
+}
diff --git a/src/pulsecore/mutex.h b/src/pulsecore/mutex.h
new file mode 100644
index 0000000..9cdd857
--- /dev/null
+++ b/src/pulsecore/mutex.h
@@ -0,0 +1,57 @@
+#ifndef foopulsemutexhfoo
+#define foopulsemutexhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulsecore/macro.h>
+#include <pulsecore/atomic.h>
+
+typedef struct pa_mutex pa_mutex;
+
+/* Please think twice before enabling priority inheritance. This is no
+ * magic wand! Use it only when the potentially prioritized threads are
+ * good candidates for it. Don't use this blindly! Also, note that
+ * only very few operating systems actually implement this, hence this
+ * is merely a hint. */
+pa_mutex* pa_mutex_new(bool recursive, bool inherit_priority);
+
+void pa_mutex_free(pa_mutex *m);
+void pa_mutex_lock(pa_mutex *m);
+bool pa_mutex_try_lock(pa_mutex *m);
+void pa_mutex_unlock(pa_mutex *m);
+
+typedef struct pa_cond pa_cond;
+
+pa_cond *pa_cond_new(void);
+void pa_cond_free(pa_cond *c);
+void pa_cond_signal(pa_cond *c, int broadcast);
+int pa_cond_wait(pa_cond *c, pa_mutex *m);
+
+/* Static mutexes are basically just atomically updated pointers to pa_mutex objects */
+
+typedef struct pa_static_mutex {
+ pa_atomic_ptr_t ptr;
+} pa_static_mutex;
+
+#define PA_STATIC_MUTEX_INIT { PA_ATOMIC_PTR_INIT(NULL) }
+
+pa_mutex* pa_static_mutex_get(pa_static_mutex *m, bool recursive, bool inherit_priority);
+
+#endif
diff --git a/src/pulsecore/namereg.c b/src/pulsecore/namereg.c
new file mode 100644
index 0000000..0b73885
--- /dev/null
+++ b/src/pulsecore/namereg.c
@@ -0,0 +1,227 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/source.h>
+#include <pulsecore/sink.h>
+#include <pulsecore/core-subscribe.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/macro.h>
+
+#include "namereg.h"
+
+struct namereg_entry {
+ pa_namereg_type_t type;
+ char *name;
+ void *data;
+};
+
+static bool is_valid_char(char c) {
+ return
+ (c >= 'a' && c <= 'z') ||
+ (c >= 'A' && c <= 'Z') ||
+ (c >= '0' && c <= '9') ||
+ c == '.' ||
+ c == '-' ||
+ c == '_';
+}
+
+bool pa_namereg_is_valid_name(const char *name) {
+ const char *c;
+
+ pa_assert(name);
+
+ if (*name == 0)
+ return false;
+
+ for (c = name; *c && (c-name < PA_NAME_MAX); c++)
+ if (!is_valid_char(*c))
+ return false;
+
+ if (*c)
+ return false;
+
+ return true;
+}
+
+bool pa_namereg_is_valid_name_or_wildcard(const char *name, pa_namereg_type_t type) {
+
+ pa_assert(name);
+
+ if (pa_namereg_is_valid_name(name))
+ return true;
+
+ if (type == PA_NAMEREG_SINK &&
+ pa_streq(name, "@DEFAULT_SINK@"))
+ return true;
+
+ if (type == PA_NAMEREG_SOURCE &&
+ (pa_streq(name, "@DEFAULT_SOURCE@") ||
+ pa_streq(name, "@DEFAULT_MONITOR@")))
+ return true;
+
+ return false;
+}
+
+char* pa_namereg_make_valid_name(const char *name) {
+ const char *a;
+ char *b, *n;
+
+ if (*name == 0)
+ return NULL;
+
+ n = pa_xnew(char, strlen(name)+1);
+
+ for (a = name, b = n; *a && (a-name < PA_NAME_MAX); a++, b++)
+ *b = (char) (is_valid_char(*a) ? *a : '_');
+
+ *b = 0;
+
+ return n;
+}
+
+const char *pa_namereg_register(pa_core *c, const char *name, pa_namereg_type_t type, void *data, bool fail) {
+ struct namereg_entry *e;
+ char *n = NULL;
+
+ pa_assert(c);
+ pa_assert(name);
+ pa_assert(data);
+
+ if (!*name)
+ return NULL;
+
+ if ((type == PA_NAMEREG_SINK || type == PA_NAMEREG_SOURCE || type == PA_NAMEREG_CARD) &&
+ !pa_namereg_is_valid_name(name)) {
+
+ if (fail)
+ return NULL;
+
+ if (!(name = n = pa_namereg_make_valid_name(name)))
+ return NULL;
+ }
+
+ if ((e = pa_hashmap_get(c->namereg, name)) && fail) {
+ pa_xfree(n);
+ return NULL;
+ }
+
+ if (e) {
+ unsigned i;
+ size_t l = strlen(name);
+ char *k;
+
+ if (l+4 > PA_NAME_MAX) {
+ pa_xfree(n);
+ return NULL;
+ }
+
+ k = pa_xmalloc(l+4);
+
+ for (i = 2; i <= 99; i++) {
+ pa_snprintf(k, l+4, "%s.%u", name, i);
+
+ if (!(e = pa_hashmap_get(c->namereg, k)))
+ break;
+ }
+
+ if (e) {
+ pa_xfree(n);
+ pa_xfree(k);
+ return NULL;
+ }
+
+ pa_xfree(n);
+ n = k;
+ }
+
+ e = pa_xnew(struct namereg_entry, 1);
+ e->type = type;
+ e->name = n ? n : pa_xstrdup(name);
+ e->data = data;
+
+ pa_assert_se(pa_hashmap_put(c->namereg, e->name, e) >= 0);
+
+ return e->name;
+}
+
+void pa_namereg_unregister(pa_core *c, const char *name) {
+ struct namereg_entry *e;
+
+ pa_assert(c);
+ pa_assert(name);
+
+ pa_assert_se(e = pa_hashmap_remove(c->namereg, name));
+ pa_xfree(e->name);
+ pa_xfree(e);
+}
+
+void* pa_namereg_get(pa_core *c, const char *name, pa_namereg_type_t type) {
+ struct namereg_entry *e;
+ uint32_t idx;
+ pa_assert(c);
+
+ if (type == PA_NAMEREG_SOURCE && (!name || pa_streq(name, "@DEFAULT_SOURCE@"))) {
+ return c->default_source;
+
+ } else if (type == PA_NAMEREG_SINK && (!name || pa_streq(name, "@DEFAULT_SINK@"))) {
+ return c->default_sink;
+
+ } else if (type == PA_NAMEREG_SOURCE && name && pa_streq(name, "@DEFAULT_MONITOR@")) {
+ if (c->default_sink)
+ return c->default_sink->monitor_source;
+ else
+ return NULL;
+ }
+
+ if (!name)
+ return NULL;
+
+ if ((type == PA_NAMEREG_SINK || type == PA_NAMEREG_SOURCE || type == PA_NAMEREG_CARD) &&
+ !pa_namereg_is_valid_name(name))
+ return NULL;
+
+ if ((e = pa_hashmap_get(c->namereg, name)))
+ if (e->type == type)
+ return e->data;
+
+ if (pa_atou(name, &idx) < 0)
+ return NULL;
+
+ if (type == PA_NAMEREG_SINK)
+ return pa_idxset_get_by_index(c->sinks, idx);
+ else if (type == PA_NAMEREG_SOURCE)
+ return pa_idxset_get_by_index(c->sources, idx);
+ else if (type == PA_NAMEREG_SAMPLE && c->scache)
+ return pa_idxset_get_by_index(c->scache, idx);
+ else if (type == PA_NAMEREG_CARD)
+ return pa_idxset_get_by_index(c->cards, idx);
+
+ return NULL;
+}
diff --git a/src/pulsecore/namereg.h b/src/pulsecore/namereg.h
new file mode 100644
index 0000000..eb3475a
--- /dev/null
+++ b/src/pulsecore/namereg.h
@@ -0,0 +1,43 @@
+#ifndef foonamereghfoo
+#define foonamereghfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulsecore/core.h>
+#include <pulsecore/macro.h>
+
+#define PA_NAME_MAX 128
+
+typedef enum pa_namereg_type {
+ PA_NAMEREG_SINK,
+ PA_NAMEREG_SOURCE,
+ PA_NAMEREG_SAMPLE,
+ PA_NAMEREG_CARD
+} pa_namereg_type_t;
+
+const char *pa_namereg_register(pa_core *c, const char *name, pa_namereg_type_t type, void *data, bool fail);
+void pa_namereg_unregister(pa_core *c, const char *name);
+void* pa_namereg_get(pa_core *c, const char *name, pa_namereg_type_t type);
+
+bool pa_namereg_is_valid_name(const char *name);
+bool pa_namereg_is_valid_name_or_wildcard(const char *name, pa_namereg_type_t type);
+char* pa_namereg_make_valid_name(const char *name);
+
+#endif
diff --git a/src/pulsecore/native-common.c b/src/pulsecore/native-common.c
new file mode 100644
index 0000000..282a4ed
--- /dev/null
+++ b/src/pulsecore/native-common.c
@@ -0,0 +1,78 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2016 Ahmed S. Darwish <darwish.07@gmail.com>
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/creds.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/pdispatch.h>
+#include <pulsecore/pstream.h>
+#include <pulsecore/tagstruct.h>
+
+#include "native-common.h"
+
+/*
+ * Command handlers shared between client and server
+ */
+
+/* Check pa_pstream_register_memfd_mempool() for further details */
+int pa_common_command_register_memfd_shmid(pa_pstream *p, pa_pdispatch *pd, uint32_t version,
+ uint32_t command, pa_tagstruct *t) {
+#if defined(HAVE_CREDS) && defined(HAVE_MEMFD)
+ pa_cmsg_ancil_data *ancil = NULL;
+ unsigned shm_id;
+ int ret = -1;
+
+ pa_assert(pd);
+ pa_assert(command == PA_COMMAND_REGISTER_MEMFD_SHMID);
+ pa_assert(t);
+
+ ancil = pa_pdispatch_take_ancil_data(pd);
+ if (!ancil)
+ goto finish;
+
+ /* Upon fd leaks and reaching our open fd limit, recvmsg(2)
+ * just strips all passed fds from the ancillary data */
+ if (ancil->nfd == 0) {
+ pa_log("Expected 1 memfd fd to be received over pipe; got 0");
+ pa_log("Did we reach our open file descriptors limit?");
+ goto finish;
+ }
+
+ if (ancil->nfd != 1 || ancil->fds[0] == -1)
+ goto finish;
+
+ if (version < 31 || pa_tagstruct_getu32(t, &shm_id) < 0 || !pa_tagstruct_eof(t))
+ goto finish;
+
+ pa_pstream_attach_memfd_shmid(p, shm_id, ancil->fds[0]);
+
+ ret = 0;
+finish:
+ if (ancil)
+ pa_cmsg_ancil_data_close_fds(ancil);
+
+ return ret;
+#else
+ return -1;
+#endif
+}
diff --git a/src/pulsecore/native-common.h b/src/pulsecore/native-common.h
new file mode 100644
index 0000000..70338b9
--- /dev/null
+++ b/src/pulsecore/native-common.h
@@ -0,0 +1,209 @@
+#ifndef foonativecommonhfoo
+#define foonativecommonhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulse/cdecl.h>
+#include <pulse/def.h>
+
+#include <pulsecore/pdispatch.h>
+#include <pulsecore/pstream.h>
+#include <pulsecore/tagstruct.h>
+
+PA_C_DECL_BEGIN
+
+enum {
+ /* Generic commands */
+ PA_COMMAND_ERROR,
+ PA_COMMAND_TIMEOUT, /* pseudo command */
+ PA_COMMAND_REPLY,
+
+ /* CLIENT->SERVER */
+ PA_COMMAND_CREATE_PLAYBACK_STREAM, /* Payload changed in v9, v12 (0.9.0, 0.9.8) */
+ PA_COMMAND_DELETE_PLAYBACK_STREAM,
+ PA_COMMAND_CREATE_RECORD_STREAM, /* Payload changed in v9, v12 (0.9.0, 0.9.8) */
+ PA_COMMAND_DELETE_RECORD_STREAM,
+ PA_COMMAND_EXIT,
+ PA_COMMAND_AUTH,
+ PA_COMMAND_SET_CLIENT_NAME,
+ PA_COMMAND_LOOKUP_SINK,
+ PA_COMMAND_LOOKUP_SOURCE,
+ PA_COMMAND_DRAIN_PLAYBACK_STREAM,
+ PA_COMMAND_STAT,
+ PA_COMMAND_GET_PLAYBACK_LATENCY,
+ PA_COMMAND_CREATE_UPLOAD_STREAM,
+ PA_COMMAND_DELETE_UPLOAD_STREAM,
+ PA_COMMAND_FINISH_UPLOAD_STREAM,
+ PA_COMMAND_PLAY_SAMPLE,
+ PA_COMMAND_REMOVE_SAMPLE,
+
+ PA_COMMAND_GET_SERVER_INFO,
+ PA_COMMAND_GET_SINK_INFO,
+ PA_COMMAND_GET_SINK_INFO_LIST,
+ PA_COMMAND_GET_SOURCE_INFO,
+ PA_COMMAND_GET_SOURCE_INFO_LIST,
+ PA_COMMAND_GET_MODULE_INFO,
+ PA_COMMAND_GET_MODULE_INFO_LIST,
+ PA_COMMAND_GET_CLIENT_INFO,
+ PA_COMMAND_GET_CLIENT_INFO_LIST,
+ PA_COMMAND_GET_SINK_INPUT_INFO, /* Payload changed in v11 (0.9.7) */
+ PA_COMMAND_GET_SINK_INPUT_INFO_LIST, /* Payload changed in v11 (0.9.7) */
+ PA_COMMAND_GET_SOURCE_OUTPUT_INFO,
+ PA_COMMAND_GET_SOURCE_OUTPUT_INFO_LIST,
+ PA_COMMAND_GET_SAMPLE_INFO,
+ PA_COMMAND_GET_SAMPLE_INFO_LIST,
+ PA_COMMAND_SUBSCRIBE,
+
+ PA_COMMAND_SET_SINK_VOLUME,
+ PA_COMMAND_SET_SINK_INPUT_VOLUME,
+ PA_COMMAND_SET_SOURCE_VOLUME,
+
+ PA_COMMAND_SET_SINK_MUTE,
+ PA_COMMAND_SET_SOURCE_MUTE,
+
+ PA_COMMAND_CORK_PLAYBACK_STREAM,
+ PA_COMMAND_FLUSH_PLAYBACK_STREAM,
+ PA_COMMAND_TRIGGER_PLAYBACK_STREAM,
+
+ PA_COMMAND_SET_DEFAULT_SINK,
+ PA_COMMAND_SET_DEFAULT_SOURCE,
+
+ PA_COMMAND_SET_PLAYBACK_STREAM_NAME,
+ PA_COMMAND_SET_RECORD_STREAM_NAME,
+
+ PA_COMMAND_KILL_CLIENT,
+ PA_COMMAND_KILL_SINK_INPUT,
+ PA_COMMAND_KILL_SOURCE_OUTPUT,
+
+ PA_COMMAND_LOAD_MODULE,
+ PA_COMMAND_UNLOAD_MODULE,
+
+ /* Obsolete */
+ PA_COMMAND_ADD_AUTOLOAD___OBSOLETE,
+ PA_COMMAND_REMOVE_AUTOLOAD___OBSOLETE,
+ PA_COMMAND_GET_AUTOLOAD_INFO___OBSOLETE,
+ PA_COMMAND_GET_AUTOLOAD_INFO_LIST___OBSOLETE,
+
+ PA_COMMAND_GET_RECORD_LATENCY,
+ PA_COMMAND_CORK_RECORD_STREAM,
+ PA_COMMAND_FLUSH_RECORD_STREAM,
+ PA_COMMAND_PREBUF_PLAYBACK_STREAM,
+
+ /* SERVER->CLIENT */
+ PA_COMMAND_REQUEST,
+ PA_COMMAND_OVERFLOW,
+ PA_COMMAND_UNDERFLOW,
+ PA_COMMAND_PLAYBACK_STREAM_KILLED,
+ PA_COMMAND_RECORD_STREAM_KILLED,
+ PA_COMMAND_SUBSCRIBE_EVENT,
+
+ /* A few more client->server commands */
+
+ /* Supported since protocol v10 (0.9.5) */
+ PA_COMMAND_MOVE_SINK_INPUT,
+ PA_COMMAND_MOVE_SOURCE_OUTPUT,
+
+ /* Supported since protocol v11 (0.9.7) */
+ PA_COMMAND_SET_SINK_INPUT_MUTE,
+
+ PA_COMMAND_SUSPEND_SINK,
+ PA_COMMAND_SUSPEND_SOURCE,
+
+ /* Supported since protocol v12 (0.9.8) */
+ PA_COMMAND_SET_PLAYBACK_STREAM_BUFFER_ATTR,
+ PA_COMMAND_SET_RECORD_STREAM_BUFFER_ATTR,
+
+ PA_COMMAND_UPDATE_PLAYBACK_STREAM_SAMPLE_RATE,
+ PA_COMMAND_UPDATE_RECORD_STREAM_SAMPLE_RATE,
+
+ /* SERVER->CLIENT */
+ PA_COMMAND_PLAYBACK_STREAM_SUSPENDED,
+ PA_COMMAND_RECORD_STREAM_SUSPENDED,
+ PA_COMMAND_PLAYBACK_STREAM_MOVED,
+ PA_COMMAND_RECORD_STREAM_MOVED,
+
+ /* Supported since protocol v13 (0.9.11) */
+ PA_COMMAND_UPDATE_RECORD_STREAM_PROPLIST,
+ PA_COMMAND_UPDATE_PLAYBACK_STREAM_PROPLIST,
+ PA_COMMAND_UPDATE_CLIENT_PROPLIST,
+ PA_COMMAND_REMOVE_RECORD_STREAM_PROPLIST,
+ PA_COMMAND_REMOVE_PLAYBACK_STREAM_PROPLIST,
+ PA_COMMAND_REMOVE_CLIENT_PROPLIST,
+
+ /* SERVER->CLIENT */
+ PA_COMMAND_STARTED,
+
+ /* Supported since protocol v14 (0.9.12) */
+ PA_COMMAND_EXTENSION,
+
+ /* Supported since protocol v15 (0.9.15) */
+ PA_COMMAND_GET_CARD_INFO,
+ PA_COMMAND_GET_CARD_INFO_LIST,
+ PA_COMMAND_SET_CARD_PROFILE,
+
+ PA_COMMAND_CLIENT_EVENT,
+ PA_COMMAND_PLAYBACK_STREAM_EVENT,
+ PA_COMMAND_RECORD_STREAM_EVENT,
+
+ /* SERVER->CLIENT */
+ PA_COMMAND_PLAYBACK_BUFFER_ATTR_CHANGED,
+ PA_COMMAND_RECORD_BUFFER_ATTR_CHANGED,
+
+ /* Supported since protocol v16 (0.9.16) */
+ PA_COMMAND_SET_SINK_PORT,
+ PA_COMMAND_SET_SOURCE_PORT,
+
+ /* Supported since protocol v22 (1.0) */
+ PA_COMMAND_SET_SOURCE_OUTPUT_VOLUME,
+ PA_COMMAND_SET_SOURCE_OUTPUT_MUTE,
+
+ /* Supported since protocol v27 (3.0) */
+ PA_COMMAND_SET_PORT_LATENCY_OFFSET,
+
+ /* Supported since protocol v30 (6.0) */
+ /* BOTH DIRECTIONS */
+ PA_COMMAND_ENABLE_SRBCHANNEL,
+ PA_COMMAND_DISABLE_SRBCHANNEL,
+
+ /* Supported since protocol v31 (9.0)
+ * BOTH DIRECTIONS */
+ PA_COMMAND_REGISTER_MEMFD_SHMID,
+
+ PA_COMMAND_MAX
+};
+
+#define PA_NATIVE_COOKIE_LENGTH 256
+#define PA_NATIVE_COOKIE_FILE "cookie"
+#define PA_NATIVE_COOKIE_FILE_FALLBACK ".pulse-cookie"
+
+#define PA_NATIVE_DEFAULT_PORT 4713
+
+#define PA_NATIVE_COOKIE_PROPERTY_NAME "protocol-native-cookie"
+#define PA_NATIVE_SERVER_PROPERTY_NAME "protocol-native-server"
+
+#define PA_NATIVE_DEFAULT_UNIX_SOCKET "native"
+
+int pa_common_command_register_memfd_shmid(pa_pstream *p, pa_pdispatch *pd, uint32_t version,
+ uint32_t command, pa_tagstruct *t);
+
+PA_C_DECL_END
+
+#endif
diff --git a/src/pulsecore/object.c b/src/pulsecore/object.c
new file mode 100644
index 0000000..cb0328f
--- /dev/null
+++ b/src/pulsecore/object.c
@@ -0,0 +1,70 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "object.h"
+
+const char pa_object_type_id[] = "pa_object";
+
+pa_object *pa_object_new_internal(size_t size, const char *type_id, bool (*check_type)(const char *type_id)) {
+ pa_object *o;
+
+ pa_assert(size > sizeof(pa_object));
+ pa_assert(type_id);
+
+ if (!check_type)
+ check_type = pa_object_check_type;
+
+ pa_assert(check_type(type_id));
+ pa_assert(check_type(pa_object_type_id));
+
+ o = pa_xmalloc0(size);
+ PA_REFCNT_INIT(o);
+ o->type_id = type_id;
+ o->free = pa_object_free;
+ o->check_type = check_type;
+
+ return o;
+}
+
+pa_object *pa_object_ref(pa_object *o) {
+ pa_object_assert_ref(o);
+
+ PA_REFCNT_INC(o);
+ return o;
+}
+
+void pa_object_unref(pa_object *o) {
+ pa_object_assert_ref(o);
+
+ if (PA_REFCNT_DEC(o) <= 0) {
+ pa_assert(o->free);
+ o->free(o);
+ }
+}
+
+bool pa_object_check_type(const char *type_id) {
+ pa_assert(type_id);
+
+ return type_id == pa_object_type_id;
+}
diff --git a/src/pulsecore/object.h b/src/pulsecore/object.h
new file mode 100644
index 0000000..15e8365
--- /dev/null
+++ b/src/pulsecore/object.h
@@ -0,0 +1,117 @@
+#ifndef foopulseobjecthfoo
+#define foopulseobjecthfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/types.h>
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/refcnt.h>
+#include <pulsecore/macro.h>
+
+typedef struct pa_object pa_object;
+
+struct pa_object {
+ PA_REFCNT_DECLARE;
+ const char *type_id;
+ void (*free)(pa_object *o);
+ bool (*check_type)(const char *type_name);
+};
+
+pa_object *pa_object_new_internal(size_t size, const char *type_id, bool (*check_type)(const char *type_id));
+#define pa_object_new(type) ((type*) pa_object_new_internal(sizeof(type), type##_type_id, type##_check_type))
+
+#define pa_object_free ((void (*) (pa_object* _obj)) pa_xfree)
+
+bool pa_object_check_type(const char *type_id);
+
+extern const char pa_object_type_id[];
+
+static inline bool pa_object_isinstance(void *o) {
+ pa_object *obj = (pa_object*) o;
+ return obj ? obj->check_type(pa_object_type_id) : true;
+}
+
+pa_object *pa_object_ref(pa_object *o);
+void pa_object_unref(pa_object *o);
+
+static inline int pa_object_refcnt(pa_object *o) {
+ return o ? PA_REFCNT_VALUE(o) : 0;
+}
+
+static inline pa_object* pa_object_cast(void *o) {
+ pa_object *obj = (pa_object*) o;
+ pa_assert(!obj || obj->check_type(pa_object_type_id));
+ return obj;
+}
+
+#define pa_object_assert_ref(o) pa_assert(pa_object_refcnt(o) > 0)
+
+#define PA_OBJECT(o) pa_object_cast(o)
+
+#define PA_DECLARE_CLASS_COMMON(c) \
+ static inline bool c##_isinstance(void *o) { \
+ pa_object *obj = (pa_object*) o; \
+ return obj ? obj->check_type(c##_type_id) : true; \
+ } \
+ static inline c* c##_cast(void *o) { \
+ pa_assert(c##_isinstance(o)); \
+ return (c*) o; \
+ } \
+ static inline c* c##_ref(c *o) { \
+ return (c *) ((void *) pa_object_ref(PA_OBJECT(o))); \
+ } \
+ static inline void c##_unref(c* o) { \
+ pa_object_unref(PA_OBJECT(o)); \
+ } \
+ static inline int c##_refcnt(c* o) { \
+ return pa_object_refcnt(PA_OBJECT(o)); \
+ } \
+ static inline void c##_assert_ref(c *o) { \
+ pa_object_assert_ref(PA_OBJECT(o)); \
+ } \
+ struct __stupid_useless_struct_to_allow_trailing_semicolon
+
+#define PA_DECLARE_PUBLIC_CLASS(c) \
+ extern const char c##_type_id[]; \
+ PA_DECLARE_CLASS_COMMON(c); \
+ bool c##_check_type(const char *type_id)
+
+#define PA_DEFINE_PUBLIC_CLASS(c, parent) \
+ const char c##_type_id[] = #c; \
+ bool c##_check_type(const char *type_id) { \
+ if (type_id == c##_type_id) \
+ return true; \
+ return parent##_check_type(type_id); \
+ } \
+ struct __stupid_useless_struct_to_allow_trailing_semicolon
+
+#define PA_DEFINE_PRIVATE_CLASS(c, parent) \
+ static const char c##_type_id[] = #c; \
+ PA_DECLARE_CLASS_COMMON(c); \
+ static bool c##_check_type(const char *type_id) { \
+ if (type_id == c##_type_id) \
+ return true; \
+ return parent##_check_type(type_id); \
+ } \
+ struct __stupid_useless_struct_to_allow_trailing_semicolon
+
+#endif
diff --git a/src/pulsecore/once.c b/src/pulsecore/once.c
new file mode 100644
index 0000000..6e52801
--- /dev/null
+++ b/src/pulsecore/once.c
@@ -0,0 +1,75 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulsecore/macro.h>
+
+#include "once.h"
+
+/* See http://www.hpl.hp.com/research/linux/atomic_ops/example.php4 for the
+ * reference algorithm used here. */
+
+bool pa_once_begin(pa_once *control) {
+ pa_mutex *m;
+
+ pa_assert(control);
+
+ if (pa_atomic_load(&control->done))
+ return false;
+
+ /* Caveat: We have to make sure that the once func has completed
+ * before returning, even if the once func is not actually
+ * executed by us. Hence the awkward locking. */
+
+ m = pa_static_mutex_get(&control->mutex, false, false);
+ pa_mutex_lock(m);
+
+ if (pa_atomic_load(&control->done)) {
+ pa_mutex_unlock(m);
+ return false;
+ }
+
+ return true;
+}
+
+void pa_once_end(pa_once *control) {
+ pa_mutex *m;
+
+ pa_assert(control);
+
+ pa_assert(!pa_atomic_load(&control->done));
+ pa_atomic_store(&control->done, 1);
+
+ m = pa_static_mutex_get(&control->mutex, false, false);
+ pa_mutex_unlock(m);
+}
+
+/* Not reentrant -- how could it be? */
+void pa_run_once(pa_once *control, pa_once_func_t func) {
+ pa_assert(control);
+ pa_assert(func);
+
+ if (pa_once_begin(control)) {
+ func();
+ pa_once_end(control);
+ }
+}
diff --git a/src/pulsecore/once.h b/src/pulsecore/once.h
new file mode 100644
index 0000000..8cd7894
--- /dev/null
+++ b/src/pulsecore/once.h
@@ -0,0 +1,71 @@
+#ifndef foopulseoncehfoo
+#define foopulseoncehfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulsecore/atomic.h>
+#include <pulsecore/mutex.h>
+
+typedef struct pa_once {
+ pa_static_mutex mutex;
+ pa_atomic_t done;
+} pa_once;
+
+#define PA_ONCE_INIT \
+ { \
+ .mutex = PA_STATIC_MUTEX_INIT, \
+ .done = PA_ATOMIC_INIT(0) \
+ }
+
+/* Not to be called directly, use the macros defined below instead */
+bool pa_once_begin(pa_once *o);
+void pa_once_end(pa_once *o);
+
+#define PA_ONCE_BEGIN \
+ do { \
+ static pa_once _once = PA_ONCE_INIT; \
+ if (pa_once_begin(&_once)) {{
+
+#define PA_ONCE_END \
+ } \
+ pa_once_end(&_once); \
+ } \
+ } while(0)
+
+/*
+
+ Usage of these macros is like this:
+
+ void foo() {
+
+ PA_ONCE_BEGIN {
+
+ ... stuff to be called just once ...
+
+ } PA_ONCE_END;
+ }
+
+*/
+
+/* Same API but calls a function */
+typedef void (*pa_once_func_t) (void);
+void pa_run_once(pa_once *o, pa_once_func_t f);
+
+#endif
diff --git a/src/pulsecore/packet.c b/src/pulsecore/packet.c
new file mode 100644
index 0000000..2a61d58
--- /dev/null
+++ b/src/pulsecore/packet.c
@@ -0,0 +1,122 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/refcnt.h>
+#include <pulsecore/flist.h>
+
+#include "packet.h"
+
+#define MAX_APPENDED_SIZE 128
+
+struct pa_packet {
+ PA_REFCNT_DECLARE;
+ enum { PA_PACKET_APPENDED, PA_PACKET_DYNAMIC } type;
+ size_t length;
+ uint8_t *data;
+ union {
+ uint8_t appended[MAX_APPENDED_SIZE];
+ } per_type;
+};
+
+PA_STATIC_FLIST_DECLARE(packets, 0, pa_xfree);
+
+pa_packet* pa_packet_new(size_t length) {
+ pa_packet *p;
+
+ pa_assert(length > 0);
+
+ if (!(p = pa_flist_pop(PA_STATIC_FLIST_GET(packets))))
+ p = pa_xnew(pa_packet, 1);
+ PA_REFCNT_INIT(p);
+ p->length = length;
+ if (length > MAX_APPENDED_SIZE) {
+ p->data = pa_xmalloc(length);
+ p->type = PA_PACKET_DYNAMIC;
+ } else {
+ p->data = p->per_type.appended;
+ p->type = PA_PACKET_APPENDED;
+ }
+
+ return p;
+}
+
+pa_packet* pa_packet_new_data(const void* data, size_t length) {
+ pa_packet *p = pa_packet_new(length);
+
+ pa_assert(data);
+ pa_assert(length > 0);
+
+ memcpy(p->data, data, length);
+
+ return p;
+}
+
+pa_packet* pa_packet_new_dynamic(void* data, size_t length) {
+ pa_packet *p;
+
+ pa_assert(data);
+ pa_assert(length > 0);
+
+ if (!(p = pa_flist_pop(PA_STATIC_FLIST_GET(packets))))
+ p = pa_xnew(pa_packet, 1);
+ PA_REFCNT_INIT(p);
+ p->length = length;
+ p->data = data;
+ p->type = PA_PACKET_DYNAMIC;
+
+ return p;
+}
+
+const void* pa_packet_data(pa_packet *p, size_t *l) {
+ pa_assert(PA_REFCNT_VALUE(p) >= 1);
+ pa_assert(p->data);
+ pa_assert(l);
+
+ *l = p->length;
+
+ return p->data;
+}
+
+pa_packet* pa_packet_ref(pa_packet *p) {
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) >= 1);
+
+ PA_REFCNT_INC(p);
+ return p;
+}
+
+void pa_packet_unref(pa_packet *p) {
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) >= 1);
+
+ if (PA_REFCNT_DEC(p) <= 0) {
+ if (p->type == PA_PACKET_DYNAMIC)
+ pa_xfree(p->data);
+ if (pa_flist_push(PA_STATIC_FLIST_GET(packets), p) < 0)
+ pa_xfree(p);
+ }
+}
diff --git a/src/pulsecore/packet.h b/src/pulsecore/packet.h
new file mode 100644
index 0000000..2060987
--- /dev/null
+++ b/src/pulsecore/packet.h
@@ -0,0 +1,45 @@
+#ifndef foopackethfoo
+#define foopackethfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/types.h>
+#include <inttypes.h>
+
+typedef struct pa_packet pa_packet;
+
+/* create empty packet (either of type appended or dynamic depending
+ * on length) */
+pa_packet* pa_packet_new(size_t length);
+
+/* create packet (either of type appended or dynamic depending on length)
+ * and copy data */
+pa_packet* pa_packet_new_data(const void* data, size_t length);
+
+/* data must have been malloc()ed; the packet takes ownership of the memory,
+ * i.e. memory is free()d with the packet */
+pa_packet* pa_packet_new_dynamic(void* data, size_t length);
+
+const void* pa_packet_data(pa_packet *p, size_t *l);
+
+pa_packet* pa_packet_ref(pa_packet *p);
+void pa_packet_unref(pa_packet *p);
+
+#endif
diff --git a/src/pulsecore/parseaddr.c b/src/pulsecore/parseaddr.c
new file mode 100644
index 0000000..b909f52
--- /dev/null
+++ b/src/pulsecore/parseaddr.c
@@ -0,0 +1,156 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/arpa-inet.h>
+
+#include "parseaddr.h"
+
+/* Parse addresses in one of the following forms:
+ * HOSTNAME
+ * HOSTNAME:PORT
+ * [HOSTNAME]
+ * [HOSTNAME]:PORT
+ *
+ * Return a newly allocated string of the hostname and fill in *ret_port if specified */
+
+static char *parse_host(const char *s, uint16_t *ret_port) {
+ pa_assert(s);
+ pa_assert(ret_port);
+
+ if (*s == '[') {
+ char *e;
+ if (!(e = strchr(s+1, ']')))
+ return NULL;
+
+ if (e[1] == ':') {
+ uint32_t p;
+
+ if (pa_atou(e+2, &p) < 0)
+ return NULL;
+
+ *ret_port = (uint16_t) p;
+ } else if (e[1] != 0)
+ return NULL;
+
+ return pa_xstrndup(s+1, (size_t) (e-s-1));
+ } else {
+ char *e;
+ uint32_t p;
+
+ if (!(e = strrchr(s, ':')))
+ return pa_xstrdup(s);
+
+ if (pa_atou(e+1, &p) < 0)
+ return NULL;
+
+ *ret_port = (uint16_t) p;
+ return pa_xstrndup(s, (size_t) (e-s));
+ }
+}
+
+int pa_parse_address(const char *name, pa_parsed_address *ret_p) {
+ const char *p;
+
+ pa_assert(name);
+ pa_assert(ret_p);
+
+ memset(ret_p, 0, sizeof(pa_parsed_address));
+ ret_p->type = PA_PARSED_ADDRESS_TCP_AUTO;
+
+ if (*name == '{') {
+ char *id, *pfx;
+
+ /* The URL starts with a host id for detecting local connections */
+ if (!(id = pa_machine_id()))
+ return -1;
+
+ pfx = pa_sprintf_malloc("{%s}", id);
+ pa_xfree(id);
+
+ if (!pa_startswith(name, pfx)) {
+ pa_xfree(pfx);
+ /* Not local */
+ return -1;
+ }
+
+ p = name + strlen(pfx);
+ pa_xfree(pfx);
+ } else
+ p = name;
+
+ if (*p == '/')
+ ret_p->type = PA_PARSED_ADDRESS_UNIX;
+ else if (pa_startswith(p, "unix:")) {
+ ret_p->type = PA_PARSED_ADDRESS_UNIX;
+ p += sizeof("unix:")-1;
+ } else if (pa_startswith(p, "tcp:")) {
+ ret_p->type = PA_PARSED_ADDRESS_TCP4;
+ p += sizeof("tcp:")-1;
+ } else if (pa_startswith(p, "tcp4:")) {
+ ret_p->type = PA_PARSED_ADDRESS_TCP4;
+ p += sizeof("tcp4:")-1;
+ } else if (pa_startswith(p, "tcp6:")) {
+ ret_p->type = PA_PARSED_ADDRESS_TCP6;
+ p += sizeof("tcp6:")-1;
+ }
+
+ if (ret_p->type == PA_PARSED_ADDRESS_UNIX)
+ ret_p->path_or_host = pa_xstrdup(p);
+ else
+ if (!(ret_p->path_or_host = parse_host(p, &ret_p->port)))
+ return -1;
+
+ return 0;
+}
+
+bool pa_is_ip_address(const char *a) {
+ char buf[INET6_ADDRSTRLEN];
+
+ pa_assert(a);
+
+ if (inet_pton(AF_INET6, a, buf) >= 1)
+ return true;
+
+ if (inet_pton(AF_INET, a, buf) >= 1)
+ return true;
+
+ return false;
+}
+
+bool pa_is_ip6_address(const char *a) {
+ char buf[INET6_ADDRSTRLEN];
+
+ pa_assert(a);
+
+ if (inet_pton(AF_INET6, a, buf) >= 1)
+ return true;
+
+ return false;
+}
diff --git a/src/pulsecore/parseaddr.h b/src/pulsecore/parseaddr.h
new file mode 100644
index 0000000..6bb4d85
--- /dev/null
+++ b/src/pulsecore/parseaddr.h
@@ -0,0 +1,46 @@
+#ifndef foopulsecoreparseaddrhfoo
+#define foopulsecoreparseaddrhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <inttypes.h>
+
+#include <pulsecore/macro.h>
+
+typedef enum pa_parsed_address_type {
+ PA_PARSED_ADDRESS_UNIX,
+ PA_PARSED_ADDRESS_TCP4,
+ PA_PARSED_ADDRESS_TCP6,
+ PA_PARSED_ADDRESS_TCP_AUTO
+} pa_parsed_address_type_t;
+
+typedef struct pa_parsed_address {
+ pa_parsed_address_type_t type;
+ char *path_or_host;
+ uint16_t port;
+} pa_parsed_address;
+
+int pa_parse_address(const char *a, pa_parsed_address *ret_p);
+
+bool pa_is_ip_address(const char *a);
+
+bool pa_is_ip6_address(const char *a);
+
+#endif
diff --git a/src/pulsecore/pdispatch.c b/src/pulsecore/pdispatch.c
new file mode 100644
index 0000000..ab632a5
--- /dev/null
+++ b/src/pulsecore/pdispatch.c
@@ -0,0 +1,475 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <pulse/rtclock.h>
+#include <pulse/timeval.h>
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/native-common.h>
+#include <pulsecore/llist.h>
+#include <pulsecore/log.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/refcnt.h>
+#include <pulsecore/flist.h>
+#include <pulsecore/core-rtclock.h>
+
+#include "pdispatch.h"
+
+/* #define DEBUG_OPCODES */
+
+#ifdef DEBUG_OPCODES
+
+static const char *command_names[PA_COMMAND_MAX] = {
+ /* Generic commands */
+ [PA_COMMAND_ERROR] = "ERROR",
+ [PA_COMMAND_TIMEOUT] = "TIMEOUT",
+ [PA_COMMAND_REPLY] = "REPLY",
+
+ /* CLIENT->SERVER */
+ [PA_COMMAND_CREATE_PLAYBACK_STREAM] = "CREATE_PLAYBACK_STREAM",
+ [PA_COMMAND_DELETE_PLAYBACK_STREAM] = "DELETE_PLAYBACK_STREAM",
+ [PA_COMMAND_CREATE_RECORD_STREAM] = "CREATE_RECORD_STREAM",
+ [PA_COMMAND_DELETE_RECORD_STREAM] = "DELETE_RECORD_STREAM",
+ [PA_COMMAND_AUTH] = "AUTH",
+ [PA_COMMAND_EXIT] = "EXIT",
+ [PA_COMMAND_SET_CLIENT_NAME] = "SET_CLIENT_NAME",
+ [PA_COMMAND_LOOKUP_SINK] = "LOOKUP_SINK",
+ [PA_COMMAND_LOOKUP_SOURCE] = "LOOKUP_SOURCE",
+ [PA_COMMAND_DRAIN_PLAYBACK_STREAM] = "DRAIN_PLAYBACK_STREAM",
+ [PA_COMMAND_STAT] = "STAT",
+ [PA_COMMAND_GET_PLAYBACK_LATENCY] = "GET_PLAYBACK_LATENCY",
+ [PA_COMMAND_CREATE_UPLOAD_STREAM] = "CREATE_UPLOAD_STREAM",
+ [PA_COMMAND_DELETE_UPLOAD_STREAM] = "DELETE_UPLOAD_STREAM",
+ [PA_COMMAND_FINISH_UPLOAD_STREAM] = "FINISH_UPLOAD_STREAM",
+ [PA_COMMAND_PLAY_SAMPLE] = "PLAY_SAMPLE",
+ [PA_COMMAND_REMOVE_SAMPLE] = "REMOVE_SAMPLE",
+
+ [PA_COMMAND_GET_SERVER_INFO] = "GET_SERVER_INFO",
+ [PA_COMMAND_GET_SINK_INFO] = "GET_SINK_INFO",
+ [PA_COMMAND_GET_SINK_INFO_LIST] = "GET_SINK_INFO_LIST",
+ [PA_COMMAND_GET_SOURCE_INFO] = "GET_SOURCE_INFO",
+ [PA_COMMAND_GET_SOURCE_INFO_LIST] = "GET_SOURCE_INFO_LIST",
+ [PA_COMMAND_GET_MODULE_INFO] = "GET_MODULE_INFO",
+ [PA_COMMAND_GET_MODULE_INFO_LIST] = "GET_MODULE_INFO_LIST",
+ [PA_COMMAND_GET_CLIENT_INFO] = "GET_CLIENT_INFO",
+ [PA_COMMAND_GET_CLIENT_INFO_LIST] = "GET_CLIENT_INFO_LIST",
+ [PA_COMMAND_GET_SAMPLE_INFO] = "GET_SAMPLE_INFO",
+ [PA_COMMAND_GET_SAMPLE_INFO_LIST] = "GET_SAMPLE_INFO_LIST",
+ [PA_COMMAND_GET_SINK_INPUT_INFO] = "GET_SINK_INPUT_INFO",
+ [PA_COMMAND_GET_SINK_INPUT_INFO_LIST] = "GET_SINK_INPUT_INFO_LIST",
+ [PA_COMMAND_GET_SOURCE_OUTPUT_INFO] = "GET_SOURCE_OUTPUT_INFO",
+ [PA_COMMAND_GET_SOURCE_OUTPUT_INFO_LIST] = "GET_SOURCE_OUTPUT_INFO_LIST",
+ [PA_COMMAND_SUBSCRIBE] = "SUBSCRIBE",
+
+ [PA_COMMAND_SET_SINK_VOLUME] = "SET_SINK_VOLUME",
+ [PA_COMMAND_SET_SINK_INPUT_VOLUME] = "SET_SINK_INPUT_VOLUME",
+ [PA_COMMAND_SET_SOURCE_VOLUME] = "SET_SOURCE_VOLUME",
+
+ [PA_COMMAND_SET_SINK_MUTE] = "SET_SINK_MUTE",
+ [PA_COMMAND_SET_SOURCE_MUTE] = "SET_SOURCE_MUTE",
+
+ [PA_COMMAND_TRIGGER_PLAYBACK_STREAM] = "TRIGGER_PLAYBACK_STREAM",
+ [PA_COMMAND_FLUSH_PLAYBACK_STREAM] = "FLUSH_PLAYBACK_STREAM",
+ [PA_COMMAND_CORK_PLAYBACK_STREAM] = "CORK_PLAYBACK_STREAM",
+
+ [PA_COMMAND_SET_DEFAULT_SINK] = "SET_DEFAULT_SINK",
+ [PA_COMMAND_SET_DEFAULT_SOURCE] = "SET_DEFAULT_SOURCE",
+
+ [PA_COMMAND_SET_PLAYBACK_STREAM_NAME] = "SET_PLAYBACK_STREAM_NAME",
+ [PA_COMMAND_SET_RECORD_STREAM_NAME] = "SET_RECORD_STREAM_NAME",
+
+ [PA_COMMAND_KILL_CLIENT] = "KILL_CLIENT",
+ [PA_COMMAND_KILL_SINK_INPUT] = "KILL_SINK_INPUT",
+ [PA_COMMAND_KILL_SOURCE_OUTPUT] = "KILL_SOURCE_OUTPUT",
+
+ [PA_COMMAND_LOAD_MODULE] = "LOAD_MODULE",
+ [PA_COMMAND_UNLOAD_MODULE] = "UNLOAD_MODULE",
+
+ [PA_COMMAND_ADD_AUTOLOAD___OBSOLETE] = "ADD_AUTOLOAD (obsolete)",
+ [PA_COMMAND_REMOVE_AUTOLOAD___OBSOLETE] = "REMOVE_AUTOLOAD (obsolete)",
+ [PA_COMMAND_GET_AUTOLOAD_INFO___OBSOLETE] = "GET_AUTOLOAD_INFO (obsolete)",
+ [PA_COMMAND_GET_AUTOLOAD_INFO_LIST___OBSOLETE] = "GET_AUTOLOAD_INFO_LIST (obsolete)",
+
+ [PA_COMMAND_GET_RECORD_LATENCY] = "GET_RECORD_LATENCY",
+ [PA_COMMAND_CORK_RECORD_STREAM] = "CORK_RECORD_STREAM",
+ [PA_COMMAND_FLUSH_RECORD_STREAM] = "FLUSH_RECORD_STREAM",
+ [PA_COMMAND_PREBUF_PLAYBACK_STREAM] = "PREBUF_PLAYBACK_STREAM",
+
+ /* SERVER->CLIENT */
+ [PA_COMMAND_REQUEST] = "REQUEST",
+ [PA_COMMAND_OVERFLOW] = "OVERFLOW",
+ [PA_COMMAND_UNDERFLOW] = "UNDERFLOW",
+ [PA_COMMAND_PLAYBACK_STREAM_KILLED] = "PLAYBACK_STREAM_KILLED",
+ [PA_COMMAND_RECORD_STREAM_KILLED] = "RECORD_STREAM_KILLED",
+ [PA_COMMAND_SUBSCRIBE_EVENT] = "SUBSCRIBE_EVENT",
+
+ /* A few more client->server commands */
+
+ /* Supported since protocol v10 (0.9.5) */
+ [PA_COMMAND_MOVE_SINK_INPUT] = "MOVE_SINK_INPUT",
+ [PA_COMMAND_MOVE_SOURCE_OUTPUT] = "MOVE_SOURCE_OUTPUT",
+
+ /* Supported since protocol v11 (0.9.7) */
+ [PA_COMMAND_SET_SINK_INPUT_MUTE] = "SET_SINK_INPUT_MUTE",
+
+ [PA_COMMAND_SUSPEND_SINK] = "SUSPEND_SINK",
+ [PA_COMMAND_SUSPEND_SOURCE] = "SUSPEND_SOURCE",
+
+ /* Supported since protocol v12 (0.9.8) */
+ [PA_COMMAND_SET_PLAYBACK_STREAM_BUFFER_ATTR] = "SET_PLAYBACK_STREAM_BUFFER_ATTR",
+ [PA_COMMAND_SET_RECORD_STREAM_BUFFER_ATTR] = "SET_RECORD_STREAM_BUFFER_ATTR",
+
+ [PA_COMMAND_UPDATE_PLAYBACK_STREAM_SAMPLE_RATE] = "UPDATE_PLAYBACK_STREAM_SAMPLE_RATE",
+ [PA_COMMAND_UPDATE_RECORD_STREAM_SAMPLE_RATE] = "UPDATE_RECORD_STREAM_SAMPLE_RATE",
+
+ /* SERVER->CLIENT */
+ [PA_COMMAND_PLAYBACK_STREAM_SUSPENDED] = "PLAYBACK_STREAM_SUSPENDED",
+ [PA_COMMAND_RECORD_STREAM_SUSPENDED] = "RECORD_STREAM_SUSPENDED",
+ [PA_COMMAND_PLAYBACK_STREAM_MOVED] = "PLAYBACK_STREAM_MOVED",
+ [PA_COMMAND_RECORD_STREAM_MOVED] = "RECORD_STREAM_MOVED",
+
+ /* Supported since protocol v13 (0.9.11) */
+ [PA_COMMAND_UPDATE_RECORD_STREAM_PROPLIST] = "UPDATE_RECORD_STREAM_PROPLIST",
+ [PA_COMMAND_UPDATE_PLAYBACK_STREAM_PROPLIST] = "UPDATE_PLAYBACK_STREAM_PROPLIST",
+ [PA_COMMAND_UPDATE_CLIENT_PROPLIST] = "UPDATE_CLIENT_PROPLIST",
+ [PA_COMMAND_REMOVE_RECORD_STREAM_PROPLIST] = "REMOVE_RECORD_STREAM_PROPLIST",
+ [PA_COMMAND_REMOVE_PLAYBACK_STREAM_PROPLIST] = "REMOVE_PLAYBACK_STREAM_PROPLIST",
+ [PA_COMMAND_REMOVE_CLIENT_PROPLIST] = "REMOVE_CLIENT_PROPLIST",
+
+ /* SERVER->CLIENT */
+ [PA_COMMAND_STARTED] = "STARTED",
+
+ /* Supported since protocol v14 (0.9.12) */
+ [PA_COMMAND_EXTENSION] = "EXTENSION",
+
+ /* Supported since protocol v15 (0.9.15) */
+ [PA_COMMAND_GET_CARD_INFO] = "GET_CARD_INFO",
+ [PA_COMMAND_GET_CARD_INFO_LIST] = "GET_CARD_INFO_LIST",
+ [PA_COMMAND_SET_CARD_PROFILE] = "SET_CARD_PROFILE",
+
+ [PA_COMMAND_CLIENT_EVENT] = "CLIENT_EVENT",
+ [PA_COMMAND_PLAYBACK_STREAM_EVENT] = "PLAYBACK_STREAM_EVENT",
+ [PA_COMMAND_RECORD_STREAM_EVENT] = "RECORD_STREAM_EVENT",
+
+ /* SERVER->CLIENT */
+ [PA_COMMAND_PLAYBACK_BUFFER_ATTR_CHANGED] = "PLAYBACK_BUFFER_ATTR_CHANGED",
+ [PA_COMMAND_RECORD_BUFFER_ATTR_CHANGED] = "RECORD_BUFFER_ATTR_CHANGED",
+
+ /* Supported since protocol v16 (0.9.16) */
+ [PA_COMMAND_SET_SINK_PORT] = "SET_SINK_PORT",
+ [PA_COMMAND_SET_SOURCE_PORT] = "SET_SOURCE_PORT",
+
+ /* Supported since protocol v22 (1.0) */
+ [PA_COMMAND_SET_SOURCE_OUTPUT_VOLUME] = "SET_SOURCE_OUTPUT_VOLUME",
+ [PA_COMMAND_SET_SOURCE_OUTPUT_MUTE] = "SET_SOURCE_OUTPUT_MUTE",
+
+ /* Supported since protocol v27 (3.0) */
+ [PA_COMMAND_SET_PORT_LATENCY_OFFSET] = "SET_PORT_LATENCY_OFFSET",
+
+ /* Supported since protocol v30 (6.0) */
+ /* BOTH DIRECTIONS */
+ [PA_COMMAND_ENABLE_SRBCHANNEL] = "ENABLE_SRBCHANNEL",
+ [PA_COMMAND_DISABLE_SRBCHANNEL] = "DISABLE_SRBCHANNEL",
+
+ /* Supported since protocol v31 (9.0) */
+ /* BOTH DIRECTIONS */
+ [PA_COMMAND_REGISTER_MEMFD_SHMID] = "REGISTER_MEMFD_SHMID",
+};
+
+#endif
+
+PA_STATIC_FLIST_DECLARE(reply_infos, 0, pa_xfree);
+
+struct reply_info {
+ pa_pdispatch *pdispatch;
+ PA_LLIST_FIELDS(struct reply_info);
+ pa_pdispatch_cb_t callback;
+ void *userdata;
+ pa_free_cb_t free_cb;
+ uint32_t tag;
+ pa_time_event *time_event;
+};
+
+struct pa_pdispatch {
+ PA_REFCNT_DECLARE;
+ pa_mainloop_api *mainloop;
+ const pa_pdispatch_cb_t *callback_table;
+ unsigned n_commands;
+ PA_LLIST_HEAD(struct reply_info, replies);
+ pa_pdispatch_drain_cb_t drain_callback;
+ void *drain_userdata;
+ pa_cmsg_ancil_data *ancil_data;
+ bool use_rtclock;
+};
+
+static void reply_info_free(struct reply_info *r) {
+ pa_assert(r);
+ pa_assert(r->pdispatch);
+ pa_assert(r->pdispatch->mainloop);
+
+ if (r->time_event)
+ r->pdispatch->mainloop->time_free(r->time_event);
+
+ PA_LLIST_REMOVE(struct reply_info, r->pdispatch->replies, r);
+
+ if (pa_flist_push(PA_STATIC_FLIST_GET(reply_infos), r) < 0)
+ pa_xfree(r);
+}
+
+pa_pdispatch* pa_pdispatch_new(pa_mainloop_api *mainloop, bool use_rtclock, const pa_pdispatch_cb_t *table, unsigned entries) {
+ pa_pdispatch *pd;
+
+ pa_assert(mainloop);
+ pa_assert((entries && table) || (!entries && !table));
+
+ pd = pa_xnew0(pa_pdispatch, 1);
+ PA_REFCNT_INIT(pd);
+ pd->mainloop = mainloop;
+ pd->callback_table = table;
+ pd->n_commands = entries;
+ PA_LLIST_HEAD_INIT(struct reply_info, pd->replies);
+ pd->use_rtclock = use_rtclock;
+
+ return pd;
+}
+
+static void pdispatch_free(pa_pdispatch *pd) {
+ pa_assert(pd);
+
+ while (pd->replies) {
+ if (pd->replies->free_cb)
+ pd->replies->free_cb(pd->replies->userdata);
+
+ reply_info_free(pd->replies);
+ }
+
+ pa_xfree(pd);
+}
+
+static void run_action(pa_pdispatch *pd, struct reply_info *r, uint32_t command, pa_tagstruct *ts) {
+ pa_pdispatch_cb_t callback;
+ void *userdata;
+ uint32_t tag;
+ pa_assert(r);
+
+ pa_pdispatch_ref(pd);
+
+ callback = r->callback;
+ userdata = r->userdata;
+ tag = r->tag;
+
+ reply_info_free(r);
+
+ callback(pd, command, tag, ts, userdata);
+
+ if (pd->drain_callback && !pa_pdispatch_is_pending(pd))
+ pd->drain_callback(pd, pd->drain_userdata);
+
+ pa_pdispatch_unref(pd);
+}
+
+int pa_pdispatch_run(pa_pdispatch *pd, pa_packet *packet, pa_cmsg_ancil_data *ancil_data, void *userdata) {
+ uint32_t tag, command;
+ pa_tagstruct *ts = NULL;
+ int ret = -1;
+ const void *pdata;
+ size_t plen;
+
+ pa_assert(pd);
+ pa_assert(PA_REFCNT_VALUE(pd) >= 1);
+ pa_assert(packet);
+
+ pa_pdispatch_ref(pd);
+
+ pdata = pa_packet_data(packet, &plen);
+ if (plen <= 8)
+ goto finish;
+
+ ts = pa_tagstruct_new_fixed(pdata, plen);
+
+ if (pa_tagstruct_getu32(ts, &command) < 0 ||
+ pa_tagstruct_getu32(ts, &tag) < 0)
+ goto finish;
+
+#ifdef DEBUG_OPCODES
+{
+ char t[256];
+ char const *p = NULL;
+
+ if (command >= PA_COMMAND_MAX || !(p = command_names[command]))
+ pa_snprintf((char*) (p = t), sizeof(t), "%u", command);
+
+ pa_log("[%p] Received opcode <%s>", pd, p);
+}
+#endif
+
+ pd->ancil_data = ancil_data;
+
+ if (command == PA_COMMAND_ERROR || command == PA_COMMAND_REPLY) {
+ struct reply_info *r;
+
+ PA_LLIST_FOREACH(r, pd->replies)
+ if (r->tag == tag)
+ break;
+
+ if (r)
+ run_action(pd, r, command, ts);
+
+ } else if (pd->callback_table && (command < pd->n_commands) && pd->callback_table[command]) {
+ const pa_pdispatch_cb_t *cb = pd->callback_table+command;
+
+ (*cb)(pd, command, tag, ts, userdata);
+ } else {
+ pa_log("Received unsupported command %u", command);
+ goto finish;
+ }
+
+ ret = 0;
+
+finish:
+ pd->ancil_data = NULL;
+
+ if (ts)
+ pa_tagstruct_free(ts);
+
+ pa_pdispatch_unref(pd);
+
+ return ret;
+}
+
+static void timeout_callback(pa_mainloop_api*m, pa_time_event*e, const struct timeval *t, void *userdata) {
+ struct reply_info*r = userdata;
+
+ pa_assert(r);
+ pa_assert(r->time_event == e);
+ pa_assert(r->pdispatch);
+ pa_assert(r->pdispatch->mainloop == m);
+ pa_assert(r->callback);
+
+ run_action(r->pdispatch, r, PA_COMMAND_TIMEOUT, NULL);
+}
+
+void pa_pdispatch_register_reply(pa_pdispatch *pd, uint32_t tag, int timeout, pa_pdispatch_cb_t cb, void *userdata, pa_free_cb_t free_cb) {
+ struct reply_info *r;
+ struct timeval tv;
+
+ pa_assert(pd);
+ pa_assert(PA_REFCNT_VALUE(pd) >= 1);
+ pa_assert(cb);
+
+ if (!(r = pa_flist_pop(PA_STATIC_FLIST_GET(reply_infos))))
+ r = pa_xnew(struct reply_info, 1);
+
+ r->pdispatch = pd;
+ r->callback = cb;
+ r->userdata = userdata;
+ r->free_cb = free_cb;
+ r->tag = tag;
+
+ pa_assert_se(r->time_event = pd->mainloop->time_new(pd->mainloop,
+ pa_timeval_rtstore(&tv, pa_rtclock_now() + timeout * PA_USEC_PER_SEC, pd->use_rtclock),
+ timeout_callback, r));
+
+ PA_LLIST_PREPEND(struct reply_info, pd->replies, r);
+}
+
+int pa_pdispatch_is_pending(pa_pdispatch *pd) {
+ pa_assert(pd);
+ pa_assert(PA_REFCNT_VALUE(pd) >= 1);
+
+ return !!pd->replies;
+}
+
+void pa_pdispatch_set_drain_callback(pa_pdispatch *pd, pa_pdispatch_drain_cb_t cb, void *userdata) {
+ pa_assert(pd);
+ pa_assert(PA_REFCNT_VALUE(pd) >= 1);
+ pa_assert(!cb || pa_pdispatch_is_pending(pd));
+
+ pd->drain_callback = cb;
+ pd->drain_userdata = userdata;
+}
+
+void pa_pdispatch_unregister_reply(pa_pdispatch *pd, void *userdata) {
+ struct reply_info *r, *n;
+
+ pa_assert(pd);
+ pa_assert(PA_REFCNT_VALUE(pd) >= 1);
+
+ PA_LLIST_FOREACH_SAFE(r, n, pd->replies)
+ if (r->userdata == userdata)
+ reply_info_free(r);
+}
+
+void pa_pdispatch_unref(pa_pdispatch *pd) {
+ pa_assert(pd);
+ pa_assert(PA_REFCNT_VALUE(pd) >= 1);
+
+ if (PA_REFCNT_DEC(pd) <= 0)
+ pdispatch_free(pd);
+}
+
+pa_pdispatch* pa_pdispatch_ref(pa_pdispatch *pd) {
+ pa_assert(pd);
+ pa_assert(PA_REFCNT_VALUE(pd) >= 1);
+
+ PA_REFCNT_INC(pd);
+ return pd;
+}
+
+#ifdef HAVE_CREDS
+
+const pa_creds * pa_pdispatch_creds(pa_pdispatch *pd) {
+ pa_assert(pd);
+ pa_assert(PA_REFCNT_VALUE(pd) >= 1);
+
+ if (pd->ancil_data && pd->ancil_data->creds_valid)
+ return &pd->ancil_data->creds;
+ return NULL;
+}
+
+/* Should be called only once during the dispatcher lifetime
+ *
+ * If the returned ancillary data contains any fds, caller maintains sole
+ * responsibility of closing them down using pa_cmsg_ancil_data_close_fds() */
+pa_cmsg_ancil_data *pa_pdispatch_take_ancil_data(pa_pdispatch *pd) {
+ pa_cmsg_ancil_data *ancil;
+
+ pa_assert(pd);
+ pa_assert(PA_REFCNT_VALUE(pd) >= 1);
+
+ ancil = pd->ancil_data;
+
+ /* iochannel guarantees us that nfd will always be capped */
+ if (ancil)
+ pa_assert(ancil->nfd <= MAX_ANCIL_DATA_FDS);
+
+ pd->ancil_data = NULL;
+ return ancil;
+}
+
+#endif
diff --git a/src/pulsecore/pdispatch.h b/src/pulsecore/pdispatch.h
new file mode 100644
index 0000000..af76981
--- /dev/null
+++ b/src/pulsecore/pdispatch.h
@@ -0,0 +1,56 @@
+#ifndef foopdispatchhfoo
+#define foopdispatchhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <inttypes.h>
+
+#include <pulse/mainloop-api.h>
+#include <pulse/def.h>
+
+#include <pulsecore/tagstruct.h>
+#include <pulsecore/packet.h>
+#include <pulsecore/creds.h>
+
+typedef struct pa_pdispatch pa_pdispatch;
+
+typedef void (*pa_pdispatch_cb_t)(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+typedef void (*pa_pdispatch_drain_cb_t)(pa_pdispatch *pd, void *userdata);
+
+pa_pdispatch* pa_pdispatch_new(pa_mainloop_api *m, bool use_rtclock, const pa_pdispatch_cb_t *table, unsigned entries);
+void pa_pdispatch_unref(pa_pdispatch *pd);
+pa_pdispatch* pa_pdispatch_ref(pa_pdispatch *pd);
+
+int pa_pdispatch_run(pa_pdispatch *pd, pa_packet *p, pa_cmsg_ancil_data *ancil_data, void *userdata);
+
+void pa_pdispatch_register_reply(pa_pdispatch *pd, uint32_t tag, int timeout, pa_pdispatch_cb_t callback, void *userdata, pa_free_cb_t free_cb);
+
+int pa_pdispatch_is_pending(pa_pdispatch *pd);
+
+void pa_pdispatch_set_drain_callback(pa_pdispatch *pd, pa_pdispatch_drain_cb_t callback, void *userdata);
+
+/* Remove all reply slots with the give userdata parameter */
+void pa_pdispatch_unregister_reply(pa_pdispatch *pd, void *userdata);
+
+const pa_creds * pa_pdispatch_creds(pa_pdispatch *pd);
+pa_cmsg_ancil_data *pa_pdispatch_take_ancil_data(pa_pdispatch *pd);
+
+#endif
diff --git a/src/pulsecore/pid.c b/src/pulsecore/pid.c
new file mode 100644
index 0000000..8749b42
--- /dev/null
+++ b/src/pulsecore/pid.c
@@ -0,0 +1,398 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <signal.h>
+
+#ifdef HAVE_WINDOWS_H
+#include <windows.h>
+#endif
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/core-error.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+
+#include "pid.h"
+
+/* Read the PID data from the file descriptor fd, and return it. If no
+ * pid could be read, return 0, on failure (pid_t) -1 */
+static pid_t read_pid(const char *fn, int fd) {
+ ssize_t r;
+ char t[20], *e;
+ uint32_t pid;
+
+ pa_assert(fn);
+ pa_assert(fd >= 0);
+
+ if ((r = pa_loop_read(fd, t, sizeof(t)-1, NULL)) < 0) {
+ pa_log_warn("Failed to read PID file '%s': %s", fn, pa_cstrerror(errno));
+ return (pid_t) -1;
+ }
+
+ if (r == 0)
+ return (pid_t) 0;
+
+ t[r] = 0;
+ if ((e = strchr(t, '\n')))
+ *e = 0;
+
+ if (pa_atou(t, &pid) < 0) {
+ pa_log_warn("Failed to parse PID file '%s'", fn);
+ errno = EINVAL;
+ return (pid_t) -1;
+ }
+
+ return (pid_t) pid;
+}
+
+static int open_pid_file(const char *fn, int mode) {
+ int fd;
+
+ pa_assert(fn);
+
+ for (;;) {
+ struct stat st;
+
+ if ((fd = pa_open_cloexec(fn, mode
+#ifdef O_NOFOLLOW
+ |O_NOFOLLOW
+#endif
+ , S_IRUSR|S_IWUSR
+ )) < 0) {
+ if (mode != O_RDONLY || errno != ENOENT)
+ pa_log_warn("Failed to open PID file '%s': %s", fn, pa_cstrerror(errno));
+ goto fail;
+ }
+
+ /* Try to lock the file. If that fails, go without */
+ if (pa_lock_fd(fd, 1) < 0)
+ goto fail;
+
+ if (fstat(fd, &st) < 0) {
+ pa_log_warn("Failed to fstat() PID file '%s': %s", fn, pa_cstrerror(errno));
+ goto fail;
+ }
+
+ /* Does the file still exist in the file system? When yes, we're done, otherwise restart */
+ if (st.st_nlink >= 1)
+ break;
+
+ if (pa_lock_fd(fd, 0) < 0)
+ goto fail;
+
+ if (pa_close(fd) < 0) {
+ pa_log_warn("Failed to close file '%s': %s", fn, pa_cstrerror(errno));
+ fd = -1;
+ goto fail;
+ }
+ }
+
+ return fd;
+
+fail:
+
+ if (fd >= 0) {
+ int saved_errno = errno;
+ pa_lock_fd(fd, 0);
+ pa_close(fd);
+ errno = saved_errno;
+ }
+
+ return -1;
+}
+
+static int proc_name_ours(pid_t pid, const char *procname) {
+#ifdef __linux__
+ char bn[PATH_MAX];
+ FILE *f;
+
+ pa_snprintf(bn, sizeof(bn), "/proc/%lu/stat", (unsigned long) pid);
+
+ if (!(f = pa_fopen_cloexec(bn, "r"))) {
+ pa_log_info("Failed to open %s: %s", bn, pa_cstrerror(errno));
+ return -1;
+ } else {
+ char *expected;
+ bool good;
+ char stored[64];
+
+ if (!(fgets(stored, sizeof(stored), f))) {
+ int saved_errno = feof(f) ? EINVAL : errno;
+ pa_log_info("Failed to read from %s: %s", bn, feof(f) ? "EOF" : pa_cstrerror(errno));
+ fclose(f);
+
+ errno = saved_errno;
+ return -1;
+ }
+
+ fclose(f);
+
+ expected = pa_sprintf_malloc("%lu (%s)", (unsigned long) pid, procname);
+ good = pa_startswith(stored, expected);
+ pa_xfree(expected);
+
+/*#if !defined(__OPTIMIZE__)*/
+ if (!good) {
+ /* libtool likes to rename our binary names ... */
+ expected = pa_sprintf_malloc("%lu (lt-%s)", (unsigned long) pid, procname);
+ good = pa_startswith(stored, expected);
+ pa_xfree(expected);
+ }
+/*#endif*/
+
+ return good;
+ }
+#else
+
+ return 1;
+#endif
+
+}
+
+/* Create a new PID file for the current process. */
+int pa_pid_file_create(const char *procname) {
+ int fd = -1;
+ int ret = -1;
+ char t[20];
+ pid_t pid;
+ size_t l;
+ char *fn;
+
+#ifdef OS_IS_WIN32
+ HANDLE process;
+#endif
+
+ if (!(fn = pa_runtime_path("pid")))
+ goto fail;
+
+ if ((fd = open_pid_file(fn, O_CREAT|O_RDWR)) < 0)
+ goto fail;
+
+ if ((pid = read_pid(fn, fd)) == (pid_t) -1)
+ pa_log_warn("Corrupt PID file, overwriting.");
+ else if (pid > 0) {
+ int ours = 1;
+
+#ifdef OS_IS_WIN32
+ if ((process = OpenProcess(PROCESS_QUERY_INFORMATION, false, pid)) != NULL) {
+ CloseHandle(process);
+#else
+ if (kill(pid, 0) >= 0 || errno != ESRCH) {
+#endif
+
+ if (procname)
+ if ((ours = proc_name_ours(pid, procname)) < 0) {
+ pa_log_warn("Could not check to see if pid %lu is a pulseaudio process. "
+ "Assuming it is and the daemon is already running.", (unsigned long) pid);
+ goto fail;
+ }
+
+ if (ours) {
+ pa_log("Daemon already running.");
+ ret = 1;
+ goto fail;
+ }
+ }
+
+ pa_log_warn("Stale PID file, overwriting.");
+ }
+
+ /* Overwrite the current PID file */
+ if (lseek(fd, (off_t) 0, SEEK_SET) == (off_t) -1 || ftruncate(fd, (off_t) 0) < 0) {
+ pa_log("Failed to truncate PID file '%s': %s", fn, pa_cstrerror(errno));
+ goto fail;
+ }
+
+ pa_snprintf(t, sizeof(t), "%lu\n", (unsigned long) getpid());
+ l = strlen(t);
+
+ if (pa_loop_write(fd, t, l, NULL) != (ssize_t) l) {
+ pa_log("Failed to write PID file.");
+ goto fail;
+ }
+
+ ret = 0;
+
+fail:
+ if (fd >= 0) {
+ pa_lock_fd(fd, 0);
+
+ if (pa_close(fd) < 0) {
+ pa_log("Failed to close PID file '%s': %s", fn, pa_cstrerror(errno));
+ ret = -1;
+ }
+ }
+
+ pa_xfree(fn);
+
+ return ret;
+}
+
+/* Remove the PID file, if it is ours */
+int pa_pid_file_remove(void) {
+ int fd = -1;
+ char *fn;
+ int ret = -1;
+ pid_t pid;
+
+ if (!(fn = pa_runtime_path("pid")))
+ goto fail;
+
+ if ((fd = open_pid_file(fn, O_RDWR)) < 0) {
+ pa_log_warn("Failed to open PID file '%s': %s", fn, pa_cstrerror(errno));
+ goto fail;
+ }
+
+ if ((pid = read_pid(fn, fd)) == (pid_t) -1)
+ goto fail;
+
+ if (pid != getpid()) {
+ pa_log("PID file '%s' not mine!", fn);
+ goto fail;
+ }
+
+ if (ftruncate(fd, (off_t) 0) < 0) {
+ pa_log_warn("Failed to truncate PID file '%s': %s", fn, pa_cstrerror(errno));
+ goto fail;
+ }
+
+#ifdef OS_IS_WIN32
+ pa_lock_fd(fd, 0);
+ pa_close(fd);
+ fd = -1;
+#endif
+
+ if (unlink(fn) < 0) {
+ pa_log_warn("Failed to remove PID file '%s': %s", fn, pa_cstrerror(errno));
+ goto fail;
+ }
+
+ ret = 0;
+
+fail:
+
+ if (fd >= 0) {
+ pa_lock_fd(fd, 0);
+
+ if (pa_close(fd) < 0) {
+ pa_log_warn("Failed to close PID file '%s': %s", fn, pa_cstrerror(errno));
+ ret = -1;
+ }
+ }
+
+ pa_xfree(fn);
+
+ return ret;
+}
+
+/* Check whether the daemon is currently running, i.e. if a PID file
+ * exists and the PID therein too. Returns 0 on success, -1
+ * otherwise. If pid is non-NULL and a running daemon was found,
+ * return its PID therein */
+int pa_pid_file_check_running(pid_t *pid, const char *procname) {
+ return pa_pid_file_kill(0, pid, procname);
+}
+
+#ifndef OS_IS_WIN32
+
+/* Kill a current running daemon. Return non-zero on success, -1
+ * otherwise. If successful *pid contains the PID of the daemon
+ * process. */
+int pa_pid_file_kill(int sig, pid_t *pid, const char *procname) {
+ int fd = -1;
+ char *fn;
+ int ret = -1;
+ pid_t _pid;
+#ifdef __linux__
+ char *e = NULL;
+#endif
+
+ if (!pid)
+ pid = &_pid;
+
+ if (!(fn = pa_runtime_path("pid")))
+ goto fail;
+
+ if ((fd = open_pid_file(fn, O_RDONLY)) < 0) {
+
+ if (errno == ENOENT)
+ errno = ESRCH;
+
+ goto fail;
+ }
+
+ if ((*pid = read_pid(fn, fd)) == (pid_t) -1)
+ goto fail;
+
+ if (procname) {
+ int ours;
+
+ if ((ours = proc_name_ours(*pid, procname)) < 0)
+ goto fail;
+
+ if (!ours) {
+ errno = ESRCH;
+ goto fail;
+ }
+ }
+
+ ret = kill(*pid, sig);
+
+fail:
+
+ if (fd >= 0) {
+ int saved_errno = errno;
+ pa_lock_fd(fd, 0);
+ pa_close(fd);
+ errno = saved_errno;
+ }
+
+#ifdef __linux__
+ pa_xfree(e);
+#endif
+
+ pa_xfree(fn);
+
+ return ret;
+
+}
+
+#else /* OS_IS_WIN32 */
+
+int pa_pid_file_kill(int sig, pid_t *pid, const char *exe_name) {
+ return -1;
+}
+
+#endif
diff --git a/src/pulsecore/pid.h b/src/pulsecore/pid.h
new file mode 100644
index 0000000..ff0356b
--- /dev/null
+++ b/src/pulsecore/pid.h
@@ -0,0 +1,28 @@
+#ifndef foopidhfoo
+#define foopidhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+int pa_pid_file_create(const char *procname);
+int pa_pid_file_remove(void);
+int pa_pid_file_check_running(pid_t *pid, const char *procname);
+int pa_pid_file_kill(int sig, pid_t *pid, const char *procname);
+
+#endif
diff --git a/src/pulsecore/pipe.c b/src/pulsecore/pipe.c
new file mode 100644
index 0000000..9f86a48
--- /dev/null
+++ b/src/pulsecore/pipe.c
@@ -0,0 +1,155 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2006-2007 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <sys/types.h>
+
+#include <pulsecore/socket.h>
+#include <pulsecore/core-util.h>
+
+#include "pipe.h"
+
+#ifndef HAVE_PIPE
+
+static int set_block(int fd, int blocking) {
+#ifdef O_NONBLOCK
+
+ int v;
+
+ pa_assert(fd >= 0);
+
+ if ((v = fcntl(fd, F_GETFL)) < 0)
+ return -1;
+
+ if (blocking)
+ v &= ~O_NONBLOCK;
+ else
+ v |= O_NONBLOCK;
+
+ if (fcntl(fd, F_SETFL, v) < 0)
+ return -1;
+
+ return 0;
+
+#elif defined(OS_IS_WIN32)
+
+ u_long arg;
+
+ arg = !blocking;
+
+ if (ioctlsocket(fd, FIONBIO, &arg) < 0)
+ return -1;
+
+ return 0;
+
+#else
+
+ return -1;
+
+#endif
+}
+
+int pipe(int filedes[2]) {
+ int listener;
+ struct sockaddr_in addr, peer;
+ socklen_t len;
+
+ listener = -1;
+ filedes[0] = -1;
+ filedes[1] = -1;
+
+ listener = socket(PF_INET, SOCK_STREAM, 0);
+ if (listener < 0)
+ goto error;
+
+ filedes[0] = socket(PF_INET, SOCK_STREAM, 0);
+ if (filedes[0] < 0)
+ goto error;
+
+ filedes[1] = socket(PF_INET, SOCK_STREAM, 0);
+ if (filedes[1] < 0)
+ goto error;
+
+ /* Make non-blocking so that connect() won't block */
+ if (set_block(filedes[0], 0) < 0)
+ goto error;
+
+ addr.sin_family = AF_INET;
+ addr.sin_port = 0;
+ addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+ if (bind(listener, (struct sockaddr*)&addr, sizeof(addr)) < 0)
+ goto error;
+
+ if (listen(listener, 1) < 0)
+ goto error;
+
+ len = sizeof(addr);
+ if (getsockname(listener, (struct sockaddr*)&addr, &len) < 0)
+ goto error;
+
+ if (connect(filedes[0], (struct sockaddr*)&addr, sizeof(addr)) < 0) {
+#ifdef OS_IS_WIN32
+ if (WSAGetLastError() != EWOULDBLOCK)
+#else
+ if (errno != EINPROGRESS)
+#endif
+ goto error;
+ }
+
+ len = sizeof(peer);
+ filedes[1] = accept(listener, (struct sockaddr*)&peer, &len);
+ if (filedes[1] < 0)
+ goto error;
+
+ /* Restore blocking */
+ if (set_block(filedes[0], 1) < 0)
+ goto error;
+
+ len = sizeof(addr);
+ if (getsockname(filedes[0], (struct sockaddr*)&addr, &len) < 0)
+ goto error;
+
+ /* Check that someone else didn't steal the connection */
+ if ((addr.sin_port != peer.sin_port) || (addr.sin_addr.s_addr != peer.sin_addr.s_addr))
+ goto error;
+
+ pa_close(listener);
+
+ return 0;
+
+error:
+ if (listener >= 0)
+ pa_close(listener);
+ if (filedes[0] >= 0)
+ pa_close(filedes[0]);
+ if (filedes[1] >= 0)
+ pa_close(filedes[1]);
+
+ return -1;
+}
+
+#endif /* HAVE_PIPE */
diff --git a/src/pulsecore/pipe.h b/src/pulsecore/pipe.h
new file mode 100644
index 0000000..54e9819
--- /dev/null
+++ b/src/pulsecore/pipe.h
@@ -0,0 +1,29 @@
+#ifndef foopipehfoo
+#define foopipehfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifndef HAVE_PIPE
+
+int pipe(int filedes[2]);
+
+#endif
+
+#endif
diff --git a/src/pulsecore/play-memblockq.c b/src/pulsecore/play-memblockq.c
new file mode 100644
index 0000000..da586a8
--- /dev/null
+++ b/src/pulsecore/play-memblockq.c
@@ -0,0 +1,283 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2006-2008 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/sink-input.h>
+#include <pulsecore/thread-mq.h>
+
+#include "play-memblockq.h"
+
+typedef struct memblockq_stream {
+ pa_msgobject parent;
+ pa_core *core;
+ pa_sink_input *sink_input;
+ pa_memblockq *memblockq;
+} memblockq_stream;
+
+enum {
+ MEMBLOCKQ_STREAM_MESSAGE_UNLINK,
+};
+
+PA_DEFINE_PRIVATE_CLASS(memblockq_stream, pa_msgobject);
+#define MEMBLOCKQ_STREAM(o) (memblockq_stream_cast(o))
+
+static void memblockq_stream_unlink(memblockq_stream *u) {
+ pa_assert(u);
+
+ if (!u->sink_input)
+ return;
+
+ pa_sink_input_unlink(u->sink_input);
+ pa_sink_input_unref(u->sink_input);
+ u->sink_input = NULL;
+
+ memblockq_stream_unref(u);
+}
+
+static void memblockq_stream_free(pa_object *o) {
+ memblockq_stream *u = MEMBLOCKQ_STREAM(o);
+ pa_assert(u);
+
+ if (u->memblockq)
+ pa_memblockq_free(u->memblockq);
+
+ pa_xfree(u);
+}
+
+static int memblockq_stream_process_msg(pa_msgobject *o, int code, void*userdata, int64_t offset, pa_memchunk *chunk) {
+ memblockq_stream *u = MEMBLOCKQ_STREAM(o);
+ memblockq_stream_assert_ref(u);
+
+ switch (code) {
+ case MEMBLOCKQ_STREAM_MESSAGE_UNLINK:
+ memblockq_stream_unlink(u);
+ break;
+ }
+
+ return 0;
+}
+
+static void sink_input_kill_cb(pa_sink_input *i) {
+ memblockq_stream *u;
+
+ pa_sink_input_assert_ref(i);
+ u = MEMBLOCKQ_STREAM(i->userdata);
+ memblockq_stream_assert_ref(u);
+
+ memblockq_stream_unlink(u);
+}
+
+/* Called from IO thread context */
+static void sink_input_state_change_cb(pa_sink_input *i, pa_sink_input_state_t state) {
+ memblockq_stream *u;
+
+ pa_sink_input_assert_ref(i);
+ u = MEMBLOCKQ_STREAM(i->userdata);
+ memblockq_stream_assert_ref(u);
+
+ /* If we are added for the first time, ask for a rewinding so that
+ * we are heard right-away. */
+ if (PA_SINK_INPUT_IS_LINKED(state) &&
+ i->thread_info.state == PA_SINK_INPUT_INIT && i->sink)
+ pa_sink_input_request_rewind(i, 0, false, true, true);
+}
+
+static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk) {
+ memblockq_stream *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert(chunk);
+ u = MEMBLOCKQ_STREAM(i->userdata);
+ memblockq_stream_assert_ref(u);
+
+ if (!u->memblockq)
+ return -1;
+
+ if (pa_memblockq_peek(u->memblockq, chunk) < 0) {
+
+ if (pa_sink_input_safe_to_remove(i)) {
+
+ pa_memblockq_free(u->memblockq);
+ u->memblockq = NULL;
+
+ pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(u), MEMBLOCKQ_STREAM_MESSAGE_UNLINK, NULL, 0, NULL, NULL);
+ }
+
+ return -1;
+ }
+
+ /* If there's no memblock, there's going to be data in the memblockq after
+ * a gap with length chunk->length. Drop the gap and peek the actual
+ * data. There should always be some data coming - hence the assert. The
+ * gap will occur if the memblockq is rewound beyond index 0.*/
+ if (!chunk->memblock) {
+ pa_memblockq_drop(u->memblockq, chunk->length);
+ pa_assert_se(pa_memblockq_peek(u->memblockq, chunk) >= 0);
+ }
+
+ chunk->length = PA_MIN(chunk->length, nbytes);
+ pa_memblockq_drop(u->memblockq, chunk->length);
+
+ return 0;
+}
+
+static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) {
+ memblockq_stream *u;
+
+ pa_sink_input_assert_ref(i);
+ u = MEMBLOCKQ_STREAM(i->userdata);
+ memblockq_stream_assert_ref(u);
+
+ if (!u->memblockq)
+ return;
+
+ pa_memblockq_rewind(u->memblockq, nbytes);
+}
+
+static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) {
+ memblockq_stream *u;
+
+ pa_sink_input_assert_ref(i);
+ u = MEMBLOCKQ_STREAM(i->userdata);
+ memblockq_stream_assert_ref(u);
+
+ if (!u->memblockq)
+ return;
+
+ pa_memblockq_set_maxrewind(u->memblockq, nbytes);
+}
+
+pa_sink_input* pa_memblockq_sink_input_new(
+ pa_sink *sink,
+ const pa_sample_spec *ss,
+ const pa_channel_map *map,
+ pa_memblockq *q,
+ pa_cvolume *volume,
+ pa_proplist *p,
+ pa_sink_input_flags_t flags) {
+
+ memblockq_stream *u = NULL;
+ pa_sink_input_new_data data;
+
+ pa_assert(sink);
+ pa_assert(ss);
+
+ /* We allow creating this stream with no q set, so that it can be
+ * filled in later */
+
+ u = pa_msgobject_new(memblockq_stream);
+ u->parent.parent.free = memblockq_stream_free;
+ u->parent.process_msg = memblockq_stream_process_msg;
+ u->core = sink->core;
+ u->sink_input = NULL;
+ u->memblockq = NULL;
+
+ pa_sink_input_new_data_init(&data);
+ pa_sink_input_new_data_set_sink(&data, sink, false, true);
+ data.driver = __FILE__;
+ pa_sink_input_new_data_set_sample_spec(&data, ss);
+ pa_sink_input_new_data_set_channel_map(&data, map);
+ pa_sink_input_new_data_set_volume(&data, volume);
+ pa_proplist_update(data.proplist, PA_UPDATE_REPLACE, p);
+ data.flags |= flags;
+
+ pa_sink_input_new(&u->sink_input, sink->core, &data);
+ pa_sink_input_new_data_done(&data);
+
+ if (!u->sink_input)
+ goto fail;
+
+ u->sink_input->pop = sink_input_pop_cb;
+ u->sink_input->process_rewind = sink_input_process_rewind_cb;
+ u->sink_input->update_max_rewind = sink_input_update_max_rewind_cb;
+ u->sink_input->kill = sink_input_kill_cb;
+ u->sink_input->state_change = sink_input_state_change_cb;
+ u->sink_input->userdata = u;
+
+ if (q)
+ pa_memblockq_sink_input_set_queue(u->sink_input, q);
+
+ /* The reference to u is dangling here, because we want
+ * to keep this stream around until it is fully played. */
+
+ /* This sink input is not "put" yet, i.e. pa_sink_input_put() has
+ * not been called! */
+
+ return pa_sink_input_ref(u->sink_input);
+
+fail:
+ if (u)
+ memblockq_stream_unref(u);
+
+ return NULL;
+}
+
+int pa_play_memblockq(
+ pa_sink *sink,
+ const pa_sample_spec *ss,
+ const pa_channel_map *map,
+ pa_memblockq *q,
+ pa_cvolume *volume,
+ pa_proplist *p,
+ pa_sink_input_flags_t flags,
+ uint32_t *sink_input_index) {
+
+ pa_sink_input *i;
+
+ pa_assert(sink);
+ pa_assert(ss);
+ pa_assert(q);
+
+ if (!(i = pa_memblockq_sink_input_new(sink, ss, map, q, volume, p, flags)))
+ return -1;
+
+ pa_sink_input_put(i);
+
+ if (sink_input_index)
+ *sink_input_index = i->index;
+
+ pa_sink_input_unref(i);
+
+ return 0;
+}
+
+void pa_memblockq_sink_input_set_queue(pa_sink_input *i, pa_memblockq *q) {
+ memblockq_stream *u;
+
+ pa_sink_input_assert_ref(i);
+ u = MEMBLOCKQ_STREAM(i->userdata);
+ memblockq_stream_assert_ref(u);
+
+ if (u->memblockq)
+ pa_memblockq_free(u->memblockq);
+
+ if ((u->memblockq = q)) {
+ pa_memblockq_set_prebuf(q, 0);
+ pa_memblockq_set_silence(q, NULL);
+ pa_memblockq_willneed(q);
+ }
+}
diff --git a/src/pulsecore/play-memblockq.h b/src/pulsecore/play-memblockq.h
new file mode 100644
index 0000000..c13aced
--- /dev/null
+++ b/src/pulsecore/play-memblockq.h
@@ -0,0 +1,47 @@
+#ifndef fooplaymemblockqhfoo
+#define fooplaymemblockqhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulsecore/sink.h>
+#include <pulsecore/memblockq.h>
+
+pa_sink_input* pa_memblockq_sink_input_new(
+ pa_sink *sink,
+ const pa_sample_spec *ss,
+ const pa_channel_map *map,
+ pa_memblockq *q,
+ pa_cvolume *volume,
+ pa_proplist *p,
+ pa_sink_input_flags_t flags);
+
+void pa_memblockq_sink_input_set_queue(pa_sink_input *i, pa_memblockq *q);
+
+int pa_play_memblockq(
+ pa_sink *sink,
+ const pa_sample_spec *ss,
+ const pa_channel_map *map,
+ pa_memblockq *q,
+ pa_cvolume *cvolume,
+ pa_proplist *p,
+ pa_sink_input_flags_t flags,
+ uint32_t *sink_input_index);
+
+#endif
diff --git a/src/pulsecore/play-memchunk.c b/src/pulsecore/play-memchunk.c
new file mode 100644
index 0000000..cc0bc16
--- /dev/null
+++ b/src/pulsecore/play-memchunk.c
@@ -0,0 +1,62 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2008 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <pulsecore/sink-input.h>
+#include <pulsecore/play-memblockq.h>
+
+#include "play-memchunk.h"
+
+int pa_play_memchunk(
+ pa_sink *sink,
+ const pa_sample_spec *ss,
+ const pa_channel_map *map,
+ const pa_memchunk *chunk,
+ pa_cvolume *volume,
+ pa_proplist *p,
+ pa_sink_input_flags_t flags,
+ uint32_t *sink_input_index) {
+
+ pa_memblockq *q;
+ int r;
+ pa_memchunk silence;
+
+ pa_assert(sink);
+ pa_assert(ss);
+ pa_assert(chunk);
+
+ pa_silence_memchunk_get(&sink->core->silence_cache, sink->core->mempool, &silence, ss, 0);
+ q = pa_memblockq_new("pa_play_memchunk() q", 0, chunk->length, 0, ss, 1, 1, 0, &silence);
+ pa_memblock_unref(silence.memblock);
+
+ pa_assert_se(pa_memblockq_push(q, chunk) >= 0);
+
+ if ((r = pa_play_memblockq(sink, ss, map, q, volume, p, flags, sink_input_index)) < 0) {
+ pa_memblockq_free(q);
+ return r;
+ }
+
+ return 0;
+}
diff --git a/src/pulsecore/play-memchunk.h b/src/pulsecore/play-memchunk.h
new file mode 100644
index 0000000..7c56fe3
--- /dev/null
+++ b/src/pulsecore/play-memchunk.h
@@ -0,0 +1,36 @@
+#ifndef fooplaychunkhfoo
+#define fooplaychunkhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulsecore/sink.h>
+#include <pulsecore/memchunk.h>
+
+int pa_play_memchunk(
+ pa_sink *sink,
+ const pa_sample_spec *ss,
+ const pa_channel_map *map,
+ const pa_memchunk *chunk,
+ pa_cvolume *cvolume,
+ pa_proplist *p,
+ pa_sink_input_flags_t flags,
+ uint32_t *sink_input_index);
+
+#endif
diff --git a/src/pulsecore/poll-posix.c b/src/pulsecore/poll-posix.c
new file mode 100644
index 0000000..e232295
--- /dev/null
+++ b/src/pulsecore/poll-posix.c
@@ -0,0 +1,235 @@
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+/***
+ Based on work for the GNU C Library.
+ Copyright (C) 1994, 1996, 1997 Free Software Foundation, Inc.
+***/
+
+/* Poll the file descriptors described by the NFDS structures starting at
+ FDS. If TIMEOUT is nonzero and not -1, allow TIMEOUT milliseconds for
+ an event to occur; if TIMEOUT is -1, block until an event occurs.
+ Returns the number of file descriptors with events, zero if timed out,
+ or -1 for errors. */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+
+#include <errno.h>
+#include <fcntl.h>
+
+#ifdef HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#endif
+
+#include <pulsecore/socket.h>
+#include <pulsecore/core-util.h>
+#include <pulse/util.h>
+
+#include "poll.h"
+
+/* Mac OSX fails to implement poll() in a working way since 10.4. IOW, for
+ * several years. We need to enable a dirty workaround and emulate that call
+ * with select(), just like for Windows. sic! */
+
+#if !defined(HAVE_POLL_H) || defined(OS_IS_DARWIN)
+
+int pa_poll (struct pollfd *fds, unsigned long int nfds, int timeout) {
+ struct timeval tv;
+ fd_set rset, wset, xset;
+ struct pollfd *f;
+ int ready;
+ int maxfd = 0;
+#ifdef OS_IS_WIN32
+ char data[64];
+#endif
+
+ FD_ZERO (&rset);
+ FD_ZERO (&wset);
+ FD_ZERO (&xset);
+
+ if (nfds == 0) {
+ if (timeout >= 0) {
+ pa_msleep(timeout);
+ return 0;
+ }
+
+#ifdef OS_IS_WIN32
+ /*
+ * Windows does not support signals properly so waiting for them would
+ * mean a deadlock.
+ */
+ pa_msleep(100);
+ return 0;
+#else
+ return select(0, NULL, NULL, NULL, NULL);
+#endif
+ }
+
+ for (f = fds; f < &fds[nfds]; ++f) {
+ if (f->fd != -1) {
+ if (f->events & POLLIN)
+ FD_SET (f->fd, &rset);
+ if (f->events & POLLOUT)
+ FD_SET (f->fd, &wset);
+ if (f->events & POLLPRI)
+ FD_SET (f->fd, &xset);
+ if (f->fd > maxfd && (f->events & (POLLIN|POLLOUT|POLLPRI)))
+ maxfd = f->fd;
+ }
+ }
+
+ tv.tv_sec = timeout / 1000;
+ tv.tv_usec = (timeout % 1000) * 1000;
+
+ ready = select(maxfd + 1, &rset, &wset, &xset, (timeout == -1 ? NULL : &tv));
+
+ if ((ready == -1) && (errno == EBADF)) {
+ ready = 0;
+ maxfd = -1;
+
+#ifdef OS_IS_WIN32
+ /*
+ * Windows has no fcntl(), so we have to trick around with more
+ * select() calls to find out what went wrong
+ */
+
+ FD_ZERO (&rset);
+ FD_ZERO (&wset);
+ FD_ZERO (&xset);
+
+ for (f = fds; f < &fds[nfds]; ++f) {
+ if (f->fd != -1) {
+ fd_set sngl_rset, sngl_wset, sngl_xset;
+
+ FD_ZERO (&sngl_rset);
+ FD_ZERO (&sngl_wset);
+ FD_ZERO (&sngl_xset);
+
+ if (f->events & POLLIN)
+ FD_SET (f->fd, &sngl_rset);
+ if (f->events & POLLOUT)
+ FD_SET (f->fd, &sngl_wset);
+ if (f->events & POLLPRI)
+ FD_SET (f->fd, &sngl_xset);
+ if (f->events & (POLLIN|POLLOUT|POLLPRI)) {
+ struct timeval singl_tv;
+
+ singl_tv.tv_sec = 0;
+ singl_tv.tv_usec = 0;
+
+ if (select(f->fd, &rset, &wset, &xset, &singl_tv) != -1) {
+ if (f->events & POLLIN)
+ FD_SET (f->fd, &rset);
+ if (f->events & POLLOUT)
+ FD_SET (f->fd, &wset);
+ if (f->events & POLLPRI)
+ FD_SET (f->fd, &xset);
+ if (f->fd > maxfd && (f->events & (POLLIN|POLLOUT|POLLPRI)))
+ maxfd = f->fd;
+ ++ready;
+ } else if (errno == EBADF)
+ f->revents |= POLLNVAL;
+ }
+ }
+ }
+
+#else /* !OS_IS_WIN32 */
+
+ for (f = fds; f < &fds[nfds]; f++)
+ if (f->fd != -1) {
+ /* use fcntl() to find out whether the descriptor is valid */
+ if (fcntl(f->fd, F_GETFL) != -1) {
+ if (f->fd > maxfd && (f->events & (POLLIN|POLLOUT|POLLPRI))) {
+ maxfd = f->fd;
+ ready++;
+ }
+ } else {
+ FD_CLR(f->fd, &rset);
+ FD_CLR(f->fd, &wset);
+ FD_CLR(f->fd, &xset);
+ }
+ }
+
+#endif
+
+ if (ready) {
+ /* Linux alters the tv struct... but it shouldn't matter here ...
+ * as we're going to be a little bit out anyway as we've just eaten
+ * more than a couple of cpu cycles above */
+ ready = select(maxfd + 1, &rset, &wset, &xset, (timeout == -1 ? NULL : &tv));
+ }
+ }
+
+#ifdef OS_IS_WIN32
+ errno = WSAGetLastError();
+#endif
+
+ if (ready > 0) {
+ ready = 0;
+ for (f = fds; f < &fds[nfds]; ++f) {
+ f->revents = 0;
+ if (f->fd != -1) {
+ if (FD_ISSET (f->fd, &rset)) {
+ /* support for POLLHUP. An hung up descriptor does not
+ increase the return value! */
+#ifdef OS_IS_DARWIN
+ /* There is a bug in Mac OS X that causes it to ignore MSG_PEEK
+ * for some kinds of descriptors. Detect if this descriptor is a
+ * connected socket, a server socket, or something else using a
+ * 0-byte recv, and use ioctl(2) to detect POLLHUP. */
+ int r = recv(f->fd, NULL, 0, MSG_PEEK);
+ if (r == 0 || (r < 0 && errno == ENOTSOCK))
+ ioctl(f->fd, FIONREAD, &r);
+
+ if (r == 0)
+ f->revents |= POLLHUP;
+#else /* !OS_IS_DARWIN */
+ if (recv (f->fd, data, 64, MSG_PEEK) == -1) {
+ if (errno == ESHUTDOWN || errno == ECONNRESET ||
+ errno == ECONNABORTED || errno == ENETRESET) {
+ fprintf(stderr, "Hangup\n");
+ f->revents |= POLLHUP;
+ }
+ }
+#endif
+
+ if (f->revents == 0)
+ f->revents |= POLLIN;
+ }
+ if (FD_ISSET (f->fd, &wset))
+ f->revents |= POLLOUT;
+ if (FD_ISSET (f->fd, &xset))
+ f->revents |= POLLPRI;
+ }
+ if (f->revents)
+ ready++;
+ }
+ }
+
+ return ready;
+}
+
+#endif /* HAVE_SYS_POLL_H */
diff --git a/src/pulsecore/poll-win32.c b/src/pulsecore/poll-win32.c
new file mode 100644
index 0000000..c927e53
--- /dev/null
+++ b/src/pulsecore/poll-win32.c
@@ -0,0 +1,646 @@
+
+/* Emulation for poll(2)
+ Contributed by Paolo Bonzini.
+
+ Copyright 2001-2003, 2006-2012 Free Software Foundation, Inc.
+
+ This file is part of gnulib.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License along
+ with this program; if not, see <http://www.gnu.org/licenses/>. */
+
+/* Tell gcc not to warn about the (nfd < 0) tests, below. */
+#if (__GNUC__ == 4 && 3 <= __GNUC_MINOR__) || 4 < __GNUC__
+# pragma GCC diagnostic ignored "-Wtype-limits"
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <malloc.h>
+
+#include <sys/types.h>
+
+/* Specification. */
+#include "poll.h"
+typedef unsigned long nfds_t;
+
+#include <errno.h>
+#include <limits.h>
+#include <assert.h>
+
+#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
+# define WINDOWS_NATIVE
+# include <winsock2.h>
+# include <windows.h>
+# include <io.h>
+# include <stdio.h>
+# include <conio.h>
+# include <signal.h>
+# if 0
+# include "msvc-nothrow.h"
+# endif
+#else
+# include <sys/time.h>
+# include <sys/socket.h>
+# include <sys/select.h>
+# include <unistd.h>
+#endif
+
+#ifdef HAVE_SYS_IOCTL_H
+# include <sys/ioctl.h>
+#endif
+#ifdef HAVE_SYS_FILIO_H
+# include <sys/filio.h>
+#endif
+
+#include <time.h>
+
+#include <pulsecore/core-util.h>
+
+#ifndef INFTIM
+# define INFTIM (-1)
+#endif
+
+/* BeOS does not have MSG_PEEK. */
+#ifndef MSG_PEEK
+# define MSG_PEEK 0
+#endif
+
+#ifndef POLLRDNORM
+# define POLLRDNORM 0
+# define POLLRDBAND 0
+# define POLLWRNORM 0
+# define POLLWRBAND 0
+#endif
+
+#ifdef WINDOWS_NATIVE
+
+/* Optimized test whether a HANDLE refers to a console.
+ See <http://lists.gnu.org/archive/html/bug-gnulib/2009-08/msg00065.html>. */
+#define IsConsoleHandle(h) (((intptr_t) (h) & 3) == 3)
+
+static BOOL
+IsSocketHandle (HANDLE h)
+{
+ WSANETWORKEVENTS ev;
+
+ if (IsConsoleHandle (h))
+ return FALSE;
+
+ /* Under Wine, it seems that getsockopt returns 0 for pipes too.
+ WSAEnumNetworkEvents instead distinguishes the two correctly. */
+ ev.lNetworkEvents = 0xDEADBEEFl;
+ WSAEnumNetworkEvents ((SOCKET) h, NULL, &ev);
+ return ev.lNetworkEvents != 0xDEADBEEFl;
+}
+
+static HANDLE
+HandleFromFd (int fd)
+{
+ /* since socket() returns a HANDLE already, try that first */
+ if (IsSocketHandle((HANDLE) fd))
+ return ((HANDLE) fd);
+
+ return ((HANDLE) _get_osfhandle(fd));
+}
+
+/* Declare data structures for ntdll functions. */
+typedef struct _FILE_PIPE_LOCAL_INFORMATION {
+ ULONG NamedPipeType;
+ ULONG NamedPipeConfiguration;
+ ULONG MaximumInstances;
+ ULONG CurrentInstances;
+ ULONG InboundQuota;
+ ULONG ReadDataAvailable;
+ ULONG OutboundQuota;
+ ULONG WriteQuotaAvailable;
+ ULONG NamedPipeState;
+ ULONG NamedPipeEnd;
+} FILE_PIPE_LOCAL_INFORMATION, *PFILE_PIPE_LOCAL_INFORMATION;
+
+typedef struct _IO_STATUS_BLOCK
+{
+ union {
+ DWORD Status;
+ PVOID Pointer;
+ } u;
+ ULONG_PTR Information;
+} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;
+
+typedef enum _FILE_INFORMATION_CLASS {
+ FilePipeLocalInformation = 24
+} FILE_INFORMATION_CLASS, *PFILE_INFORMATION_CLASS;
+
+typedef DWORD (WINAPI *PNtQueryInformationFile)
+ (HANDLE, IO_STATUS_BLOCK *, VOID *, ULONG, FILE_INFORMATION_CLASS);
+
+# ifndef PIPE_BUF
+# define PIPE_BUF 512
+# endif
+
+/* Compute revents values for file handle H. If some events cannot happen
+ for the handle, eliminate them from *P_SOUGHT. */
+
+static int
+windows_compute_revents (HANDLE h, int *p_sought)
+{
+ int i, ret, happened;
+ INPUT_RECORD *irbuffer;
+ DWORD avail, nbuffer;
+ BOOL bRet;
+ IO_STATUS_BLOCK iosb;
+ FILE_PIPE_LOCAL_INFORMATION fpli;
+ static PNtQueryInformationFile NtQueryInformationFile;
+ static BOOL once_only;
+
+ switch (GetFileType (h))
+ {
+ case FILE_TYPE_PIPE:
+ if (!once_only)
+ {
+ NtQueryInformationFile = (PNtQueryInformationFile)
+ GetProcAddress (GetModuleHandle ("ntdll.dll"),
+ "NtQueryInformationFile");
+ once_only = TRUE;
+ }
+
+ happened = 0;
+ if (PeekNamedPipe (h, NULL, 0, NULL, &avail, NULL) != 0)
+ {
+ if (avail)
+ happened |= *p_sought & (POLLIN | POLLRDNORM);
+ }
+ else if (GetLastError () == ERROR_BROKEN_PIPE)
+ happened |= POLLHUP;
+
+ else
+ {
+ /* It was the write-end of the pipe. Check if it is writable.
+ If NtQueryInformationFile fails, optimistically assume the pipe is
+ writable. This could happen on Windows 9x, where
+ NtQueryInformationFile is not available, or if we inherit a pipe
+ that doesn't permit FILE_READ_ATTRIBUTES access on the write end
+ (I think this should not happen since Windows XP SP2; WINE seems
+ fine too). Otherwise, ensure that enough space is available for
+ atomic writes. */
+ memset (&iosb, 0, sizeof (iosb));
+ memset (&fpli, 0, sizeof (fpli));
+
+ if (!NtQueryInformationFile
+ || NtQueryInformationFile (h, &iosb, &fpli, sizeof (fpli),
+ FilePipeLocalInformation)
+ || fpli.WriteQuotaAvailable >= PIPE_BUF
+ || (fpli.OutboundQuota < PIPE_BUF &&
+ fpli.WriteQuotaAvailable == fpli.OutboundQuota))
+ happened |= *p_sought & (POLLOUT | POLLWRNORM | POLLWRBAND);
+ }
+ return happened;
+
+ case FILE_TYPE_CHAR:
+ ret = WaitForSingleObject (h, 0);
+ if (!IsConsoleHandle (h))
+ return ret == WAIT_OBJECT_0 ? *p_sought & ~(POLLPRI | POLLRDBAND) : 0;
+
+ nbuffer = avail = 0;
+ bRet = GetNumberOfConsoleInputEvents (h, &nbuffer);
+ if (bRet)
+ {
+ /* Input buffer. */
+ *p_sought &= POLLIN | POLLRDNORM;
+ if (nbuffer == 0)
+ return POLLHUP;
+ if (!*p_sought)
+ return 0;
+
+ irbuffer = (INPUT_RECORD *) alloca (nbuffer * sizeof (INPUT_RECORD));
+ bRet = PeekConsoleInput (h, irbuffer, nbuffer, &avail);
+ if (!bRet || avail == 0)
+ return POLLHUP;
+
+ for (i = 0; i < avail; i++)
+ if (irbuffer[i].EventType == KEY_EVENT)
+ return *p_sought;
+ return 0;
+ }
+ else
+ {
+ /* Screen buffer. */
+ *p_sought &= POLLOUT | POLLWRNORM | POLLWRBAND;
+ return *p_sought;
+ }
+
+ default:
+ ret = WaitForSingleObject (h, 0);
+ if (ret == WAIT_OBJECT_0)
+ return *p_sought & ~(POLLPRI | POLLRDBAND);
+
+ return *p_sought & (POLLOUT | POLLWRNORM | POLLWRBAND);
+ }
+}
+
+/* Convert fd_sets returned by select into revents values. */
+
+static int
+windows_compute_revents_socket (SOCKET h, int sought, long lNetworkEvents)
+{
+ int happened = 0;
+
+ if ((lNetworkEvents & (FD_READ | FD_ACCEPT | FD_CLOSE)) == FD_ACCEPT)
+ happened |= (POLLIN | POLLRDNORM) & sought;
+
+ else if (lNetworkEvents & (FD_READ | FD_ACCEPT | FD_CLOSE))
+ {
+ int r, error;
+
+ char data[64];
+ WSASetLastError (0);
+ r = recv (h, data, sizeof (data), MSG_PEEK);
+ error = WSAGetLastError ();
+ WSASetLastError (0);
+
+ if (r > 0 || error == WSAENOTCONN)
+ happened |= (POLLIN | POLLRDNORM) & sought;
+
+ /* Distinguish hung-up sockets from other errors. */
+ else if (r == 0 || error == WSAESHUTDOWN || error == WSAECONNRESET
+ || error == WSAECONNABORTED || error == WSAENETRESET)
+ happened |= POLLHUP;
+
+ else
+ happened |= POLLERR;
+ }
+
+ if (lNetworkEvents & (FD_WRITE | FD_CONNECT))
+ happened |= (POLLOUT | POLLWRNORM | POLLWRBAND) & sought;
+
+ if (lNetworkEvents & FD_OOB)
+ happened |= (POLLPRI | POLLRDBAND) & sought;
+
+ return happened;
+}
+
+#else /* !MinGW */
+
+/* Convert select(2) returned fd_sets into poll(2) revents values. */
+static int
+compute_revents (int fd, int sought, fd_set *rfds, fd_set *wfds, fd_set *efds)
+{
+ int happened = 0;
+ if (FD_ISSET (fd, rfds))
+ {
+ int r;
+ int socket_errno;
+
+# if defined __MACH__ && defined __APPLE__
+ /* There is a bug in Mac OS X that causes it to ignore MSG_PEEK
+ for some kinds of descriptors. Detect if this descriptor is a
+ connected socket, a server socket, or something else using a
+ 0-byte recv, and use ioctl(2) to detect POLLHUP. */
+ r = recv (fd, NULL, 0, MSG_PEEK);
+ socket_errno = (r < 0) ? errno : 0;
+ if (r == 0 || socket_errno == ENOTSOCK)
+ ioctl (fd, FIONREAD, &r);
+# else
+ char data[64];
+ r = recv (fd, data, sizeof (data), MSG_PEEK);
+ socket_errno = (r < 0) ? errno : 0;
+# endif
+ if (r == 0)
+ happened |= POLLHUP;
+
+ /* If the event happened on an unconnected server socket,
+ that's fine. */
+ else if (r > 0 || ( /* (r == -1) && */ socket_errno == ENOTCONN))
+ happened |= (POLLIN | POLLRDNORM) & sought;
+
+ /* Distinguish hung-up sockets from other errors. */
+ else if (socket_errno == ESHUTDOWN || socket_errno == ECONNRESET
+ || socket_errno == ECONNABORTED || socket_errno == ENETRESET)
+ happened |= POLLHUP;
+
+ /* some systems can't use recv() on non-socket, including HP NonStop */
+ else if (socket_errno == ENOTSOCK)
+ happened |= (POLLIN | POLLRDNORM) & sought;
+
+ else
+ happened |= POLLERR;
+ }
+
+ if (FD_ISSET (fd, wfds))
+ happened |= (POLLOUT | POLLWRNORM | POLLWRBAND) & sought;
+
+ if (FD_ISSET (fd, efds))
+ happened |= (POLLPRI | POLLRDBAND) & sought;
+
+ return happened;
+}
+#endif /* !MinGW */
+
+int
+pa_poll (struct pollfd *pfd, nfds_t nfd, int timeout)
+{
+ struct timeval tv;
+
+#ifndef WINDOWS_NATIVE
+ struct timeval *ptv;
+ fd_set rfds, wfds, efds;
+ int maxfd, rc;
+ nfds_t i;
+
+# ifdef _SC_OPEN_MAX
+ static int sc_open_max = -1;
+
+ if (nfd < 0
+ || (nfd > sc_open_max
+ && (sc_open_max != -1
+ || nfd > (sc_open_max = sysconf (_SC_OPEN_MAX)))))
+ {
+ errno = EINVAL;
+ return -1;
+ }
+# else /* !_SC_OPEN_MAX */
+# ifdef OPEN_MAX
+ if (nfd < 0 || nfd > OPEN_MAX)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+# endif /* OPEN_MAX -- else, no check is needed */
+# endif /* !_SC_OPEN_MAX */
+
+ /* EFAULT is not necessary to implement, but let's do it in the
+ simplest case. */
+ if (!pfd && nfd)
+ {
+ errno = EFAULT;
+ return -1;
+ }
+
+ /* convert timeout number into a timeval structure */
+ if (timeout == 0)
+ {
+ ptv = &tv;
+ ptv->tv_sec = 0;
+ ptv->tv_usec = 0;
+ }
+ else if (timeout > 0)
+ {
+ ptv = &tv;
+ ptv->tv_sec = timeout / 1000;
+ ptv->tv_usec = (timeout % 1000) * 1000;
+ }
+ else if (timeout == INFTIM)
+ /* wait forever */
+ ptv = NULL;
+ else
+ {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* create fd sets and determine max fd */
+ maxfd = -1;
+ FD_ZERO (&rfds);
+ FD_ZERO (&wfds);
+ FD_ZERO (&efds);
+ for (i = 0; i < nfd; i++)
+ {
+ if (pfd[i].fd < 0)
+ continue;
+
+ if (pfd[i].events & (POLLIN | POLLRDNORM))
+ FD_SET (pfd[i].fd, &rfds);
+
+ /* see select(2): "the only exceptional condition detectable
+ is out-of-band data received on a socket", hence we push
+ POLLWRBAND events onto wfds instead of efds. */
+ if (pfd[i].events & (POLLOUT | POLLWRNORM | POLLWRBAND))
+ FD_SET (pfd[i].fd, &wfds);
+ if (pfd[i].events & (POLLPRI | POLLRDBAND))
+ FD_SET (pfd[i].fd, &efds);
+ if (pfd[i].fd >= maxfd
+ && (pfd[i].events & (POLLIN | POLLOUT | POLLPRI
+ | POLLRDNORM | POLLRDBAND
+ | POLLWRNORM | POLLWRBAND)))
+ {
+ maxfd = pfd[i].fd;
+ if (maxfd > FD_SETSIZE)
+ {
+ errno = EOVERFLOW;
+ return -1;
+ }
+ }
+ }
+
+ /* examine fd sets */
+ rc = select (maxfd + 1, &rfds, &wfds, &efds, ptv);
+ if (rc < 0)
+ return rc;
+
+ /* establish results */
+ rc = 0;
+ for (i = 0; i < nfd; i++)
+ if (pfd[i].fd < 0)
+ pfd[i].revents = 0;
+ else
+ {
+ int happened = compute_revents (pfd[i].fd, pfd[i].events,
+ &rfds, &wfds, &efds);
+ if (happened)
+ {
+ pfd[i].revents = happened;
+ rc++;
+ }
+ }
+
+ return rc;
+#else /* WINDOWS_NATIVE*/
+ HANDLE hEvent;
+ WSANETWORKEVENTS ev;
+ HANDLE h, handle_array[FD_SETSIZE + 2];
+ DWORD ret, wait_timeout, nhandles;
+ fd_set rfds, wfds, xfds;
+ BOOL poll_again;
+ MSG msg;
+ int rc = 0;
+ nfds_t i;
+
+ hEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
+
+restart:
+ handle_array[0] = hEvent;
+ nhandles = 1;
+ FD_ZERO (&rfds);
+ FD_ZERO (&wfds);
+ FD_ZERO (&xfds);
+
+ /* Classify socket handles and create fd sets. */
+ for (i = 0; i < nfd; i++)
+ {
+ int sought = pfd[i].events;
+ pfd[i].revents = 0;
+ if (pfd[i].fd < 0)
+ continue;
+ if (!(sought & (POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM | POLLWRBAND
+ | POLLPRI | POLLRDBAND)))
+ continue;
+
+ h = HandleFromFd (pfd[i].fd);
+ assert (h != NULL && h != INVALID_HANDLE_VALUE);
+ if (IsSocketHandle (h))
+ {
+ int requested = FD_CLOSE;
+
+ /* see above; socket handles are mapped onto select. */
+ if (sought & (POLLIN | POLLRDNORM))
+ {
+ requested |= FD_READ | FD_ACCEPT;
+ FD_SET ((SOCKET) h, &rfds);
+ }
+ if (sought & (POLLOUT | POLLWRNORM | POLLWRBAND))
+ {
+ requested |= FD_WRITE | FD_CONNECT;
+ FD_SET ((SOCKET) h, &wfds);
+ }
+ if (sought & (POLLPRI | POLLRDBAND))
+ {
+ requested |= FD_OOB;
+ FD_SET ((SOCKET) h, &xfds);
+ }
+
+ if (requested)
+ WSAEventSelect ((SOCKET) h, hEvent, requested);
+ }
+ else
+ {
+ /* Poll now. If we get an event, do not poll again. Also,
+ screen buffer handles are waitable, and they'll block until
+ a character is available. windows_compute_revents eliminates
+ bits for the "wrong" direction. */
+ pfd[i].revents = windows_compute_revents (h, &sought);
+ if (sought)
+ handle_array[nhandles++] = h;
+ if (pfd[i].revents)
+ timeout = 0;
+ }
+ }
+
+ /* We poll current status using select(). It cannot be used to check
+ anything but sockets, so we still have to wait in
+ MsgWaitForMultipleObjects(). But that in turn cannot check existing
+ state, so we can't remove this select(). */
+ /* FIXME: MSDN states that we cannot give empty fd_set:s. */
+ tv.tv_sec = tv.tv_usec = 0;
+ if (select (0, &rfds, &wfds, &xfds, &tv) > 0)
+ {
+ /* Do MsgWaitForMultipleObjects anyway to dispatch messages, but
+ no need to call select again. */
+ poll_again = FALSE;
+ wait_timeout = 0;
+ }
+ else
+ {
+ poll_again = TRUE;
+ if (timeout == INFTIM)
+ wait_timeout = INFINITE;
+ else
+ wait_timeout = timeout;
+ }
+
+ for (;;)
+ {
+ ret = MsgWaitForMultipleObjects (nhandles, handle_array, FALSE,
+ wait_timeout, QS_ALLINPUT);
+
+ if (ret == WAIT_OBJECT_0 + nhandles)
+ {
+ /* new input of some other kind */
+ BOOL bRet;
+ while ((bRet = PeekMessage (&msg, NULL, 0, 0, PM_REMOVE)) != 0)
+ {
+ if (msg.message == WM_QUIT)
+ raise(SIGTERM);
+ else
+ {
+ TranslateMessage (&msg);
+ DispatchMessage (&msg);
+ }
+ }
+ }
+ else
+ break;
+ }
+
+ if (poll_again)
+ select (0, &rfds, &wfds, &xfds, &tv);
+
+ /* Place a sentinel at the end of the array. */
+ handle_array[nhandles] = NULL;
+ nhandles = 1;
+ for (i = 0; i < nfd; i++)
+ {
+ int happened;
+
+ if (pfd[i].fd < 0)
+ continue;
+ if (!(pfd[i].events & (POLLIN | POLLRDNORM |
+ POLLOUT | POLLWRNORM | POLLWRBAND)))
+ continue;
+
+ h = (HANDLE) HandleFromFd (pfd[i].fd);
+ if (h != handle_array[nhandles])
+ {
+ /* It's a socket. */
+ WSAEnumNetworkEvents ((SOCKET) h, NULL, &ev);
+ WSAEventSelect ((SOCKET) h, 0, 0);
+ /* Have to restore blocking as WSAEventSelect() clears it */
+ if (!pa_is_fd_nonblock(pfd[i].fd))
+ pa_make_fd_block(pfd[i].fd);
+
+ /* If we're lucky, WSAEnumNetworkEvents already provided a way
+ to distinguish FD_READ and FD_ACCEPT; this saves a recv later. */
+ if (FD_ISSET ((SOCKET) h, &rfds)
+ && !(ev.lNetworkEvents & (FD_READ | FD_ACCEPT)))
+ ev.lNetworkEvents |= FD_READ | FD_ACCEPT;
+ if (FD_ISSET ((SOCKET) h, &wfds))
+ ev.lNetworkEvents |= FD_WRITE | FD_CONNECT;
+ if (FD_ISSET ((SOCKET) h, &xfds))
+ ev.lNetworkEvents |= FD_OOB;
+
+ happened = windows_compute_revents_socket ((SOCKET) h, pfd[i].events,
+ ev.lNetworkEvents);
+ }
+ else
+ {
+ /* Not a socket. */
+ int sought = pfd[i].events;
+ happened = windows_compute_revents (h, &sought);
+ nhandles++;
+ }
+
+ if ((pfd[i].revents |= happened) != 0)
+ rc++;
+ }
+
+ if (!rc && timeout == INFTIM)
+ {
+ SleepEx (1, TRUE);
+ goto restart;
+ }
+
+ CloseHandle(hEvent);
+
+ return rc;
+#endif
+}
diff --git a/src/pulsecore/poll.h b/src/pulsecore/poll.h
new file mode 100644
index 0000000..4af1b99
--- /dev/null
+++ b/src/pulsecore/poll.h
@@ -0,0 +1,62 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+/***
+ Based on work for the GNU C Library.
+ Copyright (C) 1994,96,97,98,99,2000,2001,2004 Free Software Foundation, Inc.
+***/
+
+#if defined(HAVE_POLL_H)
+#include <poll.h>
+#else
+
+/* Event types that can be polled for. These bits may be set in `events'
+ to indicate the interesting event types; they will appear in `revents'
+ to indicate the status of the file descriptor. */
+#define POLLIN 0x001 /* There is data to read. */
+#define POLLPRI 0x002 /* There is urgent data to read. */
+#define POLLOUT 0x004 /* Writing now will not block. */
+
+/* Event types always implicitly polled for. These bits need not be set in
+ `events', but they will appear in `revents' to indicate the status of
+ the file descriptor. */
+#define POLLERR 0x008 /* Error condition. */
+#define POLLHUP 0x010 /* Hung up. */
+#define POLLNVAL 0x020 /* Invalid polling request. */
+
+/* Data structure describing a polling request. */
+struct pollfd {
+ int fd; /* File descriptor to poll. */
+ short int events; /* Types of events poller cares about. */
+ short int revents; /* Types of events that actually occurred. */
+};
+
+/* Poll the file descriptors described by the NFDS structures starting at
+ FDS. If TIMEOUT is nonzero and not -1, allow TIMEOUT milliseconds for
+ an event to occur; if TIMEOUT is -1, block until an event occurs.
+ Returns the number of file descriptors with events, zero if timed out,
+ or -1 for errors. */
+
+#endif /* HAVE_POLL_H */
+
+#if defined(HAVE_POLL_H) && !defined(OS_IS_DARWIN)
+#define pa_poll(fds,nfds,timeout) poll((fds),(nfds),(timeout))
+#else
+int pa_poll(struct pollfd *fds, unsigned long nfds, int timeout);
+#endif
diff --git a/src/pulsecore/proplist-util.c b/src/pulsecore/proplist-util.c
new file mode 100644
index 0000000..f966579
--- /dev/null
+++ b/src/pulsecore/proplist-util.c
@@ -0,0 +1,276 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2008 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <locale.h>
+
+#ifdef ENABLE_NLS
+#include <libintl.h>
+#endif
+
+#ifdef __APPLE__
+#include <crt_externs.h>
+#define environ (*_NSGetEnviron())
+#elif !HAVE_DECL_ENVIRON
+extern char **environ;
+#endif
+
+#include <pulse/gccmacro.h>
+#include <pulse/proplist.h>
+#include <pulse/utf8.h>
+#include <pulse/xmalloc.h>
+#include <pulse/util.h>
+
+#include <pulsecore/core-util.h>
+
+#if defined(HAVE_GLIB) && defined(PA_GCC_WEAKREF)
+#include <glib.h>
+static G_CONST_RETURN gchar* _g_get_application_name(void) PA_GCC_WEAKREF(g_get_application_name);
+#endif
+
+#if defined(HAVE_GTK) && defined(PA_GCC_WEAKREF)
+#pragma GCC diagnostic ignored "-Wstrict-prototypes"
+#include <gtk/gtk.h>
+#include <gdk/gdkx.h>
+static G_CONST_RETURN gchar* _gtk_window_get_default_icon_name(void) PA_GCC_WEAKREF(gtk_window_get_default_icon_name);
+static Display *_gdk_display PA_GCC_WEAKREF(gdk_display);
+#endif
+
+#include "proplist-util.h"
+
+static void add_glib_properties(pa_proplist *p) {
+
+#if defined(HAVE_GLIB) && defined(PA_GCC_WEAKREF)
+
+ if (!pa_proplist_contains(p, PA_PROP_APPLICATION_NAME))
+ if (_g_get_application_name) {
+ const gchar *t;
+
+ /* We ignore the tiny race condition here. */
+
+ if ((t = _g_get_application_name()))
+ pa_proplist_sets(p, PA_PROP_APPLICATION_NAME, t);
+ }
+
+#endif
+}
+
+static void add_gtk_properties(pa_proplist *p) {
+
+#if defined(HAVE_GTK) && defined(PA_GCC_WEAKREF)
+
+ if (!pa_proplist_contains(p, PA_PROP_APPLICATION_ICON_NAME))
+ if (_gtk_window_get_default_icon_name) {
+ const gchar *t;
+
+ /* We ignore the tiny race condition here. */
+
+ if ((t = _gtk_window_get_default_icon_name()))
+ pa_proplist_sets(p, PA_PROP_APPLICATION_ICON_NAME, t);
+ }
+
+ if (!pa_proplist_contains(p, PA_PROP_WINDOW_X11_DISPLAY))
+ if (&_gdk_display && _gdk_display) {
+ const char *t;
+
+ /* We ignore the tiny race condition here. */
+
+ if ((t = DisplayString(_gdk_display)))
+ pa_proplist_sets(p, PA_PROP_WINDOW_X11_DISPLAY, t);
+ }
+
+#endif
+}
+
+void pa_init_proplist(pa_proplist *p) {
+ char **e;
+ const char *pp;
+
+ pa_assert(p);
+
+ if (environ) {
+
+ /* Some applications seem to reset environ to NULL for various
+ * reasons, hence we need to check for this explicitly. See
+ * rhbz #473080 */
+
+ for (e = environ; *e; e++) {
+
+ if (pa_startswith(*e, "PULSE_PROP_")) {
+ size_t kl, skip;
+ char *k;
+ bool override;
+
+ if (pa_startswith(*e, "PULSE_PROP_OVERRIDE_")) {
+ skip = 20;
+ override = true;
+ } else {
+ skip = 11;
+ override = false;
+ }
+
+ kl = strcspn(*e+skip, "=");
+
+ if ((*e)[skip+kl] != '=')
+ continue;
+
+ k = pa_xstrndup(*e+skip, kl);
+
+ if (!pa_streq(k, "OVERRIDE"))
+ if (override || !pa_proplist_contains(p, k))
+ pa_proplist_sets(p, k, *e+skip+kl+1);
+ pa_xfree(k);
+ }
+ }
+ }
+
+ if ((pp = getenv("PULSE_PROP"))) {
+ pa_proplist *t;
+
+ if ((t = pa_proplist_from_string(pp))) {
+ pa_proplist_update(p, PA_UPDATE_MERGE, t);
+ pa_proplist_free(t);
+ }
+ }
+
+ if ((pp = getenv("PULSE_PROP_OVERRIDE"))) {
+ pa_proplist *t;
+
+ if ((t = pa_proplist_from_string(pp))) {
+ pa_proplist_update(p, PA_UPDATE_REPLACE, t);
+ pa_proplist_free(t);
+ }
+ }
+
+ if (!pa_proplist_contains(p, PA_PROP_APPLICATION_PROCESS_ID)) {
+ char t[32];
+ pa_snprintf(t, sizeof(t), "%lu", (unsigned long) getpid());
+ pa_proplist_sets(p, PA_PROP_APPLICATION_PROCESS_ID, t);
+ }
+
+ if (!pa_proplist_contains(p, PA_PROP_APPLICATION_PROCESS_USER)) {
+ char *u;
+
+ if ((u = pa_get_user_name_malloc())) {
+ pa_proplist_sets(p, PA_PROP_APPLICATION_PROCESS_USER, u);
+ pa_xfree(u);
+ }
+ }
+
+ if (!pa_proplist_contains(p, PA_PROP_APPLICATION_PROCESS_HOST)) {
+ char *h;
+
+ if ((h = pa_get_host_name_malloc())) {
+ pa_proplist_sets(p, PA_PROP_APPLICATION_PROCESS_HOST, h);
+ pa_xfree(h);
+ }
+ }
+
+ if (!pa_proplist_contains(p, PA_PROP_APPLICATION_PROCESS_BINARY)) {
+ char *t;
+
+ if ((t = pa_get_binary_name_malloc())) {
+ char *c = pa_utf8_filter(t);
+ pa_proplist_sets(p, PA_PROP_APPLICATION_PROCESS_BINARY, c);
+ pa_xfree(t);
+ pa_xfree(c);
+ }
+ }
+
+ add_glib_properties(p);
+ add_gtk_properties(p);
+
+ if (!pa_proplist_contains(p, PA_PROP_APPLICATION_NAME)) {
+ const char *t;
+
+ if ((t = pa_proplist_gets(p, PA_PROP_APPLICATION_PROCESS_BINARY)))
+ pa_proplist_sets(p, PA_PROP_APPLICATION_NAME, t);
+ }
+
+#ifdef ENABLE_NLS
+ if (!pa_proplist_contains(p, PA_PROP_APPLICATION_LANGUAGE)) {
+ const char *l;
+
+ if ((l = setlocale(LC_MESSAGES, NULL)))
+ pa_proplist_sets(p, PA_PROP_APPLICATION_LANGUAGE, l);
+ }
+#endif
+
+ if (!pa_proplist_contains(p, PA_PROP_WINDOW_X11_DISPLAY)) {
+ const char *t;
+
+ if ((t = getenv("DISPLAY"))) {
+ char *c = pa_utf8_filter(t);
+ pa_proplist_sets(p, PA_PROP_WINDOW_X11_DISPLAY, c);
+ pa_xfree(c);
+ }
+ }
+
+ if (!pa_proplist_contains(p, PA_PROP_APPLICATION_PROCESS_MACHINE_ID)) {
+ char *m;
+
+ if ((m = pa_machine_id())) {
+ pa_proplist_sets(p, PA_PROP_APPLICATION_PROCESS_MACHINE_ID, m);
+ pa_xfree(m);
+ }
+ }
+
+ if (!pa_proplist_contains(p, PA_PROP_APPLICATION_PROCESS_SESSION_ID)) {
+ char *s;
+
+ if ((s = pa_session_id())) {
+ pa_proplist_sets(p, PA_PROP_APPLICATION_PROCESS_SESSION_ID, s);
+ pa_xfree(s);
+ }
+ }
+}
+
+char *pa_proplist_get_stream_group(pa_proplist *p, const char *prefix, const char *cache) {
+ const char *r;
+ char *t;
+
+ if (!p)
+ return NULL;
+
+ if (cache && (r = pa_proplist_gets(p, cache)))
+ return pa_xstrdup(r);
+
+ if (!prefix)
+ prefix = "stream";
+
+ if ((r = pa_proplist_gets(p, PA_PROP_MEDIA_ROLE)))
+ t = pa_sprintf_malloc("%s-by-media-role:%s", prefix, r);
+ else if ((r = pa_proplist_gets(p, PA_PROP_APPLICATION_ID)))
+ t = pa_sprintf_malloc("%s-by-application-id:%s", prefix, r);
+ else if ((r = pa_proplist_gets(p, PA_PROP_APPLICATION_NAME)))
+ t = pa_sprintf_malloc("%s-by-application-name:%s", prefix, r);
+ else if ((r = pa_proplist_gets(p, PA_PROP_MEDIA_NAME)))
+ t = pa_sprintf_malloc("%s-by-media-name:%s", prefix, r);
+ else
+ t = pa_sprintf_malloc("%s-fallback:%s", prefix, r);
+
+ if (cache)
+ pa_proplist_sets(p, cache, t);
+
+ return t;
+}
diff --git a/src/pulsecore/proplist-util.h b/src/pulsecore/proplist-util.h
new file mode 100644
index 0000000..17f231f
--- /dev/null
+++ b/src/pulsecore/proplist-util.h
@@ -0,0 +1,28 @@
+#ifndef fooproplistutilutilhfoo
+#define fooproplistutilutilhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2008 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulse/proplist.h>
+
+void pa_init_proplist(pa_proplist *p);
+char *pa_proplist_get_stream_group(pa_proplist *pl, const char *prefix, const char *cache);
+
+#endif
diff --git a/src/pulsecore/protocol-cli.c b/src/pulsecore/protocol-cli.c
new file mode 100644
index 0000000..522a829
--- /dev/null
+++ b/src/pulsecore/protocol-cli.c
@@ -0,0 +1,140 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/cli.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/shared.h>
+
+#include "protocol-cli.h"
+
+/* Don't allow more than this many concurrent connections */
+#define MAX_CONNECTIONS 25
+
+struct pa_cli_protocol {
+ PA_REFCNT_DECLARE;
+
+ pa_core *core;
+ pa_idxset *connections;
+};
+
+static void cli_unlink(pa_cli_protocol *p, pa_cli *c) {
+ pa_assert(p);
+ pa_assert(c);
+
+ pa_idxset_remove_by_data(p->connections, c, NULL);
+ pa_cli_free(c);
+}
+
+static void cli_eof_cb(pa_cli*c, void*userdata) {
+ pa_cli_protocol *p = userdata;
+ pa_assert(p);
+
+ cli_unlink(p, c);
+}
+
+void pa_cli_protocol_connect(pa_cli_protocol *p, pa_iochannel *io, pa_module *m) {
+ pa_cli *c;
+
+ pa_assert(p);
+ pa_assert(io);
+ pa_assert(m);
+
+ if (pa_idxset_size(p->connections)+1 > MAX_CONNECTIONS) {
+ pa_log("Warning! Too many connections (%u), dropping incoming connection.", MAX_CONNECTIONS);
+ pa_iochannel_free(io);
+ return;
+ }
+
+ c = pa_cli_new(p->core, io, m);
+ pa_cli_set_eof_callback(c, cli_eof_cb, p);
+
+ pa_idxset_put(p->connections, c, NULL);
+}
+
+void pa_cli_protocol_disconnect(pa_cli_protocol *p, pa_module *m) {
+ pa_cli *c;
+ void *state = NULL;
+
+ pa_assert(p);
+ pa_assert(m);
+
+ while ((c = pa_idxset_iterate(p->connections, &state, NULL)))
+ if (pa_cli_get_module(c) == m)
+ cli_unlink(p, c);
+}
+
+static pa_cli_protocol* cli_protocol_new(pa_core *c) {
+ pa_cli_protocol *p;
+
+ pa_assert(c);
+
+ p = pa_xnew(pa_cli_protocol, 1);
+ PA_REFCNT_INIT(p);
+ p->core = c;
+ p->connections = pa_idxset_new(NULL, NULL);
+
+ pa_assert_se(pa_shared_set(c, "cli-protocol", p) >= 0);
+
+ return p;
+}
+
+pa_cli_protocol* pa_cli_protocol_get(pa_core *c) {
+ pa_cli_protocol *p;
+
+ if ((p = pa_shared_get(c, "cli-protocol")))
+ return pa_cli_protocol_ref(p);
+
+ return cli_protocol_new(c);
+}
+
+pa_cli_protocol* pa_cli_protocol_ref(pa_cli_protocol *p) {
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) >= 1);
+
+ PA_REFCNT_INC(p);
+
+ return p;
+}
+
+void pa_cli_protocol_unref(pa_cli_protocol *p) {
+ pa_cli *c;
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) >= 1);
+
+ if (PA_REFCNT_DEC(p) > 0)
+ return;
+
+ while ((c = pa_idxset_first(p->connections, NULL)))
+ cli_unlink(p, c);
+
+ pa_idxset_free(p->connections, NULL);
+
+ pa_assert_se(pa_shared_remove(p->core, "cli-protocol") >= 0);
+
+ pa_xfree(p);
+}
diff --git a/src/pulsecore/protocol-cli.h b/src/pulsecore/protocol-cli.h
new file mode 100644
index 0000000..fde6e38
--- /dev/null
+++ b/src/pulsecore/protocol-cli.h
@@ -0,0 +1,36 @@
+#ifndef fooprotocolclihfoo
+#define fooprotocolclihfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulsecore/core.h>
+#include <pulsecore/socket-server.h>
+#include <pulsecore/module.h>
+#include <pulsecore/modargs.h>
+
+typedef struct pa_cli_protocol pa_cli_protocol;
+
+pa_cli_protocol* pa_cli_protocol_get(pa_core *core);
+pa_cli_protocol* pa_cli_protocol_ref(pa_cli_protocol *p);
+void pa_cli_protocol_unref(pa_cli_protocol *p);
+void pa_cli_protocol_connect(pa_cli_protocol *p, pa_iochannel *io, pa_module *m);
+void pa_cli_protocol_disconnect(pa_cli_protocol *o, pa_module *m);
+
+#endif
diff --git a/src/pulsecore/protocol-dbus.c b/src/pulsecore/protocol-dbus.c
new file mode 100644
index 0000000..31a48d1
--- /dev/null
+++ b/src/pulsecore/protocol-dbus.c
@@ -0,0 +1,1140 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2009 Tanu Kaskinen
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <dbus/dbus.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/dbus-util.h>
+#include <pulsecore/hashmap.h>
+#include <pulsecore/idxset.h>
+#include <pulsecore/shared.h>
+#include <pulsecore/strbuf.h>
+
+#include "protocol-dbus.h"
+
+struct pa_dbus_protocol {
+ PA_REFCNT_DECLARE;
+
+ pa_core *core;
+ pa_hashmap *objects; /* Object path -> struct object_entry */
+ pa_hashmap *connections; /* DBusConnection -> struct connection_entry */
+ pa_idxset *extensions; /* Strings */
+
+ pa_hook hooks[PA_DBUS_PROTOCOL_HOOK_MAX];
+};
+
+struct object_entry {
+ char *path;
+ pa_hashmap *interfaces; /* Interface name -> struct interface_entry */
+ char *introspection;
+};
+
+struct connection_entry {
+ DBusConnection *connection;
+ pa_client *client;
+
+ bool listening_for_all_signals;
+
+ /* Contains object paths. If this is empty, then signals from all objects
+ * are accepted. Only used when listening_for_all_signals == true. */
+ pa_idxset *all_signals_objects;
+
+ /* Signal name -> signal paths entry. The entries contain object paths. If
+ * a path set is empty, then that signal is accepted from all objects. This
+ * variable is only used when listening_for_all_signals == false. */
+ pa_hashmap *listening_signals;
+};
+
+/* Only used in connection entries' listening_signals hashmap. */
+struct signal_paths_entry {
+ char *signal;
+ pa_idxset *paths;
+};
+
+struct interface_entry {
+ char *name;
+ pa_hashmap *method_handlers;
+ pa_hashmap *method_signatures; /* Derived from method_handlers. Contains only "in" arguments. */
+ pa_hashmap *property_handlers;
+ pa_dbus_receive_cb_t get_all_properties_cb;
+ pa_dbus_signal_info *signals;
+ unsigned n_signals;
+ void *userdata;
+};
+
+char *pa_get_dbus_address_from_server_type(pa_server_type_t server_type) {
+ char *address = NULL;
+ char *runtime_path = NULL;
+ char *escaped_path = NULL;
+
+ switch (server_type) {
+ case PA_SERVER_TYPE_USER:
+ pa_assert_se((runtime_path = pa_runtime_path(PA_DBUS_SOCKET_NAME)));
+ pa_assert_se((escaped_path = dbus_address_escape_value(runtime_path)));
+ address = pa_sprintf_malloc("unix:path=%s", escaped_path);
+ break;
+
+ case PA_SERVER_TYPE_SYSTEM:
+ pa_assert_se((escaped_path = dbus_address_escape_value(PA_DBUS_SYSTEM_SOCKET_PATH)));
+ address = pa_sprintf_malloc("unix:path=%s", escaped_path);
+ break;
+
+ case PA_SERVER_TYPE_NONE:
+ address = pa_xnew0(char, 1);
+ break;
+
+ default:
+ pa_assert_not_reached();
+ }
+
+ pa_xfree(runtime_path);
+ dbus_free(escaped_path);
+
+ return address;
+}
+
+static pa_dbus_protocol *dbus_protocol_new(pa_core *c) {
+ pa_dbus_protocol *p;
+ unsigned i;
+
+ pa_assert(c);
+
+ p = pa_xnew(pa_dbus_protocol, 1);
+ PA_REFCNT_INIT(p);
+ p->core = c;
+ p->objects = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+ p->connections = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
+ p->extensions = pa_idxset_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+
+ for (i = 0; i < PA_DBUS_PROTOCOL_HOOK_MAX; ++i)
+ pa_hook_init(&p->hooks[i], p);
+
+ pa_assert_se(pa_shared_set(c, "dbus-protocol", p) >= 0);
+
+ return p;
+}
+
+pa_dbus_protocol* pa_dbus_protocol_get(pa_core *c) {
+ pa_dbus_protocol *p;
+
+ if ((p = pa_shared_get(c, "dbus-protocol")))
+ return pa_dbus_protocol_ref(p);
+
+ return dbus_protocol_new(c);
+}
+
+pa_dbus_protocol* pa_dbus_protocol_ref(pa_dbus_protocol *p) {
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) >= 1);
+
+ PA_REFCNT_INC(p);
+
+ return p;
+}
+
+void pa_dbus_protocol_unref(pa_dbus_protocol *p) {
+ unsigned i;
+
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) >= 1);
+
+ if (PA_REFCNT_DEC(p) > 0)
+ return;
+
+ pa_assert(pa_hashmap_isempty(p->objects));
+ pa_assert(pa_hashmap_isempty(p->connections));
+ pa_assert(pa_idxset_isempty(p->extensions));
+
+ pa_hashmap_free(p->objects);
+ pa_hashmap_free(p->connections);
+ pa_idxset_free(p->extensions, NULL);
+
+ for (i = 0; i < PA_DBUS_PROTOCOL_HOOK_MAX; ++i)
+ pa_hook_done(&p->hooks[i]);
+
+ pa_assert_se(pa_shared_remove(p->core, "dbus-protocol") >= 0);
+
+ pa_xfree(p);
+}
+
+static void update_introspection(struct object_entry *oe) {
+ pa_strbuf *buf;
+ void *interfaces_state = NULL;
+ struct interface_entry *iface_entry = NULL;
+
+ pa_assert(oe);
+
+ buf = pa_strbuf_new();
+ pa_strbuf_puts(buf, DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE);
+ pa_strbuf_puts(buf, "<node>\n");
+
+ PA_HASHMAP_FOREACH(iface_entry, oe->interfaces, interfaces_state) {
+ pa_dbus_method_handler *method_handler;
+ pa_dbus_property_handler *property_handler;
+ void *handlers_state = NULL;
+ unsigned i;
+ unsigned j;
+
+ pa_strbuf_printf(buf, " <interface name=\"%s\">\n", iface_entry->name);
+
+ PA_HASHMAP_FOREACH(method_handler, iface_entry->method_handlers, handlers_state) {
+ pa_strbuf_printf(buf, " <method name=\"%s\">\n", method_handler->method_name);
+
+ for (i = 0; i < method_handler->n_arguments; ++i)
+ pa_strbuf_printf(buf, " <arg name=\"%s\" type=\"%s\" direction=\"%s\"/>\n",
+ method_handler->arguments[i].name,
+ method_handler->arguments[i].type,
+ method_handler->arguments[i].direction);
+
+ pa_strbuf_puts(buf, " </method>\n");
+ }
+
+ handlers_state = NULL;
+
+ PA_HASHMAP_FOREACH(property_handler, iface_entry->property_handlers, handlers_state)
+ pa_strbuf_printf(buf, " <property name=\"%s\" type=\"%s\" access=\"%s\"/>\n",
+ property_handler->property_name,
+ property_handler->type,
+ property_handler->get_cb ? (property_handler->set_cb ? "readwrite" : "read") : "write");
+
+ for (i = 0; i < iface_entry->n_signals; ++i) {
+ pa_strbuf_printf(buf, " <signal name=\"%s\">\n", iface_entry->signals[i].name);
+
+ for (j = 0; j < iface_entry->signals[i].n_arguments; ++j)
+ pa_strbuf_printf(buf, " <arg name=\"%s\" type=\"%s\"/>\n", iface_entry->signals[i].arguments[j].name,
+ iface_entry->signals[i].arguments[j].type);
+
+ pa_strbuf_puts(buf, " </signal>\n");
+ }
+
+ pa_strbuf_puts(buf, " </interface>\n");
+ }
+
+ pa_strbuf_puts(buf, " <interface name=\"" DBUS_INTERFACE_INTROSPECTABLE "\">\n"
+ " <method name=\"Introspect\">\n"
+ " <arg name=\"data\" type=\"s\" direction=\"out\"/>\n"
+ " </method>\n"
+ " </interface>\n"
+ " <interface name=\"" DBUS_INTERFACE_PROPERTIES "\">\n"
+ " <method name=\"Get\">\n"
+ " <arg name=\"interface_name\" type=\"s\" direction=\"in\"/>\n"
+ " <arg name=\"property_name\" type=\"s\" direction=\"in\"/>\n"
+ " <arg name=\"value\" type=\"v\" direction=\"out\"/>\n"
+ " </method>\n"
+ " <method name=\"Set\">\n"
+ " <arg name=\"interface_name\" type=\"s\" direction=\"in\"/>\n"
+ " <arg name=\"property_name\" type=\"s\" direction=\"in\"/>\n"
+ " <arg name=\"value\" type=\"v\" direction=\"in\"/>\n"
+ " </method>\n"
+ " <method name=\"GetAll\">\n"
+ " <arg name=\"interface_name\" type=\"s\" direction=\"in\"/>\n"
+ " <arg name=\"props\" type=\"a{sv}\" direction=\"out\"/>\n"
+ " </method>\n"
+ " </interface>\n");
+
+ pa_strbuf_puts(buf, "</node>\n");
+
+ pa_xfree(oe->introspection);
+ oe->introspection = pa_strbuf_to_string_free(buf);
+}
+
+/* Return value of find_handler() and its subfunctions. */
+enum find_result_t {
+ /* The received message is a valid .Get call. */
+ FOUND_GET_PROPERTY,
+
+ /* The received message is a valid .Set call. */
+ FOUND_SET_PROPERTY,
+
+ /* The received message is a valid .GetAll call. */
+ FOUND_GET_ALL,
+
+ /* The received message is a valid method call. */
+ FOUND_METHOD,
+
+ /* The interface of the received message hasn't been registered for the
+ * destination object. */
+ NO_SUCH_INTERFACE,
+
+ /* No property handler was found for the received .Get or .Set call. */
+ NO_SUCH_PROPERTY,
+
+ /* The interface argument of a property call didn't match any registered
+ * interface. */
+ NO_SUCH_PROPERTY_INTERFACE,
+
+ /* The received message called .Get or .Set for a property whose access
+ * mode doesn't match the call. */
+ PROPERTY_ACCESS_DENIED,
+
+ /* The new value signature of a .Set call didn't match the expected
+ * signature. */
+ INVALID_PROPERTY_SIG,
+
+ /* No method handler was found for the received message. */
+ NO_SUCH_METHOD,
+
+ /* The signature of the received message didn't match the expected
+ * signature. Despite the name, this can also be returned for a property
+ * call if its message signature is invalid. */
+ INVALID_METHOD_SIG
+};
+
+/* Data for resolving the correct reaction to a received message. */
+struct call_info {
+ DBusMessage *message; /* The received message. */
+ struct object_entry *obj_entry;
+ const char *interface; /* Destination interface name (extracted from the message). */
+ struct interface_entry *iface_entry;
+
+ const char *property; /* Property name (extracted from the message). */
+ const char *property_interface; /* The interface argument of a property call is stored here. */
+ pa_dbus_property_handler *property_handler;
+ const char *expected_property_sig; /* Property signature from the introspection data. */
+ char *property_sig; /* The signature of the new value in the received .Set message. */
+ DBusMessageIter variant_iter; /* Iterator pointing to the beginning of the new value variant of a .Set call. */
+
+ const char *method; /* Method name (extracted from the message). */
+ pa_dbus_method_handler *method_handler;
+ const char *expected_method_sig; /* Method signature from the introspection data. */
+ const char *method_sig; /* The signature of the received message. */
+};
+
+/* Called when call_info->property has been set and the property interface has
+ * not been given. In case of a Set call, call_info->property_sig is also set,
+ * which is checked against the expected value in this function. */
+static enum find_result_t find_handler_by_property(struct call_info *call_info) {
+ void *state = NULL;
+
+ pa_assert(call_info);
+
+ PA_HASHMAP_FOREACH(call_info->iface_entry, call_info->obj_entry->interfaces, state) {
+ if ((call_info->property_handler = pa_hashmap_get(call_info->iface_entry->property_handlers, call_info->property))) {
+ if (pa_streq(call_info->method, "Get"))
+ return call_info->property_handler->get_cb ? FOUND_GET_PROPERTY : PROPERTY_ACCESS_DENIED;
+
+ else if (pa_streq(call_info->method, "Set")) {
+ call_info->expected_property_sig = call_info->property_handler->type;
+
+ if (pa_streq(call_info->property_sig, call_info->expected_property_sig))
+ return call_info->property_handler->set_cb ? FOUND_SET_PROPERTY : PROPERTY_ACCESS_DENIED;
+ else
+ return INVALID_PROPERTY_SIG;
+
+ } else
+ pa_assert_not_reached();
+ }
+ }
+
+ return NO_SUCH_PROPERTY;
+}
+
+static enum find_result_t find_handler_by_method(struct call_info *call_info) {
+ void *state = NULL;
+
+ pa_assert(call_info);
+
+ PA_HASHMAP_FOREACH(call_info->iface_entry, call_info->obj_entry->interfaces, state) {
+ if ((call_info->method_handler = pa_hashmap_get(call_info->iface_entry->method_handlers, call_info->method))) {
+ pa_assert_se(call_info->expected_method_sig = pa_hashmap_get(call_info->iface_entry->method_signatures, call_info->method));
+
+ if (pa_streq(call_info->method_sig, call_info->expected_method_sig))
+ return FOUND_METHOD;
+ else
+ return INVALID_METHOD_SIG;
+ }
+ }
+
+ return NO_SUCH_METHOD;
+}
+
+static enum find_result_t find_handler_from_properties_call(struct call_info *call_info) {
+ pa_assert(call_info);
+
+ if (pa_streq(call_info->method, "GetAll")) {
+ call_info->expected_method_sig = "s";
+ if (!pa_streq(call_info->method_sig, call_info->expected_method_sig))
+ return INVALID_METHOD_SIG;
+
+ pa_assert_se(dbus_message_get_args(call_info->message, NULL,
+ DBUS_TYPE_STRING, &call_info->property_interface,
+ DBUS_TYPE_INVALID));
+
+ if (*call_info->property_interface) {
+ if ((call_info->iface_entry = pa_hashmap_get(call_info->obj_entry->interfaces, call_info->property_interface)))
+ return FOUND_GET_ALL;
+ else
+ return NO_SUCH_PROPERTY_INTERFACE;
+
+ } else {
+ pa_assert_se(call_info->iface_entry = pa_hashmap_first(call_info->obj_entry->interfaces));
+ return FOUND_GET_ALL;
+ }
+
+ } else if (pa_streq(call_info->method, "Get")) {
+ call_info->expected_method_sig = "ss";
+ if (!pa_streq(call_info->method_sig, call_info->expected_method_sig))
+ return INVALID_METHOD_SIG;
+
+ pa_assert_se(dbus_message_get_args(call_info->message, NULL,
+ DBUS_TYPE_STRING, &call_info->property_interface,
+ DBUS_TYPE_STRING, &call_info->property,
+ DBUS_TYPE_INVALID));
+
+ if (*call_info->property_interface) {
+ if (!(call_info->iface_entry = pa_hashmap_get(call_info->obj_entry->interfaces, call_info->property_interface)))
+ return NO_SUCH_PROPERTY_INTERFACE;
+ else if ((call_info->property_handler =
+ pa_hashmap_get(call_info->iface_entry->property_handlers, call_info->property)))
+ return call_info->property_handler->get_cb ? FOUND_GET_PROPERTY : PROPERTY_ACCESS_DENIED;
+ else
+ return NO_SUCH_PROPERTY;
+
+ } else
+ return find_handler_by_property(call_info);
+
+ } else if (pa_streq(call_info->method, "Set")) {
+ DBusMessageIter msg_iter;
+
+ call_info->expected_method_sig = "ssv";
+ if (!pa_streq(call_info->method_sig, call_info->expected_method_sig))
+ return INVALID_METHOD_SIG;
+
+ pa_assert_se(dbus_message_iter_init(call_info->message, &msg_iter));
+
+ dbus_message_iter_get_basic(&msg_iter, &call_info->property_interface);
+ pa_assert_se(dbus_message_iter_next(&msg_iter));
+ dbus_message_iter_get_basic(&msg_iter, &call_info->property);
+ pa_assert_se(dbus_message_iter_next(&msg_iter));
+
+ dbus_message_iter_recurse(&msg_iter, &call_info->variant_iter);
+
+ pa_assert_se(call_info->property_sig = dbus_message_iter_get_signature(&call_info->variant_iter));
+
+ if (*call_info->property_interface) {
+ if (!(call_info->iface_entry = pa_hashmap_get(call_info->obj_entry->interfaces, call_info->property_interface)))
+ return NO_SUCH_PROPERTY_INTERFACE;
+
+ else if ((call_info->property_handler =
+ pa_hashmap_get(call_info->iface_entry->property_handlers, call_info->property))) {
+ call_info->expected_property_sig = call_info->property_handler->type;
+
+ if (pa_streq(call_info->property_sig, call_info->expected_property_sig))
+ return call_info->property_handler->set_cb ? FOUND_SET_PROPERTY : PROPERTY_ACCESS_DENIED;
+ else
+ return INVALID_PROPERTY_SIG;
+
+ } else
+ return NO_SUCH_PROPERTY;
+
+ } else
+ return find_handler_by_property(call_info);
+
+ } else
+ pa_assert_not_reached();
+}
+
+static enum find_result_t find_handler(struct call_info *call_info) {
+ pa_assert(call_info);
+
+ if (call_info->interface) {
+ if (pa_streq(call_info->interface, DBUS_INTERFACE_PROPERTIES))
+ return find_handler_from_properties_call(call_info);
+
+ else if (!(call_info->iface_entry = pa_hashmap_get(call_info->obj_entry->interfaces, call_info->interface)))
+ return NO_SUCH_INTERFACE;
+
+ else if ((call_info->method_handler = pa_hashmap_get(call_info->iface_entry->method_handlers, call_info->method))) {
+ pa_assert_se(call_info->expected_method_sig = pa_hashmap_get(call_info->iface_entry->method_signatures, call_info->method));
+
+ if (!pa_streq(call_info->method_sig, call_info->expected_method_sig))
+ return INVALID_METHOD_SIG;
+
+ return FOUND_METHOD;
+
+ } else
+ return NO_SUCH_METHOD;
+
+ } else { /* The method call doesn't contain an interface. */
+ if (pa_streq(call_info->method, "Get") || pa_streq(call_info->method, "Set") || pa_streq(call_info->method, "GetAll")) {
+ if (find_handler_by_method(call_info) == FOUND_METHOD)
+ /* The object has a method named Get, Set or GetAll in some other interface than .Properties. */
+ return FOUND_METHOD;
+ else
+ /* Assume this is a .Properties call. */
+ return find_handler_from_properties_call(call_info);
+
+ } else /* This is not a .Properties call. */
+ return find_handler_by_method(call_info);
+ }
+}
+
+static DBusHandlerResult handle_message_cb(DBusConnection *connection, DBusMessage *message, void *user_data) {
+ pa_dbus_protocol *p = user_data;
+ struct call_info call_info;
+ call_info.property_sig = NULL;
+
+ pa_assert(connection);
+ pa_assert(message);
+ pa_assert(p);
+ pa_assert(p->objects);
+
+ if (dbus_message_get_type(message) != DBUS_MESSAGE_TYPE_METHOD_CALL)
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+ pa_log_debug("Received message: destination = %s, interface = %s, member = %s",
+ dbus_message_get_path(message),
+ dbus_message_get_interface(message),
+ dbus_message_get_member(message));
+
+ call_info.message = message;
+ pa_assert_se(call_info.obj_entry = pa_hashmap_get(p->objects, dbus_message_get_path(message)));
+ call_info.interface = dbus_message_get_interface(message);
+ pa_assert_se(call_info.method = dbus_message_get_member(message));
+ pa_assert_se(call_info.method_sig = dbus_message_get_signature(message));
+
+ if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Introspectable", "Introspect") ||
+ (!dbus_message_get_interface(message) && dbus_message_has_member(message, "Introspect"))) {
+ pa_dbus_send_basic_value_reply(connection, message, DBUS_TYPE_STRING, &call_info.obj_entry->introspection);
+ goto finish;
+ }
+
+ switch (find_handler(&call_info)) {
+ case FOUND_GET_PROPERTY:
+ call_info.property_handler->get_cb(connection, message, call_info.iface_entry->userdata);
+ break;
+
+ case FOUND_SET_PROPERTY:
+ call_info.property_handler->set_cb(connection, message, &call_info.variant_iter, call_info.iface_entry->userdata);
+ break;
+
+ case FOUND_METHOD:
+ call_info.method_handler->receive_cb(connection, message, call_info.iface_entry->userdata);
+ break;
+
+ case FOUND_GET_ALL:
+ if (call_info.iface_entry->get_all_properties_cb)
+ call_info.iface_entry->get_all_properties_cb(connection, message, call_info.iface_entry->userdata);
+ else {
+ DBusMessage *dummy_reply = NULL;
+ DBusMessageIter msg_iter;
+ DBusMessageIter dict_iter;
+
+ pa_assert_se(dummy_reply = dbus_message_new_method_return(message));
+ dbus_message_iter_init_append(dummy_reply, &msg_iter);
+ pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter));
+ pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter));
+ pa_assert_se(dbus_connection_send(connection, dummy_reply, NULL));
+ dbus_message_unref(dummy_reply);
+ }
+ break;
+
+ case PROPERTY_ACCESS_DENIED:
+ pa_dbus_send_error(connection, message, DBUS_ERROR_ACCESS_DENIED,
+ "%s access denied for property %s", call_info.method, call_info.property);
+ break;
+
+ case NO_SUCH_METHOD:
+ pa_dbus_send_error(connection, message, DBUS_ERROR_UNKNOWN_METHOD, "No such method: %s", call_info.method);
+ break;
+
+ case NO_SUCH_INTERFACE:
+ pa_dbus_send_error(connection, message, PA_DBUS_ERROR_NO_SUCH_INTERFACE, "No such interface: %s", call_info.interface);
+ break;
+
+ case NO_SUCH_PROPERTY:
+ pa_dbus_send_error(connection, message, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "No such property: %s", call_info.property);
+ break;
+
+ case NO_SUCH_PROPERTY_INTERFACE:
+ pa_dbus_send_error(connection, message, PA_DBUS_ERROR_NO_SUCH_INTERFACE, "No such property interface: %s", call_info.property_interface);
+ break;
+
+ case INVALID_METHOD_SIG:
+ pa_dbus_send_error(connection, message, DBUS_ERROR_INVALID_ARGS,
+ "Invalid signature for method %s: '%s'. Expected '%s'.",
+ call_info.method, call_info.method_sig, call_info.expected_method_sig);
+ break;
+
+ case INVALID_PROPERTY_SIG:
+ pa_dbus_send_error(connection, message, DBUS_ERROR_INVALID_ARGS,
+ "Invalid signature for property %s: '%s'. Expected '%s'.",
+ call_info.property, call_info.property_sig, call_info.expected_property_sig);
+ break;
+
+ default:
+ pa_assert_not_reached();
+ }
+
+finish:
+ if (call_info.property_sig)
+ dbus_free(call_info.property_sig);
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static DBusObjectPathVTable vtable = {
+ .unregister_function = NULL,
+ .message_function = handle_message_cb,
+ .dbus_internal_pad1 = NULL,
+ .dbus_internal_pad2 = NULL,
+ .dbus_internal_pad3 = NULL,
+ .dbus_internal_pad4 = NULL
+};
+
+static void register_object(pa_dbus_protocol *p, struct object_entry *obj_entry) {
+ struct connection_entry *conn_entry;
+ void *state = NULL;
+
+ pa_assert(p);
+ pa_assert(obj_entry);
+
+ PA_HASHMAP_FOREACH(conn_entry, p->connections, state)
+ pa_assert_se(dbus_connection_register_object_path(conn_entry->connection, obj_entry->path, &vtable, p));
+}
+
+static pa_dbus_arg_info *copy_args(const pa_dbus_arg_info *src, unsigned n) {
+ pa_dbus_arg_info *dst;
+ unsigned i;
+
+ if (n == 0)
+ return NULL;
+
+ pa_assert(src);
+
+ dst = pa_xnew0(pa_dbus_arg_info, n);
+
+ for (i = 0; i < n; ++i) {
+ dst[i].name = pa_xstrdup(src[i].name);
+ dst[i].type = pa_xstrdup(src[i].type);
+ dst[i].direction = pa_xstrdup(src[i].direction);
+ }
+
+ return dst;
+}
+
+static void method_handler_free(pa_dbus_method_handler *h) {
+ unsigned i;
+
+ pa_assert(h);
+
+ pa_xfree((char *) h->method_name);
+
+ for (i = 0; i < h->n_arguments; ++i) {
+ pa_xfree((char *) h->arguments[i].name);
+ pa_xfree((char *) h->arguments[i].type);
+ pa_xfree((char *) h->arguments[i].direction);
+ }
+
+ pa_xfree((pa_dbus_arg_info *) h->arguments);
+ pa_xfree(h);
+}
+
+static pa_hashmap *create_method_handlers(const pa_dbus_interface_info *info) {
+ pa_hashmap *handlers;
+ unsigned i;
+
+ pa_assert(info);
+ pa_assert(info->method_handlers || info->n_method_handlers == 0);
+
+ handlers = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, (pa_free_cb_t) method_handler_free);
+
+ for (i = 0; i < info->n_method_handlers; ++i) {
+ pa_dbus_method_handler *h = pa_xnew(pa_dbus_method_handler, 1);
+ h->method_name = pa_xstrdup(info->method_handlers[i].method_name);
+ h->arguments = copy_args(info->method_handlers[i].arguments, info->method_handlers[i].n_arguments);
+ h->n_arguments = info->method_handlers[i].n_arguments;
+ h->receive_cb = info->method_handlers[i].receive_cb;
+
+ pa_hashmap_put(handlers, (char *) h->method_name, h);
+ }
+
+ return handlers;
+}
+
+static pa_hashmap *extract_method_signatures(pa_hashmap *method_handlers) {
+ pa_hashmap *signatures = NULL;
+ pa_dbus_method_handler *handler = NULL;
+ void *state = NULL;
+ pa_strbuf *sig_buf = NULL;
+ unsigned i = 0;
+
+ pa_assert(method_handlers);
+
+ signatures = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, pa_xfree);
+
+ PA_HASHMAP_FOREACH(handler, method_handlers, state) {
+ sig_buf = pa_strbuf_new();
+
+ for (i = 0; i < handler->n_arguments; ++i) {
+ if (pa_streq(handler->arguments[i].direction, "in"))
+ pa_strbuf_puts(sig_buf, handler->arguments[i].type);
+ }
+
+ pa_hashmap_put(signatures, (char *) handler->method_name, pa_strbuf_to_string_free(sig_buf));
+ }
+
+ return signatures;
+}
+
+static void property_handler_free(pa_dbus_property_handler *h) {
+ pa_assert(h);
+
+ pa_xfree((char *) h->property_name);
+ pa_xfree((char *) h->type);
+
+ pa_xfree(h);
+}
+
+static pa_hashmap *create_property_handlers(const pa_dbus_interface_info *info) {
+ pa_hashmap *handlers;
+ unsigned i = 0;
+
+ pa_assert(info);
+ pa_assert(info->property_handlers || info->n_property_handlers == 0);
+
+ handlers = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, (pa_free_cb_t) property_handler_free);
+
+ for (i = 0; i < info->n_property_handlers; ++i) {
+ pa_dbus_property_handler *h = pa_xnew(pa_dbus_property_handler, 1);
+ h->property_name = pa_xstrdup(info->property_handlers[i].property_name);
+ h->type = pa_xstrdup(info->property_handlers[i].type);
+ h->get_cb = info->property_handlers[i].get_cb;
+ h->set_cb = info->property_handlers[i].set_cb;
+
+ pa_hashmap_put(handlers, (char *) h->property_name, h);
+ }
+
+ return handlers;
+}
+
+static pa_dbus_signal_info *copy_signals(const pa_dbus_interface_info *info) {
+ pa_dbus_signal_info *dst;
+ unsigned i;
+
+ pa_assert(info);
+
+ if (info->n_signals == 0)
+ return NULL;
+
+ pa_assert(info->signals);
+
+ dst = pa_xnew(pa_dbus_signal_info, info->n_signals);
+
+ for (i = 0; i < info->n_signals; ++i) {
+ dst[i].name = pa_xstrdup(info->signals[i].name);
+ dst[i].arguments = copy_args(info->signals[i].arguments, info->signals[i].n_arguments);
+ dst[i].n_arguments = info->signals[i].n_arguments;
+ }
+
+ return dst;
+}
+
+int pa_dbus_protocol_add_interface(pa_dbus_protocol *p,
+ const char *path,
+ const pa_dbus_interface_info *info,
+ void *userdata) {
+ struct object_entry *obj_entry;
+ struct interface_entry *iface_entry;
+ bool obj_entry_created = false;
+
+ pa_assert(p);
+ pa_assert(path);
+ pa_assert(info);
+ pa_assert(info->name);
+ pa_assert(info->method_handlers || info->n_method_handlers == 0);
+ pa_assert(info->property_handlers || info->n_property_handlers == 0);
+ pa_assert(info->get_all_properties_cb || info->n_property_handlers == 0);
+ pa_assert(info->signals || info->n_signals == 0);
+
+ if (!(obj_entry = pa_hashmap_get(p->objects, path))) {
+ obj_entry = pa_xnew(struct object_entry, 1);
+ obj_entry->path = pa_xstrdup(path);
+ obj_entry->interfaces = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+ obj_entry->introspection = NULL;
+
+ pa_hashmap_put(p->objects, obj_entry->path, obj_entry);
+ obj_entry_created = true;
+ }
+
+ if (pa_hashmap_get(obj_entry->interfaces, info->name) != NULL)
+ goto fail; /* The interface was already registered. */
+
+ iface_entry = pa_xnew(struct interface_entry, 1);
+ iface_entry->name = pa_xstrdup(info->name);
+ iface_entry->method_handlers = create_method_handlers(info);
+ iface_entry->method_signatures = extract_method_signatures(iface_entry->method_handlers);
+ iface_entry->property_handlers = create_property_handlers(info);
+ iface_entry->get_all_properties_cb = info->get_all_properties_cb;
+ iface_entry->signals = copy_signals(info);
+ iface_entry->n_signals = info->n_signals;
+ iface_entry->userdata = userdata;
+ pa_hashmap_put(obj_entry->interfaces, iface_entry->name, iface_entry);
+
+ update_introspection(obj_entry);
+
+ if (obj_entry_created)
+ register_object(p, obj_entry);
+
+ pa_log_debug("Interface %s added for object %s", iface_entry->name, obj_entry->path);
+
+ return 0;
+
+fail:
+ return -1;
+}
+
+static void unregister_object(pa_dbus_protocol *p, struct object_entry *obj_entry) {
+ struct connection_entry *conn_entry;
+ void *state = NULL;
+
+ pa_assert(p);
+ pa_assert(obj_entry);
+
+ PA_HASHMAP_FOREACH(conn_entry, p->connections, state)
+ pa_assert_se(dbus_connection_unregister_object_path(conn_entry->connection, obj_entry->path));
+}
+
+int pa_dbus_protocol_remove_interface(pa_dbus_protocol *p, const char* path, const char* interface) {
+ struct object_entry *obj_entry;
+ struct interface_entry *iface_entry;
+ unsigned i;
+
+ pa_assert(p);
+ pa_assert(path);
+ pa_assert(interface);
+
+ if (!(obj_entry = pa_hashmap_get(p->objects, path)))
+ return -1;
+
+ if (!(iface_entry = pa_hashmap_remove(obj_entry->interfaces, interface)))
+ return -1;
+
+ update_introspection(obj_entry);
+
+ pa_log_debug("Interface %s removed from object %s", iface_entry->name, obj_entry->path);
+
+ pa_xfree(iface_entry->name);
+ pa_hashmap_free(iface_entry->method_signatures);
+ pa_hashmap_free(iface_entry->method_handlers);
+ pa_hashmap_free(iface_entry->property_handlers);
+
+ for (i = 0; i < iface_entry->n_signals; ++i) {
+ unsigned j;
+
+ pa_xfree((char *) iface_entry->signals[i].name);
+
+ for (j = 0; j < iface_entry->signals[i].n_arguments; ++j) {
+ pa_xfree((char *) iface_entry->signals[i].arguments[j].name);
+ pa_xfree((char *) iface_entry->signals[i].arguments[j].type);
+ pa_assert(iface_entry->signals[i].arguments[j].direction == NULL);
+ }
+
+ pa_xfree((pa_dbus_arg_info *) iface_entry->signals[i].arguments);
+ }
+
+ pa_xfree(iface_entry->signals);
+ pa_xfree(iface_entry);
+
+ if (pa_hashmap_isempty(obj_entry->interfaces)) {
+ unregister_object(p, obj_entry);
+
+ pa_hashmap_remove(p->objects, path);
+ pa_xfree(obj_entry->path);
+ pa_hashmap_free(obj_entry->interfaces);
+ pa_xfree(obj_entry->introspection);
+ pa_xfree(obj_entry);
+ }
+
+ return 0;
+}
+
+static void register_all_objects(pa_dbus_protocol *p, DBusConnection *conn) {
+ struct object_entry *obj_entry;
+ void *state = NULL;
+
+ pa_assert(p);
+ pa_assert(conn);
+
+ PA_HASHMAP_FOREACH(obj_entry, p->objects, state)
+ pa_assert_se(dbus_connection_register_object_path(conn, obj_entry->path, &vtable, p));
+}
+
+static void signal_paths_entry_free(struct signal_paths_entry *e) {
+ pa_assert(e);
+
+ pa_xfree(e->signal);
+ pa_idxset_free(e->paths, pa_xfree);
+ pa_xfree(e);
+}
+
+int pa_dbus_protocol_register_connection(pa_dbus_protocol *p, DBusConnection *conn, pa_client *client) {
+ struct connection_entry *conn_entry;
+
+ pa_assert(p);
+ pa_assert(conn);
+ pa_assert(client);
+
+ if (pa_hashmap_get(p->connections, conn))
+ return -1; /* The connection was already registered. */
+
+ register_all_objects(p, conn);
+
+ conn_entry = pa_xnew(struct connection_entry, 1);
+ conn_entry->connection = dbus_connection_ref(conn);
+ conn_entry->client = client;
+ conn_entry->listening_for_all_signals = false;
+ conn_entry->all_signals_objects = pa_idxset_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+ conn_entry->listening_signals = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL,
+ (pa_free_cb_t) signal_paths_entry_free);
+
+ pa_hashmap_put(p->connections, conn, conn_entry);
+
+ return 0;
+}
+
+static void unregister_all_objects(pa_dbus_protocol *p, DBusConnection *conn) {
+ struct object_entry *obj_entry;
+ void *state = NULL;
+
+ pa_assert(p);
+ pa_assert(conn);
+
+ PA_HASHMAP_FOREACH(obj_entry, p->objects, state)
+ pa_assert_se(dbus_connection_unregister_object_path(conn, obj_entry->path));
+}
+
+static struct signal_paths_entry *signal_paths_entry_new(const char *signal_name) {
+ struct signal_paths_entry *e = NULL;
+
+ pa_assert(signal_name);
+
+ e = pa_xnew0(struct signal_paths_entry, 1);
+ e->signal = pa_xstrdup(signal_name);
+ e->paths = pa_idxset_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+
+ return e;
+}
+
+int pa_dbus_protocol_unregister_connection(pa_dbus_protocol *p, DBusConnection *conn) {
+ struct connection_entry *conn_entry = NULL;
+
+ pa_assert(p);
+ pa_assert(conn);
+
+ if (!(conn_entry = pa_hashmap_remove(p->connections, conn)))
+ return -1;
+
+ unregister_all_objects(p, conn);
+
+ dbus_connection_unref(conn_entry->connection);
+ pa_idxset_free(conn_entry->all_signals_objects, pa_xfree);
+ pa_hashmap_free(conn_entry->listening_signals);
+ pa_xfree(conn_entry);
+
+ return 0;
+}
+
+pa_client *pa_dbus_protocol_get_client(pa_dbus_protocol *p, DBusConnection *conn) {
+ struct connection_entry *conn_entry;
+
+ pa_assert(p);
+ pa_assert(conn);
+
+ if (!(conn_entry = pa_hashmap_get(p->connections, conn)))
+ return NULL;
+
+ return conn_entry->client;
+}
+
+void pa_dbus_protocol_add_signal_listener(
+ pa_dbus_protocol *p,
+ DBusConnection *conn,
+ const char *signal_name,
+ char **objects,
+ unsigned n_objects) {
+ struct connection_entry *conn_entry = NULL;
+ struct signal_paths_entry *signal_paths_entry = NULL;
+ unsigned i = 0;
+
+ pa_assert(p);
+ pa_assert(conn);
+ pa_assert(objects || n_objects == 0);
+
+ pa_assert_se((conn_entry = pa_hashmap_get(p->connections, conn)));
+
+ /* all_signals_objects will either be emptied or replaced with new objects,
+ * so we empty it here unconditionally. If listening_for_all_signals is
+ * currently false, the idxset is empty already so this does nothing. */
+ pa_idxset_remove_all(conn_entry->all_signals_objects, pa_xfree);
+
+ if (signal_name) {
+ conn_entry->listening_for_all_signals = false;
+
+ /* Replace the old signal paths entry for this signal with a new
+ * one. */
+ pa_hashmap_remove_and_free(conn_entry->listening_signals, signal_name);
+ signal_paths_entry = signal_paths_entry_new(signal_name);
+
+ for (i = 0; i < n_objects; ++i)
+ pa_idxset_put(signal_paths_entry->paths, pa_xstrdup(objects[i]), NULL);
+
+ pa_hashmap_put(conn_entry->listening_signals, signal_paths_entry->signal, signal_paths_entry);
+
+ } else {
+ conn_entry->listening_for_all_signals = true;
+
+ /* We're not interested in individual signals anymore, so let's empty
+ * listening_signals. */
+ pa_hashmap_remove_all(conn_entry->listening_signals);
+
+ for (i = 0; i < n_objects; ++i)
+ pa_idxset_put(conn_entry->all_signals_objects, pa_xstrdup(objects[i]), NULL);
+ }
+}
+
+void pa_dbus_protocol_remove_signal_listener(pa_dbus_protocol *p, DBusConnection *conn, const char *signal_name) {
+ struct connection_entry *conn_entry = NULL;
+ struct signal_paths_entry *signal_paths_entry = NULL;
+
+ pa_assert(p);
+ pa_assert(conn);
+
+ pa_assert_se((conn_entry = pa_hashmap_get(p->connections, conn)));
+
+ if (signal_name) {
+ if ((signal_paths_entry = pa_hashmap_remove(conn_entry->listening_signals, signal_name)))
+ signal_paths_entry_free(signal_paths_entry);
+
+ } else {
+ conn_entry->listening_for_all_signals = false;
+ pa_idxset_remove_all(conn_entry->all_signals_objects, pa_xfree);
+ pa_hashmap_remove_all(conn_entry->listening_signals);
+ }
+}
+
+void pa_dbus_protocol_send_signal(pa_dbus_protocol *p, DBusMessage *signal_msg) {
+ struct connection_entry *conn_entry;
+ struct signal_paths_entry *signal_paths_entry;
+ void *state = NULL;
+ DBusMessage *signal_copy;
+ char *signal_string;
+
+ pa_assert(p);
+ pa_assert(signal_msg);
+ pa_assert(dbus_message_get_type(signal_msg) == DBUS_MESSAGE_TYPE_SIGNAL);
+ pa_assert(dbus_message_get_path(signal_msg));
+ pa_assert(dbus_message_get_interface(signal_msg));
+ pa_assert(dbus_message_get_member(signal_msg));
+
+ signal_string = pa_sprintf_malloc("%s.%s", dbus_message_get_interface(signal_msg), dbus_message_get_member(signal_msg));
+
+ PA_HASHMAP_FOREACH(conn_entry, p->connections, state) {
+ if ((conn_entry->listening_for_all_signals /* Case 1: listening for all signals */
+ && (pa_idxset_get_by_data(conn_entry->all_signals_objects, dbus_message_get_path(signal_msg), NULL)
+ || pa_idxset_isempty(conn_entry->all_signals_objects)))
+
+ || (!conn_entry->listening_for_all_signals /* Case 2: not listening for all signals */
+ && (signal_paths_entry = pa_hashmap_get(conn_entry->listening_signals, signal_string))
+ && (pa_idxset_get_by_data(signal_paths_entry->paths, dbus_message_get_path(signal_msg), NULL)
+ || pa_idxset_isempty(signal_paths_entry->paths)))) {
+
+ pa_assert_se(signal_copy = dbus_message_copy(signal_msg));
+ pa_assert_se(dbus_connection_send(conn_entry->connection, signal_copy, NULL));
+ dbus_message_unref(signal_copy);
+ }
+ }
+
+ pa_xfree(signal_string);
+}
+
+const char **pa_dbus_protocol_get_extensions(pa_dbus_protocol *p, unsigned *n) {
+ const char **extensions;
+ const char *ext_name;
+ void *state = NULL;
+ unsigned i = 0;
+
+ pa_assert(p);
+ pa_assert(n);
+
+ *n = pa_idxset_size(p->extensions);
+
+ if (*n <= 0)
+ return NULL;
+
+ extensions = pa_xnew(const char *, *n);
+
+ while ((ext_name = pa_idxset_iterate(p->extensions, &state, NULL)))
+ extensions[i++] = ext_name;
+
+ return extensions;
+}
+
+int pa_dbus_protocol_register_extension(pa_dbus_protocol *p, const char *name) {
+ char *internal_name;
+
+ pa_assert(p);
+ pa_assert(name);
+
+ internal_name = pa_xstrdup(name);
+
+ if (pa_idxset_put(p->extensions, internal_name, NULL) < 0) {
+ pa_xfree(internal_name);
+ return -1;
+ }
+
+ pa_hook_fire(&p->hooks[PA_DBUS_PROTOCOL_HOOK_EXTENSION_REGISTERED], internal_name);
+
+ return 0;
+}
+
+int pa_dbus_protocol_unregister_extension(pa_dbus_protocol *p, const char *name) {
+ char *internal_name;
+
+ pa_assert(p);
+ pa_assert(name);
+
+ if (!(internal_name = pa_idxset_remove_by_data(p->extensions, name, NULL)))
+ return -1;
+
+ pa_hook_fire(&p->hooks[PA_DBUS_PROTOCOL_HOOK_EXTENSION_UNREGISTERED], internal_name);
+
+ pa_xfree(internal_name);
+
+ return 0;
+}
+
+pa_hook_slot *pa_dbus_protocol_hook_connect(
+ pa_dbus_protocol *p,
+ pa_dbus_protocol_hook_t hook,
+ pa_hook_priority_t prio,
+ pa_hook_cb_t cb,
+ void *data) {
+ pa_assert(p);
+ pa_assert(hook < PA_DBUS_PROTOCOL_HOOK_MAX);
+ pa_assert(cb);
+
+ return pa_hook_connect(&p->hooks[hook], prio, cb, data);
+}
diff --git a/src/pulsecore/protocol-dbus.h b/src/pulsecore/protocol-dbus.h
new file mode 100644
index 0000000..fdc1ae9
--- /dev/null
+++ b/src/pulsecore/protocol-dbus.h
@@ -0,0 +1,215 @@
+#ifndef fooprotocoldbushfoo
+#define fooprotocoldbushfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2009 Tanu Kaskinen
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <dbus/dbus.h>
+
+#include <pulsecore/core.h>
+#include <pulsecore/macro.h>
+
+#define PA_DBUS_DEFAULT_PORT 24883
+#define PA_DBUS_SOCKET_NAME "dbus-socket"
+
+#define PA_DBUS_SYSTEM_SOCKET_PATH PA_SYSTEM_RUNTIME_PATH PA_PATH_SEP PA_DBUS_SOCKET_NAME
+
+#define PA_DBUS_CORE_INTERFACE "org.PulseAudio.Core1"
+#define PA_DBUS_CORE_OBJECT_PATH "/org/pulseaudio/core1"
+
+#define PA_DBUS_ERROR_NO_SUCH_INTERFACE PA_DBUS_CORE_INTERFACE ".NoSuchInterfaceError"
+#define PA_DBUS_ERROR_NO_SUCH_PROPERTY PA_DBUS_CORE_INTERFACE ".NoSuchPropertyError"
+#define PA_DBUS_ERROR_NOT_FOUND PA_DBUS_CORE_INTERFACE ".NotFoundError"
+
+/* Returns the default address of the server type in the escaped form. For
+ * PA_SERVER_TYPE_NONE an empty string is returned. The caller frees the
+ * string. */
+char *pa_get_dbus_address_from_server_type(pa_server_type_t server_type);
+
+typedef struct pa_dbus_protocol pa_dbus_protocol;
+
+/* This function either creates a new pa_dbus_protocol object, or if one
+ * already exists, increases the reference count. */
+pa_dbus_protocol* pa_dbus_protocol_get(pa_core *c);
+
+pa_dbus_protocol* pa_dbus_protocol_ref(pa_dbus_protocol *p);
+void pa_dbus_protocol_unref(pa_dbus_protocol *p);
+
+/* Called when a received message needs handling. Completely ignoring the
+ * message isn't a good idea; if you can't handle the message, reply with an
+ * error.
+ *
+ * The message signature is already checked against the introspection data, so
+ * you don't have to do that yourself.
+ *
+ * All messages are method calls. */
+typedef void (*pa_dbus_receive_cb_t)(DBusConnection *conn, DBusMessage *msg, void *userdata);
+
+/* A specialized version of pa_dbus_receive_cb_t: the additional iterator
+ * argument points to the element inside the new value variant.
+ *
+ * The new value signature is checked against the introspection data, so you
+ * don't have to do that yourself. */
+typedef void (*pa_dbus_set_property_cb_t)(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata);
+
+typedef struct pa_dbus_arg_info {
+ const char *name;
+ const char *type;
+ const char *direction; /* NULL for signal arguments. */
+} pa_dbus_arg_info;
+
+typedef struct pa_dbus_signal_info {
+ const char *name;
+ const pa_dbus_arg_info *arguments; /* NULL, if the signal has no args. */
+ unsigned n_arguments;
+} pa_dbus_signal_info;
+
+typedef struct pa_dbus_method_handler {
+ const char *method_name;
+ const pa_dbus_arg_info *arguments; /* NULL, if the method has no args. */
+ unsigned n_arguments;
+ pa_dbus_receive_cb_t receive_cb;
+} pa_dbus_method_handler;
+
+typedef struct pa_dbus_property_handler {
+ const char *property_name;
+ const char *type;
+
+ /* The access mode for the property is determined by checking whether
+ * get_cb or set_cb is NULL. */
+ pa_dbus_receive_cb_t get_cb;
+ pa_dbus_set_property_cb_t set_cb;
+} pa_dbus_property_handler;
+
+typedef struct pa_dbus_interface_info {
+ const char* name;
+ const pa_dbus_method_handler *method_handlers; /* NULL, if the interface has no methods. */
+ unsigned n_method_handlers;
+ const pa_dbus_property_handler *property_handlers; /* NULL, if the interface has no properties. */
+ unsigned n_property_handlers;
+ const pa_dbus_receive_cb_t get_all_properties_cb; /* May be NULL, in which case GetAll returns an error. */
+ const pa_dbus_signal_info *signals; /* NULL, if the interface has no signals. */
+ unsigned n_signals;
+} pa_dbus_interface_info;
+
+/* The following functions may only be called from the main thread. */
+
+/* Registers the given interface to the given object path. It doesn't matter
+ * whether or not the object has already been registered; if it is, then its
+ * interface set is extended.
+ *
+ * Introspection requests are handled automatically.
+ *
+ * Userdata is passed to all the callbacks.
+ *
+ * Fails and returns a negative number if the object already has the interface
+ * registered. */
+int pa_dbus_protocol_add_interface(pa_dbus_protocol *p, const char *path, const pa_dbus_interface_info *info, void *userdata);
+
+/* Returns a negative number if the given object doesn't have the given
+ * interface registered. */
+int pa_dbus_protocol_remove_interface(pa_dbus_protocol *p, const char* path, const char* interface);
+
+/* Fails and returns a negative number if the connection is already
+ * registered. */
+int pa_dbus_protocol_register_connection(pa_dbus_protocol *p, DBusConnection *conn, pa_client *client);
+
+/* Returns a negative number if the connection isn't registered. */
+int pa_dbus_protocol_unregister_connection(pa_dbus_protocol *p, DBusConnection *conn);
+
+/* Returns NULL if the connection isn't registered. */
+pa_client *pa_dbus_protocol_get_client(pa_dbus_protocol *p, DBusConnection *conn);
+
+/* Enables signal receiving for the given connection. The connection must have
+ * been registered earlier. The signal string must contain both the signal
+ * interface and the signal name, concatenated using a period as the separator.
+ *
+ * If the signal argument is NULL, all signals will be sent to the connection,
+ * otherwise calling this function only adds the given signal to the list of
+ * signals that will be delivered to the connection.
+ *
+ * The objects argument is a list of object paths. If the list is not empty,
+ * only signals from the given objects are delivered. If this function is
+ * called multiple time for the same connection and signal, the latest call
+ * always replaces the previous object list. */
+void pa_dbus_protocol_add_signal_listener(
+ pa_dbus_protocol *p,
+ DBusConnection *conn,
+ const char *signal,
+ char **objects,
+ unsigned n_objects);
+
+/* Disables the delivery of the signal for the given connection. The connection
+ * must have been registered. If signal is NULL, all signals are disabled. If
+ * signal is non-NULL and _add_signal_listener() was previously called with
+ * NULL signal (causing all signals to be enabled), this function doesn't do
+ * anything. Also, if the signal wasn't enabled before, this function doesn't
+ * do anything in that case either. */
+void pa_dbus_protocol_remove_signal_listener(pa_dbus_protocol *p, DBusConnection *conn, const char *signal);
+
+/* Sends the given signal to all interested clients. By default no signals are
+ * sent - clients have to explicitly to request signals by calling
+ * .Core1.ListenForSignal. That method's handler then calls
+ * pa_dbus_protocol_add_signal_listener(). */
+void pa_dbus_protocol_send_signal(pa_dbus_protocol *p, DBusMessage *signal);
+
+/* Returns an array of extension identifier strings. The strings pointers point
+ * to the internal copies, so don't free the strings. The caller must free the
+ * array, however. Also, do not save the returned pointer or any of the string
+ * pointers, because the contained strings may be freed at any time. If you
+ * need to save the array, copy it. */
+const char **pa_dbus_protocol_get_extensions(pa_dbus_protocol *p, unsigned *n);
+
+/* Modules that want to provide a D-Bus interface for clients should register
+ * an identifier that the clients can use to check whether the additional
+ * functionality is available.
+ *
+ * This function registers the extension with the given name. It is recommended
+ * that the name follows the D-Bus interface naming convention, so that the
+ * names remain unique in case there will be at some point in the future
+ * extensions that aren't included with the main PulseAudio source tree. For
+ * in-tree extensions the convention is to use the org.PulseAudio.Ext
+ * namespace.
+ *
+ * It is suggested that the name contains a version number, and whenever the
+ * extension interface is modified in non-backwards compatible way, the version
+ * number is incremented.
+ *
+ * Fails and returns a negative number if the extension is already registered.
+ */
+int pa_dbus_protocol_register_extension(pa_dbus_protocol *p, const char *name);
+
+/* Returns a negative number if the extension isn't registered. */
+int pa_dbus_protocol_unregister_extension(pa_dbus_protocol *p, const char *name);
+
+/* All hooks have the pa_dbus_protocol object as hook data. */
+typedef enum pa_dbus_protocol_hook {
+ PA_DBUS_PROTOCOL_HOOK_EXTENSION_REGISTERED, /* Extension name as call data. */
+ PA_DBUS_PROTOCOL_HOOK_EXTENSION_UNREGISTERED, /* Extension name as call data. */
+ PA_DBUS_PROTOCOL_HOOK_MAX
+} pa_dbus_protocol_hook_t;
+
+pa_hook_slot *pa_dbus_protocol_hook_connect(
+ pa_dbus_protocol *p,
+ pa_dbus_protocol_hook_t hook,
+ pa_hook_priority_t prio,
+ pa_hook_cb_t cb,
+ void *data);
+
+#endif
diff --git a/src/pulsecore/protocol-esound.c b/src/pulsecore/protocol-esound.c
new file mode 100644
index 0000000..d54c7f8
--- /dev/null
+++ b/src/pulsecore/protocol-esound.c
@@ -0,0 +1,1734 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <pulse/rtclock.h>
+#include <pulse/sample.h>
+#include <pulse/timeval.h>
+#include <pulse/utf8.h>
+#include <pulse/xmalloc.h>
+#include <pulse/proplist.h>
+
+#include <pulsecore/esound.h>
+#include <pulsecore/memblock.h>
+#include <pulsecore/client.h>
+#include <pulsecore/sink-input.h>
+#include <pulsecore/sink.h>
+#include <pulsecore/source-output.h>
+#include <pulsecore/source.h>
+#include <pulsecore/core-scache.h>
+#include <pulsecore/sample-util.h>
+#include <pulsecore/namereg.h>
+#include <pulsecore/log.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/core-error.h>
+#include <pulsecore/ipacl.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/thread-mq.h>
+#include <pulsecore/shared.h>
+#include <pulsecore/endianmacros.h>
+
+#include "protocol-esound.h"
+
+/* Don't accept more connection than this */
+#define MAX_CONNECTIONS 64
+
+/* Kick a client if it doesn't authenticate within this time */
+#define AUTH_TIMEOUT (5*PA_USEC_PER_SEC)
+
+#define DEFAULT_COOKIE_FILE ".esd_auth"
+
+#define PLAYBACK_BUFFER_SECONDS (.25)
+#define PLAYBACK_BUFFER_FRAGMENTS (10)
+#define RECORD_BUFFER_SECONDS (5)
+
+#define MAX_CACHE_SAMPLE_SIZE (2048000)
+
+#define DEFAULT_SINK_LATENCY (150*PA_USEC_PER_MSEC)
+#define DEFAULT_SOURCE_LATENCY (150*PA_USEC_PER_MSEC)
+
+#define SCACHE_PREFIX "esound."
+
+/* This is heavily based on esound's code */
+
+typedef struct connection {
+ pa_msgobject parent;
+
+ uint32_t index;
+ bool dead;
+ pa_esound_protocol *protocol;
+ pa_esound_options *options;
+ pa_iochannel *io;
+ pa_client *client;
+ bool authorized, swap_byte_order;
+ void *write_data;
+ size_t write_data_alloc, write_data_index, write_data_length;
+ void *read_data;
+ size_t read_data_alloc, read_data_length;
+ esd_proto_t request;
+ esd_client_state_t state;
+ pa_sink_input *sink_input;
+ pa_source_output *source_output;
+ pa_memblockq *input_memblockq, *output_memblockq;
+ pa_defer_event *defer_event;
+
+ char *original_name;
+
+ struct {
+ pa_memblock *current_memblock;
+ size_t memblock_index;
+ pa_atomic_t missing;
+ bool underrun;
+ } playback;
+
+ struct {
+ pa_memchunk memchunk;
+ char *name;
+ pa_sample_spec sample_spec;
+ } scache;
+
+ pa_time_event *auth_timeout_event;
+} connection;
+
+PA_DEFINE_PRIVATE_CLASS(connection, pa_msgobject);
+#define CONNECTION(o) (connection_cast(o))
+
+struct pa_esound_protocol {
+ PA_REFCNT_DECLARE;
+
+ pa_core *core;
+ pa_idxset *connections;
+ unsigned n_player;
+};
+
+enum {
+ SINK_INPUT_MESSAGE_POST_DATA = PA_SINK_INPUT_MESSAGE_MAX, /* data from main loop to sink input */
+ SINK_INPUT_MESSAGE_DISABLE_PREBUF
+};
+
+enum {
+ CONNECTION_MESSAGE_REQUEST_DATA,
+ CONNECTION_MESSAGE_POST_DATA,
+ CONNECTION_MESSAGE_UNLINK_CONNECTION
+};
+
+typedef struct proto_handler {
+ size_t data_length;
+ int (*proc)(connection *c, esd_proto_t request, const void *data, size_t length);
+ const char *description;
+} esd_proto_handler_info_t;
+
+static int sink_input_pop_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk);
+static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes);
+static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes);
+static void sink_input_kill_cb(pa_sink_input *i);
+static int sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk);
+static pa_usec_t source_output_get_latency_cb(pa_source_output *o);
+
+static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk);
+static void source_output_kill_cb(pa_source_output *o);
+
+static int esd_proto_connect(connection *c, esd_proto_t request, const void *data, size_t length);
+static int esd_proto_stream_play(connection *c, esd_proto_t request, const void *data, size_t length);
+static int esd_proto_stream_record(connection *c, esd_proto_t request, const void *data, size_t length);
+static int esd_proto_get_latency(connection *c, esd_proto_t request, const void *data, size_t length);
+static int esd_proto_server_info(connection *c, esd_proto_t request, const void *data, size_t length);
+static int esd_proto_all_info(connection *c, esd_proto_t request, const void *data, size_t length);
+static int esd_proto_stream_pan(connection *c, esd_proto_t request, const void *data, size_t length);
+static int esd_proto_sample_pan(connection *c, esd_proto_t request, const void *data, size_t length);
+static int esd_proto_sample_cache(connection *c, esd_proto_t request, const void *data, size_t length);
+static int esd_proto_sample_free_or_play(connection *c, esd_proto_t request, const void *data, size_t length);
+static int esd_proto_sample_get_id(connection *c, esd_proto_t request, const void *data, size_t length);
+static int esd_proto_standby_or_resume(connection *c, esd_proto_t request, const void *data, size_t length);
+static int esd_proto_standby_mode(connection *c, esd_proto_t request, const void *data, size_t length);
+
+/* the big map of protocol handler info */
+static struct proto_handler proto_map[ESD_PROTO_MAX] = {
+ { ESD_KEY_LEN + sizeof(int), esd_proto_connect, "connect" },
+ { ESD_KEY_LEN + sizeof(int), NULL, "lock" },
+ { ESD_KEY_LEN + sizeof(int), NULL, "unlock" },
+
+ { ESD_NAME_MAX + 2 * sizeof(int), esd_proto_stream_play, "stream play" },
+ { ESD_NAME_MAX + 2 * sizeof(int), esd_proto_stream_record, "stream rec" },
+ { ESD_NAME_MAX + 2 * sizeof(int), esd_proto_stream_record, "stream mon" },
+
+ { ESD_NAME_MAX + 3 * sizeof(int), esd_proto_sample_cache, "sample cache" }, /* 6 */
+ { sizeof(int), esd_proto_sample_free_or_play, "sample free" },
+ { sizeof(int), esd_proto_sample_free_or_play, "sample play" }, /* 8 */
+ { sizeof(int), NULL, "sample loop" },
+ { sizeof(int), NULL, "sample stop" },
+ { (size_t) -1, NULL, "TODO: sample kill" },
+
+ { ESD_KEY_LEN + sizeof(int), esd_proto_standby_or_resume, "standby" },
+ { ESD_KEY_LEN + sizeof(int), esd_proto_standby_or_resume, "resume" }, /* 13 */
+
+ { ESD_NAME_MAX, esd_proto_sample_get_id, "sample getid" }, /* 14 */
+ { ESD_NAME_MAX + 2 * sizeof(int), NULL, "stream filter" },
+
+ { sizeof(int), esd_proto_server_info, "server info" },
+ { sizeof(int), esd_proto_all_info, "all info" },
+ { (size_t) -1, NULL, "TODO: subscribe" },
+ { (size_t) -1, NULL, "TODO: unsubscribe" },
+
+ { 3 * sizeof(int), esd_proto_stream_pan, "stream pan"},
+ { 3 * sizeof(int), esd_proto_sample_pan, "sample pan" },
+
+ { sizeof(int), esd_proto_standby_mode, "standby mode" },
+ { 0, esd_proto_get_latency, "get latency" }
+};
+
+static void connection_unlink(connection *c) {
+ pa_assert(c);
+
+ if (!c->protocol)
+ return;
+
+ if (c->options) {
+ pa_esound_options_unref(c->options);
+ c->options = NULL;
+ }
+
+ if (c->sink_input) {
+ pa_sink_input_unlink(c->sink_input);
+ pa_sink_input_unref(c->sink_input);
+ c->sink_input = NULL;
+ }
+
+ if (c->source_output) {
+ pa_source_output_unlink(c->source_output);
+ pa_source_output_unref(c->source_output);
+ c->source_output = NULL;
+ }
+
+ if (c->client) {
+ pa_client_free(c->client);
+ c->client = NULL;
+ }
+
+ if (c->state == ESD_STREAMING_DATA)
+ c->protocol->n_player--;
+
+ if (c->io) {
+ pa_iochannel_free(c->io);
+ c->io = NULL;
+ }
+
+ if (c->defer_event) {
+ c->protocol->core->mainloop->defer_free(c->defer_event);
+ c->defer_event = NULL;
+ }
+
+ if (c->auth_timeout_event) {
+ c->protocol->core->mainloop->time_free(c->auth_timeout_event);
+ c->auth_timeout_event = NULL;
+ }
+
+ pa_assert_se(pa_idxset_remove_by_data(c->protocol->connections, c, NULL) == c);
+ c->protocol = NULL;
+ connection_unref(c);
+}
+
+static void connection_free(pa_object *obj) {
+ connection *c = CONNECTION(obj);
+ pa_assert(c);
+
+ if (c->input_memblockq)
+ pa_memblockq_free(c->input_memblockq);
+ if (c->output_memblockq)
+ pa_memblockq_free(c->output_memblockq);
+
+ if (c->playback.current_memblock)
+ pa_memblock_unref(c->playback.current_memblock);
+
+ pa_xfree(c->read_data);
+ pa_xfree(c->write_data);
+
+ if (c->scache.memchunk.memblock)
+ pa_memblock_unref(c->scache.memchunk.memblock);
+ pa_xfree(c->scache.name);
+
+ pa_xfree(c->original_name);
+ pa_xfree(c);
+}
+
+static void connection_write_prepare(connection *c, size_t length) {
+ size_t t;
+ pa_assert(c);
+
+ t = c->write_data_length+length;
+
+ if (c->write_data_alloc < t)
+ c->write_data = pa_xrealloc(c->write_data, c->write_data_alloc = t);
+
+ pa_assert(c->write_data);
+}
+
+static void connection_write(connection *c, const void *data, size_t length) {
+ size_t i;
+ pa_assert(c);
+
+ c->protocol->core->mainloop->defer_enable(c->defer_event, 1);
+
+ connection_write_prepare(c, length);
+
+ pa_assert(c->write_data);
+
+ i = c->write_data_length;
+ c->write_data_length += length;
+
+ memcpy((uint8_t*) c->write_data + i, data, length);
+}
+
+static void format_esd2native(int format, bool swap_bytes, pa_sample_spec *ss) {
+ pa_assert(ss);
+
+ ss->channels = (uint8_t) (((format & ESD_MASK_CHAN) == ESD_STEREO) ? 2 : 1);
+ if ((format & ESD_MASK_BITS) == ESD_BITS16)
+ ss->format = swap_bytes ? PA_SAMPLE_S16RE : PA_SAMPLE_S16NE;
+ else
+ ss->format = PA_SAMPLE_U8;
+}
+
+static int format_native2esd(pa_sample_spec *ss) {
+ int format = 0;
+
+ format = (ss->format == PA_SAMPLE_U8) ? ESD_BITS8 : ESD_BITS16;
+ format |= (ss->channels >= 2) ? ESD_STEREO : ESD_MONO;
+
+ return format;
+}
+
+#define CHECK_VALIDITY(expression, ...) do { \
+ if (PA_UNLIKELY(!(expression))) { \
+ pa_log_warn(__FILE__ ": " __VA_ARGS__); \
+ return -1; \
+ } \
+ } while(0);
+
+/*** esound commands ***/
+
+static int esd_proto_connect(connection *c, esd_proto_t request, const void *data, size_t length) {
+ uint32_t ekey;
+ int ok;
+
+ connection_assert_ref(c);
+ pa_assert(data);
+ pa_assert(length == (ESD_KEY_LEN + sizeof(uint32_t)));
+
+ if (!c->authorized && c->options->auth_cookie) {
+ const uint8_t*key;
+
+ if ((key = pa_auth_cookie_read(c->options->auth_cookie, ESD_KEY_LEN)))
+ if (memcmp(data, key, ESD_KEY_LEN) == 0)
+ c->authorized = true;
+ }
+
+ if (!c->authorized) {
+ pa_log("Kicked client with invalid authentication key.");
+ return -1;
+ }
+
+ if (c->auth_timeout_event) {
+ c->protocol->core->mainloop->time_free(c->auth_timeout_event);
+ c->auth_timeout_event = NULL;
+ }
+
+ data = (const char*)data + ESD_KEY_LEN;
+
+ memcpy(&ekey, data, sizeof(uint32_t));
+ if (ekey == ESD_ENDIAN_KEY)
+ c->swap_byte_order = false;
+ else if (ekey == ESD_SWAP_ENDIAN_KEY)
+ c->swap_byte_order = true;
+ else {
+ pa_log_warn("Client sent invalid endian key");
+ return -1;
+ }
+
+ pa_proplist_sets(c->client->proplist, "esound.byte_order", c->swap_byte_order ? "reverse" : "native");
+
+ ok = 1;
+ connection_write(c, &ok, sizeof(int));
+ return 0;
+}
+
+static int esd_proto_stream_play(connection *c, esd_proto_t request, const void *data, size_t length) {
+ char name[ESD_NAME_MAX], *utf8_name;
+ int32_t format, rate;
+ pa_sample_spec ss;
+ size_t l;
+ pa_sink *sink = NULL;
+ pa_sink_input_new_data sdata;
+ pa_memchunk silence;
+
+ connection_assert_ref(c);
+ pa_assert(data);
+ pa_assert(length == (sizeof(int32_t)*2+ESD_NAME_MAX));
+
+ memcpy(&format, data, sizeof(int32_t));
+ format = PA_MAYBE_INT32_SWAP(c->swap_byte_order, format);
+ data = (const char*) data + sizeof(int32_t);
+
+ memcpy(&rate, data, sizeof(int32_t));
+ rate = PA_MAYBE_INT32_SWAP(c->swap_byte_order, rate);
+ data = (const char*) data + sizeof(int32_t);
+
+ ss.rate = (uint32_t) rate;
+ format_esd2native(format, c->swap_byte_order, &ss);
+
+ CHECK_VALIDITY(pa_sample_spec_valid(&ss), "Invalid sample specification");
+
+ if (c->options->default_sink) {
+ sink = pa_namereg_get(c->protocol->core, c->options->default_sink, PA_NAMEREG_SINK);
+ CHECK_VALIDITY(sink, "No such sink: %s", pa_strnull(c->options->default_sink));
+ }
+
+ pa_strlcpy(name, data, sizeof(name));
+
+ utf8_name = pa_utf8_filter(name);
+ pa_client_set_name(c->client, utf8_name);
+ pa_xfree(utf8_name);
+
+ c->original_name = pa_xstrdup(name);
+
+ pa_assert(!c->sink_input && !c->input_memblockq);
+
+ pa_sink_input_new_data_init(&sdata);
+ sdata.driver = __FILE__;
+ sdata.module = c->options->module;
+ sdata.client = c->client;
+ if (sink)
+ pa_sink_input_new_data_set_sink(&sdata, sink, false, true);
+ pa_sink_input_new_data_set_sample_spec(&sdata, &ss);
+
+ pa_sink_input_new(&c->sink_input, c->protocol->core, &sdata);
+ pa_sink_input_new_data_done(&sdata);
+
+ CHECK_VALIDITY(c->sink_input, "Failed to create sink input.");
+
+ l = (size_t) ((double) pa_bytes_per_second(&ss)*PLAYBACK_BUFFER_SECONDS);
+ pa_sink_input_get_silence(c->sink_input, &silence);
+ c->input_memblockq = pa_memblockq_new(
+ "esound protocol connection input_memblockq",
+ 0,
+ l,
+ l,
+ &ss,
+ (size_t) -1,
+ l/PLAYBACK_BUFFER_FRAGMENTS,
+ 0,
+ &silence);
+ pa_memblock_unref(silence.memblock);
+ pa_iochannel_socket_set_rcvbuf(c->io, l);
+
+ c->sink_input->parent.process_msg = sink_input_process_msg;
+ c->sink_input->pop = sink_input_pop_cb;
+ c->sink_input->process_rewind = sink_input_process_rewind_cb;
+ c->sink_input->update_max_rewind = sink_input_update_max_rewind_cb;
+ c->sink_input->kill = sink_input_kill_cb;
+ c->sink_input->userdata = c;
+
+ pa_sink_input_set_requested_latency(c->sink_input, DEFAULT_SINK_LATENCY);
+
+ c->state = ESD_STREAMING_DATA;
+
+ c->protocol->n_player++;
+
+ pa_atomic_store(&c->playback.missing, (int) pa_memblockq_pop_missing(c->input_memblockq));
+
+ pa_sink_input_put(c->sink_input);
+
+ return 0;
+}
+
+static int esd_proto_stream_record(connection *c, esd_proto_t request, const void *data, size_t length) {
+ char name[ESD_NAME_MAX], *utf8_name;
+ int32_t format, rate;
+ pa_source *source = NULL;
+ pa_sample_spec ss;
+ size_t l;
+ pa_source_output_new_data sdata;
+
+ connection_assert_ref(c);
+ pa_assert(data);
+ pa_assert(length == (sizeof(int32_t)*2+ESD_NAME_MAX));
+
+ memcpy(&format, data, sizeof(int32_t));
+ format = PA_MAYBE_INT32_SWAP(c->swap_byte_order, format);
+ data = (const char*) data + sizeof(int32_t);
+
+ memcpy(&rate, data, sizeof(int32_t));
+ rate = PA_MAYBE_INT32_SWAP(c->swap_byte_order, rate);
+ data = (const char*) data + sizeof(int32_t);
+
+ ss.rate = (uint32_t) rate;
+ format_esd2native(format, c->swap_byte_order, &ss);
+
+ CHECK_VALIDITY(pa_sample_spec_valid(&ss), "Invalid sample specification.");
+
+ if (request == ESD_PROTO_STREAM_MON) {
+ pa_sink* sink;
+
+ sink = pa_namereg_get(c->protocol->core, c->options->default_sink, PA_NAMEREG_SINK);
+ CHECK_VALIDITY(sink, "No such sink: %s", pa_strnull(c->options->default_sink));
+
+ source = sink->monitor_source;
+ CHECK_VALIDITY(source, "No such source.");
+ } else {
+ pa_assert(request == ESD_PROTO_STREAM_REC);
+
+ if (c->options->default_source) {
+ source = pa_namereg_get(c->protocol->core, c->options->default_source, PA_NAMEREG_SOURCE);
+ CHECK_VALIDITY(source, "No such source: %s", pa_strnull(c->options->default_source));
+ }
+ }
+
+ pa_strlcpy(name, data, sizeof(name));
+
+ utf8_name = pa_utf8_filter(name);
+ pa_client_set_name(c->client, utf8_name);
+ pa_xfree(utf8_name);
+
+ c->original_name = pa_xstrdup(name);
+
+ pa_assert(!c->output_memblockq && !c->source_output);
+
+ pa_source_output_new_data_init(&sdata);
+ sdata.driver = __FILE__;
+ sdata.module = c->options->module;
+ sdata.client = c->client;
+ if (source)
+ pa_source_output_new_data_set_source(&sdata, source, false, true);
+ pa_source_output_new_data_set_sample_spec(&sdata, &ss);
+
+ pa_source_output_new(&c->source_output, c->protocol->core, &sdata);
+ pa_source_output_new_data_done(&sdata);
+
+ CHECK_VALIDITY(c->source_output, "Failed to create source output.");
+
+ l = (size_t) (pa_bytes_per_second(&ss)*RECORD_BUFFER_SECONDS);
+ c->output_memblockq = pa_memblockq_new(
+ "esound protocol connection output_memblockq",
+ 0,
+ l,
+ l,
+ &ss,
+ 1,
+ 0,
+ 0,
+ NULL);
+ pa_iochannel_socket_set_sndbuf(c->io, l);
+
+ c->source_output->push = source_output_push_cb;
+ c->source_output->kill = source_output_kill_cb;
+ c->source_output->get_latency = source_output_get_latency_cb;
+ c->source_output->userdata = c;
+
+ pa_source_output_set_requested_latency(c->source_output, DEFAULT_SOURCE_LATENCY);
+
+ c->state = ESD_STREAMING_DATA;
+
+ c->protocol->n_player++;
+
+ pa_source_output_put(c->source_output);
+
+ return 0;
+}
+
+static int esd_proto_get_latency(connection *c, esd_proto_t request, const void *data, size_t length) {
+ pa_sink *sink;
+ int32_t latency;
+
+ connection_assert_ref(c);
+ pa_assert(!data);
+ pa_assert(length == 0);
+
+ if (!(sink = pa_namereg_get(c->protocol->core, c->options->default_sink, PA_NAMEREG_SINK)))
+ latency = 0;
+ else {
+ double usec = (double) pa_sink_get_requested_latency(sink);
+ latency = (int) ((usec*44100)/1000000);
+ }
+
+ latency = PA_MAYBE_INT32_SWAP(c->swap_byte_order, latency);
+ connection_write(c, &latency, sizeof(int32_t));
+
+ return 0;
+}
+
+static int esd_proto_server_info(connection *c, esd_proto_t request, const void *data, size_t length) {
+ int32_t rate = 44100, format = ESD_STEREO|ESD_BITS16;
+ int32_t response;
+ pa_sink *sink;
+
+ connection_assert_ref(c);
+ pa_assert(data);
+ pa_assert(length == sizeof(int32_t));
+
+ if ((sink = pa_namereg_get(c->protocol->core, c->options->default_sink, PA_NAMEREG_SINK))) {
+ rate = (int32_t) sink->sample_spec.rate;
+ format = format_native2esd(&sink->sample_spec);
+ }
+
+ connection_write_prepare(c, sizeof(int32_t) * 3);
+
+ response = 0;
+ connection_write(c, &response, sizeof(int32_t));
+ rate = PA_MAYBE_INT32_SWAP(c->swap_byte_order, rate);
+ connection_write(c, &rate, sizeof(int32_t));
+ format = PA_MAYBE_INT32_SWAP(c->swap_byte_order, format);
+ connection_write(c, &format, sizeof(int32_t));
+
+ return 0;
+}
+
+static int esd_proto_all_info(connection *c, esd_proto_t request, const void *data, size_t length) {
+ size_t t, k, s;
+ connection *conn;
+ uint32_t idx = PA_IDXSET_INVALID;
+ unsigned nsamples;
+ char terminator[sizeof(int32_t)*6+ESD_NAME_MAX];
+
+ connection_assert_ref(c);
+ pa_assert(data);
+ pa_assert(length == sizeof(int32_t));
+
+ if (esd_proto_server_info(c, request, data, length) < 0)
+ return -1;
+
+ k = sizeof(int32_t)*5+ESD_NAME_MAX;
+ s = sizeof(int32_t)*6+ESD_NAME_MAX;
+ nsamples = pa_idxset_size(c->protocol->core->scache);
+ t = s*(nsamples+1) + k*(c->protocol->n_player+1);
+
+ connection_write_prepare(c, t);
+
+ memset(terminator, 0, sizeof(terminator));
+
+ PA_IDXSET_FOREACH(conn, c->protocol->connections, idx) {
+ int32_t id, format = ESD_BITS16 | ESD_STEREO, rate = 44100, lvolume = ESD_VOLUME_BASE, rvolume = ESD_VOLUME_BASE;
+ char name[ESD_NAME_MAX];
+
+ if (conn->state != ESD_STREAMING_DATA)
+ continue;
+
+ pa_assert(t >= k*2+s);
+
+ if (conn->sink_input) {
+ pa_cvolume volume;
+ pa_sink_input_get_volume(conn->sink_input, &volume, true);
+ rate = (int32_t) conn->sink_input->sample_spec.rate;
+ lvolume = (int32_t) ((volume.values[0]*ESD_VOLUME_BASE)/PA_VOLUME_NORM);
+ rvolume = (int32_t) ((volume.values[volume.channels == 2 ? 1 : 0]*ESD_VOLUME_BASE)/PA_VOLUME_NORM);
+ format = format_native2esd(&conn->sink_input->sample_spec);
+ }
+
+ /* id */
+ id = PA_MAYBE_INT32_SWAP(c->swap_byte_order, (int32_t) (conn->index+1));
+ connection_write(c, &id, sizeof(int32_t));
+
+ /* name */
+ memset(name, 0, ESD_NAME_MAX); /* don't leak old data */
+ if (conn->original_name)
+ strncpy(name, conn->original_name, ESD_NAME_MAX);
+ else if (conn->client && pa_proplist_gets(conn->client->proplist, PA_PROP_APPLICATION_NAME))
+ strncpy(name, pa_proplist_gets(conn->client->proplist, PA_PROP_APPLICATION_NAME), ESD_NAME_MAX);
+ connection_write(c, name, ESD_NAME_MAX);
+
+ /* rate */
+ rate = PA_MAYBE_INT32_SWAP(c->swap_byte_order, rate);
+ connection_write(c, &rate, sizeof(int32_t));
+
+ /* left */
+ lvolume = PA_MAYBE_INT32_SWAP(c->swap_byte_order, lvolume);
+ connection_write(c, &lvolume, sizeof(int32_t));
+
+ /*right*/
+ rvolume = PA_MAYBE_INT32_SWAP(c->swap_byte_order, rvolume);
+ connection_write(c, &rvolume, sizeof(int32_t));
+
+ /*format*/
+ format = PA_MAYBE_INT32_SWAP(c->swap_byte_order, format);
+ connection_write(c, &format, sizeof(int32_t));
+
+ t -= k;
+ }
+
+ pa_assert(t == s*(nsamples+1)+k);
+ t -= k;
+
+ connection_write(c, terminator, k);
+
+ if (nsamples) {
+ pa_scache_entry *ce;
+
+ idx = PA_IDXSET_INVALID;
+
+ PA_IDXSET_FOREACH(ce, c->protocol->core->scache, idx) {
+ int32_t id, rate, lvolume, rvolume, format, len;
+ char name[ESD_NAME_MAX];
+ pa_channel_map stereo = { .channels = 2, .map = { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT } };
+ pa_cvolume volume;
+ pa_sample_spec ss;
+
+ pa_assert(t >= s*2);
+
+ if (ce->volume_is_set) {
+ volume = ce->volume;
+ pa_cvolume_remap(&volume, &ce->channel_map, &stereo);
+ } else
+ pa_cvolume_reset(&volume, 2);
+
+ if (ce->memchunk.memblock)
+ ss = ce->sample_spec;
+ else {
+ ss.format = PA_SAMPLE_S16NE;
+ ss.rate = 44100;
+ ss.channels = 2;
+ }
+
+ /* id */
+ id = PA_MAYBE_INT32_SWAP(c->swap_byte_order, (int) (ce->index+1));
+ connection_write(c, &id, sizeof(int32_t));
+
+ /* name */
+ memset(name, 0, ESD_NAME_MAX); /* don't leak old data */
+ if (strncmp(ce->name, SCACHE_PREFIX, sizeof(SCACHE_PREFIX)-1) == 0)
+ strncpy(name, ce->name+sizeof(SCACHE_PREFIX)-1, ESD_NAME_MAX);
+ else
+ pa_snprintf(name, ESD_NAME_MAX, "native.%s", ce->name);
+ connection_write(c, name, ESD_NAME_MAX);
+
+ /* rate */
+ rate = PA_MAYBE_INT32_SWAP(c->swap_byte_order, (int32_t) ss.rate);
+ connection_write(c, &rate, sizeof(int32_t));
+
+ /* left */
+ lvolume = PA_MAYBE_INT32_SWAP(c->swap_byte_order, (int32_t) ((volume.values[0]*ESD_VOLUME_BASE)/PA_VOLUME_NORM));
+ connection_write(c, &lvolume, sizeof(int32_t));
+
+ /*right*/
+ rvolume = PA_MAYBE_INT32_SWAP(c->swap_byte_order, (int32_t) ((volume.values[1]*ESD_VOLUME_BASE)/PA_VOLUME_NORM));
+ connection_write(c, &rvolume, sizeof(int32_t));
+
+ /*format*/
+ format = PA_MAYBE_INT32_SWAP(c->swap_byte_order, format_native2esd(&ss));
+ connection_write(c, &format, sizeof(int32_t));
+
+ /*length*/
+ len = PA_MAYBE_INT32_SWAP(c->swap_byte_order, (int) ce->memchunk.length);
+ connection_write(c, &len, sizeof(int32_t));
+
+ t -= s;
+ }
+ }
+
+ pa_assert(t == s);
+
+ connection_write(c, terminator, s);
+
+ return 0;
+}
+
+static int esd_proto_stream_pan(connection *c, esd_proto_t request, const void *data, size_t length) {
+ int32_t ok;
+ uint32_t idx, lvolume, rvolume;
+ connection *conn;
+
+ connection_assert_ref(c);
+ pa_assert(data);
+ pa_assert(length == sizeof(int32_t)*3);
+
+ memcpy(&idx, data, sizeof(uint32_t));
+ idx = PA_MAYBE_UINT32_SWAP(c->swap_byte_order, idx) - 1;
+ data = (const char*)data + sizeof(uint32_t);
+
+ memcpy(&lvolume, data, sizeof(uint32_t));
+ lvolume = PA_MAYBE_UINT32_SWAP(c->swap_byte_order, lvolume);
+ data = (const char*)data + sizeof(uint32_t);
+
+ memcpy(&rvolume, data, sizeof(uint32_t));
+ rvolume = PA_MAYBE_UINT32_SWAP(c->swap_byte_order, rvolume);
+
+ if ((conn = pa_idxset_get_by_index(c->protocol->connections, idx)) && conn->sink_input) {
+ pa_cvolume volume;
+ volume.values[0] = (lvolume*PA_VOLUME_NORM)/ESD_VOLUME_BASE;
+ volume.values[1] = (rvolume*PA_VOLUME_NORM)/ESD_VOLUME_BASE;
+ volume.channels = conn->sink_input->sample_spec.channels;
+
+ pa_sink_input_set_volume(conn->sink_input, &volume, true, true);
+ ok = 1;
+ } else
+ ok = 0;
+
+ connection_write(c, &ok, sizeof(int32_t));
+
+ return 0;
+}
+
+static int esd_proto_sample_pan(connection *c, esd_proto_t request, const void *data, size_t length) {
+ int32_t ok = 0;
+ uint32_t idx, lvolume, rvolume;
+ pa_cvolume volume;
+ pa_scache_entry *ce;
+
+ connection_assert_ref(c);
+ pa_assert(data);
+ pa_assert(length == sizeof(int32_t)*3);
+
+ memcpy(&idx, data, sizeof(uint32_t));
+ idx = PA_MAYBE_UINT32_SWAP(c->swap_byte_order, idx) - 1;
+ data = (const char*)data + sizeof(uint32_t);
+
+ memcpy(&lvolume, data, sizeof(uint32_t));
+ lvolume = PA_MAYBE_UINT32_SWAP(c->swap_byte_order, lvolume);
+ data = (const char*)data + sizeof(uint32_t);
+
+ memcpy(&rvolume, data, sizeof(uint32_t));
+ rvolume = PA_MAYBE_UINT32_SWAP(c->swap_byte_order, rvolume);
+
+ volume.values[0] = (lvolume*PA_VOLUME_NORM)/ESD_VOLUME_BASE;
+ volume.values[1] = (rvolume*PA_VOLUME_NORM)/ESD_VOLUME_BASE;
+ volume.channels = 2;
+
+ if ((ce = pa_idxset_get_by_index(c->protocol->core->scache, idx))) {
+ pa_channel_map stereo = { .channels = 2, .map = { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT } };
+
+ pa_cvolume_remap(&volume, &stereo, &ce->channel_map);
+ ce->volume = volume;
+ ce->volume_is_set = true;
+ ok = 1;
+ }
+
+ connection_write(c, &ok, sizeof(int32_t));
+
+ return 0;
+}
+
+static int esd_proto_sample_cache(connection *c, esd_proto_t request, const void *data, size_t length) {
+ pa_sample_spec ss;
+ int32_t format, rate, sc_length;
+ uint32_t idx;
+ char name[ESD_NAME_MAX+sizeof(SCACHE_PREFIX)-1];
+
+ connection_assert_ref(c);
+ pa_assert(data);
+ pa_assert(length == (ESD_NAME_MAX+3*sizeof(int32_t)));
+
+ memcpy(&format, data, sizeof(int32_t));
+ format = PA_MAYBE_INT32_SWAP(c->swap_byte_order, format);
+ data = (const char*)data + sizeof(int32_t);
+
+ memcpy(&rate, data, sizeof(int32_t));
+ rate = PA_MAYBE_INT32_SWAP(c->swap_byte_order, rate);
+ data = (const char*)data + sizeof(int32_t);
+
+ ss.rate = (uint32_t) rate;
+ format_esd2native(format, c->swap_byte_order, &ss);
+
+ CHECK_VALIDITY(pa_sample_spec_valid(&ss), "Invalid sample specification.");
+
+ memcpy(&sc_length, data, sizeof(int32_t));
+ sc_length = PA_MAYBE_INT32_SWAP(c->swap_byte_order, sc_length);
+ data = (const char*)data + sizeof(int32_t);
+
+ CHECK_VALIDITY(sc_length <= MAX_CACHE_SAMPLE_SIZE, "Sample too large (%d bytes).", (int)sc_length);
+
+ strcpy(name, SCACHE_PREFIX);
+ pa_strlcpy(name+sizeof(SCACHE_PREFIX)-1, data, ESD_NAME_MAX);
+
+ CHECK_VALIDITY(pa_utf8_valid(name), "Invalid UTF8 in sample name.");
+
+ pa_assert(!c->scache.memchunk.memblock);
+ c->scache.memchunk.memblock = pa_memblock_new(c->protocol->core->mempool, (size_t) sc_length);
+ c->scache.memchunk.index = 0;
+ c->scache.memchunk.length = (size_t) sc_length;
+ c->scache.sample_spec = ss;
+ pa_assert(!c->scache.name);
+ c->scache.name = pa_xstrdup(name);
+
+ c->state = ESD_CACHING_SAMPLE;
+
+ pa_scache_add_item(c->protocol->core, c->scache.name, NULL, NULL, NULL, c->client->proplist, &idx);
+
+ idx += 1;
+ connection_write(c, &idx, sizeof(uint32_t));
+
+ return 0;
+}
+
+static int esd_proto_sample_get_id(connection *c, esd_proto_t request, const void *data, size_t length) {
+ int32_t ok;
+ uint32_t idx;
+ char name[ESD_NAME_MAX+sizeof(SCACHE_PREFIX)-1];
+
+ connection_assert_ref(c);
+ pa_assert(data);
+ pa_assert(length == ESD_NAME_MAX);
+
+ strcpy(name, SCACHE_PREFIX);
+ pa_strlcpy(name+sizeof(SCACHE_PREFIX)-1, data, ESD_NAME_MAX);
+
+ CHECK_VALIDITY(pa_utf8_valid(name), "Invalid UTF8 in sample name.");
+
+ ok = -1;
+ if ((idx = pa_scache_get_id_by_name(c->protocol->core, name)) != PA_IDXSET_INVALID)
+ ok = (int32_t) idx + 1;
+
+ connection_write(c, &ok, sizeof(int32_t));
+
+ return 0;
+}
+
+static int esd_proto_sample_free_or_play(connection *c, esd_proto_t request, const void *data, size_t length) {
+ int32_t ok;
+ const char *name;
+ uint32_t idx;
+
+ connection_assert_ref(c);
+ pa_assert(data);
+ pa_assert(length == sizeof(int32_t));
+
+ memcpy(&idx, data, sizeof(uint32_t));
+ idx = PA_MAYBE_UINT32_SWAP(c->swap_byte_order, idx) - 1;
+
+ ok = 0;
+
+ if ((name = pa_scache_get_name_by_id(c->protocol->core, idx))) {
+ if (request == ESD_PROTO_SAMPLE_PLAY) {
+ pa_sink *sink;
+
+ if ((sink = pa_namereg_get(c->protocol->core, c->options->default_sink, PA_NAMEREG_SINK)))
+ if (pa_scache_play_item(c->protocol->core, name, sink, PA_VOLUME_NORM, c->client->proplist, NULL) >= 0)
+ ok = (int32_t) idx + 1;
+ } else {
+ pa_assert(request == ESD_PROTO_SAMPLE_FREE);
+
+ if (pa_scache_remove_item(c->protocol->core, name) >= 0)
+ ok = (int32_t) idx + 1;
+ }
+ }
+
+ connection_write(c, &ok, sizeof(int32_t));
+
+ return 0;
+}
+
+static int esd_proto_standby_or_resume(connection *c, esd_proto_t request, const void *data, size_t length) {
+ int32_t ok = 1;
+
+ connection_assert_ref(c);
+
+ connection_write_prepare(c, sizeof(int32_t) * 2);
+ connection_write(c, &ok, sizeof(int32_t));
+
+ pa_log_debug("%s of all sinks and sources requested by client %" PRIu32 ".",
+ request == ESD_PROTO_STANDBY ? "Suspending" : "Resuming", c->client->index);
+
+ if (request == ESD_PROTO_STANDBY) {
+ ok = pa_sink_suspend_all(c->protocol->core, true, PA_SUSPEND_USER) >= 0;
+ ok &= pa_source_suspend_all(c->protocol->core, true, PA_SUSPEND_USER) >= 0;
+ } else {
+ pa_assert(request == ESD_PROTO_RESUME);
+ ok = pa_sink_suspend_all(c->protocol->core, false, PA_SUSPEND_USER) >= 0;
+ ok &= pa_source_suspend_all(c->protocol->core, false, PA_SUSPEND_USER) >= 0;
+ }
+
+ connection_write(c, &ok, sizeof(int32_t));
+
+ return 0;
+}
+
+static int esd_proto_standby_mode(connection *c, esd_proto_t request, const void *data, size_t length) {
+ int32_t mode;
+ pa_sink *sink;
+ pa_source *source;
+
+ connection_assert_ref(c);
+
+ mode = ESM_RUNNING;
+
+ if ((sink = pa_namereg_get(c->protocol->core, c->options->default_sink, PA_NAMEREG_SINK)))
+ if (sink->state == PA_SINK_SUSPENDED)
+ mode = ESM_ON_STANDBY;
+
+ if ((source = pa_namereg_get(c->protocol->core, c->options->default_source, PA_NAMEREG_SOURCE)))
+ if (source->state == PA_SOURCE_SUSPENDED)
+ mode = ESM_ON_STANDBY;
+
+ mode = PA_MAYBE_INT32_SWAP(c->swap_byte_order, mode);
+
+ connection_write(c, &mode, sizeof(mode));
+ return 0;
+}
+
+/*** client callbacks ***/
+
+static void client_kill_cb(pa_client *c) {
+ pa_assert(c);
+
+ connection_unlink(CONNECTION(c->userdata));
+}
+
+/*** pa_iochannel callbacks ***/
+
+static int do_read(connection *c) {
+ connection_assert_ref(c);
+
+/* pa_log("READ"); */
+
+ if (c->state == ESD_NEXT_REQUEST) {
+ ssize_t r;
+ pa_assert(c->read_data_length < sizeof(c->request));
+
+ if ((r = pa_iochannel_read(c->io,
+ ((uint8_t*) &c->request) + c->read_data_length,
+ sizeof(c->request) - c->read_data_length)) <= 0) {
+
+ if (r < 0 && (errno == EINTR || errno == EAGAIN))
+ return 0;
+
+ pa_log_debug("read(): %s", r < 0 ? pa_cstrerror(errno) : "EOF");
+ return -1;
+ }
+
+ c->read_data_length += (size_t) r;
+
+ if (c->read_data_length >= sizeof(c->request)) {
+ struct proto_handler *handler;
+
+ c->request = PA_MAYBE_INT32_SWAP(c->swap_byte_order, c->request);
+
+ if (c->request < ESD_PROTO_CONNECT || c->request >= ESD_PROTO_MAX) {
+ pa_log("received invalid request.");
+ return -1;
+ }
+
+ handler = proto_map+c->request;
+
+/* pa_log("executing request #%u", c->request); */
+
+ if (!handler->proc) {
+ pa_log("received unimplemented request #%u.", c->request);
+ return -1;
+ }
+
+ if (handler->data_length == 0) {
+ c->read_data_length = 0;
+
+ if (handler->proc(c, c->request, NULL, 0) < 0)
+ return -1;
+
+ } else {
+ if (c->read_data_alloc < handler->data_length)
+ c->read_data = pa_xrealloc(c->read_data, c->read_data_alloc = handler->data_length);
+ pa_assert(c->read_data);
+
+ c->state = ESD_NEEDS_REQDATA;
+ c->read_data_length = 0;
+ }
+ }
+
+ } else if (c->state == ESD_NEEDS_REQDATA) {
+ ssize_t r;
+ struct proto_handler *handler = proto_map+c->request;
+
+ pa_assert(handler->proc);
+
+ pa_assert(c->read_data && c->read_data_length < handler->data_length);
+
+ if ((r = pa_iochannel_read(c->io,
+ (uint8_t*) c->read_data + c->read_data_length,
+ handler->data_length - c->read_data_length)) <= 0) {
+
+ if (r < 0 && (errno == EINTR || errno == EAGAIN))
+ return 0;
+
+ pa_log_debug("read(): %s", r < 0 ? pa_cstrerror(errno) : "EOF");
+ return -1;
+ }
+
+ c->read_data_length += (size_t) r;
+ if (c->read_data_length >= handler->data_length) {
+ size_t l = c->read_data_length;
+ pa_assert(handler->proc);
+
+ c->state = ESD_NEXT_REQUEST;
+ c->read_data_length = 0;
+
+ if (handler->proc(c, c->request, c->read_data, l) < 0)
+ return -1;
+ }
+ } else if (c->state == ESD_CACHING_SAMPLE) {
+ ssize_t r;
+ void *p;
+
+ pa_assert(c->scache.memchunk.memblock);
+ pa_assert(c->scache.name);
+ pa_assert(c->scache.memchunk.index < c->scache.memchunk.length);
+
+ p = pa_memblock_acquire(c->scache.memchunk.memblock);
+ r = pa_iochannel_read(c->io, (uint8_t*) p+c->scache.memchunk.index, c->scache.memchunk.length-c->scache.memchunk.index);
+ pa_memblock_release(c->scache.memchunk.memblock);
+
+ if (r <= 0) {
+ if (r < 0 && (errno == EINTR || errno == EAGAIN))
+ return 0;
+
+ pa_log_debug("read(): %s", r < 0 ? pa_cstrerror(errno) : "EOF");
+ return -1;
+ }
+
+ c->scache.memchunk.index += (size_t) r;
+ pa_assert(c->scache.memchunk.index <= c->scache.memchunk.length);
+
+ if (c->scache.memchunk.index == c->scache.memchunk.length) {
+ uint32_t idx;
+
+ c->scache.memchunk.index = 0;
+ pa_scache_add_item(c->protocol->core, c->scache.name, &c->scache.sample_spec, NULL, &c->scache.memchunk, c->client->proplist, &idx);
+
+ pa_memblock_unref(c->scache.memchunk.memblock);
+ pa_memchunk_reset(&c->scache.memchunk);
+
+ pa_xfree(c->scache.name);
+ c->scache.name = NULL;
+
+ c->state = ESD_NEXT_REQUEST;
+
+ idx += 1;
+ connection_write(c, &idx, sizeof(uint32_t));
+ }
+
+ } else if (c->state == ESD_STREAMING_DATA && c->sink_input) {
+ pa_memchunk chunk;
+ ssize_t r;
+ size_t l;
+ void *p;
+ size_t space = 0;
+
+ pa_assert(c->input_memblockq);
+
+/* pa_log("STREAMING_DATA"); */
+
+ if ((l = (size_t) pa_atomic_load(&c->playback.missing)) <= 0)
+ return 0;
+
+ if (c->playback.current_memblock) {
+
+ space = pa_memblock_get_length(c->playback.current_memblock) - c->playback.memblock_index;
+
+ if (space <= 0) {
+ pa_memblock_unref(c->playback.current_memblock);
+ c->playback.current_memblock = NULL;
+ }
+ }
+
+ if (!c->playback.current_memblock) {
+ pa_assert_se(c->playback.current_memblock = pa_memblock_new(c->protocol->core->mempool, (size_t) -1));
+ c->playback.memblock_index = 0;
+
+ space = pa_memblock_get_length(c->playback.current_memblock);
+ }
+
+ if (l > space)
+ l = space;
+
+ p = pa_memblock_acquire(c->playback.current_memblock);
+ r = pa_iochannel_read(c->io, (uint8_t*) p+c->playback.memblock_index, l);
+ pa_memblock_release(c->playback.current_memblock);
+
+ if (r <= 0) {
+
+ if (r < 0 && (errno == EINTR || errno == EAGAIN))
+ return 0;
+
+ pa_log_debug("read(): %s", r < 0 ? pa_cstrerror(errno) : "EOF");
+ return -1;
+ }
+
+ chunk.memblock = c->playback.current_memblock;
+ chunk.index = c->playback.memblock_index;
+ chunk.length = (size_t) r;
+
+ c->playback.memblock_index += (size_t) r;
+
+ pa_atomic_sub(&c->playback.missing, (int) r);
+ pa_asyncmsgq_post(c->sink_input->sink->asyncmsgq, PA_MSGOBJECT(c->sink_input), SINK_INPUT_MESSAGE_POST_DATA, NULL, 0, &chunk, NULL);
+ }
+
+ return 0;
+}
+
+static int do_write(connection *c) {
+ connection_assert_ref(c);
+
+/* pa_log("WRITE"); */
+
+ if (c->write_data_length) {
+ ssize_t r;
+
+ pa_assert(c->write_data_index < c->write_data_length);
+ if ((r = pa_iochannel_write(c->io, (uint8_t*) c->write_data+c->write_data_index, c->write_data_length-c->write_data_index)) < 0) {
+ pa_log("write(): %s", pa_cstrerror(errno));
+ return -1;
+ }
+
+ c->write_data_index += (size_t) r;
+ if (c->write_data_index >= c->write_data_length)
+ c->write_data_length = c->write_data_index = 0;
+
+ return 1;
+
+ } else if (c->state == ESD_STREAMING_DATA && c->source_output) {
+ pa_memchunk chunk;
+ ssize_t r;
+ void *p;
+
+ if (pa_memblockq_peek(c->output_memblockq, &chunk) < 0)
+ return 0;
+
+ pa_assert(chunk.memblock);
+ pa_assert(chunk.length);
+
+ p = pa_memblock_acquire(chunk.memblock);
+ r = pa_iochannel_write(c->io, (uint8_t*) p+chunk.index, chunk.length);
+ pa_memblock_release(chunk.memblock);
+
+ pa_memblock_unref(chunk.memblock);
+
+ if (r < 0) {
+ pa_log("write(): %s", pa_cstrerror(errno));
+ return -1;
+ }
+
+ pa_memblockq_drop(c->output_memblockq, (size_t) r);
+ return 1;
+ }
+
+ return 0;
+}
+
+static void do_work(connection *c) {
+ connection_assert_ref(c);
+
+ c->protocol->core->mainloop->defer_enable(c->defer_event, 0);
+
+ if (c->dead)
+ return;
+
+ if (pa_iochannel_is_readable(c->io))
+ if (do_read(c) < 0)
+ goto fail;
+
+ if (c->state == ESD_STREAMING_DATA && !c->sink_input && pa_iochannel_is_hungup(c->io))
+ /* In case we are in capture mode we will never call read()
+ * on the socket, hence we need to detect the hangup manually
+ * here, instead of simply waiting for read() to return 0. */
+ goto fail;
+
+ while (pa_iochannel_is_writable(c->io)) {
+ int r = do_write(c);
+ if (r < 0)
+ goto fail;
+ if (r == 0)
+ break;
+ }
+
+ return;
+
+fail:
+
+ if (c->state == ESD_STREAMING_DATA && c->sink_input) {
+ c->dead = true;
+
+ pa_iochannel_free(c->io);
+ c->io = NULL;
+
+ pa_asyncmsgq_post(c->sink_input->sink->asyncmsgq, PA_MSGOBJECT(c->sink_input), SINK_INPUT_MESSAGE_DISABLE_PREBUF, NULL, 0, NULL, NULL);
+ } else
+ connection_unlink(c);
+}
+
+static void io_callback(pa_iochannel*io, void *userdata) {
+ connection *c = CONNECTION(userdata);
+
+ connection_assert_ref(c);
+ pa_assert(io);
+
+ do_work(c);
+}
+
+static void defer_callback(pa_mainloop_api*a, pa_defer_event *e, void *userdata) {
+ connection *c = CONNECTION(userdata);
+
+ connection_assert_ref(c);
+ pa_assert(e);
+
+ do_work(c);
+}
+
+static int connection_process_msg(pa_msgobject *o, int code, void*userdata, int64_t offset, pa_memchunk *chunk) {
+ connection *c = CONNECTION(o);
+ connection_assert_ref(c);
+
+ if (!c->protocol)
+ return -1;
+
+ switch (code) {
+ case CONNECTION_MESSAGE_REQUEST_DATA:
+ do_work(c);
+ break;
+
+ case CONNECTION_MESSAGE_POST_DATA:
+/* pa_log("got data %u", chunk->length); */
+ pa_memblockq_push_align(c->output_memblockq, chunk);
+ do_work(c);
+ break;
+
+ case CONNECTION_MESSAGE_UNLINK_CONNECTION:
+ connection_unlink(c);
+ break;
+ }
+
+ return 0;
+}
+
+/*** sink_input callbacks ***/
+
+/* Called from thread context */
+static int sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk) {
+ pa_sink_input *i = PA_SINK_INPUT(o);
+ connection*c;
+
+ pa_sink_input_assert_ref(i);
+ c = CONNECTION(i->userdata);
+ connection_assert_ref(c);
+
+ switch (code) {
+
+ case SINK_INPUT_MESSAGE_POST_DATA: {
+ pa_assert(chunk);
+
+ /* New data from the main loop */
+ pa_memblockq_push_align(c->input_memblockq, chunk);
+
+ if (pa_memblockq_is_readable(c->input_memblockq) && c->playback.underrun) {
+ pa_log_debug("Requesting rewind due to end of underrun.");
+ pa_sink_input_request_rewind(c->sink_input, 0, false, true, false);
+ }
+
+/* pa_log("got data, %u", pa_memblockq_get_length(c->input_memblockq)); */
+
+ return 0;
+ }
+
+ case SINK_INPUT_MESSAGE_DISABLE_PREBUF:
+ pa_memblockq_prebuf_disable(c->input_memblockq);
+ return 0;
+
+ case PA_SINK_INPUT_MESSAGE_GET_LATENCY: {
+ pa_usec_t *r = userdata;
+
+ /* The default handler will add in the extra latency added by the resampler. */
+ *r = pa_bytes_to_usec(pa_memblockq_get_length(c->input_memblockq), &c->sink_input->sample_spec);
+ }
+ /* Fall through. */
+
+ default:
+ return pa_sink_input_process_msg(o, code, userdata, offset, chunk);
+ }
+}
+
+/* Called from thread context */
+static int sink_input_pop_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) {
+ connection*c;
+
+ pa_sink_input_assert_ref(i);
+ c = CONNECTION(i->userdata);
+ connection_assert_ref(c);
+ pa_assert(chunk);
+
+ if (pa_memblockq_peek(c->input_memblockq, chunk) < 0) {
+
+ c->playback.underrun = true;
+
+ if (c->dead && pa_sink_input_safe_to_remove(i))
+ pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(c), CONNECTION_MESSAGE_UNLINK_CONNECTION, NULL, 0, NULL, NULL);
+
+ return -1;
+ } else {
+ size_t m;
+
+ c->playback.underrun = false;
+
+ chunk->length = PA_MIN(length, chunk->length);
+ pa_memblockq_drop(c->input_memblockq, chunk->length);
+ m = pa_memblockq_pop_missing(c->input_memblockq);
+
+ if (m > 0)
+ if (pa_atomic_add(&c->playback.missing, (int) m) <= 0)
+ pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(c), CONNECTION_MESSAGE_REQUEST_DATA, NULL, 0, NULL, NULL);
+
+ return 0;
+ }
+}
+
+/* Called from thread context */
+static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) {
+ connection *c;
+
+ pa_sink_input_assert_ref(i);
+ c = CONNECTION(i->userdata);
+ connection_assert_ref(c);
+
+ /* If we are in an underrun, then we don't rewind */
+ if (i->thread_info.underrun_for > 0)
+ return;
+
+ pa_memblockq_rewind(c->input_memblockq, nbytes);
+}
+
+/* Called from thread context */
+static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) {
+ connection *c;
+
+ pa_sink_input_assert_ref(i);
+ c = CONNECTION(i->userdata);
+ connection_assert_ref(c);
+
+ pa_memblockq_set_maxrewind(c->input_memblockq, nbytes);
+}
+
+static void sink_input_kill_cb(pa_sink_input *i) {
+ pa_sink_input_assert_ref(i);
+
+ connection_unlink(CONNECTION(i->userdata));
+}
+
+/*** source_output callbacks ***/
+
+/* Called from thread context */
+static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk) {
+ connection *c;
+
+ pa_source_output_assert_ref(o);
+ c = CONNECTION(o->userdata);
+ pa_assert(c);
+ pa_assert(chunk);
+
+ pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(c), CONNECTION_MESSAGE_POST_DATA, NULL, 0, chunk, NULL);
+}
+
+static void source_output_kill_cb(pa_source_output *o) {
+ pa_source_output_assert_ref(o);
+
+ connection_unlink(CONNECTION(o->userdata));
+}
+
+static pa_usec_t source_output_get_latency_cb(pa_source_output *o) {
+ connection*c;
+
+ pa_source_output_assert_ref(o);
+ c = CONNECTION(o->userdata);
+ pa_assert(c);
+
+ return pa_bytes_to_usec(pa_memblockq_get_length(c->output_memblockq), &c->source_output->sample_spec);
+}
+
+/*** entry points ***/
+
+static void auth_timeout(pa_mainloop_api *m, pa_time_event *e, const struct timeval *t, void *userdata) {
+ connection *c = CONNECTION(userdata);
+
+ pa_assert(m);
+ connection_assert_ref(c);
+ pa_assert(c->auth_timeout_event == e);
+
+ if (!c->authorized)
+ connection_unlink(c);
+}
+
+void pa_esound_protocol_connect(pa_esound_protocol *p, pa_iochannel *io, pa_esound_options *o) {
+ connection *c;
+ char pname[128];
+ pa_client_new_data data;
+ pa_client *client;
+
+ pa_assert(p);
+ pa_assert(io);
+ pa_assert(o);
+
+ if (pa_idxset_size(p->connections)+1 > MAX_CONNECTIONS) {
+ pa_log("Warning! Too many connections (%u), dropping incoming connection.", MAX_CONNECTIONS);
+ pa_iochannel_free(io);
+ return;
+ }
+
+ pa_client_new_data_init(&data);
+ data.module = o->module;
+ data.driver = __FILE__;
+ pa_iochannel_socket_peer_to_string(io, pname, sizeof(pname));
+ pa_proplist_setf(data.proplist, PA_PROP_APPLICATION_NAME, "EsounD client (%s)", pname);
+ pa_proplist_sets(data.proplist, "esound-protocol.peer", pname);
+ client = pa_client_new(p->core, &data);
+ pa_client_new_data_done(&data);
+
+ if (!client)
+ return;
+
+ c = pa_msgobject_new(connection);
+ c->parent.parent.free = connection_free;
+ c->parent.process_msg = connection_process_msg;
+ c->protocol = p;
+ c->io = io;
+ pa_iochannel_set_callback(c->io, io_callback, c);
+
+ c->client = client;
+ c->client->kill = client_kill_cb;
+ c->client->userdata = c;
+
+ c->options = pa_esound_options_ref(o);
+ c->authorized = false;
+ c->swap_byte_order = false;
+ c->dead = false;
+
+ c->read_data_length = 0;
+ c->read_data = pa_xmalloc(c->read_data_alloc = proto_map[ESD_PROTO_CONNECT].data_length);
+
+ c->write_data_length = c->write_data_index = c->write_data_alloc = 0;
+ c->write_data = NULL;
+
+ c->state = ESD_NEEDS_REQDATA;
+ c->request = ESD_PROTO_CONNECT;
+
+ c->sink_input = NULL;
+ c->input_memblockq = NULL;
+
+ c->source_output = NULL;
+ c->output_memblockq = NULL;
+
+ c->playback.current_memblock = NULL;
+ c->playback.memblock_index = 0;
+ c->playback.underrun = true;
+ pa_atomic_store(&c->playback.missing, 0);
+
+ pa_memchunk_reset(&c->scache.memchunk);
+ c->scache.name = NULL;
+
+ c->original_name = NULL;
+
+ if (o->auth_anonymous) {
+ pa_log_info("Client authenticated anonymously.");
+ c->authorized = true;
+ }
+
+ if (!c->authorized &&
+ o->auth_ip_acl &&
+ pa_ip_acl_check(o->auth_ip_acl, pa_iochannel_get_recv_fd(io)) > 0) {
+
+ pa_log_info("Client authenticated by IP ACL.");
+ c->authorized = true;
+ }
+
+ if (!c->authorized)
+ c->auth_timeout_event = pa_core_rttime_new(p->core, pa_rtclock_now() + AUTH_TIMEOUT, auth_timeout, c);
+ else
+ c->auth_timeout_event = NULL;
+
+ c->defer_event = p->core->mainloop->defer_new(p->core->mainloop, defer_callback, c);
+ p->core->mainloop->defer_enable(c->defer_event, 0);
+
+ pa_idxset_put(p->connections, c, &c->index);
+}
+
+void pa_esound_protocol_disconnect(pa_esound_protocol *p, pa_module *m) {
+ connection *c;
+ void *state = NULL;
+
+ pa_assert(p);
+ pa_assert(m);
+
+ while ((c = pa_idxset_iterate(p->connections, &state, NULL)))
+ if (c->options->module == m)
+ connection_unlink(c);
+}
+
+static pa_esound_protocol* esound_protocol_new(pa_core *c) {
+ pa_esound_protocol *p;
+
+ pa_assert(c);
+
+ p = pa_xnew(pa_esound_protocol, 1);
+ PA_REFCNT_INIT(p);
+ p->core = c;
+ p->connections = pa_idxset_new(NULL, NULL);
+ p->n_player = 0;
+
+ pa_assert_se(pa_shared_set(c, "esound-protocol", p) >= 0);
+
+ return p;
+}
+
+pa_esound_protocol* pa_esound_protocol_get(pa_core *c) {
+ pa_esound_protocol *p;
+
+ if ((p = pa_shared_get(c, "esound-protocol")))
+ return pa_esound_protocol_ref(p);
+
+ return esound_protocol_new(c);
+}
+
+pa_esound_protocol* pa_esound_protocol_ref(pa_esound_protocol *p) {
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) >= 1);
+
+ PA_REFCNT_INC(p);
+
+ return p;
+}
+
+void pa_esound_protocol_unref(pa_esound_protocol *p) {
+ connection *c;
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) >= 1);
+
+ if (PA_REFCNT_DEC(p) > 0)
+ return;
+
+ while ((c = pa_idxset_first(p->connections, NULL)))
+ connection_unlink(c);
+
+ pa_idxset_free(p->connections, NULL);
+
+ pa_assert_se(pa_shared_remove(p->core, "esound-protocol") >= 0);
+
+ pa_xfree(p);
+}
+
+pa_esound_options* pa_esound_options_new(void) {
+ pa_esound_options *o;
+
+ o = pa_xnew0(pa_esound_options, 1);
+ PA_REFCNT_INIT(o);
+
+ return o;
+}
+
+pa_esound_options* pa_esound_options_ref(pa_esound_options *o) {
+ pa_assert(o);
+ pa_assert(PA_REFCNT_VALUE(o) >= 1);
+
+ PA_REFCNT_INC(o);
+
+ return o;
+}
+
+void pa_esound_options_unref(pa_esound_options *o) {
+ pa_assert(o);
+ pa_assert(PA_REFCNT_VALUE(o) >= 1);
+
+ if (PA_REFCNT_DEC(o) > 0)
+ return;
+
+ if (o->auth_ip_acl)
+ pa_ip_acl_free(o->auth_ip_acl);
+
+ if (o->auth_cookie)
+ pa_auth_cookie_unref(o->auth_cookie);
+
+ pa_xfree(o->default_sink);
+ pa_xfree(o->default_source);
+
+ pa_xfree(o);
+}
+
+int pa_esound_options_parse(pa_esound_options *o, pa_core *c, pa_modargs *ma) {
+ bool enabled;
+ const char *acl;
+
+ pa_assert(o);
+ pa_assert(PA_REFCNT_VALUE(o) >= 1);
+ pa_assert(ma);
+
+ if (pa_modargs_get_value_boolean(ma, "auth-anonymous", &o->auth_anonymous) < 0) {
+ pa_log("auth-anonymous= expects a boolean argument.");
+ return -1;
+ }
+
+ if ((acl = pa_modargs_get_value(ma, "auth-ip-acl", NULL))) {
+ pa_ip_acl *ipa;
+
+ if (!(ipa = pa_ip_acl_new(acl))) {
+ pa_log("Failed to parse IP ACL '%s'", acl);
+ return -1;
+ }
+
+ if (o->auth_ip_acl)
+ pa_ip_acl_free(o->auth_ip_acl);
+
+ o->auth_ip_acl = ipa;
+ }
+
+ enabled = true;
+ if (pa_modargs_get_value_boolean(ma, "auth-cookie-enabled", &enabled) < 0) {
+ pa_log("auth-cookie-enabled= expects a boolean argument.");
+ return -1;
+ }
+
+ if (o->auth_cookie)
+ pa_auth_cookie_unref(o->auth_cookie);
+
+ if (enabled) {
+ char *cn;
+
+ /* The new name for this is 'auth-cookie', for compat reasons
+ * we check the old name too */
+ if (!(cn = pa_xstrdup(pa_modargs_get_value(ma, "auth-cookie", NULL)))) {
+ if (!(cn = pa_xstrdup(pa_modargs_get_value(ma, "cookie", NULL)))) {
+ if (pa_append_to_home_dir(DEFAULT_COOKIE_FILE, &cn) < 0)
+ return -1;
+ }
+ }
+
+ o->auth_cookie = pa_auth_cookie_get(c, cn, true, ESD_KEY_LEN);
+ pa_xfree(cn);
+ if (!o->auth_cookie)
+ return -1;
+
+ } else
+ o->auth_cookie = NULL;
+
+ pa_xfree(o->default_sink);
+ o->default_sink = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL));
+
+ pa_xfree(o->default_source);
+ o->default_source = pa_xstrdup(pa_modargs_get_value(ma, "source", NULL));
+
+ return 0;
+}
diff --git a/src/pulsecore/protocol-esound.h b/src/pulsecore/protocol-esound.h
new file mode 100644
index 0000000..6208640
--- /dev/null
+++ b/src/pulsecore/protocol-esound.h
@@ -0,0 +1,56 @@
+#ifndef fooprotocolesoundhfoo
+#define fooprotocolesoundhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulsecore/core.h>
+#include <pulsecore/ipacl.h>
+#include <pulsecore/auth-cookie.h>
+#include <pulsecore/iochannel.h>
+#include <pulsecore/module.h>
+#include <pulsecore/modargs.h>
+
+typedef struct pa_esound_protocol pa_esound_protocol;
+
+typedef struct pa_esound_options {
+ PA_REFCNT_DECLARE;
+
+ pa_module *module;
+
+ bool auth_anonymous;
+ pa_ip_acl *auth_ip_acl;
+ pa_auth_cookie *auth_cookie;
+
+ char *default_sink, *default_source;
+} pa_esound_options;
+
+pa_esound_protocol* pa_esound_protocol_get(pa_core*core);
+pa_esound_protocol* pa_esound_protocol_ref(pa_esound_protocol *p);
+void pa_esound_protocol_unref(pa_esound_protocol *p);
+void pa_esound_protocol_connect(pa_esound_protocol *p, pa_iochannel *io, pa_esound_options *o);
+void pa_esound_protocol_disconnect(pa_esound_protocol *p, pa_module *m);
+
+pa_esound_options* pa_esound_options_new(void);
+pa_esound_options* pa_esound_options_ref(pa_esound_options *o);
+void pa_esound_options_unref(pa_esound_options *o);
+int pa_esound_options_parse(pa_esound_options *o, pa_core *c, pa_modargs *ma);
+
+#endif
diff --git a/src/pulsecore/protocol-http.c b/src/pulsecore/protocol-http.c
new file mode 100644
index 0000000..e8d22ed
--- /dev/null
+++ b/src/pulsecore/protocol-http.c
@@ -0,0 +1,817 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2005-2009 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include <pulse/util.h>
+#include <pulse/xmalloc.h>
+#include <pulse/timeval.h>
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/ioline.h>
+#include <pulsecore/thread-mq.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/log.h>
+#include <pulsecore/namereg.h>
+#include <pulsecore/cli-text.h>
+#include <pulsecore/shared.h>
+#include <pulsecore/core-error.h>
+#include <pulsecore/mime-type.h>
+
+#include "protocol-http.h"
+
+/* Don't allow more than this many concurrent connections */
+#define MAX_CONNECTIONS 10
+
+#define URL_ROOT "/"
+#define URL_CSS "/style"
+#define URL_STATUS "/status"
+#define URL_LISTEN "/listen"
+#define URL_LISTEN_SOURCE "/listen/source/"
+
+#define MIME_HTML "text/html; charset=utf-8"
+#define MIME_TEXT "text/plain; charset=utf-8"
+#define MIME_CSS "text/css"
+
+#define HTML_HEADER(t) \
+ "<?xml version=\"1.0\"?>\n" \
+ "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n" \
+ "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n" \
+ " <head>\n" \
+ " <title>"t"</title>\n" \
+ " <link rel=\"stylesheet\" type=\"text/css\" href=\"style\"/>\n" \
+ " </head>\n" \
+ " <body>\n"
+
+#define HTML_FOOTER \
+ " </body>\n" \
+ "</html>\n"
+
+#define RECORD_BUFFER_SECONDS (5)
+#define DEFAULT_SOURCE_LATENCY (300*PA_USEC_PER_MSEC)
+
+enum state {
+ STATE_REQUEST_LINE,
+ STATE_MIME_HEADER,
+ STATE_DATA
+};
+
+enum method {
+ METHOD_GET,
+ METHOD_HEAD
+};
+
+struct connection {
+ pa_http_protocol *protocol;
+ pa_iochannel *io;
+ pa_ioline *line;
+ pa_memblockq *output_memblockq;
+ pa_source_output *source_output;
+ pa_client *client;
+ enum state state;
+ char *url;
+ enum method method;
+ pa_module *module;
+};
+
+struct pa_http_protocol {
+ PA_REFCNT_DECLARE;
+
+ pa_core *core;
+ pa_idxset *connections;
+
+ pa_strlist *servers;
+};
+
+enum {
+ SOURCE_OUTPUT_MESSAGE_POST_DATA = PA_SOURCE_OUTPUT_MESSAGE_MAX
+};
+
+/* Called from main context */
+static void connection_unlink(struct connection *c) {
+ pa_assert(c);
+
+ if (c->source_output) {
+ pa_source_output_unlink(c->source_output);
+ c->source_output->userdata = NULL;
+ pa_source_output_unref(c->source_output);
+ }
+
+ if (c->client)
+ pa_client_free(c->client);
+
+ pa_xfree(c->url);
+
+ if (c->line)
+ pa_ioline_unref(c->line);
+
+ if (c->io)
+ pa_iochannel_free(c->io);
+
+ if (c->output_memblockq)
+ pa_memblockq_free(c->output_memblockq);
+
+ pa_idxset_remove_by_data(c->protocol->connections, c, NULL);
+
+ pa_xfree(c);
+}
+
+/* Called from main context */
+static int do_write(struct connection *c) {
+ pa_memchunk chunk;
+ ssize_t r;
+ void *p;
+
+ pa_assert(c);
+
+ if (pa_memblockq_peek(c->output_memblockq, &chunk) < 0)
+ return 0;
+
+ pa_assert(chunk.memblock);
+ pa_assert(chunk.length > 0);
+
+ p = pa_memblock_acquire(chunk.memblock);
+ r = pa_iochannel_write(c->io, (uint8_t*) p+chunk.index, chunk.length);
+ pa_memblock_release(chunk.memblock);
+
+ pa_memblock_unref(chunk.memblock);
+
+ if (r < 0) {
+ pa_log("write(): %s", pa_cstrerror(errno));
+ return -1;
+ }
+
+ pa_memblockq_drop(c->output_memblockq, (size_t) r);
+
+ return 1;
+}
+
+/* Called from main context */
+static void do_work(struct connection *c) {
+ pa_assert(c);
+
+ if (pa_iochannel_is_hungup(c->io))
+ goto fail;
+
+ while (pa_iochannel_is_writable(c->io)) {
+ int r = do_write(c);
+ if (r < 0)
+ goto fail;
+ if (r == 0)
+ break;
+ }
+
+ return;
+
+fail:
+ connection_unlink(c);
+}
+
+/* Called from thread context, except when it is not */
+static int source_output_process_msg(pa_msgobject *m, int code, void *userdata, int64_t offset, pa_memchunk *chunk) {
+ pa_source_output *o = PA_SOURCE_OUTPUT(m);
+ struct connection *c;
+
+ pa_source_output_assert_ref(o);
+
+ if (!(c = o->userdata))
+ return -1;
+
+ switch (code) {
+
+ case SOURCE_OUTPUT_MESSAGE_POST_DATA:
+ /* While this function is usually called from IO thread
+ * context, this specific command is not! */
+ pa_memblockq_push_align(c->output_memblockq, chunk);
+ do_work(c);
+ break;
+
+ default:
+ return pa_source_output_process_msg(m, code, userdata, offset, chunk);
+ }
+
+ return 0;
+}
+
+/* Called from thread context */
+static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk) {
+ struct connection *c;
+
+ pa_source_output_assert_ref(o);
+ pa_assert_se(c = o->userdata);
+ pa_assert(chunk);
+
+ pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(o), SOURCE_OUTPUT_MESSAGE_POST_DATA, NULL, 0, chunk, NULL);
+}
+
+/* Called from main context */
+static void source_output_kill_cb(pa_source_output *o) {
+ struct connection*c;
+
+ pa_source_output_assert_ref(o);
+ pa_assert_se(c = o->userdata);
+
+ connection_unlink(c);
+}
+
+/* Called from main context */
+static pa_usec_t source_output_get_latency_cb(pa_source_output *o) {
+ struct connection*c;
+
+ pa_source_output_assert_ref(o);
+ pa_assert_se(c = o->userdata);
+
+ return pa_bytes_to_usec(pa_memblockq_get_length(c->output_memblockq), &c->source_output->sample_spec);
+}
+
+/*** client callbacks ***/
+static void client_kill_cb(pa_client *client) {
+ struct connection*c;
+
+ pa_assert(client);
+ pa_assert_se(c = client->userdata);
+
+ connection_unlink(c);
+}
+
+/*** pa_iochannel callbacks ***/
+static void io_callback(pa_iochannel*io, void *userdata) {
+ struct connection *c = userdata;
+
+ pa_assert(c);
+ pa_assert(io);
+
+ do_work(c);
+}
+
+static char *escape_html(const char *t) {
+ pa_strbuf *sb;
+ const char *p, *e;
+
+ sb = pa_strbuf_new();
+
+ for (e = p = t; *p; p++) {
+
+ if (*p == '>' || *p == '<' || *p == '&') {
+
+ if (p > e) {
+ pa_strbuf_putsn(sb, e, p-e);
+ e = p + 1;
+ }
+
+ if (*p == '>')
+ pa_strbuf_puts(sb, "&gt;");
+ else if (*p == '<')
+ pa_strbuf_puts(sb, "&lt;");
+ else
+ pa_strbuf_puts(sb, "&amp;");
+ }
+ }
+
+ if (p > e)
+ pa_strbuf_putsn(sb, e, p-e);
+
+ return pa_strbuf_to_string_free(sb);
+}
+
+static void http_response(
+ struct connection *c,
+ int code,
+ const char *msg,
+ const char *mime) {
+
+ char *s;
+
+ pa_assert(c);
+ pa_assert(msg);
+ pa_assert(mime);
+
+ s = pa_sprintf_malloc(
+ "HTTP/1.0 %i %s\n"
+ "Connection: close\n"
+ "Content-Type: %s\n"
+ "Cache-Control: no-cache\n"
+ "Expires: 0\n"
+ "Server: "PACKAGE_NAME"/"PACKAGE_VERSION"\n"
+ "\n", code, msg, mime);
+ pa_ioline_puts(c->line, s);
+ pa_xfree(s);
+}
+
+static void html_response(
+ struct connection *c,
+ int code,
+ const char *msg,
+ const char *text) {
+
+ char *s;
+ pa_assert(c);
+
+ http_response(c, code, msg, MIME_HTML);
+
+ if (c->method == METHOD_HEAD) {
+ pa_ioline_defer_close(c->line);
+ return;
+ }
+
+ if (!text)
+ text = msg;
+
+ s = pa_sprintf_malloc(
+ HTML_HEADER("%s")
+ "%s"
+ HTML_FOOTER,
+ text, text);
+
+ pa_ioline_puts(c->line, s);
+ pa_xfree(s);
+
+ pa_ioline_defer_close(c->line);
+}
+
+static void html_print_field(pa_ioline *line, const char *left, const char *right) {
+ char *eleft, *eright;
+
+ eleft = escape_html(left);
+ eright = escape_html(right);
+
+ pa_ioline_printf(line,
+ "<tr><td><b>%s</b></td>"
+ "<td>%s</td></tr>\n", eleft, eright);
+
+ pa_xfree(eleft);
+ pa_xfree(eright);
+}
+
+static void handle_root(struct connection *c) {
+ char *t;
+
+ pa_assert(c);
+
+ http_response(c, 200, "OK", MIME_HTML);
+
+ if (c->method == METHOD_HEAD) {
+ pa_ioline_defer_close(c->line);
+ return;
+ }
+
+ pa_ioline_puts(c->line,
+ HTML_HEADER(PACKAGE_NAME" "PACKAGE_VERSION)
+ "<h1>"PACKAGE_NAME" "PACKAGE_VERSION"</h1>\n"
+ "<table>\n");
+
+ t = pa_get_user_name_malloc();
+ html_print_field(c->line, "User Name:", t);
+ pa_xfree(t);
+
+ t = pa_get_host_name_malloc();
+ html_print_field(c->line, "Host name:", t);
+ pa_xfree(t);
+
+ t = pa_machine_id();
+ html_print_field(c->line, "Machine ID:", t);
+ pa_xfree(t);
+
+ t = pa_uname_string();
+ html_print_field(c->line, "System:", t);
+ pa_xfree(t);
+
+ t = pa_sprintf_malloc("%lu", (unsigned long) getpid());
+ html_print_field(c->line, "Process ID:", t);
+ pa_xfree(t);
+
+ pa_ioline_puts(c->line,
+ "</table>\n"
+ "<p><a href=\"" URL_STATUS "\">Show an extensive server status report</a></p>\n"
+ "<p><a href=\"" URL_LISTEN "\">Monitor sinks and sources</a></p>\n"
+ HTML_FOOTER);
+
+ pa_ioline_defer_close(c->line);
+}
+
+static void handle_css(struct connection *c) {
+ pa_assert(c);
+
+ http_response(c, 200, "OK", MIME_CSS);
+
+ if (c->method == METHOD_HEAD) {
+ pa_ioline_defer_close(c->line);
+ return;
+ }
+
+ pa_ioline_puts(c->line,
+ "body { color: black; background-color: white; }\n"
+ "a:link, a:visited { color: #900000; }\n"
+ "div.news-date { font-size: 80%; font-style: italic; }\n"
+ "pre { background-color: #f0f0f0; padding: 0.4cm; }\n"
+ ".grey { color: #8f8f8f; font-size: 80%; }"
+ "table { margin-left: 1cm; border:1px solid lightgrey; padding: 0.2cm; }\n"
+ "td { padding-left:10px; padding-right:10px; }\n");
+
+ pa_ioline_defer_close(c->line);
+}
+
+static void handle_status(struct connection *c) {
+ char *r;
+
+ pa_assert(c);
+
+ http_response(c, 200, "OK", MIME_TEXT);
+
+ if (c->method == METHOD_HEAD) {
+ pa_ioline_defer_close(c->line);
+ return;
+ }
+
+ r = pa_full_status_string(c->protocol->core);
+ pa_ioline_puts(c->line, r);
+ pa_xfree(r);
+
+ pa_ioline_defer_close(c->line);
+}
+
+static void handle_listen(struct connection *c) {
+ pa_source *source;
+ pa_sink *sink;
+ uint32_t idx;
+
+ http_response(c, 200, "OK", MIME_HTML);
+
+ pa_ioline_puts(c->line,
+ HTML_HEADER("Listen")
+ "<h2>Sinks</h2>\n"
+ "<p>\n");
+
+ if (c->method == METHOD_HEAD) {
+ pa_ioline_defer_close(c->line);
+ return;
+ }
+
+ PA_IDXSET_FOREACH(sink, c->protocol->core->sinks, idx) {
+ char *t, *m;
+
+ t = escape_html(pa_strna(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_DESCRIPTION)));
+ m = pa_sample_spec_to_mime_type_mimefy(&sink->sample_spec, &sink->channel_map);
+
+ pa_ioline_printf(c->line,
+ "<a href=\"" URL_LISTEN_SOURCE "%s\" title=\"%s\">%s</a><br/>\n",
+ sink->monitor_source->name, m, t);
+
+ pa_xfree(t);
+ pa_xfree(m);
+ }
+
+ pa_ioline_puts(c->line,
+ "</p>\n"
+ "<h2>Sources</h2>\n"
+ "<p>\n");
+
+ PA_IDXSET_FOREACH(source, c->protocol->core->sources, idx) {
+ char *t, *m;
+
+ if (source->monitor_of)
+ continue;
+
+ t = escape_html(pa_strna(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_DESCRIPTION)));
+ m = pa_sample_spec_to_mime_type_mimefy(&source->sample_spec, &source->channel_map);
+
+ pa_ioline_printf(c->line,
+ "<a href=\"" URL_LISTEN_SOURCE "%s\" title=\"%s\">%s</a><br/>\n",
+ source->name, m, t);
+
+ pa_xfree(m);
+ pa_xfree(t);
+
+ }
+
+ pa_ioline_puts(c->line,
+ "</p>\n"
+ HTML_FOOTER);
+
+ pa_ioline_defer_close(c->line);
+}
+
+static void line_drain_callback(pa_ioline *l, void *userdata) {
+ struct connection *c;
+
+ pa_assert(l);
+ pa_assert_se(c = userdata);
+
+ /* We don't need the line reader anymore, instead we need a real
+ * binary io channel */
+ pa_assert_se(c->io = pa_ioline_detach_iochannel(c->line));
+ pa_iochannel_set_callback(c->io, io_callback, c);
+
+ pa_iochannel_socket_set_sndbuf(c->io, pa_memblockq_get_length(c->output_memblockq));
+
+ pa_ioline_unref(c->line);
+ c->line = NULL;
+}
+
+static void handle_listen_prefix(struct connection *c, const char *source_name) {
+ pa_source *source;
+ pa_source_output_new_data data;
+ pa_sample_spec ss;
+ pa_channel_map cm;
+ char *t;
+ size_t l;
+
+ pa_assert(c);
+ pa_assert(source_name);
+
+ pa_assert(c->line);
+ pa_assert(!c->io);
+
+ if (!(source = pa_namereg_get(c->protocol->core, source_name, PA_NAMEREG_SOURCE))) {
+ html_response(c, 404, "Source not found", NULL);
+ return;
+ }
+
+ ss = source->sample_spec;
+ cm = source->channel_map;
+
+ pa_sample_spec_mimefy(&ss, &cm);
+
+ pa_source_output_new_data_init(&data);
+ data.driver = __FILE__;
+ data.module = c->module;
+ data.client = c->client;
+ pa_source_output_new_data_set_source(&data, source, false, true);
+ pa_proplist_update(data.proplist, PA_UPDATE_MERGE, c->client->proplist);
+ pa_source_output_new_data_set_sample_spec(&data, &ss);
+ pa_source_output_new_data_set_channel_map(&data, &cm);
+
+ pa_source_output_new(&c->source_output, c->protocol->core, &data);
+ pa_source_output_new_data_done(&data);
+
+ if (!c->source_output) {
+ html_response(c, 403, "Cannot create source output", NULL);
+ return;
+ }
+
+ c->source_output->parent.process_msg = source_output_process_msg;
+ c->source_output->push = source_output_push_cb;
+ c->source_output->kill = source_output_kill_cb;
+ c->source_output->get_latency = source_output_get_latency_cb;
+ c->source_output->userdata = c;
+
+ pa_source_output_set_requested_latency(c->source_output, DEFAULT_SOURCE_LATENCY);
+
+ l = (size_t) (pa_bytes_per_second(&ss)*RECORD_BUFFER_SECONDS);
+ c->output_memblockq = pa_memblockq_new(
+ "http protocol connection output_memblockq",
+ 0,
+ l,
+ 0,
+ &ss,
+ 1,
+ 0,
+ 0,
+ NULL);
+
+ pa_source_output_put(c->source_output);
+
+ t = pa_sample_spec_to_mime_type(&ss, &cm);
+ http_response(c, 200, "OK", t);
+ pa_xfree(t);
+
+ if (c->method == METHOD_HEAD) {
+ connection_unlink(c);
+ return;
+ }
+ pa_ioline_set_callback(c->line, NULL, NULL);
+
+ if (pa_ioline_is_drained(c->line))
+ line_drain_callback(c->line, c);
+ else
+ pa_ioline_set_drain_callback(c->line, line_drain_callback, c);
+}
+
+static void handle_url(struct connection *c) {
+ pa_assert(c);
+
+ pa_log_debug("Request for %s", c->url);
+
+ if (pa_streq(c->url, URL_ROOT))
+ handle_root(c);
+ else if (pa_streq(c->url, URL_CSS))
+ handle_css(c);
+ else if (pa_streq(c->url, URL_STATUS))
+ handle_status(c);
+ else if (pa_streq(c->url, URL_LISTEN))
+ handle_listen(c);
+ else if (pa_startswith(c->url, URL_LISTEN_SOURCE))
+ handle_listen_prefix(c, c->url + sizeof(URL_LISTEN_SOURCE)-1);
+ else
+ html_response(c, 404, "Not Found", NULL);
+}
+
+static void line_callback(pa_ioline *line, const char *s, void *userdata) {
+ struct connection *c = userdata;
+ pa_assert(line);
+ pa_assert(c);
+
+ if (!s) {
+ /* EOF */
+ connection_unlink(c);
+ return;
+ }
+
+ switch (c->state) {
+ case STATE_REQUEST_LINE: {
+ if (pa_startswith(s, "GET ")) {
+ c->method = METHOD_GET;
+ s +=4;
+ } else if (pa_startswith(s, "HEAD ")) {
+ c->method = METHOD_HEAD;
+ s +=5;
+ } else {
+ goto fail;
+ }
+
+ c->url = pa_xstrndup(s, strcspn(s, " \r\n\t?"));
+ c->state = STATE_MIME_HEADER;
+ break;
+ }
+
+ case STATE_MIME_HEADER: {
+
+ /* Ignore MIME headers */
+ if (strcspn(s, " \r\n") != 0)
+ break;
+
+ /* We're done */
+ c->state = STATE_DATA;
+
+ handle_url(c);
+ break;
+ }
+
+ default:
+ ;
+ }
+
+ return;
+
+fail:
+ html_response(c, 500, "Internal Server Error", NULL);
+}
+
+void pa_http_protocol_connect(pa_http_protocol *p, pa_iochannel *io, pa_module *m) {
+ struct connection *c;
+ pa_client_new_data client_data;
+ char pname[128];
+
+ pa_assert(p);
+ pa_assert(io);
+ pa_assert(m);
+
+ if (pa_idxset_size(p->connections)+1 > MAX_CONNECTIONS) {
+ pa_log("Warning! Too many connections (%u), dropping incoming connection.", MAX_CONNECTIONS);
+ pa_iochannel_free(io);
+ return;
+ }
+
+ c = pa_xnew0(struct connection, 1);
+ c->protocol = p;
+ c->state = STATE_REQUEST_LINE;
+ c->module = m;
+
+ c->line = pa_ioline_new(io);
+ pa_ioline_set_callback(c->line, line_callback, c);
+
+ pa_client_new_data_init(&client_data);
+ client_data.module = c->module;
+ client_data.driver = __FILE__;
+ pa_iochannel_socket_peer_to_string(io, pname, sizeof(pname));
+ pa_proplist_setf(client_data.proplist, PA_PROP_APPLICATION_NAME, "HTTP client (%s)", pname);
+ pa_proplist_sets(client_data.proplist, "http-protocol.peer", pname);
+ c->client = pa_client_new(p->core, &client_data);
+ pa_client_new_data_done(&client_data);
+
+ if (!c->client)
+ goto fail;
+
+ c->client->kill = client_kill_cb;
+ c->client->userdata = c;
+
+ pa_idxset_put(p->connections, c, NULL);
+
+ return;
+
+fail:
+ if (c)
+ connection_unlink(c);
+}
+
+void pa_http_protocol_disconnect(pa_http_protocol *p, pa_module *m) {
+ struct connection *c;
+ uint32_t idx;
+
+ pa_assert(p);
+ pa_assert(m);
+
+ PA_IDXSET_FOREACH(c, p->connections, idx)
+ if (c->module == m)
+ connection_unlink(c);
+}
+
+static pa_http_protocol* http_protocol_new(pa_core *c) {
+ pa_http_protocol *p;
+
+ pa_assert(c);
+
+ p = pa_xnew0(pa_http_protocol, 1);
+ PA_REFCNT_INIT(p);
+ p->core = c;
+ p->connections = pa_idxset_new(NULL, NULL);
+
+ pa_assert_se(pa_shared_set(c, "http-protocol", p) >= 0);
+
+ return p;
+}
+
+pa_http_protocol* pa_http_protocol_get(pa_core *c) {
+ pa_http_protocol *p;
+
+ if ((p = pa_shared_get(c, "http-protocol")))
+ return pa_http_protocol_ref(p);
+
+ return http_protocol_new(c);
+}
+
+pa_http_protocol* pa_http_protocol_ref(pa_http_protocol *p) {
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) >= 1);
+
+ PA_REFCNT_INC(p);
+
+ return p;
+}
+
+void pa_http_protocol_unref(pa_http_protocol *p) {
+ struct connection *c;
+
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) >= 1);
+
+ if (PA_REFCNT_DEC(p) > 0)
+ return;
+
+ while ((c = pa_idxset_first(p->connections, NULL)))
+ connection_unlink(c);
+
+ pa_idxset_free(p->connections, NULL);
+
+ pa_strlist_free(p->servers);
+
+ pa_assert_se(pa_shared_remove(p->core, "http-protocol") >= 0);
+
+ pa_xfree(p);
+}
+
+void pa_http_protocol_add_server_string(pa_http_protocol *p, const char *name) {
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) >= 1);
+ pa_assert(name);
+
+ p->servers = pa_strlist_prepend(p->servers, name);
+}
+
+void pa_http_protocol_remove_server_string(pa_http_protocol *p, const char *name) {
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) >= 1);
+ pa_assert(name);
+
+ p->servers = pa_strlist_remove(p->servers, name);
+}
+
+pa_strlist *pa_http_protocol_servers(pa_http_protocol *p) {
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) >= 1);
+
+ return p->servers;
+}
diff --git a/src/pulsecore/protocol-http.h b/src/pulsecore/protocol-http.h
new file mode 100644
index 0000000..89a6518
--- /dev/null
+++ b/src/pulsecore/protocol-http.h
@@ -0,0 +1,41 @@
+#ifndef fooprotocolhttphfoo
+#define fooprotocolhttphfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2005-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulsecore/core.h>
+#include <pulsecore/module.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/iochannel.h>
+#include <pulsecore/strlist.h>
+
+typedef struct pa_http_protocol pa_http_protocol;
+
+pa_http_protocol* pa_http_protocol_get(pa_core *core);
+pa_http_protocol* pa_http_protocol_ref(pa_http_protocol *p);
+void pa_http_protocol_unref(pa_http_protocol *p);
+void pa_http_protocol_connect(pa_http_protocol *p, pa_iochannel *io, pa_module *m);
+void pa_http_protocol_disconnect(pa_http_protocol *p, pa_module *m);
+
+void pa_http_protocol_add_server_string(pa_http_protocol *p, const char *name);
+void pa_http_protocol_remove_server_string(pa_http_protocol *p, const char *name);
+pa_strlist *pa_http_protocol_servers(pa_http_protocol *p);
+
+#endif
diff --git a/src/pulsecore/protocol-native.c b/src/pulsecore/protocol-native.c
new file mode 100644
index 0000000..e8559b2
--- /dev/null
+++ b/src/pulsecore/protocol-native.c
@@ -0,0 +1,5514 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <pulse/rtclock.h>
+#include <pulse/timeval.h>
+#include <pulse/version.h>
+#include <pulse/utf8.h>
+#include <pulse/util.h>
+#include <pulse/xmalloc.h>
+#include <pulse/internal.h>
+
+#include <pulsecore/native-common.h>
+#include <pulsecore/packet.h>
+#include <pulsecore/client.h>
+#include <pulsecore/source-output.h>
+#include <pulsecore/sink-input.h>
+#include <pulsecore/pstream.h>
+#include <pulsecore/tagstruct.h>
+#include <pulsecore/pdispatch.h>
+#include <pulsecore/pstream-util.h>
+#include <pulsecore/namereg.h>
+#include <pulsecore/core-scache.h>
+#include <pulsecore/core-subscribe.h>
+#include <pulsecore/log.h>
+#include <pulsecore/mem.h>
+#include <pulsecore/strlist.h>
+#include <pulsecore/shared.h>
+#include <pulsecore/sample-util.h>
+#include <pulsecore/creds.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/ipacl.h>
+#include <pulsecore/thread-mq.h>
+#include <pulsecore/mem.h>
+
+#include "protocol-native.h"
+
+/* #define PROTOCOL_NATIVE_DEBUG */
+
+/* Kick a client if it doesn't authenticate within this time */
+#define AUTH_TIMEOUT (60 * PA_USEC_PER_SEC)
+
+/* Don't accept more connection than this */
+#define MAX_CONNECTIONS 64
+
+#define MAX_MEMBLOCKQ_LENGTH (4*1024*1024) /* 4MB */
+#define DEFAULT_TLENGTH_MSEC 2000 /* 2s */
+#define DEFAULT_PROCESS_MSEC 20 /* 20ms */
+#define DEFAULT_FRAGSIZE_MSEC DEFAULT_TLENGTH_MSEC
+
+struct pa_native_protocol;
+
+typedef struct record_stream {
+ pa_msgobject parent;
+
+ pa_native_connection *connection;
+ uint32_t index;
+
+ pa_source_output *source_output;
+ pa_memblockq *memblockq;
+
+ bool adjust_latency:1;
+ bool early_requests:1;
+
+ /* Requested buffer attributes */
+ pa_buffer_attr buffer_attr_req;
+ /* Fixed-up and adjusted buffer attributes */
+ pa_buffer_attr buffer_attr;
+
+ pa_atomic_t on_the_fly;
+ pa_usec_t configured_source_latency;
+ size_t drop_initial;
+
+ /* Only updated after SOURCE_OUTPUT_MESSAGE_UPDATE_LATENCY */
+ size_t on_the_fly_snapshot;
+ pa_usec_t current_monitor_latency;
+ pa_usec_t current_source_latency;
+} record_stream;
+
+#define RECORD_STREAM(o) (record_stream_cast(o))
+PA_DEFINE_PRIVATE_CLASS(record_stream, pa_msgobject);
+
+typedef struct output_stream {
+ pa_msgobject parent;
+} output_stream;
+
+#define OUTPUT_STREAM(o) (output_stream_cast(o))
+PA_DEFINE_PRIVATE_CLASS(output_stream, pa_msgobject);
+
+typedef struct playback_stream {
+ output_stream parent;
+
+ pa_native_connection *connection;
+ uint32_t index;
+
+ pa_sink_input *sink_input;
+ pa_memblockq *memblockq;
+
+ bool adjust_latency:1;
+ bool early_requests:1;
+
+ bool is_underrun:1;
+ bool drain_request:1;
+ uint32_t drain_tag;
+ uint32_t syncid;
+
+ /* Optimization to avoid too many rewinds with a lot of small blocks */
+ pa_atomic_t seek_or_post_in_queue;
+ int64_t seek_windex;
+
+ pa_atomic_t missing;
+ pa_usec_t configured_sink_latency;
+ /* Requested buffer attributes */
+ pa_buffer_attr buffer_attr_req;
+ /* Fixed-up and adjusted buffer attributes */
+ pa_buffer_attr buffer_attr;
+
+ /* Only updated after SINK_INPUT_MESSAGE_UPDATE_LATENCY */
+ int64_t read_index, write_index;
+ size_t render_memblockq_length;
+ pa_usec_t current_sink_latency;
+ uint64_t playing_for, underrun_for;
+} playback_stream;
+
+#define PLAYBACK_STREAM(o) (playback_stream_cast(o))
+PA_DEFINE_PRIVATE_CLASS(playback_stream, output_stream);
+
+typedef struct upload_stream {
+ output_stream parent;
+
+ pa_native_connection *connection;
+ uint32_t index;
+
+ pa_memchunk memchunk;
+ size_t length;
+ char *name;
+ pa_sample_spec sample_spec;
+ pa_channel_map channel_map;
+ pa_proplist *proplist;
+} upload_stream;
+
+#define UPLOAD_STREAM(o) (upload_stream_cast(o))
+PA_DEFINE_PRIVATE_CLASS(upload_stream, output_stream);
+
+struct pa_native_connection {
+ pa_msgobject parent;
+ pa_native_protocol *protocol;
+ pa_native_options *options;
+ bool authorized:1;
+ bool is_local:1;
+ uint32_t version;
+ pa_client *client;
+ /* R/W mempool, one per client connection, for srbchannel transport.
+ * Both server and client can write to this shm area.
+ *
+ * Note: This will be NULL if our connection with the client does
+ * not support srbchannels */
+ pa_mempool *rw_mempool;
+ pa_pstream *pstream;
+ pa_pdispatch *pdispatch;
+ pa_idxset *record_streams, *output_streams;
+ uint32_t rrobin_index;
+ pa_subscription *subscription;
+ pa_time_event *auth_timeout_event;
+ pa_srbchannel *srbpending;
+};
+
+#define PA_NATIVE_CONNECTION(o) (pa_native_connection_cast(o))
+PA_DEFINE_PRIVATE_CLASS(pa_native_connection, pa_msgobject);
+
+struct pa_native_protocol {
+ PA_REFCNT_DECLARE;
+
+ pa_core *core;
+ pa_idxset *connections;
+
+ pa_strlist *servers;
+ pa_hook hooks[PA_NATIVE_HOOK_MAX];
+
+ pa_hashmap *extensions;
+};
+
+enum {
+ SOURCE_OUTPUT_MESSAGE_UPDATE_LATENCY = PA_SOURCE_OUTPUT_MESSAGE_MAX
+};
+
+enum {
+ SINK_INPUT_MESSAGE_POST_DATA = PA_SINK_INPUT_MESSAGE_MAX, /* data from main loop to sink input */
+ SINK_INPUT_MESSAGE_DRAIN, /* disabled prebuf, get playback started. */
+ SINK_INPUT_MESSAGE_FLUSH,
+ SINK_INPUT_MESSAGE_TRIGGER,
+ SINK_INPUT_MESSAGE_SEEK,
+ SINK_INPUT_MESSAGE_PREBUF_FORCE,
+ SINK_INPUT_MESSAGE_UPDATE_LATENCY,
+ SINK_INPUT_MESSAGE_UPDATE_BUFFER_ATTR
+};
+
+enum {
+ PLAYBACK_STREAM_MESSAGE_REQUEST_DATA, /* data requested from sink input from the main loop */
+ PLAYBACK_STREAM_MESSAGE_UNDERFLOW,
+ PLAYBACK_STREAM_MESSAGE_OVERFLOW,
+ PLAYBACK_STREAM_MESSAGE_DRAIN_ACK,
+ PLAYBACK_STREAM_MESSAGE_STARTED,
+ PLAYBACK_STREAM_MESSAGE_UPDATE_TLENGTH
+};
+
+enum {
+ RECORD_STREAM_MESSAGE_POST_DATA /* data from source output to main loop */
+};
+
+enum {
+ CONNECTION_MESSAGE_RELEASE,
+ CONNECTION_MESSAGE_REVOKE
+};
+
+static bool sink_input_process_underrun_cb(pa_sink_input *i);
+static int sink_input_pop_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk);
+static void sink_input_kill_cb(pa_sink_input *i);
+static void sink_input_suspend_cb(pa_sink_input *i, pa_sink_state_t old_state, pa_suspend_cause_t old_suspend_cause);
+static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest);
+static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes);
+static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes);
+static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes);
+static void sink_input_send_event_cb(pa_sink_input *i, const char *event, pa_proplist *pl);
+
+static void native_connection_send_memblock(pa_native_connection *c);
+static void playback_stream_request_bytes(struct playback_stream*s);
+
+static void source_output_kill_cb(pa_source_output *o);
+static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk);
+static void source_output_suspend_cb(pa_source_output *o, pa_source_state_t old_state, pa_suspend_cause_t old_suspend_cause);
+static void source_output_moving_cb(pa_source_output *o, pa_source *dest);
+static pa_usec_t source_output_get_latency_cb(pa_source_output *o);
+static void source_output_send_event_cb(pa_source_output *o, const char *event, pa_proplist *pl);
+
+static int sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk);
+static int source_output_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk);
+
+/* structure management */
+
+/* Called from main context */
+static void upload_stream_unlink(upload_stream *s) {
+ pa_assert(s);
+
+ if (!s->connection)
+ return;
+
+ pa_assert_se(pa_idxset_remove_by_data(s->connection->output_streams, s, NULL) == s);
+ s->connection = NULL;
+ upload_stream_unref(s);
+}
+
+/* Called from main context */
+static void upload_stream_free(pa_object *o) {
+ upload_stream *s = UPLOAD_STREAM(o);
+ pa_assert(s);
+
+ upload_stream_unlink(s);
+
+ pa_xfree(s->name);
+
+ if (s->proplist)
+ pa_proplist_free(s->proplist);
+
+ if (s->memchunk.memblock)
+ pa_memblock_unref(s->memchunk.memblock);
+
+ pa_xfree(s);
+}
+
+/* Called from main context */
+static upload_stream* upload_stream_new(
+ pa_native_connection *c,
+ const pa_sample_spec *ss,
+ const pa_channel_map *map,
+ const char *name,
+ size_t length,
+ pa_proplist *p) {
+
+ upload_stream *s;
+
+ pa_assert(c);
+ pa_assert(ss);
+ pa_assert(name);
+ pa_assert(length > 0);
+ pa_assert(p);
+
+ s = pa_msgobject_new(upload_stream);
+ s->parent.parent.parent.free = upload_stream_free;
+ s->connection = c;
+ s->sample_spec = *ss;
+ s->channel_map = *map;
+ s->name = pa_xstrdup(name);
+ pa_memchunk_reset(&s->memchunk);
+ s->length = length;
+ s->proplist = pa_proplist_copy(p);
+ pa_proplist_update(s->proplist, PA_UPDATE_MERGE, c->client->proplist);
+
+ pa_idxset_put(c->output_streams, s, &s->index);
+
+ return s;
+}
+
+/* Called from main context */
+static void record_stream_unlink(record_stream *s) {
+ pa_assert(s);
+
+ if (!s->connection)
+ return;
+
+ if (s->source_output) {
+ pa_source_output_unlink(s->source_output);
+ pa_source_output_unref(s->source_output);
+ s->source_output = NULL;
+ }
+
+ pa_assert_se(pa_idxset_remove_by_data(s->connection->record_streams, s, NULL) == s);
+ s->connection = NULL;
+ record_stream_unref(s);
+}
+
+/* Called from main context */
+static void record_stream_free(pa_object *o) {
+ record_stream *s = RECORD_STREAM(o);
+ pa_assert(s);
+
+ record_stream_unlink(s);
+
+ pa_memblockq_free(s->memblockq);
+ pa_xfree(s);
+}
+
+/* Called from main context */
+static int record_stream_process_msg(pa_msgobject *o, int code, void*userdata, int64_t offset, pa_memchunk *chunk) {
+ record_stream *s = RECORD_STREAM(o);
+ record_stream_assert_ref(s);
+
+ if (!s->connection)
+ return -1;
+
+ switch (code) {
+
+ case RECORD_STREAM_MESSAGE_POST_DATA:
+
+ /* We try to keep up to date with how many bytes are
+ * currently on the fly */
+ pa_atomic_sub(&s->on_the_fly, chunk->length);
+
+ if (pa_memblockq_push_align(s->memblockq, chunk) < 0) {
+/* pa_log_warn("Failed to push data into output queue."); */
+ return -1;
+ }
+
+ if (!pa_pstream_is_pending(s->connection->pstream))
+ native_connection_send_memblock(s->connection);
+
+ break;
+ }
+
+ return 0;
+}
+
+/* Called from main context */
+static void fix_record_buffer_attr_pre(record_stream *s) {
+
+ size_t frame_size;
+ pa_usec_t orig_fragsize_usec, fragsize_usec, source_usec;
+
+ pa_assert(s);
+
+ /* This function will be called from the main thread, before as
+ * well as after the source output has been activated using
+ * pa_source_output_put()! That means it may not touch any
+ * ->thread_info data! */
+
+ frame_size = pa_frame_size(&s->source_output->sample_spec);
+ s->buffer_attr = s->buffer_attr_req;
+
+ if (s->buffer_attr.maxlength == (uint32_t) -1 || s->buffer_attr.maxlength > MAX_MEMBLOCKQ_LENGTH)
+ s->buffer_attr.maxlength = MAX_MEMBLOCKQ_LENGTH;
+ if (s->buffer_attr.maxlength <= 0)
+ s->buffer_attr.maxlength = (uint32_t) frame_size;
+
+ if (s->buffer_attr.fragsize == (uint32_t) -1)
+ s->buffer_attr.fragsize = (uint32_t) pa_usec_to_bytes(DEFAULT_FRAGSIZE_MSEC*PA_USEC_PER_MSEC, &s->source_output->sample_spec);
+ if (s->buffer_attr.fragsize <= 0)
+ s->buffer_attr.fragsize = (uint32_t) frame_size;
+
+ orig_fragsize_usec = fragsize_usec = pa_bytes_to_usec(s->buffer_attr.fragsize, &s->source_output->sample_spec);
+
+ if (s->early_requests) {
+
+ /* In early request mode we need to emulate the classic
+ * fragment-based playback model. Unfortunately we have no
+ * mechanism to tell the source how often we want it to send us
+ * data. The next best thing we can do is to set the source's
+ * total buffer (i.e. its latency) to the fragment size. That
+ * way it will have to send data at least that often. */
+
+ source_usec = fragsize_usec;
+
+ } else if (s->adjust_latency) {
+
+ /* So, the user asked us to adjust the latency according to
+ * what the source can provide. We set the source to whatever
+ * latency it can provide that is closest to what we want, and
+ * let the client buffer be equally large. This does NOT mean
+ * that we are doing (2 * fragsize) bytes of buffering, since
+ * the client-side buffer is only data that is on the way to
+ * the client. */
+
+ source_usec = fragsize_usec;
+
+ } else {
+
+ /* Ok, the user didn't ask us to adjust the latency, hence we
+ * don't */
+
+ source_usec = (pa_usec_t) -1;
+ }
+
+ if (source_usec != (pa_usec_t) -1)
+ s->configured_source_latency = pa_source_output_set_requested_latency(s->source_output, source_usec);
+ else
+ s->configured_source_latency = 0;
+
+ if (s->early_requests) {
+
+ /* Ok, we didn't necessarily get what we were asking for. We
+ * might still get the proper fragment interval, we just can't
+ * guarantee it. */
+
+ if (fragsize_usec != s->configured_source_latency)
+ pa_log_debug("Could not configure a sufficiently low latency. Early requests might not be satisfied.");
+
+ } else if (s->adjust_latency) {
+
+ /* We keep the client buffer large enough to transfer one
+ * hardware-buffer-sized chunk at a time to the client. */
+
+ fragsize_usec = s->configured_source_latency;
+ }
+
+ if (pa_usec_to_bytes(orig_fragsize_usec, &s->source_output->sample_spec) !=
+ pa_usec_to_bytes(fragsize_usec, &s->source_output->sample_spec))
+
+ s->buffer_attr.fragsize = (uint32_t) pa_usec_to_bytes(fragsize_usec, &s->source_output->sample_spec);
+
+ if (s->buffer_attr.fragsize <= 0)
+ s->buffer_attr.fragsize = (uint32_t) frame_size;
+}
+
+/* Called from main context */
+static void fix_record_buffer_attr_post(record_stream *s) {
+ size_t base;
+
+ pa_assert(s);
+
+ /* This function will be called from the main thread, before as
+ * well as after the source output has been activated using
+ * pa_source_output_put()! That means it may not touch and
+ * ->thread_info data! */
+
+ base = pa_frame_size(&s->source_output->sample_spec);
+
+ s->buffer_attr.fragsize = (s->buffer_attr.fragsize/base)*base;
+ if (s->buffer_attr.fragsize <= 0)
+ s->buffer_attr.fragsize = base;
+
+ if (s->buffer_attr.fragsize > s->buffer_attr.maxlength)
+ s->buffer_attr.fragsize = s->buffer_attr.maxlength;
+}
+
+/* Called from main context */
+static record_stream* record_stream_new(
+ pa_native_connection *c,
+ pa_source *source,
+ pa_sample_spec *ss,
+ pa_channel_map *map,
+ pa_idxset *formats,
+ pa_buffer_attr *attr,
+ pa_cvolume *volume,
+ bool muted,
+ bool muted_set,
+ pa_source_output_flags_t flags,
+ pa_proplist *p,
+ bool adjust_latency,
+ bool early_requests,
+ bool relative_volume,
+ bool peak_detect,
+ pa_sink_input *direct_on_input,
+ int *ret) {
+
+ /* Note: This function takes ownership of the 'formats' param, so we need
+ * to take extra care to not leak it */
+
+ record_stream *s;
+ pa_source_output *source_output = NULL;
+ pa_source_output_new_data data;
+ char *memblockq_name;
+
+ pa_assert(c);
+ pa_assert(ss);
+ pa_assert(p);
+ pa_assert(ret);
+
+ pa_source_output_new_data_init(&data);
+
+ pa_proplist_update(data.proplist, PA_UPDATE_REPLACE, p);
+ data.driver = __FILE__;
+ data.module = c->options->module;
+ data.client = c->client;
+ if (source)
+ pa_source_output_new_data_set_source(&data, source, false, true);
+ if (pa_sample_spec_valid(ss))
+ pa_source_output_new_data_set_sample_spec(&data, ss);
+ if (pa_channel_map_valid(map))
+ pa_source_output_new_data_set_channel_map(&data, map);
+ if (formats)
+ pa_source_output_new_data_set_formats(&data, formats);
+ data.direct_on_input = direct_on_input;
+ if (volume) {
+ pa_source_output_new_data_set_volume(&data, volume);
+ data.volume_is_absolute = !relative_volume;
+ data.save_volume = false;
+ }
+ if (muted_set) {
+ pa_source_output_new_data_set_muted(&data, muted);
+ data.save_muted = false;
+ }
+ if (peak_detect)
+ data.resample_method = PA_RESAMPLER_PEAKS;
+ data.flags = flags;
+
+ *ret = -pa_source_output_new(&source_output, c->protocol->core, &data);
+
+ pa_source_output_new_data_done(&data);
+
+ if (!source_output)
+ return NULL;
+
+ s = pa_msgobject_new(record_stream);
+ s->parent.parent.free = record_stream_free;
+ s->parent.process_msg = record_stream_process_msg;
+ s->connection = c;
+ s->source_output = source_output;
+ s->buffer_attr_req = *attr;
+ s->adjust_latency = adjust_latency;
+ s->early_requests = early_requests;
+ pa_atomic_store(&s->on_the_fly, 0);
+
+ s->source_output->parent.process_msg = source_output_process_msg;
+ s->source_output->push = source_output_push_cb;
+ s->source_output->kill = source_output_kill_cb;
+ s->source_output->get_latency = source_output_get_latency_cb;
+ s->source_output->moving = source_output_moving_cb;
+ s->source_output->suspend = source_output_suspend_cb;
+ s->source_output->send_event = source_output_send_event_cb;
+ s->source_output->userdata = s;
+
+ fix_record_buffer_attr_pre(s);
+
+ memblockq_name = pa_sprintf_malloc("native protocol record stream memblockq [%u]", s->source_output->index);
+ s->memblockq = pa_memblockq_new(
+ memblockq_name,
+ 0,
+ s->buffer_attr.maxlength,
+ 0,
+ &source_output->sample_spec,
+ 1,
+ 0,
+ 0,
+ NULL);
+ pa_xfree(memblockq_name);
+
+ pa_memblockq_get_attr(s->memblockq, &s->buffer_attr);
+ fix_record_buffer_attr_post(s);
+
+ *ss = s->source_output->sample_spec;
+ *map = s->source_output->channel_map;
+
+ pa_idxset_put(c->record_streams, s, &s->index);
+
+ pa_log_info("Final latency %0.2f ms = %0.2f ms + %0.2f ms",
+ ((double) pa_bytes_to_usec(s->buffer_attr.fragsize, &source_output->sample_spec) + (double) s->configured_source_latency) / PA_USEC_PER_MSEC,
+ (double) pa_bytes_to_usec(s->buffer_attr.fragsize, &source_output->sample_spec) / PA_USEC_PER_MSEC,
+ (double) s->configured_source_latency / PA_USEC_PER_MSEC);
+
+ pa_source_output_put(s->source_output);
+ return s;
+}
+
+/* Called from main context */
+static void record_stream_send_killed(record_stream *r) {
+ pa_tagstruct *t;
+ record_stream_assert_ref(r);
+
+ t = pa_tagstruct_new();
+ pa_tagstruct_putu32(t, PA_COMMAND_RECORD_STREAM_KILLED);
+ pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */
+ pa_tagstruct_putu32(t, r->index);
+ pa_pstream_send_tagstruct(r->connection->pstream, t);
+}
+
+/* Called from main context */
+static void playback_stream_unlink(playback_stream *s) {
+ pa_assert(s);
+
+ if (!s->connection)
+ return;
+
+ if (s->sink_input) {
+ pa_sink_input_unlink(s->sink_input);
+ pa_sink_input_unref(s->sink_input);
+ s->sink_input = NULL;
+ }
+
+ if (s->drain_request)
+ pa_pstream_send_error(s->connection->pstream, s->drain_tag, PA_ERR_NOENTITY);
+
+ pa_assert_se(pa_idxset_remove_by_data(s->connection->output_streams, s, NULL) == s);
+ s->connection = NULL;
+ playback_stream_unref(s);
+}
+
+/* Called from main context */
+static void playback_stream_free(pa_object* o) {
+ playback_stream *s = PLAYBACK_STREAM(o);
+ pa_assert(s);
+
+ playback_stream_unlink(s);
+
+ pa_memblockq_free(s->memblockq);
+ pa_xfree(s);
+}
+
+/* Called from main context */
+static int playback_stream_process_msg(pa_msgobject *o, int code, void*userdata, int64_t offset, pa_memchunk *chunk) {
+ playback_stream *s = PLAYBACK_STREAM(o);
+ playback_stream_assert_ref(s);
+
+ if (!s->connection)
+ return -1;
+
+ switch (code) {
+
+ case PLAYBACK_STREAM_MESSAGE_REQUEST_DATA: {
+ pa_tagstruct *t;
+ int l = 0;
+
+ for (;;) {
+ if ((l = pa_atomic_load(&s->missing)) <= 0)
+ return 0;
+
+ if (pa_atomic_cmpxchg(&s->missing, l, 0))
+ break;
+ }
+
+ t = pa_tagstruct_new();
+ pa_tagstruct_putu32(t, PA_COMMAND_REQUEST);
+ pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */
+ pa_tagstruct_putu32(t, s->index);
+ pa_tagstruct_putu32(t, (uint32_t) l);
+ pa_pstream_send_tagstruct(s->connection->pstream, t);
+
+#ifdef PROTOCOL_NATIVE_DEBUG
+ pa_log("Requesting %lu bytes", (unsigned long) l);
+#endif
+ break;
+ }
+
+ case PLAYBACK_STREAM_MESSAGE_UNDERFLOW: {
+ pa_tagstruct *t;
+
+#ifdef PROTOCOL_NATIVE_DEBUG
+ pa_log("signalling underflow");
+#endif
+
+ /* Report that we're empty */
+ t = pa_tagstruct_new();
+ pa_tagstruct_putu32(t, PA_COMMAND_UNDERFLOW);
+ pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */
+ pa_tagstruct_putu32(t, s->index);
+ if (s->connection->version >= 23)
+ pa_tagstruct_puts64(t, offset);
+ pa_pstream_send_tagstruct(s->connection->pstream, t);
+ break;
+ }
+
+ case PLAYBACK_STREAM_MESSAGE_OVERFLOW: {
+ pa_tagstruct *t;
+
+ /* Notify the user we're overflowed*/
+ t = pa_tagstruct_new();
+ pa_tagstruct_putu32(t, PA_COMMAND_OVERFLOW);
+ pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */
+ pa_tagstruct_putu32(t, s->index);
+ pa_pstream_send_tagstruct(s->connection->pstream, t);
+ break;
+ }
+
+ case PLAYBACK_STREAM_MESSAGE_STARTED:
+
+ if (s->connection->version >= 13) {
+ pa_tagstruct *t;
+
+ /* Notify the user we started playback */
+ t = pa_tagstruct_new();
+ pa_tagstruct_putu32(t, PA_COMMAND_STARTED);
+ pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */
+ pa_tagstruct_putu32(t, s->index);
+ pa_pstream_send_tagstruct(s->connection->pstream, t);
+ }
+
+ break;
+
+ case PLAYBACK_STREAM_MESSAGE_DRAIN_ACK:
+ pa_pstream_send_simple_ack(s->connection->pstream, PA_PTR_TO_UINT(userdata));
+ break;
+
+ case PLAYBACK_STREAM_MESSAGE_UPDATE_TLENGTH:
+
+ s->buffer_attr.tlength = (uint32_t) offset;
+
+ if (s->connection->version >= 15) {
+ pa_tagstruct *t;
+
+ t = pa_tagstruct_new();
+ pa_tagstruct_putu32(t, PA_COMMAND_PLAYBACK_BUFFER_ATTR_CHANGED);
+ pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */
+ pa_tagstruct_putu32(t, s->index);
+ pa_tagstruct_putu32(t, s->buffer_attr.maxlength);
+ pa_tagstruct_putu32(t, s->buffer_attr.tlength);
+ pa_tagstruct_putu32(t, s->buffer_attr.prebuf);
+ pa_tagstruct_putu32(t, s->buffer_attr.minreq);
+ pa_tagstruct_put_usec(t, s->configured_sink_latency);
+ pa_pstream_send_tagstruct(s->connection->pstream, t);
+ }
+
+ break;
+ }
+
+ return 0;
+}
+
+/* Called from main context */
+static void fix_playback_buffer_attr(playback_stream *s) {
+ size_t frame_size, max_prebuf;
+ pa_usec_t orig_tlength_usec, tlength_usec, orig_minreq_usec, minreq_usec, sink_usec;
+
+ pa_assert(s);
+
+#ifdef PROTOCOL_NATIVE_DEBUG
+ pa_log("Client requested: maxlength=%li bytes tlength=%li bytes minreq=%li bytes prebuf=%li bytes",
+ (long) s->buffer_attr_req.maxlength,
+ (long) s->buffer_attr_req.tlength,
+ (long) s->buffer_attr_req.minreq,
+ (long) s->buffer_attr_req.prebuf);
+
+ pa_log("Client requested: maxlength=%lu ms tlength=%lu ms minreq=%lu ms prebuf=%lu ms",
+ (unsigned long) (pa_bytes_to_usec(s->buffer_attr_req.maxlength, &s->sink_input->sample_spec) / PA_USEC_PER_MSEC),
+ (unsigned long) (pa_bytes_to_usec(s->buffer_attr_req.tlength, &s->sink_input->sample_spec) / PA_USEC_PER_MSEC),
+ (unsigned long) (pa_bytes_to_usec(s->buffer_attr_req.minreq, &s->sink_input->sample_spec) / PA_USEC_PER_MSEC),
+ (unsigned long) (pa_bytes_to_usec(s->buffer_attr_req.prebuf, &s->sink_input->sample_spec) / PA_USEC_PER_MSEC));
+#endif
+
+ /* This function will be called from the main thread, before as
+ * well as after the sink input has been activated using
+ * pa_sink_input_put()! That means it may not touch any
+ * ->thread_info data, such as the memblockq! */
+
+ frame_size = pa_frame_size(&s->sink_input->sample_spec);
+ s->buffer_attr = s->buffer_attr_req;
+
+ if (s->buffer_attr.maxlength == (uint32_t) -1 || s->buffer_attr.maxlength > MAX_MEMBLOCKQ_LENGTH)
+ s->buffer_attr.maxlength = MAX_MEMBLOCKQ_LENGTH;
+ if (s->buffer_attr.maxlength <= 0)
+ s->buffer_attr.maxlength = (uint32_t) frame_size;
+
+ if (s->buffer_attr.tlength == (uint32_t) -1)
+ s->buffer_attr.tlength = (uint32_t) pa_usec_to_bytes_round_up(DEFAULT_TLENGTH_MSEC*PA_USEC_PER_MSEC, &s->sink_input->sample_spec);
+ if (s->buffer_attr.tlength <= 0)
+ s->buffer_attr.tlength = (uint32_t) frame_size;
+ if (s->buffer_attr.tlength > s->buffer_attr.maxlength)
+ s->buffer_attr.tlength = s->buffer_attr.maxlength;
+
+ if (s->buffer_attr.minreq == (uint32_t) -1) {
+ uint32_t process = (uint32_t) pa_usec_to_bytes_round_up(DEFAULT_PROCESS_MSEC*PA_USEC_PER_MSEC, &s->sink_input->sample_spec);
+ /* With low-latency, tlength/4 gives a decent default in all of traditional, adjust latency and early request modes. */
+ uint32_t m = s->buffer_attr.tlength / 4;
+ if (frame_size)
+ m -= m % frame_size;
+ s->buffer_attr.minreq = PA_MIN(process, m);
+ }
+ if (s->buffer_attr.minreq <= 0)
+ s->buffer_attr.minreq = (uint32_t) frame_size;
+
+ if (s->buffer_attr.tlength < s->buffer_attr.minreq+frame_size)
+ s->buffer_attr.tlength = s->buffer_attr.minreq+(uint32_t) frame_size;
+
+ orig_tlength_usec = tlength_usec = pa_bytes_to_usec(s->buffer_attr.tlength, &s->sink_input->sample_spec);
+ orig_minreq_usec = minreq_usec = pa_bytes_to_usec(s->buffer_attr.minreq, &s->sink_input->sample_spec);
+
+ pa_log_info("Requested tlength=%0.2f ms, minreq=%0.2f ms",
+ (double) tlength_usec / PA_USEC_PER_MSEC,
+ (double) minreq_usec / PA_USEC_PER_MSEC);
+
+ if (s->early_requests) {
+
+ /* In early request mode we need to emulate the classic
+ * fragment-based playback model. Unfortunately we have no
+ * mechanism to tell the sink how often we want to be queried
+ * for data. The next best thing we can do is to set the sink's
+ * total buffer (i.e. its latency) to the fragment size. That
+ * way it will have to query us at least that often. */
+
+ sink_usec = minreq_usec;
+ pa_log_debug("Early requests mode enabled, configuring sink latency to minreq.");
+
+ } else if (s->adjust_latency) {
+
+ /* So, the user asked us to adjust the latency of the stream
+ * buffer according to the what the sink can provide. The
+ * tlength passed in shall be the overall latency. Roughly
+ * half the latency will be spent on the hw buffer, the other
+ * half of it in the async buffer queue we maintain for each
+ * client. In between we'll have a safety space of size
+ * 2*minreq. Why the 2*minreq? When the hw buffer is completely
+ * empty and needs to be filled, then our buffer must have
+ * enough data to fulfill this request immediately and thus
+ * have at least the same tlength as the size of the hw
+ * buffer. It additionally needs space for 2 times minreq
+ * because if the buffer ran empty and a partial fillup
+ * happens immediately on the next iteration we need to be
+ * able to fulfill it and give the application also minreq
+ * time to fill it up again for the next request Makes 2 times
+ * minreq in plus.. */
+
+ if (tlength_usec > minreq_usec*2)
+ sink_usec = (tlength_usec - minreq_usec*2)/2;
+ else
+ sink_usec = 0;
+
+ pa_log_debug("Adjust latency mode enabled, configuring sink latency to half of overall latency.");
+
+ } else {
+
+ /* Ok, the user didn't ask us to adjust the latency, but we
+ * still need to make sure that the parameters from the user
+ * do make sense. */
+
+ if (tlength_usec > minreq_usec*2)
+ sink_usec = (tlength_usec - minreq_usec*2);
+ else
+ sink_usec = 0;
+
+ pa_log_debug("Traditional mode enabled, modifying sink usec only for compat with minreq.");
+ }
+
+ s->configured_sink_latency = pa_sink_input_set_requested_latency(s->sink_input, sink_usec);
+
+ if (s->early_requests) {
+
+ /* Ok, we didn't necessarily get what we were asking for. We
+ * might still get the proper fragment interval, we just can't
+ * guarantee it. */
+
+ if (minreq_usec != s->configured_sink_latency)
+ pa_log_debug("Could not configure a sufficiently low latency. Early requests might not be satisfied.");
+
+ } else if (s->adjust_latency) {
+
+ /* Ok, we didn't necessarily get what we were asking for, so
+ * let's subtract from what we asked for for the remaining
+ * buffer space */
+
+ if (tlength_usec >= s->configured_sink_latency)
+ tlength_usec -= s->configured_sink_latency;
+ }
+
+ pa_log_debug("Requested latency=%0.2f ms, Received latency=%0.2f ms",
+ (double) sink_usec / PA_USEC_PER_MSEC,
+ (double) s->configured_sink_latency / PA_USEC_PER_MSEC);
+
+ /* FIXME: This is actually larger than necessary, since not all of
+ * the sink latency is actually rewritable. */
+ if (tlength_usec < s->configured_sink_latency + 2*minreq_usec)
+ tlength_usec = s->configured_sink_latency + 2*minreq_usec;
+
+ if (pa_usec_to_bytes_round_up(orig_tlength_usec, &s->sink_input->sample_spec) !=
+ pa_usec_to_bytes_round_up(tlength_usec, &s->sink_input->sample_spec))
+ s->buffer_attr.tlength = (uint32_t) pa_usec_to_bytes_round_up(tlength_usec, &s->sink_input->sample_spec);
+
+ if (pa_usec_to_bytes(orig_minreq_usec, &s->sink_input->sample_spec) !=
+ pa_usec_to_bytes(minreq_usec, &s->sink_input->sample_spec))
+ s->buffer_attr.minreq = (uint32_t) pa_usec_to_bytes(minreq_usec, &s->sink_input->sample_spec);
+
+ if (s->buffer_attr.minreq <= 0) {
+ s->buffer_attr.minreq = (uint32_t) frame_size;
+ s->buffer_attr.tlength += (uint32_t) frame_size*2;
+ }
+
+ if (s->buffer_attr.tlength <= s->buffer_attr.minreq)
+ s->buffer_attr.tlength = s->buffer_attr.minreq*2 + (uint32_t) frame_size;
+
+ max_prebuf = s->buffer_attr.tlength + (uint32_t)frame_size - s->buffer_attr.minreq;
+
+ if (s->buffer_attr.prebuf == (uint32_t) -1 ||
+ s->buffer_attr.prebuf > max_prebuf)
+ s->buffer_attr.prebuf = max_prebuf;
+
+#ifdef PROTOCOL_NATIVE_DEBUG
+ pa_log("Client accepted: maxlength=%lu ms tlength=%lu ms minreq=%lu ms prebuf=%lu ms",
+ (unsigned long) (pa_bytes_to_usec(s->buffer_attr.maxlength, &s->sink_input->sample_spec) / PA_USEC_PER_MSEC),
+ (unsigned long) (pa_bytes_to_usec(s->buffer_attr.tlength, &s->sink_input->sample_spec) / PA_USEC_PER_MSEC),
+ (unsigned long) (pa_bytes_to_usec(s->buffer_attr.minreq, &s->sink_input->sample_spec) / PA_USEC_PER_MSEC),
+ (unsigned long) (pa_bytes_to_usec(s->buffer_attr.prebuf, &s->sink_input->sample_spec) / PA_USEC_PER_MSEC));
+#endif
+}
+
+/* Called from main context */
+static playback_stream* playback_stream_new(
+ pa_native_connection *c,
+ pa_sink *sink,
+ pa_sample_spec *ss,
+ pa_channel_map *map,
+ pa_idxset *formats,
+ pa_buffer_attr *a,
+ pa_cvolume *volume,
+ bool muted,
+ bool muted_set,
+ pa_sink_input_flags_t flags,
+ pa_proplist *p,
+ bool adjust_latency,
+ bool early_requests,
+ bool relative_volume,
+ uint32_t syncid,
+ uint32_t *missing,
+ int *ret) {
+
+ /* Note: This function takes ownership of the 'formats' param, so we need
+ * to take extra care to not leak it */
+
+ playback_stream *ssync;
+ playback_stream *s = NULL;
+ pa_sink_input *sink_input = NULL;
+ pa_memchunk silence;
+ uint32_t idx;
+ int64_t start_index;
+ pa_sink_input_new_data data;
+ char *memblockq_name;
+
+ pa_assert(c);
+ pa_assert(ss);
+ pa_assert(missing);
+ pa_assert(p);
+ pa_assert(ret);
+
+ /* Find syncid group */
+ PA_IDXSET_FOREACH(ssync, c->output_streams, idx) {
+
+ if (!playback_stream_isinstance(ssync))
+ continue;
+
+ if (ssync->syncid == syncid)
+ break;
+ }
+
+ /* Synced streams must connect to the same sink */
+ if (ssync) {
+
+ if (!sink)
+ sink = ssync->sink_input->sink;
+ else if (sink != ssync->sink_input->sink) {
+ *ret = PA_ERR_INVALID;
+ goto out;
+ }
+ }
+
+ pa_sink_input_new_data_init(&data);
+
+ pa_proplist_update(data.proplist, PA_UPDATE_REPLACE, p);
+ data.driver = __FILE__;
+ data.module = c->options->module;
+ data.client = c->client;
+ if (sink)
+ pa_sink_input_new_data_set_sink(&data, sink, false, true);
+ if (pa_sample_spec_valid(ss))
+ pa_sink_input_new_data_set_sample_spec(&data, ss);
+ if (pa_channel_map_valid(map))
+ pa_sink_input_new_data_set_channel_map(&data, map);
+ if (formats) {
+ pa_sink_input_new_data_set_formats(&data, formats);
+ /* Ownership transferred to new_data, so we don't free it ourselves */
+ formats = NULL;
+ }
+ if (volume) {
+ pa_sink_input_new_data_set_volume(&data, volume);
+ data.volume_is_absolute = !relative_volume;
+ data.save_volume = false;
+ }
+ if (muted_set) {
+ pa_sink_input_new_data_set_muted(&data, muted);
+ data.save_muted = false;
+ }
+ data.sync_base = ssync ? ssync->sink_input : NULL;
+ data.flags = flags;
+
+ *ret = -pa_sink_input_new(&sink_input, c->protocol->core, &data);
+
+ pa_sink_input_new_data_done(&data);
+
+ if (!sink_input)
+ goto out;
+
+ s = pa_msgobject_new(playback_stream);
+ s->parent.parent.parent.free = playback_stream_free;
+ s->parent.parent.process_msg = playback_stream_process_msg;
+ s->connection = c;
+ s->syncid = syncid;
+ s->sink_input = sink_input;
+ s->is_underrun = true;
+ s->drain_request = false;
+ pa_atomic_store(&s->missing, 0);
+ s->buffer_attr_req = *a;
+ s->adjust_latency = adjust_latency;
+ s->early_requests = early_requests;
+ pa_atomic_store(&s->seek_or_post_in_queue, 0);
+ s->seek_windex = -1;
+
+ s->sink_input->parent.process_msg = sink_input_process_msg;
+ s->sink_input->pop = sink_input_pop_cb;
+ s->sink_input->process_underrun = sink_input_process_underrun_cb;
+ s->sink_input->process_rewind = sink_input_process_rewind_cb;
+ s->sink_input->update_max_rewind = sink_input_update_max_rewind_cb;
+ s->sink_input->update_max_request = sink_input_update_max_request_cb;
+ s->sink_input->kill = sink_input_kill_cb;
+ s->sink_input->moving = sink_input_moving_cb;
+ s->sink_input->suspend = sink_input_suspend_cb;
+ s->sink_input->send_event = sink_input_send_event_cb;
+ s->sink_input->userdata = s;
+
+ start_index = ssync ? pa_memblockq_get_read_index(ssync->memblockq) : 0;
+
+ fix_playback_buffer_attr(s);
+
+ pa_sink_input_get_silence(sink_input, &silence);
+ memblockq_name = pa_sprintf_malloc("native protocol playback stream memblockq [%u]", s->sink_input->index);
+ s->memblockq = pa_memblockq_new(
+ memblockq_name,
+ start_index,
+ s->buffer_attr.maxlength,
+ s->buffer_attr.tlength,
+ &sink_input->sample_spec,
+ s->buffer_attr.prebuf,
+ s->buffer_attr.minreq,
+ 0,
+ &silence);
+ pa_xfree(memblockq_name);
+ pa_memblock_unref(silence.memblock);
+
+ pa_memblockq_get_attr(s->memblockq, &s->buffer_attr);
+
+ *missing = (uint32_t) pa_memblockq_pop_missing(s->memblockq);
+
+#ifdef PROTOCOL_NATIVE_DEBUG
+ pa_log("missing original: %li", (long int) *missing);
+#endif
+
+ *ss = s->sink_input->sample_spec;
+ *map = s->sink_input->channel_map;
+
+ pa_idxset_put(c->output_streams, s, &s->index);
+
+ pa_log_info("Final latency %0.2f ms = %0.2f ms + 2*%0.2f ms + %0.2f ms",
+ ((double) pa_bytes_to_usec(s->buffer_attr.tlength, &sink_input->sample_spec) + (double) s->configured_sink_latency) / PA_USEC_PER_MSEC,
+ (double) pa_bytes_to_usec(s->buffer_attr.tlength-s->buffer_attr.minreq*2, &sink_input->sample_spec) / PA_USEC_PER_MSEC,
+ (double) pa_bytes_to_usec(s->buffer_attr.minreq, &sink_input->sample_spec) / PA_USEC_PER_MSEC,
+ (double) s->configured_sink_latency / PA_USEC_PER_MSEC);
+
+ pa_sink_input_put(s->sink_input);
+
+out:
+ if (formats)
+ pa_idxset_free(formats, (pa_free_cb_t) pa_format_info_free);
+
+ return s;
+}
+
+/* Called from IO context */
+static void playback_stream_request_bytes(playback_stream *s) {
+ size_t m;
+
+ playback_stream_assert_ref(s);
+
+ m = pa_memblockq_pop_missing(s->memblockq);
+
+ /* pa_log("request_bytes(%lu) (tlength=%lu minreq=%lu length=%lu really missing=%lli)", */
+ /* (unsigned long) m, */
+ /* pa_memblockq_get_tlength(s->memblockq), */
+ /* pa_memblockq_get_minreq(s->memblockq), */
+ /* pa_memblockq_get_length(s->memblockq), */
+ /* (long long) pa_memblockq_get_tlength(s->memblockq) - (long long) pa_memblockq_get_length(s->memblockq)); */
+
+ if (m <= 0)
+ return;
+
+#ifdef PROTOCOL_NATIVE_DEBUG
+ pa_log("request_bytes(%lu)", (unsigned long) m);
+#endif
+
+ if (pa_atomic_add(&s->missing, (int) m) <= 0)
+ pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PLAYBACK_STREAM_MESSAGE_REQUEST_DATA, NULL, 0, NULL, NULL);
+}
+
+/* Called from main context */
+static void playback_stream_send_killed(playback_stream *p) {
+ pa_tagstruct *t;
+ playback_stream_assert_ref(p);
+
+ t = pa_tagstruct_new();
+ pa_tagstruct_putu32(t, PA_COMMAND_PLAYBACK_STREAM_KILLED);
+ pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */
+ pa_tagstruct_putu32(t, p->index);
+ pa_pstream_send_tagstruct(p->connection->pstream, t);
+}
+
+/* Called from main context */
+static int native_connection_process_msg(pa_msgobject *o, int code, void*userdata, int64_t offset, pa_memchunk *chunk) {
+ pa_native_connection *c = PA_NATIVE_CONNECTION(o);
+ pa_native_connection_assert_ref(c);
+
+ if (!c->protocol)
+ return -1;
+
+ switch (code) {
+
+ case CONNECTION_MESSAGE_REVOKE:
+ pa_pstream_send_revoke(c->pstream, PA_PTR_TO_UINT(userdata));
+ break;
+
+ case CONNECTION_MESSAGE_RELEASE:
+ pa_pstream_send_release(c->pstream, PA_PTR_TO_UINT(userdata));
+ break;
+ }
+
+ return 0;
+}
+
+/* Called from main context */
+static void native_connection_unlink(pa_native_connection *c) {
+ record_stream *r;
+ output_stream *o;
+
+ pa_assert(c);
+
+ if (!c->protocol)
+ return;
+
+ pa_hook_fire(&c->protocol->hooks[PA_NATIVE_HOOK_CONNECTION_UNLINK], c);
+
+ if (c->options)
+ pa_native_options_unref(c->options);
+
+ if (c->srbpending)
+ pa_srbchannel_free(c->srbpending);
+
+ while ((r = pa_idxset_first(c->record_streams, NULL)))
+ record_stream_unlink(r);
+
+ while ((o = pa_idxset_first(c->output_streams, NULL)))
+ if (playback_stream_isinstance(o))
+ playback_stream_unlink(PLAYBACK_STREAM(o));
+ else
+ upload_stream_unlink(UPLOAD_STREAM(o));
+
+ if (c->subscription)
+ pa_subscription_free(c->subscription);
+
+ if (c->pstream)
+ pa_pstream_unlink(c->pstream);
+
+ if (c->auth_timeout_event) {
+ c->protocol->core->mainloop->time_free(c->auth_timeout_event);
+ c->auth_timeout_event = NULL;
+ }
+
+ pa_assert_se(pa_idxset_remove_by_data(c->protocol->connections, c, NULL) == c);
+ c->protocol = NULL;
+ pa_native_connection_unref(c);
+}
+
+/* Called from main context */
+static void native_connection_free(pa_object *o) {
+ pa_native_connection *c = PA_NATIVE_CONNECTION(o);
+
+ pa_assert(c);
+
+ native_connection_unlink(c);
+
+ pa_idxset_free(c->record_streams, NULL);
+ pa_idxset_free(c->output_streams, NULL);
+
+ pa_pdispatch_unref(c->pdispatch);
+ pa_pstream_unref(c->pstream);
+ if (c->rw_mempool)
+ pa_mempool_unref(c->rw_mempool);
+
+ pa_client_free(c->client);
+
+ pa_xfree(c);
+}
+
+/* Called from main context */
+static void native_connection_send_memblock(pa_native_connection *c) {
+ uint32_t start;
+ record_stream *r;
+
+ start = PA_IDXSET_INVALID;
+ for (;;) {
+ pa_memchunk chunk;
+
+ if (!(r = RECORD_STREAM(pa_idxset_rrobin(c->record_streams, &c->rrobin_index))))
+ return;
+
+ if (start == PA_IDXSET_INVALID)
+ start = c->rrobin_index;
+ else if (start == c->rrobin_index)
+ return;
+
+ if (pa_memblockq_peek(r->memblockq, &chunk) >= 0) {
+ pa_memchunk schunk = chunk;
+
+ if (schunk.length > r->buffer_attr.fragsize)
+ schunk.length = r->buffer_attr.fragsize;
+
+ pa_pstream_send_memblock(c->pstream, r->index, 0, PA_SEEK_RELATIVE, &schunk);
+
+ pa_memblockq_drop(r->memblockq, schunk.length);
+ pa_memblock_unref(schunk.memblock);
+
+ return;
+ }
+ }
+}
+
+/*** sink input callbacks ***/
+
+/* Called from thread context */
+static void handle_seek(playback_stream *s, int64_t indexw) {
+ playback_stream_assert_ref(s);
+
+/* pa_log("handle_seek: %llu -- %i", (unsigned long long) s->sink_input->thread_info.underrun_for, pa_memblockq_is_readable(s->memblockq)); */
+
+ if (s->sink_input->thread_info.underrun_for > 0) {
+
+/* pa_log("%lu vs. %lu", (unsigned long) pa_memblockq_get_length(s->memblockq), (unsigned long) pa_memblockq_get_prebuf(s->memblockq)); */
+
+ if (pa_memblockq_is_readable(s->memblockq)) {
+
+ /* We just ended an underrun, let's ask the sink
+ * for a complete rewind rewrite */
+
+ pa_log_debug("Requesting rewind due to end of underrun.");
+ pa_sink_input_request_rewind(s->sink_input,
+ (size_t) (s->sink_input->thread_info.underrun_for == (uint64_t) -1 ? 0 :
+ s->sink_input->thread_info.underrun_for),
+ false, true, false);
+ }
+
+ } else {
+ int64_t indexr;
+
+ indexr = pa_memblockq_get_read_index(s->memblockq);
+
+ if (indexw < indexr) {
+ /* OK, the sink already asked for this data, so
+ * let's have it ask us again */
+
+ pa_log_debug("Requesting rewind due to rewrite.");
+ pa_sink_input_request_rewind(s->sink_input, (size_t) (indexr - indexw), true, false, false);
+ }
+ }
+
+ playback_stream_request_bytes(s);
+}
+
+static void flush_write_no_account(pa_memblockq *q) {
+ pa_memblockq_flush_write(q, false);
+}
+
+/* Called from thread context */
+static int sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk) {
+ pa_sink_input *i = PA_SINK_INPUT(o);
+ playback_stream *s;
+
+ pa_sink_input_assert_ref(i);
+ s = PLAYBACK_STREAM(i->userdata);
+ playback_stream_assert_ref(s);
+
+ switch (code) {
+
+ case SINK_INPUT_MESSAGE_SEEK:
+ case SINK_INPUT_MESSAGE_POST_DATA: {
+ int64_t windex = pa_memblockq_get_write_index(s->memblockq);
+
+ if (code == SINK_INPUT_MESSAGE_SEEK) {
+ /* The client side is incapable of accounting correctly
+ * for seeks of a type != PA_SEEK_RELATIVE. We need to be
+ * able to deal with that. */
+
+ pa_memblockq_seek(s->memblockq, offset, PA_PTR_TO_UINT(userdata), PA_PTR_TO_UINT(userdata) == PA_SEEK_RELATIVE);
+ windex = PA_MIN(windex, pa_memblockq_get_write_index(s->memblockq));
+ }
+
+ if (chunk && pa_memblockq_push_align(s->memblockq, chunk) < 0) {
+ if (pa_log_ratelimit(PA_LOG_WARN))
+ pa_log_warn("Failed to push data into queue");
+ pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PLAYBACK_STREAM_MESSAGE_OVERFLOW, NULL, 0, NULL, NULL);
+ pa_memblockq_seek(s->memblockq, (int64_t) chunk->length, PA_SEEK_RELATIVE, true);
+ }
+
+ /* If more data is in queue, we rewind later instead. */
+ if (s->seek_windex != -1)
+ windex = PA_MIN(windex, s->seek_windex);
+ if (pa_atomic_dec(&s->seek_or_post_in_queue) > 1)
+ s->seek_windex = windex;
+ else {
+ s->seek_windex = -1;
+ handle_seek(s, windex);
+ }
+ return 0;
+ }
+
+ case SINK_INPUT_MESSAGE_DRAIN:
+ case SINK_INPUT_MESSAGE_FLUSH:
+ case SINK_INPUT_MESSAGE_PREBUF_FORCE:
+ case SINK_INPUT_MESSAGE_TRIGGER: {
+
+ int64_t windex;
+ pa_sink_input *isync;
+ void (*func)(pa_memblockq *bq);
+
+ switch (code) {
+ case SINK_INPUT_MESSAGE_FLUSH:
+ func = flush_write_no_account;
+ break;
+
+ case SINK_INPUT_MESSAGE_PREBUF_FORCE:
+ func = pa_memblockq_prebuf_force;
+ break;
+
+ case SINK_INPUT_MESSAGE_DRAIN:
+ case SINK_INPUT_MESSAGE_TRIGGER:
+ func = pa_memblockq_prebuf_disable;
+ break;
+
+ default:
+ pa_assert_not_reached();
+ }
+
+ windex = pa_memblockq_get_write_index(s->memblockq);
+ func(s->memblockq);
+ handle_seek(s, windex);
+
+ /* Do the same for all other members in the sync group */
+ for (isync = i->sync_prev; isync; isync = isync->sync_prev) {
+ playback_stream *ssync = PLAYBACK_STREAM(isync->userdata);
+ windex = pa_memblockq_get_write_index(ssync->memblockq);
+ func(ssync->memblockq);
+ handle_seek(ssync, windex);
+ }
+
+ for (isync = i->sync_next; isync; isync = isync->sync_next) {
+ playback_stream *ssync = PLAYBACK_STREAM(isync->userdata);
+ windex = pa_memblockq_get_write_index(ssync->memblockq);
+ func(ssync->memblockq);
+ handle_seek(ssync, windex);
+ }
+
+ if (code == SINK_INPUT_MESSAGE_DRAIN) {
+ if (!pa_memblockq_is_readable(s->memblockq))
+ pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PLAYBACK_STREAM_MESSAGE_DRAIN_ACK, userdata, 0, NULL, NULL);
+ else {
+ s->drain_tag = PA_PTR_TO_UINT(userdata);
+ s->drain_request = true;
+ }
+ }
+
+ return 0;
+ }
+
+ case SINK_INPUT_MESSAGE_UPDATE_LATENCY:
+ /* Atomically get a snapshot of all timing parameters... */
+ s->read_index = pa_memblockq_get_read_index(s->memblockq);
+ s->write_index = pa_memblockq_get_write_index(s->memblockq);
+ s->render_memblockq_length = pa_memblockq_get_length(s->sink_input->thread_info.render_memblockq);
+ s->current_sink_latency = pa_sink_get_latency_within_thread(s->sink_input->sink, false);
+ s->underrun_for = s->sink_input->thread_info.underrun_for;
+ s->playing_for = s->sink_input->thread_info.playing_for;
+
+ return 0;
+
+ case PA_SINK_INPUT_MESSAGE_SET_STATE: {
+ int64_t windex;
+
+ windex = pa_memblockq_get_write_index(s->memblockq);
+
+ /* We enable prebuffering so that after CORKED -> RUNNING
+ * transitions we don't have trouble with underruns in case the
+ * buffer has too little data. This must not be done when draining
+ * has been requested, however, otherwise the buffered audio would
+ * never play. */
+ if (!s->drain_request)
+ pa_memblockq_prebuf_force(s->memblockq);
+
+ handle_seek(s, windex);
+
+ /* Fall through to the default handler */
+ break;
+ }
+
+ case PA_SINK_INPUT_MESSAGE_GET_LATENCY: {
+ pa_usec_t *r = userdata;
+
+ *r = pa_bytes_to_usec(pa_memblockq_get_length(s->memblockq), &i->sample_spec);
+
+ /* Fall through, the default handler will add in the extra
+ * latency added by the resampler */
+ break;
+ }
+
+ case SINK_INPUT_MESSAGE_UPDATE_BUFFER_ATTR: {
+ pa_memblockq_apply_attr(s->memblockq, &s->buffer_attr);
+ pa_memblockq_get_attr(s->memblockq, &s->buffer_attr);
+ return 0;
+ }
+ }
+
+ return pa_sink_input_process_msg(o, code, userdata, offset, chunk);
+}
+
+static bool handle_input_underrun(playback_stream *s, bool force) {
+ bool send_drain;
+
+ if (pa_memblockq_is_readable(s->memblockq))
+ return false;
+
+ if (!s->is_underrun)
+ pa_log_debug("%s %s of '%s'", force ? "Actual" : "Implicit",
+ s->drain_request ? "drain" : "underrun", pa_strnull(pa_proplist_gets(s->sink_input->proplist, PA_PROP_MEDIA_NAME)));
+
+ send_drain = s->drain_request && (force || pa_sink_input_safe_to_remove(s->sink_input));
+
+ if (send_drain) {
+ s->drain_request = false;
+ pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PLAYBACK_STREAM_MESSAGE_DRAIN_ACK, PA_UINT_TO_PTR(s->drain_tag), 0, NULL, NULL);
+ pa_log_debug("Drain acknowledged of '%s'", pa_strnull(pa_proplist_gets(s->sink_input->proplist, PA_PROP_MEDIA_NAME)));
+ } else if (!s->is_underrun) {
+ pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PLAYBACK_STREAM_MESSAGE_UNDERFLOW, NULL, pa_memblockq_get_read_index(s->memblockq), NULL, NULL);
+ }
+ s->is_underrun = true;
+ playback_stream_request_bytes(s);
+ return true;
+}
+
+/* Called from thread context */
+static bool sink_input_process_underrun_cb(pa_sink_input *i) {
+ playback_stream *s;
+
+ pa_sink_input_assert_ref(i);
+ s = PLAYBACK_STREAM(i->userdata);
+ playback_stream_assert_ref(s);
+
+ return handle_input_underrun(s, true);
+}
+
+/* Called from thread context */
+static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk) {
+ playback_stream *s;
+
+ pa_sink_input_assert_ref(i);
+ s = PLAYBACK_STREAM(i->userdata);
+ playback_stream_assert_ref(s);
+ pa_assert(chunk);
+
+#ifdef PROTOCOL_NATIVE_DEBUG
+ pa_log("%s, pop(): %lu", pa_proplist_gets(i->proplist, PA_PROP_MEDIA_NAME), (unsigned long) pa_memblockq_get_length(s->memblockq));
+#endif
+
+ if (!handle_input_underrun(s, false))
+ s->is_underrun = false;
+
+ /* This call will not fail with prebuf=0, hence we check for
+ underrun explicitly in handle_input_underrun */
+ if (pa_memblockq_peek(s->memblockq, chunk) < 0)
+ return -1;
+
+ chunk->length = PA_MIN(nbytes, chunk->length);
+
+ if (i->thread_info.underrun_for > 0)
+ pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PLAYBACK_STREAM_MESSAGE_STARTED, NULL, 0, NULL, NULL);
+
+ pa_memblockq_drop(s->memblockq, chunk->length);
+ playback_stream_request_bytes(s);
+
+ return 0;
+}
+
+/* Called from thread context */
+static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) {
+ playback_stream *s;
+
+ pa_sink_input_assert_ref(i);
+ s = PLAYBACK_STREAM(i->userdata);
+ playback_stream_assert_ref(s);
+
+ /* If we are in an underrun, then we don't rewind */
+ if (i->thread_info.underrun_for > 0)
+ return;
+
+ pa_memblockq_rewind(s->memblockq, nbytes);
+}
+
+/* Called from thread context */
+static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) {
+ playback_stream *s;
+
+ pa_sink_input_assert_ref(i);
+ s = PLAYBACK_STREAM(i->userdata);
+ playback_stream_assert_ref(s);
+
+ pa_memblockq_set_maxrewind(s->memblockq, nbytes);
+}
+
+/* Called from thread context */
+static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) {
+ playback_stream *s;
+ size_t new_tlength, old_tlength;
+
+ pa_sink_input_assert_ref(i);
+ s = PLAYBACK_STREAM(i->userdata);
+ playback_stream_assert_ref(s);
+
+ old_tlength = pa_memblockq_get_tlength(s->memblockq);
+ new_tlength = nbytes+2*pa_memblockq_get_minreq(s->memblockq);
+
+ if (old_tlength < new_tlength) {
+ pa_log_debug("max_request changed, trying to update from %zu to %zu.", old_tlength, new_tlength);
+ pa_memblockq_set_tlength(s->memblockq, new_tlength);
+ new_tlength = pa_memblockq_get_tlength(s->memblockq);
+
+ if (new_tlength == old_tlength)
+ pa_log_debug("Failed to increase tlength");
+ else {
+ pa_log_debug("Notifying client about increased tlength");
+ pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PLAYBACK_STREAM_MESSAGE_UPDATE_TLENGTH, NULL, pa_memblockq_get_tlength(s->memblockq), NULL, NULL);
+ }
+ }
+}
+
+/* Called from main context */
+static void sink_input_kill_cb(pa_sink_input *i) {
+ playback_stream *s;
+
+ pa_sink_input_assert_ref(i);
+ s = PLAYBACK_STREAM(i->userdata);
+ playback_stream_assert_ref(s);
+
+ playback_stream_send_killed(s);
+ playback_stream_unlink(s);
+}
+
+/* Called from main context */
+static void sink_input_send_event_cb(pa_sink_input *i, const char *event, pa_proplist *pl) {
+ playback_stream *s;
+ pa_tagstruct *t;
+
+ pa_sink_input_assert_ref(i);
+ s = PLAYBACK_STREAM(i->userdata);
+ playback_stream_assert_ref(s);
+
+ if (s->connection->version < 15)
+ return;
+
+ t = pa_tagstruct_new();
+ pa_tagstruct_putu32(t, PA_COMMAND_PLAYBACK_STREAM_EVENT);
+ pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */
+ pa_tagstruct_putu32(t, s->index);
+ pa_tagstruct_puts(t, event);
+ pa_tagstruct_put_proplist(t, pl);
+ pa_pstream_send_tagstruct(s->connection->pstream, t);
+}
+
+/* Called from main context */
+static void sink_input_suspend_cb(pa_sink_input *i, pa_sink_state_t old_state, pa_suspend_cause_t old_suspend_cause) {
+ playback_stream *s;
+ pa_tagstruct *t;
+ bool suspend;
+
+ pa_sink_input_assert_ref(i);
+
+ /* State has not changed, nothing to do */
+ if (old_state == i->sink->state)
+ return;
+
+ suspend = (i->sink->state == PA_SINK_SUSPENDED);
+
+ s = PLAYBACK_STREAM(i->userdata);
+ playback_stream_assert_ref(s);
+
+ if (s->connection->version < 12)
+ return;
+
+ t = pa_tagstruct_new();
+ pa_tagstruct_putu32(t, PA_COMMAND_PLAYBACK_STREAM_SUSPENDED);
+ pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */
+ pa_tagstruct_putu32(t, s->index);
+ pa_tagstruct_put_boolean(t, suspend);
+ pa_pstream_send_tagstruct(s->connection->pstream, t);
+}
+
+/* Called from main context */
+static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest) {
+ playback_stream *s;
+ pa_tagstruct *t;
+
+ pa_sink_input_assert_ref(i);
+ s = PLAYBACK_STREAM(i->userdata);
+ playback_stream_assert_ref(s);
+
+ if (!dest)
+ return;
+
+ fix_playback_buffer_attr(s);
+ pa_memblockq_apply_attr(s->memblockq, &s->buffer_attr);
+ pa_memblockq_get_attr(s->memblockq, &s->buffer_attr);
+
+ if (s->connection->version < 12)
+ return;
+
+ t = pa_tagstruct_new();
+ pa_tagstruct_putu32(t, PA_COMMAND_PLAYBACK_STREAM_MOVED);
+ pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */
+ pa_tagstruct_putu32(t, s->index);
+ pa_tagstruct_putu32(t, dest->index);
+ pa_tagstruct_puts(t, dest->name);
+ pa_tagstruct_put_boolean(t, dest->state == PA_SINK_SUSPENDED);
+
+ if (s->connection->version >= 13) {
+ pa_tagstruct_putu32(t, s->buffer_attr.maxlength);
+ pa_tagstruct_putu32(t, s->buffer_attr.tlength);
+ pa_tagstruct_putu32(t, s->buffer_attr.prebuf);
+ pa_tagstruct_putu32(t, s->buffer_attr.minreq);
+ pa_tagstruct_put_usec(t, s->configured_sink_latency);
+ }
+
+ pa_pstream_send_tagstruct(s->connection->pstream, t);
+}
+
+/*** source_output callbacks ***/
+
+/* Called from thread context */
+static int source_output_process_msg(pa_msgobject *_o, int code, void *userdata, int64_t offset, pa_memchunk *chunk) {
+ pa_source_output *o = PA_SOURCE_OUTPUT(_o);
+ record_stream *s;
+
+ pa_source_output_assert_ref(o);
+ s = RECORD_STREAM(o->userdata);
+ record_stream_assert_ref(s);
+
+ switch (code) {
+ case SOURCE_OUTPUT_MESSAGE_UPDATE_LATENCY:
+ /* Atomically get a snapshot of all timing parameters... */
+ s->current_monitor_latency = o->source->monitor_of ? pa_sink_get_latency_within_thread(o->source->monitor_of, false) : 0;
+ s->current_source_latency = pa_source_get_latency_within_thread(o->source, false);
+ s->on_the_fly_snapshot = pa_atomic_load(&s->on_the_fly);
+ return 0;
+ }
+
+ return pa_source_output_process_msg(_o, code, userdata, offset, chunk);
+}
+
+/* Called from thread context */
+static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk) {
+ record_stream *s;
+
+ pa_source_output_assert_ref(o);
+ s = RECORD_STREAM(o->userdata);
+ record_stream_assert_ref(s);
+ pa_assert(chunk);
+
+ pa_atomic_add(&s->on_the_fly, chunk->length);
+ pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), RECORD_STREAM_MESSAGE_POST_DATA, NULL, 0, chunk, NULL);
+}
+
+static void source_output_kill_cb(pa_source_output *o) {
+ record_stream *s;
+
+ pa_source_output_assert_ref(o);
+ s = RECORD_STREAM(o->userdata);
+ record_stream_assert_ref(s);
+
+ record_stream_send_killed(s);
+ record_stream_unlink(s);
+}
+
+static pa_usec_t source_output_get_latency_cb(pa_source_output *o) {
+ record_stream *s;
+
+ pa_source_output_assert_ref(o);
+ s = RECORD_STREAM(o->userdata);
+ record_stream_assert_ref(s);
+
+ /*pa_log("get_latency: %u", pa_memblockq_get_length(s->memblockq));*/
+
+ return pa_bytes_to_usec(pa_memblockq_get_length(s->memblockq), &o->sample_spec);
+}
+
+/* Called from main context */
+static void source_output_send_event_cb(pa_source_output *o, const char *event, pa_proplist *pl) {
+ record_stream *s;
+ pa_tagstruct *t;
+
+ pa_source_output_assert_ref(o);
+ s = RECORD_STREAM(o->userdata);
+ record_stream_assert_ref(s);
+
+ if (s->connection->version < 15)
+ return;
+
+ t = pa_tagstruct_new();
+ pa_tagstruct_putu32(t, PA_COMMAND_RECORD_STREAM_EVENT);
+ pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */
+ pa_tagstruct_putu32(t, s->index);
+ pa_tagstruct_puts(t, event);
+ pa_tagstruct_put_proplist(t, pl);
+ pa_pstream_send_tagstruct(s->connection->pstream, t);
+}
+
+/* Called from main context */
+static void source_output_suspend_cb(pa_source_output *o, pa_source_state_t old_state, pa_suspend_cause_t old_suspend_cause) {
+ record_stream *s;
+ pa_tagstruct *t;
+ bool suspend;
+
+ pa_source_output_assert_ref(o);
+
+ /* State has not changed, nothing to do */
+ if (old_state == o->source->state)
+ return;
+
+ suspend = (o->source->state == PA_SOURCE_SUSPENDED);
+
+ s = RECORD_STREAM(o->userdata);
+ record_stream_assert_ref(s);
+
+ if (s->connection->version < 12)
+ return;
+
+ t = pa_tagstruct_new();
+ pa_tagstruct_putu32(t, PA_COMMAND_RECORD_STREAM_SUSPENDED);
+ pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */
+ pa_tagstruct_putu32(t, s->index);
+ pa_tagstruct_put_boolean(t, suspend);
+ pa_pstream_send_tagstruct(s->connection->pstream, t);
+}
+
+/* Called from main context */
+static void source_output_moving_cb(pa_source_output *o, pa_source *dest) {
+ record_stream *s;
+ pa_tagstruct *t;
+
+ pa_source_output_assert_ref(o);
+ s = RECORD_STREAM(o->userdata);
+ record_stream_assert_ref(s);
+
+ if (!dest)
+ return;
+
+ fix_record_buffer_attr_pre(s);
+ pa_memblockq_set_maxlength(s->memblockq, s->buffer_attr.maxlength);
+ pa_memblockq_get_attr(s->memblockq, &s->buffer_attr);
+ fix_record_buffer_attr_post(s);
+
+ if (s->connection->version < 12)
+ return;
+
+ t = pa_tagstruct_new();
+ pa_tagstruct_putu32(t, PA_COMMAND_RECORD_STREAM_MOVED);
+ pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */
+ pa_tagstruct_putu32(t, s->index);
+ pa_tagstruct_putu32(t, dest->index);
+ pa_tagstruct_puts(t, dest->name);
+ pa_tagstruct_put_boolean(t, dest->state == PA_SOURCE_SUSPENDED);
+
+ if (s->connection->version >= 13) {
+ pa_tagstruct_putu32(t, s->buffer_attr.maxlength);
+ pa_tagstruct_putu32(t, s->buffer_attr.fragsize);
+ pa_tagstruct_put_usec(t, s->configured_source_latency);
+ }
+
+ pa_pstream_send_tagstruct(s->connection->pstream, t);
+}
+
+/*** pdispatch callbacks ***/
+
+static void protocol_error(pa_native_connection *c) {
+ pa_log("protocol error, kicking client");
+ native_connection_unlink(c);
+}
+
+#define CHECK_VALIDITY(pstream, expression, tag, error) do { \
+if (!(expression)) { \
+ pa_pstream_send_error((pstream), (tag), (error)); \
+ return; \
+} \
+} while(0);
+
+#define CHECK_VALIDITY_GOTO(pstream, expression, tag, error, label) do { \
+if (!(expression)) { \
+ pa_pstream_send_error((pstream), (tag), (error)); \
+ goto label; \
+} \
+} while(0);
+
+static pa_tagstruct *reply_new(uint32_t tag) {
+ pa_tagstruct *reply;
+
+ reply = pa_tagstruct_new();
+ pa_tagstruct_putu32(reply, PA_COMMAND_REPLY);
+ pa_tagstruct_putu32(reply, tag);
+ return reply;
+}
+
+static void command_create_playback_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
+ playback_stream *s;
+ uint32_t sink_index, syncid, missing = 0;
+ pa_buffer_attr attr;
+ const char *name = NULL, *sink_name;
+ pa_sample_spec ss;
+ pa_channel_map map;
+ pa_tagstruct *reply;
+ pa_sink *sink = NULL;
+ pa_cvolume volume;
+ bool
+ corked = false,
+ no_remap = false,
+ no_remix = false,
+ fix_format = false,
+ fix_rate = false,
+ fix_channels = false,
+ no_move = false,
+ variable_rate = false,
+ muted = false,
+ adjust_latency = false,
+ early_requests = false,
+ dont_inhibit_auto_suspend = false,
+ volume_set = true,
+ muted_set = false,
+ fail_on_suspend = false,
+ relative_volume = false,
+ passthrough = false;
+
+ pa_sink_input_flags_t flags = 0;
+ pa_proplist *p = NULL;
+ int ret = PA_ERR_INVALID;
+ uint8_t n_formats = 0;
+ pa_format_info *format;
+ pa_idxset *formats = NULL;
+ uint32_t i;
+
+ pa_native_connection_assert_ref(c);
+ pa_assert(t);
+ memset(&attr, 0, sizeof(attr));
+
+ if ((c->version < 13 && (pa_tagstruct_gets(t, &name) < 0 || !name)) ||
+ pa_tagstruct_get(
+ t,
+ PA_TAG_SAMPLE_SPEC, &ss,
+ PA_TAG_CHANNEL_MAP, &map,
+ PA_TAG_U32, &sink_index,
+ PA_TAG_STRING, &sink_name,
+ PA_TAG_U32, &attr.maxlength,
+ PA_TAG_BOOLEAN, &corked,
+ PA_TAG_U32, &attr.tlength,
+ PA_TAG_U32, &attr.prebuf,
+ PA_TAG_U32, &attr.minreq,
+ PA_TAG_U32, &syncid,
+ PA_TAG_CVOLUME, &volume,
+ PA_TAG_INVALID) < 0) {
+
+ protocol_error(c);
+ goto finish;
+ }
+
+ CHECK_VALIDITY_GOTO(c->pstream, c->authorized, tag, PA_ERR_ACCESS, finish);
+ CHECK_VALIDITY_GOTO(c->pstream, !sink_name || pa_namereg_is_valid_name_or_wildcard(sink_name, PA_NAMEREG_SINK), tag, PA_ERR_INVALID, finish);
+ CHECK_VALIDITY_GOTO(c->pstream, sink_index == PA_INVALID_INDEX || !sink_name, tag, PA_ERR_INVALID, finish);
+ CHECK_VALIDITY_GOTO(c->pstream, !sink_name || sink_index == PA_INVALID_INDEX, tag, PA_ERR_INVALID, finish);
+ CHECK_VALIDITY_GOTO(c->pstream, pa_cvolume_valid(&volume), tag, PA_ERR_INVALID, finish);
+
+ p = pa_proplist_new();
+
+ if (name)
+ pa_proplist_sets(p, PA_PROP_MEDIA_NAME, name);
+
+ if (c->version >= 12) {
+ /* Since 0.9.8 the user can ask for a couple of additional flags */
+
+ if (pa_tagstruct_get_boolean(t, &no_remap) < 0 ||
+ pa_tagstruct_get_boolean(t, &no_remix) < 0 ||
+ pa_tagstruct_get_boolean(t, &fix_format) < 0 ||
+ pa_tagstruct_get_boolean(t, &fix_rate) < 0 ||
+ pa_tagstruct_get_boolean(t, &fix_channels) < 0 ||
+ pa_tagstruct_get_boolean(t, &no_move) < 0 ||
+ pa_tagstruct_get_boolean(t, &variable_rate) < 0) {
+
+ protocol_error(c);
+ goto finish;
+ }
+ }
+
+ if (c->version >= 13) {
+
+ if (pa_tagstruct_get_boolean(t, &muted) < 0 ||
+ pa_tagstruct_get_boolean(t, &adjust_latency) < 0 ||
+ pa_tagstruct_get_proplist(t, p) < 0) {
+
+ protocol_error(c);
+ goto finish;
+ }
+ }
+
+ if (c->version >= 14) {
+
+ if (pa_tagstruct_get_boolean(t, &volume_set) < 0 ||
+ pa_tagstruct_get_boolean(t, &early_requests) < 0) {
+
+ protocol_error(c);
+ goto finish;
+ }
+ }
+
+ if (c->version >= 15) {
+
+ if (pa_tagstruct_get_boolean(t, &muted_set) < 0 ||
+ pa_tagstruct_get_boolean(t, &dont_inhibit_auto_suspend) < 0 ||
+ pa_tagstruct_get_boolean(t, &fail_on_suspend) < 0) {
+
+ protocol_error(c);
+ goto finish;
+ }
+ }
+
+ if (c->version >= 17) {
+
+ if (pa_tagstruct_get_boolean(t, &relative_volume) < 0) {
+
+ protocol_error(c);
+ goto finish;
+ }
+ }
+
+ if (c->version >= 18) {
+
+ if (pa_tagstruct_get_boolean(t, &passthrough) < 0 ) {
+ protocol_error(c);
+ goto finish;
+ }
+ }
+
+ if (c->version >= 21) {
+
+ if (pa_tagstruct_getu8(t, &n_formats) < 0) {
+ protocol_error(c);
+ goto finish;
+ }
+
+ if (n_formats)
+ formats = pa_idxset_new(NULL, NULL);
+
+ for (i = 0; i < n_formats; i++) {
+ format = pa_format_info_new();
+ if (pa_tagstruct_get_format_info(t, format) < 0) {
+ protocol_error(c);
+ goto finish;
+ }
+ pa_idxset_put(formats, format, NULL);
+ }
+ }
+
+ if (n_formats == 0) {
+ CHECK_VALIDITY_GOTO(c->pstream, pa_sample_spec_valid(&ss), tag, PA_ERR_INVALID, finish);
+ CHECK_VALIDITY_GOTO(c->pstream, map.channels == ss.channels && volume.channels == ss.channels, tag, PA_ERR_INVALID, finish);
+ CHECK_VALIDITY_GOTO(c->pstream, pa_channel_map_valid(&map), tag, PA_ERR_INVALID, finish);
+ } else {
+ PA_IDXSET_FOREACH(format, formats, i) {
+ CHECK_VALIDITY_GOTO(c->pstream, pa_format_info_valid(format), tag, PA_ERR_INVALID, finish);
+ }
+ }
+
+ if (!pa_tagstruct_eof(t)) {
+ protocol_error(c);
+ goto finish;
+ }
+
+ if (sink_index != PA_INVALID_INDEX) {
+
+ if (!(sink = pa_idxset_get_by_index(c->protocol->core->sinks, sink_index))) {
+ pa_pstream_send_error(c->pstream, tag, PA_ERR_NOENTITY);
+ goto finish;
+ }
+
+ } else if (sink_name) {
+
+ if (!(sink = pa_namereg_get(c->protocol->core, sink_name, PA_NAMEREG_SINK))) {
+ pa_pstream_send_error(c->pstream, tag, PA_ERR_NOENTITY);
+ goto finish;
+ }
+ }
+
+ flags =
+ (corked ? PA_SINK_INPUT_START_CORKED : 0) |
+ (no_remap ? PA_SINK_INPUT_NO_REMAP : 0) |
+ (no_remix ? PA_SINK_INPUT_NO_REMIX : 0) |
+ (fix_format ? PA_SINK_INPUT_FIX_FORMAT : 0) |
+ (fix_rate ? PA_SINK_INPUT_FIX_RATE : 0) |
+ (fix_channels ? PA_SINK_INPUT_FIX_CHANNELS : 0) |
+ (no_move ? PA_SINK_INPUT_DONT_MOVE : 0) |
+ (variable_rate ? PA_SINK_INPUT_VARIABLE_RATE : 0) |
+ (dont_inhibit_auto_suspend ? PA_SINK_INPUT_DONT_INHIBIT_AUTO_SUSPEND : 0) |
+ (fail_on_suspend ? PA_SINK_INPUT_NO_CREATE_ON_SUSPEND|PA_SINK_INPUT_KILL_ON_SUSPEND : 0) |
+ (passthrough ? PA_SINK_INPUT_PASSTHROUGH : 0);
+
+ /* Only since protocol version 15 there's a separate muted_set
+ * flag. For older versions we synthesize it here */
+ muted_set = muted_set || muted;
+
+ s = playback_stream_new(c, sink, &ss, &map, formats, &attr, volume_set ? &volume : NULL, muted, muted_set, flags, p, adjust_latency, early_requests, relative_volume, syncid, &missing, &ret);
+ /* We no longer own the formats idxset */
+ formats = NULL;
+
+ CHECK_VALIDITY_GOTO(c->pstream, s, tag, ret, finish);
+
+ reply = reply_new(tag);
+ pa_tagstruct_putu32(reply, s->index);
+ pa_assert(s->sink_input);
+ pa_tagstruct_putu32(reply, s->sink_input->index);
+ pa_tagstruct_putu32(reply, missing);
+
+#ifdef PROTOCOL_NATIVE_DEBUG
+ pa_log("initial request is %u", missing);
+#endif
+
+ if (c->version >= 9) {
+ /* Since 0.9.0 we support sending the buffer metrics back to the client */
+
+ pa_tagstruct_putu32(reply, (uint32_t) s->buffer_attr.maxlength);
+ pa_tagstruct_putu32(reply, (uint32_t) s->buffer_attr.tlength);
+ pa_tagstruct_putu32(reply, (uint32_t) s->buffer_attr.prebuf);
+ pa_tagstruct_putu32(reply, (uint32_t) s->buffer_attr.minreq);
+ }
+
+ if (c->version >= 12) {
+ /* Since 0.9.8 we support sending the chosen sample
+ * spec/channel map/device/suspend status back to the
+ * client */
+
+ pa_tagstruct_put_sample_spec(reply, &ss);
+ pa_tagstruct_put_channel_map(reply, &map);
+
+ pa_tagstruct_putu32(reply, s->sink_input->sink->index);
+ pa_tagstruct_puts(reply, s->sink_input->sink->name);
+
+ pa_tagstruct_put_boolean(reply, s->sink_input->sink->state == PA_SINK_SUSPENDED);
+ }
+
+ if (c->version >= 13)
+ pa_tagstruct_put_usec(reply, s->configured_sink_latency);
+
+ if (c->version >= 21) {
+ /* Send back the format we negotiated */
+ if (s->sink_input->format)
+ pa_tagstruct_put_format_info(reply, s->sink_input->format);
+ else {
+ pa_format_info *f = pa_format_info_new();
+ pa_tagstruct_put_format_info(reply, f);
+ pa_format_info_free(f);
+ }
+ }
+
+ pa_pstream_send_tagstruct(c->pstream, reply);
+
+finish:
+ if (p)
+ pa_proplist_free(p);
+ if (formats)
+ pa_idxset_free(formats, (pa_free_cb_t) pa_format_info_free);
+}
+
+static void command_delete_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
+ uint32_t channel;
+
+ pa_native_connection_assert_ref(c);
+ pa_assert(t);
+
+ if (pa_tagstruct_getu32(t, &channel) < 0 ||
+ !pa_tagstruct_eof(t)) {
+ protocol_error(c);
+ return;
+ }
+
+ CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
+
+ switch (command) {
+
+ case PA_COMMAND_DELETE_PLAYBACK_STREAM: {
+ playback_stream *s;
+ if (!(s = pa_idxset_get_by_index(c->output_streams, channel)) || !playback_stream_isinstance(s)) {
+ pa_pstream_send_error(c->pstream, tag, PA_ERR_NOENTITY);
+ return;
+ }
+
+ playback_stream_unlink(s);
+ break;
+ }
+
+ case PA_COMMAND_DELETE_RECORD_STREAM: {
+ record_stream *s;
+ if (!(s = pa_idxset_get_by_index(c->record_streams, channel))) {
+ pa_pstream_send_error(c->pstream, tag, PA_ERR_NOENTITY);
+ return;
+ }
+
+ record_stream_unlink(s);
+ break;
+ }
+
+ case PA_COMMAND_DELETE_UPLOAD_STREAM: {
+ upload_stream *s;
+
+ if (!(s = pa_idxset_get_by_index(c->output_streams, channel)) || !upload_stream_isinstance(s)) {
+ pa_pstream_send_error(c->pstream, tag, PA_ERR_NOENTITY);
+ return;
+ }
+
+ upload_stream_unlink(s);
+ break;
+ }
+
+ default:
+ pa_assert_not_reached();
+ }
+
+ pa_pstream_send_simple_ack(c->pstream, tag);
+}
+
+static void command_create_record_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
+ record_stream *s;
+ pa_buffer_attr attr;
+ uint32_t source_index;
+ const char *name = NULL, *source_name;
+ pa_sample_spec ss;
+ pa_channel_map map;
+ pa_tagstruct *reply;
+ pa_source *source = NULL;
+ pa_cvolume volume;
+ bool
+ corked = false,
+ no_remap = false,
+ no_remix = false,
+ fix_format = false,
+ fix_rate = false,
+ fix_channels = false,
+ no_move = false,
+ variable_rate = false,
+ muted = false,
+ adjust_latency = false,
+ peak_detect = false,
+ early_requests = false,
+ dont_inhibit_auto_suspend = false,
+ volume_set = false,
+ muted_set = false,
+ fail_on_suspend = false,
+ relative_volume = false,
+ passthrough = false;
+
+ pa_source_output_flags_t flags = 0;
+ pa_proplist *p = NULL;
+ uint32_t direct_on_input_idx = PA_INVALID_INDEX;
+ pa_sink_input *direct_on_input = NULL;
+ int ret = PA_ERR_INVALID;
+ uint8_t n_formats = 0;
+ pa_format_info *format;
+ pa_idxset *formats = NULL;
+ uint32_t i;
+
+ pa_native_connection_assert_ref(c);
+ pa_assert(t);
+
+ memset(&attr, 0, sizeof(attr));
+
+ if ((c->version < 13 && (pa_tagstruct_gets(t, &name) < 0 || !name)) ||
+ pa_tagstruct_get_sample_spec(t, &ss) < 0 ||
+ pa_tagstruct_get_channel_map(t, &map) < 0 ||
+ pa_tagstruct_getu32(t, &source_index) < 0 ||
+ pa_tagstruct_gets(t, &source_name) < 0 ||
+ pa_tagstruct_getu32(t, &attr.maxlength) < 0 ||
+ pa_tagstruct_get_boolean(t, &corked) < 0 ||
+ pa_tagstruct_getu32(t, &attr.fragsize) < 0) {
+
+ protocol_error(c);
+ goto finish;
+ }
+
+ CHECK_VALIDITY_GOTO(c->pstream, c->authorized, tag, PA_ERR_ACCESS, finish);
+ CHECK_VALIDITY_GOTO(c->pstream, !source_name || pa_namereg_is_valid_name_or_wildcard(source_name, PA_NAMEREG_SOURCE), tag, PA_ERR_INVALID, finish);
+ CHECK_VALIDITY_GOTO(c->pstream, source_index == PA_INVALID_INDEX || !source_name, tag, PA_ERR_INVALID, finish);
+ CHECK_VALIDITY_GOTO(c->pstream, !source_name || source_index == PA_INVALID_INDEX, tag, PA_ERR_INVALID, finish);
+
+ p = pa_proplist_new();
+
+ if (name)
+ pa_proplist_sets(p, PA_PROP_MEDIA_NAME, name);
+
+ if (c->version >= 12) {
+ /* Since 0.9.8 the user can ask for a couple of additional flags */
+
+ if (pa_tagstruct_get_boolean(t, &no_remap) < 0 ||
+ pa_tagstruct_get_boolean(t, &no_remix) < 0 ||
+ pa_tagstruct_get_boolean(t, &fix_format) < 0 ||
+ pa_tagstruct_get_boolean(t, &fix_rate) < 0 ||
+ pa_tagstruct_get_boolean(t, &fix_channels) < 0 ||
+ pa_tagstruct_get_boolean(t, &no_move) < 0 ||
+ pa_tagstruct_get_boolean(t, &variable_rate) < 0) {
+
+ protocol_error(c);
+ goto finish;
+ }
+ }
+
+ if (c->version >= 13) {
+
+ if (pa_tagstruct_get_boolean(t, &peak_detect) < 0 ||
+ pa_tagstruct_get_boolean(t, &adjust_latency) < 0 ||
+ pa_tagstruct_get_proplist(t, p) < 0 ||
+ pa_tagstruct_getu32(t, &direct_on_input_idx) < 0) {
+
+ protocol_error(c);
+ goto finish;
+ }
+ }
+
+ if (c->version >= 14) {
+
+ if (pa_tagstruct_get_boolean(t, &early_requests) < 0) {
+ protocol_error(c);
+ goto finish;
+ }
+ }
+
+ if (c->version >= 15) {
+
+ if (pa_tagstruct_get_boolean(t, &dont_inhibit_auto_suspend) < 0 ||
+ pa_tagstruct_get_boolean(t, &fail_on_suspend) < 0) {
+
+ protocol_error(c);
+ goto finish;
+ }
+ }
+
+ if (c->version >= 22) {
+ /* For newer client versions (with per-source-output volumes), we try
+ * to make the behaviour for playback and record streams the same. */
+ volume_set = true;
+
+ if (pa_tagstruct_getu8(t, &n_formats) < 0) {
+ protocol_error(c);
+ goto finish;
+ }
+
+ if (n_formats)
+ formats = pa_idxset_new(NULL, NULL);
+
+ for (i = 0; i < n_formats; i++) {
+ format = pa_format_info_new();
+ if (pa_tagstruct_get_format_info(t, format) < 0) {
+ protocol_error(c);
+ goto finish;
+ }
+ pa_idxset_put(formats, format, NULL);
+ }
+
+ if (pa_tagstruct_get_cvolume(t, &volume) < 0 ||
+ pa_tagstruct_get_boolean(t, &muted) < 0 ||
+ pa_tagstruct_get_boolean(t, &volume_set) < 0 ||
+ pa_tagstruct_get_boolean(t, &muted_set) < 0 ||
+ pa_tagstruct_get_boolean(t, &relative_volume) < 0 ||
+ pa_tagstruct_get_boolean(t, &passthrough) < 0) {
+
+ protocol_error(c);
+ goto finish;
+ }
+
+ CHECK_VALIDITY_GOTO(c->pstream, pa_cvolume_valid(&volume), tag, PA_ERR_INVALID, finish);
+ }
+
+ if (n_formats == 0) {
+ CHECK_VALIDITY_GOTO(c->pstream, pa_sample_spec_valid(&ss), tag, PA_ERR_INVALID, finish);
+ CHECK_VALIDITY_GOTO(c->pstream, map.channels == ss.channels, tag, PA_ERR_INVALID, finish);
+ CHECK_VALIDITY_GOTO(c->pstream, c->version < 22 || (volume.channels == ss.channels), tag, PA_ERR_INVALID, finish);
+ CHECK_VALIDITY_GOTO(c->pstream, pa_channel_map_valid(&map), tag, PA_ERR_INVALID, finish);
+ } else {
+ PA_IDXSET_FOREACH(format, formats, i) {
+ CHECK_VALIDITY_GOTO(c->pstream, pa_format_info_valid(format), tag, PA_ERR_INVALID, finish);
+ }
+ }
+
+ if (!pa_tagstruct_eof(t)) {
+ protocol_error(c);
+ goto finish;
+ }
+
+ if (source_index != PA_INVALID_INDEX) {
+
+ if (!(source = pa_idxset_get_by_index(c->protocol->core->sources, source_index))) {
+ pa_pstream_send_error(c->pstream, tag, PA_ERR_NOENTITY);
+ goto finish;
+ }
+
+ } else if (source_name) {
+
+ if (!(source = pa_namereg_get(c->protocol->core, source_name, PA_NAMEREG_SOURCE))) {
+ pa_pstream_send_error(c->pstream, tag, PA_ERR_NOENTITY);
+ goto finish;
+ }
+ }
+
+ if (direct_on_input_idx != PA_INVALID_INDEX) {
+
+ if (!(direct_on_input = pa_idxset_get_by_index(c->protocol->core->sink_inputs, direct_on_input_idx))) {
+ pa_pstream_send_error(c->pstream, tag, PA_ERR_NOENTITY);
+ goto finish;
+ }
+ }
+
+ flags =
+ (corked ? PA_SOURCE_OUTPUT_START_CORKED : 0) |
+ (no_remap ? PA_SOURCE_OUTPUT_NO_REMAP : 0) |
+ (no_remix ? PA_SOURCE_OUTPUT_NO_REMIX : 0) |
+ (fix_format ? PA_SOURCE_OUTPUT_FIX_FORMAT : 0) |
+ (fix_rate ? PA_SOURCE_OUTPUT_FIX_RATE : 0) |
+ (fix_channels ? PA_SOURCE_OUTPUT_FIX_CHANNELS : 0) |
+ (no_move ? PA_SOURCE_OUTPUT_DONT_MOVE : 0) |
+ (variable_rate ? PA_SOURCE_OUTPUT_VARIABLE_RATE : 0) |
+ (dont_inhibit_auto_suspend ? PA_SOURCE_OUTPUT_DONT_INHIBIT_AUTO_SUSPEND : 0) |
+ (fail_on_suspend ? PA_SOURCE_OUTPUT_NO_CREATE_ON_SUSPEND|PA_SOURCE_OUTPUT_KILL_ON_SUSPEND : 0) |
+ (passthrough ? PA_SOURCE_OUTPUT_PASSTHROUGH : 0);
+
+ s = record_stream_new(c, source, &ss, &map, formats, &attr, volume_set ? &volume : NULL, muted, muted_set, flags, p, adjust_latency, early_requests, relative_volume, peak_detect, direct_on_input, &ret);
+ /* We no longer own the formats idxset */
+ formats = NULL;
+
+ CHECK_VALIDITY_GOTO(c->pstream, s, tag, ret, finish);
+
+ reply = reply_new(tag);
+ pa_tagstruct_putu32(reply, s->index);
+ pa_assert(s->source_output);
+ pa_tagstruct_putu32(reply, s->source_output->index);
+
+ if (c->version >= 9) {
+ /* Since 0.9 we support sending the buffer metrics back to the client */
+
+ pa_tagstruct_putu32(reply, (uint32_t) s->buffer_attr.maxlength);
+ pa_tagstruct_putu32(reply, (uint32_t) s->buffer_attr.fragsize);
+ }
+
+ if (c->version >= 12) {
+ /* Since 0.9.8 we support sending the chosen sample
+ * spec/channel map/device/suspend status back to the
+ * client */
+
+ pa_tagstruct_put_sample_spec(reply, &ss);
+ pa_tagstruct_put_channel_map(reply, &map);
+
+ pa_tagstruct_putu32(reply, s->source_output->source->index);
+ pa_tagstruct_puts(reply, s->source_output->source->name);
+
+ pa_tagstruct_put_boolean(reply, s->source_output->source->state == PA_SOURCE_SUSPENDED);
+ }
+
+ if (c->version >= 13)
+ pa_tagstruct_put_usec(reply, s->configured_source_latency);
+
+ if (c->version >= 22) {
+ /* Send back the format we negotiated */
+ if (s->source_output->format)
+ pa_tagstruct_put_format_info(reply, s->source_output->format);
+ else {
+ pa_format_info *f = pa_format_info_new();
+ pa_tagstruct_put_format_info(reply, f);
+ pa_format_info_free(f);
+ }
+ }
+
+ pa_pstream_send_tagstruct(c->pstream, reply);
+
+finish:
+ if (p)
+ pa_proplist_free(p);
+ if (formats)
+ pa_idxset_free(formats, (pa_free_cb_t) pa_format_info_free);
+}
+
+static void command_exit(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
+ int ret;
+
+ pa_native_connection_assert_ref(c);
+ pa_assert(t);
+
+ if (!pa_tagstruct_eof(t)) {
+ protocol_error(c);
+ return;
+ }
+
+ CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
+ ret = pa_core_exit(c->protocol->core, false, 0);
+ CHECK_VALIDITY(c->pstream, ret >= 0, tag, PA_ERR_ACCESS);
+
+ pa_log_debug("Client %s asks us to terminate.", pa_strnull(pa_proplist_gets(c->client->proplist, PA_PROP_APPLICATION_PROCESS_BINARY)));
+
+ pa_pstream_send_simple_ack(c->pstream, tag); /* nonsense */
+}
+
+static void setup_srbchannel(pa_native_connection *c, pa_mem_type_t shm_type) {
+ pa_srbchannel_template srbt;
+ pa_srbchannel *srb;
+ pa_memchunk mc;
+ pa_tagstruct *t;
+ int fdlist[2];
+
+#ifndef HAVE_CREDS
+ pa_log_debug("Disabling srbchannel, reason: No fd passing support");
+ return;
+#endif
+
+ if (!c->options->srbchannel) {
+ pa_log_debug("Disabling srbchannel, reason: Must be enabled by module parameter");
+ return;
+ }
+
+ if (c->version < 30) {
+ pa_log_debug("Disabling srbchannel, reason: Protocol too old");
+ return;
+ }
+
+ if (!pa_pstream_get_shm(c->pstream)) {
+ pa_log_debug("Disabling srbchannel, reason: No SHM support");
+ return;
+ }
+
+ if (c->rw_mempool) {
+ pa_log_debug("Ignoring srbchannel setup, reason: received COMMAND_AUTH "
+ "more than once");
+ return;
+ }
+
+ if (!(c->rw_mempool = pa_mempool_new(shm_type, c->protocol->core->shm_size, true))) {
+ pa_log_warn("Disabling srbchannel, reason: Failed to allocate shared "
+ "writable memory pool.");
+ return;
+ }
+
+ if (shm_type == PA_MEM_TYPE_SHARED_MEMFD) {
+ const char *reason;
+ if (pa_pstream_register_memfd_mempool(c->pstream, c->rw_mempool, &reason)) {
+ pa_log_warn("Disabling srbchannel, reason: Failed to register memfd mempool: %s", reason);
+ goto fail;
+ }
+ }
+ pa_mempool_set_is_remote_writable(c->rw_mempool, true);
+
+ srb = pa_srbchannel_new(c->protocol->core->mainloop, c->rw_mempool);
+ if (!srb) {
+ pa_log_debug("Failed to create srbchannel");
+ goto fail;
+ }
+ pa_log_debug("Enabling srbchannel...");
+ pa_srbchannel_export(srb, &srbt);
+
+ /* Send enable command to client */
+ t = pa_tagstruct_new();
+ pa_tagstruct_putu32(t, PA_COMMAND_ENABLE_SRBCHANNEL);
+ pa_tagstruct_putu32(t, (size_t) srb); /* tag */
+ fdlist[0] = srbt.readfd;
+ fdlist[1] = srbt.writefd;
+ pa_pstream_send_tagstruct_with_fds(c->pstream, t, 2, fdlist, false);
+
+ /* Send ringbuffer memblock to client */
+ mc.memblock = srbt.memblock;
+ mc.index = 0;
+ mc.length = pa_memblock_get_length(srbt.memblock);
+ pa_pstream_send_memblock(c->pstream, 0, 0, 0, &mc);
+
+ c->srbpending = srb;
+ return;
+
+fail:
+ if (c->rw_mempool) {
+ pa_mempool_unref(c->rw_mempool);
+ c->rw_mempool = NULL;
+ }
+}
+
+static void command_enable_srbchannel(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
+
+ if (tag != (uint32_t) (size_t) c->srbpending) {
+ protocol_error(c);
+ return;
+ }
+
+ pa_log_debug("Client enabled srbchannel.");
+ pa_pstream_set_srbchannel(c->pstream, c->srbpending);
+ c->srbpending = NULL;
+}
+
+static void command_auth(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
+ const void*cookie;
+ bool memfd_on_remote = false, do_memfd = false;
+ pa_tagstruct *reply;
+ pa_mem_type_t shm_type;
+ bool shm_on_remote = false, do_shm;
+
+ pa_native_connection_assert_ref(c);
+ pa_assert(t);
+
+ if (pa_tagstruct_getu32(t, &c->version) < 0 ||
+ pa_tagstruct_get_arbitrary(t, &cookie, PA_NATIVE_COOKIE_LENGTH) < 0 ||
+ !pa_tagstruct_eof(t)) {
+ protocol_error(c);
+ return;
+ }
+
+ /* Minimum supported version */
+ if (c->version < 8) {
+ pa_pstream_send_error(c->pstream, tag, PA_ERR_VERSION);
+ return;
+ }
+
+ /* Starting with protocol version 13 the MSB of the version tag
+ reflects if shm is available for this pa_native_connection or
+ not. */
+ if ((c->version & PA_PROTOCOL_VERSION_MASK) >= 13) {
+ shm_on_remote = !!(c->version & PA_PROTOCOL_FLAG_SHM);
+
+ /* Starting with protocol version 31, the second MSB of the version
+ * tag reflects whether memfd is supported on the other PA end. */
+ if ((c->version & PA_PROTOCOL_VERSION_MASK) >= 31)
+ memfd_on_remote = !!(c->version & PA_PROTOCOL_FLAG_MEMFD);
+
+ /* Reserve the two most-significant _bytes_ of the version tag
+ * for flags. */
+ c->version &= PA_PROTOCOL_VERSION_MASK;
+ }
+
+ pa_log_debug("Protocol version: remote %u, local %u", c->version, PA_PROTOCOL_VERSION);
+
+ pa_proplist_setf(c->client->proplist, "native-protocol.version", "%u", c->version);
+
+ if (!c->authorized) {
+ bool success = false;
+
+#ifdef HAVE_CREDS
+ const pa_creds *creds;
+
+ if ((creds = pa_pdispatch_creds(pd))) {
+ if (creds->uid == getuid())
+ success = true;
+ else if (c->options->auth_group) {
+ int r;
+ gid_t gid;
+
+ if ((gid = pa_get_gid_of_group(c->options->auth_group)) == (gid_t) -1)
+ pa_log_warn("Failed to get GID of group '%s'", c->options->auth_group);
+ else if (gid == creds->gid)
+ success = true;
+
+ if (!success) {
+ if ((r = pa_uid_in_group(creds->uid, c->options->auth_group)) < 0)
+ pa_log_warn("Failed to check group membership.");
+ else if (r > 0)
+ success = true;
+ }
+ }
+
+ pa_log_info("Got credentials: uid=%lu gid=%lu success=%i",
+ (unsigned long) creds->uid,
+ (unsigned long) creds->gid,
+ (int) success);
+ }
+#endif
+
+ if (!success && c->options->auth_cookie) {
+ const uint8_t *ac;
+
+ if ((ac = pa_auth_cookie_read(c->options->auth_cookie, PA_NATIVE_COOKIE_LENGTH)))
+ if (memcmp(ac, cookie, PA_NATIVE_COOKIE_LENGTH) == 0)
+ success = true;
+ }
+
+ if (!success) {
+ pa_log_warn("Denied access to client with invalid authentication data.");
+ pa_pstream_send_error(c->pstream, tag, PA_ERR_ACCESS);
+ return;
+ }
+
+ c->authorized = true;
+ if (c->auth_timeout_event) {
+ c->protocol->core->mainloop->time_free(c->auth_timeout_event);
+ c->auth_timeout_event = NULL;
+ }
+ }
+
+ /* Enable shared memory and memfd support if possible */
+ do_shm =
+ pa_mempool_is_shared(c->protocol->core->mempool) &&
+ c->is_local;
+
+ pa_log_debug("SHM possible: %s", pa_yes_no(do_shm));
+
+ if (do_shm)
+ if (c->version < 10 || (c->version >= 13 && !shm_on_remote))
+ do_shm = false;
+
+#ifdef HAVE_CREDS
+ if (do_shm) {
+ /* Only enable SHM if both sides are owned by the same
+ * user. This is a security measure because otherwise data
+ * private to the user might leak. */
+
+ const pa_creds *creds;
+ if (!(creds = pa_pdispatch_creds(pd)) || getuid() != creds->uid)
+ do_shm = false;
+ }
+#endif
+
+ pa_log_debug("Negotiated SHM: %s", pa_yes_no(do_shm));
+ pa_pstream_enable_shm(c->pstream, do_shm);
+
+ /* Do not declare memfd support for 9.0 client libraries (protocol v31).
+ *
+ * Although they support memfd transport, such 9.0 clients has an iochannel
+ * bug that would break memfd audio if they're run in 32-bit mode over a
+ * 64-bit kernel. Thus influence them to use the POSIX shared memory model
+ * instead. Check commit 451d1d676237c81 for further details. */
+ do_memfd =
+ c->version >= 32 && do_shm && pa_mempool_is_memfd_backed(c->protocol->core->mempool);
+
+ shm_type = PA_MEM_TYPE_PRIVATE;
+ if (do_shm) {
+ if (do_memfd && memfd_on_remote) {
+ pa_pstream_enable_memfd(c->pstream);
+ shm_type = PA_MEM_TYPE_SHARED_MEMFD;
+ } else
+ shm_type = PA_MEM_TYPE_SHARED_POSIX;
+
+ pa_log_debug("Memfd possible: %s", pa_yes_no(pa_memfd_is_locally_supported()));
+ pa_log_debug("Negotiated SHM type: %s", pa_mem_type_to_string(shm_type));
+ }
+
+ reply = reply_new(tag);
+ pa_tagstruct_putu32(reply, PA_PROTOCOL_VERSION | (do_shm ? 0x80000000 : 0) |
+ (do_memfd ? 0x40000000 : 0));
+
+#ifdef HAVE_CREDS
+{
+ /* SHM support is only enabled after both sides made sure they are the same user. */
+
+ pa_creds ucred;
+
+ ucred.uid = getuid();
+ ucred.gid = getgid();
+
+ pa_pstream_send_tagstruct_with_creds(c->pstream, reply, &ucred);
+}
+#else
+ pa_pstream_send_tagstruct(c->pstream, reply);
+#endif
+
+ /* The client enables memfd transport on its pstream only after
+ * inspecting our version flags to see if we support memfds too.
+ *
+ * Thus register any pools after sending the server's version
+ * flags and _never_ before it. */
+ if (shm_type == PA_MEM_TYPE_SHARED_MEMFD) {
+ const char *reason;
+
+ if (pa_pstream_register_memfd_mempool(c->pstream, c->protocol->core->mempool, &reason))
+ pa_log("Failed to register memfd mempool. Reason: %s", reason);
+ }
+
+ setup_srbchannel(c, shm_type);
+}
+
+static void command_register_memfd_shmid(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
+
+ pa_native_connection_assert_ref(c);
+ pa_assert(t);
+
+ if (pa_common_command_register_memfd_shmid(c->pstream, pd, c->version, command, t))
+ protocol_error(c);
+}
+
+static void command_set_client_name(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
+ const char *name = NULL;
+ pa_proplist *p;
+ pa_tagstruct *reply;
+
+ pa_native_connection_assert_ref(c);
+ pa_assert(t);
+
+ p = pa_proplist_new();
+
+ if ((c->version < 13 && pa_tagstruct_gets(t, &name) < 0) ||
+ (c->version >= 13 && pa_tagstruct_get_proplist(t, p) < 0) ||
+ !pa_tagstruct_eof(t)) {
+
+ protocol_error(c);
+ pa_proplist_free(p);
+ return;
+ }
+
+ if (name)
+ if (pa_proplist_sets(p, PA_PROP_APPLICATION_NAME, name) < 0) {
+ pa_pstream_send_error(c->pstream, tag, PA_ERR_INVALID);
+ pa_proplist_free(p);
+ return;
+ }
+
+ pa_client_update_proplist(c->client, PA_UPDATE_REPLACE, p);
+ pa_proplist_free(p);
+
+ reply = reply_new(tag);
+
+ if (c->version >= 13)
+ pa_tagstruct_putu32(reply, c->client->index);
+
+ pa_pstream_send_tagstruct(c->pstream, reply);
+}
+
+static void command_lookup(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
+ const char *name;
+ uint32_t idx = PA_IDXSET_INVALID;
+
+ pa_native_connection_assert_ref(c);
+ pa_assert(t);
+
+ if (pa_tagstruct_gets(t, &name) < 0 ||
+ !pa_tagstruct_eof(t)) {
+ protocol_error(c);
+ return;
+ }
+
+ CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
+ CHECK_VALIDITY(c->pstream, name && pa_namereg_is_valid_name_or_wildcard(name, command == PA_COMMAND_LOOKUP_SINK ? PA_NAMEREG_SINK : PA_NAMEREG_SOURCE), tag, PA_ERR_INVALID);
+
+ if (command == PA_COMMAND_LOOKUP_SINK) {
+ pa_sink *sink;
+ if ((sink = pa_namereg_get(c->protocol->core, name, PA_NAMEREG_SINK)))
+ idx = sink->index;
+ } else {
+ pa_source *source;
+ pa_assert(command == PA_COMMAND_LOOKUP_SOURCE);
+ if ((source = pa_namereg_get(c->protocol->core, name, PA_NAMEREG_SOURCE)))
+ idx = source->index;
+ }
+
+ if (idx == PA_IDXSET_INVALID)
+ pa_pstream_send_error(c->pstream, tag, PA_ERR_NOENTITY);
+ else {
+ pa_tagstruct *reply;
+ reply = reply_new(tag);
+ pa_tagstruct_putu32(reply, idx);
+ pa_pstream_send_tagstruct(c->pstream, reply);
+ }
+}
+
+static void command_drain_playback_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
+ uint32_t idx;
+ playback_stream *s;
+
+ pa_native_connection_assert_ref(c);
+ pa_assert(t);
+
+ if (pa_tagstruct_getu32(t, &idx) < 0 ||
+ !pa_tagstruct_eof(t)) {
+ protocol_error(c);
+ return;
+ }
+
+ CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
+ s = pa_idxset_get_by_index(c->output_streams, idx);
+ CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY);
+ CHECK_VALIDITY(c->pstream, playback_stream_isinstance(s), tag, PA_ERR_NOENTITY);
+
+ pa_asyncmsgq_post(s->sink_input->sink->asyncmsgq, PA_MSGOBJECT(s->sink_input), SINK_INPUT_MESSAGE_DRAIN, PA_UINT_TO_PTR(tag), 0, NULL, NULL);
+}
+
+static void command_stat(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
+ pa_tagstruct *reply;
+ const pa_mempool_stat *stat;
+
+ pa_native_connection_assert_ref(c);
+ pa_assert(t);
+
+ if (!pa_tagstruct_eof(t)) {
+ protocol_error(c);
+ return;
+ }
+
+ CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
+
+ stat = pa_mempool_get_stat(c->protocol->core->mempool);
+
+ reply = reply_new(tag);
+ pa_tagstruct_putu32(reply, (uint32_t) pa_atomic_load(&stat->n_allocated));
+ pa_tagstruct_putu32(reply, (uint32_t) pa_atomic_load(&stat->allocated_size));
+ pa_tagstruct_putu32(reply, (uint32_t) pa_atomic_load(&stat->n_accumulated));
+ pa_tagstruct_putu32(reply, (uint32_t) pa_atomic_load(&stat->accumulated_size));
+ pa_tagstruct_putu32(reply, (uint32_t) pa_scache_total_size(c->protocol->core));
+ pa_pstream_send_tagstruct(c->pstream, reply);
+}
+
+static void command_get_playback_latency(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
+ pa_tagstruct *reply;
+ playback_stream *s;
+ struct timeval tv, now;
+ uint32_t idx;
+
+ pa_native_connection_assert_ref(c);
+ pa_assert(t);
+
+ if (pa_tagstruct_getu32(t, &idx) < 0 ||
+ pa_tagstruct_get_timeval(t, &tv) < 0 ||
+ !pa_tagstruct_eof(t)) {
+ protocol_error(c);
+ return;
+ }
+
+ CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
+ s = pa_idxset_get_by_index(c->output_streams, idx);
+ CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY);
+ CHECK_VALIDITY(c->pstream, playback_stream_isinstance(s), tag, PA_ERR_NOENTITY);
+
+ /* Get an atomic snapshot of all timing parameters */
+ pa_assert_se(pa_asyncmsgq_send(s->sink_input->sink->asyncmsgq, PA_MSGOBJECT(s->sink_input), SINK_INPUT_MESSAGE_UPDATE_LATENCY, s, 0, NULL) == 0);
+
+ reply = reply_new(tag);
+ pa_tagstruct_put_usec(reply,
+ s->current_sink_latency +
+ pa_bytes_to_usec(s->render_memblockq_length, &s->sink_input->sink->sample_spec));
+ pa_tagstruct_put_usec(reply, 0);
+ pa_tagstruct_put_boolean(reply,
+ s->playing_for > 0 &&
+ s->sink_input->sink->state == PA_SINK_RUNNING &&
+ s->sink_input->state == PA_SINK_INPUT_RUNNING);
+ pa_tagstruct_put_timeval(reply, &tv);
+ pa_tagstruct_put_timeval(reply, pa_gettimeofday(&now));
+ pa_tagstruct_puts64(reply, s->write_index);
+ pa_tagstruct_puts64(reply, s->read_index);
+
+ if (c->version >= 13) {
+ pa_tagstruct_putu64(reply, s->underrun_for);
+ pa_tagstruct_putu64(reply, s->playing_for);
+ }
+
+ pa_pstream_send_tagstruct(c->pstream, reply);
+}
+
+static void command_get_record_latency(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
+ pa_tagstruct *reply;
+ record_stream *s;
+ struct timeval tv, now;
+ uint32_t idx;
+
+ pa_native_connection_assert_ref(c);
+ pa_assert(t);
+
+ if (pa_tagstruct_getu32(t, &idx) < 0 ||
+ pa_tagstruct_get_timeval(t, &tv) < 0 ||
+ !pa_tagstruct_eof(t)) {
+ protocol_error(c);
+ return;
+ }
+
+ CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
+ s = pa_idxset_get_by_index(c->record_streams, idx);
+ CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY);
+
+ /* Get an atomic snapshot of all timing parameters */
+ pa_assert_se(pa_asyncmsgq_send(s->source_output->source->asyncmsgq, PA_MSGOBJECT(s->source_output), SOURCE_OUTPUT_MESSAGE_UPDATE_LATENCY, s, 0, NULL) == 0);
+
+ reply = reply_new(tag);
+ pa_tagstruct_put_usec(reply, s->current_monitor_latency);
+ pa_tagstruct_put_usec(reply,
+ s->current_source_latency +
+ pa_bytes_to_usec(s->on_the_fly_snapshot, &s->source_output->sample_spec));
+ pa_tagstruct_put_boolean(reply,
+ s->source_output->source->state == PA_SOURCE_RUNNING &&
+ s->source_output->state == PA_SOURCE_OUTPUT_RUNNING);
+ pa_tagstruct_put_timeval(reply, &tv);
+ pa_tagstruct_put_timeval(reply, pa_gettimeofday(&now));
+ pa_tagstruct_puts64(reply, pa_memblockq_get_write_index(s->memblockq));
+ pa_tagstruct_puts64(reply, pa_memblockq_get_read_index(s->memblockq));
+ pa_pstream_send_tagstruct(c->pstream, reply);
+}
+
+static void command_create_upload_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
+ upload_stream *s;
+ uint32_t length;
+ const char *name = NULL;
+ pa_sample_spec ss;
+ pa_channel_map map;
+ pa_tagstruct *reply;
+ pa_proplist *p;
+
+ pa_native_connection_assert_ref(c);
+ pa_assert(t);
+
+ if (pa_tagstruct_gets(t, &name) < 0 ||
+ pa_tagstruct_get_sample_spec(t, &ss) < 0 ||
+ pa_tagstruct_get_channel_map(t, &map) < 0 ||
+ pa_tagstruct_getu32(t, &length) < 0) {
+ protocol_error(c);
+ return;
+ }
+
+ CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
+ CHECK_VALIDITY(c->pstream, pa_sample_spec_valid(&ss), tag, PA_ERR_INVALID);
+ CHECK_VALIDITY(c->pstream, pa_channel_map_valid(&map), tag, PA_ERR_INVALID);
+ CHECK_VALIDITY(c->pstream, map.channels == ss.channels, tag, PA_ERR_INVALID);
+ CHECK_VALIDITY(c->pstream, (length % pa_frame_size(&ss)) == 0 && length > 0, tag, PA_ERR_INVALID);
+ CHECK_VALIDITY(c->pstream, length <= PA_SCACHE_ENTRY_SIZE_MAX, tag, PA_ERR_TOOLARGE);
+
+ p = pa_proplist_new();
+
+ if ((c->version >= 13 && pa_tagstruct_get_proplist(t, p) < 0) ||
+ !pa_tagstruct_eof(t)) {
+
+ protocol_error(c);
+ pa_proplist_free(p);
+ return;
+ }
+
+ if (c->version < 13)
+ pa_proplist_sets(p, PA_PROP_MEDIA_NAME, name);
+ else if (!name)
+ if (!(name = pa_proplist_gets(p, PA_PROP_EVENT_ID)))
+ name = pa_proplist_gets(p, PA_PROP_MEDIA_NAME);
+
+ if (!name || !pa_namereg_is_valid_name(name)) {
+ pa_proplist_free(p);
+ CHECK_VALIDITY(c->pstream, false, tag, PA_ERR_INVALID);
+ }
+
+ s = upload_stream_new(c, &ss, &map, name, length, p);
+ pa_proplist_free(p);
+
+ CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_INVALID);
+
+ reply = reply_new(tag);
+ pa_tagstruct_putu32(reply, s->index);
+ pa_tagstruct_putu32(reply, length);
+ pa_pstream_send_tagstruct(c->pstream, reply);
+}
+
+static void command_finish_upload_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
+ uint32_t channel;
+ upload_stream *s;
+ uint32_t idx;
+
+ pa_native_connection_assert_ref(c);
+ pa_assert(t);
+
+ if (pa_tagstruct_getu32(t, &channel) < 0 ||
+ !pa_tagstruct_eof(t)) {
+ protocol_error(c);
+ return;
+ }
+
+ CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
+
+ s = pa_idxset_get_by_index(c->output_streams, channel);
+ CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY);
+ CHECK_VALIDITY(c->pstream, upload_stream_isinstance(s), tag, PA_ERR_NOENTITY);
+
+ if (!s->memchunk.memblock)
+ pa_pstream_send_error(c->pstream, tag, PA_ERR_TOOLARGE);
+ else if (pa_scache_add_item(c->protocol->core, s->name, &s->sample_spec, &s->channel_map, &s->memchunk, s->proplist, &idx) < 0)
+ pa_pstream_send_error(c->pstream, tag, PA_ERR_INTERNAL);
+ else
+ pa_pstream_send_simple_ack(c->pstream, tag);
+
+ upload_stream_unlink(s);
+}
+
+static void command_play_sample(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
+ uint32_t sink_index;
+ pa_volume_t volume;
+ pa_sink *sink;
+ const char *name, *sink_name;
+ uint32_t idx;
+ pa_proplist *p;
+ pa_tagstruct *reply;
+
+ pa_native_connection_assert_ref(c);
+ pa_assert(t);
+
+ CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
+
+ if (pa_tagstruct_getu32(t, &sink_index) < 0 ||
+ pa_tagstruct_gets(t, &sink_name) < 0 ||
+ pa_tagstruct_getu32(t, &volume) < 0 ||
+ pa_tagstruct_gets(t, &name) < 0) {
+ protocol_error(c);
+ return;
+ }
+
+ CHECK_VALIDITY(c->pstream, !sink_name || pa_namereg_is_valid_name_or_wildcard(sink_name, PA_NAMEREG_SINK), tag, PA_ERR_INVALID);
+ CHECK_VALIDITY(c->pstream, sink_index == PA_INVALID_INDEX || !sink_name, tag, PA_ERR_INVALID);
+ CHECK_VALIDITY(c->pstream, !sink_name || sink_index == PA_INVALID_INDEX, tag, PA_ERR_INVALID);
+ CHECK_VALIDITY(c->pstream, name && pa_namereg_is_valid_name(name), tag, PA_ERR_INVALID);
+
+ if (sink_index != PA_INVALID_INDEX)
+ sink = pa_idxset_get_by_index(c->protocol->core->sinks, sink_index);
+ else
+ sink = pa_namereg_get(c->protocol->core, sink_name, PA_NAMEREG_SINK);
+
+ CHECK_VALIDITY(c->pstream, sink, tag, PA_ERR_NOENTITY);
+
+ p = pa_proplist_new();
+
+ if ((c->version >= 13 && pa_tagstruct_get_proplist(t, p) < 0) ||
+ !pa_tagstruct_eof(t)) {
+ protocol_error(c);
+ pa_proplist_free(p);
+ return;
+ }
+
+ pa_proplist_update(p, PA_UPDATE_MERGE, c->client->proplist);
+
+ if (pa_scache_play_item(c->protocol->core, name, sink, volume, p, &idx) < 0) {
+ pa_pstream_send_error(c->pstream, tag, PA_ERR_NOENTITY);
+ pa_proplist_free(p);
+ return;
+ }
+
+ pa_proplist_free(p);
+
+ reply = reply_new(tag);
+
+ if (c->version >= 13)
+ pa_tagstruct_putu32(reply, idx);
+
+ pa_pstream_send_tagstruct(c->pstream, reply);
+}
+
+static void command_remove_sample(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
+ const char *name;
+
+ pa_native_connection_assert_ref(c);
+ pa_assert(t);
+
+ if (pa_tagstruct_gets(t, &name) < 0 ||
+ !pa_tagstruct_eof(t)) {
+ protocol_error(c);
+ return;
+ }
+
+ CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
+ CHECK_VALIDITY(c->pstream, name && pa_namereg_is_valid_name(name), tag, PA_ERR_INVALID);
+
+ if (pa_scache_remove_item(c->protocol->core, name) < 0) {
+ pa_pstream_send_error(c->pstream, tag, PA_ERR_NOENTITY);
+ return;
+ }
+
+ pa_pstream_send_simple_ack(c->pstream, tag);
+}
+
+static void fixup_sample_spec(pa_native_connection *c, pa_sample_spec *fixed, const pa_sample_spec *original) {
+ pa_assert(c);
+ pa_assert(fixed);
+ pa_assert(original);
+
+ *fixed = *original;
+
+ if (c->version < 12) {
+ /* Before protocol version 12 we didn't support S32 samples,
+ * so we need to lie about this to the client */
+
+ if (fixed->format == PA_SAMPLE_S32LE)
+ fixed->format = PA_SAMPLE_FLOAT32LE;
+ if (fixed->format == PA_SAMPLE_S32BE)
+ fixed->format = PA_SAMPLE_FLOAT32BE;
+ }
+
+ if (c->version < 15) {
+ if (fixed->format == PA_SAMPLE_S24LE || fixed->format == PA_SAMPLE_S24_32LE)
+ fixed->format = PA_SAMPLE_FLOAT32LE;
+ if (fixed->format == PA_SAMPLE_S24BE || fixed->format == PA_SAMPLE_S24_32BE)
+ fixed->format = PA_SAMPLE_FLOAT32BE;
+ }
+}
+
+static void sink_fill_tagstruct(pa_native_connection *c, pa_tagstruct *t, pa_sink *sink) {
+ pa_sample_spec fixed_ss;
+
+ pa_assert(t);
+ pa_sink_assert_ref(sink);
+
+ fixup_sample_spec(c, &fixed_ss, &sink->sample_spec);
+
+ pa_tagstruct_put(
+ t,
+ PA_TAG_U32, sink->index,
+ PA_TAG_STRING, sink->name,
+ PA_TAG_STRING, pa_strnull(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_DESCRIPTION)),
+ PA_TAG_SAMPLE_SPEC, &fixed_ss,
+ PA_TAG_CHANNEL_MAP, &sink->channel_map,
+ PA_TAG_U32, sink->module ? sink->module->index : PA_INVALID_INDEX,
+ PA_TAG_CVOLUME, pa_sink_get_volume(sink, false),
+ PA_TAG_BOOLEAN, pa_sink_get_mute(sink, false),
+ PA_TAG_U32, sink->monitor_source ? sink->monitor_source->index : PA_INVALID_INDEX,
+ PA_TAG_STRING, sink->monitor_source ? sink->monitor_source->name : NULL,
+ PA_TAG_USEC, pa_sink_get_latency(sink),
+ PA_TAG_STRING, sink->driver,
+ PA_TAG_U32, sink->flags & PA_SINK_CLIENT_FLAGS_MASK,
+ PA_TAG_INVALID);
+
+ if (c->version >= 13) {
+ pa_tagstruct_put_proplist(t, sink->proplist);
+ pa_tagstruct_put_usec(t, pa_sink_get_requested_latency(sink));
+ }
+
+ if (c->version >= 15) {
+ pa_tagstruct_put_volume(t, sink->base_volume);
+ if (PA_UNLIKELY(sink->state == PA_SINK_INVALID_STATE))
+ pa_log_error("Internal sink state is invalid.");
+ pa_tagstruct_putu32(t, sink->state);
+ pa_tagstruct_putu32(t, sink->n_volume_steps);
+ pa_tagstruct_putu32(t, sink->card ? sink->card->index : PA_INVALID_INDEX);
+ }
+
+ if (c->version >= 16) {
+ void *state;
+ pa_device_port *p;
+
+ pa_tagstruct_putu32(t, pa_hashmap_size(sink->ports));
+
+ PA_HASHMAP_FOREACH(p, sink->ports, state) {
+ pa_tagstruct_puts(t, p->name);
+ pa_tagstruct_puts(t, p->description);
+ pa_tagstruct_putu32(t, p->priority);
+ if (c->version >= 24) {
+ pa_tagstruct_putu32(t, p->available);
+ if (c->version >= 34) {
+ pa_tagstruct_puts(t, p->availability_group);
+ pa_tagstruct_putu32(t, p->type);
+ }
+ }
+ }
+
+ pa_tagstruct_puts(t, sink->active_port ? sink->active_port->name : NULL);
+ }
+
+ if (c->version >= 21) {
+ uint32_t i;
+ pa_format_info *f;
+ pa_idxset *formats = pa_sink_get_formats(sink);
+
+ pa_tagstruct_putu8(t, (uint8_t) pa_idxset_size(formats));
+ PA_IDXSET_FOREACH(f, formats, i) {
+ pa_tagstruct_put_format_info(t, f);
+ }
+
+ pa_idxset_free(formats, (pa_free_cb_t) pa_format_info_free);
+ }
+}
+
+static void source_fill_tagstruct(pa_native_connection *c, pa_tagstruct *t, pa_source *source) {
+ pa_sample_spec fixed_ss;
+
+ pa_assert(t);
+ pa_source_assert_ref(source);
+
+ fixup_sample_spec(c, &fixed_ss, &source->sample_spec);
+
+ pa_tagstruct_put(
+ t,
+ PA_TAG_U32, source->index,
+ PA_TAG_STRING, source->name,
+ PA_TAG_STRING, pa_strnull(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_DESCRIPTION)),
+ PA_TAG_SAMPLE_SPEC, &fixed_ss,
+ PA_TAG_CHANNEL_MAP, &source->channel_map,
+ PA_TAG_U32, source->module ? source->module->index : PA_INVALID_INDEX,
+ PA_TAG_CVOLUME, pa_source_get_volume(source, false),
+ PA_TAG_BOOLEAN, pa_source_get_mute(source, false),
+ PA_TAG_U32, source->monitor_of ? source->monitor_of->index : PA_INVALID_INDEX,
+ PA_TAG_STRING, source->monitor_of ? source->monitor_of->name : NULL,
+ PA_TAG_USEC, pa_source_get_latency(source),
+ PA_TAG_STRING, source->driver,
+ PA_TAG_U32, source->flags & PA_SOURCE_CLIENT_FLAGS_MASK,
+ PA_TAG_INVALID);
+
+ if (c->version >= 13) {
+ pa_tagstruct_put_proplist(t, source->proplist);
+ pa_tagstruct_put_usec(t, pa_source_get_requested_latency(source));
+ }
+
+ if (c->version >= 15) {
+ pa_tagstruct_put_volume(t, source->base_volume);
+ if (PA_UNLIKELY(source->state == PA_SOURCE_INVALID_STATE))
+ pa_log_error("Internal source state is invalid.");
+ pa_tagstruct_putu32(t, source->state);
+ pa_tagstruct_putu32(t, source->n_volume_steps);
+ pa_tagstruct_putu32(t, source->card ? source->card->index : PA_INVALID_INDEX);
+ }
+
+ if (c->version >= 16) {
+ void *state;
+ pa_device_port *p;
+
+ pa_tagstruct_putu32(t, pa_hashmap_size(source->ports));
+
+ PA_HASHMAP_FOREACH(p, source->ports, state) {
+ pa_tagstruct_puts(t, p->name);
+ pa_tagstruct_puts(t, p->description);
+ pa_tagstruct_putu32(t, p->priority);
+ if (c->version >= 24) {
+ pa_tagstruct_putu32(t, p->available);
+ if (c->version >= 34) {
+ pa_tagstruct_puts(t, p->availability_group);
+ pa_tagstruct_putu32(t, p->type);
+ }
+ }
+ }
+
+ pa_tagstruct_puts(t, source->active_port ? source->active_port->name : NULL);
+ }
+
+ if (c->version >= 22) {
+ uint32_t i;
+ pa_format_info *f;
+ pa_idxset *formats = pa_source_get_formats(source);
+
+ pa_tagstruct_putu8(t, (uint8_t) pa_idxset_size(formats));
+ PA_IDXSET_FOREACH(f, formats, i) {
+ pa_tagstruct_put_format_info(t, f);
+ }
+
+ pa_idxset_free(formats, (pa_free_cb_t) pa_format_info_free);
+ }
+}
+
+static void client_fill_tagstruct(pa_native_connection *c, pa_tagstruct *t, pa_client *client) {
+ pa_assert(t);
+ pa_assert(client);
+
+ pa_tagstruct_putu32(t, client->index);
+ pa_tagstruct_puts(t, pa_strnull(pa_proplist_gets(client->proplist, PA_PROP_APPLICATION_NAME)));
+ pa_tagstruct_putu32(t, client->module ? client->module->index : PA_INVALID_INDEX);
+ pa_tagstruct_puts(t, client->driver);
+
+ if (c->version >= 13)
+ pa_tagstruct_put_proplist(t, client->proplist);
+}
+
+static void card_fill_tagstruct(pa_native_connection *c, pa_tagstruct *t, pa_card *card) {
+ void *state = NULL;
+ pa_card_profile *p;
+ pa_device_port *port;
+
+ pa_assert(t);
+ pa_assert(card);
+
+ pa_tagstruct_putu32(t, card->index);
+ pa_tagstruct_puts(t, card->name);
+ pa_tagstruct_putu32(t, card->module ? card->module->index : PA_INVALID_INDEX);
+ pa_tagstruct_puts(t, card->driver);
+
+ pa_tagstruct_putu32(t, pa_hashmap_size(card->profiles));
+
+ PA_HASHMAP_FOREACH(p, card->profiles, state) {
+ pa_tagstruct_puts(t, p->name);
+ pa_tagstruct_puts(t, p->description);
+ pa_tagstruct_putu32(t, p->n_sinks);
+ pa_tagstruct_putu32(t, p->n_sources);
+ pa_tagstruct_putu32(t, p->priority);
+
+ if (c->version >= 29)
+ pa_tagstruct_putu32(t, (p->available != PA_AVAILABLE_NO));
+ }
+
+ pa_tagstruct_puts(t, card->active_profile->name);
+ pa_tagstruct_put_proplist(t, card->proplist);
+
+ if (c->version < 26)
+ return;
+
+ pa_tagstruct_putu32(t, pa_hashmap_size(card->ports));
+
+ PA_HASHMAP_FOREACH(port, card->ports, state) {
+ void *state2;
+
+ pa_tagstruct_puts(t, port->name);
+ pa_tagstruct_puts(t, port->description);
+ pa_tagstruct_putu32(t, port->priority);
+ pa_tagstruct_putu32(t, port->available);
+ pa_tagstruct_putu8(t, port->direction);
+ pa_tagstruct_put_proplist(t, port->proplist);
+
+ pa_tagstruct_putu32(t, pa_hashmap_size(port->profiles));
+
+ PA_HASHMAP_FOREACH(p, port->profiles, state2)
+ pa_tagstruct_puts(t, p->name);
+
+ if (c->version >= 27) {
+ pa_tagstruct_puts64(t, port->latency_offset);
+ if (c->version >= 34) {
+ pa_tagstruct_puts(t, port->availability_group);
+ pa_tagstruct_putu32(t, port->type);
+ }
+ }
+ }
+}
+
+static void module_fill_tagstruct(pa_native_connection *c, pa_tagstruct *t, pa_module *module) {
+ pa_assert(t);
+ pa_assert(module);
+
+ pa_tagstruct_putu32(t, module->index);
+ pa_tagstruct_puts(t, module->name);
+ pa_tagstruct_puts(t, module->argument);
+ pa_tagstruct_putu32(t, (uint32_t) pa_module_get_n_used(module));
+
+ if (c->version < 15)
+ pa_tagstruct_put_boolean(t, false); /* autoload is obsolete */
+
+ if (c->version >= 15)
+ pa_tagstruct_put_proplist(t, module->proplist);
+}
+
+static void sink_input_fill_tagstruct(pa_native_connection *c, pa_tagstruct *t, pa_sink_input *s) {
+ pa_sample_spec fixed_ss;
+ pa_usec_t sink_latency;
+ pa_cvolume v;
+ bool has_volume = false;
+
+ pa_assert(t);
+ pa_sink_input_assert_ref(s);
+
+ fixup_sample_spec(c, &fixed_ss, &s->sample_spec);
+
+ has_volume = pa_sink_input_is_volume_readable(s);
+ if (has_volume)
+ pa_sink_input_get_volume(s, &v, true);
+ else
+ pa_cvolume_reset(&v, fixed_ss.channels);
+
+ pa_tagstruct_putu32(t, s->index);
+ pa_tagstruct_puts(t, pa_strnull(pa_proplist_gets(s->proplist, PA_PROP_MEDIA_NAME)));
+ pa_tagstruct_putu32(t, s->module ? s->module->index : PA_INVALID_INDEX);
+ pa_tagstruct_putu32(t, s->client ? s->client->index : PA_INVALID_INDEX);
+ pa_tagstruct_putu32(t, s->sink->index);
+ pa_tagstruct_put_sample_spec(t, &fixed_ss);
+ pa_tagstruct_put_channel_map(t, &s->channel_map);
+ pa_tagstruct_put_cvolume(t, &v);
+ pa_tagstruct_put_usec(t, pa_sink_input_get_latency(s, &sink_latency));
+ pa_tagstruct_put_usec(t, sink_latency);
+ pa_tagstruct_puts(t, pa_resample_method_to_string(pa_sink_input_get_resample_method(s)));
+ pa_tagstruct_puts(t, s->driver);
+ if (c->version >= 11)
+ pa_tagstruct_put_boolean(t, s->muted);
+ if (c->version >= 13)
+ pa_tagstruct_put_proplist(t, s->proplist);
+ if (c->version >= 19)
+ pa_tagstruct_put_boolean(t, s->state == PA_SINK_INPUT_CORKED);
+ if (c->version >= 20) {
+ pa_tagstruct_put_boolean(t, has_volume);
+ pa_tagstruct_put_boolean(t, s->volume_writable);
+ }
+ if (c->version >= 21)
+ pa_tagstruct_put_format_info(t, s->format);
+}
+
+static void source_output_fill_tagstruct(pa_native_connection *c, pa_tagstruct *t, pa_source_output *s) {
+ pa_sample_spec fixed_ss;
+ pa_usec_t source_latency;
+ pa_cvolume v;
+ bool has_volume = false;
+
+ pa_assert(t);
+ pa_source_output_assert_ref(s);
+
+ fixup_sample_spec(c, &fixed_ss, &s->sample_spec);
+
+ has_volume = pa_source_output_is_volume_readable(s);
+ if (has_volume)
+ pa_source_output_get_volume(s, &v, true);
+ else
+ pa_cvolume_reset(&v, fixed_ss.channels);
+
+ pa_tagstruct_putu32(t, s->index);
+ pa_tagstruct_puts(t, pa_strnull(pa_proplist_gets(s->proplist, PA_PROP_MEDIA_NAME)));
+ pa_tagstruct_putu32(t, s->module ? s->module->index : PA_INVALID_INDEX);
+ pa_tagstruct_putu32(t, s->client ? s->client->index : PA_INVALID_INDEX);
+ pa_tagstruct_putu32(t, s->source->index);
+ pa_tagstruct_put_sample_spec(t, &fixed_ss);
+ pa_tagstruct_put_channel_map(t, &s->channel_map);
+ pa_tagstruct_put_usec(t, pa_source_output_get_latency(s, &source_latency));
+ pa_tagstruct_put_usec(t, source_latency);
+ pa_tagstruct_puts(t, pa_resample_method_to_string(pa_source_output_get_resample_method(s)));
+ pa_tagstruct_puts(t, s->driver);
+ if (c->version >= 13)
+ pa_tagstruct_put_proplist(t, s->proplist);
+ if (c->version >= 19)
+ pa_tagstruct_put_boolean(t, s->state == PA_SOURCE_OUTPUT_CORKED);
+ if (c->version >= 22) {
+ pa_tagstruct_put_cvolume(t, &v);
+ pa_tagstruct_put_boolean(t, s->muted);
+ pa_tagstruct_put_boolean(t, has_volume);
+ pa_tagstruct_put_boolean(t, s->volume_writable);
+ pa_tagstruct_put_format_info(t, s->format);
+ }
+}
+
+static void scache_fill_tagstruct(pa_native_connection *c, pa_tagstruct *t, pa_scache_entry *e) {
+ pa_sample_spec fixed_ss;
+ pa_cvolume v;
+
+ pa_assert(t);
+ pa_assert(e);
+
+ if (e->memchunk.memblock)
+ fixup_sample_spec(c, &fixed_ss, &e->sample_spec);
+ else
+ memset(&fixed_ss, 0, sizeof(fixed_ss));
+
+ pa_tagstruct_putu32(t, e->index);
+ pa_tagstruct_puts(t, e->name);
+
+ if (e->volume_is_set)
+ v = e->volume;
+ else
+ pa_cvolume_init(&v);
+
+ pa_tagstruct_put_cvolume(t, &v);
+ pa_tagstruct_put_usec(t, e->memchunk.memblock ? pa_bytes_to_usec(e->memchunk.length, &e->sample_spec) : 0);
+ pa_tagstruct_put_sample_spec(t, &fixed_ss);
+ pa_tagstruct_put_channel_map(t, &e->channel_map);
+ pa_tagstruct_putu32(t, (uint32_t) e->memchunk.length);
+ pa_tagstruct_put_boolean(t, e->lazy);
+ pa_tagstruct_puts(t, e->filename);
+
+ if (c->version >= 13)
+ pa_tagstruct_put_proplist(t, e->proplist);
+}
+
+static void command_get_info(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
+ uint32_t idx;
+ pa_sink *sink = NULL;
+ pa_source *source = NULL;
+ pa_client *client = NULL;
+ pa_card *card = NULL;
+ pa_module *module = NULL;
+ pa_sink_input *si = NULL;
+ pa_source_output *so = NULL;
+ pa_scache_entry *sce = NULL;
+ const char *name = NULL;
+ pa_tagstruct *reply;
+
+ pa_native_connection_assert_ref(c);
+ pa_assert(t);
+
+ if (pa_tagstruct_getu32(t, &idx) < 0 ||
+ (command != PA_COMMAND_GET_CLIENT_INFO &&
+ command != PA_COMMAND_GET_MODULE_INFO &&
+ command != PA_COMMAND_GET_SINK_INPUT_INFO &&
+ command != PA_COMMAND_GET_SOURCE_OUTPUT_INFO &&
+ pa_tagstruct_gets(t, &name) < 0) ||
+ !pa_tagstruct_eof(t)) {
+ protocol_error(c);
+ return;
+ }
+
+ CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
+ CHECK_VALIDITY(c->pstream, !name ||
+ (command == PA_COMMAND_GET_SINK_INFO &&
+ pa_namereg_is_valid_name_or_wildcard(name, PA_NAMEREG_SINK)) ||
+ (command == PA_COMMAND_GET_SOURCE_INFO &&
+ pa_namereg_is_valid_name_or_wildcard(name, PA_NAMEREG_SOURCE)) ||
+ pa_namereg_is_valid_name(name), tag, PA_ERR_INVALID);
+ CHECK_VALIDITY(c->pstream, command == PA_COMMAND_GET_SINK_INFO ||
+ command == PA_COMMAND_GET_SOURCE_INFO ||
+ (idx != PA_INVALID_INDEX || name), tag, PA_ERR_INVALID);
+ CHECK_VALIDITY(c->pstream, idx == PA_INVALID_INDEX || !name, tag, PA_ERR_INVALID);
+
+ if (command == PA_COMMAND_GET_SINK_INFO) {
+ if (idx != PA_INVALID_INDEX)
+ sink = pa_idxset_get_by_index(c->protocol->core->sinks, idx);
+ else
+ sink = pa_namereg_get(c->protocol->core, name, PA_NAMEREG_SINK);
+ } else if (command == PA_COMMAND_GET_SOURCE_INFO) {
+ if (idx != PA_INVALID_INDEX)
+ source = pa_idxset_get_by_index(c->protocol->core->sources, idx);
+ else
+ source = pa_namereg_get(c->protocol->core, name, PA_NAMEREG_SOURCE);
+ } else if (command == PA_COMMAND_GET_CARD_INFO) {
+ if (idx != PA_INVALID_INDEX)
+ card = pa_idxset_get_by_index(c->protocol->core->cards, idx);
+ else
+ card = pa_namereg_get(c->protocol->core, name, PA_NAMEREG_CARD);
+ } else if (command == PA_COMMAND_GET_CLIENT_INFO)
+ client = pa_idxset_get_by_index(c->protocol->core->clients, idx);
+ else if (command == PA_COMMAND_GET_MODULE_INFO)
+ module = pa_idxset_get_by_index(c->protocol->core->modules, idx);
+ else if (command == PA_COMMAND_GET_SINK_INPUT_INFO)
+ si = pa_idxset_get_by_index(c->protocol->core->sink_inputs, idx);
+ else if (command == PA_COMMAND_GET_SOURCE_OUTPUT_INFO)
+ so = pa_idxset_get_by_index(c->protocol->core->source_outputs, idx);
+ else {
+ pa_assert(command == PA_COMMAND_GET_SAMPLE_INFO);
+ if (idx != PA_INVALID_INDEX)
+ sce = pa_idxset_get_by_index(c->protocol->core->scache, idx);
+ else
+ sce = pa_namereg_get(c->protocol->core, name, PA_NAMEREG_SAMPLE);
+ }
+
+ if (!sink && !source && !client && !card && !module && !si && !so && !sce) {
+ pa_pstream_send_error(c->pstream, tag, PA_ERR_NOENTITY);
+ return;
+ }
+
+ reply = reply_new(tag);
+ if (sink)
+ sink_fill_tagstruct(c, reply, sink);
+ else if (source)
+ source_fill_tagstruct(c, reply, source);
+ else if (client)
+ client_fill_tagstruct(c, reply, client);
+ else if (card)
+ card_fill_tagstruct(c, reply, card);
+ else if (module)
+ module_fill_tagstruct(c, reply, module);
+ else if (si)
+ sink_input_fill_tagstruct(c, reply, si);
+ else if (so)
+ source_output_fill_tagstruct(c, reply, so);
+ else
+ scache_fill_tagstruct(c, reply, sce);
+ pa_pstream_send_tagstruct(c->pstream, reply);
+}
+
+static void command_get_info_list(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
+ pa_idxset *i;
+ uint32_t idx;
+ void *p;
+ pa_tagstruct *reply;
+
+ pa_native_connection_assert_ref(c);
+ pa_assert(t);
+
+ if (!pa_tagstruct_eof(t)) {
+ protocol_error(c);
+ return;
+ }
+
+ CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
+
+ reply = reply_new(tag);
+
+ if (command == PA_COMMAND_GET_SINK_INFO_LIST)
+ i = c->protocol->core->sinks;
+ else if (command == PA_COMMAND_GET_SOURCE_INFO_LIST)
+ i = c->protocol->core->sources;
+ else if (command == PA_COMMAND_GET_CLIENT_INFO_LIST)
+ i = c->protocol->core->clients;
+ else if (command == PA_COMMAND_GET_CARD_INFO_LIST)
+ i = c->protocol->core->cards;
+ else if (command == PA_COMMAND_GET_MODULE_INFO_LIST)
+ i = c->protocol->core->modules;
+ else if (command == PA_COMMAND_GET_SINK_INPUT_INFO_LIST)
+ i = c->protocol->core->sink_inputs;
+ else if (command == PA_COMMAND_GET_SOURCE_OUTPUT_INFO_LIST)
+ i = c->protocol->core->source_outputs;
+ else {
+ pa_assert(command == PA_COMMAND_GET_SAMPLE_INFO_LIST);
+ i = c->protocol->core->scache;
+ }
+
+ if (i) {
+ PA_IDXSET_FOREACH(p, i, idx) {
+ if (command == PA_COMMAND_GET_SINK_INFO_LIST)
+ sink_fill_tagstruct(c, reply, p);
+ else if (command == PA_COMMAND_GET_SOURCE_INFO_LIST)
+ source_fill_tagstruct(c, reply, p);
+ else if (command == PA_COMMAND_GET_CLIENT_INFO_LIST)
+ client_fill_tagstruct(c, reply, p);
+ else if (command == PA_COMMAND_GET_CARD_INFO_LIST)
+ card_fill_tagstruct(c, reply, p);
+ else if (command == PA_COMMAND_GET_MODULE_INFO_LIST)
+ module_fill_tagstruct(c, reply, p);
+ else if (command == PA_COMMAND_GET_SINK_INPUT_INFO_LIST)
+ sink_input_fill_tagstruct(c, reply, p);
+ else if (command == PA_COMMAND_GET_SOURCE_OUTPUT_INFO_LIST)
+ source_output_fill_tagstruct(c, reply, p);
+ else {
+ pa_assert(command == PA_COMMAND_GET_SAMPLE_INFO_LIST);
+ scache_fill_tagstruct(c, reply, p);
+ }
+ }
+ }
+
+ pa_pstream_send_tagstruct(c->pstream, reply);
+}
+
+static void command_get_server_info(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
+ pa_tagstruct *reply;
+ pa_sample_spec fixed_ss;
+ char *h, *u;
+ pa_core *core;
+
+ pa_native_connection_assert_ref(c);
+ pa_assert(t);
+
+ if (!pa_tagstruct_eof(t)) {
+ protocol_error(c);
+ return;
+ }
+
+ CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
+
+ reply = reply_new(tag);
+ pa_tagstruct_puts(reply, PACKAGE_NAME);
+ pa_tagstruct_puts(reply, PACKAGE_VERSION);
+
+ u = pa_get_user_name_malloc();
+ pa_tagstruct_puts(reply, u);
+ pa_xfree(u);
+
+ h = pa_get_host_name_malloc();
+ pa_tagstruct_puts(reply, h);
+ pa_xfree(h);
+
+ core = c->protocol->core;
+
+ fixup_sample_spec(c, &fixed_ss, &core->default_sample_spec);
+ pa_tagstruct_put_sample_spec(reply, &fixed_ss);
+
+ pa_tagstruct_puts(reply, core->default_sink ? core->default_sink->name : NULL);
+ pa_tagstruct_puts(reply, core->default_source ? core->default_source->name : NULL);
+
+ pa_tagstruct_putu32(reply, c->protocol->core->cookie);
+
+ if (c->version >= 15)
+ pa_tagstruct_put_channel_map(reply, &core->default_channel_map);
+
+ pa_pstream_send_tagstruct(c->pstream, reply);
+}
+
+static void subscription_cb(pa_core *core, pa_subscription_event_type_t e, uint32_t idx, void *userdata) {
+ pa_tagstruct *t;
+ pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
+
+ pa_native_connection_assert_ref(c);
+
+ t = pa_tagstruct_new();
+ pa_tagstruct_putu32(t, PA_COMMAND_SUBSCRIBE_EVENT);
+ pa_tagstruct_putu32(t, (uint32_t) -1);
+ pa_tagstruct_putu32(t, e);
+ pa_tagstruct_putu32(t, idx);
+ pa_pstream_send_tagstruct(c->pstream, t);
+}
+
+static void command_subscribe(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
+ pa_subscription_mask_t m;
+
+ pa_native_connection_assert_ref(c);
+ pa_assert(t);
+
+ if (pa_tagstruct_getu32(t, &m) < 0 ||
+ !pa_tagstruct_eof(t)) {
+ protocol_error(c);
+ return;
+ }
+
+ CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
+ CHECK_VALIDITY(c->pstream, (m & ~PA_SUBSCRIPTION_MASK_ALL) == 0, tag, PA_ERR_INVALID);
+
+ if (c->subscription)
+ pa_subscription_free(c->subscription);
+
+ if (m != 0) {
+ c->subscription = pa_subscription_new(c->protocol->core, m, subscription_cb, c);
+ pa_assert(c->subscription);
+ } else
+ c->subscription = NULL;
+
+ pa_pstream_send_simple_ack(c->pstream, tag);
+}
+
+static void command_set_volume(
+ pa_pdispatch *pd,
+ uint32_t command,
+ uint32_t tag,
+ pa_tagstruct *t,
+ void *userdata) {
+
+ pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
+ uint32_t idx;
+ pa_cvolume volume;
+ pa_sink *sink = NULL;
+ pa_source *source = NULL;
+ pa_sink_input *si = NULL;
+ pa_source_output *so = NULL;
+ const char *name = NULL;
+ const char *client_name;
+
+ pa_native_connection_assert_ref(c);
+ pa_assert(t);
+
+ if (pa_tagstruct_getu32(t, &idx) < 0 ||
+ (command == PA_COMMAND_SET_SINK_VOLUME && pa_tagstruct_gets(t, &name) < 0) ||
+ (command == PA_COMMAND_SET_SOURCE_VOLUME && pa_tagstruct_gets(t, &name) < 0) ||
+ pa_tagstruct_get_cvolume(t, &volume) ||
+ !pa_tagstruct_eof(t)) {
+ protocol_error(c);
+ return;
+ }
+
+ CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
+ CHECK_VALIDITY(c->pstream, !name || pa_namereg_is_valid_name_or_wildcard(name, command == PA_COMMAND_SET_SINK_VOLUME ? PA_NAMEREG_SINK : PA_NAMEREG_SOURCE), tag, PA_ERR_INVALID);
+ CHECK_VALIDITY(c->pstream, (idx != PA_INVALID_INDEX) ^ (name != NULL), tag, PA_ERR_INVALID);
+ CHECK_VALIDITY(c->pstream, pa_cvolume_valid(&volume), tag, PA_ERR_INVALID);
+
+ switch (command) {
+
+ case PA_COMMAND_SET_SINK_VOLUME:
+ if (idx != PA_INVALID_INDEX)
+ sink = pa_idxset_get_by_index(c->protocol->core->sinks, idx);
+ else
+ sink = pa_namereg_get(c->protocol->core, name, PA_NAMEREG_SINK);
+ break;
+
+ case PA_COMMAND_SET_SOURCE_VOLUME:
+ if (idx != PA_INVALID_INDEX)
+ source = pa_idxset_get_by_index(c->protocol->core->sources, idx);
+ else
+ source = pa_namereg_get(c->protocol->core, name, PA_NAMEREG_SOURCE);
+ break;
+
+ case PA_COMMAND_SET_SINK_INPUT_VOLUME:
+ si = pa_idxset_get_by_index(c->protocol->core->sink_inputs, idx);
+ break;
+
+ case PA_COMMAND_SET_SOURCE_OUTPUT_VOLUME:
+ so = pa_idxset_get_by_index(c->protocol->core->source_outputs, idx);
+ break;
+
+ default:
+ pa_assert_not_reached();
+ }
+
+ CHECK_VALIDITY(c->pstream, si || so || sink || source, tag, PA_ERR_NOENTITY);
+
+ client_name = pa_strnull(pa_proplist_gets(c->client->proplist, PA_PROP_APPLICATION_PROCESS_BINARY));
+
+ if (sink) {
+ CHECK_VALIDITY(c->pstream, volume.channels == 1 || pa_cvolume_compatible(&volume, &sink->sample_spec), tag, PA_ERR_INVALID);
+
+ pa_log_debug("Client %s changes volume of sink %s.", client_name, sink->name);
+ pa_sink_set_volume(sink, &volume, true, true);
+ } else if (source) {
+ CHECK_VALIDITY(c->pstream, volume.channels == 1 || pa_cvolume_compatible(&volume, &source->sample_spec), tag, PA_ERR_INVALID);
+
+ pa_log_debug("Client %s changes volume of source %s.", client_name, source->name);
+ pa_source_set_volume(source, &volume, true, true);
+ } else if (si) {
+ CHECK_VALIDITY(c->pstream, si->volume_writable, tag, PA_ERR_BADSTATE);
+ CHECK_VALIDITY(c->pstream, volume.channels == 1 || pa_cvolume_compatible(&volume, &si->sample_spec), tag, PA_ERR_INVALID);
+
+ pa_log_debug("Client %s changes volume of sink input %s.",
+ client_name,
+ pa_strnull(pa_proplist_gets(si->proplist, PA_PROP_MEDIA_NAME)));
+ pa_sink_input_set_volume(si, &volume, true, true);
+ } else if (so) {
+ CHECK_VALIDITY(c->pstream, so->volume_writable, tag, PA_ERR_BADSTATE);
+ CHECK_VALIDITY(c->pstream, volume.channels == 1 || pa_cvolume_compatible(&volume, &so->sample_spec), tag, PA_ERR_INVALID);
+
+ pa_log_debug("Client %s changes volume of source output %s.",
+ client_name,
+ pa_strnull(pa_proplist_gets(so->proplist, PA_PROP_MEDIA_NAME)));
+ pa_source_output_set_volume(so, &volume, true, true);
+ }
+
+ pa_pstream_send_simple_ack(c->pstream, tag);
+}
+
+static void command_set_mute(
+ pa_pdispatch *pd,
+ uint32_t command,
+ uint32_t tag,
+ pa_tagstruct *t,
+ void *userdata) {
+
+ pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
+ uint32_t idx;
+ bool mute;
+ pa_sink *sink = NULL;
+ pa_source *source = NULL;
+ pa_sink_input *si = NULL;
+ pa_source_output *so = NULL;
+ const char *name = NULL, *client_name;
+
+ pa_native_connection_assert_ref(c);
+ pa_assert(t);
+
+ if (pa_tagstruct_getu32(t, &idx) < 0 ||
+ (command == PA_COMMAND_SET_SINK_MUTE && pa_tagstruct_gets(t, &name) < 0) ||
+ (command == PA_COMMAND_SET_SOURCE_MUTE && pa_tagstruct_gets(t, &name) < 0) ||
+ pa_tagstruct_get_boolean(t, &mute) ||
+ !pa_tagstruct_eof(t)) {
+ protocol_error(c);
+ return;
+ }
+
+ CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
+ CHECK_VALIDITY(c->pstream, !name || pa_namereg_is_valid_name_or_wildcard(name, command == PA_COMMAND_SET_SINK_MUTE ? PA_NAMEREG_SINK : PA_NAMEREG_SOURCE), tag, PA_ERR_INVALID);
+ CHECK_VALIDITY(c->pstream, (idx != PA_INVALID_INDEX) ^ (name != NULL), tag, PA_ERR_INVALID);
+
+ switch (command) {
+
+ case PA_COMMAND_SET_SINK_MUTE:
+ if (idx != PA_INVALID_INDEX)
+ sink = pa_idxset_get_by_index(c->protocol->core->sinks, idx);
+ else
+ sink = pa_namereg_get(c->protocol->core, name, PA_NAMEREG_SINK);
+
+ break;
+
+ case PA_COMMAND_SET_SOURCE_MUTE:
+ if (idx != PA_INVALID_INDEX)
+ source = pa_idxset_get_by_index(c->protocol->core->sources, idx);
+ else
+ source = pa_namereg_get(c->protocol->core, name, PA_NAMEREG_SOURCE);
+
+ break;
+
+ case PA_COMMAND_SET_SINK_INPUT_MUTE:
+ si = pa_idxset_get_by_index(c->protocol->core->sink_inputs, idx);
+ break;
+
+ case PA_COMMAND_SET_SOURCE_OUTPUT_MUTE:
+ so = pa_idxset_get_by_index(c->protocol->core->source_outputs, idx);
+ break;
+
+ default:
+ pa_assert_not_reached();
+ }
+
+ CHECK_VALIDITY(c->pstream, si || so || sink || source, tag, PA_ERR_NOENTITY);
+
+ client_name = pa_strnull(pa_proplist_gets(c->client->proplist, PA_PROP_APPLICATION_PROCESS_BINARY));
+
+ if (sink) {
+ pa_log_debug("Client %s changes mute of sink %s.", client_name, sink->name);
+ pa_sink_set_mute(sink, mute, true);
+ } else if (source) {
+ pa_log_debug("Client %s changes mute of source %s.", client_name, source->name);
+ pa_source_set_mute(source, mute, true);
+ } else if (si) {
+ pa_log_debug("Client %s changes mute of sink input %s.",
+ client_name,
+ pa_strnull(pa_proplist_gets(si->proplist, PA_PROP_MEDIA_NAME)));
+ pa_sink_input_set_mute(si, mute, true);
+ } else if (so) {
+ pa_log_debug("Client %s changes mute of source output %s.",
+ client_name,
+ pa_strnull(pa_proplist_gets(so->proplist, PA_PROP_MEDIA_NAME)));
+ pa_source_output_set_mute(so, mute, true);
+ }
+
+ pa_pstream_send_simple_ack(c->pstream, tag);
+}
+
+static void command_cork_playback_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
+ uint32_t idx;
+ bool b;
+ playback_stream *s;
+
+ pa_native_connection_assert_ref(c);
+ pa_assert(t);
+
+ if (pa_tagstruct_getu32(t, &idx) < 0 ||
+ pa_tagstruct_get_boolean(t, &b) < 0 ||
+ !pa_tagstruct_eof(t)) {
+ protocol_error(c);
+ return;
+ }
+
+ CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
+ CHECK_VALIDITY(c->pstream, idx != PA_INVALID_INDEX, tag, PA_ERR_INVALID);
+ s = pa_idxset_get_by_index(c->output_streams, idx);
+ CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY);
+ CHECK_VALIDITY(c->pstream, playback_stream_isinstance(s), tag, PA_ERR_NOENTITY);
+
+ pa_sink_input_cork(s->sink_input, b);
+
+ if (b)
+ s->is_underrun = true;
+
+ pa_pstream_send_simple_ack(c->pstream, tag);
+}
+
+static void command_trigger_or_flush_or_prebuf_playback_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
+ uint32_t idx;
+ playback_stream *s;
+
+ pa_native_connection_assert_ref(c);
+ pa_assert(t);
+
+ if (pa_tagstruct_getu32(t, &idx) < 0 ||
+ !pa_tagstruct_eof(t)) {
+ protocol_error(c);
+ return;
+ }
+
+ CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
+ CHECK_VALIDITY(c->pstream, idx != PA_INVALID_INDEX, tag, PA_ERR_INVALID);
+ s = pa_idxset_get_by_index(c->output_streams, idx);
+ CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY);
+ CHECK_VALIDITY(c->pstream, playback_stream_isinstance(s), tag, PA_ERR_NOENTITY);
+
+ switch (command) {
+ case PA_COMMAND_FLUSH_PLAYBACK_STREAM:
+ pa_asyncmsgq_send(s->sink_input->sink->asyncmsgq, PA_MSGOBJECT(s->sink_input), SINK_INPUT_MESSAGE_FLUSH, NULL, 0, NULL);
+ break;
+
+ case PA_COMMAND_PREBUF_PLAYBACK_STREAM:
+ pa_asyncmsgq_send(s->sink_input->sink->asyncmsgq, PA_MSGOBJECT(s->sink_input), SINK_INPUT_MESSAGE_PREBUF_FORCE, NULL, 0, NULL);
+ break;
+
+ case PA_COMMAND_TRIGGER_PLAYBACK_STREAM:
+ pa_asyncmsgq_send(s->sink_input->sink->asyncmsgq, PA_MSGOBJECT(s->sink_input), SINK_INPUT_MESSAGE_TRIGGER, NULL, 0, NULL);
+ break;
+
+ default:
+ pa_assert_not_reached();
+ }
+
+ pa_pstream_send_simple_ack(c->pstream, tag);
+}
+
+static void command_cork_record_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
+ uint32_t idx;
+ record_stream *s;
+ bool b;
+
+ pa_native_connection_assert_ref(c);
+ pa_assert(t);
+
+ if (pa_tagstruct_getu32(t, &idx) < 0 ||
+ pa_tagstruct_get_boolean(t, &b) < 0 ||
+ !pa_tagstruct_eof(t)) {
+ protocol_error(c);
+ return;
+ }
+
+ CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
+ s = pa_idxset_get_by_index(c->record_streams, idx);
+ CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY);
+
+ pa_source_output_cork(s->source_output, b);
+ pa_memblockq_prebuf_force(s->memblockq);
+ pa_pstream_send_simple_ack(c->pstream, tag);
+}
+
+static void command_flush_record_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
+ uint32_t idx;
+ record_stream *s;
+
+ pa_native_connection_assert_ref(c);
+ pa_assert(t);
+
+ if (pa_tagstruct_getu32(t, &idx) < 0 ||
+ !pa_tagstruct_eof(t)) {
+ protocol_error(c);
+ return;
+ }
+
+ CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
+ s = pa_idxset_get_by_index(c->record_streams, idx);
+ CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY);
+
+ pa_memblockq_flush_read(s->memblockq);
+ pa_pstream_send_simple_ack(c->pstream, tag);
+}
+
+static void command_set_stream_buffer_attr(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
+ uint32_t idx;
+ pa_buffer_attr a;
+ pa_tagstruct *reply;
+
+ pa_native_connection_assert_ref(c);
+ pa_assert(t);
+
+ memset(&a, 0, sizeof(a));
+
+ if (pa_tagstruct_getu32(t, &idx) < 0) {
+ protocol_error(c);
+ return;
+ }
+
+ CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
+
+ if (command == PA_COMMAND_SET_PLAYBACK_STREAM_BUFFER_ATTR) {
+ playback_stream *s;
+ bool adjust_latency = false, early_requests = false;
+
+ s = pa_idxset_get_by_index(c->output_streams, idx);
+ CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY);
+ CHECK_VALIDITY(c->pstream, playback_stream_isinstance(s), tag, PA_ERR_NOENTITY);
+
+ if (pa_tagstruct_get(
+ t,
+ PA_TAG_U32, &a.maxlength,
+ PA_TAG_U32, &a.tlength,
+ PA_TAG_U32, &a.prebuf,
+ PA_TAG_U32, &a.minreq,
+ PA_TAG_INVALID) < 0 ||
+ (c->version >= 13 && pa_tagstruct_get_boolean(t, &adjust_latency) < 0) ||
+ (c->version >= 14 && pa_tagstruct_get_boolean(t, &early_requests) < 0) ||
+ !pa_tagstruct_eof(t)) {
+ protocol_error(c);
+ return;
+ }
+
+ s->adjust_latency = adjust_latency;
+ s->early_requests = early_requests;
+ s->buffer_attr_req = a;
+
+ fix_playback_buffer_attr(s);
+ pa_assert_se(pa_asyncmsgq_send(s->sink_input->sink->asyncmsgq, PA_MSGOBJECT(s->sink_input), SINK_INPUT_MESSAGE_UPDATE_BUFFER_ATTR, NULL, 0, NULL) == 0);
+
+ reply = reply_new(tag);
+ pa_tagstruct_putu32(reply, s->buffer_attr.maxlength);
+ pa_tagstruct_putu32(reply, s->buffer_attr.tlength);
+ pa_tagstruct_putu32(reply, s->buffer_attr.prebuf);
+ pa_tagstruct_putu32(reply, s->buffer_attr.minreq);
+
+ if (c->version >= 13)
+ pa_tagstruct_put_usec(reply, s->configured_sink_latency);
+
+ } else {
+ record_stream *s;
+ bool adjust_latency = false, early_requests = false;
+ pa_assert(command == PA_COMMAND_SET_RECORD_STREAM_BUFFER_ATTR);
+
+ s = pa_idxset_get_by_index(c->record_streams, idx);
+ CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY);
+
+ if (pa_tagstruct_get(
+ t,
+ PA_TAG_U32, &a.maxlength,
+ PA_TAG_U32, &a.fragsize,
+ PA_TAG_INVALID) < 0 ||
+ (c->version >= 13 && pa_tagstruct_get_boolean(t, &adjust_latency) < 0) ||
+ (c->version >= 14 && pa_tagstruct_get_boolean(t, &early_requests) < 0) ||
+ !pa_tagstruct_eof(t)) {
+ protocol_error(c);
+ return;
+ }
+
+ s->adjust_latency = adjust_latency;
+ s->early_requests = early_requests;
+ s->buffer_attr_req = a;
+
+ fix_record_buffer_attr_pre(s);
+ pa_memblockq_set_maxlength(s->memblockq, s->buffer_attr.maxlength);
+ pa_memblockq_get_attr(s->memblockq, &s->buffer_attr);
+ fix_record_buffer_attr_post(s);
+
+ reply = reply_new(tag);
+ pa_tagstruct_putu32(reply, s->buffer_attr.maxlength);
+ pa_tagstruct_putu32(reply, s->buffer_attr.fragsize);
+
+ if (c->version >= 13)
+ pa_tagstruct_put_usec(reply, s->configured_source_latency);
+ }
+
+ pa_pstream_send_tagstruct(c->pstream, reply);
+}
+
+static void command_update_stream_sample_rate(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
+ uint32_t idx;
+ uint32_t rate;
+
+ pa_native_connection_assert_ref(c);
+ pa_assert(t);
+
+ if (pa_tagstruct_getu32(t, &idx) < 0 ||
+ pa_tagstruct_getu32(t, &rate) < 0 ||
+ !pa_tagstruct_eof(t)) {
+ protocol_error(c);
+ return;
+ }
+
+ CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
+ CHECK_VALIDITY(c->pstream, pa_sample_rate_valid(rate), tag, PA_ERR_INVALID);
+
+ if (command == PA_COMMAND_UPDATE_PLAYBACK_STREAM_SAMPLE_RATE) {
+ playback_stream *s;
+
+ s = pa_idxset_get_by_index(c->output_streams, idx);
+ CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY);
+ CHECK_VALIDITY(c->pstream, playback_stream_isinstance(s), tag, PA_ERR_NOENTITY);
+
+ pa_sink_input_set_rate(s->sink_input, rate);
+
+ } else {
+ record_stream *s;
+ pa_assert(command == PA_COMMAND_UPDATE_RECORD_STREAM_SAMPLE_RATE);
+
+ s = pa_idxset_get_by_index(c->record_streams, idx);
+ CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY);
+
+ pa_source_output_set_rate(s->source_output, rate);
+ }
+
+ pa_pstream_send_simple_ack(c->pstream, tag);
+}
+
+static void command_update_proplist(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
+ uint32_t idx;
+ uint32_t mode;
+ pa_proplist *p;
+
+ pa_native_connection_assert_ref(c);
+ pa_assert(t);
+
+ CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
+
+ p = pa_proplist_new();
+
+ if (command == PA_COMMAND_UPDATE_CLIENT_PROPLIST) {
+
+ if (pa_tagstruct_getu32(t, &mode) < 0 ||
+ pa_tagstruct_get_proplist(t, p) < 0 ||
+ !pa_tagstruct_eof(t)) {
+ protocol_error(c);
+ pa_proplist_free(p);
+ return;
+ }
+
+ } else {
+
+ if (pa_tagstruct_getu32(t, &idx) < 0 ||
+ pa_tagstruct_getu32(t, &mode) < 0 ||
+ pa_tagstruct_get_proplist(t, p) < 0 ||
+ !pa_tagstruct_eof(t)) {
+ protocol_error(c);
+ pa_proplist_free(p);
+ return;
+ }
+ }
+
+ if (!(mode == PA_UPDATE_SET || mode == PA_UPDATE_MERGE || mode == PA_UPDATE_REPLACE)) {
+ pa_proplist_free(p);
+ CHECK_VALIDITY(c->pstream, false, tag, PA_ERR_INVALID);
+ }
+
+ if (command == PA_COMMAND_UPDATE_PLAYBACK_STREAM_PROPLIST) {
+ playback_stream *s;
+
+ s = pa_idxset_get_by_index(c->output_streams, idx);
+ if (!s || !playback_stream_isinstance(s)) {
+ pa_proplist_free(p);
+ CHECK_VALIDITY(c->pstream, false, tag, PA_ERR_NOENTITY);
+ }
+ pa_sink_input_update_proplist(s->sink_input, mode, p);
+
+ } else if (command == PA_COMMAND_UPDATE_RECORD_STREAM_PROPLIST) {
+ record_stream *s;
+
+ if (!(s = pa_idxset_get_by_index(c->record_streams, idx))) {
+ pa_proplist_free(p);
+ CHECK_VALIDITY(c->pstream, false, tag, PA_ERR_NOENTITY);
+ }
+ pa_source_output_update_proplist(s->source_output, mode, p);
+
+ } else {
+ pa_assert(command == PA_COMMAND_UPDATE_CLIENT_PROPLIST);
+
+ pa_client_update_proplist(c->client, mode, p);
+ }
+
+ pa_pstream_send_simple_ack(c->pstream, tag);
+ pa_proplist_free(p);
+}
+
+static void command_remove_proplist(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
+ uint32_t idx;
+ unsigned changed = 0;
+ pa_proplist *p;
+ pa_strlist *l = NULL;
+
+ pa_native_connection_assert_ref(c);
+ pa_assert(t);
+
+ CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
+
+ if (command != PA_COMMAND_REMOVE_CLIENT_PROPLIST) {
+
+ if (pa_tagstruct_getu32(t, &idx) < 0) {
+ protocol_error(c);
+ return;
+ }
+ }
+
+ if (command == PA_COMMAND_REMOVE_PLAYBACK_STREAM_PROPLIST) {
+ playback_stream *s;
+
+ s = pa_idxset_get_by_index(c->output_streams, idx);
+ CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY);
+ CHECK_VALIDITY(c->pstream, playback_stream_isinstance(s), tag, PA_ERR_NOENTITY);
+
+ p = s->sink_input->proplist;
+
+ } else if (command == PA_COMMAND_REMOVE_RECORD_STREAM_PROPLIST) {
+ record_stream *s;
+
+ s = pa_idxset_get_by_index(c->record_streams, idx);
+ CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY);
+
+ p = s->source_output->proplist;
+ } else {
+ pa_assert(command == PA_COMMAND_REMOVE_CLIENT_PROPLIST);
+
+ p = c->client->proplist;
+ }
+
+ for (;;) {
+ const char *k;
+
+ if (pa_tagstruct_gets(t, &k) < 0) {
+ protocol_error(c);
+ pa_strlist_free(l);
+ return;
+ }
+
+ if (!k)
+ break;
+
+ l = pa_strlist_prepend(l, k);
+ }
+
+ if (!pa_tagstruct_eof(t)) {
+ protocol_error(c);
+ pa_strlist_free(l);
+ return;
+ }
+
+ for (;;) {
+ char *z;
+
+ l = pa_strlist_pop(l, &z);
+
+ if (!z)
+ break;
+
+ changed += (unsigned) (pa_proplist_unset(p, z) >= 0);
+ pa_xfree(z);
+ }
+
+ pa_pstream_send_simple_ack(c->pstream, tag);
+
+ if (changed) {
+ if (command == PA_COMMAND_REMOVE_PLAYBACK_STREAM_PROPLIST) {
+ playback_stream *s;
+
+ s = pa_idxset_get_by_index(c->output_streams, idx);
+ pa_subscription_post(c->protocol->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, s->sink_input->index);
+
+ } else if (command == PA_COMMAND_REMOVE_RECORD_STREAM_PROPLIST) {
+ record_stream *s;
+
+ s = pa_idxset_get_by_index(c->record_streams, idx);
+ pa_subscription_post(c->protocol->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, s->source_output->index);
+
+ } else {
+ pa_assert(command == PA_COMMAND_REMOVE_CLIENT_PROPLIST);
+ pa_subscription_post(c->protocol->core, PA_SUBSCRIPTION_EVENT_CLIENT|PA_SUBSCRIPTION_EVENT_CHANGE, c->client->index);
+ }
+ }
+}
+
+static void command_set_default_sink_or_source(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
+ const char *s;
+
+ pa_native_connection_assert_ref(c);
+ pa_assert(t);
+
+ if (pa_tagstruct_gets(t, &s) < 0 ||
+ !pa_tagstruct_eof(t)) {
+ protocol_error(c);
+ return;
+ }
+
+ CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
+ CHECK_VALIDITY(c->pstream, !s || pa_namereg_is_valid_name(s), tag, PA_ERR_INVALID);
+
+ if (command == PA_COMMAND_SET_DEFAULT_SOURCE) {
+ pa_source *source;
+
+ source = pa_namereg_get(c->protocol->core, s, PA_NAMEREG_SOURCE);
+ CHECK_VALIDITY(c->pstream, source, tag, PA_ERR_NOENTITY);
+
+ pa_core_set_configured_default_source(c->protocol->core, source->name);
+ } else {
+ pa_sink *sink;
+ pa_assert(command == PA_COMMAND_SET_DEFAULT_SINK);
+
+ sink = pa_namereg_get(c->protocol->core, s, PA_NAMEREG_SINK);
+ CHECK_VALIDITY(c->pstream, sink, tag, PA_ERR_NOENTITY);
+
+ pa_core_set_configured_default_sink(c->protocol->core, sink->name);
+ }
+
+ pa_pstream_send_simple_ack(c->pstream, tag);
+}
+
+static void command_set_stream_name(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
+ uint32_t idx;
+ const char *name;
+
+ pa_native_connection_assert_ref(c);
+ pa_assert(t);
+
+ if (pa_tagstruct_getu32(t, &idx) < 0 ||
+ pa_tagstruct_gets(t, &name) < 0 ||
+ !pa_tagstruct_eof(t)) {
+ protocol_error(c);
+ return;
+ }
+
+ CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
+ CHECK_VALIDITY(c->pstream, name && pa_utf8_valid(name), tag, PA_ERR_INVALID);
+
+ if (command == PA_COMMAND_SET_PLAYBACK_STREAM_NAME) {
+ playback_stream *s;
+
+ s = pa_idxset_get_by_index(c->output_streams, idx);
+ CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY);
+ CHECK_VALIDITY(c->pstream, playback_stream_isinstance(s), tag, PA_ERR_NOENTITY);
+
+ pa_sink_input_set_property(s->sink_input, PA_PROP_MEDIA_NAME, name);
+
+ } else {
+ record_stream *s;
+ pa_assert(command == PA_COMMAND_SET_RECORD_STREAM_NAME);
+
+ s = pa_idxset_get_by_index(c->record_streams, idx);
+ CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY);
+
+ pa_source_output_set_property(s->source_output, PA_PROP_MEDIA_NAME, name);
+ }
+
+ pa_pstream_send_simple_ack(c->pstream, tag);
+}
+
+static void command_kill(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
+ uint32_t idx;
+
+ pa_native_connection_assert_ref(c);
+ pa_assert(t);
+
+ if (pa_tagstruct_getu32(t, &idx) < 0 ||
+ !pa_tagstruct_eof(t)) {
+ protocol_error(c);
+ return;
+ }
+
+ CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
+
+ if (command == PA_COMMAND_KILL_CLIENT) {
+ pa_client *client;
+
+ client = pa_idxset_get_by_index(c->protocol->core->clients, idx);
+ CHECK_VALIDITY(c->pstream, client, tag, PA_ERR_NOENTITY);
+
+ pa_native_connection_ref(c);
+ pa_client_kill(client);
+
+ } else if (command == PA_COMMAND_KILL_SINK_INPUT) {
+ pa_sink_input *s;
+
+ s = pa_idxset_get_by_index(c->protocol->core->sink_inputs, idx);
+ CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY);
+
+ pa_native_connection_ref(c);
+ pa_sink_input_kill(s);
+ } else {
+ pa_source_output *s;
+
+ pa_assert(command == PA_COMMAND_KILL_SOURCE_OUTPUT);
+
+ s = pa_idxset_get_by_index(c->protocol->core->source_outputs, idx);
+ CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY);
+
+ pa_native_connection_ref(c);
+ pa_source_output_kill(s);
+ }
+
+ pa_pstream_send_simple_ack(c->pstream, tag);
+ pa_native_connection_unref(c);
+}
+
+static void command_load_module(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
+ pa_module *m;
+ const char *name, *argument;
+ pa_tagstruct *reply;
+
+ pa_native_connection_assert_ref(c);
+ pa_assert(t);
+
+ if (pa_tagstruct_gets(t, &name) < 0 ||
+ pa_tagstruct_gets(t, &argument) < 0 ||
+ !pa_tagstruct_eof(t)) {
+ protocol_error(c);
+ return;
+ }
+
+ CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
+ CHECK_VALIDITY(c->pstream, name && *name && pa_utf8_valid(name) && !strchr(name, '/'), tag, PA_ERR_INVALID);
+ CHECK_VALIDITY(c->pstream, !argument || pa_utf8_valid(argument), tag, PA_ERR_INVALID);
+
+ if (pa_module_load(&m, c->protocol->core, name, argument) < 0) {
+ pa_pstream_send_error(c->pstream, tag, PA_ERR_MODINITFAILED);
+ return;
+ }
+
+ reply = reply_new(tag);
+ pa_tagstruct_putu32(reply, m->index);
+ pa_pstream_send_tagstruct(c->pstream, reply);
+}
+
+static void command_unload_module(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
+ uint32_t idx;
+ pa_module *m;
+
+ pa_native_connection_assert_ref(c);
+ pa_assert(t);
+
+ if (pa_tagstruct_getu32(t, &idx) < 0 ||
+ !pa_tagstruct_eof(t)) {
+ protocol_error(c);
+ return;
+ }
+
+ CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
+ m = pa_idxset_get_by_index(c->protocol->core->modules, idx);
+ CHECK_VALIDITY(c->pstream, m, tag, PA_ERR_NOENTITY);
+
+ pa_module_unload_request(m, false);
+ pa_pstream_send_simple_ack(c->pstream, tag);
+}
+
+static void command_move_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
+ uint32_t idx = PA_INVALID_INDEX, idx_device = PA_INVALID_INDEX;
+ const char *name_device = NULL;
+
+ pa_native_connection_assert_ref(c);
+ pa_assert(t);
+
+ if (pa_tagstruct_getu32(t, &idx) < 0 ||
+ pa_tagstruct_getu32(t, &idx_device) < 0 ||
+ pa_tagstruct_gets(t, &name_device) < 0 ||
+ !pa_tagstruct_eof(t)) {
+ protocol_error(c);
+ return;
+ }
+
+ CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
+ CHECK_VALIDITY(c->pstream, idx != PA_INVALID_INDEX, tag, PA_ERR_INVALID);
+
+ CHECK_VALIDITY(c->pstream, !name_device || pa_namereg_is_valid_name_or_wildcard(name_device, command == PA_COMMAND_MOVE_SINK_INPUT ? PA_NAMEREG_SINK : PA_NAMEREG_SOURCE), tag, PA_ERR_INVALID);
+ CHECK_VALIDITY(c->pstream, (idx_device != PA_INVALID_INDEX) ^ (name_device != NULL), tag, PA_ERR_INVALID);
+
+ if (command == PA_COMMAND_MOVE_SINK_INPUT) {
+ pa_sink_input *si = NULL;
+ pa_sink *sink = NULL;
+
+ si = pa_idxset_get_by_index(c->protocol->core->sink_inputs, idx);
+
+ if (idx_device != PA_INVALID_INDEX)
+ sink = pa_idxset_get_by_index(c->protocol->core->sinks, idx_device);
+ else
+ sink = pa_namereg_get(c->protocol->core, name_device, PA_NAMEREG_SINK);
+
+ CHECK_VALIDITY(c->pstream, si && sink, tag, PA_ERR_NOENTITY);
+
+ if (pa_sink_input_move_to(si, sink, true) < 0) {
+ pa_pstream_send_error(c->pstream, tag, PA_ERR_INVALID);
+ return;
+ }
+ } else {
+ pa_source_output *so = NULL;
+ pa_source *source;
+
+ pa_assert(command == PA_COMMAND_MOVE_SOURCE_OUTPUT);
+
+ so = pa_idxset_get_by_index(c->protocol->core->source_outputs, idx);
+
+ if (idx_device != PA_INVALID_INDEX)
+ source = pa_idxset_get_by_index(c->protocol->core->sources, idx_device);
+ else
+ source = pa_namereg_get(c->protocol->core, name_device, PA_NAMEREG_SOURCE);
+
+ CHECK_VALIDITY(c->pstream, so && source, tag, PA_ERR_NOENTITY);
+
+ if (pa_source_output_move_to(so, source, true) < 0) {
+ pa_pstream_send_error(c->pstream, tag, PA_ERR_INVALID);
+ return;
+ }
+ }
+
+ pa_pstream_send_simple_ack(c->pstream, tag);
+}
+
+static void command_suspend(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
+ uint32_t idx = PA_INVALID_INDEX;
+ const char *name = NULL;
+ bool b;
+
+ pa_native_connection_assert_ref(c);
+ pa_assert(t);
+
+ if (pa_tagstruct_getu32(t, &idx) < 0 ||
+ pa_tagstruct_gets(t, &name) < 0 ||
+ pa_tagstruct_get_boolean(t, &b) < 0 ||
+ !pa_tagstruct_eof(t)) {
+ protocol_error(c);
+ return;
+ }
+
+ CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
+ CHECK_VALIDITY(c->pstream, !name || pa_namereg_is_valid_name_or_wildcard(name, command == PA_COMMAND_SUSPEND_SINK ? PA_NAMEREG_SINK : PA_NAMEREG_SOURCE) || *name == 0, tag, PA_ERR_INVALID);
+ CHECK_VALIDITY(c->pstream, (idx != PA_INVALID_INDEX) ^ (name != NULL), tag, PA_ERR_INVALID);
+
+ if (command == PA_COMMAND_SUSPEND_SINK) {
+
+ if (idx == PA_INVALID_INDEX && name && !*name) {
+
+ pa_log_debug("%s all sinks", b ? "Suspending" : "Resuming");
+
+ if (pa_sink_suspend_all(c->protocol->core, b, PA_SUSPEND_USER) < 0) {
+ pa_pstream_send_error(c->pstream, tag, PA_ERR_INVALID);
+ return;
+ }
+ } else {
+ pa_sink *sink = NULL;
+
+ if (idx != PA_INVALID_INDEX)
+ sink = pa_idxset_get_by_index(c->protocol->core->sinks, idx);
+ else
+ sink = pa_namereg_get(c->protocol->core, name, PA_NAMEREG_SINK);
+
+ CHECK_VALIDITY(c->pstream, sink, tag, PA_ERR_NOENTITY);
+
+ pa_log_debug("%s of sink %s requested by client %" PRIu32 ".",
+ b ? "Suspending" : "Resuming", sink->name, c->client->index);
+
+ if (pa_sink_suspend(sink, b, PA_SUSPEND_USER) < 0) {
+ pa_pstream_send_error(c->pstream, tag, PA_ERR_INVALID);
+ return;
+ }
+ }
+ } else {
+
+ pa_assert(command == PA_COMMAND_SUSPEND_SOURCE);
+
+ if (idx == PA_INVALID_INDEX && name && !*name) {
+
+ pa_log_debug("%s all sources", b ? "Suspending" : "Resuming");
+
+ if (pa_source_suspend_all(c->protocol->core, b, PA_SUSPEND_USER) < 0) {
+ pa_pstream_send_error(c->pstream, tag, PA_ERR_INVALID);
+ return;
+ }
+
+ } else {
+ pa_source *source;
+
+ if (idx != PA_INVALID_INDEX)
+ source = pa_idxset_get_by_index(c->protocol->core->sources, idx);
+ else
+ source = pa_namereg_get(c->protocol->core, name, PA_NAMEREG_SOURCE);
+
+ CHECK_VALIDITY(c->pstream, source, tag, PA_ERR_NOENTITY);
+
+ pa_log_debug("%s of source %s requested by client %" PRIu32 ".",
+ b ? "Suspending" : "Resuming", source->name, c->client->index);
+
+ if (pa_source_suspend(source, b, PA_SUSPEND_USER) < 0) {
+ pa_pstream_send_error(c->pstream, tag, PA_ERR_INVALID);
+ return;
+ }
+ }
+ }
+
+ pa_pstream_send_simple_ack(c->pstream, tag);
+}
+
+static void command_extension(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
+ uint32_t idx = PA_INVALID_INDEX;
+ const char *name = NULL;
+ pa_module *m;
+ pa_native_protocol_ext_cb_t cb;
+
+ pa_native_connection_assert_ref(c);
+ pa_assert(t);
+
+ if (pa_tagstruct_getu32(t, &idx) < 0 ||
+ pa_tagstruct_gets(t, &name) < 0) {
+ protocol_error(c);
+ return;
+ }
+
+ CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
+ CHECK_VALIDITY(c->pstream, !name || pa_utf8_valid(name), tag, PA_ERR_INVALID);
+ CHECK_VALIDITY(c->pstream, (idx != PA_INVALID_INDEX) ^ (name != NULL), tag, PA_ERR_INVALID);
+
+ if (idx != PA_INVALID_INDEX)
+ m = pa_idxset_get_by_index(c->protocol->core->modules, idx);
+ else
+ PA_IDXSET_FOREACH(m, c->protocol->core->modules, idx)
+ if (pa_streq(name, m->name))
+ break;
+
+ CHECK_VALIDITY(c->pstream, m, tag, PA_ERR_NOEXTENSION);
+ CHECK_VALIDITY(c->pstream, m->load_once || idx != PA_INVALID_INDEX, tag, PA_ERR_INVALID);
+
+ cb = (pa_native_protocol_ext_cb_t) (unsigned long) pa_hashmap_get(c->protocol->extensions, m);
+ CHECK_VALIDITY(c->pstream, cb, tag, PA_ERR_NOEXTENSION);
+
+ if (cb(c->protocol, m, c, tag, t) < 0)
+ protocol_error(c);
+}
+
+static void command_set_card_profile(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
+ uint32_t idx = PA_INVALID_INDEX;
+ const char *name = NULL, *profile_name = NULL;
+ pa_card *card = NULL;
+ pa_card_profile *profile;
+ int ret;
+
+ pa_native_connection_assert_ref(c);
+ pa_assert(t);
+
+ if (pa_tagstruct_getu32(t, &idx) < 0 ||
+ pa_tagstruct_gets(t, &name) < 0 ||
+ pa_tagstruct_gets(t, &profile_name) < 0 ||
+ !pa_tagstruct_eof(t)) {
+ protocol_error(c);
+ return;
+ }
+
+ CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
+ CHECK_VALIDITY(c->pstream, !name || pa_namereg_is_valid_name(name), tag, PA_ERR_INVALID);
+ CHECK_VALIDITY(c->pstream, (idx != PA_INVALID_INDEX) ^ (name != NULL), tag, PA_ERR_INVALID);
+ CHECK_VALIDITY(c->pstream, profile_name, tag, PA_ERR_INVALID);
+
+ if (idx != PA_INVALID_INDEX)
+ card = pa_idxset_get_by_index(c->protocol->core->cards, idx);
+ else
+ card = pa_namereg_get(c->protocol->core, name, PA_NAMEREG_CARD);
+
+ CHECK_VALIDITY(c->pstream, card, tag, PA_ERR_NOENTITY);
+
+ profile = pa_hashmap_get(card->profiles, profile_name);
+
+ CHECK_VALIDITY(c->pstream, profile, tag, PA_ERR_NOENTITY);
+
+ pa_log_info("Application \"%s\" requests card profile change. card = %s, profile = %s",
+ pa_strnull(pa_proplist_gets(c->client->proplist, PA_PROP_APPLICATION_NAME)),
+ card->name,
+ profile->name);
+
+ if ((ret = pa_card_set_profile(card, profile, true)) < 0) {
+ pa_pstream_send_error(c->pstream, tag, -ret);
+ return;
+ }
+
+ pa_pstream_send_simple_ack(c->pstream, tag);
+}
+
+static void command_set_sink_or_source_port(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
+ uint32_t idx = PA_INVALID_INDEX;
+ const char *name = NULL, *port = NULL;
+ int ret;
+
+ pa_native_connection_assert_ref(c);
+ pa_assert(t);
+
+ if (pa_tagstruct_getu32(t, &idx) < 0 ||
+ pa_tagstruct_gets(t, &name) < 0 ||
+ pa_tagstruct_gets(t, &port) < 0 ||
+ !pa_tagstruct_eof(t)) {
+ protocol_error(c);
+ return;
+ }
+
+ CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
+ CHECK_VALIDITY(c->pstream, !name || pa_namereg_is_valid_name_or_wildcard(name, command == PA_COMMAND_SET_SINK_PORT ? PA_NAMEREG_SINK : PA_NAMEREG_SOURCE), tag, PA_ERR_INVALID);
+ CHECK_VALIDITY(c->pstream, (idx != PA_INVALID_INDEX) ^ (name != NULL), tag, PA_ERR_INVALID);
+ CHECK_VALIDITY(c->pstream, port, tag, PA_ERR_INVALID);
+
+ if (command == PA_COMMAND_SET_SINK_PORT) {
+ pa_sink *sink;
+
+ if (idx != PA_INVALID_INDEX)
+ sink = pa_idxset_get_by_index(c->protocol->core->sinks, idx);
+ else
+ sink = pa_namereg_get(c->protocol->core, name, PA_NAMEREG_SINK);
+
+ CHECK_VALIDITY(c->pstream, sink, tag, PA_ERR_NOENTITY);
+
+ if ((ret = pa_sink_set_port(sink, port, true)) < 0) {
+ pa_pstream_send_error(c->pstream, tag, -ret);
+ return;
+ }
+ } else {
+ pa_source *source;
+
+ pa_assert(command == PA_COMMAND_SET_SOURCE_PORT);
+
+ if (idx != PA_INVALID_INDEX)
+ source = pa_idxset_get_by_index(c->protocol->core->sources, idx);
+ else
+ source = pa_namereg_get(c->protocol->core, name, PA_NAMEREG_SOURCE);
+
+ CHECK_VALIDITY(c->pstream, source, tag, PA_ERR_NOENTITY);
+
+ if ((ret = pa_source_set_port(source, port, true)) < 0) {
+ pa_pstream_send_error(c->pstream, tag, -ret);
+ return;
+ }
+ }
+
+ pa_pstream_send_simple_ack(c->pstream, tag);
+}
+
+static void command_set_port_latency_offset(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
+ const char *port_name, *card_name;
+ uint32_t idx = PA_INVALID_INDEX;
+ int64_t offset;
+ pa_card *card = NULL;
+ pa_device_port *port = NULL;
+
+ pa_native_connection_assert_ref(c);
+ pa_assert(t);
+
+ if (pa_tagstruct_getu32(t, &idx) < 0 ||
+ pa_tagstruct_gets(t, &card_name) < 0 ||
+ pa_tagstruct_gets(t, &port_name) < 0 ||
+ pa_tagstruct_gets64(t, &offset) < 0 ||
+ !pa_tagstruct_eof(t)) {
+ protocol_error(c);
+ return;
+ }
+
+ CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
+ CHECK_VALIDITY(c->pstream, !card_name || pa_namereg_is_valid_name(card_name), tag, PA_ERR_INVALID);
+ CHECK_VALIDITY(c->pstream, (idx != PA_INVALID_INDEX) ^ (card_name != NULL), tag, PA_ERR_INVALID);
+ CHECK_VALIDITY(c->pstream, port_name, tag, PA_ERR_INVALID);
+
+ if (idx != PA_INVALID_INDEX)
+ card = pa_idxset_get_by_index(c->protocol->core->cards, idx);
+ else
+ card = pa_namereg_get(c->protocol->core, card_name, PA_NAMEREG_CARD);
+
+ CHECK_VALIDITY(c->pstream, card, tag, PA_ERR_NOENTITY);
+
+ port = pa_hashmap_get(card->ports, port_name);
+ CHECK_VALIDITY(c->pstream, port, tag, PA_ERR_NOENTITY);
+
+ pa_device_port_set_latency_offset(port, offset);
+
+ pa_pstream_send_simple_ack(c->pstream, tag);
+}
+
+static const pa_pdispatch_cb_t command_table[PA_COMMAND_MAX] = {
+ [PA_COMMAND_ERROR] = NULL,
+ [PA_COMMAND_TIMEOUT] = NULL,
+ [PA_COMMAND_REPLY] = NULL,
+ [PA_COMMAND_CREATE_PLAYBACK_STREAM] = command_create_playback_stream,
+ [PA_COMMAND_DELETE_PLAYBACK_STREAM] = command_delete_stream,
+ [PA_COMMAND_DRAIN_PLAYBACK_STREAM] = command_drain_playback_stream,
+ [PA_COMMAND_CREATE_RECORD_STREAM] = command_create_record_stream,
+ [PA_COMMAND_DELETE_RECORD_STREAM] = command_delete_stream,
+ [PA_COMMAND_AUTH] = command_auth,
+ [PA_COMMAND_REQUEST] = NULL,
+ [PA_COMMAND_EXIT] = command_exit,
+ [PA_COMMAND_SET_CLIENT_NAME] = command_set_client_name,
+ [PA_COMMAND_LOOKUP_SINK] = command_lookup,
+ [PA_COMMAND_LOOKUP_SOURCE] = command_lookup,
+ [PA_COMMAND_STAT] = command_stat,
+ [PA_COMMAND_GET_PLAYBACK_LATENCY] = command_get_playback_latency,
+ [PA_COMMAND_GET_RECORD_LATENCY] = command_get_record_latency,
+ [PA_COMMAND_CREATE_UPLOAD_STREAM] = command_create_upload_stream,
+ [PA_COMMAND_DELETE_UPLOAD_STREAM] = command_delete_stream,
+ [PA_COMMAND_FINISH_UPLOAD_STREAM] = command_finish_upload_stream,
+ [PA_COMMAND_PLAY_SAMPLE] = command_play_sample,
+ [PA_COMMAND_REMOVE_SAMPLE] = command_remove_sample,
+ [PA_COMMAND_GET_SINK_INFO] = command_get_info,
+ [PA_COMMAND_GET_SOURCE_INFO] = command_get_info,
+ [PA_COMMAND_GET_CLIENT_INFO] = command_get_info,
+ [PA_COMMAND_GET_CARD_INFO] = command_get_info,
+ [PA_COMMAND_GET_MODULE_INFO] = command_get_info,
+ [PA_COMMAND_GET_SINK_INPUT_INFO] = command_get_info,
+ [PA_COMMAND_GET_SOURCE_OUTPUT_INFO] = command_get_info,
+ [PA_COMMAND_GET_SAMPLE_INFO] = command_get_info,
+ [PA_COMMAND_GET_SINK_INFO_LIST] = command_get_info_list,
+ [PA_COMMAND_GET_SOURCE_INFO_LIST] = command_get_info_list,
+ [PA_COMMAND_GET_MODULE_INFO_LIST] = command_get_info_list,
+ [PA_COMMAND_GET_CLIENT_INFO_LIST] = command_get_info_list,
+ [PA_COMMAND_GET_CARD_INFO_LIST] = command_get_info_list,
+ [PA_COMMAND_GET_SINK_INPUT_INFO_LIST] = command_get_info_list,
+ [PA_COMMAND_GET_SOURCE_OUTPUT_INFO_LIST] = command_get_info_list,
+ [PA_COMMAND_GET_SAMPLE_INFO_LIST] = command_get_info_list,
+ [PA_COMMAND_GET_SERVER_INFO] = command_get_server_info,
+ [PA_COMMAND_SUBSCRIBE] = command_subscribe,
+
+ [PA_COMMAND_SET_SINK_VOLUME] = command_set_volume,
+ [PA_COMMAND_SET_SINK_INPUT_VOLUME] = command_set_volume,
+ [PA_COMMAND_SET_SOURCE_VOLUME] = command_set_volume,
+ [PA_COMMAND_SET_SOURCE_OUTPUT_VOLUME] = command_set_volume,
+
+ [PA_COMMAND_SET_SINK_MUTE] = command_set_mute,
+ [PA_COMMAND_SET_SINK_INPUT_MUTE] = command_set_mute,
+ [PA_COMMAND_SET_SOURCE_MUTE] = command_set_mute,
+ [PA_COMMAND_SET_SOURCE_OUTPUT_MUTE] = command_set_mute,
+
+ [PA_COMMAND_SUSPEND_SINK] = command_suspend,
+ [PA_COMMAND_SUSPEND_SOURCE] = command_suspend,
+
+ [PA_COMMAND_CORK_PLAYBACK_STREAM] = command_cork_playback_stream,
+ [PA_COMMAND_FLUSH_PLAYBACK_STREAM] = command_trigger_or_flush_or_prebuf_playback_stream,
+ [PA_COMMAND_TRIGGER_PLAYBACK_STREAM] = command_trigger_or_flush_or_prebuf_playback_stream,
+ [PA_COMMAND_PREBUF_PLAYBACK_STREAM] = command_trigger_or_flush_or_prebuf_playback_stream,
+
+ [PA_COMMAND_CORK_RECORD_STREAM] = command_cork_record_stream,
+ [PA_COMMAND_FLUSH_RECORD_STREAM] = command_flush_record_stream,
+
+ [PA_COMMAND_SET_DEFAULT_SINK] = command_set_default_sink_or_source,
+ [PA_COMMAND_SET_DEFAULT_SOURCE] = command_set_default_sink_or_source,
+ [PA_COMMAND_SET_PLAYBACK_STREAM_NAME] = command_set_stream_name,
+ [PA_COMMAND_SET_RECORD_STREAM_NAME] = command_set_stream_name,
+ [PA_COMMAND_KILL_CLIENT] = command_kill,
+ [PA_COMMAND_KILL_SINK_INPUT] = command_kill,
+ [PA_COMMAND_KILL_SOURCE_OUTPUT] = command_kill,
+ [PA_COMMAND_LOAD_MODULE] = command_load_module,
+ [PA_COMMAND_UNLOAD_MODULE] = command_unload_module,
+
+ [PA_COMMAND_GET_AUTOLOAD_INFO___OBSOLETE] = NULL,
+ [PA_COMMAND_GET_AUTOLOAD_INFO_LIST___OBSOLETE] = NULL,
+ [PA_COMMAND_ADD_AUTOLOAD___OBSOLETE] = NULL,
+ [PA_COMMAND_REMOVE_AUTOLOAD___OBSOLETE] = NULL,
+
+ [PA_COMMAND_MOVE_SINK_INPUT] = command_move_stream,
+ [PA_COMMAND_MOVE_SOURCE_OUTPUT] = command_move_stream,
+
+ [PA_COMMAND_SET_PLAYBACK_STREAM_BUFFER_ATTR] = command_set_stream_buffer_attr,
+ [PA_COMMAND_SET_RECORD_STREAM_BUFFER_ATTR] = command_set_stream_buffer_attr,
+
+ [PA_COMMAND_UPDATE_PLAYBACK_STREAM_SAMPLE_RATE] = command_update_stream_sample_rate,
+ [PA_COMMAND_UPDATE_RECORD_STREAM_SAMPLE_RATE] = command_update_stream_sample_rate,
+
+ [PA_COMMAND_UPDATE_RECORD_STREAM_PROPLIST] = command_update_proplist,
+ [PA_COMMAND_UPDATE_PLAYBACK_STREAM_PROPLIST] = command_update_proplist,
+ [PA_COMMAND_UPDATE_CLIENT_PROPLIST] = command_update_proplist,
+
+ [PA_COMMAND_REMOVE_RECORD_STREAM_PROPLIST] = command_remove_proplist,
+ [PA_COMMAND_REMOVE_PLAYBACK_STREAM_PROPLIST] = command_remove_proplist,
+ [PA_COMMAND_REMOVE_CLIENT_PROPLIST] = command_remove_proplist,
+
+ [PA_COMMAND_SET_CARD_PROFILE] = command_set_card_profile,
+
+ [PA_COMMAND_SET_SINK_PORT] = command_set_sink_or_source_port,
+ [PA_COMMAND_SET_SOURCE_PORT] = command_set_sink_or_source_port,
+
+ [PA_COMMAND_SET_PORT_LATENCY_OFFSET] = command_set_port_latency_offset,
+
+ [PA_COMMAND_ENABLE_SRBCHANNEL] = command_enable_srbchannel,
+
+ [PA_COMMAND_REGISTER_MEMFD_SHMID] = command_register_memfd_shmid,
+
+ [PA_COMMAND_EXTENSION] = command_extension
+};
+
+/*** pstream callbacks ***/
+
+static void pstream_packet_callback(pa_pstream *p, pa_packet *packet, pa_cmsg_ancil_data *ancil_data, void *userdata) {
+ pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
+
+ pa_assert(p);
+ pa_assert(packet);
+ pa_native_connection_assert_ref(c);
+
+ if (pa_pdispatch_run(c->pdispatch, packet, ancil_data, c) < 0) {
+ pa_log("invalid packet.");
+ native_connection_unlink(c);
+ }
+}
+
+static void pstream_memblock_callback(pa_pstream *p, uint32_t channel, int64_t offset, pa_seek_mode_t seek, const pa_memchunk *chunk, void *userdata) {
+ pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
+ output_stream *stream;
+
+ pa_assert(p);
+ pa_assert(chunk);
+ pa_native_connection_assert_ref(c);
+
+ if (!(stream = OUTPUT_STREAM(pa_idxset_get_by_index(c->output_streams, channel)))) {
+ pa_log_debug("Client sent block for invalid stream.");
+ /* Ignoring */
+ return;
+ }
+
+#ifdef PROTOCOL_NATIVE_DEBUG
+ pa_log("got %lu bytes from client", (unsigned long) chunk->length);
+#endif
+
+ if (playback_stream_isinstance(stream)) {
+ playback_stream *ps = PLAYBACK_STREAM(stream);
+
+ size_t frame_size = pa_frame_size(&ps->sink_input->sample_spec);
+ if (chunk->index % frame_size != 0 || chunk->length % frame_size != 0) {
+ pa_log_warn("Client sent non-aligned memblock: index %d, length %d, frame size: %d",
+ (int) chunk->index, (int) chunk->length, (int) frame_size);
+ return;
+ }
+
+ pa_atomic_inc(&ps->seek_or_post_in_queue);
+ if (chunk->memblock) {
+ if (seek != PA_SEEK_RELATIVE || offset != 0)
+ pa_asyncmsgq_post(ps->sink_input->sink->asyncmsgq, PA_MSGOBJECT(ps->sink_input), SINK_INPUT_MESSAGE_SEEK, PA_UINT_TO_PTR(seek), offset, chunk, NULL);
+ else
+ pa_asyncmsgq_post(ps->sink_input->sink->asyncmsgq, PA_MSGOBJECT(ps->sink_input), SINK_INPUT_MESSAGE_POST_DATA, NULL, 0, chunk, NULL);
+ } else
+ pa_asyncmsgq_post(ps->sink_input->sink->asyncmsgq, PA_MSGOBJECT(ps->sink_input), SINK_INPUT_MESSAGE_SEEK, PA_UINT_TO_PTR(seek), offset+chunk->length, NULL, NULL);
+
+ } else {
+ upload_stream *u = UPLOAD_STREAM(stream);
+ size_t l;
+
+ if (!u->memchunk.memblock) {
+ if (u->length == chunk->length && chunk->memblock) {
+ u->memchunk = *chunk;
+ pa_memblock_ref(u->memchunk.memblock);
+ u->length = 0;
+ } else {
+ u->memchunk.memblock = pa_memblock_new(c->protocol->core->mempool, u->length);
+ u->memchunk.index = u->memchunk.length = 0;
+ }
+ }
+
+ pa_assert(u->memchunk.memblock);
+
+ l = u->length;
+ if (l > chunk->length)
+ l = chunk->length;
+
+ if (l > 0) {
+ void *dst;
+ dst = pa_memblock_acquire(u->memchunk.memblock);
+
+ if (chunk->memblock) {
+ void *src;
+ src = pa_memblock_acquire(chunk->memblock);
+
+ memcpy((uint8_t*) dst + u->memchunk.index + u->memchunk.length,
+ (uint8_t*) src + chunk->index, l);
+
+ pa_memblock_release(chunk->memblock);
+ } else
+ pa_silence_memory((uint8_t*) dst + u->memchunk.index + u->memchunk.length, l, &u->sample_spec);
+
+ pa_memblock_release(u->memchunk.memblock);
+
+ u->memchunk.length += l;
+ u->length -= l;
+ }
+ }
+}
+
+static void pstream_die_callback(pa_pstream *p, void *userdata) {
+ pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
+
+ pa_assert(p);
+ pa_native_connection_assert_ref(c);
+
+ native_connection_unlink(c);
+ pa_log_info("Connection died.");
+}
+
+static void pstream_drain_callback(pa_pstream *p, void *userdata) {
+ pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
+
+ pa_assert(p);
+ pa_native_connection_assert_ref(c);
+
+ native_connection_send_memblock(c);
+}
+
+static void pstream_revoke_callback(pa_pstream *p, uint32_t block_id, void *userdata) {
+ pa_thread_mq *q;
+
+ if (!(q = pa_thread_mq_get()))
+ pa_pstream_send_revoke(p, block_id);
+ else
+ pa_asyncmsgq_post(q->outq, PA_MSGOBJECT(userdata), CONNECTION_MESSAGE_REVOKE, PA_UINT_TO_PTR(block_id), 0, NULL, NULL);
+}
+
+static void pstream_release_callback(pa_pstream *p, uint32_t block_id, void *userdata) {
+ pa_thread_mq *q;
+
+ if (!(q = pa_thread_mq_get()))
+ pa_pstream_send_release(p, block_id);
+ else
+ pa_asyncmsgq_post(q->outq, PA_MSGOBJECT(userdata), CONNECTION_MESSAGE_RELEASE, PA_UINT_TO_PTR(block_id), 0, NULL, NULL);
+}
+
+/*** client callbacks ***/
+
+static void client_kill_cb(pa_client *c) {
+ pa_assert(c);
+
+ native_connection_unlink(PA_NATIVE_CONNECTION(c->userdata));
+ pa_log_info("Connection killed.");
+}
+
+static void client_send_event_cb(pa_client *client, const char*event, pa_proplist *pl) {
+ pa_tagstruct *t;
+ pa_native_connection *c;
+
+ pa_assert(client);
+ c = PA_NATIVE_CONNECTION(client->userdata);
+ pa_native_connection_assert_ref(c);
+
+ if (c->version < 15)
+ return;
+
+ t = pa_tagstruct_new();
+ pa_tagstruct_putu32(t, PA_COMMAND_CLIENT_EVENT);
+ pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */
+ pa_tagstruct_puts(t, event);
+ pa_tagstruct_put_proplist(t, pl);
+ pa_pstream_send_tagstruct(c->pstream, t);
+}
+
+/*** module entry points ***/
+
+static void auth_timeout(pa_mainloop_api*m, pa_time_event *e, const struct timeval *t, void *userdata) {
+ pa_native_connection *c = PA_NATIVE_CONNECTION(userdata);
+
+ pa_assert(m);
+ pa_native_connection_assert_ref(c);
+ pa_assert(c->auth_timeout_event == e);
+
+ if (!c->authorized) {
+ native_connection_unlink(c);
+ pa_log_info("Connection terminated due to authentication timeout.");
+ }
+}
+
+void pa_native_protocol_connect(pa_native_protocol *p, pa_iochannel *io, pa_native_options *o) {
+ pa_native_connection *c;
+ char pname[128];
+ pa_client *client;
+ pa_client_new_data data;
+
+ pa_assert(p);
+ pa_assert(io);
+ pa_assert(o);
+
+ if (pa_idxset_size(p->connections)+1 > MAX_CONNECTIONS) {
+ pa_log_warn("Warning! Too many connections (%u), dropping incoming connection.", MAX_CONNECTIONS);
+ pa_iochannel_free(io);
+ return;
+ }
+
+ pa_client_new_data_init(&data);
+ data.module = o->module;
+ data.driver = __FILE__;
+ pa_iochannel_socket_peer_to_string(io, pname, sizeof(pname));
+ pa_proplist_setf(data.proplist, PA_PROP_APPLICATION_NAME, "Native client (%s)", pname);
+ pa_proplist_sets(data.proplist, "native-protocol.peer", pname);
+ client = pa_client_new(p->core, &data);
+ pa_client_new_data_done(&data);
+
+ if (!client)
+ return;
+
+ c = pa_msgobject_new(pa_native_connection);
+ c->parent.parent.free = native_connection_free;
+ c->parent.process_msg = native_connection_process_msg;
+ c->protocol = p;
+ c->options = pa_native_options_ref(o);
+ c->authorized = false;
+ c->srbpending = NULL;
+
+ if (o->auth_anonymous) {
+ pa_log_info("Client authenticated anonymously.");
+ c->authorized = true;
+ }
+
+ if (!c->authorized &&
+ o->auth_ip_acl &&
+ pa_ip_acl_check(o->auth_ip_acl, pa_iochannel_get_recv_fd(io)) > 0) {
+
+ pa_log_info("Client authenticated by IP ACL.");
+ c->authorized = true;
+ }
+
+ if (!c->authorized)
+ c->auth_timeout_event = pa_core_rttime_new(p->core, pa_rtclock_now() + AUTH_TIMEOUT, auth_timeout, c);
+ else
+ c->auth_timeout_event = NULL;
+
+ c->is_local = pa_iochannel_socket_is_local(io);
+ c->version = 8;
+
+ c->client = client;
+ c->client->kill = client_kill_cb;
+ c->client->send_event = client_send_event_cb;
+ c->client->userdata = c;
+
+ c->rw_mempool = NULL;
+
+ c->pstream = pa_pstream_new(p->core->mainloop, io, p->core->mempool);
+ pa_pstream_set_receive_packet_callback(c->pstream, pstream_packet_callback, c);
+ pa_pstream_set_receive_memblock_callback(c->pstream, pstream_memblock_callback, c);
+ pa_pstream_set_die_callback(c->pstream, pstream_die_callback, c);
+ pa_pstream_set_drain_callback(c->pstream, pstream_drain_callback, c);
+ pa_pstream_set_revoke_callback(c->pstream, pstream_revoke_callback, c);
+ pa_pstream_set_release_callback(c->pstream, pstream_release_callback, c);
+
+ c->pdispatch = pa_pdispatch_new(p->core->mainloop, true, command_table, PA_COMMAND_MAX);
+
+ c->record_streams = pa_idxset_new(NULL, NULL);
+ c->output_streams = pa_idxset_new(NULL, NULL);
+
+ c->rrobin_index = PA_IDXSET_INVALID;
+ c->subscription = NULL;
+
+ pa_idxset_put(p->connections, c, NULL);
+
+#ifdef HAVE_CREDS
+ if (pa_iochannel_creds_supported(io))
+ pa_iochannel_creds_enable(io);
+#endif
+
+ pa_hook_fire(&p->hooks[PA_NATIVE_HOOK_CONNECTION_PUT], c);
+}
+
+void pa_native_protocol_disconnect(pa_native_protocol *p, pa_module *m) {
+ pa_native_connection *c;
+ void *state = NULL;
+
+ pa_assert(p);
+ pa_assert(m);
+
+ while ((c = pa_idxset_iterate(p->connections, &state, NULL)))
+ if (c->options->module == m)
+ native_connection_unlink(c);
+}
+
+static pa_native_protocol* native_protocol_new(pa_core *c) {
+ pa_native_protocol *p;
+ pa_native_hook_t h;
+
+ pa_assert(c);
+
+ p = pa_xnew(pa_native_protocol, 1);
+ PA_REFCNT_INIT(p);
+ p->core = c;
+ p->connections = pa_idxset_new(NULL, NULL);
+
+ p->servers = NULL;
+
+ p->extensions = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
+
+ for (h = 0; h < PA_NATIVE_HOOK_MAX; h++)
+ pa_hook_init(&p->hooks[h], p);
+
+ pa_assert_se(pa_shared_set(c, "native-protocol", p) >= 0);
+
+ return p;
+}
+
+pa_native_protocol* pa_native_protocol_get(pa_core *c) {
+ pa_native_protocol *p;
+
+ if ((p = pa_shared_get(c, "native-protocol")))
+ return pa_native_protocol_ref(p);
+
+ return native_protocol_new(c);
+}
+
+pa_native_protocol* pa_native_protocol_ref(pa_native_protocol *p) {
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) >= 1);
+
+ PA_REFCNT_INC(p);
+
+ return p;
+}
+
+void pa_native_protocol_unref(pa_native_protocol *p) {
+ pa_native_connection *c;
+ pa_native_hook_t h;
+
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) >= 1);
+
+ if (PA_REFCNT_DEC(p) > 0)
+ return;
+
+ while ((c = pa_idxset_first(p->connections, NULL)))
+ native_connection_unlink(c);
+
+ pa_idxset_free(p->connections, NULL);
+
+ pa_strlist_free(p->servers);
+
+ for (h = 0; h < PA_NATIVE_HOOK_MAX; h++)
+ pa_hook_done(&p->hooks[h]);
+
+ pa_hashmap_free(p->extensions);
+
+ pa_assert_se(pa_shared_remove(p->core, "native-protocol") >= 0);
+
+ pa_xfree(p);
+}
+
+void pa_native_protocol_add_server_string(pa_native_protocol *p, const char *name) {
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) >= 1);
+ pa_assert(name);
+
+ p->servers = pa_strlist_prepend(p->servers, name);
+
+ pa_hook_fire(&p->hooks[PA_NATIVE_HOOK_SERVERS_CHANGED], p->servers);
+}
+
+void pa_native_protocol_remove_server_string(pa_native_protocol *p, const char *name) {
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) >= 1);
+ pa_assert(name);
+
+ p->servers = pa_strlist_remove(p->servers, name);
+
+ pa_hook_fire(&p->hooks[PA_NATIVE_HOOK_SERVERS_CHANGED], p->servers);
+}
+
+pa_hook *pa_native_protocol_hooks(pa_native_protocol *p) {
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) >= 1);
+
+ return p->hooks;
+}
+
+pa_strlist *pa_native_protocol_servers(pa_native_protocol *p) {
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) >= 1);
+
+ return p->servers;
+}
+
+int pa_native_protocol_install_ext(pa_native_protocol *p, pa_module *m, pa_native_protocol_ext_cb_t cb) {
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) >= 1);
+ pa_assert(m);
+ pa_assert(cb);
+ pa_assert(!pa_hashmap_get(p->extensions, m));
+
+ pa_assert_se(pa_hashmap_put(p->extensions, m, (void*) (unsigned long) cb) == 0);
+ return 0;
+}
+
+void pa_native_protocol_remove_ext(pa_native_protocol *p, pa_module *m) {
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) >= 1);
+ pa_assert(m);
+
+ pa_assert_se(pa_hashmap_remove(p->extensions, m));
+}
+
+pa_native_options* pa_native_options_new(void) {
+ pa_native_options *o;
+
+ o = pa_xnew0(pa_native_options, 1);
+ PA_REFCNT_INIT(o);
+
+ return o;
+}
+
+pa_native_options* pa_native_options_ref(pa_native_options *o) {
+ pa_assert(o);
+ pa_assert(PA_REFCNT_VALUE(o) >= 1);
+
+ PA_REFCNT_INC(o);
+
+ return o;
+}
+
+void pa_native_options_unref(pa_native_options *o) {
+ pa_assert(o);
+ pa_assert(PA_REFCNT_VALUE(o) >= 1);
+
+ if (PA_REFCNT_DEC(o) > 0)
+ return;
+
+ pa_xfree(o->auth_group);
+
+ if (o->auth_ip_acl)
+ pa_ip_acl_free(o->auth_ip_acl);
+
+ if (o->auth_cookie)
+ pa_auth_cookie_unref(o->auth_cookie);
+
+ pa_xfree(o);
+}
+
+int pa_native_options_parse(pa_native_options *o, pa_core *c, pa_modargs *ma) {
+ bool enabled;
+ const char *acl;
+
+ pa_assert(o);
+ pa_assert(PA_REFCNT_VALUE(o) >= 1);
+ pa_assert(ma);
+
+ o->srbchannel = true;
+ if (pa_modargs_get_value_boolean(ma, "srbchannel", &o->srbchannel) < 0) {
+ pa_log("srbchannel= expects a boolean argument.");
+ return -1;
+ }
+
+ if (pa_modargs_get_value_boolean(ma, "auth-anonymous", &o->auth_anonymous) < 0) {
+ pa_log("auth-anonymous= expects a boolean argument.");
+ return -1;
+ }
+
+ enabled = true;
+ if (pa_modargs_get_value_boolean(ma, "auth-group-enable", &enabled) < 0) {
+ pa_log("auth-group-enable= expects a boolean argument.");
+ return -1;
+ }
+
+ pa_xfree(o->auth_group);
+ o->auth_group = enabled ? pa_xstrdup(pa_modargs_get_value(ma, "auth-group", pa_in_system_mode() ? PA_ACCESS_GROUP : NULL)) : NULL;
+
+#ifndef HAVE_CREDS
+ if (o->auth_group)
+ pa_log_warn("Authentication group configured, but not available on local system. Ignoring.");
+#endif
+
+ if ((acl = pa_modargs_get_value(ma, "auth-ip-acl", NULL))) {
+ pa_ip_acl *ipa;
+
+ if (!(ipa = pa_ip_acl_new(acl))) {
+ pa_log("Failed to parse IP ACL '%s'", acl);
+ return -1;
+ }
+
+ if (o->auth_ip_acl)
+ pa_ip_acl_free(o->auth_ip_acl);
+
+ o->auth_ip_acl = ipa;
+ }
+
+ enabled = true;
+ if (pa_modargs_get_value_boolean(ma, "auth-cookie-enabled", &enabled) < 0) {
+ pa_log("auth-cookie-enabled= expects a boolean argument.");
+ return -1;
+ }
+
+ if (o->auth_cookie)
+ pa_auth_cookie_unref(o->auth_cookie);
+
+ if (enabled) {
+ const char *cn;
+
+ /* The new name for this is 'auth-cookie', for compat reasons
+ * we check the old name too */
+ cn = pa_modargs_get_value(ma, "auth-cookie", NULL);
+ if (!cn)
+ cn = pa_modargs_get_value(ma, "cookie", NULL);
+
+ if (cn)
+ o->auth_cookie = pa_auth_cookie_get(c, cn, true, PA_NATIVE_COOKIE_LENGTH);
+ else {
+ o->auth_cookie = pa_auth_cookie_get(c, PA_NATIVE_COOKIE_FILE, false, PA_NATIVE_COOKIE_LENGTH);
+ if (!o->auth_cookie) {
+ char *fallback_path;
+
+ if (pa_append_to_home_dir(PA_NATIVE_COOKIE_FILE_FALLBACK, &fallback_path) >= 0) {
+ o->auth_cookie = pa_auth_cookie_get(c, fallback_path, false, PA_NATIVE_COOKIE_LENGTH);
+ pa_xfree(fallback_path);
+ }
+
+ if (!o->auth_cookie)
+ o->auth_cookie = pa_auth_cookie_get(c, PA_NATIVE_COOKIE_FILE, true, PA_NATIVE_COOKIE_LENGTH);
+ }
+ }
+
+ if (!o->auth_cookie)
+ return -1;
+
+ } else
+ o->auth_cookie = NULL;
+
+ return 0;
+}
+
+pa_pstream* pa_native_connection_get_pstream(pa_native_connection *c) {
+ pa_native_connection_assert_ref(c);
+
+ return c->pstream;
+}
+
+pa_client* pa_native_connection_get_client(pa_native_connection *c) {
+ pa_native_connection_assert_ref(c);
+
+ return c->client;
+}
diff --git a/src/pulsecore/protocol-native.h b/src/pulsecore/protocol-native.h
new file mode 100644
index 0000000..0347fdf
--- /dev/null
+++ b/src/pulsecore/protocol-native.h
@@ -0,0 +1,88 @@
+#ifndef fooprotocolnativehfoo
+#define fooprotocolnativehfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulsecore/core.h>
+#include <pulsecore/ipacl.h>
+#include <pulsecore/auth-cookie.h>
+#include <pulsecore/iochannel.h>
+#include <pulsecore/module.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/strlist.h>
+#include <pulsecore/hook-list.h>
+#include <pulsecore/pstream.h>
+#include <pulsecore/tagstruct.h>
+
+typedef struct pa_native_protocol pa_native_protocol;
+
+typedef struct pa_native_connection pa_native_connection;
+
+typedef struct pa_native_options {
+ PA_REFCNT_DECLARE;
+
+ pa_module *module;
+
+ bool auth_anonymous;
+ bool srbchannel;
+ char *auth_group;
+ pa_ip_acl *auth_ip_acl;
+ pa_auth_cookie *auth_cookie;
+} pa_native_options;
+
+typedef enum pa_native_hook {
+ PA_NATIVE_HOOK_SERVERS_CHANGED,
+ PA_NATIVE_HOOK_CONNECTION_PUT,
+ PA_NATIVE_HOOK_CONNECTION_UNLINK,
+ PA_NATIVE_HOOK_MAX
+} pa_native_hook_t;
+
+pa_native_protocol* pa_native_protocol_get(pa_core *core);
+pa_native_protocol* pa_native_protocol_ref(pa_native_protocol *p);
+void pa_native_protocol_unref(pa_native_protocol *p);
+void pa_native_protocol_connect(pa_native_protocol *p, pa_iochannel *io, pa_native_options *a);
+void pa_native_protocol_disconnect(pa_native_protocol *p, pa_module *m);
+
+pa_hook *pa_native_protocol_hooks(pa_native_protocol *p);
+
+void pa_native_protocol_add_server_string(pa_native_protocol *p, const char *name);
+void pa_native_protocol_remove_server_string(pa_native_protocol *p, const char *name);
+pa_strlist *pa_native_protocol_servers(pa_native_protocol *p);
+
+typedef int (*pa_native_protocol_ext_cb_t)(
+ pa_native_protocol *p,
+ pa_module *m,
+ pa_native_connection *c,
+ uint32_t tag,
+ pa_tagstruct *t);
+
+int pa_native_protocol_install_ext(pa_native_protocol *p, pa_module *m, pa_native_protocol_ext_cb_t cb);
+void pa_native_protocol_remove_ext(pa_native_protocol *p, pa_module *m);
+
+pa_pstream* pa_native_connection_get_pstream(pa_native_connection *c);
+pa_client* pa_native_connection_get_client(pa_native_connection *c);
+
+pa_native_options* pa_native_options_new(void);
+pa_native_options* pa_native_options_ref(pa_native_options *o);
+void pa_native_options_unref(pa_native_options *o);
+int pa_native_options_parse(pa_native_options *o, pa_core *c, pa_modargs *ma);
+
+#endif
diff --git a/src/pulsecore/protocol-simple.c b/src/pulsecore/protocol-simple.c
new file mode 100644
index 0000000..77d0539
--- /dev/null
+++ b/src/pulsecore/protocol-simple.c
@@ -0,0 +1,770 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+
+#include <pulse/xmalloc.h>
+#include <pulse/timeval.h>
+
+#include <pulsecore/sink-input.h>
+#include <pulsecore/source-output.h>
+#include <pulsecore/client.h>
+#include <pulsecore/sample-util.h>
+#include <pulsecore/namereg.h>
+#include <pulsecore/log.h>
+#include <pulsecore/core-error.h>
+#include <pulsecore/atomic.h>
+#include <pulsecore/thread-mq.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/shared.h>
+
+#include "protocol-simple.h"
+
+/* Don't allow more than this many concurrent connections */
+#define MAX_CONNECTIONS 10
+
+typedef struct connection {
+ pa_msgobject parent;
+ pa_simple_protocol *protocol;
+ pa_simple_options *options;
+ pa_iochannel *io;
+ pa_sink_input *sink_input;
+ pa_source_output *source_output;
+ pa_client *client;
+ pa_memblockq *input_memblockq, *output_memblockq;
+
+ bool dead;
+
+ struct {
+ pa_memblock *current_memblock;
+ size_t memblock_index;
+ pa_atomic_t missing;
+ bool underrun;
+ } playback;
+} connection;
+
+PA_DEFINE_PRIVATE_CLASS(connection, pa_msgobject);
+#define CONNECTION(o) (connection_cast(o))
+
+struct pa_simple_protocol {
+ PA_REFCNT_DECLARE;
+
+ pa_core *core;
+ pa_idxset *connections;
+};
+
+enum {
+ SINK_INPUT_MESSAGE_POST_DATA = PA_SINK_INPUT_MESSAGE_MAX, /* data from main loop to sink input */
+ SINK_INPUT_MESSAGE_DISABLE_PREBUF /* disabled prebuf, get playback started. */
+};
+
+enum {
+ CONNECTION_MESSAGE_REQUEST_DATA, /* data requested from sink input from the main loop */
+ CONNECTION_MESSAGE_POST_DATA, /* data from source output to main loop */
+ CONNECTION_MESSAGE_UNLINK_CONNECTION /* Please drop the connection now */
+};
+
+#define PLAYBACK_BUFFER_SECONDS (.5)
+#define PLAYBACK_BUFFER_FRAGMENTS (10)
+#define RECORD_BUFFER_SECONDS (5)
+#define DEFAULT_SINK_LATENCY (300*PA_USEC_PER_MSEC)
+#define DEFAULT_SOURCE_LATENCY (300*PA_USEC_PER_MSEC)
+
+static void connection_unlink(connection *c) {
+ pa_assert(c);
+
+ if (!c->protocol)
+ return;
+
+ if (c->options) {
+ pa_simple_options_unref(c->options);
+ c->options = NULL;
+ }
+
+ if (c->sink_input) {
+ pa_sink_input_unlink(c->sink_input);
+ pa_sink_input_unref(c->sink_input);
+ c->sink_input = NULL;
+ }
+
+ if (c->source_output) {
+ pa_source_output_unlink(c->source_output);
+ pa_source_output_unref(c->source_output);
+ c->source_output = NULL;
+ }
+
+ if (c->client) {
+ pa_client_free(c->client);
+ c->client = NULL;
+ }
+
+ if (c->io) {
+ pa_iochannel_free(c->io);
+ c->io = NULL;
+ }
+
+ pa_idxset_remove_by_data(c->protocol->connections, c, NULL);
+ c->protocol = NULL;
+ connection_unref(c);
+}
+
+static void connection_free(pa_object *o) {
+ connection *c = CONNECTION(o);
+ pa_assert(c);
+
+ if (c->playback.current_memblock)
+ pa_memblock_unref(c->playback.current_memblock);
+
+ if (c->input_memblockq)
+ pa_memblockq_free(c->input_memblockq);
+ if (c->output_memblockq)
+ pa_memblockq_free(c->output_memblockq);
+
+ pa_xfree(c);
+}
+
+static int do_read(connection *c) {
+ pa_memchunk chunk;
+ ssize_t r;
+ size_t l;
+ void *p;
+ size_t space = 0;
+
+ connection_assert_ref(c);
+
+ if (!c->sink_input || (l = (size_t) pa_atomic_load(&c->playback.missing)) <= 0)
+ return 0;
+
+ if (c->playback.current_memblock) {
+
+ space = pa_memblock_get_length(c->playback.current_memblock) - c->playback.memblock_index;
+
+ if (space <= 0) {
+ pa_memblock_unref(c->playback.current_memblock);
+ c->playback.current_memblock = NULL;
+ }
+ }
+
+ if (!c->playback.current_memblock) {
+ pa_assert_se(c->playback.current_memblock = pa_memblock_new(c->protocol->core->mempool, (size_t) -1));
+ c->playback.memblock_index = 0;
+
+ space = pa_memblock_get_length(c->playback.current_memblock);
+ }
+
+ if (l > space)
+ l = space;
+
+ p = pa_memblock_acquire(c->playback.current_memblock);
+ r = pa_iochannel_read(c->io, (uint8_t*) p + c->playback.memblock_index, l);
+ pa_memblock_release(c->playback.current_memblock);
+
+ if (r <= 0) {
+
+ if (r < 0 && (errno == EINTR || errno == EAGAIN))
+ return 0;
+
+ pa_log_debug("read(): %s", r == 0 ? "EOF" : pa_cstrerror(errno));
+ return -1;
+ }
+
+ chunk.memblock = c->playback.current_memblock;
+ chunk.index = c->playback.memblock_index;
+ chunk.length = (size_t) r;
+
+ c->playback.memblock_index += (size_t) r;
+
+ pa_asyncmsgq_post(c->sink_input->sink->asyncmsgq, PA_MSGOBJECT(c->sink_input), SINK_INPUT_MESSAGE_POST_DATA, NULL, 0, &chunk, NULL);
+ pa_atomic_sub(&c->playback.missing, (int) r);
+
+ return 0;
+}
+
+static int do_write(connection *c) {
+ pa_memchunk chunk;
+ ssize_t r;
+ void *p;
+
+ connection_assert_ref(c);
+
+ if (!c->source_output)
+ return 0;
+
+ if (pa_memblockq_peek(c->output_memblockq, &chunk) < 0) {
+/* pa_log("peek failed"); */
+ return 0;
+ }
+
+ pa_assert(chunk.memblock);
+ pa_assert(chunk.length);
+
+ p = pa_memblock_acquire(chunk.memblock);
+ r = pa_iochannel_write(c->io, (uint8_t*) p+chunk.index, chunk.length);
+ pa_memblock_release(chunk.memblock);
+
+ pa_memblock_unref(chunk.memblock);
+
+ if (r < 0) {
+ pa_log("write(): %s", pa_cstrerror(errno));
+ return -1;
+ }
+
+ pa_memblockq_drop(c->output_memblockq, (size_t) r);
+
+ return 1;
+}
+
+static void do_work(connection *c) {
+ connection_assert_ref(c);
+
+ if (c->dead)
+ return;
+
+ if (pa_iochannel_is_readable(c->io))
+ if (do_read(c) < 0)
+ goto fail;
+
+ if (!c->sink_input && pa_iochannel_is_hungup(c->io))
+ goto fail;
+
+ while (pa_iochannel_is_writable(c->io)) {
+ int r = do_write(c);
+ if (r < 0)
+ goto fail;
+ if (r == 0)
+ break;
+ }
+
+ return;
+
+fail:
+
+ if (c->sink_input) {
+
+ /* If there is a sink input, we first drain what we already have read before shutting down the connection */
+ c->dead = true;
+
+ pa_iochannel_free(c->io);
+ c->io = NULL;
+
+ pa_asyncmsgq_post(c->sink_input->sink->asyncmsgq, PA_MSGOBJECT(c->sink_input), SINK_INPUT_MESSAGE_DISABLE_PREBUF, NULL, 0, NULL, NULL);
+ } else
+ connection_unlink(c);
+}
+
+static int connection_process_msg(pa_msgobject *o, int code, void*userdata, int64_t offset, pa_memchunk *chunk) {
+ connection *c = CONNECTION(o);
+ connection_assert_ref(c);
+
+ if (!c->protocol)
+ return -1;
+
+ switch (code) {
+ case CONNECTION_MESSAGE_REQUEST_DATA:
+ do_work(c);
+ break;
+
+ case CONNECTION_MESSAGE_POST_DATA:
+/* pa_log("got data %u", chunk->length); */
+ pa_memblockq_push_align(c->output_memblockq, chunk);
+ do_work(c);
+ break;
+
+ case CONNECTION_MESSAGE_UNLINK_CONNECTION:
+ connection_unlink(c);
+ break;
+ }
+
+ return 0;
+}
+
+/*** sink_input callbacks ***/
+
+/* Called from thread context */
+static int sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk) {
+ pa_sink_input *i = PA_SINK_INPUT(o);
+ connection*c;
+
+ pa_sink_input_assert_ref(i);
+ c = CONNECTION(i->userdata);
+ connection_assert_ref(c);
+
+ switch (code) {
+
+ case SINK_INPUT_MESSAGE_POST_DATA: {
+ pa_assert(chunk);
+
+ /* New data from the main loop */
+ pa_memblockq_push_align(c->input_memblockq, chunk);
+
+ if (pa_memblockq_is_readable(c->input_memblockq) && c->playback.underrun) {
+ pa_log_debug("Requesting rewind due to end of underrun.");
+ pa_sink_input_request_rewind(c->sink_input, 0, false, true, false);
+ }
+
+/* pa_log("got data, %u", pa_memblockq_get_length(c->input_memblockq)); */
+
+ return 0;
+ }
+
+ case SINK_INPUT_MESSAGE_DISABLE_PREBUF:
+ pa_memblockq_prebuf_disable(c->input_memblockq);
+ return 0;
+
+ case PA_SINK_INPUT_MESSAGE_GET_LATENCY: {
+ pa_usec_t *r = userdata;
+
+ /* The default handler will add in the extra latency added by the resampler.*/
+ *r = pa_bytes_to_usec(pa_memblockq_get_length(c->input_memblockq), &c->sink_input->sample_spec);
+ }
+ /* Fall through. */
+
+ default:
+ return pa_sink_input_process_msg(o, code, userdata, offset, chunk);
+ }
+}
+
+/* Called from thread context */
+static int sink_input_pop_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) {
+ connection *c;
+
+ pa_sink_input_assert_ref(i);
+ c = CONNECTION(i->userdata);
+ connection_assert_ref(c);
+ pa_assert(chunk);
+
+ if (pa_memblockq_peek(c->input_memblockq, chunk) < 0) {
+
+ c->playback.underrun = true;
+
+ if (c->dead && pa_sink_input_safe_to_remove(i))
+ pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(c), CONNECTION_MESSAGE_UNLINK_CONNECTION, NULL, 0, NULL, NULL);
+
+ return -1;
+ } else {
+ size_t m;
+
+ chunk->length = PA_MIN(length, chunk->length);
+
+ c->playback.underrun = false;
+
+ pa_memblockq_drop(c->input_memblockq, chunk->length);
+ m = pa_memblockq_pop_missing(c->input_memblockq);
+
+ if (m > 0)
+ if (pa_atomic_add(&c->playback.missing, (int) m) <= 0)
+ pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(c), CONNECTION_MESSAGE_REQUEST_DATA, NULL, 0, NULL, NULL);
+
+ return 0;
+ }
+}
+
+/* Called from thread context */
+static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) {
+ connection *c;
+
+ pa_sink_input_assert_ref(i);
+ c = CONNECTION(i->userdata);
+ connection_assert_ref(c);
+
+ /* If we are in an underrun, then we don't rewind */
+ if (i->thread_info.underrun_for > 0)
+ return;
+
+ pa_memblockq_rewind(c->input_memblockq, nbytes);
+}
+
+/* Called from thread context */
+static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) {
+ connection *c;
+
+ pa_sink_input_assert_ref(i);
+ c = CONNECTION(i->userdata);
+ connection_assert_ref(c);
+
+ pa_memblockq_set_maxrewind(c->input_memblockq, nbytes);
+}
+
+/* Called from main context */
+static void sink_input_kill_cb(pa_sink_input *i) {
+ pa_sink_input_assert_ref(i);
+
+ connection_unlink(CONNECTION(i->userdata));
+}
+
+/*** source_output callbacks ***/
+
+/* Called from thread context */
+static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk) {
+ connection *c;
+
+ pa_source_output_assert_ref(o);
+ c = CONNECTION(o->userdata);
+ pa_assert(c);
+ pa_assert(chunk);
+
+ pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(c), CONNECTION_MESSAGE_POST_DATA, NULL, 0, chunk, NULL);
+}
+
+/* Called from main context */
+static void source_output_kill_cb(pa_source_output *o) {
+ pa_source_output_assert_ref(o);
+
+ connection_unlink(CONNECTION(o->userdata));
+}
+
+/* Called from main context */
+static pa_usec_t source_output_get_latency_cb(pa_source_output *o) {
+ connection*c;
+
+ pa_source_output_assert_ref(o);
+ c = CONNECTION(o->userdata);
+ pa_assert(c);
+
+ return pa_bytes_to_usec(pa_memblockq_get_length(c->output_memblockq), &c->source_output->sample_spec);
+}
+
+/*** client callbacks ***/
+
+static void client_kill_cb(pa_client *client) {
+ connection*c;
+
+ pa_assert(client);
+ c = CONNECTION(client->userdata);
+ pa_assert(c);
+
+ connection_unlink(c);
+}
+
+/*** pa_iochannel callbacks ***/
+
+static void io_callback(pa_iochannel*io, void *userdata) {
+ connection *c = CONNECTION(userdata);
+
+ connection_assert_ref(c);
+ pa_assert(io);
+
+ do_work(c);
+}
+
+/*** socket_server callbacks ***/
+
+void pa_simple_protocol_connect(pa_simple_protocol *p, pa_iochannel *io, pa_simple_options *o) {
+ connection *c = NULL;
+ char pname[128];
+ pa_client_new_data client_data;
+
+ pa_assert(p);
+ pa_assert(io);
+ pa_assert(o);
+
+ if (pa_idxset_size(p->connections)+1 > MAX_CONNECTIONS) {
+ pa_log("Warning! Too many connections (%u), dropping incoming connection.", MAX_CONNECTIONS);
+ pa_iochannel_free(io);
+ return;
+ }
+
+ c = pa_msgobject_new(connection);
+ c->parent.parent.free = connection_free;
+ c->parent.process_msg = connection_process_msg;
+ c->io = io;
+ pa_iochannel_set_callback(c->io, io_callback, c);
+
+ c->sink_input = NULL;
+ c->source_output = NULL;
+ c->input_memblockq = c->output_memblockq = NULL;
+ c->protocol = p;
+ c->options = pa_simple_options_ref(o);
+ c->playback.current_memblock = NULL;
+ c->playback.memblock_index = 0;
+ c->dead = false;
+ c->playback.underrun = true;
+ pa_atomic_store(&c->playback.missing, 0);
+
+ pa_client_new_data_init(&client_data);
+ client_data.module = o->module;
+ client_data.driver = __FILE__;
+ pa_iochannel_socket_peer_to_string(io, pname, sizeof(pname));
+ pa_proplist_setf(client_data.proplist, PA_PROP_APPLICATION_NAME, "Simple client (%s)", pname);
+ pa_proplist_sets(client_data.proplist, "simple-protocol.peer", pname);
+ c->client = pa_client_new(p->core, &client_data);
+ pa_client_new_data_done(&client_data);
+
+ if (!c->client)
+ goto fail;
+
+ c->client->kill = client_kill_cb;
+ c->client->userdata = c;
+
+ if (o->playback) {
+ pa_sink_input_new_data data;
+ pa_memchunk silence;
+ size_t l;
+ pa_sink *sink;
+
+ if (!(sink = pa_namereg_get(c->protocol->core, o->default_sink, PA_NAMEREG_SINK))) {
+ pa_log("Failed to get sink.");
+ goto fail;
+ }
+
+ pa_sink_input_new_data_init(&data);
+ data.driver = __FILE__;
+ data.module = o->module;
+ data.client = c->client;
+ pa_sink_input_new_data_set_sink(&data, sink, false, true);
+ pa_proplist_update(data.proplist, PA_UPDATE_MERGE, c->client->proplist);
+ pa_sink_input_new_data_set_sample_spec(&data, &o->sample_spec);
+
+ pa_sink_input_new(&c->sink_input, p->core, &data);
+ pa_sink_input_new_data_done(&data);
+
+ if (!c->sink_input) {
+ pa_log("Failed to create sink input.");
+ goto fail;
+ }
+
+ c->sink_input->parent.process_msg = sink_input_process_msg;
+ c->sink_input->pop = sink_input_pop_cb;
+ c->sink_input->process_rewind = sink_input_process_rewind_cb;
+ c->sink_input->update_max_rewind = sink_input_update_max_rewind_cb;
+ c->sink_input->kill = sink_input_kill_cb;
+ c->sink_input->userdata = c;
+
+ pa_sink_input_set_requested_latency(c->sink_input, DEFAULT_SINK_LATENCY);
+
+ l = (size_t) ((double) pa_bytes_per_second(&o->sample_spec)*PLAYBACK_BUFFER_SECONDS);
+ pa_sink_input_get_silence(c->sink_input, &silence);
+ c->input_memblockq = pa_memblockq_new(
+ "simple protocol connection input_memblockq",
+ 0,
+ l,
+ l,
+ &o->sample_spec,
+ (size_t) -1,
+ l/PLAYBACK_BUFFER_FRAGMENTS,
+ 0,
+ &silence);
+ pa_memblock_unref(silence.memblock);
+
+ pa_iochannel_socket_set_rcvbuf(io, l);
+
+ pa_atomic_store(&c->playback.missing, (int) pa_memblockq_pop_missing(c->input_memblockq));
+
+ pa_sink_input_put(c->sink_input);
+ }
+
+ if (o->record) {
+ pa_source_output_new_data data;
+ size_t l;
+ pa_source *source;
+
+ if (!(source = pa_namereg_get(c->protocol->core, o->default_source, PA_NAMEREG_SOURCE))) {
+ pa_log("Failed to get source.");
+ goto fail;
+ }
+
+ pa_source_output_new_data_init(&data);
+ data.driver = __FILE__;
+ data.module = o->module;
+ data.client = c->client;
+ pa_source_output_new_data_set_source(&data, source, false, true);
+ pa_proplist_update(data.proplist, PA_UPDATE_MERGE, c->client->proplist);
+ pa_source_output_new_data_set_sample_spec(&data, &o->sample_spec);
+
+ pa_source_output_new(&c->source_output, p->core, &data);
+ pa_source_output_new_data_done(&data);
+
+ if (!c->source_output) {
+ pa_log("Failed to create source output.");
+ goto fail;
+ }
+ c->source_output->push = source_output_push_cb;
+ c->source_output->kill = source_output_kill_cb;
+ c->source_output->get_latency = source_output_get_latency_cb;
+ c->source_output->userdata = c;
+
+ pa_source_output_set_requested_latency(c->source_output, DEFAULT_SOURCE_LATENCY);
+
+ l = (size_t) (pa_bytes_per_second(&o->sample_spec)*RECORD_BUFFER_SECONDS);
+ c->output_memblockq = pa_memblockq_new(
+ "simple protocol connection output_memblockq",
+ 0,
+ l,
+ 0,
+ &o->sample_spec,
+ 1,
+ 0,
+ 0,
+ NULL);
+ pa_iochannel_socket_set_sndbuf(io, l);
+
+ pa_source_output_put(c->source_output);
+ }
+
+ pa_idxset_put(p->connections, c, NULL);
+
+ return;
+
+fail:
+ connection_unlink(c);
+}
+
+void pa_simple_protocol_disconnect(pa_simple_protocol *p, pa_module *m) {
+ connection *c;
+ void *state = NULL;
+
+ pa_assert(p);
+ pa_assert(m);
+
+ while ((c = pa_idxset_iterate(p->connections, &state, NULL)))
+ if (c->options->module == m)
+ connection_unlink(c);
+}
+
+static pa_simple_protocol* simple_protocol_new(pa_core *c) {
+ pa_simple_protocol *p;
+
+ pa_assert(c);
+
+ p = pa_xnew(pa_simple_protocol, 1);
+ PA_REFCNT_INIT(p);
+ p->core = c;
+ p->connections = pa_idxset_new(NULL, NULL);
+
+ pa_assert_se(pa_shared_set(c, "simple-protocol", p) >= 0);
+
+ return p;
+}
+
+pa_simple_protocol* pa_simple_protocol_get(pa_core *c) {
+ pa_simple_protocol *p;
+
+ if ((p = pa_shared_get(c, "simple-protocol")))
+ return pa_simple_protocol_ref(p);
+
+ return simple_protocol_new(c);
+}
+
+pa_simple_protocol* pa_simple_protocol_ref(pa_simple_protocol *p) {
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) >= 1);
+
+ PA_REFCNT_INC(p);
+
+ return p;
+}
+
+void pa_simple_protocol_unref(pa_simple_protocol *p) {
+ connection *c;
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) >= 1);
+
+ if (PA_REFCNT_DEC(p) > 0)
+ return;
+
+ while ((c = pa_idxset_first(p->connections, NULL)))
+ connection_unlink(c);
+
+ pa_idxset_free(p->connections, NULL);
+
+ pa_assert_se(pa_shared_remove(p->core, "simple-protocol") >= 0);
+
+ pa_xfree(p);
+}
+
+pa_simple_options* pa_simple_options_new(void) {
+ pa_simple_options *o;
+
+ o = pa_xnew0(pa_simple_options, 1);
+ PA_REFCNT_INIT(o);
+
+ o->record = false;
+ o->playback = true;
+
+ return o;
+}
+
+pa_simple_options* pa_simple_options_ref(pa_simple_options *o) {
+ pa_assert(o);
+ pa_assert(PA_REFCNT_VALUE(o) >= 1);
+
+ PA_REFCNT_INC(o);
+
+ return o;
+}
+
+void pa_simple_options_unref(pa_simple_options *o) {
+ pa_assert(o);
+ pa_assert(PA_REFCNT_VALUE(o) >= 1);
+
+ if (PA_REFCNT_DEC(o) > 0)
+ return;
+
+ pa_xfree(o->default_sink);
+ pa_xfree(o->default_source);
+
+ pa_xfree(o);
+}
+
+int pa_simple_options_parse(pa_simple_options *o, pa_core *c, pa_modargs *ma) {
+ bool enabled;
+
+ pa_assert(o);
+ pa_assert(PA_REFCNT_VALUE(o) >= 1);
+ pa_assert(ma);
+
+ o->sample_spec = c->default_sample_spec;
+ if (pa_modargs_get_sample_spec_and_channel_map(ma, &o->sample_spec, &o->channel_map, PA_CHANNEL_MAP_DEFAULT) < 0) {
+ pa_log("Failed to parse sample type specification.");
+ return -1;
+ }
+
+ pa_xfree(o->default_source);
+ o->default_source = pa_xstrdup(pa_modargs_get_value(ma, "source", NULL));
+
+ pa_xfree(o->default_sink);
+ o->default_sink = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL));
+
+ enabled = o->record;
+ if (pa_modargs_get_value_boolean(ma, "record", &enabled) < 0) {
+ pa_log("record= expects a boolean argument.");
+ return -1;
+ }
+ o->record = enabled;
+
+ enabled = o->playback;
+ if (pa_modargs_get_value_boolean(ma, "playback", &enabled) < 0) {
+ pa_log("playback= expects a boolean argument.");
+ return -1;
+ }
+ o->playback = enabled;
+
+ if (!o->playback && !o->record) {
+ pa_log("neither playback nor recording enabled for protocol.");
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/src/pulsecore/protocol-simple.h b/src/pulsecore/protocol-simple.h
new file mode 100644
index 0000000..0a72cd7
--- /dev/null
+++ b/src/pulsecore/protocol-simple.h
@@ -0,0 +1,55 @@
+#ifndef fooprotocolsimplehfoo
+#define fooprotocolsimplehfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulsecore/socket-server.h>
+#include <pulsecore/module.h>
+#include <pulsecore/core.h>
+#include <pulsecore/modargs.h>
+
+typedef struct pa_simple_protocol pa_simple_protocol;
+
+typedef struct pa_simple_options {
+ PA_REFCNT_DECLARE;
+
+ pa_module *module;
+
+ char *default_sink, *default_source;
+
+ pa_sample_spec sample_spec;
+ pa_channel_map channel_map;
+
+ bool record:1;
+ bool playback:1;
+} pa_simple_options;
+
+pa_simple_protocol* pa_simple_protocol_get(pa_core*core);
+pa_simple_protocol* pa_simple_protocol_ref(pa_simple_protocol *p);
+void pa_simple_protocol_unref(pa_simple_protocol *p);
+void pa_simple_protocol_connect(pa_simple_protocol *p, pa_iochannel *io, pa_simple_options *o);
+void pa_simple_protocol_disconnect(pa_simple_protocol *p, pa_module *m);
+
+pa_simple_options* pa_simple_options_new(void);
+pa_simple_options* pa_simple_options_ref(pa_simple_options *o);
+void pa_simple_options_unref(pa_simple_options *o);
+int pa_simple_options_parse(pa_simple_options *o, pa_core *c, pa_modargs *ma);
+
+#endif
diff --git a/src/pulsecore/pstream-util.c b/src/pulsecore/pstream-util.c
new file mode 100644
index 0000000..d0d6c66
--- /dev/null
+++ b/src/pulsecore/pstream-util.c
@@ -0,0 +1,198 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/native-common.h>
+#include <pulsecore/pstream.h>
+#include <pulsecore/refcnt.h>
+#include <pulse/xmalloc.h>
+
+#include "pstream-util.h"
+
+static void pa_pstream_send_tagstruct_with_ancil_data(pa_pstream *p, pa_tagstruct *t, pa_cmsg_ancil_data *ancil_data) {
+ size_t length;
+ const uint8_t *data;
+ pa_packet *packet;
+
+ pa_assert(p);
+ pa_assert(t);
+
+ pa_assert_se(data = pa_tagstruct_data(t, &length));
+ pa_assert_se(packet = pa_packet_new_data(data, length));
+ pa_tagstruct_free(t);
+
+ pa_pstream_send_packet(p, packet, ancil_data);
+ pa_packet_unref(packet);
+}
+
+#ifdef HAVE_CREDS
+
+void pa_pstream_send_tagstruct_with_creds(pa_pstream *p, pa_tagstruct *t, const pa_creds *creds) {
+ if (creds) {
+ pa_cmsg_ancil_data a;
+
+ a.nfd = 0;
+ a.creds_valid = true;
+ a.creds = *creds;
+ pa_pstream_send_tagstruct_with_ancil_data(p, t, &a);
+ }
+ else
+ pa_pstream_send_tagstruct_with_ancil_data(p, t, NULL);
+}
+
+/* @close_fds: If set then the pstreams code, after invoking a sendmsg(),
+ * will close all passed fds.
+ *
+ * Such fds cannot be closed here as this might lead to freeing them
+ * before they're actually passed to the other end. The internally-used
+ * pa_pstream_send_packet() does not do any actual writes and just
+ * defers write events over the pstream. */
+void pa_pstream_send_tagstruct_with_fds(pa_pstream *p, pa_tagstruct *t, int nfd, const int *fds,
+ bool close_fds) {
+ if (nfd > 0) {
+ pa_cmsg_ancil_data a;
+
+ a.nfd = nfd;
+ a.creds_valid = false;
+ a.close_fds_on_cleanup = close_fds;
+ pa_assert(nfd <= MAX_ANCIL_DATA_FDS);
+ memcpy(a.fds, fds, sizeof(int) * nfd);
+ pa_pstream_send_tagstruct_with_ancil_data(p, t, &a);
+ }
+ else
+ pa_pstream_send_tagstruct_with_ancil_data(p, t, NULL);
+}
+
+#else
+
+void pa_pstream_send_tagstruct_with_creds(pa_pstream *p, pa_tagstruct *t, const pa_creds *creds) {
+ pa_pstream_send_tagstruct_with_ancil_data(p, t, NULL);
+}
+
+void PA_GCC_NORETURN pa_pstream_send_tagstruct_with_fds(pa_pstream *p, pa_tagstruct *t, int nfd, const int *fds,
+ bool close_fds) {
+ pa_assert_not_reached();
+}
+
+#endif
+
+void pa_pstream_send_error(pa_pstream *p, uint32_t tag, uint32_t error) {
+ pa_tagstruct *t;
+
+ pa_assert_se(t = pa_tagstruct_new());
+ pa_tagstruct_putu32(t, PA_COMMAND_ERROR);
+ pa_tagstruct_putu32(t, tag);
+ pa_tagstruct_putu32(t, error);
+ pa_pstream_send_tagstruct(p, t);
+}
+
+void pa_pstream_send_simple_ack(pa_pstream *p, uint32_t tag) {
+ pa_tagstruct *t;
+
+ pa_assert_se(t = pa_tagstruct_new());
+ pa_tagstruct_putu32(t, PA_COMMAND_REPLY);
+ pa_tagstruct_putu32(t, tag);
+ pa_pstream_send_tagstruct(p, t);
+}
+
+/* Before sending blocks from a memfd-backed pool over the pipe, we
+ * must call this method first.
+ *
+ * This is needed to transfer memfd blocks without passing their fd
+ * every time, thus minimizing overhead and avoiding fd leaks.
+ *
+ * On registration a packet is sent with the memfd fd as ancil data;
+ * such packet has an ID that uniquely identifies the pool's memfd
+ * region. Upon arrival the other end creates a permanent mapping
+ * between that ID and the passed memfd memory area.
+ *
+ * By doing so, we won't need to reference the pool's memfd fd any
+ * further - just its ID. Both endpoints can then close their fds. */
+int pa_pstream_register_memfd_mempool(pa_pstream *p, pa_mempool *pool, const char **fail_reason) {
+#if defined(HAVE_CREDS) && defined(HAVE_MEMFD)
+ unsigned shm_id;
+ int memfd_fd, ret = -1;
+ pa_tagstruct *t;
+ bool per_client_mempool;
+
+ pa_assert(p);
+ pa_assert(fail_reason);
+
+ *fail_reason = NULL;
+ per_client_mempool = pa_mempool_is_per_client(pool);
+
+ pa_pstream_ref(p);
+
+ if (!pa_mempool_is_shared(pool)) {
+ *fail_reason = "mempool is not shared";
+ goto finish;
+ }
+
+ if (!pa_mempool_is_memfd_backed(pool)) {
+ *fail_reason = "mempool is not memfd-backed";
+ goto finish;
+ }
+
+ if (pa_mempool_get_shm_id(pool, &shm_id)) {
+ *fail_reason = "could not extract pool SHM ID";
+ goto finish;
+ }
+
+ if (!pa_pstream_get_memfd(p)) {
+ *fail_reason = "pipe does not support memfd transport";
+ goto finish;
+ }
+
+ memfd_fd = (per_client_mempool) ? pa_mempool_take_memfd_fd(pool) :
+ pa_mempool_get_memfd_fd(pool);
+
+ /* Note! For per-client mempools we've taken ownership of the memfd
+ * fd, and we're thus the sole code path responsible for closing it.
+ * In case of any failure, it MUST be closed. */
+
+ if (pa_pstream_attach_memfd_shmid(p, shm_id, memfd_fd)) {
+ *fail_reason = "could not attach memfd SHM ID to pipe";
+
+ if (per_client_mempool)
+ pa_assert_se(pa_close(memfd_fd) == 0);
+ goto finish;
+ }
+
+ t = pa_tagstruct_new();
+ pa_tagstruct_putu32(t, PA_COMMAND_REGISTER_MEMFD_SHMID);
+ pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */
+ pa_tagstruct_putu32(t, shm_id);
+ pa_pstream_send_tagstruct_with_fds(p, t, 1, &memfd_fd, per_client_mempool);
+
+ ret = 0;
+finish:
+ pa_pstream_unref(p);
+ return ret;
+
+#else
+ pa_assert(fail_reason);
+ *fail_reason = "memfd support not compiled in";
+ return -1;
+#endif
+}
diff --git a/src/pulsecore/pstream-util.h b/src/pulsecore/pstream-util.h
new file mode 100644
index 0000000..1191d48
--- /dev/null
+++ b/src/pulsecore/pstream-util.h
@@ -0,0 +1,39 @@
+#ifndef foopstreamutilhfoo
+#define foopstreamutilhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <inttypes.h>
+#include <pulsecore/pstream.h>
+#include <pulsecore/tagstruct.h>
+#include <pulsecore/creds.h>
+
+/* The tagstruct is freed!*/
+void pa_pstream_send_tagstruct_with_creds(pa_pstream *p, pa_tagstruct *t, const pa_creds *creds);
+void pa_pstream_send_tagstruct_with_fds(pa_pstream *p, pa_tagstruct *t, int nfd, const int *fds, bool close_fds);
+
+#define pa_pstream_send_tagstruct(p, t) pa_pstream_send_tagstruct_with_creds((p), (t), NULL)
+
+void pa_pstream_send_error(pa_pstream *p, uint32_t tag, uint32_t error);
+void pa_pstream_send_simple_ack(pa_pstream *p, uint32_t tag);
+
+int pa_pstream_register_memfd_mempool(pa_pstream *p, pa_mempool *pool, const char **fail_reason);
+
+#endif
diff --git a/src/pulsecore/pstream.c b/src/pulsecore/pstream.c
new file mode 100644
index 0000000..eb70508
--- /dev/null
+++ b/src/pulsecore/pstream.c
@@ -0,0 +1,1290 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/idxset.h>
+#include <pulsecore/socket.h>
+#include <pulsecore/queue.h>
+#include <pulsecore/log.h>
+#include <pulsecore/creds.h>
+#include <pulsecore/refcnt.h>
+#include <pulsecore/flist.h>
+#include <pulsecore/macro.h>
+
+#include "pstream.h"
+
+/* We piggyback information if audio data blocks are stored in SHM on the seek mode */
+#define PA_FLAG_SHMDATA 0x80000000LU
+#define PA_FLAG_SHMDATA_MEMFD_BLOCK 0x20000000LU
+#define PA_FLAG_SHMRELEASE 0x40000000LU
+#define PA_FLAG_SHMREVOKE 0xC0000000LU
+#define PA_FLAG_SHMMASK 0xFF000000LU
+#define PA_FLAG_SEEKMASK 0x000000FFLU
+#define PA_FLAG_SHMWRITABLE 0x00800000LU
+
+/* The sequence descriptor header consists of 5 32bit integers: */
+enum {
+ PA_PSTREAM_DESCRIPTOR_LENGTH,
+ PA_PSTREAM_DESCRIPTOR_CHANNEL,
+ PA_PSTREAM_DESCRIPTOR_OFFSET_HI,
+ PA_PSTREAM_DESCRIPTOR_OFFSET_LO,
+ PA_PSTREAM_DESCRIPTOR_FLAGS,
+ PA_PSTREAM_DESCRIPTOR_MAX
+};
+
+/* If we have an SHM block, this info follows the descriptor */
+enum {
+ PA_PSTREAM_SHM_BLOCKID,
+ PA_PSTREAM_SHM_SHMID,
+ PA_PSTREAM_SHM_INDEX,
+ PA_PSTREAM_SHM_LENGTH,
+ PA_PSTREAM_SHM_MAX
+};
+
+typedef uint32_t pa_pstream_descriptor[PA_PSTREAM_DESCRIPTOR_MAX];
+
+#define PA_PSTREAM_DESCRIPTOR_SIZE (PA_PSTREAM_DESCRIPTOR_MAX*sizeof(uint32_t))
+
+#define MINIBUF_SIZE (256)
+
+/* To allow uploading a single sample in one frame, this value should be the
+ * same size (16 MB) as PA_SCACHE_ENTRY_SIZE_MAX from pulsecore/core-scache.h.
+ */
+#define FRAME_SIZE_MAX_ALLOW (1024*1024*16)
+
+PA_STATIC_FLIST_DECLARE(items, 0, pa_xfree);
+
+struct item_info {
+ enum {
+ PA_PSTREAM_ITEM_PACKET,
+ PA_PSTREAM_ITEM_MEMBLOCK,
+ PA_PSTREAM_ITEM_SHMRELEASE,
+ PA_PSTREAM_ITEM_SHMREVOKE
+ } type;
+
+ /* packet info */
+ pa_packet *packet;
+#ifdef HAVE_CREDS
+ bool with_ancil_data;
+ pa_cmsg_ancil_data ancil_data;
+#endif
+
+ /* memblock info */
+ pa_memchunk chunk;
+ uint32_t channel;
+ int64_t offset;
+ pa_seek_mode_t seek_mode;
+
+ /* release/revoke info */
+ uint32_t block_id;
+};
+
+struct pstream_read {
+ pa_pstream_descriptor descriptor;
+ pa_memblock *memblock;
+ pa_packet *packet;
+ uint32_t shm_info[PA_PSTREAM_SHM_MAX];
+ void *data;
+ size_t index;
+};
+
+struct pa_pstream {
+ PA_REFCNT_DECLARE;
+
+ pa_mainloop_api *mainloop;
+ pa_defer_event *defer_event;
+ pa_iochannel *io;
+ pa_srbchannel *srb, *srbpending;
+ bool is_srbpending;
+
+ pa_queue *send_queue;
+
+ bool dead;
+
+ struct {
+ union {
+ uint8_t minibuf[MINIBUF_SIZE];
+ pa_pstream_descriptor descriptor;
+ };
+ struct item_info* current;
+ void *data;
+ size_t index;
+ int minibuf_validsize;
+ pa_memchunk memchunk;
+ } write;
+
+ struct pstream_read readio, readsrb;
+
+ /* @use_shm: beside copying the full audio data to the other
+ * PA end, this pipe supports just sending references of the
+ * same audio data blocks if they reside in a SHM pool.
+ *
+ * @use_memfd: pipe supports sending SHM memfd block references
+ *
+ * @registered_memfd_ids: registered memfd pools SHM IDs. Check
+ * pa_pstream_register_memfd_mempool() for more information. */
+ bool use_shm, use_memfd;
+ pa_idxset *registered_memfd_ids;
+
+ pa_memimport *import;
+ pa_memexport *export;
+
+ pa_pstream_packet_cb_t receive_packet_callback;
+ void *receive_packet_callback_userdata;
+
+ pa_pstream_memblock_cb_t receive_memblock_callback;
+ void *receive_memblock_callback_userdata;
+
+ pa_pstream_notify_cb_t drain_callback;
+ void *drain_callback_userdata;
+
+ pa_pstream_notify_cb_t die_callback;
+ void *die_callback_userdata;
+
+ pa_pstream_block_id_cb_t revoke_callback;
+ void *revoke_callback_userdata;
+
+ pa_pstream_block_id_cb_t release_callback;
+ void *release_callback_userdata;
+
+ pa_mempool *mempool;
+
+#ifdef HAVE_CREDS
+ pa_cmsg_ancil_data read_ancil_data, *write_ancil_data;
+ bool send_ancil_data_now;
+#endif
+};
+
+#ifdef HAVE_CREDS
+/*
+ * memfd-backed SHM pools blocks transfer occur without passing the pool's
+ * fd every time, thus minimizing overhead and avoiding fd leaks. A
+ * REGISTER_MEMFD_SHMID command is sent, with the pool's memfd fd, very early
+ * on. This command has an ID that uniquely identifies the pool in question.
+ * Further pool's block references can then be exclusively done using such ID;
+ * the fd can be safely closed – on both ends – afterwards.
+ *
+ * On the sending side of this command, we want to close the passed fds
+ * directly after being sent. Meanwhile we're only allowed to asynchronously
+ * schedule packet writes to the pstream, so the job of closing passed fds is
+ * left to the pstream's actual writing function do_write(): it knows the
+ * exact point in time where the fds are passed to the other end through
+ * iochannels and the sendmsg() system call.
+ *
+ * Nonetheless not all code paths in the system desire their socket-passed
+ * fds to be closed after the send. srbchannel needs the passed fds to still
+ * be open for further communication. System-wide global memfd-backed pools
+ * also require the passed fd to be open: they pass the same fd, with the same
+ * ID registration mechanism, for each newly connected client to the system.
+ *
+ * So from all of the above, never close the ancillary fds by your own and
+ * always call below method instead. It takes care of closing the passed fds
+ * _only if allowed_ by the code paths that originally created them to do so.
+ * Moreover, it is multiple-invocations safe: failure handlers can, and
+ * should, call it for passed fds cleanup without worrying too much about
+ * the system state.
+ */
+void pa_cmsg_ancil_data_close_fds(struct pa_cmsg_ancil_data *ancil) {
+ if (ancil && ancil->nfd > 0 && ancil->close_fds_on_cleanup) {
+ int i;
+
+ pa_assert(ancil->nfd <= MAX_ANCIL_DATA_FDS);
+
+ for (i = 0; i < ancil->nfd; i++)
+ if (ancil->fds[i] != -1) {
+ pa_assert_se(pa_close(ancil->fds[i]) == 0);
+ ancil->fds[i] = -1;
+ }
+
+ ancil->nfd = 0;
+ ancil->close_fds_on_cleanup = false;
+ }
+}
+#endif
+
+static int do_write(pa_pstream *p);
+static int do_read(pa_pstream *p, struct pstream_read *re);
+
+static void do_pstream_read_write(pa_pstream *p) {
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) > 0);
+
+ pa_pstream_ref(p);
+
+ p->mainloop->defer_enable(p->defer_event, 0);
+
+ if (!p->dead && p->srb) {
+ int r = 0;
+
+ if(do_write(p) < 0)
+ goto fail;
+
+ while (!p->dead && r == 0) {
+ r = do_read(p, &p->readsrb);
+ if (r < 0)
+ goto fail;
+ }
+ }
+
+ if (!p->dead && pa_iochannel_is_readable(p->io)) {
+ if (do_read(p, &p->readio) < 0)
+ goto fail;
+ } else if (!p->dead && pa_iochannel_is_hungup(p->io))
+ goto fail;
+
+ while (!p->dead && pa_iochannel_is_writable(p->io)) {
+ int r = do_write(p);
+ if (r < 0)
+ goto fail;
+ if (r == 0)
+ break;
+ }
+
+ pa_pstream_unref(p);
+ return;
+
+fail:
+
+ if (p->die_callback)
+ p->die_callback(p, p->die_callback_userdata);
+
+ pa_pstream_unlink(p);
+ pa_pstream_unref(p);
+}
+
+static bool srb_callback(pa_srbchannel *srb, void *userdata) {
+ bool b;
+ pa_pstream *p = userdata;
+
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) > 0);
+ pa_assert(p->srb == srb);
+
+ pa_pstream_ref(p);
+
+ do_pstream_read_write(p);
+
+ /* If either pstream or the srb is going away, return false.
+ We need to check this before p is destroyed. */
+ b = (PA_REFCNT_VALUE(p) > 1) && (p->srb == srb);
+ pa_pstream_unref(p);
+
+ return b;
+}
+
+static void io_callback(pa_iochannel*io, void *userdata) {
+ pa_pstream *p = userdata;
+
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) > 0);
+ pa_assert(p->io == io);
+
+ do_pstream_read_write(p);
+}
+
+static void defer_callback(pa_mainloop_api *m, pa_defer_event *e, void*userdata) {
+ pa_pstream *p = userdata;
+
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) > 0);
+ pa_assert(p->defer_event == e);
+ pa_assert(p->mainloop == m);
+
+ do_pstream_read_write(p);
+}
+
+static void memimport_release_cb(pa_memimport *i, uint32_t block_id, void *userdata);
+
+pa_pstream *pa_pstream_new(pa_mainloop_api *m, pa_iochannel *io, pa_mempool *pool) {
+ pa_pstream *p;
+
+ pa_assert(m);
+ pa_assert(io);
+ pa_assert(pool);
+
+ p = pa_xnew0(pa_pstream, 1);
+ PA_REFCNT_INIT(p);
+ p->io = io;
+ pa_iochannel_set_callback(io, io_callback, p);
+
+ p->mainloop = m;
+ p->defer_event = m->defer_new(m, defer_callback, p);
+ m->defer_enable(p->defer_event, 0);
+
+ p->send_queue = pa_queue_new();
+
+ p->mempool = pool;
+
+ /* We do importing unconditionally */
+ p->import = pa_memimport_new(p->mempool, memimport_release_cb, p);
+
+ pa_iochannel_socket_set_rcvbuf(io, pa_mempool_block_size_max(p->mempool));
+ pa_iochannel_socket_set_sndbuf(io, pa_mempool_block_size_max(p->mempool));
+
+ return p;
+}
+
+/* Attach memfd<->SHM_ID mapping to given pstream and its memimport.
+ * Check pa_pstream_register_memfd_mempool() for further info.
+ *
+ * Caller owns the passed @memfd_fd and must close it down when appropriate. */
+int pa_pstream_attach_memfd_shmid(pa_pstream *p, unsigned shm_id, int memfd_fd) {
+ int err = -1;
+
+ pa_assert(memfd_fd != -1);
+
+ if (!p->use_memfd) {
+ pa_log_warn("Received memfd ID registration request over a pipe "
+ "that does not support memfds");
+ return err;
+ }
+
+ if (pa_idxset_get_by_data(p->registered_memfd_ids, PA_UINT32_TO_PTR(shm_id), NULL)) {
+ pa_log_warn("previously registered memfd SHM ID = %u", shm_id);
+ return err;
+ }
+
+ if (pa_memimport_attach_memfd(p->import, shm_id, memfd_fd, true)) {
+ pa_log("Failed to create permanent mapping for memfd region with ID = %u", shm_id);
+ return err;
+ }
+
+ pa_assert_se(pa_idxset_put(p->registered_memfd_ids, PA_UINT32_TO_PTR(shm_id), NULL) == 0);
+ return 0;
+}
+
+static void item_free(void *item) {
+ struct item_info *i = item;
+ pa_assert(i);
+
+ if (i->type == PA_PSTREAM_ITEM_MEMBLOCK) {
+ pa_assert(i->chunk.memblock);
+ pa_memblock_unref(i->chunk.memblock);
+ } else if (i->type == PA_PSTREAM_ITEM_PACKET) {
+ pa_assert(i->packet);
+ pa_packet_unref(i->packet);
+ }
+
+#ifdef HAVE_CREDS
+ /* On error recovery paths, there might be lingering items
+ * on the pstream send queue and they are usually freed with
+ * a call to 'pa_queue_free(p->send_queue, item_free)'. Make
+ * sure we do not leak any fds in that case! */
+ if (i->with_ancil_data)
+ pa_cmsg_ancil_data_close_fds(&i->ancil_data);
+#endif
+
+ if (pa_flist_push(PA_STATIC_FLIST_GET(items), i) < 0)
+ pa_xfree(i);
+}
+
+static void pstream_free(pa_pstream *p) {
+ pa_assert(p);
+
+ pa_pstream_unlink(p);
+
+ pa_queue_free(p->send_queue, item_free);
+
+ if (p->write.current)
+ item_free(p->write.current);
+
+ if (p->write.memchunk.memblock)
+ pa_memblock_unref(p->write.memchunk.memblock);
+
+ if (p->readsrb.memblock)
+ pa_memblock_unref(p->readsrb.memblock);
+
+ if (p->readsrb.packet)
+ pa_packet_unref(p->readsrb.packet);
+
+ if (p->readio.memblock)
+ pa_memblock_unref(p->readio.memblock);
+
+ if (p->readio.packet)
+ pa_packet_unref(p->readio.packet);
+
+ if (p->registered_memfd_ids)
+ pa_idxset_free(p->registered_memfd_ids, NULL);
+
+ pa_xfree(p);
+}
+
+void pa_pstream_send_packet(pa_pstream*p, pa_packet *packet, pa_cmsg_ancil_data *ancil_data) {
+ struct item_info *i;
+
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) > 0);
+ pa_assert(packet);
+
+ if (p->dead) {
+#ifdef HAVE_CREDS
+ pa_cmsg_ancil_data_close_fds(ancil_data);
+#endif
+ return;
+ }
+
+ if (!(i = pa_flist_pop(PA_STATIC_FLIST_GET(items))))
+ i = pa_xnew(struct item_info, 1);
+
+ i->type = PA_PSTREAM_ITEM_PACKET;
+ i->packet = pa_packet_ref(packet);
+
+#ifdef HAVE_CREDS
+ if ((i->with_ancil_data = !!ancil_data)) {
+ i->ancil_data = *ancil_data;
+ if (ancil_data->creds_valid)
+ pa_assert(ancil_data->nfd == 0);
+ else
+ pa_assert(ancil_data->nfd > 0);
+ }
+#endif
+
+ pa_queue_push(p->send_queue, i);
+
+ p->mainloop->defer_enable(p->defer_event, 1);
+}
+
+void pa_pstream_send_memblock(pa_pstream*p, uint32_t channel, int64_t offset, pa_seek_mode_t seek_mode, const pa_memchunk *chunk) {
+ size_t length, idx;
+ size_t bsm;
+
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) > 0);
+ pa_assert(channel != (uint32_t) -1);
+ pa_assert(chunk);
+
+ if (p->dead)
+ return;
+
+ idx = 0;
+ length = chunk->length;
+
+ bsm = pa_mempool_block_size_max(p->mempool);
+
+ while (length > 0) {
+ struct item_info *i;
+ size_t n;
+
+ if (!(i = pa_flist_pop(PA_STATIC_FLIST_GET(items))))
+ i = pa_xnew(struct item_info, 1);
+ i->type = PA_PSTREAM_ITEM_MEMBLOCK;
+
+ n = PA_MIN(length, bsm);
+ i->chunk.index = chunk->index + idx;
+ i->chunk.length = n;
+ i->chunk.memblock = pa_memblock_ref(chunk->memblock);
+
+ i->channel = channel;
+ i->offset = offset;
+ i->seek_mode = seek_mode;
+#ifdef HAVE_CREDS
+ i->with_ancil_data = false;
+#endif
+
+ pa_queue_push(p->send_queue, i);
+
+ idx += n;
+ length -= n;
+ }
+
+ p->mainloop->defer_enable(p->defer_event, 1);
+}
+
+void pa_pstream_send_release(pa_pstream *p, uint32_t block_id) {
+ struct item_info *item;
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) > 0);
+
+ if (p->dead)
+ return;
+
+/* pa_log("Releasing block %u", block_id); */
+
+ if (!(item = pa_flist_pop(PA_STATIC_FLIST_GET(items))))
+ item = pa_xnew(struct item_info, 1);
+ item->type = PA_PSTREAM_ITEM_SHMRELEASE;
+ item->block_id = block_id;
+#ifdef HAVE_CREDS
+ item->with_ancil_data = false;
+#endif
+
+ pa_queue_push(p->send_queue, item);
+ p->mainloop->defer_enable(p->defer_event, 1);
+}
+
+/* might be called from thread context */
+static void memimport_release_cb(pa_memimport *i, uint32_t block_id, void *userdata) {
+ pa_pstream *p = userdata;
+
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) > 0);
+
+ if (p->dead)
+ return;
+
+ if (p->release_callback)
+ p->release_callback(p, block_id, p->release_callback_userdata);
+ else
+ pa_pstream_send_release(p, block_id);
+}
+
+void pa_pstream_send_revoke(pa_pstream *p, uint32_t block_id) {
+ struct item_info *item;
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) > 0);
+
+ if (p->dead)
+ return;
+/* pa_log("Revoking block %u", block_id); */
+
+ if (!(item = pa_flist_pop(PA_STATIC_FLIST_GET(items))))
+ item = pa_xnew(struct item_info, 1);
+ item->type = PA_PSTREAM_ITEM_SHMREVOKE;
+ item->block_id = block_id;
+#ifdef HAVE_CREDS
+ item->with_ancil_data = false;
+#endif
+
+ pa_queue_push(p->send_queue, item);
+ p->mainloop->defer_enable(p->defer_event, 1);
+}
+
+/* might be called from thread context */
+static void memexport_revoke_cb(pa_memexport *e, uint32_t block_id, void *userdata) {
+ pa_pstream *p = userdata;
+
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) > 0);
+
+ if (p->revoke_callback)
+ p->revoke_callback(p, block_id, p->revoke_callback_userdata);
+ else
+ pa_pstream_send_revoke(p, block_id);
+}
+
+static void prepare_next_write_item(pa_pstream *p) {
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) > 0);
+
+ p->write.current = pa_queue_pop(p->send_queue);
+
+ if (!p->write.current)
+ return;
+ p->write.index = 0;
+ p->write.data = NULL;
+ p->write.minibuf_validsize = 0;
+ pa_memchunk_reset(&p->write.memchunk);
+
+ p->write.descriptor[PA_PSTREAM_DESCRIPTOR_LENGTH] = 0;
+ p->write.descriptor[PA_PSTREAM_DESCRIPTOR_CHANNEL] = htonl((uint32_t) -1);
+ p->write.descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_HI] = 0;
+ p->write.descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_LO] = 0;
+ p->write.descriptor[PA_PSTREAM_DESCRIPTOR_FLAGS] = 0;
+
+ if (p->write.current->type == PA_PSTREAM_ITEM_PACKET) {
+ size_t plen;
+
+ pa_assert(p->write.current->packet);
+
+ p->write.data = (void *) pa_packet_data(p->write.current->packet, &plen);
+ p->write.descriptor[PA_PSTREAM_DESCRIPTOR_LENGTH] = htonl((uint32_t) plen);
+
+ if (plen <= MINIBUF_SIZE - PA_PSTREAM_DESCRIPTOR_SIZE) {
+ memcpy(&p->write.minibuf[PA_PSTREAM_DESCRIPTOR_SIZE], p->write.data, plen);
+ p->write.minibuf_validsize = PA_PSTREAM_DESCRIPTOR_SIZE + plen;
+ }
+
+ } else if (p->write.current->type == PA_PSTREAM_ITEM_SHMRELEASE) {
+
+ p->write.descriptor[PA_PSTREAM_DESCRIPTOR_FLAGS] = htonl(PA_FLAG_SHMRELEASE);
+ p->write.descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_HI] = htonl(p->write.current->block_id);
+
+ } else if (p->write.current->type == PA_PSTREAM_ITEM_SHMREVOKE) {
+
+ p->write.descriptor[PA_PSTREAM_DESCRIPTOR_FLAGS] = htonl(PA_FLAG_SHMREVOKE);
+ p->write.descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_HI] = htonl(p->write.current->block_id);
+
+ } else {
+ uint32_t flags;
+ bool send_payload = true;
+
+ pa_assert(p->write.current->type == PA_PSTREAM_ITEM_MEMBLOCK);
+ pa_assert(p->write.current->chunk.memblock);
+
+ p->write.descriptor[PA_PSTREAM_DESCRIPTOR_CHANNEL] = htonl(p->write.current->channel);
+ p->write.descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_HI] = htonl((uint32_t) (((uint64_t) p->write.current->offset) >> 32));
+ p->write.descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_LO] = htonl((uint32_t) ((uint64_t) p->write.current->offset));
+
+ flags = (uint32_t) (p->write.current->seek_mode & PA_FLAG_SEEKMASK);
+
+ if (p->use_shm) {
+ pa_mem_type_t type;
+ uint32_t block_id, shm_id;
+ size_t offset, length;
+ uint32_t *shm_info = (uint32_t *) &p->write.minibuf[PA_PSTREAM_DESCRIPTOR_SIZE];
+ size_t shm_size = sizeof(uint32_t) * PA_PSTREAM_SHM_MAX;
+ pa_mempool *current_pool = pa_memblock_get_pool(p->write.current->chunk.memblock);
+ pa_memexport *current_export;
+
+ if (p->mempool == current_pool)
+ pa_assert_se(current_export = p->export);
+ else
+ pa_assert_se(current_export = pa_memexport_new(current_pool, memexport_revoke_cb, p));
+
+ if (pa_memexport_put(current_export,
+ p->write.current->chunk.memblock,
+ &type,
+ &block_id,
+ &shm_id,
+ &offset,
+ &length) >= 0) {
+
+ if (type == PA_MEM_TYPE_SHARED_POSIX)
+ send_payload = false;
+
+ if (type == PA_MEM_TYPE_SHARED_MEMFD && p->use_memfd) {
+ if (pa_idxset_get_by_data(p->registered_memfd_ids, PA_UINT32_TO_PTR(shm_id), NULL)) {
+ flags |= PA_FLAG_SHMDATA_MEMFD_BLOCK;
+ send_payload = false;
+ } else {
+ if (pa_log_ratelimit(PA_LOG_ERROR)) {
+ pa_log("Cannot send block reference with non-registered memfd ID = %u", shm_id);
+ pa_log("Fallig back to copying full block data over socket");
+ }
+ }
+ }
+
+ if (send_payload) {
+ pa_assert_se(pa_memexport_process_release(current_export, block_id) == 0);
+ } else {
+ flags |= PA_FLAG_SHMDATA;
+ if (pa_mempool_is_remote_writable(current_pool))
+ flags |= PA_FLAG_SHMWRITABLE;
+
+ shm_info[PA_PSTREAM_SHM_BLOCKID] = htonl(block_id);
+ shm_info[PA_PSTREAM_SHM_SHMID] = htonl(shm_id);
+ shm_info[PA_PSTREAM_SHM_INDEX] = htonl((uint32_t) (offset + p->write.current->chunk.index));
+ shm_info[PA_PSTREAM_SHM_LENGTH] = htonl((uint32_t) p->write.current->chunk.length);
+
+ p->write.descriptor[PA_PSTREAM_DESCRIPTOR_LENGTH] = htonl(shm_size);
+ p->write.minibuf_validsize = PA_PSTREAM_DESCRIPTOR_SIZE + shm_size;
+ }
+ }
+/* else */
+/* FIXME: Avoid memexport slot leaks. Call pa_memexport_process_release() */
+/* pa_log_warn("Failed to export memory block."); */
+
+ if (current_export != p->export)
+ pa_memexport_free(current_export);
+ pa_mempool_unref(current_pool);
+ }
+
+ if (send_payload) {
+ p->write.descriptor[PA_PSTREAM_DESCRIPTOR_LENGTH] = htonl((uint32_t) p->write.current->chunk.length);
+ p->write.memchunk = p->write.current->chunk;
+ pa_memblock_ref(p->write.memchunk.memblock);
+ }
+
+ p->write.descriptor[PA_PSTREAM_DESCRIPTOR_FLAGS] = htonl(flags);
+ }
+
+#ifdef HAVE_CREDS
+ if ((p->send_ancil_data_now = p->write.current->with_ancil_data))
+ p->write_ancil_data = &p->write.current->ancil_data;
+#endif
+}
+
+static void check_srbpending(pa_pstream *p) {
+ if (!p->is_srbpending)
+ return;
+
+ if (p->srb)
+ pa_srbchannel_free(p->srb);
+
+ p->srb = p->srbpending;
+ p->is_srbpending = false;
+
+ if (p->srb)
+ pa_srbchannel_set_callback(p->srb, srb_callback, p);
+}
+
+static int do_write(pa_pstream *p) {
+ void *d;
+ size_t l;
+ ssize_t r;
+ pa_memblock *release_memblock = NULL;
+
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) > 0);
+
+ if (!p->write.current)
+ prepare_next_write_item(p);
+
+ if (!p->write.current) {
+ /* The out queue is empty, so switching channels is safe */
+ check_srbpending(p);
+ return 0;
+ }
+
+ if (p->write.minibuf_validsize > 0) {
+ d = p->write.minibuf + p->write.index;
+ l = p->write.minibuf_validsize - p->write.index;
+ } else if (p->write.index < PA_PSTREAM_DESCRIPTOR_SIZE) {
+ d = (uint8_t*) p->write.descriptor + p->write.index;
+ l = PA_PSTREAM_DESCRIPTOR_SIZE - p->write.index;
+ } else {
+ pa_assert(p->write.data || p->write.memchunk.memblock);
+
+ if (p->write.data)
+ d = p->write.data;
+ else {
+ d = pa_memblock_acquire_chunk(&p->write.memchunk);
+ release_memblock = p->write.memchunk.memblock;
+ }
+
+ d = (uint8_t*) d + p->write.index - PA_PSTREAM_DESCRIPTOR_SIZE;
+ l = ntohl(p->write.descriptor[PA_PSTREAM_DESCRIPTOR_LENGTH]) - (p->write.index - PA_PSTREAM_DESCRIPTOR_SIZE);
+ }
+
+ pa_assert(l > 0);
+
+#ifdef HAVE_CREDS
+ if (p->send_ancil_data_now) {
+ if (p->write_ancil_data->creds_valid) {
+ pa_assert(p->write_ancil_data->nfd == 0);
+ if ((r = pa_iochannel_write_with_creds(p->io, d, l, &p->write_ancil_data->creds)) < 0)
+ goto fail;
+ }
+ else
+ if ((r = pa_iochannel_write_with_fds(p->io, d, l, p->write_ancil_data->nfd, p->write_ancil_data->fds)) < 0)
+ goto fail;
+
+ pa_cmsg_ancil_data_close_fds(p->write_ancil_data);
+ p->send_ancil_data_now = false;
+ } else
+#endif
+ if (p->srb)
+ r = pa_srbchannel_write(p->srb, d, l);
+ else if ((r = pa_iochannel_write(p->io, d, l)) < 0)
+ goto fail;
+
+ if (release_memblock)
+ pa_memblock_release(release_memblock);
+
+ p->write.index += (size_t) r;
+
+ if (p->write.index >= PA_PSTREAM_DESCRIPTOR_SIZE + ntohl(p->write.descriptor[PA_PSTREAM_DESCRIPTOR_LENGTH])) {
+ pa_assert(p->write.current);
+ item_free(p->write.current);
+ p->write.current = NULL;
+
+ if (p->write.memchunk.memblock)
+ pa_memblock_unref(p->write.memchunk.memblock);
+
+ pa_memchunk_reset(&p->write.memchunk);
+
+ if (p->drain_callback && !pa_pstream_is_pending(p))
+ p->drain_callback(p, p->drain_callback_userdata);
+ }
+
+ return (size_t) r == l ? 1 : 0;
+
+fail:
+#ifdef HAVE_CREDS
+ if (p->send_ancil_data_now)
+ pa_cmsg_ancil_data_close_fds(p->write_ancil_data);
+#endif
+
+ if (release_memblock)
+ pa_memblock_release(release_memblock);
+
+ return -1;
+}
+
+static void memblock_complete(pa_pstream *p, struct pstream_read *re) {
+ pa_memchunk chunk;
+ int64_t offset;
+
+ if (!p->receive_memblock_callback)
+ return;
+
+ chunk.memblock = re->memblock;
+ chunk.index = 0;
+ chunk.length = re->index - PA_PSTREAM_DESCRIPTOR_SIZE;
+
+ offset = (int64_t) (
+ (((uint64_t) ntohl(re->descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_HI])) << 32) |
+ (((uint64_t) ntohl(re->descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_LO]))));
+
+ p->receive_memblock_callback(
+ p,
+ ntohl(re->descriptor[PA_PSTREAM_DESCRIPTOR_CHANNEL]),
+ offset,
+ ntohl(re->descriptor[PA_PSTREAM_DESCRIPTOR_FLAGS]) & PA_FLAG_SEEKMASK,
+ &chunk,
+ p->receive_memblock_callback_userdata);
+}
+
+static int do_read(pa_pstream *p, struct pstream_read *re) {
+ void *d;
+ size_t l;
+ ssize_t r;
+ pa_memblock *release_memblock = NULL;
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) > 0);
+
+ if (re->index < PA_PSTREAM_DESCRIPTOR_SIZE) {
+ d = (uint8_t*) re->descriptor + re->index;
+ l = PA_PSTREAM_DESCRIPTOR_SIZE - re->index;
+ } else {
+ pa_assert(re->data || re->memblock);
+
+ if (re->data)
+ d = re->data;
+ else {
+ d = pa_memblock_acquire(re->memblock);
+ release_memblock = re->memblock;
+ }
+
+ d = (uint8_t*) d + re->index - PA_PSTREAM_DESCRIPTOR_SIZE;
+ l = ntohl(re->descriptor[PA_PSTREAM_DESCRIPTOR_LENGTH]) - (re->index - PA_PSTREAM_DESCRIPTOR_SIZE);
+ }
+
+ if (re == &p->readsrb) {
+ r = pa_srbchannel_read(p->srb, d, l);
+ if (r == 0) {
+ if (release_memblock)
+ pa_memblock_release(release_memblock);
+ return 1;
+ }
+ }
+ else
+#ifdef HAVE_CREDS
+ {
+ pa_cmsg_ancil_data b;
+
+ if ((r = pa_iochannel_read_with_ancil_data(p->io, d, l, &b)) <= 0)
+ goto fail;
+
+ if (b.creds_valid) {
+ p->read_ancil_data.creds_valid = true;
+ p->read_ancil_data.creds = b.creds;
+ }
+ if (b.nfd > 0) {
+ pa_assert(b.nfd <= MAX_ANCIL_DATA_FDS);
+ p->read_ancil_data.nfd = b.nfd;
+ memcpy(p->read_ancil_data.fds, b.fds, sizeof(int) * b.nfd);
+ p->read_ancil_data.close_fds_on_cleanup = b.close_fds_on_cleanup;
+ }
+ }
+#else
+ if ((r = pa_iochannel_read(p->io, d, l)) <= 0)
+ goto fail;
+#endif
+
+ if (release_memblock)
+ pa_memblock_release(release_memblock);
+
+ re->index += (size_t) r;
+
+ if (re->index == PA_PSTREAM_DESCRIPTOR_SIZE) {
+ uint32_t flags, length, channel;
+ /* Reading of frame descriptor complete */
+
+ flags = ntohl(re->descriptor[PA_PSTREAM_DESCRIPTOR_FLAGS]);
+
+ if (!p->use_shm && (flags & PA_FLAG_SHMMASK) != 0) {
+ pa_log_warn("Received SHM frame on a socket where SHM is disabled.");
+ return -1;
+ }
+
+ if (flags == PA_FLAG_SHMRELEASE) {
+
+ /* This is a SHM memblock release frame with no payload */
+
+/* pa_log("Got release frame for %u", ntohl(re->descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_HI])); */
+
+ pa_assert(p->export);
+ pa_memexport_process_release(p->export, ntohl(re->descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_HI]));
+
+ goto frame_done;
+
+ } else if (flags == PA_FLAG_SHMREVOKE) {
+
+ /* This is a SHM memblock revoke frame with no payload */
+
+/* pa_log("Got revoke frame for %u", ntohl(re->descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_HI])); */
+
+ pa_assert(p->import);
+ pa_memimport_process_revoke(p->import, ntohl(re->descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_HI]));
+
+ goto frame_done;
+ }
+
+ length = ntohl(re->descriptor[PA_PSTREAM_DESCRIPTOR_LENGTH]);
+
+ if (length > FRAME_SIZE_MAX_ALLOW || length <= 0) {
+ pa_log_warn("Received invalid frame size: %lu", (unsigned long) length);
+ return -1;
+ }
+
+ pa_assert(!re->packet && !re->memblock);
+
+ channel = ntohl(re->descriptor[PA_PSTREAM_DESCRIPTOR_CHANNEL]);
+
+ if (channel == (uint32_t) -1) {
+ size_t plen;
+
+ if (flags != 0) {
+ pa_log_warn("Received packet frame with invalid flags value.");
+ return -1;
+ }
+
+ /* Frame is a packet frame */
+ re->packet = pa_packet_new(length);
+ re->data = (void *) pa_packet_data(re->packet, &plen);
+
+ } else {
+
+ if ((flags & PA_FLAG_SEEKMASK) > PA_SEEK_RELATIVE_END) {
+ pa_log_warn("Received memblock frame with invalid seek mode.");
+ return -1;
+ }
+
+ if (((flags & PA_FLAG_SHMMASK) & PA_FLAG_SHMDATA) != 0) {
+
+ if (length != sizeof(re->shm_info)) {
+ pa_log_warn("Received SHM memblock frame with invalid frame length.");
+ return -1;
+ }
+
+ /* Frame is a memblock frame referencing an SHM memblock */
+ re->data = re->shm_info;
+
+ } else if ((flags & PA_FLAG_SHMMASK) == 0) {
+
+ /* Frame is a memblock frame */
+
+ re->memblock = pa_memblock_new(p->mempool, length);
+ re->data = NULL;
+ } else {
+
+ pa_log_warn("Received memblock frame with invalid flags value.");
+ return -1;
+ }
+ }
+
+ } else if (re->index >= ntohl(re->descriptor[PA_PSTREAM_DESCRIPTOR_LENGTH]) + PA_PSTREAM_DESCRIPTOR_SIZE) {
+ /* Frame complete */
+
+ if (re->memblock) {
+ memblock_complete(p, re);
+
+ /* This was a memblock frame. We can unref the memblock now */
+ pa_memblock_unref(re->memblock);
+
+ } else if (re->packet) {
+
+ if (p->receive_packet_callback)
+#ifdef HAVE_CREDS
+ p->receive_packet_callback(p, re->packet, &p->read_ancil_data, p->receive_packet_callback_userdata);
+#else
+ p->receive_packet_callback(p, re->packet, NULL, p->receive_packet_callback_userdata);
+#endif
+
+ pa_packet_unref(re->packet);
+ } else {
+ pa_memblock *b = NULL;
+ uint32_t flags = ntohl(re->descriptor[PA_PSTREAM_DESCRIPTOR_FLAGS]);
+ uint32_t shm_id = ntohl(re->shm_info[PA_PSTREAM_SHM_SHMID]);
+ pa_mem_type_t type = (flags & PA_FLAG_SHMDATA_MEMFD_BLOCK) ?
+ PA_MEM_TYPE_SHARED_MEMFD : PA_MEM_TYPE_SHARED_POSIX;
+
+ pa_assert(((flags & PA_FLAG_SHMMASK) & PA_FLAG_SHMDATA) != 0);
+ pa_assert(p->import);
+
+ if (type == PA_MEM_TYPE_SHARED_MEMFD && p->use_memfd &&
+ !pa_idxset_get_by_data(p->registered_memfd_ids, PA_UINT32_TO_PTR(shm_id), NULL)) {
+
+ if (pa_log_ratelimit(PA_LOG_ERROR))
+ pa_log("Ignoring received block reference with non-registered memfd ID = %u", shm_id);
+
+ } else if (!(b = pa_memimport_get(p->import,
+ type,
+ ntohl(re->shm_info[PA_PSTREAM_SHM_BLOCKID]),
+ shm_id,
+ ntohl(re->shm_info[PA_PSTREAM_SHM_INDEX]),
+ ntohl(re->shm_info[PA_PSTREAM_SHM_LENGTH]),
+ !!(flags & PA_FLAG_SHMWRITABLE)))) {
+
+ if (pa_log_ratelimit(PA_LOG_DEBUG))
+ pa_log_debug("Failed to import memory block.");
+ }
+
+ if (p->receive_memblock_callback) {
+ int64_t offset;
+ pa_memchunk chunk;
+
+ chunk.memblock = b;
+ chunk.index = 0;
+ chunk.length = b ? pa_memblock_get_length(b) : ntohl(re->shm_info[PA_PSTREAM_SHM_LENGTH]);
+
+ offset = (int64_t) (
+ (((uint64_t) ntohl(re->descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_HI])) << 32) |
+ (((uint64_t) ntohl(re->descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_LO]))));
+
+ p->receive_memblock_callback(
+ p,
+ ntohl(re->descriptor[PA_PSTREAM_DESCRIPTOR_CHANNEL]),
+ offset,
+ ntohl(re->descriptor[PA_PSTREAM_DESCRIPTOR_FLAGS]) & PA_FLAG_SEEKMASK,
+ &chunk,
+ p->receive_memblock_callback_userdata);
+ }
+
+ if (b)
+ pa_memblock_unref(b);
+ }
+
+ goto frame_done;
+ }
+
+ return 0;
+
+frame_done:
+ re->memblock = NULL;
+ re->packet = NULL;
+ re->index = 0;
+ re->data = NULL;
+
+#ifdef HAVE_CREDS
+ /* FIXME: Close received ancillary data fds if the pstream's
+ * receive_packet_callback did not do so.
+ *
+ * Malicious clients can attach fds to unknown commands, or attach them
+ * to commands that does not expect fds. By doing so, server will reach
+ * its open fd limit and future clients' SHM transfers will always fail.
+ */
+ p->read_ancil_data.creds_valid = false;
+ p->read_ancil_data.nfd = 0;
+#endif
+
+ return 0;
+
+fail:
+ if (release_memblock)
+ pa_memblock_release(release_memblock);
+
+ return -1;
+}
+
+void pa_pstream_set_die_callback(pa_pstream *p, pa_pstream_notify_cb_t cb, void *userdata) {
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) > 0);
+
+ p->die_callback = cb;
+ p->die_callback_userdata = userdata;
+}
+
+void pa_pstream_set_drain_callback(pa_pstream *p, pa_pstream_notify_cb_t cb, void *userdata) {
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) > 0);
+
+ p->drain_callback = cb;
+ p->drain_callback_userdata = userdata;
+}
+
+void pa_pstream_set_receive_packet_callback(pa_pstream *p, pa_pstream_packet_cb_t cb, void *userdata) {
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) > 0);
+
+ p->receive_packet_callback = cb;
+ p->receive_packet_callback_userdata = userdata;
+}
+
+void pa_pstream_set_receive_memblock_callback(pa_pstream *p, pa_pstream_memblock_cb_t cb, void *userdata) {
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) > 0);
+
+ p->receive_memblock_callback = cb;
+ p->receive_memblock_callback_userdata = userdata;
+}
+
+void pa_pstream_set_release_callback(pa_pstream *p, pa_pstream_block_id_cb_t cb, void *userdata) {
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) > 0);
+
+ p->release_callback = cb;
+ p->release_callback_userdata = userdata;
+}
+
+void pa_pstream_set_revoke_callback(pa_pstream *p, pa_pstream_block_id_cb_t cb, void *userdata) {
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) > 0);
+
+ p->revoke_callback = cb;
+ p->revoke_callback_userdata = userdata;
+}
+
+bool pa_pstream_is_pending(pa_pstream *p) {
+ bool b;
+
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) > 0);
+
+ if (p->dead)
+ b = false;
+ else
+ b = p->write.current || !pa_queue_isempty(p->send_queue);
+
+ return b;
+}
+
+void pa_pstream_unref(pa_pstream*p) {
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) > 0);
+
+ if (PA_REFCNT_DEC(p) <= 0)
+ pstream_free(p);
+}
+
+pa_pstream* pa_pstream_ref(pa_pstream*p) {
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) > 0);
+
+ PA_REFCNT_INC(p);
+ return p;
+}
+
+void pa_pstream_unlink(pa_pstream *p) {
+ pa_assert(p);
+
+ if (p->dead)
+ return;
+
+ p->dead = true;
+
+ while (p->srb || p->is_srbpending) /* In theory there could be one active and one pending */
+ pa_pstream_set_srbchannel(p, NULL);
+
+ if (p->import) {
+ pa_memimport_free(p->import);
+ p->import = NULL;
+ }
+
+ if (p->export) {
+ pa_memexport_free(p->export);
+ p->export = NULL;
+ }
+
+ if (p->io) {
+ pa_iochannel_free(p->io);
+ p->io = NULL;
+ }
+
+ if (p->defer_event) {
+ p->mainloop->defer_free(p->defer_event);
+ p->defer_event = NULL;
+ }
+
+ p->die_callback = NULL;
+ p->drain_callback = NULL;
+ p->receive_packet_callback = NULL;
+ p->receive_memblock_callback = NULL;
+}
+
+void pa_pstream_enable_shm(pa_pstream *p, bool enable) {
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) > 0);
+
+ p->use_shm = enable;
+
+ if (enable) {
+
+ if (!p->export)
+ p->export = pa_memexport_new(p->mempool, memexport_revoke_cb, p);
+
+ } else {
+
+ if (p->export) {
+ pa_memexport_free(p->export);
+ p->export = NULL;
+ }
+ }
+}
+
+void pa_pstream_enable_memfd(pa_pstream *p) {
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) > 0);
+ pa_assert(p->use_shm);
+
+ p->use_memfd = true;
+
+ if (!p->registered_memfd_ids) {
+ p->registered_memfd_ids = pa_idxset_new(NULL, NULL);
+ }
+}
+
+bool pa_pstream_get_shm(pa_pstream *p) {
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) > 0);
+
+ return p->use_shm;
+}
+
+bool pa_pstream_get_memfd(pa_pstream *p) {
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) > 0);
+
+ return p->use_memfd;
+}
+
+void pa_pstream_set_srbchannel(pa_pstream *p, pa_srbchannel *srb) {
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) > 0 || srb == NULL);
+
+ if (srb == p->srb)
+ return;
+
+ /* We can't handle quick switches between srbchannels. */
+ pa_assert(!p->is_srbpending);
+
+ p->srbpending = srb;
+ p->is_srbpending = true;
+
+ /* Switch immediately, if possible. */
+ if (p->dead)
+ check_srbpending(p);
+ else
+ do_write(p);
+}
diff --git a/src/pulsecore/pstream.h b/src/pulsecore/pstream.h
new file mode 100644
index 0000000..2bff270
--- /dev/null
+++ b/src/pulsecore/pstream.h
@@ -0,0 +1,76 @@
+#ifndef foopstreamhfoo
+#define foopstreamhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <inttypes.h>
+
+#include <pulse/mainloop-api.h>
+#include <pulse/def.h>
+
+#include <pulsecore/packet.h>
+#include <pulsecore/memblock.h>
+#include <pulsecore/iochannel.h>
+#include <pulsecore/srbchannel.h>
+#include <pulsecore/memchunk.h>
+#include <pulsecore/creds.h>
+#include <pulsecore/macro.h>
+
+typedef struct pa_pstream pa_pstream;
+
+typedef void (*pa_pstream_packet_cb_t)(pa_pstream *p, pa_packet *packet, pa_cmsg_ancil_data *ancil_data, void *userdata);
+typedef void (*pa_pstream_memblock_cb_t)(pa_pstream *p, uint32_t channel, int64_t offset, pa_seek_mode_t seek, const pa_memchunk *chunk, void *userdata);
+typedef void (*pa_pstream_notify_cb_t)(pa_pstream *p, void *userdata);
+typedef void (*pa_pstream_block_id_cb_t)(pa_pstream *p, uint32_t block_id, void *userdata);
+
+pa_pstream* pa_pstream_new(pa_mainloop_api *m, pa_iochannel *io, pa_mempool *p);
+
+pa_pstream* pa_pstream_ref(pa_pstream*p);
+void pa_pstream_unref(pa_pstream*p);
+
+void pa_pstream_unlink(pa_pstream *p);
+
+int pa_pstream_attach_memfd_shmid(pa_pstream *p, unsigned shm_id, int memfd_fd);
+
+void pa_pstream_send_packet(pa_pstream*p, pa_packet *packet, pa_cmsg_ancil_data *ancil_data);
+void pa_pstream_send_memblock(pa_pstream*p, uint32_t channel, int64_t offset, pa_seek_mode_t seek, const pa_memchunk *chunk);
+void pa_pstream_send_release(pa_pstream *p, uint32_t block_id);
+void pa_pstream_send_revoke(pa_pstream *p, uint32_t block_id);
+
+void pa_pstream_set_receive_packet_callback(pa_pstream *p, pa_pstream_packet_cb_t cb, void *userdata);
+void pa_pstream_set_receive_memblock_callback(pa_pstream *p, pa_pstream_memblock_cb_t cb, void *userdata);
+void pa_pstream_set_drain_callback(pa_pstream *p, pa_pstream_notify_cb_t cb, void *userdata);
+void pa_pstream_set_die_callback(pa_pstream *p, pa_pstream_notify_cb_t cb, void *userdata);
+void pa_pstream_set_release_callback(pa_pstream *p, pa_pstream_block_id_cb_t cb, void *userdata);
+void pa_pstream_set_revoke_callback(pa_pstream *p, pa_pstream_block_id_cb_t cb, void *userdata);
+
+bool pa_pstream_is_pending(pa_pstream *p);
+
+void pa_pstream_enable_shm(pa_pstream *p, bool enable);
+void pa_pstream_enable_memfd(pa_pstream *p);
+bool pa_pstream_get_shm(pa_pstream *p);
+bool pa_pstream_get_memfd(pa_pstream *p);
+
+/* Enables shared ringbuffer channel. Note that the srbchannel is now owned by the pstream.
+ Setting srb to NULL will free any existing srbchannel. */
+void pa_pstream_set_srbchannel(pa_pstream *p, pa_srbchannel *srb);
+
+#endif
diff --git a/src/pulsecore/queue.c b/src/pulsecore/queue.c
new file mode 100644
index 0000000..2b952b6
--- /dev/null
+++ b/src/pulsecore/queue.c
@@ -0,0 +1,122 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2008 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/flist.h>
+
+#include "queue.h"
+
+PA_STATIC_FLIST_DECLARE(entries, 0, pa_xfree);
+
+struct queue_entry {
+ struct queue_entry *next;
+ void *data;
+};
+
+struct pa_queue {
+ struct queue_entry *front, *back;
+ unsigned length;
+};
+
+pa_queue* pa_queue_new(void) {
+ pa_queue *q = pa_xnew(pa_queue, 1);
+
+ q->front = q->back = NULL;
+ q->length = 0;
+
+ return q;
+}
+
+void pa_queue_free(pa_queue *q, pa_free_cb_t free_func) {
+ void *data;
+ pa_assert(q);
+
+ while ((data = pa_queue_pop(q)))
+ if (free_func)
+ free_func(data);
+
+ pa_assert(!q->front);
+ pa_assert(!q->back);
+ pa_assert(q->length == 0);
+
+ pa_xfree(q);
+}
+
+void pa_queue_push(pa_queue *q, void *p) {
+ struct queue_entry *e;
+
+ pa_assert(q);
+ pa_assert(p);
+
+ if (!(e = pa_flist_pop(PA_STATIC_FLIST_GET(entries))))
+ e = pa_xnew(struct queue_entry, 1);
+
+ e->data = p;
+ e->next = NULL;
+
+ if (q->back) {
+ pa_assert(q->front);
+ q->back->next = e;
+ } else {
+ pa_assert(!q->front);
+ q->front = e;
+ }
+
+ q->back = e;
+ q->length++;
+}
+
+void* pa_queue_pop(pa_queue *q) {
+ void *p;
+ struct queue_entry *e;
+
+ pa_assert(q);
+
+ if (!(e = q->front))
+ return NULL;
+
+ q->front = e->next;
+
+ if (q->back == e) {
+ pa_assert(!e->next);
+ q->back = NULL;
+ }
+
+ p = e->data;
+
+ if (pa_flist_push(PA_STATIC_FLIST_GET(entries), e) < 0)
+ pa_xfree(e);
+
+ q->length--;
+
+ return p;
+}
+
+int pa_queue_isempty(pa_queue *q) {
+ pa_assert(q);
+
+ return q->length == 0;
+}
diff --git a/src/pulsecore/queue.h b/src/pulsecore/queue.h
new file mode 100644
index 0000000..14cc555
--- /dev/null
+++ b/src/pulsecore/queue.h
@@ -0,0 +1,41 @@
+#ifndef foopulsecorequeuehfoo
+#define foopulsecorequeuehfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2008 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulse/def.h>
+
+typedef struct pa_queue pa_queue;
+
+/* A simple implementation of the abstract data type queue. Stores
+ * pointers as members. The memory has to be managed by the caller. */
+
+pa_queue* pa_queue_new(void);
+
+/* Free the queue and run the specified callback function for every
+ * remaining entry. The callback function may be NULL. */
+void pa_queue_free(pa_queue *q, pa_free_cb_t free_func);
+
+void pa_queue_push(pa_queue *q, void *p);
+void* pa_queue_pop(pa_queue *q);
+
+int pa_queue_isempty(pa_queue *q);
+
+#endif
diff --git a/src/pulsecore/random.c b/src/pulsecore/random.c
new file mode 100644
index 0000000..508a6f8
--- /dev/null
+++ b/src/pulsecore/random.c
@@ -0,0 +1,129 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <time.h>
+
+#ifdef HAVE_WINDOWS_H
+#include <windows.h>
+#include <wincrypt.h>
+#endif
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+
+#include "random.h"
+
+static bool has_whined = false;
+
+static const char * const devices[] = { "/dev/urandom", "/dev/random", NULL };
+
+static int random_proper(void *ret_data, size_t length) {
+#ifdef OS_IS_WIN32
+ int ret = -1;
+
+ HCRYPTPROV hCryptProv = 0;
+
+ pa_assert(ret_data);
+ pa_assert(length > 0);
+
+ if (CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) {
+ if (CryptGenRandom(hCryptProv, length, ret_data))
+ ret = 0;
+ CryptReleaseContext(hCryptProv, 0);
+ }
+
+ return ret;
+
+#else /* OS_IS_WIN32 */
+
+ int fd, ret = -1;
+ ssize_t r = 0;
+ const char *const * device;
+
+ pa_assert(ret_data);
+ pa_assert(length > 0);
+
+ device = devices;
+
+ while (*device) {
+ ret = 0;
+
+ if ((fd = pa_open_cloexec(*device, O_RDONLY, 0)) >= 0) {
+
+ if ((r = pa_loop_read(fd, ret_data, length, NULL)) < 0 || (size_t) r != length)
+ ret = -1;
+
+ pa_close(fd);
+ } else
+ ret = -1;
+
+ if (ret == 0)
+ break;
+
+ device++;
+ }
+
+ return ret;
+#endif /* OS_IS_WIN32 */
+}
+
+void pa_random_seed(void) {
+ unsigned int seed;
+
+ if (random_proper(&seed, sizeof(unsigned int)) < 0) {
+
+ if (!has_whined) {
+ pa_log_warn("Failed to get proper entropy. Falling back to seeding with current time.");
+ has_whined = true;
+ }
+
+ seed = (unsigned int) time(NULL);
+ }
+
+ srand(seed);
+}
+
+void pa_random(void *ret_data, size_t length) {
+ uint8_t *p;
+ size_t l;
+
+ pa_assert(ret_data);
+ pa_assert(length > 0);
+
+ if (random_proper(ret_data, length) >= 0)
+ return;
+
+ if (!has_whined) {
+ pa_log_warn("Failed to get proper entropy. Falling back to unsecure pseudo RNG.");
+ has_whined = true;
+ }
+
+ for (p = ret_data, l = length; l > 0; p++, l--)
+ *p = (uint8_t) rand();
+}
diff --git a/src/pulsecore/random.h b/src/pulsecore/random.h
new file mode 100644
index 0000000..7c7755d
--- /dev/null
+++ b/src/pulsecore/random.h
@@ -0,0 +1,29 @@
+#ifndef foorandomhfoo
+#define foorandomhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/types.h>
+
+void pa_random_seed(void);
+void pa_random(void *ret_data, size_t length);
+
+#endif
diff --git a/src/pulsecore/ratelimit.c b/src/pulsecore/ratelimit.c
new file mode 100644
index 0000000..0bfc956
--- /dev/null
+++ b/src/pulsecore/ratelimit.c
@@ -0,0 +1,74 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2009 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulse/rtclock.h>
+
+#include <pulsecore/log.h>
+#include <pulsecore/mutex.h>
+
+#include "ratelimit.h"
+
+static pa_static_mutex mutex = PA_STATIC_MUTEX_INIT;
+
+/* Modelled after Linux' lib/ratelimit.c by Dave Young
+ * <hidave.darkstar@gmail.com>, which is licensed GPLv2. */
+
+bool pa_ratelimit_test(pa_ratelimit *r, pa_log_level_t t) {
+ pa_usec_t now;
+ pa_mutex *m;
+
+ now = pa_rtclock_now();
+
+ m = pa_static_mutex_get(&mutex, false, false);
+ pa_mutex_lock(m);
+
+ pa_assert(r);
+ pa_assert(r->interval > 0);
+ pa_assert(r->burst > 0);
+
+ if (r->begin <= 0 ||
+ r->begin + r->interval < now) {
+
+ if (r->n_missed > 0)
+ pa_logl(t, "%u events suppressed", r->n_missed);
+
+ r->begin = now;
+
+ /* Reset counters */
+ r->n_printed = 0;
+ r->n_missed = 0;
+ goto good;
+ }
+
+ if (r->n_printed <= r->burst)
+ goto good;
+
+ r->n_missed++;
+ pa_mutex_unlock(m);
+ return false;
+
+good:
+ r->n_printed++;
+ pa_mutex_unlock(m);
+ return true;
+}
diff --git a/src/pulsecore/ratelimit.h b/src/pulsecore/ratelimit.h
new file mode 100644
index 0000000..d1e2f22
--- /dev/null
+++ b/src/pulsecore/ratelimit.h
@@ -0,0 +1,55 @@
+#ifndef foopulsecoreratelimithfoo
+#define foopulsecoreratelimithfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2009 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulse/sample.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+
+typedef struct pa_ratelimit {
+ pa_usec_t interval;
+ unsigned burst;
+ unsigned n_printed, n_missed;
+ pa_usec_t begin;
+} pa_ratelimit;
+
+#define PA_DEFINE_RATELIMIT(_name, _interval, _burst) \
+ pa_ratelimit _name = { \
+ .interval = (_interval), \
+ .burst = (_burst), \
+ .n_printed = 0, \
+ .n_missed = 0, \
+ .begin = 0 \
+ }
+
+#define PA_INIT_RATELIMIT(v, _interval, _burst) \
+ do { \
+ pa_ratelimit *r = &(v); \
+ r->interval = (_interval); \
+ r->burst = (_burst); \
+ r->n_printed = 0; \
+ r->n_missed = 0; \
+ r->begin = 0; \
+ } while (false);
+
+bool pa_ratelimit_test(pa_ratelimit *r, pa_log_level_t t);
+
+#endif
diff --git a/src/pulsecore/refcnt.h b/src/pulsecore/refcnt.h
new file mode 100644
index 0000000..2e818ab
--- /dev/null
+++ b/src/pulsecore/refcnt.h
@@ -0,0 +1,79 @@
+#ifndef foopulserefcnthfoo
+#define foopulserefcnthfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulsecore/atomic.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/log.h>
+
+/* #define DEBUG_REF */
+
+#define PA_REFCNT_DECLARE \
+ pa_atomic_t _ref
+
+#define PA_REFCNT_VALUE(p) \
+ pa_atomic_load(&(p)->_ref)
+
+#define PA_REFCNT_INIT_ZERO(p) \
+ pa_atomic_store(&(p)->_ref, 0)
+
+#ifndef DEBUG_REF
+
+#define PA_REFCNT_INIT(p) \
+ pa_atomic_store(&(p)->_ref, 1)
+
+#define PA_REFCNT_INC(p) \
+ pa_atomic_inc(&(p)->_ref)
+
+#define PA_REFCNT_DEC(p) \
+ (pa_atomic_dec(&(p)->_ref)-1)
+
+#else
+
+/* If you need to debug ref counting problems define DEBUG_REF and
+ * set $PULSE_LOG_BACKTRACE=5 or suchlike in the shell when running
+ * PA */
+
+#define PA_REFCNT_INIT(p) \
+ do { \
+ pa_atomic_store(&(p)->_ref, 1); \
+ pa_log("REF: Init %p", p); \
+ } while (false)
+
+#define PA_REFCNT_INC(p) \
+ do { \
+ pa_atomic_inc(&(p)->_ref); \
+ pa_log("REF: Inc %p", p); \
+ } while (false) \
+
+#define PA_REFCNT_DEC(p) \
+ ({ \
+ int _j = (pa_atomic_dec(&(p)->_ref)-1); \
+ if (_j <= 0) \
+ pa_log("REF: Done %p", p); \
+ else \
+ pa_log("REF: Dec %p", p); \
+ _j; \
+ })
+
+#endif
+
+#endif
diff --git a/src/pulsecore/remap.c b/src/pulsecore/remap.c
new file mode 100644
index 0000000..35fffd7
--- /dev/null
+++ b/src/pulsecore/remap.c
@@ -0,0 +1,626 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2009 Wim Taymans <wim.taymans@collabora.co.uk.com>
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include <pulse/xmalloc.h>
+#include <pulse/sample.h>
+#include <pulse/volume.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+
+#include "cpu.h"
+#include "remap.h"
+
+static void remap_mono_to_stereo_s16ne_c(pa_remap_t *m, int16_t *dst, const int16_t *src, unsigned n) {
+ unsigned i;
+
+ for (i = n >> 2; i; i--) {
+ dst[0] = dst[1] = src[0];
+ dst[2] = dst[3] = src[1];
+ dst[4] = dst[5] = src[2];
+ dst[6] = dst[7] = src[3];
+ src += 4;
+ dst += 8;
+ }
+ for (i = n & 3; i; i--) {
+ dst[0] = dst[1] = src[0];
+ src++;
+ dst += 2;
+ }
+}
+
+static void remap_mono_to_stereo_s32ne_c(pa_remap_t *m, int32_t *dst, const int32_t *src, unsigned n) {
+ unsigned i;
+
+ for (i = n >> 2; i; i--) {
+ dst[0] = dst[1] = src[0];
+ dst[2] = dst[3] = src[1];
+ dst[4] = dst[5] = src[2];
+ dst[6] = dst[7] = src[3];
+ src += 4;
+ dst += 8;
+ }
+ for (i = n & 3; i; i--) {
+ dst[0] = dst[1] = src[0];
+ src++;
+ dst += 2;
+ }
+}
+
+static void remap_mono_to_stereo_float32ne_c(pa_remap_t *m, float *dst, const float *src, unsigned n) {
+ unsigned i;
+
+ for (i = n >> 2; i; i--) {
+ dst[0] = dst[1] = src[0];
+ dst[2] = dst[3] = src[1];
+ dst[4] = dst[5] = src[2];
+ dst[6] = dst[7] = src[3];
+ src += 4;
+ dst += 8;
+ }
+ for (i = n & 3; i; i--) {
+ dst[0] = dst[1] = src[0];
+ src++;
+ dst += 2;
+ }
+}
+
+static void remap_stereo_to_mono_s16ne_c(pa_remap_t *m, int16_t *dst, const int16_t *src, unsigned n) {
+ unsigned i;
+
+ for (i = n >> 2; i > 0; i--) {
+ dst[0] = (src[0] + src[1])/2;
+ dst[1] = (src[2] + src[3])/2;
+ dst[2] = (src[4] + src[5])/2;
+ dst[3] = (src[6] + src[7])/2;
+ src += 8;
+ dst += 4;
+ }
+ for (i = n & 3; i; i--) {
+ dst[0] = (src[0] + src[1])/2;
+ src += 2;
+ dst += 1;
+ }
+}
+
+static void remap_stereo_to_mono_s32ne_c(pa_remap_t *m, int32_t *dst, const int32_t *src, unsigned n) {
+ unsigned i;
+
+ for (i = n >> 2; i > 0; i--) {
+ /* Avoid overflow by performing division first. We accept a
+ * difference of +/- 1 to the ideal result. */
+ dst[0] = (src[0]/2 + src[1]/2);
+ dst[1] = (src[2]/2 + src[3]/2);
+ dst[2] = (src[4]/2 + src[5]/2);
+ dst[3] = (src[6]/2 + src[7]/2);
+ src += 8;
+ dst += 4;
+ }
+ for (i = n & 3; i; i--) {
+ /* Avoid overflow by performing division first. We accept a
+ * difference of +/- 1 to the ideal result. */
+ dst[0] = (src[0]/2 + src[1]/2);
+ src += 2;
+ dst += 1;
+ }
+}
+
+static void remap_stereo_to_mono_float32ne_c(pa_remap_t *m, float *dst, const float *src, unsigned n) {
+ unsigned i;
+
+ for (i = n >> 2; i > 0; i--) {
+ dst[0] = (src[0] + src[1])*0.5f;
+ dst[1] = (src[2] + src[3])*0.5f;
+ dst[2] = (src[4] + src[5])*0.5f;
+ dst[3] = (src[6] + src[7])*0.5f;
+ src += 8;
+ dst += 4;
+ }
+ for (i = n & 3; i; i--) {
+ dst[0] = (src[0] + src[1])*0.5f;
+ src += 2;
+ dst += 1;
+ }
+}
+
+static void remap_mono_to_ch4_s16ne_c(pa_remap_t *m, int16_t *dst, const int16_t *src, unsigned n) {
+ unsigned i;
+
+ for (i = n >> 2; i; i--) {
+ dst[0] = dst[1] = dst[2] = dst[3] = src[0];
+ dst[4] = dst[5] = dst[6] = dst[7] = src[1];
+ dst[8] = dst[9] = dst[10] = dst[11] = src[2];
+ dst[12] = dst[13] = dst[14] = dst[15] = src[3];
+ src += 4;
+ dst += 16;
+ }
+ for (i = n & 3; i; i--) {
+ dst[0] = dst[1] = dst[2] = dst[3] = src[0];
+ src++;
+ dst += 4;
+ }
+}
+
+static void remap_mono_to_ch4_s32ne_c(pa_remap_t *m, int32_t *dst, const int32_t *src, unsigned n) {
+ unsigned i;
+
+ for (i = n >> 2; i; i--) {
+ dst[0] = dst[1] = dst[2] = dst[3] = src[0];
+ dst[4] = dst[5] = dst[6] = dst[7] = src[1];
+ dst[8] = dst[9] = dst[10] = dst[11] = src[2];
+ dst[12] = dst[13] = dst[14] = dst[15] = src[3];
+ src += 4;
+ dst += 16;
+ }
+ for (i = n & 3; i; i--) {
+ dst[0] = dst[1] = dst[2] = dst[3] = src[0];
+ src++;
+ dst += 4;
+ }
+}
+
+static void remap_mono_to_ch4_float32ne_c(pa_remap_t *m, float *dst, const float *src, unsigned n) {
+ unsigned i;
+
+ for (i = n >> 2; i; i--) {
+ dst[0] = dst[1] = dst[2] = dst[3] = src[0];
+ dst[4] = dst[5] = dst[6] = dst[7] = src[1];
+ dst[8] = dst[9] = dst[10] = dst[11] = src[2];
+ dst[12] = dst[13] = dst[14] = dst[15] = src[3];
+ src += 4;
+ dst += 16;
+ }
+ for (i = n & 3; i; i--) {
+ dst[0] = dst[1] = dst[2] = dst[3] = src[0];
+ src++;
+ dst += 4;
+ }
+}
+
+static void remap_ch4_to_mono_s16ne_c(pa_remap_t *m, int16_t *dst, const int16_t *src, unsigned n) {
+ unsigned i;
+
+ for (i = n >> 2; i > 0; i--) {
+ dst[0] = (src[0] + src[1] + src[2] + src[3])/4;
+ dst[1] = (src[4] + src[5] + src[6] + src[7])/4;
+ dst[2] = (src[8] + src[9] + src[10] + src[11])/4;
+ dst[3] = (src[12] + src[13] + src[14] + src[15])/4;
+ src += 16;
+ dst += 4;
+ }
+ for (i = n & 3; i; i--) {
+ dst[0] = (src[0] + src[1] + src[2] + src[3])/4;
+ src += 4;
+ dst += 1;
+ }
+}
+
+static void remap_ch4_to_mono_s32ne_c(pa_remap_t *m, int32_t *dst, const int32_t *src, unsigned n) {
+ unsigned i;
+
+ for (i = n >> 2; i > 0; i--) {
+ /* Avoid overflow by performing division first. We accept a
+ * difference of +/- 3 to the ideal result. */
+ dst[0] = (src[0]/4 + src[1]/4 + src[2]/4 + src[3]/4);
+ dst[1] = (src[4]/4 + src[5]/4 + src[6]/4 + src[7]/4);
+ dst[2] = (src[8]/4 + src[9]/4 + src[10]/4 + src[11]/4);
+ dst[3] = (src[12]/4 + src[13]/4 + src[14]/4 + src[15]/4);
+ src += 16;
+ dst += 4;
+ }
+ for (i = n & 3; i; i--) {
+ /* Avoid overflow by performing division first. We accept a
+ * difference of +/- 3 to the ideal result. */
+ dst[0] = (src[0]/4 + src[1]/4 + src[2]/4 + src[3]/4);
+ src += 4;
+ dst += 1;
+ }
+}
+
+static void remap_ch4_to_mono_float32ne_c(pa_remap_t *m, float *dst, const float *src, unsigned n) {
+ unsigned i;
+
+ for (i = n >> 2; i > 0; i--) {
+ dst[0] = (src[0] + src[1] + src[2] + src[3])*0.25f;
+ dst[1] = (src[4] + src[5] + src[6] + src[7])*0.25f;
+ dst[2] = (src[8] + src[9] + src[10] + src[11])*0.25f;
+ dst[3] = (src[12] + src[13] + src[14] + src[15])*0.25f;
+ src += 16;
+ dst += 4;
+ }
+ for (i = n & 3; i; i--) {
+ dst[0] = (src[0] + src[1] + src[2] + src[3])*0.25f;
+ src += 4;
+ dst += 1;
+ }
+}
+
+static void remap_channels_matrix_s16ne_c(pa_remap_t *m, int16_t *dst, const int16_t *src, unsigned n) {
+
+ unsigned oc, ic, i;
+ unsigned n_ic, n_oc;
+
+ n_ic = m->i_ss.channels;
+ n_oc = m->o_ss.channels;
+
+ memset(dst, 0, n * sizeof(int16_t) * n_oc);
+
+ for (oc = 0; oc < n_oc; oc++) {
+
+ for (ic = 0; ic < n_ic; ic++) {
+ int16_t *d = dst + oc;
+ const int16_t *s = src + ic;
+ int32_t vol = m->map_table_i[oc][ic];
+
+ if (vol <= 0)
+ continue;
+
+ if (vol >= 0x10000) {
+ for (i = n; i > 0; i--, s += n_ic, d += n_oc)
+ *d += *s;
+ } else {
+ for (i = n; i > 0; i--, s += n_ic, d += n_oc)
+ *d += (int16_t) (((int32_t)*s * vol) >> 16);
+ }
+ }
+ }
+}
+
+static void remap_channels_matrix_s32ne_c(pa_remap_t *m, int32_t *dst, const int32_t *src, unsigned n) {
+ unsigned oc, ic, i;
+ unsigned n_ic, n_oc;
+
+ n_ic = m->i_ss.channels;
+ n_oc = m->o_ss.channels;
+
+ memset(dst, 0, n * sizeof(int32_t) * n_oc);
+
+ for (oc = 0; oc < n_oc; oc++) {
+
+ for (ic = 0; ic < n_ic; ic++) {
+ int32_t *d = dst + oc;
+ const int32_t *s = src + ic;
+ int32_t vol = m->map_table_i[oc][ic];
+
+ if (vol <= 0)
+ continue;
+
+ if (vol >= 0x10000) {
+ for (i = n; i > 0; i--, s += n_ic, d += n_oc)
+ *d += *s;
+ } else {
+ for (i = n; i > 0; i--, s += n_ic, d += n_oc)
+ *d += (int32_t) (((int64_t)*s * vol) >> 16);
+ }
+ }
+ }
+}
+
+static void remap_channels_matrix_float32ne_c(pa_remap_t *m, float *dst, const float *src, unsigned n) {
+ unsigned oc, ic, i;
+ unsigned n_ic, n_oc;
+
+ n_ic = m->i_ss.channels;
+ n_oc = m->o_ss.channels;
+
+ memset(dst, 0, n * sizeof(float) * n_oc);
+
+ for (oc = 0; oc < n_oc; oc++) {
+
+ for (ic = 0; ic < n_ic; ic++) {
+ float *d = dst + oc;
+ const float *s = src + ic;
+ float vol = m->map_table_f[oc][ic];
+
+ if (vol <= 0.0f)
+ continue;
+
+ if (vol >= 1.0f) {
+ for (i = n; i > 0; i--, s += n_ic, d += n_oc)
+ *d += *s;
+ } else {
+ for (i = n; i > 0; i--, s += n_ic, d += n_oc)
+ *d += *s * vol;
+ }
+ }
+ }
+}
+
+/* Produce an array containing input channel indices to map to output channels.
+ * If the output channel is empty, the array element is -1. */
+bool pa_setup_remap_arrange(const pa_remap_t *m, int8_t arrange[PA_CHANNELS_MAX]) {
+ unsigned ic, oc;
+ unsigned n_ic, n_oc;
+ unsigned count_output = 0;
+
+ pa_assert(m);
+
+ n_ic = m->i_ss.channels;
+ n_oc = m->o_ss.channels;
+
+ for (oc = 0; oc < n_oc; oc++) {
+ arrange[oc] = -1;
+ for (ic = 0; ic < n_ic; ic++) {
+ int32_t vol = m->map_table_i[oc][ic];
+
+ /* input channel is not used */
+ if (vol == 0)
+ continue;
+
+ /* if mixing this channel, we cannot just rearrange */
+ if (vol != 0x10000 || arrange[oc] >= 0)
+ return false;
+
+ arrange[oc] = ic;
+ count_output++;
+ }
+ }
+
+ return count_output > 0;
+}
+
+static void remap_arrange_mono_s16ne_c(pa_remap_t *m, int16_t *dst, const int16_t *src, unsigned n) {
+ const unsigned n_ic = m->i_ss.channels;
+ const int8_t *arrange = m->state;
+
+ src += arrange[0];
+ for (; n > 0; n--) {
+ *dst++ = *src;
+ src += n_ic;
+ }
+}
+
+static void remap_arrange_stereo_s16ne_c(pa_remap_t *m, int16_t *dst, const int16_t *src, unsigned n) {
+ const unsigned n_ic = m->i_ss.channels;
+ const int8_t *arrange = m->state;
+ const int8_t ic0 = arrange[0], ic1 = arrange[1];
+
+ for (; n > 0; n--) {
+ *dst++ = (ic0 >= 0) ? *(src + ic0) : 0;
+ *dst++ = (ic1 >= 0) ? *(src + ic1) : 0;
+ src += n_ic;
+ }
+}
+
+static void remap_arrange_ch4_s16ne_c(pa_remap_t *m, int16_t *dst, const int16_t *src, unsigned n) {
+ const unsigned n_ic = m->i_ss.channels;
+ const int8_t *arrange = m->state;
+ const int8_t ic0 = arrange[0], ic1 = arrange[1],
+ ic2 = arrange[2], ic3 = arrange[3];
+
+ for (; n > 0; n--) {
+ *dst++ = (ic0 >= 0) ? *(src + ic0) : 0;
+ *dst++ = (ic1 >= 0) ? *(src + ic1) : 0;
+ *dst++ = (ic2 >= 0) ? *(src + ic2) : 0;
+ *dst++ = (ic3 >= 0) ? *(src + ic3) : 0;
+ src += n_ic;
+ }
+}
+
+static void remap_arrange_mono_s32ne_c(pa_remap_t *m, int32_t *dst, const int32_t *src, unsigned n) {
+ const unsigned n_ic = m->i_ss.channels;
+ const int8_t *arrange = m->state;
+
+ src += arrange[0];
+ for (; n > 0; n--) {
+ *dst++ = *src;
+ src += n_ic;
+ }
+}
+
+static void remap_arrange_stereo_s32ne_c(pa_remap_t *m, int32_t *dst, const int32_t *src, unsigned n) {
+ const unsigned n_ic = m->i_ss.channels;
+ const int8_t *arrange = m->state;
+ const int ic0 = arrange[0], ic1 = arrange[1];
+
+ for (; n > 0; n--) {
+ *dst++ = (ic0 >= 0) ? *(src + ic0) : 0;
+ *dst++ = (ic1 >= 0) ? *(src + ic1) : 0;
+ src += n_ic;
+ }
+}
+
+static void remap_arrange_ch4_s32ne_c(pa_remap_t *m, int32_t *dst, const int32_t *src, unsigned n) {
+ const unsigned n_ic = m->i_ss.channels;
+ const int8_t *arrange = m->state;
+ const int ic0 = arrange[0], ic1 = arrange[1],
+ ic2 = arrange[2], ic3 = arrange[3];
+
+ for (; n > 0; n--) {
+ *dst++ = (ic0 >= 0) ? *(src + ic0) : 0;
+ *dst++ = (ic1 >= 0) ? *(src + ic1) : 0;
+ *dst++ = (ic2 >= 0) ? *(src + ic2) : 0;
+ *dst++ = (ic3 >= 0) ? *(src + ic3) : 0;
+ src += n_ic;
+ }
+}
+
+static void remap_arrange_mono_float32ne_c(pa_remap_t *m, float *dst, const float *src, unsigned n) {
+ const unsigned n_ic = m->i_ss.channels;
+ const int8_t *arrange = m->state;
+
+ src += arrange[0];
+ for (; n > 0; n--) {
+ *dst++ = *src;
+ src += n_ic;
+ }
+}
+
+static void remap_arrange_stereo_float32ne_c(pa_remap_t *m, float *dst, const float *src, unsigned n) {
+ const unsigned n_ic = m->i_ss.channels;
+ const int8_t *arrange = m->state;
+ const int ic0 = arrange[0], ic1 = arrange[1];
+
+ for (; n > 0; n--) {
+ *dst++ = (ic0 >= 0) ? *(src + ic0) : 0.0f;
+ *dst++ = (ic1 >= 0) ? *(src + ic1) : 0.0f;
+ src += n_ic;
+ }
+}
+
+static void remap_arrange_ch4_float32ne_c(pa_remap_t *m, float *dst, const float *src, unsigned n) {
+ const unsigned n_ic = m->i_ss.channels;
+ const int8_t *arrange = m->state;
+ const int ic0 = arrange[0], ic1 = arrange[1],
+ ic2 = arrange[2], ic3 = arrange[3];
+
+ for (; n > 0; n--) {
+ *dst++ = (ic0 >= 0) ? *(src + ic0) : 0.0f;
+ *dst++ = (ic1 >= 0) ? *(src + ic1) : 0.0f;
+ *dst++ = (ic2 >= 0) ? *(src + ic2) : 0.0f;
+ *dst++ = (ic3 >= 0) ? *(src + ic3) : 0.0f;
+ src += n_ic;
+ }
+}
+
+void pa_set_remap_func(pa_remap_t *m, pa_do_remap_func_t func_s16,
+ pa_do_remap_func_t func_s32, pa_do_remap_func_t func_float) {
+
+ pa_assert(m);
+
+ if (m->format == PA_SAMPLE_S16NE)
+ m->do_remap = func_s16;
+ else if (m->format == PA_SAMPLE_S32NE)
+ m->do_remap = func_s32;
+ else if (m->format == PA_SAMPLE_FLOAT32NE)
+ m->do_remap = func_float;
+ else
+ pa_assert_not_reached();
+ pa_assert(m->do_remap);
+}
+
+static bool force_generic_code = false;
+
+/* set the function that will execute the remapping based on the matrices */
+static void init_remap_c(pa_remap_t *m) {
+ unsigned n_oc, n_ic;
+ int8_t arrange[PA_CHANNELS_MAX];
+
+ n_oc = m->o_ss.channels;
+ n_ic = m->i_ss.channels;
+
+ /* find some common channel remappings, fall back to full matrix operation. */
+ if (force_generic_code) {
+ pa_log_info("Forced to use generic matrix remapping");
+ pa_set_remap_func(m, (pa_do_remap_func_t) remap_channels_matrix_s16ne_c,
+ (pa_do_remap_func_t) remap_channels_matrix_s32ne_c,
+ (pa_do_remap_func_t) remap_channels_matrix_float32ne_c);
+ return;
+ }
+
+ if (n_ic == 1 && n_oc == 2 &&
+ m->map_table_i[0][0] == 0x10000 && m->map_table_i[1][0] == 0x10000) {
+
+ pa_log_info("Using mono to stereo remapping");
+ pa_set_remap_func(m, (pa_do_remap_func_t) remap_mono_to_stereo_s16ne_c,
+ (pa_do_remap_func_t) remap_mono_to_stereo_s32ne_c,
+ (pa_do_remap_func_t) remap_mono_to_stereo_float32ne_c);
+ } else if (n_ic == 2 && n_oc == 1 &&
+ m->map_table_i[0][0] == 0x8000 && m->map_table_i[0][1] == 0x8000) {
+
+ pa_log_info("Using stereo to mono remapping");
+ pa_set_remap_func(m, (pa_do_remap_func_t) remap_stereo_to_mono_s16ne_c,
+ (pa_do_remap_func_t) remap_stereo_to_mono_s32ne_c,
+ (pa_do_remap_func_t) remap_stereo_to_mono_float32ne_c);
+ } else if (n_ic == 1 && n_oc == 4 &&
+ m->map_table_i[0][0] == 0x10000 && m->map_table_i[1][0] == 0x10000 &&
+ m->map_table_i[2][0] == 0x10000 && m->map_table_i[3][0] == 0x10000) {
+
+ pa_log_info("Using mono to 4-channel remapping");
+ pa_set_remap_func(m, (pa_do_remap_func_t)remap_mono_to_ch4_s16ne_c,
+ (pa_do_remap_func_t) remap_mono_to_ch4_s32ne_c,
+ (pa_do_remap_func_t) remap_mono_to_ch4_float32ne_c);
+ } else if (n_ic == 4 && n_oc == 1 &&
+ m->map_table_i[0][0] == 0x4000 && m->map_table_i[0][1] == 0x4000 &&
+ m->map_table_i[0][2] == 0x4000 && m->map_table_i[0][3] == 0x4000) {
+
+ pa_log_info("Using 4-channel to mono remapping");
+ pa_set_remap_func(m, (pa_do_remap_func_t) remap_ch4_to_mono_s16ne_c,
+ (pa_do_remap_func_t) remap_ch4_to_mono_s32ne_c,
+ (pa_do_remap_func_t) remap_ch4_to_mono_float32ne_c);
+ } else if (pa_setup_remap_arrange(m, arrange) && n_oc == 1) {
+
+ pa_log_info("Using mono arrange remapping");
+ pa_set_remap_func(m, (pa_do_remap_func_t) remap_arrange_mono_s16ne_c,
+ (pa_do_remap_func_t) remap_arrange_mono_s32ne_c,
+ (pa_do_remap_func_t) remap_arrange_mono_float32ne_c);
+
+ /* setup state */
+ m->state = pa_xnewdup(int8_t, arrange, PA_CHANNELS_MAX);
+ } else if (pa_setup_remap_arrange(m, arrange) && n_oc == 2) {
+
+ pa_log_info("Using stereo arrange remapping");
+ pa_set_remap_func(m, (pa_do_remap_func_t) remap_arrange_stereo_s16ne_c,
+ (pa_do_remap_func_t) remap_arrange_stereo_s32ne_c,
+ (pa_do_remap_func_t) remap_arrange_stereo_float32ne_c);
+
+ /* setup state */
+ m->state = pa_xnewdup(int8_t, arrange, PA_CHANNELS_MAX);
+ } else if (pa_setup_remap_arrange(m, arrange) && n_oc == 4) {
+
+ pa_log_info("Using 4-channel arrange remapping");
+ pa_set_remap_func(m, (pa_do_remap_func_t) remap_arrange_ch4_s16ne_c,
+ (pa_do_remap_func_t) remap_arrange_ch4_s32ne_c,
+ (pa_do_remap_func_t) remap_arrange_ch4_float32ne_c);
+
+ /* setup state */
+ m->state = pa_xnewdup(int8_t, arrange, PA_CHANNELS_MAX);
+ } else {
+
+ pa_log_info("Using generic matrix remapping");
+ pa_set_remap_func(m, (pa_do_remap_func_t) remap_channels_matrix_s16ne_c,
+ (pa_do_remap_func_t) remap_channels_matrix_s32ne_c,
+ (pa_do_remap_func_t) remap_channels_matrix_float32ne_c);
+ }
+}
+
+/* default C implementation */
+static pa_init_remap_func_t init_remap_func = init_remap_c;
+
+void pa_init_remap_func(pa_remap_t *m) {
+ pa_assert(init_remap_func);
+
+ m->do_remap = NULL;
+
+ /* call the installed remap init function */
+ init_remap_func(m);
+
+ if (m->do_remap == NULL) {
+ /* nothing was installed, fallback to C version */
+ init_remap_c(m);
+ }
+}
+
+pa_init_remap_func_t pa_get_init_remap_func(void) {
+ return init_remap_func;
+}
+
+void pa_set_init_remap_func(pa_init_remap_func_t func) {
+ init_remap_func = func;
+}
+
+void pa_remap_func_init(const pa_cpu_info *cpu_info) {
+ force_generic_code = cpu_info->force_generic_code;
+}
diff --git a/src/pulsecore/remap.h b/src/pulsecore/remap.h
new file mode 100644
index 0000000..473f0ce
--- /dev/null
+++ b/src/pulsecore/remap.h
@@ -0,0 +1,60 @@
+#ifndef fooremapfoo
+#define fooremapfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2009 Wim Taymans <wim.taymans@collabora.co.uk.com>
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulse/sample.h>
+
+typedef struct pa_remap pa_remap_t;
+
+typedef void (*pa_do_remap_func_t) (pa_remap_t *m, void *d, const void *s, unsigned n);
+
+struct pa_remap {
+ pa_sample_format_t format;
+ pa_sample_spec i_ss, o_ss;
+ float map_table_f[PA_CHANNELS_MAX][PA_CHANNELS_MAX];
+ int32_t map_table_i[PA_CHANNELS_MAX][PA_CHANNELS_MAX];
+ pa_do_remap_func_t do_remap;
+ void *state; /* optional state information for the remap function */
+};
+
+void pa_init_remap_func(pa_remap_t *m);
+
+/* custom installation of init functions */
+typedef void (*pa_init_remap_func_t) (pa_remap_t *m);
+
+pa_init_remap_func_t pa_get_init_remap_func(void);
+void pa_set_init_remap_func(pa_init_remap_func_t func);
+
+/* Check if remapping can be performed by just copying some or all input
+ * channels' data to output channels. Returns true and a table of input
+ * channel indices, or false otherwise.
+ *
+ * The table contains an entry for each output channels. Each table entry given
+ * either the input channel index to be copied, or -1 indicating that the
+ * output channel is not used and hence zero.
+ */
+bool pa_setup_remap_arrange(const pa_remap_t *m, int8_t arrange[PA_CHANNELS_MAX]);
+
+void pa_set_remap_func(pa_remap_t *m, pa_do_remap_func_t func_s16,
+ pa_do_remap_func_t func_s32, pa_do_remap_func_t func_float);
+
+#endif /* fooremapfoo */
diff --git a/src/pulsecore/remap_mmx.c b/src/pulsecore/remap_mmx.c
new file mode 100644
index 0000000..9d07671
--- /dev/null
+++ b/src/pulsecore/remap_mmx.c
@@ -0,0 +1,155 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2009 Wim Taymans <wim.taymans@collabora.co.uk.com>
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulse/sample.h>
+#include <pulse/volume.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+
+#include "cpu-x86.h"
+#include "remap.h"
+
+#define LOAD_SAMPLES \
+ " movq (%1), %%mm0 \n\t" \
+ " movq 8(%1), %%mm2 \n\t" \
+ " movq 16(%1), %%mm4 \n\t" \
+ " movq 24(%1), %%mm6 \n\t" \
+ " movq %%mm0, %%mm1 \n\t" \
+ " movq %%mm2, %%mm3 \n\t" \
+ " movq %%mm4, %%mm5 \n\t" \
+ " movq %%mm6, %%mm7 \n\t"
+
+#define UNPACK_SAMPLES(s) \
+ " punpckl"#s" %%mm0, %%mm0 \n\t" \
+ " punpckh"#s" %%mm1, %%mm1 \n\t" \
+ " punpckl"#s" %%mm2, %%mm2 \n\t" \
+ " punpckh"#s" %%mm3, %%mm3 \n\t" \
+ " punpckl"#s" %%mm4, %%mm4 \n\t" \
+ " punpckh"#s" %%mm5, %%mm5 \n\t" \
+ " punpckl"#s" %%mm6, %%mm6 \n\t" \
+ " punpckh"#s" %%mm7, %%mm7 \n\t"
+
+#define STORE_SAMPLES \
+ " movq %%mm0, (%0) \n\t" \
+ " movq %%mm1, 8(%0) \n\t" \
+ " movq %%mm2, 16(%0) \n\t" \
+ " movq %%mm3, 24(%0) \n\t" \
+ " movq %%mm4, 32(%0) \n\t" \
+ " movq %%mm5, 40(%0) \n\t" \
+ " movq %%mm6, 48(%0) \n\t" \
+ " movq %%mm7, 56(%0) \n\t" \
+ " add $32, %1 \n\t" \
+ " add $64, %0 \n\t"
+
+#define HANDLE_SINGLE_dq() \
+ " movd (%1), %%mm0 \n\t" \
+ " punpckldq %%mm0, %%mm0 \n\t" \
+ " movq %%mm0, (%0) \n\t" \
+ " add $4, %1 \n\t" \
+ " add $8, %0 \n\t"
+
+#define HANDLE_SINGLE_wd() \
+ " movw (%1), %w3 \n\t" \
+ " movd %3, %%mm0 \n\t" \
+ " punpcklwd %%mm0, %%mm0 \n\t" \
+ " movd %%mm0, (%0) \n\t" \
+ " add $2, %1 \n\t" \
+ " add $4, %0 \n\t"
+
+#define MONO_TO_STEREO(s,shift,mask) \
+ " mov %4, %2 \n\t" \
+ " sar $"#shift", %2 \n\t" \
+ " cmp $0, %2 \n\t" \
+ " je 2f \n\t" \
+ "1: \n\t" \
+ LOAD_SAMPLES \
+ UNPACK_SAMPLES(s) \
+ STORE_SAMPLES \
+ " dec %2 \n\t" \
+ " jne 1b \n\t" \
+ "2: \n\t" \
+ " mov %4, %2 \n\t" \
+ " and $"#mask", %2 \n\t" \
+ " je 4f \n\t" \
+ "3: \n\t" \
+ HANDLE_SINGLE_##s() \
+ " dec %2 \n\t" \
+ " jne 3b \n\t" \
+ "4: \n\t" \
+ " emms \n\t"
+
+#if defined (__i386__) || defined (__amd64__)
+static void remap_mono_to_stereo_s16ne_mmx(pa_remap_t *m, int16_t *dst, const int16_t *src, unsigned n) {
+ pa_reg_x86 temp, temp2;
+
+ __asm__ __volatile__ (
+ MONO_TO_STEREO(wd,4,15) /* do words to doubles */
+ : "+r" (dst), "+r" (src), "=&r" (temp), "=&r" (temp2)
+ : "r" ((pa_reg_x86)n)
+ : "cc"
+ );
+}
+
+/* Works for both S32NE and FLOAT32NE */
+static void remap_mono_to_stereo_any32ne_mmx(pa_remap_t *m, float *dst, const float *src, unsigned n) {
+ pa_reg_x86 temp, temp2;
+
+ __asm__ __volatile__ (
+ MONO_TO_STEREO(dq,3,7) /* do doubles to quads */
+ : "+r" (dst), "+r" (src), "=&r" (temp), "=&r" (temp2)
+ : "r" ((pa_reg_x86)n)
+ : "cc"
+ );
+}
+
+/* set the function that will execute the remapping based on the matrices */
+static void init_remap_mmx(pa_remap_t *m) {
+ unsigned n_oc, n_ic;
+
+ n_oc = m->o_ss.channels;
+ n_ic = m->i_ss.channels;
+
+ /* find some common channel remappings, fall back to full matrix operation. */
+ if (n_ic == 1 && n_oc == 2 &&
+ m->map_table_i[0][0] == 0x10000 && m->map_table_i[1][0] == 0x10000) {
+
+ pa_log_info("Using MMX mono to stereo remapping");
+ pa_set_remap_func(m, (pa_do_remap_func_t) remap_mono_to_stereo_s16ne_mmx,
+ (pa_do_remap_func_t) remap_mono_to_stereo_any32ne_mmx,
+ (pa_do_remap_func_t) remap_mono_to_stereo_any32ne_mmx);
+ }
+}
+#endif /* defined (__i386__) || defined (__amd64__) */
+
+void pa_remap_func_init_mmx(pa_cpu_x86_flag_t flags) {
+#if defined (__i386__) || defined (__amd64__)
+
+ if (flags & PA_CPU_X86_MMX) {
+ pa_log_info("Initialising MMX optimized remappers.");
+
+ pa_set_init_remap_func((pa_init_remap_func_t) init_remap_mmx);
+ }
+
+#endif /* defined (__i386__) || defined (__amd64__) */
+}
diff --git a/src/pulsecore/remap_neon.c b/src/pulsecore/remap_neon.c
new file mode 100644
index 0000000..6f71345
--- /dev/null
+++ b/src/pulsecore/remap_neon.c
@@ -0,0 +1,545 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2013 Peter Meerwald <p.meerwald@bct-electronic.com>
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulse/sample.h>
+#include <pulse/xmalloc.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+
+#include "cpu-arm.h"
+#include "remap.h"
+
+#include <arm_neon.h>
+
+static void remap_mono_to_stereo_float32ne_neon_a8(pa_remap_t *m, float *dst, const float *src, unsigned n) {
+ for (; n >= 4; n -= 4) {
+ __asm__ __volatile__ (
+ "vld1.32 {q0}, [%[src]]! \n\t"
+ "vmov q1, q0 \n\t"
+ "vst2.32 {q0,q1}, [%[dst]]! \n\t"
+ : [dst] "+r" (dst), [src] "+r" (src) /* output operands */
+ : /* input operands */
+ : "memory", "q0", "q1" /* clobber list */
+ );
+ }
+
+ for (; n > 0; n--) {
+ dst[0] = dst[1] = src[0];
+ src++;
+ dst += 2;
+ }
+}
+
+static void remap_mono_to_stereo_float32ne_generic_arm(pa_remap_t *m, float *dst, const float *src, unsigned n) {
+ for (; n >= 2; n -= 2) {
+ __asm__ __volatile__ (
+ "ldm %[src]!, {r4,r6} \n\t"
+ "mov r5, r4 \n\t"
+
+ /* We use r12 instead of r7 here, because r7 is reserved for the
+ * frame pointer when using Thumb. */
+ "mov r12, r6 \n\t"
+
+ "stm %[dst]!, {r4-r6,r12} \n\t"
+ : [dst] "+r" (dst), [src] "+r" (src) /* output operands */
+ : /* input operands */
+ : "memory", "r4", "r5", "r6", "r12" /* clobber list */
+ );
+ }
+
+ if (n > 0)
+ dst[0] = dst[1] = src[0];
+}
+
+static void remap_mono_to_stereo_s16ne_neon(pa_remap_t *m, int16_t *dst, const int16_t *src, unsigned n) {
+ for (; n >= 8; n -= 8) {
+ __asm__ __volatile__ (
+ "vld1.16 {q0}, [%[src]]! \n\t"
+ "vmov q1, q0 \n\t"
+ "vst2.16 {q0,q1}, [%[dst]]! \n\t"
+ : [dst] "+r" (dst), [src] "+r" (src) /* output operands */
+ : /* input operands */
+ : "memory", "q0", "q1" /* clobber list */
+ );
+ }
+
+ for (; n > 0; n--) {
+ dst[0] = dst[1] = src[0];
+ src++;
+ dst += 2;
+ }
+}
+
+static void remap_mono_to_ch4_float32ne_neon(pa_remap_t *m, float *dst, const float *src, unsigned n) {
+ for (; n >= 2; n -= 2) {
+ __asm__ __volatile__ (
+ "vld1.32 {d0}, [%[src]]! \n\t"
+ "vdup.f32 q1, d0[0] \n\t"
+ "vdup.f32 q2, d0[1] \n\t"
+ "vst1.32 {q1,q2}, [%[dst]]! \n\t"
+ : [dst] "+r" (dst), [src] "+r" (src) /* output operands */
+ : /* input operands */
+ : "memory", "q0", "q1", "q2" /* clobber list */
+ );
+ }
+
+ if (n--)
+ dst[0] = dst[1] = dst[2] = dst[3] = src[0];
+}
+
+static void remap_mono_to_ch4_s16ne_neon(pa_remap_t *m, int16_t *dst, const int16_t *src, unsigned n) {
+ for (; n >= 4; n -= 4) {
+ __asm__ __volatile__ (
+ "vld1.16 {d0}, [%[src]]! \n\t"
+ "vdup.s16 d1, d0[1] \n\t"
+ "vdup.s16 d2, d0[2] \n\t"
+ "vdup.s16 d3, d0[3] \n\t"
+ "vdup.s16 d0, d0[0] \n\t"
+ "vst1.16 {d0,d1,d2,d3}, [%[dst]]!\n\t"
+ : [dst] "+r" (dst), [src] "+r" (src) /* output operands */
+ : /* input operands */
+ : "memory", "d0", "d1", "d2", "d3" /* clobber list */
+ );
+ }
+
+ for (; n > 0; n--) {
+ dst[0] = dst[1] = dst[2] = dst[3] = src[0];
+ src++;
+ dst += 4;
+ }
+}
+
+static void remap_stereo_to_mono_float32ne_neon(pa_remap_t *m, float *dst, const float *src, unsigned n) {
+ const float32x4_t halve = vdupq_n_f32(0.5f);
+ for (; n >= 4; n -= 4) {
+ __asm__ __volatile__ (
+ "vld2.32 {q0,q1}, [%[src]]! \n\t"
+ "vadd.f32 q0, q0, q1 \n\t"
+ "vmul.f32 q0, q0, %q[halve] \n\t"
+ "vst1.32 {q0}, [%[dst]]! \n\t"
+ : [dst] "+r" (dst), [src] "+r" (src) /* output operands */
+ : [halve] "w" (halve) /* input operands */
+ : "memory", "q0", "q1" /* clobber list */
+ );
+ }
+
+ for (; n > 0; n--) {
+ dst[0] = (src[0] + src[1])*0.5f;
+ src += 2;
+ dst++;
+ }
+}
+
+static void remap_stereo_to_mono_s32ne_neon(pa_remap_t *m, int32_t *dst, const int32_t *src, unsigned n) {
+ for (; n >= 4; n -= 4) {
+ __asm__ __volatile__ (
+ "vld2.32 {q0,q1}, [%[src]]! \n\t"
+ "vrhadd.s32 q0, q0, q1 \n\t"
+ "vst1.32 {q0}, [%[dst]]! \n\t"
+ : [dst] "+r" (dst), [src] "+r" (src) /* output operands */
+ : /* input operands */
+ : "memory", "q0", "q1" /* clobber list */
+ );
+ }
+
+ for (; n > 0; n--) {
+ dst[0] = src[0]/2 + src[1]/2;
+ src += 2;
+ dst++;
+ }
+}
+
+static void remap_stereo_to_mono_s16ne_neon(pa_remap_t *m, int16_t *dst, const int16_t *src, unsigned n) {
+ for (; n >= 8; n -= 8) {
+ __asm__ __volatile__ (
+ "vld2.16 {q0,q1}, [%[src]]! \n\t"
+ "vrhadd.s16 q0, q0, q1 \n\t"
+ "vst1.16 {q0}, [%[dst]]! \n\t"
+ : [dst] "+r" (dst), [src] "+r" (src) /* output operands */
+ : /* input operands */
+ : "memory", "q0", "q1" /* clobber list */
+ );
+ }
+
+ for (; n > 0; n--) {
+ dst[0] = (src[0] + src[1])/2;
+ src += 2;
+ dst++;
+ }
+}
+
+static void remap_ch4_to_mono_float32ne_neon(pa_remap_t *m, float *dst, const float *src, unsigned n) {
+ const float32x2_t quart = vdup_n_f32(0.25f);
+ for (; n >= 2; n -= 2) {
+ __asm__ __volatile__ (
+ "vld4.32 {d0,d1,d2,d3}, [%[src]]!\n\t"
+ "vadd.f32 d0, d0, d1 \n\t"
+ "vadd.f32 d2, d2, d3 \n\t"
+ "vadd.f32 d0, d0, d2 \n\t"
+ "vmul.f32 d0, d0, %P[quart] \n\t"
+ "vst1.32 {d0}, [%[dst]]! \n\t"
+ : [dst] "+r" (dst), [src] "+r" (src) /* output operands */
+ : [quart] "w" (quart) /* input operands */
+ : "memory", "d0", "d1", "d2", "d3" /* clobber list */
+ );
+ }
+
+ if (n > 0)
+ dst[0] = (src[0] + src[1] + src[2] + src[3])*0.25f;
+}
+
+static void remap_ch4_to_mono_s16ne_neon(pa_remap_t *m, int16_t *dst, const int16_t *src, unsigned n) {
+ for (; n >= 4; n -= 4) {
+ __asm__ __volatile__ (
+ "vld4.16 {d0,d1,d2,d3}, [%[src]]!\n\t"
+ "vrhadd.s16 d0, d0, d1 \n\t"
+ "vrhadd.s16 d2, d2, d3 \n\t"
+ "vrhadd.s16 d0, d0, d2 \n\t"
+ "vst1.16 {d0}, [%[dst]]! \n\t"
+ : [dst] "+r" (dst), [src] "+r" (src) /* output operands */
+ : /* input operands */
+ : "memory", "d0", "d1", "d2", "d3" /* clobber list */
+ );
+ }
+
+ for (; n > 0; n--) {
+ dst[0] = (src[0] + src[1] + src[2] + src[3])/4;
+ src += 4;
+ dst++;
+ }
+}
+
+static void remap_ch4_s16ne_neon(pa_remap_t *m, int16_t *dst, const int16_t *src, unsigned n) {
+ int32x4_t *f = m->state;
+ const int32x4_t f0 = f[0], f1 = f[1], f2 = f[2], f3 = f[3];
+
+ for (; n > 0; n--) {
+ __asm__ __volatile__ (
+ "vld1.16 {d0}, [%[src]]! \n\t"
+ "vmovl.s16 q0, d0 \n\t"
+ "vdup.s32 q1, d0[0] \n\t"
+ "vmul.s32 q1, q1, %q[f0] \n\t"
+ "vdup.s32 q2, d0[1] \n\t"
+ "vmla.s32 q1, q2, %q[f1] \n\t"
+ "vdup.s32 q2, d1[0] \n\t"
+ "vmla.s32 q1, q2, %q[f2] \n\t"
+ "vdup.s32 q2, d1[1] \n\t"
+ "vmla.s32 q1, q2, %q[f3] \n\t"
+ "vqshrn.s32 d2, q1, #16 \n\t"
+ "vst1.32 {d2}, [%[dst]]! \n\t"
+ : [dst] "+r" (dst), [src] "+r" (src)
+ : [f0] "w" (f0), [f1] "w" (f1), [f2] "w" (f2), [f3] "w" (f3)
+ : "memory", "q0", "q1", "q2"
+ );
+ }
+}
+
+static void remap_ch4_float32ne_neon(pa_remap_t *m, float *dst, const float *src, unsigned n) {
+ float32x4_t *f = m->state;
+ const float32x4_t f0 = f[0], f1 = f[1], f2 = f[2], f3 = f[3];
+
+ for (; n > 0; n--) {
+ __asm__ __volatile__ (
+ "vld1.32 {d0,d1}, [%[src]]! \n\t"
+ "vdup.f32 q1, d0[0] \n\t"
+ "vmul.f32 q1, q1, %q[f0] \n\t"
+ "vdup.f32 q2, d0[1] \n\t"
+ "vmla.f32 q1, q2, %q[f1] \n\t"
+ "vdup.f32 q2, d1[0] \n\t"
+ "vmla.f32 q1, q2, %q[f2] \n\t"
+ "vdup.f32 q2, d1[1] \n\t"
+ "vmla.f32 q1, q2, %q[f3] \n\t"
+ "vst1.32 {d2,d3}, [%[dst]]! \n\t"
+ : [dst] "+r" (dst), [src] "+r" (src)
+ : [f0] "w" (f0), [f1] "w" (f1), [f2] "w" (f2), [f3] "w" (f3)
+ : "memory", "q0", "q1", "q2"
+ );
+ }
+}
+
+static void remap_arrange_stereo_s16ne_neon(pa_remap_t *m, int16_t *dst, const int16_t *src, unsigned n) {
+ const uint8x8_t t = ((uint8x8_t *) m->state)[0];
+
+ for (; n >= 2; n -= 2) {
+ __asm__ __volatile__ (
+ "vld1.s16 d0, [%[src]]! \n\t"
+ "vtbl.8 d0, {d0}, %P[t] \n\t"
+ "vst1.s16 d0, [%[dst]]! \n\t"
+ : [dst] "+r" (dst), [src] "+r" (src) /* output operands */
+ : [t] "w" (t) /* input operands */
+ : "memory", "d0" /* clobber list */
+ );
+ }
+
+ if (n > 0) {
+ __asm__ __volatile__ (
+ "vld1.32 d0[0], [%[src]]! \n\t"
+ "vtbl.8 d0, {d0}, %P[t] \n\t"
+ "vst1.32 d0[0], [%[dst]]! \n\t"
+ : [dst] "+r" (dst), [src] "+r" (src) /* output operands */
+ : [t] "w" (t) /* input operands */
+ : "memory", "d0" /* clobber list */
+ );
+ }
+}
+
+static void remap_arrange_ch2_ch4_s16ne_neon(pa_remap_t *m, int16_t *dst, const int16_t *src, unsigned n) {
+ const uint8x8_t t = ((uint8x8_t *) m->state)[0];
+
+ for (; n > 0; n--) {
+ __asm__ __volatile__ (
+ "vld1.32 d0[0], [%[src]]! \n\t"
+ "vtbl.8 d0, {d0}, %P[t] \n\t"
+ "vst1.s16 d0, [%[dst]]! \n\t"
+ : [dst] "+r" (dst), [src] "+r" (src) /* output operands */
+ : [t] "w" (t) /* input operands */
+ : "memory", "d0" /* clobber list */
+ );
+ }
+}
+
+static void remap_arrange_ch4_s16ne_neon(pa_remap_t *m, int16_t *dst, const int16_t *src, unsigned n) {
+ const uint8x8_t t = ((uint8x8_t *) m->state)[0];
+
+ for (; n > 0; n--) {
+ __asm__ __volatile__ (
+ "vld1.s16 d0, [%[src]]! \n\t"
+ "vtbl.8 d0, {d0}, %P[t] \n\t"
+ "vst1.s16 d0, [%[dst]]! \n\t"
+ : [dst] "+r" (dst), [src] "+r" (src) /* output operands */
+ : [t] "w" (t) /* input operands */
+ : "memory", "d0" /* clobber list */
+ );
+ }
+}
+
+static void remap_arrange_stereo_float32ne_neon(pa_remap_t *m, float *dst, const float *src, unsigned n) {
+ const uint8x8_t t = ((uint8x8_t *)m->state)[0];
+
+ for (; n > 0; n--) {
+ __asm__ __volatile__ (
+ "vld1.f32 d0, [%[src]]! \n\t"
+ "vtbl.8 d0, {d0}, %P[t] \n\t"
+ "vst1.s16 {d0}, [%[dst]]! \n\t"
+ : [dst] "+r" (dst), [src] "+r" (src) /* output operands */
+ : [t] "w" (t) /* input operands */
+ : "memory", "d0" /* clobber list */
+ );
+ }
+}
+
+/* Works for both S32NE and FLOAT32NE */
+static void remap_arrange_ch2_ch4_any32ne_neon(pa_remap_t *m, float *dst, const float *src, unsigned n) {
+ const uint8x8_t t0 = ((uint8x8_t *)m->state)[0];
+ const uint8x8_t t1 = ((uint8x8_t *)m->state)[1];
+
+ for (; n > 0; n--) {
+ __asm__ __volatile__ (
+ "vld1.f32 d0, [%[src]]! \n\t"
+ "vtbl.8 d1, {d0}, %P[t0] \n\t"
+ "vtbl.8 d2, {d0}, %P[t1] \n\t"
+ "vst1.s16 {d1,d2}, [%[dst]]! \n\t"
+ : [dst] "+r" (dst), [src] "+r" (src) /* output operands */
+ : [t0] "w" (t0), [t1] "w" (t1) /* input operands */
+ : "memory", "d0", "d1", "d2" /* clobber list */
+ );
+ }
+}
+
+static void remap_arrange_ch4_float32ne_neon(pa_remap_t *m, float *dst, const float *src, unsigned n) {
+ const uint8x8_t t0 = ((uint8x8_t *)m->state)[0];
+ const uint8x8_t t1 = ((uint8x8_t *)m->state)[1];
+
+ for (; n > 0; n--) {
+ __asm__ __volatile__ (
+ "vld1.f32 {d0,d1}, [%[src]]! \n\t"
+ "vtbl.8 d2, {d0,d1}, %P[t0] \n\t"
+ "vtbl.8 d3, {d0,d1}, %P[t1] \n\t"
+ "vst1.s16 {d2,d3}, [%[dst]]! \n\t"
+ : [dst] "+r" (dst), [src] "+r" (src) /* output operands */
+ : [t0] "w" (t0), [t1] "w" (t1) /* input operands */
+ : "memory", "d0", "d1", "d2", "d3" /* clobber list */
+ );
+ }
+}
+
+static pa_cpu_arm_flag_t arm_flags;
+
+static void init_remap_neon(pa_remap_t *m) {
+ unsigned n_oc, n_ic;
+ int8_t arrange[PA_CHANNELS_MAX];
+
+ n_oc = m->o_ss.channels;
+ n_ic = m->i_ss.channels;
+
+ /* We short-circuit remap function selection for S32NE in most
+ * cases as the corresponding generic C code is performing
+ * similarly or even better. However there are a few cases where
+ * there actually is a significant improvement from using
+ * hand-crafted NEON assembly so we cannot just bail out for S32NE
+ * here. */
+ if (n_ic == 1 && n_oc == 2 &&
+ m->map_table_i[0][0] == 0x10000 && m->map_table_i[1][0] == 0x10000) {
+ if (m->format == PA_SAMPLE_S32NE)
+ return;
+ if (arm_flags & PA_CPU_ARM_CORTEX_A8) {
+
+ pa_log_info("Using ARM NEON/A8 mono to stereo remapping");
+ pa_set_remap_func(m, (pa_do_remap_func_t) remap_mono_to_stereo_s16ne_neon,
+ NULL, (pa_do_remap_func_t) remap_mono_to_stereo_float32ne_neon_a8);
+ }
+ else {
+ pa_log_info("Using ARM NEON mono to stereo remapping");
+ pa_set_remap_func(m, (pa_do_remap_func_t) remap_mono_to_stereo_s16ne_neon,
+ NULL, (pa_do_remap_func_t) remap_mono_to_stereo_float32ne_generic_arm);
+ }
+ } else if (n_ic == 1 && n_oc == 4 &&
+ m->map_table_i[0][0] == 0x10000 && m->map_table_i[1][0] == 0x10000 &&
+ m->map_table_i[2][0] == 0x10000 && m->map_table_i[3][0] == 0x10000) {
+
+ if (m->format == PA_SAMPLE_S32NE)
+ return;
+ pa_log_info("Using ARM NEON mono to 4-channel remapping");
+ pa_set_remap_func(m, (pa_do_remap_func_t) remap_mono_to_ch4_s16ne_neon,
+ NULL, (pa_do_remap_func_t) remap_mono_to_ch4_float32ne_neon);
+ } else if (n_ic == 2 && n_oc == 1 &&
+ m->map_table_i[0][0] == 0x8000 && m->map_table_i[0][1] == 0x8000) {
+
+ pa_log_info("Using ARM NEON stereo to mono remapping");
+ pa_set_remap_func(m, (pa_do_remap_func_t) remap_stereo_to_mono_s16ne_neon,
+ (pa_do_remap_func_t) remap_stereo_to_mono_s32ne_neon,
+ (pa_do_remap_func_t) remap_stereo_to_mono_float32ne_neon);
+ } else if (n_ic == 4 && n_oc == 1 &&
+ m->map_table_i[0][0] == 0x4000 && m->map_table_i[0][1] == 0x4000 &&
+ m->map_table_i[0][2] == 0x4000 && m->map_table_i[0][3] == 0x4000) {
+
+ if (m->format == PA_SAMPLE_S32NE)
+ return;
+ pa_log_info("Using ARM NEON 4-channel to mono remapping");
+ pa_set_remap_func(m, (pa_do_remap_func_t) remap_ch4_to_mono_s16ne_neon,
+ NULL, (pa_do_remap_func_t) remap_ch4_to_mono_float32ne_neon);
+ } else if (pa_setup_remap_arrange(m, arrange) &&
+ ((n_ic == 2 && n_oc == 2) ||
+ (n_ic == 2 && n_oc == 4) ||
+ (n_ic == 4 && n_oc == 4))) {
+ unsigned o;
+
+ if (n_ic == 2 && n_oc == 2) {
+ if (m->format == PA_SAMPLE_S32NE)
+ return;
+ pa_log_info("Using NEON stereo arrange remapping");
+ pa_set_remap_func(m, (pa_do_remap_func_t) remap_arrange_stereo_s16ne_neon,
+ NULL, (pa_do_remap_func_t) remap_arrange_stereo_float32ne_neon);
+ } else if (n_ic == 2 && n_oc == 4) {
+ pa_log_info("Using NEON 2-channel to 4-channel arrange remapping");
+ pa_set_remap_func(m, (pa_do_remap_func_t) remap_arrange_ch2_ch4_s16ne_neon,
+ (pa_do_remap_func_t) remap_arrange_ch2_ch4_any32ne_neon,
+ (pa_do_remap_func_t) remap_arrange_ch2_ch4_any32ne_neon);
+ } else if (n_ic == 4 && n_oc == 4) {
+ if (m->format == PA_SAMPLE_S32NE)
+ return;
+ pa_log_info("Using NEON 4-channel arrange remapping");
+ pa_set_remap_func(m, (pa_do_remap_func_t) remap_arrange_ch4_s16ne_neon,
+ NULL, (pa_do_remap_func_t) remap_arrange_ch4_float32ne_neon);
+ }
+
+ /* setup state */
+ switch (m->format) {
+ case PA_SAMPLE_S16NE: {
+ uint8x8_t *t = m->state = pa_xnew0(uint8x8_t, 1);
+ for (o = 0; o < 4; o++) {
+ if (arrange[o % n_oc] >= 0) {
+ /* convert channel index to vtbl indices */
+ unsigned frame = o / n_oc;
+ ((uint8_t *) t)[o * 2 + 0] = (frame * n_oc + arrange[o % n_oc]) * 2 + 0;
+ ((uint8_t *) t)[o * 2 + 1] = (frame * n_oc + arrange[o % n_oc]) * 2 + 1;
+ } else {
+ /* use invalid table indices to map to 0 */
+ ((uint8_t *) t)[o * 2 + 0] = 0xff;
+ ((uint8_t *) t)[o * 2 + 1] = 0xff;
+ }
+ }
+ break;
+ }
+ case PA_SAMPLE_S32NE:
+ /* fall-through */
+ case PA_SAMPLE_FLOAT32NE: {
+ uint8x8_t *t = m->state = pa_xnew0(uint8x8_t, 2);
+ for (o = 0; o < n_oc; o++) {
+ if (arrange[o] >= 0) {
+ /* convert channel index to vtbl indices */
+ ((uint8_t *) t)[o * 4 + 0] = arrange[o] * 4 + 0;
+ ((uint8_t *) t)[o * 4 + 1] = arrange[o] * 4 + 1;
+ ((uint8_t *) t)[o * 4 + 2] = arrange[o] * 4 + 2;
+ ((uint8_t *) t)[o * 4 + 3] = arrange[o] * 4 + 3;
+ } else {
+ /* use invalid table indices to map to 0 */
+ ((uint8_t *) t)[o * 4 + 0] = 0xff;
+ ((uint8_t *) t)[o * 4 + 1] = 0xff;
+ ((uint8_t *) t)[o * 4 + 2] = 0xff;
+ ((uint8_t *) t)[o * 4 + 3] = 0xff;
+ }
+ }
+ break;
+ }
+ default:
+ pa_assert_not_reached();
+ }
+ } else if (n_ic == 4 && n_oc == 4) {
+ unsigned i, o;
+
+ if (m->format == PA_SAMPLE_S32NE)
+ return;
+ pa_log_info("Using ARM NEON 4-channel remapping");
+ pa_set_remap_func(m, (pa_do_remap_func_t) remap_ch4_s16ne_neon,
+ (pa_do_remap_func_t) NULL,
+ (pa_do_remap_func_t) remap_ch4_float32ne_neon);
+
+ /* setup state */
+ switch (m->format) {
+ case PA_SAMPLE_S16NE: {
+ int32x4_t *f = m->state = pa_xnew0(int32x4_t, 4);
+ for (o = 0; o < 4; o++) {
+ for (i = 0; i < 4; i++) {
+ ((int *) &f[i])[o] = PA_CLAMP_UNLIKELY(m->map_table_i[o][i], 0, 0x10000);
+ }
+ }
+ break;
+ }
+ case PA_SAMPLE_FLOAT32NE: {
+ float32x4_t *f = m->state = pa_xnew0(float32x4_t, 4);
+ for (o = 0; o < 4; o++) {
+ for (i = 0; i < 4; i++) {
+ ((float *) &f[i])[o] = PA_CLAMP_UNLIKELY(m->map_table_f[o][i], 0.0f, 1.0f);
+ }
+ }
+ break;
+ }
+ default:
+ pa_assert_not_reached();
+ }
+ }
+}
+
+void pa_remap_func_init_neon(pa_cpu_arm_flag_t flags) {
+ pa_log_info("Initialising ARM NEON optimized remappers.");
+ arm_flags = flags;
+ pa_set_init_remap_func((pa_init_remap_func_t) init_remap_neon);
+}
diff --git a/src/pulsecore/remap_sse.c b/src/pulsecore/remap_sse.c
new file mode 100644
index 0000000..5c3b931
--- /dev/null
+++ b/src/pulsecore/remap_sse.c
@@ -0,0 +1,153 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2009 Wim Taymans <wim.taymans@collabora.co.uk.com>
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulse/sample.h>
+#include <pulse/volume.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+
+#include "cpu-x86.h"
+#include "remap.h"
+
+#define LOAD_SAMPLES \
+ " movdqu (%1), %%xmm0 \n\t" \
+ " movdqu 16(%1), %%xmm2 \n\t" \
+ " movdqu 32(%1), %%xmm4 \n\t" \
+ " movdqu 48(%1), %%xmm6 \n\t" \
+ " movdqa %%xmm0, %%xmm1 \n\t" \
+ " movdqa %%xmm2, %%xmm3 \n\t" \
+ " movdqa %%xmm4, %%xmm5 \n\t" \
+ " movdqa %%xmm6, %%xmm7 \n\t"
+
+#define UNPACK_SAMPLES(s) \
+ " punpckl"#s" %%xmm0, %%xmm0 \n\t" \
+ " punpckh"#s" %%xmm1, %%xmm1 \n\t" \
+ " punpckl"#s" %%xmm2, %%xmm2 \n\t" \
+ " punpckh"#s" %%xmm3, %%xmm3 \n\t" \
+ " punpckl"#s" %%xmm4, %%xmm4 \n\t" \
+ " punpckh"#s" %%xmm5, %%xmm5 \n\t" \
+ " punpckl"#s" %%xmm6, %%xmm6 \n\t" \
+ " punpckh"#s" %%xmm7, %%xmm7 \n\t"
+
+#define STORE_SAMPLES \
+ " movdqu %%xmm0, (%0) \n\t" \
+ " movdqu %%xmm1, 16(%0) \n\t" \
+ " movdqu %%xmm2, 32(%0) \n\t" \
+ " movdqu %%xmm3, 48(%0) \n\t" \
+ " movdqu %%xmm4, 64(%0) \n\t" \
+ " movdqu %%xmm5, 80(%0) \n\t" \
+ " movdqu %%xmm6, 96(%0) \n\t" \
+ " movdqu %%xmm7, 112(%0) \n\t" \
+ " add $64, %1 \n\t" \
+ " add $128, %0 \n\t"
+
+#define HANDLE_SINGLE_dq() \
+ " movd (%1), %%xmm0 \n\t" \
+ " punpckldq %%xmm0, %%xmm0 \n\t" \
+ " movq %%xmm0, (%0) \n\t" \
+ " add $4, %1 \n\t" \
+ " add $8, %0 \n\t"
+
+#define HANDLE_SINGLE_wd() \
+ " movw (%1), %w3 \n\t" \
+ " movd %3, %%xmm0 \n\t" \
+ " punpcklwd %%xmm0, %%xmm0 \n\t" \
+ " movd %%xmm0, (%0) \n\t" \
+ " add $2, %1 \n\t" \
+ " add $4, %0 \n\t"
+
+#define MONO_TO_STEREO(s,shift,mask) \
+ " mov %4, %2 \n\t" \
+ " sar $"#shift", %2 \n\t" \
+ " cmp $0, %2 \n\t" \
+ " je 2f \n\t" \
+ "1: \n\t" \
+ LOAD_SAMPLES \
+ UNPACK_SAMPLES(s) \
+ STORE_SAMPLES \
+ " dec %2 \n\t" \
+ " jne 1b \n\t" \
+ "2: \n\t" \
+ " mov %4, %2 \n\t" \
+ " and $"#mask", %2 \n\t" \
+ " je 4f \n\t" \
+ "3: \n\t" \
+ HANDLE_SINGLE_##s() \
+ " dec %2 \n\t" \
+ " jne 3b \n\t" \
+ "4: \n\t"
+
+#if defined (__i386__) || defined (__amd64__)
+static void remap_mono_to_stereo_s16ne_sse2(pa_remap_t *m, int16_t *dst, const int16_t *src, unsigned n) {
+ pa_reg_x86 temp, temp2;
+
+ __asm__ __volatile__ (
+ MONO_TO_STEREO(wd, 5, 31) /* do words to doubles */
+ : "+r" (dst), "+r" (src), "=&r" (temp), "=&r" (temp2)
+ : "r" ((pa_reg_x86)n)
+ : "cc"
+ );
+}
+
+/* Works for both S32NE and FLOAT32NE */
+static void remap_mono_to_stereo_any32ne_sse2(pa_remap_t *m, float *dst, const float *src, unsigned n) {
+ pa_reg_x86 temp, temp2;
+
+ __asm__ __volatile__ (
+ MONO_TO_STEREO(dq, 4, 15) /* do doubles to quads */
+ : "+r" (dst), "+r" (src), "=&r" (temp), "=&r" (temp2)
+ : "r" ((pa_reg_x86)n)
+ : "cc"
+ );
+}
+
+/* set the function that will execute the remapping based on the matrices */
+static void init_remap_sse2(pa_remap_t *m) {
+ unsigned n_oc, n_ic;
+
+ n_oc = m->o_ss.channels;
+ n_ic = m->i_ss.channels;
+
+ /* find some common channel remappings, fall back to full matrix operation. */
+ if (n_ic == 1 && n_oc == 2 &&
+ m->map_table_i[0][0] == 0x10000 && m->map_table_i[1][0] == 0x10000) {
+
+ pa_log_info("Using SSE2 mono to stereo remapping");
+ pa_set_remap_func(m, (pa_do_remap_func_t) remap_mono_to_stereo_s16ne_sse2,
+ (pa_do_remap_func_t) remap_mono_to_stereo_any32ne_sse2,
+ (pa_do_remap_func_t) remap_mono_to_stereo_any32ne_sse2);
+ }
+}
+#endif /* defined (__i386__) || defined (__amd64__) */
+
+void pa_remap_func_init_sse(pa_cpu_x86_flag_t flags) {
+#if defined (__i386__) || defined (__amd64__)
+
+ if (flags & PA_CPU_X86_SSE2) {
+ pa_log_info("Initialising SSE2 optimized remappers.");
+ pa_set_init_remap_func ((pa_init_remap_func_t) init_remap_sse2);
+ }
+
+#endif /* defined (__i386__) || defined (__amd64__) */
+}
diff --git a/src/pulsecore/resampler.c b/src/pulsecore/resampler.c
new file mode 100644
index 0000000..58463f1
--- /dev/null
+++ b/src/pulsecore/resampler.c
@@ -0,0 +1,1507 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/strbuf.h>
+#include <pulsecore/core-util.h>
+
+#include "resampler.h"
+
+/* Number of samples of extra space we allow the resamplers to return */
+#define EXTRA_FRAMES 128
+
+struct ffmpeg_data { /* data specific to ffmpeg */
+ struct AVResampleContext *state;
+};
+
+static int copy_init(pa_resampler *r);
+
+static void setup_remap(const pa_resampler *r, pa_remap_t *m, bool *lfe_remixed);
+static void free_remap(pa_remap_t *m);
+
+static int (* const init_table[])(pa_resampler *r) = {
+#ifdef HAVE_LIBSAMPLERATE
+ [PA_RESAMPLER_SRC_SINC_BEST_QUALITY] = pa_resampler_libsamplerate_init,
+ [PA_RESAMPLER_SRC_SINC_MEDIUM_QUALITY] = pa_resampler_libsamplerate_init,
+ [PA_RESAMPLER_SRC_SINC_FASTEST] = pa_resampler_libsamplerate_init,
+ [PA_RESAMPLER_SRC_ZERO_ORDER_HOLD] = pa_resampler_libsamplerate_init,
+ [PA_RESAMPLER_SRC_LINEAR] = pa_resampler_libsamplerate_init,
+#else
+ [PA_RESAMPLER_SRC_SINC_BEST_QUALITY] = NULL,
+ [PA_RESAMPLER_SRC_SINC_MEDIUM_QUALITY] = NULL,
+ [PA_RESAMPLER_SRC_SINC_FASTEST] = NULL,
+ [PA_RESAMPLER_SRC_ZERO_ORDER_HOLD] = NULL,
+ [PA_RESAMPLER_SRC_LINEAR] = NULL,
+#endif
+ [PA_RESAMPLER_TRIVIAL] = pa_resampler_trivial_init,
+#ifdef HAVE_SPEEX
+ [PA_RESAMPLER_SPEEX_FLOAT_BASE+0] = pa_resampler_speex_init,
+ [PA_RESAMPLER_SPEEX_FLOAT_BASE+1] = pa_resampler_speex_init,
+ [PA_RESAMPLER_SPEEX_FLOAT_BASE+2] = pa_resampler_speex_init,
+ [PA_RESAMPLER_SPEEX_FLOAT_BASE+3] = pa_resampler_speex_init,
+ [PA_RESAMPLER_SPEEX_FLOAT_BASE+4] = pa_resampler_speex_init,
+ [PA_RESAMPLER_SPEEX_FLOAT_BASE+5] = pa_resampler_speex_init,
+ [PA_RESAMPLER_SPEEX_FLOAT_BASE+6] = pa_resampler_speex_init,
+ [PA_RESAMPLER_SPEEX_FLOAT_BASE+7] = pa_resampler_speex_init,
+ [PA_RESAMPLER_SPEEX_FLOAT_BASE+8] = pa_resampler_speex_init,
+ [PA_RESAMPLER_SPEEX_FLOAT_BASE+9] = pa_resampler_speex_init,
+ [PA_RESAMPLER_SPEEX_FLOAT_BASE+10] = pa_resampler_speex_init,
+ [PA_RESAMPLER_SPEEX_FIXED_BASE+0] = pa_resampler_speex_init,
+ [PA_RESAMPLER_SPEEX_FIXED_BASE+1] = pa_resampler_speex_init,
+ [PA_RESAMPLER_SPEEX_FIXED_BASE+2] = pa_resampler_speex_init,
+ [PA_RESAMPLER_SPEEX_FIXED_BASE+3] = pa_resampler_speex_init,
+ [PA_RESAMPLER_SPEEX_FIXED_BASE+4] = pa_resampler_speex_init,
+ [PA_RESAMPLER_SPEEX_FIXED_BASE+5] = pa_resampler_speex_init,
+ [PA_RESAMPLER_SPEEX_FIXED_BASE+6] = pa_resampler_speex_init,
+ [PA_RESAMPLER_SPEEX_FIXED_BASE+7] = pa_resampler_speex_init,
+ [PA_RESAMPLER_SPEEX_FIXED_BASE+8] = pa_resampler_speex_init,
+ [PA_RESAMPLER_SPEEX_FIXED_BASE+9] = pa_resampler_speex_init,
+ [PA_RESAMPLER_SPEEX_FIXED_BASE+10] = pa_resampler_speex_init,
+#else
+ [PA_RESAMPLER_SPEEX_FLOAT_BASE+0] = NULL,
+ [PA_RESAMPLER_SPEEX_FLOAT_BASE+1] = NULL,
+ [PA_RESAMPLER_SPEEX_FLOAT_BASE+2] = NULL,
+ [PA_RESAMPLER_SPEEX_FLOAT_BASE+3] = NULL,
+ [PA_RESAMPLER_SPEEX_FLOAT_BASE+4] = NULL,
+ [PA_RESAMPLER_SPEEX_FLOAT_BASE+5] = NULL,
+ [PA_RESAMPLER_SPEEX_FLOAT_BASE+6] = NULL,
+ [PA_RESAMPLER_SPEEX_FLOAT_BASE+7] = NULL,
+ [PA_RESAMPLER_SPEEX_FLOAT_BASE+8] = NULL,
+ [PA_RESAMPLER_SPEEX_FLOAT_BASE+9] = NULL,
+ [PA_RESAMPLER_SPEEX_FLOAT_BASE+10] = NULL,
+ [PA_RESAMPLER_SPEEX_FIXED_BASE+0] = NULL,
+ [PA_RESAMPLER_SPEEX_FIXED_BASE+1] = NULL,
+ [PA_RESAMPLER_SPEEX_FIXED_BASE+2] = NULL,
+ [PA_RESAMPLER_SPEEX_FIXED_BASE+3] = NULL,
+ [PA_RESAMPLER_SPEEX_FIXED_BASE+4] = NULL,
+ [PA_RESAMPLER_SPEEX_FIXED_BASE+5] = NULL,
+ [PA_RESAMPLER_SPEEX_FIXED_BASE+6] = NULL,
+ [PA_RESAMPLER_SPEEX_FIXED_BASE+7] = NULL,
+ [PA_RESAMPLER_SPEEX_FIXED_BASE+8] = NULL,
+ [PA_RESAMPLER_SPEEX_FIXED_BASE+9] = NULL,
+ [PA_RESAMPLER_SPEEX_FIXED_BASE+10] = NULL,
+#endif
+ [PA_RESAMPLER_FFMPEG] = pa_resampler_ffmpeg_init,
+ [PA_RESAMPLER_AUTO] = NULL,
+ [PA_RESAMPLER_COPY] = copy_init,
+ [PA_RESAMPLER_PEAKS] = pa_resampler_peaks_init,
+#ifdef HAVE_SOXR
+ [PA_RESAMPLER_SOXR_MQ] = pa_resampler_soxr_init,
+ [PA_RESAMPLER_SOXR_HQ] = pa_resampler_soxr_init,
+ [PA_RESAMPLER_SOXR_VHQ] = pa_resampler_soxr_init,
+#else
+ [PA_RESAMPLER_SOXR_MQ] = NULL,
+ [PA_RESAMPLER_SOXR_HQ] = NULL,
+ [PA_RESAMPLER_SOXR_VHQ] = NULL,
+#endif
+};
+
+static pa_resample_method_t choose_auto_resampler(pa_resample_flags_t flags) {
+ pa_resample_method_t method;
+
+ if (pa_resample_method_supported(PA_RESAMPLER_SPEEX_FLOAT_BASE + 1))
+ method = PA_RESAMPLER_SPEEX_FLOAT_BASE + 1;
+ else if (flags & PA_RESAMPLER_VARIABLE_RATE)
+ method = PA_RESAMPLER_TRIVIAL;
+ else
+ method = PA_RESAMPLER_FFMPEG;
+
+ return method;
+}
+
+static pa_resample_method_t fix_method(
+ pa_resample_flags_t flags,
+ pa_resample_method_t method,
+ const uint32_t rate_a,
+ const uint32_t rate_b) {
+
+ pa_assert(pa_sample_rate_valid(rate_a));
+ pa_assert(pa_sample_rate_valid(rate_b));
+ pa_assert(method >= 0);
+ pa_assert(method < PA_RESAMPLER_MAX);
+
+ if (!(flags & PA_RESAMPLER_VARIABLE_RATE) && rate_a == rate_b) {
+ pa_log_info("Forcing resampler 'copy', because of fixed, identical sample rates.");
+ method = PA_RESAMPLER_COPY;
+ }
+
+ if (!pa_resample_method_supported(method)) {
+ pa_log_warn("Support for resampler '%s' not compiled in, reverting to 'auto'.", pa_resample_method_to_string(method));
+ method = PA_RESAMPLER_AUTO;
+ }
+
+ switch (method) {
+ case PA_RESAMPLER_COPY:
+ if (rate_a != rate_b) {
+ pa_log_info("Resampler 'copy' cannot change sampling rate, reverting to resampler 'auto'.");
+ method = PA_RESAMPLER_AUTO;
+ break;
+ }
+ /* Else fall through */
+ case PA_RESAMPLER_FFMPEG:
+ case PA_RESAMPLER_SOXR_MQ:
+ case PA_RESAMPLER_SOXR_HQ:
+ case PA_RESAMPLER_SOXR_VHQ:
+ if (flags & PA_RESAMPLER_VARIABLE_RATE) {
+ pa_log_info("Resampler '%s' cannot do variable rate, reverting to resampler 'auto'.", pa_resample_method_to_string(method));
+ method = PA_RESAMPLER_AUTO;
+ }
+ break;
+
+ /* The Peaks resampler only supports downsampling.
+ * Revert to auto if we are upsampling */
+ case PA_RESAMPLER_PEAKS:
+ if (rate_a < rate_b) {
+ pa_log_warn("The 'peaks' resampler only supports downsampling, reverting to resampler 'auto'.");
+ method = PA_RESAMPLER_AUTO;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if (method == PA_RESAMPLER_AUTO)
+ method = choose_auto_resampler(flags);
+
+#ifdef HAVE_SPEEX
+ /* At this point, method is supported in the sense that it
+ * has an init function and supports the required flags. However,
+ * speex-float implementation in PulseAudio relies on the
+ * assumption that is invalid if speex has been compiled with
+ * --enable-fixed-point. Besides, speex-fixed is more efficient
+ * in this configuration. So use it instead.
+ */
+ if (method >= PA_RESAMPLER_SPEEX_FLOAT_BASE && method <= PA_RESAMPLER_SPEEX_FLOAT_MAX) {
+ if (pa_speex_is_fixed_point()) {
+ pa_log_info("Speex appears to be compiled with --enable-fixed-point. "
+ "Switching to a fixed-point resampler because it should be faster.");
+ method = method - PA_RESAMPLER_SPEEX_FLOAT_BASE + PA_RESAMPLER_SPEEX_FIXED_BASE;
+ }
+ }
+#endif
+
+ return method;
+}
+
+/* Return true if a is a more precise sample format than b, else return false */
+static bool sample_format_more_precise(pa_sample_format_t a, pa_sample_format_t b) {
+ pa_assert(pa_sample_format_valid(a));
+ pa_assert(pa_sample_format_valid(b));
+
+ switch (a) {
+ case PA_SAMPLE_U8:
+ case PA_SAMPLE_ALAW:
+ case PA_SAMPLE_ULAW:
+ return false;
+ break;
+
+ case PA_SAMPLE_S16LE:
+ case PA_SAMPLE_S16BE:
+ if (b == PA_SAMPLE_ULAW || b == PA_SAMPLE_ALAW || b == PA_SAMPLE_U8)
+ return true;
+ else
+ return false;
+ break;
+
+ case PA_SAMPLE_S24LE:
+ case PA_SAMPLE_S24BE:
+ case PA_SAMPLE_S24_32LE:
+ case PA_SAMPLE_S24_32BE:
+ if (b == PA_SAMPLE_ULAW || b == PA_SAMPLE_ALAW || b == PA_SAMPLE_U8 ||
+ b == PA_SAMPLE_S16LE || b == PA_SAMPLE_S16BE)
+ return true;
+ else
+ return false;
+ break;
+
+ case PA_SAMPLE_FLOAT32LE:
+ case PA_SAMPLE_FLOAT32BE:
+ case PA_SAMPLE_S32LE:
+ case PA_SAMPLE_S32BE:
+ if (b == PA_SAMPLE_FLOAT32LE || b == PA_SAMPLE_FLOAT32BE ||
+ b == PA_SAMPLE_S32LE || b == PA_SAMPLE_S32BE)
+ return false;
+ else
+ return true;
+ break;
+
+ default:
+ return false;
+ }
+}
+
+static pa_sample_format_t choose_work_format(
+ pa_resample_method_t method,
+ pa_sample_format_t a,
+ pa_sample_format_t b,
+ bool map_required) {
+ pa_sample_format_t work_format;
+
+ pa_assert(pa_sample_format_valid(a));
+ pa_assert(pa_sample_format_valid(b));
+ pa_assert(method >= 0);
+ pa_assert(method < PA_RESAMPLER_MAX);
+
+ if (method >= PA_RESAMPLER_SPEEX_FIXED_BASE && method <= PA_RESAMPLER_SPEEX_FIXED_MAX)
+ method = PA_RESAMPLER_SPEEX_FIXED_BASE;
+
+ switch (method) {
+ /* This block is for resampling functions that only
+ * support the S16 sample format. */
+ case PA_RESAMPLER_SPEEX_FIXED_BASE:
+ case PA_RESAMPLER_FFMPEG:
+ work_format = PA_SAMPLE_S16NE;
+ break;
+
+ /* This block is for resampling functions that support
+ * any sample format. */
+ case PA_RESAMPLER_COPY:
+ case PA_RESAMPLER_TRIVIAL:
+ if (!map_required && a == b) {
+ work_format = a;
+ break;
+ }
+ /* If both input and output are using S32NE and we don't
+ * need any resampling we can use S32NE directly, avoiding
+ * converting back and forth between S32NE and
+ * FLOAT32NE. */
+ if ((a == PA_SAMPLE_S32NE) && (b == PA_SAMPLE_S32NE)) {
+ work_format = PA_SAMPLE_S32NE;
+ break;
+ }
+ /* Else fall through */
+ case PA_RESAMPLER_PEAKS:
+ /* PEAKS, COPY and TRIVIAL do not benefit from increased
+ * working precision, so for better performance use s16ne
+ * if either input or output fits in it. */
+ if (a == PA_SAMPLE_S16NE || b == PA_SAMPLE_S16NE) {
+ work_format = PA_SAMPLE_S16NE;
+ break;
+ }
+ /* Else fall through */
+ case PA_RESAMPLER_SOXR_MQ:
+ case PA_RESAMPLER_SOXR_HQ:
+ case PA_RESAMPLER_SOXR_VHQ:
+ /* Do processing with max precision of input and output. */
+ if (sample_format_more_precise(a, PA_SAMPLE_S16NE) ||
+ sample_format_more_precise(b, PA_SAMPLE_S16NE))
+ work_format = PA_SAMPLE_FLOAT32NE;
+ else
+ work_format = PA_SAMPLE_S16NE;
+ break;
+
+ default:
+ work_format = PA_SAMPLE_FLOAT32NE;
+ }
+
+ return work_format;
+}
+
+pa_resampler* pa_resampler_new(
+ pa_mempool *pool,
+ const pa_sample_spec *a,
+ const pa_channel_map *am,
+ const pa_sample_spec *b,
+ const pa_channel_map *bm,
+ unsigned crossover_freq,
+ pa_resample_method_t method,
+ pa_resample_flags_t flags) {
+
+ pa_resampler *r = NULL;
+ bool lfe_remixed = false;
+
+ pa_assert(pool);
+ pa_assert(a);
+ pa_assert(b);
+ pa_assert(pa_sample_spec_valid(a));
+ pa_assert(pa_sample_spec_valid(b));
+ pa_assert(method >= 0);
+ pa_assert(method < PA_RESAMPLER_MAX);
+
+ method = fix_method(flags, method, a->rate, b->rate);
+
+ r = pa_xnew0(pa_resampler, 1);
+ r->mempool = pool;
+ r->method = method;
+ r->flags = flags;
+
+ /* Fill sample specs */
+ r->i_ss = *a;
+ r->o_ss = *b;
+
+ if (am)
+ r->i_cm = *am;
+ else if (!pa_channel_map_init_auto(&r->i_cm, r->i_ss.channels, PA_CHANNEL_MAP_DEFAULT))
+ goto fail;
+
+ if (bm)
+ r->o_cm = *bm;
+ else if (!pa_channel_map_init_auto(&r->o_cm, r->o_ss.channels, PA_CHANNEL_MAP_DEFAULT))
+ goto fail;
+
+ r->i_fz = pa_frame_size(a);
+ r->o_fz = pa_frame_size(b);
+
+ r->map_required = (r->i_ss.channels != r->o_ss.channels || (!(r->flags & PA_RESAMPLER_NO_REMAP) &&
+ !pa_channel_map_equal(&r->i_cm, &r->o_cm)));
+
+ r->work_format = choose_work_format(method, a->format, b->format, r->map_required);
+ r->w_sz = pa_sample_size_of_format(r->work_format);
+
+ if (r->i_ss.format != r->work_format) {
+ if (r->work_format == PA_SAMPLE_FLOAT32NE) {
+ if (!(r->to_work_format_func = pa_get_convert_to_float32ne_function(r->i_ss.format)))
+ goto fail;
+ } else {
+ pa_assert(r->work_format == PA_SAMPLE_S16NE);
+ if (!(r->to_work_format_func = pa_get_convert_to_s16ne_function(r->i_ss.format)))
+ goto fail;
+ }
+ }
+
+ if (r->o_ss.format != r->work_format) {
+ if (r->work_format == PA_SAMPLE_FLOAT32NE) {
+ if (!(r->from_work_format_func = pa_get_convert_from_float32ne_function(r->o_ss.format)))
+ goto fail;
+ } else {
+ pa_assert(r->work_format == PA_SAMPLE_S16NE);
+ if (!(r->from_work_format_func = pa_get_convert_from_s16ne_function(r->o_ss.format)))
+ goto fail;
+ }
+ }
+
+ if (r->o_ss.channels <= r->i_ss.channels) {
+ /* pipeline is: format conv. -> remap -> resample -> format conv. */
+ r->work_channels = r->o_ss.channels;
+
+ /* leftover buffer is remap output buffer (before resampling) */
+ r->leftover_buf = &r->remap_buf;
+ r->leftover_buf_size = &r->remap_buf_size;
+ r->have_leftover = &r->leftover_in_remap;
+ } else {
+ /* pipeline is: format conv. -> resample -> remap -> format conv. */
+ r->work_channels = r->i_ss.channels;
+
+ /* leftover buffer is to_work output buffer (before resampling) */
+ r->leftover_buf = &r->to_work_format_buf;
+ r->leftover_buf_size = &r->to_work_format_buf_size;
+ r->have_leftover = &r->leftover_in_to_work;
+ }
+ r->w_fz = pa_sample_size_of_format(r->work_format) * r->work_channels;
+
+ pa_log_debug("Resampler:");
+ pa_log_debug(" rate %d -> %d (method %s)", a->rate, b->rate, pa_resample_method_to_string(r->method));
+ pa_log_debug(" format %s -> %s (intermediate %s)", pa_sample_format_to_string(a->format),
+ pa_sample_format_to_string(b->format), pa_sample_format_to_string(r->work_format));
+ pa_log_debug(" channels %d -> %d (resampling %d)", a->channels, b->channels, r->work_channels);
+
+ /* set up the remap structure */
+ if (r->map_required)
+ setup_remap(r, &r->remap, &lfe_remixed);
+
+ if (lfe_remixed && crossover_freq > 0) {
+ pa_sample_spec wss = r->o_ss;
+ wss.format = r->work_format;
+ /* FIXME: For now just hardcode maxrewind to 3 seconds */
+ r->lfe_filter = pa_lfe_filter_new(&wss, &r->o_cm, (float)crossover_freq, b->rate * 3);
+ pa_log_debug(" lfe filter activated (LR4 type), the crossover_freq = %uHz", crossover_freq);
+ }
+
+ /* initialize implementation */
+ if (init_table[method](r) < 0)
+ goto fail;
+
+ return r;
+
+fail:
+ if (r->lfe_filter)
+ pa_lfe_filter_free(r->lfe_filter);
+ pa_xfree(r);
+
+ return NULL;
+}
+
+void pa_resampler_free(pa_resampler *r) {
+ pa_assert(r);
+
+ if (r->impl.free)
+ r->impl.free(r);
+ else
+ pa_xfree(r->impl.data);
+
+ if (r->lfe_filter)
+ pa_lfe_filter_free(r->lfe_filter);
+
+ if (r->to_work_format_buf.memblock)
+ pa_memblock_unref(r->to_work_format_buf.memblock);
+ if (r->remap_buf.memblock)
+ pa_memblock_unref(r->remap_buf.memblock);
+ if (r->resample_buf.memblock)
+ pa_memblock_unref(r->resample_buf.memblock);
+ if (r->from_work_format_buf.memblock)
+ pa_memblock_unref(r->from_work_format_buf.memblock);
+
+ free_remap(&r->remap);
+
+ pa_xfree(r);
+}
+
+void pa_resampler_set_input_rate(pa_resampler *r, uint32_t rate) {
+ pa_assert(r);
+ pa_assert(rate > 0);
+ pa_assert(r->impl.update_rates);
+
+ if (r->i_ss.rate == rate)
+ return;
+
+ r->i_ss.rate = rate;
+
+ r->impl.update_rates(r);
+}
+
+void pa_resampler_set_output_rate(pa_resampler *r, uint32_t rate) {
+ pa_assert(r);
+ pa_assert(rate > 0);
+ pa_assert(r->impl.update_rates);
+
+ if (r->o_ss.rate == rate)
+ return;
+
+ r->o_ss.rate = rate;
+
+ r->impl.update_rates(r);
+
+ if (r->lfe_filter)
+ pa_lfe_filter_update_rate(r->lfe_filter, rate);
+}
+
+size_t pa_resampler_request(pa_resampler *r, size_t out_length) {
+ pa_assert(r);
+
+ /* Let's round up here to make it more likely that the caller will get at
+ * least out_length amount of data from pa_resampler_run().
+ *
+ * We don't take the leftover into account here. If we did, then it might
+ * be in theory possible that this function would return 0 and
+ * pa_resampler_run() would also return 0. That could lead to infinite
+ * loops. When the leftover is ignored here, such loops would eventually
+ * terminate, because the leftover would grow each round, finally
+ * surpassing the minimum input threshold of the resampler. */
+ return ((((uint64_t) ((out_length + r->o_fz-1) / r->o_fz) * r->i_ss.rate) + r->o_ss.rate-1) / r->o_ss.rate) * r->i_fz;
+}
+
+size_t pa_resampler_result(pa_resampler *r, size_t in_length) {
+ size_t frames;
+
+ pa_assert(r);
+
+ /* Let's round up here to ensure that the caller will always allocate big
+ * enough output buffer. */
+
+ frames = (in_length + r->i_fz - 1) / r->i_fz;
+ if (*r->have_leftover)
+ frames += r->leftover_buf->length / r->w_fz;
+
+ return (((uint64_t) frames * r->o_ss.rate + r->i_ss.rate - 1) / r->i_ss.rate) * r->o_fz;
+}
+
+size_t pa_resampler_max_block_size(pa_resampler *r) {
+ size_t block_size_max;
+ pa_sample_spec max_ss;
+ size_t max_fs;
+ size_t frames;
+
+ pa_assert(r);
+
+ block_size_max = pa_mempool_block_size_max(r->mempool);
+
+ /* We deduce the "largest" sample spec we're using during the
+ * conversion */
+ max_ss.channels = (uint8_t) (PA_MAX(r->i_ss.channels, r->o_ss.channels));
+
+ /* We silently assume that the format enum is ordered by size */
+ max_ss.format = PA_MAX(r->i_ss.format, r->o_ss.format);
+ max_ss.format = PA_MAX(max_ss.format, r->work_format);
+
+ max_ss.rate = PA_MAX(r->i_ss.rate, r->o_ss.rate);
+
+ max_fs = pa_frame_size(&max_ss);
+ frames = block_size_max / max_fs - EXTRA_FRAMES;
+
+ pa_assert(frames >= (r->leftover_buf->length / r->w_fz));
+ if (*r->have_leftover)
+ frames -= r->leftover_buf->length / r->w_fz;
+
+ block_size_max = ((uint64_t) frames * r->i_ss.rate / max_ss.rate) * r->i_fz;
+
+ if (block_size_max > 0)
+ return block_size_max;
+ else
+ /* A single input frame may result in so much output that it doesn't
+ * fit in one standard memblock (e.g. converting 1 Hz to 44100 Hz). In
+ * this case the max block size will be set to one frame, and some
+ * memory will be probably be allocated with malloc() instead of using
+ * the memory pool.
+ *
+ * XXX: Should we support this case at all? We could also refuse to
+ * create resamplers whose max block size would exceed the memory pool
+ * block size. In this case also updating the resampler rate should
+ * fail if the new rate would cause an excessive max block size (in
+ * which case the stream would probably have to be killed). */
+ return r->i_fz;
+}
+
+void pa_resampler_reset(pa_resampler *r) {
+ pa_assert(r);
+
+ if (r->impl.reset)
+ r->impl.reset(r);
+
+ if (r->lfe_filter)
+ pa_lfe_filter_reset(r->lfe_filter);
+
+ *r->have_leftover = false;
+}
+
+void pa_resampler_rewind(pa_resampler *r, size_t out_frames) {
+ pa_assert(r);
+
+ /* For now, we don't have any rewindable resamplers, so we just
+ reset the resampler instead (and hope that nobody hears the difference). */
+ if (r->impl.reset)
+ r->impl.reset(r);
+
+ if (r->lfe_filter)
+ pa_lfe_filter_rewind(r->lfe_filter, out_frames);
+
+ *r->have_leftover = false;
+}
+
+pa_resample_method_t pa_resampler_get_method(pa_resampler *r) {
+ pa_assert(r);
+
+ return r->method;
+}
+
+const pa_channel_map* pa_resampler_input_channel_map(pa_resampler *r) {
+ pa_assert(r);
+
+ return &r->i_cm;
+}
+
+const pa_sample_spec* pa_resampler_input_sample_spec(pa_resampler *r) {
+ pa_assert(r);
+
+ return &r->i_ss;
+}
+
+const pa_channel_map* pa_resampler_output_channel_map(pa_resampler *r) {
+ pa_assert(r);
+
+ return &r->o_cm;
+}
+
+const pa_sample_spec* pa_resampler_output_sample_spec(pa_resampler *r) {
+ pa_assert(r);
+
+ return &r->o_ss;
+}
+
+static const char * const resample_methods[] = {
+ "src-sinc-best-quality",
+ "src-sinc-medium-quality",
+ "src-sinc-fastest",
+ "src-zero-order-hold",
+ "src-linear",
+ "trivial",
+ "speex-float-0",
+ "speex-float-1",
+ "speex-float-2",
+ "speex-float-3",
+ "speex-float-4",
+ "speex-float-5",
+ "speex-float-6",
+ "speex-float-7",
+ "speex-float-8",
+ "speex-float-9",
+ "speex-float-10",
+ "speex-fixed-0",
+ "speex-fixed-1",
+ "speex-fixed-2",
+ "speex-fixed-3",
+ "speex-fixed-4",
+ "speex-fixed-5",
+ "speex-fixed-6",
+ "speex-fixed-7",
+ "speex-fixed-8",
+ "speex-fixed-9",
+ "speex-fixed-10",
+ "ffmpeg",
+ "auto",
+ "copy",
+ "peaks",
+ "soxr-mq",
+ "soxr-hq",
+ "soxr-vhq"
+};
+
+const char *pa_resample_method_to_string(pa_resample_method_t m) {
+
+ if (m < 0 || m >= PA_RESAMPLER_MAX)
+ return NULL;
+
+ return resample_methods[m];
+}
+
+int pa_resample_method_supported(pa_resample_method_t m) {
+
+ if (m < 0 || m >= PA_RESAMPLER_MAX)
+ return 0;
+
+#ifndef HAVE_LIBSAMPLERATE
+ if (m <= PA_RESAMPLER_SRC_LINEAR)
+ return 0;
+#endif
+
+#ifndef HAVE_SPEEX
+ if (m >= PA_RESAMPLER_SPEEX_FLOAT_BASE && m <= PA_RESAMPLER_SPEEX_FLOAT_MAX)
+ return 0;
+ if (m >= PA_RESAMPLER_SPEEX_FIXED_BASE && m <= PA_RESAMPLER_SPEEX_FIXED_MAX)
+ return 0;
+#endif
+
+#ifndef HAVE_SOXR
+ if (m >= PA_RESAMPLER_SOXR_MQ && m <= PA_RESAMPLER_SOXR_VHQ)
+ return 0;
+#endif
+
+ return 1;
+}
+
+pa_resample_method_t pa_parse_resample_method(const char *string) {
+ pa_resample_method_t m;
+
+ pa_assert(string);
+
+ for (m = 0; m < PA_RESAMPLER_MAX; m++)
+ if (pa_streq(string, resample_methods[m]))
+ return m;
+
+ if (pa_streq(string, "speex-fixed"))
+ return PA_RESAMPLER_SPEEX_FIXED_BASE + 1;
+
+ if (pa_streq(string, "speex-float"))
+ return PA_RESAMPLER_SPEEX_FLOAT_BASE + 1;
+
+ return PA_RESAMPLER_INVALID;
+}
+
+static bool on_left(pa_channel_position_t p) {
+
+ return
+ p == PA_CHANNEL_POSITION_FRONT_LEFT ||
+ p == PA_CHANNEL_POSITION_REAR_LEFT ||
+ p == PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER ||
+ p == PA_CHANNEL_POSITION_SIDE_LEFT ||
+ p == PA_CHANNEL_POSITION_TOP_FRONT_LEFT ||
+ p == PA_CHANNEL_POSITION_TOP_REAR_LEFT;
+}
+
+static bool on_right(pa_channel_position_t p) {
+
+ return
+ p == PA_CHANNEL_POSITION_FRONT_RIGHT ||
+ p == PA_CHANNEL_POSITION_REAR_RIGHT ||
+ p == PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER ||
+ p == PA_CHANNEL_POSITION_SIDE_RIGHT ||
+ p == PA_CHANNEL_POSITION_TOP_FRONT_RIGHT ||
+ p == PA_CHANNEL_POSITION_TOP_REAR_RIGHT;
+}
+
+static bool on_center(pa_channel_position_t p) {
+
+ return
+ p == PA_CHANNEL_POSITION_FRONT_CENTER ||
+ p == PA_CHANNEL_POSITION_REAR_CENTER ||
+ p == PA_CHANNEL_POSITION_TOP_CENTER ||
+ p == PA_CHANNEL_POSITION_TOP_FRONT_CENTER ||
+ p == PA_CHANNEL_POSITION_TOP_REAR_CENTER;
+}
+
+static bool on_lfe(pa_channel_position_t p) {
+ return
+ p == PA_CHANNEL_POSITION_LFE;
+}
+
+static bool on_front(pa_channel_position_t p) {
+ return
+ p == PA_CHANNEL_POSITION_FRONT_LEFT ||
+ p == PA_CHANNEL_POSITION_FRONT_RIGHT ||
+ p == PA_CHANNEL_POSITION_FRONT_CENTER ||
+ p == PA_CHANNEL_POSITION_TOP_FRONT_LEFT ||
+ p == PA_CHANNEL_POSITION_TOP_FRONT_RIGHT ||
+ p == PA_CHANNEL_POSITION_TOP_FRONT_CENTER ||
+ p == PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER ||
+ p == PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER;
+}
+
+static bool on_rear(pa_channel_position_t p) {
+ return
+ p == PA_CHANNEL_POSITION_REAR_LEFT ||
+ p == PA_CHANNEL_POSITION_REAR_RIGHT ||
+ p == PA_CHANNEL_POSITION_REAR_CENTER ||
+ p == PA_CHANNEL_POSITION_TOP_REAR_LEFT ||
+ p == PA_CHANNEL_POSITION_TOP_REAR_RIGHT ||
+ p == PA_CHANNEL_POSITION_TOP_REAR_CENTER;
+}
+
+static bool on_side(pa_channel_position_t p) {
+ return
+ p == PA_CHANNEL_POSITION_SIDE_LEFT ||
+ p == PA_CHANNEL_POSITION_SIDE_RIGHT ||
+ p == PA_CHANNEL_POSITION_TOP_CENTER;
+}
+
+enum {
+ ON_FRONT,
+ ON_REAR,
+ ON_SIDE,
+ ON_OTHER
+};
+
+static int front_rear_side(pa_channel_position_t p) {
+ if (on_front(p))
+ return ON_FRONT;
+ if (on_rear(p))
+ return ON_REAR;
+ if (on_side(p))
+ return ON_SIDE;
+ return ON_OTHER;
+}
+
+/* Fill a map of which output channels should get mono from input, not including
+ * LFE output channels. (The LFE output channels are mapped separately.)
+ */
+static void setup_oc_mono_map(const pa_resampler *r, float *oc_mono_map) {
+ unsigned oc;
+ unsigned n_oc;
+ bool found_oc_for_mono = false;
+
+ pa_assert(r);
+ pa_assert(oc_mono_map);
+
+ n_oc = r->o_ss.channels;
+
+ if (!(r->flags & PA_RESAMPLER_NO_FILL_SINK)) {
+ /* Mono goes to all non-LFE output channels and we're done. */
+ for (oc = 0; oc < n_oc; oc++)
+ oc_mono_map[oc] = on_lfe(r->o_cm.map[oc]) ? 0.0f : 1.0f;
+ return;
+ } else {
+ /* Initialize to all zero so we can select individual channels below. */
+ for (oc = 0; oc < n_oc; oc++)
+ oc_mono_map[oc] = 0.0f;
+ }
+
+ for (oc = 0; oc < n_oc; oc++) {
+ if (r->o_cm.map[oc] == PA_CHANNEL_POSITION_MONO) {
+ oc_mono_map[oc] = 1.0f;
+ found_oc_for_mono = true;
+ }
+ }
+ if (found_oc_for_mono)
+ return;
+
+ for (oc = 0; oc < n_oc; oc++) {
+ if (r->o_cm.map[oc] == PA_CHANNEL_POSITION_FRONT_CENTER) {
+ oc_mono_map[oc] = 1.0f;
+ found_oc_for_mono = true;
+ }
+ }
+ if (found_oc_for_mono)
+ return;
+
+ for (oc = 0; oc < n_oc; oc++) {
+ if (r->o_cm.map[oc] == PA_CHANNEL_POSITION_FRONT_LEFT || r->o_cm.map[oc] == PA_CHANNEL_POSITION_FRONT_RIGHT) {
+ oc_mono_map[oc] = 1.0f;
+ found_oc_for_mono = true;
+ }
+ }
+ if (found_oc_for_mono)
+ return;
+
+ /* Give up on finding a suitable map for mono, and just send it to all
+ * non-LFE output channels.
+ */
+ for (oc = 0; oc < n_oc; oc++)
+ oc_mono_map[oc] = on_lfe(r->o_cm.map[oc]) ? 0.0f : 1.0f;
+}
+
+static void setup_remap(const pa_resampler *r, pa_remap_t *m, bool *lfe_remixed) {
+ unsigned oc, ic;
+ unsigned n_oc, n_ic;
+ bool ic_connected[PA_CHANNELS_MAX];
+ pa_strbuf *s;
+ char *t;
+
+ pa_assert(r);
+ pa_assert(m);
+ pa_assert(lfe_remixed);
+
+ n_oc = r->o_ss.channels;
+ n_ic = r->i_ss.channels;
+
+ m->format = r->work_format;
+ m->i_ss = r->i_ss;
+ m->o_ss = r->o_ss;
+
+ memset(m->map_table_f, 0, sizeof(m->map_table_f));
+ memset(m->map_table_i, 0, sizeof(m->map_table_i));
+
+ memset(ic_connected, 0, sizeof(ic_connected));
+ *lfe_remixed = false;
+
+ if (r->flags & PA_RESAMPLER_NO_REMAP) {
+ for (oc = 0; oc < PA_MIN(n_ic, n_oc); oc++)
+ m->map_table_f[oc][oc] = 1.0f;
+
+ } else if (r->flags & PA_RESAMPLER_NO_REMIX) {
+ for (oc = 0; oc < n_oc; oc++) {
+ pa_channel_position_t b = r->o_cm.map[oc];
+
+ for (ic = 0; ic < n_ic; ic++) {
+ pa_channel_position_t a = r->i_cm.map[ic];
+
+ /* We shall not do any remixing. Hence, just check by name */
+ if (a == b)
+ m->map_table_f[oc][ic] = 1.0f;
+ }
+ }
+ } else {
+
+ /* OK, we shall do the full monty: upmixing and downmixing. Our
+ * algorithm is relatively simple, does not do spacialization, or delay
+ * elements. LFE filters are done after the remap step. Patches are always
+ * welcome, though. Oh, and it doesn't do any matrix decoding. (Which
+ * probably wouldn't make any sense anyway.)
+ *
+ * This code is not idempotent: downmixing an upmixed stereo stream is
+ * not identical to the original. The volume will not match, and the
+ * two channels will be a linear combination of both.
+ *
+ * This is loosely based on random suggestions found on the Internet,
+ * such as this:
+ * http://www.halfgaar.net/surround-sound-in-linux and the alsa upmix
+ * plugin.
+ *
+ * The algorithm works basically like this:
+ *
+ * 1) Connect all channels with matching names.
+ * This also includes fixing confusion between "5.1" and
+ * "5.1 (Side)" layouts, done by mpv.
+ *
+ * 2) Mono Handling:
+ * S:Mono: See setup_oc_mono_map().
+ * D:Mono: Avg all S:channels
+ *
+ * 3) Mix D:Left, D:Right (if PA_RESAMPLER_NO_FILL_SINK is clear):
+ * D:Left: If not connected, avg all S:Left
+ * D:Right: If not connected, avg all S:Right
+ *
+ * 4) Mix D:Center (if PA_RESAMPLER_NO_FILL_SINK is clear):
+ * If not connected, avg all S:Center
+ * If still not connected, avg all S:Left, S:Right
+ *
+ * 5) Mix D:LFE
+ * If not connected, avg all S:*
+ *
+ * 6) Make sure S:Left/S:Right is used: S:Left/S:Right: If not
+ * connected, mix into all D:left and all D:right channels. Gain is
+ * 1/9.
+ *
+ * 7) Make sure S:Center, S:LFE is used:
+ *
+ * S:Center, S:LFE: If not connected, mix into all D:left, all
+ * D:right, all D:center channels. Gain is 0.5 for center and 0.375
+ * for LFE. C-front is only mixed into L-front/R-front if available,
+ * otherwise into all L/R channels. Similarly for C-rear.
+ *
+ * 8) Normalize each row in the matrix such that the sum for each row is
+ * not larger than 1.0 in order to avoid clipping.
+ *
+ * S: and D: shall relate to the source resp. destination channels.
+ *
+ * Rationale: 1, 2 are probably obvious. For 3: this copies front to
+ * rear if needed. For 4: we try to find some suitable C source for C,
+ * if we don't find any, we avg L and R. For 5: LFE is mixed from all
+ * channels. For 6: the rear channels should not be dropped entirely,
+ * however have only minimal impact. For 7: movies usually encode
+ * speech on the center channel. Thus we have to make sure this channel
+ * is distributed to L and R if not available in the output. Also, LFE
+ * is used to achieve a greater dynamic range, and thus we should try
+ * to do our best to pass it to L+R.
+ */
+
+ unsigned
+ ic_left = 0,
+ ic_right = 0,
+ ic_center = 0,
+ ic_unconnected_left = 0,
+ ic_unconnected_right = 0,
+ ic_unconnected_center = 0,
+ ic_unconnected_lfe = 0;
+ bool ic_unconnected_center_mixed_in = 0;
+ float oc_mono_map[PA_CHANNELS_MAX];
+
+ for (ic = 0; ic < n_ic; ic++) {
+ if (on_left(r->i_cm.map[ic]))
+ ic_left++;
+ if (on_right(r->i_cm.map[ic]))
+ ic_right++;
+ if (on_center(r->i_cm.map[ic]))
+ ic_center++;
+ }
+
+ setup_oc_mono_map(r, oc_mono_map);
+
+ for (oc = 0; oc < n_oc; oc++) {
+ bool oc_connected = false;
+ pa_channel_position_t b = r->o_cm.map[oc];
+
+ for (ic = 0; ic < n_ic; ic++) {
+ pa_channel_position_t a = r->i_cm.map[ic];
+
+ if (a == b) {
+ m->map_table_f[oc][ic] = 1.0f;
+
+ oc_connected = true;
+ ic_connected[ic] = true;
+ }
+ else if (a == PA_CHANNEL_POSITION_MONO && oc_mono_map[oc] > 0.0f) {
+ m->map_table_f[oc][ic] = oc_mono_map[oc];
+
+ oc_connected = true;
+ ic_connected[ic] = true;
+ }
+ else if (b == PA_CHANNEL_POSITION_MONO) {
+ m->map_table_f[oc][ic] = 1.0f / (float) n_ic;
+
+ oc_connected = true;
+ ic_connected[ic] = true;
+ }
+ }
+
+ if (!oc_connected) {
+ /* Maybe it is due to 5.1 rear/side confustion? */
+ for (ic = 0; ic < n_ic; ic++) {
+ pa_channel_position_t a = r->i_cm.map[ic];
+ if (ic_connected[ic])
+ continue;
+
+ if ((a == PA_CHANNEL_POSITION_REAR_LEFT && b == PA_CHANNEL_POSITION_SIDE_LEFT) ||
+ (a == PA_CHANNEL_POSITION_SIDE_LEFT && b == PA_CHANNEL_POSITION_REAR_LEFT) ||
+ (a == PA_CHANNEL_POSITION_REAR_RIGHT && b == PA_CHANNEL_POSITION_SIDE_RIGHT) ||
+ (a == PA_CHANNEL_POSITION_SIDE_RIGHT && b == PA_CHANNEL_POSITION_REAR_RIGHT)) {
+
+ m->map_table_f[oc][ic] = 1.0f;
+
+ oc_connected = true;
+ ic_connected[ic] = true;
+ }
+ }
+ }
+
+ if (!oc_connected) {
+ /* Try to find matching input ports for this output port */
+
+ if (on_left(b) && !(r->flags & PA_RESAMPLER_NO_FILL_SINK)) {
+
+ /* We are not connected and on the left side, let's
+ * average all left side input channels. */
+
+ if (ic_left > 0)
+ for (ic = 0; ic < n_ic; ic++)
+ if (on_left(r->i_cm.map[ic])) {
+ m->map_table_f[oc][ic] = 1.0f / (float) ic_left;
+ ic_connected[ic] = true;
+ }
+
+ /* We ignore the case where there is no left input channel.
+ * Something is really wrong in this case anyway. */
+
+ } else if (on_right(b) && !(r->flags & PA_RESAMPLER_NO_FILL_SINK)) {
+
+ /* We are not connected and on the right side, let's
+ * average all right side input channels. */
+
+ if (ic_right > 0)
+ for (ic = 0; ic < n_ic; ic++)
+ if (on_right(r->i_cm.map[ic])) {
+ m->map_table_f[oc][ic] = 1.0f / (float) ic_right;
+ ic_connected[ic] = true;
+ }
+
+ /* We ignore the case where there is no right input
+ * channel. Something is really wrong in this case anyway.
+ * */
+
+ } else if (on_center(b) && !(r->flags & PA_RESAMPLER_NO_FILL_SINK)) {
+
+ if (ic_center > 0) {
+
+ /* We are not connected and at the center. Let's average
+ * all center input channels. */
+
+ for (ic = 0; ic < n_ic; ic++)
+ if (on_center(r->i_cm.map[ic])) {
+ m->map_table_f[oc][ic] = 1.0f / (float) ic_center;
+ ic_connected[ic] = true;
+ }
+
+ } else if (ic_left + ic_right > 0) {
+
+ /* Hmm, no center channel around, let's synthesize it
+ * by mixing L and R.*/
+
+ for (ic = 0; ic < n_ic; ic++)
+ if (on_left(r->i_cm.map[ic]) || on_right(r->i_cm.map[ic])) {
+ m->map_table_f[oc][ic] = 1.0f / (float) (ic_left + ic_right);
+ ic_connected[ic] = true;
+ }
+ }
+
+ /* We ignore the case where there is not even a left or
+ * right input channel. Something is really wrong in this
+ * case anyway. */
+
+ } else if (on_lfe(b) && (r->flags & PA_RESAMPLER_PRODUCE_LFE)) {
+
+ /* We are not connected and an LFE. Let's average all
+ * channels for LFE. */
+
+ for (ic = 0; ic < n_ic; ic++)
+ m->map_table_f[oc][ic] = 1.0f / (float) n_ic;
+
+ /* Please note that a channel connected to LFE doesn't
+ * really count as connected. */
+
+ *lfe_remixed = true;
+ }
+ }
+ }
+
+ for (ic = 0; ic < n_ic; ic++) {
+ pa_channel_position_t a = r->i_cm.map[ic];
+
+ if (ic_connected[ic])
+ continue;
+
+ if (on_left(a))
+ ic_unconnected_left++;
+ else if (on_right(a))
+ ic_unconnected_right++;
+ else if (on_center(a))
+ ic_unconnected_center++;
+ else if (on_lfe(a))
+ ic_unconnected_lfe++;
+ }
+
+ for (ic = 0; ic < n_ic; ic++) {
+ pa_channel_position_t a = r->i_cm.map[ic];
+
+ if (ic_connected[ic])
+ continue;
+
+ for (oc = 0; oc < n_oc; oc++) {
+ pa_channel_position_t b = r->o_cm.map[oc];
+
+ if (on_left(a) && on_left(b))
+ m->map_table_f[oc][ic] = (1.f/9.f) / (float) ic_unconnected_left;
+
+ else if (on_right(a) && on_right(b))
+ m->map_table_f[oc][ic] = (1.f/9.f) / (float) ic_unconnected_right;
+
+ else if (on_center(a) && on_center(b)) {
+ m->map_table_f[oc][ic] = (1.f/9.f) / (float) ic_unconnected_center;
+ ic_unconnected_center_mixed_in = true;
+
+ } else if (on_lfe(a) && (r->flags & PA_RESAMPLER_CONSUME_LFE))
+ m->map_table_f[oc][ic] = .375f / (float) ic_unconnected_lfe;
+ }
+ }
+
+ if (ic_unconnected_center > 0 && !ic_unconnected_center_mixed_in) {
+ unsigned ncenter[PA_CHANNELS_MAX];
+ bool found_frs[PA_CHANNELS_MAX];
+
+ memset(ncenter, 0, sizeof(ncenter));
+ memset(found_frs, 0, sizeof(found_frs));
+
+ /* Hmm, as it appears there was no center channel we
+ could mix our center channel in. In this case, mix it into
+ left and right. Using .5 as the factor. */
+
+ for (ic = 0; ic < n_ic; ic++) {
+
+ if (ic_connected[ic])
+ continue;
+
+ if (!on_center(r->i_cm.map[ic]))
+ continue;
+
+ for (oc = 0; oc < n_oc; oc++) {
+
+ if (!on_left(r->o_cm.map[oc]) && !on_right(r->o_cm.map[oc]))
+ continue;
+
+ if (front_rear_side(r->i_cm.map[ic]) == front_rear_side(r->o_cm.map[oc])) {
+ found_frs[ic] = true;
+ break;
+ }
+ }
+
+ for (oc = 0; oc < n_oc; oc++) {
+
+ if (!on_left(r->o_cm.map[oc]) && !on_right(r->o_cm.map[oc]))
+ continue;
+
+ if (!found_frs[ic] || front_rear_side(r->i_cm.map[ic]) == front_rear_side(r->o_cm.map[oc]))
+ ncenter[oc]++;
+ }
+ }
+
+ for (oc = 0; oc < n_oc; oc++) {
+
+ if (!on_left(r->o_cm.map[oc]) && !on_right(r->o_cm.map[oc]))
+ continue;
+
+ if (ncenter[oc] <= 0)
+ continue;
+
+ for (ic = 0; ic < n_ic; ic++) {
+
+ if (!on_center(r->i_cm.map[ic]))
+ continue;
+
+ if (!found_frs[ic] || front_rear_side(r->i_cm.map[ic]) == front_rear_side(r->o_cm.map[oc]))
+ m->map_table_f[oc][ic] = .5f / (float) ncenter[oc];
+ }
+ }
+ }
+ }
+
+ for (oc = 0; oc < n_oc; oc++) {
+ float sum = 0.0f;
+ for (ic = 0; ic < n_ic; ic++)
+ sum += m->map_table_f[oc][ic];
+
+ if (sum > 1.0f)
+ for (ic = 0; ic < n_ic; ic++)
+ m->map_table_f[oc][ic] /= sum;
+ }
+
+ /* make an 16:16 int version of the matrix */
+ for (oc = 0; oc < n_oc; oc++)
+ for (ic = 0; ic < n_ic; ic++)
+ m->map_table_i[oc][ic] = (int32_t) (m->map_table_f[oc][ic] * 0x10000);
+
+ s = pa_strbuf_new();
+
+ pa_strbuf_printf(s, " ");
+ for (ic = 0; ic < n_ic; ic++)
+ pa_strbuf_printf(s, " I%02u ", ic);
+ pa_strbuf_puts(s, "\n +");
+
+ for (ic = 0; ic < n_ic; ic++)
+ pa_strbuf_printf(s, "------");
+ pa_strbuf_puts(s, "\n");
+
+ for (oc = 0; oc < n_oc; oc++) {
+ pa_strbuf_printf(s, "O%02u |", oc);
+
+ for (ic = 0; ic < n_ic; ic++)
+ pa_strbuf_printf(s, " %1.3f", m->map_table_f[oc][ic]);
+
+ pa_strbuf_puts(s, "\n");
+ }
+
+ pa_log_debug("Channel matrix:\n%s", t = pa_strbuf_to_string_free(s));
+ pa_xfree(t);
+
+ /* initialize the remapping function */
+ pa_init_remap_func(m);
+}
+
+static void free_remap(pa_remap_t *m) {
+ pa_assert(m);
+
+ pa_xfree(m->state);
+}
+
+/* check if buf's memblock is large enough to hold 'len' bytes; create a
+ * new memblock if necessary and optionally preserve 'copy' data bytes */
+static void fit_buf(pa_resampler *r, pa_memchunk *buf, size_t len, size_t *size, size_t copy) {
+ pa_assert(size);
+
+ if (!buf->memblock || len > *size) {
+ pa_memblock *new_block = pa_memblock_new(r->mempool, len);
+
+ if (buf->memblock) {
+ if (copy > 0) {
+ void *src = pa_memblock_acquire(buf->memblock);
+ void *dst = pa_memblock_acquire(new_block);
+ pa_assert(copy <= len);
+ memcpy(dst, src, copy);
+ pa_memblock_release(new_block);
+ pa_memblock_release(buf->memblock);
+ }
+
+ pa_memblock_unref(buf->memblock);
+ }
+
+ buf->memblock = new_block;
+ *size = len;
+ }
+
+ buf->length = len;
+}
+
+static pa_memchunk* convert_to_work_format(pa_resampler *r, pa_memchunk *input) {
+ unsigned in_n_samples, out_n_samples;
+ void *src, *dst;
+ bool have_leftover;
+ size_t leftover_length = 0;
+
+ pa_assert(r);
+ pa_assert(input);
+ pa_assert(input->memblock);
+
+ /* Convert the incoming sample into the work sample format and place them
+ * in to_work_format_buf. The leftover data is already converted, so it's
+ * part of the output buffer. */
+
+ have_leftover = r->leftover_in_to_work;
+ r->leftover_in_to_work = false;
+
+ if (!have_leftover && (!r->to_work_format_func || !input->length))
+ return input;
+ else if (input->length <= 0)
+ return &r->to_work_format_buf;
+
+ in_n_samples = out_n_samples = (unsigned) ((input->length / r->i_fz) * r->i_ss.channels);
+
+ if (have_leftover) {
+ leftover_length = r->to_work_format_buf.length;
+ out_n_samples += (unsigned) (leftover_length / r->w_sz);
+ }
+
+ fit_buf(r, &r->to_work_format_buf, r->w_sz * out_n_samples, &r->to_work_format_buf_size, leftover_length);
+
+ src = pa_memblock_acquire_chunk(input);
+ dst = (uint8_t *) pa_memblock_acquire(r->to_work_format_buf.memblock) + leftover_length;
+
+ if (r->to_work_format_func)
+ r->to_work_format_func(in_n_samples, src, dst);
+ else
+ memcpy(dst, src, input->length);
+
+ pa_memblock_release(input->memblock);
+ pa_memblock_release(r->to_work_format_buf.memblock);
+
+ return &r->to_work_format_buf;
+}
+
+static pa_memchunk *remap_channels(pa_resampler *r, pa_memchunk *input) {
+ unsigned in_n_samples, out_n_samples, in_n_frames, out_n_frames;
+ void *src, *dst;
+ size_t leftover_length = 0;
+ bool have_leftover;
+
+ pa_assert(r);
+ pa_assert(input);
+ pa_assert(input->memblock);
+
+ /* Remap channels and place the result in remap_buf. There may be leftover
+ * data in the beginning of remap_buf. The leftover data is already
+ * remapped, so it's not part of the input, it's part of the output. */
+
+ have_leftover = r->leftover_in_remap;
+ r->leftover_in_remap = false;
+
+ if (!have_leftover && (!r->map_required || input->length <= 0))
+ return input;
+ else if (input->length <= 0)
+ return &r->remap_buf;
+
+ in_n_samples = (unsigned) (input->length / r->w_sz);
+ in_n_frames = out_n_frames = in_n_samples / r->i_ss.channels;
+
+ if (have_leftover) {
+ leftover_length = r->remap_buf.length;
+ out_n_frames += leftover_length / r->w_fz;
+ }
+
+ out_n_samples = out_n_frames * r->o_ss.channels;
+ fit_buf(r, &r->remap_buf, out_n_samples * r->w_sz, &r->remap_buf_size, leftover_length);
+
+ src = pa_memblock_acquire_chunk(input);
+ dst = (uint8_t *) pa_memblock_acquire(r->remap_buf.memblock) + leftover_length;
+
+ if (r->map_required) {
+ pa_remap_t *remap = &r->remap;
+
+ pa_assert(remap->do_remap);
+ remap->do_remap(remap, dst, src, in_n_frames);
+
+ } else
+ memcpy(dst, src, input->length);
+
+ pa_memblock_release(input->memblock);
+ pa_memblock_release(r->remap_buf.memblock);
+
+ return &r->remap_buf;
+}
+
+static void save_leftover(pa_resampler *r, void *buf, size_t len) {
+ void *dst;
+
+ pa_assert(r);
+ pa_assert(buf);
+ pa_assert(len > 0);
+
+ /* Store the leftover data. */
+ fit_buf(r, r->leftover_buf, len, r->leftover_buf_size, 0);
+ *r->have_leftover = true;
+
+ dst = pa_memblock_acquire(r->leftover_buf->memblock);
+ memmove(dst, buf, len);
+ pa_memblock_release(r->leftover_buf->memblock);
+}
+
+static pa_memchunk *resample(pa_resampler *r, pa_memchunk *input) {
+ unsigned in_n_frames, out_n_frames, leftover_n_frames;
+
+ pa_assert(r);
+ pa_assert(input);
+
+ /* Resample the data and place the result in resample_buf. */
+
+ if (!r->impl.resample || !input->length)
+ return input;
+
+ in_n_frames = (unsigned) (input->length / r->w_fz);
+
+ out_n_frames = ((in_n_frames*r->o_ss.rate)/r->i_ss.rate)+EXTRA_FRAMES;
+ fit_buf(r, &r->resample_buf, r->w_fz * out_n_frames, &r->resample_buf_size, 0);
+
+ leftover_n_frames = r->impl.resample(r, input, in_n_frames, &r->resample_buf, &out_n_frames);
+
+ if (leftover_n_frames > 0) {
+ void *leftover_data = (uint8_t *) pa_memblock_acquire_chunk(input) + (in_n_frames - leftover_n_frames) * r->w_fz;
+ save_leftover(r, leftover_data, leftover_n_frames * r->w_fz);
+ pa_memblock_release(input->memblock);
+ }
+
+ r->resample_buf.length = out_n_frames * r->w_fz;
+
+ return &r->resample_buf;
+}
+
+static pa_memchunk *convert_from_work_format(pa_resampler *r, pa_memchunk *input) {
+ unsigned n_samples, n_frames;
+ void *src, *dst;
+
+ pa_assert(r);
+ pa_assert(input);
+
+ /* Convert the data into the correct sample type and place the result in
+ * from_work_format_buf. */
+
+ if (!r->from_work_format_func || !input->length)
+ return input;
+
+ n_samples = (unsigned) (input->length / r->w_sz);
+ n_frames = n_samples / r->o_ss.channels;
+ fit_buf(r, &r->from_work_format_buf, r->o_fz * n_frames, &r->from_work_format_buf_size, 0);
+
+ src = pa_memblock_acquire_chunk(input);
+ dst = pa_memblock_acquire(r->from_work_format_buf.memblock);
+ r->from_work_format_func(n_samples, src, dst);
+ pa_memblock_release(input->memblock);
+ pa_memblock_release(r->from_work_format_buf.memblock);
+
+ return &r->from_work_format_buf;
+}
+
+void pa_resampler_run(pa_resampler *r, const pa_memchunk *in, pa_memchunk *out) {
+ pa_memchunk *buf;
+
+ pa_assert(r);
+ pa_assert(in);
+ pa_assert(out);
+ pa_assert(in->length);
+ pa_assert(in->memblock);
+ pa_assert(in->length % r->i_fz == 0);
+
+ buf = (pa_memchunk*) in;
+ buf = convert_to_work_format(r, buf);
+
+ /* Try to save resampling effort: if we have more output channels than
+ * input channels, do resampling first, then remapping. */
+ if (r->o_ss.channels <= r->i_ss.channels) {
+ buf = remap_channels(r, buf);
+ buf = resample(r, buf);
+ } else {
+ buf = resample(r, buf);
+ buf = remap_channels(r, buf);
+ }
+
+ if (r->lfe_filter)
+ buf = pa_lfe_filter_process(r->lfe_filter, buf);
+
+ if (buf->length) {
+ buf = convert_from_work_format(r, buf);
+ *out = *buf;
+
+ if (buf == in)
+ pa_memblock_ref(buf->memblock);
+ else
+ pa_memchunk_reset(buf);
+ } else
+ pa_memchunk_reset(out);
+}
+
+/*** copy (noop) implementation ***/
+
+static int copy_init(pa_resampler *r) {
+ pa_assert(r);
+
+ pa_assert(r->o_ss.rate == r->i_ss.rate);
+
+ return 0;
+}
diff --git a/src/pulsecore/resampler.h b/src/pulsecore/resampler.h
new file mode 100644
index 0000000..5a264b3
--- /dev/null
+++ b/src/pulsecore/resampler.h
@@ -0,0 +1,181 @@
+#ifndef fooresamplerhfoo
+#define fooresamplerhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulse/sample.h>
+#include <pulse/channelmap.h>
+#include <pulsecore/memblock.h>
+#include <pulsecore/memchunk.h>
+#include <pulsecore/sconv.h>
+#include <pulsecore/remap.h>
+#include <pulsecore/filter/lfe-filter.h>
+
+typedef struct pa_resampler pa_resampler;
+typedef struct pa_resampler_impl pa_resampler_impl;
+
+struct pa_resampler_impl {
+ void (*free)(pa_resampler *r);
+ void (*update_rates)(pa_resampler *r);
+
+ /* Returns the number of leftover frames in the input buffer. */
+ unsigned (*resample)(pa_resampler *r, const pa_memchunk *in, unsigned in_n_frames, pa_memchunk *out, unsigned *out_n_frames);
+
+ void (*reset)(pa_resampler *r);
+ void *data;
+};
+
+typedef enum pa_resample_method {
+ PA_RESAMPLER_INVALID = -1,
+ PA_RESAMPLER_SRC_SINC_BEST_QUALITY = 0, /* = SRC_SINC_BEST_QUALITY */
+ PA_RESAMPLER_SRC_SINC_MEDIUM_QUALITY = 1, /* = SRC_SINC_MEDIUM_QUALITY */
+ PA_RESAMPLER_SRC_SINC_FASTEST = 2, /* = SRC_SINC_FASTEST */
+ PA_RESAMPLER_SRC_ZERO_ORDER_HOLD = 3, /* = SRC_ZERO_ORDER_HOLD */
+ PA_RESAMPLER_SRC_LINEAR = 4, /* = SRC_LINEAR */
+ PA_RESAMPLER_TRIVIAL,
+ PA_RESAMPLER_SPEEX_FLOAT_BASE,
+ PA_RESAMPLER_SPEEX_FLOAT_MAX = PA_RESAMPLER_SPEEX_FLOAT_BASE + 10,
+ PA_RESAMPLER_SPEEX_FIXED_BASE,
+ PA_RESAMPLER_SPEEX_FIXED_MAX = PA_RESAMPLER_SPEEX_FIXED_BASE + 10,
+ PA_RESAMPLER_FFMPEG,
+ PA_RESAMPLER_AUTO, /* automatic select based on sample format */
+ PA_RESAMPLER_COPY,
+ PA_RESAMPLER_PEAKS,
+ PA_RESAMPLER_SOXR_MQ,
+ PA_RESAMPLER_SOXR_HQ,
+ PA_RESAMPLER_SOXR_VHQ,
+ PA_RESAMPLER_MAX
+} pa_resample_method_t;
+
+typedef enum pa_resample_flags {
+ PA_RESAMPLER_VARIABLE_RATE = 0x0001U,
+ PA_RESAMPLER_NO_REMAP = 0x0002U, /* implies NO_REMIX */
+ PA_RESAMPLER_NO_REMIX = 0x0004U,
+ PA_RESAMPLER_NO_FILL_SINK = 0x0010U,
+ PA_RESAMPLER_PRODUCE_LFE = 0x0020U,
+ PA_RESAMPLER_CONSUME_LFE = 0x0040U,
+} pa_resample_flags_t;
+
+struct pa_resampler {
+ pa_resample_method_t method;
+ pa_resample_flags_t flags;
+
+ pa_sample_spec i_ss, o_ss;
+ pa_channel_map i_cm, o_cm;
+ size_t i_fz, o_fz, w_fz, w_sz;
+ pa_mempool *mempool;
+
+ pa_memchunk to_work_format_buf;
+ pa_memchunk remap_buf;
+ pa_memchunk resample_buf;
+ pa_memchunk from_work_format_buf;
+ size_t to_work_format_buf_size;
+ size_t remap_buf_size;
+ size_t resample_buf_size;
+ size_t from_work_format_buf_size;
+
+ /* points to buffer before resampling stage, remap or to_work */
+ pa_memchunk *leftover_buf;
+ size_t *leftover_buf_size;
+
+ /* have_leftover points to leftover_in_remap or leftover_in_to_work */
+ bool *have_leftover;
+ bool leftover_in_remap;
+ bool leftover_in_to_work;
+
+ pa_sample_format_t work_format;
+ uint8_t work_channels;
+
+ pa_convert_func_t to_work_format_func;
+ pa_convert_func_t from_work_format_func;
+
+ pa_remap_t remap;
+ bool map_required;
+
+ pa_lfe_filter_t *lfe_filter;
+
+ pa_resampler_impl impl;
+};
+
+pa_resampler* pa_resampler_new(
+ pa_mempool *pool,
+ const pa_sample_spec *a,
+ const pa_channel_map *am,
+ const pa_sample_spec *b,
+ const pa_channel_map *bm,
+ unsigned crossover_freq,
+ pa_resample_method_t resample_method,
+ pa_resample_flags_t flags);
+
+void pa_resampler_free(pa_resampler *r);
+
+/* Returns the size of an input memory block which is required to return the specified amount of output data */
+size_t pa_resampler_request(pa_resampler *r, size_t out_length);
+
+/* Inverse of pa_resampler_request() */
+size_t pa_resampler_result(pa_resampler *r, size_t in_length);
+
+/* Returns the maximum size of input blocks we can process without needing bounce buffers larger than the mempool tile size. */
+size_t pa_resampler_max_block_size(pa_resampler *r);
+
+/* Pass the specified memory chunk to the resampler and return the newly resampled data */
+void pa_resampler_run(pa_resampler *r, const pa_memchunk *in, pa_memchunk *out);
+
+/* Change the input rate of the resampler object */
+void pa_resampler_set_input_rate(pa_resampler *r, uint32_t rate);
+
+/* Change the output rate of the resampler object */
+void pa_resampler_set_output_rate(pa_resampler *r, uint32_t rate);
+
+/* Reinitialize state of the resampler, possibly due to seeking or other discontinuities */
+void pa_resampler_reset(pa_resampler *r);
+
+/* Rewind resampler */
+void pa_resampler_rewind(pa_resampler *r, size_t out_frames);
+
+/* Return the resampling method of the resampler object */
+pa_resample_method_t pa_resampler_get_method(pa_resampler *r);
+
+/* Try to parse the resampler method */
+pa_resample_method_t pa_parse_resample_method(const char *string);
+
+/* return a human readable string for the specified resampling method. Inverse of pa_parse_resample_method() */
+const char *pa_resample_method_to_string(pa_resample_method_t m);
+
+/* Return 1 when the specified resampling method is supported */
+int pa_resample_method_supported(pa_resample_method_t m);
+
+const pa_channel_map* pa_resampler_input_channel_map(pa_resampler *r);
+const pa_sample_spec* pa_resampler_input_sample_spec(pa_resampler *r);
+const pa_channel_map* pa_resampler_output_channel_map(pa_resampler *r);
+const pa_sample_spec* pa_resampler_output_sample_spec(pa_resampler *r);
+
+/* Implementation specific init functions */
+int pa_resampler_ffmpeg_init(pa_resampler *r);
+int pa_resampler_libsamplerate_init(pa_resampler *r);
+int pa_resampler_peaks_init(pa_resampler *r);
+int pa_resampler_speex_init(pa_resampler *r);
+int pa_resampler_trivial_init(pa_resampler*r);
+int pa_resampler_soxr_init(pa_resampler *r);
+
+/* Resampler-specific quirks */
+bool pa_speex_is_fixed_point(void);
+
+#endif
diff --git a/src/pulsecore/resampler/ffmpeg.c b/src/pulsecore/resampler/ffmpeg.c
new file mode 100644
index 0000000..388b555
--- /dev/null
+++ b/src/pulsecore/resampler/ffmpeg.c
@@ -0,0 +1,132 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulse/xmalloc.h>
+#include "pulsecore/ffmpeg/avcodec.h"
+
+#include <pulsecore/resampler.h>
+
+struct ffmpeg_data { /* data specific to ffmpeg */
+ struct AVResampleContext *state;
+};
+
+static unsigned ffmpeg_resample(pa_resampler *r, const pa_memchunk *input, unsigned in_n_frames, pa_memchunk *output, unsigned *out_n_frames) {
+ unsigned used_frames = 0, c;
+ int previous_consumed_frames = -1;
+ struct ffmpeg_data *ffmpeg_data;
+
+ pa_assert(r);
+ pa_assert(input);
+ pa_assert(output);
+ pa_assert(out_n_frames);
+
+ ffmpeg_data = r->impl.data;
+
+ for (c = 0; c < r->work_channels; c++) {
+ unsigned u;
+ pa_memblock *b, *w;
+ int16_t *p, *t, *k, *q, *s;
+ int consumed_frames;
+
+ /* Allocate a new block */
+ b = pa_memblock_new(r->mempool, in_n_frames * sizeof(int16_t));
+ p = pa_memblock_acquire(b);
+
+ /* Now copy the input data, splitting up channels */
+ t = (int16_t*) pa_memblock_acquire_chunk(input) + c;
+ k = p;
+ for (u = 0; u < in_n_frames; u++) {
+ *k = *t;
+ t += r->work_channels;
+ k ++;
+ }
+ pa_memblock_release(input->memblock);
+
+ /* Allocate buffer for the result */
+ w = pa_memblock_new(r->mempool, *out_n_frames * sizeof(int16_t));
+ q = pa_memblock_acquire(w);
+
+ /* Now, resample */
+ used_frames = (unsigned) av_resample(ffmpeg_data->state,
+ q, p,
+ &consumed_frames,
+ (int) in_n_frames, (int) *out_n_frames,
+ c >= (unsigned) (r->work_channels-1));
+
+ pa_memblock_release(b);
+ pa_memblock_unref(b);
+
+ pa_assert(consumed_frames <= (int) in_n_frames);
+ pa_assert(previous_consumed_frames == -1 || consumed_frames == previous_consumed_frames);
+ previous_consumed_frames = consumed_frames;
+
+ /* And place the results in the output buffer */
+ s = (int16_t *) pa_memblock_acquire_chunk(output) + c;
+ for (u = 0; u < used_frames; u++) {
+ *s = *q;
+ q++;
+ s += r->work_channels;
+ }
+ pa_memblock_release(output->memblock);
+ pa_memblock_release(w);
+ pa_memblock_unref(w);
+ }
+
+ *out_n_frames = used_frames;
+
+ return in_n_frames - previous_consumed_frames;
+}
+
+static void ffmpeg_free(pa_resampler *r) {
+ struct ffmpeg_data *ffmpeg_data;
+
+ pa_assert(r);
+
+ ffmpeg_data = r->impl.data;
+ if (ffmpeg_data->state)
+ av_resample_close(ffmpeg_data->state);
+}
+
+int pa_resampler_ffmpeg_init(pa_resampler *r) {
+ struct ffmpeg_data *ffmpeg_data;
+
+ pa_assert(r);
+
+ ffmpeg_data = pa_xnew(struct ffmpeg_data, 1);
+
+ /* We could probably implement different quality levels by
+ * adjusting the filter parameters here. However, ffmpeg
+ * internally only uses these hardcoded values, so let's use them
+ * here for now as well until ffmpeg makes this configurable. */
+
+ if (!(ffmpeg_data->state = av_resample_init((int) r->o_ss.rate, (int) r->i_ss.rate, 16, 10, 0, 0.8))) {
+ pa_xfree(ffmpeg_data);
+ return -1;
+ }
+
+ r->impl.free = ffmpeg_free;
+ r->impl.resample = ffmpeg_resample;
+ r->impl.data = (void *) ffmpeg_data;
+
+ return 0;
+}
diff --git a/src/pulsecore/resampler/libsamplerate.c b/src/pulsecore/resampler/libsamplerate.c
new file mode 100644
index 0000000..06704fe
--- /dev/null
+++ b/src/pulsecore/resampler/libsamplerate.c
@@ -0,0 +1,100 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <samplerate.h>
+
+#include <pulsecore/resampler.h>
+
+static unsigned libsamplerate_resample(pa_resampler *r, const pa_memchunk *input, unsigned in_n_frames, pa_memchunk *output, unsigned *out_n_frames) {
+ SRC_DATA data;
+ SRC_STATE *state;
+
+ pa_assert(r);
+ pa_assert(input);
+ pa_assert(output);
+ pa_assert(out_n_frames);
+
+ state = r->impl.data;
+ memset(&data, 0, sizeof(data));
+
+ data.data_in = pa_memblock_acquire_chunk(input);
+ data.input_frames = (long int) in_n_frames;
+
+ data.data_out = pa_memblock_acquire_chunk(output);
+ data.output_frames = (long int) *out_n_frames;
+
+ data.src_ratio = (double) r->o_ss.rate / r->i_ss.rate;
+ data.end_of_input = 0;
+
+ pa_assert_se(src_process(state, &data) == 0);
+
+ pa_memblock_release(input->memblock);
+ pa_memblock_release(output->memblock);
+
+ *out_n_frames = (unsigned) data.output_frames_gen;
+
+ return in_n_frames - data.input_frames_used;
+}
+
+static void libsamplerate_update_rates(pa_resampler *r) {
+ SRC_STATE *state;
+ pa_assert(r);
+
+ state = r->impl.data;
+ pa_assert_se(src_set_ratio(state, (double) r->o_ss.rate / r->i_ss.rate) == 0);
+}
+
+static void libsamplerate_reset(pa_resampler *r) {
+ SRC_STATE *state;
+ pa_assert(r);
+
+ state = r->impl.data;
+ pa_assert_se(src_reset(state) == 0);
+}
+
+static void libsamplerate_free(pa_resampler *r) {
+ SRC_STATE *state;
+ pa_assert(r);
+
+ state = r->impl.data;
+ if (state)
+ src_delete(state);
+}
+
+int pa_resampler_libsamplerate_init(pa_resampler *r) {
+ int err;
+ SRC_STATE *state;
+
+ pa_assert(r);
+
+ if (!(state = src_new(r->method, r->work_channels, &err)))
+ return -1;
+
+ r->impl.free = libsamplerate_free;
+ r->impl.update_rates = libsamplerate_update_rates;
+ r->impl.resample = libsamplerate_resample;
+ r->impl.reset = libsamplerate_reset;
+ r->impl.data = state;
+
+ return 0;
+}
diff --git a/src/pulsecore/resampler/peaks.c b/src/pulsecore/resampler/peaks.c
new file mode 100644
index 0000000..c9b808e
--- /dev/null
+++ b/src/pulsecore/resampler/peaks.c
@@ -0,0 +1,161 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulse/xmalloc.h>
+#include <math.h>
+
+#include <pulsecore/resampler.h>
+
+struct peaks_data { /* data specific to the peak finder pseudo resampler */
+ unsigned o_counter;
+ unsigned i_counter;
+
+ float max_f[PA_CHANNELS_MAX];
+ int16_t max_i[PA_CHANNELS_MAX];
+};
+
+static unsigned peaks_resample(pa_resampler *r, const pa_memchunk *input, unsigned in_n_frames, pa_memchunk *output, unsigned *out_n_frames) {
+ unsigned c, o_index = 0;
+ unsigned i, i_end = 0;
+ void *src, *dst;
+ struct peaks_data *peaks_data;
+
+ pa_assert(r);
+ pa_assert(input);
+ pa_assert(output);
+ pa_assert(out_n_frames);
+
+ peaks_data = r->impl.data;
+ src = pa_memblock_acquire_chunk(input);
+ dst = pa_memblock_acquire_chunk(output);
+
+ i = ((uint64_t) peaks_data->o_counter * r->i_ss.rate) / r->o_ss.rate;
+ i = i > peaks_data->i_counter ? i - peaks_data->i_counter : 0;
+
+ while (i_end < in_n_frames) {
+ i_end = ((uint64_t) (peaks_data->o_counter + 1) * r->i_ss.rate) / r->o_ss.rate;
+ i_end = i_end > peaks_data->i_counter ? i_end - peaks_data->i_counter : 0;
+
+ pa_assert_fp(o_index * r->w_fz < pa_memblock_get_length(output->memblock));
+
+ /* 1ch float is treated separately, because that is the common case */
+ if (r->work_channels == 1 && r->work_format == PA_SAMPLE_FLOAT32NE) {
+ float *s = (float*) src + i;
+ float *d = (float*) dst + o_index;
+
+ for (; i < i_end && i < in_n_frames; i++) {
+ float n = fabsf(*s++);
+
+ if (n > peaks_data->max_f[0])
+ peaks_data->max_f[0] = n;
+ }
+
+ if (i == i_end) {
+ *d = peaks_data->max_f[0];
+ peaks_data->max_f[0] = 0;
+ o_index++, peaks_data->o_counter++;
+ }
+ } else if (r->work_format == PA_SAMPLE_S16NE) {
+ int16_t *s = (int16_t*) src + r->work_channels * i;
+ int16_t *d = (int16_t*) dst + r->work_channels * o_index;
+
+ for (; i < i_end && i < in_n_frames; i++)
+ for (c = 0; c < r->work_channels; c++) {
+ int16_t n = abs(*s++);
+
+ if (n > peaks_data->max_i[c])
+ peaks_data->max_i[c] = n;
+ }
+
+ if (i == i_end) {
+ for (c = 0; c < r->work_channels; c++, d++) {
+ *d = peaks_data->max_i[c];
+ peaks_data->max_i[c] = 0;
+ }
+ o_index++, peaks_data->o_counter++;
+ }
+ } else {
+ float *s = (float*) src + r->work_channels * i;
+ float *d = (float*) dst + r->work_channels * o_index;
+
+ for (; i < i_end && i < in_n_frames; i++)
+ for (c = 0; c < r->work_channels; c++) {
+ float n = fabsf(*s++);
+
+ if (n > peaks_data->max_f[c])
+ peaks_data->max_f[c] = n;
+ }
+
+ if (i == i_end) {
+ for (c = 0; c < r->work_channels; c++, d++) {
+ *d = peaks_data->max_f[c];
+ peaks_data->max_f[c] = 0;
+ }
+ o_index++, peaks_data->o_counter++;
+ }
+ }
+ }
+
+ pa_memblock_release(input->memblock);
+ pa_memblock_release(output->memblock);
+
+ *out_n_frames = o_index;
+
+ peaks_data->i_counter += in_n_frames;
+
+ /* Normalize counters */
+ while (peaks_data->i_counter >= r->i_ss.rate) {
+ pa_assert(peaks_data->o_counter >= r->o_ss.rate);
+
+ peaks_data->i_counter -= r->i_ss.rate;
+ peaks_data->o_counter -= r->o_ss.rate;
+ }
+
+ return 0;
+}
+
+static void peaks_update_rates_or_reset(pa_resampler *r) {
+ struct peaks_data *peaks_data;
+ pa_assert(r);
+
+ peaks_data = r->impl.data;
+
+ peaks_data->i_counter = 0;
+ peaks_data->o_counter = 0;
+}
+
+int pa_resampler_peaks_init(pa_resampler*r) {
+ struct peaks_data *peaks_data;
+ pa_assert(r);
+ pa_assert(r->i_ss.rate >= r->o_ss.rate);
+ pa_assert(r->work_format == PA_SAMPLE_S16NE || r->work_format == PA_SAMPLE_FLOAT32NE);
+
+ peaks_data = pa_xnew0(struct peaks_data, 1);
+
+ r->impl.resample = peaks_resample;
+ r->impl.update_rates = peaks_update_rates_or_reset;
+ r->impl.reset = peaks_update_rates_or_reset;
+ r->impl.data = peaks_data;
+
+ return 0;
+}
diff --git a/src/pulsecore/resampler/soxr.c b/src/pulsecore/resampler/soxr.c
new file mode 100644
index 0000000..b1b2e19
--- /dev/null
+++ b/src/pulsecore/resampler/soxr.c
@@ -0,0 +1,168 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2014, 2015 Andrey Semashev
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stddef.h>
+#include <soxr.h>
+
+#include <pulsecore/resampler.h>
+
+static unsigned resampler_soxr_resample(pa_resampler *r, const pa_memchunk *input, unsigned in_n_frames,
+ pa_memchunk *output, unsigned *out_n_frames) {
+ soxr_t state;
+ void *in, *out;
+ size_t consumed = 0, produced = 0;
+
+ pa_assert(r);
+ pa_assert(input);
+ pa_assert(output);
+ pa_assert(out_n_frames);
+
+ state = r->impl.data;
+ pa_assert(state);
+
+ in = pa_memblock_acquire_chunk(input);
+ out = pa_memblock_acquire_chunk(output);
+
+ pa_assert_se(soxr_process(state, in, in_n_frames, &consumed, out, *out_n_frames, &produced) == 0);
+
+ pa_memblock_release(input->memblock);
+ pa_memblock_release(output->memblock);
+
+ *out_n_frames = produced;
+
+ return in_n_frames - consumed;
+}
+
+static void resampler_soxr_free(pa_resampler *r) {
+ pa_assert(r);
+
+ if (!r->impl.data)
+ return;
+
+ soxr_delete(r->impl.data);
+ r->impl.data = NULL;
+}
+
+static void resampler_soxr_reset(pa_resampler *r) {
+#if SOXR_THIS_VERSION >= SOXR_VERSION(0, 1, 2)
+ pa_assert(r);
+
+ soxr_clear(r->impl.data);
+#else
+ /* With libsoxr prior to 0.1.2 soxr_clear() makes soxr_process() crash afterwards,
+ * so don't use this function and re-create the context instead. */
+ soxr_t old_state;
+
+ pa_assert(r);
+
+ old_state = r->impl.data;
+ r->impl.data = NULL;
+
+ if (pa_resampler_soxr_init(r) == 0) {
+ if (old_state)
+ soxr_delete(old_state);
+ } else {
+ r->impl.data = old_state;
+ pa_log_error("Failed to reset libsoxr context");
+ }
+#endif
+}
+
+static void resampler_soxr_update_rates(pa_resampler *r) {
+ soxr_t old_state;
+
+ pa_assert(r);
+
+ /* There is no update method in libsoxr,
+ * so just re-create the resampler context */
+
+ old_state = r->impl.data;
+ r->impl.data = NULL;
+
+ if (pa_resampler_soxr_init(r) == 0) {
+ if (old_state)
+ soxr_delete(old_state);
+ } else {
+ r->impl.data = old_state;
+ pa_log_error("Failed to update libsoxr sample rates");
+ }
+}
+
+int pa_resampler_soxr_init(pa_resampler *r) {
+ soxr_t state;
+ soxr_datatype_t io_format;
+ soxr_io_spec_t io_spec;
+ soxr_runtime_spec_t runtime_spec;
+ unsigned long quality_recipe;
+ soxr_quality_spec_t quality;
+ soxr_error_t err = NULL;
+
+ pa_assert(r);
+
+ switch (r->work_format) {
+ case PA_SAMPLE_S16NE:
+ io_format = SOXR_INT16_I;
+ break;
+ case PA_SAMPLE_FLOAT32NE:
+ io_format = SOXR_FLOAT32_I;
+ break;
+ default:
+ pa_assert_not_reached();
+ }
+
+ io_spec = soxr_io_spec(io_format, io_format);
+
+ /* Resample in one thread. Multithreading makes
+ * performance worse with small chunks of audio. */
+ runtime_spec = soxr_runtime_spec(1);
+
+ switch (r->method) {
+ case PA_RESAMPLER_SOXR_MQ:
+ quality_recipe = SOXR_MQ | SOXR_LINEAR_PHASE;
+ break;
+ case PA_RESAMPLER_SOXR_HQ:
+ quality_recipe = SOXR_HQ | SOXR_LINEAR_PHASE;
+ break;
+ case PA_RESAMPLER_SOXR_VHQ:
+ quality_recipe = SOXR_VHQ | SOXR_LINEAR_PHASE;
+ break;
+ default:
+ pa_assert_not_reached();
+ }
+
+ quality = soxr_quality_spec(quality_recipe, 0);
+
+ state = soxr_create(r->i_ss.rate, r->o_ss.rate, r->work_channels, &err, &io_spec, &quality, &runtime_spec);
+ if (!state) {
+ pa_log_error("Failed to create libsoxr resampler context: %s.", (err ? err : "[unknown error]"));
+ return -1;
+ }
+
+ r->impl.free = resampler_soxr_free;
+ r->impl.reset = resampler_soxr_reset;
+ r->impl.update_rates = resampler_soxr_update_rates;
+ r->impl.resample = resampler_soxr_resample;
+ r->impl.data = state;
+
+ return 0;
+}
diff --git a/src/pulsecore/resampler/speex.c b/src/pulsecore/resampler/speex.c
new file mode 100644
index 0000000..66387e5
--- /dev/null
+++ b/src/pulsecore/resampler/speex.c
@@ -0,0 +1,178 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <speex/speex_resampler.h>
+#include <math.h>
+
+#include <pulsecore/once.h>
+#include <pulsecore/resampler.h>
+
+bool pa_speex_is_fixed_point(void) {
+ static bool result = false;
+ PA_ONCE_BEGIN {
+ float f_out = -1.0f, f_in = 1.0f;
+ spx_uint32_t in_len = 1, out_len = 1;
+ SpeexResamplerState *s;
+
+ pa_assert_se(s = speex_resampler_init(1, 1, 1,
+ SPEEX_RESAMPLER_QUALITY_MIN, NULL));
+
+ /* feed one sample that is too soft for fixed-point speex */
+ pa_assert_se(speex_resampler_process_float(s, 0, &f_in, &in_len,
+ &f_out, &out_len) == RESAMPLER_ERR_SUCCESS);
+
+ /* expecting sample has been processed, one sample output */
+ pa_assert_se(in_len == 1 && out_len == 1);
+
+ /* speex compiled with --enable-fixed-point will output 0.0 due to insufficient precision */
+ if (fabsf(f_out) < 0.00001f)
+ result = true;
+
+ speex_resampler_destroy(s);
+ } PA_ONCE_END;
+ return result;
+}
+
+
+static unsigned speex_resample_float(pa_resampler *r, const pa_memchunk *input, unsigned in_n_frames, pa_memchunk *output, unsigned *out_n_frames) {
+ float *in, *out;
+ uint32_t inf = in_n_frames, outf = *out_n_frames;
+ SpeexResamplerState *state;
+
+ pa_assert(r);
+ pa_assert(input);
+ pa_assert(output);
+ pa_assert(out_n_frames);
+
+ state = r->impl.data;
+
+ in = pa_memblock_acquire_chunk(input);
+ out = pa_memblock_acquire_chunk(output);
+
+ /* Strictly speaking, speex resampler expects its input
+ * to be normalized to the [-32768.0 .. 32767.0] range.
+ * This matters if speex has been compiled with --enable-fixed-point,
+ * because such speex will round the samples to the nearest
+ * integer. speex with --enable-fixed-point is therefore incompatible
+ * with PulseAudio's floating-point sample range [-1 .. 1]. speex
+ * without --enable-fixed-point works fine with this range.
+ * Care has been taken to call speex_resample_float() only
+ * for speex compiled without --enable-fixed-point.
+ */
+ pa_assert_se(speex_resampler_process_interleaved_float(state, in, &inf, out, &outf) == 0);
+
+ pa_memblock_release(input->memblock);
+ pa_memblock_release(output->memblock);
+
+ pa_assert(inf == in_n_frames);
+ *out_n_frames = outf;
+
+ return 0;
+}
+
+static unsigned speex_resample_int(pa_resampler *r, const pa_memchunk *input, unsigned in_n_frames, pa_memchunk *output, unsigned *out_n_frames) {
+ int16_t *in, *out;
+ uint32_t inf = in_n_frames, outf = *out_n_frames;
+ SpeexResamplerState *state;
+
+ pa_assert(r);
+ pa_assert(input);
+ pa_assert(output);
+ pa_assert(out_n_frames);
+
+ state = r->impl.data;
+
+ in = pa_memblock_acquire_chunk(input);
+ out = pa_memblock_acquire_chunk(output);
+
+ pa_assert_se(speex_resampler_process_interleaved_int(state, in, &inf, out, &outf) == 0);
+
+ pa_memblock_release(input->memblock);
+ pa_memblock_release(output->memblock);
+
+ pa_assert(inf == in_n_frames);
+ *out_n_frames = outf;
+
+ return 0;
+}
+
+static void speex_update_rates(pa_resampler *r) {
+ SpeexResamplerState *state;
+ pa_assert(r);
+
+ state = r->impl.data;
+
+ pa_assert_se(speex_resampler_set_rate(state, r->i_ss.rate, r->o_ss.rate) == 0);
+}
+
+static void speex_reset(pa_resampler *r) {
+ SpeexResamplerState *state;
+ pa_assert(r);
+
+ state = r->impl.data;
+
+ pa_assert_se(speex_resampler_reset_mem(state) == 0);
+}
+
+static void speex_free(pa_resampler *r) {
+ SpeexResamplerState *state;
+ pa_assert(r);
+
+ state = r->impl.data;
+ if (!state)
+ return;
+
+ speex_resampler_destroy(state);
+}
+
+int pa_resampler_speex_init(pa_resampler *r) {
+ int q, err;
+ SpeexResamplerState *state;
+
+ pa_assert(r);
+
+ r->impl.free = speex_free;
+ r->impl.update_rates = speex_update_rates;
+ r->impl.reset = speex_reset;
+
+ if (r->method >= PA_RESAMPLER_SPEEX_FIXED_BASE && r->method <= PA_RESAMPLER_SPEEX_FIXED_MAX) {
+
+ q = r->method - PA_RESAMPLER_SPEEX_FIXED_BASE;
+ r->impl.resample = speex_resample_int;
+
+ } else {
+ pa_assert(r->method >= PA_RESAMPLER_SPEEX_FLOAT_BASE && r->method <= PA_RESAMPLER_SPEEX_FLOAT_MAX);
+
+ q = r->method - PA_RESAMPLER_SPEEX_FLOAT_BASE;
+ r->impl.resample = speex_resample_float;
+ }
+
+ pa_log_info("Choosing speex quality setting %i.", q);
+
+ if (!(state = speex_resampler_init(r->work_channels, r->i_ss.rate, r->o_ss.rate, q, &err)))
+ return -1;
+
+ r->impl.data = state;
+
+ return 0;
+}
diff --git a/src/pulsecore/resampler/trivial.c b/src/pulsecore/resampler/trivial.c
new file mode 100644
index 0000000..14e7ef3
--- /dev/null
+++ b/src/pulsecore/resampler/trivial.c
@@ -0,0 +1,100 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/resampler.h>
+
+struct trivial_data { /* data specific to the trivial resampler */
+ unsigned o_counter;
+ unsigned i_counter;
+};
+
+static unsigned trivial_resample(pa_resampler *r, const pa_memchunk *input, unsigned in_n_frames, pa_memchunk *output, unsigned *out_n_frames) {
+ unsigned i_index, o_index;
+ void *src, *dst;
+ struct trivial_data *trivial_data;
+
+ pa_assert(r);
+ pa_assert(input);
+ pa_assert(output);
+ pa_assert(out_n_frames);
+
+ trivial_data = r->impl.data;
+
+ src = pa_memblock_acquire_chunk(input);
+ dst = pa_memblock_acquire_chunk(output);
+
+ for (o_index = 0;; o_index++, trivial_data->o_counter++) {
+ i_index = ((uint64_t) trivial_data->o_counter * r->i_ss.rate) / r->o_ss.rate;
+ i_index = i_index > trivial_data->i_counter ? i_index - trivial_data->i_counter : 0;
+
+ if (i_index >= in_n_frames)
+ break;
+
+ pa_assert_fp(o_index * r->w_fz < pa_memblock_get_length(output->memblock));
+
+ memcpy((uint8_t*) dst + r->w_fz * o_index, (uint8_t*) src + r->w_fz * i_index, (int) r->w_fz);
+ }
+
+ pa_memblock_release(input->memblock);
+ pa_memblock_release(output->memblock);
+
+ *out_n_frames = o_index;
+
+ trivial_data->i_counter += in_n_frames;
+
+ /* Normalize counters */
+ while (trivial_data->i_counter >= r->i_ss.rate) {
+ pa_assert(trivial_data->o_counter >= r->o_ss.rate);
+
+ trivial_data->i_counter -= r->i_ss.rate;
+ trivial_data->o_counter -= r->o_ss.rate;
+ }
+
+ return 0;
+}
+
+static void trivial_update_rates_or_reset(pa_resampler *r) {
+ struct trivial_data *trivial_data;
+ pa_assert(r);
+
+ trivial_data = r->impl.data;
+
+ trivial_data->i_counter = 0;
+ trivial_data->o_counter = 0;
+}
+
+int pa_resampler_trivial_init(pa_resampler *r) {
+ struct trivial_data *trivial_data;
+ pa_assert(r);
+
+ trivial_data = pa_xnew0(struct trivial_data, 1);
+
+ r->impl.resample = trivial_resample;
+ r->impl.update_rates = trivial_update_rates_or_reset;
+ r->impl.reset = trivial_update_rates_or_reset;
+ r->impl.data = trivial_data;
+
+ return 0;
+}
diff --git a/src/pulsecore/rtkit.c b/src/pulsecore/rtkit.c
new file mode 100644
index 0000000..2b7eb6a
--- /dev/null
+++ b/src/pulsecore/rtkit.c
@@ -0,0 +1,313 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+ Copyright 2009 Lennart Poettering
+ Copyright 2010 David Henningsson <diwic@ubuntu.com>
+
+ Permission is hereby granted, free of charge, to any person
+ obtaining a copy of this software and associated documentation files
+ (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge,
+ publish, distribute, sublicense, and/or sell copies of the Software,
+ and to permit persons to whom the Software is furnished to do so,
+ subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+***/
+
+#include <errno.h>
+
+#include "rtkit.h"
+
+#if defined(__linux__) && !defined(__ANDROID__)
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/syscall.h>
+#include <pulsecore/core-util.h>
+
+static pid_t _gettid(void) {
+ return (pid_t) syscall(SYS_gettid);
+}
+
+static int translate_error(const char *name) {
+ if (pa_streq(name, DBUS_ERROR_NO_MEMORY))
+ return -ENOMEM;
+ if (pa_streq(name, DBUS_ERROR_SERVICE_UNKNOWN) ||
+ pa_streq(name, DBUS_ERROR_NAME_HAS_NO_OWNER))
+ return -ENOENT;
+ if (pa_streq(name, DBUS_ERROR_ACCESS_DENIED) ||
+ pa_streq(name, DBUS_ERROR_AUTH_FAILED))
+ return -EACCES;
+
+ return -EIO;
+}
+
+static long long rtkit_get_int_property(DBusConnection *connection, const char* propname, long long* propval) {
+ DBusMessage *m = NULL, *r = NULL;
+ DBusMessageIter iter, subiter;
+ dbus_int64_t i64;
+ dbus_int32_t i32;
+ DBusError error;
+ int current_type;
+ long long ret;
+ const char * interfacestr = "org.freedesktop.RealtimeKit1";
+
+ dbus_error_init(&error);
+
+ if (!(m = dbus_message_new_method_call(
+ RTKIT_SERVICE_NAME,
+ RTKIT_OBJECT_PATH,
+ "org.freedesktop.DBus.Properties",
+ "Get"))) {
+ ret = -ENOMEM;
+ goto finish;
+ }
+
+ if (!dbus_message_append_args(
+ m,
+ DBUS_TYPE_STRING, &interfacestr,
+ DBUS_TYPE_STRING, &propname,
+ DBUS_TYPE_INVALID)) {
+ ret = -ENOMEM;
+ goto finish;
+ }
+
+ if (!(r = dbus_connection_send_with_reply_and_block(connection, m, -1, &error))) {
+ ret = translate_error(error.name);
+ goto finish;
+ }
+
+ if (dbus_set_error_from_message(&error, r)) {
+ ret = translate_error(error.name);
+ goto finish;
+ }
+
+ ret = -EBADMSG;
+ dbus_message_iter_init(r, &iter);
+ while ((current_type = dbus_message_iter_get_arg_type (&iter)) != DBUS_TYPE_INVALID) {
+
+ if (current_type == DBUS_TYPE_VARIANT) {
+ dbus_message_iter_recurse(&iter, &subiter);
+
+ while ((current_type = dbus_message_iter_get_arg_type (&subiter)) != DBUS_TYPE_INVALID) {
+
+ if (current_type == DBUS_TYPE_INT32) {
+ dbus_message_iter_get_basic(&subiter, &i32);
+ *propval = i32;
+ ret = 0;
+ }
+
+ if (current_type == DBUS_TYPE_INT64) {
+ dbus_message_iter_get_basic(&subiter, &i64);
+ *propval = i64;
+ ret = 0;
+ }
+
+ dbus_message_iter_next (&subiter);
+ }
+ }
+ dbus_message_iter_next (&iter);
+ }
+
+finish:
+
+ if (m)
+ dbus_message_unref(m);
+
+ if (r)
+ dbus_message_unref(r);
+
+ dbus_error_free(&error);
+
+ return ret;
+}
+
+int rtkit_get_max_realtime_priority(DBusConnection *connection) {
+ long long retval;
+ int err;
+
+ err = rtkit_get_int_property(connection, "MaxRealtimePriority", &retval);
+ return err < 0 ? err : retval;
+}
+
+int rtkit_get_min_nice_level(DBusConnection *connection, int* min_nice_level) {
+ long long retval;
+ int err;
+
+ err = rtkit_get_int_property(connection, "MinNiceLevel", &retval);
+ if (err >= 0)
+ *min_nice_level = retval;
+ return err;
+}
+
+long long rtkit_get_rttime_usec_max(DBusConnection *connection) {
+ long long retval;
+ int err;
+
+ err = rtkit_get_int_property(connection, "RTTimeUSecMax", &retval);
+ return err < 0 ? err : retval;
+}
+
+int rtkit_make_realtime(DBusConnection *connection, pid_t thread, int priority) {
+ DBusMessage *m = NULL, *r = NULL;
+ dbus_uint64_t u64;
+ dbus_uint32_t u32;
+ DBusError error;
+ int ret;
+
+ dbus_error_init(&error);
+
+ if (thread == 0)
+ thread = _gettid();
+
+ if (!(m = dbus_message_new_method_call(
+ RTKIT_SERVICE_NAME,
+ RTKIT_OBJECT_PATH,
+ "org.freedesktop.RealtimeKit1",
+ "MakeThreadRealtime"))) {
+ ret = -ENOMEM;
+ goto finish;
+ }
+
+ u64 = (dbus_uint64_t) thread;
+ u32 = (dbus_uint32_t) priority;
+
+ if (!dbus_message_append_args(
+ m,
+ DBUS_TYPE_UINT64, &u64,
+ DBUS_TYPE_UINT32, &u32,
+ DBUS_TYPE_INVALID)) {
+ ret = -ENOMEM;
+ goto finish;
+ }
+
+ if (!(r = dbus_connection_send_with_reply_and_block(connection, m, -1, &error))) {
+ ret = translate_error(error.name);
+ goto finish;
+ }
+
+
+ if (dbus_set_error_from_message(&error, r)) {
+ ret = translate_error(error.name);
+ goto finish;
+ }
+
+ ret = 0;
+
+finish:
+
+ if (m)
+ dbus_message_unref(m);
+
+ if (r)
+ dbus_message_unref(r);
+
+ dbus_error_free(&error);
+
+ return ret;
+}
+
+int rtkit_make_high_priority(DBusConnection *connection, pid_t thread, int nice_level) {
+ DBusMessage *m = NULL, *r = NULL;
+ dbus_uint64_t u64;
+ dbus_int32_t s32;
+ DBusError error;
+ int ret;
+
+ dbus_error_init(&error);
+
+ if (thread == 0)
+ thread = _gettid();
+
+ if (!(m = dbus_message_new_method_call(
+ RTKIT_SERVICE_NAME,
+ RTKIT_OBJECT_PATH,
+ "org.freedesktop.RealtimeKit1",
+ "MakeThreadHighPriority"))) {
+ ret = -ENOMEM;
+ goto finish;
+ }
+
+ u64 = (dbus_uint64_t) thread;
+ s32 = (dbus_int32_t) nice_level;
+
+ if (!dbus_message_append_args(
+ m,
+ DBUS_TYPE_UINT64, &u64,
+ DBUS_TYPE_INT32, &s32,
+ DBUS_TYPE_INVALID)) {
+ ret = -ENOMEM;
+ goto finish;
+ }
+
+
+
+ if (!(r = dbus_connection_send_with_reply_and_block(connection, m, -1, &error))) {
+ ret = translate_error(error.name);
+ goto finish;
+ }
+
+
+ if (dbus_set_error_from_message(&error, r)) {
+ ret = translate_error(error.name);
+ goto finish;
+ }
+
+ ret = 0;
+
+finish:
+
+ if (m)
+ dbus_message_unref(m);
+
+ if (r)
+ dbus_message_unref(r);
+
+ dbus_error_free(&error);
+
+ return ret;
+}
+
+#else
+
+int rtkit_make_realtime(DBusConnection *connection, pid_t thread, int priority) {
+ return -ENOTSUP;
+}
+
+int rtkit_make_high_priority(DBusConnection *connection, pid_t thread, int nice_level) {
+ return -ENOTSUP;
+}
+
+int rtkit_get_max_realtime_priority(DBusConnection *connection) {
+ return -ENOTSUP;
+}
+
+int rtkit_get_min_nice_level(DBusConnection *connection, int* min_nice_level) {
+ return -ENOTSUP;
+}
+
+long long rtkit_get_rttime_usec_max(DBusConnection *connection) {
+ return -ENOTSUP;
+}
+
+#endif
diff --git a/src/pulsecore/rtkit.h b/src/pulsecore/rtkit.h
new file mode 100644
index 0000000..30cde72
--- /dev/null
+++ b/src/pulsecore/rtkit.h
@@ -0,0 +1,79 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef foortkithfoo
+#define foortkithfoo
+
+/***
+ Copyright 2009 Lennart Poettering
+ Copyright 2010 David Henningsson <diwic@ubuntu.com>
+
+ Permission is hereby granted, free of charge, to any person
+ obtaining a copy of this software and associated documentation files
+ (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge,
+ publish, distribute, sublicense, and/or sell copies of the Software,
+ and to permit persons to whom the Software is furnished to do so,
+ subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+***/
+
+#include <sys/types.h>
+#include <dbus/dbus.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* This is the reference implementation for a client for
+ * RealtimeKit. You don't have to use this, but if do, just copy these
+ * sources into your repository */
+
+#define RTKIT_SERVICE_NAME "org.freedesktop.RealtimeKit1"
+#define RTKIT_OBJECT_PATH "/org/freedesktop/RealtimeKit1"
+
+/* This is mostly equivalent to sched_setparam(thread, SCHED_RR, {
+ * .sched_priority = priority }). 'thread' needs to be a kernel thread
+ * id as returned by gettid(), not a pthread_t! If 'thread' is 0 the
+ * current thread is used. The returned value is a negative errno
+ * style error code, or 0 on success. */
+int rtkit_make_realtime(DBusConnection *system_bus, pid_t thread, int priority);
+
+/* This is mostly equivalent to setpriority(PRIO_PROCESS, thread,
+ * nice_level). 'thread' needs to be a kernel thread id as returned by
+ * gettid(), not a pthread_t! If 'thread' is 0 the current thread is
+ * used. The returned value is a negative errno style error code, or 0
+ * on success.*/
+int rtkit_make_high_priority(DBusConnection *system_bus, pid_t thread, int nice_level);
+
+/* Return the maximum value of realtime priority available. Realtime requests
+ * above this value will fail. A negative value is an errno style error code.
+ */
+int rtkit_get_max_realtime_priority(DBusConnection *system_bus);
+
+/* Retrieve the minimum value of nice level available. High prio requests
+ * below this value will fail. The returned value is a negative errno
+ * style error code, or 0 on success.*/
+int rtkit_get_min_nice_level(DBusConnection *system_bus, int* min_nice_level);
+
+/* Return the maximum value of RLIMIT_RTTIME to set before attempting a
+ * realtime request. A negative value is an errno style error code.
+ */
+long long rtkit_get_rttime_usec_max(DBusConnection *system_bus);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/pulsecore/rtpoll.c b/src/pulsecore/rtpoll.c
new file mode 100644
index 0000000..1085bf9
--- /dev/null
+++ b/src/pulsecore/rtpoll.c
@@ -0,0 +1,631 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include <pulse/xmalloc.h>
+#include <pulse/timeval.h>
+
+#include <pulsecore/poll.h>
+#include <pulsecore/core-error.h>
+#include <pulsecore/core-rtclock.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/llist.h>
+#include <pulsecore/flist.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/ratelimit.h>
+#include <pulse/rtclock.h>
+
+#include "rtpoll.h"
+
+/* #define DEBUG_TIMING */
+
+struct pa_rtpoll {
+ struct pollfd *pollfd, *pollfd2;
+ unsigned n_pollfd_alloc, n_pollfd_used;
+
+ struct timeval next_elapse;
+ bool timer_enabled:1;
+
+ bool scan_for_dead:1;
+ bool running:1;
+ bool rebuild_needed:1;
+ bool quit:1;
+ bool timer_elapsed:1;
+
+#ifdef DEBUG_TIMING
+ pa_usec_t timestamp;
+ pa_usec_t slept, awake;
+#endif
+
+ PA_LLIST_HEAD(pa_rtpoll_item, items);
+};
+
+struct pa_rtpoll_item {
+ pa_rtpoll *rtpoll;
+ bool dead;
+
+ pa_rtpoll_priority_t priority;
+
+ struct pollfd *pollfd;
+ unsigned n_pollfd;
+
+ int (*work_cb)(pa_rtpoll_item *i);
+ int (*before_cb)(pa_rtpoll_item *i);
+ void (*after_cb)(pa_rtpoll_item *i);
+ void *work_userdata;
+ void *before_userdata;
+ void *after_userdata;
+
+ PA_LLIST_FIELDS(pa_rtpoll_item);
+};
+
+PA_STATIC_FLIST_DECLARE(items, 0, pa_xfree);
+
+pa_rtpoll *pa_rtpoll_new(void) {
+ pa_rtpoll *p;
+
+ p = pa_xnew0(pa_rtpoll, 1);
+
+ p->n_pollfd_alloc = 32;
+ p->pollfd = pa_xnew(struct pollfd, p->n_pollfd_alloc);
+ p->pollfd2 = pa_xnew(struct pollfd, p->n_pollfd_alloc);
+
+#ifdef DEBUG_TIMING
+ p->timestamp = pa_rtclock_now();
+#endif
+
+ return p;
+}
+
+static void rtpoll_rebuild(pa_rtpoll *p) {
+
+ struct pollfd *e, *t;
+ pa_rtpoll_item *i;
+ int ra = 0;
+
+ pa_assert(p);
+
+ p->rebuild_needed = false;
+
+ if (p->n_pollfd_used > p->n_pollfd_alloc) {
+ /* Hmm, we have to allocate some more space */
+ p->n_pollfd_alloc = p->n_pollfd_used * 2;
+ p->pollfd2 = pa_xrealloc(p->pollfd2, p->n_pollfd_alloc * sizeof(struct pollfd));
+ ra = 1;
+ }
+
+ e = p->pollfd2;
+
+ for (i = p->items; i; i = i->next) {
+
+ if (i->n_pollfd > 0) {
+ size_t l = i->n_pollfd * sizeof(struct pollfd);
+
+ if (i->pollfd)
+ memcpy(e, i->pollfd, l);
+ else
+ memset(e, 0, l);
+
+ i->pollfd = e;
+ } else
+ i->pollfd = NULL;
+
+ e += i->n_pollfd;
+ }
+
+ pa_assert((unsigned) (e - p->pollfd2) == p->n_pollfd_used);
+ t = p->pollfd;
+ p->pollfd = p->pollfd2;
+ p->pollfd2 = t;
+
+ if (ra)
+ p->pollfd2 = pa_xrealloc(p->pollfd2, p->n_pollfd_alloc * sizeof(struct pollfd));
+}
+
+static void rtpoll_item_destroy(pa_rtpoll_item *i) {
+ pa_rtpoll *p;
+
+ pa_assert(i);
+
+ p = i->rtpoll;
+
+ PA_LLIST_REMOVE(pa_rtpoll_item, p->items, i);
+
+ p->n_pollfd_used -= i->n_pollfd;
+
+ if (pa_flist_push(PA_STATIC_FLIST_GET(items), i) < 0)
+ pa_xfree(i);
+
+ p->rebuild_needed = true;
+}
+
+void pa_rtpoll_free(pa_rtpoll *p) {
+ pa_assert(p);
+
+ while (p->items)
+ rtpoll_item_destroy(p->items);
+
+ pa_xfree(p->pollfd);
+ pa_xfree(p->pollfd2);
+
+ pa_xfree(p);
+}
+
+static void reset_revents(pa_rtpoll_item *i) {
+ struct pollfd *f;
+ unsigned n;
+
+ pa_assert(i);
+
+ if (!(f = pa_rtpoll_item_get_pollfd(i, &n)))
+ return;
+
+ for (; n > 0; n--)
+ f[n-1].revents = 0;
+}
+
+static void reset_all_revents(pa_rtpoll *p) {
+ pa_rtpoll_item *i;
+
+ pa_assert(p);
+
+ for (i = p->items; i; i = i->next) {
+
+ if (i->dead)
+ continue;
+
+ reset_revents(i);
+ }
+}
+
+int pa_rtpoll_run(pa_rtpoll *p) {
+ pa_rtpoll_item *i;
+ int r = 0;
+ struct timeval timeout;
+
+ pa_assert(p);
+ pa_assert(!p->running);
+
+#ifdef DEBUG_TIMING
+ pa_log("rtpoll_run");
+#endif
+
+ p->running = true;
+ p->timer_elapsed = false;
+
+ /* First, let's do some work */
+ for (i = p->items; i && i->priority < PA_RTPOLL_NEVER; i = i->next) {
+ int k;
+
+ if (i->dead)
+ continue;
+
+ if (!i->work_cb)
+ continue;
+
+ if (p->quit) {
+#ifdef DEBUG_TIMING
+ pa_log("rtpoll finish");
+#endif
+ goto finish;
+ }
+
+ if ((k = i->work_cb(i)) != 0) {
+ if (k < 0)
+ r = k;
+#ifdef DEBUG_TIMING
+ pa_log("rtpoll finish");
+#endif
+ goto finish;
+ }
+ }
+
+ /* Now let's prepare for entering the sleep */
+ for (i = p->items; i && i->priority < PA_RTPOLL_NEVER; i = i->next) {
+ int k = 0;
+
+ if (i->dead)
+ continue;
+
+ if (!i->before_cb)
+ continue;
+
+ if (p->quit || (k = i->before_cb(i)) != 0) {
+
+ /* Hmm, this one doesn't let us enter the poll, so rewind everything */
+
+ for (i = i->prev; i; i = i->prev) {
+
+ if (i->dead)
+ continue;
+
+ if (!i->after_cb)
+ continue;
+
+ i->after_cb(i);
+ }
+
+ if (k < 0)
+ r = k;
+#ifdef DEBUG_TIMING
+ pa_log("rtpoll finish");
+#endif
+ goto finish;
+ }
+ }
+
+ if (p->rebuild_needed)
+ rtpoll_rebuild(p);
+
+ pa_zero(timeout);
+
+ /* Calculate timeout */
+ if (!p->quit && p->timer_enabled) {
+ struct timeval now;
+ pa_rtclock_get(&now);
+
+ if (pa_timeval_cmp(&p->next_elapse, &now) > 0)
+ pa_timeval_add(&timeout, pa_timeval_diff(&p->next_elapse, &now));
+ }
+
+#ifdef DEBUG_TIMING
+ {
+ pa_usec_t now = pa_rtclock_now();
+ p->awake = now - p->timestamp;
+ p->timestamp = now;
+ if (!p->quit && p->timer_enabled)
+ pa_log("poll timeout: %d ms ",(int) ((timeout.tv_sec*1000) + (timeout.tv_usec / 1000)));
+ else if (p->quit)
+ pa_log("poll timeout is ZERO");
+ else
+ pa_log("poll timeout is FOREVER");
+ }
+#endif
+
+ /* OK, now let's sleep */
+#ifdef HAVE_PPOLL
+ {
+ struct timespec ts;
+ ts.tv_sec = timeout.tv_sec;
+ ts.tv_nsec = timeout.tv_usec * 1000;
+ r = ppoll(p->pollfd, p->n_pollfd_used, (p->quit || p->timer_enabled) ? &ts : NULL, NULL);
+ }
+#else
+ r = pa_poll(p->pollfd, p->n_pollfd_used, (p->quit || p->timer_enabled) ? (int) ((timeout.tv_sec*1000) + (timeout.tv_usec / 1000)) : -1);
+#endif
+
+ p->timer_elapsed = r == 0;
+
+#ifdef DEBUG_TIMING
+ {
+ pa_usec_t now = pa_rtclock_now();
+ p->slept = now - p->timestamp;
+ p->timestamp = now;
+
+ pa_log("Process time %llu ms; sleep time %llu ms",
+ (unsigned long long) (p->awake / PA_USEC_PER_MSEC),
+ (unsigned long long) (p->slept / PA_USEC_PER_MSEC));
+ }
+#endif
+
+ if (r < 0) {
+ if (errno == EAGAIN || errno == EINTR)
+ r = 0;
+ else
+ pa_log_error("poll(): %s", pa_cstrerror(errno));
+
+ reset_all_revents(p);
+ }
+
+ /* Let's tell everyone that we left the sleep */
+ for (i = p->items; i && i->priority < PA_RTPOLL_NEVER; i = i->next) {
+
+ if (i->dead)
+ continue;
+
+ if (!i->after_cb)
+ continue;
+
+ i->after_cb(i);
+ }
+
+finish:
+
+ p->running = false;
+
+ if (p->scan_for_dead) {
+ pa_rtpoll_item *n;
+
+ p->scan_for_dead = false;
+
+ for (i = p->items; i; i = n) {
+ n = i->next;
+
+ if (i->dead)
+ rtpoll_item_destroy(i);
+ }
+ }
+
+ return r < 0 ? r : !p->quit;
+}
+
+void pa_rtpoll_set_timer_absolute(pa_rtpoll *p, pa_usec_t usec) {
+ pa_assert(p);
+
+ pa_timeval_store(&p->next_elapse, usec);
+ p->timer_enabled = true;
+}
+
+void pa_rtpoll_set_timer_relative(pa_rtpoll *p, pa_usec_t usec) {
+ pa_assert(p);
+
+ /* Scheduling a timeout for more than an hour is very very suspicious */
+ pa_assert(usec <= PA_USEC_PER_SEC*60ULL*60ULL);
+
+ pa_rtclock_get(&p->next_elapse);
+ pa_timeval_add(&p->next_elapse, usec);
+ p->timer_enabled = true;
+}
+
+void pa_rtpoll_set_timer_disabled(pa_rtpoll *p) {
+ pa_assert(p);
+
+ memset(&p->next_elapse, 0, sizeof(p->next_elapse));
+ p->timer_enabled = false;
+}
+
+pa_rtpoll_item *pa_rtpoll_item_new(pa_rtpoll *p, pa_rtpoll_priority_t prio, unsigned n_fds) {
+ pa_rtpoll_item *i, *j, *l = NULL;
+
+ pa_assert(p);
+
+ if (!(i = pa_flist_pop(PA_STATIC_FLIST_GET(items))))
+ i = pa_xnew(pa_rtpoll_item, 1);
+
+ i->rtpoll = p;
+ i->dead = false;
+ i->n_pollfd = n_fds;
+ i->pollfd = NULL;
+ i->priority = prio;
+
+ i->work_userdata = NULL;
+ i->before_userdata = NULL;
+ i->work_userdata = NULL;
+ i->before_cb = NULL;
+ i->after_cb = NULL;
+ i->work_cb = NULL;
+
+ for (j = p->items; j; j = j->next) {
+ if (prio <= j->priority)
+ break;
+
+ l = j;
+ }
+
+ PA_LLIST_INSERT_AFTER(pa_rtpoll_item, p->items, j ? j->prev : l, i);
+
+ if (n_fds > 0) {
+ p->rebuild_needed = 1;
+ p->n_pollfd_used += n_fds;
+ }
+
+ return i;
+}
+
+void pa_rtpoll_item_free(pa_rtpoll_item *i) {
+ pa_assert(i);
+
+ if (i->rtpoll->running) {
+ i->dead = true;
+ i->rtpoll->scan_for_dead = true;
+ return;
+ }
+
+ rtpoll_item_destroy(i);
+}
+
+struct pollfd *pa_rtpoll_item_get_pollfd(pa_rtpoll_item *i, unsigned *n_fds) {
+ pa_assert(i);
+
+ if (i->n_pollfd > 0)
+ if (i->rtpoll->rebuild_needed)
+ rtpoll_rebuild(i->rtpoll);
+
+ if (n_fds)
+ *n_fds = i->n_pollfd;
+
+ return i->pollfd;
+}
+
+void pa_rtpoll_item_set_before_callback(pa_rtpoll_item *i, int (*before_cb)(pa_rtpoll_item *i), void *userdata) {
+ pa_assert(i);
+ pa_assert(i->priority < PA_RTPOLL_NEVER);
+
+ i->before_cb = before_cb;
+ i->before_userdata = userdata;
+}
+
+void pa_rtpoll_item_set_after_callback(pa_rtpoll_item *i, void (*after_cb)(pa_rtpoll_item *i), void *userdata) {
+ pa_assert(i);
+ pa_assert(i->priority < PA_RTPOLL_NEVER);
+
+ i->after_cb = after_cb;
+ i->after_userdata = userdata;
+}
+
+void pa_rtpoll_item_set_work_callback(pa_rtpoll_item *i, int (*work_cb)(pa_rtpoll_item *i), void *userdata) {
+ pa_assert(i);
+ pa_assert(i->priority < PA_RTPOLL_NEVER);
+
+ i->work_cb = work_cb;
+ i->work_userdata = userdata;
+}
+
+void* pa_rtpoll_item_get_work_userdata(pa_rtpoll_item *i) {
+ pa_assert(i);
+
+ return i->work_userdata;
+}
+
+static int fdsem_before(pa_rtpoll_item *i) {
+
+ if (pa_fdsem_before_poll(i->before_userdata) < 0)
+ return 1; /* 1 means immediate restart of the loop */
+
+ return 0;
+}
+
+static void fdsem_after(pa_rtpoll_item *i) {
+ pa_assert(i);
+
+ pa_assert((i->pollfd[0].revents & ~POLLIN) == 0);
+ pa_fdsem_after_poll(i->after_userdata);
+}
+
+pa_rtpoll_item *pa_rtpoll_item_new_fdsem(pa_rtpoll *p, pa_rtpoll_priority_t prio, pa_fdsem *f) {
+ pa_rtpoll_item *i;
+ struct pollfd *pollfd;
+
+ pa_assert(p);
+ pa_assert(f);
+
+ i = pa_rtpoll_item_new(p, prio, 1);
+
+ pollfd = pa_rtpoll_item_get_pollfd(i, NULL);
+
+ pollfd->fd = pa_fdsem_get(f);
+ pollfd->events = POLLIN;
+
+ pa_rtpoll_item_set_before_callback(i, fdsem_before, f);
+ pa_rtpoll_item_set_after_callback(i, fdsem_after, f);
+
+ return i;
+}
+
+static int asyncmsgq_read_before(pa_rtpoll_item *i) {
+ pa_assert(i);
+
+ if (pa_asyncmsgq_read_before_poll(i->before_userdata) < 0)
+ return 1; /* 1 means immediate restart of the loop */
+
+ return 0;
+}
+
+static void asyncmsgq_read_after(pa_rtpoll_item *i) {
+ pa_assert(i);
+
+ pa_assert((i->pollfd[0].revents & ~POLLIN) == 0);
+ pa_asyncmsgq_read_after_poll(i->after_userdata);
+}
+
+static int asyncmsgq_read_work(pa_rtpoll_item *i) {
+ pa_msgobject *object;
+ int code;
+ void *data;
+ pa_memchunk chunk;
+ int64_t offset;
+
+ pa_assert(i);
+
+ if (pa_asyncmsgq_get(i->work_userdata, &object, &code, &data, &offset, &chunk, 0) == 0) {
+ int ret;
+
+ if (!object && code == PA_MESSAGE_SHUTDOWN) {
+ pa_asyncmsgq_done(i->work_userdata, 0);
+ /* Requests the loop to exit. Will cause the next iteration of
+ * pa_rtpoll_run() to return 0 */
+ i->rtpoll->quit = true;
+ return 1;
+ }
+
+ ret = pa_asyncmsgq_dispatch(object, code, data, offset, &chunk);
+ pa_asyncmsgq_done(i->work_userdata, ret);
+ return 1;
+ }
+
+ return 0;
+}
+
+pa_rtpoll_item *pa_rtpoll_item_new_asyncmsgq_read(pa_rtpoll *p, pa_rtpoll_priority_t prio, pa_asyncmsgq *q) {
+ pa_rtpoll_item *i;
+ struct pollfd *pollfd;
+
+ pa_assert(p);
+ pa_assert(q);
+
+ i = pa_rtpoll_item_new(p, prio, 1);
+
+ pollfd = pa_rtpoll_item_get_pollfd(i, NULL);
+ pollfd->fd = pa_asyncmsgq_read_fd(q);
+ pollfd->events = POLLIN;
+
+ pa_rtpoll_item_set_before_callback(i, asyncmsgq_read_before, q);
+ pa_rtpoll_item_set_after_callback(i, asyncmsgq_read_after, q);
+ pa_rtpoll_item_set_work_callback(i, asyncmsgq_read_work, q);
+
+ return i;
+}
+
+static int asyncmsgq_write_before(pa_rtpoll_item *i) {
+ pa_assert(i);
+
+ pa_asyncmsgq_write_before_poll(i->before_userdata);
+ return 0;
+}
+
+static void asyncmsgq_write_after(pa_rtpoll_item *i) {
+ pa_assert(i);
+
+ pa_assert((i->pollfd[0].revents & ~POLLIN) == 0);
+ pa_asyncmsgq_write_after_poll(i->after_userdata);
+}
+
+pa_rtpoll_item *pa_rtpoll_item_new_asyncmsgq_write(pa_rtpoll *p, pa_rtpoll_priority_t prio, pa_asyncmsgq *q) {
+ pa_rtpoll_item *i;
+ struct pollfd *pollfd;
+
+ pa_assert(p);
+ pa_assert(q);
+
+ i = pa_rtpoll_item_new(p, prio, 1);
+
+ pollfd = pa_rtpoll_item_get_pollfd(i, NULL);
+ pollfd->fd = pa_asyncmsgq_write_fd(q);
+ pollfd->events = POLLIN;
+
+ pa_rtpoll_item_set_before_callback(i, asyncmsgq_write_before, q);
+ pa_rtpoll_item_set_after_callback(i, asyncmsgq_write_after, q);
+
+ return i;
+}
+
+bool pa_rtpoll_timer_elapsed(pa_rtpoll *p) {
+ pa_assert(p);
+
+ return p->timer_elapsed;
+}
diff --git a/src/pulsecore/rtpoll.h b/src/pulsecore/rtpoll.h
new file mode 100644
index 0000000..121b51e
--- /dev/null
+++ b/src/pulsecore/rtpoll.h
@@ -0,0 +1,100 @@
+#ifndef foopulsertpollhfoo
+#define foopulsertpollhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/types.h>
+#include <limits.h>
+
+#include <pulse/sample.h>
+#include <pulsecore/asyncmsgq.h>
+#include <pulsecore/fdsem.h>
+#include <pulsecore/macro.h>
+
+/* An implementation of a "real-time" poll loop. Basically, this is
+ * yet another wrapper around poll(). However it has certain
+ * advantages over pa_mainloop and suchlike:
+ *
+ * 1) High resolution timers are used
+ *
+ * 2) It allows raw access to the pollfd data to users
+ *
+ * 3) It allows arbitrary functions to be run before entering the
+ * actual poll() and after it.
+ *
+ * Only a single interval timer is supported. */
+
+typedef struct pa_rtpoll pa_rtpoll;
+typedef struct pa_rtpoll_item pa_rtpoll_item;
+
+typedef enum pa_rtpoll_priority {
+ PA_RTPOLL_EARLY = -100, /* For very important stuff, like handling control messages */
+ PA_RTPOLL_NORMAL = 0, /* For normal stuff */
+ PA_RTPOLL_LATE = +100, /* For housekeeping */
+ PA_RTPOLL_NEVER = INT_MAX, /* For stuff that doesn't register any callbacks, but only fds to listen on */
+} pa_rtpoll_priority_t;
+
+pa_rtpoll *pa_rtpoll_new(void);
+void pa_rtpoll_free(pa_rtpoll *p);
+
+/* Sleep on the rtpoll until the time event, or any of the fd events
+ * is triggered. Returns negative on error, positive if the loop
+ * should continue to run, 0 when the loop should be terminated
+ * cleanly. */
+int pa_rtpoll_run(pa_rtpoll *f);
+
+void pa_rtpoll_set_timer_absolute(pa_rtpoll *p, pa_usec_t usec);
+void pa_rtpoll_set_timer_relative(pa_rtpoll *p, pa_usec_t usec);
+void pa_rtpoll_set_timer_disabled(pa_rtpoll *p);
+
+/* Return true when the elapsed timer was the reason for
+ * the last pa_rtpoll_run() invocation to finish */
+bool pa_rtpoll_timer_elapsed(pa_rtpoll *p);
+
+/* A new fd wakeup item for pa_rtpoll */
+pa_rtpoll_item *pa_rtpoll_item_new(pa_rtpoll *p, pa_rtpoll_priority_t prio, unsigned n_fds);
+void pa_rtpoll_item_free(pa_rtpoll_item *i);
+
+/* Please note that this pointer might change on every call and when
+ * pa_rtpoll_run() is called. Hence: call this immediately before
+ * using the pointer and don't save the result anywhere */
+struct pollfd *pa_rtpoll_item_get_pollfd(pa_rtpoll_item *i, unsigned *n_fds);
+
+/* Set the callback that shall be called when there's time to do some work: If the
+ * callback returns a value > 0, the poll is skipped and the next
+ * iteration of the loop will start immediately. */
+void pa_rtpoll_item_set_work_callback(pa_rtpoll_item *i, int (*work_cb)(pa_rtpoll_item *i), void *userdata);
+
+/* Set the callback that shall be called immediately before entering
+ * the sleeping poll: If the callback returns a value > 0, the poll is
+ * skipped and the next iteration of the loop will start immediately. */
+void pa_rtpoll_item_set_before_callback(pa_rtpoll_item *i, int (*before_cb)(pa_rtpoll_item *i), void *userdata);
+
+/* Set the callback that shall be called immediately after having
+ * entered the sleeping poll */
+void pa_rtpoll_item_set_after_callback(pa_rtpoll_item *i, void (*after_cb)(pa_rtpoll_item *i), void *userdata);
+
+void* pa_rtpoll_item_get_work_userdata(pa_rtpoll_item *i);
+
+pa_rtpoll_item *pa_rtpoll_item_new_fdsem(pa_rtpoll *p, pa_rtpoll_priority_t prio, pa_fdsem *s);
+pa_rtpoll_item *pa_rtpoll_item_new_asyncmsgq_read(pa_rtpoll *p, pa_rtpoll_priority_t prio, pa_asyncmsgq *q);
+pa_rtpoll_item *pa_rtpoll_item_new_asyncmsgq_write(pa_rtpoll *p, pa_rtpoll_priority_t prio, pa_asyncmsgq *q);
+
+#endif
diff --git a/src/pulsecore/sample-util.c b/src/pulsecore/sample-util.c
new file mode 100644
index 0000000..b2a28eb
--- /dev/null
+++ b/src/pulsecore/sample-util.c
@@ -0,0 +1,405 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <math.h>
+
+#include <pulse/timeval.h>
+
+#include <pulsecore/log.h>
+#include <pulsecore/core-error.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/g711.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/endianmacros.h>
+
+#include "sample-util.h"
+
+#define PA_SILENCE_MAX (pa_page_size()*16)
+
+pa_memblock *pa_silence_memblock(pa_memblock* b, const pa_sample_spec *spec) {
+ void *data;
+
+ pa_assert(b);
+ pa_assert(spec);
+
+ data = pa_memblock_acquire(b);
+ pa_silence_memory(data, pa_memblock_get_length(b), spec);
+ pa_memblock_release(b);
+
+ return b;
+}
+
+pa_memchunk* pa_silence_memchunk(pa_memchunk *c, const pa_sample_spec *spec) {
+ void *data;
+
+ pa_assert(c);
+ pa_assert(c->memblock);
+ pa_assert(spec);
+
+ data = pa_memblock_acquire(c->memblock);
+ pa_silence_memory((uint8_t*) data+c->index, c->length, spec);
+ pa_memblock_release(c->memblock);
+
+ return c;
+}
+
+static uint8_t silence_byte(pa_sample_format_t format) {
+ switch (format) {
+ case PA_SAMPLE_U8:
+ return 0x80;
+ case PA_SAMPLE_S16LE:
+ case PA_SAMPLE_S16BE:
+ case PA_SAMPLE_S32LE:
+ case PA_SAMPLE_S32BE:
+ case PA_SAMPLE_FLOAT32LE:
+ case PA_SAMPLE_FLOAT32BE:
+ case PA_SAMPLE_S24LE:
+ case PA_SAMPLE_S24BE:
+ case PA_SAMPLE_S24_32LE:
+ case PA_SAMPLE_S24_32BE:
+ return 0;
+ case PA_SAMPLE_ALAW:
+ return 0xd5;
+ case PA_SAMPLE_ULAW:
+ return 0xff;
+ default:
+ pa_assert_not_reached();
+ }
+}
+
+void* pa_silence_memory(void *p, size_t length, const pa_sample_spec *spec) {
+ pa_assert(p);
+ pa_assert(length > 0);
+ pa_assert(spec);
+
+ memset(p, silence_byte(spec->format), length);
+ return p;
+}
+
+size_t pa_frame_align(size_t l, const pa_sample_spec *ss) {
+ size_t fs;
+
+ pa_assert(ss);
+
+ fs = pa_frame_size(ss);
+
+ return (l/fs) * fs;
+}
+
+bool pa_frame_aligned(size_t l, const pa_sample_spec *ss) {
+ size_t fs;
+
+ pa_assert(ss);
+
+ fs = pa_frame_size(ss);
+
+ return l % fs == 0;
+}
+
+void pa_interleave(const void *src[], unsigned channels, void *dst, size_t ss, unsigned n) {
+ unsigned c;
+ size_t fs;
+
+ pa_assert(src);
+ pa_assert(channels > 0);
+ pa_assert(dst);
+ pa_assert(ss > 0);
+ pa_assert(n > 0);
+
+ fs = ss * channels;
+
+ for (c = 0; c < channels; c++) {
+ unsigned j;
+ void *d;
+ const void *s;
+
+ s = src[c];
+ d = (uint8_t*) dst + c * ss;
+
+ for (j = 0; j < n; j ++) {
+ memcpy(d, s, (int) ss);
+ s = (uint8_t*) s + ss;
+ d = (uint8_t*) d + fs;
+ }
+ }
+}
+
+void pa_deinterleave(const void *src, void *dst[], unsigned channels, size_t ss, unsigned n) {
+ size_t fs;
+ unsigned c;
+
+ pa_assert(src);
+ pa_assert(dst);
+ pa_assert(channels > 0);
+ pa_assert(ss > 0);
+ pa_assert(n > 0);
+
+ fs = ss * channels;
+
+ for (c = 0; c < channels; c++) {
+ unsigned j;
+ const void *s;
+ void *d;
+
+ s = (uint8_t*) src + c * ss;
+ d = dst[c];
+
+ for (j = 0; j < n; j ++) {
+ memcpy(d, s, (int) ss);
+ s = (uint8_t*) s + fs;
+ d = (uint8_t*) d + ss;
+ }
+ }
+}
+
+static pa_memblock *silence_memblock_new(pa_mempool *pool, uint8_t c) {
+ pa_memblock *b;
+ size_t length;
+ void *data;
+
+ pa_assert(pool);
+
+ length = PA_MIN(pa_mempool_block_size_max(pool), PA_SILENCE_MAX);
+
+ b = pa_memblock_new(pool, length);
+
+ data = pa_memblock_acquire(b);
+ memset(data, c, length);
+ pa_memblock_release(b);
+
+ pa_memblock_set_is_silence(b, true);
+
+ return b;
+}
+
+void pa_silence_cache_init(pa_silence_cache *cache) {
+ pa_assert(cache);
+
+ memset(cache, 0, sizeof(pa_silence_cache));
+}
+
+void pa_silence_cache_done(pa_silence_cache *cache) {
+ pa_sample_format_t f;
+ pa_assert(cache);
+
+ for (f = 0; f < PA_SAMPLE_MAX; f++)
+ if (cache->blocks[f])
+ pa_memblock_unref(cache->blocks[f]);
+
+ memset(cache, 0, sizeof(pa_silence_cache));
+}
+
+pa_memchunk* pa_silence_memchunk_get(pa_silence_cache *cache, pa_mempool *pool, pa_memchunk* ret, const pa_sample_spec *spec, size_t length) {
+ pa_memblock *b;
+ size_t l;
+
+ pa_assert(cache);
+ pa_assert(pa_sample_spec_valid(spec));
+
+ if (!(b = cache->blocks[spec->format]))
+
+ switch (spec->format) {
+ case PA_SAMPLE_U8:
+ cache->blocks[PA_SAMPLE_U8] = b = silence_memblock_new(pool, 0x80);
+ break;
+ case PA_SAMPLE_S16LE:
+ case PA_SAMPLE_S16BE:
+ case PA_SAMPLE_S32LE:
+ case PA_SAMPLE_S32BE:
+ case PA_SAMPLE_S24LE:
+ case PA_SAMPLE_S24BE:
+ case PA_SAMPLE_S24_32LE:
+ case PA_SAMPLE_S24_32BE:
+ case PA_SAMPLE_FLOAT32LE:
+ case PA_SAMPLE_FLOAT32BE:
+ cache->blocks[PA_SAMPLE_S16LE] = b = silence_memblock_new(pool, 0);
+ cache->blocks[PA_SAMPLE_S16BE] = pa_memblock_ref(b);
+ cache->blocks[PA_SAMPLE_S32LE] = pa_memblock_ref(b);
+ cache->blocks[PA_SAMPLE_S32BE] = pa_memblock_ref(b);
+ cache->blocks[PA_SAMPLE_S24LE] = pa_memblock_ref(b);
+ cache->blocks[PA_SAMPLE_S24BE] = pa_memblock_ref(b);
+ cache->blocks[PA_SAMPLE_S24_32LE] = pa_memblock_ref(b);
+ cache->blocks[PA_SAMPLE_S24_32BE] = pa_memblock_ref(b);
+ cache->blocks[PA_SAMPLE_FLOAT32LE] = pa_memblock_ref(b);
+ cache->blocks[PA_SAMPLE_FLOAT32BE] = pa_memblock_ref(b);
+ break;
+ case PA_SAMPLE_ALAW:
+ cache->blocks[PA_SAMPLE_ALAW] = b = silence_memblock_new(pool, 0xd5);
+ break;
+ case PA_SAMPLE_ULAW:
+ cache->blocks[PA_SAMPLE_ULAW] = b = silence_memblock_new(pool, 0xff);
+ break;
+ default:
+ pa_assert_not_reached();
+ }
+
+ pa_assert(b);
+
+ ret->memblock = pa_memblock_ref(b);
+
+ l = pa_memblock_get_length(b);
+ if (length > l || length == 0)
+ length = l;
+
+ ret->length = pa_frame_align(length, spec);
+ ret->index = 0;
+
+ return ret;
+}
+
+void pa_sample_clamp(pa_sample_format_t format, void *dst, size_t dstr, const void *src, size_t sstr, unsigned n) {
+ const float *s;
+ float *d;
+
+ s = src; d = dst;
+
+ if (format == PA_SAMPLE_FLOAT32NE) {
+ for (; n > 0; n--) {
+ float f;
+
+ f = *s;
+ *d = PA_CLAMP_UNLIKELY(f, -1.0f, 1.0f);
+
+ s = (const float*) ((const uint8_t*) s + sstr);
+ d = (float*) ((uint8_t*) d + dstr);
+ }
+ } else {
+ pa_assert(format == PA_SAMPLE_FLOAT32RE);
+
+ for (; n > 0; n--) {
+ float f;
+
+ f = PA_READ_FLOAT32RE(s);
+ f = PA_CLAMP_UNLIKELY(f, -1.0f, 1.0f);
+ PA_WRITE_FLOAT32RE(d, f);
+
+ s = (const float*) ((const uint8_t*) s + sstr);
+ d = (float*) ((uint8_t*) d + dstr);
+ }
+ }
+}
+
+/* Similar to pa_bytes_to_usec() but rounds up, not down */
+
+pa_usec_t pa_bytes_to_usec_round_up(uint64_t length, const pa_sample_spec *spec) {
+ size_t fs;
+ pa_usec_t usec;
+
+ pa_assert(spec);
+
+ fs = pa_frame_size(spec);
+ length = (length + fs - 1) / fs;
+
+ usec = (pa_usec_t) length * PA_USEC_PER_SEC;
+
+ return (usec + spec->rate - 1) / spec->rate;
+}
+
+/* Similar to pa_usec_to_bytes() but rounds up, not down */
+
+size_t pa_usec_to_bytes_round_up(pa_usec_t t, const pa_sample_spec *spec) {
+ uint64_t u;
+ pa_assert(spec);
+
+ u = (uint64_t) t * (uint64_t) spec->rate;
+
+ u = (u + PA_USEC_PER_SEC - 1) / PA_USEC_PER_SEC;
+
+ u *= pa_frame_size(spec);
+
+ return (size_t) u;
+}
+
+void pa_memchunk_dump_to_file(pa_memchunk *c, const char *fn) {
+ FILE *f;
+ void *p;
+
+ pa_assert(c);
+ pa_assert(fn);
+
+ /* Only for debugging purposes */
+
+ f = pa_fopen_cloexec(fn, "a");
+
+ if (!f) {
+ pa_log_warn("Failed to open '%s': %s", fn, pa_cstrerror(errno));
+ return;
+ }
+
+ p = pa_memblock_acquire(c->memblock);
+
+ if (fwrite((uint8_t*) p + c->index, 1, c->length, f) != c->length)
+ pa_log_warn("Failed to write to '%s': %s", fn, pa_cstrerror(errno));
+
+ pa_memblock_release(c->memblock);
+
+ fclose(f);
+}
+
+static void calc_sine(float *f, size_t l, double freq) {
+ size_t i;
+
+ l /= sizeof(float);
+
+ for (i = 0; i < l; i++)
+ *(f++) = (float) 0.5f * sin((double) i*M_PI*2*freq / (double) l);
+}
+
+void pa_memchunk_sine(pa_memchunk *c, pa_mempool *pool, unsigned rate, unsigned freq) {
+ size_t l;
+ unsigned gcd, n;
+ void *p;
+
+ pa_memchunk_reset(c);
+
+ gcd = pa_gcd(rate, freq);
+ n = rate / gcd;
+
+ l = pa_mempool_block_size_max(pool) / sizeof(float);
+
+ l /= n;
+ if (l <= 0) l = 1;
+ l *= n;
+
+ c->length = l * sizeof(float);
+ c->memblock = pa_memblock_new(pool, c->length);
+
+ p = pa_memblock_acquire(c->memblock);
+ calc_sine(p, c->length, freq * l / rate);
+ pa_memblock_release(c->memblock);
+}
+
+size_t pa_convert_size(size_t size, const pa_sample_spec *from, const pa_sample_spec *to) {
+ pa_usec_t usec;
+
+ pa_assert(from);
+ pa_assert(to);
+
+ usec = pa_bytes_to_usec_round_up(size, from);
+ return pa_usec_to_bytes_round_up(usec, to);
+}
diff --git a/src/pulsecore/sample-util.h b/src/pulsecore/sample-util.h
new file mode 100644
index 0000000..0732e8d
--- /dev/null
+++ b/src/pulsecore/sample-util.h
@@ -0,0 +1,154 @@
+#ifndef foosampleutilhfoo
+#define foosampleutilhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <inttypes.h>
+#include <limits.h>
+
+#include <pulse/gccmacro.h>
+#include <pulse/sample.h>
+#include <pulse/volume.h>
+#include <pulse/channelmap.h>
+
+#include <pulsecore/memblock.h>
+#include <pulsecore/memchunk.h>
+
+typedef struct pa_silence_cache {
+ pa_memblock* blocks[PA_SAMPLE_MAX];
+} pa_silence_cache;
+
+void pa_silence_cache_init(pa_silence_cache *cache);
+void pa_silence_cache_done(pa_silence_cache *cache);
+
+void *pa_silence_memory(void *p, size_t length, const pa_sample_spec *spec);
+pa_memchunk* pa_silence_memchunk(pa_memchunk *c, const pa_sample_spec *spec);
+pa_memblock* pa_silence_memblock(pa_memblock *b, const pa_sample_spec *spec);
+
+pa_memchunk* pa_silence_memchunk_get(pa_silence_cache *cache, pa_mempool *pool, pa_memchunk* ret, const pa_sample_spec *spec, size_t length);
+
+size_t pa_frame_align(size_t l, const pa_sample_spec *ss) PA_GCC_PURE;
+
+bool pa_frame_aligned(size_t l, const pa_sample_spec *ss) PA_GCC_PURE;
+
+void pa_interleave(const void *src[], unsigned channels, void *dst, size_t ss, unsigned n);
+void pa_deinterleave(const void *src, void *dst[], unsigned channels, size_t ss, unsigned n);
+
+void pa_sample_clamp(pa_sample_format_t format, void *dst, size_t dstr, const void *src, size_t sstr, unsigned n);
+
+static inline int32_t pa_mult_s16_volume(int16_t v, int32_t cv) {
+#ifdef HAVE_FAST_64BIT_OPERATIONS
+ /* Multiply with 64 bit integers on 64 bit platforms */
+ return (v * (int64_t) cv) >> 16;
+#else
+ /* Multiplying the 32 bit volume factor with the
+ * 16 bit sample might result in an 48 bit value. We
+ * want to do without 64 bit integers and hence do
+ * the multiplication independently for the HI and
+ * LO part of the volume. */
+
+ int32_t hi = cv >> 16;
+ int32_t lo = cv & 0xFFFF;
+ return ((v * lo) >> 16) + (v * hi);
+#endif
+}
+
+pa_usec_t pa_bytes_to_usec_round_up(uint64_t length, const pa_sample_spec *spec);
+size_t pa_usec_to_bytes_round_up(pa_usec_t t, const pa_sample_spec *spec);
+
+void pa_memchunk_dump_to_file(pa_memchunk *c, const char *fn);
+
+void pa_memchunk_sine(pa_memchunk *c, pa_mempool *pool, unsigned rate, unsigned freq);
+
+typedef void (*pa_do_volume_func_t) (void *samples, const void *volumes, unsigned channels, unsigned length);
+
+pa_do_volume_func_t pa_get_volume_func(pa_sample_format_t f);
+void pa_set_volume_func(pa_sample_format_t f, pa_do_volume_func_t func);
+
+size_t pa_convert_size(size_t size, const pa_sample_spec *from, const pa_sample_spec *to);
+
+#define PA_CHANNEL_POSITION_MASK_LEFT \
+ (PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_LEFT) \
+ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_REAR_LEFT) \
+ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER) \
+ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_SIDE_LEFT) \
+ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_LEFT) \
+ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_LEFT)) \
+
+#define PA_CHANNEL_POSITION_MASK_RIGHT \
+ (PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_RIGHT) \
+ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_REAR_RIGHT) \
+ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER) \
+ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_SIDE_RIGHT) \
+ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_RIGHT) \
+ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_RIGHT))
+
+#define PA_CHANNEL_POSITION_MASK_CENTER \
+ (PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_CENTER) \
+ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_REAR_CENTER) \
+ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_CENTER) \
+ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_CENTER) \
+ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_CENTER))
+
+#define PA_CHANNEL_POSITION_MASK_FRONT \
+ (PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_LEFT) \
+ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_RIGHT) \
+ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_CENTER) \
+ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER) \
+ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER) \
+ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_LEFT) \
+ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_RIGHT) \
+ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_CENTER))
+
+#define PA_CHANNEL_POSITION_MASK_REAR \
+ (PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_REAR_LEFT) \
+ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_REAR_RIGHT) \
+ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_REAR_CENTER) \
+ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_LEFT) \
+ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_RIGHT) \
+ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_CENTER))
+
+#define PA_CHANNEL_POSITION_MASK_LFE \
+ PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_LFE)
+
+#define PA_CHANNEL_POSITION_MASK_HFE \
+ (PA_CHANNEL_POSITION_MASK_REAR | PA_CHANNEL_POSITION_MASK_FRONT \
+ | PA_CHANNEL_POSITION_MASK_LEFT | PA_CHANNEL_POSITION_MASK_RIGHT \
+ | PA_CHANNEL_POSITION_MASK_CENTER)
+
+#define PA_CHANNEL_POSITION_MASK_SIDE_OR_TOP_CENTER \
+ (PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_SIDE_LEFT) \
+ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_SIDE_RIGHT) \
+ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_CENTER))
+
+#define PA_CHANNEL_POSITION_MASK_TOP \
+ (PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_CENTER) \
+ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_LEFT) \
+ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_RIGHT) \
+ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_CENTER) \
+ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_LEFT) \
+ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_RIGHT) \
+ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_CENTER))
+
+#define PA_CHANNEL_POSITION_MASK_ALL \
+ ((pa_channel_position_mask_t) (PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_MAX)-1))
+
+#endif
diff --git a/src/pulsecore/sconv-s16be.c b/src/pulsecore/sconv-s16be.c
new file mode 100644
index 0000000..6228c74
--- /dev/null
+++ b/src/pulsecore/sconv-s16be.c
@@ -0,0 +1,81 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "endianmacros.h"
+
+#define INT16_FROM PA_INT16_FROM_BE
+#define INT16_TO PA_INT16_TO_BE
+#define UINT16_FROM PA_UINT16_FROM_BE
+#define UINT16_TO PA_UINT16_TO_BE
+
+#define INT32_FROM PA_INT32_FROM_BE
+#define INT32_TO PA_INT32_TO_BE
+#define UINT32_FROM PA_UINT32_FROM_BE
+#define UINT32_TO PA_UINT32_TO_BE
+
+#define READ24 PA_READ24BE
+#define WRITE24 PA_WRITE24BE
+
+#define pa_sconv_s16le_to_float32ne pa_sconv_s16be_to_float32ne
+#define pa_sconv_s16le_from_float32ne pa_sconv_s16be_from_float32ne
+#define pa_sconv_s16le_to_float32re pa_sconv_s16be_to_float32re
+#define pa_sconv_s16le_from_float32re pa_sconv_s16be_from_float32re
+
+#define pa_sconv_s32le_to_float32ne pa_sconv_s32be_to_float32ne
+#define pa_sconv_s32le_from_float32ne pa_sconv_s32be_from_float32ne
+#define pa_sconv_s32le_to_float32re pa_sconv_s32be_to_float32re
+#define pa_sconv_s32le_from_float32re pa_sconv_s32be_from_float32re
+
+#define pa_sconv_s24le_to_float32ne pa_sconv_s24be_to_float32ne
+#define pa_sconv_s24le_from_float32ne pa_sconv_s24be_from_float32ne
+#define pa_sconv_s24le_to_float32re pa_sconv_s24be_to_float32re
+#define pa_sconv_s24le_from_float32re pa_sconv_s24be_from_float32re
+
+#define pa_sconv_s24_32le_to_float32ne pa_sconv_s24_32be_to_float32ne
+#define pa_sconv_s24_32le_from_float32ne pa_sconv_s24_32be_from_float32ne
+#define pa_sconv_s24_32le_to_float32re pa_sconv_s24_32be_to_float32re
+#define pa_sconv_s24_32le_from_float32re pa_sconv_s24_32be_from_float32re
+
+#define pa_sconv_s32le_to_s16ne pa_sconv_s32be_to_s16ne
+#define pa_sconv_s32le_from_s16ne pa_sconv_s32be_from_s16ne
+#define pa_sconv_s32le_to_s16re pa_sconv_s32be_to_s16re
+#define pa_sconv_s32le_from_s16re pa_sconv_s32be_from_s16re
+
+#define pa_sconv_s24le_to_s16ne pa_sconv_s24be_to_s16ne
+#define pa_sconv_s24le_from_s16ne pa_sconv_s24be_from_s16ne
+#define pa_sconv_s24le_to_s16re pa_sconv_s24be_to_s16re
+#define pa_sconv_s24le_from_s16re pa_sconv_s24be_from_s16re
+
+#define pa_sconv_s24_32le_to_s16ne pa_sconv_s24_32be_to_s16ne
+#define pa_sconv_s24_32le_from_s16ne pa_sconv_s24_32be_from_s16ne
+#define pa_sconv_s24_32le_to_s16re pa_sconv_s24_32be_to_s16re
+#define pa_sconv_s24_32le_from_s16re pa_sconv_s24_32be_from_s16re
+
+#ifdef WORDS_BIGENDIAN
+#define SWAP_WORDS 0
+#else
+#define SWAP_WORDS 1
+#endif
+
+#include "sconv-s16le.h"
+#include "sconv-s16le.c"
diff --git a/src/pulsecore/sconv-s16be.h b/src/pulsecore/sconv-s16be.h
new file mode 100644
index 0000000..83b05fd
--- /dev/null
+++ b/src/pulsecore/sconv-s16be.h
@@ -0,0 +1,67 @@
+#ifndef foosconv_s16befoo
+#define foosconv_s16befoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <inttypes.h>
+
+void pa_sconv_s16be_to_float32ne(unsigned n, const int16_t *a, float *b);
+void pa_sconv_s16be_from_float32ne(unsigned n, const float *a, int16_t *b);
+void pa_sconv_s16be_to_float32re(unsigned n, const int16_t *a, float *b);
+void pa_sconv_s16be_from_float32re(unsigned n, const float *a, int16_t *b);
+
+void pa_sconv_s32be_to_float32ne(unsigned n, const int32_t *a, float *b);
+void pa_sconv_s32be_from_float32ne(unsigned n, const float *a, int32_t *b);
+void pa_sconv_s32be_to_float32re(unsigned n, const int32_t *a, float *b);
+void pa_sconv_s32be_from_float32re(unsigned n, const float *a, int32_t *b);
+
+void pa_sconv_s24be_to_float32ne(unsigned n, const uint8_t *a, float *b);
+void pa_sconv_s24be_from_float32ne(unsigned n, const float *a, uint8_t *b);
+void pa_sconv_s24be_to_float32re(unsigned n, const uint8_t *a, float *b);
+void pa_sconv_s24be_from_float32re(unsigned n, const float *a, uint8_t *b);
+
+void pa_sconv_s24_32be_to_float32ne(unsigned n, const uint32_t *a, float *b);
+void pa_sconv_s24_32be_from_float32ne(unsigned n, const float *a, uint32_t *b);
+void pa_sconv_s24_32be_to_float32re(unsigned n, const uint8_t *a, float *b);
+void pa_sconv_s24_32be_from_float32re(unsigned n, const float *a, uint8_t *b);
+
+void pa_sconv_s32be_to_s16ne(unsigned n, const int32_t *a, int16_t *b);
+void pa_sconv_s32be_from_s16ne(unsigned n, const int16_t *a, int32_t *b);
+void pa_sconv_s32be_to_s16re(unsigned n, const int32_t *a, int16_t *b);
+void pa_sconv_s32be_from_s16re(unsigned n, const int16_t *a, int32_t *b);
+
+void pa_sconv_s24be_to_s16ne(unsigned n, const uint8_t *a, int16_t *b);
+void pa_sconv_s24be_from_s16ne(unsigned n, const int16_t *a, uint8_t *b);
+void pa_sconv_s24be_to_s16re(unsigned n, const uint8_t *a, int16_t *b);
+void pa_sconv_s24be_from_s16re(unsigned n, const int16_t *a, uint8_t *b);
+
+void pa_sconv_s24_32be_to_s16ne(unsigned n, const uint32_t *a, int16_t *b);
+void pa_sconv_s24_32be_from_s16ne(unsigned n, const int16_t *a, uint32_t *b);
+void pa_sconv_s24_32be_to_s16re(unsigned n, const uint8_t *a, int16_t *b);
+void pa_sconv_s24_32be_from_s16re(unsigned n, const int16_t *a, uint8_t *b);
+
+#ifdef WORDS_BIGENDIAN
+#define pa_sconv_float32be_to_s16ne pa_sconv_s16be_from_float32ne
+#define pa_sconv_float32be_from_s16ne pa_sconv_s16be_to_float32ne
+#define pa_sconv_float32le_to_s16ne pa_sconv_s16be_from_float32re
+#define pa_sconv_float32le_from_s16ne pa_sconv_s16be_to_float32re
+#endif
+
+#endif
diff --git a/src/pulsecore/sconv-s16le.c b/src/pulsecore/sconv-s16le.c
new file mode 100644
index 0000000..c503e0e
--- /dev/null
+++ b/src/pulsecore/sconv-s16le.c
@@ -0,0 +1,439 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+/* Despite the name of this file we implement S32 and S24 handling here, too. */
+
+#include <inttypes.h>
+#include <stdio.h>
+#include <math.h>
+
+#include <pulsecore/sconv.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/endianmacros.h>
+
+#include "sconv-s16le.h"
+
+#ifndef INT16_FROM
+#define INT16_FROM PA_INT16_FROM_LE
+#endif
+#ifndef UINT16_FROM
+#define UINT16_FROM PA_UINT16_FROM_LE
+#endif
+
+#ifndef INT16_TO
+#define INT16_TO PA_INT16_TO_LE
+#endif
+#ifndef UINT16_TO
+#define UINT16_TO PA_UINT16_TO_LE
+#endif
+
+#ifndef INT32_FROM
+#define INT32_FROM PA_INT32_FROM_LE
+#endif
+#ifndef UINT32_FROM
+#define UINT32_FROM PA_UINT32_FROM_LE
+#endif
+
+#ifndef INT32_TO
+#define INT32_TO PA_INT32_TO_LE
+#endif
+#ifndef UINT32_TO
+#define UINT32_TO PA_UINT32_TO_LE
+#endif
+
+#ifndef READ24
+#define READ24 PA_READ24LE
+#endif
+#ifndef WRITE24
+#define WRITE24 PA_WRITE24LE
+#endif
+
+#ifndef SWAP_WORDS
+#ifdef WORDS_BIGENDIAN
+#define SWAP_WORDS 1
+#else
+#define SWAP_WORDS 0
+#endif
+#endif
+
+void pa_sconv_s16le_to_float32ne(unsigned n, const int16_t *a, float *b) {
+ pa_assert(a);
+ pa_assert(b);
+
+#if SWAP_WORDS == 1
+ for (; n > 0; n--) {
+ int16_t s = *(a++);
+ *(b++) = INT16_FROM(s) * (1.0f / (1 << 15));
+ }
+#else
+ for (; n > 0; n--)
+ *(b++) = *(a++) * (1.0f / (1 << 15));
+#endif
+}
+
+void pa_sconv_s32le_to_float32ne(unsigned n, const int32_t *a, float *b) {
+ pa_assert(a);
+ pa_assert(b);
+
+#if SWAP_WORDS == 1
+ for (; n > 0; n--) {
+ int32_t s = *(a++);
+ *(b++) = INT32_FROM(s) * (1.0f / (1U << 31));
+ }
+#else
+ for (; n > 0; n--)
+ *(b++) = *(a++) * (1.0f / (1U << 31));
+#endif
+}
+
+void pa_sconv_s16le_from_float32ne(unsigned n, const float *a, int16_t *b) {
+ pa_assert(a);
+ pa_assert(b);
+
+#if SWAP_WORDS == 1
+ for (; n > 0; n--) {
+ int16_t s;
+ float v = *(a++) * (1 << 15);
+
+ s = (int16_t) PA_CLAMP_UNLIKELY(lrintf(v), -0x8000, 0x7FFF);
+ *(b++) = INT16_TO(s);
+ }
+#else
+ for (; n > 0; n--) {
+ float v = *(a++) * (1 << 15);
+
+ *(b++) = (int16_t) PA_CLAMP_UNLIKELY(lrintf(v), -0x8000, 0x7FFF);
+ }
+#endif
+}
+
+void pa_sconv_s32le_from_float32ne(unsigned n, const float *a, int32_t *b) {
+ pa_assert(a);
+ pa_assert(b);
+
+#if SWAP_WORDS == 1
+ for (; n > 0; n--) {
+ int32_t s;
+ float v = *(a++) * (1U << 31);
+
+ s = (int32_t) PA_CLAMP_UNLIKELY(llrintf(v), -0x80000000LL, 0x7FFFFFFFLL);
+ *(b++) = INT32_TO(s);
+ }
+#else
+ for (; n > 0; n--) {
+ float v = *(a++) * (1U << 31);
+
+ *(b++) = (int32_t) PA_CLAMP_UNLIKELY(llrintf(v), -0x80000000LL, 0x7FFFFFFFLL);
+ }
+#endif
+}
+
+void pa_sconv_s16le_to_float32re(unsigned n, const int16_t *a, float *b) {
+ pa_assert(a);
+ pa_assert(b);
+
+ for (; n > 0; n--) {
+ int16_t s = *(a++);
+ float k = INT16_FROM(s) * (1.0f / (1 << 15));
+ PA_WRITE_FLOAT32RE(b++, k);
+ }
+}
+
+void pa_sconv_s32le_to_float32re(unsigned n, const int32_t *a, float *b) {
+ pa_assert(a);
+ pa_assert(b);
+
+ for (; n > 0; n--) {
+ int32_t s = *(a++);
+ float k = INT32_FROM(s) * (1.0f / (1U << 31));
+ PA_WRITE_FLOAT32RE(b++, k);
+ }
+}
+
+void pa_sconv_s16le_from_float32re(unsigned n, const float *a, int16_t *b) {
+ pa_assert(a);
+ pa_assert(b);
+
+ for (; n > 0; n--) {
+ int16_t s;
+ float v = PA_READ_FLOAT32RE(a++) * (1 << 15);
+ s = (int16_t) PA_CLAMP_UNLIKELY(lrintf(v), -0x8000, 0x7FFF);
+ *(b++) = INT16_TO(s);
+ }
+}
+
+void pa_sconv_s32le_from_float32re(unsigned n, const float *a, int32_t *b) {
+ pa_assert(a);
+ pa_assert(b);
+
+ for (; n > 0; n--) {
+ int32_t s;
+ float v = PA_READ_FLOAT32RE(a++) * (1U << 31);
+ s = (int32_t) PA_CLAMP_UNLIKELY(llrintf(v), -0x80000000LL, 0x7FFFFFFFLL);
+ *(b++) = INT32_TO(s);
+ }
+}
+
+void pa_sconv_s32le_to_s16ne(unsigned n, const int32_t*a, int16_t *b) {
+ pa_assert(a);
+ pa_assert(b);
+
+ for (; n > 0; n--) {
+ *b = (int16_t) (INT32_FROM(*a) >> 16);
+ a++;
+ b++;
+ }
+}
+
+void pa_sconv_s32le_to_s16re(unsigned n, const int32_t*a, int16_t *b) {
+ pa_assert(a);
+ pa_assert(b);
+
+ for (; n > 0; n--) {
+ int16_t s = (int16_t) (INT32_FROM(*a) >> 16);
+ *b = PA_INT16_SWAP(s);
+ a++;
+ b++;
+ }
+}
+
+void pa_sconv_s32le_from_s16ne(unsigned n, const int16_t *a, int32_t *b) {
+ pa_assert(a);
+ pa_assert(b);
+
+ for (; n > 0; n--) {
+ *b = INT32_TO(((int32_t) *a) << 16);
+ a++;
+ b++;
+ }
+}
+
+void pa_sconv_s32le_from_s16re(unsigned n, const int16_t *a, int32_t *b) {
+ pa_assert(a);
+ pa_assert(b);
+
+ for (; n > 0; n--) {
+ int32_t s = ((int32_t) PA_INT16_SWAP(*a)) << 16;
+ *b = INT32_TO(s);
+ a++;
+ b++;
+ }
+}
+
+void pa_sconv_s24le_to_s16ne(unsigned n, const uint8_t *a, int16_t *b) {
+ pa_assert(a);
+ pa_assert(b);
+
+ for (; n > 0; n--) {
+ *b = (int16_t) (READ24(a) >> 8);
+ a += 3;
+ b++;
+ }
+}
+
+void pa_sconv_s24le_from_s16ne(unsigned n, const int16_t *a, uint8_t *b) {
+ pa_assert(a);
+ pa_assert(b);
+
+ for (; n > 0; n--) {
+ WRITE24(b, ((uint32_t) *a) << 8);
+ a++;
+ b += 3;
+ }
+}
+
+void pa_sconv_s24le_to_s16re(unsigned n, const uint8_t *a, int16_t *b) {
+ pa_assert(a);
+ pa_assert(b);
+
+ for (; n > 0; n--) {
+ int16_t s = (int16_t) (READ24(a) >> 8);
+ *b = PA_INT16_SWAP(s);
+ a += 3;
+ b++;
+ }
+}
+
+void pa_sconv_s24le_from_s16re(unsigned n, const int16_t *a, uint8_t *b) {
+ pa_assert(a);
+ pa_assert(b);
+
+ for (; n > 0; n--) {
+ uint32_t s = ((uint32_t) PA_INT16_SWAP(*a)) << 8;
+ WRITE24(b, s);
+ a++;
+ b += 3;
+ }
+}
+
+void pa_sconv_s24le_to_float32ne(unsigned n, const uint8_t *a, float *b) {
+ pa_assert(a);
+ pa_assert(b);
+
+ for (; n > 0; n--) {
+ int32_t s = READ24(a) << 8;
+ *b = s * (1.0f / (1U << 31));
+ a += 3;
+ b++;
+ }
+}
+
+void pa_sconv_s24le_from_float32ne(unsigned n, const float *a, uint8_t *b) {
+ pa_assert(a);
+ pa_assert(b);
+
+ for (; n > 0; n--) {
+ int32_t s;
+ float v = *a * (1U << 31);
+ s = (int32_t) PA_CLAMP_UNLIKELY(llrint(v), -0x80000000LL, 0x7FFFFFFFLL);
+ WRITE24(b, ((uint32_t) s) >> 8);
+ a++;
+ b += 3;
+ }
+}
+
+void pa_sconv_s24le_to_float32re(unsigned n, const uint8_t *a, float *b) {
+ pa_assert(a);
+ pa_assert(b);
+
+ for (; n > 0; n--) {
+ int32_t s = READ24(a) << 8;
+ float k = s * (1.0f / (1U << 31));
+ PA_WRITE_FLOAT32RE(b, k);
+ a += 3;
+ b++;
+ }
+}
+
+void pa_sconv_s24le_from_float32re(unsigned n, const float *a, uint8_t *b) {
+ pa_assert(a);
+ pa_assert(b);
+
+ for (; n > 0; n--) {
+ int32_t s;
+ float v = PA_READ_FLOAT32RE(a) * (1U << 31);
+ s = (int32_t) PA_CLAMP_UNLIKELY(llrint(v), -0x80000000LL, 0x7FFFFFFFLL);
+ WRITE24(b, ((uint32_t) s) >> 8);
+ a++;
+ b+=3;
+ }
+}
+
+void pa_sconv_s24_32le_to_s16ne(unsigned n, const uint32_t *a, int16_t *b) {
+ pa_assert(a);
+ pa_assert(b);
+
+ for (; n > 0; n--) {
+ *b = (int16_t) (((int32_t) (UINT32_FROM(*a) << 8)) >> 16);
+ a++;
+ b++;
+ }
+}
+
+void pa_sconv_s24_32le_to_s16re(unsigned n, const uint32_t *a, int16_t *b) {
+ pa_assert(a);
+ pa_assert(b);
+
+ for (; n > 0; n--) {
+ int16_t s = (int16_t) ((int32_t) (UINT32_FROM(*a) << 8) >> 16);
+ *b = PA_INT16_SWAP(s);
+ a++;
+ b++;
+ }
+}
+
+void pa_sconv_s24_32le_from_s16ne(unsigned n, const int16_t *a, uint32_t *b) {
+ pa_assert(a);
+ pa_assert(b);
+
+ for (; n > 0; n--) {
+ *b = UINT32_TO(((uint32_t) ((int32_t) *a << 16)) >> 8);
+ a++;
+ b++;
+ }
+}
+
+void pa_sconv_s24_32le_from_s16re(unsigned n, const int16_t *a, uint32_t *b) {
+ pa_assert(a);
+ pa_assert(b);
+
+ for (; n > 0; n--) {
+ uint32_t s = ((uint32_t) ((int32_t) PA_INT16_SWAP(*a) << 16)) >> 8;
+ *b = UINT32_TO(s);
+ a++;
+ b++;
+ }
+}
+
+void pa_sconv_s24_32le_to_float32ne(unsigned n, const uint32_t *a, float *b) {
+ pa_assert(a);
+ pa_assert(b);
+
+ for (; n > 0; n--) {
+ int32_t s = (int32_t) (UINT32_FROM(*a) << 8);
+ *b = s * (1.0f / (1U << 31));
+ a++;
+ b++;
+ }
+}
+
+void pa_sconv_s24_32le_to_float32re(unsigned n, const uint32_t *a, float *b) {
+ pa_assert(a);
+ pa_assert(b);
+
+ for (; n > 0; n--) {
+ int32_t s = (int32_t) (UINT32_FROM(*a) << 8);
+ float k = s * (1.0f / (1U << 31));
+ PA_WRITE_FLOAT32RE(b, k);
+ a++;
+ b++;
+ }
+}
+
+void pa_sconv_s24_32le_from_float32ne(unsigned n, const float *a, uint32_t *b) {
+ pa_assert(a);
+ pa_assert(b);
+
+ for (; n > 0; n--) {
+ int32_t s;
+ float v = *a * (1U << 31);
+ s = (int32_t) PA_CLAMP_UNLIKELY(llrint(v), -0x80000000LL, 0x7FFFFFFFLL);
+ *b = UINT32_TO(((uint32_t) s) >> 8);
+ a++;
+ b++;
+ }
+}
+
+void pa_sconv_s24_32le_from_float32re(unsigned n, const float *a, uint32_t *b) {
+ pa_assert(a);
+ pa_assert(b);
+
+ for (; n > 0; n--) {
+ int32_t s;
+ float v = PA_READ_FLOAT32RE(a) * (1U << 31);
+ s = (int32_t) PA_CLAMP_UNLIKELY(llrint(v), -0x80000000LL, 0x7FFFFFFFLL);
+ *b = UINT32_TO(((uint32_t) s) >> 8);
+ a++;
+ b++;
+ }
+}
diff --git a/src/pulsecore/sconv-s16le.h b/src/pulsecore/sconv-s16le.h
new file mode 100644
index 0000000..5301373
--- /dev/null
+++ b/src/pulsecore/sconv-s16le.h
@@ -0,0 +1,67 @@
+#ifndef foosconv_s16lefoo
+#define foosconv_s16lefoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <inttypes.h>
+
+void pa_sconv_s16le_to_float32ne(unsigned n, const int16_t *a, float *b);
+void pa_sconv_s16le_from_float32ne(unsigned n, const float *a, int16_t *b);
+void pa_sconv_s16le_to_float32re(unsigned n, const int16_t *a, float *b);
+void pa_sconv_s16le_from_float32re(unsigned n, const float *a, int16_t *b);
+
+void pa_sconv_s32le_to_float32ne(unsigned n, const int32_t *a, float *b);
+void pa_sconv_s32le_from_float32ne(unsigned n, const float *a, int32_t *b);
+void pa_sconv_s32le_to_float32re(unsigned n, const int32_t *a, float *b);
+void pa_sconv_s32le_from_float32re(unsigned n, const float *a, int32_t *b);
+
+void pa_sconv_s24le_to_float32ne(unsigned n, const uint8_t *a, float *b);
+void pa_sconv_s24le_from_float32ne(unsigned n, const float *a, uint8_t *b);
+void pa_sconv_s24le_to_float32re(unsigned n, const uint8_t *a, float *b);
+void pa_sconv_s24le_from_float32re(unsigned n, const float *a, uint8_t *b);
+
+void pa_sconv_s24_32le_to_float32ne(unsigned n, const uint32_t *a, float *b);
+void pa_sconv_s24_32le_from_float32ne(unsigned n, const float *a, uint32_t *b);
+void pa_sconv_s24_32le_to_float32re(unsigned n, const uint32_t *a, float *b);
+void pa_sconv_s24_32le_from_float32re(unsigned n, const float *a, uint32_t *b);
+
+void pa_sconv_s32le_to_s16ne(unsigned n, const int32_t *a, int16_t *b);
+void pa_sconv_s32le_from_s16ne(unsigned n, const int16_t *a, int32_t *b);
+void pa_sconv_s32le_to_s16re(unsigned n, const int32_t *a, int16_t *b);
+void pa_sconv_s32le_from_s16re(unsigned n, const int16_t *a, int32_t *b);
+
+void pa_sconv_s24le_to_s16ne(unsigned n, const uint8_t *a, int16_t *b);
+void pa_sconv_s24le_from_s16ne(unsigned n, const int16_t *a, uint8_t *b);
+void pa_sconv_s24le_to_s16re(unsigned n, const uint8_t *a, int16_t *b);
+void pa_sconv_s24le_from_s16re(unsigned n, const int16_t *a, uint8_t *b);
+
+void pa_sconv_s24_32le_to_s16ne(unsigned n, const uint32_t *a, int16_t *b);
+void pa_sconv_s24_32le_from_s16ne(unsigned n, const int16_t *a, uint32_t *b);
+void pa_sconv_s24_32le_to_s16re(unsigned n, const uint32_t *a, int16_t *b);
+void pa_sconv_s24_32le_from_s16re(unsigned n, const int16_t *a, uint32_t *b);
+
+#ifndef WORDS_BIGENDIAN
+#define pa_sconv_float32be_to_s16ne pa_sconv_s16le_from_float32re
+#define pa_sconv_float32be_from_s16ne pa_sconv_s16le_to_float32re
+#define pa_sconv_float32le_to_s16ne pa_sconv_s16le_from_float32ne
+#define pa_sconv_float32le_from_s16ne pa_sconv_s16le_to_float32ne
+#endif
+
+#endif
diff --git a/src/pulsecore/sconv.c b/src/pulsecore/sconv.c
new file mode 100644
index 0000000..0781b6e
--- /dev/null
+++ b/src/pulsecore/sconv.c
@@ -0,0 +1,296 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include <pulsecore/g711.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/endianmacros.h>
+
+#include <pulsecore/sconv-s16le.h>
+#include <pulsecore/sconv-s16be.h>
+
+#include "sconv.h"
+
+/* u8 */
+static void u8_to_float32ne(unsigned n, const uint8_t *a, float *b) {
+ pa_assert(a);
+ pa_assert(b);
+
+ for (; n > 0; n--, a++, b++)
+ *b = (*a * 1.0/128.0) - 1.0;
+}
+
+static void u8_from_float32ne(unsigned n, const float *a, uint8_t *b) {
+ pa_assert(a);
+ pa_assert(b);
+
+ for (; n > 0; n--, a++, b++) {
+ float v;
+ v = (*a * 127.0) + 128.0;
+ v = PA_CLAMP_UNLIKELY (v, 0.0, 255.0);
+ *b = rint (v);
+ }
+}
+
+static void u8_to_s16ne(unsigned n, const uint8_t *a, int16_t *b) {
+ pa_assert(a);
+ pa_assert(b);
+
+ for (; n > 0; n--, a++, b++)
+ *b = (((int16_t)*a) - 128) << 8;
+}
+
+static void u8_from_s16ne(unsigned n, const int16_t *a, uint8_t *b) {
+
+ pa_assert(a);
+ pa_assert(b);
+
+ for (; n > 0; n--, a++, b++)
+ *b = (uint8_t) ((uint16_t) *a >> 8) + (uint8_t) 0x80U;
+}
+
+/* float32 */
+
+static void float32ne_to_float32ne(unsigned n, const float *a, float *b) {
+ pa_assert(a);
+ pa_assert(b);
+
+ memcpy(b, a, (int) (sizeof(float) * n));
+}
+
+static void float32re_to_float32ne(unsigned n, const float *a, float *b) {
+ pa_assert(a);
+ pa_assert(b);
+
+ for (; n > 0; n--, a++, b++)
+ *((uint32_t *) b) = PA_UINT32_SWAP(*((uint32_t *) a));
+}
+
+/* s16 */
+
+static void s16ne_to_s16ne(unsigned n, const int16_t *a, int16_t *b) {
+ pa_assert(a);
+ pa_assert(b);
+
+ memcpy(b, a, (int) (sizeof(int16_t) * n));
+}
+
+static void s16re_to_s16ne(unsigned n, const int16_t *a, int16_t *b) {
+ pa_assert(a);
+ pa_assert(b);
+
+ for (; n > 0; n--, a++, b++)
+ *b = PA_INT16_SWAP(*a);
+}
+
+/* ulaw */
+
+static void ulaw_to_float32ne(unsigned n, const uint8_t *a, float *b) {
+ pa_assert(a);
+ pa_assert(b);
+
+ for (; n > 0; n--)
+ *(b++) = (float) st_ulaw2linear16(*(a++)) / 0x8000;
+}
+
+static void ulaw_from_float32ne(unsigned n, const float *a, uint8_t *b) {
+ pa_assert(a);
+ pa_assert(b);
+
+ for (; n > 0; n--) {
+ float v = *(a++);
+ v = PA_CLAMP_UNLIKELY(v, -1.0f, 1.0f);
+ v *= 0x1FFF;
+ *(b++) = st_14linear2ulaw((int16_t) lrintf(v));
+ }
+}
+
+static void ulaw_to_s16ne(unsigned n, const uint8_t *a, int16_t *b) {
+ pa_assert(a);
+ pa_assert(b);
+
+ for (; n > 0; n--, a++, b++)
+ *b = st_ulaw2linear16(*a);
+}
+
+static void ulaw_from_s16ne(unsigned n, const int16_t *a, uint8_t *b) {
+ pa_assert(a);
+ pa_assert(b);
+
+ for (; n > 0; n--, a++, b++)
+ *b = st_14linear2ulaw(*a >> 2);
+}
+
+/* alaw */
+
+static void alaw_to_float32ne(unsigned n, const uint8_t *a, float *b) {
+ pa_assert(a);
+ pa_assert(b);
+
+ for (; n > 0; n--, a++, b++)
+ *b = (float) st_alaw2linear16(*a) / 0x8000;
+}
+
+static void alaw_from_float32ne(unsigned n, const float *a, uint8_t *b) {
+ pa_assert(a);
+ pa_assert(b);
+
+ for (; n > 0; n--, a++, b++) {
+ float v = *a;
+ v = PA_CLAMP_UNLIKELY(v, -1.0f, 1.0f);
+ v *= 0xFFF;
+ *b = st_13linear2alaw((int16_t) lrintf(v));
+ }
+}
+
+static void alaw_to_s16ne(unsigned n, const int8_t *a, int16_t *b) {
+ pa_assert(a);
+ pa_assert(b);
+
+ for (; n > 0; n--, a++, b++)
+ *b = st_alaw2linear16((uint8_t) *a);
+}
+
+static void alaw_from_s16ne(unsigned n, const int16_t *a, uint8_t *b) {
+ pa_assert(a);
+ pa_assert(b);
+
+ for (; n > 0; n--, a++, b++)
+ *b = st_13linear2alaw(*a >> 3);
+}
+
+static pa_convert_func_t to_float32ne_table[] = {
+ [PA_SAMPLE_U8] = (pa_convert_func_t) u8_to_float32ne,
+ [PA_SAMPLE_ALAW] = (pa_convert_func_t) alaw_to_float32ne,
+ [PA_SAMPLE_ULAW] = (pa_convert_func_t) ulaw_to_float32ne,
+ [PA_SAMPLE_S16LE] = (pa_convert_func_t) pa_sconv_s16le_to_float32ne,
+ [PA_SAMPLE_S16BE] = (pa_convert_func_t) pa_sconv_s16be_to_float32ne,
+ [PA_SAMPLE_S32LE] = (pa_convert_func_t) pa_sconv_s32le_to_float32ne,
+ [PA_SAMPLE_S32BE] = (pa_convert_func_t) pa_sconv_s32be_to_float32ne,
+ [PA_SAMPLE_S24LE] = (pa_convert_func_t) pa_sconv_s24le_to_float32ne,
+ [PA_SAMPLE_S24BE] = (pa_convert_func_t) pa_sconv_s24be_to_float32ne,
+ [PA_SAMPLE_S24_32LE] = (pa_convert_func_t) pa_sconv_s24_32le_to_float32ne,
+ [PA_SAMPLE_S24_32BE] = (pa_convert_func_t) pa_sconv_s24_32be_to_float32ne,
+ [PA_SAMPLE_FLOAT32NE] = (pa_convert_func_t) float32ne_to_float32ne,
+ [PA_SAMPLE_FLOAT32RE] = (pa_convert_func_t) float32re_to_float32ne,
+};
+
+pa_convert_func_t pa_get_convert_to_float32ne_function(pa_sample_format_t f) {
+ pa_assert(pa_sample_format_valid(f));
+
+ return to_float32ne_table[f];
+}
+
+void pa_set_convert_to_float32ne_function(pa_sample_format_t f, pa_convert_func_t func) {
+ pa_assert(pa_sample_format_valid(f));
+
+ to_float32ne_table[f] = func;
+}
+
+static pa_convert_func_t from_float32ne_table[] = {
+ [PA_SAMPLE_U8] = (pa_convert_func_t) u8_from_float32ne,
+ [PA_SAMPLE_S16LE] = (pa_convert_func_t) pa_sconv_s16le_from_float32ne,
+ [PA_SAMPLE_S16BE] = (pa_convert_func_t) pa_sconv_s16be_from_float32ne,
+ [PA_SAMPLE_S32LE] = (pa_convert_func_t) pa_sconv_s32le_from_float32ne,
+ [PA_SAMPLE_S32BE] = (pa_convert_func_t) pa_sconv_s32be_from_float32ne,
+ [PA_SAMPLE_S24LE] = (pa_convert_func_t) pa_sconv_s24le_from_float32ne,
+ [PA_SAMPLE_S24BE] = (pa_convert_func_t) pa_sconv_s24be_from_float32ne,
+ [PA_SAMPLE_S24_32LE] = (pa_convert_func_t) pa_sconv_s24_32le_from_float32ne,
+ [PA_SAMPLE_S24_32BE] = (pa_convert_func_t) pa_sconv_s24_32be_from_float32ne,
+ [PA_SAMPLE_FLOAT32NE] = (pa_convert_func_t) float32ne_to_float32ne,
+ [PA_SAMPLE_FLOAT32RE] = (pa_convert_func_t) float32re_to_float32ne,
+ [PA_SAMPLE_ALAW] = (pa_convert_func_t) alaw_from_float32ne,
+ [PA_SAMPLE_ULAW] = (pa_convert_func_t) ulaw_from_float32ne
+};
+
+pa_convert_func_t pa_get_convert_from_float32ne_function(pa_sample_format_t f) {
+ pa_assert(pa_sample_format_valid(f));
+
+ return from_float32ne_table[f];
+}
+
+void pa_set_convert_from_float32ne_function(pa_sample_format_t f, pa_convert_func_t func) {
+ pa_assert(pa_sample_format_valid(f));
+
+ from_float32ne_table[f] = func;
+}
+
+static pa_convert_func_t to_s16ne_table[] = {
+ [PA_SAMPLE_U8] = (pa_convert_func_t) u8_to_s16ne,
+ [PA_SAMPLE_S16NE] = (pa_convert_func_t) s16ne_to_s16ne,
+ [PA_SAMPLE_S16RE] = (pa_convert_func_t) s16re_to_s16ne,
+ [PA_SAMPLE_FLOAT32BE] = (pa_convert_func_t) pa_sconv_float32be_to_s16ne,
+ [PA_SAMPLE_FLOAT32LE] = (pa_convert_func_t) pa_sconv_float32le_to_s16ne,
+ [PA_SAMPLE_S32BE] = (pa_convert_func_t) pa_sconv_s32be_to_s16ne,
+ [PA_SAMPLE_S32LE] = (pa_convert_func_t) pa_sconv_s32le_to_s16ne,
+ [PA_SAMPLE_S24BE] = (pa_convert_func_t) pa_sconv_s24be_to_s16ne,
+ [PA_SAMPLE_S24LE] = (pa_convert_func_t) pa_sconv_s24le_to_s16ne,
+ [PA_SAMPLE_S24_32BE] = (pa_convert_func_t) pa_sconv_s24_32be_to_s16ne,
+ [PA_SAMPLE_S24_32LE] = (pa_convert_func_t) pa_sconv_s24_32le_to_s16ne,
+ [PA_SAMPLE_ALAW] = (pa_convert_func_t) alaw_to_s16ne,
+ [PA_SAMPLE_ULAW] = (pa_convert_func_t) ulaw_to_s16ne
+};
+
+pa_convert_func_t pa_get_convert_to_s16ne_function(pa_sample_format_t f) {
+ pa_assert(pa_sample_format_valid(f));
+
+ return to_s16ne_table[f];
+}
+
+void pa_set_convert_to_s16ne_function(pa_sample_format_t f, pa_convert_func_t func) {
+ pa_assert(pa_sample_format_valid(f));
+
+ to_s16ne_table[f] = func;
+}
+
+static pa_convert_func_t from_s16ne_table[] = {
+ [PA_SAMPLE_U8] = (pa_convert_func_t) u8_from_s16ne,
+ [PA_SAMPLE_S16NE] = (pa_convert_func_t) s16ne_to_s16ne,
+ [PA_SAMPLE_S16RE] = (pa_convert_func_t) s16re_to_s16ne,
+ [PA_SAMPLE_FLOAT32BE] = (pa_convert_func_t) pa_sconv_float32be_from_s16ne,
+ [PA_SAMPLE_FLOAT32LE] = (pa_convert_func_t) pa_sconv_float32le_from_s16ne,
+ [PA_SAMPLE_S32BE] = (pa_convert_func_t) pa_sconv_s32be_from_s16ne,
+ [PA_SAMPLE_S32LE] = (pa_convert_func_t) pa_sconv_s32le_from_s16ne,
+ [PA_SAMPLE_S24BE] = (pa_convert_func_t) pa_sconv_s24be_from_s16ne,
+ [PA_SAMPLE_S24LE] = (pa_convert_func_t) pa_sconv_s24le_from_s16ne,
+ [PA_SAMPLE_S24_32BE] = (pa_convert_func_t) pa_sconv_s24_32be_from_s16ne,
+ [PA_SAMPLE_S24_32LE] = (pa_convert_func_t) pa_sconv_s24_32le_from_s16ne,
+ [PA_SAMPLE_ALAW] = (pa_convert_func_t) alaw_from_s16ne,
+ [PA_SAMPLE_ULAW] = (pa_convert_func_t) ulaw_from_s16ne,
+};
+
+pa_convert_func_t pa_get_convert_from_s16ne_function(pa_sample_format_t f) {
+ pa_assert(pa_sample_format_valid(f));
+
+ return from_s16ne_table[f];
+}
+
+void pa_set_convert_from_s16ne_function(pa_sample_format_t f, pa_convert_func_t func) {
+ pa_assert(pa_sample_format_valid(f));
+
+ from_s16ne_table[f] = func;
+}
diff --git a/src/pulsecore/sconv.h b/src/pulsecore/sconv.h
new file mode 100644
index 0000000..c457429
--- /dev/null
+++ b/src/pulsecore/sconv.h
@@ -0,0 +1,41 @@
+#ifndef foosconvhfoo
+#define foosconvhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulse/gccmacro.h>
+#include <pulse/sample.h>
+
+typedef void (*pa_convert_func_t)(unsigned n, const void *a, void *b);
+
+pa_convert_func_t pa_get_convert_to_float32ne_function(pa_sample_format_t f) PA_GCC_PURE;
+pa_convert_func_t pa_get_convert_from_float32ne_function(pa_sample_format_t f) PA_GCC_PURE;
+
+pa_convert_func_t pa_get_convert_to_s16ne_function(pa_sample_format_t f) PA_GCC_PURE;
+pa_convert_func_t pa_get_convert_from_s16ne_function(pa_sample_format_t f) PA_GCC_PURE;
+
+void pa_set_convert_to_float32ne_function(pa_sample_format_t f, pa_convert_func_t func);
+void pa_set_convert_from_float32ne_function(pa_sample_format_t f, pa_convert_func_t func);
+
+void pa_set_convert_to_s16ne_function(pa_sample_format_t f, pa_convert_func_t func);
+void pa_set_convert_from_s16ne_function(pa_sample_format_t f, pa_convert_func_t func);
+
+#endif
diff --git a/src/pulsecore/sconv_neon.c b/src/pulsecore/sconv_neon.c
new file mode 100644
index 0000000..11d94d2
--- /dev/null
+++ b/src/pulsecore/sconv_neon.c
@@ -0,0 +1,96 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2012 Peter Meerwald <p.meerwald@bct-electronic.com>
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulsecore/macro.h>
+#include <pulsecore/endianmacros.h>
+
+#include "cpu-arm.h"
+#include "sconv.h"
+
+#include <math.h>
+#include <arm_neon.h>
+
+static void pa_sconv_s16le_from_f32ne_neon(unsigned n, const float *src, int16_t *dst) {
+ unsigned i = n & 3;
+
+ __asm__ __volatile__ (
+ "movs %[n], %[n], lsr #2 \n\t"
+ "beq 2f \n\t"
+
+ "1: \n\t"
+ "vld1.32 {q0}, [%[src]]! \n\t"
+ "vcvt.s32.f32 q0, q0, #31 \n\t" /* s32<-f32 as 16:16 fixed-point, with implicit multiplication by 32768 */
+ "vqrshrn.s32 d0, q0, #16 \n\t" /* shift, round, narrow */
+ "subs %[n], %[n], #1 \n\t"
+ "vst1.16 {d0}, [%[dst]]! \n\t"
+ "bgt 1b \n\t"
+
+ "2: \n\t"
+
+ : [dst] "+r" (dst), [src] "+r" (src), [n] "+r" (n) /* output operands (or input operands that get modified) */
+ : /* input operands */
+ : "memory", "cc", "q0" /* clobber list */
+ );
+
+ /* leftovers */
+ while (i--) {
+ *dst++ = (int16_t) PA_CLAMP_UNLIKELY(lrintf(*src * (1 << 15)), -0x8000, 0x7FFF);
+ src++;
+ }
+}
+
+static void pa_sconv_s16le_to_f32ne_neon(unsigned n, const int16_t *src, float *dst) {
+ unsigned i = n & 3;
+ const float invscale = 1.0f / (1 << 15);
+
+ __asm__ __volatile__ (
+ "movs %[n], %[n], lsr #2 \n\t"
+ "beq 2f \n\t"
+
+ "1: \n\t"
+ "vld1.16 {d0}, [%[src]]! \n\t"
+ "vmovl.s16 q0, d0 \n\t" /* widen */
+ "vcvt.f32.s32 q0, q0, #15 \n\t" /* f32<-s32 and divide by (1<<15) */
+ "subs %[n], %[n], #1 \n\t"
+ "vst1.32 {q0}, [%[dst]]! \n\t"
+ "bgt 1b \n\t"
+
+ "2: \n\t"
+
+ : [dst] "+r" (dst), [src] "+r" (src), [n] "+r" (n) /* output operands (or input operands that get modified) */
+ : /* input operands */
+ : "memory", "cc", "q0" /* clobber list */
+ );
+
+ /* leftovers */
+ while (i--) {
+ *dst++ = *src++ * invscale;
+ }
+}
+
+void pa_convert_func_init_neon(pa_cpu_arm_flag_t flags) {
+ pa_log_info("Initialising ARM NEON optimized conversions.");
+ pa_set_convert_from_float32ne_function(PA_SAMPLE_S16LE, (pa_convert_func_t) pa_sconv_s16le_from_f32ne_neon);
+ pa_set_convert_to_float32ne_function(PA_SAMPLE_S16LE, (pa_convert_func_t) pa_sconv_s16le_to_f32ne_neon);
+#ifndef WORDS_BIGENDIAN
+ pa_set_convert_from_s16ne_function(PA_SAMPLE_FLOAT32LE, (pa_convert_func_t) pa_sconv_s16le_to_f32ne_neon);
+ pa_set_convert_to_s16ne_function(PA_SAMPLE_FLOAT32LE, (pa_convert_func_t) pa_sconv_s16le_from_f32ne_neon);
+#endif
+}
diff --git a/src/pulsecore/sconv_sse.c b/src/pulsecore/sconv_sse.c
new file mode 100644
index 0000000..1b097b8
--- /dev/null
+++ b/src/pulsecore/sconv_sse.c
@@ -0,0 +1,177 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <pulsecore/macro.h>
+#include <pulsecore/endianmacros.h>
+
+#include "cpu-x86.h"
+#include "sconv.h"
+
+#if (!defined(__APPLE__) && !defined(__FreeBSD__) && !defined(__FreeBSD_kernel__) && defined (__i386__)) || defined (__amd64__)
+
+static const PA_DECLARE_ALIGNED (16, float, scale[4]) = { 0x8000, 0x8000, 0x8000, 0x8000 };
+
+static void pa_sconv_s16le_from_f32ne_sse(unsigned n, const float *a, int16_t *b) {
+ pa_reg_x86 temp, i;
+
+ __asm__ __volatile__ (
+ " movaps %5, %%xmm5 \n\t"
+ " xor %0, %0 \n\t"
+
+ " mov %4, %1 \n\t"
+ " sar $3, %1 \n\t" /* 8 floats at a time */
+ " cmp $0, %1 \n\t"
+ " je 2f \n\t"
+
+ "1: \n\t"
+ " movups (%q2, %0, 2), %%xmm0 \n\t" /* read 8 floats */
+ " movups 16(%q2, %0, 2), %%xmm2 \n\t"
+ " mulps %%xmm5, %%xmm0 \n\t" /* *= 0x8000 */
+ " mulps %%xmm5, %%xmm2 \n\t"
+
+ " cvtps2pi %%xmm0, %%mm0 \n\t" /* low part to int */
+ " cvtps2pi %%xmm2, %%mm2 \n\t"
+ " movhlps %%xmm0, %%xmm0 \n\t" /* bring high part in position */
+ " movhlps %%xmm2, %%xmm2 \n\t"
+ " cvtps2pi %%xmm0, %%mm1 \n\t" /* high part to int */
+ " cvtps2pi %%xmm2, %%mm3 \n\t"
+
+ " packssdw %%mm1, %%mm0 \n\t" /* pack parts */
+ " packssdw %%mm3, %%mm2 \n\t"
+ " movq %%mm0, (%q3, %0) \n\t"
+ " movq %%mm2, 8(%q3, %0) \n\t"
+
+ " add $16, %0 \n\t"
+ " dec %1 \n\t"
+ " jne 1b \n\t"
+
+ "2: \n\t"
+ " mov %4, %1 \n\t" /* prepare for leftovers */
+ " and $7, %1 \n\t"
+ " je 5f \n\t"
+
+ "3: \n\t"
+ " movss (%q2, %0, 2), %%xmm0 \n\t"
+ " mulss %%xmm5, %%xmm0 \n\t"
+ " cvtss2si %%xmm0, %4 \n\t"
+ " add $0x8000, %4 \n\t" /* check for saturation */
+ " and $~0xffff, %4 \n\t"
+ " cvtss2si %%xmm0, %4 \n\t"
+ " je 4f \n\t"
+ " sar $31, %4 \n\t"
+ " xor $0x7fff, %4 \n\t"
+
+ "4: \n\t"
+ " movw %w4, (%q3, %0) \n\t" /* store leftover */
+ " add $2, %0 \n\t"
+ " dec %1 \n\t"
+ " jne 3b \n\t"
+
+ "5: \n\t"
+ " emms \n\t"
+
+ : "=&r" (i), "=&r" (temp)
+ : "r" (a), "r" (b), "r" ((pa_reg_x86)n), "m" (*scale)
+ : "cc", "memory"
+ );
+}
+
+static void pa_sconv_s16le_from_f32ne_sse2(unsigned n, const float *a, int16_t *b) {
+ pa_reg_x86 temp, i;
+
+ __asm__ __volatile__ (
+ " movaps %5, %%xmm5 \n\t"
+ " xor %0, %0 \n\t"
+
+ " mov %4, %1 \n\t"
+ " sar $3, %1 \n\t" /* 8 floats at a time */
+ " cmp $0, %1 \n\t"
+ " je 2f \n\t"
+
+ "1: \n\t"
+ " movups (%q2, %0, 2), %%xmm0 \n\t" /* read 8 floats */
+ " movups 16(%q2, %0, 2), %%xmm2 \n\t"
+ " mulps %%xmm5, %%xmm0 \n\t" /* *= 0x8000 */
+ " mulps %%xmm5, %%xmm2 \n\t"
+
+ " cvtps2dq %%xmm0, %%xmm0 \n\t"
+ " cvtps2dq %%xmm2, %%xmm2 \n\t"
+
+ " packssdw %%xmm2, %%xmm0 \n\t"
+ " movdqu %%xmm0, (%q3, %0) \n\t"
+
+ " add $16, %0 \n\t"
+ " dec %1 \n\t"
+ " jne 1b \n\t"
+
+ "2: \n\t"
+ " mov %4, %1 \n\t" /* prepare for leftovers */
+ " and $7, %1 \n\t"
+ " je 5f \n\t"
+
+ "3: \n\t"
+ " movss (%q2, %0, 2), %%xmm0 \n\t"
+ " mulss %%xmm5, %%xmm0 \n\t"
+ " cvtss2si %%xmm0, %4 \n\t"
+ " add $0x8000, %4 \n\t"
+ " and $~0xffff, %4 \n\t" /* check for saturation */
+ " cvtss2si %%xmm0, %4 \n\t"
+ " je 4f \n\t"
+ " sar $31, %4 \n\t"
+ " xor $0x7fff, %4 \n\t"
+
+ "4: \n\t"
+ " movw %w4, (%q3, %0) \n\t" /* store leftover */
+ " add $2, %0 \n\t"
+ " dec %1 \n\t"
+ " jne 3b \n\t"
+
+ "5: \n\t"
+
+ : "=&r" (i), "=&r" (temp)
+ : "r" (a), "r" (b), "r" ((pa_reg_x86)n), "m" (*scale)
+ : "cc", "memory"
+ );
+}
+
+#endif /* defined (__i386__) || defined (__amd64__) */
+
+void pa_convert_func_init_sse(pa_cpu_x86_flag_t flags) {
+#if (!defined(__APPLE__) && !defined(__FreeBSD__) && !defined(__FreeBSD_kernel__) && defined (__i386__)) || defined (__amd64__)
+
+ if (flags & PA_CPU_X86_SSE2) {
+ pa_log_info("Initialising SSE2 optimized conversions.");
+ pa_set_convert_from_float32ne_function(PA_SAMPLE_S16LE, (pa_convert_func_t) pa_sconv_s16le_from_f32ne_sse2);
+ pa_set_convert_to_s16ne_function(PA_SAMPLE_FLOAT32LE, (pa_convert_func_t) pa_sconv_s16le_from_f32ne_sse2);
+ } else if (flags & PA_CPU_X86_SSE) {
+ pa_log_info("Initialising SSE optimized conversions.");
+ pa_set_convert_from_float32ne_function(PA_SAMPLE_S16LE, (pa_convert_func_t) pa_sconv_s16le_from_f32ne_sse);
+ pa_set_convert_to_s16ne_function(PA_SAMPLE_FLOAT32LE, (pa_convert_func_t) pa_sconv_s16le_from_f32ne_sse);
+ }
+
+#endif /* defined (__i386__) || defined (__amd64__) */
+}
diff --git a/src/pulsecore/semaphore-osx.c b/src/pulsecore/semaphore-osx.c
new file mode 100644
index 0000000..c957f26
--- /dev/null
+++ b/src/pulsecore/semaphore-osx.c
@@ -0,0 +1,92 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2006 Lennart Poettering
+ Copyright 2013 Albert Zeyer
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <pthread.h>
+#include <semaphore.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/atomic.h>
+#include <pulsecore/core-util.h>
+
+#include "semaphore.h"
+
+/* OSX doesn't support unnamed semaphores (via sem_init).
+ * Thus, we use a counter to give them enumerated names. */
+static pa_atomic_t id_counter = PA_ATOMIC_INIT(0);
+
+struct pa_semaphore {
+ sem_t *sem;
+ int id;
+};
+
+static char *sem_name(char *fn, size_t l, int id) {
+ pa_snprintf(fn, l, "/pulse-sem-%u-%u", getpid(), id);
+ return fn;
+}
+
+pa_semaphore *pa_semaphore_new(unsigned value) {
+ pa_semaphore *s;
+ char fn[32];
+
+ s = pa_xnew(pa_semaphore, 1);
+ s->id = pa_atomic_inc(&id_counter);
+ sem_name(fn, sizeof(fn), s->id);
+ sem_unlink(fn); /* in case an old stale semaphore is left around */
+ pa_assert_se(s->sem = sem_open(fn, O_CREAT|O_EXCL, 0700, value));
+ pa_assert(s->sem != SEM_FAILED);
+ return s;
+}
+
+void pa_semaphore_free(pa_semaphore *s) {
+ char fn[32];
+
+ pa_assert(s);
+
+ pa_assert_se(sem_close(s->sem) == 0);
+ sem_name(fn, sizeof(fn), s->id);
+ pa_assert_se(sem_unlink(fn) == 0);
+ pa_xfree(s);
+}
+
+void pa_semaphore_post(pa_semaphore *s) {
+ pa_assert(s);
+ pa_assert_se(sem_post(s->sem) == 0);
+}
+
+void pa_semaphore_wait(pa_semaphore *s) {
+ int ret;
+
+ pa_assert(s);
+
+ do {
+ ret = sem_wait(s->sem);
+ } while (ret < 0 && errno == EINTR);
+
+ pa_assert(ret == 0);
+}
diff --git a/src/pulsecore/semaphore-posix.c b/src/pulsecore/semaphore-posix.c
new file mode 100644
index 0000000..7907c19
--- /dev/null
+++ b/src/pulsecore/semaphore-posix.c
@@ -0,0 +1,87 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <pthread.h>
+#include <semaphore.h>
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/macro.h>
+
+#include "semaphore.h"
+
+struct pa_semaphore {
+ sem_t sem;
+};
+
+pa_semaphore* pa_semaphore_new(unsigned value) {
+ pa_semaphore *s;
+
+ s = pa_xnew(pa_semaphore, 1);
+ pa_assert_se(sem_init(&s->sem, 0, value) == 0);
+ return s;
+}
+
+void pa_semaphore_free(pa_semaphore *s) {
+ pa_assert(s);
+ pa_assert_se(sem_destroy(&s->sem) == 0);
+ pa_xfree(s);
+}
+
+void pa_semaphore_post(pa_semaphore *s) {
+ pa_assert(s);
+ pa_assert_se(sem_post(&s->sem) == 0);
+}
+
+void pa_semaphore_wait(pa_semaphore *s) {
+ int ret;
+ pa_assert(s);
+
+ do {
+ ret = sem_wait(&s->sem);
+ } while (ret < 0 && errno == EINTR);
+
+ pa_assert(ret == 0);
+}
+
+pa_semaphore* pa_static_semaphore_get(pa_static_semaphore *s, unsigned value) {
+ pa_semaphore *m;
+
+ pa_assert(s);
+
+ /* First, check if already initialized and short cut */
+ if ((m = pa_atomic_ptr_load(&s->ptr)))
+ return m;
+
+ /* OK, not initialized, so let's allocate, and fill in */
+ m = pa_semaphore_new(value);
+ if ((pa_atomic_ptr_cmpxchg(&s->ptr, NULL, m)))
+ return m;
+
+ pa_semaphore_free(m);
+
+ /* Him, filling in failed, so someone else must have filled in
+ * already */
+ pa_assert_se(m = pa_atomic_ptr_load(&s->ptr));
+ return m;
+}
diff --git a/src/pulsecore/semaphore-win32.c b/src/pulsecore/semaphore-win32.c
new file mode 100644
index 0000000..660d4bd
--- /dev/null
+++ b/src/pulsecore/semaphore-win32.c
@@ -0,0 +1,60 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <windows.h>
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/macro.h>
+
+#include "semaphore.h"
+
+struct pa_semaphore {
+ HANDLE sema;
+};
+
+pa_semaphore* pa_semaphore_new(unsigned value) {
+ pa_semaphore *s;
+
+ s = pa_xnew(pa_semaphore, 1);
+
+ s->sema = CreateSemaphore(NULL, value, 32767, NULL);
+ pa_assert(s->sema != NULL);
+
+ return s;
+}
+
+void pa_semaphore_free(pa_semaphore *s) {
+ pa_assert(s);
+ CloseHandle(s->sema);
+ pa_xfree(s);
+}
+
+void pa_semaphore_post(pa_semaphore *s) {
+ pa_assert(s);
+ ReleaseSemaphore(s->sema, 1, NULL);
+}
+
+void pa_semaphore_wait(pa_semaphore *s) {
+ pa_assert(s);
+ WaitForSingleObject(s->sema, INFINITE);
+}
diff --git a/src/pulsecore/semaphore.h b/src/pulsecore/semaphore.h
new file mode 100644
index 0000000..4c746f1
--- /dev/null
+++ b/src/pulsecore/semaphore.h
@@ -0,0 +1,46 @@
+#ifndef foopulsesemaphorehfoo
+#define foopulsesemaphorehfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulsecore/macro.h>
+#include <pulsecore/atomic.h>
+
+typedef struct pa_semaphore pa_semaphore;
+
+pa_semaphore* pa_semaphore_new(unsigned value);
+void pa_semaphore_free(pa_semaphore *m);
+
+void pa_semaphore_post(pa_semaphore *m);
+void pa_semaphore_wait(pa_semaphore *m);
+
+/* Static semaphores are basically just atomically updated pointers to
+ * pa_semaphore objects */
+
+typedef struct pa_static_semaphore {
+ pa_atomic_ptr_t ptr;
+} pa_static_semaphore;
+
+#define PA_STATIC_SEMAPHORE_INIT { PA_ATOMIC_PTR_INIT(NULL) }
+
+/* When you call this make sure to pass always the same value parameter! */
+pa_semaphore* pa_static_semaphore_get(pa_static_semaphore *m, unsigned value);
+
+#endif
diff --git a/src/pulsecore/shared.c b/src/pulsecore/shared.c
new file mode 100644
index 0000000..9bc7eb5
--- /dev/null
+++ b/src/pulsecore/shared.c
@@ -0,0 +1,116 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/macro.h>
+
+#include "shared.h"
+
+typedef struct pa_shared {
+ char *name; /* Points to memory allocated by the shared property system */
+ void *data; /* Points to memory maintained by the caller */
+} pa_shared;
+
+/* Allocate a new shared property object */
+static pa_shared* shared_new(const char *name, void *data) {
+ pa_shared* p;
+
+ pa_assert(name);
+ pa_assert(data);
+
+ p = pa_xnew(pa_shared, 1);
+ p->name = pa_xstrdup(name);
+ p->data = data;
+
+ return p;
+}
+
+/* Free a shared property object */
+static void shared_free(pa_shared *p) {
+ pa_assert(p);
+
+ pa_xfree(p->name);
+ pa_xfree(p);
+}
+
+void* pa_shared_get(pa_core *c, const char *name) {
+ pa_shared *p;
+
+ pa_assert(c);
+ pa_assert(name);
+ pa_assert(c->shared);
+
+ if (!(p = pa_hashmap_get(c->shared, name)))
+ return NULL;
+
+ return p->data;
+}
+
+int pa_shared_set(pa_core *c, const char *name, void *data) {
+ pa_shared *p;
+
+ pa_assert(c);
+ pa_assert(name);
+ pa_assert(data);
+ pa_assert(c->shared);
+
+ if (pa_hashmap_get(c->shared, name))
+ return -1;
+
+ p = shared_new(name, data);
+ pa_hashmap_put(c->shared, p->name, p);
+ return 0;
+}
+
+int pa_shared_remove(pa_core *c, const char *name) {
+ pa_shared *p;
+
+ pa_assert(c);
+ pa_assert(name);
+ pa_assert(c->shared);
+
+ if (!(p = pa_hashmap_remove(c->shared, name)))
+ return -1;
+
+ shared_free(p);
+ return 0;
+}
+
+void pa_shared_dump(pa_core *c, pa_strbuf *s) {
+ void *state = NULL;
+ pa_shared *p;
+
+ pa_assert(c);
+ pa_assert(s);
+
+ while ((p = pa_hashmap_iterate(c->shared, &state, NULL)))
+ pa_strbuf_printf(s, "[%s] -> [%p]\n", p->name, p->data);
+}
+
+int pa_shared_replace(pa_core *c, const char *name, void *data) {
+ pa_assert(c);
+ pa_assert(name);
+
+ (void) pa_shared_remove(c, name);
+ return pa_shared_set(c, name, data);
+}
diff --git a/src/pulsecore/shared.h b/src/pulsecore/shared.h
new file mode 100644
index 0000000..19ac462
--- /dev/null
+++ b/src/pulsecore/shared.h
@@ -0,0 +1,55 @@
+#ifndef foosharedshfoo
+#define foosharedshfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulsecore/core.h>
+#include <pulsecore/strbuf.h>
+
+/* The shared property subsystem is to be used to share data between
+ * modules. Consider them to be kind of "global" variables for a
+ * core. Why not use the hashmap functions directly? The hashmap
+ * functions copy neither the key nor value, while this property
+ * system copies the key. Users of this system have to think about
+ * reference counting themselves. */
+
+/* Note: please don't confuse this with the proplist framework in
+ * pulse/proplist.[ch]! */
+
+/* Return a pointer to the value of the specified shared property. */
+void* pa_shared_get(pa_core *c, const char *name);
+
+/* Set the shared property 'name' to 'data'. This function fails in
+ * case a property by this name already exists. The property data is
+ * not copied or reference counted. This is the caller's job. */
+int pa_shared_set(pa_core *c, const char *name, void *data);
+
+/* Remove the specified shared property. Return non-zero on failure */
+int pa_shared_remove(pa_core *c, const char *name);
+
+/* A combination of pa_shared_remove() and pa_shared_set(); this function
+ * first tries to remove the property by this name and then sets the
+ * property. Return non-zero on failure. */
+int pa_shared_replace(pa_core *c, const char *name, void *data);
+
+/* Dump the current set of shared properties */
+void pa_shared_dump(pa_core *c, pa_strbuf *s);
+
+#endif
diff --git a/src/pulsecore/shm.c b/src/pulsecore/shm.c
new file mode 100644
index 0000000..0742cb8
--- /dev/null
+++ b/src/pulsecore/shm.c
@@ -0,0 +1,495 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <signal.h>
+
+#ifdef HAVE_SYS_MMAN_H
+#include <sys/mman.h>
+#endif
+
+/* This is deprecated on glibc but is still used by FreeBSD */
+#if !defined(MAP_ANONYMOUS) && defined(MAP_ANON)
+# define MAP_ANONYMOUS MAP_ANON
+#endif
+
+#include <pulse/xmalloc.h>
+#include <pulse/gccmacro.h>
+
+#include <pulsecore/memfd-wrappers.h>
+#include <pulsecore/core-error.h>
+#include <pulsecore/log.h>
+#include <pulsecore/random.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/atomic.h>
+#include <pulsecore/mem.h>
+
+#include "shm.h"
+
+#if defined(__linux__) && !defined(MADV_REMOVE)
+#define MADV_REMOVE 9
+#endif
+
+/* 1 GiB at max */
+#define MAX_SHM_SIZE (PA_ALIGN(1024*1024*1024))
+
+#ifdef __linux__
+/* On Linux we know that the shared memory blocks are files in
+ * /dev/shm. We can use that information to list all blocks and
+ * cleanup unused ones */
+#define SHM_PATH "/dev/shm/"
+#define SHM_ID_LEN 10
+#elif defined(__sun)
+#define SHM_PATH "/tmp"
+#define SHM_ID_LEN 15
+#else
+#undef SHM_PATH
+#undef SHM_ID_LEN
+#endif
+
+#define SHM_MARKER ((int) 0xbeefcafe)
+
+/* We now put this SHM marker at the end of each segment. It's
+ * optional, to not require a reboot when upgrading, though. Note that
+ * on multiarch systems 32bit and 64bit processes might access this
+ * region simultaneously. The header fields need to be independent
+ * from the process' word with */
+struct shm_marker {
+ pa_atomic_t marker; /* 0xbeefcafe */
+ pa_atomic_t pid;
+ uint64_t _reserved1;
+ uint64_t _reserved2;
+ uint64_t _reserved3;
+ uint64_t _reserved4;
+} PA_GCC_PACKED;
+
+static inline size_t shm_marker_size(pa_mem_type_t type) {
+ if (type == PA_MEM_TYPE_SHARED_POSIX)
+ return PA_ALIGN(sizeof(struct shm_marker));
+
+ return 0;
+}
+
+#ifdef HAVE_SHM_OPEN
+static char *segment_name(char *fn, size_t l, unsigned id) {
+ pa_snprintf(fn, l, "/pulse-shm-%u", id);
+ return fn;
+}
+#endif
+
+static int privatemem_create(pa_shm *m, size_t size) {
+ pa_assert(m);
+ pa_assert(size > 0);
+
+ m->type = PA_MEM_TYPE_PRIVATE;
+ m->id = 0;
+ m->size = size;
+ m->do_unlink = false;
+ m->fd = -1;
+
+#ifdef MAP_ANONYMOUS
+ if ((m->ptr = mmap(NULL, m->size, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, (off_t) 0)) == MAP_FAILED) {
+ pa_log("mmap() failed: %s", pa_cstrerror(errno));
+ return -1;
+ }
+#elif defined(HAVE_POSIX_MEMALIGN)
+ {
+ int r;
+
+ if ((r = posix_memalign(&m->ptr, pa_page_size(), size)) < 0) {
+ pa_log("posix_memalign() failed: %s", pa_cstrerror(r));
+ return r;
+ }
+ }
+#else
+ m->ptr = pa_xmalloc(m->size);
+#endif
+
+ return 0;
+}
+
+static int sharedmem_create(pa_shm *m, pa_mem_type_t type, size_t size, mode_t mode) {
+#if defined(HAVE_SHM_OPEN) || defined(HAVE_MEMFD)
+ char fn[32];
+ int fd = -1;
+ struct shm_marker *marker;
+ bool do_unlink = false;
+
+ /* Each time we create a new SHM area, let's first drop all stale
+ * ones */
+ pa_shm_cleanup();
+
+ pa_random(&m->id, sizeof(m->id));
+
+ switch (type) {
+#ifdef HAVE_SHM_OPEN
+ case PA_MEM_TYPE_SHARED_POSIX:
+ segment_name(fn, sizeof(fn), m->id);
+ fd = shm_open(fn, O_RDWR|O_CREAT|O_EXCL, mode);
+ do_unlink = true;
+ break;
+#endif
+#ifdef HAVE_MEMFD
+ case PA_MEM_TYPE_SHARED_MEMFD:
+ fd = memfd_create("pulseaudio", MFD_ALLOW_SEALING);
+ break;
+#endif
+ default:
+ goto fail;
+ }
+
+ if (fd < 0) {
+ pa_log("%s open() failed: %s", pa_mem_type_to_string(type), pa_cstrerror(errno));
+ goto fail;
+ }
+
+ m->type = type;
+ m->size = size + shm_marker_size(type);
+ m->do_unlink = do_unlink;
+
+ if (ftruncate(fd, (off_t) m->size) < 0) {
+ pa_log("ftruncate() failed: %s", pa_cstrerror(errno));
+ goto fail;
+ }
+
+#ifndef MAP_NORESERVE
+#define MAP_NORESERVE 0
+#endif
+
+ if ((m->ptr = mmap(NULL, PA_PAGE_ALIGN(m->size), PROT_READ|PROT_WRITE, MAP_SHARED|MAP_NORESERVE, fd, (off_t) 0)) == MAP_FAILED) {
+ pa_log("mmap() failed: %s", pa_cstrerror(errno));
+ goto fail;
+ }
+
+ if (type == PA_MEM_TYPE_SHARED_POSIX) {
+ /* We store our PID at the end of the shm block, so that we
+ * can check for dead shm segments later */
+ marker = (struct shm_marker*) ((uint8_t*) m->ptr + m->size - shm_marker_size(type));
+ pa_atomic_store(&marker->pid, (int) getpid());
+ pa_atomic_store(&marker->marker, SHM_MARKER);
+ }
+
+ /* For memfds, we keep the fd open until we pass it
+ * to the other PA endpoint over unix domain socket. */
+ if (type != PA_MEM_TYPE_SHARED_MEMFD) {
+ pa_assert_se(pa_close(fd) == 0);
+ m->fd = -1;
+ }
+#ifdef HAVE_MEMFD
+ else
+ m->fd = fd;
+#endif
+
+ return 0;
+
+fail:
+ if (fd >= 0) {
+#ifdef HAVE_SHM_OPEN
+ if (type == PA_MEM_TYPE_SHARED_POSIX)
+ shm_unlink(fn);
+#endif
+ pa_close(fd);
+ }
+#endif /* defined(HAVE_SHM_OPEN) || defined(HAVE_MEMFD) */
+
+ return -1;
+}
+
+int pa_shm_create_rw(pa_shm *m, pa_mem_type_t type, size_t size, mode_t mode) {
+ pa_assert(m);
+ pa_assert(size > 0);
+ pa_assert(size <= MAX_SHM_SIZE);
+ pa_assert(!(mode & ~0777));
+ pa_assert(mode >= 0600);
+
+ /* Round up to make it page aligned */
+ size = PA_PAGE_ALIGN(size);
+
+ if (type == PA_MEM_TYPE_PRIVATE)
+ return privatemem_create(m, size);
+
+ return sharedmem_create(m, type, size, mode);
+}
+
+static void privatemem_free(pa_shm *m) {
+ pa_assert(m);
+ pa_assert(m->ptr);
+ pa_assert(m->size > 0);
+
+#ifdef MAP_ANONYMOUS
+ if (munmap(m->ptr, m->size) < 0)
+ pa_log("munmap() failed: %s", pa_cstrerror(errno));
+#elif defined(HAVE_POSIX_MEMALIGN)
+ free(m->ptr);
+#else
+ pa_xfree(m->ptr);
+#endif
+}
+
+void pa_shm_free(pa_shm *m) {
+ pa_assert(m);
+ pa_assert(m->ptr);
+ pa_assert(m->size > 0);
+
+#ifdef MAP_FAILED
+ pa_assert(m->ptr != MAP_FAILED);
+#endif
+
+ if (m->type == PA_MEM_TYPE_PRIVATE) {
+ privatemem_free(m);
+ goto finish;
+ }
+
+#if defined(HAVE_SHM_OPEN) || defined(HAVE_MEMFD)
+ if (munmap(m->ptr, PA_PAGE_ALIGN(m->size)) < 0)
+ pa_log("munmap() failed: %s", pa_cstrerror(errno));
+
+#ifdef HAVE_SHM_OPEN
+ if (m->type == PA_MEM_TYPE_SHARED_POSIX && m->do_unlink) {
+ char fn[32];
+
+ segment_name(fn, sizeof(fn), m->id);
+ if (shm_unlink(fn) < 0)
+ pa_log(" shm_unlink(%s) failed: %s", fn, pa_cstrerror(errno));
+ }
+#endif
+#ifdef HAVE_MEMFD
+ if (m->type == PA_MEM_TYPE_SHARED_MEMFD && m->fd != -1)
+ pa_assert_se(pa_close(m->fd) == 0);
+#endif
+
+#else
+ /* We shouldn't be here without shm or memfd support */
+ pa_assert_not_reached();
+#endif
+
+finish:
+ pa_zero(*m);
+}
+
+void pa_shm_punch(pa_shm *m, size_t offset, size_t size) {
+ void *ptr;
+ size_t o;
+ const size_t page_size = pa_page_size();
+
+ pa_assert(m);
+ pa_assert(m->ptr);
+ pa_assert(m->size > 0);
+ pa_assert(offset+size <= m->size);
+
+#ifdef MAP_FAILED
+ pa_assert(m->ptr != MAP_FAILED);
+#endif
+
+ /* You're welcome to implement this as NOOP on systems that don't
+ * support it */
+
+ /* Align the pointer up to multiples of the page size */
+ ptr = (uint8_t*) m->ptr + offset;
+ o = (size_t) ((uint8_t*) ptr - (uint8_t*) PA_PAGE_ALIGN_PTR(ptr));
+
+ if (o > 0) {
+ size_t delta = page_size - o;
+ ptr = (uint8_t*) ptr + delta;
+ size -= delta;
+ }
+
+ /* Align the size down to multiples of page size */
+ size = (size / page_size) * page_size;
+
+#ifdef MADV_REMOVE
+ if (madvise(ptr, size, MADV_REMOVE) >= 0)
+ return;
+#endif
+
+#ifdef MADV_FREE
+ if (madvise(ptr, size, MADV_FREE) >= 0)
+ return;
+#endif
+
+#ifdef MADV_DONTNEED
+ madvise(ptr, size, MADV_DONTNEED);
+#elif defined(POSIX_MADV_DONTNEED)
+ posix_madvise(ptr, size, POSIX_MADV_DONTNEED);
+#endif
+}
+
+static int shm_attach(pa_shm *m, pa_mem_type_t type, unsigned id, int memfd_fd, bool writable, bool for_cleanup) {
+#if defined(HAVE_SHM_OPEN) || defined(HAVE_MEMFD)
+ char fn[32];
+ int fd = -1;
+ int prot;
+ struct stat st;
+
+ pa_assert(m);
+
+ switch (type) {
+#ifdef HAVE_SHM_OPEN
+ case PA_MEM_TYPE_SHARED_POSIX:
+ pa_assert(memfd_fd == -1);
+ segment_name(fn, sizeof(fn), id);
+ if ((fd = shm_open(fn, writable ? O_RDWR : O_RDONLY, 0)) < 0) {
+ if ((errno != EACCES && errno != ENOENT) || !for_cleanup)
+ pa_log("shm_open() failed: %s", pa_cstrerror(errno));
+ goto fail;
+ }
+ break;
+#endif
+#ifdef HAVE_MEMFD
+ case PA_MEM_TYPE_SHARED_MEMFD:
+ pa_assert(memfd_fd != -1);
+ fd = memfd_fd;
+ break;
+#endif
+ default:
+ goto fail;
+ }
+
+ if (fstat(fd, &st) < 0) {
+ pa_log("fstat() failed: %s", pa_cstrerror(errno));
+ goto fail;
+ }
+
+ if (st.st_size <= 0 ||
+ st.st_size > (off_t) MAX_SHM_SIZE + (off_t) shm_marker_size(type) ||
+ PA_ALIGN((size_t) st.st_size) != (size_t) st.st_size) {
+ pa_log("Invalid shared memory segment size");
+ goto fail;
+ }
+
+ prot = writable ? PROT_READ | PROT_WRITE : PROT_READ;
+ if ((m->ptr = mmap(NULL, PA_PAGE_ALIGN(st.st_size), prot, MAP_SHARED, fd, (off_t) 0)) == MAP_FAILED) {
+ pa_log("mmap() failed: %s", pa_cstrerror(errno));
+ goto fail;
+ }
+
+ /* In case of attaching to memfd areas, _the caller_ maintains
+ * ownership of the passed fd and has the sole responsibility
+ * of closing it down.. For other types, we're the code path
+ * which created the fd in the first place and we're thus the
+ * ones responsible for closing it down */
+ if (type != PA_MEM_TYPE_SHARED_MEMFD)
+ pa_assert_se(pa_close(fd) == 0);
+
+ m->type = type;
+ m->id = id;
+ m->size = (size_t) st.st_size;
+ m->do_unlink = false;
+ m->fd = -1;
+
+ return 0;
+
+fail:
+ /* In case of memfds, caller maintains fd ownership */
+ if (fd >= 0 && type != PA_MEM_TYPE_SHARED_MEMFD)
+ pa_close(fd);
+
+#endif /* defined(HAVE_SHM_OPEN) || defined(HAVE_MEMFD) */
+
+ return -1;
+}
+
+/* Caller owns passed @memfd_fd and must close it down when appropriate. */
+int pa_shm_attach(pa_shm *m, pa_mem_type_t type, unsigned id, int memfd_fd, bool writable) {
+ return shm_attach(m, type, id, memfd_fd, writable, false);
+}
+
+int pa_shm_cleanup(void) {
+
+#ifdef HAVE_SHM_OPEN
+#ifdef SHM_PATH
+ DIR *d;
+ struct dirent *de;
+
+ if (!(d = opendir(SHM_PATH))) {
+ pa_log_warn("Failed to read "SHM_PATH": %s", pa_cstrerror(errno));
+ return -1;
+ }
+
+ while ((de = readdir(d))) {
+ pa_shm seg;
+ unsigned id;
+ pid_t pid;
+ char fn[128];
+ struct shm_marker *m;
+
+#if defined(__sun)
+ if (strncmp(de->d_name, ".SHMDpulse-shm-", SHM_ID_LEN))
+#else
+ if (strncmp(de->d_name, "pulse-shm-", SHM_ID_LEN))
+#endif
+ continue;
+
+ if (pa_atou(de->d_name + SHM_ID_LEN, &id) < 0)
+ continue;
+
+ if (shm_attach(&seg, PA_MEM_TYPE_SHARED_POSIX, id, -1, false, true) < 0)
+ continue;
+
+ if (seg.size < shm_marker_size(seg.type)) {
+ pa_shm_free(&seg);
+ continue;
+ }
+
+ m = (struct shm_marker*) ((uint8_t*) seg.ptr + seg.size - shm_marker_size(seg.type));
+
+ if (pa_atomic_load(&m->marker) != SHM_MARKER) {
+ pa_shm_free(&seg);
+ continue;
+ }
+
+ if (!(pid = (pid_t) pa_atomic_load(&m->pid))) {
+ pa_shm_free(&seg);
+ continue;
+ }
+
+ if (kill(pid, 0) == 0 || errno != ESRCH) {
+ pa_shm_free(&seg);
+ continue;
+ }
+
+ pa_shm_free(&seg);
+
+ /* Ok, the owner of this shms segment is dead, so, let's remove the segment */
+ segment_name(fn, sizeof(fn), id);
+
+ if (shm_unlink(fn) < 0 && errno != EACCES && errno != ENOENT)
+ pa_log_warn("Failed to remove SHM segment %s: %s", fn, pa_cstrerror(errno));
+ }
+
+ closedir(d);
+#endif /* SHM_PATH */
+#endif /* HAVE_SHM_OPEN */
+
+ return 0;
+}
diff --git a/src/pulsecore/shm.h b/src/pulsecore/shm.h
new file mode 100644
index 0000000..67a2114
--- /dev/null
+++ b/src/pulsecore/shm.h
@@ -0,0 +1,61 @@
+#ifndef foopulseshmhfoo
+#define foopulseshmhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/types.h>
+
+#include <pulsecore/macro.h>
+#include <pulsecore/mem.h>
+
+typedef struct pa_shm {
+ pa_mem_type_t type;
+ unsigned id;
+ void *ptr;
+ size_t size;
+
+ /* Only for type = PA_MEM_TYPE_SHARED_POSIX */
+ bool do_unlink:1;
+
+ /* Only for type = PA_MEM_TYPE_SHARED_MEMFD
+ *
+ * To avoid fd leaks, we keep this fd open only until we pass it
+ * to the other PA endpoint over unix domain socket.
+ *
+ * When we don't have ownership for the memfd fd in question (e.g.
+ * pa_shm_attach()), or the file descriptor has now been closed,
+ * this is set to -1.
+ *
+ * For the special case of a global mempool, we keep this fd
+ * always open. Check comments on top of pa_mempool_new() for
+ * rationale. */
+ int fd;
+} pa_shm;
+
+int pa_shm_create_rw(pa_shm *m, pa_mem_type_t type, size_t size, mode_t mode);
+int pa_shm_attach(pa_shm *m, pa_mem_type_t type, unsigned id, int memfd_fd, bool writable);
+
+void pa_shm_punch(pa_shm *m, size_t offset, size_t size);
+
+void pa_shm_free(pa_shm *m);
+
+int pa_shm_cleanup(void);
+
+#endif
diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c
new file mode 100644
index 0000000..12e3c8b
--- /dev/null
+++ b/src/pulsecore/sink-input.c
@@ -0,0 +1,2449 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <pulse/utf8.h>
+#include <pulse/xmalloc.h>
+#include <pulse/util.h>
+#include <pulse/internal.h>
+
+#include <pulsecore/core-format.h>
+#include <pulsecore/mix.h>
+#include <pulsecore/stream-util.h>
+#include <pulsecore/core-subscribe.h>
+#include <pulsecore/log.h>
+#include <pulsecore/play-memblockq.h>
+#include <pulsecore/namereg.h>
+#include <pulsecore/core-util.h>
+
+#include "sink-input.h"
+
+/* #define SINK_INPUT_DEBUG */
+
+#define MEMBLOCKQ_MAXLENGTH (32*1024*1024)
+#define CONVERT_BUFFER_LENGTH (pa_page_size())
+
+PA_DEFINE_PUBLIC_CLASS(pa_sink_input, pa_msgobject);
+
+struct volume_factor_entry {
+ char *key;
+ pa_cvolume volume;
+};
+
+static struct volume_factor_entry *volume_factor_entry_new(const char *key, const pa_cvolume *volume) {
+ struct volume_factor_entry *entry;
+
+ pa_assert(key);
+ pa_assert(volume);
+
+ entry = pa_xnew(struct volume_factor_entry, 1);
+ entry->key = pa_xstrdup(key);
+
+ entry->volume = *volume;
+
+ return entry;
+}
+
+static void volume_factor_entry_free(struct volume_factor_entry *volume_entry) {
+ pa_assert(volume_entry);
+
+ pa_xfree(volume_entry->key);
+ pa_xfree(volume_entry);
+}
+
+static void volume_factor_from_hashmap(pa_cvolume *v, pa_hashmap *items, uint8_t channels) {
+ struct volume_factor_entry *entry;
+ void *state = NULL;
+
+ pa_cvolume_reset(v, channels);
+ PA_HASHMAP_FOREACH(entry, items, state)
+ pa_sw_cvolume_multiply(v, v, &entry->volume);
+}
+
+static void sink_input_free(pa_object *o);
+static void set_real_ratio(pa_sink_input *i, const pa_cvolume *v);
+
+static int check_passthrough_connection(bool passthrough, pa_sink *dest) {
+ if (pa_sink_is_passthrough(dest)) {
+ pa_log_warn("Sink is already connected to PASSTHROUGH input");
+ return -PA_ERR_BUSY;
+ }
+
+ /* If current input(s) exist, check new input is not PASSTHROUGH */
+ if (pa_idxset_size(dest->inputs) > 0 && passthrough) {
+ pa_log_warn("Sink is already connected, cannot accept new PASSTHROUGH INPUT");
+ return -PA_ERR_BUSY;
+ }
+
+ return PA_OK;
+}
+
+pa_sink_input_new_data* pa_sink_input_new_data_init(pa_sink_input_new_data *data) {
+ pa_assert(data);
+
+ pa_zero(*data);
+ data->resample_method = PA_RESAMPLER_INVALID;
+ data->proplist = pa_proplist_new();
+ data->volume_writable = true;
+
+ data->volume_factor_items = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL,
+ (pa_free_cb_t) volume_factor_entry_free);
+ data->volume_factor_sink_items = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL,
+ (pa_free_cb_t) volume_factor_entry_free);
+
+ return data;
+}
+
+void pa_sink_input_new_data_set_sample_spec(pa_sink_input_new_data *data, const pa_sample_spec *spec) {
+ pa_assert(data);
+
+ if ((data->sample_spec_is_set = !!spec))
+ data->sample_spec = *spec;
+}
+
+void pa_sink_input_new_data_set_channel_map(pa_sink_input_new_data *data, const pa_channel_map *map) {
+ pa_assert(data);
+
+ if ((data->channel_map_is_set = !!map))
+ data->channel_map = *map;
+}
+
+bool pa_sink_input_new_data_is_passthrough(pa_sink_input_new_data *data) {
+ pa_assert(data);
+
+ if (PA_LIKELY(data->format) && PA_UNLIKELY(!pa_format_info_is_pcm(data->format)))
+ return true;
+
+ if (PA_UNLIKELY(data->flags & PA_SINK_INPUT_PASSTHROUGH))
+ return true;
+
+ return false;
+}
+
+void pa_sink_input_new_data_set_volume(pa_sink_input_new_data *data, const pa_cvolume *volume) {
+ pa_assert(data);
+ pa_assert(data->volume_writable);
+
+ if ((data->volume_is_set = !!volume))
+ data->volume = *volume;
+}
+
+void pa_sink_input_new_data_add_volume_factor(pa_sink_input_new_data *data, const char *key, const pa_cvolume *volume_factor) {
+ struct volume_factor_entry *v;
+
+ pa_assert(data);
+ pa_assert(key);
+ pa_assert(volume_factor);
+
+ v = volume_factor_entry_new(key, volume_factor);
+ pa_assert_se(pa_hashmap_put(data->volume_factor_items, v->key, v) >= 0);
+}
+
+void pa_sink_input_new_data_add_volume_factor_sink(pa_sink_input_new_data *data, const char *key, const pa_cvolume *volume_factor) {
+ struct volume_factor_entry *v;
+
+ pa_assert(data);
+ pa_assert(key);
+ pa_assert(volume_factor);
+
+ v = volume_factor_entry_new(key, volume_factor);
+ pa_assert_se(pa_hashmap_put(data->volume_factor_sink_items, v->key, v) >= 0);
+}
+
+void pa_sink_input_new_data_set_muted(pa_sink_input_new_data *data, bool mute) {
+ pa_assert(data);
+
+ data->muted_is_set = true;
+ data->muted = mute;
+}
+
+bool pa_sink_input_new_data_set_sink(pa_sink_input_new_data *data, pa_sink *s, bool save, bool requested_by_application) {
+ bool ret = true;
+ pa_idxset *formats = NULL;
+
+ pa_assert(data);
+ pa_assert(s);
+
+ if (!data->req_formats) {
+ /* We're not working with the extended API */
+ data->sink = s;
+ if (save) {
+ pa_xfree(data->preferred_sink);
+ data->preferred_sink = pa_xstrdup(s->name);
+ }
+ data->sink_requested_by_application = requested_by_application;
+ } else {
+ /* Extended API: let's see if this sink supports the formats the client can provide */
+ formats = pa_sink_check_formats(s, data->req_formats);
+
+ if (formats && !pa_idxset_isempty(formats)) {
+ /* Sink supports at least one of the requested formats */
+ data->sink = s;
+ if (save) {
+ pa_xfree(data->preferred_sink);
+ data->preferred_sink = pa_xstrdup(s->name);
+ }
+ data->sink_requested_by_application = requested_by_application;
+ if (data->nego_formats)
+ pa_idxset_free(data->nego_formats, (pa_free_cb_t) pa_format_info_free);
+ data->nego_formats = formats;
+ } else {
+ /* Sink doesn't support any of the formats requested by the client */
+ if (formats)
+ pa_idxset_free(formats, (pa_free_cb_t) pa_format_info_free);
+ ret = false;
+ }
+ }
+
+ return ret;
+}
+
+bool pa_sink_input_new_data_set_formats(pa_sink_input_new_data *data, pa_idxset *formats) {
+ pa_assert(data);
+ pa_assert(formats);
+
+ if (data->req_formats)
+ pa_idxset_free(data->req_formats, (pa_free_cb_t) pa_format_info_free);
+
+ data->req_formats = formats;
+
+ if (data->sink) {
+ /* Trigger format negotiation */
+ return pa_sink_input_new_data_set_sink(data, data->sink, (data->preferred_sink != NULL), data->sink_requested_by_application);
+ }
+
+ return true;
+}
+
+void pa_sink_input_new_data_done(pa_sink_input_new_data *data) {
+ pa_assert(data);
+
+ if (data->req_formats)
+ pa_idxset_free(data->req_formats, (pa_free_cb_t) pa_format_info_free);
+
+ if (data->nego_formats)
+ pa_idxset_free(data->nego_formats, (pa_free_cb_t) pa_format_info_free);
+
+ if (data->format)
+ pa_format_info_free(data->format);
+
+ if (data->volume_factor_items)
+ pa_hashmap_free(data->volume_factor_items);
+
+ if (data->volume_factor_sink_items)
+ pa_hashmap_free(data->volume_factor_sink_items);
+
+ if (data->preferred_sink)
+ pa_xfree(data->preferred_sink);
+
+ pa_proplist_free(data->proplist);
+}
+
+/* Called from main context */
+static void reset_callbacks(pa_sink_input *i) {
+ pa_assert(i);
+
+ i->pop = NULL;
+ i->process_underrun = NULL;
+ i->process_rewind = NULL;
+ i->update_max_rewind = NULL;
+ i->update_max_request = NULL;
+ i->update_sink_requested_latency = NULL;
+ i->update_sink_latency_range = NULL;
+ i->update_sink_fixed_latency = NULL;
+ i->attach = NULL;
+ i->detach = NULL;
+ i->suspend = NULL;
+ i->suspend_within_thread = NULL;
+ i->moving = NULL;
+ i->kill = NULL;
+ i->get_latency = NULL;
+ i->state_change = NULL;
+ i->may_move_to = NULL;
+ i->send_event = NULL;
+ i->volume_changed = NULL;
+ i->mute_changed = NULL;
+}
+
+/* Called from main context */
+int pa_sink_input_new(
+ pa_sink_input **_i,
+ pa_core *core,
+ pa_sink_input_new_data *data) {
+
+ pa_sink_input *i;
+ pa_resampler *resampler = NULL;
+ char st[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX], fmt[PA_FORMAT_INFO_SNPRINT_MAX];
+ pa_channel_map volume_map;
+ int r;
+ char *pt;
+ char *memblockq_name;
+
+ pa_assert(_i);
+ pa_assert(core);
+ pa_assert(data);
+ pa_assert_ctl_context();
+
+ if (data->client)
+ pa_proplist_update(data->proplist, PA_UPDATE_MERGE, data->client->proplist);
+
+ if (data->origin_sink && (data->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER))
+ data->volume_writable = false;
+
+ if (!data->req_formats) {
+ /* From this point on, we want to work only with formats, and get back
+ * to using the sample spec and channel map after all decisions w.r.t.
+ * routing are complete. */
+ pa_format_info *f;
+ pa_idxset *formats;
+
+ f = pa_format_info_from_sample_spec2(&data->sample_spec, data->channel_map_is_set ? &data->channel_map : NULL,
+ !(data->flags & PA_SINK_INPUT_FIX_FORMAT),
+ !(data->flags & PA_SINK_INPUT_FIX_RATE),
+ !(data->flags & PA_SINK_INPUT_FIX_CHANNELS));
+ if (!f)
+ return -PA_ERR_INVALID;
+
+ formats = pa_idxset_new(NULL, NULL);
+ pa_idxset_put(formats, f, NULL);
+ pa_sink_input_new_data_set_formats(data, formats);
+ }
+
+ if ((r = pa_hook_fire(&core->hooks[PA_CORE_HOOK_SINK_INPUT_NEW], data)) < 0)
+ return r;
+
+ pa_return_val_if_fail(!data->driver || pa_utf8_valid(data->driver), -PA_ERR_INVALID);
+
+ if (!data->sink) {
+ pa_sink *sink = pa_namereg_get(core, NULL, PA_NAMEREG_SINK);
+ pa_return_val_if_fail(sink, -PA_ERR_NOENTITY);
+ pa_sink_input_new_data_set_sink(data, sink, false, false);
+ }
+
+ /* If something didn't pick a format for us, pick the top-most format since
+ * we assume this is sorted in priority order */
+ if (!data->format && data->nego_formats && !pa_idxset_isempty(data->nego_formats))
+ data->format = pa_format_info_copy(pa_idxset_first(data->nego_formats, NULL));
+
+ if (PA_LIKELY(data->format)) {
+ /* We know that data->sink is set, because data->format has been set.
+ * data->format is set after a successful format negotiation, and that
+ * can't happen before data->sink has been set. */
+ pa_assert(data->sink);
+
+ pa_log_debug("Negotiated format: %s", pa_format_info_snprint(fmt, sizeof(fmt), data->format));
+ } else {
+ pa_format_info *format;
+ uint32_t idx;
+
+ pa_log_info("Sink does not support any requested format:");
+ PA_IDXSET_FOREACH(format, data->req_formats, idx)
+ pa_log_info(" -- %s", pa_format_info_snprint(fmt, sizeof(fmt), format));
+
+ return -PA_ERR_NOTSUPPORTED;
+ }
+
+ pa_return_val_if_fail(PA_SINK_IS_LINKED(data->sink->state), -PA_ERR_BADSTATE);
+ pa_return_val_if_fail(!data->sync_base || (data->sync_base->sink == data->sink
+ && data->sync_base->state == PA_SINK_INPUT_CORKED),
+ -PA_ERR_INVALID);
+
+ /* Routing is done. We have a sink and a format. */
+
+ if (data->volume_is_set && !pa_sink_input_new_data_is_passthrough(data)) {
+ /* If volume is set, we need to save the original data->channel_map,
+ * so that we can remap the volume from the original channel map to the
+ * final channel map of the stream in case data->channel_map gets
+ * modified in pa_format_info_to_sample_spec2(). */
+ r = pa_stream_get_volume_channel_map(&data->volume, data->channel_map_is_set ? &data->channel_map : NULL, data->format, &volume_map);
+ if (r < 0)
+ return r;
+ } else {
+ /* Initialize volume_map to invalid state. We check the state later to
+ * determine if volume remapping is needed. */
+ pa_channel_map_init(&volume_map);
+ }
+
+ /* Now populate the sample spec and channel map according to the final
+ * format that we've negotiated */
+ r = pa_format_info_to_sample_spec2(data->format, &data->sample_spec, &data->channel_map, &data->sink->sample_spec,
+ &data->sink->channel_map);
+ if (r < 0)
+ return r;
+
+ r = check_passthrough_connection(pa_sink_input_new_data_is_passthrough(data), data->sink);
+ if (r != PA_OK)
+ return r;
+
+ /* Don't restore (or save) stream volume for passthrough streams and
+ * prevent attenuation/gain */
+ if (pa_sink_input_new_data_is_passthrough(data)) {
+ data->volume_is_set = true;
+ pa_cvolume_reset(&data->volume, data->sample_spec.channels);
+ data->volume_is_absolute = true;
+ data->save_volume = false;
+ }
+
+ if (!data->volume_is_set) {
+ pa_cvolume_reset(&data->volume, data->sample_spec.channels);
+ data->volume_is_absolute = false;
+ data->save_volume = false;
+ }
+
+ if (!data->volume_writable)
+ data->save_volume = false;
+
+ if (pa_channel_map_valid(&volume_map))
+ /* The original volume channel map may be different than the final
+ * stream channel map, so remapping may be needed. */
+ pa_cvolume_remap(&data->volume, &volume_map, &data->channel_map);
+
+ if (!data->muted_is_set)
+ data->muted = false;
+
+ if (!(data->flags & PA_SINK_INPUT_VARIABLE_RATE) &&
+ !pa_sample_spec_equal(&data->sample_spec, &data->sink->sample_spec)) {
+ /* try to change sink format and rate. This is done before the FIXATE hook since
+ module-suspend-on-idle can resume a sink */
+
+ pa_log_info("Trying to change sample spec");
+ pa_sink_reconfigure(data->sink, &data->sample_spec, pa_sink_input_new_data_is_passthrough(data));
+ }
+
+ if (pa_sink_input_new_data_is_passthrough(data) &&
+ !pa_sample_spec_equal(&data->sample_spec, &data->sink->sample_spec)) {
+ /* rate update failed, or other parts of sample spec didn't match */
+
+ pa_log_debug("Could not update sink sample spec to match passthrough stream");
+ return -PA_ERR_NOTSUPPORTED;
+ }
+
+ if (data->resample_method == PA_RESAMPLER_INVALID)
+ data->resample_method = core->resample_method;
+
+ pa_return_val_if_fail(data->resample_method < PA_RESAMPLER_MAX, -PA_ERR_INVALID);
+
+ if ((r = pa_hook_fire(&core->hooks[PA_CORE_HOOK_SINK_INPUT_FIXATE], data)) < 0)
+ return r;
+
+ if ((data->flags & PA_SINK_INPUT_NO_CREATE_ON_SUSPEND) &&
+ data->sink->state == PA_SINK_SUSPENDED) {
+ pa_log_warn("Failed to create sink input: sink is suspended.");
+ return -PA_ERR_BADSTATE;
+ }
+
+ if (pa_idxset_size(data->sink->inputs) >= PA_MAX_INPUTS_PER_SINK) {
+ pa_log_warn("Failed to create sink input: too many inputs per sink.");
+ return -PA_ERR_TOOLARGE;
+ }
+
+ if ((data->flags & PA_SINK_INPUT_VARIABLE_RATE) ||
+ !pa_sample_spec_equal(&data->sample_spec, &data->sink->sample_spec) ||
+ !pa_channel_map_equal(&data->channel_map, &data->sink->channel_map)) {
+
+ /* Note: for passthrough content we need to adjust the output rate to that of the current sink-input */
+ if (!pa_sink_input_new_data_is_passthrough(data)) /* no resampler for passthrough content */
+ if (!(resampler = pa_resampler_new(
+ core->mempool,
+ &data->sample_spec, &data->channel_map,
+ &data->sink->sample_spec, &data->sink->channel_map,
+ core->lfe_crossover_freq,
+ data->resample_method,
+ ((data->flags & PA_SINK_INPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0) |
+ ((data->flags & PA_SINK_INPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) |
+ (core->disable_remixing || (data->flags & PA_SINK_INPUT_NO_REMIX) ? PA_RESAMPLER_NO_REMIX : 0) |
+ (core->remixing_use_all_sink_channels ? 0 : PA_RESAMPLER_NO_FILL_SINK) |
+ (core->remixing_produce_lfe ? PA_RESAMPLER_PRODUCE_LFE : 0) |
+ (core->remixing_consume_lfe ? PA_RESAMPLER_CONSUME_LFE : 0)))) {
+ pa_log_warn("Unsupported resampling operation.");
+ return -PA_ERR_NOTSUPPORTED;
+ }
+ }
+
+ i = pa_msgobject_new(pa_sink_input);
+ i->parent.parent.free = sink_input_free;
+ i->parent.process_msg = pa_sink_input_process_msg;
+
+ i->core = core;
+ i->state = PA_SINK_INPUT_INIT;
+ i->flags = data->flags;
+ i->proplist = pa_proplist_copy(data->proplist);
+ i->driver = pa_xstrdup(pa_path_get_filename(data->driver));
+ i->module = data->module;
+ i->sink = data->sink;
+ i->sink_requested_by_application = data->sink_requested_by_application;
+ i->origin_sink = data->origin_sink;
+ i->client = data->client;
+
+ i->requested_resample_method = data->resample_method;
+ i->actual_resample_method = resampler ? pa_resampler_get_method(resampler) : PA_RESAMPLER_INVALID;
+ i->sample_spec = data->sample_spec;
+ i->channel_map = data->channel_map;
+ i->format = pa_format_info_copy(data->format);
+
+ if (!data->volume_is_absolute && pa_sink_flat_volume_enabled(i->sink)) {
+ pa_cvolume remapped;
+
+ /* When the 'absolute' bool is not set then we'll treat the volume
+ * as relative to the sink volume even in flat volume mode */
+ remapped = data->sink->reference_volume;
+ pa_cvolume_remap(&remapped, &data->sink->channel_map, &data->channel_map);
+ pa_sw_cvolume_multiply(&i->volume, &data->volume, &remapped);
+ } else
+ i->volume = data->volume;
+
+ i->volume_factor_items = data->volume_factor_items;
+ data->volume_factor_items = NULL;
+ volume_factor_from_hashmap(&i->volume_factor, i->volume_factor_items, i->sample_spec.channels);
+
+ i->volume_factor_sink_items = data->volume_factor_sink_items;
+ data->volume_factor_sink_items = NULL;
+ volume_factor_from_hashmap(&i->volume_factor_sink, i->volume_factor_sink_items, i->sink->sample_spec.channels);
+
+ i->real_ratio = i->reference_ratio = data->volume;
+ pa_cvolume_reset(&i->soft_volume, i->sample_spec.channels);
+ pa_cvolume_reset(&i->real_ratio, i->sample_spec.channels);
+ i->volume_writable = data->volume_writable;
+ i->save_volume = data->save_volume;
+ i->preferred_sink = pa_xstrdup(data->preferred_sink);
+ i->save_muted = data->save_muted;
+
+ i->muted = data->muted;
+
+ if (data->sync_base) {
+ i->sync_next = data->sync_base->sync_next;
+ i->sync_prev = data->sync_base;
+
+ if (data->sync_base->sync_next)
+ data->sync_base->sync_next->sync_prev = i;
+ data->sync_base->sync_next = i;
+ } else
+ i->sync_next = i->sync_prev = NULL;
+
+ i->direct_outputs = pa_idxset_new(NULL, NULL);
+
+ reset_callbacks(i);
+ i->userdata = NULL;
+
+ i->thread_info.state = i->state;
+ i->thread_info.attached = false;
+ i->thread_info.sample_spec = i->sample_spec;
+ i->thread_info.resampler = resampler;
+ i->thread_info.soft_volume = i->soft_volume;
+ i->thread_info.muted = i->muted;
+ i->thread_info.requested_sink_latency = (pa_usec_t) -1;
+ i->thread_info.rewrite_nbytes = 0;
+ i->thread_info.rewrite_flush = false;
+ i->thread_info.dont_rewind_render = false;
+ i->thread_info.underrun_for = (uint64_t) -1;
+ i->thread_info.underrun_for_sink = 0;
+ i->thread_info.playing_for = 0;
+ i->thread_info.direct_outputs = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
+
+ pa_assert_se(pa_idxset_put(core->sink_inputs, i, &i->index) == 0);
+ pa_assert_se(pa_idxset_put(i->sink->inputs, pa_sink_input_ref(i), NULL) == 0);
+
+ if (i->client)
+ pa_assert_se(pa_idxset_put(i->client->sink_inputs, i, NULL) >= 0);
+
+ memblockq_name = pa_sprintf_malloc("sink input render_memblockq [%u]", i->index);
+ i->thread_info.render_memblockq = pa_memblockq_new(
+ memblockq_name,
+ 0,
+ MEMBLOCKQ_MAXLENGTH,
+ 0,
+ &i->sink->sample_spec,
+ 0,
+ 1,
+ 0,
+ &i->sink->silence);
+ pa_xfree(memblockq_name);
+
+ pt = pa_proplist_to_string_sep(i->proplist, "\n ");
+ pa_log_info("Created input %u \"%s\" on %s with sample spec %s and channel map %s\n %s",
+ i->index,
+ pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_MEDIA_NAME)),
+ i->sink->name,
+ pa_sample_spec_snprint(st, sizeof(st), &i->sample_spec),
+ pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map),
+ pt);
+ pa_xfree(pt);
+
+ /* Don't forget to call pa_sink_input_put! */
+
+ *_i = i;
+ return 0;
+}
+
+/* Called from main context */
+static void update_n_corked(pa_sink_input *i, pa_sink_input_state_t state) {
+ pa_assert(i);
+ pa_assert_ctl_context();
+
+ if (!i->sink)
+ return;
+
+ if (i->state == PA_SINK_INPUT_CORKED && state != PA_SINK_INPUT_CORKED)
+ pa_assert_se(i->sink->n_corked -- >= 1);
+ else if (i->state != PA_SINK_INPUT_CORKED && state == PA_SINK_INPUT_CORKED)
+ i->sink->n_corked++;
+}
+
+/* Called from main context */
+static void sink_input_set_state(pa_sink_input *i, pa_sink_input_state_t state) {
+ pa_sink_input *ssync;
+ pa_assert(i);
+ pa_assert_ctl_context();
+
+ if (i->state == state)
+ return;
+
+ if (i->sink) {
+ if (i->state == PA_SINK_INPUT_CORKED && state == PA_SINK_INPUT_RUNNING && pa_sink_used_by(i->sink) == 0 &&
+ !pa_sample_spec_equal(&i->sample_spec, &i->sink->sample_spec)) {
+ /* We were uncorked and the sink was not playing anything -- let's try
+ * to update the sample format and rate to avoid resampling */
+ pa_sink_reconfigure(i->sink, &i->sample_spec, pa_sink_input_is_passthrough(i));
+ }
+
+ pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_STATE, PA_UINT_TO_PTR(state), 0, NULL) == 0);
+ } else {
+ /* If the sink is not valid, pa_sink_input_set_state_within_thread() must be called directly */
+
+ pa_sink_input_set_state_within_thread(i, state);
+
+ for (ssync = i->thread_info.sync_prev; ssync; ssync = ssync->thread_info.sync_prev)
+ pa_sink_input_set_state_within_thread(ssync, state);
+
+ for (ssync = i->thread_info.sync_next; ssync; ssync = ssync->thread_info.sync_next)
+ pa_sink_input_set_state_within_thread(ssync, state);
+ }
+
+ update_n_corked(i, state);
+ i->state = state;
+
+ for (ssync = i->sync_prev; ssync; ssync = ssync->sync_prev) {
+ update_n_corked(ssync, state);
+ ssync->state = state;
+ }
+ for (ssync = i->sync_next; ssync; ssync = ssync->sync_next) {
+ update_n_corked(ssync, state);
+ ssync->state = state;
+ }
+
+ if (state != PA_SINK_INPUT_UNLINKED) {
+ pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_STATE_CHANGED], i);
+
+ for (ssync = i->sync_prev; ssync; ssync = ssync->sync_prev)
+ pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_STATE_CHANGED], ssync);
+
+ for (ssync = i->sync_next; ssync; ssync = ssync->sync_next)
+ pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_STATE_CHANGED], ssync);
+
+ if (PA_SINK_INPUT_IS_LINKED(state))
+ pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
+ }
+
+ if (i->sink)
+ pa_sink_update_status(i->sink);
+}
+
+/* Called from main context */
+void pa_sink_input_unlink(pa_sink_input *i) {
+ bool linked;
+ pa_source_output *o, PA_UNUSED *p = NULL;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_ctl_context();
+
+ /* See pa_sink_unlink() for a couple of comments how this function
+ * works */
+
+ pa_sink_input_ref(i);
+
+ linked = PA_SINK_INPUT_IS_LINKED(i->state);
+
+ if (linked)
+ pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_UNLINK], i);
+
+ if (i->sync_prev)
+ i->sync_prev->sync_next = i->sync_next;
+ if (i->sync_next)
+ i->sync_next->sync_prev = i->sync_prev;
+
+ i->sync_prev = i->sync_next = NULL;
+
+ pa_idxset_remove_by_data(i->core->sink_inputs, i, NULL);
+
+ if (i->sink)
+ if (pa_idxset_remove_by_data(i->sink->inputs, i, NULL))
+ pa_sink_input_unref(i);
+
+ if (i->client)
+ pa_idxset_remove_by_data(i->client->sink_inputs, i, NULL);
+
+ while ((o = pa_idxset_first(i->direct_outputs, NULL))) {
+ pa_assert(o != p);
+ pa_source_output_kill(o);
+ p = o;
+ }
+
+ update_n_corked(i, PA_SINK_INPUT_UNLINKED);
+ i->state = PA_SINK_INPUT_UNLINKED;
+
+ if (linked && i->sink) {
+ if (pa_sink_input_is_passthrough(i))
+ pa_sink_leave_passthrough(i->sink);
+
+ /* We might need to update the sink's volume if we are in flat volume mode. */
+ if (pa_sink_flat_volume_enabled(i->sink))
+ pa_sink_set_volume(i->sink, NULL, false, false);
+
+ if (i->sink->asyncmsgq)
+ pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_REMOVE_INPUT, i, 0, NULL) == 0);
+ }
+
+ reset_callbacks(i);
+
+ if (i->sink) {
+ if (PA_SINK_IS_LINKED(i->sink->state))
+ pa_sink_update_status(i->sink);
+
+ i->sink = NULL;
+ }
+
+ if (linked) {
+ pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_REMOVE, i->index);
+ pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_UNLINK_POST], i);
+ }
+
+ pa_core_maybe_vacuum(i->core);
+
+ pa_sink_input_unref(i);
+}
+
+/* Called from main context */
+static void sink_input_free(pa_object *o) {
+ pa_sink_input* i = PA_SINK_INPUT(o);
+
+ pa_assert(i);
+ pa_assert_ctl_context();
+ pa_assert(pa_sink_input_refcnt(i) == 0);
+ pa_assert(!PA_SINK_INPUT_IS_LINKED(i->state));
+
+ pa_log_info("Freeing input %u \"%s\"", i->index,
+ i->proplist ? pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_MEDIA_NAME)) : "");
+
+ /* Side note: this function must be able to destruct properly any
+ * kind of sink input in any state, even those which are
+ * "half-moved" or are connected to sinks that have no asyncmsgq
+ * and are hence half-destructed themselves! */
+
+ if (i->thread_info.render_memblockq)
+ pa_memblockq_free(i->thread_info.render_memblockq);
+
+ if (i->thread_info.resampler)
+ pa_resampler_free(i->thread_info.resampler);
+
+ if (i->format)
+ pa_format_info_free(i->format);
+
+ if (i->proplist)
+ pa_proplist_free(i->proplist);
+
+ if (i->direct_outputs)
+ pa_idxset_free(i->direct_outputs, NULL);
+
+ if (i->thread_info.direct_outputs)
+ pa_hashmap_free(i->thread_info.direct_outputs);
+
+ if (i->volume_factor_items)
+ pa_hashmap_free(i->volume_factor_items);
+
+ if (i->volume_factor_sink_items)
+ pa_hashmap_free(i->volume_factor_sink_items);
+
+ if (i->preferred_sink)
+ pa_xfree(i->preferred_sink);
+
+ pa_xfree(i->driver);
+ pa_xfree(i);
+}
+
+/* Called from main context */
+void pa_sink_input_put(pa_sink_input *i) {
+ pa_sink_input_state_t state;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_ctl_context();
+
+ pa_assert(i->state == PA_SINK_INPUT_INIT);
+
+ /* The following fields must be initialized properly */
+ pa_assert(i->pop);
+ pa_assert(i->process_rewind);
+ pa_assert(i->kill);
+
+ state = i->flags & PA_SINK_INPUT_START_CORKED ? PA_SINK_INPUT_CORKED : PA_SINK_INPUT_RUNNING;
+
+ update_n_corked(i, state);
+ i->state = state;
+
+ /* We might need to update the sink's volume if we are in flat volume mode. */
+ if (pa_sink_flat_volume_enabled(i->sink))
+ pa_sink_set_volume(i->sink, NULL, false, i->save_volume);
+ else {
+ if (i->origin_sink && (i->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)) {
+ pa_assert(pa_cvolume_is_norm(&i->volume));
+ pa_assert(pa_cvolume_is_norm(&i->reference_ratio));
+ }
+
+ set_real_ratio(i, &i->volume);
+ }
+
+ if (pa_sink_input_is_passthrough(i))
+ pa_sink_enter_passthrough(i->sink);
+
+ i->thread_info.soft_volume = i->soft_volume;
+ i->thread_info.muted = i->muted;
+
+ pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_ADD_INPUT, i, 0, NULL) == 0);
+
+ pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW, i->index);
+ pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_PUT], i);
+
+ pa_sink_update_status(i->sink);
+}
+
+/* Called from main context */
+void pa_sink_input_kill(pa_sink_input*i) {
+ pa_sink_input_assert_ref(i);
+ pa_assert_ctl_context();
+ pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
+
+ i->kill(i);
+}
+
+/* Called from main context */
+pa_usec_t pa_sink_input_get_latency(pa_sink_input *i, pa_usec_t *sink_latency) {
+ pa_usec_t r[2] = { 0, 0 };
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_ctl_context();
+ pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
+
+ pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_GET_LATENCY, r, 0, NULL) == 0);
+
+ if (i->get_latency)
+ r[0] += i->get_latency(i);
+
+ if (sink_latency)
+ *sink_latency = r[1];
+
+ return r[0];
+}
+
+/* Called from thread context */
+void pa_sink_input_peek(pa_sink_input *i, size_t slength /* in sink bytes */, pa_memchunk *chunk, pa_cvolume *volume) {
+ bool do_volume_adj_here, need_volume_factor_sink;
+ bool volume_is_norm;
+ size_t block_size_max_sink, block_size_max_sink_input;
+ size_t ilength;
+ size_t ilength_full;
+
+ pa_sink_input_assert_ref(i);
+ pa_sink_input_assert_io_context(i);
+ pa_assert(PA_SINK_INPUT_IS_LINKED(i->thread_info.state));
+ pa_assert(pa_frame_aligned(slength, &i->sink->sample_spec));
+ pa_assert(chunk);
+ pa_assert(volume);
+
+#ifdef SINK_INPUT_DEBUG
+ pa_log_debug("peek");
+#endif
+
+ block_size_max_sink_input = i->thread_info.resampler ?
+ pa_resampler_max_block_size(i->thread_info.resampler) :
+ pa_frame_align(pa_mempool_block_size_max(i->core->mempool), &i->sample_spec);
+
+ block_size_max_sink = pa_frame_align(pa_mempool_block_size_max(i->core->mempool), &i->sink->sample_spec);
+
+ /* Default buffer size */
+ if (slength <= 0)
+ slength = pa_frame_align(CONVERT_BUFFER_LENGTH, &i->sink->sample_spec);
+
+ if (slength > block_size_max_sink)
+ slength = block_size_max_sink;
+
+ if (i->thread_info.resampler) {
+ ilength = pa_resampler_request(i->thread_info.resampler, slength);
+
+ if (ilength <= 0)
+ ilength = pa_frame_align(CONVERT_BUFFER_LENGTH, &i->sample_spec);
+ } else
+ ilength = slength;
+
+ /* Length corresponding to slength (without limiting to
+ * block_size_max_sink_input). */
+ ilength_full = ilength;
+
+ if (ilength > block_size_max_sink_input)
+ ilength = block_size_max_sink_input;
+
+ /* If the channel maps of the sink and this stream differ, we need
+ * to adjust the volume *before* we resample. Otherwise we can do
+ * it after and leave it for the sink code */
+
+ do_volume_adj_here = !pa_channel_map_equal(&i->channel_map, &i->sink->channel_map);
+ volume_is_norm = pa_cvolume_is_norm(&i->thread_info.soft_volume) && !i->thread_info.muted;
+ need_volume_factor_sink = !pa_cvolume_is_norm(&i->volume_factor_sink);
+
+ while (!pa_memblockq_is_readable(i->thread_info.render_memblockq)) {
+ pa_memchunk tchunk;
+
+ /* There's nothing in our render queue. We need to fill it up
+ * with data from the implementor. */
+
+ if (i->thread_info.state == PA_SINK_INPUT_CORKED ||
+ i->pop(i, ilength, &tchunk) < 0) {
+
+ /* OK, we're corked or the implementor didn't give us any
+ * data, so let's just hand out silence */
+
+ pa_memblockq_seek(i->thread_info.render_memblockq, (int64_t) slength, PA_SEEK_RELATIVE, true);
+ i->thread_info.playing_for = 0;
+ if (i->thread_info.underrun_for != (uint64_t) -1) {
+ i->thread_info.underrun_for += ilength_full;
+ i->thread_info.underrun_for_sink += slength;
+ }
+ break;
+ }
+
+ pa_assert(tchunk.length > 0);
+ pa_assert(tchunk.memblock);
+
+ i->thread_info.underrun_for = 0;
+ i->thread_info.underrun_for_sink = 0;
+ i->thread_info.playing_for += tchunk.length;
+
+ while (tchunk.length > 0) {
+ pa_memchunk wchunk;
+ bool nvfs = need_volume_factor_sink;
+
+ wchunk = tchunk;
+ pa_memblock_ref(wchunk.memblock);
+
+ if (wchunk.length > block_size_max_sink_input)
+ wchunk.length = block_size_max_sink_input;
+
+ /* It might be necessary to adjust the volume here */
+ if (do_volume_adj_here && !volume_is_norm) {
+ pa_memchunk_make_writable(&wchunk, 0);
+
+ if (i->thread_info.muted) {
+ pa_silence_memchunk(&wchunk, &i->thread_info.sample_spec);
+ nvfs = false;
+
+ } else if (!i->thread_info.resampler && nvfs) {
+ pa_cvolume v;
+
+ /* If we don't need a resampler we can merge the
+ * post and the pre volume adjustment into one */
+
+ pa_sw_cvolume_multiply(&v, &i->thread_info.soft_volume, &i->volume_factor_sink);
+ pa_volume_memchunk(&wchunk, &i->thread_info.sample_spec, &v);
+ nvfs = false;
+
+ } else
+ pa_volume_memchunk(&wchunk, &i->thread_info.sample_spec, &i->thread_info.soft_volume);
+ }
+
+ if (!i->thread_info.resampler) {
+
+ if (nvfs) {
+ pa_memchunk_make_writable(&wchunk, 0);
+ pa_volume_memchunk(&wchunk, &i->sink->sample_spec, &i->volume_factor_sink);
+ }
+
+ pa_memblockq_push_align(i->thread_info.render_memblockq, &wchunk);
+ } else {
+ pa_memchunk rchunk;
+ pa_resampler_run(i->thread_info.resampler, &wchunk, &rchunk);
+
+#ifdef SINK_INPUT_DEBUG
+ pa_log_debug("pushing %lu", (unsigned long) rchunk.length);
+#endif
+
+ if (rchunk.memblock) {
+
+ if (nvfs) {
+ pa_memchunk_make_writable(&rchunk, 0);
+ pa_volume_memchunk(&rchunk, &i->sink->sample_spec, &i->volume_factor_sink);
+ }
+
+ pa_memblockq_push_align(i->thread_info.render_memblockq, &rchunk);
+ pa_memblock_unref(rchunk.memblock);
+ }
+ }
+
+ pa_memblock_unref(wchunk.memblock);
+
+ tchunk.index += wchunk.length;
+ tchunk.length -= wchunk.length;
+ }
+
+ pa_memblock_unref(tchunk.memblock);
+ }
+
+ pa_assert_se(pa_memblockq_peek(i->thread_info.render_memblockq, chunk) >= 0);
+
+ pa_assert(chunk->length > 0);
+ pa_assert(chunk->memblock);
+
+#ifdef SINK_INPUT_DEBUG
+ pa_log_debug("peeking %lu", (unsigned long) chunk->length);
+#endif
+
+ if (chunk->length > block_size_max_sink)
+ chunk->length = block_size_max_sink;
+
+ /* Let's see if we had to apply the volume adjustment ourselves,
+ * or if this can be done by the sink for us */
+
+ if (do_volume_adj_here)
+ /* We had different channel maps, so we already did the adjustment */
+ pa_cvolume_reset(volume, i->sink->sample_spec.channels);
+ else if (i->thread_info.muted)
+ /* We've both the same channel map, so let's have the sink do the adjustment for us*/
+ pa_cvolume_mute(volume, i->sink->sample_spec.channels);
+ else
+ *volume = i->thread_info.soft_volume;
+}
+
+/* Called from thread context */
+void pa_sink_input_drop(pa_sink_input *i, size_t nbytes /* in sink sample spec */) {
+
+ pa_sink_input_assert_ref(i);
+ pa_sink_input_assert_io_context(i);
+ pa_assert(PA_SINK_INPUT_IS_LINKED(i->thread_info.state));
+ pa_assert(pa_frame_aligned(nbytes, &i->sink->sample_spec));
+ pa_assert(nbytes > 0);
+
+#ifdef SINK_INPUT_DEBUG
+ pa_log_debug("dropping %lu", (unsigned long) nbytes);
+#endif
+
+ pa_memblockq_drop(i->thread_info.render_memblockq, nbytes);
+}
+
+/* Called from thread context */
+bool pa_sink_input_process_underrun(pa_sink_input *i) {
+ pa_sink_input_assert_ref(i);
+ pa_sink_input_assert_io_context(i);
+
+ if (pa_memblockq_is_readable(i->thread_info.render_memblockq))
+ return false;
+
+ if (i->process_underrun && i->process_underrun(i)) {
+ /* All valid data has been played back, so we can empty this queue. */
+ pa_memblockq_silence(i->thread_info.render_memblockq);
+ return true;
+ }
+ return false;
+}
+
+/* Called from thread context */
+void pa_sink_input_process_rewind(pa_sink_input *i, size_t nbytes /* in sink sample spec */) {
+ size_t lbq;
+ bool called = false;
+
+ pa_sink_input_assert_ref(i);
+ pa_sink_input_assert_io_context(i);
+ pa_assert(PA_SINK_INPUT_IS_LINKED(i->thread_info.state));
+ pa_assert(pa_frame_aligned(nbytes, &i->sink->sample_spec));
+
+#ifdef SINK_INPUT_DEBUG
+ pa_log_debug("rewind(%lu, %lu)", (unsigned long) nbytes, (unsigned long) i->thread_info.rewrite_nbytes);
+#endif
+
+ lbq = pa_memblockq_get_length(i->thread_info.render_memblockq);
+
+ if (nbytes > 0 && !i->thread_info.dont_rewind_render) {
+ pa_log_debug("Have to rewind %lu bytes on render memblockq.", (unsigned long) nbytes);
+ pa_memblockq_rewind(i->thread_info.render_memblockq, nbytes);
+ }
+
+ if (i->thread_info.rewrite_nbytes == (size_t) -1) {
+
+ /* We were asked to drop all buffered data, and rerequest new
+ * data from implementor the next time peek() is called */
+
+ pa_memblockq_flush_write(i->thread_info.render_memblockq, true);
+
+ } else if (i->thread_info.rewrite_nbytes > 0) {
+ size_t max_rewrite, amount;
+
+ /* Calculate how much make sense to rewrite at most */
+ max_rewrite = nbytes;
+ if (nbytes > 0)
+ max_rewrite += lbq;
+
+ /* Transform into local domain */
+ if (i->thread_info.resampler)
+ max_rewrite = pa_resampler_request(i->thread_info.resampler, max_rewrite);
+
+ /* Calculate how much of the rewinded data should actually be rewritten */
+ amount = PA_MIN(i->thread_info.rewrite_nbytes, max_rewrite);
+
+ if (amount > 0) {
+ pa_log_debug("Have to rewind %lu bytes on implementor.", (unsigned long) amount);
+
+ /* Tell the implementor */
+ if (i->process_rewind)
+ i->process_rewind(i, amount);
+ called = true;
+
+ /* Convert back to sink domain */
+ if (i->thread_info.resampler)
+ amount = pa_resampler_result(i->thread_info.resampler, amount);
+
+ if (amount > 0)
+ /* Ok, now update the write pointer */
+ pa_memblockq_seek(i->thread_info.render_memblockq, - ((int64_t) amount), PA_SEEK_RELATIVE, true);
+
+ if (i->thread_info.rewrite_flush)
+ pa_memblockq_silence(i->thread_info.render_memblockq);
+
+ /* And rewind the resampler */
+ if (i->thread_info.resampler)
+ pa_resampler_rewind(i->thread_info.resampler, amount);
+ }
+ }
+
+ if (!called)
+ if (i->process_rewind)
+ i->process_rewind(i, 0);
+
+ i->thread_info.rewrite_nbytes = 0;
+ i->thread_info.rewrite_flush = false;
+ i->thread_info.dont_rewind_render = false;
+}
+
+/* Called from thread context */
+size_t pa_sink_input_get_max_rewind(pa_sink_input *i) {
+ pa_sink_input_assert_ref(i);
+ pa_sink_input_assert_io_context(i);
+
+ return i->thread_info.resampler ? pa_resampler_request(i->thread_info.resampler, i->sink->thread_info.max_rewind) : i->sink->thread_info.max_rewind;
+}
+
+/* Called from thread context */
+size_t pa_sink_input_get_max_request(pa_sink_input *i) {
+ pa_sink_input_assert_ref(i);
+ pa_sink_input_assert_io_context(i);
+
+ /* We're not verifying the status here, to allow this to be called
+ * in the state change handler between _INIT and _RUNNING */
+
+ return i->thread_info.resampler ? pa_resampler_request(i->thread_info.resampler, i->sink->thread_info.max_request) : i->sink->thread_info.max_request;
+}
+
+/* Called from thread context */
+void pa_sink_input_update_max_rewind(pa_sink_input *i, size_t nbytes /* in the sink's sample spec */) {
+ pa_sink_input_assert_ref(i);
+ pa_sink_input_assert_io_context(i);
+ pa_assert(PA_SINK_INPUT_IS_LINKED(i->thread_info.state));
+ pa_assert(pa_frame_aligned(nbytes, &i->sink->sample_spec));
+
+ pa_memblockq_set_maxrewind(i->thread_info.render_memblockq, nbytes);
+
+ if (i->update_max_rewind)
+ i->update_max_rewind(i, i->thread_info.resampler ? pa_resampler_request(i->thread_info.resampler, nbytes) : nbytes);
+}
+
+/* Called from thread context */
+void pa_sink_input_update_max_request(pa_sink_input *i, size_t nbytes /* in the sink's sample spec */) {
+ pa_sink_input_assert_ref(i);
+ pa_sink_input_assert_io_context(i);
+ pa_assert(PA_SINK_INPUT_IS_LINKED(i->thread_info.state));
+ pa_assert(pa_frame_aligned(nbytes, &i->sink->sample_spec));
+
+ if (i->update_max_request)
+ i->update_max_request(i, i->thread_info.resampler ? pa_resampler_request(i->thread_info.resampler, nbytes) : nbytes);
+}
+
+/* Called from thread context */
+pa_usec_t pa_sink_input_set_requested_latency_within_thread(pa_sink_input *i, pa_usec_t usec) {
+ pa_sink_input_assert_ref(i);
+ pa_sink_input_assert_io_context(i);
+
+ if (!(i->sink->flags & PA_SINK_DYNAMIC_LATENCY))
+ usec = i->sink->thread_info.fixed_latency;
+
+ if (usec != (pa_usec_t) -1)
+ usec = PA_CLAMP(usec, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency);
+
+ i->thread_info.requested_sink_latency = usec;
+ pa_sink_invalidate_requested_latency(i->sink, true);
+
+ return usec;
+}
+
+/* Called from main context */
+pa_usec_t pa_sink_input_set_requested_latency(pa_sink_input *i, pa_usec_t usec) {
+ pa_sink_input_assert_ref(i);
+ pa_assert_ctl_context();
+
+ if (PA_SINK_INPUT_IS_LINKED(i->state) && i->sink) {
+ pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_REQUESTED_LATENCY, &usec, 0, NULL) == 0);
+ return usec;
+ }
+
+ /* If this sink input is not realized yet or we are being moved,
+ * we have to touch the thread info data directly */
+
+ if (i->sink) {
+ if (!(i->sink->flags & PA_SINK_DYNAMIC_LATENCY))
+ usec = pa_sink_get_fixed_latency(i->sink);
+
+ if (usec != (pa_usec_t) -1) {
+ pa_usec_t min_latency, max_latency;
+ pa_sink_get_latency_range(i->sink, &min_latency, &max_latency);
+ usec = PA_CLAMP(usec, min_latency, max_latency);
+ }
+ }
+
+ i->thread_info.requested_sink_latency = usec;
+
+ return usec;
+}
+
+/* Called from main context */
+pa_usec_t pa_sink_input_get_requested_latency(pa_sink_input *i) {
+ pa_sink_input_assert_ref(i);
+ pa_assert_ctl_context();
+
+ if (PA_SINK_INPUT_IS_LINKED(i->state) && i->sink) {
+ pa_usec_t usec = 0;
+ pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_GET_REQUESTED_LATENCY, &usec, 0, NULL) == 0);
+ return usec;
+ }
+
+ /* If this sink input is not realized yet or we are being moved,
+ * we have to touch the thread info data directly */
+
+ return i->thread_info.requested_sink_latency;
+}
+
+/* Called from main context */
+void pa_sink_input_set_volume(pa_sink_input *i, const pa_cvolume *volume, bool save, bool absolute) {
+ pa_cvolume v;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_ctl_context();
+ pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
+ pa_assert(volume);
+ pa_assert(pa_cvolume_valid(volume));
+ pa_assert(volume->channels == 1 || pa_cvolume_compatible(volume, &i->sample_spec));
+ pa_assert(i->volume_writable);
+
+ if (!absolute && pa_sink_flat_volume_enabled(i->sink)) {
+ v = i->sink->reference_volume;
+ pa_cvolume_remap(&v, &i->sink->channel_map, &i->channel_map);
+
+ if (pa_cvolume_compatible(volume, &i->sample_spec))
+ volume = pa_sw_cvolume_multiply(&v, &v, volume);
+ else
+ volume = pa_sw_cvolume_multiply_scalar(&v, &v, pa_cvolume_max(volume));
+ } else {
+ if (!pa_cvolume_compatible(volume, &i->sample_spec)) {
+ v = i->volume;
+ volume = pa_cvolume_scale(&v, pa_cvolume_max(volume));
+ }
+ }
+
+ if (pa_cvolume_equal(volume, &i->volume)) {
+ i->save_volume = i->save_volume || save;
+ return;
+ }
+
+ pa_sink_input_set_volume_direct(i, volume);
+ i->save_volume = save;
+
+ if (pa_sink_flat_volume_enabled(i->sink)) {
+ /* We are in flat volume mode, so let's update all sink input
+ * volumes and update the flat volume of the sink */
+
+ pa_sink_set_volume(i->sink, NULL, true, save);
+
+ } else {
+ /* OK, we are in normal volume mode. The volume only affects
+ * ourselves */
+ set_real_ratio(i, volume);
+ pa_sink_input_set_reference_ratio(i, &i->volume);
+
+ /* Copy the new soft_volume to the thread_info struct */
+ pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_SOFT_VOLUME, NULL, 0, NULL) == 0);
+ }
+}
+
+void pa_sink_input_add_volume_factor(pa_sink_input *i, const char *key, const pa_cvolume *volume_factor) {
+ struct volume_factor_entry *v;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_ctl_context();
+ pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
+ pa_assert(volume_factor);
+ pa_assert(key);
+ pa_assert(pa_cvolume_valid(volume_factor));
+ pa_assert(volume_factor->channels == 1 || pa_cvolume_compatible(volume_factor, &i->sample_spec));
+
+ v = volume_factor_entry_new(key, volume_factor);
+ if (!pa_cvolume_compatible(volume_factor, &i->sample_spec))
+ pa_cvolume_set(&v->volume, i->sample_spec.channels, volume_factor->values[0]);
+
+ pa_assert_se(pa_hashmap_put(i->volume_factor_items, v->key, v) >= 0);
+ if (pa_hashmap_size(i->volume_factor_items) == 1)
+ i->volume_factor = v->volume;
+ else
+ pa_sw_cvolume_multiply(&i->volume_factor, &i->volume_factor, &v->volume);
+
+ pa_sw_cvolume_multiply(&i->soft_volume, &i->real_ratio, &i->volume_factor);
+
+ /* Copy the new soft_volume to the thread_info struct */
+ pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_SOFT_VOLUME, NULL, 0, NULL) == 0);
+}
+
+/* Returns 0 if an entry was removed and -1 if no entry for the given key was
+ * found. */
+int pa_sink_input_remove_volume_factor(pa_sink_input *i, const char *key) {
+ struct volume_factor_entry *v;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert(key);
+ pa_assert_ctl_context();
+ pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
+
+ if (pa_hashmap_remove_and_free(i->volume_factor_items, key) < 0)
+ return -1;
+
+ switch (pa_hashmap_size(i->volume_factor_items)) {
+ case 0:
+ pa_cvolume_reset(&i->volume_factor, i->sample_spec.channels);
+ break;
+ case 1:
+ v = pa_hashmap_first(i->volume_factor_items);
+ i->volume_factor = v->volume;
+ break;
+ default:
+ volume_factor_from_hashmap(&i->volume_factor, i->volume_factor_items, i->volume_factor.channels);
+ }
+
+ pa_sw_cvolume_multiply(&i->soft_volume, &i->real_ratio, &i->volume_factor);
+
+ /* Copy the new soft_volume to the thread_info struct */
+ pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_SOFT_VOLUME, NULL, 0, NULL) == 0);
+
+ return 0;
+}
+
+/* Called from main context */
+static void set_real_ratio(pa_sink_input *i, const pa_cvolume *v) {
+ pa_sink_input_assert_ref(i);
+ pa_assert_ctl_context();
+ pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
+ pa_assert(!v || pa_cvolume_compatible(v, &i->sample_spec));
+
+ /* This basically calculates:
+ *
+ * i->real_ratio := v
+ * i->soft_volume := i->real_ratio * i->volume_factor */
+
+ if (v)
+ i->real_ratio = *v;
+ else
+ pa_cvolume_reset(&i->real_ratio, i->sample_spec.channels);
+
+ pa_sw_cvolume_multiply(&i->soft_volume, &i->real_ratio, &i->volume_factor);
+ /* We don't copy the data to the thread_info data. That's left for someone else to do */
+}
+
+/* Called from main or I/O context */
+bool pa_sink_input_is_passthrough(pa_sink_input *i) {
+ pa_sink_input_assert_ref(i);
+
+ if (PA_UNLIKELY(!pa_format_info_is_pcm(i->format)))
+ return true;
+
+ if (PA_UNLIKELY(i->flags & PA_SINK_INPUT_PASSTHROUGH))
+ return true;
+
+ return false;
+}
+
+/* Called from main context */
+bool pa_sink_input_is_volume_readable(pa_sink_input *i) {
+ pa_sink_input_assert_ref(i);
+ pa_assert_ctl_context();
+
+ return !pa_sink_input_is_passthrough(i);
+}
+
+/* Called from main context */
+pa_cvolume *pa_sink_input_get_volume(pa_sink_input *i, pa_cvolume *volume, bool absolute) {
+ pa_sink_input_assert_ref(i);
+ pa_assert_ctl_context();
+ pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
+ pa_assert(pa_sink_input_is_volume_readable(i));
+
+ if (absolute || !pa_sink_flat_volume_enabled(i->sink))
+ *volume = i->volume;
+ else
+ *volume = i->reference_ratio;
+
+ return volume;
+}
+
+/* Called from main context */
+void pa_sink_input_set_mute(pa_sink_input *i, bool mute, bool save) {
+ bool old_mute;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_ctl_context();
+ pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
+
+ old_mute = i->muted;
+
+ if (mute == old_mute) {
+ i->save_muted |= save;
+ return;
+ }
+
+ i->muted = mute;
+ pa_log_debug("The mute of sink input %u changed from %s to %s.", i->index, pa_yes_no(old_mute), pa_yes_no(mute));
+
+ i->save_muted = save;
+
+ pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_SOFT_MUTE, NULL, 0, NULL) == 0);
+
+ /* The mute status changed, let's tell people so */
+ if (i->mute_changed)
+ i->mute_changed(i);
+
+ pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
+ pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_MUTE_CHANGED], i);
+}
+
+void pa_sink_input_set_property(pa_sink_input *i, const char *key, const char *value) {
+ char *old_value = NULL;
+ const char *new_value;
+
+ pa_assert(i);
+ pa_assert(key);
+
+ if (pa_proplist_contains(i->proplist, key)) {
+ old_value = pa_xstrdup(pa_proplist_gets(i->proplist, key));
+ if (value && old_value && pa_streq(value, old_value))
+ goto finish;
+
+ if (!old_value)
+ old_value = pa_xstrdup("(data)");
+ } else {
+ if (!value)
+ goto finish;
+
+ old_value = pa_xstrdup("(unset)");
+ }
+
+ if (value) {
+ pa_proplist_sets(i->proplist, key, value);
+ new_value = value;
+ } else {
+ pa_proplist_unset(i->proplist, key);
+ new_value = "(unset)";
+ }
+
+ if (PA_SINK_INPUT_IS_LINKED(i->state)) {
+ pa_log_debug("Sink input %u: proplist[%s]: %s -> %s", i->index, key, old_value, new_value);
+ pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_PROPLIST_CHANGED], i);
+ pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT | PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
+ }
+
+finish:
+ pa_xfree(old_value);
+}
+
+void pa_sink_input_set_property_arbitrary(pa_sink_input *i, const char *key, const uint8_t *value, size_t nbytes) {
+ const uint8_t *old_value;
+ size_t old_nbytes;
+ const char *old_value_str;
+ const char *new_value_str;
+
+ pa_assert(i);
+ pa_assert(key);
+
+ if (pa_proplist_get(i->proplist, key, (const void **) &old_value, &old_nbytes) >= 0) {
+ if (value && nbytes == old_nbytes && !memcmp(value, old_value, nbytes))
+ return;
+
+ old_value_str = "(data)";
+
+ } else {
+ if (!value)
+ return;
+
+ old_value_str = "(unset)";
+ }
+
+ if (value) {
+ pa_proplist_set(i->proplist, key, value, nbytes);
+ new_value_str = "(data)";
+ } else {
+ pa_proplist_unset(i->proplist, key);
+ new_value_str = "(unset)";
+ }
+
+ if (PA_SINK_INPUT_IS_LINKED(i->state)) {
+ pa_log_debug("Sink input %u: proplist[%s]: %s -> %s", i->index, key, old_value_str, new_value_str);
+ pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_PROPLIST_CHANGED], i);
+ pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT | PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
+ }
+}
+
+/* Called from main thread */
+void pa_sink_input_update_proplist(pa_sink_input *i, pa_update_mode_t mode, pa_proplist *p) {
+ void *state;
+ const char *key;
+ const uint8_t *value;
+ size_t nbytes;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert(p);
+ pa_assert_ctl_context();
+
+ switch (mode) {
+ case PA_UPDATE_SET:
+ /* Delete everything that is not in p. */
+ for (state = NULL; (key = pa_proplist_iterate(i->proplist, &state));) {
+ if (!pa_proplist_contains(p, key))
+ pa_sink_input_set_property(i, key, NULL);
+ }
+
+ /* Fall through. */
+ case PA_UPDATE_REPLACE:
+ for (state = NULL; (key = pa_proplist_iterate(p, &state));) {
+ pa_proplist_get(p, key, (const void **) &value, &nbytes);
+ pa_sink_input_set_property_arbitrary(i, key, value, nbytes);
+ }
+
+ break;
+ case PA_UPDATE_MERGE:
+ for (state = NULL; (key = pa_proplist_iterate(p, &state));) {
+ if (pa_proplist_contains(i->proplist, key))
+ continue;
+
+ pa_proplist_get(p, key, (const void **) &value, &nbytes);
+ pa_sink_input_set_property_arbitrary(i, key, value, nbytes);
+ }
+
+ break;
+ }
+}
+
+/* Called from main context */
+void pa_sink_input_cork(pa_sink_input *i, bool b) {
+ pa_sink_input_assert_ref(i);
+ pa_assert_ctl_context();
+ pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
+
+ sink_input_set_state(i, b ? PA_SINK_INPUT_CORKED : PA_SINK_INPUT_RUNNING);
+}
+
+/* Called from main context */
+int pa_sink_input_set_rate(pa_sink_input *i, uint32_t rate) {
+ pa_sink_input_assert_ref(i);
+ pa_assert_ctl_context();
+ pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
+ pa_return_val_if_fail(i->thread_info.resampler, -PA_ERR_BADSTATE);
+
+ if (i->sample_spec.rate == rate)
+ return 0;
+
+ i->sample_spec.rate = rate;
+
+ if (i->sink)
+ pa_asyncmsgq_post(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_RATE, PA_UINT_TO_PTR(rate), 0, NULL, NULL);
+ else {
+ i->thread_info.sample_spec.rate = rate;
+ pa_resampler_set_input_rate(i->thread_info.resampler, rate);
+ }
+
+ pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
+ return 0;
+}
+
+/* Called from main context */
+pa_resample_method_t pa_sink_input_get_resample_method(pa_sink_input *i) {
+ pa_sink_input_assert_ref(i);
+ pa_assert_ctl_context();
+
+ return i->actual_resample_method;
+}
+
+/* Called from main context */
+bool pa_sink_input_may_move(pa_sink_input *i) {
+ pa_sink_input_assert_ref(i);
+ pa_assert_ctl_context();
+ pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
+
+ if (i->flags & PA_SINK_INPUT_DONT_MOVE)
+ return false;
+
+ if (i->sync_next || i->sync_prev) {
+ pa_log_warn("Moving synchronized streams not supported.");
+ return false;
+ }
+
+ return true;
+}
+
+static bool find_filter_sink_input(pa_sink_input *target, pa_sink *s) {
+ unsigned PA_UNUSED i = 0;
+ while (s && s->input_to_master) {
+ if (s->input_to_master == target)
+ return true;
+ s = s->input_to_master->sink;
+ pa_assert(i++ < 100);
+ }
+ return false;
+}
+
+static bool is_filter_sink_moving(pa_sink_input *i) {
+ pa_sink *sink = i->sink;
+
+ if (!sink)
+ return false;
+
+ while (sink->input_to_master) {
+ sink = sink->input_to_master->sink;
+
+ if (!sink)
+ return true;
+ }
+
+ return false;
+}
+
+/* Called from main context */
+bool pa_sink_input_may_move_to(pa_sink_input *i, pa_sink *dest) {
+ pa_sink_input_assert_ref(i);
+ pa_assert_ctl_context();
+ pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
+ pa_sink_assert_ref(dest);
+
+ if (dest == i->sink)
+ return true;
+
+ if (dest->unlink_requested)
+ return false;
+
+ if (!pa_sink_input_may_move(i))
+ return false;
+
+ /* Make sure we're not creating a filter sink cycle */
+ if (find_filter_sink_input(i, dest)) {
+ pa_log_debug("Can't connect input to %s, as that would create a cycle.", dest->name);
+ return false;
+ }
+
+ /* If this sink input is connected to a filter sink that itself is moving,
+ * then don't allow the move. Moving requires sending a message to the IO
+ * thread of the old sink, and if the old sink is a filter sink that is
+ * moving, there's no IO thread associated to the old sink. */
+ if (is_filter_sink_moving(i)) {
+ pa_log_debug("Can't move input from filter sink %s, because the filter sink itself is currently moving.",
+ i->sink->name);
+ return false;
+ }
+
+ if (pa_idxset_size(dest->inputs) >= PA_MAX_INPUTS_PER_SINK) {
+ pa_log_warn("Failed to move sink input: too many inputs per sink.");
+ return false;
+ }
+
+ if (check_passthrough_connection(pa_sink_input_is_passthrough(i), dest) < 0)
+ return false;
+
+ if (i->may_move_to)
+ if (!i->may_move_to(i, dest))
+ return false;
+
+ return true;
+}
+
+/* Called from main context */
+int pa_sink_input_start_move(pa_sink_input *i) {
+ pa_source_output *o, PA_UNUSED *p = NULL;
+ struct volume_factor_entry *v;
+ void *state = NULL;
+ int r;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_ctl_context();
+ pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
+ pa_assert(i->sink);
+
+ if (!pa_sink_input_may_move(i))
+ return -PA_ERR_NOTSUPPORTED;
+
+ if ((r = pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_START], i)) < 0)
+ return r;
+
+ pa_log_debug("Starting to move sink input %u from '%s'", (unsigned) i->index, i->sink->name);
+
+ /* Kill directly connected outputs */
+ while ((o = pa_idxset_first(i->direct_outputs, NULL))) {
+ pa_assert(o != p);
+ pa_source_output_kill(o);
+ p = o;
+ }
+ pa_assert(pa_idxset_isempty(i->direct_outputs));
+
+ pa_idxset_remove_by_data(i->sink->inputs, i, NULL);
+
+ if (i->state == PA_SINK_INPUT_CORKED)
+ pa_assert_se(i->sink->n_corked-- >= 1);
+
+ if (pa_sink_input_is_passthrough(i))
+ pa_sink_leave_passthrough(i->sink);
+
+ if (pa_sink_flat_volume_enabled(i->sink))
+ /* We might need to update the sink's volume if we are in flat
+ * volume mode. */
+ pa_sink_set_volume(i->sink, NULL, false, false);
+
+ pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_START_MOVE, i, 0, NULL) == 0);
+
+ pa_sink_update_status(i->sink);
+
+ PA_HASHMAP_FOREACH(v, i->volume_factor_sink_items, state)
+ pa_cvolume_remap(&v->volume, &i->sink->channel_map, &i->channel_map);
+
+ pa_cvolume_remap(&i->volume_factor_sink, &i->sink->channel_map, &i->channel_map);
+
+ i->sink = NULL;
+ i->sink_requested_by_application = false;
+
+ pa_sink_input_unref(i);
+
+ return 0;
+}
+
+/* Called from main context. If i has an origin sink that uses volume sharing,
+ * then also the origin sink and all streams connected to it need to update
+ * their volume - this function does all that by using recursion. */
+static void update_volume_due_to_moving(pa_sink_input *i, pa_sink *dest) {
+ pa_cvolume new_volume;
+
+ pa_assert(i);
+ pa_assert(dest);
+ pa_assert(i->sink); /* The destination sink should already be set. */
+
+ if (i->origin_sink && (i->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)) {
+ pa_sink *root_sink = pa_sink_get_master(i->sink);
+ pa_sink_input *origin_sink_input;
+ uint32_t idx;
+
+ if (PA_UNLIKELY(!root_sink))
+ return;
+
+ if (pa_sink_flat_volume_enabled(i->sink)) {
+ /* Ok, so the origin sink uses volume sharing, and flat volume is
+ * enabled. The volume will have to be updated as follows:
+ *
+ * i->volume := i->sink->real_volume
+ * (handled later by pa_sink_set_volume)
+ * i->reference_ratio := i->volume / i->sink->reference_volume
+ * (handled later by pa_sink_set_volume)
+ * i->real_ratio stays unchanged
+ * (streams whose origin sink uses volume sharing should
+ * always have real_ratio of 0 dB)
+ * i->soft_volume stays unchanged
+ * (streams whose origin sink uses volume sharing should
+ * always have volume_factor as soft_volume, so no change
+ * should be needed) */
+
+ pa_assert(pa_cvolume_is_norm(&i->real_ratio));
+ pa_assert(pa_cvolume_equal(&i->soft_volume, &i->volume_factor));
+
+ /* Notifications will be sent by pa_sink_set_volume(). */
+
+ } else {
+ /* Ok, so the origin sink uses volume sharing, and flat volume is
+ * disabled. The volume will have to be updated as follows:
+ *
+ * i->volume := 0 dB
+ * i->reference_ratio := 0 dB
+ * i->real_ratio stays unchanged
+ * (streams whose origin sink uses volume sharing should
+ * always have real_ratio of 0 dB)
+ * i->soft_volume stays unchanged
+ * (streams whose origin sink uses volume sharing should
+ * always have volume_factor as soft_volume, so no change
+ * should be needed) */
+
+ pa_cvolume_reset(&new_volume, i->volume.channels);
+ pa_sink_input_set_volume_direct(i, &new_volume);
+ pa_sink_input_set_reference_ratio(i, &new_volume);
+ pa_assert(pa_cvolume_is_norm(&i->real_ratio));
+ pa_assert(pa_cvolume_equal(&i->soft_volume, &i->volume_factor));
+ }
+
+ /* Additionally, the origin sink volume needs updating:
+ *
+ * i->origin_sink->reference_volume := root_sink->reference_volume
+ * i->origin_sink->real_volume := root_sink->real_volume
+ * i->origin_sink->soft_volume stays unchanged
+ * (sinks that use volume sharing should always have
+ * soft_volume of 0 dB) */
+
+ new_volume = root_sink->reference_volume;
+ pa_cvolume_remap(&new_volume, &root_sink->channel_map, &i->origin_sink->channel_map);
+ pa_sink_set_reference_volume_direct(i->origin_sink, &new_volume);
+
+ i->origin_sink->real_volume = root_sink->real_volume;
+ pa_cvolume_remap(&i->origin_sink->real_volume, &root_sink->channel_map, &i->origin_sink->channel_map);
+
+ pa_assert(pa_cvolume_is_norm(&i->origin_sink->soft_volume));
+
+ /* If you wonder whether i->origin_sink->set_volume() should be called
+ * somewhere, that's not the case, because sinks that use volume
+ * sharing shouldn't have any internal volume that set_volume() would
+ * update. If you wonder whether the thread_info variables should be
+ * synced, yes, they should, and it's done by the
+ * PA_SINK_MESSAGE_FINISH_MOVE message handler. */
+
+ /* Recursively update origin sink inputs. */
+ PA_IDXSET_FOREACH(origin_sink_input, i->origin_sink->inputs, idx)
+ update_volume_due_to_moving(origin_sink_input, dest);
+
+ } else {
+ if (pa_sink_flat_volume_enabled(i->sink)) {
+ /* Ok, so this is a regular stream, and flat volume is enabled. The
+ * volume will have to be updated as follows:
+ *
+ * i->volume := i->reference_ratio * i->sink->reference_volume
+ * i->reference_ratio stays unchanged
+ * i->real_ratio := i->volume / i->sink->real_volume
+ * (handled later by pa_sink_set_volume)
+ * i->soft_volume := i->real_ratio * i->volume_factor
+ * (handled later by pa_sink_set_volume) */
+
+ new_volume = i->sink->reference_volume;
+ pa_cvolume_remap(&new_volume, &i->sink->channel_map, &i->channel_map);
+ pa_sw_cvolume_multiply(&new_volume, &new_volume, &i->reference_ratio);
+ pa_sink_input_set_volume_direct(i, &new_volume);
+
+ } else {
+ /* Ok, so this is a regular stream, and flat volume is disabled.
+ * The volume will have to be updated as follows:
+ *
+ * i->volume := i->reference_ratio
+ * i->reference_ratio stays unchanged
+ * i->real_ratio := i->reference_ratio
+ * i->soft_volume := i->real_ratio * i->volume_factor */
+
+ pa_sink_input_set_volume_direct(i, &i->reference_ratio);
+ i->real_ratio = i->reference_ratio;
+ pa_sw_cvolume_multiply(&i->soft_volume, &i->real_ratio, &i->volume_factor);
+ }
+ }
+
+ /* If i->sink == dest, then recursion has finished, and we can finally call
+ * pa_sink_set_volume(), which will do the rest of the updates. */
+ if ((i->sink == dest) && pa_sink_flat_volume_enabled(i->sink))
+ pa_sink_set_volume(i->sink, NULL, false, i->save_volume);
+}
+
+/* Called from main context */
+int pa_sink_input_finish_move(pa_sink_input *i, pa_sink *dest, bool save) {
+ struct volume_factor_entry *v;
+ void *state = NULL;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_ctl_context();
+ pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
+ pa_assert(!i->sink);
+ pa_sink_assert_ref(dest);
+
+ if (!pa_sink_input_may_move_to(i, dest))
+ return -PA_ERR_NOTSUPPORTED;
+
+ if (pa_sink_input_is_passthrough(i) && !pa_sink_check_format(dest, i->format)) {
+ pa_proplist *p = pa_proplist_new();
+ pa_log_debug("New sink doesn't support stream format, sending format-changed and killing");
+ /* Tell the client what device we want to be on if it is going to
+ * reconnect */
+ pa_proplist_sets(p, "device", dest->name);
+ pa_sink_input_send_event(i, PA_STREAM_EVENT_FORMAT_LOST, p);
+ pa_proplist_free(p);
+ return -PA_ERR_NOTSUPPORTED;
+ }
+
+ if (!(i->flags & PA_SINK_INPUT_VARIABLE_RATE) &&
+ !pa_sample_spec_equal(&i->sample_spec, &dest->sample_spec)) {
+ /* try to change dest sink format and rate if possible without glitches.
+ module-suspend-on-idle resumes destination sink with
+ SINK_INPUT_MOVE_FINISH hook */
+
+ pa_log_info("Trying to change sample spec");
+ pa_sink_reconfigure(dest, &i->sample_spec, pa_sink_input_is_passthrough(i));
+ }
+
+ if (i->moving)
+ i->moving(i, dest);
+
+ i->sink = dest;
+ /* save == true, means user is calling the move_to() and want to
+ save the preferred_sink */
+ if (save) {
+ pa_xfree(i->preferred_sink);
+ if (dest == dest->core->default_sink)
+ i->preferred_sink = NULL;
+ else
+ i->preferred_sink = pa_xstrdup(dest->name);
+ }
+
+ pa_idxset_put(dest->inputs, pa_sink_input_ref(i), NULL);
+
+ PA_HASHMAP_FOREACH(v, i->volume_factor_sink_items, state)
+ pa_cvolume_remap(&v->volume, &i->channel_map, &i->sink->channel_map);
+
+ pa_cvolume_remap(&i->volume_factor_sink, &i->channel_map, &i->sink->channel_map);
+
+ if (i->state == PA_SINK_INPUT_CORKED)
+ i->sink->n_corked++;
+
+ pa_sink_input_update_resampler(i);
+
+ pa_sink_update_status(dest);
+
+ update_volume_due_to_moving(i, dest);
+
+ if (pa_sink_input_is_passthrough(i))
+ pa_sink_enter_passthrough(i->sink);
+
+ pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_FINISH_MOVE, i, 0, NULL) == 0);
+
+ pa_log_debug("Successfully moved sink input %i to %s.", i->index, dest->name);
+
+ /* Notify everyone */
+ pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_FINISH], i);
+ pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
+
+ return 0;
+}
+
+/* Called from main context */
+void pa_sink_input_fail_move(pa_sink_input *i) {
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_ctl_context();
+ pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
+ pa_assert(!i->sink);
+
+ /* Check if someone wants this sink input? */
+ if (pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_FAIL], i) == PA_HOOK_STOP)
+ return;
+
+ /* Can we move the sink input to the default sink? */
+ if (i->core->rescue_streams && pa_sink_input_may_move_to(i, i->core->default_sink)) {
+ if (pa_sink_input_finish_move(i, i->core->default_sink, false) >= 0)
+ return;
+ }
+
+ if (i->moving)
+ i->moving(i, NULL);
+
+ pa_sink_input_kill(i);
+}
+
+/* Called from main context */
+int pa_sink_input_move_to(pa_sink_input *i, pa_sink *dest, bool save) {
+ int r;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_ctl_context();
+ pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
+ pa_assert(i->sink);
+ pa_sink_assert_ref(dest);
+
+ if (dest == i->sink)
+ return 0;
+
+ if (!pa_sink_input_may_move_to(i, dest))
+ return -PA_ERR_NOTSUPPORTED;
+
+ pa_sink_input_ref(i);
+
+ if ((r = pa_sink_input_start_move(i)) < 0) {
+ pa_sink_input_unref(i);
+ return r;
+ }
+
+ if ((r = pa_sink_input_finish_move(i, dest, save)) < 0) {
+ pa_sink_input_fail_move(i);
+ pa_sink_input_unref(i);
+ return r;
+ }
+
+ pa_sink_input_unref(i);
+
+ return 0;
+}
+
+/* Called from IO thread context except when cork() is called without a valid sink. */
+void pa_sink_input_set_state_within_thread(pa_sink_input *i, pa_sink_input_state_t state) {
+ bool corking, uncorking;
+
+ pa_sink_input_assert_ref(i);
+
+ if (state == i->thread_info.state)
+ return;
+
+ corking = state == PA_SINK_INPUT_CORKED && i->thread_info.state == PA_SINK_INPUT_RUNNING;
+ uncorking = i->thread_info.state == PA_SINK_INPUT_CORKED && state == PA_SINK_INPUT_RUNNING;
+
+ if (i->state_change)
+ i->state_change(i, state);
+
+ if (corking) {
+
+ pa_log_debug("Requesting rewind due to corking");
+
+ /* This will tell the implementing sink input driver to rewind
+ * so that the unplayed already mixed data is not lost */
+ if (i->sink)
+ pa_sink_input_request_rewind(i, 0, true, true, false);
+
+ /* Set the corked state *after* requesting rewind */
+ i->thread_info.state = state;
+
+ } else if (uncorking) {
+
+ pa_log_debug("Requesting rewind due to uncorking");
+
+ i->thread_info.underrun_for = (uint64_t) -1;
+ i->thread_info.underrun_for_sink = 0;
+ i->thread_info.playing_for = 0;
+
+ /* Set the uncorked state *before* requesting rewind */
+ i->thread_info.state = state;
+
+ /* OK, we're being uncorked. Make sure we're not rewound when
+ * the hw buffer is remixed and request a remix. */
+ if (i->sink)
+ pa_sink_input_request_rewind(i, 0, false, true, true);
+ } else
+ /* We may not be corking or uncorking, but we still need to set the state. */
+ i->thread_info.state = state;
+}
+
+/* Called from thread context, except when it is not. */
+int pa_sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk) {
+ pa_sink_input *i = PA_SINK_INPUT(o);
+ pa_sink_input_assert_ref(i);
+
+ switch (code) {
+
+ case PA_SINK_INPUT_MESSAGE_SET_SOFT_VOLUME:
+ if (!pa_cvolume_equal(&i->thread_info.soft_volume, &i->soft_volume)) {
+ i->thread_info.soft_volume = i->soft_volume;
+ pa_sink_input_request_rewind(i, 0, true, false, false);
+ }
+ return 0;
+
+ case PA_SINK_INPUT_MESSAGE_SET_SOFT_MUTE:
+ if (i->thread_info.muted != i->muted) {
+ i->thread_info.muted = i->muted;
+ pa_sink_input_request_rewind(i, 0, true, false, false);
+ }
+ return 0;
+
+ case PA_SINK_INPUT_MESSAGE_GET_LATENCY: {
+ pa_usec_t *r = userdata;
+
+ r[0] += pa_bytes_to_usec(pa_memblockq_get_length(i->thread_info.render_memblockq), &i->sink->sample_spec);
+ r[1] += pa_sink_get_latency_within_thread(i->sink, false);
+
+ return 0;
+ }
+
+ case PA_SINK_INPUT_MESSAGE_SET_RATE:
+
+ i->thread_info.sample_spec.rate = PA_PTR_TO_UINT(userdata);
+ pa_resampler_set_input_rate(i->thread_info.resampler, PA_PTR_TO_UINT(userdata));
+
+ return 0;
+
+ case PA_SINK_INPUT_MESSAGE_SET_STATE: {
+ pa_sink_input *ssync;
+
+ pa_sink_input_set_state_within_thread(i, PA_PTR_TO_UINT(userdata));
+
+ for (ssync = i->thread_info.sync_prev; ssync; ssync = ssync->thread_info.sync_prev)
+ pa_sink_input_set_state_within_thread(ssync, PA_PTR_TO_UINT(userdata));
+
+ for (ssync = i->thread_info.sync_next; ssync; ssync = ssync->thread_info.sync_next)
+ pa_sink_input_set_state_within_thread(ssync, PA_PTR_TO_UINT(userdata));
+
+ return 0;
+ }
+
+ case PA_SINK_INPUT_MESSAGE_SET_REQUESTED_LATENCY: {
+ pa_usec_t *usec = userdata;
+
+ *usec = pa_sink_input_set_requested_latency_within_thread(i, *usec);
+ return 0;
+ }
+
+ case PA_SINK_INPUT_MESSAGE_GET_REQUESTED_LATENCY: {
+ pa_usec_t *r = userdata;
+
+ *r = i->thread_info.requested_sink_latency;
+ return 0;
+ }
+ }
+
+ return -PA_ERR_NOTIMPLEMENTED;
+}
+
+/* Called from IO context */
+bool pa_sink_input_safe_to_remove(pa_sink_input *i) {
+ pa_sink_input_assert_ref(i);
+ pa_sink_input_assert_io_context(i);
+
+ if (PA_SINK_INPUT_IS_LINKED(i->thread_info.state))
+ return pa_memblockq_is_empty(i->thread_info.render_memblockq);
+
+ return true;
+}
+
+/* Called from IO context */
+void pa_sink_input_request_rewind(
+ pa_sink_input *i,
+ size_t nbytes /* in our sample spec */,
+ bool rewrite, /* rewrite what we have, or get fresh data? */
+ bool flush, /* flush render memblockq? */
+ bool dont_rewind_render) {
+
+ size_t lbq;
+
+ /* If 'rewrite' is true the sink is rewound as far as requested
+ * and possible and the exact value of this is passed back the
+ * implementor via process_rewind(). If 'flush' is also true all
+ * already rendered data is also dropped.
+ *
+ * If 'rewrite' is false the sink is rewound as far as requested
+ * and possible and the already rendered data is dropped so that
+ * in the next iteration we read new data from the
+ * implementor. This implies 'flush' is true. If
+ * dont_rewind_render is true then the render memblockq is not
+ * rewound. */
+
+ /* nbytes = 0 means maximum rewind request */
+
+ pa_sink_input_assert_ref(i);
+ pa_sink_input_assert_io_context(i);
+ pa_assert(rewrite || flush);
+ pa_assert(!dont_rewind_render || !rewrite);
+
+ /* We don't take rewind requests while we are corked */
+ if (i->thread_info.state == PA_SINK_INPUT_CORKED)
+ return;
+
+ nbytes = PA_MAX(i->thread_info.rewrite_nbytes, nbytes);
+
+#ifdef SINK_INPUT_DEBUG
+ pa_log_debug("request rewrite %zu", nbytes);
+#endif
+
+ /* Calculate how much we can rewind locally without having to
+ * touch the sink */
+ if (rewrite)
+ lbq = pa_memblockq_get_length(i->thread_info.render_memblockq);
+ else
+ lbq = 0;
+
+ /* Check if rewinding for the maximum is requested, and if so, fix up */
+ if (nbytes <= 0) {
+
+ /* Calculate maximum number of bytes that could be rewound in theory */
+ nbytes = i->sink->thread_info.max_rewind + lbq;
+
+ /* Transform from sink domain */
+ if (i->thread_info.resampler)
+ nbytes = pa_resampler_request(i->thread_info.resampler, nbytes);
+ }
+
+ /* Remember how much we actually want to rewrite */
+ if (i->thread_info.rewrite_nbytes != (size_t) -1) {
+ if (rewrite) {
+ /* Make sure to not overwrite over underruns */
+ if (nbytes > i->thread_info.playing_for)
+ nbytes = (size_t) i->thread_info.playing_for;
+
+ i->thread_info.rewrite_nbytes = nbytes;
+ } else
+ i->thread_info.rewrite_nbytes = (size_t) -1;
+ }
+
+ i->thread_info.rewrite_flush =
+ i->thread_info.rewrite_flush || flush;
+
+ i->thread_info.dont_rewind_render =
+ i->thread_info.dont_rewind_render ||
+ dont_rewind_render;
+
+ /* nbytes is -1 if some earlier rewind request had rewrite == false. */
+ if (nbytes != (size_t) -1) {
+
+ /* Transform to sink domain */
+ if (i->thread_info.resampler)
+ nbytes = pa_resampler_result(i->thread_info.resampler, nbytes);
+
+ if (nbytes > lbq)
+ pa_sink_request_rewind(i->sink, nbytes - lbq);
+ else
+ /* This call will make sure process_rewind() is called later */
+ pa_sink_request_rewind(i->sink, 0);
+ }
+}
+
+/* Called from main context */
+pa_memchunk* pa_sink_input_get_silence(pa_sink_input *i, pa_memchunk *ret) {
+ pa_sink_input_assert_ref(i);
+ pa_assert_ctl_context();
+ pa_assert(ret);
+
+ /* FIXME: Shouldn't access resampler object from main context! */
+
+ pa_silence_memchunk_get(
+ &i->core->silence_cache,
+ i->core->mempool,
+ ret,
+ &i->sample_spec,
+ i->thread_info.resampler ? pa_resampler_max_block_size(i->thread_info.resampler) : 0);
+
+ return ret;
+}
+
+/* Called from main context */
+void pa_sink_input_send_event(pa_sink_input *i, const char *event, pa_proplist *data) {
+ pa_proplist *pl = NULL;
+ pa_sink_input_send_event_hook_data hook_data;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_ctl_context();
+ pa_assert(event);
+
+ if (!i->send_event)
+ return;
+
+ if (!data)
+ data = pl = pa_proplist_new();
+
+ hook_data.sink_input = i;
+ hook_data.data = data;
+ hook_data.event = event;
+
+ if (pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_SEND_EVENT], &hook_data) < 0)
+ goto finish;
+
+ i->send_event(i, event, data);
+
+finish:
+ if (pl)
+ pa_proplist_free(pl);
+}
+
+/* Called from main context */
+/* Updates the sink input's resampler with whatever the current sink requires
+ * -- useful when the underlying sink's sample spec might have changed */
+int pa_sink_input_update_resampler(pa_sink_input *i) {
+ pa_resampler *new_resampler;
+ char *memblockq_name;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_ctl_context();
+
+ if (i->thread_info.resampler &&
+ pa_sample_spec_equal(pa_resampler_output_sample_spec(i->thread_info.resampler), &i->sink->sample_spec) &&
+ pa_channel_map_equal(pa_resampler_output_channel_map(i->thread_info.resampler), &i->sink->channel_map))
+
+ new_resampler = i->thread_info.resampler;
+
+ else if (!pa_sink_input_is_passthrough(i) &&
+ ((i->flags & PA_SINK_INPUT_VARIABLE_RATE) ||
+ !pa_sample_spec_equal(&i->sample_spec, &i->sink->sample_spec) ||
+ !pa_channel_map_equal(&i->channel_map, &i->sink->channel_map))) {
+
+ new_resampler = pa_resampler_new(i->core->mempool,
+ &i->sample_spec, &i->channel_map,
+ &i->sink->sample_spec, &i->sink->channel_map,
+ i->core->lfe_crossover_freq,
+ i->requested_resample_method,
+ ((i->flags & PA_SINK_INPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0) |
+ ((i->flags & PA_SINK_INPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) |
+ (i->core->disable_remixing || (i->flags & PA_SINK_INPUT_NO_REMIX) ? PA_RESAMPLER_NO_REMIX : 0) |
+ (i->core->remixing_use_all_sink_channels ? 0 : PA_RESAMPLER_NO_FILL_SINK) |
+ (i->core->remixing_produce_lfe ? PA_RESAMPLER_PRODUCE_LFE : 0) |
+ (i->core->remixing_consume_lfe ? PA_RESAMPLER_CONSUME_LFE : 0));
+
+ if (!new_resampler) {
+ pa_log_warn("Unsupported resampling operation.");
+ return -PA_ERR_NOTSUPPORTED;
+ }
+ } else
+ new_resampler = NULL;
+
+ if (new_resampler == i->thread_info.resampler)
+ return 0;
+
+ if (i->thread_info.resampler)
+ pa_resampler_free(i->thread_info.resampler);
+
+ i->thread_info.resampler = new_resampler;
+
+ pa_memblockq_free(i->thread_info.render_memblockq);
+
+ memblockq_name = pa_sprintf_malloc("sink input render_memblockq [%u]", i->index);
+ i->thread_info.render_memblockq = pa_memblockq_new(
+ memblockq_name,
+ 0,
+ MEMBLOCKQ_MAXLENGTH,
+ 0,
+ &i->sink->sample_spec,
+ 0,
+ 1,
+ 0,
+ &i->sink->silence);
+ pa_xfree(memblockq_name);
+
+ i->actual_resample_method = new_resampler ? pa_resampler_get_method(new_resampler) : PA_RESAMPLER_INVALID;
+
+ pa_log_debug("Updated resampler for sink input %d", i->index);
+
+ return 0;
+}
+
+/* Called from the IO thread. */
+void pa_sink_input_attach(pa_sink_input *i) {
+ pa_assert(i);
+ pa_assert(!i->thread_info.attached);
+
+ i->thread_info.attached = true;
+
+ if (i->attach)
+ i->attach(i);
+}
+
+/* Called from the IO thread. */
+void pa_sink_input_detach(pa_sink_input *i) {
+ pa_assert(i);
+
+ if (!i->thread_info.attached)
+ return;
+
+ i->thread_info.attached = false;
+
+ if (i->detach)
+ i->detach(i);
+}
+
+/* Called from the main thread. */
+void pa_sink_input_set_volume_direct(pa_sink_input *i, const pa_cvolume *volume) {
+ pa_cvolume old_volume;
+ char old_volume_str[PA_CVOLUME_SNPRINT_VERBOSE_MAX];
+ char new_volume_str[PA_CVOLUME_SNPRINT_VERBOSE_MAX];
+
+ pa_assert(i);
+ pa_assert(volume);
+
+ old_volume = i->volume;
+
+ if (pa_cvolume_equal(volume, &old_volume))
+ return;
+
+ i->volume = *volume;
+ pa_log_debug("The volume of sink input %u changed from %s to %s.", i->index,
+ pa_cvolume_snprint_verbose(old_volume_str, sizeof(old_volume_str), &old_volume, &i->channel_map, true),
+ pa_cvolume_snprint_verbose(new_volume_str, sizeof(new_volume_str), volume, &i->channel_map, true));
+
+ if (i->volume_changed)
+ i->volume_changed(i);
+
+ pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
+ pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_VOLUME_CHANGED], i);
+}
+
+/* Called from the main thread. */
+void pa_sink_input_set_reference_ratio(pa_sink_input *i, const pa_cvolume *ratio) {
+ pa_cvolume old_ratio;
+ char old_ratio_str[PA_CVOLUME_SNPRINT_VERBOSE_MAX];
+ char new_ratio_str[PA_CVOLUME_SNPRINT_VERBOSE_MAX];
+
+ pa_assert(i);
+ pa_assert(ratio);
+
+ old_ratio = i->reference_ratio;
+
+ if (pa_cvolume_equal(ratio, &old_ratio))
+ return;
+
+ i->reference_ratio = *ratio;
+
+ if (!PA_SINK_INPUT_IS_LINKED(i->state))
+ return;
+
+ pa_log_debug("Sink input %u reference ratio changed from %s to %s.", i->index,
+ pa_cvolume_snprint_verbose(old_ratio_str, sizeof(old_ratio_str), &old_ratio, &i->channel_map, true),
+ pa_cvolume_snprint_verbose(new_ratio_str, sizeof(new_ratio_str), ratio, &i->channel_map, true));
+}
+
+/* Called from the main thread. */
+void pa_sink_input_set_preferred_sink(pa_sink_input *i, pa_sink *s) {
+ pa_assert(i);
+
+ pa_xfree(i->preferred_sink);
+ if (s) {
+ i->preferred_sink = pa_xstrdup(s->name);
+ pa_sink_input_move_to(i, s, false);
+ } else {
+ i->preferred_sink = NULL;
+ pa_sink_input_move_to(i, i->core->default_sink, false);
+ }
+}
diff --git a/src/pulsecore/sink-input.h b/src/pulsecore/sink-input.h
new file mode 100644
index 0000000..d3de6e3
--- /dev/null
+++ b/src/pulsecore/sink-input.h
@@ -0,0 +1,469 @@
+#ifndef foopulsesinkinputhfoo
+#define foopulsesinkinputhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <inttypes.h>
+
+#include <pulsecore/typedefs.h>
+#include <pulse/sample.h>
+#include <pulse/format.h>
+#include <pulsecore/memblockq.h>
+#include <pulsecore/resampler.h>
+#include <pulsecore/module.h>
+#include <pulsecore/client.h>
+#include <pulsecore/sink.h>
+#include <pulsecore/core.h>
+
+typedef enum pa_sink_input_state {
+ PA_SINK_INPUT_INIT, /*< The stream is not active yet, because pa_sink_input_put() has not been called yet */
+ PA_SINK_INPUT_RUNNING, /*< The stream is alive and kicking */
+ PA_SINK_INPUT_CORKED, /*< The stream was corked on user request */
+ PA_SINK_INPUT_UNLINKED /*< The stream is dead */
+ /* FIXME: we need a state for MOVING here */
+} pa_sink_input_state_t;
+
+static inline bool PA_SINK_INPUT_IS_LINKED(pa_sink_input_state_t x) {
+ return x == PA_SINK_INPUT_RUNNING || x == PA_SINK_INPUT_CORKED;
+}
+
+typedef enum pa_sink_input_flags {
+ PA_SINK_INPUT_VARIABLE_RATE = 1,
+ PA_SINK_INPUT_DONT_MOVE = 2,
+ PA_SINK_INPUT_START_CORKED = 4,
+ PA_SINK_INPUT_NO_REMAP = 8,
+ PA_SINK_INPUT_NO_REMIX = 16,
+ PA_SINK_INPUT_FIX_FORMAT = 32,
+ PA_SINK_INPUT_FIX_RATE = 64,
+ PA_SINK_INPUT_FIX_CHANNELS = 128,
+ PA_SINK_INPUT_DONT_INHIBIT_AUTO_SUSPEND = 256,
+ PA_SINK_INPUT_NO_CREATE_ON_SUSPEND = 512,
+ PA_SINK_INPUT_KILL_ON_SUSPEND = 1024,
+ PA_SINK_INPUT_PASSTHROUGH = 2048
+} pa_sink_input_flags_t;
+
+struct pa_sink_input {
+ pa_msgobject parent;
+
+ uint32_t index;
+ pa_core *core;
+
+ pa_sink_input_state_t state;
+ pa_sink_input_flags_t flags;
+
+ char *driver; /* may be NULL */
+ pa_proplist *proplist;
+
+ pa_module *module; /* may be NULL */
+ pa_client *client; /* may be NULL */
+
+ pa_sink *sink; /* NULL while we are being moved */
+
+ /* This is set to true when creating the sink input if the sink was
+ * requested by the application that created the sink input. This is
+ * sometimes useful for determining whether the sink input should be
+ * moved by some automatic policy. If the sink input is moved away from the
+ * sink that the application requested, this flag is reset to false. */
+ bool sink_requested_by_application;
+
+ pa_sink *origin_sink; /* only set by filter sinks */
+
+ /* A sink input may be connected to multiple source outputs
+ * directly, so that they don't get mixed data of the entire
+ * source. */
+ pa_idxset *direct_outputs;
+
+ pa_sample_spec sample_spec;
+ pa_channel_map channel_map;
+ pa_format_info *format;
+
+ pa_sink_input *sync_prev, *sync_next;
+
+ /* Also see http://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/Developer/Volumes/ */
+ pa_cvolume volume; /* The volume clients are informed about */
+ pa_cvolume reference_ratio; /* The ratio of the stream's volume to the sink's reference volume */
+ pa_cvolume real_ratio; /* The ratio of the stream's volume to the sink's real volume */
+ /* volume_factor is an internally used "additional volume" that can be used
+ * by modules without having the volume visible to clients. volume_factor
+ * calculated by merging all the individual items in volume_factor_items.
+ * Modules must not modify these variables directly, instead
+ * pa_sink_input_add/remove_volume_factor() have to be used to add and
+ * remove items, or pa_sink_input_new_data_add_volume_factor() during input
+ * creation time. */
+ pa_cvolume volume_factor;
+ pa_hashmap *volume_factor_items;
+ pa_cvolume soft_volume; /* The internal software volume we apply to all PCM data while it passes through. Usually calculated as real_ratio * volume_factor */
+
+ pa_cvolume volume_factor_sink; /* A second volume factor in format of the sink this stream is connected to. */
+ pa_hashmap *volume_factor_sink_items;
+
+ bool volume_writable:1;
+
+ bool muted:1;
+
+ /* if true then the volume and the mute state of this sink-input
+ * are worth remembering, module-stream-restore looks for
+ * this.*/
+ bool save_volume:1, save_muted:1;
+
+ /* if users move the sink-input to a sink, and the sink is not default_sink,
+ * the sink->name will be saved in preferred_sink. And later if sink-input
+ * is moved to other sinks for some reason, it still can be restored to the
+ * preferred_sink at an appropriate time */
+ char *preferred_sink;
+
+ pa_resample_method_t requested_resample_method, actual_resample_method;
+
+ /* Returns the chunk of audio data and drops it from the
+ * queue. Returns -1 on failure. Called from IO thread context. If
+ * data needs to be generated from scratch then please in the
+ * specified length request_nbytes. This is an optimization
+ * only. If less data is available, it's fine to return a smaller
+ * block. If more data is already ready, it is better to return
+ * the full block. */
+ int (*pop) (pa_sink_input *i, size_t request_nbytes, pa_memchunk *chunk); /* may NOT be NULL */
+
+ /* This is called when the playback buffer has actually played back
+ all available data. Return true unless there is more data to play back.
+ Called from IO context. */
+ bool (*process_underrun) (pa_sink_input *i);
+
+ /* Rewind the queue by the specified number of bytes. Called just
+ * before peek() if it is called at all. Only called if the sink
+ * input driver ever plans to call
+ * pa_sink_input_request_rewind(). Called from IO context. */
+ void (*process_rewind) (pa_sink_input *i, size_t nbytes); /* may NOT be NULL */
+
+ /* Called whenever the maximum rewindable size of the sink
+ * changes. Called from IO context. */
+ void (*update_max_rewind) (pa_sink_input *i, size_t nbytes); /* may be NULL */
+
+ /* Called whenever the maximum request size of the sink
+ * changes. Called from IO context. */
+ void (*update_max_request) (pa_sink_input *i, size_t nbytes); /* may be NULL */
+
+ /* Called whenever the configured latency of the sink
+ * changes. Called from IO context. */
+ void (*update_sink_requested_latency) (pa_sink_input *i); /* may be NULL */
+
+ /* Called whenever the latency range of the sink changes. Called
+ * from IO context. */
+ void (*update_sink_latency_range) (pa_sink_input *i); /* may be NULL */
+
+ /* Called whenever the fixed latency of the sink changes, if there
+ * is one. Called from IO context. */
+ void (*update_sink_fixed_latency) (pa_sink_input *i); /* may be NULL */
+
+ /* If non-NULL this function is called when the input is first
+ * connected to a sink or when the rtpoll/asyncmsgq fields
+ * change. You usually don't need to implement this function
+ * unless you rewrite a sink that is piggy-backed onto
+ * another. Called from IO thread context */
+ void (*attach) (pa_sink_input *i); /* may be NULL */
+
+ /* If non-NULL this function is called when the output is
+ * disconnected from its sink. Called from IO thread context */
+ void (*detach) (pa_sink_input *i); /* may be NULL */
+
+ /* If non-NULL called whenever the sink this input is attached
+ * to suspends or resumes or if the suspend cause changes.
+ * Called from main context */
+ void (*suspend) (pa_sink_input *i, pa_sink_state_t old_state, pa_suspend_cause_t old_suspend_cause); /* may be NULL */
+
+ /* If non-NULL called whenever the sink this input is attached
+ * to suspends or resumes. Called from IO context */
+ void (*suspend_within_thread) (pa_sink_input *i, bool b); /* may be NULL */
+
+ /* If non-NULL called whenever the sink input is moved to a new
+ * sink. Called from main context after the sink input has been
+ * detached from the old sink and before it has been attached to
+ * the new sink. If dest is NULL the move was executed in two
+ * phases and the second one failed; the stream will be destroyed
+ * after this call. */
+ void (*moving) (pa_sink_input *i, pa_sink *dest); /* may be NULL */
+
+ /* Supposed to unlink and destroy this stream. Called from main
+ * context. */
+ void (*kill) (pa_sink_input *i); /* may NOT be NULL */
+
+ /* Return the current latency (i.e. length of buffered audio) of
+ this stream. Called from main context. This is added to what the
+ PA_SINK_INPUT_MESSAGE_GET_LATENCY message sent to the IO thread
+ returns */
+ pa_usec_t (*get_latency) (pa_sink_input *i); /* may be NULL */
+
+ /* If non-NULL this function is called from thread context if the
+ * state changes. The old state is found in thread_info.state. */
+ void (*state_change) (pa_sink_input *i, pa_sink_input_state_t state); /* may be NULL */
+
+ /* If non-NULL this function is called before this sink input is
+ * move to a sink and if it returns false the move will not
+ * be allowed */
+ bool (*may_move_to) (pa_sink_input *i, pa_sink *s); /* may be NULL */
+
+ /* If non-NULL this function is used to dispatch asynchronous
+ * control events. Called from main context. */
+ void (*send_event)(pa_sink_input *i, const char *event, pa_proplist* data); /* may be NULL */
+
+ /* If non-NULL this function is called whenever the sink input
+ * volume changes. Called from main context */
+ void (*volume_changed)(pa_sink_input *i); /* may be NULL */
+
+ /* If non-NULL this function is called whenever the sink input
+ * mute status changes. Called from main context */
+ void (*mute_changed)(pa_sink_input *i); /* may be NULL */
+
+ struct {
+ pa_sink_input_state_t state;
+
+ pa_cvolume soft_volume;
+ bool muted:1;
+
+ bool attached:1; /* True only between ->attach() and ->detach() calls */
+
+ /* rewrite_nbytes: 0: rewrite nothing, (size_t) -1: rewrite everything, otherwise how many bytes to rewrite */
+ bool rewrite_flush:1, dont_rewind_render:1;
+ size_t rewrite_nbytes;
+ uint64_t underrun_for, playing_for;
+ uint64_t underrun_for_sink; /* Like underrun_for, but in sink sample spec */
+
+ pa_sample_spec sample_spec;
+
+ pa_resampler *resampler; /* may be NULL */
+
+ /* We maintain a history of resampled audio data here. */
+ pa_memblockq *render_memblockq;
+
+ pa_sink_input *sync_prev, *sync_next;
+
+ /* The requested latency for the sink */
+ pa_usec_t requested_sink_latency;
+
+ pa_hashmap *direct_outputs;
+ } thread_info;
+
+ void *userdata;
+};
+
+PA_DECLARE_PUBLIC_CLASS(pa_sink_input);
+#define PA_SINK_INPUT(o) pa_sink_input_cast(o)
+
+enum {
+ PA_SINK_INPUT_MESSAGE_SET_SOFT_VOLUME,
+ PA_SINK_INPUT_MESSAGE_SET_SOFT_MUTE,
+ PA_SINK_INPUT_MESSAGE_GET_LATENCY,
+ PA_SINK_INPUT_MESSAGE_SET_RATE,
+ PA_SINK_INPUT_MESSAGE_SET_STATE,
+ PA_SINK_INPUT_MESSAGE_SET_REQUESTED_LATENCY,
+ PA_SINK_INPUT_MESSAGE_GET_REQUESTED_LATENCY,
+ PA_SINK_INPUT_MESSAGE_MAX
+};
+
+typedef struct pa_sink_input_send_event_hook_data {
+ pa_sink_input *sink_input;
+ const char *event;
+ pa_proplist *data;
+} pa_sink_input_send_event_hook_data;
+
+typedef struct pa_sink_input_new_data {
+ pa_sink_input_flags_t flags;
+
+ pa_proplist *proplist;
+
+ const char *driver;
+ pa_module *module;
+ pa_client *client;
+
+ pa_sink *sink;
+ bool sink_requested_by_application;
+ pa_sink *origin_sink;
+
+ pa_resample_method_t resample_method;
+
+ pa_sink_input *sync_base;
+
+ pa_sample_spec sample_spec;
+ pa_channel_map channel_map;
+ pa_format_info *format;
+ pa_idxset *req_formats;
+ pa_idxset *nego_formats;
+
+ pa_cvolume volume;
+ bool muted:1;
+ pa_hashmap *volume_factor_items, *volume_factor_sink_items;
+
+ bool sample_spec_is_set:1;
+ bool channel_map_is_set:1;
+
+ bool volume_is_set:1;
+ bool muted_is_set:1;
+
+ bool volume_is_absolute:1;
+
+ bool volume_writable:1;
+
+ bool save_volume:1, save_muted:1;
+
+ char *preferred_sink;
+} pa_sink_input_new_data;
+
+pa_sink_input_new_data* pa_sink_input_new_data_init(pa_sink_input_new_data *data);
+void pa_sink_input_new_data_set_sample_spec(pa_sink_input_new_data *data, const pa_sample_spec *spec);
+void pa_sink_input_new_data_set_channel_map(pa_sink_input_new_data *data, const pa_channel_map *map);
+bool pa_sink_input_new_data_is_passthrough(pa_sink_input_new_data *data);
+void pa_sink_input_new_data_set_volume(pa_sink_input_new_data *data, const pa_cvolume *volume);
+void pa_sink_input_new_data_add_volume_factor(pa_sink_input_new_data *data, const char *key, const pa_cvolume *volume_factor);
+void pa_sink_input_new_data_add_volume_factor_sink(pa_sink_input_new_data *data, const char *key, const pa_cvolume *volume_factor);
+void pa_sink_input_new_data_set_muted(pa_sink_input_new_data *data, bool mute);
+bool pa_sink_input_new_data_set_sink(pa_sink_input_new_data *data, pa_sink *s, bool save, bool requested_by_application);
+bool pa_sink_input_new_data_set_formats(pa_sink_input_new_data *data, pa_idxset *formats);
+void pa_sink_input_new_data_done(pa_sink_input_new_data *data);
+
+/* To be called by the implementing module only */
+
+int pa_sink_input_new(
+ pa_sink_input **i,
+ pa_core *core,
+ pa_sink_input_new_data *data);
+
+void pa_sink_input_put(pa_sink_input *i);
+void pa_sink_input_unlink(pa_sink_input* i);
+
+pa_usec_t pa_sink_input_set_requested_latency(pa_sink_input *i, pa_usec_t usec);
+
+/* Request that the specified number of bytes already written out to
+the hw device is rewritten, if possible. Please note that this is
+only a kind request. The sink driver may not be able to fulfill it
+fully -- or at all. If the request for a rewrite was successful, the
+sink driver will call ->rewind() and pass the number of bytes that
+could be rewound in the HW device. This functionality is required for
+implementing the "zero latency" write-through functionality. */
+void pa_sink_input_request_rewind(pa_sink_input *i, size_t nbytes, bool rewrite, bool flush, bool dont_rewind_render);
+
+void pa_sink_input_cork(pa_sink_input *i, bool b);
+
+int pa_sink_input_set_rate(pa_sink_input *i, uint32_t rate);
+int pa_sink_input_update_resampler(pa_sink_input *i);
+
+/* This returns the sink's fields converted into out sample type */
+size_t pa_sink_input_get_max_rewind(pa_sink_input *i);
+size_t pa_sink_input_get_max_request(pa_sink_input *i);
+
+/* Callable by everyone from main thread*/
+
+/* External code may request disconnection with this function */
+void pa_sink_input_kill(pa_sink_input*i);
+
+pa_usec_t pa_sink_input_get_latency(pa_sink_input *i, pa_usec_t *sink_latency);
+
+bool pa_sink_input_is_passthrough(pa_sink_input *i);
+bool pa_sink_input_is_volume_readable(pa_sink_input *i);
+void pa_sink_input_set_volume(pa_sink_input *i, const pa_cvolume *volume, bool save, bool absolute);
+void pa_sink_input_add_volume_factor(pa_sink_input *i, const char *key, const pa_cvolume *volume_factor);
+int pa_sink_input_remove_volume_factor(pa_sink_input *i, const char *key);
+pa_cvolume *pa_sink_input_get_volume(pa_sink_input *i, pa_cvolume *volume, bool absolute);
+
+void pa_sink_input_set_mute(pa_sink_input *i, bool mute, bool save);
+
+void pa_sink_input_set_property(pa_sink_input *i, const char *key, const char *value);
+void pa_sink_input_set_property_arbitrary(pa_sink_input *i, const char *key, const uint8_t *value, size_t nbytes);
+void pa_sink_input_update_proplist(pa_sink_input *i, pa_update_mode_t mode, pa_proplist *p);
+
+pa_resample_method_t pa_sink_input_get_resample_method(pa_sink_input *i);
+
+void pa_sink_input_send_event(pa_sink_input *i, const char *name, pa_proplist *data);
+
+int pa_sink_input_move_to(pa_sink_input *i, pa_sink *dest, bool save);
+bool pa_sink_input_may_move(pa_sink_input *i); /* may this sink input move at all? */
+bool pa_sink_input_may_move_to(pa_sink_input *i, pa_sink *dest); /* may this sink input move to this sink? */
+
+/* The same as pa_sink_input_move_to() but in two separate steps,
+ * first the detaching from the old sink, then the attaching to the
+ * new sink */
+int pa_sink_input_start_move(pa_sink_input *i);
+int pa_sink_input_finish_move(pa_sink_input *i, pa_sink *dest, bool save);
+void pa_sink_input_fail_move(pa_sink_input *i);
+
+pa_usec_t pa_sink_input_get_requested_latency(pa_sink_input *i);
+
+/* To be used exclusively by the sink driver IO thread */
+
+void pa_sink_input_peek(pa_sink_input *i, size_t length, pa_memchunk *chunk, pa_cvolume *volume);
+void pa_sink_input_drop(pa_sink_input *i, size_t length);
+void pa_sink_input_process_rewind(pa_sink_input *i, size_t nbytes /* in the sink's sample spec */);
+void pa_sink_input_update_max_rewind(pa_sink_input *i, size_t nbytes /* in the sink's sample spec */);
+void pa_sink_input_update_max_request(pa_sink_input *i, size_t nbytes /* in the sink's sample spec */);
+
+void pa_sink_input_set_state_within_thread(pa_sink_input *i, pa_sink_input_state_t state);
+
+int pa_sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk);
+
+pa_usec_t pa_sink_input_set_requested_latency_within_thread(pa_sink_input *i, pa_usec_t usec);
+
+bool pa_sink_input_safe_to_remove(pa_sink_input *i);
+bool pa_sink_input_process_underrun(pa_sink_input *i);
+
+pa_memchunk* pa_sink_input_get_silence(pa_sink_input *i, pa_memchunk *ret);
+
+/* Calls the attach() callback if it's set. The input must be in detached
+ * state. */
+void pa_sink_input_attach(pa_sink_input *i);
+
+/* Calls the detach() callback if it's set and the input is attached. The input
+ * is allowed to be already detached, in which case this does nothing.
+ *
+ * The reason why this can be called for already-detached inputs is that when
+ * a filter sink's input is detached, it has to detach also all inputs
+ * connected to the filter sink. In case the filter sink's input was detached
+ * because the filter sink is being removed, those other inputs will be moved
+ * to another sink or removed, and moving and removing involve detaching the
+ * inputs, but the inputs at that point are already detached.
+ *
+ * XXX: Moving or removing an input also involves sending messages to the
+ * input's sink. If the input's sink is a detached filter sink, shouldn't
+ * sending messages to it be prohibited? The messages are processed in the
+ * root sink's IO thread, and when the filter sink is detached, it would seem
+ * logical to prohibit any interaction with the IO thread that isn't any more
+ * associated with the filter sink. Currently sending messages to detached
+ * filter sinks mostly works, because the filter sinks don't update their
+ * asyncmsgq pointer when detaching, so messages still find their way to the
+ * old IO thread. */
+void pa_sink_input_detach(pa_sink_input *i);
+
+/* Called from the main thread, from sink.c only. The normal way to set the
+ * sink input volume is to call pa_sink_input_set_volume(), but the flat volume
+ * logic in sink.c needs also a function that doesn't do all the extra stuff
+ * that pa_sink_input_set_volume() does. This function simply sets i->volume
+ * and fires change notifications. */
+void pa_sink_input_set_volume_direct(pa_sink_input *i, const pa_cvolume *volume);
+
+/* Called from the main thread, from sink.c only. This shouldn't be a public
+ * function, but the flat volume logic in sink.c currently needs a way to
+ * directly set the sink input reference ratio. This function simply sets
+ * i->reference_ratio and logs a message if the value changes. */
+void pa_sink_input_set_reference_ratio(pa_sink_input *i, const pa_cvolume *ratio);
+
+void pa_sink_input_set_preferred_sink(pa_sink_input *i, pa_sink *s);
+
+#define pa_sink_input_assert_io_context(s) \
+ pa_assert(pa_thread_mq_get() || !PA_SINK_INPUT_IS_LINKED((s)->state))
+
+#endif
diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c
new file mode 100644
index 0000000..e89b596
--- /dev/null
+++ b/src/pulsecore/sink.c
@@ -0,0 +1,3996 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <pulse/introspect.h>
+#include <pulse/format.h>
+#include <pulse/utf8.h>
+#include <pulse/xmalloc.h>
+#include <pulse/timeval.h>
+#include <pulse/util.h>
+#include <pulse/rtclock.h>
+#include <pulse/internal.h>
+
+#include <pulsecore/i18n.h>
+#include <pulsecore/sink-input.h>
+#include <pulsecore/namereg.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/sample-util.h>
+#include <pulsecore/mix.h>
+#include <pulsecore/core-subscribe.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/play-memblockq.h>
+#include <pulsecore/flist.h>
+
+#include "sink.h"
+
+#define MAX_MIX_CHANNELS 32
+#define MIX_BUFFER_LENGTH (pa_page_size())
+#define ABSOLUTE_MIN_LATENCY (500)
+#define ABSOLUTE_MAX_LATENCY (10*PA_USEC_PER_SEC)
+#define DEFAULT_FIXED_LATENCY (250*PA_USEC_PER_MSEC)
+
+PA_DEFINE_PUBLIC_CLASS(pa_sink, pa_msgobject);
+
+struct pa_sink_volume_change {
+ pa_usec_t at;
+ pa_cvolume hw_volume;
+
+ PA_LLIST_FIELDS(pa_sink_volume_change);
+};
+
+struct set_state_data {
+ pa_sink_state_t state;
+ pa_suspend_cause_t suspend_cause;
+};
+
+static void sink_free(pa_object *s);
+
+static void pa_sink_volume_change_push(pa_sink *s);
+static void pa_sink_volume_change_flush(pa_sink *s);
+static void pa_sink_volume_change_rewind(pa_sink *s, size_t nbytes);
+
+pa_sink_new_data* pa_sink_new_data_init(pa_sink_new_data *data) {
+ pa_assert(data);
+
+ pa_zero(*data);
+ data->proplist = pa_proplist_new();
+ data->ports = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, (pa_free_cb_t) pa_device_port_unref);
+
+ return data;
+}
+
+void pa_sink_new_data_set_name(pa_sink_new_data *data, const char *name) {
+ pa_assert(data);
+
+ pa_xfree(data->name);
+ data->name = pa_xstrdup(name);
+}
+
+void pa_sink_new_data_set_sample_spec(pa_sink_new_data *data, const pa_sample_spec *spec) {
+ pa_assert(data);
+
+ if ((data->sample_spec_is_set = !!spec))
+ data->sample_spec = *spec;
+}
+
+void pa_sink_new_data_set_channel_map(pa_sink_new_data *data, const pa_channel_map *map) {
+ pa_assert(data);
+
+ if ((data->channel_map_is_set = !!map))
+ data->channel_map = *map;
+}
+
+void pa_sink_new_data_set_alternate_sample_rate(pa_sink_new_data *data, const uint32_t alternate_sample_rate) {
+ pa_assert(data);
+
+ data->alternate_sample_rate_is_set = true;
+ data->alternate_sample_rate = alternate_sample_rate;
+}
+
+void pa_sink_new_data_set_avoid_resampling(pa_sink_new_data *data, bool avoid_resampling) {
+ pa_assert(data);
+
+ data->avoid_resampling_is_set = true;
+ data->avoid_resampling = avoid_resampling;
+}
+
+void pa_sink_new_data_set_volume(pa_sink_new_data *data, const pa_cvolume *volume) {
+ pa_assert(data);
+
+ if ((data->volume_is_set = !!volume))
+ data->volume = *volume;
+}
+
+void pa_sink_new_data_set_muted(pa_sink_new_data *data, bool mute) {
+ pa_assert(data);
+
+ data->muted_is_set = true;
+ data->muted = mute;
+}
+
+void pa_sink_new_data_set_port(pa_sink_new_data *data, const char *port) {
+ pa_assert(data);
+
+ pa_xfree(data->active_port);
+ data->active_port = pa_xstrdup(port);
+}
+
+void pa_sink_new_data_done(pa_sink_new_data *data) {
+ pa_assert(data);
+
+ pa_proplist_free(data->proplist);
+
+ if (data->ports)
+ pa_hashmap_free(data->ports);
+
+ pa_xfree(data->name);
+ pa_xfree(data->active_port);
+}
+
+/* Called from main context */
+static void reset_callbacks(pa_sink *s) {
+ pa_assert(s);
+
+ s->set_state_in_main_thread = NULL;
+ s->set_state_in_io_thread = NULL;
+ s->get_volume = NULL;
+ s->set_volume = NULL;
+ s->write_volume = NULL;
+ s->get_mute = NULL;
+ s->set_mute = NULL;
+ s->request_rewind = NULL;
+ s->update_requested_latency = NULL;
+ s->set_port = NULL;
+ s->get_formats = NULL;
+ s->set_formats = NULL;
+ s->reconfigure = NULL;
+}
+
+/* Called from main context */
+pa_sink* pa_sink_new(
+ pa_core *core,
+ pa_sink_new_data *data,
+ pa_sink_flags_t flags) {
+
+ pa_sink *s;
+ const char *name;
+ char st[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX];
+ pa_source_new_data source_data;
+ const char *dn;
+ char *pt;
+
+ pa_assert(core);
+ pa_assert(data);
+ pa_assert(data->name);
+ pa_assert_ctl_context();
+
+ s = pa_msgobject_new(pa_sink);
+
+ if (!(name = pa_namereg_register(core, data->name, PA_NAMEREG_SINK, s, data->namereg_fail))) {
+ pa_log_debug("Failed to register name %s.", data->name);
+ pa_xfree(s);
+ return NULL;
+ }
+
+ pa_sink_new_data_set_name(data, name);
+
+ if (pa_hook_fire(&core->hooks[PA_CORE_HOOK_SINK_NEW], data) < 0) {
+ pa_xfree(s);
+ pa_namereg_unregister(core, name);
+ return NULL;
+ }
+
+ /* FIXME, need to free s here on failure */
+
+ pa_return_null_if_fail(!data->driver || pa_utf8_valid(data->driver));
+ pa_return_null_if_fail(data->name && pa_utf8_valid(data->name) && data->name[0]);
+
+ pa_return_null_if_fail(data->sample_spec_is_set && pa_sample_spec_valid(&data->sample_spec));
+
+ if (!data->channel_map_is_set)
+ pa_return_null_if_fail(pa_channel_map_init_auto(&data->channel_map, data->sample_spec.channels, PA_CHANNEL_MAP_DEFAULT));
+
+ pa_return_null_if_fail(pa_channel_map_valid(&data->channel_map));
+ pa_return_null_if_fail(data->channel_map.channels == data->sample_spec.channels);
+
+ /* FIXME: There should probably be a general function for checking whether
+ * the sink volume is allowed to be set, like there is for sink inputs. */
+ pa_assert(!data->volume_is_set || !(flags & PA_SINK_SHARE_VOLUME_WITH_MASTER));
+
+ if (!data->volume_is_set) {
+ pa_cvolume_reset(&data->volume, data->sample_spec.channels);
+ data->save_volume = false;
+ }
+
+ pa_return_null_if_fail(pa_cvolume_valid(&data->volume));
+ pa_return_null_if_fail(pa_cvolume_compatible(&data->volume, &data->sample_spec));
+
+ if (!data->muted_is_set)
+ data->muted = false;
+
+ if (data->card)
+ pa_proplist_update(data->proplist, PA_UPDATE_MERGE, data->card->proplist);
+
+ pa_device_init_description(data->proplist, data->card);
+ pa_device_init_icon(data->proplist, true);
+ pa_device_init_intended_roles(data->proplist);
+
+ if (!data->active_port) {
+ pa_device_port *p = pa_device_port_find_best(data->ports);
+ if (p)
+ pa_sink_new_data_set_port(data, p->name);
+ }
+
+ if (pa_hook_fire(&core->hooks[PA_CORE_HOOK_SINK_FIXATE], data) < 0) {
+ pa_xfree(s);
+ pa_namereg_unregister(core, name);
+ return NULL;
+ }
+
+ s->parent.parent.free = sink_free;
+ s->parent.process_msg = pa_sink_process_msg;
+
+ s->core = core;
+ s->state = PA_SINK_INIT;
+ s->flags = flags;
+ s->priority = 0;
+ s->suspend_cause = data->suspend_cause;
+ s->name = pa_xstrdup(name);
+ s->proplist = pa_proplist_copy(data->proplist);
+ s->driver = pa_xstrdup(pa_path_get_filename(data->driver));
+ s->module = data->module;
+ s->card = data->card;
+
+ s->priority = pa_device_init_priority(s->proplist);
+
+ s->sample_spec = data->sample_spec;
+ s->channel_map = data->channel_map;
+ s->default_sample_rate = s->sample_spec.rate;
+
+ if (data->alternate_sample_rate_is_set)
+ s->alternate_sample_rate = data->alternate_sample_rate;
+ else
+ s->alternate_sample_rate = s->core->alternate_sample_rate;
+
+ if (data->avoid_resampling_is_set)
+ s->avoid_resampling = data->avoid_resampling;
+ else
+ s->avoid_resampling = s->core->avoid_resampling;
+
+ s->inputs = pa_idxset_new(NULL, NULL);
+ s->n_corked = 0;
+ s->input_to_master = NULL;
+
+ s->reference_volume = s->real_volume = data->volume;
+ pa_cvolume_reset(&s->soft_volume, s->sample_spec.channels);
+ s->base_volume = PA_VOLUME_NORM;
+ s->n_volume_steps = PA_VOLUME_NORM+1;
+ s->muted = data->muted;
+ s->refresh_volume = s->refresh_muted = false;
+
+ reset_callbacks(s);
+ s->userdata = NULL;
+
+ s->asyncmsgq = NULL;
+
+ /* As a minor optimization we just steal the list instead of
+ * copying it here */
+ s->ports = data->ports;
+ data->ports = NULL;
+
+ s->active_port = NULL;
+ s->save_port = false;
+
+ if (data->active_port)
+ if ((s->active_port = pa_hashmap_get(s->ports, data->active_port)))
+ s->save_port = data->save_port;
+
+ /* Hopefully the active port has already been assigned in the previous call
+ to pa_device_port_find_best, but better safe than sorry */
+ if (!s->active_port)
+ s->active_port = pa_device_port_find_best(s->ports);
+
+ if (s->active_port)
+ s->port_latency_offset = s->active_port->latency_offset;
+ else
+ s->port_latency_offset = 0;
+
+ s->save_volume = data->save_volume;
+ s->save_muted = data->save_muted;
+
+ pa_silence_memchunk_get(
+ &core->silence_cache,
+ core->mempool,
+ &s->silence,
+ &s->sample_spec,
+ 0);
+
+ s->thread_info.rtpoll = NULL;
+ s->thread_info.inputs = pa_hashmap_new_full(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func, NULL,
+ (pa_free_cb_t) pa_sink_input_unref);
+ s->thread_info.soft_volume = s->soft_volume;
+ s->thread_info.soft_muted = s->muted;
+ s->thread_info.state = s->state;
+ s->thread_info.rewind_nbytes = 0;
+ s->thread_info.rewind_requested = false;
+ s->thread_info.max_rewind = 0;
+ s->thread_info.max_request = 0;
+ s->thread_info.requested_latency_valid = false;
+ s->thread_info.requested_latency = 0;
+ s->thread_info.min_latency = ABSOLUTE_MIN_LATENCY;
+ s->thread_info.max_latency = ABSOLUTE_MAX_LATENCY;
+ s->thread_info.fixed_latency = flags & PA_SINK_DYNAMIC_LATENCY ? 0 : DEFAULT_FIXED_LATENCY;
+
+ PA_LLIST_HEAD_INIT(pa_sink_volume_change, s->thread_info.volume_changes);
+ s->thread_info.volume_changes_tail = NULL;
+ pa_sw_cvolume_divide(&s->thread_info.current_hw_volume, &s->real_volume, &s->soft_volume);
+ s->thread_info.volume_change_safety_margin = core->deferred_volume_safety_margin_usec;
+ s->thread_info.volume_change_extra_delay = core->deferred_volume_extra_delay_usec;
+ s->thread_info.port_latency_offset = s->port_latency_offset;
+
+ /* FIXME: This should probably be moved to pa_sink_put() */
+ pa_assert_se(pa_idxset_put(core->sinks, s, &s->index) >= 0);
+
+ if (s->card)
+ pa_assert_se(pa_idxset_put(s->card->sinks, s, NULL) >= 0);
+
+ pt = pa_proplist_to_string_sep(s->proplist, "\n ");
+ pa_log_info("Created sink %u \"%s\" with sample spec %s and channel map %s\n %s",
+ s->index,
+ s->name,
+ pa_sample_spec_snprint(st, sizeof(st), &s->sample_spec),
+ pa_channel_map_snprint(cm, sizeof(cm), &s->channel_map),
+ pt);
+ pa_xfree(pt);
+
+ pa_source_new_data_init(&source_data);
+ pa_source_new_data_set_sample_spec(&source_data, &s->sample_spec);
+ pa_source_new_data_set_channel_map(&source_data, &s->channel_map);
+ pa_source_new_data_set_alternate_sample_rate(&source_data, s->alternate_sample_rate);
+ pa_source_new_data_set_avoid_resampling(&source_data, s->avoid_resampling);
+ source_data.name = pa_sprintf_malloc("%s.monitor", name);
+ source_data.driver = data->driver;
+ source_data.module = data->module;
+ source_data.card = data->card;
+
+ dn = pa_proplist_gets(s->proplist, PA_PROP_DEVICE_DESCRIPTION);
+ pa_proplist_setf(source_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Monitor of %s", dn ? dn : s->name);
+ pa_proplist_sets(source_data.proplist, PA_PROP_DEVICE_CLASS, "monitor");
+
+ s->monitor_source = pa_source_new(core, &source_data,
+ ((flags & PA_SINK_LATENCY) ? PA_SOURCE_LATENCY : 0) |
+ ((flags & PA_SINK_DYNAMIC_LATENCY) ? PA_SOURCE_DYNAMIC_LATENCY : 0));
+
+ pa_source_new_data_done(&source_data);
+
+ if (!s->monitor_source) {
+ pa_sink_unlink(s);
+ pa_sink_unref(s);
+ return NULL;
+ }
+
+ s->monitor_source->monitor_of = s;
+
+ pa_source_set_latency_range(s->monitor_source, s->thread_info.min_latency, s->thread_info.max_latency);
+ pa_source_set_fixed_latency(s->monitor_source, s->thread_info.fixed_latency);
+ pa_source_set_max_rewind(s->monitor_source, s->thread_info.max_rewind);
+
+ return s;
+}
+
+/* Called from main context */
+static int sink_set_state(pa_sink *s, pa_sink_state_t state, pa_suspend_cause_t suspend_cause) {
+ int ret = 0;
+ bool state_changed;
+ bool suspend_cause_changed;
+ bool suspending;
+ bool resuming;
+ pa_sink_state_t old_state;
+ pa_suspend_cause_t old_suspend_cause;
+
+ pa_assert(s);
+ pa_assert_ctl_context();
+
+ state_changed = state != s->state;
+ suspend_cause_changed = suspend_cause != s->suspend_cause;
+
+ if (!state_changed && !suspend_cause_changed)
+ return 0;
+
+ suspending = PA_SINK_IS_OPENED(s->state) && state == PA_SINK_SUSPENDED;
+ resuming = s->state == PA_SINK_SUSPENDED && PA_SINK_IS_OPENED(state);
+
+ /* If we are resuming, suspend_cause must be 0. */
+ pa_assert(!resuming || !suspend_cause);
+
+ /* Here's something to think about: what to do with the suspend cause if
+ * resuming the sink fails? The old suspend cause will be incorrect, so we
+ * can't use that. On the other hand, if we set no suspend cause (as is the
+ * case currently), then it looks strange to have a sink suspended without
+ * any cause. It might be a good idea to add a new "resume failed" suspend
+ * cause, or it might just add unnecessary complexity, given that the
+ * current approach of not setting any suspend cause works well enough. */
+
+ if (s->set_state_in_main_thread) {
+ if ((ret = s->set_state_in_main_thread(s, state, suspend_cause)) < 0) {
+ /* set_state_in_main_thread() is allowed to fail only when resuming. */
+ pa_assert(resuming);
+
+ /* If resuming fails, we set the state to SUSPENDED and
+ * suspend_cause to 0. */
+ state = PA_SINK_SUSPENDED;
+ suspend_cause = 0;
+ state_changed = false;
+ suspend_cause_changed = suspend_cause != s->suspend_cause;
+ resuming = false;
+
+ /* We know the state isn't changing. If the suspend cause isn't
+ * changing either, then there's nothing more to do. */
+ if (!suspend_cause_changed)
+ return ret;
+ }
+ }
+
+ if (s->asyncmsgq) {
+ struct set_state_data data = { .state = state, .suspend_cause = suspend_cause };
+
+ if ((ret = pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_STATE, &data, 0, NULL)) < 0) {
+ /* SET_STATE is allowed to fail only when resuming. */
+ pa_assert(resuming);
+
+ if (s->set_state_in_main_thread)
+ s->set_state_in_main_thread(s, PA_SINK_SUSPENDED, 0);
+
+ /* If resuming fails, we set the state to SUSPENDED and
+ * suspend_cause to 0. */
+ state = PA_SINK_SUSPENDED;
+ suspend_cause = 0;
+ state_changed = false;
+ suspend_cause_changed = suspend_cause != s->suspend_cause;
+ resuming = false;
+
+ /* We know the state isn't changing. If the suspend cause isn't
+ * changing either, then there's nothing more to do. */
+ if (!suspend_cause_changed)
+ return ret;
+ }
+ }
+
+ old_suspend_cause = s->suspend_cause;
+ if (suspend_cause_changed) {
+ char old_cause_buf[PA_SUSPEND_CAUSE_TO_STRING_BUF_SIZE];
+ char new_cause_buf[PA_SUSPEND_CAUSE_TO_STRING_BUF_SIZE];
+
+ pa_log_debug("%s: suspend_cause: %s -> %s", s->name, pa_suspend_cause_to_string(s->suspend_cause, old_cause_buf),
+ pa_suspend_cause_to_string(suspend_cause, new_cause_buf));
+ s->suspend_cause = suspend_cause;
+ }
+
+ old_state = s->state;
+ if (state_changed) {
+ pa_log_debug("%s: state: %s -> %s", s->name, pa_sink_state_to_string(s->state), pa_sink_state_to_string(state));
+ s->state = state;
+
+ /* If we enter UNLINKED state, then we don't send change notifications.
+ * pa_sink_unlink() will send unlink notifications instead. */
+ if (state != PA_SINK_UNLINKED) {
+ pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_STATE_CHANGED], s);
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK | PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+ }
+ }
+
+ if (suspending || resuming || suspend_cause_changed) {
+ pa_sink_input *i;
+ uint32_t idx;
+
+ /* We're suspending or resuming, tell everyone about it */
+
+ PA_IDXSET_FOREACH(i, s->inputs, idx)
+ if (s->state == PA_SINK_SUSPENDED &&
+ (i->flags & PA_SINK_INPUT_KILL_ON_SUSPEND))
+ pa_sink_input_kill(i);
+ else if (i->suspend)
+ i->suspend(i, old_state, old_suspend_cause);
+ }
+
+ if ((suspending || resuming || suspend_cause_changed) && s->monitor_source && state != PA_SINK_UNLINKED)
+ pa_source_sync_suspend(s->monitor_source);
+
+ return ret;
+}
+
+void pa_sink_set_get_volume_callback(pa_sink *s, pa_sink_cb_t cb) {
+ pa_assert(s);
+
+ s->get_volume = cb;
+}
+
+void pa_sink_set_set_volume_callback(pa_sink *s, pa_sink_cb_t cb) {
+ pa_sink_flags_t flags;
+
+ pa_assert(s);
+ pa_assert(!s->write_volume || cb);
+
+ s->set_volume = cb;
+
+ /* Save the current flags so we can tell if they've changed */
+ flags = s->flags;
+
+ if (cb) {
+ /* The sink implementor is responsible for setting decibel volume support */
+ s->flags |= PA_SINK_HW_VOLUME_CTRL;
+ } else {
+ s->flags &= ~PA_SINK_HW_VOLUME_CTRL;
+ /* See note below in pa_sink_put() about volume sharing and decibel volumes */
+ pa_sink_enable_decibel_volume(s, !(s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER));
+ }
+
+ /* If the flags have changed after init, let any clients know via a change event */
+ if (s->state != PA_SINK_INIT && flags != s->flags)
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+}
+
+void pa_sink_set_write_volume_callback(pa_sink *s, pa_sink_cb_t cb) {
+ pa_sink_flags_t flags;
+
+ pa_assert(s);
+ pa_assert(!cb || s->set_volume);
+
+ s->write_volume = cb;
+
+ /* Save the current flags so we can tell if they've changed */
+ flags = s->flags;
+
+ if (cb)
+ s->flags |= PA_SINK_DEFERRED_VOLUME;
+ else
+ s->flags &= ~PA_SINK_DEFERRED_VOLUME;
+
+ /* If the flags have changed after init, let any clients know via a change event */
+ if (s->state != PA_SINK_INIT && flags != s->flags)
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+}
+
+void pa_sink_set_get_mute_callback(pa_sink *s, pa_sink_get_mute_cb_t cb) {
+ pa_assert(s);
+
+ s->get_mute = cb;
+}
+
+void pa_sink_set_set_mute_callback(pa_sink *s, pa_sink_cb_t cb) {
+ pa_sink_flags_t flags;
+
+ pa_assert(s);
+
+ s->set_mute = cb;
+
+ /* Save the current flags so we can tell if they've changed */
+ flags = s->flags;
+
+ if (cb)
+ s->flags |= PA_SINK_HW_MUTE_CTRL;
+ else
+ s->flags &= ~PA_SINK_HW_MUTE_CTRL;
+
+ /* If the flags have changed after init, let any clients know via a change event */
+ if (s->state != PA_SINK_INIT && flags != s->flags)
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+}
+
+static void enable_flat_volume(pa_sink *s, bool enable) {
+ pa_sink_flags_t flags;
+
+ pa_assert(s);
+
+ /* Always follow the overall user preference here */
+ enable = enable && s->core->flat_volumes;
+
+ /* Save the current flags so we can tell if they've changed */
+ flags = s->flags;
+
+ if (enable)
+ s->flags |= PA_SINK_FLAT_VOLUME;
+ else
+ s->flags &= ~PA_SINK_FLAT_VOLUME;
+
+ /* If the flags have changed after init, let any clients know via a change event */
+ if (s->state != PA_SINK_INIT && flags != s->flags)
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+}
+
+void pa_sink_enable_decibel_volume(pa_sink *s, bool enable) {
+ pa_sink_flags_t flags;
+
+ pa_assert(s);
+
+ /* Save the current flags so we can tell if they've changed */
+ flags = s->flags;
+
+ if (enable) {
+ s->flags |= PA_SINK_DECIBEL_VOLUME;
+ enable_flat_volume(s, true);
+ } else {
+ s->flags &= ~PA_SINK_DECIBEL_VOLUME;
+ enable_flat_volume(s, false);
+ }
+
+ /* If the flags have changed after init, let any clients know via a change event */
+ if (s->state != PA_SINK_INIT && flags != s->flags)
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+}
+
+/* Called from main context */
+void pa_sink_put(pa_sink* s) {
+ pa_sink_assert_ref(s);
+ pa_assert_ctl_context();
+
+ pa_assert(s->state == PA_SINK_INIT);
+ pa_assert(!(s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER) || pa_sink_is_filter(s));
+
+ /* The following fields must be initialized properly when calling _put() */
+ pa_assert(s->asyncmsgq);
+ pa_assert(s->thread_info.min_latency <= s->thread_info.max_latency);
+
+ /* Generally, flags should be initialized via pa_sink_new(). As a
+ * special exception we allow some volume related flags to be set
+ * between _new() and _put() by the callback setter functions above.
+ *
+ * Thus we implement a couple safeguards here which ensure the above
+ * setters were used (or at least the implementor made manual changes
+ * in a compatible way).
+ *
+ * Note: All of these flags set here can change over the life time
+ * of the sink. */
+ pa_assert(!(s->flags & PA_SINK_HW_VOLUME_CTRL) || s->set_volume);
+ pa_assert(!(s->flags & PA_SINK_DEFERRED_VOLUME) || s->write_volume);
+ pa_assert(!(s->flags & PA_SINK_HW_MUTE_CTRL) || s->set_mute);
+
+ /* XXX: Currently decibel volume is disabled for all sinks that use volume
+ * sharing. When the master sink supports decibel volume, it would be good
+ * to have the flag also in the filter sink, but currently we don't do that
+ * so that the flags of the filter sink never change when it's moved from
+ * a master sink to another. One solution for this problem would be to
+ * remove user-visible volume altogether from filter sinks when volume
+ * sharing is used, but the current approach was easier to implement... */
+ /* We always support decibel volumes in software, otherwise we leave it to
+ * the sink implementor to set this flag as needed.
+ *
+ * Note: This flag can also change over the life time of the sink. */
+ if (!(s->flags & PA_SINK_HW_VOLUME_CTRL) && !(s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)) {
+ pa_sink_enable_decibel_volume(s, true);
+ s->soft_volume = s->reference_volume;
+ }
+
+ /* If the sink implementor support DB volumes by itself, we should always
+ * try and enable flat volumes too */
+ if ((s->flags & PA_SINK_DECIBEL_VOLUME))
+ enable_flat_volume(s, true);
+
+ if (s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER) {
+ pa_sink *root_sink = pa_sink_get_master(s);
+
+ pa_assert(root_sink);
+
+ s->reference_volume = root_sink->reference_volume;
+ pa_cvolume_remap(&s->reference_volume, &root_sink->channel_map, &s->channel_map);
+
+ s->real_volume = root_sink->real_volume;
+ pa_cvolume_remap(&s->real_volume, &root_sink->channel_map, &s->channel_map);
+ } else
+ /* We assume that if the sink implementor changed the default
+ * volume they did so in real_volume, because that is the usual
+ * place where they are supposed to place their changes. */
+ s->reference_volume = s->real_volume;
+
+ s->thread_info.soft_volume = s->soft_volume;
+ s->thread_info.soft_muted = s->muted;
+ pa_sw_cvolume_divide(&s->thread_info.current_hw_volume, &s->real_volume, &s->soft_volume);
+
+ pa_assert((s->flags & PA_SINK_HW_VOLUME_CTRL)
+ || (s->base_volume == PA_VOLUME_NORM
+ && ((s->flags & PA_SINK_DECIBEL_VOLUME || (s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)))));
+ pa_assert(!(s->flags & PA_SINK_DECIBEL_VOLUME) || s->n_volume_steps == PA_VOLUME_NORM+1);
+ pa_assert(!(s->flags & PA_SINK_DYNAMIC_LATENCY) == !(s->thread_info.fixed_latency == 0));
+ pa_assert(!(s->flags & PA_SINK_LATENCY) == !(s->monitor_source->flags & PA_SOURCE_LATENCY));
+ pa_assert(!(s->flags & PA_SINK_DYNAMIC_LATENCY) == !(s->monitor_source->flags & PA_SOURCE_DYNAMIC_LATENCY));
+
+ pa_assert(s->monitor_source->thread_info.fixed_latency == s->thread_info.fixed_latency);
+ pa_assert(s->monitor_source->thread_info.min_latency == s->thread_info.min_latency);
+ pa_assert(s->monitor_source->thread_info.max_latency == s->thread_info.max_latency);
+
+ if (s->suspend_cause)
+ pa_assert_se(sink_set_state(s, PA_SINK_SUSPENDED, s->suspend_cause) == 0);
+ else
+ pa_assert_se(sink_set_state(s, PA_SINK_IDLE, 0) == 0);
+
+ pa_source_put(s->monitor_source);
+
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK | PA_SUBSCRIPTION_EVENT_NEW, s->index);
+ pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_PUT], s);
+
+ /* It's good to fire the SINK_PUT hook before updating the default sink,
+ * because module-switch-on-connect will set the new sink as the default
+ * sink, and if we were to call pa_core_update_default_sink() before that,
+ * the default sink might change twice, causing unnecessary stream moving. */
+
+ pa_core_update_default_sink(s->core);
+
+ pa_core_move_streams_to_newly_available_preferred_sink(s->core, s);
+}
+
+/* Called from main context */
+void pa_sink_unlink(pa_sink* s) {
+ bool linked;
+ pa_sink_input *i, PA_UNUSED *j = NULL;
+
+ pa_sink_assert_ref(s);
+ pa_assert_ctl_context();
+
+ /* Please note that pa_sink_unlink() does more than simply
+ * reversing pa_sink_put(). It also undoes the registrations
+ * already done in pa_sink_new()! */
+
+ if (s->unlink_requested)
+ return;
+
+ s->unlink_requested = true;
+
+ linked = PA_SINK_IS_LINKED(s->state);
+
+ if (linked)
+ pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_UNLINK], s);
+
+ if (s->state != PA_SINK_UNLINKED)
+ pa_namereg_unregister(s->core, s->name);
+ pa_idxset_remove_by_data(s->core->sinks, s, NULL);
+
+ pa_core_update_default_sink(s->core);
+
+ if (linked && s->core->rescue_streams)
+ pa_sink_move_streams_to_default_sink(s->core, s, false);
+
+ if (s->card)
+ pa_idxset_remove_by_data(s->card->sinks, s, NULL);
+
+ while ((i = pa_idxset_first(s->inputs, NULL))) {
+ pa_assert(i != j);
+ pa_sink_input_kill(i);
+ j = i;
+ }
+
+ if (linked)
+ /* It's important to keep the suspend cause unchanged when unlinking,
+ * because if we remove the SESSION suspend cause here, the alsa sink
+ * will sync its volume with the hardware while another user is
+ * active, messing up the volume for that other user. */
+ sink_set_state(s, PA_SINK_UNLINKED, s->suspend_cause);
+ else
+ s->state = PA_SINK_UNLINKED;
+
+ reset_callbacks(s);
+
+ if (s->monitor_source)
+ pa_source_unlink(s->monitor_source);
+
+ if (linked) {
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK | PA_SUBSCRIPTION_EVENT_REMOVE, s->index);
+ pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_UNLINK_POST], s);
+ }
+}
+
+/* Called from main context */
+static void sink_free(pa_object *o) {
+ pa_sink *s = PA_SINK(o);
+
+ pa_assert(s);
+ pa_assert_ctl_context();
+ pa_assert(pa_sink_refcnt(s) == 0);
+ pa_assert(!PA_SINK_IS_LINKED(s->state));
+
+ pa_log_info("Freeing sink %u \"%s\"", s->index, s->name);
+
+ pa_sink_volume_change_flush(s);
+
+ if (s->monitor_source) {
+ pa_source_unref(s->monitor_source);
+ s->monitor_source = NULL;
+ }
+
+ pa_idxset_free(s->inputs, NULL);
+ pa_hashmap_free(s->thread_info.inputs);
+
+ if (s->silence.memblock)
+ pa_memblock_unref(s->silence.memblock);
+
+ pa_xfree(s->name);
+ pa_xfree(s->driver);
+
+ if (s->proplist)
+ pa_proplist_free(s->proplist);
+
+ if (s->ports)
+ pa_hashmap_free(s->ports);
+
+ pa_xfree(s);
+}
+
+/* Called from main context, and not while the IO thread is active, please */
+void pa_sink_set_asyncmsgq(pa_sink *s, pa_asyncmsgq *q) {
+ pa_sink_assert_ref(s);
+ pa_assert_ctl_context();
+
+ s->asyncmsgq = q;
+
+ if (s->monitor_source)
+ pa_source_set_asyncmsgq(s->monitor_source, q);
+}
+
+/* Called from main context, and not while the IO thread is active, please */
+void pa_sink_update_flags(pa_sink *s, pa_sink_flags_t mask, pa_sink_flags_t value) {
+ pa_sink_flags_t old_flags;
+ pa_sink_input *input;
+ uint32_t idx;
+
+ pa_sink_assert_ref(s);
+ pa_assert_ctl_context();
+
+ /* For now, allow only a minimal set of flags to be changed. */
+ pa_assert((mask & ~(PA_SINK_DYNAMIC_LATENCY|PA_SINK_LATENCY)) == 0);
+
+ old_flags = s->flags;
+ s->flags = (s->flags & ~mask) | (value & mask);
+
+ if (s->flags == old_flags)
+ return;
+
+ if ((s->flags & PA_SINK_LATENCY) != (old_flags & PA_SINK_LATENCY))
+ pa_log_debug("Sink %s: LATENCY flag %s.", s->name, (s->flags & PA_SINK_LATENCY) ? "enabled" : "disabled");
+
+ if ((s->flags & PA_SINK_DYNAMIC_LATENCY) != (old_flags & PA_SINK_DYNAMIC_LATENCY))
+ pa_log_debug("Sink %s: DYNAMIC_LATENCY flag %s.",
+ s->name, (s->flags & PA_SINK_DYNAMIC_LATENCY) ? "enabled" : "disabled");
+
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK | PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+ pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_FLAGS_CHANGED], s);
+
+ if (s->monitor_source)
+ pa_source_update_flags(s->monitor_source,
+ ((mask & PA_SINK_LATENCY) ? PA_SOURCE_LATENCY : 0) |
+ ((mask & PA_SINK_DYNAMIC_LATENCY) ? PA_SOURCE_DYNAMIC_LATENCY : 0),
+ ((value & PA_SINK_LATENCY) ? PA_SOURCE_LATENCY : 0) |
+ ((value & PA_SINK_DYNAMIC_LATENCY) ? PA_SOURCE_DYNAMIC_LATENCY : 0));
+
+ PA_IDXSET_FOREACH(input, s->inputs, idx) {
+ if (input->origin_sink)
+ pa_sink_update_flags(input->origin_sink, mask, value);
+ }
+}
+
+/* Called from IO context, or before _put() from main context */
+void pa_sink_set_rtpoll(pa_sink *s, pa_rtpoll *p) {
+ pa_sink_assert_ref(s);
+ pa_sink_assert_io_context(s);
+
+ s->thread_info.rtpoll = p;
+
+ if (s->monitor_source)
+ pa_source_set_rtpoll(s->monitor_source, p);
+}
+
+/* Called from main context */
+int pa_sink_update_status(pa_sink*s) {
+ pa_sink_assert_ref(s);
+ pa_assert_ctl_context();
+ pa_assert(PA_SINK_IS_LINKED(s->state));
+
+ if (s->state == PA_SINK_SUSPENDED)
+ return 0;
+
+ return sink_set_state(s, pa_sink_used_by(s) ? PA_SINK_RUNNING : PA_SINK_IDLE, 0);
+}
+
+/* Called from main context */
+int pa_sink_suspend(pa_sink *s, bool suspend, pa_suspend_cause_t cause) {
+ pa_suspend_cause_t merged_cause;
+
+ pa_sink_assert_ref(s);
+ pa_assert_ctl_context();
+ pa_assert(PA_SINK_IS_LINKED(s->state));
+ pa_assert(cause != 0);
+
+ if (suspend)
+ merged_cause = s->suspend_cause | cause;
+ else
+ merged_cause = s->suspend_cause & ~cause;
+
+ if (merged_cause)
+ return sink_set_state(s, PA_SINK_SUSPENDED, merged_cause);
+ else
+ return sink_set_state(s, pa_sink_used_by(s) ? PA_SINK_RUNNING : PA_SINK_IDLE, 0);
+}
+
+/* Called from main context */
+pa_queue *pa_sink_move_all_start(pa_sink *s, pa_queue *q) {
+ pa_sink_input *i, *n;
+ uint32_t idx;
+
+ pa_sink_assert_ref(s);
+ pa_assert_ctl_context();
+ pa_assert(PA_SINK_IS_LINKED(s->state));
+
+ if (!q)
+ q = pa_queue_new();
+
+ for (i = PA_SINK_INPUT(pa_idxset_first(s->inputs, &idx)); i; i = n) {
+ n = PA_SINK_INPUT(pa_idxset_next(s->inputs, &idx));
+
+ pa_sink_input_ref(i);
+
+ if (pa_sink_input_start_move(i) >= 0)
+ pa_queue_push(q, i);
+ else
+ pa_sink_input_unref(i);
+ }
+
+ return q;
+}
+
+/* Called from main context */
+void pa_sink_move_all_finish(pa_sink *s, pa_queue *q, bool save) {
+ pa_sink_input *i;
+
+ pa_sink_assert_ref(s);
+ pa_assert_ctl_context();
+ pa_assert(PA_SINK_IS_LINKED(s->state));
+ pa_assert(q);
+
+ while ((i = PA_SINK_INPUT(pa_queue_pop(q)))) {
+ if (PA_SINK_INPUT_IS_LINKED(i->state)) {
+ if (pa_sink_input_finish_move(i, s, save) < 0)
+ pa_sink_input_fail_move(i);
+
+ }
+ pa_sink_input_unref(i);
+ }
+
+ pa_queue_free(q, NULL);
+}
+
+/* Called from main context */
+void pa_sink_move_all_fail(pa_queue *q) {
+ pa_sink_input *i;
+
+ pa_assert_ctl_context();
+ pa_assert(q);
+
+ while ((i = PA_SINK_INPUT(pa_queue_pop(q)))) {
+ pa_sink_input_fail_move(i);
+ pa_sink_input_unref(i);
+ }
+
+ pa_queue_free(q, NULL);
+}
+
+ /* Called from IO thread context */
+size_t pa_sink_process_input_underruns(pa_sink *s, size_t left_to_play) {
+ pa_sink_input *i;
+ void *state = NULL;
+ size_t result = 0;
+
+ pa_sink_assert_ref(s);
+ pa_sink_assert_io_context(s);
+
+ PA_HASHMAP_FOREACH(i, s->thread_info.inputs, state) {
+ size_t uf = i->thread_info.underrun_for_sink;
+
+ /* Propagate down the filter tree */
+ if (i->origin_sink) {
+ size_t filter_result, left_to_play_origin;
+
+ /* The recursive call works in the origin sink domain ... */
+ left_to_play_origin = pa_convert_size(left_to_play, &i->sink->sample_spec, &i->origin_sink->sample_spec);
+
+ /* .. and returns the time to sleep before waking up. We need the
+ * underrun duration for comparisons, so we undo the subtraction on
+ * the return value... */
+ filter_result = left_to_play_origin - pa_sink_process_input_underruns(i->origin_sink, left_to_play_origin);
+
+ /* ... and convert it back to the master sink domain */
+ filter_result = pa_convert_size(filter_result, &i->origin_sink->sample_spec, &i->sink->sample_spec);
+
+ /* Remember the longest underrun so far */
+ if (filter_result > result)
+ result = filter_result;
+ }
+
+ if (uf == 0) {
+ /* No underrun here, move on */
+ continue;
+ } else if (uf >= left_to_play) {
+ /* The sink has possibly consumed all the data the sink input provided */
+ pa_sink_input_process_underrun(i);
+ } else if (uf > result) {
+ /* Remember the longest underrun so far */
+ result = uf;
+ }
+ }
+
+ if (result > 0)
+ pa_log_debug("%s: Found underrun %ld bytes ago (%ld bytes ahead in playback buffer)", s->name,
+ (long) result, (long) left_to_play - result);
+ return left_to_play - result;
+}
+
+/* Called from IO thread context */
+void pa_sink_process_rewind(pa_sink *s, size_t nbytes) {
+ pa_sink_input *i;
+ void *state = NULL;
+
+ pa_sink_assert_ref(s);
+ pa_sink_assert_io_context(s);
+ pa_assert(PA_SINK_IS_LINKED(s->thread_info.state));
+
+ /* If nobody requested this and this is actually no real rewind
+ * then we can short cut this. Please note that this means that
+ * not all rewind requests triggered upstream will always be
+ * translated in actual requests! */
+ if (!s->thread_info.rewind_requested && nbytes <= 0)
+ return;
+
+ s->thread_info.rewind_nbytes = 0;
+ s->thread_info.rewind_requested = false;
+
+ if (nbytes > 0) {
+ pa_log_debug("Processing rewind...");
+ if (s->flags & PA_SINK_DEFERRED_VOLUME)
+ pa_sink_volume_change_rewind(s, nbytes);
+ }
+
+ PA_HASHMAP_FOREACH(i, s->thread_info.inputs, state) {
+ pa_sink_input_assert_ref(i);
+ pa_sink_input_process_rewind(i, nbytes);
+ }
+
+ if (nbytes > 0) {
+ if (s->monitor_source && PA_SOURCE_IS_LINKED(s->monitor_source->thread_info.state))
+ pa_source_process_rewind(s->monitor_source, nbytes);
+ }
+}
+
+/* Called from IO thread context */
+static unsigned fill_mix_info(pa_sink *s, size_t *length, pa_mix_info *info, unsigned maxinfo) {
+ pa_sink_input *i;
+ unsigned n = 0;
+ void *state = NULL;
+ size_t mixlength = *length;
+
+ pa_sink_assert_ref(s);
+ pa_sink_assert_io_context(s);
+ pa_assert(info);
+
+ while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL)) && maxinfo > 0) {
+ pa_sink_input_assert_ref(i);
+
+ pa_sink_input_peek(i, *length, &info->chunk, &info->volume);
+
+ if (mixlength == 0 || info->chunk.length < mixlength)
+ mixlength = info->chunk.length;
+
+ if (pa_memblock_is_silence(info->chunk.memblock)) {
+ pa_memblock_unref(info->chunk.memblock);
+ continue;
+ }
+
+ info->userdata = pa_sink_input_ref(i);
+
+ pa_assert(info->chunk.memblock);
+ pa_assert(info->chunk.length > 0);
+
+ info++;
+ n++;
+ maxinfo--;
+ }
+
+ if (mixlength > 0)
+ *length = mixlength;
+
+ return n;
+}
+
+/* Called from IO thread context */
+static void inputs_drop(pa_sink *s, pa_mix_info *info, unsigned n, pa_memchunk *result) {
+ pa_sink_input *i;
+ void *state;
+ unsigned p = 0;
+ unsigned n_unreffed = 0;
+
+ pa_sink_assert_ref(s);
+ pa_sink_assert_io_context(s);
+ pa_assert(result);
+ pa_assert(result->memblock);
+ pa_assert(result->length > 0);
+
+ /* We optimize for the case where the order of the inputs has not changed */
+
+ PA_HASHMAP_FOREACH(i, s->thread_info.inputs, state) {
+ unsigned j;
+ pa_mix_info* m = NULL;
+
+ pa_sink_input_assert_ref(i);
+
+ /* Let's try to find the matching entry info the pa_mix_info array */
+ for (j = 0; j < n; j ++) {
+
+ if (info[p].userdata == i) {
+ m = info + p;
+ break;
+ }
+
+ p++;
+ if (p >= n)
+ p = 0;
+ }
+
+ /* Drop read data */
+ pa_sink_input_drop(i, result->length);
+
+ if (s->monitor_source && PA_SOURCE_IS_LINKED(s->monitor_source->thread_info.state)) {
+
+ if (pa_hashmap_size(i->thread_info.direct_outputs) > 0) {
+ void *ostate = NULL;
+ pa_source_output *o;
+ pa_memchunk c;
+
+ if (m && m->chunk.memblock) {
+ c = m->chunk;
+ pa_memblock_ref(c.memblock);
+ pa_assert(result->length <= c.length);
+ c.length = result->length;
+
+ pa_memchunk_make_writable(&c, 0);
+ pa_volume_memchunk(&c, &s->sample_spec, &m->volume);
+ } else {
+ c = s->silence;
+ pa_memblock_ref(c.memblock);
+ pa_assert(result->length <= c.length);
+ c.length = result->length;
+ }
+
+ while ((o = pa_hashmap_iterate(i->thread_info.direct_outputs, &ostate, NULL))) {
+ pa_source_output_assert_ref(o);
+ pa_assert(o->direct_on_input == i);
+ pa_source_post_direct(s->monitor_source, o, &c);
+ }
+
+ pa_memblock_unref(c.memblock);
+ }
+ }
+
+ if (m) {
+ if (m->chunk.memblock) {
+ pa_memblock_unref(m->chunk.memblock);
+ pa_memchunk_reset(&m->chunk);
+ }
+
+ pa_sink_input_unref(m->userdata);
+ m->userdata = NULL;
+
+ n_unreffed += 1;
+ }
+ }
+
+ /* Now drop references to entries that are included in the
+ * pa_mix_info array but don't exist anymore */
+
+ if (n_unreffed < n) {
+ for (; n > 0; info++, n--) {
+ if (info->userdata)
+ pa_sink_input_unref(info->userdata);
+ if (info->chunk.memblock)
+ pa_memblock_unref(info->chunk.memblock);
+ }
+ }
+
+ if (s->monitor_source && PA_SOURCE_IS_LINKED(s->monitor_source->thread_info.state))
+ pa_source_post(s->monitor_source, result);
+}
+
+/* Called from IO thread context */
+void pa_sink_render(pa_sink*s, size_t length, pa_memchunk *result) {
+ pa_mix_info info[MAX_MIX_CHANNELS];
+ unsigned n;
+ size_t block_size_max;
+
+ pa_sink_assert_ref(s);
+ pa_sink_assert_io_context(s);
+ pa_assert(PA_SINK_IS_LINKED(s->thread_info.state));
+ pa_assert(pa_frame_aligned(length, &s->sample_spec));
+ pa_assert(result);
+
+ pa_assert(!s->thread_info.rewind_requested);
+ pa_assert(s->thread_info.rewind_nbytes == 0);
+
+ if (s->thread_info.state == PA_SINK_SUSPENDED) {
+ result->memblock = pa_memblock_ref(s->silence.memblock);
+ result->index = s->silence.index;
+ result->length = PA_MIN(s->silence.length, length);
+ return;
+ }
+
+ pa_sink_ref(s);
+
+ if (length <= 0)
+ length = pa_frame_align(MIX_BUFFER_LENGTH, &s->sample_spec);
+
+ block_size_max = pa_mempool_block_size_max(s->core->mempool);
+ if (length > block_size_max)
+ length = pa_frame_align(block_size_max, &s->sample_spec);
+
+ pa_assert(length > 0);
+
+ n = fill_mix_info(s, &length, info, MAX_MIX_CHANNELS);
+
+ if (n == 0) {
+
+ *result = s->silence;
+ pa_memblock_ref(result->memblock);
+
+ if (result->length > length)
+ result->length = length;
+
+ } else if (n == 1) {
+ pa_cvolume volume;
+
+ *result = info[0].chunk;
+ pa_memblock_ref(result->memblock);
+
+ if (result->length > length)
+ result->length = length;
+
+ pa_sw_cvolume_multiply(&volume, &s->thread_info.soft_volume, &info[0].volume);
+
+ if (s->thread_info.soft_muted || pa_cvolume_is_muted(&volume)) {
+ pa_memblock_unref(result->memblock);
+ pa_silence_memchunk_get(&s->core->silence_cache,
+ s->core->mempool,
+ result,
+ &s->sample_spec,
+ result->length);
+ } else if (!pa_cvolume_is_norm(&volume)) {
+ pa_memchunk_make_writable(result, 0);
+ pa_volume_memchunk(result, &s->sample_spec, &volume);
+ }
+ } else {
+ void *ptr;
+ result->memblock = pa_memblock_new(s->core->mempool, length);
+
+ ptr = pa_memblock_acquire(result->memblock);
+ result->length = pa_mix(info, n,
+ ptr, length,
+ &s->sample_spec,
+ &s->thread_info.soft_volume,
+ s->thread_info.soft_muted);
+ pa_memblock_release(result->memblock);
+
+ result->index = 0;
+ }
+
+ inputs_drop(s, info, n, result);
+
+ pa_sink_unref(s);
+}
+
+/* Called from IO thread context */
+void pa_sink_render_into(pa_sink*s, pa_memchunk *target) {
+ pa_mix_info info[MAX_MIX_CHANNELS];
+ unsigned n;
+ size_t length, block_size_max;
+
+ pa_sink_assert_ref(s);
+ pa_sink_assert_io_context(s);
+ pa_assert(PA_SINK_IS_LINKED(s->thread_info.state));
+ pa_assert(target);
+ pa_assert(target->memblock);
+ pa_assert(target->length > 0);
+ pa_assert(pa_frame_aligned(target->length, &s->sample_spec));
+
+ pa_assert(!s->thread_info.rewind_requested);
+ pa_assert(s->thread_info.rewind_nbytes == 0);
+
+ if (s->thread_info.state == PA_SINK_SUSPENDED) {
+ pa_silence_memchunk(target, &s->sample_spec);
+ return;
+ }
+
+ pa_sink_ref(s);
+
+ length = target->length;
+ block_size_max = pa_mempool_block_size_max(s->core->mempool);
+ if (length > block_size_max)
+ length = pa_frame_align(block_size_max, &s->sample_spec);
+
+ pa_assert(length > 0);
+
+ n = fill_mix_info(s, &length, info, MAX_MIX_CHANNELS);
+
+ if (n == 0) {
+ if (target->length > length)
+ target->length = length;
+
+ pa_silence_memchunk(target, &s->sample_spec);
+ } else if (n == 1) {
+ pa_cvolume volume;
+
+ if (target->length > length)
+ target->length = length;
+
+ pa_sw_cvolume_multiply(&volume, &s->thread_info.soft_volume, &info[0].volume);
+
+ if (s->thread_info.soft_muted || pa_cvolume_is_muted(&volume))
+ pa_silence_memchunk(target, &s->sample_spec);
+ else {
+ pa_memchunk vchunk;
+
+ vchunk = info[0].chunk;
+ pa_memblock_ref(vchunk.memblock);
+
+ if (vchunk.length > length)
+ vchunk.length = length;
+
+ if (!pa_cvolume_is_norm(&volume)) {
+ pa_memchunk_make_writable(&vchunk, 0);
+ pa_volume_memchunk(&vchunk, &s->sample_spec, &volume);
+ }
+
+ pa_memchunk_memcpy(target, &vchunk);
+ pa_memblock_unref(vchunk.memblock);
+ }
+
+ } else {
+ void *ptr;
+
+ ptr = pa_memblock_acquire(target->memblock);
+
+ target->length = pa_mix(info, n,
+ (uint8_t*) ptr + target->index, length,
+ &s->sample_spec,
+ &s->thread_info.soft_volume,
+ s->thread_info.soft_muted);
+
+ pa_memblock_release(target->memblock);
+ }
+
+ inputs_drop(s, info, n, target);
+
+ pa_sink_unref(s);
+}
+
+/* Called from IO thread context */
+void pa_sink_render_into_full(pa_sink *s, pa_memchunk *target) {
+ pa_memchunk chunk;
+ size_t l, d;
+
+ pa_sink_assert_ref(s);
+ pa_sink_assert_io_context(s);
+ pa_assert(PA_SINK_IS_LINKED(s->thread_info.state));
+ pa_assert(target);
+ pa_assert(target->memblock);
+ pa_assert(target->length > 0);
+ pa_assert(pa_frame_aligned(target->length, &s->sample_spec));
+
+ pa_assert(!s->thread_info.rewind_requested);
+ pa_assert(s->thread_info.rewind_nbytes == 0);
+
+ if (s->thread_info.state == PA_SINK_SUSPENDED) {
+ pa_silence_memchunk(target, &s->sample_spec);
+ return;
+ }
+
+ pa_sink_ref(s);
+
+ l = target->length;
+ d = 0;
+ while (l > 0) {
+ chunk = *target;
+ chunk.index += d;
+ chunk.length -= d;
+
+ pa_sink_render_into(s, &chunk);
+
+ d += chunk.length;
+ l -= chunk.length;
+ }
+
+ pa_sink_unref(s);
+}
+
+/* Called from IO thread context */
+void pa_sink_render_full(pa_sink *s, size_t length, pa_memchunk *result) {
+ pa_sink_assert_ref(s);
+ pa_sink_assert_io_context(s);
+ pa_assert(PA_SINK_IS_LINKED(s->thread_info.state));
+ pa_assert(length > 0);
+ pa_assert(pa_frame_aligned(length, &s->sample_spec));
+ pa_assert(result);
+
+ pa_assert(!s->thread_info.rewind_requested);
+ pa_assert(s->thread_info.rewind_nbytes == 0);
+
+ pa_sink_ref(s);
+
+ pa_sink_render(s, length, result);
+
+ if (result->length < length) {
+ pa_memchunk chunk;
+
+ pa_memchunk_make_writable(result, length);
+
+ chunk.memblock = result->memblock;
+ chunk.index = result->index + result->length;
+ chunk.length = length - result->length;
+
+ pa_sink_render_into_full(s, &chunk);
+
+ result->length = length;
+ }
+
+ pa_sink_unref(s);
+}
+
+/* Called from main thread */
+void pa_sink_reconfigure(pa_sink *s, pa_sample_spec *spec, bool passthrough) {
+ pa_sample_spec desired_spec;
+ uint32_t default_rate = s->default_sample_rate;
+ uint32_t alternate_rate = s->alternate_sample_rate;
+ uint32_t idx;
+ pa_sink_input *i;
+ bool default_rate_is_usable = false;
+ bool alternate_rate_is_usable = false;
+ bool avoid_resampling = s->avoid_resampling;
+
+ if (pa_sample_spec_equal(spec, &s->sample_spec))
+ return;
+
+ if (!s->reconfigure)
+ return;
+
+ if (PA_UNLIKELY(default_rate == alternate_rate && !passthrough && !avoid_resampling)) {
+ pa_log_debug("Default and alternate sample rates are the same, so there is no point in switching.");
+ return;
+ }
+
+ if (PA_SINK_IS_RUNNING(s->state)) {
+ pa_log_info("Cannot update sample spec, SINK_IS_RUNNING, will keep using %s and %u Hz",
+ pa_sample_format_to_string(s->sample_spec.format), s->sample_spec.rate);
+ return;
+ }
+
+ if (s->monitor_source) {
+ if (PA_SOURCE_IS_RUNNING(s->monitor_source->state) == true) {
+ pa_log_info("Cannot update sample spec, monitor source is RUNNING");
+ return;
+ }
+ }
+
+ if (PA_UNLIKELY(!pa_sample_spec_valid(spec)))
+ return;
+
+ desired_spec = s->sample_spec;
+
+ if (passthrough) {
+ /* We have to try to use the sink input format and rate */
+ desired_spec.format = spec->format;
+ desired_spec.rate = spec->rate;
+
+ } else if (avoid_resampling) {
+ /* We just try to set the sink input's sample rate if it's not too low */
+ if (spec->rate >= default_rate || spec->rate >= alternate_rate)
+ desired_spec.rate = spec->rate;
+ desired_spec.format = spec->format;
+
+ } else if (default_rate == spec->rate || alternate_rate == spec->rate) {
+ /* We can directly try to use this rate */
+ desired_spec.rate = spec->rate;
+
+ }
+
+ if (desired_spec.rate != spec->rate) {
+ /* See if we can pick a rate that results in less resampling effort */
+ if (default_rate % 11025 == 0 && spec->rate % 11025 == 0)
+ default_rate_is_usable = true;
+ if (default_rate % 4000 == 0 && spec->rate % 4000 == 0)
+ default_rate_is_usable = true;
+ if (alternate_rate % 11025 == 0 && spec->rate % 11025 == 0)
+ alternate_rate_is_usable = true;
+ if (alternate_rate % 4000 == 0 && spec->rate % 4000 == 0)
+ alternate_rate_is_usable = true;
+
+ if (alternate_rate_is_usable && !default_rate_is_usable)
+ desired_spec.rate = alternate_rate;
+ else
+ desired_spec.rate = default_rate;
+ }
+
+ if (pa_sample_spec_equal(&desired_spec, &s->sample_spec) && passthrough == pa_sink_is_passthrough(s))
+ return;
+
+ if (!passthrough && pa_sink_used_by(s) > 0)
+ return;
+
+ pa_log_debug("Suspending sink %s due to changing format, desired format = %s rate = %u",
+ s->name, pa_sample_format_to_string(desired_spec.format), desired_spec.rate);
+ pa_sink_suspend(s, true, PA_SUSPEND_INTERNAL);
+
+ s->reconfigure(s, &desired_spec, passthrough);
+
+ /* update monitor source as well */
+ if (s->monitor_source && !passthrough)
+ pa_source_reconfigure(s->monitor_source, &s->sample_spec, false);
+ pa_log_info("Reconfigured successfully");
+
+ PA_IDXSET_FOREACH(i, s->inputs, idx) {
+ if (i->state == PA_SINK_INPUT_CORKED)
+ pa_sink_input_update_resampler(i);
+ }
+
+ pa_sink_suspend(s, false, PA_SUSPEND_INTERNAL);
+}
+
+/* Called from main thread */
+pa_usec_t pa_sink_get_latency(pa_sink *s) {
+ int64_t usec = 0;
+
+ pa_sink_assert_ref(s);
+ pa_assert_ctl_context();
+ pa_assert(PA_SINK_IS_LINKED(s->state));
+
+ /* The returned value is supposed to be in the time domain of the sound card! */
+
+ if (s->state == PA_SINK_SUSPENDED)
+ return 0;
+
+ if (!(s->flags & PA_SINK_LATENCY))
+ return 0;
+
+ pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL) == 0);
+
+ /* the return value is unsigned, so check that the offset can be added to usec without
+ * underflowing. */
+ if (-s->port_latency_offset <= usec)
+ usec += s->port_latency_offset;
+ else
+ usec = 0;
+
+ return (pa_usec_t)usec;
+}
+
+/* Called from IO thread */
+int64_t pa_sink_get_latency_within_thread(pa_sink *s, bool allow_negative) {
+ int64_t usec = 0;
+ pa_msgobject *o;
+
+ pa_sink_assert_ref(s);
+ pa_sink_assert_io_context(s);
+ pa_assert(PA_SINK_IS_LINKED(s->thread_info.state));
+
+ /* The returned value is supposed to be in the time domain of the sound card! */
+
+ if (s->thread_info.state == PA_SINK_SUSPENDED)
+ return 0;
+
+ if (!(s->flags & PA_SINK_LATENCY))
+ return 0;
+
+ o = PA_MSGOBJECT(s);
+
+ /* FIXME: We probably should make this a proper vtable callback instead of going through process_msg() */
+
+ o->process_msg(o, PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL);
+
+ /* If allow_negative is false, the call should only return positive values, */
+ usec += s->thread_info.port_latency_offset;
+ if (!allow_negative && usec < 0)
+ usec = 0;
+
+ return usec;
+}
+
+/* Called from the main thread (and also from the IO thread while the main
+ * thread is waiting).
+ *
+ * When a sink uses volume sharing, it never has the PA_SINK_FLAT_VOLUME flag
+ * set. Instead, flat volume mode is detected by checking whether the root sink
+ * has the flag set. */
+bool pa_sink_flat_volume_enabled(pa_sink *s) {
+ pa_sink_assert_ref(s);
+
+ s = pa_sink_get_master(s);
+
+ if (PA_LIKELY(s))
+ return (s->flags & PA_SINK_FLAT_VOLUME);
+ else
+ return false;
+}
+
+/* Called from the main thread (and also from the IO thread while the main
+ * thread is waiting). */
+pa_sink *pa_sink_get_master(pa_sink *s) {
+ pa_sink_assert_ref(s);
+
+ while (s && (s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)) {
+ if (PA_UNLIKELY(!s->input_to_master))
+ return NULL;
+
+ s = s->input_to_master->sink;
+ }
+
+ return s;
+}
+
+/* Called from main context */
+bool pa_sink_is_filter(pa_sink *s) {
+ pa_sink_assert_ref(s);
+
+ return (s->input_to_master != NULL);
+}
+
+/* Called from main context */
+bool pa_sink_is_passthrough(pa_sink *s) {
+ pa_sink_input *alt_i;
+ uint32_t idx;
+
+ pa_sink_assert_ref(s);
+
+ /* one and only one PASSTHROUGH input can possibly be connected */
+ if (pa_idxset_size(s->inputs) == 1) {
+ alt_i = pa_idxset_first(s->inputs, &idx);
+
+ if (pa_sink_input_is_passthrough(alt_i))
+ return true;
+ }
+
+ return false;
+}
+
+/* Called from main context */
+void pa_sink_enter_passthrough(pa_sink *s) {
+ pa_cvolume volume;
+
+ /* The sink implementation is reconfigured for passthrough in
+ * pa_sink_reconfigure(). This function sets the PA core objects to
+ * passthrough mode. */
+
+ /* disable the monitor in passthrough mode */
+ if (s->monitor_source) {
+ pa_log_debug("Suspending monitor source %s, because the sink is entering the passthrough mode.", s->monitor_source->name);
+ pa_source_suspend(s->monitor_source, true, PA_SUSPEND_PASSTHROUGH);
+ }
+
+ /* set the volume to NORM */
+ s->saved_volume = *pa_sink_get_volume(s, true);
+ s->saved_save_volume = s->save_volume;
+
+ pa_cvolume_set(&volume, s->sample_spec.channels, PA_MIN(s->base_volume, PA_VOLUME_NORM));
+ pa_sink_set_volume(s, &volume, true, false);
+
+ pa_log_debug("Suspending/Restarting sink %s to enter passthrough mode", s->name);
+}
+
+/* Called from main context */
+void pa_sink_leave_passthrough(pa_sink *s) {
+ /* Unsuspend monitor */
+ if (s->monitor_source) {
+ pa_log_debug("Resuming monitor source %s, because the sink is leaving the passthrough mode.", s->monitor_source->name);
+ pa_source_suspend(s->monitor_source, false, PA_SUSPEND_PASSTHROUGH);
+ }
+
+ /* Restore sink volume to what it was before we entered passthrough mode */
+ pa_sink_set_volume(s, &s->saved_volume, true, s->saved_save_volume);
+
+ pa_cvolume_init(&s->saved_volume);
+ s->saved_save_volume = false;
+
+}
+
+/* Called from main context. */
+static void compute_reference_ratio(pa_sink_input *i) {
+ unsigned c = 0;
+ pa_cvolume remapped;
+ pa_cvolume ratio;
+
+ pa_assert(i);
+ pa_assert(pa_sink_flat_volume_enabled(i->sink));
+
+ /*
+ * Calculates the reference ratio from the sink's reference
+ * volume. This basically calculates:
+ *
+ * i->reference_ratio = i->volume / i->sink->reference_volume
+ */
+
+ remapped = i->sink->reference_volume;
+ pa_cvolume_remap(&remapped, &i->sink->channel_map, &i->channel_map);
+
+ ratio = i->reference_ratio;
+
+ for (c = 0; c < i->sample_spec.channels; c++) {
+
+ /* We don't update when the sink volume is 0 anyway */
+ if (remapped.values[c] <= PA_VOLUME_MUTED)
+ continue;
+
+ /* Don't update the reference ratio unless necessary */
+ if (pa_sw_volume_multiply(
+ ratio.values[c],
+ remapped.values[c]) == i->volume.values[c])
+ continue;
+
+ ratio.values[c] = pa_sw_volume_divide(
+ i->volume.values[c],
+ remapped.values[c]);
+ }
+
+ pa_sink_input_set_reference_ratio(i, &ratio);
+}
+
+/* Called from main context. Only called for the root sink in volume sharing
+ * cases, except for internal recursive calls. */
+static void compute_reference_ratios(pa_sink *s) {
+ uint32_t idx;
+ pa_sink_input *i;
+
+ pa_sink_assert_ref(s);
+ pa_assert_ctl_context();
+ pa_assert(PA_SINK_IS_LINKED(s->state));
+ pa_assert(pa_sink_flat_volume_enabled(s));
+
+ PA_IDXSET_FOREACH(i, s->inputs, idx) {
+ compute_reference_ratio(i);
+
+ if (i->origin_sink && (i->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)
+ && PA_SINK_IS_LINKED(i->origin_sink->state))
+ compute_reference_ratios(i->origin_sink);
+ }
+}
+
+/* Called from main context. Only called for the root sink in volume sharing
+ * cases, except for internal recursive calls. */
+static void compute_real_ratios(pa_sink *s) {
+ pa_sink_input *i;
+ uint32_t idx;
+
+ pa_sink_assert_ref(s);
+ pa_assert_ctl_context();
+ pa_assert(PA_SINK_IS_LINKED(s->state));
+ pa_assert(pa_sink_flat_volume_enabled(s));
+
+ PA_IDXSET_FOREACH(i, s->inputs, idx) {
+ unsigned c;
+ pa_cvolume remapped;
+
+ if (i->origin_sink && (i->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)) {
+ /* The origin sink uses volume sharing, so this input's real ratio
+ * is handled as a special case - the real ratio must be 0 dB, and
+ * as a result i->soft_volume must equal i->volume_factor. */
+ pa_cvolume_reset(&i->real_ratio, i->real_ratio.channels);
+ i->soft_volume = i->volume_factor;
+
+ if (PA_SINK_IS_LINKED(i->origin_sink->state))
+ compute_real_ratios(i->origin_sink);
+
+ continue;
+ }
+
+ /*
+ * This basically calculates:
+ *
+ * i->real_ratio := i->volume / s->real_volume
+ * i->soft_volume := i->real_ratio * i->volume_factor
+ */
+
+ remapped = s->real_volume;
+ pa_cvolume_remap(&remapped, &s->channel_map, &i->channel_map);
+
+ i->real_ratio.channels = i->sample_spec.channels;
+ i->soft_volume.channels = i->sample_spec.channels;
+
+ for (c = 0; c < i->sample_spec.channels; c++) {
+
+ if (remapped.values[c] <= PA_VOLUME_MUTED) {
+ /* We leave i->real_ratio untouched */
+ i->soft_volume.values[c] = PA_VOLUME_MUTED;
+ continue;
+ }
+
+ /* Don't lose accuracy unless necessary */
+ if (pa_sw_volume_multiply(
+ i->real_ratio.values[c],
+ remapped.values[c]) != i->volume.values[c])
+
+ i->real_ratio.values[c] = pa_sw_volume_divide(
+ i->volume.values[c],
+ remapped.values[c]);
+
+ i->soft_volume.values[c] = pa_sw_volume_multiply(
+ i->real_ratio.values[c],
+ i->volume_factor.values[c]);
+ }
+
+ /* We don't copy the soft_volume to the thread_info data
+ * here. That must be done by the caller */
+ }
+}
+
+static pa_cvolume *cvolume_remap_minimal_impact(
+ pa_cvolume *v,
+ const pa_cvolume *template,
+ const pa_channel_map *from,
+ const pa_channel_map *to) {
+
+ pa_cvolume t;
+
+ pa_assert(v);
+ pa_assert(template);
+ pa_assert(from);
+ pa_assert(to);
+ pa_assert(pa_cvolume_compatible_with_channel_map(v, from));
+ pa_assert(pa_cvolume_compatible_with_channel_map(template, to));
+
+ /* Much like pa_cvolume_remap(), but tries to minimize impact when
+ * mapping from sink input to sink volumes:
+ *
+ * If template is a possible remapping from v it is used instead
+ * of remapping anew.
+ *
+ * If the channel maps don't match we set an all-channel volume on
+ * the sink to ensure that changing a volume on one stream has no
+ * effect that cannot be compensated for in another stream that
+ * does not have the same channel map as the sink. */
+
+ if (pa_channel_map_equal(from, to))
+ return v;
+
+ t = *template;
+ if (pa_cvolume_equal(pa_cvolume_remap(&t, to, from), v)) {
+ *v = *template;
+ return v;
+ }
+
+ pa_cvolume_set(v, to->channels, pa_cvolume_max(v));
+ return v;
+}
+
+/* Called from main thread. Only called for the root sink in volume sharing
+ * cases, except for internal recursive calls. */
+static void get_maximum_input_volume(pa_sink *s, pa_cvolume *max_volume, const pa_channel_map *channel_map) {
+ pa_sink_input *i;
+ uint32_t idx;
+
+ pa_sink_assert_ref(s);
+ pa_assert(max_volume);
+ pa_assert(channel_map);
+ pa_assert(pa_sink_flat_volume_enabled(s));
+
+ PA_IDXSET_FOREACH(i, s->inputs, idx) {
+ pa_cvolume remapped;
+
+ if (i->origin_sink && (i->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)) {
+ if (PA_SINK_IS_LINKED(i->origin_sink->state))
+ get_maximum_input_volume(i->origin_sink, max_volume, channel_map);
+
+ /* Ignore this input. The origin sink uses volume sharing, so this
+ * input's volume will be set to be equal to the root sink's real
+ * volume. Obviously this input's current volume must not then
+ * affect what the root sink's real volume will be. */
+ continue;
+ }
+
+ remapped = i->volume;
+ cvolume_remap_minimal_impact(&remapped, max_volume, &i->channel_map, channel_map);
+ pa_cvolume_merge(max_volume, max_volume, &remapped);
+ }
+}
+
+/* Called from main thread. Only called for the root sink in volume sharing
+ * cases, except for internal recursive calls. */
+static bool has_inputs(pa_sink *s) {
+ pa_sink_input *i;
+ uint32_t idx;
+
+ pa_sink_assert_ref(s);
+
+ PA_IDXSET_FOREACH(i, s->inputs, idx) {
+ if (!i->origin_sink || !(i->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER) || has_inputs(i->origin_sink))
+ return true;
+ }
+
+ return false;
+}
+
+/* Called from main thread. Only called for the root sink in volume sharing
+ * cases, except for internal recursive calls. */
+static void update_real_volume(pa_sink *s, const pa_cvolume *new_volume, pa_channel_map *channel_map) {
+ pa_sink_input *i;
+ uint32_t idx;
+
+ pa_sink_assert_ref(s);
+ pa_assert(new_volume);
+ pa_assert(channel_map);
+
+ s->real_volume = *new_volume;
+ pa_cvolume_remap(&s->real_volume, channel_map, &s->channel_map);
+
+ PA_IDXSET_FOREACH(i, s->inputs, idx) {
+ if (i->origin_sink && (i->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)) {
+ if (pa_sink_flat_volume_enabled(s)) {
+ pa_cvolume new_input_volume;
+
+ /* Follow the root sink's real volume. */
+ new_input_volume = *new_volume;
+ pa_cvolume_remap(&new_input_volume, channel_map, &i->channel_map);
+ pa_sink_input_set_volume_direct(i, &new_input_volume);
+ compute_reference_ratio(i);
+ }
+
+ if (PA_SINK_IS_LINKED(i->origin_sink->state))
+ update_real_volume(i->origin_sink, new_volume, channel_map);
+ }
+ }
+}
+
+/* Called from main thread. Only called for the root sink in shared volume
+ * cases. */
+static void compute_real_volume(pa_sink *s) {
+ pa_sink_assert_ref(s);
+ pa_assert_ctl_context();
+ pa_assert(PA_SINK_IS_LINKED(s->state));
+ pa_assert(pa_sink_flat_volume_enabled(s));
+ pa_assert(!(s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER));
+
+ /* This determines the maximum volume of all streams and sets
+ * s->real_volume accordingly. */
+
+ if (!has_inputs(s)) {
+ /* In the special case that we have no sink inputs we leave the
+ * volume unmodified. */
+ update_real_volume(s, &s->reference_volume, &s->channel_map);
+ return;
+ }
+
+ pa_cvolume_mute(&s->real_volume, s->channel_map.channels);
+
+ /* First let's determine the new maximum volume of all inputs
+ * connected to this sink */
+ get_maximum_input_volume(s, &s->real_volume, &s->channel_map);
+ update_real_volume(s, &s->real_volume, &s->channel_map);
+
+ /* Then, let's update the real ratios/soft volumes of all inputs
+ * connected to this sink */
+ compute_real_ratios(s);
+}
+
+/* Called from main thread. Only called for the root sink in shared volume
+ * cases, except for internal recursive calls. */
+static void propagate_reference_volume(pa_sink *s) {
+ pa_sink_input *i;
+ uint32_t idx;
+
+ pa_sink_assert_ref(s);
+ pa_assert_ctl_context();
+ pa_assert(PA_SINK_IS_LINKED(s->state));
+ pa_assert(pa_sink_flat_volume_enabled(s));
+
+ /* This is called whenever the sink volume changes that is not
+ * caused by a sink input volume change. We need to fix up the
+ * sink input volumes accordingly */
+
+ PA_IDXSET_FOREACH(i, s->inputs, idx) {
+ pa_cvolume new_volume;
+
+ if (i->origin_sink && (i->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)) {
+ if (PA_SINK_IS_LINKED(i->origin_sink->state))
+ propagate_reference_volume(i->origin_sink);
+
+ /* Since the origin sink uses volume sharing, this input's volume
+ * needs to be updated to match the root sink's real volume, but
+ * that will be done later in update_real_volume(). */
+ continue;
+ }
+
+ /* This basically calculates:
+ *
+ * i->volume := s->reference_volume * i->reference_ratio */
+
+ new_volume = s->reference_volume;
+ pa_cvolume_remap(&new_volume, &s->channel_map, &i->channel_map);
+ pa_sw_cvolume_multiply(&new_volume, &new_volume, &i->reference_ratio);
+ pa_sink_input_set_volume_direct(i, &new_volume);
+ }
+}
+
+/* Called from main thread. Only called for the root sink in volume sharing
+ * cases, except for internal recursive calls. The return value indicates
+ * whether any reference volume actually changed. */
+static bool update_reference_volume(pa_sink *s, const pa_cvolume *v, const pa_channel_map *channel_map, bool save) {
+ pa_cvolume volume;
+ bool reference_volume_changed;
+ pa_sink_input *i;
+ uint32_t idx;
+
+ pa_sink_assert_ref(s);
+ pa_assert(PA_SINK_IS_LINKED(s->state));
+ pa_assert(v);
+ pa_assert(channel_map);
+ pa_assert(pa_cvolume_valid(v));
+
+ volume = *v;
+ pa_cvolume_remap(&volume, channel_map, &s->channel_map);
+
+ reference_volume_changed = !pa_cvolume_equal(&volume, &s->reference_volume);
+ pa_sink_set_reference_volume_direct(s, &volume);
+
+ s->save_volume = (!reference_volume_changed && s->save_volume) || save;
+
+ if (!reference_volume_changed && !(s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER))
+ /* If the root sink's volume doesn't change, then there can't be any
+ * changes in the other sinks in the sink tree either.
+ *
+ * It's probably theoretically possible that even if the root sink's
+ * volume changes slightly, some filter sink doesn't change its volume
+ * due to rounding errors. If that happens, we still want to propagate
+ * the changed root sink volume to the sinks connected to the
+ * intermediate sink that didn't change its volume. This theoretical
+ * possibility is the reason why we have that !(s->flags &
+ * PA_SINK_SHARE_VOLUME_WITH_MASTER) condition. Probably nobody would
+ * notice even if we returned here false always if
+ * reference_volume_changed is false. */
+ return false;
+
+ PA_IDXSET_FOREACH(i, s->inputs, idx) {
+ if (i->origin_sink && (i->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)
+ && PA_SINK_IS_LINKED(i->origin_sink->state))
+ update_reference_volume(i->origin_sink, v, channel_map, false);
+ }
+
+ return true;
+}
+
+/* Called from main thread */
+void pa_sink_set_volume(
+ pa_sink *s,
+ const pa_cvolume *volume,
+ bool send_msg,
+ bool save) {
+
+ pa_cvolume new_reference_volume;
+ pa_sink *root_sink;
+
+ pa_sink_assert_ref(s);
+ pa_assert_ctl_context();
+ pa_assert(PA_SINK_IS_LINKED(s->state));
+ pa_assert(!volume || pa_cvolume_valid(volume));
+ pa_assert(volume || pa_sink_flat_volume_enabled(s));
+ pa_assert(!volume || volume->channels == 1 || pa_cvolume_compatible(volume, &s->sample_spec));
+
+ /* make sure we don't change the volume when a PASSTHROUGH input is connected ...
+ * ... *except* if we're being invoked to reset the volume to ensure 0 dB gain */
+ if (pa_sink_is_passthrough(s) && (!volume || !pa_cvolume_is_norm(volume))) {
+ pa_log_warn("Cannot change volume, Sink is connected to PASSTHROUGH input");
+ return;
+ }
+
+ /* In case of volume sharing, the volume is set for the root sink first,
+ * from which it's then propagated to the sharing sinks. */
+ root_sink = pa_sink_get_master(s);
+
+ if (PA_UNLIKELY(!root_sink))
+ return;
+
+ /* As a special exception we accept mono volumes on all sinks --
+ * even on those with more complex channel maps */
+
+ if (volume) {
+ if (pa_cvolume_compatible(volume, &s->sample_spec))
+ new_reference_volume = *volume;
+ else {
+ new_reference_volume = s->reference_volume;
+ pa_cvolume_scale(&new_reference_volume, pa_cvolume_max(volume));
+ }
+
+ pa_cvolume_remap(&new_reference_volume, &s->channel_map, &root_sink->channel_map);
+
+ if (update_reference_volume(root_sink, &new_reference_volume, &root_sink->channel_map, save)) {
+ if (pa_sink_flat_volume_enabled(root_sink)) {
+ /* OK, propagate this volume change back to the inputs */
+ propagate_reference_volume(root_sink);
+
+ /* And now recalculate the real volume */
+ compute_real_volume(root_sink);
+ } else
+ update_real_volume(root_sink, &root_sink->reference_volume, &root_sink->channel_map);
+ }
+
+ } else {
+ /* If volume is NULL we synchronize the sink's real and
+ * reference volumes with the stream volumes. */
+
+ pa_assert(pa_sink_flat_volume_enabled(root_sink));
+
+ /* Ok, let's determine the new real volume */
+ compute_real_volume(root_sink);
+
+ /* Let's 'push' the reference volume if necessary */
+ pa_cvolume_merge(&new_reference_volume, &s->reference_volume, &root_sink->real_volume);
+ /* If the sink and its root don't have the same number of channels, we need to remap */
+ if (s != root_sink && !pa_channel_map_equal(&s->channel_map, &root_sink->channel_map))
+ pa_cvolume_remap(&new_reference_volume, &s->channel_map, &root_sink->channel_map);
+ update_reference_volume(root_sink, &new_reference_volume, &root_sink->channel_map, save);
+
+ /* Now that the reference volume is updated, we can update the streams'
+ * reference ratios. */
+ compute_reference_ratios(root_sink);
+ }
+
+ if (root_sink->set_volume) {
+ /* If we have a function set_volume(), then we do not apply a
+ * soft volume by default. However, set_volume() is free to
+ * apply one to root_sink->soft_volume */
+
+ pa_cvolume_reset(&root_sink->soft_volume, root_sink->sample_spec.channels);
+ if (!(root_sink->flags & PA_SINK_DEFERRED_VOLUME))
+ root_sink->set_volume(root_sink);
+
+ } else
+ /* If we have no function set_volume(), then the soft volume
+ * becomes the real volume */
+ root_sink->soft_volume = root_sink->real_volume;
+
+ /* This tells the sink that soft volume and/or real volume changed */
+ if (send_msg)
+ pa_assert_se(pa_asyncmsgq_send(root_sink->asyncmsgq, PA_MSGOBJECT(root_sink), PA_SINK_MESSAGE_SET_SHARED_VOLUME, NULL, 0, NULL) == 0);
+}
+
+/* Called from the io thread if sync volume is used, otherwise from the main thread.
+ * Only to be called by sink implementor */
+void pa_sink_set_soft_volume(pa_sink *s, const pa_cvolume *volume) {
+
+ pa_sink_assert_ref(s);
+ pa_assert(!(s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER));
+
+ if (s->flags & PA_SINK_DEFERRED_VOLUME)
+ pa_sink_assert_io_context(s);
+ else
+ pa_assert_ctl_context();
+
+ if (!volume)
+ pa_cvolume_reset(&s->soft_volume, s->sample_spec.channels);
+ else
+ s->soft_volume = *volume;
+
+ if (PA_SINK_IS_LINKED(s->state) && !(s->flags & PA_SINK_DEFERRED_VOLUME))
+ pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_VOLUME, NULL, 0, NULL) == 0);
+ else
+ s->thread_info.soft_volume = s->soft_volume;
+}
+
+/* Called from the main thread. Only called for the root sink in volume sharing
+ * cases, except for internal recursive calls. */
+static void propagate_real_volume(pa_sink *s, const pa_cvolume *old_real_volume) {
+ pa_sink_input *i;
+ uint32_t idx;
+
+ pa_sink_assert_ref(s);
+ pa_assert(old_real_volume);
+ pa_assert_ctl_context();
+ pa_assert(PA_SINK_IS_LINKED(s->state));
+
+ /* This is called when the hardware's real volume changes due to
+ * some external event. We copy the real volume into our
+ * reference volume and then rebuild the stream volumes based on
+ * i->real_ratio which should stay fixed. */
+
+ if (!(s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)) {
+ if (pa_cvolume_equal(old_real_volume, &s->real_volume))
+ return;
+
+ /* 1. Make the real volume the reference volume */
+ update_reference_volume(s, &s->real_volume, &s->channel_map, true);
+ }
+
+ if (pa_sink_flat_volume_enabled(s)) {
+
+ PA_IDXSET_FOREACH(i, s->inputs, idx) {
+ pa_cvolume new_volume;
+
+ /* 2. Since the sink's reference and real volumes are equal
+ * now our ratios should be too. */
+ pa_sink_input_set_reference_ratio(i, &i->real_ratio);
+
+ /* 3. Recalculate the new stream reference volume based on the
+ * reference ratio and the sink's reference volume.
+ *
+ * This basically calculates:
+ *
+ * i->volume = s->reference_volume * i->reference_ratio
+ *
+ * This is identical to propagate_reference_volume() */
+ new_volume = s->reference_volume;
+ pa_cvolume_remap(&new_volume, &s->channel_map, &i->channel_map);
+ pa_sw_cvolume_multiply(&new_volume, &new_volume, &i->reference_ratio);
+ pa_sink_input_set_volume_direct(i, &new_volume);
+
+ if (i->origin_sink && (i->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)
+ && PA_SINK_IS_LINKED(i->origin_sink->state))
+ propagate_real_volume(i->origin_sink, old_real_volume);
+ }
+ }
+
+ /* Something got changed in the hardware. It probably makes sense
+ * to save changed hw settings given that hw volume changes not
+ * triggered by PA are almost certainly done by the user. */
+ if (!(s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER))
+ s->save_volume = true;
+}
+
+/* Called from io thread */
+void pa_sink_update_volume_and_mute(pa_sink *s) {
+ pa_assert(s);
+ pa_sink_assert_io_context(s);
+
+ pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_UPDATE_VOLUME_AND_MUTE, NULL, 0, NULL, NULL);
+}
+
+/* Called from main thread */
+const pa_cvolume *pa_sink_get_volume(pa_sink *s, bool force_refresh) {
+ pa_sink_assert_ref(s);
+ pa_assert_ctl_context();
+ pa_assert(PA_SINK_IS_LINKED(s->state));
+
+ if (s->refresh_volume || force_refresh) {
+ struct pa_cvolume old_real_volume;
+
+ pa_assert(!(s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER));
+
+ old_real_volume = s->real_volume;
+
+ if (!(s->flags & PA_SINK_DEFERRED_VOLUME) && s->get_volume)
+ s->get_volume(s);
+
+ pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_VOLUME, NULL, 0, NULL) == 0);
+
+ update_real_volume(s, &s->real_volume, &s->channel_map);
+ propagate_real_volume(s, &old_real_volume);
+ }
+
+ return &s->reference_volume;
+}
+
+/* Called from main thread. In volume sharing cases, only the root sink may
+ * call this. */
+void pa_sink_volume_changed(pa_sink *s, const pa_cvolume *new_real_volume) {
+ pa_cvolume old_real_volume;
+
+ pa_sink_assert_ref(s);
+ pa_assert_ctl_context();
+ pa_assert(PA_SINK_IS_LINKED(s->state));
+ pa_assert(!(s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER));
+
+ /* The sink implementor may call this if the volume changed to make sure everyone is notified */
+
+ old_real_volume = s->real_volume;
+ update_real_volume(s, new_real_volume, &s->channel_map);
+ propagate_real_volume(s, &old_real_volume);
+}
+
+/* Called from main thread */
+void pa_sink_set_mute(pa_sink *s, bool mute, bool save) {
+ bool old_muted;
+
+ pa_sink_assert_ref(s);
+ pa_assert_ctl_context();
+
+ old_muted = s->muted;
+
+ if (mute == old_muted) {
+ s->save_muted |= save;
+ return;
+ }
+
+ s->muted = mute;
+ s->save_muted = save;
+
+ if (!(s->flags & PA_SINK_DEFERRED_VOLUME) && s->set_mute) {
+ s->set_mute_in_progress = true;
+ s->set_mute(s);
+ s->set_mute_in_progress = false;
+ }
+
+ if (!PA_SINK_IS_LINKED(s->state))
+ return;
+
+ pa_log_debug("The mute of sink %s changed from %s to %s.", s->name, pa_yes_no(old_muted), pa_yes_no(mute));
+ pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_MUTE, NULL, 0, NULL) == 0);
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+ pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_MUTE_CHANGED], s);
+}
+
+/* Called from main thread */
+bool pa_sink_get_mute(pa_sink *s, bool force_refresh) {
+
+ pa_sink_assert_ref(s);
+ pa_assert_ctl_context();
+ pa_assert(PA_SINK_IS_LINKED(s->state));
+
+ if ((s->refresh_muted || force_refresh) && s->get_mute) {
+ bool mute;
+
+ if (s->flags & PA_SINK_DEFERRED_VOLUME) {
+ if (pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_MUTE, &mute, 0, NULL) >= 0)
+ pa_sink_mute_changed(s, mute);
+ } else {
+ if (s->get_mute(s, &mute) >= 0)
+ pa_sink_mute_changed(s, mute);
+ }
+ }
+
+ return s->muted;
+}
+
+/* Called from main thread */
+void pa_sink_mute_changed(pa_sink *s, bool new_muted) {
+ pa_sink_assert_ref(s);
+ pa_assert_ctl_context();
+ pa_assert(PA_SINK_IS_LINKED(s->state));
+
+ if (s->set_mute_in_progress)
+ return;
+
+ /* pa_sink_set_mute() does this same check, so this may appear redundant,
+ * but we must have this here also, because the save parameter of
+ * pa_sink_set_mute() would otherwise have unintended side effects (saving
+ * the mute state when it shouldn't be saved). */
+ if (new_muted == s->muted)
+ return;
+
+ pa_sink_set_mute(s, new_muted, true);
+}
+
+/* Called from main thread */
+bool pa_sink_update_proplist(pa_sink *s, pa_update_mode_t mode, pa_proplist *p) {
+ pa_sink_assert_ref(s);
+ pa_assert_ctl_context();
+
+ if (p)
+ pa_proplist_update(s->proplist, mode, p);
+
+ if (PA_SINK_IS_LINKED(s->state)) {
+ pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_PROPLIST_CHANGED], s);
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+ }
+
+ return true;
+}
+
+/* Called from main thread */
+/* FIXME -- this should be dropped and be merged into pa_sink_update_proplist() */
+void pa_sink_set_description(pa_sink *s, const char *description) {
+ const char *old;
+ pa_sink_assert_ref(s);
+ pa_assert_ctl_context();
+
+ if (!description && !pa_proplist_contains(s->proplist, PA_PROP_DEVICE_DESCRIPTION))
+ return;
+
+ old = pa_proplist_gets(s->proplist, PA_PROP_DEVICE_DESCRIPTION);
+
+ if (old && description && pa_streq(old, description))
+ return;
+
+ if (description)
+ pa_proplist_sets(s->proplist, PA_PROP_DEVICE_DESCRIPTION, description);
+ else
+ pa_proplist_unset(s->proplist, PA_PROP_DEVICE_DESCRIPTION);
+
+ if (s->monitor_source) {
+ char *n;
+
+ n = pa_sprintf_malloc("Monitor Source of %s", description ? description : s->name);
+ pa_source_set_description(s->monitor_source, n);
+ pa_xfree(n);
+ }
+
+ if (PA_SINK_IS_LINKED(s->state)) {
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+ pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_PROPLIST_CHANGED], s);
+ }
+}
+
+/* Called from main thread */
+unsigned pa_sink_linked_by(pa_sink *s) {
+ unsigned ret;
+
+ pa_sink_assert_ref(s);
+ pa_assert_ctl_context();
+ pa_assert(PA_SINK_IS_LINKED(s->state));
+
+ ret = pa_idxset_size(s->inputs);
+
+ /* We add in the number of streams connected to us here. Please
+ * note the asymmetry to pa_sink_used_by()! */
+
+ if (s->monitor_source)
+ ret += pa_source_linked_by(s->monitor_source);
+
+ return ret;
+}
+
+/* Called from main thread */
+unsigned pa_sink_used_by(pa_sink *s) {
+ unsigned ret;
+
+ pa_sink_assert_ref(s);
+ pa_assert_ctl_context();
+ pa_assert(PA_SINK_IS_LINKED(s->state));
+
+ ret = pa_idxset_size(s->inputs);
+ pa_assert(ret >= s->n_corked);
+
+ /* Streams connected to our monitor source do not matter for
+ * pa_sink_used_by()!.*/
+
+ return ret - s->n_corked;
+}
+
+/* Called from main thread */
+unsigned pa_sink_check_suspend(pa_sink *s, pa_sink_input *ignore_input, pa_source_output *ignore_output) {
+ unsigned ret;
+ pa_sink_input *i;
+ uint32_t idx;
+
+ pa_sink_assert_ref(s);
+ pa_assert_ctl_context();
+
+ if (!PA_SINK_IS_LINKED(s->state))
+ return 0;
+
+ ret = 0;
+
+ PA_IDXSET_FOREACH(i, s->inputs, idx) {
+ if (i == ignore_input)
+ continue;
+
+ /* We do not assert here. It is perfectly valid for a sink input to
+ * be in the INIT state (i.e. created, marked done but not yet put)
+ * and we should not care if it's unlinked as it won't contribute
+ * towards our busy status.
+ */
+ if (!PA_SINK_INPUT_IS_LINKED(i->state))
+ continue;
+
+ if (i->state == PA_SINK_INPUT_CORKED)
+ continue;
+
+ if (i->flags & PA_SINK_INPUT_DONT_INHIBIT_AUTO_SUSPEND)
+ continue;
+
+ ret ++;
+ }
+
+ if (s->monitor_source)
+ ret += pa_source_check_suspend(s->monitor_source, ignore_output);
+
+ return ret;
+}
+
+const char *pa_sink_state_to_string(pa_sink_state_t state) {
+ switch (state) {
+ case PA_SINK_INIT: return "INIT";
+ case PA_SINK_IDLE: return "IDLE";
+ case PA_SINK_RUNNING: return "RUNNING";
+ case PA_SINK_SUSPENDED: return "SUSPENDED";
+ case PA_SINK_UNLINKED: return "UNLINKED";
+ case PA_SINK_INVALID_STATE: return "INVALID_STATE";
+ }
+
+ pa_assert_not_reached();
+}
+
+/* Called from the IO thread */
+static void sync_input_volumes_within_thread(pa_sink *s) {
+ pa_sink_input *i;
+ void *state = NULL;
+
+ pa_sink_assert_ref(s);
+ pa_sink_assert_io_context(s);
+
+ PA_HASHMAP_FOREACH(i, s->thread_info.inputs, state) {
+ if (pa_cvolume_equal(&i->thread_info.soft_volume, &i->soft_volume))
+ continue;
+
+ i->thread_info.soft_volume = i->soft_volume;
+ pa_sink_input_request_rewind(i, 0, true, false, false);
+ }
+}
+
+/* Called from the IO thread. Only called for the root sink in volume sharing
+ * cases, except for internal recursive calls. */
+static void set_shared_volume_within_thread(pa_sink *s) {
+ pa_sink_input *i = NULL;
+ void *state = NULL;
+
+ pa_sink_assert_ref(s);
+
+ PA_MSGOBJECT(s)->process_msg(PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_VOLUME_SYNCED, NULL, 0, NULL);
+
+ PA_HASHMAP_FOREACH(i, s->thread_info.inputs, state) {
+ if (i->origin_sink && (i->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER))
+ set_shared_volume_within_thread(i->origin_sink);
+ }
+}
+
+/* Called from IO thread, except when it is not */
+int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk) {
+ pa_sink *s = PA_SINK(o);
+ pa_sink_assert_ref(s);
+
+ switch ((pa_sink_message_t) code) {
+
+ case PA_SINK_MESSAGE_ADD_INPUT: {
+ pa_sink_input *i = PA_SINK_INPUT(userdata);
+
+ /* If you change anything here, make sure to change the
+ * sink input handling a few lines down at
+ * PA_SINK_MESSAGE_FINISH_MOVE, too. */
+
+ pa_hashmap_put(s->thread_info.inputs, PA_UINT32_TO_PTR(i->index), pa_sink_input_ref(i));
+
+ /* Since the caller sleeps in pa_sink_input_put(), we can
+ * safely access data outside of thread_info even though
+ * it is mutable */
+
+ if ((i->thread_info.sync_prev = i->sync_prev)) {
+ pa_assert(i->sink == i->thread_info.sync_prev->sink);
+ pa_assert(i->sync_prev->sync_next == i);
+ i->thread_info.sync_prev->thread_info.sync_next = i;
+ }
+
+ if ((i->thread_info.sync_next = i->sync_next)) {
+ pa_assert(i->sink == i->thread_info.sync_next->sink);
+ pa_assert(i->sync_next->sync_prev == i);
+ i->thread_info.sync_next->thread_info.sync_prev = i;
+ }
+
+ pa_sink_input_attach(i);
+
+ pa_sink_input_set_state_within_thread(i, i->state);
+
+ /* The requested latency of the sink input needs to be fixed up and
+ * then configured on the sink. If this causes the sink latency to
+ * go down, the sink implementor is responsible for doing a rewind
+ * in the update_requested_latency() callback to ensure that the
+ * sink buffer doesn't contain more data than what the new latency
+ * allows.
+ *
+ * XXX: Does it really make sense to push this responsibility to
+ * the sink implementors? Wouldn't it be better to do it once in
+ * the core than many times in the modules? */
+
+ if (i->thread_info.requested_sink_latency != (pa_usec_t) -1)
+ pa_sink_input_set_requested_latency_within_thread(i, i->thread_info.requested_sink_latency);
+
+ pa_sink_input_update_max_rewind(i, s->thread_info.max_rewind);
+ pa_sink_input_update_max_request(i, s->thread_info.max_request);
+
+ /* We don't rewind here automatically. This is left to the
+ * sink input implementor because some sink inputs need a
+ * slow start, i.e. need some time to buffer client
+ * samples before beginning streaming.
+ *
+ * XXX: Does it really make sense to push this functionality to
+ * the sink implementors? Wouldn't it be better to do it once in
+ * the core than many times in the modules? */
+
+ /* In flat volume mode we need to update the volume as
+ * well */
+ return o->process_msg(o, PA_SINK_MESSAGE_SET_SHARED_VOLUME, NULL, 0, NULL);
+ }
+
+ case PA_SINK_MESSAGE_REMOVE_INPUT: {
+ pa_sink_input *i = PA_SINK_INPUT(userdata);
+
+ /* If you change anything here, make sure to change the
+ * sink input handling a few lines down at
+ * PA_SINK_MESSAGE_START_MOVE, too. */
+
+ pa_sink_input_detach(i);
+
+ pa_sink_input_set_state_within_thread(i, i->state);
+
+ /* Since the caller sleeps in pa_sink_input_unlink(),
+ * we can safely access data outside of thread_info even
+ * though it is mutable */
+
+ pa_assert(!i->sync_prev);
+ pa_assert(!i->sync_next);
+
+ if (i->thread_info.sync_prev) {
+ i->thread_info.sync_prev->thread_info.sync_next = i->thread_info.sync_prev->sync_next;
+ i->thread_info.sync_prev = NULL;
+ }
+
+ if (i->thread_info.sync_next) {
+ i->thread_info.sync_next->thread_info.sync_prev = i->thread_info.sync_next->sync_prev;
+ i->thread_info.sync_next = NULL;
+ }
+
+ pa_hashmap_remove_and_free(s->thread_info.inputs, PA_UINT32_TO_PTR(i->index));
+ pa_sink_invalidate_requested_latency(s, true);
+ pa_sink_request_rewind(s, (size_t) -1);
+
+ /* In flat volume mode we need to update the volume as
+ * well */
+ return o->process_msg(o, PA_SINK_MESSAGE_SET_SHARED_VOLUME, NULL, 0, NULL);
+ }
+
+ case PA_SINK_MESSAGE_START_MOVE: {
+ pa_sink_input *i = PA_SINK_INPUT(userdata);
+
+ /* We don't support moving synchronized streams. */
+ pa_assert(!i->sync_prev);
+ pa_assert(!i->sync_next);
+ pa_assert(!i->thread_info.sync_next);
+ pa_assert(!i->thread_info.sync_prev);
+
+ if (i->thread_info.state != PA_SINK_INPUT_CORKED) {
+ pa_usec_t usec = 0;
+ size_t sink_nbytes, total_nbytes;
+
+ /* The old sink probably has some audio from this
+ * stream in its buffer. We want to "take it back" as
+ * much as possible and play it to the new sink. We
+ * don't know at this point how much the old sink can
+ * rewind. We have to pick something, and that
+ * something is the full latency of the old sink here.
+ * So we rewind the stream buffer by the sink latency
+ * amount, which may be more than what we should
+ * rewind. This can result in a chunk of audio being
+ * played both to the old sink and the new sink.
+ *
+ * FIXME: Fix this code so that we don't have to make
+ * guesses about how much the sink will actually be
+ * able to rewind. If someone comes up with a solution
+ * for this, something to note is that the part of the
+ * latency that the old sink couldn't rewind should
+ * ideally be compensated after the stream has moved
+ * to the new sink by adding silence. The new sink
+ * most likely can't start playing the moved stream
+ * immediately, and that gap should be removed from
+ * the "compensation silence" (at least at the time of
+ * writing this, the move finish code will actually
+ * already take care of dropping the new sink's
+ * unrewindable latency, so taking into account the
+ * unrewindable latency of the old sink is the only
+ * problem).
+ *
+ * The render_memblockq contents are discarded,
+ * because when the sink changes, the format of the
+ * audio stored in the render_memblockq may change
+ * too, making the stored audio invalid. FIXME:
+ * However, the read and write indices are moved back
+ * the same amount, so if they are not the same now,
+ * they won't be the same after the rewind either. If
+ * the write index of the render_memblockq is ahead of
+ * the read index, then the render_memblockq will feed
+ * the new sink some silence first, which it shouldn't
+ * do. The write index should be flushed to be the
+ * same as the read index. */
+
+ /* Get the latency of the sink */
+ usec = pa_sink_get_latency_within_thread(s, false);
+ sink_nbytes = pa_usec_to_bytes(usec, &s->sample_spec);
+ total_nbytes = sink_nbytes + pa_memblockq_get_length(i->thread_info.render_memblockq);
+
+ if (total_nbytes > 0) {
+ i->thread_info.rewrite_nbytes = i->thread_info.resampler ? pa_resampler_request(i->thread_info.resampler, total_nbytes) : total_nbytes;
+ i->thread_info.rewrite_flush = true;
+ pa_sink_input_process_rewind(i, sink_nbytes);
+ }
+ }
+
+ pa_sink_input_detach(i);
+
+ /* Let's remove the sink input ...*/
+ pa_hashmap_remove_and_free(s->thread_info.inputs, PA_UINT32_TO_PTR(i->index));
+
+ pa_sink_invalidate_requested_latency(s, true);
+
+ pa_log_debug("Requesting rewind due to started move");
+ pa_sink_request_rewind(s, (size_t) -1);
+
+ /* In flat volume mode we need to update the volume as
+ * well */
+ return o->process_msg(o, PA_SINK_MESSAGE_SET_SHARED_VOLUME, NULL, 0, NULL);
+ }
+
+ case PA_SINK_MESSAGE_FINISH_MOVE: {
+ pa_sink_input *i = PA_SINK_INPUT(userdata);
+
+ /* We don't support moving synchronized streams. */
+ pa_assert(!i->sync_prev);
+ pa_assert(!i->sync_next);
+ pa_assert(!i->thread_info.sync_next);
+ pa_assert(!i->thread_info.sync_prev);
+
+ pa_hashmap_put(s->thread_info.inputs, PA_UINT32_TO_PTR(i->index), pa_sink_input_ref(i));
+
+ pa_sink_input_attach(i);
+
+ if (i->thread_info.state != PA_SINK_INPUT_CORKED) {
+ pa_usec_t usec = 0;
+ size_t nbytes;
+
+ /* In the ideal case the new sink would start playing
+ * the stream immediately. That requires the sink to
+ * be able to rewind all of its latency, which usually
+ * isn't possible, so there will probably be some gap
+ * before the moved stream becomes audible. We then
+ * have two possibilities: 1) start playing the stream
+ * from where it is now, or 2) drop the unrewindable
+ * latency of the sink from the stream. With option 1
+ * we won't lose any audio but the stream will have a
+ * pause. With option 2 we may lose some audio but the
+ * stream time will be somewhat in sync with the wall
+ * clock. Lennart seems to have chosen option 2 (one
+ * of the reasons might have been that option 1 is
+ * actually much harder to implement), so we drop the
+ * latency of the new sink from the moved stream and
+ * hope that the sink will undo most of that in the
+ * rewind. */
+
+ /* Get the latency of the sink */
+ usec = pa_sink_get_latency_within_thread(s, false);
+ nbytes = pa_usec_to_bytes(usec, &s->sample_spec);
+
+ if (nbytes > 0)
+ pa_sink_input_drop(i, nbytes);
+
+ pa_log_debug("Requesting rewind due to finished move");
+ pa_sink_request_rewind(s, nbytes);
+ }
+
+ /* Updating the requested sink latency has to be done
+ * after the sink rewind request, not before, because
+ * otherwise the sink may limit the rewind amount
+ * needlessly. */
+
+ if (i->thread_info.requested_sink_latency != (pa_usec_t) -1)
+ pa_sink_input_set_requested_latency_within_thread(i, i->thread_info.requested_sink_latency);
+
+ pa_sink_input_update_max_rewind(i, s->thread_info.max_rewind);
+ pa_sink_input_update_max_request(i, s->thread_info.max_request);
+
+ return o->process_msg(o, PA_SINK_MESSAGE_SET_SHARED_VOLUME, NULL, 0, NULL);
+ }
+
+ case PA_SINK_MESSAGE_SET_SHARED_VOLUME: {
+ pa_sink *root_sink = pa_sink_get_master(s);
+
+ if (PA_LIKELY(root_sink))
+ set_shared_volume_within_thread(root_sink);
+
+ return 0;
+ }
+
+ case PA_SINK_MESSAGE_SET_VOLUME_SYNCED:
+
+ if (s->flags & PA_SINK_DEFERRED_VOLUME) {
+ s->set_volume(s);
+ pa_sink_volume_change_push(s);
+ }
+ /* Fall through ... */
+
+ case PA_SINK_MESSAGE_SET_VOLUME:
+
+ if (!pa_cvolume_equal(&s->thread_info.soft_volume, &s->soft_volume)) {
+ s->thread_info.soft_volume = s->soft_volume;
+ pa_sink_request_rewind(s, (size_t) -1);
+ }
+
+ /* Fall through ... */
+
+ case PA_SINK_MESSAGE_SYNC_VOLUMES:
+ sync_input_volumes_within_thread(s);
+ return 0;
+
+ case PA_SINK_MESSAGE_GET_VOLUME:
+
+ if ((s->flags & PA_SINK_DEFERRED_VOLUME) && s->get_volume) {
+ s->get_volume(s);
+ pa_sink_volume_change_flush(s);
+ pa_sw_cvolume_divide(&s->thread_info.current_hw_volume, &s->real_volume, &s->soft_volume);
+ }
+
+ /* In case sink implementor reset SW volume. */
+ if (!pa_cvolume_equal(&s->thread_info.soft_volume, &s->soft_volume)) {
+ s->thread_info.soft_volume = s->soft_volume;
+ pa_sink_request_rewind(s, (size_t) -1);
+ }
+
+ return 0;
+
+ case PA_SINK_MESSAGE_SET_MUTE:
+
+ if (s->thread_info.soft_muted != s->muted) {
+ s->thread_info.soft_muted = s->muted;
+ pa_sink_request_rewind(s, (size_t) -1);
+ }
+
+ if (s->flags & PA_SINK_DEFERRED_VOLUME && s->set_mute)
+ s->set_mute(s);
+
+ return 0;
+
+ case PA_SINK_MESSAGE_GET_MUTE:
+
+ if (s->flags & PA_SINK_DEFERRED_VOLUME && s->get_mute)
+ return s->get_mute(s, userdata);
+
+ return 0;
+
+ case PA_SINK_MESSAGE_SET_STATE: {
+ struct set_state_data *data = userdata;
+ bool suspend_change =
+ (s->thread_info.state == PA_SINK_SUSPENDED && PA_SINK_IS_OPENED(data->state)) ||
+ (PA_SINK_IS_OPENED(s->thread_info.state) && data->state == PA_SINK_SUSPENDED);
+
+ if (s->set_state_in_io_thread) {
+ int r;
+
+ if ((r = s->set_state_in_io_thread(s, data->state, data->suspend_cause)) < 0)
+ return r;
+ }
+
+ s->thread_info.state = data->state;
+
+ if (s->thread_info.state == PA_SINK_SUSPENDED) {
+ s->thread_info.rewind_nbytes = 0;
+ s->thread_info.rewind_requested = false;
+ }
+
+ if (suspend_change) {
+ pa_sink_input *i;
+ void *state = NULL;
+
+ while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL)))
+ if (i->suspend_within_thread)
+ i->suspend_within_thread(i, s->thread_info.state == PA_SINK_SUSPENDED);
+ }
+
+ return 0;
+ }
+
+ case PA_SINK_MESSAGE_GET_REQUESTED_LATENCY: {
+
+ pa_usec_t *usec = userdata;
+ *usec = pa_sink_get_requested_latency_within_thread(s);
+
+ /* Yes, that's right, the IO thread will see -1 when no
+ * explicit requested latency is configured, the main
+ * thread will see max_latency */
+ if (*usec == (pa_usec_t) -1)
+ *usec = s->thread_info.max_latency;
+
+ return 0;
+ }
+
+ case PA_SINK_MESSAGE_SET_LATENCY_RANGE: {
+ pa_usec_t *r = userdata;
+
+ pa_sink_set_latency_range_within_thread(s, r[0], r[1]);
+
+ return 0;
+ }
+
+ case PA_SINK_MESSAGE_GET_LATENCY_RANGE: {
+ pa_usec_t *r = userdata;
+
+ r[0] = s->thread_info.min_latency;
+ r[1] = s->thread_info.max_latency;
+
+ return 0;
+ }
+
+ case PA_SINK_MESSAGE_GET_FIXED_LATENCY:
+
+ *((pa_usec_t*) userdata) = s->thread_info.fixed_latency;
+ return 0;
+
+ case PA_SINK_MESSAGE_SET_FIXED_LATENCY:
+
+ pa_sink_set_fixed_latency_within_thread(s, (pa_usec_t) offset);
+ return 0;
+
+ case PA_SINK_MESSAGE_GET_MAX_REWIND:
+
+ *((size_t*) userdata) = s->thread_info.max_rewind;
+ return 0;
+
+ case PA_SINK_MESSAGE_GET_MAX_REQUEST:
+
+ *((size_t*) userdata) = s->thread_info.max_request;
+ return 0;
+
+ case PA_SINK_MESSAGE_SET_MAX_REWIND:
+
+ pa_sink_set_max_rewind_within_thread(s, (size_t) offset);
+ return 0;
+
+ case PA_SINK_MESSAGE_SET_MAX_REQUEST:
+
+ pa_sink_set_max_request_within_thread(s, (size_t) offset);
+ return 0;
+
+ case PA_SINK_MESSAGE_UPDATE_VOLUME_AND_MUTE:
+ /* This message is sent from IO-thread and handled in main thread. */
+ pa_assert_ctl_context();
+
+ /* Make sure we're not messing with main thread when no longer linked */
+ if (!PA_SINK_IS_LINKED(s->state))
+ return 0;
+
+ pa_sink_get_volume(s, true);
+ pa_sink_get_mute(s, true);
+ return 0;
+
+ case PA_SINK_MESSAGE_SET_PORT_LATENCY_OFFSET:
+ s->thread_info.port_latency_offset = offset;
+ return 0;
+
+ case PA_SINK_MESSAGE_GET_LATENCY:
+ case PA_SINK_MESSAGE_MAX:
+ ;
+ }
+
+ return -1;
+}
+
+/* Called from main thread */
+int pa_sink_suspend_all(pa_core *c, bool suspend, pa_suspend_cause_t cause) {
+ pa_sink *sink;
+ uint32_t idx;
+ int ret = 0;
+
+ pa_core_assert_ref(c);
+ pa_assert_ctl_context();
+ pa_assert(cause != 0);
+
+ PA_IDXSET_FOREACH(sink, c->sinks, idx) {
+ int r;
+
+ if ((r = pa_sink_suspend(sink, suspend, cause)) < 0)
+ ret = r;
+ }
+
+ return ret;
+}
+
+/* Called from IO thread */
+void pa_sink_detach_within_thread(pa_sink *s) {
+ pa_sink_input *i;
+ void *state = NULL;
+
+ pa_sink_assert_ref(s);
+ pa_sink_assert_io_context(s);
+ pa_assert(PA_SINK_IS_LINKED(s->thread_info.state));
+
+ PA_HASHMAP_FOREACH(i, s->thread_info.inputs, state)
+ pa_sink_input_detach(i);
+
+ if (s->monitor_source)
+ pa_source_detach_within_thread(s->monitor_source);
+}
+
+/* Called from IO thread */
+void pa_sink_attach_within_thread(pa_sink *s) {
+ pa_sink_input *i;
+ void *state = NULL;
+
+ pa_sink_assert_ref(s);
+ pa_sink_assert_io_context(s);
+ pa_assert(PA_SINK_IS_LINKED(s->thread_info.state));
+
+ PA_HASHMAP_FOREACH(i, s->thread_info.inputs, state)
+ pa_sink_input_attach(i);
+
+ if (s->monitor_source)
+ pa_source_attach_within_thread(s->monitor_source);
+}
+
+/* Called from IO thread */
+void pa_sink_request_rewind(pa_sink*s, size_t nbytes) {
+ pa_sink_assert_ref(s);
+ pa_sink_assert_io_context(s);
+ pa_assert(PA_SINK_IS_LINKED(s->thread_info.state));
+
+ if (nbytes == (size_t) -1)
+ nbytes = s->thread_info.max_rewind;
+
+ nbytes = PA_MIN(nbytes, s->thread_info.max_rewind);
+
+ if (s->thread_info.rewind_requested &&
+ nbytes <= s->thread_info.rewind_nbytes)
+ return;
+
+ s->thread_info.rewind_nbytes = nbytes;
+ s->thread_info.rewind_requested = true;
+
+ if (s->request_rewind)
+ s->request_rewind(s);
+}
+
+/* Called from IO thread */
+pa_usec_t pa_sink_get_requested_latency_within_thread(pa_sink *s) {
+ pa_usec_t result = (pa_usec_t) -1;
+ pa_sink_input *i;
+ void *state = NULL;
+ pa_usec_t monitor_latency;
+
+ pa_sink_assert_ref(s);
+ pa_sink_assert_io_context(s);
+
+ if (!(s->flags & PA_SINK_DYNAMIC_LATENCY))
+ return PA_CLAMP(s->thread_info.fixed_latency, s->thread_info.min_latency, s->thread_info.max_latency);
+
+ if (s->thread_info.requested_latency_valid)
+ return s->thread_info.requested_latency;
+
+ PA_HASHMAP_FOREACH(i, s->thread_info.inputs, state)
+ if (i->thread_info.requested_sink_latency != (pa_usec_t) -1 &&
+ (result == (pa_usec_t) -1 || result > i->thread_info.requested_sink_latency))
+ result = i->thread_info.requested_sink_latency;
+
+ monitor_latency = pa_source_get_requested_latency_within_thread(s->monitor_source);
+
+ if (monitor_latency != (pa_usec_t) -1 &&
+ (result == (pa_usec_t) -1 || result > monitor_latency))
+ result = monitor_latency;
+
+ if (result != (pa_usec_t) -1)
+ result = PA_CLAMP(result, s->thread_info.min_latency, s->thread_info.max_latency);
+
+ if (PA_SINK_IS_LINKED(s->thread_info.state)) {
+ /* Only cache if properly initialized */
+ s->thread_info.requested_latency = result;
+ s->thread_info.requested_latency_valid = true;
+ }
+
+ return result;
+}
+
+/* Called from main thread */
+pa_usec_t pa_sink_get_requested_latency(pa_sink *s) {
+ pa_usec_t usec = 0;
+
+ pa_sink_assert_ref(s);
+ pa_assert_ctl_context();
+ pa_assert(PA_SINK_IS_LINKED(s->state));
+
+ if (s->state == PA_SINK_SUSPENDED)
+ return 0;
+
+ pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_REQUESTED_LATENCY, &usec, 0, NULL) == 0);
+
+ return usec;
+}
+
+/* Called from IO as well as the main thread -- the latter only before the IO thread started up */
+void pa_sink_set_max_rewind_within_thread(pa_sink *s, size_t max_rewind) {
+ pa_sink_input *i;
+ void *state = NULL;
+
+ pa_sink_assert_ref(s);
+ pa_sink_assert_io_context(s);
+
+ if (max_rewind == s->thread_info.max_rewind)
+ return;
+
+ s->thread_info.max_rewind = max_rewind;
+
+ if (PA_SINK_IS_LINKED(s->thread_info.state))
+ PA_HASHMAP_FOREACH(i, s->thread_info.inputs, state)
+ pa_sink_input_update_max_rewind(i, s->thread_info.max_rewind);
+
+ if (s->monitor_source)
+ pa_source_set_max_rewind_within_thread(s->monitor_source, s->thread_info.max_rewind);
+}
+
+/* Called from main thread */
+void pa_sink_set_max_rewind(pa_sink *s, size_t max_rewind) {
+ pa_sink_assert_ref(s);
+ pa_assert_ctl_context();
+
+ if (PA_SINK_IS_LINKED(s->state))
+ pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_MAX_REWIND, NULL, max_rewind, NULL) == 0);
+ else
+ pa_sink_set_max_rewind_within_thread(s, max_rewind);
+}
+
+/* Called from IO as well as the main thread -- the latter only before the IO thread started up */
+void pa_sink_set_max_request_within_thread(pa_sink *s, size_t max_request) {
+ void *state = NULL;
+
+ pa_sink_assert_ref(s);
+ pa_sink_assert_io_context(s);
+
+ if (max_request == s->thread_info.max_request)
+ return;
+
+ s->thread_info.max_request = max_request;
+
+ if (PA_SINK_IS_LINKED(s->thread_info.state)) {
+ pa_sink_input *i;
+
+ PA_HASHMAP_FOREACH(i, s->thread_info.inputs, state)
+ pa_sink_input_update_max_request(i, s->thread_info.max_request);
+ }
+}
+
+/* Called from main thread */
+void pa_sink_set_max_request(pa_sink *s, size_t max_request) {
+ pa_sink_assert_ref(s);
+ pa_assert_ctl_context();
+
+ if (PA_SINK_IS_LINKED(s->state))
+ pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_MAX_REQUEST, NULL, max_request, NULL) == 0);
+ else
+ pa_sink_set_max_request_within_thread(s, max_request);
+}
+
+/* Called from IO thread */
+void pa_sink_invalidate_requested_latency(pa_sink *s, bool dynamic) {
+ pa_sink_input *i;
+ void *state = NULL;
+
+ pa_sink_assert_ref(s);
+ pa_sink_assert_io_context(s);
+
+ if ((s->flags & PA_SINK_DYNAMIC_LATENCY))
+ s->thread_info.requested_latency_valid = false;
+ else if (dynamic)
+ return;
+
+ if (PA_SINK_IS_LINKED(s->thread_info.state)) {
+
+ if (s->update_requested_latency)
+ s->update_requested_latency(s);
+
+ PA_HASHMAP_FOREACH(i, s->thread_info.inputs, state)
+ if (i->update_sink_requested_latency)
+ i->update_sink_requested_latency(i);
+ }
+}
+
+/* Called from main thread */
+void pa_sink_set_latency_range(pa_sink *s, pa_usec_t min_latency, pa_usec_t max_latency) {
+ pa_sink_assert_ref(s);
+ pa_assert_ctl_context();
+
+ /* min_latency == 0: no limit
+ * min_latency anything else: specified limit
+ *
+ * Similar for max_latency */
+
+ if (min_latency < ABSOLUTE_MIN_LATENCY)
+ min_latency = ABSOLUTE_MIN_LATENCY;
+
+ if (max_latency <= 0 ||
+ max_latency > ABSOLUTE_MAX_LATENCY)
+ max_latency = ABSOLUTE_MAX_LATENCY;
+
+ pa_assert(min_latency <= max_latency);
+
+ /* Hmm, let's see if someone forgot to set PA_SINK_DYNAMIC_LATENCY here... */
+ pa_assert((min_latency == ABSOLUTE_MIN_LATENCY &&
+ max_latency == ABSOLUTE_MAX_LATENCY) ||
+ (s->flags & PA_SINK_DYNAMIC_LATENCY));
+
+ if (PA_SINK_IS_LINKED(s->state)) {
+ pa_usec_t r[2];
+
+ r[0] = min_latency;
+ r[1] = max_latency;
+
+ pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_LATENCY_RANGE, r, 0, NULL) == 0);
+ } else
+ pa_sink_set_latency_range_within_thread(s, min_latency, max_latency);
+}
+
+/* Called from main thread */
+void pa_sink_get_latency_range(pa_sink *s, pa_usec_t *min_latency, pa_usec_t *max_latency) {
+ pa_sink_assert_ref(s);
+ pa_assert_ctl_context();
+ pa_assert(min_latency);
+ pa_assert(max_latency);
+
+ if (PA_SINK_IS_LINKED(s->state)) {
+ pa_usec_t r[2] = { 0, 0 };
+
+ pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_LATENCY_RANGE, r, 0, NULL) == 0);
+
+ *min_latency = r[0];
+ *max_latency = r[1];
+ } else {
+ *min_latency = s->thread_info.min_latency;
+ *max_latency = s->thread_info.max_latency;
+ }
+}
+
+/* Called from IO thread */
+void pa_sink_set_latency_range_within_thread(pa_sink *s, pa_usec_t min_latency, pa_usec_t max_latency) {
+ pa_sink_assert_ref(s);
+ pa_sink_assert_io_context(s);
+
+ pa_assert(min_latency >= ABSOLUTE_MIN_LATENCY);
+ pa_assert(max_latency <= ABSOLUTE_MAX_LATENCY);
+ pa_assert(min_latency <= max_latency);
+
+ /* Hmm, let's see if someone forgot to set PA_SINK_DYNAMIC_LATENCY here... */
+ pa_assert((min_latency == ABSOLUTE_MIN_LATENCY &&
+ max_latency == ABSOLUTE_MAX_LATENCY) ||
+ (s->flags & PA_SINK_DYNAMIC_LATENCY));
+
+ if (s->thread_info.min_latency == min_latency &&
+ s->thread_info.max_latency == max_latency)
+ return;
+
+ s->thread_info.min_latency = min_latency;
+ s->thread_info.max_latency = max_latency;
+
+ if (PA_SINK_IS_LINKED(s->thread_info.state)) {
+ pa_sink_input *i;
+ void *state = NULL;
+
+ PA_HASHMAP_FOREACH(i, s->thread_info.inputs, state)
+ if (i->update_sink_latency_range)
+ i->update_sink_latency_range(i);
+ }
+
+ pa_sink_invalidate_requested_latency(s, false);
+
+ pa_source_set_latency_range_within_thread(s->monitor_source, min_latency, max_latency);
+}
+
+/* Called from main thread */
+void pa_sink_set_fixed_latency(pa_sink *s, pa_usec_t latency) {
+ pa_sink_assert_ref(s);
+ pa_assert_ctl_context();
+
+ if (s->flags & PA_SINK_DYNAMIC_LATENCY) {
+ pa_assert(latency == 0);
+ return;
+ }
+
+ if (latency < ABSOLUTE_MIN_LATENCY)
+ latency = ABSOLUTE_MIN_LATENCY;
+
+ if (latency > ABSOLUTE_MAX_LATENCY)
+ latency = ABSOLUTE_MAX_LATENCY;
+
+ if (PA_SINK_IS_LINKED(s->state))
+ pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_FIXED_LATENCY, NULL, (int64_t) latency, NULL) == 0);
+ else
+ s->thread_info.fixed_latency = latency;
+
+ pa_source_set_fixed_latency(s->monitor_source, latency);
+}
+
+/* Called from main thread */
+pa_usec_t pa_sink_get_fixed_latency(pa_sink *s) {
+ pa_usec_t latency;
+
+ pa_sink_assert_ref(s);
+ pa_assert_ctl_context();
+
+ if (s->flags & PA_SINK_DYNAMIC_LATENCY)
+ return 0;
+
+ if (PA_SINK_IS_LINKED(s->state))
+ pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_FIXED_LATENCY, &latency, 0, NULL) == 0);
+ else
+ latency = s->thread_info.fixed_latency;
+
+ return latency;
+}
+
+/* Called from IO thread */
+void pa_sink_set_fixed_latency_within_thread(pa_sink *s, pa_usec_t latency) {
+ pa_sink_assert_ref(s);
+ pa_sink_assert_io_context(s);
+
+ if (s->flags & PA_SINK_DYNAMIC_LATENCY) {
+ pa_assert(latency == 0);
+ s->thread_info.fixed_latency = 0;
+
+ if (s->monitor_source)
+ pa_source_set_fixed_latency_within_thread(s->monitor_source, 0);
+
+ return;
+ }
+
+ pa_assert(latency >= ABSOLUTE_MIN_LATENCY);
+ pa_assert(latency <= ABSOLUTE_MAX_LATENCY);
+
+ if (s->thread_info.fixed_latency == latency)
+ return;
+
+ s->thread_info.fixed_latency = latency;
+
+ if (PA_SINK_IS_LINKED(s->thread_info.state)) {
+ pa_sink_input *i;
+ void *state = NULL;
+
+ PA_HASHMAP_FOREACH(i, s->thread_info.inputs, state)
+ if (i->update_sink_fixed_latency)
+ i->update_sink_fixed_latency(i);
+ }
+
+ pa_sink_invalidate_requested_latency(s, false);
+
+ pa_source_set_fixed_latency_within_thread(s->monitor_source, latency);
+}
+
+/* Called from main context */
+void pa_sink_set_port_latency_offset(pa_sink *s, int64_t offset) {
+ pa_sink_assert_ref(s);
+
+ s->port_latency_offset = offset;
+
+ if (PA_SINK_IS_LINKED(s->state))
+ pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_PORT_LATENCY_OFFSET, NULL, offset, NULL) == 0);
+ else
+ s->thread_info.port_latency_offset = offset;
+
+ pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_PORT_LATENCY_OFFSET_CHANGED], s);
+}
+
+/* Called from main context */
+size_t pa_sink_get_max_rewind(pa_sink *s) {
+ size_t r;
+ pa_assert_ctl_context();
+ pa_sink_assert_ref(s);
+
+ if (!PA_SINK_IS_LINKED(s->state))
+ return s->thread_info.max_rewind;
+
+ pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_MAX_REWIND, &r, 0, NULL) == 0);
+
+ return r;
+}
+
+/* Called from main context */
+size_t pa_sink_get_max_request(pa_sink *s) {
+ size_t r;
+ pa_sink_assert_ref(s);
+ pa_assert_ctl_context();
+
+ if (!PA_SINK_IS_LINKED(s->state))
+ return s->thread_info.max_request;
+
+ pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_MAX_REQUEST, &r, 0, NULL) == 0);
+
+ return r;
+}
+
+/* Called from main context */
+int pa_sink_set_port(pa_sink *s, const char *name, bool save) {
+ pa_device_port *port;
+
+ pa_sink_assert_ref(s);
+ pa_assert_ctl_context();
+
+ if (!s->set_port) {
+ pa_log_debug("set_port() operation not implemented for sink %u \"%s\"", s->index, s->name);
+ return -PA_ERR_NOTIMPLEMENTED;
+ }
+
+ if (!name)
+ return -PA_ERR_NOENTITY;
+
+ if (!(port = pa_hashmap_get(s->ports, name)))
+ return -PA_ERR_NOENTITY;
+
+ if (s->active_port == port) {
+ s->save_port = s->save_port || save;
+ return 0;
+ }
+
+ if (s->set_port(s, port) < 0)
+ return -PA_ERR_NOENTITY;
+
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+
+ pa_log_info("Changed port of sink %u \"%s\" to %s", s->index, s->name, port->name);
+
+ s->active_port = port;
+ s->save_port = save;
+
+ pa_sink_set_port_latency_offset(s, s->active_port->latency_offset);
+
+ /* The active port affects the default sink selection. */
+ pa_core_update_default_sink(s->core);
+
+ pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_PORT_CHANGED], s);
+
+ return 0;
+}
+
+bool pa_device_init_icon(pa_proplist *p, bool is_sink) {
+ const char *ff, *c, *t = NULL, *s = "", *profile, *bus;
+
+ pa_assert(p);
+
+ if (pa_proplist_contains(p, PA_PROP_DEVICE_ICON_NAME))
+ return true;
+
+ if ((ff = pa_proplist_gets(p, PA_PROP_DEVICE_FORM_FACTOR))) {
+
+ if (pa_streq(ff, "microphone"))
+ t = "audio-input-microphone";
+ else if (pa_streq(ff, "webcam"))
+ t = "camera-web";
+ else if (pa_streq(ff, "computer"))
+ t = "computer";
+ else if (pa_streq(ff, "handset"))
+ t = "phone";
+ else if (pa_streq(ff, "portable"))
+ t = "multimedia-player";
+ else if (pa_streq(ff, "tv"))
+ t = "video-display";
+
+ /*
+ * The following icons are not part of the icon naming spec,
+ * because Rodney Dawes sucks as the maintainer of that spec.
+ *
+ * http://lists.freedesktop.org/archives/xdg/2009-May/010397.html
+ */
+ else if (pa_streq(ff, "headset"))
+ t = "audio-headset";
+ else if (pa_streq(ff, "headphone"))
+ t = "audio-headphones";
+ else if (pa_streq(ff, "speaker"))
+ t = "audio-speakers";
+ else if (pa_streq(ff, "hands-free"))
+ t = "audio-handsfree";
+ }
+
+ if (!t)
+ if ((c = pa_proplist_gets(p, PA_PROP_DEVICE_CLASS)))
+ if (pa_streq(c, "modem"))
+ t = "modem";
+
+ if (!t) {
+ if (is_sink)
+ t = "audio-card";
+ else
+ t = "audio-input-microphone";
+ }
+
+ if ((profile = pa_proplist_gets(p, PA_PROP_DEVICE_PROFILE_NAME))) {
+ if (strstr(profile, "analog"))
+ s = "-analog";
+ else if (strstr(profile, "iec958"))
+ s = "-iec958";
+ else if (strstr(profile, "hdmi"))
+ s = "-hdmi";
+ }
+
+ bus = pa_proplist_gets(p, PA_PROP_DEVICE_BUS);
+
+ pa_proplist_setf(p, PA_PROP_DEVICE_ICON_NAME, "%s%s%s%s", t, pa_strempty(s), bus ? "-" : "", pa_strempty(bus));
+
+ return true;
+}
+
+bool pa_device_init_description(pa_proplist *p, pa_card *card) {
+ const char *s, *d = NULL, *k;
+ pa_assert(p);
+
+ if (pa_proplist_contains(p, PA_PROP_DEVICE_DESCRIPTION))
+ return true;
+
+ if (card)
+ if ((s = pa_proplist_gets(card->proplist, PA_PROP_DEVICE_DESCRIPTION)))
+ d = s;
+
+ if (!d)
+ if ((s = pa_proplist_gets(p, PA_PROP_DEVICE_FORM_FACTOR)))
+ if (pa_streq(s, "internal"))
+ d = _("Built-in Audio");
+
+ if (!d)
+ if ((s = pa_proplist_gets(p, PA_PROP_DEVICE_CLASS)))
+ if (pa_streq(s, "modem"))
+ d = _("Modem");
+
+ if (!d)
+ d = pa_proplist_gets(p, PA_PROP_DEVICE_PRODUCT_NAME);
+
+ if (!d)
+ return false;
+
+ k = pa_proplist_gets(p, PA_PROP_DEVICE_PROFILE_DESCRIPTION);
+
+ if (d && k)
+ pa_proplist_setf(p, PA_PROP_DEVICE_DESCRIPTION, "%s %s", d, k);
+ else if (d)
+ pa_proplist_sets(p, PA_PROP_DEVICE_DESCRIPTION, d);
+
+ return true;
+}
+
+bool pa_device_init_intended_roles(pa_proplist *p) {
+ const char *s;
+ pa_assert(p);
+
+ if (pa_proplist_contains(p, PA_PROP_DEVICE_INTENDED_ROLES))
+ return true;
+
+ if ((s = pa_proplist_gets(p, PA_PROP_DEVICE_FORM_FACTOR)))
+ if (pa_streq(s, "handset") || pa_streq(s, "hands-free")
+ || pa_streq(s, "headset")) {
+ pa_proplist_sets(p, PA_PROP_DEVICE_INTENDED_ROLES, "phone");
+ return true;
+ }
+
+ return false;
+}
+
+unsigned pa_device_init_priority(pa_proplist *p) {
+ const char *s;
+ unsigned priority = 0;
+
+ pa_assert(p);
+
+ if ((s = pa_proplist_gets(p, PA_PROP_DEVICE_CLASS))) {
+
+ if (pa_streq(s, "sound"))
+ priority += 9000;
+ else if (!pa_streq(s, "modem"))
+ priority += 1000;
+ }
+
+ if ((s = pa_proplist_gets(p, PA_PROP_DEVICE_FORM_FACTOR))) {
+
+ if (pa_streq(s, "headphone"))
+ priority += 900;
+ else if (pa_streq(s, "hifi"))
+ priority += 600;
+ else if (pa_streq(s, "speaker"))
+ priority += 500;
+ else if (pa_streq(s, "portable"))
+ priority += 450;
+ }
+
+ if ((s = pa_proplist_gets(p, PA_PROP_DEVICE_BUS))) {
+
+ if (pa_streq(s, "bluetooth"))
+ priority += 50;
+ else if (pa_streq(s, "usb"))
+ priority += 40;
+ else if (pa_streq(s, "pci"))
+ priority += 30;
+ }
+
+ if ((s = pa_proplist_gets(p, PA_PROP_DEVICE_PROFILE_NAME))) {
+
+ if (pa_startswith(s, "analog-"))
+ priority += 9;
+ else if (pa_startswith(s, "iec958-"))
+ priority += 8;
+ }
+
+ return priority;
+}
+
+PA_STATIC_FLIST_DECLARE(pa_sink_volume_change, 0, pa_xfree);
+
+/* Called from the IO thread. */
+static pa_sink_volume_change *pa_sink_volume_change_new(pa_sink *s) {
+ pa_sink_volume_change *c;
+ if (!(c = pa_flist_pop(PA_STATIC_FLIST_GET(pa_sink_volume_change))))
+ c = pa_xnew(pa_sink_volume_change, 1);
+
+ PA_LLIST_INIT(pa_sink_volume_change, c);
+ c->at = 0;
+ pa_cvolume_reset(&c->hw_volume, s->sample_spec.channels);
+ return c;
+}
+
+/* Called from the IO thread. */
+static void pa_sink_volume_change_free(pa_sink_volume_change *c) {
+ pa_assert(c);
+ if (pa_flist_push(PA_STATIC_FLIST_GET(pa_sink_volume_change), c) < 0)
+ pa_xfree(c);
+}
+
+/* Called from the IO thread. */
+void pa_sink_volume_change_push(pa_sink *s) {
+ pa_sink_volume_change *c = NULL;
+ pa_sink_volume_change *nc = NULL;
+ pa_sink_volume_change *pc = NULL;
+ uint32_t safety_margin = s->thread_info.volume_change_safety_margin;
+
+ const char *direction = NULL;
+
+ pa_assert(s);
+ nc = pa_sink_volume_change_new(s);
+
+ /* NOTE: There is already more different volumes in pa_sink that I can remember.
+ * Adding one more volume for HW would get us rid of this, but I am trying
+ * to survive with the ones we already have. */
+ pa_sw_cvolume_divide(&nc->hw_volume, &s->real_volume, &s->soft_volume);
+
+ if (!s->thread_info.volume_changes && pa_cvolume_equal(&nc->hw_volume, &s->thread_info.current_hw_volume)) {
+ pa_log_debug("Volume not changing");
+ pa_sink_volume_change_free(nc);
+ return;
+ }
+
+ nc->at = pa_sink_get_latency_within_thread(s, false);
+ nc->at += pa_rtclock_now() + s->thread_info.volume_change_extra_delay;
+
+ if (s->thread_info.volume_changes_tail) {
+ for (c = s->thread_info.volume_changes_tail; c; c = c->prev) {
+ /* If volume is going up let's do it a bit late. If it is going
+ * down let's do it a bit early. */
+ if (pa_cvolume_avg(&nc->hw_volume) > pa_cvolume_avg(&c->hw_volume)) {
+ if (nc->at + safety_margin > c->at) {
+ nc->at += safety_margin;
+ direction = "up";
+ break;
+ }
+ }
+ else if (nc->at - safety_margin > c->at) {
+ nc->at -= safety_margin;
+ direction = "down";
+ break;
+ }
+ }
+ }
+
+ if (c == NULL) {
+ if (pa_cvolume_avg(&nc->hw_volume) > pa_cvolume_avg(&s->thread_info.current_hw_volume)) {
+ nc->at += safety_margin;
+ direction = "up";
+ } else {
+ nc->at -= safety_margin;
+ direction = "down";
+ }
+ PA_LLIST_PREPEND(pa_sink_volume_change, s->thread_info.volume_changes, nc);
+ }
+ else {
+ PA_LLIST_INSERT_AFTER(pa_sink_volume_change, s->thread_info.volume_changes, c, nc);
+ }
+
+ pa_log_debug("Volume going %s to %d at %llu", direction, pa_cvolume_avg(&nc->hw_volume), (long long unsigned) nc->at);
+
+ /* We can ignore volume events that came earlier but should happen later than this. */
+ PA_LLIST_FOREACH_SAFE(c, pc, nc->next) {
+ pa_log_debug("Volume change to %d at %llu was dropped", pa_cvolume_avg(&c->hw_volume), (long long unsigned) c->at);
+ pa_sink_volume_change_free(c);
+ }
+ nc->next = NULL;
+ s->thread_info.volume_changes_tail = nc;
+}
+
+/* Called from the IO thread. */
+static void pa_sink_volume_change_flush(pa_sink *s) {
+ pa_sink_volume_change *c = s->thread_info.volume_changes;
+ pa_assert(s);
+ s->thread_info.volume_changes = NULL;
+ s->thread_info.volume_changes_tail = NULL;
+ while (c) {
+ pa_sink_volume_change *next = c->next;
+ pa_sink_volume_change_free(c);
+ c = next;
+ }
+}
+
+/* Called from the IO thread. */
+bool pa_sink_volume_change_apply(pa_sink *s, pa_usec_t *usec_to_next) {
+ pa_usec_t now;
+ bool ret = false;
+
+ pa_assert(s);
+
+ if (!s->thread_info.volume_changes || !PA_SINK_IS_LINKED(s->state)) {
+ if (usec_to_next)
+ *usec_to_next = 0;
+ return ret;
+ }
+
+ pa_assert(s->write_volume);
+
+ now = pa_rtclock_now();
+
+ while (s->thread_info.volume_changes && now >= s->thread_info.volume_changes->at) {
+ pa_sink_volume_change *c = s->thread_info.volume_changes;
+ PA_LLIST_REMOVE(pa_sink_volume_change, s->thread_info.volume_changes, c);
+ pa_log_debug("Volume change to %d at %llu was written %llu usec late",
+ pa_cvolume_avg(&c->hw_volume), (long long unsigned) c->at, (long long unsigned) (now - c->at));
+ ret = true;
+ s->thread_info.current_hw_volume = c->hw_volume;
+ pa_sink_volume_change_free(c);
+ }
+
+ if (ret)
+ s->write_volume(s);
+
+ if (s->thread_info.volume_changes) {
+ if (usec_to_next)
+ *usec_to_next = s->thread_info.volume_changes->at - now;
+ if (pa_log_ratelimit(PA_LOG_DEBUG))
+ pa_log_debug("Next volume change in %lld usec", (long long) (s->thread_info.volume_changes->at - now));
+ }
+ else {
+ if (usec_to_next)
+ *usec_to_next = 0;
+ s->thread_info.volume_changes_tail = NULL;
+ }
+ return ret;
+}
+
+/* Called from the IO thread. */
+static void pa_sink_volume_change_rewind(pa_sink *s, size_t nbytes) {
+ /* All the queued volume events later than current latency are shifted to happen earlier. */
+ pa_sink_volume_change *c;
+ pa_volume_t prev_vol = pa_cvolume_avg(&s->thread_info.current_hw_volume);
+ pa_usec_t rewound = pa_bytes_to_usec(nbytes, &s->sample_spec);
+ pa_usec_t limit = pa_sink_get_latency_within_thread(s, false);
+
+ pa_log_debug("latency = %lld", (long long) limit);
+ limit += pa_rtclock_now() + s->thread_info.volume_change_extra_delay;
+
+ PA_LLIST_FOREACH(c, s->thread_info.volume_changes) {
+ pa_usec_t modified_limit = limit;
+ if (prev_vol > pa_cvolume_avg(&c->hw_volume))
+ modified_limit -= s->thread_info.volume_change_safety_margin;
+ else
+ modified_limit += s->thread_info.volume_change_safety_margin;
+ if (c->at > modified_limit) {
+ c->at -= rewound;
+ if (c->at < modified_limit)
+ c->at = modified_limit;
+ }
+ prev_vol = pa_cvolume_avg(&c->hw_volume);
+ }
+ pa_sink_volume_change_apply(s, NULL);
+}
+
+/* Called from the main thread */
+/* Gets the list of formats supported by the sink. The members and idxset must
+ * be freed by the caller. */
+pa_idxset* pa_sink_get_formats(pa_sink *s) {
+ pa_idxset *ret;
+
+ pa_assert(s);
+
+ if (s->get_formats) {
+ /* Sink supports format query, all is good */
+ ret = s->get_formats(s);
+ } else {
+ /* Sink doesn't support format query, so assume it does PCM */
+ pa_format_info *f = pa_format_info_new();
+ f->encoding = PA_ENCODING_PCM;
+
+ ret = pa_idxset_new(NULL, NULL);
+ pa_idxset_put(ret, f, NULL);
+ }
+
+ return ret;
+}
+
+/* Called from the main thread */
+/* Allows an external source to set what formats a sink supports if the sink
+ * permits this. The function makes a copy of the formats on success. */
+bool pa_sink_set_formats(pa_sink *s, pa_idxset *formats) {
+ pa_assert(s);
+ pa_assert(formats);
+
+ if (s->set_formats)
+ /* Sink supports setting formats -- let's give it a shot */
+ return s->set_formats(s, formats);
+ else
+ /* Sink doesn't support setting this -- bail out */
+ return false;
+}
+
+/* Called from the main thread */
+/* Checks if the sink can accept this format */
+bool pa_sink_check_format(pa_sink *s, pa_format_info *f) {
+ pa_idxset *formats = NULL;
+ bool ret = false;
+
+ pa_assert(s);
+ pa_assert(f);
+
+ formats = pa_sink_get_formats(s);
+
+ if (formats) {
+ pa_format_info *finfo_device;
+ uint32_t i;
+
+ PA_IDXSET_FOREACH(finfo_device, formats, i) {
+ if (pa_format_info_is_compatible(finfo_device, f)) {
+ ret = true;
+ break;
+ }
+ }
+
+ pa_idxset_free(formats, (pa_free_cb_t) pa_format_info_free);
+ }
+
+ return ret;
+}
+
+/* Called from the main thread */
+/* Calculates the intersection between formats supported by the sink and
+ * in_formats, and returns these, in the order of the sink's formats. */
+pa_idxset* pa_sink_check_formats(pa_sink *s, pa_idxset *in_formats) {
+ pa_idxset *out_formats = pa_idxset_new(NULL, NULL), *sink_formats = NULL;
+ pa_format_info *f_sink, *f_in;
+ uint32_t i, j;
+
+ pa_assert(s);
+
+ if (!in_formats || pa_idxset_isempty(in_formats))
+ goto done;
+
+ sink_formats = pa_sink_get_formats(s);
+
+ PA_IDXSET_FOREACH(f_sink, sink_formats, i) {
+ PA_IDXSET_FOREACH(f_in, in_formats, j) {
+ if (pa_format_info_is_compatible(f_sink, f_in))
+ pa_idxset_put(out_formats, pa_format_info_copy(f_in), NULL);
+ }
+ }
+
+done:
+ if (sink_formats)
+ pa_idxset_free(sink_formats, (pa_free_cb_t) pa_format_info_free);
+
+ return out_formats;
+}
+
+/* Called from the main thread */
+void pa_sink_set_sample_format(pa_sink *s, pa_sample_format_t format) {
+ pa_sample_format_t old_format;
+
+ pa_assert(s);
+ pa_assert(pa_sample_format_valid(format));
+
+ old_format = s->sample_spec.format;
+ if (old_format == format)
+ return;
+
+ pa_log_info("%s: format: %s -> %s",
+ s->name, pa_sample_format_to_string(old_format), pa_sample_format_to_string(format));
+
+ s->sample_spec.format = format;
+
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK | PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+}
+
+/* Called from the main thread */
+void pa_sink_set_sample_rate(pa_sink *s, uint32_t rate) {
+ uint32_t old_rate;
+
+ pa_assert(s);
+ pa_assert(pa_sample_rate_valid(rate));
+
+ old_rate = s->sample_spec.rate;
+ if (old_rate == rate)
+ return;
+
+ pa_log_info("%s: rate: %u -> %u", s->name, old_rate, rate);
+
+ s->sample_spec.rate = rate;
+
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK | PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+}
+
+/* Called from the main thread. */
+void pa_sink_set_reference_volume_direct(pa_sink *s, const pa_cvolume *volume) {
+ pa_cvolume old_volume;
+ char old_volume_str[PA_CVOLUME_SNPRINT_VERBOSE_MAX];
+ char new_volume_str[PA_CVOLUME_SNPRINT_VERBOSE_MAX];
+
+ pa_assert(s);
+ pa_assert(volume);
+
+ old_volume = s->reference_volume;
+
+ if (pa_cvolume_equal(volume, &old_volume))
+ return;
+
+ s->reference_volume = *volume;
+ pa_log_debug("The reference volume of sink %s changed from %s to %s.", s->name,
+ pa_cvolume_snprint_verbose(old_volume_str, sizeof(old_volume_str), &old_volume, &s->channel_map,
+ s->flags & PA_SINK_DECIBEL_VOLUME),
+ pa_cvolume_snprint_verbose(new_volume_str, sizeof(new_volume_str), volume, &s->channel_map,
+ s->flags & PA_SINK_DECIBEL_VOLUME));
+
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+ pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_VOLUME_CHANGED], s);
+}
+
+void pa_sink_move_streams_to_default_sink(pa_core *core, pa_sink *old_sink, bool default_sink_changed) {
+ pa_sink_input *i;
+ uint32_t idx;
+
+ pa_assert(core);
+ pa_assert(old_sink);
+
+ if (core->state == PA_CORE_SHUTDOWN)
+ return;
+
+ if (core->default_sink == NULL || core->default_sink->unlink_requested)
+ return;
+
+ if (old_sink == core->default_sink)
+ return;
+
+ PA_IDXSET_FOREACH(i, old_sink->inputs, idx) {
+ if (!PA_SINK_INPUT_IS_LINKED(i->state))
+ continue;
+
+ if (!i->sink)
+ continue;
+
+ /* Don't move sink-inputs which connect filter sinks to their target sinks */
+ if (i->origin_sink)
+ continue;
+
+ /* If default_sink_changed is false, the old sink became unavailable, so all streams must be moved. */
+ if (pa_safe_streq(old_sink->name, i->preferred_sink) && default_sink_changed)
+ continue;
+
+ if (!pa_sink_input_may_move_to(i, core->default_sink))
+ continue;
+
+ if (default_sink_changed)
+ pa_log_info("The sink input %u \"%s\" is moving to %s due to change of the default sink.",
+ i->index, pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME)), core->default_sink->name);
+ else
+ pa_log_info("The sink input %u \"%s\" is moving to %s, because the old sink became unavailable.",
+ i->index, pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME)), core->default_sink->name);
+
+ pa_sink_input_move_to(i, core->default_sink, false);
+ }
+}
diff --git a/src/pulsecore/sink.h b/src/pulsecore/sink.h
new file mode 100644
index 0000000..c3f5fbc
--- /dev/null
+++ b/src/pulsecore/sink.h
@@ -0,0 +1,575 @@
+#ifndef foopulsesinkhfoo
+#define foopulsesinkhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <inttypes.h>
+
+#include <pulsecore/typedefs.h>
+#include <pulse/def.h>
+#include <pulse/format.h>
+#include <pulse/sample.h>
+#include <pulse/channelmap.h>
+#include <pulse/volume.h>
+
+#include <pulsecore/core.h>
+#include <pulsecore/idxset.h>
+#include <pulsecore/memchunk.h>
+#include <pulsecore/source.h>
+#include <pulsecore/module.h>
+#include <pulsecore/asyncmsgq.h>
+#include <pulsecore/msgobject.h>
+#include <pulsecore/rtpoll.h>
+#include <pulsecore/device-port.h>
+#include <pulsecore/card.h>
+#include <pulsecore/queue.h>
+#include <pulsecore/thread-mq.h>
+#include <pulsecore/sink-input.h>
+
+#define PA_MAX_INPUTS_PER_SINK 256
+
+/* Returns true if sink is linked: registered and accessible from client side. */
+static inline bool PA_SINK_IS_LINKED(pa_sink_state_t x) {
+ return x == PA_SINK_RUNNING || x == PA_SINK_IDLE || x == PA_SINK_SUSPENDED;
+}
+
+/* A generic definition for void callback functions */
+typedef void(*pa_sink_cb_t)(pa_sink *s);
+
+typedef int (*pa_sink_get_mute_cb_t)(pa_sink *s, bool *mute);
+
+struct pa_sink {
+ pa_msgobject parent;
+
+ uint32_t index;
+ pa_core *core;
+
+ pa_sink_state_t state;
+
+ /* Set in the beginning of pa_sink_unlink() before setting the sink state
+ * to UNLINKED. The purpose is to prevent moving streams to a sink that is
+ * about to be removed. */
+ bool unlink_requested;
+
+ pa_sink_flags_t flags;
+ pa_suspend_cause_t suspend_cause;
+
+ char *name;
+ char *driver; /* may be NULL */
+ pa_proplist *proplist;
+
+ pa_module *module; /* may be NULL */
+ pa_card *card; /* may be NULL */
+
+ pa_sample_spec sample_spec;
+ pa_channel_map channel_map;
+ uint32_t default_sample_rate;
+ uint32_t alternate_sample_rate;
+ bool avoid_resampling:1;
+
+ pa_idxset *inputs;
+ unsigned n_corked;
+ pa_source *monitor_source;
+ pa_sink_input *input_to_master; /* non-NULL only for filter sinks */
+
+ pa_volume_t base_volume; /* shall be constant */
+ unsigned n_volume_steps; /* shall be constant */
+
+ /* Also see http://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/Developer/Volumes/ */
+ pa_cvolume reference_volume; /* The volume exported and taken as reference base for relative sink input volumes */
+ pa_cvolume real_volume; /* The volume that the hardware is configured to */
+ pa_cvolume soft_volume; /* The internal software volume we apply to all PCM data while it passes through */
+
+ bool muted:1;
+
+ bool refresh_volume:1;
+ bool refresh_muted:1;
+ bool save_port:1;
+ bool save_volume:1;
+ bool save_muted:1;
+
+ /* Saved volume state while we're in passthrough mode */
+ pa_cvolume saved_volume;
+ bool saved_save_volume:1;
+
+ pa_asyncmsgq *asyncmsgq;
+
+ pa_memchunk silence;
+
+ pa_hashmap *ports;
+ pa_device_port *active_port;
+
+ /* The latency offset is inherited from the currently active port */
+ int64_t port_latency_offset;
+
+ unsigned priority;
+
+ bool set_mute_in_progress;
+
+ /* Callbacks for doing things when the sink state and/or suspend cause is
+ * changed. It's fine to set either or both of the callbacks to NULL if the
+ * implementation doesn't have anything to do on state or suspend cause
+ * changes.
+ *
+ * set_state_in_main_thread() is called first. The callback is allowed to
+ * report failure if and only if the sink changes its state from
+ * SUSPENDED to IDLE or RUNNING. (FIXME: It would make sense to allow
+ * failure also when changing state from INIT to IDLE or RUNNING, but
+ * currently that will crash pa_sink_put().) If
+ * set_state_in_main_thread() fails, set_state_in_io_thread() won't be
+ * called.
+ *
+ * If set_state_in_main_thread() is successful (or not set), then
+ * set_state_in_io_thread() is called. Again, failure is allowed if and
+ * only if the sink changes state from SUSPENDED to IDLE or RUNNING. If
+ * set_state_in_io_thread() fails, then set_state_in_main_thread() is
+ * called again, this time with the state parameter set to SUSPENDED and
+ * the suspend_cause parameter set to 0.
+ *
+ * pa_sink.state, pa_sink.thread_info.state and pa_sink.suspend_cause
+ * are updated only after all the callback calls. In case of failure, the
+ * state is set to SUSPENDED and the suspend cause is set to 0. */
+ int (*set_state_in_main_thread)(pa_sink *s, pa_sink_state_t state, pa_suspend_cause_t suspend_cause); /* may be NULL */
+ int (*set_state_in_io_thread)(pa_sink *s, pa_sink_state_t state, pa_suspend_cause_t suspend_cause); /* may be NULL */
+
+ /* Sink drivers that support hardware volume may set this
+ * callback. This is called when the current volume needs to be
+ * re-read from the hardware.
+ *
+ * There are two ways for drivers to implement hardware volume
+ * query: either set this callback or handle
+ * PA_SINK_MESSAGE_GET_VOLUME. The callback implementation or the
+ * message handler must update s->real_volume and s->soft_volume
+ * (using pa_sink_set_soft_volume()) to match the current hardware
+ * volume.
+ *
+ * If PA_SINK_DEFERRED_VOLUME is not set, then this is called from the
+ * main thread before sending PA_SINK_MESSAGE_GET_VOLUME, so in
+ * this case the driver can choose whether to read the volume from
+ * the hardware in the main thread or in the IO thread.
+ *
+ * If PA_SINK_DEFERRED_VOLUME is set, then this is called from the IO
+ * thread within the default handler for
+ * PA_SINK_MESSAGE_GET_VOLUME (the main thread is waiting while
+ * the message is being processed), so there's no choice of where
+ * to do the volume reading - it has to be done in the IO thread
+ * always.
+ *
+ * You must use the function pa_sink_set_get_volume_callback() to
+ * set this callback. */
+ pa_sink_cb_t get_volume; /* may be NULL */
+
+ /* Sink drivers that support hardware volume must set this
+ * callback. This is called when the hardware volume needs to be
+ * updated.
+ *
+ * If PA_SINK_DEFERRED_VOLUME is not set, then this is called from the
+ * main thread. The callback implementation must set the hardware
+ * volume according to s->real_volume. If the driver can't set the
+ * hardware volume to the exact requested value, it has to update
+ * s->real_volume and/or s->soft_volume so that they together
+ * match the actual hardware volume that was set.
+ *
+ * If PA_SINK_DEFERRED_VOLUME is set, then this is called from the IO
+ * thread. The callback implementation must not actually set the
+ * hardware volume yet, but it must check how close to the
+ * requested volume the hardware volume can be set, and update
+ * s->real_volume and/or s->soft_volume so that they together
+ * match the actual hardware volume that will be set later in the
+ * write_volume callback.
+ *
+ * You must use the function pa_sink_set_set_volume_callback() to
+ * set this callback. */
+ pa_sink_cb_t set_volume; /* may be NULL */
+
+ /* Sink drivers that set PA_SINK_DEFERRED_VOLUME must provide this
+ * callback. This callback is not used with sinks that do not set
+ * PA_SINK_DEFERRED_VOLUME. This is called from the IO thread when a
+ * pending hardware volume change has to be written to the
+ * hardware. The requested volume is passed to the callback
+ * implementation in s->thread_info.current_hw_volume.
+ *
+ * The call is done inside pa_sink_volume_change_apply(), which is
+ * not called automatically - it is the driver's responsibility to
+ * schedule that function to be called at the right times in the
+ * IO thread.
+ *
+ * You must use the function pa_sink_set_write_volume_callback() to
+ * set this callback. */
+ pa_sink_cb_t write_volume; /* may be NULL */
+
+ /* If the sink mute can change "spontaneously" (i.e. initiated by the sink
+ * implementation, not by someone else calling pa_sink_set_mute()), then
+ * the sink implementation can notify about changed mute either by calling
+ * pa_sink_mute_changed() or by calling pa_sink_get_mute() with
+ * force_refresh=true. If the implementation chooses the latter approach,
+ * it should implement the get_mute callback. Otherwise get_mute can be
+ * NULL.
+ *
+ * This is called when pa_sink_get_mute() is called with
+ * force_refresh=true. This is called from the IO thread if the
+ * PA_SINK_DEFERRED_VOLUME flag is set, otherwise this is called from the
+ * main thread. On success, the implementation is expected to return 0 and
+ * set the mute parameter that is passed as a reference. On failure, the
+ * implementation is expected to return -1.
+ *
+ * You must use the function pa_sink_set_get_mute_callback() to
+ * set this callback. */
+ pa_sink_get_mute_cb_t get_mute;
+
+ /* Called when the mute setting shall be changed. A PA_SINK_MESSAGE_SET_MUTE
+ * message will also be sent. Called from IO thread if PA_SINK_DEFERRED_VOLUME
+ * flag is set otherwise from main loop context.
+ *
+ * You must use the function pa_sink_set_set_mute_callback() to
+ * set this callback. */
+ pa_sink_cb_t set_mute; /* may be NULL */
+
+ /* Called when a rewind request is issued. Called from IO thread
+ * context. */
+ pa_sink_cb_t request_rewind; /* may be NULL */
+
+ /* Called when a the requested latency is changed. Called from IO
+ * thread context. */
+ pa_sink_cb_t update_requested_latency; /* may be NULL */
+
+ /* Called whenever the port shall be changed. Called from the main
+ * thread. */
+ int (*set_port)(pa_sink *s, pa_device_port *port); /* may be NULL */
+
+ /* Called to get the list of formats supported by the sink, sorted
+ * in descending order of preference. */
+ pa_idxset* (*get_formats)(pa_sink *s); /* may be NULL */
+
+ /* Called to set the list of formats supported by the sink. Can be
+ * NULL if the sink does not support this. Returns true on success,
+ * false otherwise (for example when an unsupportable format is
+ * set). Makes a copy of the formats passed in. */
+ bool (*set_formats)(pa_sink *s, pa_idxset *formats); /* may be NULL */
+
+ /* Called whenever device parameters need to be changed. Called from
+ * main thread. */
+ void (*reconfigure)(pa_sink *s, pa_sample_spec *spec, bool passthrough);
+
+ /* Contains copies of the above data so that the real-time worker
+ * thread can work without access locking */
+ struct {
+ pa_sink_state_t state;
+ pa_hashmap *inputs;
+
+ pa_rtpoll *rtpoll;
+
+ pa_cvolume soft_volume;
+ bool soft_muted:1;
+
+ /* The requested latency is used for dynamic latency
+ * sinks. For fixed latency sinks it is always identical to
+ * the fixed_latency. See below. */
+ bool requested_latency_valid:1;
+ pa_usec_t requested_latency;
+
+ /* The number of bytes streams need to keep around as history to
+ * be able to satisfy every DMA buffer rewrite */
+ size_t max_rewind;
+
+ /* The number of bytes streams need to keep around to satisfy
+ * every DMA write request */
+ size_t max_request;
+
+ /* Maximum of what clients requested to rewind in this cycle */
+ size_t rewind_nbytes;
+ bool rewind_requested;
+
+ /* Both dynamic and fixed latencies will be clamped to this
+ * range. */
+ pa_usec_t min_latency; /* we won't go below this latency */
+ pa_usec_t max_latency; /* An upper limit for the latencies */
+
+ /* 'Fixed' simply means that the latency is exclusively
+ * decided on by the sink, and the clients have no influence
+ * in changing it */
+ pa_usec_t fixed_latency; /* for sinks with PA_SINK_DYNAMIC_LATENCY this is 0 */
+
+ /* This latency offset is a direct copy from s->port_latency_offset */
+ int64_t port_latency_offset;
+
+ /* Delayed volume change events are queued here. The events
+ * are stored in expiration order. The one expiring next is in
+ * the head of the list. */
+ PA_LLIST_HEAD(pa_sink_volume_change, volume_changes);
+ pa_sink_volume_change *volume_changes_tail;
+ /* This value is updated in pa_sink_volume_change_apply() and
+ * used only by sinks with PA_SINK_DEFERRED_VOLUME. */
+ pa_cvolume current_hw_volume;
+
+ /* The amount of usec volume up events are delayed and volume
+ * down events are made earlier. */
+ uint32_t volume_change_safety_margin;
+ /* Usec delay added to all volume change events, may be negative. */
+ int32_t volume_change_extra_delay;
+ } thread_info;
+
+ void *userdata;
+};
+
+PA_DECLARE_PUBLIC_CLASS(pa_sink);
+#define PA_SINK(s) (pa_sink_cast(s))
+
+typedef enum pa_sink_message {
+ PA_SINK_MESSAGE_ADD_INPUT,
+ PA_SINK_MESSAGE_REMOVE_INPUT,
+ PA_SINK_MESSAGE_GET_VOLUME,
+ PA_SINK_MESSAGE_SET_SHARED_VOLUME,
+ PA_SINK_MESSAGE_SET_VOLUME_SYNCED,
+ PA_SINK_MESSAGE_SET_VOLUME,
+ PA_SINK_MESSAGE_SYNC_VOLUMES,
+ PA_SINK_MESSAGE_GET_MUTE,
+ PA_SINK_MESSAGE_SET_MUTE,
+ PA_SINK_MESSAGE_GET_LATENCY,
+ PA_SINK_MESSAGE_GET_REQUESTED_LATENCY,
+ PA_SINK_MESSAGE_SET_STATE,
+ PA_SINK_MESSAGE_START_MOVE,
+ PA_SINK_MESSAGE_FINISH_MOVE,
+ PA_SINK_MESSAGE_SET_LATENCY_RANGE,
+ PA_SINK_MESSAGE_GET_LATENCY_RANGE,
+ PA_SINK_MESSAGE_SET_FIXED_LATENCY,
+ PA_SINK_MESSAGE_GET_FIXED_LATENCY,
+ PA_SINK_MESSAGE_GET_MAX_REWIND,
+ PA_SINK_MESSAGE_GET_MAX_REQUEST,
+ PA_SINK_MESSAGE_SET_MAX_REWIND,
+ PA_SINK_MESSAGE_SET_MAX_REQUEST,
+ PA_SINK_MESSAGE_UPDATE_VOLUME_AND_MUTE,
+ PA_SINK_MESSAGE_SET_PORT_LATENCY_OFFSET,
+ PA_SINK_MESSAGE_MAX
+} pa_sink_message_t;
+
+typedef struct pa_sink_new_data {
+ pa_suspend_cause_t suspend_cause;
+
+ char *name;
+ pa_proplist *proplist;
+
+ const char *driver;
+ pa_module *module;
+ pa_card *card;
+
+ pa_hashmap *ports;
+ char *active_port;
+
+ pa_sample_spec sample_spec;
+ pa_channel_map channel_map;
+ uint32_t alternate_sample_rate;
+ bool avoid_resampling:1;
+ pa_cvolume volume;
+ bool muted:1;
+
+ bool sample_spec_is_set:1;
+ bool channel_map_is_set:1;
+ bool alternate_sample_rate_is_set:1;
+ bool avoid_resampling_is_set:1;
+ bool volume_is_set:1;
+ bool muted_is_set:1;
+
+ bool namereg_fail:1;
+
+ bool save_port:1;
+ bool save_volume:1;
+ bool save_muted:1;
+} pa_sink_new_data;
+
+pa_sink_new_data* pa_sink_new_data_init(pa_sink_new_data *data);
+void pa_sink_new_data_set_name(pa_sink_new_data *data, const char *name);
+void pa_sink_new_data_set_sample_spec(pa_sink_new_data *data, const pa_sample_spec *spec);
+void pa_sink_new_data_set_channel_map(pa_sink_new_data *data, const pa_channel_map *map);
+void pa_sink_new_data_set_alternate_sample_rate(pa_sink_new_data *data, const uint32_t alternate_sample_rate);
+void pa_sink_new_data_set_avoid_resampling(pa_sink_new_data *data, bool avoid_resampling);
+void pa_sink_new_data_set_volume(pa_sink_new_data *data, const pa_cvolume *volume);
+void pa_sink_new_data_set_muted(pa_sink_new_data *data, bool mute);
+void pa_sink_new_data_set_port(pa_sink_new_data *data, const char *port);
+void pa_sink_new_data_done(pa_sink_new_data *data);
+
+/*** To be called exclusively by the sink driver, from main context */
+
+pa_sink* pa_sink_new(
+ pa_core *core,
+ pa_sink_new_data *data,
+ pa_sink_flags_t flags);
+
+void pa_sink_set_get_volume_callback(pa_sink *s, pa_sink_cb_t cb);
+void pa_sink_set_set_volume_callback(pa_sink *s, pa_sink_cb_t cb);
+void pa_sink_set_write_volume_callback(pa_sink *s, pa_sink_cb_t cb);
+void pa_sink_set_get_mute_callback(pa_sink *s, pa_sink_get_mute_cb_t cb);
+void pa_sink_set_set_mute_callback(pa_sink *s, pa_sink_cb_t cb);
+void pa_sink_enable_decibel_volume(pa_sink *s, bool enable);
+
+void pa_sink_put(pa_sink *s);
+void pa_sink_unlink(pa_sink* s);
+
+void pa_sink_set_description(pa_sink *s, const char *description);
+void pa_sink_set_asyncmsgq(pa_sink *s, pa_asyncmsgq *q);
+void pa_sink_set_rtpoll(pa_sink *s, pa_rtpoll *p);
+
+void pa_sink_set_max_rewind(pa_sink *s, size_t max_rewind);
+void pa_sink_set_max_request(pa_sink *s, size_t max_request);
+void pa_sink_set_latency_range(pa_sink *s, pa_usec_t min_latency, pa_usec_t max_latency);
+void pa_sink_set_fixed_latency(pa_sink *s, pa_usec_t latency);
+
+void pa_sink_set_soft_volume(pa_sink *s, const pa_cvolume *volume);
+void pa_sink_volume_changed(pa_sink *s, const pa_cvolume *new_volume);
+void pa_sink_mute_changed(pa_sink *s, bool new_muted);
+
+void pa_sink_update_flags(pa_sink *s, pa_sink_flags_t mask, pa_sink_flags_t value);
+
+bool pa_device_init_description(pa_proplist *p, pa_card *card);
+bool pa_device_init_icon(pa_proplist *p, bool is_sink);
+bool pa_device_init_intended_roles(pa_proplist *p);
+unsigned pa_device_init_priority(pa_proplist *p);
+
+/**** May be called by everyone, from main context */
+
+void pa_sink_reconfigure(pa_sink *s, pa_sample_spec *spec, bool passthrough);
+void pa_sink_set_port_latency_offset(pa_sink *s, int64_t offset);
+
+/* The returned value is supposed to be in the time domain of the sound card! */
+pa_usec_t pa_sink_get_latency(pa_sink *s);
+pa_usec_t pa_sink_get_requested_latency(pa_sink *s);
+void pa_sink_get_latency_range(pa_sink *s, pa_usec_t *min_latency, pa_usec_t *max_latency);
+pa_usec_t pa_sink_get_fixed_latency(pa_sink *s);
+
+size_t pa_sink_get_max_rewind(pa_sink *s);
+size_t pa_sink_get_max_request(pa_sink *s);
+
+int pa_sink_update_status(pa_sink*s);
+int pa_sink_suspend(pa_sink *s, bool suspend, pa_suspend_cause_t cause);
+int pa_sink_suspend_all(pa_core *c, bool suspend, pa_suspend_cause_t cause);
+
+/* Use this instead of checking s->flags & PA_SINK_FLAT_VOLUME directly. */
+bool pa_sink_flat_volume_enabled(pa_sink *s);
+
+/* Get the master sink when sharing volumes */
+pa_sink *pa_sink_get_master(pa_sink *s);
+
+bool pa_sink_is_filter(pa_sink *s);
+
+/* Is the sink in passthrough mode? (that is, is there a passthrough sink input
+ * connected to this sink? */
+bool pa_sink_is_passthrough(pa_sink *s);
+/* These should be called when a sink enters/leaves passthrough mode */
+void pa_sink_enter_passthrough(pa_sink *s);
+void pa_sink_leave_passthrough(pa_sink *s);
+
+void pa_sink_set_volume(pa_sink *sink, const pa_cvolume *volume, bool sendmsg, bool save);
+const pa_cvolume *pa_sink_get_volume(pa_sink *sink, bool force_refresh);
+
+void pa_sink_set_mute(pa_sink *sink, bool mute, bool save);
+bool pa_sink_get_mute(pa_sink *sink, bool force_refresh);
+
+bool pa_sink_update_proplist(pa_sink *s, pa_update_mode_t mode, pa_proplist *p);
+
+int pa_sink_set_port(pa_sink *s, const char *name, bool save);
+
+unsigned pa_sink_linked_by(pa_sink *s); /* Number of connected streams */
+unsigned pa_sink_used_by(pa_sink *s); /* Number of connected streams which are not corked */
+
+/* Returns how many streams are active that don't allow suspensions. If
+ * "ignore_input" or "ignore_output" is non-NULL, that stream is not included
+ * in the count (the returned count includes the value from
+ * pa_source_check_suspend(), which is called for the monitor source, so that's
+ * why "ignore_output" may be relevant). */
+unsigned pa_sink_check_suspend(pa_sink *s, pa_sink_input *ignore_input, pa_source_output *ignore_output);
+
+const char *pa_sink_state_to_string(pa_sink_state_t state);
+
+/* Moves all inputs away, and stores them in pa_queue */
+pa_queue *pa_sink_move_all_start(pa_sink *s, pa_queue *q);
+void pa_sink_move_all_finish(pa_sink *s, pa_queue *q, bool save);
+void pa_sink_move_all_fail(pa_queue *q);
+
+/* Returns a copy of the sink formats. TODO: Get rid of this function (or at
+ * least get rid of the copying). There's no good reason to copy the formats
+ * every time someone wants to know what formats the sink supports. The formats
+ * idxset could be stored directly in the pa_sink struct.
+ * https://bugs.freedesktop.org/show_bug.cgi?id=71924 */
+pa_idxset* pa_sink_get_formats(pa_sink *s);
+
+bool pa_sink_set_formats(pa_sink *s, pa_idxset *formats);
+bool pa_sink_check_format(pa_sink *s, pa_format_info *f);
+pa_idxset* pa_sink_check_formats(pa_sink *s, pa_idxset *in_formats);
+
+void pa_sink_set_sample_format(pa_sink *s, pa_sample_format_t format);
+void pa_sink_set_sample_rate(pa_sink *s, uint32_t rate);
+
+/*** To be called exclusively by the sink driver, from IO context */
+
+void pa_sink_render(pa_sink*s, size_t length, pa_memchunk *result);
+void pa_sink_render_full(pa_sink *s, size_t length, pa_memchunk *result);
+void pa_sink_render_into(pa_sink*s, pa_memchunk *target);
+void pa_sink_render_into_full(pa_sink *s, pa_memchunk *target);
+
+void pa_sink_process_rewind(pa_sink *s, size_t nbytes);
+
+int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk);
+
+void pa_sink_attach_within_thread(pa_sink *s);
+void pa_sink_detach_within_thread(pa_sink *s);
+
+pa_usec_t pa_sink_get_requested_latency_within_thread(pa_sink *s);
+
+void pa_sink_set_max_rewind_within_thread(pa_sink *s, size_t max_rewind);
+void pa_sink_set_max_request_within_thread(pa_sink *s, size_t max_request);
+
+void pa_sink_set_latency_range_within_thread(pa_sink *s, pa_usec_t min_latency, pa_usec_t max_latency);
+void pa_sink_set_fixed_latency_within_thread(pa_sink *s, pa_usec_t latency);
+
+void pa_sink_update_volume_and_mute(pa_sink *s);
+
+bool pa_sink_volume_change_apply(pa_sink *s, pa_usec_t *usec_to_next);
+
+size_t pa_sink_process_input_underruns(pa_sink *s, size_t left_to_play);
+
+/*** To be called exclusively by sink input drivers, from IO context */
+
+void pa_sink_request_rewind(pa_sink*s, size_t nbytes);
+
+void pa_sink_invalidate_requested_latency(pa_sink *s, bool dynamic);
+
+int64_t pa_sink_get_latency_within_thread(pa_sink *s, bool allow_negative);
+
+/* Called from the main thread, from sink-input.c only. The normal way to set
+ * the sink reference volume is to call pa_sink_set_volume(), but the flat
+ * volume logic in sink-input.c needs also a function that doesn't do all the
+ * extra stuff that pa_sink_set_volume() does. This function simply sets
+ * s->reference_volume and fires change notifications. */
+void pa_sink_set_reference_volume_direct(pa_sink *s, const pa_cvolume *volume);
+
+/* When the default_sink is changed or the active_port of a sink is changed to
+ * PA_AVAILABLE_NO, this function is called to move the streams of the old
+ * default_sink or the sink with active_port equals PA_AVAILABLE_NO to the
+ * current default_sink conditionally*/
+void pa_sink_move_streams_to_default_sink(pa_core *core, pa_sink *old_sink, bool default_sink_changed);
+
+/* Verify that we called in IO context (aka 'thread context), or that
+ * the sink is not yet set up, i.e. the thread not set up yet. See
+ * pa_assert_io_context() in thread-mq.h for more information. */
+#define pa_sink_assert_io_context(s) \
+ pa_assert(pa_thread_mq_get() || !PA_SINK_IS_LINKED((s)->state))
+
+#endif
diff --git a/src/pulsecore/sioman.c b/src/pulsecore/sioman.c
new file mode 100644
index 0000000..315f10a
--- /dev/null
+++ b/src/pulsecore/sioman.c
@@ -0,0 +1,37 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulsecore/macro.h>
+#include <pulsecore/atomic.h>
+
+#include "sioman.h"
+
+static pa_atomic_t stdio_inuse = PA_ATOMIC_INIT(0);
+
+int pa_stdio_acquire(void) {
+ return pa_atomic_cmpxchg(&stdio_inuse, 0, 1) ? 0 : -1;
+}
+
+void pa_stdio_release(void) {
+ pa_assert_se(pa_atomic_cmpxchg(&stdio_inuse, 1, 0));
+}
diff --git a/src/pulsecore/sioman.h b/src/pulsecore/sioman.h
new file mode 100644
index 0000000..10ad382
--- /dev/null
+++ b/src/pulsecore/sioman.h
@@ -0,0 +1,26 @@
+#ifndef foosiomanhfoo
+#define foosiomanhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+int pa_stdio_acquire(void);
+void pa_stdio_release(void);
+
+#endif
diff --git a/src/pulsecore/sndfile-util.c b/src/pulsecore/sndfile-util.c
new file mode 100644
index 0000000..b6cc65e
--- /dev/null
+++ b/src/pulsecore/sndfile-util.c
@@ -0,0 +1,461 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2009 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+/* Shared between pacat/parec/paplay and the server */
+
+#include <pulse/xmalloc.h>
+#include <pulse/utf8.h>
+
+#include <pulsecore/macro.h>
+
+#include "sndfile-util.h"
+
+int pa_sndfile_read_sample_spec(SNDFILE *sf, pa_sample_spec *ss) {
+ SF_INFO sfi;
+ int sf_errno;
+
+ pa_assert(sf);
+ pa_assert(ss);
+
+ pa_zero(sfi);
+ if ((sf_errno = sf_command(sf, SFC_GET_CURRENT_SF_INFO, &sfi, sizeof(sfi)))) {
+ pa_log_error("sndfile: %s", sf_error_number(sf_errno));
+ return -1;
+ }
+
+ switch (sfi.format & SF_FORMAT_SUBMASK) {
+
+ case SF_FORMAT_PCM_16:
+ case SF_FORMAT_PCM_U8:
+ case SF_FORMAT_PCM_S8:
+ ss->format = PA_SAMPLE_S16NE;
+ break;
+
+ case SF_FORMAT_PCM_24:
+ ss->format = PA_SAMPLE_S24NE;
+ break;
+
+ case SF_FORMAT_PCM_32:
+ ss->format = PA_SAMPLE_S32NE;
+ break;
+
+ case SF_FORMAT_ULAW:
+ ss->format = PA_SAMPLE_ULAW;
+ break;
+
+ case SF_FORMAT_ALAW:
+ ss->format = PA_SAMPLE_ALAW;
+ break;
+
+ case SF_FORMAT_FLOAT:
+ case SF_FORMAT_DOUBLE:
+ default:
+ ss->format = PA_SAMPLE_FLOAT32NE;
+ break;
+ }
+
+ ss->rate = (uint32_t) sfi.samplerate;
+ ss->channels = (uint8_t) sfi.channels;
+
+ if (!pa_sample_spec_valid(ss))
+ return -1;
+
+ return 0;
+}
+
+int pa_sndfile_write_sample_spec(SF_INFO *sfi, pa_sample_spec *ss) {
+ pa_assert(sfi);
+ pa_assert(ss);
+
+ sfi->samplerate = (int) ss->rate;
+ sfi->channels = (int) ss->channels;
+
+ if (pa_sample_format_is_le(ss->format) > 0)
+ sfi->format = SF_ENDIAN_LITTLE;
+ else if (pa_sample_format_is_be(ss->format) > 0)
+ sfi->format = SF_ENDIAN_BIG;
+
+ switch (ss->format) {
+
+ case PA_SAMPLE_U8:
+ ss->format = PA_SAMPLE_S16NE;
+ sfi->format = SF_FORMAT_PCM_U8;
+ break;
+
+ case PA_SAMPLE_S16LE:
+ case PA_SAMPLE_S16BE:
+ ss->format = PA_SAMPLE_S16NE;
+ sfi->format |= SF_FORMAT_PCM_16;
+ break;
+
+ case PA_SAMPLE_S24LE:
+ case PA_SAMPLE_S24BE:
+ ss->format = PA_SAMPLE_S24NE;
+ sfi->format |= SF_FORMAT_PCM_24;
+ break;
+
+ case PA_SAMPLE_S24_32LE:
+ case PA_SAMPLE_S24_32BE:
+ ss->format = PA_SAMPLE_S24_32NE;
+ sfi->format |= SF_FORMAT_PCM_32;
+ break;
+
+ case PA_SAMPLE_S32LE:
+ case PA_SAMPLE_S32BE:
+ ss->format = PA_SAMPLE_S32NE;
+ sfi->format |= SF_FORMAT_PCM_32;
+ break;
+
+ case PA_SAMPLE_ULAW:
+ sfi->format = SF_FORMAT_ULAW;
+ break;
+
+ case PA_SAMPLE_ALAW:
+ sfi->format = SF_FORMAT_ALAW;
+ break;
+
+ case PA_SAMPLE_FLOAT32LE:
+ case PA_SAMPLE_FLOAT32BE:
+ default:
+ ss->format = PA_SAMPLE_FLOAT32NE;
+ sfi->format |= SF_FORMAT_FLOAT;
+ break;
+ }
+
+ if (!pa_sample_spec_valid(ss))
+ return -1;
+
+ return 0;
+}
+
+int pa_sndfile_read_channel_map(SNDFILE *sf, pa_channel_map *cm) {
+
+ static const pa_channel_position_t table[] = {
+ [SF_CHANNEL_MAP_MONO] = PA_CHANNEL_POSITION_MONO,
+ [SF_CHANNEL_MAP_LEFT] = PA_CHANNEL_POSITION_FRONT_LEFT, /* libsndfile distinguishes left and front-left, which we don't */
+ [SF_CHANNEL_MAP_RIGHT] = PA_CHANNEL_POSITION_FRONT_RIGHT,
+ [SF_CHANNEL_MAP_CENTER] = PA_CHANNEL_POSITION_FRONT_CENTER,
+ [SF_CHANNEL_MAP_FRONT_LEFT] = PA_CHANNEL_POSITION_FRONT_LEFT,
+ [SF_CHANNEL_MAP_FRONT_RIGHT] = PA_CHANNEL_POSITION_FRONT_RIGHT,
+ [SF_CHANNEL_MAP_FRONT_CENTER] = PA_CHANNEL_POSITION_FRONT_CENTER,
+ [SF_CHANNEL_MAP_REAR_CENTER] = PA_CHANNEL_POSITION_REAR_CENTER,
+ [SF_CHANNEL_MAP_REAR_LEFT] = PA_CHANNEL_POSITION_REAR_LEFT,
+ [SF_CHANNEL_MAP_REAR_RIGHT] = PA_CHANNEL_POSITION_REAR_RIGHT,
+ [SF_CHANNEL_MAP_LFE] = PA_CHANNEL_POSITION_LFE,
+ [SF_CHANNEL_MAP_FRONT_LEFT_OF_CENTER] = PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER,
+ [SF_CHANNEL_MAP_FRONT_RIGHT_OF_CENTER] = PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER,
+ [SF_CHANNEL_MAP_SIDE_LEFT] = PA_CHANNEL_POSITION_SIDE_LEFT,
+ [SF_CHANNEL_MAP_SIDE_RIGHT] = PA_CHANNEL_POSITION_SIDE_RIGHT,
+ [SF_CHANNEL_MAP_TOP_CENTER] = PA_CHANNEL_POSITION_TOP_CENTER,
+ [SF_CHANNEL_MAP_TOP_FRONT_LEFT] = PA_CHANNEL_POSITION_TOP_FRONT_LEFT,
+ [SF_CHANNEL_MAP_TOP_FRONT_RIGHT] = PA_CHANNEL_POSITION_TOP_FRONT_RIGHT,
+ [SF_CHANNEL_MAP_TOP_FRONT_CENTER] = PA_CHANNEL_POSITION_TOP_FRONT_CENTER,
+ [SF_CHANNEL_MAP_TOP_REAR_LEFT] = PA_CHANNEL_POSITION_TOP_REAR_LEFT,
+ [SF_CHANNEL_MAP_TOP_REAR_RIGHT] = PA_CHANNEL_POSITION_TOP_REAR_RIGHT,
+ [SF_CHANNEL_MAP_TOP_REAR_CENTER] = PA_CHANNEL_POSITION_TOP_REAR_CENTER
+ };
+
+ SF_INFO sfi;
+ int sf_errno;
+ int *channels;
+ unsigned c;
+
+ pa_assert(sf);
+ pa_assert(cm);
+
+ pa_zero(sfi);
+ if ((sf_errno = sf_command(sf, SFC_GET_CURRENT_SF_INFO, &sfi, sizeof(sfi)))) {
+ pa_log_error("sndfile: %s", sf_error_number(sf_errno));
+ return -1;
+ }
+
+ channels = pa_xnew(int, sfi.channels);
+ if (!sf_command(sf, SFC_GET_CHANNEL_MAP_INFO, channels, sizeof(channels[0]) * sfi.channels)) {
+ pa_xfree(channels);
+ return -1;
+ }
+
+ cm->channels = (uint8_t) sfi.channels;
+ for (c = 0; c < cm->channels; c++) {
+ if (channels[c] <= SF_CHANNEL_MAP_INVALID ||
+ (unsigned) channels[c] >= PA_ELEMENTSOF(table)) {
+ pa_xfree(channels);
+ return -1;
+ }
+
+ cm->map[c] = table[channels[c]];
+ }
+
+ pa_xfree(channels);
+
+ if (!pa_channel_map_valid(cm))
+ return -1;
+
+ return 0;
+}
+
+int pa_sndfile_write_channel_map(SNDFILE *sf, pa_channel_map *cm) {
+ static const int table[PA_CHANNEL_POSITION_MAX] = {
+ [PA_CHANNEL_POSITION_MONO] = SF_CHANNEL_MAP_MONO,
+
+ [PA_CHANNEL_POSITION_FRONT_LEFT] = SF_CHANNEL_MAP_FRONT_LEFT,
+ [PA_CHANNEL_POSITION_FRONT_RIGHT] = SF_CHANNEL_MAP_FRONT_RIGHT,
+ [PA_CHANNEL_POSITION_FRONT_CENTER] = SF_CHANNEL_MAP_FRONT_CENTER,
+
+ [PA_CHANNEL_POSITION_REAR_CENTER] = SF_CHANNEL_MAP_REAR_CENTER,
+ [PA_CHANNEL_POSITION_REAR_LEFT] = SF_CHANNEL_MAP_REAR_LEFT,
+ [PA_CHANNEL_POSITION_REAR_RIGHT] = SF_CHANNEL_MAP_REAR_RIGHT,
+
+ [PA_CHANNEL_POSITION_LFE] = SF_CHANNEL_MAP_LFE,
+
+ [PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER] = SF_CHANNEL_MAP_FRONT_LEFT_OF_CENTER,
+ [PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER] = SF_CHANNEL_MAP_FRONT_RIGHT_OF_CENTER,
+
+ [PA_CHANNEL_POSITION_SIDE_LEFT] = SF_CHANNEL_MAP_SIDE_LEFT,
+ [PA_CHANNEL_POSITION_SIDE_RIGHT] = SF_CHANNEL_MAP_SIDE_RIGHT,
+
+ [PA_CHANNEL_POSITION_AUX0] = -1,
+ [PA_CHANNEL_POSITION_AUX1] = -1,
+ [PA_CHANNEL_POSITION_AUX2] = -1,
+ [PA_CHANNEL_POSITION_AUX3] = -1,
+ [PA_CHANNEL_POSITION_AUX4] = -1,
+ [PA_CHANNEL_POSITION_AUX5] = -1,
+ [PA_CHANNEL_POSITION_AUX6] = -1,
+ [PA_CHANNEL_POSITION_AUX7] = -1,
+ [PA_CHANNEL_POSITION_AUX8] = -1,
+ [PA_CHANNEL_POSITION_AUX9] = -1,
+ [PA_CHANNEL_POSITION_AUX10] = -1,
+ [PA_CHANNEL_POSITION_AUX11] = -1,
+ [PA_CHANNEL_POSITION_AUX12] = -1,
+ [PA_CHANNEL_POSITION_AUX13] = -1,
+ [PA_CHANNEL_POSITION_AUX14] = -1,
+ [PA_CHANNEL_POSITION_AUX15] = -1,
+ [PA_CHANNEL_POSITION_AUX16] = -1,
+ [PA_CHANNEL_POSITION_AUX17] = -1,
+ [PA_CHANNEL_POSITION_AUX18] = -1,
+ [PA_CHANNEL_POSITION_AUX19] = -1,
+ [PA_CHANNEL_POSITION_AUX20] = -1,
+ [PA_CHANNEL_POSITION_AUX21] = -1,
+ [PA_CHANNEL_POSITION_AUX22] = -1,
+ [PA_CHANNEL_POSITION_AUX23] = -1,
+ [PA_CHANNEL_POSITION_AUX24] = -1,
+ [PA_CHANNEL_POSITION_AUX25] = -1,
+ [PA_CHANNEL_POSITION_AUX26] = -1,
+ [PA_CHANNEL_POSITION_AUX27] = -1,
+ [PA_CHANNEL_POSITION_AUX28] = -1,
+ [PA_CHANNEL_POSITION_AUX29] = -1,
+ [PA_CHANNEL_POSITION_AUX30] = -1,
+ [PA_CHANNEL_POSITION_AUX31] = -1,
+
+ [PA_CHANNEL_POSITION_TOP_CENTER] = SF_CHANNEL_MAP_TOP_CENTER,
+
+ [PA_CHANNEL_POSITION_TOP_FRONT_LEFT] = SF_CHANNEL_MAP_TOP_FRONT_LEFT,
+ [PA_CHANNEL_POSITION_TOP_FRONT_RIGHT] = SF_CHANNEL_MAP_TOP_FRONT_RIGHT,
+ [PA_CHANNEL_POSITION_TOP_FRONT_CENTER] = SF_CHANNEL_MAP_TOP_FRONT_CENTER ,
+
+ [PA_CHANNEL_POSITION_TOP_REAR_LEFT] = SF_CHANNEL_MAP_TOP_REAR_LEFT,
+ [PA_CHANNEL_POSITION_TOP_REAR_RIGHT] = SF_CHANNEL_MAP_TOP_REAR_RIGHT,
+ [PA_CHANNEL_POSITION_TOP_REAR_CENTER] = SF_CHANNEL_MAP_TOP_REAR_CENTER,
+ };
+
+ int *channels;
+ unsigned c;
+
+ pa_assert(sf);
+ pa_assert(cm);
+
+ /* Suppress channel mapping for the obvious cases */
+ if (cm->channels == 1 && cm->map[0] == PA_CHANNEL_POSITION_MONO)
+ return 0;
+
+ if (cm->channels == 2 &&
+ cm->map[0] == PA_CHANNEL_POSITION_FRONT_LEFT &&
+ cm->map[1] == PA_CHANNEL_POSITION_FRONT_RIGHT)
+ return 0;
+
+ channels = pa_xnew(int, cm->channels);
+ for (c = 0; c < cm->channels; c++) {
+
+ if (cm->map[c] < 0 ||
+ cm->map[c] >= PA_CHANNEL_POSITION_MAX ||
+ table[cm->map[c]] < 0) {
+ pa_xfree(channels);
+ return -1;
+ }
+
+ channels[c] = table[cm->map[c]];
+ }
+
+ if (!sf_command(sf, SFC_SET_CHANNEL_MAP_INFO, channels, sizeof(channels[0]) * cm->channels)) {
+ pa_xfree(channels);
+ return -1;
+ }
+
+ pa_xfree(channels);
+ return 0;
+}
+
+void pa_sndfile_init_proplist(SNDFILE *sf, pa_proplist *p) {
+
+ static const char* table[] = {
+ [SF_STR_TITLE] = PA_PROP_MEDIA_TITLE,
+ [SF_STR_COPYRIGHT] = PA_PROP_MEDIA_COPYRIGHT,
+ [SF_STR_SOFTWARE] = PA_PROP_MEDIA_SOFTWARE,
+ [SF_STR_ARTIST] = PA_PROP_MEDIA_ARTIST,
+ [SF_STR_COMMENT] = "media.comment",
+ [SF_STR_DATE] = "media.date"
+ };
+
+ SF_INFO sfi;
+ SF_FORMAT_INFO fi;
+ int sf_errno;
+ unsigned c;
+
+ pa_assert(sf);
+ pa_assert(p);
+
+ for (c = 0; c < PA_ELEMENTSOF(table); c++) {
+ const char *s;
+ char *t;
+
+ if (!table[c])
+ continue;
+
+ if (!(s = sf_get_string(sf, c)))
+ continue;
+
+ t = pa_utf8_filter(s);
+ pa_proplist_sets(p, table[c], t);
+ pa_xfree(t);
+ }
+
+ pa_zero(sfi);
+ if ((sf_errno = sf_command(sf, SFC_GET_CURRENT_SF_INFO, &sfi, sizeof(sfi)))) {
+ pa_log_error("sndfile: %s", sf_error_number(sf_errno));
+ return;
+ }
+
+ pa_zero(fi);
+ fi.format = sfi.format;
+ if (sf_command(sf, SFC_GET_FORMAT_INFO, &fi, sizeof(fi)) == 0 && fi.name) {
+ char *t;
+
+ t = pa_utf8_filter(fi.name);
+ pa_proplist_sets(p, "media.format", t);
+ pa_xfree(t);
+ }
+}
+
+pa_sndfile_readf_t pa_sndfile_readf_function(const pa_sample_spec *ss) {
+ pa_assert(ss);
+
+ switch (ss->format) {
+ case PA_SAMPLE_S16NE:
+ return (pa_sndfile_readf_t) sf_readf_short;
+
+ case PA_SAMPLE_S32NE:
+ case PA_SAMPLE_S24_32NE:
+ return (pa_sndfile_readf_t) sf_readf_int;
+
+ case PA_SAMPLE_FLOAT32NE:
+ return (pa_sndfile_readf_t) sf_readf_float;
+
+ case PA_SAMPLE_ULAW:
+ case PA_SAMPLE_ALAW:
+ case PA_SAMPLE_S24NE:
+ return NULL;
+
+ default:
+ pa_assert_not_reached();
+ }
+}
+
+pa_sndfile_writef_t pa_sndfile_writef_function(const pa_sample_spec *ss) {
+ pa_assert(ss);
+
+ switch (ss->format) {
+ case PA_SAMPLE_S16NE:
+ return (pa_sndfile_writef_t) sf_writef_short;
+
+ case PA_SAMPLE_S32NE:
+ case PA_SAMPLE_S24_32NE:
+ return (pa_sndfile_writef_t) sf_writef_int;
+
+ case PA_SAMPLE_FLOAT32NE:
+ return (pa_sndfile_writef_t) sf_writef_float;
+
+ case PA_SAMPLE_ULAW:
+ case PA_SAMPLE_ALAW:
+ case PA_SAMPLE_S24NE:
+ return NULL;
+
+ default:
+ pa_assert_not_reached();
+ }
+}
+
+int pa_sndfile_format_from_string(const char *name) {
+ int i, count = 0;
+
+ if (!name[0])
+ return -1;
+
+ pa_assert_se(sf_command(NULL, SFC_GET_FORMAT_MAJOR_COUNT, &count, sizeof(int)) == 0);
+
+ for (i = 0; i < count; i++) {
+ SF_FORMAT_INFO fi;
+ pa_zero(fi);
+ fi.format = i;
+
+ pa_assert_se(sf_command(NULL, SFC_GET_FORMAT_MAJOR, &fi, sizeof(fi)) == 0);
+
+ /* First try to match via full type string */
+ if (strcasecmp(name, fi.name) == 0)
+ return fi.format;
+
+ /* Then, try to match via the full extension */
+ if (strcasecmp(name, fi.extension) == 0)
+ return fi.format;
+
+ /* Then, try to match via the start of the type string */
+ if (strncasecmp(name, fi.name, strlen(name)) == 0)
+ return fi.format;
+ }
+
+ return -1;
+}
+
+void pa_sndfile_dump_formats(void) {
+ int i, count = 0;
+
+ pa_assert_se(sf_command(NULL, SFC_GET_FORMAT_MAJOR_COUNT, &count, sizeof(int)) == 0);
+
+ for (i = 0; i < count; i++) {
+ SF_FORMAT_INFO fi;
+ pa_zero(fi);
+ fi.format = i;
+
+ pa_assert_se(sf_command(NULL, SFC_GET_FORMAT_MAJOR, &fi, sizeof(fi)) == 0);
+ printf("%s\t%s\n", fi.extension, fi.name);
+ }
+}
diff --git a/src/pulsecore/sndfile-util.h b/src/pulsecore/sndfile-util.h
new file mode 100644
index 0000000..1f67ac3
--- /dev/null
+++ b/src/pulsecore/sndfile-util.h
@@ -0,0 +1,50 @@
+#ifndef foopulsecoresndfileutilhfoo
+#define foopulsecoresndfileutilhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2009 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sndfile.h>
+
+#include <pulse/sample.h>
+#include <pulse/channelmap.h>
+#include <pulse/proplist.h>
+
+int pa_sndfile_read_sample_spec(SNDFILE *sf, pa_sample_spec *ss);
+int pa_sndfile_read_channel_map(SNDFILE *sf, pa_channel_map *cm);
+
+int pa_sndfile_write_sample_spec(SF_INFO *sfi, pa_sample_spec *ss);
+int pa_sndfile_write_channel_map(SNDFILE *sf, pa_channel_map *cm);
+
+void pa_sndfile_init_proplist(SNDFILE *sf, pa_proplist *p);
+
+typedef sf_count_t (*pa_sndfile_readf_t)(SNDFILE *sndfile, void *ptr, sf_count_t frames);
+typedef sf_count_t (*pa_sndfile_writef_t)(SNDFILE *sndfile, const void *ptr, sf_count_t frames);
+
+/* Returns NULL if sf_read_raw() shall be used */
+pa_sndfile_readf_t pa_sndfile_readf_function(const pa_sample_spec *ss);
+
+/* Returns NULL if sf_write_raw() shall be used */
+pa_sndfile_writef_t pa_sndfile_writef_function(const pa_sample_spec *ss);
+
+int pa_sndfile_format_from_string(const char *extension);
+
+void pa_sndfile_dump_formats(void);
+
+#endif
diff --git a/src/pulsecore/socket-client.c b/src/pulsecore/socket-client.c
new file mode 100644
index 0000000..c87406d
--- /dev/null
+++ b/src/pulsecore/socket-client.c
@@ -0,0 +1,563 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006-2007 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+/* #undef HAVE_LIBASYNCNS */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+
+#ifdef HAVE_SYS_UN_H
+#include <sys/un.h>
+#endif
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+#ifdef HAVE_NETDB_H
+#include <netdb.h>
+#endif
+
+#ifdef HAVE_LIBASYNCNS
+#include <asyncns.h>
+#endif
+
+#include <pulse/rtclock.h>
+#include <pulse/timeval.h>
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/socket.h>
+#include <pulsecore/socket-util.h>
+#include <pulsecore/core-error.h>
+#include <pulsecore/core-rtclock.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/socket-util.h>
+#include <pulsecore/log.h>
+#include <pulsecore/parseaddr.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/refcnt.h>
+#include <pulsecore/arpa-inet.h>
+
+#include "socket-client.h"
+
+#define CONNECT_TIMEOUT 5
+
+struct pa_socket_client {
+ PA_REFCNT_DECLARE;
+ int fd;
+
+ pa_mainloop_api *mainloop;
+ pa_io_event *io_event;
+ pa_time_event *timeout_event;
+ pa_defer_event *defer_event;
+
+ pa_socket_client_cb_t callback;
+ void *userdata;
+
+ bool local;
+
+#ifdef HAVE_LIBASYNCNS
+ asyncns_t *asyncns;
+ asyncns_query_t * asyncns_query;
+ pa_io_event *asyncns_io_event;
+#endif
+};
+
+static pa_socket_client* socket_client_new(pa_mainloop_api *m) {
+ pa_socket_client *c;
+ pa_assert(m);
+
+ c = pa_xnew0(pa_socket_client, 1);
+ PA_REFCNT_INIT(c);
+ c->mainloop = m;
+ c->fd = -1;
+
+ return c;
+}
+
+static void free_events(pa_socket_client *c) {
+ pa_assert(c);
+
+ if (c->io_event) {
+ c->mainloop->io_free(c->io_event);
+ c->io_event = NULL;
+ }
+
+ if (c->timeout_event) {
+ c->mainloop->time_free(c->timeout_event);
+ c->timeout_event = NULL;
+ }
+
+ if (c->defer_event) {
+ c->mainloop->defer_free(c->defer_event);
+ c->defer_event = NULL;
+ }
+}
+
+static void do_call(pa_socket_client *c) {
+ pa_iochannel *io = NULL;
+ int error;
+ socklen_t lerror;
+
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+ pa_assert(c->callback);
+
+ pa_socket_client_ref(c);
+
+ if (c->fd < 0)
+ goto finish;
+
+ lerror = sizeof(error);
+ if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, (void*)&error, &lerror) < 0) {
+ pa_log("getsockopt(): %s", pa_cstrerror(errno));
+ goto finish;
+ }
+
+ if (lerror != sizeof(error)) {
+ pa_log("getsockopt() returned invalid size.");
+ goto finish;
+ }
+
+ if (error != 0) {
+ pa_log_debug("connect(): %s", pa_cstrerror(error));
+ errno = error;
+ goto finish;
+ }
+
+ io = pa_iochannel_new(c->mainloop, c->fd, c->fd);
+
+finish:
+ if (!io && c->fd >= 0)
+ pa_close(c->fd);
+ c->fd = -1;
+
+ free_events(c);
+
+ c->callback(c, io, c->userdata);
+
+ pa_socket_client_unref(c);
+}
+
+static void connect_defer_cb(pa_mainloop_api *m, pa_defer_event *e, void *userdata) {
+ pa_socket_client *c = userdata;
+
+ pa_assert(m);
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+ pa_assert(c->defer_event == e);
+
+ do_call(c);
+}
+
+static void connect_io_cb(pa_mainloop_api*m, pa_io_event *e, int fd, pa_io_event_flags_t f, void *userdata) {
+ pa_socket_client *c = userdata;
+
+ pa_assert(m);
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+ pa_assert(c->io_event == e);
+ pa_assert(fd >= 0);
+
+ do_call(c);
+}
+
+static int do_connect(pa_socket_client *c, const struct sockaddr *sa, socklen_t len) {
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+ pa_assert(sa);
+ pa_assert(len > 0);
+
+ pa_make_fd_nonblock(c->fd);
+
+ if (connect(c->fd, sa, len) < 0) {
+#ifdef OS_IS_WIN32
+ if (WSAGetLastError() != EWOULDBLOCK) {
+ pa_log_debug("connect(): %d", WSAGetLastError());
+#else
+ if (errno != EINPROGRESS) {
+ pa_log_debug("connect(): %s (%d)", pa_cstrerror(errno), errno);
+#endif
+ return -1;
+ }
+
+ c->io_event = c->mainloop->io_new(c->mainloop, c->fd, PA_IO_EVENT_OUTPUT, connect_io_cb, c);
+ } else
+ c->defer_event = c->mainloop->defer_new(c->mainloop, connect_defer_cb, c);
+
+ return 0;
+}
+
+pa_socket_client* pa_socket_client_new_ipv4(pa_mainloop_api *m, uint32_t address, uint16_t port) {
+ struct sockaddr_in sa;
+
+ pa_assert(m);
+ pa_assert(port > 0);
+
+ pa_zero(sa);
+ sa.sin_family = AF_INET;
+ sa.sin_port = htons(port);
+ sa.sin_addr.s_addr = htonl(address);
+
+ return pa_socket_client_new_sockaddr(m, (struct sockaddr*) &sa, sizeof(sa));
+}
+
+pa_socket_client* pa_socket_client_new_unix(pa_mainloop_api *m, const char *filename) {
+#ifdef HAVE_SYS_UN_H
+ struct sockaddr_un sa;
+
+ pa_assert(m);
+ pa_assert(filename);
+
+ pa_zero(sa);
+ sa.sun_family = AF_UNIX;
+ pa_strlcpy(sa.sun_path, filename, sizeof(sa.sun_path));
+
+ return pa_socket_client_new_sockaddr(m, (struct sockaddr*) &sa, sizeof(sa));
+#else /* HAVE_SYS_UN_H */
+
+ return NULL;
+#endif /* HAVE_SYS_UN_H */
+}
+
+static int sockaddr_prepare(pa_socket_client *c, const struct sockaddr *sa, size_t salen) {
+ pa_assert(c);
+ pa_assert(sa);
+ pa_assert(salen);
+
+ c->local = pa_socket_address_is_local(sa);
+
+ if ((c->fd = pa_socket_cloexec(sa->sa_family, SOCK_STREAM, 0)) < 0) {
+ pa_log("socket(): %s", pa_cstrerror(errno));
+ return -1;
+ }
+
+#ifdef HAVE_IPV6
+ if (sa->sa_family == AF_INET || sa->sa_family == AF_INET6)
+#else
+ if (sa->sa_family == AF_INET)
+#endif
+ pa_make_tcp_socket_low_delay(c->fd);
+ else
+ pa_make_socket_low_delay(c->fd);
+
+ if (do_connect(c, sa, (socklen_t) salen) < 0)
+ return -1;
+
+ return 0;
+}
+
+pa_socket_client* pa_socket_client_new_sockaddr(pa_mainloop_api *m, const struct sockaddr *sa, size_t salen) {
+ pa_socket_client *c;
+
+ pa_assert(m);
+ pa_assert(sa);
+ pa_assert(salen > 0);
+
+ c = socket_client_new(m);
+
+ if (sockaddr_prepare(c, sa, salen) < 0)
+ goto fail;
+
+ return c;
+
+fail:
+ pa_socket_client_unref(c);
+ return NULL;
+}
+
+static void socket_client_free(pa_socket_client *c) {
+ pa_assert(c);
+ pa_assert(c->mainloop);
+
+ free_events(c);
+
+ if (c->fd >= 0)
+ pa_close(c->fd);
+
+#ifdef HAVE_LIBASYNCNS
+ if (c->asyncns_query)
+ asyncns_cancel(c->asyncns, c->asyncns_query);
+ if (c->asyncns)
+ asyncns_free(c->asyncns);
+ if (c->asyncns_io_event)
+ c->mainloop->io_free(c->asyncns_io_event);
+#endif
+
+ pa_xfree(c);
+}
+
+void pa_socket_client_unref(pa_socket_client *c) {
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ if (PA_REFCNT_DEC(c) <= 0)
+ socket_client_free(c);
+}
+
+pa_socket_client* pa_socket_client_ref(pa_socket_client *c) {
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ PA_REFCNT_INC(c);
+ return c;
+}
+
+void pa_socket_client_set_callback(pa_socket_client *c, pa_socket_client_cb_t on_connection, void *userdata) {
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ c->callback = on_connection;
+ c->userdata = userdata;
+}
+
+pa_socket_client* pa_socket_client_new_ipv6(pa_mainloop_api *m, uint8_t address[16], uint16_t port) {
+#ifdef HAVE_IPV6
+ struct sockaddr_in6 sa;
+
+ pa_assert(m);
+ pa_assert(address);
+ pa_assert(port > 0);
+
+ pa_zero(sa);
+ sa.sin6_family = AF_INET6;
+ sa.sin6_port = htons(port);
+ memcpy(&sa.sin6_addr, address, sizeof(sa.sin6_addr));
+
+ return pa_socket_client_new_sockaddr(m, (struct sockaddr*) &sa, sizeof(sa));
+
+#else
+ return NULL;
+#endif
+}
+
+#ifdef HAVE_LIBASYNCNS
+
+static void asyncns_cb(pa_mainloop_api*m, pa_io_event *e, int fd, pa_io_event_flags_t f, void *userdata) {
+ pa_socket_client *c = userdata;
+ struct addrinfo *res = NULL;
+ int ret;
+
+ pa_assert(m);
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+ pa_assert(c->asyncns_io_event == e);
+ pa_assert(fd >= 0);
+
+ if (asyncns_wait(c->asyncns, 0) < 0)
+ goto fail;
+
+ if (!asyncns_isdone(c->asyncns, c->asyncns_query))
+ return;
+
+ ret = asyncns_getaddrinfo_done(c->asyncns, c->asyncns_query, &res);
+ c->asyncns_query = NULL;
+
+ if (ret != 0 || !res)
+ goto fail;
+
+ if (res->ai_addr)
+ if (sockaddr_prepare(c, res->ai_addr, res->ai_addrlen) < 0)
+ goto fail;
+
+ asyncns_freeaddrinfo(res);
+
+ m->io_free(c->asyncns_io_event);
+ c->asyncns_io_event = NULL;
+ return;
+
+fail:
+ m->io_free(c->asyncns_io_event);
+ c->asyncns_io_event = NULL;
+
+ errno = EHOSTUNREACH;
+ do_call(c);
+ return;
+
+}
+
+#endif
+
+static void timeout_cb(pa_mainloop_api *m, pa_time_event *e, const struct timeval *t, void *userdata) {
+ pa_socket_client *c = userdata;
+
+ pa_assert(m);
+ pa_assert(e);
+ pa_assert(c);
+
+ if (c->fd >= 0) {
+ pa_close(c->fd);
+ c->fd = -1;
+ }
+
+ errno = ETIMEDOUT;
+ do_call(c);
+}
+
+static void start_timeout(pa_socket_client *c, bool use_rtclock) {
+ struct timeval tv;
+
+ pa_assert(c);
+ pa_assert(!c->timeout_event);
+
+ c->timeout_event = c->mainloop->time_new(c->mainloop, pa_timeval_rtstore(&tv, pa_rtclock_now() + CONNECT_TIMEOUT * PA_USEC_PER_SEC, use_rtclock), timeout_cb, c);
+}
+
+pa_socket_client* pa_socket_client_new_string(pa_mainloop_api *m, bool use_rtclock, const char*name, uint16_t default_port) {
+ pa_socket_client *c = NULL;
+ pa_parsed_address a;
+ char *name_buf;
+
+ pa_assert(m);
+ pa_assert(name);
+
+ a.path_or_host = NULL;
+
+ if (pa_is_ip6_address(name)) {
+ size_t len = strlen(name);
+ name_buf = pa_xmalloc(len + 3);
+ memcpy(name_buf + 1, name, len);
+ name_buf[0] = '[';
+ name_buf[len + 1] = ']';
+ name_buf[len + 2] = '\0';
+ } else {
+ name_buf = pa_xstrdup(name);
+ }
+
+ if (pa_parse_address(name_buf, &a) < 0) {
+ pa_log_warn("parsing address failed: %s", name_buf);
+ goto finish;
+ }
+
+ if (!a.port)
+ a.port = default_port;
+
+ switch (a.type) {
+ case PA_PARSED_ADDRESS_UNIX:
+ if ((c = pa_socket_client_new_unix(m, a.path_or_host)))
+ start_timeout(c, use_rtclock);
+ break;
+
+ case PA_PARSED_ADDRESS_TCP4: /* Fallthrough */
+ case PA_PARSED_ADDRESS_TCP6: /* Fallthrough */
+ case PA_PARSED_ADDRESS_TCP_AUTO: {
+ struct addrinfo hints;
+ char port[12];
+
+ pa_snprintf(port, sizeof(port), "%u", (unsigned) a.port);
+
+ pa_zero(hints);
+ if (a.type == PA_PARSED_ADDRESS_TCP4)
+ hints.ai_family = PF_INET;
+#ifdef HAVE_IPV6
+ else if (a.type == PA_PARSED_ADDRESS_TCP6)
+ hints.ai_family = PF_INET6;
+#endif
+ else
+ hints.ai_family = PF_UNSPEC;
+
+ hints.ai_socktype = SOCK_STREAM;
+
+#if defined(HAVE_LIBASYNCNS)
+ {
+ asyncns_t *asyncns;
+
+ if (!(asyncns = asyncns_new(1)))
+ goto finish;
+
+ c = socket_client_new(m);
+ c->asyncns = asyncns;
+ c->asyncns_io_event = m->io_new(m, asyncns_fd(c->asyncns), PA_IO_EVENT_INPUT, asyncns_cb, c);
+ pa_assert_se(c->asyncns_query = asyncns_getaddrinfo(c->asyncns, a.path_or_host, port, &hints));
+ start_timeout(c, use_rtclock);
+ }
+#elif defined(HAVE_GETADDRINFO)
+ {
+ int ret;
+ struct addrinfo *res = NULL;
+
+ ret = getaddrinfo(a.path_or_host, port, &hints, &res);
+
+ if (ret < 0 || !res)
+ goto finish;
+
+ if (res->ai_addr) {
+ if ((c = pa_socket_client_new_sockaddr(m, res->ai_addr, res->ai_addrlen)))
+ start_timeout(c, use_rtclock);
+ }
+
+ freeaddrinfo(res);
+ }
+#else
+ {
+ struct hostent *host = NULL;
+ struct sockaddr_in s;
+
+#ifdef HAVE_IPV6
+ /* FIXME: PF_INET6 support */
+ if (hints.ai_family == PF_INET6) {
+ pa_log_error("IPv6 is not supported on Windows");
+ goto finish;
+ }
+#endif
+
+ host = gethostbyname(a.path_or_host);
+ if (!host) {
+ unsigned int addr = inet_addr(a.path_or_host);
+ if (addr != INADDR_NONE)
+ host = gethostbyaddr((char*)&addr, 4, AF_INET);
+ }
+
+ if (!host)
+ goto finish;
+
+ pa_zero(s);
+ s.sin_family = AF_INET;
+ memcpy(&s.sin_addr, host->h_addr, sizeof(struct in_addr));
+ s.sin_port = htons(a.port);
+
+ if ((c = pa_socket_client_new_sockaddr(m, (struct sockaddr*)&s, sizeof(s))))
+ start_timeout(c, use_rtclock);
+ }
+#endif /* HAVE_LIBASYNCNS */
+ }
+ }
+
+finish:
+ pa_xfree(name_buf);
+ pa_xfree(a.path_or_host);
+ return c;
+
+}
+
+/* Return non-zero when the target sockaddr is considered
+ local. "local" means UNIX socket or TCP socket on localhost. Other
+ local IP addresses are not considered local. */
+bool pa_socket_client_is_local(pa_socket_client *c) {
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ return c->local;
+}
diff --git a/src/pulsecore/socket-client.h b/src/pulsecore/socket-client.h
new file mode 100644
index 0000000..9d22072
--- /dev/null
+++ b/src/pulsecore/socket-client.h
@@ -0,0 +1,48 @@
+#ifndef foosocketclienthfoo
+#define foosocketclienthfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <inttypes.h>
+
+#include <pulse/mainloop-api.h>
+#include <pulsecore/iochannel.h>
+
+struct sockaddr;
+
+typedef struct pa_socket_client pa_socket_client;
+
+typedef void (*pa_socket_client_cb_t)(pa_socket_client *c, pa_iochannel*io, void *userdata);
+
+pa_socket_client* pa_socket_client_new_ipv4(pa_mainloop_api *m, uint32_t address, uint16_t port);
+pa_socket_client* pa_socket_client_new_ipv6(pa_mainloop_api *m, uint8_t address[16], uint16_t port);
+pa_socket_client* pa_socket_client_new_unix(pa_mainloop_api *m, const char *filename);
+pa_socket_client* pa_socket_client_new_sockaddr(pa_mainloop_api *m, const struct sockaddr *sa, size_t salen);
+pa_socket_client* pa_socket_client_new_string(pa_mainloop_api *m, bool use_rtclock, const char *a, uint16_t default_port);
+
+pa_socket_client* pa_socket_client_ref(pa_socket_client *c);
+void pa_socket_client_unref(pa_socket_client *c);
+
+void pa_socket_client_set_callback(pa_socket_client *c, pa_socket_client_cb_t on_connection, void *userdata);
+
+bool pa_socket_client_is_local(pa_socket_client *c);
+
+#endif
diff --git a/src/pulsecore/socket-server.c b/src/pulsecore/socket-server.c
new file mode 100644
index 0000000..bc5116a
--- /dev/null
+++ b/src/pulsecore/socket-server.c
@@ -0,0 +1,613 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006-2007 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/types.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#ifdef HAVE_SYS_UN_H
+#include <sys/un.h>
+#ifndef SUN_LEN
+#define SUN_LEN(ptr) \
+ ((size_t)(((struct sockaddr_un *) 0)->sun_path) + strlen((ptr)->sun_path))
+#endif
+#endif
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+
+#ifdef HAVE_LIBWRAP
+#include <tcpd.h>
+
+/* Solaris requires that the allow_severity and deny_severity variables be
+ * defined in the client program. */
+#ifdef __sun
+#include <syslog.h>
+int allow_severity = LOG_INFO;
+int deny_severity = LOG_WARNING;
+#endif
+
+#endif /* HAVE_LIBWRAP */
+
+#ifdef HAVE_SYSTEMD_DAEMON
+#include <systemd/sd-daemon.h>
+#endif
+
+#include <pulse/xmalloc.h>
+#include <pulse/util.h>
+
+#include <pulsecore/socket.h>
+#include <pulsecore/socket-util.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/core-error.h>
+#include <pulsecore/refcnt.h>
+#include <pulsecore/arpa-inet.h>
+
+#include "socket-server.h"
+
+struct pa_socket_server {
+ PA_REFCNT_DECLARE;
+ int fd;
+ char *filename;
+ bool activated;
+ char *tcpwrap_service;
+
+ pa_socket_server_on_connection_cb_t on_connection;
+ void *userdata;
+
+ pa_io_event *io_event;
+ pa_mainloop_api *mainloop;
+ enum {
+ SOCKET_SERVER_IPV4,
+ SOCKET_SERVER_UNIX,
+ SOCKET_SERVER_IPV6
+ } type;
+};
+
+static void callback(pa_mainloop_api *mainloop, pa_io_event *e, int fd, pa_io_event_flags_t f, void *userdata) {
+ pa_socket_server *s = userdata;
+ pa_iochannel *io;
+ int nfd;
+
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+ pa_assert(s->mainloop == mainloop);
+ pa_assert(s->io_event == e);
+ pa_assert(e);
+ pa_assert(fd >= 0);
+ pa_assert(fd == s->fd);
+
+ pa_socket_server_ref(s);
+
+ if ((nfd = pa_accept_cloexec(fd, NULL, NULL)) < 0) {
+ pa_log("accept(): %s", pa_cstrerror(errno));
+ goto finish;
+ }
+
+ if (!s->on_connection) {
+ pa_close(nfd);
+ goto finish;
+ }
+
+#ifdef HAVE_LIBWRAP
+
+ if (s->tcpwrap_service) {
+ struct request_info req;
+
+ request_init(&req, RQ_DAEMON, s->tcpwrap_service, RQ_FILE, nfd, NULL);
+ fromhost(&req);
+ if (!hosts_access(&req)) {
+ pa_log_warn("TCP connection refused by tcpwrap.");
+ pa_close(nfd);
+ goto finish;
+ }
+
+ pa_log_info("TCP connection accepted by tcpwrap.");
+ }
+#endif
+
+ /* There should be a check for socket type here */
+ if (s->type == SOCKET_SERVER_IPV4)
+ pa_make_tcp_socket_low_delay(nfd);
+ else
+ pa_make_socket_low_delay(nfd);
+
+ pa_assert_se(io = pa_iochannel_new(s->mainloop, nfd, nfd));
+ s->on_connection(s, io, s->userdata);
+
+finish:
+ pa_socket_server_unref(s);
+}
+
+static pa_socket_server* socket_server_new(pa_mainloop_api *m, int fd) {
+ pa_socket_server *s;
+
+ pa_assert(m);
+ pa_assert(fd >= 0);
+
+ s = pa_xnew0(pa_socket_server, 1);
+ PA_REFCNT_INIT(s);
+ s->fd = fd;
+ s->mainloop = m;
+
+ pa_assert_se(s->io_event = m->io_new(m, fd, PA_IO_EVENT_INPUT, callback, s));
+
+ return s;
+}
+
+pa_socket_server* pa_socket_server_ref(pa_socket_server *s) {
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+ PA_REFCNT_INC(s);
+ return s;
+}
+
+#ifdef HAVE_SYS_UN_H
+
+pa_socket_server* pa_socket_server_new_unix(pa_mainloop_api *m, const char *filename) {
+ int fd = -1;
+ bool activated = false;
+ struct sockaddr_un sa;
+ pa_socket_server *s;
+
+ pa_assert(m);
+ pa_assert(filename);
+
+#ifdef HAVE_SYSTEMD_DAEMON
+ {
+ int n = sd_listen_fds(0);
+ if (n > 0) {
+ for (int i = 0; i < n; ++i) {
+ if (sd_is_socket_unix(SD_LISTEN_FDS_START + i, SOCK_STREAM, 1, filename, 0) > 0) {
+ fd = SD_LISTEN_FDS_START + i;
+ activated = true;
+ pa_log_info("Found socket activation socket for '%s' \\o/", filename);
+ break;
+ }
+ }
+ }
+ }
+#endif
+
+ if (fd < 0) {
+ if ((fd = pa_socket_cloexec(PF_UNIX, SOCK_STREAM, 0)) < 0) {
+ pa_log("socket(PF_UNIX): %s", pa_cstrerror(errno));
+ goto fail;
+ }
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sun_family = AF_UNIX;
+ pa_strlcpy(sa.sun_path, filename, sizeof(sa.sun_path));
+
+ pa_make_socket_low_delay(fd);
+
+ if (bind(fd, (struct sockaddr*) &sa, (socklen_t) SUN_LEN(&sa)) < 0) {
+ pa_log("bind(): %s", pa_cstrerror(errno));
+ goto fail;
+ }
+
+ /* Allow access from all clients. Sockets like this one should
+ * always be put inside a directory with proper access rights,
+ * because not all OS check the access rights on the socket
+ * inodes. */
+ chmod(filename, 0777);
+
+ if (listen(fd, 5) < 0) {
+ pa_log("listen(): %s", pa_cstrerror(errno));
+ goto fail;
+ }
+ }
+
+ pa_assert_se(s = socket_server_new(m, fd));
+
+ s->filename = pa_xstrdup(filename);
+ s->type = SOCKET_SERVER_UNIX;
+ s->activated = activated;
+
+ return s;
+
+fail:
+ if (fd >= 0)
+ pa_close(fd);
+
+ return NULL;
+}
+
+#else /* HAVE_SYS_UN_H */
+
+pa_socket_server* pa_socket_server_new_unix(pa_mainloop_api *m, const char *filename) {
+ return NULL;
+}
+
+#endif /* HAVE_SYS_UN_H */
+
+pa_socket_server* pa_socket_server_new_ipv4(pa_mainloop_api *m, uint32_t address, uint16_t port, bool fallback, const char *tcpwrap_service) {
+ pa_socket_server *ss;
+ int fd = -1;
+ bool activated = false;
+ struct sockaddr_in sa;
+ int on = 1;
+
+ pa_assert(m);
+ pa_assert(port);
+
+#ifdef HAVE_SYSTEMD_DAEMON
+ {
+ int n = sd_listen_fds(0);
+ if (n > 0) {
+ for (int i = 0; i < n; ++i) {
+ if (sd_is_socket_inet(SD_LISTEN_FDS_START + i, AF_INET, SOCK_STREAM, 1, port) > 0) {
+ fd = SD_LISTEN_FDS_START + i;
+ activated = true;
+ pa_log_info("Found socket activation socket for ipv4 in port '%d' \\o/", port);
+ break;
+ }
+ }
+ }
+ }
+#endif
+
+ if (fd < 0) {
+ if ((fd = pa_socket_cloexec(PF_INET, SOCK_STREAM, 0)) < 0) {
+ pa_log("socket(PF_INET): %s", pa_cstrerror(errno));
+ goto fail;
+ }
+
+#ifdef SO_REUSEADDR
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const void *) &on, sizeof(on)) < 0)
+ pa_log("setsockopt(): %s", pa_cstrerror(errno));
+#endif
+
+ pa_make_tcp_socket_low_delay(fd);
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sin_family = AF_INET;
+ sa.sin_port = htons(port);
+ sa.sin_addr.s_addr = htonl(address);
+
+ if (bind(fd, (struct sockaddr *) &sa, sizeof(sa)) < 0) {
+
+ if (errno == EADDRINUSE && fallback) {
+ sa.sin_port = 0;
+
+ if (bind(fd, (struct sockaddr *) &sa, sizeof(sa)) < 0) {
+ pa_log("bind(): %s", pa_cstrerror(errno));
+ goto fail;
+ }
+ } else {
+ pa_log("bind(): %s", pa_cstrerror(errno));
+ goto fail;
+ }
+ }
+
+ if (listen(fd, 5) < 0) {
+ pa_log("listen(): %s", pa_cstrerror(errno));
+ goto fail;
+ }
+ }
+
+ pa_assert_se(ss = socket_server_new(m, fd));
+
+ ss->type = SOCKET_SERVER_IPV4;
+ ss->tcpwrap_service = pa_xstrdup(tcpwrap_service);
+ ss->activated = activated;
+
+ return ss;
+
+fail:
+ if (fd >= 0)
+ pa_close(fd);
+
+ return NULL;
+}
+
+#ifdef HAVE_IPV6
+pa_socket_server* pa_socket_server_new_ipv6(pa_mainloop_api *m, const uint8_t address[16], uint16_t port, bool fallback, const char *tcpwrap_service) {
+ pa_socket_server *ss;
+ int fd = -1;
+ bool activated = false;
+ struct sockaddr_in6 sa;
+ int on;
+
+ pa_assert(m);
+ pa_assert(port > 0);
+
+#ifdef HAVE_SYSTEMD_DAEMON
+ {
+ int n = sd_listen_fds(0);
+ if (n > 0) {
+ for (int i = 0; i < n; ++i) {
+ if (sd_is_socket_inet(SD_LISTEN_FDS_START + i, AF_INET6, SOCK_STREAM, 1, port) > 0) {
+ fd = SD_LISTEN_FDS_START + i;
+ activated = true;
+ pa_log_info("Found socket activation socket for ipv6 in port '%d' \\o/", port);
+ break;
+ }
+ }
+ }
+ }
+#endif
+
+ if (fd < 0) {
+ if ((fd = pa_socket_cloexec(PF_INET6, SOCK_STREAM, 0)) < 0) {
+ pa_log("socket(PF_INET6): %s", pa_cstrerror(errno));
+ goto fail;
+ }
+
+#ifdef IPV6_V6ONLY
+ on = 1;
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (const void *) &on, sizeof(on)) < 0)
+ pa_log("setsockopt(IPPROTO_IPV6, IPV6_V6ONLY): %s", pa_cstrerror(errno));
+#endif
+
+#ifdef SO_REUSEADDR
+ on = 1;
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const void *) &on, sizeof(on)) < 0)
+ pa_log("setsockopt(SOL_SOCKET, SO_REUSEADDR, 1): %s", pa_cstrerror(errno));
+#endif
+
+ pa_make_tcp_socket_low_delay(fd);
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sin6_family = AF_INET6;
+ sa.sin6_port = htons(port);
+ memcpy(sa.sin6_addr.s6_addr, address, 16);
+
+ if (bind(fd, (struct sockaddr *) &sa, sizeof(sa)) < 0) {
+
+ if (errno == EADDRINUSE && fallback) {
+ sa.sin6_port = 0;
+
+ if (bind(fd, (struct sockaddr *) &sa, sizeof(sa)) < 0) {
+ pa_log("bind(): %s", pa_cstrerror(errno));
+ goto fail;
+ }
+ } else {
+ pa_log("bind(): %s", pa_cstrerror(errno));
+ goto fail;
+ }
+ }
+
+ if (listen(fd, 5) < 0) {
+ pa_log("listen(): %s", pa_cstrerror(errno));
+ goto fail;
+ }
+ }
+
+ pa_assert_se(ss = socket_server_new(m, fd));
+
+ ss->type = SOCKET_SERVER_IPV6;
+ ss->tcpwrap_service = pa_xstrdup(tcpwrap_service);
+ ss->activated = activated;
+
+ return ss;
+
+fail:
+ if (fd >= 0)
+ pa_close(fd);
+
+ return NULL;
+}
+#endif
+
+pa_socket_server* pa_socket_server_new_ipv4_loopback(pa_mainloop_api *m, uint16_t port, bool fallback, const char *tcpwrap_service) {
+ pa_assert(m);
+ pa_assert(port > 0);
+
+ return pa_socket_server_new_ipv4(m, INADDR_LOOPBACK, port, fallback, tcpwrap_service);
+}
+
+#ifdef HAVE_IPV6
+pa_socket_server* pa_socket_server_new_ipv6_loopback(pa_mainloop_api *m, uint16_t port, bool fallback, const char *tcpwrap_service) {
+ pa_assert(m);
+ pa_assert(port > 0);
+
+ return pa_socket_server_new_ipv6(m, in6addr_loopback.s6_addr, port, fallback, tcpwrap_service);
+}
+#endif
+
+pa_socket_server* pa_socket_server_new_ipv4_any(pa_mainloop_api *m, uint16_t port, bool fallback, const char *tcpwrap_service) {
+ pa_assert(m);
+ pa_assert(port > 0);
+
+ return pa_socket_server_new_ipv4(m, INADDR_ANY, port, fallback, tcpwrap_service);
+}
+
+#ifdef HAVE_IPV6
+pa_socket_server* pa_socket_server_new_ipv6_any(pa_mainloop_api *m, uint16_t port, bool fallback, const char *tcpwrap_service) {
+ pa_assert(m);
+ pa_assert(port > 0);
+
+ return pa_socket_server_new_ipv6(m, in6addr_any.s6_addr, port, fallback, tcpwrap_service);
+}
+#endif
+
+pa_socket_server* pa_socket_server_new_ipv4_string(pa_mainloop_api *m, const char *name, uint16_t port, bool fallback, const char *tcpwrap_service) {
+ struct in_addr ipv4;
+
+ pa_assert(m);
+ pa_assert(name);
+ pa_assert(port > 0);
+
+ if (inet_pton(AF_INET, name, &ipv4) > 0)
+ return pa_socket_server_new_ipv4(m, ntohl(ipv4.s_addr), port, fallback, tcpwrap_service);
+
+ return NULL;
+}
+
+#ifdef HAVE_IPV6
+pa_socket_server* pa_socket_server_new_ipv6_string(pa_mainloop_api *m, const char *name, uint16_t port, bool fallback, const char *tcpwrap_service) {
+ struct in6_addr ipv6;
+
+ pa_assert(m);
+ pa_assert(name);
+ pa_assert(port > 0);
+
+ if (inet_pton(AF_INET6, name, &ipv6) > 0)
+ return pa_socket_server_new_ipv6(m, ipv6.s6_addr, port, fallback, tcpwrap_service);
+
+ return NULL;
+}
+#endif
+
+static void socket_server_free(pa_socket_server*s) {
+ pa_assert(s);
+
+ if (!s->activated && s->filename)
+ unlink(s->filename);
+ pa_xfree(s->filename);
+
+ pa_close(s->fd);
+
+ pa_xfree(s->tcpwrap_service);
+
+ s->mainloop->io_free(s->io_event);
+ pa_xfree(s);
+}
+
+void pa_socket_server_unref(pa_socket_server *s) {
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+ if (PA_REFCNT_DEC(s) <= 0)
+ socket_server_free(s);
+}
+
+void pa_socket_server_set_callback(pa_socket_server*s, pa_socket_server_on_connection_cb_t on_connection, void *userdata) {
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+ s->on_connection = on_connection;
+ s->userdata = userdata;
+}
+
+char *pa_socket_server_get_address(pa_socket_server *s, char *c, size_t l) {
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+ pa_assert(c);
+ pa_assert(l > 0);
+
+ switch (s->type) {
+#ifdef HAVE_IPV6
+ case SOCKET_SERVER_IPV6: {
+ struct sockaddr_in6 sa;
+ socklen_t sa_len = sizeof(sa);
+
+ if (getsockname(s->fd, (struct sockaddr*) &sa, &sa_len) < 0) {
+ pa_log("getsockname(): %s", pa_cstrerror(errno));
+ return NULL;
+ }
+
+ if (memcmp(&in6addr_any, &sa.sin6_addr, sizeof(in6addr_any)) == 0) {
+ char fqdn[256];
+ if (!pa_get_fqdn(fqdn, sizeof(fqdn)))
+ return NULL;
+
+ pa_snprintf(c, l, "tcp6:%s:%u", fqdn, (unsigned) ntohs(sa.sin6_port));
+
+ } else if (memcmp(&in6addr_loopback, &sa.sin6_addr, sizeof(in6addr_loopback)) == 0) {
+ char *id;
+
+ if (!(id = pa_machine_id()))
+ return NULL;
+
+ pa_snprintf(c, l, "{%s}tcp6:localhost:%u", id, (unsigned) ntohs(sa.sin6_port));
+ pa_xfree(id);
+ } else {
+ char ip[INET6_ADDRSTRLEN];
+
+ if (!inet_ntop(AF_INET6, &sa.sin6_addr, ip, sizeof(ip))) {
+ pa_log("inet_ntop(): %s", pa_cstrerror(errno));
+ return NULL;
+ }
+
+ pa_snprintf(c, l, "tcp6:[%s]:%u", ip, (unsigned) ntohs(sa.sin6_port));
+ }
+
+ return c;
+ }
+#endif
+
+ case SOCKET_SERVER_IPV4: {
+ struct sockaddr_in sa;
+ socklen_t sa_len = sizeof(sa);
+
+ if (getsockname(s->fd, (struct sockaddr*) &sa, &sa_len) < 0) {
+ pa_log("getsockname(): %s", pa_cstrerror(errno));
+ return NULL;
+ }
+
+ if (sa.sin_addr.s_addr == INADDR_ANY) {
+ char fqdn[256];
+ if (!pa_get_fqdn(fqdn, sizeof(fqdn)))
+ return NULL;
+
+ pa_snprintf(c, l, "tcp:%s:%u", fqdn, (unsigned) ntohs(sa.sin_port));
+ } else if (sa.sin_addr.s_addr == INADDR_LOOPBACK) {
+ char *id;
+
+ if (!(id = pa_machine_id()))
+ return NULL;
+
+ pa_snprintf(c, l, "{%s}tcp:localhost:%u", id, (unsigned) ntohs(sa.sin_port));
+ pa_xfree(id);
+ } else {
+ char ip[INET_ADDRSTRLEN];
+
+ if (!inet_ntop(AF_INET, &sa.sin_addr, ip, sizeof(ip))) {
+ pa_log("inet_ntop(): %s", pa_cstrerror(errno));
+ return NULL;
+ }
+
+ pa_snprintf(c, l, "tcp:[%s]:%u", ip, (unsigned) ntohs(sa.sin_port));
+ }
+
+ return c;
+ }
+
+ case SOCKET_SERVER_UNIX: {
+ char *id;
+
+ if (!s->filename)
+ return NULL;
+
+ if (!(id = pa_machine_id()))
+ return NULL;
+
+ pa_snprintf(c, l, "{%s}unix:%s", id, s->filename);
+ pa_xfree(id);
+ return c;
+ }
+
+ default:
+ return NULL;
+ }
+}
diff --git a/src/pulsecore/socket-server.h b/src/pulsecore/socket-server.h
new file mode 100644
index 0000000..0793baf
--- /dev/null
+++ b/src/pulsecore/socket-server.h
@@ -0,0 +1,53 @@
+#ifndef foosocketserverhfoo
+#define foosocketserverhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <inttypes.h>
+#include <pulse/mainloop-api.h>
+#include <pulsecore/iochannel.h>
+
+/* It is safe to destroy the calling socket_server object from the callback */
+
+typedef struct pa_socket_server pa_socket_server;
+
+pa_socket_server* pa_socket_server_new_unix(pa_mainloop_api *m, const char *filename);
+pa_socket_server* pa_socket_server_new_ipv4(pa_mainloop_api *m, uint32_t address, uint16_t port, bool fallback, const char *tcpwrap_service);
+pa_socket_server* pa_socket_server_new_ipv4_loopback(pa_mainloop_api *m, uint16_t port, bool fallback, const char *tcpwrap_service);
+pa_socket_server* pa_socket_server_new_ipv4_any(pa_mainloop_api *m, uint16_t port, bool fallback, const char *tcpwrap_service);
+pa_socket_server* pa_socket_server_new_ipv4_string(pa_mainloop_api *m, const char *name, uint16_t port, bool fallback, const char *tcpwrap_service);
+#ifdef HAVE_IPV6
+pa_socket_server* pa_socket_server_new_ipv6(pa_mainloop_api *m, const uint8_t address[16], uint16_t port, bool fallback, const char *tcpwrap_service);
+pa_socket_server* pa_socket_server_new_ipv6_loopback(pa_mainloop_api *m, uint16_t port, bool fallback, const char *tcpwrap_service);
+pa_socket_server* pa_socket_server_new_ipv6_any(pa_mainloop_api *m, uint16_t port, bool fallback, const char *tcpwrap_service);
+pa_socket_server* pa_socket_server_new_ipv6_string(pa_mainloop_api *m, const char *name, uint16_t port, bool fallback, const char *tcpwrap_service);
+#endif
+
+void pa_socket_server_unref(pa_socket_server*s);
+pa_socket_server* pa_socket_server_ref(pa_socket_server *s);
+
+typedef void (*pa_socket_server_on_connection_cb_t)(pa_socket_server*s, pa_iochannel *io, void *userdata);
+
+void pa_socket_server_set_callback(pa_socket_server*s, pa_socket_server_on_connection_cb_t connection_cb, void *userdata);
+
+char *pa_socket_server_get_address(pa_socket_server *s, char *c, size_t l);
+
+#endif
diff --git a/src/pulsecore/socket-util.c b/src/pulsecore/socket-util.c
new file mode 100644
index 0000000..e389ef2
--- /dev/null
+++ b/src/pulsecore/socket-util.c
@@ -0,0 +1,338 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2004 Joe Marcus Clarke
+ Copyright 2006-2007 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <signal.h>
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#ifdef HAVE_SYS_UN_H
+#include <sys/un.h>
+#endif
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+#ifdef HAVE_NETINET_IN_SYSTM_H
+#include <netinet/in_systm.h>
+#endif
+#ifdef HAVE_NETINET_IP_H
+#include <netinet/ip.h>
+#endif
+#ifdef HAVE_NETINET_TCP_H
+#include <netinet/tcp.h>
+#endif
+#ifdef HAVE_NETDB_H
+#include <netdb.h>
+#endif
+#ifdef HAVE_SYSTEMD_DAEMON
+#include <systemd/sd-daemon.h>
+#endif
+
+#include <pulsecore/core-error.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/socket.h>
+#include <pulsecore/arpa-inet.h>
+
+#include "socket-util.h"
+
+void pa_socket_peer_to_string(int fd, char *c, size_t l) {
+#ifndef OS_IS_WIN32
+ struct stat st;
+#endif
+
+ pa_assert(fd >= 0);
+ pa_assert(c);
+ pa_assert(l > 0);
+
+#ifndef OS_IS_WIN32
+ pa_assert_se(fstat(fd, &st) == 0);
+
+ if (S_ISSOCK(st.st_mode))
+#endif
+ {
+ union {
+ struct sockaddr_storage storage;
+ struct sockaddr sa;
+ struct sockaddr_in in;
+#ifdef HAVE_IPV6
+ struct sockaddr_in6 in6;
+#endif
+#ifdef HAVE_SYS_UN_H
+ struct sockaddr_un un;
+#endif
+ } sa;
+ socklen_t sa_len = sizeof(sa);
+
+ if (getpeername(fd, &sa.sa, &sa_len) >= 0) {
+
+ if (sa.sa.sa_family == AF_INET) {
+ uint32_t ip = ntohl(sa.in.sin_addr.s_addr);
+
+ pa_snprintf(c, l, "TCP/IP client from %i.%i.%i.%i:%u",
+ ip >> 24,
+ (ip >> 16) & 0xFF,
+ (ip >> 8) & 0xFF,
+ ip & 0xFF,
+ ntohs(sa.in.sin_port));
+ return;
+#ifdef HAVE_IPV6
+ } else if (sa.sa.sa_family == AF_INET6) {
+ char buf[INET6_ADDRSTRLEN];
+ const char *res;
+
+ res = inet_ntop(AF_INET6, &sa.in6.sin6_addr, buf, sizeof(buf));
+ if (res) {
+ pa_snprintf(c, l, "TCP/IP client from [%s]:%u", buf, ntohs(sa.in6.sin6_port));
+ return;
+ }
+#endif
+#ifdef HAVE_SYS_UN_H
+ } else if (sa.sa.sa_family == AF_UNIX) {
+ pa_snprintf(c, l, "UNIX socket client");
+ return;
+#endif
+ }
+ }
+
+ pa_snprintf(c, l, "Unknown network client");
+ return;
+ }
+#ifndef OS_IS_WIN32
+ else if (S_ISCHR(st.st_mode) && (fd == 0 || fd == 1)) {
+ pa_snprintf(c, l, "STDIN/STDOUT client");
+ return;
+ }
+#endif /* OS_IS_WIN32 */
+
+ pa_snprintf(c, l, "Unknown client");
+}
+
+void pa_make_socket_low_delay(int fd) {
+
+#ifdef SO_PRIORITY
+ int priority;
+ pa_assert(fd >= 0);
+
+ priority = 6;
+ if (setsockopt(fd, SOL_SOCKET, SO_PRIORITY, (const void *) &priority, sizeof(priority)) < 0)
+ pa_log_warn("SO_PRIORITY failed: %s", pa_cstrerror(errno));
+#endif
+}
+
+void pa_make_tcp_socket_low_delay(int fd) {
+ pa_assert(fd >= 0);
+
+ pa_make_socket_low_delay(fd);
+
+#if defined(SOL_TCP) || defined(IPPROTO_TCP)
+ {
+ int on = 1;
+#if defined(SOL_TCP)
+ if (setsockopt(fd, SOL_TCP, TCP_NODELAY, (const void *) &on, sizeof(on)) < 0)
+#else
+ if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (const void *) &on, sizeof(on)) < 0)
+#endif
+ pa_log_warn("TCP_NODELAY failed: %s", pa_cstrerror(errno));
+ }
+#endif
+
+#if defined(IPTOS_LOWDELAY) && defined(IP_TOS) && (defined(SOL_IP) || defined(IPPROTO_IP))
+ {
+ int tos = IPTOS_LOWDELAY;
+#ifdef SOL_IP
+ if (setsockopt(fd, SOL_IP, IP_TOS, (const void *) &tos, sizeof(tos)) < 0)
+#else
+ if (setsockopt(fd, IPPROTO_IP, IP_TOS, (const void *) &tos, sizeof(tos)) < 0)
+#endif
+ pa_log_warn("IP_TOS failed: %s", pa_cstrerror(errno));
+ }
+#endif
+}
+
+void pa_make_udp_socket_low_delay(int fd) {
+ pa_assert(fd >= 0);
+
+ pa_make_socket_low_delay(fd);
+
+#if defined(IPTOS_LOWDELAY) && defined(IP_TOS) && (defined(SOL_IP) || defined(IPPROTO_IP))
+ {
+ int tos = IPTOS_LOWDELAY;
+#ifdef SOL_IP
+ if (setsockopt(fd, SOL_IP, IP_TOS, (const void *) &tos, sizeof(tos)) < 0)
+#else
+ if (setsockopt(fd, IPPROTO_IP, IP_TOS, (const void *) &tos, sizeof(tos)) < 0)
+#endif
+ pa_log_warn("IP_TOS failed: %s", pa_cstrerror(errno));
+ }
+#endif
+}
+
+int pa_socket_set_rcvbuf(int fd, size_t l) {
+ int bufsz = (int) l;
+
+ pa_assert(fd >= 0);
+
+ if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (const void *) &bufsz, sizeof(bufsz)) < 0) {
+ pa_log_warn("SO_RCVBUF: %s", pa_cstrerror(errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+int pa_socket_set_sndbuf(int fd, size_t l) {
+ int bufsz = (int) l;
+
+ pa_assert(fd >= 0);
+
+ if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (const void *) &bufsz, sizeof(bufsz)) < 0) {
+ pa_log_warn("SO_SNDBUF: %s", pa_cstrerror(errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+#ifdef HAVE_SYS_UN_H
+
+int pa_unix_socket_is_stale(const char *fn) {
+ struct sockaddr_un sa;
+ int fd = -1, ret = -1;
+
+ pa_assert(fn);
+
+ if ((fd = pa_socket_cloexec(PF_UNIX, SOCK_STREAM, 0)) < 0) {
+ pa_log("socket(): %s", pa_cstrerror(errno));
+ goto finish;
+ }
+
+ sa.sun_family = AF_UNIX;
+ strncpy(sa.sun_path, fn, sizeof(sa.sun_path)-1);
+ sa.sun_path[sizeof(sa.sun_path) - 1] = 0;
+
+ if (connect(fd, (struct sockaddr*) &sa, sizeof(sa)) < 0) {
+ if (errno == ECONNREFUSED)
+ ret = 1;
+ } else
+ ret = 0;
+
+finish:
+ if (fd >= 0)
+ pa_close(fd);
+
+ return ret;
+}
+
+int pa_unix_socket_remove_stale(const char *fn) {
+ int r;
+
+ pa_assert(fn);
+
+#ifdef HAVE_SYSTEMD_DAEMON
+ {
+ int n = sd_listen_fds(0);
+ if (n > 0) {
+ for (int i = 0; i < n; ++i) {
+ if (sd_is_socket_unix(SD_LISTEN_FDS_START + i, SOCK_STREAM, 1, fn, 0) > 0) {
+ /* This is a socket activated socket, therefore do not consider
+ * it stale. */
+ return 0;
+ }
+ }
+ }
+ }
+#endif
+
+ if ((r = pa_unix_socket_is_stale(fn)) < 0)
+ return errno != ENOENT ? -1 : 0;
+
+ if (!r)
+ return 0;
+
+ /* Yes, here is a race condition. But who cares? */
+ if (unlink(fn) < 0)
+ return -1;
+
+ return 0;
+}
+
+#else /* HAVE_SYS_UN_H */
+
+int pa_unix_socket_is_stale(const char *fn) {
+ return -1;
+}
+
+int pa_unix_socket_remove_stale(const char *fn) {
+ return -1;
+}
+
+#endif /* HAVE_SYS_UN_H */
+
+bool pa_socket_address_is_local(const struct sockaddr *sa) {
+ pa_assert(sa);
+
+ switch (sa->sa_family) {
+ case AF_UNIX:
+ return true;
+
+ case AF_INET:
+ return ((const struct sockaddr_in*) sa)->sin_addr.s_addr == INADDR_LOOPBACK;
+
+#ifdef HAVE_IPV6
+ case AF_INET6:
+ return memcmp(&((const struct sockaddr_in6*) sa)->sin6_addr, &in6addr_loopback, sizeof(struct in6_addr)) == 0;
+#endif
+
+ default:
+ return false;
+ }
+}
+
+bool pa_socket_is_local(int fd) {
+
+ union {
+ struct sockaddr_storage storage;
+ struct sockaddr sa;
+ struct sockaddr_in in;
+#ifdef HAVE_IPV6
+ struct sockaddr_in6 in6;
+#endif
+#ifdef HAVE_SYS_UN_H
+ struct sockaddr_un un;
+#endif
+ } sa;
+ socklen_t sa_len = sizeof(sa);
+
+ if (getpeername(fd, &sa.sa, &sa_len) < 0)
+ return false;
+
+ return pa_socket_address_is_local(&sa.sa);
+}
diff --git a/src/pulsecore/socket-util.h b/src/pulsecore/socket-util.h
new file mode 100644
index 0000000..f120769
--- /dev/null
+++ b/src/pulsecore/socket-util.h
@@ -0,0 +1,44 @@
+#ifndef foosocketutilhfoo
+#define foosocketutilhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/types.h>
+
+#include <pulsecore/socket.h>
+#include <pulsecore/macro.h>
+
+void pa_socket_peer_to_string(int fd, char *c, size_t l);
+
+void pa_make_socket_low_delay(int fd);
+void pa_make_tcp_socket_low_delay(int fd);
+void pa_make_udp_socket_low_delay(int fd);
+
+int pa_socket_set_sndbuf(int fd, size_t l);
+int pa_socket_set_rcvbuf(int fd, size_t l);
+
+int pa_unix_socket_is_stale(const char *fn);
+int pa_unix_socket_remove_stale(const char *fn);
+
+bool pa_socket_address_is_local(const struct sockaddr *sa);
+bool pa_socket_is_local(int fd);
+
+#endif
diff --git a/src/pulsecore/socket.h b/src/pulsecore/socket.h
new file mode 100644
index 0000000..72f2228
--- /dev/null
+++ b/src/pulsecore/socket.h
@@ -0,0 +1,20 @@
+#ifndef foopulsecoresockethfoo
+#define foopulsecoresockethfoo
+
+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+
+#ifdef HAVE_WINSOCK2_H
+#include <winsock2.h>
+#include "winerrno.h"
+
+typedef long suseconds_t;
+
+#endif
+
+#ifdef HAVE_WS2TCPIP_H
+#include <ws2tcpip.h>
+#endif
+
+#endif
diff --git a/src/pulsecore/sound-file-stream.c b/src/pulsecore/sound-file-stream.c
new file mode 100644
index 0000000..147aa22
--- /dev/null
+++ b/src/pulsecore/sound-file-stream.c
@@ -0,0 +1,339 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2008 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include <sndfile.h>
+
+#include <pulse/xmalloc.h>
+#include <pulse/util.h>
+
+#include <pulsecore/core-error.h>
+#include <pulsecore/sink-input.h>
+#include <pulsecore/log.h>
+#include <pulsecore/thread-mq.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/mix.h>
+#include <pulsecore/sndfile-util.h>
+
+#include "sound-file-stream.h"
+
+#define MEMBLOCKQ_MAXLENGTH (16*1024*1024)
+
+typedef struct file_stream {
+ pa_msgobject parent;
+ pa_core *core;
+ pa_sink_input *sink_input;
+
+ SNDFILE *sndfile;
+ sf_count_t (*readf_function)(SNDFILE *sndfile, void *ptr, sf_count_t frames);
+
+ /* We need this memblockq here to easily fulfill rewind requests
+ * (even beyond the file start!) */
+ pa_memblockq *memblockq;
+} file_stream;
+
+enum {
+ FILE_STREAM_MESSAGE_UNLINK
+};
+
+PA_DEFINE_PRIVATE_CLASS(file_stream, pa_msgobject);
+#define FILE_STREAM(o) (file_stream_cast(o))
+
+/* Called from main context */
+static void file_stream_unlink(file_stream *u) {
+ pa_assert(u);
+
+ if (!u->sink_input)
+ return;
+
+ pa_sink_input_unlink(u->sink_input);
+ pa_sink_input_unref(u->sink_input);
+ u->sink_input = NULL;
+
+ /* Make sure we don't decrease the ref count twice. */
+ file_stream_unref(u);
+}
+
+/* Called from main context */
+static void file_stream_free(pa_object *o) {
+ file_stream *u = FILE_STREAM(o);
+ pa_assert(u);
+
+ if (u->memblockq)
+ pa_memblockq_free(u->memblockq);
+
+ if (u->sndfile)
+ sf_close(u->sndfile);
+
+ pa_xfree(u);
+}
+
+/* Called from main context */
+static int file_stream_process_msg(pa_msgobject *o, int code, void*userdata, int64_t offset, pa_memchunk *chunk) {
+ file_stream *u = FILE_STREAM(o);
+ file_stream_assert_ref(u);
+
+ switch (code) {
+ case FILE_STREAM_MESSAGE_UNLINK:
+ file_stream_unlink(u);
+ break;
+ }
+
+ return 0;
+}
+
+/* Called from main context */
+static void sink_input_kill_cb(pa_sink_input *i) {
+ file_stream *u;
+
+ pa_sink_input_assert_ref(i);
+ u = FILE_STREAM(i->userdata);
+ file_stream_assert_ref(u);
+
+ file_stream_unlink(u);
+}
+
+/* Called from IO thread context */
+static void sink_input_state_change_cb(pa_sink_input *i, pa_sink_input_state_t state) {
+ file_stream *u;
+
+ pa_sink_input_assert_ref(i);
+ u = FILE_STREAM(i->userdata);
+ file_stream_assert_ref(u);
+
+ /* If we are added for the first time, ask for a rewinding so that
+ * we are heard right-away. */
+ if (PA_SINK_INPUT_IS_LINKED(state) &&
+ i->thread_info.state == PA_SINK_INPUT_INIT && i->sink)
+ pa_sink_input_request_rewind(i, 0, false, true, true);
+}
+
+/* Called from IO thread context */
+static int sink_input_pop_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) {
+ file_stream *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert(chunk);
+ u = FILE_STREAM(i->userdata);
+ file_stream_assert_ref(u);
+
+ if (!u->memblockq)
+ return -1;
+
+ for (;;) {
+ pa_memchunk tchunk;
+ size_t fs;
+ void *p;
+ sf_count_t n;
+
+ if (pa_memblockq_peek(u->memblockq, chunk) >= 0) {
+ chunk->length = PA_MIN(chunk->length, length);
+ pa_memblockq_drop(u->memblockq, chunk->length);
+ return 0;
+ }
+
+ if (!u->sndfile)
+ break;
+
+ tchunk.memblock = pa_memblock_new(i->sink->core->mempool, length);
+ tchunk.index = 0;
+
+ p = pa_memblock_acquire(tchunk.memblock);
+
+ if (u->readf_function) {
+ fs = pa_frame_size(&i->sample_spec);
+ n = u->readf_function(u->sndfile, p, (sf_count_t) (length/fs));
+ } else {
+ fs = 1;
+ n = sf_read_raw(u->sndfile, p, (sf_count_t) length);
+ }
+
+ pa_memblock_release(tchunk.memblock);
+
+ if (n <= 0) {
+ pa_memblock_unref(tchunk.memblock);
+
+ sf_close(u->sndfile);
+ u->sndfile = NULL;
+ break;
+ }
+
+ tchunk.length = (size_t) n * fs;
+
+ pa_memblockq_push(u->memblockq, &tchunk);
+ pa_memblock_unref(tchunk.memblock);
+ }
+
+ if (pa_sink_input_safe_to_remove(i)) {
+ pa_memblockq_free(u->memblockq);
+ u->memblockq = NULL;
+
+ pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(u), FILE_STREAM_MESSAGE_UNLINK, NULL, 0, NULL, NULL);
+ }
+
+ return -1;
+}
+
+static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) {
+ file_stream *u;
+
+ pa_sink_input_assert_ref(i);
+ u = FILE_STREAM(i->userdata);
+ file_stream_assert_ref(u);
+
+ if (!u->memblockq)
+ return;
+
+ pa_memblockq_rewind(u->memblockq, nbytes);
+}
+
+static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) {
+ file_stream *u;
+
+ pa_sink_input_assert_ref(i);
+ u = FILE_STREAM(i->userdata);
+ file_stream_assert_ref(u);
+
+ if (!u->memblockq)
+ return;
+
+ pa_memblockq_set_maxrewind(u->memblockq, nbytes);
+}
+
+int pa_play_file(
+ pa_sink *sink,
+ const char *fname,
+ const pa_cvolume *volume) {
+
+ file_stream *u = NULL;
+ pa_sample_spec ss;
+ pa_channel_map cm;
+ pa_sink_input_new_data data;
+ int fd;
+ SF_INFO sfi;
+ pa_memchunk silence;
+
+ pa_assert(sink);
+ pa_assert(fname);
+
+ u = pa_msgobject_new(file_stream);
+ u->parent.parent.free = file_stream_free;
+ u->parent.process_msg = file_stream_process_msg;
+ u->core = sink->core;
+ u->sink_input = NULL;
+ u->sndfile = NULL;
+ u->readf_function = NULL;
+ u->memblockq = NULL;
+
+ if ((fd = pa_open_cloexec(fname, O_RDONLY, 0)) < 0) {
+ pa_log("Failed to open file %s: %s", fname, pa_cstrerror(errno));
+ goto fail;
+ }
+
+ /* FIXME: For now we just use posix_fadvise to avoid page faults
+ * when accessing the file data. Eventually we should move the
+ * file reader into the main event loop and pass the data over the
+ * asyncmsgq. */
+
+#ifdef HAVE_POSIX_FADVISE
+ if (posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL) < 0) {
+ pa_log_warn("POSIX_FADV_SEQUENTIAL failed: %s", pa_cstrerror(errno));
+ goto fail;
+ } else
+ pa_log_debug("POSIX_FADV_SEQUENTIAL succeeded.");
+
+ if (posix_fadvise(fd, 0, 0, POSIX_FADV_WILLNEED) < 0) {
+ pa_log_warn("POSIX_FADV_WILLNEED failed: %s", pa_cstrerror(errno));
+ goto fail;
+ } else
+ pa_log_debug("POSIX_FADV_WILLNEED succeeded.");
+#endif
+
+ pa_zero(sfi);
+ if (!(u->sndfile = sf_open_fd(fd, SFM_READ, &sfi, 1))) {
+ pa_log("Failed to open file %s", fname);
+ goto fail;
+ }
+
+ fd = -1;
+
+ if (pa_sndfile_read_sample_spec(u->sndfile, &ss) < 0) {
+ pa_log("Failed to determine file sample format.");
+ goto fail;
+ }
+
+ if (pa_sndfile_read_channel_map(u->sndfile, &cm) < 0) {
+ if (ss.channels > 2)
+ pa_log_info("Failed to determine file channel map, synthesizing one.");
+ pa_channel_map_init_extend(&cm, ss.channels, PA_CHANNEL_MAP_DEFAULT);
+ }
+
+ u->readf_function = pa_sndfile_readf_function(&ss);
+
+ pa_sink_input_new_data_init(&data);
+ pa_sink_input_new_data_set_sink(&data, sink, false, true);
+ data.driver = __FILE__;
+ pa_sink_input_new_data_set_sample_spec(&data, &ss);
+ pa_sink_input_new_data_set_channel_map(&data, &cm);
+ pa_sink_input_new_data_set_volume(&data, volume);
+ pa_proplist_sets(data.proplist, PA_PROP_MEDIA_NAME, pa_path_get_filename(fname));
+ pa_proplist_sets(data.proplist, PA_PROP_MEDIA_FILENAME, fname);
+ pa_sndfile_init_proplist(u->sndfile, data.proplist);
+
+ pa_sink_input_new(&u->sink_input, sink->core, &data);
+ pa_sink_input_new_data_done(&data);
+
+ if (!u->sink_input)
+ goto fail;
+
+ u->sink_input->pop = sink_input_pop_cb;
+ u->sink_input->process_rewind = sink_input_process_rewind_cb;
+ u->sink_input->update_max_rewind = sink_input_update_max_rewind_cb;
+ u->sink_input->kill = sink_input_kill_cb;
+ u->sink_input->state_change = sink_input_state_change_cb;
+ u->sink_input->userdata = u;
+
+ pa_sink_input_get_silence(u->sink_input, &silence);
+ u->memblockq = pa_memblockq_new("sound-file-stream memblockq", 0, MEMBLOCKQ_MAXLENGTH, 0, &ss, 1, 1, 0, &silence);
+ pa_memblock_unref(silence.memblock);
+
+ pa_sink_input_put(u->sink_input);
+
+ /* The reference to u is dangling here, because we want to keep
+ * this stream around until it is fully played. */
+
+ return 0;
+
+fail:
+ file_stream_unref(u);
+
+ if (fd >= 0)
+ pa_close(fd);
+
+ return -1;
+}
diff --git a/src/pulsecore/sound-file-stream.h b/src/pulsecore/sound-file-stream.h
new file mode 100644
index 0000000..de55c6d
--- /dev/null
+++ b/src/pulsecore/sound-file-stream.h
@@ -0,0 +1,27 @@
+#ifndef foosoundfilestreamhfoo
+#define foosoundfilestreamhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulsecore/sink.h>
+
+int pa_play_file(pa_sink *sink, const char *fname, const pa_cvolume *volume);
+
+#endif
diff --git a/src/pulsecore/sound-file.c b/src/pulsecore/sound-file.c
new file mode 100644
index 0000000..f678430
--- /dev/null
+++ b/src/pulsecore/sound-file.c
@@ -0,0 +1,163 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include <sndfile.h>
+
+#include <pulse/sample.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/core-error.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/core-scache.h>
+#include <pulsecore/sndfile-util.h>
+
+#include "sound-file.h"
+
+int pa_sound_file_load(
+ pa_mempool *pool,
+ const char *fname,
+ pa_sample_spec *ss,
+ pa_channel_map *map,
+ pa_memchunk *chunk,
+ pa_proplist *p) {
+
+ SNDFILE *sf = NULL;
+ SF_INFO sfi;
+ int ret = -1;
+ size_t l;
+ sf_count_t (*readf_function)(SNDFILE *sndfile, void *ptr, sf_count_t frames) = NULL;
+ void *ptr = NULL;
+ int fd;
+
+ pa_assert(fname);
+ pa_assert(ss);
+ pa_assert(chunk);
+
+ pa_memchunk_reset(chunk);
+
+ if ((fd = pa_open_cloexec(fname, O_RDONLY, 0)) < 0) {
+ pa_log("Failed to open file %s: %s", fname, pa_cstrerror(errno));
+ goto finish;
+ }
+
+#ifdef HAVE_POSIX_FADVISE
+ if (posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL) < 0) {
+ pa_log_warn("POSIX_FADV_SEQUENTIAL failed: %s", pa_cstrerror(errno));
+ goto finish;
+ } else
+ pa_log_debug("POSIX_FADV_SEQUENTIAL succeeded.");
+#endif
+
+ pa_zero(sfi);
+ if (!(sf = sf_open_fd(fd, SFM_READ, &sfi, 1))) {
+ pa_log("Failed to open file %s", fname);
+ goto finish;
+ }
+
+ fd = -1;
+
+ if (pa_sndfile_read_sample_spec(sf, ss) < 0) {
+ pa_log("Failed to determine file sample format.");
+ goto finish;
+ }
+
+ if ((map && pa_sndfile_read_channel_map(sf, map) < 0)) {
+ if (ss->channels > 2)
+ pa_log("Failed to determine file channel map, synthesizing one.");
+ pa_channel_map_init_extend(map, ss->channels, PA_CHANNEL_MAP_DEFAULT);
+ }
+
+ if (p)
+ pa_sndfile_init_proplist(sf, p);
+
+ if ((l = pa_frame_size(ss) * (size_t) sfi.frames) > PA_SCACHE_ENTRY_SIZE_MAX) {
+ pa_log("File too large");
+ goto finish;
+ }
+
+ chunk->memblock = pa_memblock_new(pool, l);
+ chunk->index = 0;
+ chunk->length = l;
+
+ readf_function = pa_sndfile_readf_function(ss);
+
+ ptr = pa_memblock_acquire(chunk->memblock);
+
+ if ((readf_function && readf_function(sf, ptr, sfi.frames) != sfi.frames) ||
+ (!readf_function && sf_read_raw(sf, ptr, (sf_count_t) l) != (sf_count_t) l)) {
+ pa_log("Premature file end");
+ goto finish;
+ }
+
+ ret = 0;
+
+finish:
+
+ if (sf)
+ sf_close(sf);
+
+ if (ptr)
+ pa_memblock_release(chunk->memblock);
+
+ if (ret != 0 && chunk->memblock)
+ pa_memblock_unref(chunk->memblock);
+
+ if (fd >= 0)
+ pa_close(fd);
+
+ return ret;
+}
+
+int pa_sound_file_too_big_to_cache(const char *fname) {
+
+ SNDFILE*sf = NULL;
+ SF_INFO sfi;
+ pa_sample_spec ss;
+
+ pa_assert(fname);
+
+ pa_zero(sfi);
+ if (!(sf = sf_open(fname, SFM_READ, &sfi))) {
+ pa_log("Failed to open file %s", fname);
+ return -1;
+ }
+
+ if (pa_sndfile_read_sample_spec(sf, &ss) < 0) {
+ pa_log("Failed to determine file sample format.");
+ sf_close(sf);
+ return -1;
+ }
+
+ sf_close(sf);
+
+ if ((pa_frame_size(&ss) * (size_t) sfi.frames) > PA_SCACHE_ENTRY_SIZE_MAX) {
+ pa_log("File too large: %s", fname);
+ return 1;
+ }
+
+ return 0;
+}
diff --git a/src/pulsecore/sound-file.h b/src/pulsecore/sound-file.h
new file mode 100644
index 0000000..01afa4e
--- /dev/null
+++ b/src/pulsecore/sound-file.h
@@ -0,0 +1,31 @@
+#ifndef soundfilehfoo
+#define soundfilehfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulse/sample.h>
+#include <pulse/channelmap.h>
+#include <pulsecore/memchunk.h>
+
+int pa_sound_file_load(pa_mempool *pool, const char *fname, pa_sample_spec *ss, pa_channel_map *map, pa_memchunk *chunk, pa_proplist *p);
+
+int pa_sound_file_too_big_to_cache(const char *fname);
+
+#endif
diff --git a/src/pulsecore/source-output.c b/src/pulsecore/source-output.c
new file mode 100644
index 0000000..92f74d4
--- /dev/null
+++ b/src/pulsecore/source-output.c
@@ -0,0 +1,1912 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <pulse/utf8.h>
+#include <pulse/xmalloc.h>
+#include <pulse/util.h>
+#include <pulse/internal.h>
+
+#include <pulsecore/core-format.h>
+#include <pulsecore/mix.h>
+#include <pulsecore/stream-util.h>
+#include <pulsecore/core-subscribe.h>
+#include <pulsecore/log.h>
+#include <pulsecore/namereg.h>
+#include <pulsecore/core-util.h>
+
+#include "source-output.h"
+
+#define MEMBLOCKQ_MAXLENGTH (32*1024*1024)
+
+PA_DEFINE_PUBLIC_CLASS(pa_source_output, pa_msgobject);
+
+static void source_output_free(pa_object* mo);
+static void set_real_ratio(pa_source_output *o, const pa_cvolume *v);
+
+pa_source_output_new_data* pa_source_output_new_data_init(pa_source_output_new_data *data) {
+ pa_assert(data);
+
+ pa_zero(*data);
+ data->resample_method = PA_RESAMPLER_INVALID;
+ data->proplist = pa_proplist_new();
+ data->volume_writable = true;
+
+ return data;
+}
+
+void pa_source_output_new_data_set_sample_spec(pa_source_output_new_data *data, const pa_sample_spec *spec) {
+ pa_assert(data);
+
+ if ((data->sample_spec_is_set = !!spec))
+ data->sample_spec = *spec;
+}
+
+void pa_source_output_new_data_set_channel_map(pa_source_output_new_data *data, const pa_channel_map *map) {
+ pa_assert(data);
+
+ if ((data->channel_map_is_set = !!map))
+ data->channel_map = *map;
+}
+
+bool pa_source_output_new_data_is_passthrough(pa_source_output_new_data *data) {
+ pa_assert(data);
+
+ if (PA_LIKELY(data->format) && PA_UNLIKELY(!pa_format_info_is_pcm(data->format)))
+ return true;
+
+ if (PA_UNLIKELY(data->flags & PA_SOURCE_OUTPUT_PASSTHROUGH))
+ return true;
+
+ return false;
+}
+
+void pa_source_output_new_data_set_volume(pa_source_output_new_data *data, const pa_cvolume *volume) {
+ pa_assert(data);
+ pa_assert(data->volume_writable);
+
+ if ((data->volume_is_set = !!volume))
+ data->volume = *volume;
+}
+
+void pa_source_output_new_data_apply_volume_factor(pa_source_output_new_data *data, const pa_cvolume *volume_factor) {
+ pa_assert(data);
+ pa_assert(volume_factor);
+
+ if (data->volume_factor_is_set)
+ pa_sw_cvolume_multiply(&data->volume_factor, &data->volume_factor, volume_factor);
+ else {
+ data->volume_factor_is_set = true;
+ data->volume_factor = *volume_factor;
+ }
+}
+
+void pa_source_output_new_data_apply_volume_factor_source(pa_source_output_new_data *data, const pa_cvolume *volume_factor) {
+ pa_assert(data);
+ pa_assert(volume_factor);
+
+ if (data->volume_factor_source_is_set)
+ pa_sw_cvolume_multiply(&data->volume_factor_source, &data->volume_factor_source, volume_factor);
+ else {
+ data->volume_factor_source_is_set = true;
+ data->volume_factor_source = *volume_factor;
+ }
+}
+
+void pa_source_output_new_data_set_muted(pa_source_output_new_data *data, bool mute) {
+ pa_assert(data);
+
+ data->muted_is_set = true;
+ data->muted = mute;
+}
+
+bool pa_source_output_new_data_set_source(pa_source_output_new_data *data, pa_source *s, bool save,
+ bool requested_by_application) {
+ bool ret = true;
+ pa_idxset *formats = NULL;
+
+ pa_assert(data);
+ pa_assert(s);
+
+ if (!data->req_formats) {
+ /* We're not working with the extended API */
+ data->source = s;
+ if (save) {
+ pa_xfree(data->preferred_source);
+ data->preferred_source = pa_xstrdup(s->name);
+ }
+ data->source_requested_by_application = requested_by_application;
+ } else {
+ /* Extended API: let's see if this source supports the formats the client would like */
+ formats = pa_source_check_formats(s, data->req_formats);
+
+ if (formats && !pa_idxset_isempty(formats)) {
+ /* Source supports at least one of the requested formats */
+ data->source = s;
+ if (save) {
+ pa_xfree(data->preferred_source);
+ data->preferred_source = pa_xstrdup(s->name);
+ }
+ data->source_requested_by_application = requested_by_application;
+ if (data->nego_formats)
+ pa_idxset_free(data->nego_formats, (pa_free_cb_t) pa_format_info_free);
+ data->nego_formats = formats;
+ } else {
+ /* Source doesn't support any of the formats requested by the client */
+ if (formats)
+ pa_idxset_free(formats, (pa_free_cb_t) pa_format_info_free);
+ ret = false;
+ }
+ }
+
+ return ret;
+}
+
+bool pa_source_output_new_data_set_formats(pa_source_output_new_data *data, pa_idxset *formats) {
+ pa_assert(data);
+ pa_assert(formats);
+
+ if (data->req_formats)
+ pa_idxset_free(data->req_formats, (pa_free_cb_t) pa_format_info_free);
+
+ data->req_formats = formats;
+
+ if (data->source) {
+ /* Trigger format negotiation */
+ return pa_source_output_new_data_set_source(data, data->source, (data->preferred_source != NULL),
+ data->source_requested_by_application);
+ }
+
+ return true;
+}
+
+void pa_source_output_new_data_done(pa_source_output_new_data *data) {
+ pa_assert(data);
+
+ if (data->req_formats)
+ pa_idxset_free(data->req_formats, (pa_free_cb_t) pa_format_info_free);
+
+ if (data->nego_formats)
+ pa_idxset_free(data->nego_formats, (pa_free_cb_t) pa_format_info_free);
+
+ if (data->format)
+ pa_format_info_free(data->format);
+
+ if (data->preferred_source)
+ pa_xfree(data->preferred_source);
+
+ pa_proplist_free(data->proplist);
+}
+
+/* Called from main context */
+static void reset_callbacks(pa_source_output *o) {
+ pa_assert(o);
+
+ o->push = NULL;
+ o->process_rewind = NULL;
+ o->update_max_rewind = NULL;
+ o->update_source_requested_latency = NULL;
+ o->update_source_latency_range = NULL;
+ o->update_source_fixed_latency = NULL;
+ o->attach = NULL;
+ o->detach = NULL;
+ o->suspend = NULL;
+ o->suspend_within_thread = NULL;
+ o->moving = NULL;
+ o->kill = NULL;
+ o->get_latency = NULL;
+ o->state_change = NULL;
+ o->may_move_to = NULL;
+ o->send_event = NULL;
+ o->volume_changed = NULL;
+ o->mute_changed = NULL;
+}
+
+/* Called from main context */
+int pa_source_output_new(
+ pa_source_output**_o,
+ pa_core *core,
+ pa_source_output_new_data *data) {
+
+ pa_source_output *o;
+ pa_resampler *resampler = NULL;
+ char st[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX], fmt[PA_FORMAT_INFO_SNPRINT_MAX];
+ pa_channel_map volume_map;
+ int r;
+ char *pt;
+
+ pa_assert(_o);
+ pa_assert(core);
+ pa_assert(data);
+ pa_assert_ctl_context();
+
+ if (data->client)
+ pa_proplist_update(data->proplist, PA_UPDATE_MERGE, data->client->proplist);
+
+ if (data->destination_source && (data->destination_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER))
+ data->volume_writable = false;
+
+ if (!data->req_formats) {
+ /* From this point on, we want to work only with formats, and get back
+ * to using the sample spec and channel map after all decisions w.r.t.
+ * routing are complete. */
+ pa_format_info *f;
+ pa_idxset *formats;
+
+ f = pa_format_info_from_sample_spec2(&data->sample_spec, data->channel_map_is_set ? &data->channel_map : NULL,
+ !(data->flags & PA_SOURCE_OUTPUT_FIX_FORMAT),
+ !(data->flags & PA_SOURCE_OUTPUT_FIX_RATE),
+ !(data->flags & PA_SOURCE_OUTPUT_FIX_CHANNELS));
+ if (!f)
+ return -PA_ERR_INVALID;
+
+ formats = pa_idxset_new(NULL, NULL);
+ pa_idxset_put(formats, f, NULL);
+ pa_source_output_new_data_set_formats(data, formats);
+ }
+
+ if ((r = pa_hook_fire(&core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_NEW], data)) < 0)
+ return r;
+
+ pa_return_val_if_fail(!data->driver || pa_utf8_valid(data->driver), -PA_ERR_INVALID);
+
+ if (!data->source) {
+ pa_source *source;
+
+ if (data->direct_on_input) {
+ source = data->direct_on_input->sink->monitor_source;
+ pa_return_val_if_fail(source, -PA_ERR_INVALID);
+ } else {
+ source = pa_namereg_get(core, NULL, PA_NAMEREG_SOURCE);
+ pa_return_val_if_fail(source, -PA_ERR_NOENTITY);
+ }
+
+ pa_source_output_new_data_set_source(data, source, false, false);
+ }
+
+ /* If something didn't pick a format for us, pick the top-most format since
+ * we assume this is sorted in priority order */
+ if (!data->format && data->nego_formats && !pa_idxset_isempty(data->nego_formats))
+ data->format = pa_format_info_copy(pa_idxset_first(data->nego_formats, NULL));
+
+ if (PA_LIKELY(data->format)) {
+ /* We know that data->source is set, because data->format has been set.
+ * data->format is set after a successful format negotiation, and that
+ * can't happen before data->source has been set. */
+ pa_assert(data->source);
+
+ pa_log_debug("Negotiated format: %s", pa_format_info_snprint(fmt, sizeof(fmt), data->format));
+ } else {
+ pa_format_info *format;
+ uint32_t idx;
+
+ pa_log_info("Source does not support any requested format:");
+ PA_IDXSET_FOREACH(format, data->req_formats, idx)
+ pa_log_info(" -- %s", pa_format_info_snprint(fmt, sizeof(fmt), format));
+
+ return -PA_ERR_NOTSUPPORTED;
+ }
+
+ pa_return_val_if_fail(PA_SOURCE_IS_LINKED(data->source->state), -PA_ERR_BADSTATE);
+ pa_return_val_if_fail(!data->direct_on_input || data->direct_on_input->sink == data->source->monitor_of, -PA_ERR_INVALID);
+
+ /* Routing is done. We have a source and a format. */
+
+ if (data->volume_is_set && !pa_source_output_new_data_is_passthrough(data)) {
+ /* If volume is set, we need to save the original data->channel_map,
+ * so that we can remap the volume from the original channel map to the
+ * final channel map of the stream in case data->channel_map gets
+ * modified in pa_format_info_to_sample_spec2(). */
+ r = pa_stream_get_volume_channel_map(&data->volume, data->channel_map_is_set ? &data->channel_map : NULL, data->format, &volume_map);
+ if (r < 0)
+ return r;
+ } else {
+ /* Initialize volume_map to invalid state. We check the state later to
+ * determine if volume remapping is needed. */
+ pa_channel_map_init(&volume_map);
+ }
+
+ /* Now populate the sample spec and channel map according to the final
+ * format that we've negotiated */
+ r = pa_format_info_to_sample_spec2(data->format, &data->sample_spec, &data->channel_map, &data->source->sample_spec,
+ &data->source->channel_map);
+ if (r < 0)
+ return r;
+
+ /* Don't restore (or save) stream volume for passthrough streams and
+ * prevent attenuation/gain */
+ if (pa_source_output_new_data_is_passthrough(data)) {
+ data->volume_is_set = true;
+ pa_cvolume_reset(&data->volume, data->sample_spec.channels);
+ data->volume_is_absolute = true;
+ data->save_volume = false;
+ }
+
+ if (!data->volume_is_set) {
+ pa_cvolume_reset(&data->volume, data->sample_spec.channels);
+ data->volume_is_absolute = false;
+ data->save_volume = false;
+ }
+
+ if (!data->volume_writable)
+ data->save_volume = false;
+
+ if (pa_channel_map_valid(&volume_map))
+ /* The original volume channel map may be different than the final
+ * stream channel map, so remapping may be needed. */
+ pa_cvolume_remap(&data->volume, &volume_map, &data->channel_map);
+
+ if (!data->volume_factor_is_set)
+ pa_cvolume_reset(&data->volume_factor, data->sample_spec.channels);
+
+ pa_return_val_if_fail(pa_cvolume_compatible(&data->volume_factor, &data->sample_spec), -PA_ERR_INVALID);
+
+ if (!data->volume_factor_source_is_set)
+ pa_cvolume_reset(&data->volume_factor_source, data->source->sample_spec.channels);
+
+ pa_return_val_if_fail(pa_cvolume_compatible(&data->volume_factor_source, &data->source->sample_spec), -PA_ERR_INVALID);
+
+ if (!data->muted_is_set)
+ data->muted = false;
+
+ if (!(data->flags & PA_SOURCE_OUTPUT_VARIABLE_RATE) &&
+ !pa_sample_spec_equal(&data->sample_spec, &data->source->sample_spec)) {
+ /* try to change source format and rate. This is done before the FIXATE hook since
+ module-suspend-on-idle can resume a source */
+
+ pa_log_info("Trying to change sample spec");
+ pa_source_reconfigure(data->source, &data->sample_spec, pa_source_output_new_data_is_passthrough(data));
+ }
+
+ if (pa_source_output_new_data_is_passthrough(data) &&
+ !pa_sample_spec_equal(&data->sample_spec, &data->source->sample_spec)) {
+ /* rate update failed, or other parts of sample spec didn't match */
+
+ pa_log_debug("Could not update source sample spec to match passthrough stream");
+ return -PA_ERR_NOTSUPPORTED;
+ }
+
+ if (data->resample_method == PA_RESAMPLER_INVALID)
+ data->resample_method = core->resample_method;
+
+ pa_return_val_if_fail(data->resample_method < PA_RESAMPLER_MAX, -PA_ERR_INVALID);
+
+ if ((r = pa_hook_fire(&core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_FIXATE], data)) < 0)
+ return r;
+
+ if ((data->flags & PA_SOURCE_OUTPUT_NO_CREATE_ON_SUSPEND) &&
+ data->source->state == PA_SOURCE_SUSPENDED) {
+ pa_log("Failed to create source output: source is suspended.");
+ return -PA_ERR_BADSTATE;
+ }
+
+ if (pa_idxset_size(data->source->outputs) >= PA_MAX_OUTPUTS_PER_SOURCE) {
+ pa_log("Failed to create source output: too many outputs per source.");
+ return -PA_ERR_TOOLARGE;
+ }
+
+ if ((data->flags & PA_SOURCE_OUTPUT_VARIABLE_RATE) ||
+ !pa_sample_spec_equal(&data->sample_spec, &data->source->sample_spec) ||
+ !pa_channel_map_equal(&data->channel_map, &data->source->channel_map)) {
+
+ if (!pa_source_output_new_data_is_passthrough(data)) /* no resampler for passthrough content */
+ if (!(resampler = pa_resampler_new(
+ core->mempool,
+ &data->source->sample_spec, &data->source->channel_map,
+ &data->sample_spec, &data->channel_map,
+ core->lfe_crossover_freq,
+ data->resample_method,
+ ((data->flags & PA_SOURCE_OUTPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0) |
+ ((data->flags & PA_SOURCE_OUTPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) |
+ (core->disable_remixing || (data->flags & PA_SOURCE_OUTPUT_NO_REMIX) ? PA_RESAMPLER_NO_REMIX : 0) |
+ (core->remixing_use_all_sink_channels ? 0 : PA_RESAMPLER_NO_FILL_SINK) |
+ (core->remixing_produce_lfe ? PA_RESAMPLER_PRODUCE_LFE : 0) |
+ (core->remixing_consume_lfe ? PA_RESAMPLER_CONSUME_LFE : 0)))) {
+ pa_log_warn("Unsupported resampling operation.");
+ return -PA_ERR_NOTSUPPORTED;
+ }
+ }
+
+ o = pa_msgobject_new(pa_source_output);
+ o->parent.parent.free = source_output_free;
+ o->parent.process_msg = pa_source_output_process_msg;
+
+ o->core = core;
+ o->state = PA_SOURCE_OUTPUT_INIT;
+ o->flags = data->flags;
+ o->proplist = pa_proplist_copy(data->proplist);
+ o->driver = pa_xstrdup(pa_path_get_filename(data->driver));
+ o->module = data->module;
+ o->source = data->source;
+ o->source_requested_by_application = data->source_requested_by_application;
+ o->destination_source = data->destination_source;
+ o->client = data->client;
+
+ o->requested_resample_method = data->resample_method;
+ o->actual_resample_method = resampler ? pa_resampler_get_method(resampler) : PA_RESAMPLER_INVALID;
+ o->sample_spec = data->sample_spec;
+ o->channel_map = data->channel_map;
+ o->format = pa_format_info_copy(data->format);
+
+ if (!data->volume_is_absolute && pa_source_flat_volume_enabled(o->source)) {
+ pa_cvolume remapped;
+
+ /* When the 'absolute' bool is not set then we'll treat the volume
+ * as relative to the source volume even in flat volume mode */
+ remapped = data->source->reference_volume;
+ pa_cvolume_remap(&remapped, &data->source->channel_map, &data->channel_map);
+ pa_sw_cvolume_multiply(&o->volume, &data->volume, &remapped);
+ } else
+ o->volume = data->volume;
+
+ o->volume_factor = data->volume_factor;
+ o->volume_factor_source = data->volume_factor_source;
+ o->real_ratio = o->reference_ratio = data->volume;
+ pa_cvolume_reset(&o->soft_volume, o->sample_spec.channels);
+ pa_cvolume_reset(&o->real_ratio, o->sample_spec.channels);
+ o->volume_writable = data->volume_writable;
+ o->save_volume = data->save_volume;
+ o->preferred_source = pa_xstrdup(data->preferred_source);
+ o->save_muted = data->save_muted;
+
+ o->muted = data->muted;
+
+ o->direct_on_input = data->direct_on_input;
+
+ reset_callbacks(o);
+ o->userdata = NULL;
+
+ o->thread_info.state = o->state;
+ o->thread_info.attached = false;
+ o->thread_info.sample_spec = o->sample_spec;
+ o->thread_info.resampler = resampler;
+ o->thread_info.soft_volume = o->soft_volume;
+ o->thread_info.muted = o->muted;
+ o->thread_info.requested_source_latency = (pa_usec_t) -1;
+ o->thread_info.direct_on_input = o->direct_on_input;
+
+ o->thread_info.delay_memblockq = pa_memblockq_new(
+ "source output delay_memblockq",
+ 0,
+ MEMBLOCKQ_MAXLENGTH,
+ 0,
+ &o->source->sample_spec,
+ 0,
+ 1,
+ 0,
+ &o->source->silence);
+
+ pa_assert_se(pa_idxset_put(core->source_outputs, o, &o->index) == 0);
+ pa_assert_se(pa_idxset_put(o->source->outputs, pa_source_output_ref(o), NULL) == 0);
+
+ if (o->client)
+ pa_assert_se(pa_idxset_put(o->client->source_outputs, o, NULL) >= 0);
+
+ if (o->direct_on_input)
+ pa_assert_se(pa_idxset_put(o->direct_on_input->direct_outputs, o, NULL) == 0);
+
+ pt = pa_proplist_to_string_sep(o->proplist, "\n ");
+ pa_log_info("Created output %u \"%s\" on %s with sample spec %s and channel map %s\n %s",
+ o->index,
+ pa_strnull(pa_proplist_gets(o->proplist, PA_PROP_MEDIA_NAME)),
+ o->source->name,
+ pa_sample_spec_snprint(st, sizeof(st), &o->sample_spec),
+ pa_channel_map_snprint(cm, sizeof(cm), &o->channel_map),
+ pt);
+ pa_xfree(pt);
+
+ /* Don't forget to call pa_source_output_put! */
+
+ *_o = o;
+ return 0;
+}
+
+/* Called from main context */
+static void update_n_corked(pa_source_output *o, pa_source_output_state_t state) {
+ pa_assert(o);
+ pa_assert_ctl_context();
+
+ if (!o->source)
+ return;
+
+ if (o->state == PA_SOURCE_OUTPUT_CORKED && state != PA_SOURCE_OUTPUT_CORKED)
+ pa_assert_se(o->source->n_corked -- >= 1);
+ else if (o->state != PA_SOURCE_OUTPUT_CORKED && state == PA_SOURCE_OUTPUT_CORKED)
+ o->source->n_corked++;
+}
+
+/* Called from main context */
+static void source_output_set_state(pa_source_output *o, pa_source_output_state_t state) {
+
+ pa_assert(o);
+ pa_assert_ctl_context();
+
+ if (o->state == state)
+ return;
+
+ if (o->source) {
+ if (o->state == PA_SOURCE_OUTPUT_CORKED && state == PA_SOURCE_OUTPUT_RUNNING && pa_source_used_by(o->source) == 0 &&
+ !pa_sample_spec_equal(&o->sample_spec, &o->source->sample_spec)) {
+ /* We were uncorked and the source was not playing anything -- let's try
+ * to update the sample format and rate to avoid resampling */
+ pa_source_reconfigure(o->source, &o->sample_spec, pa_source_output_is_passthrough(o));
+ }
+
+ pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_SET_STATE, PA_UINT_TO_PTR(state), 0, NULL) == 0);
+ } else
+ /* If the source is not valid, pa_source_output_set_state_within_thread() must be called directly */
+ pa_source_output_set_state_within_thread(o, state);
+
+ update_n_corked(o, state);
+ o->state = state;
+
+ if (state != PA_SOURCE_OUTPUT_UNLINKED) {
+ pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_STATE_CHANGED], o);
+
+ if (PA_SOURCE_OUTPUT_IS_LINKED(state))
+ pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index);
+ }
+
+ if (o->source)
+ pa_source_update_status(o->source);
+}
+
+/* Called from main context */
+void pa_source_output_unlink(pa_source_output*o) {
+ bool linked;
+
+ pa_source_output_assert_ref(o);
+ pa_assert_ctl_context();
+
+ /* See pa_sink_unlink() for a couple of comments how this function
+ * works */
+
+ pa_source_output_ref(o);
+
+ linked = PA_SOURCE_OUTPUT_IS_LINKED(o->state);
+
+ if (linked)
+ pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK], o);
+
+ if (o->direct_on_input)
+ pa_idxset_remove_by_data(o->direct_on_input->direct_outputs, o, NULL);
+
+ pa_idxset_remove_by_data(o->core->source_outputs, o, NULL);
+
+ if (o->source)
+ if (pa_idxset_remove_by_data(o->source->outputs, o, NULL))
+ pa_source_output_unref(o);
+
+ if (o->client)
+ pa_idxset_remove_by_data(o->client->source_outputs, o, NULL);
+
+ update_n_corked(o, PA_SOURCE_OUTPUT_UNLINKED);
+ o->state = PA_SOURCE_OUTPUT_UNLINKED;
+
+ if (linked && o->source) {
+ if (pa_source_output_is_passthrough(o))
+ pa_source_leave_passthrough(o->source);
+
+ /* We might need to update the source's volume if we are in flat volume mode. */
+ if (pa_source_flat_volume_enabled(o->source))
+ pa_source_set_volume(o->source, NULL, false, false);
+
+ if (o->source->asyncmsgq)
+ pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_REMOVE_OUTPUT, o, 0, NULL) == 0);
+ }
+
+ reset_callbacks(o);
+
+ if (o->source) {
+ if (PA_SOURCE_IS_LINKED(o->source->state))
+ pa_source_update_status(o->source);
+
+ o->source = NULL;
+ }
+
+ if (linked) {
+ pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_REMOVE, o->index);
+ pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK_POST], o);
+ }
+
+ pa_core_maybe_vacuum(o->core);
+
+ pa_source_output_unref(o);
+}
+
+/* Called from main context */
+static void source_output_free(pa_object* mo) {
+ pa_source_output *o = PA_SOURCE_OUTPUT(mo);
+
+ pa_assert(o);
+ pa_assert_ctl_context();
+ pa_assert(pa_source_output_refcnt(o) == 0);
+ pa_assert(!PA_SOURCE_OUTPUT_IS_LINKED(o->state));
+
+ pa_log_info("Freeing output %u \"%s\"", o->index,
+ o->proplist ? pa_strnull(pa_proplist_gets(o->proplist, PA_PROP_MEDIA_NAME)) : "");
+
+ if (o->thread_info.delay_memblockq)
+ pa_memblockq_free(o->thread_info.delay_memblockq);
+
+ if (o->thread_info.resampler)
+ pa_resampler_free(o->thread_info.resampler);
+
+ if (o->format)
+ pa_format_info_free(o->format);
+
+ if (o->proplist)
+ pa_proplist_free(o->proplist);
+
+ if (o->preferred_source)
+ pa_xfree(o->preferred_source);
+
+ pa_xfree(o->driver);
+ pa_xfree(o);
+}
+
+/* Called from main context */
+void pa_source_output_put(pa_source_output *o) {
+ pa_source_output_state_t state;
+
+ pa_source_output_assert_ref(o);
+ pa_assert_ctl_context();
+
+ pa_assert(o->state == PA_SOURCE_OUTPUT_INIT);
+
+ /* The following fields must be initialized properly */
+ pa_assert(o->push);
+ pa_assert(o->kill);
+
+ state = o->flags & PA_SOURCE_OUTPUT_START_CORKED ? PA_SOURCE_OUTPUT_CORKED : PA_SOURCE_OUTPUT_RUNNING;
+
+ update_n_corked(o, state);
+ o->state = state;
+
+ /* We might need to update the source's volume if we are in flat volume mode. */
+ if (pa_source_flat_volume_enabled(o->source))
+ pa_source_set_volume(o->source, NULL, false, o->save_volume);
+ else {
+ if (o->destination_source && (o->destination_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)) {
+ pa_assert(pa_cvolume_is_norm(&o->volume));
+ pa_assert(pa_cvolume_is_norm(&o->reference_ratio));
+ }
+
+ set_real_ratio(o, &o->volume);
+ }
+
+ if (pa_source_output_is_passthrough(o))
+ pa_source_enter_passthrough(o->source);
+
+ o->thread_info.soft_volume = o->soft_volume;
+ o->thread_info.muted = o->muted;
+
+ pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_ADD_OUTPUT, o, 0, NULL) == 0);
+
+ pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_NEW, o->index);
+ pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PUT], o);
+
+ pa_source_update_status(o->source);
+}
+
+/* Called from main context */
+void pa_source_output_kill(pa_source_output*o) {
+ pa_source_output_assert_ref(o);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state));
+
+ o->kill(o);
+}
+
+/* Called from main context */
+pa_usec_t pa_source_output_get_latency(pa_source_output *o, pa_usec_t *source_latency) {
+ pa_usec_t r[2] = { 0, 0 };
+
+ pa_source_output_assert_ref(o);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state));
+
+ pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_GET_LATENCY, r, 0, NULL) == 0);
+
+ if (o->get_latency)
+ r[0] += o->get_latency(o);
+
+ if (source_latency)
+ *source_latency = r[1];
+
+ return r[0];
+}
+
+/* Called from thread context */
+void pa_source_output_push(pa_source_output *o, const pa_memchunk *chunk) {
+ bool need_volume_factor_source;
+ bool volume_is_norm;
+ size_t length;
+ size_t limit, mbs = 0;
+
+ pa_source_output_assert_ref(o);
+ pa_source_output_assert_io_context(o);
+ pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->thread_info.state));
+ pa_assert(chunk);
+ pa_assert(pa_frame_aligned(chunk->length, &o->source->sample_spec));
+
+ if (!o->push || o->thread_info.state == PA_SOURCE_OUTPUT_CORKED)
+ return;
+
+ pa_assert(o->thread_info.state == PA_SOURCE_OUTPUT_RUNNING);
+
+ if (pa_memblockq_push(o->thread_info.delay_memblockq, chunk) < 0) {
+ pa_log_debug("Delay queue overflow!");
+ pa_memblockq_seek(o->thread_info.delay_memblockq, (int64_t) chunk->length, PA_SEEK_RELATIVE, true);
+ }
+
+ limit = o->process_rewind ? 0 : o->source->thread_info.max_rewind;
+
+ volume_is_norm = pa_cvolume_is_norm(&o->thread_info.soft_volume) && !o->thread_info.muted;
+ need_volume_factor_source = !pa_cvolume_is_norm(&o->volume_factor_source);
+
+ if (limit > 0 && o->source->monitor_of) {
+ pa_usec_t latency;
+ size_t n;
+
+ /* Hmm, check the latency for knowing how much of the buffered
+ * data is actually still unplayed and might hence still
+ * change. This is suboptimal. Ideally we'd have a call like
+ * pa_sink_get_changeable_size() or so that tells us how much
+ * of the queued data is actually still changeable. Hence
+ * FIXME! */
+
+ latency = pa_sink_get_latency_within_thread(o->source->monitor_of, false);
+
+ n = pa_usec_to_bytes(latency, &o->source->sample_spec);
+
+ if (n < limit)
+ limit = n;
+ }
+
+ /* Implement the delay queue */
+ while ((length = pa_memblockq_get_length(o->thread_info.delay_memblockq)) > limit) {
+ pa_memchunk qchunk;
+ bool nvfs = need_volume_factor_source;
+
+ length -= limit;
+
+ pa_assert_se(pa_memblockq_peek(o->thread_info.delay_memblockq, &qchunk) >= 0);
+
+ if (qchunk.length > length)
+ qchunk.length = length;
+
+ pa_assert(qchunk.length > 0);
+
+ /* It might be necessary to adjust the volume here */
+ if (!volume_is_norm) {
+ pa_memchunk_make_writable(&qchunk, 0);
+
+ if (o->thread_info.muted) {
+ pa_silence_memchunk(&qchunk, &o->source->sample_spec);
+ nvfs = false;
+
+ } else if (!o->thread_info.resampler && nvfs) {
+ pa_cvolume v;
+
+ /* If we don't need a resampler we can merge the
+ * post and the pre volume adjustment into one */
+
+ pa_sw_cvolume_multiply(&v, &o->thread_info.soft_volume, &o->volume_factor_source);
+ pa_volume_memchunk(&qchunk, &o->source->sample_spec, &v);
+ nvfs = false;
+
+ } else
+ pa_volume_memchunk(&qchunk, &o->source->sample_spec, &o->thread_info.soft_volume);
+ }
+
+ if (nvfs) {
+ pa_memchunk_make_writable(&qchunk, 0);
+ pa_volume_memchunk(&qchunk, &o->source->sample_spec, &o->volume_factor_source);
+ }
+
+ if (!o->thread_info.resampler)
+ o->push(o, &qchunk);
+ else {
+ pa_memchunk rchunk;
+
+ if (mbs == 0)
+ mbs = pa_resampler_max_block_size(o->thread_info.resampler);
+
+ if (qchunk.length > mbs)
+ qchunk.length = mbs;
+
+ pa_resampler_run(o->thread_info.resampler, &qchunk, &rchunk);
+
+ if (rchunk.length > 0)
+ o->push(o, &rchunk);
+
+ if (rchunk.memblock)
+ pa_memblock_unref(rchunk.memblock);
+ }
+
+ pa_memblock_unref(qchunk.memblock);
+ pa_memblockq_drop(o->thread_info.delay_memblockq, qchunk.length);
+ }
+}
+
+/* Called from thread context */
+void pa_source_output_process_rewind(pa_source_output *o, size_t nbytes /* in source sample spec */) {
+
+ pa_source_output_assert_ref(o);
+ pa_source_output_assert_io_context(o);
+ pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->thread_info.state));
+ pa_assert(pa_frame_aligned(nbytes, &o->source->sample_spec));
+
+ if (nbytes <= 0)
+ return;
+
+ if (o->process_rewind) {
+ pa_assert(pa_memblockq_get_length(o->thread_info.delay_memblockq) == 0);
+
+ if (o->thread_info.resampler)
+ nbytes = pa_resampler_result(o->thread_info.resampler, nbytes);
+
+ pa_log_debug("Have to rewind %lu bytes on implementor.", (unsigned long) nbytes);
+
+ if (nbytes > 0)
+ o->process_rewind(o, nbytes);
+
+ if (o->thread_info.resampler)
+ pa_resampler_rewind(o->thread_info.resampler, nbytes);
+
+ } else
+ pa_memblockq_seek(o->thread_info.delay_memblockq, - ((int64_t) nbytes), PA_SEEK_RELATIVE, true);
+}
+
+/* Called from thread context */
+size_t pa_source_output_get_max_rewind(pa_source_output *o) {
+ pa_source_output_assert_ref(o);
+ pa_source_output_assert_io_context(o);
+
+ return o->thread_info.resampler ? pa_resampler_request(o->thread_info.resampler, o->source->thread_info.max_rewind) : o->source->thread_info.max_rewind;
+}
+
+/* Called from thread context */
+void pa_source_output_update_max_rewind(pa_source_output *o, size_t nbytes /* in the source's sample spec */) {
+ pa_source_output_assert_ref(o);
+ pa_source_output_assert_io_context(o);
+ pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->thread_info.state));
+ pa_assert(pa_frame_aligned(nbytes, &o->source->sample_spec));
+
+ if (o->update_max_rewind)
+ o->update_max_rewind(o, o->thread_info.resampler ? pa_resampler_result(o->thread_info.resampler, nbytes) : nbytes);
+}
+
+/* Called from thread context */
+pa_usec_t pa_source_output_set_requested_latency_within_thread(pa_source_output *o, pa_usec_t usec) {
+ pa_source_output_assert_ref(o);
+ pa_source_output_assert_io_context(o);
+
+ if (!(o->source->flags & PA_SOURCE_DYNAMIC_LATENCY))
+ usec = o->source->thread_info.fixed_latency;
+
+ if (usec != (pa_usec_t) -1)
+ usec = PA_CLAMP(usec, o->source->thread_info.min_latency, o->source->thread_info.max_latency);
+
+ o->thread_info.requested_source_latency = usec;
+ pa_source_invalidate_requested_latency(o->source, true);
+
+ return usec;
+}
+
+/* Called from main context */
+pa_usec_t pa_source_output_set_requested_latency(pa_source_output *o, pa_usec_t usec) {
+ pa_source_output_assert_ref(o);
+ pa_assert_ctl_context();
+
+ if (PA_SOURCE_OUTPUT_IS_LINKED(o->state) && o->source) {
+ pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_SET_REQUESTED_LATENCY, &usec, 0, NULL) == 0);
+ return usec;
+ }
+
+ /* If this source output is not realized yet or is being moved, we
+ * have to touch the thread info data directly */
+
+ if (o->source) {
+ if (!(o->source->flags & PA_SOURCE_DYNAMIC_LATENCY))
+ usec = pa_source_get_fixed_latency(o->source);
+
+ if (usec != (pa_usec_t) -1) {
+ pa_usec_t min_latency, max_latency;
+ pa_source_get_latency_range(o->source, &min_latency, &max_latency);
+ usec = PA_CLAMP(usec, min_latency, max_latency);
+ }
+ }
+
+ o->thread_info.requested_source_latency = usec;
+
+ return usec;
+}
+
+/* Called from main context */
+pa_usec_t pa_source_output_get_requested_latency(pa_source_output *o) {
+ pa_source_output_assert_ref(o);
+ pa_assert_ctl_context();
+
+ if (PA_SOURCE_OUTPUT_IS_LINKED(o->state) && o->source) {
+ pa_usec_t usec = 0;
+ pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_GET_REQUESTED_LATENCY, &usec, 0, NULL) == 0);
+ return usec;
+ }
+
+ /* If this source output is not realized yet or is being moved, we
+ * have to touch the thread info data directly */
+
+ return o->thread_info.requested_source_latency;
+}
+
+/* Called from main context */
+void pa_source_output_set_volume(pa_source_output *o, const pa_cvolume *volume, bool save, bool absolute) {
+ pa_cvolume v;
+
+ pa_source_output_assert_ref(o);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state));
+ pa_assert(volume);
+ pa_assert(pa_cvolume_valid(volume));
+ pa_assert(volume->channels == 1 || pa_cvolume_compatible(volume, &o->sample_spec));
+ pa_assert(o->volume_writable);
+
+ if (!absolute && pa_source_flat_volume_enabled(o->source)) {
+ v = o->source->reference_volume;
+ pa_cvolume_remap(&v, &o->source->channel_map, &o->channel_map);
+
+ if (pa_cvolume_compatible(volume, &o->sample_spec))
+ volume = pa_sw_cvolume_multiply(&v, &v, volume);
+ else
+ volume = pa_sw_cvolume_multiply_scalar(&v, &v, pa_cvolume_max(volume));
+ } else {
+ if (!pa_cvolume_compatible(volume, &o->sample_spec)) {
+ v = o->volume;
+ volume = pa_cvolume_scale(&v, pa_cvolume_max(volume));
+ }
+ }
+
+ if (pa_cvolume_equal(volume, &o->volume)) {
+ o->save_volume = o->save_volume || save;
+ return;
+ }
+
+ pa_source_output_set_volume_direct(o, volume);
+ o->save_volume = save;
+
+ if (pa_source_flat_volume_enabled(o->source)) {
+ /* We are in flat volume mode, so let's update all source input
+ * volumes and update the flat volume of the source */
+
+ pa_source_set_volume(o->source, NULL, true, save);
+
+ } else {
+ /* OK, we are in normal volume mode. The volume only affects
+ * ourselves */
+ set_real_ratio(o, volume);
+
+ /* Copy the new soft_volume to the thread_info struct */
+ pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_SET_SOFT_VOLUME, NULL, 0, NULL) == 0);
+ }
+
+ /* The volume changed, let's tell people so */
+ if (o->volume_changed)
+ o->volume_changed(o);
+
+ /* The virtual volume changed, let's tell people so */
+ pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index);
+}
+
+/* Called from main context */
+static void set_real_ratio(pa_source_output *o, const pa_cvolume *v) {
+ pa_source_output_assert_ref(o);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state));
+ pa_assert(!v || pa_cvolume_compatible(v, &o->sample_spec));
+
+ /* This basically calculates:
+ *
+ * o->real_ratio := v
+ * o->soft_volume := o->real_ratio * o->volume_factor */
+
+ if (v)
+ o->real_ratio = *v;
+ else
+ pa_cvolume_reset(&o->real_ratio, o->sample_spec.channels);
+
+ pa_sw_cvolume_multiply(&o->soft_volume, &o->real_ratio, &o->volume_factor);
+ /* We don't copy the data to the thread_info data. That's left for someone else to do */
+}
+
+/* Called from main or I/O context */
+bool pa_source_output_is_passthrough(pa_source_output *o) {
+ pa_source_output_assert_ref(o);
+
+ if (PA_UNLIKELY(!pa_format_info_is_pcm(o->format)))
+ return true;
+
+ if (PA_UNLIKELY(o->flags & PA_SOURCE_OUTPUT_PASSTHROUGH))
+ return true;
+
+ return false;
+}
+
+/* Called from main context */
+bool pa_source_output_is_volume_readable(pa_source_output *o) {
+ pa_source_output_assert_ref(o);
+ pa_assert_ctl_context();
+
+ return !pa_source_output_is_passthrough(o);
+}
+
+/* Called from main context */
+pa_cvolume *pa_source_output_get_volume(pa_source_output *o, pa_cvolume *volume, bool absolute) {
+ pa_source_output_assert_ref(o);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state));
+ pa_assert(pa_source_output_is_volume_readable(o));
+
+ if (absolute || !pa_source_flat_volume_enabled(o->source))
+ *volume = o->volume;
+ else
+ *volume = o->reference_ratio;
+
+ return volume;
+}
+
+/* Called from main context */
+void pa_source_output_set_mute(pa_source_output *o, bool mute, bool save) {
+ bool old_mute;
+
+ pa_source_output_assert_ref(o);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state));
+
+ old_mute = o->muted;
+
+ if (mute == old_mute) {
+ o->save_muted |= save;
+ return;
+ }
+
+ o->muted = mute;
+ pa_log_debug("The mute of source output %u changed from %s to %s.", o->index, pa_yes_no(old_mute), pa_yes_no(mute));
+
+ o->save_muted = save;
+
+ pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_SET_SOFT_MUTE, NULL, 0, NULL) == 0);
+
+ /* The mute status changed, let's tell people so */
+ if (o->mute_changed)
+ o->mute_changed(o);
+
+ pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index);
+ pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MUTE_CHANGED], o);
+}
+
+void pa_source_output_set_property(pa_source_output *o, const char *key, const char *value) {
+ char *old_value = NULL;
+ const char *new_value;
+
+ pa_assert(o);
+ pa_assert(key);
+
+ if (pa_proplist_contains(o->proplist, key)) {
+ old_value = pa_xstrdup(pa_proplist_gets(o->proplist, key));
+ if (value && old_value && pa_streq(value, old_value))
+ goto finish;
+
+ if (!old_value)
+ old_value = pa_xstrdup("(data)");
+ } else {
+ if (!value)
+ goto finish;
+
+ old_value = pa_xstrdup("(unset)");
+ }
+
+ if (value) {
+ pa_proplist_sets(o->proplist, key, value);
+ new_value = value;
+ } else {
+ pa_proplist_unset(o->proplist, key);
+ new_value = "(unset)";
+ }
+
+ if (PA_SOURCE_OUTPUT_IS_LINKED(o->state)) {
+ pa_log_debug("Source output %u: proplist[%s]: %s -> %s", o->index, key, old_value, new_value);
+ pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PROPLIST_CHANGED], o);
+ pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT | PA_SUBSCRIPTION_EVENT_CHANGE, o->index);
+ }
+
+finish:
+ pa_xfree(old_value);
+}
+
+void pa_source_output_set_property_arbitrary(pa_source_output *o, const char *key, const uint8_t *value, size_t nbytes) {
+ const uint8_t *old_value;
+ size_t old_nbytes;
+ const char *old_value_str;
+ const char *new_value_str;
+
+ pa_assert(o);
+ pa_assert(key);
+
+ if (pa_proplist_get(o->proplist, key, (const void **) &old_value, &old_nbytes) >= 0) {
+ if (value && nbytes == old_nbytes && !memcmp(value, old_value, nbytes))
+ return;
+
+ old_value_str = "(data)";
+
+ } else {
+ if (!value)
+ return;
+
+ old_value_str = "(unset)";
+ }
+
+ if (value) {
+ pa_proplist_set(o->proplist, key, value, nbytes);
+ new_value_str = "(data)";
+ } else {
+ pa_proplist_unset(o->proplist, key);
+ new_value_str = "(unset)";
+ }
+
+ if (PA_SOURCE_OUTPUT_IS_LINKED(o->state)) {
+ pa_log_debug("Source output %u: proplist[%s]: %s -> %s", o->index, key, old_value_str, new_value_str);
+ pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PROPLIST_CHANGED], o);
+ pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT | PA_SUBSCRIPTION_EVENT_CHANGE, o->index);
+ }
+}
+
+/* Called from main thread */
+void pa_source_output_update_proplist(pa_source_output *o, pa_update_mode_t mode, pa_proplist *p) {
+ void *state;
+ const char *key;
+ const uint8_t *value;
+ size_t nbytes;
+
+ pa_source_output_assert_ref(o);
+ pa_assert(p);
+ pa_assert_ctl_context();
+
+ switch (mode) {
+ case PA_UPDATE_SET:
+ /* Delete everything that is not in p. */
+ for (state = NULL; (key = pa_proplist_iterate(o->proplist, &state));) {
+ if (!pa_proplist_contains(p, key))
+ pa_source_output_set_property(o, key, NULL);
+ }
+
+ /* Fall through. */
+ case PA_UPDATE_REPLACE:
+ for (state = NULL; (key = pa_proplist_iterate(p, &state));) {
+ pa_proplist_get(p, key, (const void **) &value, &nbytes);
+ pa_source_output_set_property_arbitrary(o, key, value, nbytes);
+ }
+
+ break;
+ case PA_UPDATE_MERGE:
+ for (state = NULL; (key = pa_proplist_iterate(p, &state));) {
+ if (pa_proplist_contains(o->proplist, key))
+ continue;
+
+ pa_proplist_get(p, key, (const void **) &value, &nbytes);
+ pa_source_output_set_property_arbitrary(o, key, value, nbytes);
+ }
+
+ break;
+ }
+}
+
+/* Called from main context */
+void pa_source_output_cork(pa_source_output *o, bool b) {
+ pa_source_output_assert_ref(o);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state));
+
+ source_output_set_state(o, b ? PA_SOURCE_OUTPUT_CORKED : PA_SOURCE_OUTPUT_RUNNING);
+}
+
+/* Called from main context */
+int pa_source_output_set_rate(pa_source_output *o, uint32_t rate) {
+ pa_source_output_assert_ref(o);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state));
+ pa_return_val_if_fail(o->thread_info.resampler, -PA_ERR_BADSTATE);
+
+ if (o->sample_spec.rate == rate)
+ return 0;
+
+ o->sample_spec.rate = rate;
+
+ pa_asyncmsgq_post(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_SET_RATE, PA_UINT_TO_PTR(rate), 0, NULL, NULL);
+
+ pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index);
+ return 0;
+}
+
+/* Called from main context */
+pa_resample_method_t pa_source_output_get_resample_method(pa_source_output *o) {
+ pa_source_output_assert_ref(o);
+ pa_assert_ctl_context();
+
+ return o->actual_resample_method;
+}
+
+/* Called from main context */
+bool pa_source_output_may_move(pa_source_output *o) {
+ pa_source_output_assert_ref(o);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state));
+
+ if (o->flags & PA_SOURCE_OUTPUT_DONT_MOVE)
+ return false;
+
+ if (o->direct_on_input)
+ return false;
+
+ return true;
+}
+
+static bool find_filter_source_output(pa_source_output *target, pa_source *s) {
+ unsigned PA_UNUSED i = 0;
+ while (s && s->output_from_master) {
+ if (s->output_from_master == target)
+ return true;
+ s = s->output_from_master->source;
+ pa_assert(i++ < 100);
+ }
+ return false;
+}
+
+static bool is_filter_source_moving(pa_source_output *o) {
+ pa_source *source = o->source;
+
+ if (!source)
+ return false;
+
+ while (source->output_from_master) {
+ source = source->output_from_master->source;
+
+ if (!source)
+ return true;
+ }
+
+ return false;
+}
+
+/* Called from main context */
+bool pa_source_output_may_move_to(pa_source_output *o, pa_source *dest) {
+ pa_source_output_assert_ref(o);
+ pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state));
+ pa_source_assert_ref(dest);
+
+ if (dest == o->source)
+ return true;
+
+ if (dest->unlink_requested)
+ return false;
+
+ if (!pa_source_output_may_move(o))
+ return false;
+
+ /* Make sure we're not creating a filter source cycle */
+ if (find_filter_source_output(o, dest)) {
+ pa_log_debug("Can't connect output to %s, as that would create a cycle.", dest->name);
+ return false;
+ }
+
+ /* If this source output is connected to a filter source that itself is
+ * moving, then don't allow the move. Moving requires sending a message to
+ * the IO thread of the old source, and if the old source is a filter
+ * source that is moving, there's no IO thread associated to the old
+ * source. */
+ if (is_filter_source_moving(o)) {
+ pa_log_debug("Can't move output from filter source %s, because the filter source itself is currently moving.",
+ o->source->name);
+ return false;
+ }
+
+ if (pa_idxset_size(dest->outputs) >= PA_MAX_OUTPUTS_PER_SOURCE) {
+ pa_log_warn("Failed to move source output: too many outputs per source.");
+ return false;
+ }
+
+ if (o->may_move_to)
+ if (!o->may_move_to(o, dest))
+ return false;
+
+ return true;
+}
+
+/* Called from main context */
+int pa_source_output_start_move(pa_source_output *o) {
+ pa_source *origin;
+ int r;
+
+ pa_source_output_assert_ref(o);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state));
+ pa_assert(o->source);
+
+ if (!pa_source_output_may_move(o))
+ return -PA_ERR_NOTSUPPORTED;
+
+ if ((r = pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_START], o)) < 0)
+ return r;
+
+ pa_log_debug("Starting to move source output %u from '%s'", (unsigned) o->index, o->source->name);
+
+ origin = o->source;
+
+ pa_idxset_remove_by_data(o->source->outputs, o, NULL);
+
+ if (o->state == PA_SOURCE_OUTPUT_CORKED)
+ pa_assert_se(origin->n_corked-- >= 1);
+
+ if (pa_source_output_is_passthrough(o))
+ pa_source_leave_passthrough(o->source);
+
+ if (pa_source_flat_volume_enabled(o->source))
+ /* We might need to update the source's volume if we are in flat
+ * volume mode. */
+ pa_source_set_volume(o->source, NULL, false, false);
+
+ pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_REMOVE_OUTPUT, o, 0, NULL) == 0);
+
+ pa_source_update_status(o->source);
+
+ pa_cvolume_remap(&o->volume_factor_source, &o->source->channel_map, &o->channel_map);
+
+ o->source = NULL;
+ o->source_requested_by_application = false;
+
+ pa_source_output_unref(o);
+
+ return 0;
+}
+
+/* Called from main context. If it has an origin source that uses volume sharing,
+ * then also the origin source and all streams connected to it need to update
+ * their volume - this function does all that by using recursion. */
+static void update_volume_due_to_moving(pa_source_output *o, pa_source *dest) {
+ pa_cvolume new_volume;
+
+ pa_assert(o);
+ pa_assert(dest);
+ pa_assert(o->source); /* The destination source should already be set. */
+
+ if (o->destination_source && (o->destination_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)) {
+ pa_source *root_source;
+ pa_source_output *destination_source_output;
+ uint32_t idx;
+
+ root_source = pa_source_get_master(o->source);
+
+ if (PA_UNLIKELY(!root_source))
+ return;
+
+ if (pa_source_flat_volume_enabled(o->source)) {
+ /* Ok, so the origin source uses volume sharing, and flat volume is
+ * enabled. The volume will have to be updated as follows:
+ *
+ * o->volume := o->source->real_volume
+ * (handled later by pa_source_set_volume)
+ * o->reference_ratio := o->volume / o->source->reference_volume
+ * (handled later by pa_source_set_volume)
+ * o->real_ratio stays unchanged
+ * (streams whose origin source uses volume sharing should
+ * always have real_ratio of 0 dB)
+ * o->soft_volume stays unchanged
+ * (streams whose origin source uses volume sharing should
+ * always have volume_factor as soft_volume, so no change
+ * should be needed) */
+
+ pa_assert(pa_cvolume_is_norm(&o->real_ratio));
+ pa_assert(pa_cvolume_equal(&o->soft_volume, &o->volume_factor));
+
+ /* Notifications will be sent by pa_source_set_volume(). */
+
+ } else {
+ /* Ok, so the origin source uses volume sharing, and flat volume is
+ * disabled. The volume will have to be updated as follows:
+ *
+ * o->volume := 0 dB
+ * o->reference_ratio := 0 dB
+ * o->real_ratio stays unchanged
+ * (streams whose origin source uses volume sharing should
+ * always have real_ratio of 0 dB)
+ * o->soft_volume stays unchanged
+ * (streams whose origin source uses volume sharing should
+ * always have volume_factor as soft_volume, so no change
+ * should be needed) */
+
+ pa_cvolume_reset(&new_volume, o->volume.channels);
+ pa_source_output_set_volume_direct(o, &new_volume);
+ pa_source_output_set_reference_ratio(o, &new_volume);
+ pa_assert(pa_cvolume_is_norm(&o->real_ratio));
+ pa_assert(pa_cvolume_equal(&o->soft_volume, &o->volume_factor));
+ }
+
+ /* Additionally, the origin source volume needs updating:
+ *
+ * o->destination_source->reference_volume := root_source->reference_volume
+ * o->destination_source->real_volume := root_source->real_volume
+ * o->destination_source->soft_volume stays unchanged
+ * (sources that use volume sharing should always have
+ * soft_volume of 0 dB) */
+
+ new_volume = root_source->reference_volume;
+ pa_cvolume_remap(&new_volume, &root_source->channel_map, &o->destination_source->channel_map);
+ pa_source_set_reference_volume_direct(o->destination_source, &new_volume);
+
+ o->destination_source->real_volume = root_source->real_volume;
+ pa_cvolume_remap(&o->destination_source->real_volume, &root_source->channel_map, &o->destination_source->channel_map);
+
+ pa_assert(pa_cvolume_is_norm(&o->destination_source->soft_volume));
+
+ /* If you wonder whether o->destination_source->set_volume() should be
+ * called somewhere, that's not the case, because sources that use
+ * volume sharing shouldn't have any internal volume that set_volume()
+ * would update. If you wonder whether the thread_info variables should
+ * be synced, yes, they should, and it's done by the
+ * PA_SOURCE_MESSAGE_FINISH_MOVE message handler. */
+
+ /* Recursively update origin source outputs. */
+ PA_IDXSET_FOREACH(destination_source_output, o->destination_source->outputs, idx)
+ update_volume_due_to_moving(destination_source_output, dest);
+
+ } else {
+ if (pa_source_flat_volume_enabled(o->source)) {
+ /* Ok, so this is a regular stream, and flat volume is enabled. The
+ * volume will have to be updated as follows:
+ *
+ * o->volume := o->reference_ratio * o->source->reference_volume
+ * o->reference_ratio stays unchanged
+ * o->real_ratio := o->volume / o->source->real_volume
+ * (handled later by pa_source_set_volume)
+ * o->soft_volume := o->real_ratio * o->volume_factor
+ * (handled later by pa_source_set_volume) */
+
+ new_volume = o->source->reference_volume;
+ pa_cvolume_remap(&new_volume, &o->source->channel_map, &o->channel_map);
+ pa_sw_cvolume_multiply(&new_volume, &new_volume, &o->reference_ratio);
+ pa_source_output_set_volume_direct(o, &new_volume);
+
+ } else {
+ /* Ok, so this is a regular stream, and flat volume is disabled.
+ * The volume will have to be updated as follows:
+ *
+ * o->volume := o->reference_ratio
+ * o->reference_ratio stays unchanged
+ * o->real_ratio := o->reference_ratio
+ * o->soft_volume := o->real_ratio * o->volume_factor */
+
+ pa_source_output_set_volume_direct(o, &o->reference_ratio);
+ o->real_ratio = o->reference_ratio;
+ pa_sw_cvolume_multiply(&o->soft_volume, &o->real_ratio, &o->volume_factor);
+ }
+ }
+
+ /* If o->source == dest, then recursion has finished, and we can finally call
+ * pa_source_set_volume(), which will do the rest of the updates. */
+ if ((o->source == dest) && pa_source_flat_volume_enabled(o->source))
+ pa_source_set_volume(o->source, NULL, false, o->save_volume);
+}
+
+/* Called from main context */
+int pa_source_output_finish_move(pa_source_output *o, pa_source *dest, bool save) {
+ pa_source_output_assert_ref(o);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state));
+ pa_assert(!o->source);
+ pa_source_assert_ref(dest);
+
+ if (!pa_source_output_may_move_to(o, dest))
+ return -PA_ERR_NOTSUPPORTED;
+
+ if (pa_source_output_is_passthrough(o) && !pa_source_check_format(dest, o->format)) {
+ pa_proplist *p = pa_proplist_new();
+ pa_log_debug("New source doesn't support stream format, sending format-changed and killing");
+ /* Tell the client what device we want to be on if it is going to
+ * reconnect */
+ pa_proplist_sets(p, "device", dest->name);
+ pa_source_output_send_event(o, PA_STREAM_EVENT_FORMAT_LOST, p);
+ pa_proplist_free(p);
+ return -PA_ERR_NOTSUPPORTED;
+ }
+
+ if (!(o->flags & PA_SOURCE_OUTPUT_VARIABLE_RATE) &&
+ !pa_sample_spec_equal(&o->sample_spec, &dest->sample_spec)) {
+ /* try to change dest source format and rate if possible without glitches.
+ module-suspend-on-idle resumes destination source with
+ SOURCE_OUTPUT_MOVE_FINISH hook */
+
+ pa_log_info("Trying to change sample spec");
+ pa_source_reconfigure(dest, &o->sample_spec, pa_source_output_is_passthrough(o));
+ }
+
+ if (o->moving)
+ o->moving(o, dest);
+
+ o->source = dest;
+ /* save == true, means user is calling the move_to() and want to
+ save the preferred_source */
+ if (save) {
+ pa_xfree(o->preferred_source);
+ if (dest == dest->core->default_source)
+ o->preferred_source = NULL;
+ else
+ o->preferred_source = pa_xstrdup(dest->name);
+ }
+
+ pa_idxset_put(o->source->outputs, pa_source_output_ref(o), NULL);
+
+ pa_cvolume_remap(&o->volume_factor_source, &o->channel_map, &o->source->channel_map);
+
+ if (o->state == PA_SOURCE_OUTPUT_CORKED)
+ o->source->n_corked++;
+
+ pa_source_output_update_resampler(o);
+
+ pa_source_update_status(dest);
+
+ update_volume_due_to_moving(o, dest);
+
+ if (pa_source_output_is_passthrough(o))
+ pa_source_enter_passthrough(o->source);
+
+ pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_ADD_OUTPUT, o, 0, NULL) == 0);
+
+ pa_log_debug("Successfully moved source output %i to %s.", o->index, dest->name);
+
+ /* Notify everyone */
+ pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_FINISH], o);
+ pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index);
+
+ return 0;
+}
+
+/* Called from main context */
+void pa_source_output_fail_move(pa_source_output *o) {
+
+ pa_source_output_assert_ref(o);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state));
+ pa_assert(!o->source);
+
+ /* Check if someone wants this source output? */
+ if (pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_FAIL], o) == PA_HOOK_STOP)
+ return;
+
+ /* Can we move the source output to the default source? */
+ if (o->core->rescue_streams && pa_source_output_may_move_to(o, o->core->default_source)) {
+ if (pa_source_output_finish_move(o, o->core->default_source, false) >= 0)
+ return;
+ }
+
+ if (o->moving)
+ o->moving(o, NULL);
+
+ pa_source_output_kill(o);
+}
+
+/* Called from main context */
+int pa_source_output_move_to(pa_source_output *o, pa_source *dest, bool save) {
+ int r;
+
+ pa_source_output_assert_ref(o);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state));
+ pa_assert(o->source);
+ pa_source_assert_ref(dest);
+
+ if (dest == o->source)
+ return 0;
+
+ if (!pa_source_output_may_move_to(o, dest))
+ return -PA_ERR_NOTSUPPORTED;
+
+ pa_source_output_ref(o);
+
+ if ((r = pa_source_output_start_move(o)) < 0) {
+ pa_source_output_unref(o);
+ return r;
+ }
+
+ if ((r = pa_source_output_finish_move(o, dest, save)) < 0) {
+ pa_source_output_fail_move(o);
+ pa_source_output_unref(o);
+ return r;
+ }
+
+ pa_source_output_unref(o);
+
+ return 0;
+}
+
+/* Called from IO thread context except when cork() is called without a valid source. */
+void pa_source_output_set_state_within_thread(pa_source_output *o, pa_source_output_state_t state) {
+ pa_source_output_assert_ref(o);
+
+ if (state == o->thread_info.state)
+ return;
+
+ if (o->state_change)
+ o->state_change(o, state);
+
+ o->thread_info.state = state;
+}
+
+/* Called from IO thread context, except when it is not */
+int pa_source_output_process_msg(pa_msgobject *mo, int code, void *userdata, int64_t offset, pa_memchunk* chunk) {
+ pa_source_output *o = PA_SOURCE_OUTPUT(mo);
+ pa_source_output_assert_ref(o);
+
+ switch (code) {
+
+ case PA_SOURCE_OUTPUT_MESSAGE_GET_LATENCY: {
+ pa_usec_t *r = userdata;
+
+ r[0] += pa_bytes_to_usec(pa_memblockq_get_length(o->thread_info.delay_memblockq), &o->source->sample_spec);
+ r[1] += pa_source_get_latency_within_thread(o->source, false);
+
+ return 0;
+ }
+
+ case PA_SOURCE_OUTPUT_MESSAGE_SET_RATE:
+
+ o->thread_info.sample_spec.rate = PA_PTR_TO_UINT(userdata);
+ pa_resampler_set_output_rate(o->thread_info.resampler, PA_PTR_TO_UINT(userdata));
+ return 0;
+
+ case PA_SOURCE_OUTPUT_MESSAGE_SET_STATE:
+
+ pa_source_output_set_state_within_thread(o, PA_PTR_TO_UINT(userdata));
+
+ return 0;
+
+ case PA_SOURCE_OUTPUT_MESSAGE_SET_REQUESTED_LATENCY: {
+ pa_usec_t *usec = userdata;
+
+ *usec = pa_source_output_set_requested_latency_within_thread(o, *usec);
+
+ return 0;
+ }
+
+ case PA_SOURCE_OUTPUT_MESSAGE_GET_REQUESTED_LATENCY: {
+ pa_usec_t *r = userdata;
+
+ *r = o->thread_info.requested_source_latency;
+ return 0;
+ }
+
+ case PA_SOURCE_OUTPUT_MESSAGE_SET_SOFT_VOLUME:
+ if (!pa_cvolume_equal(&o->thread_info.soft_volume, &o->soft_volume)) {
+ o->thread_info.soft_volume = o->soft_volume;
+ }
+ return 0;
+
+ case PA_SOURCE_OUTPUT_MESSAGE_SET_SOFT_MUTE:
+ if (o->thread_info.muted != o->muted) {
+ o->thread_info.muted = o->muted;
+ }
+ return 0;
+ }
+
+ return -PA_ERR_NOTIMPLEMENTED;
+}
+
+/* Called from main context */
+void pa_source_output_send_event(pa_source_output *o, const char *event, pa_proplist *data) {
+ pa_proplist *pl = NULL;
+ pa_source_output_send_event_hook_data hook_data;
+
+ pa_source_output_assert_ref(o);
+ pa_assert_ctl_context();
+ pa_assert(event);
+
+ if (!o->send_event)
+ return;
+
+ if (!data)
+ data = pl = pa_proplist_new();
+
+ hook_data.source_output = o;
+ hook_data.data = data;
+ hook_data.event = event;
+
+ if (pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_SEND_EVENT], &hook_data) < 0)
+ goto finish;
+
+ o->send_event(o, event, data);
+
+finish:
+ if (pl)
+ pa_proplist_free(pl);
+}
+
+/* Called from main context */
+/* Updates the source output's resampler with whatever the current source
+ * requires -- useful when the underlying source's sample spec might have changed */
+int pa_source_output_update_resampler(pa_source_output *o) {
+ pa_resampler *new_resampler;
+ char *memblockq_name;
+
+ pa_source_output_assert_ref(o);
+ pa_assert_ctl_context();
+
+ if (o->thread_info.resampler &&
+ pa_sample_spec_equal(pa_resampler_input_sample_spec(o->thread_info.resampler), &o->source->sample_spec) &&
+ pa_channel_map_equal(pa_resampler_input_channel_map(o->thread_info.resampler), &o->source->channel_map))
+
+ new_resampler = o->thread_info.resampler;
+
+ else if (!pa_source_output_is_passthrough(o) &&
+ ((o->flags & PA_SOURCE_OUTPUT_VARIABLE_RATE) ||
+ !pa_sample_spec_equal(&o->sample_spec, &o->source->sample_spec) ||
+ !pa_channel_map_equal(&o->channel_map, &o->source->channel_map))) {
+
+ new_resampler = pa_resampler_new(o->core->mempool,
+ &o->source->sample_spec, &o->source->channel_map,
+ &o->sample_spec, &o->channel_map,
+ o->core->lfe_crossover_freq,
+ o->requested_resample_method,
+ ((o->flags & PA_SOURCE_OUTPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0) |
+ ((o->flags & PA_SOURCE_OUTPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) |
+ (o->core->disable_remixing || (o->flags & PA_SOURCE_OUTPUT_NO_REMIX) ? PA_RESAMPLER_NO_REMIX : 0) |
+ (o->core->remixing_use_all_sink_channels ? 0 : PA_RESAMPLER_NO_FILL_SINK) |
+ (o->core->remixing_produce_lfe ? PA_RESAMPLER_PRODUCE_LFE : 0) |
+ (o->core->remixing_consume_lfe ? PA_RESAMPLER_CONSUME_LFE : 0));
+
+ if (!new_resampler) {
+ pa_log_warn("Unsupported resampling operation.");
+ return -PA_ERR_NOTSUPPORTED;
+ }
+ } else
+ new_resampler = NULL;
+
+ if (new_resampler == o->thread_info.resampler)
+ return 0;
+
+ if (o->thread_info.resampler)
+ pa_resampler_free(o->thread_info.resampler);
+
+ o->thread_info.resampler = new_resampler;
+
+ pa_memblockq_free(o->thread_info.delay_memblockq);
+
+ memblockq_name = pa_sprintf_malloc("source output delay_memblockq [%u]", o->index);
+ o->thread_info.delay_memblockq = pa_memblockq_new(
+ memblockq_name,
+ 0,
+ MEMBLOCKQ_MAXLENGTH,
+ 0,
+ &o->source->sample_spec,
+ 0,
+ 1,
+ 0,
+ &o->source->silence);
+ pa_xfree(memblockq_name);
+
+ o->actual_resample_method = new_resampler ? pa_resampler_get_method(new_resampler) : PA_RESAMPLER_INVALID;
+
+ pa_log_debug("Updated resampler for source output %d", o->index);
+
+ return 0;
+}
+
+/* Called from the IO thread. */
+void pa_source_output_attach(pa_source_output *o) {
+ pa_assert(o);
+ pa_assert(!o->thread_info.attached);
+
+ o->thread_info.attached = true;
+
+ if (o->attach)
+ o->attach(o);
+}
+
+/* Called from the IO thread. */
+void pa_source_output_detach(pa_source_output *o) {
+ pa_assert(o);
+
+ if (!o->thread_info.attached)
+ return;
+
+ o->thread_info.attached = false;
+
+ if (o->detach)
+ o->detach(o);
+}
+
+/* Called from the main thread. */
+void pa_source_output_set_volume_direct(pa_source_output *o, const pa_cvolume *volume) {
+ pa_cvolume old_volume;
+ char old_volume_str[PA_CVOLUME_SNPRINT_VERBOSE_MAX];
+ char new_volume_str[PA_CVOLUME_SNPRINT_VERBOSE_MAX];
+
+ pa_assert(o);
+ pa_assert(volume);
+
+ old_volume = o->volume;
+
+ if (pa_cvolume_equal(volume, &old_volume))
+ return;
+
+ o->volume = *volume;
+ pa_log_debug("The volume of source output %u changed from %s to %s.", o->index,
+ pa_cvolume_snprint_verbose(old_volume_str, sizeof(old_volume_str), &old_volume, &o->channel_map, true),
+ pa_cvolume_snprint_verbose(new_volume_str, sizeof(new_volume_str), volume, &o->channel_map, true));
+
+ if (o->volume_changed)
+ o->volume_changed(o);
+
+ pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index);
+ pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_VOLUME_CHANGED], o);
+}
+
+/* Called from the main thread. */
+void pa_source_output_set_reference_ratio(pa_source_output *o, const pa_cvolume *ratio) {
+ pa_cvolume old_ratio;
+ char old_ratio_str[PA_CVOLUME_SNPRINT_VERBOSE_MAX];
+ char new_ratio_str[PA_CVOLUME_SNPRINT_VERBOSE_MAX];
+
+ pa_assert(o);
+ pa_assert(ratio);
+
+ old_ratio = o->reference_ratio;
+
+ if (pa_cvolume_equal(ratio, &old_ratio))
+ return;
+
+ o->reference_ratio = *ratio;
+
+ if (!PA_SOURCE_OUTPUT_IS_LINKED(o->state))
+ return;
+
+ pa_log_debug("Source output %u reference ratio changed from %s to %s.", o->index,
+ pa_cvolume_snprint_verbose(old_ratio_str, sizeof(old_ratio_str), &old_ratio, &o->channel_map, true),
+ pa_cvolume_snprint_verbose(new_ratio_str, sizeof(new_ratio_str), ratio, &o->channel_map, true));
+}
+
+/* Called from the main thread. */
+void pa_source_output_set_preferred_source(pa_source_output *o, pa_source *s) {
+ pa_assert(o);
+
+ pa_xfree(o->preferred_source);
+ if (s) {
+ o->preferred_source = pa_xstrdup(s->name);
+ pa_source_output_move_to(o, s, false);
+ } else {
+ o->preferred_source = NULL;
+ pa_source_output_move_to(o, o->core->default_source, false);
+ }
+}
diff --git a/src/pulsecore/source-output.h b/src/pulsecore/source-output.h
new file mode 100644
index 0000000..2bf5682
--- /dev/null
+++ b/src/pulsecore/source-output.h
@@ -0,0 +1,410 @@
+#ifndef foopulsesourceoutputhfoo
+#define foopulsesourceoutputhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <inttypes.h>
+
+#include <pulsecore/typedefs.h>
+#include <pulse/sample.h>
+#include <pulse/format.h>
+#include <pulsecore/memblockq.h>
+#include <pulsecore/resampler.h>
+#include <pulsecore/module.h>
+#include <pulsecore/client.h>
+#include <pulsecore/source.h>
+#include <pulsecore/core.h>
+#include <pulsecore/sink-input.h>
+
+typedef enum pa_source_output_state {
+ PA_SOURCE_OUTPUT_INIT,
+ PA_SOURCE_OUTPUT_RUNNING,
+ PA_SOURCE_OUTPUT_CORKED,
+ PA_SOURCE_OUTPUT_UNLINKED
+} pa_source_output_state_t;
+
+static inline bool PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_state_t x) {
+ return x == PA_SOURCE_OUTPUT_RUNNING || x == PA_SOURCE_OUTPUT_CORKED;
+}
+
+typedef enum pa_source_output_flags {
+ PA_SOURCE_OUTPUT_VARIABLE_RATE = 1,
+ PA_SOURCE_OUTPUT_DONT_MOVE = 2,
+ PA_SOURCE_OUTPUT_START_CORKED = 4,
+ PA_SOURCE_OUTPUT_NO_REMAP = 8,
+ PA_SOURCE_OUTPUT_NO_REMIX = 16,
+ PA_SOURCE_OUTPUT_FIX_FORMAT = 32,
+ PA_SOURCE_OUTPUT_FIX_RATE = 64,
+ PA_SOURCE_OUTPUT_FIX_CHANNELS = 128,
+ PA_SOURCE_OUTPUT_DONT_INHIBIT_AUTO_SUSPEND = 256,
+ PA_SOURCE_OUTPUT_NO_CREATE_ON_SUSPEND = 512,
+ PA_SOURCE_OUTPUT_KILL_ON_SUSPEND = 1024,
+ PA_SOURCE_OUTPUT_PASSTHROUGH = 2048
+} pa_source_output_flags_t;
+
+struct pa_source_output {
+ pa_msgobject parent;
+
+ uint32_t index;
+ pa_core *core;
+
+ pa_source_output_state_t state;
+ pa_source_output_flags_t flags;
+
+ char *driver; /* may be NULL */
+ pa_proplist *proplist;
+
+ pa_module *module; /* may be NULL */
+ pa_client *client; /* may be NULL */
+
+ pa_source *source; /* NULL while being moved */
+
+ /* This is set to true when creating the source output if the source was
+ * requested by the application that created the source output. This is
+ * sometimes useful for determining whether the source output should be
+ * moved by some automatic policy. If the source output is moved away from
+ * the source that the application requested, this flag is reset to
+ * false. */
+ bool source_requested_by_application;
+
+ pa_source *destination_source; /* only set by filter sources */
+
+ /* A source output can monitor just a single input of a sink, in which case we find it here */
+ pa_sink_input *direct_on_input; /* may be NULL */
+
+ pa_sample_spec sample_spec;
+ pa_channel_map channel_map;
+ pa_format_info *format;
+
+ /* Also see http://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/Developer/Volumes/ */
+ pa_cvolume volume; /* The volume clients are informed about */
+ pa_cvolume reference_ratio; /* The ratio of the stream's volume to the source's reference volume */
+ pa_cvolume real_ratio; /* The ratio of the stream's volume to the source's real volume */
+ pa_cvolume volume_factor; /* An internally used volume factor that can be used by modules to apply effects and suchlike without having that visible to the outside */
+ pa_cvolume soft_volume; /* The internal software volume we apply to all PCM data while it passes through. Usually calculated as real_ratio * volume_factor */
+
+ pa_cvolume volume_factor_source; /* A second volume factor in format of the source this stream is connected to */
+
+ bool volume_writable:1;
+
+ bool muted:1;
+
+ /* if true then the volume and the mute state of this source-output
+ * are worth remembering, module-stream-restore looks for this. */
+ bool save_volume:1, save_muted:1;
+
+ /* if users move the source-output to a source, and the source is not
+ * default_source, the source->name will be saved in preferred_source. And
+ * later if source-output is moved to other sources for some reason, it
+ * still can be restored to the preferred_source at an appropriate time */
+ char *preferred_source;
+
+ pa_resample_method_t requested_resample_method, actual_resample_method;
+
+ /* Pushes a new memchunk into the output. Called from IO thread
+ * context. */
+ void (*push)(pa_source_output *o, const pa_memchunk *chunk); /* may NOT be NULL */
+
+ /* Only relevant for monitor sources right now: called when the
+ * recorded stream is rewound. Called from IO context */
+ void (*process_rewind)(pa_source_output *o, size_t nbytes); /* may be NULL */
+
+ /* Called whenever the maximum rewindable size of the source
+ * changes. Called from IO thread context. */
+ void (*update_max_rewind) (pa_source_output *o, size_t nbytes); /* may be NULL */
+
+ /* Called whenever the configured latency of the source
+ * changes. Called from IO context. */
+ void (*update_source_requested_latency) (pa_source_output *o); /* may be NULL */
+
+ /* Called whenever the latency range of the source changes. Called
+ * from IO context. */
+ void (*update_source_latency_range) (pa_source_output *o); /* may be NULL */
+
+ /* Called whenever the fixed latency of the source changes, if there
+ * is one. Called from IO context. */
+ void (*update_source_fixed_latency) (pa_source_output *i); /* may be NULL */
+
+ /* If non-NULL this function is called when the output is first
+ * connected to a source or when the rtpoll/asyncmsgq fields
+ * change. You usually don't need to implement this function
+ * unless you rewrite a source that is piggy-backed onto
+ * another. Called from IO thread context */
+ void (*attach) (pa_source_output *o); /* may be NULL */
+
+ /* If non-NULL this function is called when the output is
+ * disconnected from its source. Called from IO thread context */
+ void (*detach) (pa_source_output *o); /* may be NULL */
+
+ /* If non-NULL called whenever the source this output is attached
+ * to suspends or resumes or if the suspend cause changes.
+ * Called from main context */
+ void (*suspend) (pa_source_output *o, pa_source_state_t old_state, pa_suspend_cause_t old_suspend_cause); /* may be NULL */
+
+ /* If non-NULL called whenever the source this output is attached
+ * to suspends or resumes. Called from IO context */
+ void (*suspend_within_thread) (pa_source_output *o, bool b); /* may be NULL */
+
+ /* If non-NULL called whenever the source output is moved to a new
+ * source. Called from main context after the source output has been
+ * detached from the old source and before it has been attached to
+ * the new source. If dest is NULL the move was executed in two
+ * phases and the second one failed; the stream will be destroyed
+ * after this call. */
+ void (*moving) (pa_source_output *o, pa_source *dest); /* may be NULL */
+
+ /* Supposed to unlink and destroy this stream. Called from main
+ * context. */
+ void (*kill)(pa_source_output* o); /* may NOT be NULL */
+
+ /* Return the current latency (i.e. length of buffered audio) of
+ this stream. Called from main context. This is added to what the
+ PA_SOURCE_OUTPUT_MESSAGE_GET_LATENCY message sent to the IO thread
+ returns */
+ pa_usec_t (*get_latency) (pa_source_output *o); /* may be NULL */
+
+ /* If non-NULL this function is called from thread context if the
+ * state changes. The old state is found in thread_info.state. */
+ void (*state_change) (pa_source_output *o, pa_source_output_state_t state); /* may be NULL */
+
+ /* If non-NULL this function is called before this source output
+ * is moved to a source and if it returns false the move
+ * will not be allowed */
+ bool (*may_move_to) (pa_source_output *o, pa_source *s); /* may be NULL */
+
+ /* If non-NULL this function is used to dispatch asynchronous
+ * control events. */
+ void (*send_event)(pa_source_output *o, const char *event, pa_proplist* data);
+
+ /* If non-NULL this function is called whenever the source output
+ * volume changes. Called from main context */
+ void (*volume_changed)(pa_source_output *o); /* may be NULL */
+
+ /* If non-NULL this function is called whenever the source output
+ * mute status changes. Called from main context */
+ void (*mute_changed)(pa_source_output *o); /* may be NULL */
+
+ struct {
+ pa_source_output_state_t state;
+
+ pa_cvolume soft_volume;
+ bool muted:1;
+
+ bool attached:1; /* True only between ->attach() and ->detach() calls */
+
+ pa_sample_spec sample_spec;
+
+ pa_resampler* resampler; /* may be NULL */
+
+ /* We maintain a delay memblockq here for source outputs that
+ * don't implement rewind() */
+ pa_memblockq *delay_memblockq;
+
+ /* The requested latency for the source */
+ pa_usec_t requested_source_latency;
+
+ pa_sink_input *direct_on_input; /* may be NULL */
+ } thread_info;
+
+ void *userdata;
+};
+
+PA_DECLARE_PUBLIC_CLASS(pa_source_output);
+#define PA_SOURCE_OUTPUT(o) pa_source_output_cast(o)
+
+enum {
+ PA_SOURCE_OUTPUT_MESSAGE_GET_LATENCY,
+ PA_SOURCE_OUTPUT_MESSAGE_SET_RATE,
+ PA_SOURCE_OUTPUT_MESSAGE_SET_STATE,
+ PA_SOURCE_OUTPUT_MESSAGE_SET_REQUESTED_LATENCY,
+ PA_SOURCE_OUTPUT_MESSAGE_GET_REQUESTED_LATENCY,
+ PA_SOURCE_OUTPUT_MESSAGE_SET_SOFT_VOLUME,
+ PA_SOURCE_OUTPUT_MESSAGE_SET_SOFT_MUTE,
+ PA_SOURCE_OUTPUT_MESSAGE_MAX
+};
+
+typedef struct pa_source_output_send_event_hook_data {
+ pa_source_output *source_output;
+ const char *event;
+ pa_proplist *data;
+} pa_source_output_send_event_hook_data;
+
+typedef struct pa_source_output_new_data {
+ pa_source_output_flags_t flags;
+
+ pa_proplist *proplist;
+ pa_sink_input *direct_on_input;
+
+ const char *driver;
+ pa_module *module;
+ pa_client *client;
+
+ pa_source *source;
+ bool source_requested_by_application;
+ pa_source *destination_source;
+
+ pa_resample_method_t resample_method;
+
+ pa_sample_spec sample_spec;
+ pa_channel_map channel_map;
+ pa_format_info *format;
+ pa_idxset *req_formats;
+ pa_idxset *nego_formats;
+
+ pa_cvolume volume, volume_factor, volume_factor_source;
+ bool muted:1;
+
+ bool sample_spec_is_set:1;
+ bool channel_map_is_set:1;
+
+ bool volume_is_set:1, volume_factor_is_set:1, volume_factor_source_is_set:1;
+ bool muted_is_set:1;
+
+ bool volume_is_absolute:1;
+
+ bool volume_writable:1;
+
+ bool save_volume:1, save_muted:1;
+ char *preferred_source;
+} pa_source_output_new_data;
+
+pa_source_output_new_data* pa_source_output_new_data_init(pa_source_output_new_data *data);
+void pa_source_output_new_data_set_sample_spec(pa_source_output_new_data *data, const pa_sample_spec *spec);
+void pa_source_output_new_data_set_channel_map(pa_source_output_new_data *data, const pa_channel_map *map);
+bool pa_source_output_new_data_is_passthrough(pa_source_output_new_data *data);
+void pa_source_output_new_data_set_volume(pa_source_output_new_data *data, const pa_cvolume *volume);
+void pa_source_output_new_data_apply_volume_factor(pa_source_output_new_data *data, const pa_cvolume *volume_factor);
+void pa_source_output_new_data_apply_volume_factor_source(pa_source_output_new_data *data, const pa_cvolume *volume_factor);
+void pa_source_output_new_data_set_muted(pa_source_output_new_data *data, bool mute);
+bool pa_source_output_new_data_set_source(pa_source_output_new_data *data, pa_source *s, bool save,
+ bool requested_by_application);
+bool pa_source_output_new_data_set_formats(pa_source_output_new_data *data, pa_idxset *formats);
+void pa_source_output_new_data_done(pa_source_output_new_data *data);
+
+/* To be called by the implementing module only */
+
+int pa_source_output_new(
+ pa_source_output**o,
+ pa_core *core,
+ pa_source_output_new_data *data);
+
+void pa_source_output_put(pa_source_output *o);
+void pa_source_output_unlink(pa_source_output*o);
+
+pa_usec_t pa_source_output_set_requested_latency(pa_source_output *o, pa_usec_t usec);
+
+void pa_source_output_cork(pa_source_output *o, bool b);
+
+int pa_source_output_set_rate(pa_source_output *o, uint32_t rate);
+int pa_source_output_update_resampler(pa_source_output *o);
+
+size_t pa_source_output_get_max_rewind(pa_source_output *o);
+
+/* Callable by everyone */
+
+/* External code may request disconnection with this function */
+void pa_source_output_kill(pa_source_output*o);
+
+pa_usec_t pa_source_output_get_latency(pa_source_output *o, pa_usec_t *source_latency);
+
+bool pa_source_output_is_volume_readable(pa_source_output *o);
+bool pa_source_output_is_passthrough(pa_source_output *o);
+void pa_source_output_set_volume(pa_source_output *o, const pa_cvolume *volume, bool save, bool absolute);
+pa_cvolume *pa_source_output_get_volume(pa_source_output *o, pa_cvolume *volume, bool absolute);
+
+void pa_source_output_set_mute(pa_source_output *o, bool mute, bool save);
+
+void pa_source_output_set_property(pa_source_output *o, const char *key, const char *value);
+void pa_source_output_set_property_arbitrary(pa_source_output *o, const char *key, const uint8_t *value, size_t nbytes);
+void pa_source_output_update_proplist(pa_source_output *o, pa_update_mode_t mode, pa_proplist *p);
+
+pa_resample_method_t pa_source_output_get_resample_method(pa_source_output *o);
+
+void pa_source_output_send_event(pa_source_output *o, const char *name, pa_proplist *data);
+
+bool pa_source_output_may_move(pa_source_output *o);
+bool pa_source_output_may_move_to(pa_source_output *o, pa_source *dest);
+int pa_source_output_move_to(pa_source_output *o, pa_source *dest, bool save);
+
+/* The same as pa_source_output_move_to() but in two separate steps,
+ * first the detaching from the old source, then the attaching to the
+ * new source */
+int pa_source_output_start_move(pa_source_output *o);
+int pa_source_output_finish_move(pa_source_output *o, pa_source *dest, bool save);
+void pa_source_output_fail_move(pa_source_output *o);
+
+pa_usec_t pa_source_output_get_requested_latency(pa_source_output *o);
+
+/* To be used exclusively by the source driver thread */
+
+void pa_source_output_push(pa_source_output *o, const pa_memchunk *chunk);
+void pa_source_output_process_rewind(pa_source_output *o, size_t nbytes);
+void pa_source_output_update_max_rewind(pa_source_output *o, size_t nbytes);
+
+void pa_source_output_set_state_within_thread(pa_source_output *o, pa_source_output_state_t state);
+
+int pa_source_output_process_msg(pa_msgobject *mo, int code, void *userdata, int64_t offset, pa_memchunk *chunk);
+
+pa_usec_t pa_source_output_set_requested_latency_within_thread(pa_source_output *o, pa_usec_t usec);
+
+/* Calls the attach() callback if it's set. The output must be in detached
+ * state. */
+void pa_source_output_attach(pa_source_output *o);
+
+/* Calls the detach() callback if it's set and the output is attached. The
+ * output is allowed to be already detached, in which case this does nothing.
+ *
+ * The reason why this can be called for already-detached outputs is that when
+ * a filter source's output is detached, it has to detach also all outputs
+ * connected to the filter source. In case the filter source's output was
+ * detached because the filter source is being removed, those other outputs
+ * will be moved to another source or removed, and moving and removing involve
+ * detaching the outputs, but the outputs at that point are already detached.
+ *
+ * XXX: Moving or removing an output also involves sending messages to the
+ * output's source. If the output's source is a detached filter source,
+ * shouldn't sending messages to it be prohibited? The messages are processed
+ * in the root source's IO thread, and when the filter source is detached, it
+ * would seem logical to prohibit any interaction with the IO thread that isn't
+ * any more associated with the filter source. Currently sending messages to
+ * detached filter sources mostly works, because the filter sources don't
+ * update their asyncmsgq pointer when detaching, so messages still find their
+ * way to the old IO thread. */
+void pa_source_output_detach(pa_source_output *o);
+
+/* Called from the main thread, from source.c only. The normal way to set the
+ * source output volume is to call pa_source_output_set_volume(), but the flat
+ * volume logic in source.c needs also a function that doesn't do all the extra
+ * stuff that pa_source_output_set_volume() does. This function simply sets
+ * o->volume and fires change notifications. */
+void pa_source_output_set_volume_direct(pa_source_output *o, const pa_cvolume *volume);
+
+/* Called from the main thread, from source.c only. This shouldn't be a public
+ * function, but the flat volume logic in source.c currently needs a way to
+ * directly set the source output reference ratio. This function simply sets
+ * o->reference_ratio and logs a message if the value changes. */
+void pa_source_output_set_reference_ratio(pa_source_output *o, const pa_cvolume *ratio);
+
+void pa_source_output_set_preferred_source(pa_source_output *o, pa_source *s);
+
+#define pa_source_output_assert_io_context(s) \
+ pa_assert(pa_thread_mq_get() || !PA_SOURCE_OUTPUT_IS_LINKED((s)->state))
+
+#endif
diff --git a/src/pulsecore/source.c b/src/pulsecore/source.c
new file mode 100644
index 0000000..efc3640
--- /dev/null
+++ b/src/pulsecore/source.c
@@ -0,0 +1,3052 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <pulse/format.h>
+#include <pulse/utf8.h>
+#include <pulse/xmalloc.h>
+#include <pulse/timeval.h>
+#include <pulse/util.h>
+#include <pulse/rtclock.h>
+#include <pulse/internal.h>
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/source-output.h>
+#include <pulsecore/namereg.h>
+#include <pulsecore/core-subscribe.h>
+#include <pulsecore/log.h>
+#include <pulsecore/mix.h>
+#include <pulsecore/flist.h>
+
+#include "source.h"
+
+#define ABSOLUTE_MIN_LATENCY (500)
+#define ABSOLUTE_MAX_LATENCY (10*PA_USEC_PER_SEC)
+#define DEFAULT_FIXED_LATENCY (250*PA_USEC_PER_MSEC)
+
+PA_DEFINE_PUBLIC_CLASS(pa_source, pa_msgobject);
+
+struct pa_source_volume_change {
+ pa_usec_t at;
+ pa_cvolume hw_volume;
+
+ PA_LLIST_FIELDS(pa_source_volume_change);
+};
+
+struct set_state_data {
+ pa_source_state_t state;
+ pa_suspend_cause_t suspend_cause;
+};
+
+static void source_free(pa_object *o);
+
+static void pa_source_volume_change_push(pa_source *s);
+static void pa_source_volume_change_flush(pa_source *s);
+
+pa_source_new_data* pa_source_new_data_init(pa_source_new_data *data) {
+ pa_assert(data);
+
+ pa_zero(*data);
+ data->proplist = pa_proplist_new();
+ data->ports = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, (pa_free_cb_t) pa_device_port_unref);
+
+ return data;
+}
+
+void pa_source_new_data_set_name(pa_source_new_data *data, const char *name) {
+ pa_assert(data);
+
+ pa_xfree(data->name);
+ data->name = pa_xstrdup(name);
+}
+
+void pa_source_new_data_set_sample_spec(pa_source_new_data *data, const pa_sample_spec *spec) {
+ pa_assert(data);
+
+ if ((data->sample_spec_is_set = !!spec))
+ data->sample_spec = *spec;
+}
+
+void pa_source_new_data_set_channel_map(pa_source_new_data *data, const pa_channel_map *map) {
+ pa_assert(data);
+
+ if ((data->channel_map_is_set = !!map))
+ data->channel_map = *map;
+}
+
+void pa_source_new_data_set_alternate_sample_rate(pa_source_new_data *data, const uint32_t alternate_sample_rate) {
+ pa_assert(data);
+
+ data->alternate_sample_rate_is_set = true;
+ data->alternate_sample_rate = alternate_sample_rate;
+}
+
+void pa_source_new_data_set_avoid_resampling(pa_source_new_data *data, bool avoid_resampling) {
+ pa_assert(data);
+
+ data->avoid_resampling_is_set = true;
+ data->avoid_resampling = avoid_resampling;
+}
+
+void pa_source_new_data_set_volume(pa_source_new_data *data, const pa_cvolume *volume) {
+ pa_assert(data);
+
+ if ((data->volume_is_set = !!volume))
+ data->volume = *volume;
+}
+
+void pa_source_new_data_set_muted(pa_source_new_data *data, bool mute) {
+ pa_assert(data);
+
+ data->muted_is_set = true;
+ data->muted = mute;
+}
+
+void pa_source_new_data_set_port(pa_source_new_data *data, const char *port) {
+ pa_assert(data);
+
+ pa_xfree(data->active_port);
+ data->active_port = pa_xstrdup(port);
+}
+
+void pa_source_new_data_done(pa_source_new_data *data) {
+ pa_assert(data);
+
+ pa_proplist_free(data->proplist);
+
+ if (data->ports)
+ pa_hashmap_free(data->ports);
+
+ pa_xfree(data->name);
+ pa_xfree(data->active_port);
+}
+
+/* Called from main context */
+static void reset_callbacks(pa_source *s) {
+ pa_assert(s);
+
+ s->set_state_in_main_thread = NULL;
+ s->set_state_in_io_thread = NULL;
+ s->get_volume = NULL;
+ s->set_volume = NULL;
+ s->write_volume = NULL;
+ s->get_mute = NULL;
+ s->set_mute = NULL;
+ s->update_requested_latency = NULL;
+ s->set_port = NULL;
+ s->get_formats = NULL;
+ s->reconfigure = NULL;
+}
+
+/* Called from main context */
+pa_source* pa_source_new(
+ pa_core *core,
+ pa_source_new_data *data,
+ pa_source_flags_t flags) {
+
+ pa_source *s;
+ const char *name;
+ char st[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX];
+ char *pt;
+
+ pa_assert(core);
+ pa_assert(data);
+ pa_assert(data->name);
+ pa_assert_ctl_context();
+
+ s = pa_msgobject_new(pa_source);
+
+ if (!(name = pa_namereg_register(core, data->name, PA_NAMEREG_SOURCE, s, data->namereg_fail))) {
+ pa_log_debug("Failed to register name %s.", data->name);
+ pa_xfree(s);
+ return NULL;
+ }
+
+ pa_source_new_data_set_name(data, name);
+
+ if (pa_hook_fire(&core->hooks[PA_CORE_HOOK_SOURCE_NEW], data) < 0) {
+ pa_xfree(s);
+ pa_namereg_unregister(core, name);
+ return NULL;
+ }
+
+ /* FIXME, need to free s here on failure */
+
+ pa_return_null_if_fail(!data->driver || pa_utf8_valid(data->driver));
+ pa_return_null_if_fail(data->name && pa_utf8_valid(data->name) && data->name[0]);
+
+ pa_return_null_if_fail(data->sample_spec_is_set && pa_sample_spec_valid(&data->sample_spec));
+
+ if (!data->channel_map_is_set)
+ pa_return_null_if_fail(pa_channel_map_init_auto(&data->channel_map, data->sample_spec.channels, PA_CHANNEL_MAP_DEFAULT));
+
+ pa_return_null_if_fail(pa_channel_map_valid(&data->channel_map));
+ pa_return_null_if_fail(data->channel_map.channels == data->sample_spec.channels);
+
+ /* FIXME: There should probably be a general function for checking whether
+ * the source volume is allowed to be set, like there is for source outputs. */
+ pa_assert(!data->volume_is_set || !(flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER));
+
+ if (!data->volume_is_set) {
+ pa_cvolume_reset(&data->volume, data->sample_spec.channels);
+ data->save_volume = false;
+ }
+
+ pa_return_null_if_fail(pa_cvolume_valid(&data->volume));
+ pa_return_null_if_fail(pa_cvolume_compatible(&data->volume, &data->sample_spec));
+
+ if (!data->muted_is_set)
+ data->muted = false;
+
+ if (data->card)
+ pa_proplist_update(data->proplist, PA_UPDATE_MERGE, data->card->proplist);
+
+ pa_device_init_description(data->proplist, data->card);
+ pa_device_init_icon(data->proplist, false);
+ pa_device_init_intended_roles(data->proplist);
+
+ if (!data->active_port) {
+ pa_device_port *p = pa_device_port_find_best(data->ports);
+ if (p)
+ pa_source_new_data_set_port(data, p->name);
+ }
+
+ if (pa_hook_fire(&core->hooks[PA_CORE_HOOK_SOURCE_FIXATE], data) < 0) {
+ pa_xfree(s);
+ pa_namereg_unregister(core, name);
+ return NULL;
+ }
+
+ s->parent.parent.free = source_free;
+ s->parent.process_msg = pa_source_process_msg;
+
+ s->core = core;
+ s->state = PA_SOURCE_INIT;
+ s->flags = flags;
+ s->priority = 0;
+ s->suspend_cause = data->suspend_cause;
+ s->name = pa_xstrdup(name);
+ s->proplist = pa_proplist_copy(data->proplist);
+ s->driver = pa_xstrdup(pa_path_get_filename(data->driver));
+ s->module = data->module;
+ s->card = data->card;
+
+ s->priority = pa_device_init_priority(s->proplist);
+
+ s->sample_spec = data->sample_spec;
+ s->channel_map = data->channel_map;
+ s->default_sample_rate = s->sample_spec.rate;
+
+ if (data->alternate_sample_rate_is_set)
+ s->alternate_sample_rate = data->alternate_sample_rate;
+ else
+ s->alternate_sample_rate = s->core->alternate_sample_rate;
+
+ if (data->avoid_resampling_is_set)
+ s->avoid_resampling = data->avoid_resampling;
+ else
+ s->avoid_resampling = s->core->avoid_resampling;
+
+ s->outputs = pa_idxset_new(NULL, NULL);
+ s->n_corked = 0;
+ s->monitor_of = NULL;
+ s->output_from_master = NULL;
+
+ s->reference_volume = s->real_volume = data->volume;
+ pa_cvolume_reset(&s->soft_volume, s->sample_spec.channels);
+ s->base_volume = PA_VOLUME_NORM;
+ s->n_volume_steps = PA_VOLUME_NORM+1;
+ s->muted = data->muted;
+ s->refresh_volume = s->refresh_muted = false;
+
+ reset_callbacks(s);
+ s->userdata = NULL;
+
+ s->asyncmsgq = NULL;
+
+ /* As a minor optimization we just steal the list instead of
+ * copying it here */
+ s->ports = data->ports;
+ data->ports = NULL;
+
+ s->active_port = NULL;
+ s->save_port = false;
+
+ if (data->active_port)
+ if ((s->active_port = pa_hashmap_get(s->ports, data->active_port)))
+ s->save_port = data->save_port;
+
+ /* Hopefully the active port has already been assigned in the previous call
+ to pa_device_port_find_best, but better safe than sorry */
+ if (!s->active_port)
+ s->active_port = pa_device_port_find_best(s->ports);
+
+ if (s->active_port)
+ s->port_latency_offset = s->active_port->latency_offset;
+ else
+ s->port_latency_offset = 0;
+
+ s->save_volume = data->save_volume;
+ s->save_muted = data->save_muted;
+
+ pa_silence_memchunk_get(
+ &core->silence_cache,
+ core->mempool,
+ &s->silence,
+ &s->sample_spec,
+ 0);
+
+ s->thread_info.rtpoll = NULL;
+ s->thread_info.outputs = pa_hashmap_new_full(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func, NULL,
+ (pa_free_cb_t) pa_source_output_unref);
+ s->thread_info.soft_volume = s->soft_volume;
+ s->thread_info.soft_muted = s->muted;
+ s->thread_info.state = s->state;
+ s->thread_info.max_rewind = 0;
+ s->thread_info.requested_latency_valid = false;
+ s->thread_info.requested_latency = 0;
+ s->thread_info.min_latency = ABSOLUTE_MIN_LATENCY;
+ s->thread_info.max_latency = ABSOLUTE_MAX_LATENCY;
+ s->thread_info.fixed_latency = flags & PA_SOURCE_DYNAMIC_LATENCY ? 0 : DEFAULT_FIXED_LATENCY;
+
+ PA_LLIST_HEAD_INIT(pa_source_volume_change, s->thread_info.volume_changes);
+ s->thread_info.volume_changes_tail = NULL;
+ pa_sw_cvolume_divide(&s->thread_info.current_hw_volume, &s->real_volume, &s->soft_volume);
+ s->thread_info.volume_change_safety_margin = core->deferred_volume_safety_margin_usec;
+ s->thread_info.volume_change_extra_delay = core->deferred_volume_extra_delay_usec;
+ s->thread_info.port_latency_offset = s->port_latency_offset;
+
+ /* FIXME: This should probably be moved to pa_source_put() */
+ pa_assert_se(pa_idxset_put(core->sources, s, &s->index) >= 0);
+
+ if (s->card)
+ pa_assert_se(pa_idxset_put(s->card->sources, s, NULL) >= 0);
+
+ pt = pa_proplist_to_string_sep(s->proplist, "\n ");
+ pa_log_info("Created source %u \"%s\" with sample spec %s and channel map %s\n %s",
+ s->index,
+ s->name,
+ pa_sample_spec_snprint(st, sizeof(st), &s->sample_spec),
+ pa_channel_map_snprint(cm, sizeof(cm), &s->channel_map),
+ pt);
+ pa_xfree(pt);
+
+ return s;
+}
+
+/* Called from main context */
+static int source_set_state(pa_source *s, pa_source_state_t state, pa_suspend_cause_t suspend_cause) {
+ int ret = 0;
+ bool state_changed;
+ bool suspend_cause_changed;
+ bool suspending;
+ bool resuming;
+ pa_source_state_t old_state;
+ pa_suspend_cause_t old_suspend_cause;
+
+ pa_assert(s);
+ pa_assert_ctl_context();
+
+ state_changed = state != s->state;
+ suspend_cause_changed = suspend_cause != s->suspend_cause;
+
+ if (!state_changed && !suspend_cause_changed)
+ return 0;
+
+ suspending = PA_SOURCE_IS_OPENED(s->state) && state == PA_SOURCE_SUSPENDED;
+ resuming = s->state == PA_SOURCE_SUSPENDED && PA_SOURCE_IS_OPENED(state);
+
+ /* If we are resuming, suspend_cause must be 0. */
+ pa_assert(!resuming || !suspend_cause);
+
+ /* Here's something to think about: what to do with the suspend cause if
+ * resuming the source fails? The old suspend cause will be incorrect, so we
+ * can't use that. On the other hand, if we set no suspend cause (as is the
+ * case currently), then it looks strange to have a source suspended without
+ * any cause. It might be a good idea to add a new "resume failed" suspend
+ * cause, or it might just add unnecessary complexity, given that the
+ * current approach of not setting any suspend cause works well enough. */
+
+ if (s->set_state_in_main_thread) {
+ if ((ret = s->set_state_in_main_thread(s, state, suspend_cause)) < 0) {
+ /* set_state_in_main_thread() is allowed to fail only when resuming. */
+ pa_assert(resuming);
+
+ /* If resuming fails, we set the state to SUSPENDED and
+ * suspend_cause to 0. */
+ state = PA_SOURCE_SUSPENDED;
+ suspend_cause = 0;
+ state_changed = false;
+ suspend_cause_changed = suspend_cause != s->suspend_cause;
+ resuming = false;
+
+ /* We know the state isn't changing. If the suspend cause isn't
+ * changing either, then there's nothing more to do. */
+ if (!suspend_cause_changed)
+ return ret;
+ }
+ }
+
+ if (s->asyncmsgq) {
+ struct set_state_data data = { .state = state, .suspend_cause = suspend_cause };
+
+ if ((ret = pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_STATE, &data, 0, NULL)) < 0) {
+ /* SET_STATE is allowed to fail only when resuming. */
+ pa_assert(resuming);
+
+ if (s->set_state_in_main_thread)
+ s->set_state_in_main_thread(s, PA_SOURCE_SUSPENDED, 0);
+
+ /* If resuming fails, we set the state to SUSPENDED and
+ * suspend_cause to 0. */
+ state = PA_SOURCE_SUSPENDED;
+ suspend_cause = 0;
+ state_changed = false;
+ suspend_cause_changed = suspend_cause != s->suspend_cause;
+ resuming = false;
+
+ /* We know the state isn't changing. If the suspend cause isn't
+ * changing either, then there's nothing more to do. */
+ if (!suspend_cause_changed)
+ return ret;
+ }
+ }
+
+ old_suspend_cause = s->suspend_cause;
+ if (suspend_cause_changed) {
+ char old_cause_buf[PA_SUSPEND_CAUSE_TO_STRING_BUF_SIZE];
+ char new_cause_buf[PA_SUSPEND_CAUSE_TO_STRING_BUF_SIZE];
+
+ pa_log_debug("%s: suspend_cause: %s -> %s", s->name, pa_suspend_cause_to_string(s->suspend_cause, old_cause_buf),
+ pa_suspend_cause_to_string(suspend_cause, new_cause_buf));
+ s->suspend_cause = suspend_cause;
+ }
+
+ old_state = s->state;
+ if (state_changed) {
+ pa_log_debug("%s: state: %s -> %s", s->name, pa_source_state_to_string(s->state), pa_source_state_to_string(state));
+ s->state = state;
+
+ /* If we enter UNLINKED state, then we don't send change notifications.
+ * pa_source_unlink() will send unlink notifications instead. */
+ if (state != PA_SOURCE_UNLINKED) {
+ pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_STATE_CHANGED], s);
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE | PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+ }
+ }
+
+ if (suspending || resuming || suspend_cause_changed) {
+ pa_source_output *o;
+ uint32_t idx;
+
+ /* We're suspending or resuming, tell everyone about it */
+
+ PA_IDXSET_FOREACH(o, s->outputs, idx)
+ if (s->state == PA_SOURCE_SUSPENDED &&
+ (o->flags & PA_SOURCE_OUTPUT_KILL_ON_SUSPEND))
+ pa_source_output_kill(o);
+ else if (o->suspend)
+ o->suspend(o, old_state, old_suspend_cause);
+ }
+
+ return ret;
+}
+
+void pa_source_set_get_volume_callback(pa_source *s, pa_source_cb_t cb) {
+ pa_assert(s);
+
+ s->get_volume = cb;
+}
+
+void pa_source_set_set_volume_callback(pa_source *s, pa_source_cb_t cb) {
+ pa_source_flags_t flags;
+
+ pa_assert(s);
+ pa_assert(!s->write_volume || cb);
+
+ s->set_volume = cb;
+
+ /* Save the current flags so we can tell if they've changed */
+ flags = s->flags;
+
+ if (cb) {
+ /* The source implementor is responsible for setting decibel volume support */
+ s->flags |= PA_SOURCE_HW_VOLUME_CTRL;
+ } else {
+ s->flags &= ~PA_SOURCE_HW_VOLUME_CTRL;
+ /* See note below in pa_source_put() about volume sharing and decibel volumes */
+ pa_source_enable_decibel_volume(s, !(s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER));
+ }
+
+ /* If the flags have changed after init, let any clients know via a change event */
+ if (s->state != PA_SOURCE_INIT && flags != s->flags)
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+}
+
+void pa_source_set_write_volume_callback(pa_source *s, pa_source_cb_t cb) {
+ pa_source_flags_t flags;
+
+ pa_assert(s);
+ pa_assert(!cb || s->set_volume);
+
+ s->write_volume = cb;
+
+ /* Save the current flags so we can tell if they've changed */
+ flags = s->flags;
+
+ if (cb)
+ s->flags |= PA_SOURCE_DEFERRED_VOLUME;
+ else
+ s->flags &= ~PA_SOURCE_DEFERRED_VOLUME;
+
+ /* If the flags have changed after init, let any clients know via a change event */
+ if (s->state != PA_SOURCE_INIT && flags != s->flags)
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+}
+
+void pa_source_set_get_mute_callback(pa_source *s, pa_source_get_mute_cb_t cb) {
+ pa_assert(s);
+
+ s->get_mute = cb;
+}
+
+void pa_source_set_set_mute_callback(pa_source *s, pa_source_cb_t cb) {
+ pa_source_flags_t flags;
+
+ pa_assert(s);
+
+ s->set_mute = cb;
+
+ /* Save the current flags so we can tell if they've changed */
+ flags = s->flags;
+
+ if (cb)
+ s->flags |= PA_SOURCE_HW_MUTE_CTRL;
+ else
+ s->flags &= ~PA_SOURCE_HW_MUTE_CTRL;
+
+ /* If the flags have changed after init, let any clients know via a change event */
+ if (s->state != PA_SOURCE_INIT && flags != s->flags)
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+}
+
+static void enable_flat_volume(pa_source *s, bool enable) {
+ pa_source_flags_t flags;
+
+ pa_assert(s);
+
+ /* Always follow the overall user preference here */
+ enable = enable && s->core->flat_volumes;
+
+ /* Save the current flags so we can tell if they've changed */
+ flags = s->flags;
+
+ if (enable)
+ s->flags |= PA_SOURCE_FLAT_VOLUME;
+ else
+ s->flags &= ~PA_SOURCE_FLAT_VOLUME;
+
+ /* If the flags have changed after init, let any clients know via a change event */
+ if (s->state != PA_SOURCE_INIT && flags != s->flags)
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+}
+
+void pa_source_enable_decibel_volume(pa_source *s, bool enable) {
+ pa_source_flags_t flags;
+
+ pa_assert(s);
+
+ /* Save the current flags so we can tell if they've changed */
+ flags = s->flags;
+
+ if (enable) {
+ s->flags |= PA_SOURCE_DECIBEL_VOLUME;
+ enable_flat_volume(s, true);
+ } else {
+ s->flags &= ~PA_SOURCE_DECIBEL_VOLUME;
+ enable_flat_volume(s, false);
+ }
+
+ /* If the flags have changed after init, let any clients know via a change event */
+ if (s->state != PA_SOURCE_INIT && flags != s->flags)
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+}
+
+/* Called from main context */
+void pa_source_put(pa_source *s) {
+ pa_source_assert_ref(s);
+ pa_assert_ctl_context();
+
+ pa_assert(s->state == PA_SOURCE_INIT);
+ pa_assert(!(s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER) || pa_source_is_filter(s));
+
+ /* The following fields must be initialized properly when calling _put() */
+ pa_assert(s->asyncmsgq);
+ pa_assert(s->thread_info.min_latency <= s->thread_info.max_latency);
+
+ /* Generally, flags should be initialized via pa_source_new(). As a
+ * special exception we allow some volume related flags to be set
+ * between _new() and _put() by the callback setter functions above.
+ *
+ * Thus we implement a couple safeguards here which ensure the above
+ * setters were used (or at least the implementor made manual changes
+ * in a compatible way).
+ *
+ * Note: All of these flags set here can change over the life time
+ * of the source. */
+ pa_assert(!(s->flags & PA_SOURCE_HW_VOLUME_CTRL) || s->set_volume);
+ pa_assert(!(s->flags & PA_SOURCE_DEFERRED_VOLUME) || s->write_volume);
+ pa_assert(!(s->flags & PA_SOURCE_HW_MUTE_CTRL) || s->set_mute);
+
+ /* XXX: Currently decibel volume is disabled for all sources that use volume
+ * sharing. When the master source supports decibel volume, it would be good
+ * to have the flag also in the filter source, but currently we don't do that
+ * so that the flags of the filter source never change when it's moved from
+ * a master source to another. One solution for this problem would be to
+ * remove user-visible volume altogether from filter sources when volume
+ * sharing is used, but the current approach was easier to implement... */
+ /* We always support decibel volumes in software, otherwise we leave it to
+ * the source implementor to set this flag as needed.
+ *
+ * Note: This flag can also change over the life time of the source. */
+ if (!(s->flags & PA_SOURCE_HW_VOLUME_CTRL) && !(s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)) {
+ pa_source_enable_decibel_volume(s, true);
+ s->soft_volume = s->reference_volume;
+ }
+
+ /* If the source implementor support DB volumes by itself, we should always
+ * try and enable flat volumes too */
+ if ((s->flags & PA_SOURCE_DECIBEL_VOLUME))
+ enable_flat_volume(s, true);
+
+ if (s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER) {
+ pa_source *root_source = pa_source_get_master(s);
+
+ pa_assert(PA_LIKELY(root_source));
+
+ s->reference_volume = root_source->reference_volume;
+ pa_cvolume_remap(&s->reference_volume, &root_source->channel_map, &s->channel_map);
+
+ s->real_volume = root_source->real_volume;
+ pa_cvolume_remap(&s->real_volume, &root_source->channel_map, &s->channel_map);
+ } else
+ /* We assume that if the sink implementor changed the default
+ * volume they did so in real_volume, because that is the usual
+ * place where they are supposed to place their changes. */
+ s->reference_volume = s->real_volume;
+
+ s->thread_info.soft_volume = s->soft_volume;
+ s->thread_info.soft_muted = s->muted;
+ pa_sw_cvolume_divide(&s->thread_info.current_hw_volume, &s->real_volume, &s->soft_volume);
+
+ pa_assert((s->flags & PA_SOURCE_HW_VOLUME_CTRL)
+ || (s->base_volume == PA_VOLUME_NORM
+ && ((s->flags & PA_SOURCE_DECIBEL_VOLUME || (s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)))));
+ pa_assert(!(s->flags & PA_SOURCE_DECIBEL_VOLUME) || s->n_volume_steps == PA_VOLUME_NORM+1);
+ pa_assert(!(s->flags & PA_SOURCE_DYNAMIC_LATENCY) == !(s->thread_info.fixed_latency == 0));
+
+ if (s->suspend_cause)
+ pa_assert_se(source_set_state(s, PA_SOURCE_SUSPENDED, s->suspend_cause) == 0);
+ else
+ pa_assert_se(source_set_state(s, PA_SOURCE_IDLE, 0) == 0);
+
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE | PA_SUBSCRIPTION_EVENT_NEW, s->index);
+ pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_PUT], s);
+
+ /* It's good to fire the SOURCE_PUT hook before updating the default source,
+ * because module-switch-on-connect will set the new source as the default
+ * source, and if we were to call pa_core_update_default_source() before that,
+ * the default source might change twice, causing unnecessary stream moving. */
+ pa_core_update_default_source(s->core);
+
+ pa_core_move_streams_to_newly_available_preferred_source(s->core, s);
+}
+
+/* Called from main context */
+void pa_source_unlink(pa_source *s) {
+ bool linked;
+ pa_source_output *o, PA_UNUSED *j = NULL;
+
+ pa_source_assert_ref(s);
+ pa_assert_ctl_context();
+
+ /* See pa_sink_unlink() for a couple of comments how this function
+ * works. */
+
+ if (s->unlink_requested)
+ return;
+
+ s->unlink_requested = true;
+
+ linked = PA_SOURCE_IS_LINKED(s->state);
+
+ if (linked)
+ pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], s);
+
+ if (s->state != PA_SOURCE_UNLINKED)
+ pa_namereg_unregister(s->core, s->name);
+ pa_idxset_remove_by_data(s->core->sources, s, NULL);
+
+ pa_core_update_default_source(s->core);
+
+ if (linked && s->core->rescue_streams)
+ pa_source_move_streams_to_default_source(s->core, s, false);
+
+ if (s->card)
+ pa_idxset_remove_by_data(s->card->sources, s, NULL);
+
+ while ((o = pa_idxset_first(s->outputs, NULL))) {
+ pa_assert(o != j);
+ pa_source_output_kill(o);
+ j = o;
+ }
+
+ if (linked)
+ /* It's important to keep the suspend cause unchanged when unlinking,
+ * because if we remove the SESSION suspend cause here, the alsa
+ * source will sync its volume with the hardware while another user is
+ * active, messing up the volume for that other user. */
+ source_set_state(s, PA_SOURCE_UNLINKED, s->suspend_cause);
+ else
+ s->state = PA_SOURCE_UNLINKED;
+
+ reset_callbacks(s);
+
+ if (linked) {
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE | PA_SUBSCRIPTION_EVENT_REMOVE, s->index);
+ pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK_POST], s);
+ }
+}
+
+/* Called from main context */
+static void source_free(pa_object *o) {
+ pa_source *s = PA_SOURCE(o);
+
+ pa_assert(s);
+ pa_assert_ctl_context();
+ pa_assert(pa_source_refcnt(s) == 0);
+ pa_assert(!PA_SOURCE_IS_LINKED(s->state));
+
+ pa_log_info("Freeing source %u \"%s\"", s->index, s->name);
+
+ pa_source_volume_change_flush(s);
+
+ pa_idxset_free(s->outputs, NULL);
+ pa_hashmap_free(s->thread_info.outputs);
+
+ if (s->silence.memblock)
+ pa_memblock_unref(s->silence.memblock);
+
+ pa_xfree(s->name);
+ pa_xfree(s->driver);
+
+ if (s->proplist)
+ pa_proplist_free(s->proplist);
+
+ if (s->ports)
+ pa_hashmap_free(s->ports);
+
+ pa_xfree(s);
+}
+
+/* Called from main context, and not while the IO thread is active, please */
+void pa_source_set_asyncmsgq(pa_source *s, pa_asyncmsgq *q) {
+ pa_source_assert_ref(s);
+ pa_assert_ctl_context();
+
+ s->asyncmsgq = q;
+}
+
+/* Called from main context, and not while the IO thread is active, please */
+void pa_source_update_flags(pa_source *s, pa_source_flags_t mask, pa_source_flags_t value) {
+ pa_source_flags_t old_flags;
+ pa_source_output *output;
+ uint32_t idx;
+
+ pa_source_assert_ref(s);
+ pa_assert_ctl_context();
+
+ /* For now, allow only a minimal set of flags to be changed. */
+ pa_assert((mask & ~(PA_SOURCE_DYNAMIC_LATENCY|PA_SOURCE_LATENCY)) == 0);
+
+ old_flags = s->flags;
+ s->flags = (s->flags & ~mask) | (value & mask);
+
+ if (s->flags == old_flags)
+ return;
+
+ if ((s->flags & PA_SOURCE_LATENCY) != (old_flags & PA_SOURCE_LATENCY))
+ pa_log_debug("Source %s: LATENCY flag %s.", s->name, (s->flags & PA_SOURCE_LATENCY) ? "enabled" : "disabled");
+
+ if ((s->flags & PA_SOURCE_DYNAMIC_LATENCY) != (old_flags & PA_SOURCE_DYNAMIC_LATENCY))
+ pa_log_debug("Source %s: DYNAMIC_LATENCY flag %s.",
+ s->name, (s->flags & PA_SOURCE_DYNAMIC_LATENCY) ? "enabled" : "disabled");
+
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE | PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+ pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_FLAGS_CHANGED], s);
+
+ PA_IDXSET_FOREACH(output, s->outputs, idx) {
+ if (output->destination_source)
+ pa_source_update_flags(output->destination_source, mask, value);
+ }
+}
+
+/* Called from IO context, or before _put() from main context */
+void pa_source_set_rtpoll(pa_source *s, pa_rtpoll *p) {
+ pa_source_assert_ref(s);
+ pa_source_assert_io_context(s);
+
+ s->thread_info.rtpoll = p;
+}
+
+/* Called from main context */
+int pa_source_update_status(pa_source*s) {
+ pa_source_assert_ref(s);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_IS_LINKED(s->state));
+
+ if (s->state == PA_SOURCE_SUSPENDED)
+ return 0;
+
+ return source_set_state(s, pa_source_used_by(s) ? PA_SOURCE_RUNNING : PA_SOURCE_IDLE, 0);
+}
+
+/* Called from main context */
+int pa_source_suspend(pa_source *s, bool suspend, pa_suspend_cause_t cause) {
+ pa_suspend_cause_t merged_cause;
+
+ pa_source_assert_ref(s);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_IS_LINKED(s->state));
+ pa_assert(cause != 0);
+
+ if (s->monitor_of && cause != PA_SUSPEND_PASSTHROUGH)
+ return -PA_ERR_NOTSUPPORTED;
+
+ if (suspend)
+ merged_cause = s->suspend_cause | cause;
+ else
+ merged_cause = s->suspend_cause & ~cause;
+
+ if (merged_cause)
+ return source_set_state(s, PA_SOURCE_SUSPENDED, merged_cause);
+ else
+ return source_set_state(s, pa_source_used_by(s) ? PA_SOURCE_RUNNING : PA_SOURCE_IDLE, 0);
+}
+
+/* Called from main context */
+int pa_source_sync_suspend(pa_source *s) {
+ pa_sink_state_t state;
+ pa_suspend_cause_t suspend_cause;
+
+ pa_source_assert_ref(s);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_IS_LINKED(s->state));
+ pa_assert(s->monitor_of);
+
+ state = s->monitor_of->state;
+ suspend_cause = s->monitor_of->suspend_cause;
+
+ /* The monitor source usually has the same state and suspend cause as the
+ * sink, the only exception is when the monitor source is suspended due to
+ * the sink being in the passthrough mode. If the monitor currently has the
+ * PASSTHROUGH suspend cause, then we have to keep the monitor suspended
+ * even if the sink is running. */
+ if (s->suspend_cause & PA_SUSPEND_PASSTHROUGH)
+ suspend_cause |= PA_SUSPEND_PASSTHROUGH;
+
+ if (state == PA_SINK_SUSPENDED || suspend_cause)
+ return source_set_state(s, PA_SOURCE_SUSPENDED, suspend_cause);
+
+ pa_assert(PA_SINK_IS_OPENED(state));
+
+ return source_set_state(s, pa_source_used_by(s) ? PA_SOURCE_RUNNING : PA_SOURCE_IDLE, 0);
+}
+
+/* Called from main context */
+pa_queue *pa_source_move_all_start(pa_source *s, pa_queue *q) {
+ pa_source_output *o, *n;
+ uint32_t idx;
+
+ pa_source_assert_ref(s);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_IS_LINKED(s->state));
+
+ if (!q)
+ q = pa_queue_new();
+
+ for (o = PA_SOURCE_OUTPUT(pa_idxset_first(s->outputs, &idx)); o; o = n) {
+ n = PA_SOURCE_OUTPUT(pa_idxset_next(s->outputs, &idx));
+
+ pa_source_output_ref(o);
+
+ if (pa_source_output_start_move(o) >= 0)
+ pa_queue_push(q, o);
+ else
+ pa_source_output_unref(o);
+ }
+
+ return q;
+}
+
+/* Called from main context */
+void pa_source_move_all_finish(pa_source *s, pa_queue *q, bool save) {
+ pa_source_output *o;
+
+ pa_source_assert_ref(s);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_IS_LINKED(s->state));
+ pa_assert(q);
+
+ while ((o = PA_SOURCE_OUTPUT(pa_queue_pop(q)))) {
+ if (PA_SOURCE_OUTPUT_IS_LINKED(o->state)) {
+ if (pa_source_output_finish_move(o, s, save) < 0)
+ pa_source_output_fail_move(o);
+
+ }
+ pa_source_output_unref(o);
+ }
+
+ pa_queue_free(q, NULL);
+}
+
+/* Called from main context */
+void pa_source_move_all_fail(pa_queue *q) {
+ pa_source_output *o;
+
+ pa_assert_ctl_context();
+ pa_assert(q);
+
+ while ((o = PA_SOURCE_OUTPUT(pa_queue_pop(q)))) {
+ pa_source_output_fail_move(o);
+ pa_source_output_unref(o);
+ }
+
+ pa_queue_free(q, NULL);
+}
+
+/* Called from IO thread context */
+void pa_source_process_rewind(pa_source *s, size_t nbytes) {
+ pa_source_output *o;
+ void *state = NULL;
+
+ pa_source_assert_ref(s);
+ pa_source_assert_io_context(s);
+ pa_assert(PA_SOURCE_IS_LINKED(s->thread_info.state));
+
+ if (nbytes <= 0)
+ return;
+
+ if (s->thread_info.state == PA_SOURCE_SUSPENDED)
+ return;
+
+ pa_log_debug("Processing rewind...");
+
+ PA_HASHMAP_FOREACH(o, s->thread_info.outputs, state) {
+ pa_source_output_assert_ref(o);
+ pa_source_output_process_rewind(o, nbytes);
+ }
+}
+
+/* Called from IO thread context */
+void pa_source_post(pa_source*s, const pa_memchunk *chunk) {
+ pa_source_output *o;
+ void *state = NULL;
+
+ pa_source_assert_ref(s);
+ pa_source_assert_io_context(s);
+ pa_assert(PA_SOURCE_IS_LINKED(s->thread_info.state));
+ pa_assert(chunk);
+
+ if (s->thread_info.state == PA_SOURCE_SUSPENDED)
+ return;
+
+ if (s->thread_info.soft_muted || !pa_cvolume_is_norm(&s->thread_info.soft_volume)) {
+ pa_memchunk vchunk = *chunk;
+
+ pa_memblock_ref(vchunk.memblock);
+ pa_memchunk_make_writable(&vchunk, 0);
+
+ if (s->thread_info.soft_muted || pa_cvolume_is_muted(&s->thread_info.soft_volume))
+ pa_silence_memchunk(&vchunk, &s->sample_spec);
+ else
+ pa_volume_memchunk(&vchunk, &s->sample_spec, &s->thread_info.soft_volume);
+
+ while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL))) {
+ pa_source_output_assert_ref(o);
+
+ if (!o->thread_info.direct_on_input)
+ pa_source_output_push(o, &vchunk);
+ }
+
+ pa_memblock_unref(vchunk.memblock);
+ } else {
+
+ while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL))) {
+ pa_source_output_assert_ref(o);
+
+ if (!o->thread_info.direct_on_input)
+ pa_source_output_push(o, chunk);
+ }
+ }
+}
+
+/* Called from IO thread context */
+void pa_source_post_direct(pa_source*s, pa_source_output *o, const pa_memchunk *chunk) {
+ pa_source_assert_ref(s);
+ pa_source_assert_io_context(s);
+ pa_assert(PA_SOURCE_IS_LINKED(s->thread_info.state));
+ pa_source_output_assert_ref(o);
+ pa_assert(o->thread_info.direct_on_input);
+ pa_assert(chunk);
+
+ if (s->thread_info.state == PA_SOURCE_SUSPENDED)
+ return;
+
+ if (s->thread_info.soft_muted || !pa_cvolume_is_norm(&s->thread_info.soft_volume)) {
+ pa_memchunk vchunk = *chunk;
+
+ pa_memblock_ref(vchunk.memblock);
+ pa_memchunk_make_writable(&vchunk, 0);
+
+ if (s->thread_info.soft_muted || pa_cvolume_is_muted(&s->thread_info.soft_volume))
+ pa_silence_memchunk(&vchunk, &s->sample_spec);
+ else
+ pa_volume_memchunk(&vchunk, &s->sample_spec, &s->thread_info.soft_volume);
+
+ pa_source_output_push(o, &vchunk);
+
+ pa_memblock_unref(vchunk.memblock);
+ } else
+ pa_source_output_push(o, chunk);
+}
+
+/* Called from main thread */
+void pa_source_reconfigure(pa_source *s, pa_sample_spec *spec, bool passthrough) {
+ uint32_t idx;
+ pa_source_output *o;
+ pa_sample_spec desired_spec;
+ uint32_t default_rate = s->default_sample_rate;
+ uint32_t alternate_rate = s->alternate_sample_rate;
+ bool default_rate_is_usable = false;
+ bool alternate_rate_is_usable = false;
+ bool avoid_resampling = s->avoid_resampling;
+
+ if (pa_sample_spec_equal(spec, &s->sample_spec))
+ return;
+
+ if (!s->reconfigure && !s->monitor_of)
+ return;
+
+ if (PA_UNLIKELY(default_rate == alternate_rate && !passthrough && !avoid_resampling)) {
+ pa_log_debug("Default and alternate sample rates are the same, so there is no point in switching.");
+ return;
+ }
+
+ if (PA_SOURCE_IS_RUNNING(s->state)) {
+ pa_log_info("Cannot update sample spec, SOURCE_IS_RUNNING, will keep using %s and %u Hz",
+ pa_sample_format_to_string(s->sample_spec.format), s->sample_spec.rate);
+ return;
+ }
+
+ if (s->monitor_of) {
+ if (PA_SINK_IS_RUNNING(s->monitor_of->state)) {
+ pa_log_info("Cannot update sample spec, this is a monitor source and the sink is running.");
+ return;
+ }
+ }
+
+ if (PA_UNLIKELY(!pa_sample_spec_valid(spec)))
+ return;
+
+ desired_spec = s->sample_spec;
+
+ if (passthrough) {
+ /* We have to try to use the source output format and rate */
+ desired_spec.format = spec->format;
+ desired_spec.rate = spec->rate;
+
+ } else if (avoid_resampling) {
+ /* We just try to set the source output's sample rate if it's not too low */
+ if (spec->rate >= default_rate || spec->rate >= alternate_rate)
+ desired_spec.rate = spec->rate;
+ desired_spec.format = spec->format;
+
+ } else if (default_rate == spec->rate || alternate_rate == spec->rate) {
+ /* We can directly try to use this rate */
+ desired_spec.rate = spec->rate;
+
+ }
+
+ if (desired_spec.rate != spec->rate) {
+ /* See if we can pick a rate that results in less resampling effort */
+ if (default_rate % 11025 == 0 && spec->rate % 11025 == 0)
+ default_rate_is_usable = true;
+ if (default_rate % 4000 == 0 && spec->rate % 4000 == 0)
+ default_rate_is_usable = true;
+ if (alternate_rate % 11025 == 0 && spec->rate % 11025 == 0)
+ alternate_rate_is_usable = true;
+ if (alternate_rate % 4000 == 0 && spec->rate % 4000 == 0)
+ alternate_rate_is_usable = true;
+
+ if (alternate_rate_is_usable && !default_rate_is_usable)
+ desired_spec.rate = alternate_rate;
+ else
+ desired_spec.rate = default_rate;
+ }
+
+ if (pa_sample_spec_equal(&desired_spec, &s->sample_spec) && passthrough == pa_source_is_passthrough(s))
+ return;
+
+ if (!passthrough && pa_source_used_by(s) > 0)
+ return;
+
+ pa_log_debug("Suspending source %s due to changing format, desired format = %s rate = %u",
+ s->name, pa_sample_format_to_string(desired_spec.format), desired_spec.rate);
+ pa_source_suspend(s, true, PA_SUSPEND_INTERNAL);
+
+ if (s->reconfigure)
+ s->reconfigure(s, &desired_spec, passthrough);
+ else {
+ /* This is a monitor source. */
+
+ /* XXX: This code is written with non-passthrough streams in mind. I
+ * have no idea whether the behaviour with passthrough streams is
+ * sensible. */
+ if (!passthrough) {
+ s->sample_spec = desired_spec;
+ pa_sink_reconfigure(s->monitor_of, &desired_spec, false);
+ s->sample_spec = s->monitor_of->sample_spec;
+ } else
+ goto unsuspend;
+ }
+
+ PA_IDXSET_FOREACH(o, s->outputs, idx) {
+ if (o->state == PA_SOURCE_OUTPUT_CORKED)
+ pa_source_output_update_resampler(o);
+ }
+
+ pa_log_info("Reconfigured successfully");
+
+unsuspend:
+ pa_source_suspend(s, false, PA_SUSPEND_INTERNAL);
+}
+
+/* Called from main thread */
+pa_usec_t pa_source_get_latency(pa_source *s) {
+ int64_t usec;
+
+ pa_source_assert_ref(s);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_IS_LINKED(s->state));
+
+ if (s->state == PA_SOURCE_SUSPENDED)
+ return 0;
+
+ if (!(s->flags & PA_SOURCE_LATENCY))
+ return 0;
+
+ pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_GET_LATENCY, &usec, 0, NULL) == 0);
+
+ /* The return value is unsigned, so check that the offset can be added to usec without
+ * underflowing. */
+ if (-s->port_latency_offset <= usec)
+ usec += s->port_latency_offset;
+ else
+ usec = 0;
+
+ return (pa_usec_t)usec;
+}
+
+/* Called from IO thread */
+int64_t pa_source_get_latency_within_thread(pa_source *s, bool allow_negative) {
+ int64_t usec = 0;
+ pa_msgobject *o;
+
+ pa_source_assert_ref(s);
+ pa_source_assert_io_context(s);
+ pa_assert(PA_SOURCE_IS_LINKED(s->thread_info.state));
+
+ /* The returned value is supposed to be in the time domain of the sound card! */
+
+ if (s->thread_info.state == PA_SOURCE_SUSPENDED)
+ return 0;
+
+ if (!(s->flags & PA_SOURCE_LATENCY))
+ return 0;
+
+ o = PA_MSGOBJECT(s);
+
+ /* FIXME: We probably should make this a proper vtable callback instead of going through process_msg() */
+
+ o->process_msg(o, PA_SOURCE_MESSAGE_GET_LATENCY, &usec, 0, NULL);
+
+ /* If allow_negative is false, the call should only return positive values, */
+ usec += s->thread_info.port_latency_offset;
+ if (!allow_negative && usec < 0)
+ usec = 0;
+
+ return usec;
+}
+
+/* Called from the main thread (and also from the IO thread while the main
+ * thread is waiting).
+ *
+ * When a source uses volume sharing, it never has the PA_SOURCE_FLAT_VOLUME flag
+ * set. Instead, flat volume mode is detected by checking whether the root source
+ * has the flag set. */
+bool pa_source_flat_volume_enabled(pa_source *s) {
+ pa_source_assert_ref(s);
+
+ s = pa_source_get_master(s);
+
+ if (PA_LIKELY(s))
+ return (s->flags & PA_SOURCE_FLAT_VOLUME);
+ else
+ return false;
+}
+
+/* Called from the main thread (and also from the IO thread while the main
+ * thread is waiting). */
+pa_source *pa_source_get_master(pa_source *s) {
+ pa_source_assert_ref(s);
+
+ while (s && (s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)) {
+ if (PA_UNLIKELY(!s->output_from_master))
+ return NULL;
+
+ s = s->output_from_master->source;
+ }
+
+ return s;
+}
+
+/* Called from main context */
+bool pa_source_is_filter(pa_source *s) {
+ pa_source_assert_ref(s);
+
+ return (s->output_from_master != NULL);
+}
+
+/* Called from main context */
+bool pa_source_is_passthrough(pa_source *s) {
+
+ pa_source_assert_ref(s);
+
+ /* NB Currently only monitor sources support passthrough mode */
+ return (s->monitor_of && pa_sink_is_passthrough(s->monitor_of));
+}
+
+/* Called from main context */
+void pa_source_enter_passthrough(pa_source *s) {
+ pa_cvolume volume;
+
+ /* set the volume to NORM */
+ s->saved_volume = *pa_source_get_volume(s, true);
+ s->saved_save_volume = s->save_volume;
+
+ pa_cvolume_set(&volume, s->sample_spec.channels, PA_MIN(s->base_volume, PA_VOLUME_NORM));
+ pa_source_set_volume(s, &volume, true, false);
+}
+
+/* Called from main context */
+void pa_source_leave_passthrough(pa_source *s) {
+ /* Restore source volume to what it was before we entered passthrough mode */
+ pa_source_set_volume(s, &s->saved_volume, true, s->saved_save_volume);
+
+ pa_cvolume_init(&s->saved_volume);
+ s->saved_save_volume = false;
+}
+
+/* Called from main context. */
+static void compute_reference_ratio(pa_source_output *o) {
+ unsigned c = 0;
+ pa_cvolume remapped;
+ pa_cvolume ratio;
+
+ pa_assert(o);
+ pa_assert(pa_source_flat_volume_enabled(o->source));
+
+ /*
+ * Calculates the reference ratio from the source's reference
+ * volume. This basically calculates:
+ *
+ * o->reference_ratio = o->volume / o->source->reference_volume
+ */
+
+ remapped = o->source->reference_volume;
+ pa_cvolume_remap(&remapped, &o->source->channel_map, &o->channel_map);
+
+ ratio = o->reference_ratio;
+
+ for (c = 0; c < o->sample_spec.channels; c++) {
+
+ /* We don't update when the source volume is 0 anyway */
+ if (remapped.values[c] <= PA_VOLUME_MUTED)
+ continue;
+
+ /* Don't update the reference ratio unless necessary */
+ if (pa_sw_volume_multiply(
+ ratio.values[c],
+ remapped.values[c]) == o->volume.values[c])
+ continue;
+
+ ratio.values[c] = pa_sw_volume_divide(
+ o->volume.values[c],
+ remapped.values[c]);
+ }
+
+ pa_source_output_set_reference_ratio(o, &ratio);
+}
+
+/* Called from main context. Only called for the root source in volume sharing
+ * cases, except for internal recursive calls. */
+static void compute_reference_ratios(pa_source *s) {
+ uint32_t idx;
+ pa_source_output *o;
+
+ pa_source_assert_ref(s);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_IS_LINKED(s->state));
+ pa_assert(pa_source_flat_volume_enabled(s));
+
+ PA_IDXSET_FOREACH(o, s->outputs, idx) {
+ compute_reference_ratio(o);
+
+ if (o->destination_source && (o->destination_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)
+ && PA_SOURCE_IS_LINKED(o->destination_source->state))
+ compute_reference_ratios(o->destination_source);
+ }
+}
+
+/* Called from main context. Only called for the root source in volume sharing
+ * cases, except for internal recursive calls. */
+static void compute_real_ratios(pa_source *s) {
+ pa_source_output *o;
+ uint32_t idx;
+
+ pa_source_assert_ref(s);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_IS_LINKED(s->state));
+ pa_assert(pa_source_flat_volume_enabled(s));
+
+ PA_IDXSET_FOREACH(o, s->outputs, idx) {
+ unsigned c;
+ pa_cvolume remapped;
+
+ if (o->destination_source && (o->destination_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)) {
+ /* The origin source uses volume sharing, so this input's real ratio
+ * is handled as a special case - the real ratio must be 0 dB, and
+ * as a result i->soft_volume must equal i->volume_factor. */
+ pa_cvolume_reset(&o->real_ratio, o->real_ratio.channels);
+ o->soft_volume = o->volume_factor;
+
+ if (PA_SOURCE_IS_LINKED(o->destination_source->state))
+ compute_real_ratios(o->destination_source);
+
+ continue;
+ }
+
+ /*
+ * This basically calculates:
+ *
+ * i->real_ratio := i->volume / s->real_volume
+ * i->soft_volume := i->real_ratio * i->volume_factor
+ */
+
+ remapped = s->real_volume;
+ pa_cvolume_remap(&remapped, &s->channel_map, &o->channel_map);
+
+ o->real_ratio.channels = o->sample_spec.channels;
+ o->soft_volume.channels = o->sample_spec.channels;
+
+ for (c = 0; c < o->sample_spec.channels; c++) {
+
+ if (remapped.values[c] <= PA_VOLUME_MUTED) {
+ /* We leave o->real_ratio untouched */
+ o->soft_volume.values[c] = PA_VOLUME_MUTED;
+ continue;
+ }
+
+ /* Don't lose accuracy unless necessary */
+ if (pa_sw_volume_multiply(
+ o->real_ratio.values[c],
+ remapped.values[c]) != o->volume.values[c])
+
+ o->real_ratio.values[c] = pa_sw_volume_divide(
+ o->volume.values[c],
+ remapped.values[c]);
+
+ o->soft_volume.values[c] = pa_sw_volume_multiply(
+ o->real_ratio.values[c],
+ o->volume_factor.values[c]);
+ }
+
+ /* We don't copy the soft_volume to the thread_info data
+ * here. That must be done by the caller */
+ }
+}
+
+static pa_cvolume *cvolume_remap_minimal_impact(
+ pa_cvolume *v,
+ const pa_cvolume *template,
+ const pa_channel_map *from,
+ const pa_channel_map *to) {
+
+ pa_cvolume t;
+
+ pa_assert(v);
+ pa_assert(template);
+ pa_assert(from);
+ pa_assert(to);
+ pa_assert(pa_cvolume_compatible_with_channel_map(v, from));
+ pa_assert(pa_cvolume_compatible_with_channel_map(template, to));
+
+ /* Much like pa_cvolume_remap(), but tries to minimize impact when
+ * mapping from source output to source volumes:
+ *
+ * If template is a possible remapping from v it is used instead
+ * of remapping anew.
+ *
+ * If the channel maps don't match we set an all-channel volume on
+ * the source to ensure that changing a volume on one stream has no
+ * effect that cannot be compensated for in another stream that
+ * does not have the same channel map as the source. */
+
+ if (pa_channel_map_equal(from, to))
+ return v;
+
+ t = *template;
+ if (pa_cvolume_equal(pa_cvolume_remap(&t, to, from), v)) {
+ *v = *template;
+ return v;
+ }
+
+ pa_cvolume_set(v, to->channels, pa_cvolume_max(v));
+ return v;
+}
+
+/* Called from main thread. Only called for the root source in volume sharing
+ * cases, except for internal recursive calls. */
+static void get_maximum_output_volume(pa_source *s, pa_cvolume *max_volume, const pa_channel_map *channel_map) {
+ pa_source_output *o;
+ uint32_t idx;
+
+ pa_source_assert_ref(s);
+ pa_assert(max_volume);
+ pa_assert(channel_map);
+ pa_assert(pa_source_flat_volume_enabled(s));
+
+ PA_IDXSET_FOREACH(o, s->outputs, idx) {
+ pa_cvolume remapped;
+
+ if (o->destination_source && (o->destination_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)) {
+ if (PA_SOURCE_IS_LINKED(o->destination_source->state))
+ get_maximum_output_volume(o->destination_source, max_volume, channel_map);
+
+ /* Ignore this output. The origin source uses volume sharing, so this
+ * output's volume will be set to be equal to the root source's real
+ * volume. Obviously this output's current volume must not then
+ * affect what the root source's real volume will be. */
+ continue;
+ }
+
+ remapped = o->volume;
+ cvolume_remap_minimal_impact(&remapped, max_volume, &o->channel_map, channel_map);
+ pa_cvolume_merge(max_volume, max_volume, &remapped);
+ }
+}
+
+/* Called from main thread. Only called for the root source in volume sharing
+ * cases, except for internal recursive calls. */
+static bool has_outputs(pa_source *s) {
+ pa_source_output *o;
+ uint32_t idx;
+
+ pa_source_assert_ref(s);
+
+ PA_IDXSET_FOREACH(o, s->outputs, idx) {
+ if (!o->destination_source || !(o->destination_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER) || has_outputs(o->destination_source))
+ return true;
+ }
+
+ return false;
+}
+
+/* Called from main thread. Only called for the root source in volume sharing
+ * cases, except for internal recursive calls. */
+static void update_real_volume(pa_source *s, const pa_cvolume *new_volume, pa_channel_map *channel_map) {
+ pa_source_output *o;
+ uint32_t idx;
+
+ pa_source_assert_ref(s);
+ pa_assert(new_volume);
+ pa_assert(channel_map);
+
+ s->real_volume = *new_volume;
+ pa_cvolume_remap(&s->real_volume, channel_map, &s->channel_map);
+
+ PA_IDXSET_FOREACH(o, s->outputs, idx) {
+ if (o->destination_source && (o->destination_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)) {
+ if (pa_source_flat_volume_enabled(s)) {
+ pa_cvolume new_output_volume;
+
+ /* Follow the root source's real volume. */
+ new_output_volume = *new_volume;
+ pa_cvolume_remap(&new_output_volume, channel_map, &o->channel_map);
+ pa_source_output_set_volume_direct(o, &new_output_volume);
+ compute_reference_ratio(o);
+ }
+
+ if (PA_SOURCE_IS_LINKED(o->destination_source->state))
+ update_real_volume(o->destination_source, new_volume, channel_map);
+ }
+ }
+}
+
+/* Called from main thread. Only called for the root source in shared volume
+ * cases. */
+static void compute_real_volume(pa_source *s) {
+ pa_source_assert_ref(s);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_IS_LINKED(s->state));
+ pa_assert(pa_source_flat_volume_enabled(s));
+ pa_assert(!(s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER));
+
+ /* This determines the maximum volume of all streams and sets
+ * s->real_volume accordingly. */
+
+ if (!has_outputs(s)) {
+ /* In the special case that we have no source outputs we leave the
+ * volume unmodified. */
+ update_real_volume(s, &s->reference_volume, &s->channel_map);
+ return;
+ }
+
+ pa_cvolume_mute(&s->real_volume, s->channel_map.channels);
+
+ /* First let's determine the new maximum volume of all outputs
+ * connected to this source */
+ get_maximum_output_volume(s, &s->real_volume, &s->channel_map);
+ update_real_volume(s, &s->real_volume, &s->channel_map);
+
+ /* Then, let's update the real ratios/soft volumes of all outputs
+ * connected to this source */
+ compute_real_ratios(s);
+}
+
+/* Called from main thread. Only called for the root source in shared volume
+ * cases, except for internal recursive calls. */
+static void propagate_reference_volume(pa_source *s) {
+ pa_source_output *o;
+ uint32_t idx;
+
+ pa_source_assert_ref(s);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_IS_LINKED(s->state));
+ pa_assert(pa_source_flat_volume_enabled(s));
+
+ /* This is called whenever the source volume changes that is not
+ * caused by a source output volume change. We need to fix up the
+ * source output volumes accordingly */
+
+ PA_IDXSET_FOREACH(o, s->outputs, idx) {
+ pa_cvolume new_volume;
+
+ if (o->destination_source && (o->destination_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)) {
+ if (PA_SOURCE_IS_LINKED(o->destination_source->state))
+ propagate_reference_volume(o->destination_source);
+
+ /* Since the origin source uses volume sharing, this output's volume
+ * needs to be updated to match the root source's real volume, but
+ * that will be done later in update_real_volume(). */
+ continue;
+ }
+
+ /* This basically calculates:
+ *
+ * o->volume := o->reference_volume * o->reference_ratio */
+
+ new_volume = s->reference_volume;
+ pa_cvolume_remap(&new_volume, &s->channel_map, &o->channel_map);
+ pa_sw_cvolume_multiply(&new_volume, &new_volume, &o->reference_ratio);
+ pa_source_output_set_volume_direct(o, &new_volume);
+ }
+}
+
+/* Called from main thread. Only called for the root source in volume sharing
+ * cases, except for internal recursive calls. The return value indicates
+ * whether any reference volume actually changed. */
+static bool update_reference_volume(pa_source *s, const pa_cvolume *v, const pa_channel_map *channel_map, bool save) {
+ pa_cvolume volume;
+ bool reference_volume_changed;
+ pa_source_output *o;
+ uint32_t idx;
+
+ pa_source_assert_ref(s);
+ pa_assert(PA_SOURCE_IS_LINKED(s->state));
+ pa_assert(v);
+ pa_assert(channel_map);
+ pa_assert(pa_cvolume_valid(v));
+
+ volume = *v;
+ pa_cvolume_remap(&volume, channel_map, &s->channel_map);
+
+ reference_volume_changed = !pa_cvolume_equal(&volume, &s->reference_volume);
+ pa_source_set_reference_volume_direct(s, &volume);
+
+ s->save_volume = (!reference_volume_changed && s->save_volume) || save;
+
+ if (!reference_volume_changed && !(s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER))
+ /* If the root source's volume doesn't change, then there can't be any
+ * changes in the other source in the source tree either.
+ *
+ * It's probably theoretically possible that even if the root source's
+ * volume changes slightly, some filter source doesn't change its volume
+ * due to rounding errors. If that happens, we still want to propagate
+ * the changed root source volume to the sources connected to the
+ * intermediate source that didn't change its volume. This theoretical
+ * possibility is the reason why we have that !(s->flags &
+ * PA_SOURCE_SHARE_VOLUME_WITH_MASTER) condition. Probably nobody would
+ * notice even if we returned here false always if
+ * reference_volume_changed is false. */
+ return false;
+
+ PA_IDXSET_FOREACH(o, s->outputs, idx) {
+ if (o->destination_source && (o->destination_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)
+ && PA_SOURCE_IS_LINKED(o->destination_source->state))
+ update_reference_volume(o->destination_source, v, channel_map, false);
+ }
+
+ return true;
+}
+
+/* Called from main thread */
+void pa_source_set_volume(
+ pa_source *s,
+ const pa_cvolume *volume,
+ bool send_msg,
+ bool save) {
+
+ pa_cvolume new_reference_volume, root_real_volume;
+ pa_source *root_source;
+
+ pa_source_assert_ref(s);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_IS_LINKED(s->state));
+ pa_assert(!volume || pa_cvolume_valid(volume));
+ pa_assert(volume || pa_source_flat_volume_enabled(s));
+ pa_assert(!volume || volume->channels == 1 || pa_cvolume_compatible(volume, &s->sample_spec));
+
+ /* make sure we don't change the volume in PASSTHROUGH mode ...
+ * ... *except* if we're being invoked to reset the volume to ensure 0 dB gain */
+ if (pa_source_is_passthrough(s) && (!volume || !pa_cvolume_is_norm(volume))) {
+ pa_log_warn("Cannot change volume, source is monitor of a PASSTHROUGH sink");
+ return;
+ }
+
+ /* In case of volume sharing, the volume is set for the root source first,
+ * from which it's then propagated to the sharing sources. */
+ root_source = pa_source_get_master(s);
+
+ if (PA_UNLIKELY(!root_source))
+ return;
+
+ /* As a special exception we accept mono volumes on all sources --
+ * even on those with more complex channel maps */
+
+ if (volume) {
+ if (pa_cvolume_compatible(volume, &s->sample_spec))
+ new_reference_volume = *volume;
+ else {
+ new_reference_volume = s->reference_volume;
+ pa_cvolume_scale(&new_reference_volume, pa_cvolume_max(volume));
+ }
+
+ pa_cvolume_remap(&new_reference_volume, &s->channel_map, &root_source->channel_map);
+
+ if (update_reference_volume(root_source, &new_reference_volume, &root_source->channel_map, save)) {
+ if (pa_source_flat_volume_enabled(root_source)) {
+ /* OK, propagate this volume change back to the outputs */
+ propagate_reference_volume(root_source);
+
+ /* And now recalculate the real volume */
+ compute_real_volume(root_source);
+ } else
+ update_real_volume(root_source, &root_source->reference_volume, &root_source->channel_map);
+ }
+
+ } else {
+ /* If volume is NULL we synchronize the source's real and
+ * reference volumes with the stream volumes. */
+
+ pa_assert(pa_source_flat_volume_enabled(root_source));
+
+ /* Ok, let's determine the new real volume */
+ compute_real_volume(root_source);
+
+ /* To propagate the reference volume from the filter to the root source,
+ * we first take the real volume from the root source and remap it to
+ * match the filter. Then, we merge in the reference volume from the
+ * filter on top of this, and remap it back to the root source channel
+ * count and map */
+ root_real_volume = root_source->real_volume;
+ /* First we remap root's real volume to filter channel count and map if needed */
+ if (s != root_source && !pa_channel_map_equal(&s->channel_map, &root_source->channel_map))
+ pa_cvolume_remap(&root_real_volume, &root_source->channel_map, &s->channel_map);
+ /* Then let's 'push' the reference volume if necessary */
+ pa_cvolume_merge(&new_reference_volume, &s->reference_volume, &root_real_volume);
+ /* If the source and its root don't have the same number of channels, we need to remap back */
+ if (s != root_source && !pa_channel_map_equal(&s->channel_map, &root_source->channel_map))
+ pa_cvolume_remap(&new_reference_volume, &s->channel_map, &root_source->channel_map);
+
+ update_reference_volume(root_source, &new_reference_volume, &root_source->channel_map, save);
+
+ /* Now that the reference volume is updated, we can update the streams'
+ * reference ratios. */
+ compute_reference_ratios(root_source);
+ }
+
+ if (root_source->set_volume) {
+ /* If we have a function set_volume(), then we do not apply a
+ * soft volume by default. However, set_volume() is free to
+ * apply one to root_source->soft_volume */
+
+ pa_cvolume_reset(&root_source->soft_volume, root_source->sample_spec.channels);
+ if (!(root_source->flags & PA_SOURCE_DEFERRED_VOLUME))
+ root_source->set_volume(root_source);
+
+ } else
+ /* If we have no function set_volume(), then the soft volume
+ * becomes the real volume */
+ root_source->soft_volume = root_source->real_volume;
+
+ /* This tells the source that soft volume and/or real volume changed */
+ if (send_msg)
+ pa_assert_se(pa_asyncmsgq_send(root_source->asyncmsgq, PA_MSGOBJECT(root_source), PA_SOURCE_MESSAGE_SET_SHARED_VOLUME, NULL, 0, NULL) == 0);
+}
+
+/* Called from the io thread if sync volume is used, otherwise from the main thread.
+ * Only to be called by source implementor */
+void pa_source_set_soft_volume(pa_source *s, const pa_cvolume *volume) {
+
+ pa_source_assert_ref(s);
+ pa_assert(!(s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER));
+
+ if (s->flags & PA_SOURCE_DEFERRED_VOLUME)
+ pa_source_assert_io_context(s);
+ else
+ pa_assert_ctl_context();
+
+ if (!volume)
+ pa_cvolume_reset(&s->soft_volume, s->sample_spec.channels);
+ else
+ s->soft_volume = *volume;
+
+ if (PA_SOURCE_IS_LINKED(s->state) && !(s->flags & PA_SOURCE_DEFERRED_VOLUME))
+ pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_VOLUME, NULL, 0, NULL) == 0);
+ else
+ s->thread_info.soft_volume = s->soft_volume;
+}
+
+/* Called from the main thread. Only called for the root source in volume sharing
+ * cases, except for internal recursive calls. */
+static void propagate_real_volume(pa_source *s, const pa_cvolume *old_real_volume) {
+ pa_source_output *o;
+ uint32_t idx;
+
+ pa_source_assert_ref(s);
+ pa_assert(old_real_volume);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_IS_LINKED(s->state));
+
+ /* This is called when the hardware's real volume changes due to
+ * some external event. We copy the real volume into our
+ * reference volume and then rebuild the stream volumes based on
+ * i->real_ratio which should stay fixed. */
+
+ if (!(s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)) {
+ if (pa_cvolume_equal(old_real_volume, &s->real_volume))
+ return;
+
+ /* 1. Make the real volume the reference volume */
+ update_reference_volume(s, &s->real_volume, &s->channel_map, true);
+ }
+
+ if (pa_source_flat_volume_enabled(s)) {
+ PA_IDXSET_FOREACH(o, s->outputs, idx) {
+ pa_cvolume new_volume;
+
+ /* 2. Since the source's reference and real volumes are equal
+ * now our ratios should be too. */
+ pa_source_output_set_reference_ratio(o, &o->real_ratio);
+
+ /* 3. Recalculate the new stream reference volume based on the
+ * reference ratio and the sink's reference volume.
+ *
+ * This basically calculates:
+ *
+ * o->volume = s->reference_volume * o->reference_ratio
+ *
+ * This is identical to propagate_reference_volume() */
+ new_volume = s->reference_volume;
+ pa_cvolume_remap(&new_volume, &s->channel_map, &o->channel_map);
+ pa_sw_cvolume_multiply(&new_volume, &new_volume, &o->reference_ratio);
+ pa_source_output_set_volume_direct(o, &new_volume);
+
+ if (o->destination_source && (o->destination_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)
+ && PA_SOURCE_IS_LINKED(o->destination_source->state))
+ propagate_real_volume(o->destination_source, old_real_volume);
+ }
+ }
+
+ /* Something got changed in the hardware. It probably makes sense
+ * to save changed hw settings given that hw volume changes not
+ * triggered by PA are almost certainly done by the user. */
+ if (!(s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER))
+ s->save_volume = true;
+}
+
+/* Called from io thread */
+void pa_source_update_volume_and_mute(pa_source *s) {
+ pa_assert(s);
+ pa_source_assert_io_context(s);
+
+ pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_UPDATE_VOLUME_AND_MUTE, NULL, 0, NULL, NULL);
+}
+
+/* Called from main thread */
+const pa_cvolume *pa_source_get_volume(pa_source *s, bool force_refresh) {
+ pa_source_assert_ref(s);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_IS_LINKED(s->state));
+
+ if (s->refresh_volume || force_refresh) {
+ struct pa_cvolume old_real_volume;
+
+ pa_assert(!(s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER));
+
+ old_real_volume = s->real_volume;
+
+ if (!(s->flags & PA_SOURCE_DEFERRED_VOLUME) && s->get_volume)
+ s->get_volume(s);
+
+ pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_GET_VOLUME, NULL, 0, NULL) == 0);
+
+ update_real_volume(s, &s->real_volume, &s->channel_map);
+ propagate_real_volume(s, &old_real_volume);
+ }
+
+ return &s->reference_volume;
+}
+
+/* Called from main thread. In volume sharing cases, only the root source may
+ * call this. */
+void pa_source_volume_changed(pa_source *s, const pa_cvolume *new_real_volume) {
+ pa_cvolume old_real_volume;
+
+ pa_source_assert_ref(s);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_IS_LINKED(s->state));
+ pa_assert(!(s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER));
+
+ /* The source implementor may call this if the volume changed to make sure everyone is notified */
+
+ old_real_volume = s->real_volume;
+ update_real_volume(s, new_real_volume, &s->channel_map);
+ propagate_real_volume(s, &old_real_volume);
+}
+
+/* Called from main thread */
+void pa_source_set_mute(pa_source *s, bool mute, bool save) {
+ bool old_muted;
+
+ pa_source_assert_ref(s);
+ pa_assert_ctl_context();
+
+ old_muted = s->muted;
+
+ if (mute == old_muted) {
+ s->save_muted |= save;
+ return;
+ }
+
+ s->muted = mute;
+ s->save_muted = save;
+
+ if (!(s->flags & PA_SOURCE_DEFERRED_VOLUME) && s->set_mute) {
+ s->set_mute_in_progress = true;
+ s->set_mute(s);
+ s->set_mute_in_progress = false;
+ }
+
+ if (!PA_SOURCE_IS_LINKED(s->state))
+ return;
+
+ pa_log_debug("The mute of source %s changed from %s to %s.", s->name, pa_yes_no(old_muted), pa_yes_no(mute));
+ pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_MUTE, NULL, 0, NULL) == 0);
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+ pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_MUTE_CHANGED], s);
+}
+
+/* Called from main thread */
+bool pa_source_get_mute(pa_source *s, bool force_refresh) {
+
+ pa_source_assert_ref(s);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_IS_LINKED(s->state));
+
+ if ((s->refresh_muted || force_refresh) && s->get_mute) {
+ bool mute;
+
+ if (s->flags & PA_SOURCE_DEFERRED_VOLUME) {
+ if (pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_GET_MUTE, &mute, 0, NULL) >= 0)
+ pa_source_mute_changed(s, mute);
+ } else {
+ if (s->get_mute(s, &mute) >= 0)
+ pa_source_mute_changed(s, mute);
+ }
+ }
+
+ return s->muted;
+}
+
+/* Called from main thread */
+void pa_source_mute_changed(pa_source *s, bool new_muted) {
+ pa_source_assert_ref(s);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_IS_LINKED(s->state));
+
+ if (s->set_mute_in_progress)
+ return;
+
+ /* pa_source_set_mute() does this same check, so this may appear redundant,
+ * but we must have this here also, because the save parameter of
+ * pa_source_set_mute() would otherwise have unintended side effects
+ * (saving the mute state when it shouldn't be saved). */
+ if (new_muted == s->muted)
+ return;
+
+ pa_source_set_mute(s, new_muted, true);
+}
+
+/* Called from main thread */
+bool pa_source_update_proplist(pa_source *s, pa_update_mode_t mode, pa_proplist *p) {
+ pa_source_assert_ref(s);
+ pa_assert_ctl_context();
+
+ if (p)
+ pa_proplist_update(s->proplist, mode, p);
+
+ if (PA_SOURCE_IS_LINKED(s->state)) {
+ pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_PROPLIST_CHANGED], s);
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+ }
+
+ return true;
+}
+
+/* Called from main thread */
+/* FIXME -- this should be dropped and be merged into pa_source_update_proplist() */
+void pa_source_set_description(pa_source *s, const char *description) {
+ const char *old;
+ pa_source_assert_ref(s);
+ pa_assert_ctl_context();
+
+ if (!description && !pa_proplist_contains(s->proplist, PA_PROP_DEVICE_DESCRIPTION))
+ return;
+
+ old = pa_proplist_gets(s->proplist, PA_PROP_DEVICE_DESCRIPTION);
+
+ if (old && description && pa_streq(old, description))
+ return;
+
+ if (description)
+ pa_proplist_sets(s->proplist, PA_PROP_DEVICE_DESCRIPTION, description);
+ else
+ pa_proplist_unset(s->proplist, PA_PROP_DEVICE_DESCRIPTION);
+
+ if (PA_SOURCE_IS_LINKED(s->state)) {
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+ pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_PROPLIST_CHANGED], s);
+ }
+}
+
+/* Called from main thread */
+unsigned pa_source_linked_by(pa_source *s) {
+ pa_source_assert_ref(s);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_IS_LINKED(s->state));
+
+ return pa_idxset_size(s->outputs);
+}
+
+/* Called from main thread */
+unsigned pa_source_used_by(pa_source *s) {
+ unsigned ret;
+
+ pa_source_assert_ref(s);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_IS_LINKED(s->state));
+
+ ret = pa_idxset_size(s->outputs);
+ pa_assert(ret >= s->n_corked);
+
+ return ret - s->n_corked;
+}
+
+/* Called from main thread */
+unsigned pa_source_check_suspend(pa_source *s, pa_source_output *ignore) {
+ unsigned ret;
+ pa_source_output *o;
+ uint32_t idx;
+
+ pa_source_assert_ref(s);
+ pa_assert_ctl_context();
+
+ if (!PA_SOURCE_IS_LINKED(s->state))
+ return 0;
+
+ ret = 0;
+
+ PA_IDXSET_FOREACH(o, s->outputs, idx) {
+ if (o == ignore)
+ continue;
+
+ /* We do not assert here. It is perfectly valid for a source output to
+ * be in the INIT state (i.e. created, marked done but not yet put)
+ * and we should not care if it's unlinked as it won't contribute
+ * towards our busy status.
+ */
+ if (!PA_SOURCE_OUTPUT_IS_LINKED(o->state))
+ continue;
+
+ if (o->state == PA_SOURCE_OUTPUT_CORKED)
+ continue;
+
+ if (o->flags & PA_SOURCE_OUTPUT_DONT_INHIBIT_AUTO_SUSPEND)
+ continue;
+
+ ret ++;
+ }
+
+ return ret;
+}
+
+const char *pa_source_state_to_string(pa_source_state_t state) {
+ switch (state) {
+ case PA_SOURCE_INIT: return "INIT";
+ case PA_SOURCE_IDLE: return "IDLE";
+ case PA_SOURCE_RUNNING: return "RUNNING";
+ case PA_SOURCE_SUSPENDED: return "SUSPENDED";
+ case PA_SOURCE_UNLINKED: return "UNLINKED";
+ case PA_SOURCE_INVALID_STATE: return "INVALID_STATE";
+ }
+
+ pa_assert_not_reached();
+}
+
+/* Called from the IO thread */
+static void sync_output_volumes_within_thread(pa_source *s) {
+ pa_source_output *o;
+ void *state = NULL;
+
+ pa_source_assert_ref(s);
+ pa_source_assert_io_context(s);
+
+ PA_HASHMAP_FOREACH(o, s->thread_info.outputs, state) {
+ if (pa_cvolume_equal(&o->thread_info.soft_volume, &o->soft_volume))
+ continue;
+
+ o->thread_info.soft_volume = o->soft_volume;
+ //pa_source_output_request_rewind(o, 0, true, false, false);
+ }
+}
+
+/* Called from the IO thread. Only called for the root source in volume sharing
+ * cases, except for internal recursive calls. */
+static void set_shared_volume_within_thread(pa_source *s) {
+ pa_source_output *o;
+ void *state = NULL;
+
+ pa_source_assert_ref(s);
+
+ PA_MSGOBJECT(s)->process_msg(PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_VOLUME_SYNCED, NULL, 0, NULL);
+
+ PA_HASHMAP_FOREACH(o, s->thread_info.outputs, state) {
+ if (o->destination_source && (o->destination_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER))
+ set_shared_volume_within_thread(o->destination_source);
+ }
+}
+
+/* Called from IO thread, except when it is not */
+int pa_source_process_msg(pa_msgobject *object, int code, void *userdata, int64_t offset, pa_memchunk *chunk) {
+ pa_source *s = PA_SOURCE(object);
+ pa_source_assert_ref(s);
+
+ switch ((pa_source_message_t) code) {
+
+ case PA_SOURCE_MESSAGE_ADD_OUTPUT: {
+ pa_source_output *o = PA_SOURCE_OUTPUT(userdata);
+
+ pa_hashmap_put(s->thread_info.outputs, PA_UINT32_TO_PTR(o->index), pa_source_output_ref(o));
+
+ if (o->direct_on_input) {
+ o->thread_info.direct_on_input = o->direct_on_input;
+ pa_hashmap_put(o->thread_info.direct_on_input->thread_info.direct_outputs, PA_UINT32_TO_PTR(o->index), o);
+ }
+
+ pa_source_output_attach(o);
+
+ pa_source_output_set_state_within_thread(o, o->state);
+
+ if (o->thread_info.requested_source_latency != (pa_usec_t) -1)
+ pa_source_output_set_requested_latency_within_thread(o, o->thread_info.requested_source_latency);
+
+ pa_source_output_update_max_rewind(o, s->thread_info.max_rewind);
+
+ /* We don't just invalidate the requested latency here,
+ * because if we are in a move we might need to fix up the
+ * requested latency. */
+ pa_source_output_set_requested_latency_within_thread(o, o->thread_info.requested_source_latency);
+
+ /* In flat volume mode we need to update the volume as
+ * well */
+ return object->process_msg(object, PA_SOURCE_MESSAGE_SET_SHARED_VOLUME, NULL, 0, NULL);
+ }
+
+ case PA_SOURCE_MESSAGE_REMOVE_OUTPUT: {
+ pa_source_output *o = PA_SOURCE_OUTPUT(userdata);
+
+ pa_source_output_set_state_within_thread(o, o->state);
+
+ pa_source_output_detach(o);
+
+ if (o->thread_info.direct_on_input) {
+ pa_hashmap_remove(o->thread_info.direct_on_input->thread_info.direct_outputs, PA_UINT32_TO_PTR(o->index));
+ o->thread_info.direct_on_input = NULL;
+ }
+
+ pa_hashmap_remove_and_free(s->thread_info.outputs, PA_UINT32_TO_PTR(o->index));
+ pa_source_invalidate_requested_latency(s, true);
+
+ /* In flat volume mode we need to update the volume as
+ * well */
+ return object->process_msg(object, PA_SOURCE_MESSAGE_SET_SHARED_VOLUME, NULL, 0, NULL);
+ }
+
+ case PA_SOURCE_MESSAGE_SET_SHARED_VOLUME: {
+ pa_source *root_source = pa_source_get_master(s);
+
+ if (PA_LIKELY(root_source))
+ set_shared_volume_within_thread(root_source);
+
+ return 0;
+ }
+
+ case PA_SOURCE_MESSAGE_SET_VOLUME_SYNCED:
+
+ if (s->flags & PA_SOURCE_DEFERRED_VOLUME) {
+ s->set_volume(s);
+ pa_source_volume_change_push(s);
+ }
+ /* Fall through ... */
+
+ case PA_SOURCE_MESSAGE_SET_VOLUME:
+
+ if (!pa_cvolume_equal(&s->thread_info.soft_volume, &s->soft_volume)) {
+ s->thread_info.soft_volume = s->soft_volume;
+ }
+
+ /* Fall through ... */
+
+ case PA_SOURCE_MESSAGE_SYNC_VOLUMES:
+ sync_output_volumes_within_thread(s);
+ return 0;
+
+ case PA_SOURCE_MESSAGE_GET_VOLUME:
+
+ if ((s->flags & PA_SOURCE_DEFERRED_VOLUME) && s->get_volume) {
+ s->get_volume(s);
+ pa_source_volume_change_flush(s);
+ pa_sw_cvolume_divide(&s->thread_info.current_hw_volume, &s->real_volume, &s->soft_volume);
+ }
+
+ /* In case source implementor reset SW volume. */
+ if (!pa_cvolume_equal(&s->thread_info.soft_volume, &s->soft_volume)) {
+ s->thread_info.soft_volume = s->soft_volume;
+ }
+
+ return 0;
+
+ case PA_SOURCE_MESSAGE_SET_MUTE:
+
+ if (s->thread_info.soft_muted != s->muted) {
+ s->thread_info.soft_muted = s->muted;
+ }
+
+ if (s->flags & PA_SOURCE_DEFERRED_VOLUME && s->set_mute)
+ s->set_mute(s);
+
+ return 0;
+
+ case PA_SOURCE_MESSAGE_GET_MUTE:
+
+ if (s->flags & PA_SOURCE_DEFERRED_VOLUME && s->get_mute)
+ return s->get_mute(s, userdata);
+
+ return 0;
+
+ case PA_SOURCE_MESSAGE_SET_STATE: {
+ struct set_state_data *data = userdata;
+ bool suspend_change =
+ (s->thread_info.state == PA_SOURCE_SUSPENDED && PA_SOURCE_IS_OPENED(data->state)) ||
+ (PA_SOURCE_IS_OPENED(s->thread_info.state) && data->state == PA_SOURCE_SUSPENDED);
+
+ if (s->set_state_in_io_thread) {
+ int r;
+
+ if ((r = s->set_state_in_io_thread(s, data->state, data->suspend_cause)) < 0)
+ return r;
+ }
+
+ s->thread_info.state = data->state;
+
+ if (suspend_change) {
+ pa_source_output *o;
+ void *state = NULL;
+
+ while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL)))
+ if (o->suspend_within_thread)
+ o->suspend_within_thread(o, s->thread_info.state == PA_SOURCE_SUSPENDED);
+ }
+
+ return 0;
+ }
+
+ case PA_SOURCE_MESSAGE_GET_REQUESTED_LATENCY: {
+
+ pa_usec_t *usec = userdata;
+ *usec = pa_source_get_requested_latency_within_thread(s);
+
+ /* Yes, that's right, the IO thread will see -1 when no
+ * explicit requested latency is configured, the main
+ * thread will see max_latency */
+ if (*usec == (pa_usec_t) -1)
+ *usec = s->thread_info.max_latency;
+
+ return 0;
+ }
+
+ case PA_SOURCE_MESSAGE_SET_LATENCY_RANGE: {
+ pa_usec_t *r = userdata;
+
+ pa_source_set_latency_range_within_thread(s, r[0], r[1]);
+
+ return 0;
+ }
+
+ case PA_SOURCE_MESSAGE_GET_LATENCY_RANGE: {
+ pa_usec_t *r = userdata;
+
+ r[0] = s->thread_info.min_latency;
+ r[1] = s->thread_info.max_latency;
+
+ return 0;
+ }
+
+ case PA_SOURCE_MESSAGE_GET_FIXED_LATENCY:
+
+ *((pa_usec_t*) userdata) = s->thread_info.fixed_latency;
+ return 0;
+
+ case PA_SOURCE_MESSAGE_SET_FIXED_LATENCY:
+
+ pa_source_set_fixed_latency_within_thread(s, (pa_usec_t) offset);
+ return 0;
+
+ case PA_SOURCE_MESSAGE_GET_MAX_REWIND:
+
+ *((size_t*) userdata) = s->thread_info.max_rewind;
+ return 0;
+
+ case PA_SOURCE_MESSAGE_SET_MAX_REWIND:
+
+ pa_source_set_max_rewind_within_thread(s, (size_t) offset);
+ return 0;
+
+ case PA_SOURCE_MESSAGE_GET_LATENCY:
+
+ if (s->monitor_of) {
+ *((int64_t*) userdata) = -pa_sink_get_latency_within_thread(s->monitor_of, true);
+ return 0;
+ }
+
+ /* Implementors need to overwrite this implementation! */
+ return -1;
+
+ case PA_SOURCE_MESSAGE_UPDATE_VOLUME_AND_MUTE:
+ /* This message is sent from IO-thread and handled in main thread. */
+ pa_assert_ctl_context();
+
+ /* Make sure we're not messing with main thread when no longer linked */
+ if (!PA_SOURCE_IS_LINKED(s->state))
+ return 0;
+
+ pa_source_get_volume(s, true);
+ pa_source_get_mute(s, true);
+ return 0;
+
+ case PA_SOURCE_MESSAGE_SET_PORT_LATENCY_OFFSET:
+ s->thread_info.port_latency_offset = offset;
+ return 0;
+
+ case PA_SOURCE_MESSAGE_MAX:
+ ;
+ }
+
+ return -1;
+}
+
+/* Called from main thread */
+int pa_source_suspend_all(pa_core *c, bool suspend, pa_suspend_cause_t cause) {
+ pa_source *source;
+ uint32_t idx;
+ int ret = 0;
+
+ pa_core_assert_ref(c);
+ pa_assert_ctl_context();
+ pa_assert(cause != 0);
+
+ for (source = PA_SOURCE(pa_idxset_first(c->sources, &idx)); source; source = PA_SOURCE(pa_idxset_next(c->sources, &idx))) {
+ int r;
+
+ if (source->monitor_of)
+ continue;
+
+ if ((r = pa_source_suspend(source, suspend, cause)) < 0)
+ ret = r;
+ }
+
+ return ret;
+}
+
+/* Called from IO thread */
+void pa_source_detach_within_thread(pa_source *s) {
+ pa_source_output *o;
+ void *state = NULL;
+
+ pa_source_assert_ref(s);
+ pa_source_assert_io_context(s);
+ pa_assert(PA_SOURCE_IS_LINKED(s->thread_info.state));
+
+ PA_HASHMAP_FOREACH(o, s->thread_info.outputs, state)
+ pa_source_output_detach(o);
+}
+
+/* Called from IO thread */
+void pa_source_attach_within_thread(pa_source *s) {
+ pa_source_output *o;
+ void *state = NULL;
+
+ pa_source_assert_ref(s);
+ pa_source_assert_io_context(s);
+ pa_assert(PA_SOURCE_IS_LINKED(s->thread_info.state));
+
+ PA_HASHMAP_FOREACH(o, s->thread_info.outputs, state)
+ pa_source_output_attach(o);
+}
+
+/* Called from IO thread */
+pa_usec_t pa_source_get_requested_latency_within_thread(pa_source *s) {
+ pa_usec_t result = (pa_usec_t) -1;
+ pa_source_output *o;
+ void *state = NULL;
+
+ pa_source_assert_ref(s);
+ pa_source_assert_io_context(s);
+
+ if (!(s->flags & PA_SOURCE_DYNAMIC_LATENCY))
+ return PA_CLAMP(s->thread_info.fixed_latency, s->thread_info.min_latency, s->thread_info.max_latency);
+
+ if (s->thread_info.requested_latency_valid)
+ return s->thread_info.requested_latency;
+
+ PA_HASHMAP_FOREACH(o, s->thread_info.outputs, state)
+ if (o->thread_info.requested_source_latency != (pa_usec_t) -1 &&
+ (result == (pa_usec_t) -1 || result > o->thread_info.requested_source_latency))
+ result = o->thread_info.requested_source_latency;
+
+ if (result != (pa_usec_t) -1)
+ result = PA_CLAMP(result, s->thread_info.min_latency, s->thread_info.max_latency);
+
+ if (PA_SOURCE_IS_LINKED(s->thread_info.state)) {
+ /* Only cache this if we are fully set up */
+ s->thread_info.requested_latency = result;
+ s->thread_info.requested_latency_valid = true;
+ }
+
+ return result;
+}
+
+/* Called from main thread */
+pa_usec_t pa_source_get_requested_latency(pa_source *s) {
+ pa_usec_t usec = 0;
+
+ pa_source_assert_ref(s);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_IS_LINKED(s->state));
+
+ if (s->state == PA_SOURCE_SUSPENDED)
+ return 0;
+
+ pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_GET_REQUESTED_LATENCY, &usec, 0, NULL) == 0);
+
+ return usec;
+}
+
+/* Called from IO thread */
+void pa_source_set_max_rewind_within_thread(pa_source *s, size_t max_rewind) {
+ pa_source_output *o;
+ void *state = NULL;
+
+ pa_source_assert_ref(s);
+ pa_source_assert_io_context(s);
+
+ if (max_rewind == s->thread_info.max_rewind)
+ return;
+
+ s->thread_info.max_rewind = max_rewind;
+
+ if (PA_SOURCE_IS_LINKED(s->thread_info.state))
+ PA_HASHMAP_FOREACH(o, s->thread_info.outputs, state)
+ pa_source_output_update_max_rewind(o, s->thread_info.max_rewind);
+}
+
+/* Called from main thread */
+void pa_source_set_max_rewind(pa_source *s, size_t max_rewind) {
+ pa_source_assert_ref(s);
+ pa_assert_ctl_context();
+
+ if (PA_SOURCE_IS_LINKED(s->state))
+ pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_MAX_REWIND, NULL, max_rewind, NULL) == 0);
+ else
+ pa_source_set_max_rewind_within_thread(s, max_rewind);
+}
+
+/* Called from IO thread */
+void pa_source_invalidate_requested_latency(pa_source *s, bool dynamic) {
+ pa_source_output *o;
+ void *state = NULL;
+
+ pa_source_assert_ref(s);
+ pa_source_assert_io_context(s);
+
+ if ((s->flags & PA_SOURCE_DYNAMIC_LATENCY))
+ s->thread_info.requested_latency_valid = false;
+ else if (dynamic)
+ return;
+
+ if (PA_SOURCE_IS_LINKED(s->thread_info.state)) {
+
+ if (s->update_requested_latency)
+ s->update_requested_latency(s);
+
+ while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL)))
+ if (o->update_source_requested_latency)
+ o->update_source_requested_latency(o);
+ }
+
+ if (s->monitor_of)
+ pa_sink_invalidate_requested_latency(s->monitor_of, dynamic);
+}
+
+/* Called from main thread */
+void pa_source_set_latency_range(pa_source *s, pa_usec_t min_latency, pa_usec_t max_latency) {
+ pa_source_assert_ref(s);
+ pa_assert_ctl_context();
+
+ /* min_latency == 0: no limit
+ * min_latency anything else: specified limit
+ *
+ * Similar for max_latency */
+
+ if (min_latency < ABSOLUTE_MIN_LATENCY)
+ min_latency = ABSOLUTE_MIN_LATENCY;
+
+ if (max_latency <= 0 ||
+ max_latency > ABSOLUTE_MAX_LATENCY)
+ max_latency = ABSOLUTE_MAX_LATENCY;
+
+ pa_assert(min_latency <= max_latency);
+
+ /* Hmm, let's see if someone forgot to set PA_SOURCE_DYNAMIC_LATENCY here... */
+ pa_assert((min_latency == ABSOLUTE_MIN_LATENCY &&
+ max_latency == ABSOLUTE_MAX_LATENCY) ||
+ (s->flags & PA_SOURCE_DYNAMIC_LATENCY));
+
+ if (PA_SOURCE_IS_LINKED(s->state)) {
+ pa_usec_t r[2];
+
+ r[0] = min_latency;
+ r[1] = max_latency;
+
+ pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_LATENCY_RANGE, r, 0, NULL) == 0);
+ } else
+ pa_source_set_latency_range_within_thread(s, min_latency, max_latency);
+}
+
+/* Called from main thread */
+void pa_source_get_latency_range(pa_source *s, pa_usec_t *min_latency, pa_usec_t *max_latency) {
+ pa_source_assert_ref(s);
+ pa_assert_ctl_context();
+ pa_assert(min_latency);
+ pa_assert(max_latency);
+
+ if (PA_SOURCE_IS_LINKED(s->state)) {
+ pa_usec_t r[2] = { 0, 0 };
+
+ pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_GET_LATENCY_RANGE, r, 0, NULL) == 0);
+
+ *min_latency = r[0];
+ *max_latency = r[1];
+ } else {
+ *min_latency = s->thread_info.min_latency;
+ *max_latency = s->thread_info.max_latency;
+ }
+}
+
+/* Called from IO thread, and from main thread before pa_source_put() is called */
+void pa_source_set_latency_range_within_thread(pa_source *s, pa_usec_t min_latency, pa_usec_t max_latency) {
+ pa_source_assert_ref(s);
+ pa_source_assert_io_context(s);
+
+ pa_assert(min_latency >= ABSOLUTE_MIN_LATENCY);
+ pa_assert(max_latency <= ABSOLUTE_MAX_LATENCY);
+ pa_assert(min_latency <= max_latency);
+
+ /* Hmm, let's see if someone forgot to set PA_SOURCE_DYNAMIC_LATENCY here... */
+ pa_assert((min_latency == ABSOLUTE_MIN_LATENCY &&
+ max_latency == ABSOLUTE_MAX_LATENCY) ||
+ (s->flags & PA_SOURCE_DYNAMIC_LATENCY) ||
+ s->monitor_of);
+
+ if (s->thread_info.min_latency == min_latency &&
+ s->thread_info.max_latency == max_latency)
+ return;
+
+ s->thread_info.min_latency = min_latency;
+ s->thread_info.max_latency = max_latency;
+
+ if (PA_SOURCE_IS_LINKED(s->thread_info.state)) {
+ pa_source_output *o;
+ void *state = NULL;
+
+ PA_HASHMAP_FOREACH(o, s->thread_info.outputs, state)
+ if (o->update_source_latency_range)
+ o->update_source_latency_range(o);
+ }
+
+ pa_source_invalidate_requested_latency(s, false);
+}
+
+/* Called from main thread, before the source is put */
+void pa_source_set_fixed_latency(pa_source *s, pa_usec_t latency) {
+ pa_source_assert_ref(s);
+ pa_assert_ctl_context();
+
+ if (s->flags & PA_SOURCE_DYNAMIC_LATENCY) {
+ pa_assert(latency == 0);
+ return;
+ }
+
+ if (latency < ABSOLUTE_MIN_LATENCY)
+ latency = ABSOLUTE_MIN_LATENCY;
+
+ if (latency > ABSOLUTE_MAX_LATENCY)
+ latency = ABSOLUTE_MAX_LATENCY;
+
+ if (PA_SOURCE_IS_LINKED(s->state))
+ pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_FIXED_LATENCY, NULL, (int64_t) latency, NULL) == 0);
+ else
+ s->thread_info.fixed_latency = latency;
+}
+
+/* Called from main thread */
+pa_usec_t pa_source_get_fixed_latency(pa_source *s) {
+ pa_usec_t latency;
+
+ pa_source_assert_ref(s);
+ pa_assert_ctl_context();
+
+ if (s->flags & PA_SOURCE_DYNAMIC_LATENCY)
+ return 0;
+
+ if (PA_SOURCE_IS_LINKED(s->state))
+ pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_GET_FIXED_LATENCY, &latency, 0, NULL) == 0);
+ else
+ latency = s->thread_info.fixed_latency;
+
+ return latency;
+}
+
+/* Called from IO thread */
+void pa_source_set_fixed_latency_within_thread(pa_source *s, pa_usec_t latency) {
+ pa_source_assert_ref(s);
+ pa_source_assert_io_context(s);
+
+ if (s->flags & PA_SOURCE_DYNAMIC_LATENCY) {
+ pa_assert(latency == 0);
+ s->thread_info.fixed_latency = 0;
+
+ return;
+ }
+
+ pa_assert(latency >= ABSOLUTE_MIN_LATENCY);
+ pa_assert(latency <= ABSOLUTE_MAX_LATENCY);
+
+ if (s->thread_info.fixed_latency == latency)
+ return;
+
+ s->thread_info.fixed_latency = latency;
+
+ if (PA_SOURCE_IS_LINKED(s->thread_info.state)) {
+ pa_source_output *o;
+ void *state = NULL;
+
+ PA_HASHMAP_FOREACH(o, s->thread_info.outputs, state)
+ if (o->update_source_fixed_latency)
+ o->update_source_fixed_latency(o);
+ }
+
+ pa_source_invalidate_requested_latency(s, false);
+}
+
+/* Called from main thread */
+void pa_source_set_port_latency_offset(pa_source *s, int64_t offset) {
+ pa_source_assert_ref(s);
+
+ s->port_latency_offset = offset;
+
+ if (PA_SOURCE_IS_LINKED(s->state))
+ pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_PORT_LATENCY_OFFSET, NULL, offset, NULL) == 0);
+ else
+ s->thread_info.port_latency_offset = offset;
+
+ pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_PORT_LATENCY_OFFSET_CHANGED], s);
+}
+
+/* Called from main thread */
+size_t pa_source_get_max_rewind(pa_source *s) {
+ size_t r;
+ pa_assert_ctl_context();
+ pa_source_assert_ref(s);
+
+ if (!PA_SOURCE_IS_LINKED(s->state))
+ return s->thread_info.max_rewind;
+
+ pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_GET_MAX_REWIND, &r, 0, NULL) == 0);
+
+ return r;
+}
+
+/* Called from main context */
+int pa_source_set_port(pa_source *s, const char *name, bool save) {
+ pa_device_port *port;
+
+ pa_source_assert_ref(s);
+ pa_assert_ctl_context();
+
+ if (!s->set_port) {
+ pa_log_debug("set_port() operation not implemented for source %u \"%s\"", s->index, s->name);
+ return -PA_ERR_NOTIMPLEMENTED;
+ }
+
+ if (!name)
+ return -PA_ERR_NOENTITY;
+
+ if (!(port = pa_hashmap_get(s->ports, name)))
+ return -PA_ERR_NOENTITY;
+
+ if (s->active_port == port) {
+ s->save_port = s->save_port || save;
+ return 0;
+ }
+
+ if (s->set_port(s, port) < 0)
+ return -PA_ERR_NOENTITY;
+
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+
+ pa_log_info("Changed port of source %u \"%s\" to %s", s->index, s->name, port->name);
+
+ s->active_port = port;
+ s->save_port = save;
+
+ /* The active port affects the default source selection. */
+ pa_core_update_default_source(s->core);
+
+ pa_source_set_port_latency_offset(s, s->active_port->latency_offset);
+
+ pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_PORT_CHANGED], s);
+
+ return 0;
+}
+
+PA_STATIC_FLIST_DECLARE(pa_source_volume_change, 0, pa_xfree);
+
+/* Called from the IO thread. */
+static pa_source_volume_change *pa_source_volume_change_new(pa_source *s) {
+ pa_source_volume_change *c;
+ if (!(c = pa_flist_pop(PA_STATIC_FLIST_GET(pa_source_volume_change))))
+ c = pa_xnew(pa_source_volume_change, 1);
+
+ PA_LLIST_INIT(pa_source_volume_change, c);
+ c->at = 0;
+ pa_cvolume_reset(&c->hw_volume, s->sample_spec.channels);
+ return c;
+}
+
+/* Called from the IO thread. */
+static void pa_source_volume_change_free(pa_source_volume_change *c) {
+ pa_assert(c);
+ if (pa_flist_push(PA_STATIC_FLIST_GET(pa_source_volume_change), c) < 0)
+ pa_xfree(c);
+}
+
+/* Called from the IO thread. */
+void pa_source_volume_change_push(pa_source *s) {
+ pa_source_volume_change *c = NULL;
+ pa_source_volume_change *nc = NULL;
+ pa_source_volume_change *pc = NULL;
+ uint32_t safety_margin = s->thread_info.volume_change_safety_margin;
+
+ const char *direction = NULL;
+
+ pa_assert(s);
+ nc = pa_source_volume_change_new(s);
+
+ /* NOTE: There is already more different volumes in pa_source that I can remember.
+ * Adding one more volume for HW would get us rid of this, but I am trying
+ * to survive with the ones we already have. */
+ pa_sw_cvolume_divide(&nc->hw_volume, &s->real_volume, &s->soft_volume);
+
+ if (!s->thread_info.volume_changes && pa_cvolume_equal(&nc->hw_volume, &s->thread_info.current_hw_volume)) {
+ pa_log_debug("Volume not changing");
+ pa_source_volume_change_free(nc);
+ return;
+ }
+
+ nc->at = pa_source_get_latency_within_thread(s, false);
+ nc->at += pa_rtclock_now() + s->thread_info.volume_change_extra_delay;
+
+ if (s->thread_info.volume_changes_tail) {
+ for (c = s->thread_info.volume_changes_tail; c; c = c->prev) {
+ /* If volume is going up let's do it a bit late. If it is going
+ * down let's do it a bit early. */
+ if (pa_cvolume_avg(&nc->hw_volume) > pa_cvolume_avg(&c->hw_volume)) {
+ if (nc->at + safety_margin > c->at) {
+ nc->at += safety_margin;
+ direction = "up";
+ break;
+ }
+ }
+ else if (nc->at - safety_margin > c->at) {
+ nc->at -= safety_margin;
+ direction = "down";
+ break;
+ }
+ }
+ }
+
+ if (c == NULL) {
+ if (pa_cvolume_avg(&nc->hw_volume) > pa_cvolume_avg(&s->thread_info.current_hw_volume)) {
+ nc->at += safety_margin;
+ direction = "up";
+ } else {
+ nc->at -= safety_margin;
+ direction = "down";
+ }
+ PA_LLIST_PREPEND(pa_source_volume_change, s->thread_info.volume_changes, nc);
+ }
+ else {
+ PA_LLIST_INSERT_AFTER(pa_source_volume_change, s->thread_info.volume_changes, c, nc);
+ }
+
+ pa_log_debug("Volume going %s to %d at %llu", direction, pa_cvolume_avg(&nc->hw_volume), (long long unsigned) nc->at);
+
+ /* We can ignore volume events that came earlier but should happen later than this. */
+ PA_LLIST_FOREACH_SAFE(c, pc, nc->next) {
+ pa_log_debug("Volume change to %d at %llu was dropped", pa_cvolume_avg(&c->hw_volume), (long long unsigned) c->at);
+ pa_source_volume_change_free(c);
+ }
+ nc->next = NULL;
+ s->thread_info.volume_changes_tail = nc;
+}
+
+/* Called from the IO thread. */
+static void pa_source_volume_change_flush(pa_source *s) {
+ pa_source_volume_change *c = s->thread_info.volume_changes;
+ pa_assert(s);
+ s->thread_info.volume_changes = NULL;
+ s->thread_info.volume_changes_tail = NULL;
+ while (c) {
+ pa_source_volume_change *next = c->next;
+ pa_source_volume_change_free(c);
+ c = next;
+ }
+}
+
+/* Called from the IO thread. */
+bool pa_source_volume_change_apply(pa_source *s, pa_usec_t *usec_to_next) {
+ pa_usec_t now;
+ bool ret = false;
+
+ pa_assert(s);
+
+ if (!s->thread_info.volume_changes || !PA_SOURCE_IS_LINKED(s->state)) {
+ if (usec_to_next)
+ *usec_to_next = 0;
+ return ret;
+ }
+
+ pa_assert(s->write_volume);
+
+ now = pa_rtclock_now();
+
+ while (s->thread_info.volume_changes && now >= s->thread_info.volume_changes->at) {
+ pa_source_volume_change *c = s->thread_info.volume_changes;
+ PA_LLIST_REMOVE(pa_source_volume_change, s->thread_info.volume_changes, c);
+ pa_log_debug("Volume change to %d at %llu was written %llu usec late",
+ pa_cvolume_avg(&c->hw_volume), (long long unsigned) c->at, (long long unsigned) (now - c->at));
+ ret = true;
+ s->thread_info.current_hw_volume = c->hw_volume;
+ pa_source_volume_change_free(c);
+ }
+
+ if (ret)
+ s->write_volume(s);
+
+ if (s->thread_info.volume_changes) {
+ if (usec_to_next)
+ *usec_to_next = s->thread_info.volume_changes->at - now;
+ if (pa_log_ratelimit(PA_LOG_DEBUG))
+ pa_log_debug("Next volume change in %lld usec", (long long) (s->thread_info.volume_changes->at - now));
+ }
+ else {
+ if (usec_to_next)
+ *usec_to_next = 0;
+ s->thread_info.volume_changes_tail = NULL;
+ }
+ return ret;
+}
+
+/* Called from the main thread */
+/* Gets the list of formats supported by the source. The members and idxset must
+ * be freed by the caller. */
+pa_idxset* pa_source_get_formats(pa_source *s) {
+ pa_idxset *ret;
+
+ pa_assert(s);
+
+ if (s->get_formats) {
+ /* Source supports format query, all is good */
+ ret = s->get_formats(s);
+ } else {
+ /* Source doesn't support format query, so assume it does PCM */
+ pa_format_info *f = pa_format_info_new();
+ f->encoding = PA_ENCODING_PCM;
+
+ ret = pa_idxset_new(NULL, NULL);
+ pa_idxset_put(ret, f, NULL);
+ }
+
+ return ret;
+}
+
+/* Called from the main thread */
+/* Checks if the source can accept this format */
+bool pa_source_check_format(pa_source *s, pa_format_info *f) {
+ pa_idxset *formats = NULL;
+ bool ret = false;
+
+ pa_assert(s);
+ pa_assert(f);
+
+ formats = pa_source_get_formats(s);
+
+ if (formats) {
+ pa_format_info *finfo_device;
+ uint32_t i;
+
+ PA_IDXSET_FOREACH(finfo_device, formats, i) {
+ if (pa_format_info_is_compatible(finfo_device, f)) {
+ ret = true;
+ break;
+ }
+ }
+
+ pa_idxset_free(formats, (pa_free_cb_t) pa_format_info_free);
+ }
+
+ return ret;
+}
+
+/* Called from the main thread */
+/* Calculates the intersection between formats supported by the source and
+ * in_formats, and returns these, in the order of the source's formats. */
+pa_idxset* pa_source_check_formats(pa_source *s, pa_idxset *in_formats) {
+ pa_idxset *out_formats = pa_idxset_new(NULL, NULL), *source_formats = NULL;
+ pa_format_info *f_source, *f_in;
+ uint32_t i, j;
+
+ pa_assert(s);
+
+ if (!in_formats || pa_idxset_isempty(in_formats))
+ goto done;
+
+ source_formats = pa_source_get_formats(s);
+
+ PA_IDXSET_FOREACH(f_source, source_formats, i) {
+ PA_IDXSET_FOREACH(f_in, in_formats, j) {
+ if (pa_format_info_is_compatible(f_source, f_in))
+ pa_idxset_put(out_formats, pa_format_info_copy(f_in), NULL);
+ }
+ }
+
+done:
+ if (source_formats)
+ pa_idxset_free(source_formats, (pa_free_cb_t) pa_format_info_free);
+
+ return out_formats;
+}
+
+/* Called from the main thread */
+void pa_source_set_sample_format(pa_source *s, pa_sample_format_t format) {
+ pa_sample_format_t old_format;
+
+ pa_assert(s);
+ pa_assert(pa_sample_format_valid(format));
+
+ old_format = s->sample_spec.format;
+ if (old_format == format)
+ return;
+
+ pa_log_info("%s: format: %s -> %s",
+ s->name, pa_sample_format_to_string(old_format), pa_sample_format_to_string(format));
+
+ s->sample_spec.format = format;
+
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE | PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+}
+
+/* Called from the main thread */
+void pa_source_set_sample_rate(pa_source *s, uint32_t rate) {
+ uint32_t old_rate;
+
+ pa_assert(s);
+ pa_assert(pa_sample_rate_valid(rate));
+
+ old_rate = s->sample_spec.rate;
+ if (old_rate == rate)
+ return;
+
+ pa_log_info("%s: rate: %u -> %u", s->name, old_rate, rate);
+
+ s->sample_spec.rate = rate;
+
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE | PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+}
+
+/* Called from the main thread. */
+void pa_source_set_reference_volume_direct(pa_source *s, const pa_cvolume *volume) {
+ pa_cvolume old_volume;
+ char old_volume_str[PA_CVOLUME_SNPRINT_VERBOSE_MAX];
+ char new_volume_str[PA_CVOLUME_SNPRINT_VERBOSE_MAX];
+
+ pa_assert(s);
+ pa_assert(volume);
+
+ old_volume = s->reference_volume;
+
+ if (pa_cvolume_equal(volume, &old_volume))
+ return;
+
+ s->reference_volume = *volume;
+ pa_log_debug("The reference volume of source %s changed from %s to %s.", s->name,
+ pa_cvolume_snprint_verbose(old_volume_str, sizeof(old_volume_str), &old_volume, &s->channel_map,
+ s->flags & PA_SOURCE_DECIBEL_VOLUME),
+ pa_cvolume_snprint_verbose(new_volume_str, sizeof(new_volume_str), volume, &s->channel_map,
+ s->flags & PA_SOURCE_DECIBEL_VOLUME));
+
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+ pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_VOLUME_CHANGED], s);
+}
+
+void pa_source_move_streams_to_default_source(pa_core *core, pa_source *old_source, bool default_source_changed) {
+ pa_source_output *o;
+ uint32_t idx;
+
+ pa_assert(core);
+ pa_assert(old_source);
+
+ if (core->state == PA_CORE_SHUTDOWN)
+ return;
+
+ if (core->default_source == NULL || core->default_source->unlink_requested)
+ return;
+
+ if (old_source == core->default_source)
+ return;
+
+ PA_IDXSET_FOREACH(o, old_source->outputs, idx) {
+ if (!PA_SOURCE_OUTPUT_IS_LINKED(o->state))
+ continue;
+
+ if (!o->source)
+ continue;
+
+ /* Don't move source-outputs which connect sources to filter sources */
+ if (o->destination_source)
+ continue;
+
+ /* If default_source_changed is false, the old source became unavailable, so all streams must be moved. */
+ if (pa_safe_streq(old_source->name, o->preferred_source) && default_source_changed)
+ continue;
+
+ if (!pa_source_output_may_move_to(o, core->default_source))
+ continue;
+
+ if (default_source_changed)
+ pa_log_info("The source output %u \"%s\" is moving to %s due to change of the default source.",
+ o->index, pa_strnull(pa_proplist_gets(o->proplist, PA_PROP_APPLICATION_NAME)), core->default_source->name);
+ else
+ pa_log_info("The source output %u \"%s\" is moving to %s, because the old source became unavailable.",
+ o->index, pa_strnull(pa_proplist_gets(o->proplist, PA_PROP_APPLICATION_NAME)), core->default_source->name);
+
+ pa_source_output_move_to(o, core->default_source, false);
+ }
+}
diff --git a/src/pulsecore/source.h b/src/pulsecore/source.h
new file mode 100644
index 0000000..aa45e6d
--- /dev/null
+++ b/src/pulsecore/source.h
@@ -0,0 +1,493 @@
+#ifndef foopulsesourcehfoo
+#define foopulsesourcehfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+
+#include <inttypes.h>
+
+#include <pulsecore/typedefs.h>
+#include <pulse/def.h>
+#include <pulse/format.h>
+#include <pulse/sample.h>
+#include <pulse/channelmap.h>
+#include <pulse/volume.h>
+
+#include <pulsecore/core.h>
+#include <pulsecore/idxset.h>
+#include <pulsecore/memchunk.h>
+#include <pulsecore/sink.h>
+#include <pulsecore/module.h>
+#include <pulsecore/asyncmsgq.h>
+#include <pulsecore/msgobject.h>
+#include <pulsecore/rtpoll.h>
+#include <pulsecore/card.h>
+#include <pulsecore/device-port.h>
+#include <pulsecore/queue.h>
+#include <pulsecore/thread-mq.h>
+#include <pulsecore/source-output.h>
+
+#define PA_MAX_OUTPUTS_PER_SOURCE 256
+
+/* Returns true if source is linked: registered and accessible from client side. */
+static inline bool PA_SOURCE_IS_LINKED(pa_source_state_t x) {
+ return x == PA_SOURCE_RUNNING || x == PA_SOURCE_IDLE || x == PA_SOURCE_SUSPENDED;
+}
+
+/* A generic definition for void callback functions */
+typedef void(*pa_source_cb_t)(pa_source *s);
+
+typedef int (*pa_source_get_mute_cb_t)(pa_source *s, bool *mute);
+
+struct pa_source {
+ pa_msgobject parent;
+
+ uint32_t index;
+ pa_core *core;
+
+ pa_source_state_t state;
+
+ /* Set in the beginning of pa_source_unlink() before setting the source
+ * state to UNLINKED. The purpose is to prevent moving streams to a source
+ * that is about to be removed. */
+ bool unlink_requested;
+
+ pa_source_flags_t flags;
+ pa_suspend_cause_t suspend_cause;
+
+ char *name;
+ char *driver; /* may be NULL */
+ pa_proplist *proplist;
+
+ pa_module *module; /* may be NULL */
+ pa_card *card; /* may be NULL */
+
+ pa_sample_spec sample_spec;
+ pa_channel_map channel_map;
+ uint32_t default_sample_rate;
+ uint32_t alternate_sample_rate;
+ bool avoid_resampling:1;
+
+ pa_idxset *outputs;
+ unsigned n_corked;
+ pa_sink *monitor_of; /* may be NULL */
+ pa_source_output *output_from_master; /* non-NULL only for filter sources */
+
+ pa_volume_t base_volume; /* shall be constant */
+ unsigned n_volume_steps; /* shall be constant */
+
+ /* Also see http://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/Developer/Volumes/ */
+ pa_cvolume reference_volume; /* The volume exported and taken as reference base for relative source output volumes */
+ pa_cvolume real_volume; /* The volume that the hardware is configured to */
+ pa_cvolume soft_volume; /* The internal software volume we apply to all PCM data while it passes through */
+
+ bool muted:1;
+
+ bool refresh_volume:1;
+ bool refresh_muted:1;
+ bool save_port:1;
+ bool save_volume:1;
+ bool save_muted:1;
+
+ /* Saved volume state while we're in passthrough mode */
+ pa_cvolume saved_volume;
+ bool saved_save_volume:1;
+
+ pa_asyncmsgq *asyncmsgq;
+
+ pa_memchunk silence;
+
+ pa_hashmap *ports;
+ pa_device_port *active_port;
+
+ /* The latency offset is inherited from the currently active port */
+ int64_t port_latency_offset;
+
+ unsigned priority;
+
+ bool set_mute_in_progress;
+
+ /* Callbacks for doing things when the source state and/or suspend cause is
+ * changed. It's fine to set either or both of the callbacks to NULL if the
+ * implementation doesn't have anything to do on state or suspend cause
+ * changes.
+ *
+ * set_state_in_main_thread() is called first. The callback is allowed to
+ * report failure if and only if the source changes its state from
+ * SUSPENDED to IDLE or RUNNING. (FIXME: It would make sense to allow
+ * failure also when changing state from INIT to IDLE or RUNNING, but
+ * currently that will crash pa_source_put().) If
+ * set_state_in_main_thread() fails, set_state_in_io_thread() won't be
+ * called.
+ *
+ * If set_state_in_main_thread() is successful (or not set), then
+ * set_state_in_io_thread() is called. Again, failure is allowed if and
+ * only if the source changes state from SUSPENDED to IDLE or RUNNING. If
+ * set_state_in_io_thread() fails, then set_state_in_main_thread() is
+ * called again, this time with the state parameter set to SUSPENDED and
+ * the suspend_cause parameter set to 0.
+ *
+ * pa_source.state, pa_source.thread_info.state and pa_source.suspend_cause
+ * are updated only after all the callback calls. In case of failure, the
+ * state is set to SUSPENDED and the suspend cause is set to 0. */
+ int (*set_state_in_main_thread)(pa_source *s, pa_source_state_t state, pa_suspend_cause_t suspend_cause); /* may be NULL */
+ int (*set_state_in_io_thread)(pa_source *s, pa_source_state_t state, pa_suspend_cause_t suspend_cause); /* may be NULL */
+
+ /* Called when the volume is queried. Called from main loop
+ * context. If this is NULL a PA_SOURCE_MESSAGE_GET_VOLUME message
+ * will be sent to the IO thread instead. If refresh_volume is
+ * false neither this function is called nor a message is sent.
+ *
+ * You must use the function pa_source_set_get_volume_callback() to
+ * set this callback. */
+ pa_source_cb_t get_volume; /* may be NULL */
+
+ /* Called when the volume shall be changed. Called from main loop
+ * context. If this is NULL a PA_SOURCE_MESSAGE_SET_VOLUME message
+ * will be sent to the IO thread instead.
+ *
+ * You must use the function pa_source_set_set_volume_callback() to
+ * set this callback. */
+ pa_source_cb_t set_volume; /* may be NULL */
+
+ /* Source drivers that set PA_SOURCE_DEFERRED_VOLUME must provide this
+ * callback. This callback is not used with source that do not set
+ * PA_SOURCE_DEFERRED_VOLUME. This is called from the IO thread when a
+ * pending hardware volume change has to be written to the
+ * hardware. The requested volume is passed to the callback
+ * implementation in s->thread_info.current_hw_volume.
+ *
+ * The call is done inside pa_source_volume_change_apply(), which is
+ * not called automatically - it is the driver's responsibility to
+ * schedule that function to be called at the right times in the
+ * IO thread.
+ *
+ * You must use the function pa_source_set_write_volume_callback() to
+ * set this callback. */
+ pa_source_cb_t write_volume; /* may be NULL */
+
+ /* If the source mute can change "spontaneously" (i.e. initiated by the
+ * source implementation, not by someone else calling
+ * pa_source_set_mute()), then the source implementation can notify about
+ * changed mute either by calling pa_source_mute_changed() or by calling
+ * pa_source_get_mute() with force_refresh=true. If the implementation
+ * chooses the latter approach, it should implement the get_mute callback.
+ * Otherwise get_mute can be NULL.
+ *
+ * This is called when pa_source_get_mute() is called with
+ * force_refresh=true. This is called from the IO thread if the
+ * PA_SINK_DEFERRED_VOLUME flag is set, otherwise this is called from the
+ * main thread. On success, the implementation is expected to return 0 and
+ * set the mute parameter that is passed as a reference. On failure, the
+ * implementation is expected to return -1.
+ *
+ * You must use the function pa_source_set_get_mute_callback() to
+ * set this callback. */
+ pa_source_get_mute_cb_t get_mute;
+
+ /* Called when the mute setting shall be changed. Called from main
+ * loop context. If this is NULL a PA_SOURCE_MESSAGE_SET_MUTE
+ * message will be sent to the IO thread instead.
+ *
+ * You must use the function pa_source_set_set_mute_callback() to
+ * set this callback. */
+ pa_source_cb_t set_mute; /* may be NULL */
+
+ /* Called when a the requested latency is changed. Called from IO
+ * thread context. */
+ pa_source_cb_t update_requested_latency; /* may be NULL */
+
+ /* Called whenever the port shall be changed. Called from the main
+ * thread. */
+ int (*set_port)(pa_source *s, pa_device_port *port); /*ditto */
+
+ /* Called to get the list of formats supported by the source, sorted
+ * in descending order of preference. */
+ pa_idxset* (*get_formats)(pa_source *s); /* ditto */
+
+ /* Called whenever device parameters need to be changed. Called from
+ * main thread. */
+ void (*reconfigure)(pa_source *s, pa_sample_spec *spec, bool passthrough);
+
+ /* Contains copies of the above data so that the real-time worker
+ * thread can work without access locking */
+ struct {
+ pa_source_state_t state;
+ pa_hashmap *outputs;
+
+ pa_rtpoll *rtpoll;
+
+ pa_cvolume soft_volume;
+ bool soft_muted:1;
+
+ bool requested_latency_valid:1;
+ pa_usec_t requested_latency;
+
+ /* Then number of bytes this source will be rewound for at
+ * max. (Only used on monitor sources) */
+ size_t max_rewind;
+
+ pa_usec_t min_latency; /* we won't go below this latency */
+ pa_usec_t max_latency; /* An upper limit for the latencies */
+
+ pa_usec_t fixed_latency; /* for sources with PA_SOURCE_DYNAMIC_LATENCY this is 0 */
+
+ /* This latency offset is a direct copy from s->port_latency_offset */
+ int64_t port_latency_offset;
+
+ /* Delayed volume change events are queued here. The events
+ * are stored in expiration order. The one expiring next is in
+ * the head of the list. */
+ PA_LLIST_HEAD(pa_source_volume_change, volume_changes);
+ pa_source_volume_change *volume_changes_tail;
+ /* This value is updated in pa_source_volume_change_apply() and
+ * used only by sources with PA_SOURCE_DEFERRED_VOLUME. */
+ pa_cvolume current_hw_volume;
+
+ /* The amount of usec volume up events are delayed and volume
+ * down events are made earlier. */
+ uint32_t volume_change_safety_margin;
+ /* Usec delay added to all volume change events, may be negative. */
+ int32_t volume_change_extra_delay;
+ } thread_info;
+
+ void *userdata;
+};
+
+PA_DECLARE_PUBLIC_CLASS(pa_source);
+#define PA_SOURCE(s) pa_source_cast(s)
+
+typedef enum pa_source_message {
+ PA_SOURCE_MESSAGE_ADD_OUTPUT,
+ PA_SOURCE_MESSAGE_REMOVE_OUTPUT,
+ PA_SOURCE_MESSAGE_GET_VOLUME,
+ PA_SOURCE_MESSAGE_SET_SHARED_VOLUME,
+ PA_SOURCE_MESSAGE_SET_VOLUME_SYNCED,
+ PA_SOURCE_MESSAGE_SET_VOLUME,
+ PA_SOURCE_MESSAGE_SYNC_VOLUMES,
+ PA_SOURCE_MESSAGE_GET_MUTE,
+ PA_SOURCE_MESSAGE_SET_MUTE,
+ PA_SOURCE_MESSAGE_GET_LATENCY,
+ PA_SOURCE_MESSAGE_GET_REQUESTED_LATENCY,
+ PA_SOURCE_MESSAGE_SET_STATE,
+ PA_SOURCE_MESSAGE_SET_LATENCY_RANGE,
+ PA_SOURCE_MESSAGE_GET_LATENCY_RANGE,
+ PA_SOURCE_MESSAGE_SET_FIXED_LATENCY,
+ PA_SOURCE_MESSAGE_GET_FIXED_LATENCY,
+ PA_SOURCE_MESSAGE_GET_MAX_REWIND,
+ PA_SOURCE_MESSAGE_SET_MAX_REWIND,
+ PA_SOURCE_MESSAGE_UPDATE_VOLUME_AND_MUTE,
+ PA_SOURCE_MESSAGE_SET_PORT_LATENCY_OFFSET,
+ PA_SOURCE_MESSAGE_MAX
+} pa_source_message_t;
+
+typedef struct pa_source_new_data {
+ pa_suspend_cause_t suspend_cause;
+
+ char *name;
+ pa_proplist *proplist;
+
+ const char *driver;
+ pa_module *module;
+ pa_card *card;
+
+ pa_hashmap *ports;
+ char *active_port;
+
+ pa_sample_spec sample_spec;
+ pa_channel_map channel_map;
+ uint32_t alternate_sample_rate;
+ bool avoid_resampling:1;
+ pa_cvolume volume;
+ bool muted:1;
+
+ bool volume_is_set:1;
+ bool muted_is_set:1;
+ bool sample_spec_is_set:1;
+ bool channel_map_is_set:1;
+ bool alternate_sample_rate_is_set:1;
+ bool avoid_resampling_is_set:1;
+
+ bool namereg_fail:1;
+
+ bool save_port:1;
+ bool save_volume:1;
+ bool save_muted:1;
+} pa_source_new_data;
+
+pa_source_new_data* pa_source_new_data_init(pa_source_new_data *data);
+void pa_source_new_data_set_name(pa_source_new_data *data, const char *name);
+void pa_source_new_data_set_sample_spec(pa_source_new_data *data, const pa_sample_spec *spec);
+void pa_source_new_data_set_channel_map(pa_source_new_data *data, const pa_channel_map *map);
+void pa_source_new_data_set_alternate_sample_rate(pa_source_new_data *data, const uint32_t alternate_sample_rate);
+void pa_source_new_data_set_avoid_resampling(pa_source_new_data *data, bool avoid_resampling);
+void pa_source_new_data_set_volume(pa_source_new_data *data, const pa_cvolume *volume);
+void pa_source_new_data_set_muted(pa_source_new_data *data, bool mute);
+void pa_source_new_data_set_port(pa_source_new_data *data, const char *port);
+void pa_source_new_data_done(pa_source_new_data *data);
+
+/*** To be called exclusively by the source driver, from main context */
+
+pa_source* pa_source_new(
+ pa_core *core,
+ pa_source_new_data *data,
+ pa_source_flags_t flags);
+
+void pa_source_set_get_volume_callback(pa_source *s, pa_source_cb_t cb);
+void pa_source_set_set_volume_callback(pa_source *s, pa_source_cb_t cb);
+void pa_source_set_write_volume_callback(pa_source *s, pa_source_cb_t cb);
+void pa_source_set_get_mute_callback(pa_source *s, pa_source_get_mute_cb_t cb);
+void pa_source_set_set_mute_callback(pa_source *s, pa_source_cb_t cb);
+void pa_source_enable_decibel_volume(pa_source *s, bool enable);
+
+void pa_source_put(pa_source *s);
+void pa_source_unlink(pa_source *s);
+
+void pa_source_set_description(pa_source *s, const char *description);
+void pa_source_set_asyncmsgq(pa_source *s, pa_asyncmsgq *q);
+void pa_source_set_rtpoll(pa_source *s, pa_rtpoll *p);
+
+void pa_source_set_max_rewind(pa_source *s, size_t max_rewind);
+void pa_source_set_latency_range(pa_source *s, pa_usec_t min_latency, pa_usec_t max_latency);
+void pa_source_set_fixed_latency(pa_source *s, pa_usec_t latency);
+
+void pa_source_set_soft_volume(pa_source *s, const pa_cvolume *volume);
+void pa_source_volume_changed(pa_source *s, const pa_cvolume *new_volume);
+void pa_source_mute_changed(pa_source *s, bool new_muted);
+
+int pa_source_sync_suspend(pa_source *s);
+
+void pa_source_update_flags(pa_source *s, pa_source_flags_t mask, pa_source_flags_t value);
+
+/*** May be called by everyone, from main context */
+
+void pa_source_set_port_latency_offset(pa_source *s, int64_t offset);
+
+/* The returned value is supposed to be in the time domain of the sound card! */
+pa_usec_t pa_source_get_latency(pa_source *s);
+pa_usec_t pa_source_get_requested_latency(pa_source *s);
+void pa_source_get_latency_range(pa_source *s, pa_usec_t *min_latency, pa_usec_t *max_latency);
+pa_usec_t pa_source_get_fixed_latency(pa_source *s);
+
+size_t pa_source_get_max_rewind(pa_source *s);
+
+int pa_source_update_status(pa_source*s);
+int pa_source_suspend(pa_source *s, bool suspend, pa_suspend_cause_t cause);
+int pa_source_suspend_all(pa_core *c, bool suspend, pa_suspend_cause_t cause);
+
+/* Use this instead of checking s->flags & PA_SOURCE_FLAT_VOLUME directly. */
+bool pa_source_flat_volume_enabled(pa_source *s);
+
+/* Get the master source when sharing volumes */
+pa_source *pa_source_get_master(pa_source *s);
+
+bool pa_source_is_filter(pa_source *s);
+
+/* Is the source in passthrough mode? (that is, is this a monitor source for a sink
+ * that has a passthrough sink input connected to it. */
+bool pa_source_is_passthrough(pa_source *s);
+/* These should be called when a source enters/leaves passthrough mode */
+void pa_source_enter_passthrough(pa_source *s);
+void pa_source_leave_passthrough(pa_source *s);
+
+void pa_source_set_volume(pa_source *source, const pa_cvolume *volume, bool sendmsg, bool save);
+const pa_cvolume *pa_source_get_volume(pa_source *source, bool force_refresh);
+
+void pa_source_set_mute(pa_source *source, bool mute, bool save);
+bool pa_source_get_mute(pa_source *source, bool force_refresh);
+
+bool pa_source_update_proplist(pa_source *s, pa_update_mode_t mode, pa_proplist *p);
+
+int pa_source_set_port(pa_source *s, const char *name, bool save);
+
+void pa_source_reconfigure(pa_source *s, pa_sample_spec *spec, bool passthrough);
+
+unsigned pa_source_linked_by(pa_source *s); /* Number of connected streams */
+unsigned pa_source_used_by(pa_source *s); /* Number of connected streams that are not corked */
+
+/* Returns how many streams are active that don't allow suspensions. If
+ * "ignore" is non-NULL, that stream is not included in the count. */
+unsigned pa_source_check_suspend(pa_source *s, pa_source_output *ignore);
+
+const char *pa_source_state_to_string(pa_source_state_t state);
+
+/* Moves all inputs away, and stores them in pa_queue */
+pa_queue *pa_source_move_all_start(pa_source *s, pa_queue *q);
+void pa_source_move_all_finish(pa_source *s, pa_queue *q, bool save);
+void pa_source_move_all_fail(pa_queue *q);
+
+/* Returns a copy of the source formats. TODO: Get rid of this function (or at
+ * least get rid of the copying). There's no good reason to copy the formats
+ * every time someone wants to know what formats the source supports. The
+ * formats idxset could be stored directly in the pa_source struct.
+ * https://bugs.freedesktop.org/show_bug.cgi?id=71924 */
+pa_idxset* pa_source_get_formats(pa_source *s);
+
+bool pa_source_check_format(pa_source *s, pa_format_info *f);
+pa_idxset* pa_source_check_formats(pa_source *s, pa_idxset *in_formats);
+
+void pa_source_set_sample_format(pa_source *s, pa_sample_format_t format);
+void pa_source_set_sample_rate(pa_source *s, uint32_t rate);
+
+/*** To be called exclusively by the source driver, from IO context */
+
+void pa_source_post(pa_source*s, const pa_memchunk *chunk);
+void pa_source_post_direct(pa_source*s, pa_source_output *o, const pa_memchunk *chunk);
+void pa_source_process_rewind(pa_source *s, size_t nbytes);
+
+int pa_source_process_msg(pa_msgobject *o, int code, void *userdata, int64_t, pa_memchunk *chunk);
+
+void pa_source_attach_within_thread(pa_source *s);
+void pa_source_detach_within_thread(pa_source *s);
+
+pa_usec_t pa_source_get_requested_latency_within_thread(pa_source *s);
+
+void pa_source_set_max_rewind_within_thread(pa_source *s, size_t max_rewind);
+
+void pa_source_set_latency_range_within_thread(pa_source *s, pa_usec_t min_latency, pa_usec_t max_latency);
+void pa_source_set_fixed_latency_within_thread(pa_source *s, pa_usec_t latency);
+
+void pa_source_update_volume_and_mute(pa_source *s);
+
+bool pa_source_volume_change_apply(pa_source *s, pa_usec_t *usec_to_next);
+
+/*** To be called exclusively by source output drivers, from IO context */
+
+void pa_source_invalidate_requested_latency(pa_source *s, bool dynamic);
+int64_t pa_source_get_latency_within_thread(pa_source *s, bool allow_negative);
+
+/* Called from the main thread, from source-output.c only. The normal way to
+ * set the source reference volume is to call pa_source_set_volume(), but the
+ * flat volume logic in source-output.c needs also a function that doesn't do
+ * all the extra stuff that pa_source_set_volume() does. This function simply
+ * sets s->reference_volume and fires change notifications. */
+void pa_source_set_reference_volume_direct(pa_source *s, const pa_cvolume *volume);
+
+/* When the default_source is changed or the active_port of a source is changed to
+ * PA_AVAILABLE_NO, this function is called to move the streams of the old
+ * default_source or the source with active_port equals PA_AVAILABLE_NO to the
+ * current default_source conditionally*/
+void pa_source_move_streams_to_default_source(pa_core *core, pa_source *old_source, bool default_source_changed);
+
+#define pa_source_assert_io_context(s) \
+ pa_assert(pa_thread_mq_get() || !PA_SOURCE_IS_LINKED((s)->state))
+
+#endif
diff --git a/src/pulsecore/srbchannel.c b/src/pulsecore/srbchannel.c
new file mode 100644
index 0000000..4cdfaa7
--- /dev/null
+++ b/src/pulsecore/srbchannel.c
@@ -0,0 +1,379 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2014 David Henningsson, Canonical Ltd.
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "srbchannel.h"
+
+#include <pulsecore/atomic.h>
+#include <pulse/xmalloc.h>
+
+/* #define DEBUG_SRBCHANNEL */
+
+/* This ringbuffer might be useful in other contexts too, but
+ * right now it's only used inside the srbchannel, so let's keep it here
+ * for the time being. */
+typedef struct pa_ringbuffer pa_ringbuffer;
+
+struct pa_ringbuffer {
+ pa_atomic_t *count; /* amount of data in the buffer */
+ int capacity;
+ uint8_t *memory;
+ int readindex, writeindex;
+};
+
+static void *pa_ringbuffer_peek(pa_ringbuffer *r, int *count) {
+ int c = pa_atomic_load(r->count);
+
+ if (r->readindex + c > r->capacity)
+ *count = r->capacity - r->readindex;
+ else
+ *count = c;
+
+ return r->memory + r->readindex;
+}
+
+/* Returns true only if the buffer was completely full before the drop. */
+static bool pa_ringbuffer_drop(pa_ringbuffer *r, int count) {
+ bool b = pa_atomic_sub(r->count, count) >= r->capacity;
+
+ r->readindex += count;
+ r->readindex %= r->capacity;
+
+ return b;
+}
+
+static void *pa_ringbuffer_begin_write(pa_ringbuffer *r, int *count) {
+ int c = pa_atomic_load(r->count);
+
+ *count = PA_MIN(r->capacity - r->writeindex, r->capacity - c);
+
+ return r->memory + r->writeindex;
+}
+
+static void pa_ringbuffer_end_write(pa_ringbuffer *r, int count) {
+ pa_atomic_add(r->count, count);
+ r->writeindex += count;
+ r->writeindex %= r->capacity;
+}
+
+struct pa_srbchannel {
+ pa_ringbuffer rb_read, rb_write;
+ pa_fdsem *sem_read, *sem_write;
+ pa_memblock *memblock;
+
+ void *cb_userdata;
+ pa_srbchannel_cb_t callback;
+
+ pa_io_event *read_event;
+ pa_defer_event *defer_event;
+ pa_mainloop_api *mainloop;
+};
+
+/* We always listen to sem_read, and always signal on sem_write.
+ *
+ * This means we signal the same semaphore for two scenarios:
+ * 1) We have written something to our send buffer, and want the other
+ * side to read it
+ * 2) We have read something from our receive buffer that was previously
+ * completely full, and want the other side to continue writing
+*/
+
+size_t pa_srbchannel_write(pa_srbchannel *sr, const void *data, size_t l) {
+ size_t written = 0;
+
+ while (l > 0) {
+ int towrite;
+ void *ptr = pa_ringbuffer_begin_write(&sr->rb_write, &towrite);
+
+ if ((size_t) towrite > l)
+ towrite = l;
+
+ if (towrite == 0) {
+#ifdef DEBUG_SRBCHANNEL
+ pa_log("srbchannel output buffer full");
+#endif
+ break;
+ }
+
+ memcpy(ptr, data, towrite);
+ pa_ringbuffer_end_write(&sr->rb_write, towrite);
+ written += towrite;
+ data = (uint8_t*) data + towrite;
+ l -= towrite;
+ }
+#ifdef DEBUG_SRBCHANNEL
+ pa_log("Wrote %d bytes to srbchannel, signalling fdsem", (int) written);
+#endif
+
+ pa_fdsem_post(sr->sem_write);
+ return written;
+}
+
+size_t pa_srbchannel_read(pa_srbchannel *sr, void *data, size_t l) {
+ size_t isread = 0;
+
+ while (l > 0) {
+ int toread;
+ void *ptr = pa_ringbuffer_peek(&sr->rb_read, &toread);
+
+ if ((size_t) toread > l)
+ toread = l;
+
+ if (toread == 0)
+ break;
+
+ memcpy(data, ptr, toread);
+
+ if (pa_ringbuffer_drop(&sr->rb_read, toread)) {
+#ifdef DEBUG_SRBCHANNEL
+ pa_log("Read from full output buffer, signalling fdsem");
+#endif
+ pa_fdsem_post(sr->sem_write);
+ }
+
+ isread += toread;
+ data = (uint8_t*) data + toread;
+ l -= toread;
+ }
+
+#ifdef DEBUG_SRBCHANNEL
+ pa_log("Read %d bytes from srbchannel", (int) isread);
+#endif
+
+ return isread;
+}
+
+/* This is the memory layout of the ringbuffer shm block. It is followed by
+ read and write ringbuffer memory. */
+struct srbheader {
+ pa_atomic_t read_count;
+ pa_atomic_t write_count;
+
+ pa_fdsem_data read_semdata;
+ pa_fdsem_data write_semdata;
+
+ int capacity;
+ int readbuf_offset;
+ int writebuf_offset;
+
+ /* TODO: Maybe a marker here to make sure we talk to a server with equally sized struct */
+};
+
+static void srbchannel_rwloop(pa_srbchannel* sr) {
+ do {
+#ifdef DEBUG_SRBCHANNEL
+ int q;
+ pa_ringbuffer_peek(&sr->rb_read, &q);
+ pa_log("In rw loop from srbchannel, before callback, count = %d", q);
+#endif
+
+ if (sr->callback) {
+ if (!sr->callback(sr, sr->cb_userdata)) {
+#ifdef DEBUG_SRBCHANNEL
+ pa_log("Aborting read loop from srbchannel");
+#endif
+ return;
+ }
+ }
+
+#ifdef DEBUG_SRBCHANNEL
+ pa_ringbuffer_peek(&sr->rb_read, &q);
+ pa_log("In rw loop from srbchannel, after callback, count = %d", q);
+#endif
+
+ } while (pa_fdsem_before_poll(sr->sem_read) < 0);
+}
+
+static void semread_cb(pa_mainloop_api *m, pa_io_event *e, int fd, pa_io_event_flags_t events, void *userdata) {
+ pa_srbchannel* sr = userdata;
+
+ pa_fdsem_after_poll(sr->sem_read);
+ srbchannel_rwloop(sr);
+}
+
+static void defer_cb(pa_mainloop_api *m, pa_defer_event *e, void *userdata) {
+ pa_srbchannel* sr = userdata;
+
+#ifdef DEBUG_SRBCHANNEL
+ pa_log("Calling rw loop from deferred event");
+#endif
+
+ m->defer_enable(e, 0);
+ srbchannel_rwloop(sr);
+}
+
+pa_srbchannel* pa_srbchannel_new(pa_mainloop_api *m, pa_mempool *p) {
+ int capacity;
+ int readfd;
+ struct srbheader *srh;
+
+ pa_srbchannel* sr = pa_xmalloc0(sizeof(pa_srbchannel));
+ sr->mainloop = m;
+ sr->memblock = pa_memblock_new_pool(p, -1);
+ if (!sr->memblock)
+ goto fail;
+
+ srh = pa_memblock_acquire(sr->memblock);
+ pa_zero(*srh);
+
+ sr->rb_read.memory = (uint8_t*) srh + PA_ALIGN(sizeof(*srh));
+ srh->readbuf_offset = sr->rb_read.memory - (uint8_t*) srh;
+
+ capacity = (pa_memblock_get_length(sr->memblock) - srh->readbuf_offset) / 2;
+
+ sr->rb_write.memory = PA_ALIGN_PTR(sr->rb_read.memory + capacity);
+ srh->writebuf_offset = sr->rb_write.memory - (uint8_t*) srh;
+
+ capacity = PA_MIN(capacity, srh->writebuf_offset - srh->readbuf_offset);
+
+ pa_log_debug("SHM block is %d bytes, ringbuffer capacity is 2 * %d bytes",
+ (int) pa_memblock_get_length(sr->memblock), capacity);
+
+ srh->capacity = sr->rb_read.capacity = sr->rb_write.capacity = capacity;
+
+ sr->rb_read.count = &srh->read_count;
+ sr->rb_write.count = &srh->write_count;
+
+ sr->sem_read = pa_fdsem_new_shm(&srh->read_semdata);
+ if (!sr->sem_read)
+ goto fail;
+
+ sr->sem_write = pa_fdsem_new_shm(&srh->write_semdata);
+ if (!sr->sem_write)
+ goto fail;
+
+ readfd = pa_fdsem_get(sr->sem_read);
+
+#ifdef DEBUG_SRBCHANNEL
+ pa_log("Enabling io event on fd %d", readfd);
+#endif
+
+ sr->read_event = m->io_new(m, readfd, PA_IO_EVENT_INPUT, semread_cb, sr);
+ m->io_enable(sr->read_event, PA_IO_EVENT_INPUT);
+
+ return sr;
+
+fail:
+ pa_srbchannel_free(sr);
+
+ return NULL;
+}
+
+static void pa_srbchannel_swap(pa_srbchannel *sr) {
+ pa_srbchannel temp = *sr;
+
+ sr->sem_read = temp.sem_write;
+ sr->sem_write = temp.sem_read;
+ sr->rb_read = temp.rb_write;
+ sr->rb_write = temp.rb_read;
+}
+
+pa_srbchannel* pa_srbchannel_new_from_template(pa_mainloop_api *m, pa_srbchannel_template *t)
+{
+ int temp;
+ struct srbheader *srh;
+ pa_srbchannel* sr = pa_xmalloc0(sizeof(pa_srbchannel));
+
+ sr->mainloop = m;
+ sr->memblock = t->memblock;
+ pa_memblock_ref(sr->memblock);
+ srh = pa_memblock_acquire(sr->memblock);
+
+ sr->rb_read.capacity = sr->rb_write.capacity = srh->capacity;
+ sr->rb_read.count = &srh->read_count;
+ sr->rb_write.count = &srh->write_count;
+
+ sr->rb_read.memory = (uint8_t*) srh + srh->readbuf_offset;
+ sr->rb_write.memory = (uint8_t*) srh + srh->writebuf_offset;
+
+ sr->sem_read = pa_fdsem_open_shm(&srh->read_semdata, t->readfd);
+ if (!sr->sem_read)
+ goto fail;
+
+ sr->sem_write = pa_fdsem_open_shm(&srh->write_semdata, t->writefd);
+ if (!sr->sem_write)
+ goto fail;
+
+ pa_srbchannel_swap(sr);
+ temp = t->readfd; t->readfd = t->writefd; t->writefd = temp;
+
+#ifdef DEBUG_SRBCHANNEL
+ pa_log("Enabling io event on fd %d", t->readfd);
+#endif
+
+ sr->read_event = m->io_new(m, t->readfd, PA_IO_EVENT_INPUT, semread_cb, sr);
+ m->io_enable(sr->read_event, PA_IO_EVENT_INPUT);
+
+ return sr;
+
+fail:
+ pa_srbchannel_free(sr);
+
+ return NULL;
+}
+
+void pa_srbchannel_export(pa_srbchannel *sr, pa_srbchannel_template *t) {
+ t->memblock = sr->memblock;
+ t->readfd = pa_fdsem_get(sr->sem_read);
+ t->writefd = pa_fdsem_get(sr->sem_write);
+}
+
+void pa_srbchannel_set_callback(pa_srbchannel *sr, pa_srbchannel_cb_t callback, void *userdata) {
+ if (sr->callback)
+ pa_fdsem_after_poll(sr->sem_read);
+
+ sr->callback = callback;
+ sr->cb_userdata = userdata;
+
+ if (sr->callback) {
+ /* If there are events to be read already in the ringbuffer, we will not get any IO event for that,
+ because that's how pa_fdsem works. Therefore check the ringbuffer in a defer event instead. */
+ if (!sr->defer_event)
+ sr->defer_event = sr->mainloop->defer_new(sr->mainloop, defer_cb, sr);
+ sr->mainloop->defer_enable(sr->defer_event, 1);
+ }
+}
+
+void pa_srbchannel_free(pa_srbchannel *sr)
+{
+#ifdef DEBUG_SRBCHANNEL
+ pa_log("Freeing srbchannel");
+#endif
+ pa_assert(sr);
+
+ if (sr->defer_event)
+ sr->mainloop->defer_free(sr->defer_event);
+ if (sr->read_event)
+ sr->mainloop->io_free(sr->read_event);
+
+ if (sr->sem_read)
+ pa_fdsem_free(sr->sem_read);
+ if (sr->sem_write)
+ pa_fdsem_free(sr->sem_write);
+
+ if (sr->memblock) {
+ pa_memblock_release(sr->memblock);
+ pa_memblock_unref(sr->memblock);
+ }
+
+ pa_xfree(sr);
+}
diff --git a/src/pulsecore/srbchannel.h b/src/pulsecore/srbchannel.h
new file mode 100644
index 0000000..09193e0
--- /dev/null
+++ b/src/pulsecore/srbchannel.h
@@ -0,0 +1,57 @@
+#ifndef foopulsesrbchannelhfoo
+#define foopulsesrbchannelhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2014 David Henningsson, Canonical Ltd.
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulse/mainloop-api.h>
+#include <pulsecore/fdsem.h>
+#include <pulsecore/memblock.h>
+
+/* An shm ringbuffer that is used for low overhead server-client communication.
+ * Signaling is done through eventfd semaphores (pa_fdsem). */
+
+typedef struct pa_srbchannel pa_srbchannel;
+
+typedef struct pa_srbchannel_template {
+ int readfd, writefd;
+ pa_memblock *memblock;
+} pa_srbchannel_template;
+
+pa_srbchannel* pa_srbchannel_new(pa_mainloop_api *m, pa_mempool *p);
+/* Note: this creates a srbchannel with swapped read and write. */
+pa_srbchannel* pa_srbchannel_new_from_template(pa_mainloop_api *m, pa_srbchannel_template *t);
+
+void pa_srbchannel_free(pa_srbchannel *sr);
+
+void pa_srbchannel_export(pa_srbchannel *sr, pa_srbchannel_template *t);
+
+size_t pa_srbchannel_write(pa_srbchannel *sr, const void *data, size_t l);
+size_t pa_srbchannel_read(pa_srbchannel *sr, void *data, size_t l);
+
+/* Set the callback function that is called whenever data becomes available for reading.
+ * It can also be called if the output buffer was full and can now be written to.
+ *
+ * Return false to abort all processing (e g if the srbchannel has been freed during the callback).
+ * Otherwise return true.
+*/
+typedef bool (*pa_srbchannel_cb_t)(pa_srbchannel *sr, void *userdata);
+void pa_srbchannel_set_callback(pa_srbchannel *sr, pa_srbchannel_cb_t callback, void *userdata);
+
+#endif
diff --git a/src/pulsecore/start-child.c b/src/pulsecore/start-child.c
new file mode 100644
index 0000000..7f0719c
--- /dev/null
+++ b/src/pulsecore/start-child.c
@@ -0,0 +1,113 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2007 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <signal.h>
+
+#ifdef HAVE_SYS_PRCTL_H
+#include <sys/prctl.h>
+#endif
+#ifdef HAVE_SYS_RESOURCE_H
+#include <sys/resource.h>
+#endif
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/core-error.h>
+#include <pulsecore/pipe.h>
+
+#include "start-child.h"
+
+int pa_start_child_for_read(const char *name, const char *argv1, pid_t *pid) {
+#ifdef HAVE_FORK
+ pid_t child;
+ int pipe_fds[2] = { -1, -1 };
+
+ if (pipe(pipe_fds) < 0) {
+ pa_log("pipe() failed: %s", pa_cstrerror(errno));
+ goto fail;
+ }
+
+ if ((child = fork()) == (pid_t) -1) {
+ pa_log("fork() failed: %s", pa_cstrerror(errno));
+ goto fail;
+
+ } else if (child != 0) {
+
+ /* Parent */
+ pa_assert_se(pa_close(pipe_fds[1]) == 0);
+
+ if (pid)
+ *pid = child;
+
+ return pipe_fds[0];
+ } else {
+ /* child */
+
+ pa_reset_personality();
+
+ pa_assert_se(pa_close(pipe_fds[0]) == 0);
+ pa_assert_se(dup2(pipe_fds[1], STDOUT_FILENO) == STDOUT_FILENO);
+
+ if (pipe_fds[1] != STDOUT_FILENO)
+ pa_assert_se(pa_close(pipe_fds[1]) == 0);
+
+ pa_close(STDIN_FILENO);
+ pa_assert_se(open("/dev/null", O_RDONLY) == STDIN_FILENO);
+
+ pa_close(STDERR_FILENO);
+ pa_assert_se(open("/dev/null", O_WRONLY) == STDERR_FILENO);
+
+ pa_close_all(-1);
+ pa_reset_sigs(-1);
+ pa_unblock_sigs(-1);
+ pa_reset_priority();
+ pa_unset_env_recorded();
+
+ /* Make sure our children are not influenced by the
+ * LD_BIND_NOW we set for ourselves. */
+ pa_unset_env("LD_BIND_NOW");
+
+#ifdef PR_SET_PDEATHSIG
+ /* On Linux we can use PR_SET_PDEATHSIG to have the helper
+ process killed when the daemon dies abnormally. On non-Linux
+ machines the client will die as soon as it writes data to
+ stdout again (SIGPIPE) */
+
+ prctl(PR_SET_PDEATHSIG, SIGTERM, 0, 0, 0);
+#endif
+
+ execl(name, name, argv1, NULL);
+ _exit(1);
+ }
+
+fail:
+ pa_close_pipe(pipe_fds);
+#endif /* HAVE_FORK */
+
+ return -1;
+}
diff --git a/src/pulsecore/start-child.h b/src/pulsecore/start-child.h
new file mode 100644
index 0000000..6222f45
--- /dev/null
+++ b/src/pulsecore/start-child.h
@@ -0,0 +1,28 @@
+#ifndef foopulsestartchildhfoo
+#define foopulsestartchildhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2007 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/types.h>
+#include <unistd.h>
+
+int pa_start_child_for_read(const char *name, const char *argv1, pid_t *pid);
+
+#endif
diff --git a/src/pulsecore/strbuf.c b/src/pulsecore/strbuf.c
new file mode 100644
index 0000000..11f131b
--- /dev/null
+++ b/src/pulsecore/strbuf.c
@@ -0,0 +1,193 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sys/types.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdio.h>
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/macro.h>
+
+#include "strbuf.h"
+
+/* A chunk of the linked list that makes up the string */
+struct chunk {
+ struct chunk *next;
+ size_t length;
+};
+
+#define CHUNK_TO_TEXT(c) ((char*) (c) + PA_ALIGN(sizeof(struct chunk)))
+
+struct pa_strbuf {
+ size_t length;
+ struct chunk *head, *tail;
+};
+
+pa_strbuf *pa_strbuf_new(void) {
+ pa_strbuf *sb;
+
+ sb = pa_xnew(pa_strbuf, 1);
+ sb->length = 0;
+ sb->head = sb->tail = NULL;
+
+ return sb;
+}
+
+void pa_strbuf_free(pa_strbuf *sb) {
+ pa_assert(sb);
+
+ while (sb->head) {
+ struct chunk *c = sb->head;
+ sb->head = sb->head->next;
+ pa_xfree(c);
+ }
+
+ pa_xfree(sb);
+}
+
+/* Make a C string from the string buffer. The caller has to free
+ * string with pa_xfree(). */
+char *pa_strbuf_to_string(pa_strbuf *sb) {
+ char *t, *e;
+ struct chunk *c;
+
+ pa_assert(sb);
+
+ e = t = pa_xmalloc(sb->length+1);
+
+ for (c = sb->head; c; c = c->next) {
+ pa_assert((size_t) (e-t) <= sb->length);
+ memcpy(e, CHUNK_TO_TEXT(c), c->length);
+ e += c->length;
+ }
+
+ /* Trailing NUL */
+ *e = 0;
+
+ pa_assert(e == t+sb->length);
+
+ return t;
+}
+
+/* Combination of pa_strbuf_free() and pa_strbuf_to_string() */
+char *pa_strbuf_to_string_free(pa_strbuf *sb) {
+ char *t;
+
+ pa_assert(sb);
+ t = pa_strbuf_to_string(sb);
+ pa_strbuf_free(sb);
+
+ return t;
+}
+
+/* Append a string to the string buffer */
+void pa_strbuf_puts(pa_strbuf *sb, const char *t) {
+
+ pa_assert(sb);
+ pa_assert(t);
+
+ pa_strbuf_putsn(sb, t, strlen(t));
+}
+
+/* Append a character to the string buffer */
+void pa_strbuf_putc(pa_strbuf *sb, char c) {
+ pa_assert(sb);
+
+ pa_strbuf_putsn(sb, &c, 1);
+}
+
+/* Append a new chunk to the linked list */
+static void append(pa_strbuf *sb, struct chunk *c) {
+ pa_assert(sb);
+ pa_assert(c);
+
+ if (sb->tail) {
+ pa_assert(sb->head);
+ sb->tail->next = c;
+ } else {
+ pa_assert(!sb->head);
+ sb->head = c;
+ }
+
+ sb->tail = c;
+ sb->length += c->length;
+ c->next = NULL;
+}
+
+/* Append up to l bytes of a string to the string buffer */
+void pa_strbuf_putsn(pa_strbuf *sb, const char *t, size_t l) {
+ struct chunk *c;
+
+ pa_assert(sb);
+ pa_assert(t);
+
+ if (!l)
+ return;
+
+ c = pa_xmalloc(PA_ALIGN(sizeof(struct chunk)) + l);
+ c->length = l;
+ memcpy(CHUNK_TO_TEXT(c), t, l);
+
+ append(sb, c);
+}
+
+/* Append a printf() style formatted string to the string buffer. */
+/* The following is based on an example from the GNU libc documentation */
+size_t pa_strbuf_printf(pa_strbuf *sb, const char *format, ...) {
+ size_t size = 100;
+ struct chunk *c = NULL;
+
+ pa_assert(sb);
+ pa_assert(format);
+
+ for(;;) {
+ va_list ap;
+ int r;
+
+ c = pa_xrealloc(c, PA_ALIGN(sizeof(struct chunk)) + size);
+
+ va_start(ap, format);
+ r = vsnprintf(CHUNK_TO_TEXT(c), size, format, ap);
+ CHUNK_TO_TEXT(c)[size-1] = 0;
+ va_end(ap);
+
+ if (r > -1 && (size_t) r < size) {
+ c->length = (size_t) r;
+ append(sb, c);
+ return (size_t) r;
+ }
+
+ if (r > -1) /* glibc 2.1 */
+ size = (size_t) r+1;
+ else /* glibc 2.0 */
+ size *= 2;
+ }
+}
+
+bool pa_strbuf_isempty(pa_strbuf *sb) {
+ pa_assert(sb);
+
+ return sb->length <= 0;
+}
diff --git a/src/pulsecore/strbuf.h b/src/pulsecore/strbuf.h
new file mode 100644
index 0000000..469f6f7
--- /dev/null
+++ b/src/pulsecore/strbuf.h
@@ -0,0 +1,40 @@
+#ifndef foostrbufhfoo
+#define foostrbufhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulse/gccmacro.h>
+#include <pulsecore/macro.h>
+
+typedef struct pa_strbuf pa_strbuf;
+
+pa_strbuf *pa_strbuf_new(void);
+void pa_strbuf_free(pa_strbuf *sb);
+char *pa_strbuf_to_string(pa_strbuf *sb);
+char *pa_strbuf_to_string_free(pa_strbuf *sb);
+
+size_t pa_strbuf_printf(pa_strbuf *sb, const char *format, ...) PA_GCC_PRINTF_ATTR(2,3);
+void pa_strbuf_puts(pa_strbuf *sb, const char *t);
+void pa_strbuf_putsn(pa_strbuf *sb, const char *t, size_t m);
+void pa_strbuf_putc(pa_strbuf *sb, char c);
+
+bool pa_strbuf_isempty(pa_strbuf *sb);
+
+#endif
diff --git a/src/pulsecore/stream-util.c b/src/pulsecore/stream-util.c
new file mode 100644
index 0000000..2574501
--- /dev/null
+++ b/src/pulsecore/stream-util.c
@@ -0,0 +1,84 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2013 Intel Corporation
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "stream-util.h"
+
+#include <pulse/def.h>
+
+#include <pulsecore/core-format.h>
+#include <pulsecore/macro.h>
+
+int pa_stream_get_volume_channel_map(const pa_cvolume *volume, const pa_channel_map *original_map, const pa_format_info *format,
+ pa_channel_map *volume_map) {
+ int r;
+ pa_channel_map volume_map_local;
+
+ pa_assert(volume);
+ pa_assert(format);
+ pa_assert(volume_map);
+
+ if (original_map) {
+ if (volume->channels == original_map->channels) {
+ *volume_map = *original_map;
+ return 0;
+ }
+
+ if (volume->channels == 1) {
+ pa_channel_map_init_mono(volume_map);
+ return 0;
+ }
+
+ pa_log_info("Invalid stream parameters: the volume is incompatible with the channel map.");
+ return -PA_ERR_INVALID;
+ }
+
+ r = pa_format_info_get_channel_map(format, &volume_map_local);
+ if (r == -PA_ERR_NOENTITY) {
+ if (volume->channels == 1) {
+ pa_channel_map_init_mono(volume_map);
+ return 0;
+ }
+
+ pa_log_info("Invalid stream parameters: multi-channel volume is set, but channel map is not.");
+ return -PA_ERR_INVALID;
+ }
+
+ if (r < 0) {
+ pa_log_info("Invalid channel map.");
+ return -PA_ERR_INVALID;
+ }
+
+ if (volume->channels == volume_map_local.channels) {
+ *volume_map = volume_map_local;
+ return 0;
+ }
+
+ if (volume->channels == 1) {
+ pa_channel_map_init_mono(volume_map);
+ return 0;
+ }
+
+ pa_log_info("Invalid stream parameters: the volume is incompatible with the channel map.");
+
+ return -PA_ERR_INVALID;
+}
diff --git a/src/pulsecore/stream-util.h b/src/pulsecore/stream-util.h
new file mode 100644
index 0000000..b2e47fe
--- /dev/null
+++ b/src/pulsecore/stream-util.h
@@ -0,0 +1,48 @@
+#ifndef foostreamutilhfoo
+#define foostreamutilhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2013 Intel Corporation
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulse/format.h>
+#include <pulse/volume.h>
+
+/* This is a helper function that is called from pa_sink_input_new() and
+ * pa_source_output_new(). The job of this function is to figure out what
+ * channel map should be used for interpreting the volume that was set for the
+ * stream. The channel map that the client intended for the volume may be
+ * different than the final stream channel map, because the client may want the
+ * server to decide the stream channel map.
+ *
+ * volume is the volume for which the channel map should be figured out.
+ *
+ * original_map is the channel map that is set in the new data struct's
+ * channel_map field. If the channel map hasn't been set in the new data, then
+ * original_map should be NULL.
+ *
+ * format is the negotiated format for the stream. It's used as a fallback if
+ * original_map is not available.
+ *
+ * On success, the result is saved in volume_map. It's possible that this
+ * function fails to figure out the right channel map for the volume, in which
+ * case a negative error code is returned. */
+int pa_stream_get_volume_channel_map(const pa_cvolume *volume, const pa_channel_map *original_map, const pa_format_info *format,
+ pa_channel_map *volume_map);
+
+#endif
diff --git a/src/pulsecore/strlist.c b/src/pulsecore/strlist.c
new file mode 100644
index 0000000..7e5b070
--- /dev/null
+++ b/src/pulsecore/strlist.c
@@ -0,0 +1,171 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/strbuf.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/core-util.h>
+
+#include "strlist.h"
+
+struct pa_strlist {
+ pa_strlist *next;
+};
+
+#define ITEM_TO_TEXT(c) ((char*) (c) + PA_ALIGN(sizeof(pa_strlist)))
+
+pa_strlist* pa_strlist_prepend(pa_strlist *l, const char *s) {
+ pa_strlist *n;
+ size_t size;
+
+ pa_assert(s);
+ size = strlen(s);
+ n = pa_xmalloc(PA_ALIGN(sizeof(pa_strlist)) + size + 1);
+ memcpy(ITEM_TO_TEXT(n), s, size + 1);
+ n->next = l;
+
+ return n;
+}
+
+char *pa_strlist_to_string(pa_strlist *l) {
+ int first = 1;
+ pa_strbuf *b;
+
+ b = pa_strbuf_new();
+ for (; l; l = l->next) {
+ if (!first)
+ pa_strbuf_puts(b, " ");
+ first = 0;
+ pa_strbuf_puts(b, ITEM_TO_TEXT(l));
+ }
+
+ return pa_strbuf_to_string_free(b);
+}
+
+pa_strlist* pa_strlist_remove(pa_strlist *l, const char *s) {
+ pa_strlist *ret = l, *prev = NULL;
+
+ pa_assert(l);
+ pa_assert(s);
+
+ while (l) {
+ if (pa_streq(ITEM_TO_TEXT(l), s)) {
+ pa_strlist *n = l->next;
+
+ if (!prev) {
+ pa_assert(ret == l);
+ ret = n;
+ } else
+ prev->next = n;
+
+ pa_xfree(l);
+
+ l = n;
+
+ } else {
+ prev = l;
+ l = l->next;
+ }
+ }
+
+ return ret;
+}
+
+void pa_strlist_free(pa_strlist *l) {
+ while (l) {
+ pa_strlist *c = l;
+ l = l->next;
+ pa_xfree(c);
+ }
+}
+
+pa_strlist* pa_strlist_pop(pa_strlist *l, char **s) {
+ pa_strlist *r;
+
+ pa_assert(s);
+
+ if (!l) {
+ *s = NULL;
+ return NULL;
+ }
+
+ *s = pa_xstrdup(ITEM_TO_TEXT(l));
+ r = l->next;
+ pa_xfree(l);
+ return r;
+}
+
+pa_strlist* pa_strlist_parse(const char *s) {
+ pa_strlist *head = NULL, *p = NULL;
+ const char *state = NULL;
+ char *r;
+
+ while ((r = pa_split_spaces(s, &state))) {
+ pa_strlist *n;
+ size_t size = strlen(r);
+
+ n = pa_xmalloc(PA_ALIGN(sizeof(pa_strlist)) + size + 1);
+ n->next = NULL;
+ memcpy(ITEM_TO_TEXT(n), r, size+1);
+ pa_xfree(r);
+
+ if (p)
+ p->next = n;
+ else
+ head = n;
+
+ p = n;
+ }
+
+ return head;
+}
+
+pa_strlist *pa_strlist_reverse(pa_strlist *l) {
+ pa_strlist *r = NULL;
+
+ while (l) {
+ pa_strlist *n;
+
+ n = l->next;
+ l->next = r;
+ r = l;
+ l = n;
+ }
+
+ return r;
+}
+
+pa_strlist *pa_strlist_next(pa_strlist *s) {
+ pa_assert(s);
+
+ return s->next;
+}
+
+const char *pa_strlist_data(pa_strlist *s) {
+ pa_assert(s);
+
+ return ITEM_TO_TEXT(s);
+}
diff --git a/src/pulsecore/strlist.h b/src/pulsecore/strlist.h
new file mode 100644
index 0000000..3cc71e8
--- /dev/null
+++ b/src/pulsecore/strlist.h
@@ -0,0 +1,54 @@
+#ifndef foostrlisthfoo
+#define foostrlisthfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+typedef struct pa_strlist pa_strlist;
+
+/* Add the specified server string to the list, return the new linked list head */
+pa_strlist* pa_strlist_prepend(pa_strlist *l, const char *s);
+
+/* Remove the specified string from the list, return the new linked list head */
+pa_strlist* pa_strlist_remove(pa_strlist *l, const char *s);
+
+/* Make a whitespace separated string of all server strings. Returned memory has to be freed with pa_xfree() */
+char *pa_strlist_to_string(pa_strlist *l);
+
+/* Free the entire list */
+void pa_strlist_free(pa_strlist *l);
+
+/* Return the next entry in the list in *string and remove it from
+ * the list. Returns the new list head. The memory *string points to
+ * has to be freed with pa_xfree() */
+pa_strlist* pa_strlist_pop(pa_strlist *l, char **s);
+
+/* Parse a whitespace separated server list */
+pa_strlist* pa_strlist_parse(const char *s);
+
+/* Reverse string list */
+pa_strlist *pa_strlist_reverse(pa_strlist *l);
+
+/* Return the next item in the list */
+pa_strlist *pa_strlist_next(pa_strlist *s);
+
+/* Return the string associated to the current item */
+const char *pa_strlist_data(pa_strlist *s);
+
+#endif
diff --git a/src/pulsecore/svolume.orc b/src/pulsecore/svolume.orc
new file mode 100644
index 0000000..26f618e
--- /dev/null
+++ b/src/pulsecore/svolume.orc
@@ -0,0 +1,84 @@
+# This file is part of PulseAudio.
+#
+# Copyright 2010 Lennart Poettering
+# Copyright 2010 Wim Taymans <wim.taymans@collabora.co.uk>
+# Copyright 2010 Arun Raghavan <arun.raghavan@collabora.co.uk>
+#
+# PulseAudio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation; either version 2.1 of the License,
+# or (at your option) any later version.
+#
+# PulseAudio is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+
+# S16NE 1- and 2-channel volume scaling work as follows:
+#
+# params: samples s (signed 16-bit), volume v (signed 32-bit < 2^31)
+#
+# 32 16 0 (type of operation)
+# sample = | sample | (signed)
+# s = | 0 | sample | (unsigned)
+#
+# if (sample < 0)
+# signc = | 0 | 0xffff | (unsigned)
+# else
+# signc = | 0 | 0 | (unsigned)
+#
+# if (sample < 0)
+# ml = | 0 | -((s*vl) >> 16) | (unsigned)
+# else
+# ml = | 0 | (s*vl) >> 16 | (unsigned)
+#
+# vh = | v >> 16 | (signed, but sign bit is always zero
+# since PA_VOLUME_MAX is 0x0fffffff)
+# mh = | (s * vh) >> 16 | (signed)
+# ml = | ml + mh | (signed)
+# sample = | (ml >> 16) | (signed, saturated)
+
+.function pa_volume_s16ne_orc_1ch
+.dest 2 samples int16_t
+.param 4 vols int32_t
+.temp 4 v
+.temp 2 vh
+.temp 4 s
+.temp 4 mh
+.temp 4 ml
+.temp 4 signc
+
+loadpl v, vols
+convuwl s, samples
+x2 cmpgtsw signc, 0, s
+x2 andw signc, signc, v
+x2 mulhuw ml, s, v
+subl ml, ml, signc
+convhlw vh, v
+mulswl mh, samples, vh
+addl ml, ml, mh
+convssslw samples, ml
+
+.function pa_volume_s16ne_orc_2ch
+.dest 4 samples int16_t
+.longparam 8 vols
+.temp 8 v
+.temp 4 vh
+.temp 8 s
+.temp 8 mh
+.temp 8 ml
+.temp 8 signc
+
+loadpq v, vols
+x2 convuwl s, samples
+x4 cmpgtsw signc, 0, s
+x4 andw signc, signc, v
+x4 mulhuw ml, s, v
+x2 subl ml, ml, signc
+x2 convhlw vh, v
+x2 mulswl mh, samples, vh
+x2 addl ml, ml, mh
+x2 convssslw samples, ml
diff --git a/src/pulsecore/svolume_arm.c b/src/pulsecore/svolume_arm.c
new file mode 100644
index 0000000..fdef313
--- /dev/null
+++ b/src/pulsecore/svolume_arm.c
@@ -0,0 +1,167 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2009 Wim Taymans <wim.taymans@collabora.co.uk>
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulsecore/random.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/endianmacros.h>
+
+#include "cpu-arm.h"
+
+#include "sample-util.h"
+
+#if defined (__arm__) && defined (HAVE_ARMV6)
+
+#define MOD_INC() \
+ " subs r0, r6, %2 \n\t" \
+ " itt cs \n\t" \
+ " addcs r0, %1 \n\t" \
+ " movcs r6, r0 \n\t"
+
+static pa_do_volume_func_t _volume_ref;
+
+static void pa_volume_s16ne_arm(int16_t *samples, const int32_t *volumes, unsigned channels, unsigned length) {
+ /* Channels must be at least 4, and always a multiple of the original number.
+ * This is also the max amount we overread the volume array, which should
+ * have enough padding. */
+ const int32_t *ve = volumes + (channels == 3 ? 6 : PA_MAX (4U, channels));
+ unsigned rem = PA_ALIGN((size_t) samples) - (size_t) samples;
+
+ /* Make sure we're word-aligned, else performance _really_ sucks */
+ if (rem) {
+ _volume_ref(samples, volumes, channels, rem < length ? rem : length);
+
+ if (rem < length) {
+ length -= rem;
+ samples += rem / sizeof(*samples);
+ } else
+ return; /* we're done */
+ }
+
+ __asm__ __volatile__ (
+ " mov r6, %4 \n\t" /* r6 = volumes + rem */
+ " mov %3, %3, LSR #1 \n\t" /* length /= sizeof (int16_t) */
+
+ " cmp %3, #4 \n\t" /* check for 4+ samples */
+ " blt 2f \n\t"
+
+ /* See final case for how the multiplication works */
+
+ "1: \n\t"
+ " ldrd r2, [r6], #8 \n\t" /* 4 samples at a time */
+ " ldrd r4, [r6], #8 \n\t"
+ " ldrd r0, [%0] \n\t"
+
+#ifdef WORDS_BIGENDIAN
+ " smulwt r2, r2, r0 \n\t"
+ " smulwb r3, r3, r0 \n\t"
+ " smulwt r4, r4, r1 \n\t"
+ " smulwb r5, r5, r1 \n\t"
+#else
+ " smulwb r2, r2, r0 \n\t"
+ " smulwt r3, r3, r0 \n\t"
+ " smulwb r4, r4, r1 \n\t"
+ " smulwt r5, r5, r1 \n\t"
+#endif
+
+ " ssat r2, #16, r2 \n\t"
+ " ssat r3, #16, r3 \n\t"
+ " ssat r4, #16, r4 \n\t"
+ " ssat r5, #16, r5 \n\t"
+
+#ifdef WORDS_BIGENDIAN
+ " pkhbt r0, r3, r2, LSL #16 \n\t"
+ " pkhbt r1, r5, r4, LSL #16 \n\t"
+#else
+ " pkhbt r0, r2, r3, LSL #16 \n\t"
+ " pkhbt r1, r4, r5, LSL #16 \n\t"
+#endif
+ " strd r0, [%0], #8 \n\t"
+
+ MOD_INC()
+
+ " subs %3, %3, #4 \n\t"
+ " cmp %3, #4 \n\t"
+ " bge 1b \n\t"
+
+ "2: \n\t"
+ " cmp %3, #2 \n\t"
+ " blt 3f \n\t"
+
+ " ldrd r2, [r6], #8 \n\t" /* 2 samples at a time */
+ " ldr r0, [%0] \n\t"
+
+#ifdef WORDS_BIGENDIAN
+ " smulwt r2, r2, r0 \n\t"
+ " smulwb r3, r3, r0 \n\t"
+#else
+ " smulwb r2, r2, r0 \n\t"
+ " smulwt r3, r3, r0 \n\t"
+#endif
+
+ " ssat r2, #16, r2 \n\t"
+ " ssat r3, #16, r3 \n\t"
+
+#ifdef WORDS_BIGENDIAN
+ " pkhbt r0, r3, r2, LSL #16 \n\t"
+#else
+ " pkhbt r0, r2, r3, LSL #16 \n\t"
+#endif
+ " str r0, [%0], #4 \n\t"
+
+ MOD_INC()
+
+ " subs %3, %3, #2 \n\t"
+
+ "3: \n\t" /* check for odd # of samples */
+ " cmp %3, #1 \n\t"
+ " bne 4f \n\t"
+
+ " ldr r0, [r6], #4 \n\t" /* r0 = volume */
+ " ldrh r2, [%0] \n\t" /* r2 = sample */
+
+ " smulwb r0, r0, r2 \n\t" /* r0 = (r0 * r2) >> 16 */
+ " ssat r0, #16, r0 \n\t" /* r0 = PA_CLAMP(r0, 0x7FFF) */
+
+ " strh r0, [%0], #2 \n\t" /* sample = r0 */
+
+ "4: \n\t"
+
+ : "+r" (samples), "+r" (volumes), "+r" (ve), "+r" (length)
+ : "r" (volumes + ((rem / sizeof(*samples)) % channels))
+ : "r6", "r5", "r4", "r3", "r2", "r1", "r0", "cc"
+ );
+}
+
+#endif /* defined (__arm__) && defined (HAVE_ARMV6) */
+
+void pa_volume_func_init_arm(pa_cpu_arm_flag_t flags) {
+#if defined (__arm__) && defined (HAVE_ARMV6)
+ pa_log_info("Initialising ARM optimized volume functions.");
+
+ if (!_volume_ref)
+ _volume_ref = pa_get_volume_func(PA_SAMPLE_S16NE);
+
+ pa_set_volume_func(PA_SAMPLE_S16NE, (pa_do_volume_func_t) pa_volume_s16ne_arm);
+#endif /* defined (__arm__) && defined (HAVE_ARMV6) */
+}
diff --git a/src/pulsecore/svolume_c.c b/src/pulsecore/svolume_c.c
new file mode 100644
index 0000000..659b337
--- /dev/null
+++ b/src/pulsecore/svolume_c.c
@@ -0,0 +1,271 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulsecore/macro.h>
+#include <pulsecore/g711.h>
+#include <pulsecore/endianmacros.h>
+
+#include "sample-util.h"
+
+static void pa_volume_u8_c(uint8_t *samples, const int32_t *volumes, unsigned channels, unsigned length) {
+ unsigned channel;
+
+ for (channel = 0; length; length--) {
+ int32_t t = pa_mult_s16_volume(*samples - 0x80, volumes[channel]);
+
+ t = PA_CLAMP_UNLIKELY(t, -0x80, 0x7F);
+ *samples++ = (uint8_t) (t + 0x80);
+
+ if (PA_UNLIKELY(++channel >= channels))
+ channel = 0;
+ }
+}
+
+static void pa_volume_alaw_c(uint8_t *samples, const int32_t *volumes, unsigned channels, unsigned length) {
+ unsigned channel;
+
+ for (channel = 0; length; length--) {
+ int32_t t = pa_mult_s16_volume(st_alaw2linear16(*samples), volumes[channel]);
+
+ t = PA_CLAMP_UNLIKELY(t, -0x8000, 0x7FFF);
+ *samples++ = (uint8_t) st_13linear2alaw((int16_t) t >> 3);
+
+ if (PA_UNLIKELY(++channel >= channels))
+ channel = 0;
+ }
+}
+
+static void pa_volume_ulaw_c(uint8_t *samples, const int32_t *volumes, unsigned channels, unsigned length) {
+ unsigned channel;
+
+ for (channel = 0; length; length--) {
+ int32_t t = pa_mult_s16_volume(st_ulaw2linear16(*samples), volumes[channel]);
+
+ t = PA_CLAMP_UNLIKELY(t, -0x8000, 0x7FFF);
+ *samples++ = (uint8_t) st_14linear2ulaw((int16_t) t >> 2);
+
+ if (PA_UNLIKELY(++channel >= channels))
+ channel = 0;
+ }
+}
+
+static void pa_volume_s16ne_c(int16_t *samples, const int32_t *volumes, unsigned channels, unsigned length) {
+ unsigned channel;
+
+ length /= sizeof(int16_t);
+
+ for (channel = 0; length; length--) {
+ int32_t t = pa_mult_s16_volume(*samples, volumes[channel]);
+
+ t = PA_CLAMP_UNLIKELY(t, -0x8000, 0x7FFF);
+ *samples++ = (int16_t) t;
+
+ if (PA_UNLIKELY(++channel >= channels))
+ channel = 0;
+ }
+}
+
+static void pa_volume_s16re_c(int16_t *samples, const int32_t *volumes, unsigned channels, unsigned length) {
+ unsigned channel;
+
+ length /= sizeof(int16_t);
+
+ for (channel = 0; length; length--) {
+ int32_t t = pa_mult_s16_volume(PA_INT16_SWAP(*samples), volumes[channel]);
+
+ t = PA_CLAMP_UNLIKELY(t, -0x8000, 0x7FFF);
+ *samples++ = PA_INT16_SWAP((int16_t) t);
+
+ if (PA_UNLIKELY(++channel >= channels))
+ channel = 0;
+ }
+}
+
+static void pa_volume_float32ne_c(float *samples, const float *volumes, unsigned channels, unsigned length) {
+ unsigned channel;
+
+ length /= sizeof(float);
+
+ for (channel = 0; length; length--) {
+ *samples++ *= volumes[channel];
+
+ if (PA_UNLIKELY(++channel >= channels))
+ channel = 0;
+ }
+}
+
+static void pa_volume_float32re_c(float *samples, const float *volumes, unsigned channels, unsigned length) {
+ unsigned channel;
+
+ length /= sizeof(float);
+
+ for (channel = 0; length; length--) {
+ float t;
+
+ t = PA_READ_FLOAT32RE(samples);
+ t *= volumes[channel];
+ PA_WRITE_FLOAT32RE(samples++, t);
+
+ if (PA_UNLIKELY(++channel >= channels))
+ channel = 0;
+ }
+}
+
+static void pa_volume_s32ne_c(int32_t *samples, const int32_t *volumes, unsigned channels, unsigned length) {
+ unsigned channel;
+
+ length /= sizeof(int32_t);
+
+ for (channel = 0; length; length--) {
+ int64_t t;
+
+ t = (int64_t)(*samples);
+ t = (t * volumes[channel]) >> 16;
+ t = PA_CLAMP_UNLIKELY(t, -0x80000000LL, 0x7FFFFFFFLL);
+ *samples++ = (int32_t) t;
+
+ if (PA_UNLIKELY(++channel >= channels))
+ channel = 0;
+ }
+}
+
+static void pa_volume_s32re_c(int32_t *samples, const int32_t *volumes, unsigned channels, unsigned length) {
+ unsigned channel;
+
+ length /= sizeof(int32_t);
+
+ for (channel = 0; length; length--) {
+ int64_t t;
+
+ t = (int64_t) PA_INT32_SWAP(*samples);
+ t = (t * volumes[channel]) >> 16;
+ t = PA_CLAMP_UNLIKELY(t, -0x80000000LL, 0x7FFFFFFFLL);
+ *samples++ = PA_INT32_SWAP((int32_t) t);
+
+ if (PA_UNLIKELY(++channel >= channels))
+ channel = 0;
+ }
+}
+
+static void pa_volume_s24ne_c(uint8_t *samples, const int32_t *volumes, unsigned channels, unsigned length) {
+ unsigned channel;
+ uint8_t *e;
+
+ e = samples + length;
+
+ for (channel = 0; samples < e; samples += 3) {
+ int64_t t;
+
+ t = (int64_t)((int32_t) (PA_READ24NE(samples) << 8));
+ t = (t * volumes[channel]) >> 16;
+ t = PA_CLAMP_UNLIKELY(t, -0x80000000LL, 0x7FFFFFFFLL);
+ PA_WRITE24NE(samples, ((uint32_t) (int32_t) t) >> 8);
+
+ if (PA_UNLIKELY(++channel >= channels))
+ channel = 0;
+ }
+}
+
+static void pa_volume_s24re_c(uint8_t *samples, const int32_t *volumes, unsigned channels, unsigned length) {
+ unsigned channel;
+ uint8_t *e;
+
+ e = samples + length;
+
+ for (channel = 0; samples < e; samples += 3) {
+ int64_t t;
+
+ t = (int64_t)((int32_t) (PA_READ24RE(samples) << 8));
+ t = (t * volumes[channel]) >> 16;
+ t = PA_CLAMP_UNLIKELY(t, -0x80000000LL, 0x7FFFFFFFLL);
+ PA_WRITE24RE(samples, ((uint32_t) (int32_t) t) >> 8);
+
+ if (PA_UNLIKELY(++channel >= channels))
+ channel = 0;
+ }
+}
+
+static void pa_volume_s24_32ne_c(uint32_t *samples, const int32_t *volumes, unsigned channels, unsigned length) {
+ unsigned channel;
+
+ length /= sizeof(uint32_t);
+
+ for (channel = 0; length; length--) {
+ int64_t t;
+
+ t = (int64_t) ((int32_t) (*samples << 8));
+ t = (t * volumes[channel]) >> 16;
+ t = PA_CLAMP_UNLIKELY(t, -0x80000000LL, 0x7FFFFFFFLL);
+ *samples++ = ((uint32_t) ((int32_t) t)) >> 8;
+
+ if (PA_UNLIKELY(++channel >= channels))
+ channel = 0;
+ }
+}
+
+static void pa_volume_s24_32re_c(uint32_t *samples, const int32_t *volumes, unsigned channels, unsigned length) {
+ unsigned channel;
+
+ length /= sizeof(uint32_t);
+
+ for (channel = 0; length; length--) {
+ int64_t t;
+
+ t = (int64_t) ((int32_t) (PA_UINT32_SWAP(*samples) << 8));
+ t = (t * volumes[channel]) >> 16;
+ t = PA_CLAMP_UNLIKELY(t, -0x80000000LL, 0x7FFFFFFFLL);
+ *samples++ = PA_UINT32_SWAP(((uint32_t) ((int32_t) t)) >> 8);
+
+ if (PA_UNLIKELY(++channel >= channels))
+ channel = 0;
+ }
+}
+
+static pa_do_volume_func_t do_volume_table[] = {
+ [PA_SAMPLE_U8] = (pa_do_volume_func_t) pa_volume_u8_c,
+ [PA_SAMPLE_ALAW] = (pa_do_volume_func_t) pa_volume_alaw_c,
+ [PA_SAMPLE_ULAW] = (pa_do_volume_func_t) pa_volume_ulaw_c,
+ [PA_SAMPLE_S16NE] = (pa_do_volume_func_t) pa_volume_s16ne_c,
+ [PA_SAMPLE_S16RE] = (pa_do_volume_func_t) pa_volume_s16re_c,
+ [PA_SAMPLE_FLOAT32NE] = (pa_do_volume_func_t) pa_volume_float32ne_c,
+ [PA_SAMPLE_FLOAT32RE] = (pa_do_volume_func_t) pa_volume_float32re_c,
+ [PA_SAMPLE_S32NE] = (pa_do_volume_func_t) pa_volume_s32ne_c,
+ [PA_SAMPLE_S32RE] = (pa_do_volume_func_t) pa_volume_s32re_c,
+ [PA_SAMPLE_S24NE] = (pa_do_volume_func_t) pa_volume_s24ne_c,
+ [PA_SAMPLE_S24RE] = (pa_do_volume_func_t) pa_volume_s24re_c,
+ [PA_SAMPLE_S24_32NE] = (pa_do_volume_func_t) pa_volume_s24_32ne_c,
+ [PA_SAMPLE_S24_32RE] = (pa_do_volume_func_t) pa_volume_s24_32re_c
+};
+
+pa_do_volume_func_t pa_get_volume_func(pa_sample_format_t f) {
+ pa_assert(pa_sample_format_valid(f));
+
+ return do_volume_table[f];
+}
+
+void pa_set_volume_func(pa_sample_format_t f, pa_do_volume_func_t func) {
+ pa_assert(pa_sample_format_valid(f));
+
+ do_volume_table[f] = func;
+}
diff --git a/src/pulsecore/svolume_mmx.c b/src/pulsecore/svolume_mmx.c
new file mode 100644
index 0000000..c5848cc
--- /dev/null
+++ b/src/pulsecore/svolume_mmx.c
@@ -0,0 +1,252 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2009 Wim Taymans <wim.taymans@collabora.co.uk>
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulse/rtclock.h>
+
+#include <pulsecore/random.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/endianmacros.h>
+
+#include "cpu-x86.h"
+
+#include "sample-util.h"
+
+#if (!defined(__APPLE__) && !defined(__FreeBSD__) && !defined(__FreeBSD_kernel__) && defined (__i386__)) || defined (__amd64__)
+/* in s: 2 int16_t samples
+ * in v: 2 int32_t volumes, fixed point 16:16
+ * out s: contains scaled and clamped int16_t samples.
+ *
+ * We calculate the high 32 bits of a 32x16 multiply which we then
+ * clamp to 16 bits. The calculation is:
+ *
+ * vl = (v & 0xffff)
+ * vh = (v >> 16)
+ * s = ((s * vl) >> 16) + (s * vh);
+ *
+ * For the first multiply we have to do a sign correction as we need to
+ * multiply a signed int with an unsigned int. Hacker's delight 8-3 gives a
+ * simple formula to correct the sign of the high word after the signed
+ * multiply.
+ */
+#define VOLUME_32x16(s,v) /* .. | vh | vl | */ \
+ " pxor %%mm4, %%mm4 \n\t" /* .. | 0 | 0 | */ \
+ " punpcklwd %%mm4, "#s" \n\t" /* .. | 0 | p0 | */ \
+ " pcmpgtw "#v", %%mm4 \n\t" /* .. | 0 | s(vl) | */ \
+ " pand "#s", %%mm4 \n\t" /* .. | 0 | (p0) | (vl >> 15) & p */ \
+ " movq "#s", %%mm5 \n\t" \
+ " pmulhw "#v", "#s" \n\t" /* .. | 0 | vl*p0 | */ \
+ " paddw %%mm4, "#s" \n\t" /* .. | 0 | vl*p0 | + sign correct */ \
+ " pslld $16, "#s" \n\t" /* .. | vl*p0 | 0 | */ \
+ " psrld $16, "#v" \n\t" /* .. | 0 | vh | */ \
+ " psrad $16, "#s" \n\t" /* .. | vl*p0 | sign extend */ \
+ " pmaddwd %%mm5, "#v" \n\t" /* .. | p0 * vh | */ \
+ " paddd "#s", "#v" \n\t" /* .. | p0 * v0 | */ \
+ " packssdw "#v", "#v" \n\t" /* .. | p1*v1 | p0*v0 | */
+
+/* approximately advances %3 = (%3 + a) % b. This function requires that
+ * a <= b. */
+#define MOD_ADD(a,b) \
+ " add "#a", %3 \n\t" \
+ " mov %3, %4 \n\t" \
+ " sub "#b", %4 \n\t" \
+ " cmovae %4, %3 \n\t"
+
+/* swap 16 bits */
+#define SWAP_16(s) \
+ " movq "#s", %%mm4 \n\t" /* .. | h l | */ \
+ " psrlw $8, %%mm4 \n\t" /* .. | 0 h | */ \
+ " psllw $8, "#s" \n\t" /* .. | l 0 | */ \
+ " por %%mm4, "#s" \n\t" /* .. | l h | */
+
+/* swap 2 registers 16 bits for better pairing */
+#define SWAP_16_2(s1,s2) \
+ " movq "#s1", %%mm4 \n\t" /* .. | h l | */ \
+ " movq "#s2", %%mm5 \n\t" \
+ " psrlw $8, %%mm4 \n\t" /* .. | 0 h | */ \
+ " psrlw $8, %%mm5 \n\t" \
+ " psllw $8, "#s1" \n\t" /* .. | l 0 | */ \
+ " psllw $8, "#s2" \n\t" \
+ " por %%mm4, "#s1" \n\t" /* .. | l h | */ \
+ " por %%mm5, "#s2" \n\t"
+
+static void pa_volume_s16ne_mmx(int16_t *samples, const int32_t *volumes, unsigned channels, unsigned length) {
+ pa_reg_x86 channel, temp;
+
+ /* Channels must be at least 4, and always a multiple of the original number.
+ * This is also the max amount we overread the volume array, which should
+ * have enough padding. */
+ channels = channels == 3 ? 6 : PA_MAX (4U, channels);
+
+ __asm__ __volatile__ (
+ " xor %3, %3 \n\t"
+ " sar $1, %2 \n\t" /* length /= sizeof (int16_t) */
+
+ " test $1, %2 \n\t" /* check for odd samples */
+ " je 2f \n\t"
+
+ " movd (%q1, %3, 4), %%mm0 \n\t" /* | v0h | v0l | */
+ " movw (%0), %w4 \n\t" /* .. | p0 | */
+ " movd %4, %%mm1 \n\t"
+ VOLUME_32x16 (%%mm1, %%mm0)
+ " movd %%mm0, %4 \n\t" /* .. | p0*v0 | */
+ " movw %w4, (%0) \n\t"
+ " add $2, %0 \n\t"
+ MOD_ADD ($1, %5)
+
+ "2: \n\t"
+ " sar $1, %2 \n\t" /* prepare for processing 2 samples at a time */
+ " test $1, %2 \n\t" /* check for odd samples */
+ " je 4f \n\t"
+
+ "3: \n\t" /* do samples in groups of 2 */
+ " movq (%q1, %3, 4), %%mm0 \n\t" /* | v1h | v1l | v0h | v0l | */
+ " movd (%0), %%mm1 \n\t" /* .. | p1 | p0 | */
+ VOLUME_32x16 (%%mm1, %%mm0)
+ " movd %%mm0, (%0) \n\t" /* .. | p1*v1 | p0*v0 | */
+ " add $4, %0 \n\t"
+ MOD_ADD ($2, %5)
+
+ "4: \n\t"
+ " sar $1, %2 \n\t" /* prepare for processing 4 samples at a time */
+ " cmp $0, %2 \n\t"
+ " je 6f \n\t"
+
+ "5: \n\t" /* do samples in groups of 4 */
+ " movq (%q1, %3, 4), %%mm0 \n\t" /* | v1h | v1l | v0h | v0l | */
+ " movq 8(%q1, %3, 4), %%mm2 \n\t" /* | v3h | v3l | v2h | v2l | */
+ " movd (%0), %%mm1 \n\t" /* .. | p1 | p0 | */
+ " movd 4(%0), %%mm3 \n\t" /* .. | p3 | p2 | */
+ VOLUME_32x16 (%%mm1, %%mm0)
+ VOLUME_32x16 (%%mm3, %%mm2)
+ " movd %%mm0, (%0) \n\t" /* .. | p1*v1 | p0*v0 | */
+ " movd %%mm2, 4(%0) \n\t" /* .. | p3*v3 | p2*v2 | */
+ " add $8, %0 \n\t"
+ MOD_ADD ($4, %5)
+ " dec %2 \n\t"
+ " jne 5b \n\t"
+
+ "6: \n\t"
+ " emms \n\t"
+
+ : "+r" (samples), "+r" (volumes), "+r" (length), "=&D" (channel), "=&r" (temp)
+#if defined (__i386__)
+ : "m" (channels)
+#else
+ : "r" ((pa_reg_x86)channels)
+#endif
+ : "cc"
+ );
+}
+
+static void pa_volume_s16re_mmx(int16_t *samples, const int32_t *volumes, unsigned channels, unsigned length) {
+ pa_reg_x86 channel, temp;
+
+ /* Channels must be at least 4, and always a multiple of the original number.
+ * This is also the max amount we overread the volume array, which should
+ * have enough padding. */
+ channels = channels == 3 ? 6 : PA_MAX (4U, channels);
+
+ __asm__ __volatile__ (
+ " xor %3, %3 \n\t"
+ " sar $1, %2 \n\t" /* length /= sizeof (int16_t) */
+ " pcmpeqw %%mm6, %%mm6 \n\t" /* .. | ffff | ffff | */
+ " pcmpeqw %%mm7, %%mm7 \n\t" /* .. | ffff | ffff | */
+ " pslld $16, %%mm6 \n\t" /* .. | ffff | 0 | */
+ " psrld $31, %%mm7 \n\t" /* .. | 0 | 1 | */
+
+ " test $1, %2 \n\t" /* check for odd samples */
+ " je 2f \n\t"
+
+ " movd (%q1, %3, 4), %%mm0 \n\t" /* | v0h | v0l | */
+ " movw (%0), %w4 \n\t" /* .. | p0 | */
+ " rorw $8, %w4 \n\t"
+ " movd %4, %%mm1 \n\t"
+ VOLUME_32x16 (%%mm1, %%mm0)
+ " movd %%mm0, %4 \n\t" /* .. | p0*v0 | */
+ " rorw $8, %w4 \n\t"
+ " movw %w4, (%0) \n\t"
+ " add $2, %0 \n\t"
+ MOD_ADD ($1, %5)
+
+ "2: \n\t"
+ " sar $1, %2 \n\t" /* prepare for processing 2 samples at a time */
+ " test $1, %2 \n\t" /* check for odd samples */
+ " je 4f \n\t"
+
+ "3: \n\t" /* do samples in groups of 2 */
+ " movq (%q1, %3, 4), %%mm0 \n\t" /* | v1h | v1l | v0h | v0l | */
+ " movd (%0), %%mm1 \n\t" /* .. | p1 | p0 | */
+ SWAP_16 (%%mm1)
+ VOLUME_32x16 (%%mm1, %%mm0)
+ SWAP_16 (%%mm0)
+ " movd %%mm0, (%0) \n\t" /* .. | p1*v1 | p0*v0 | */
+ " add $4, %0 \n\t"
+ MOD_ADD ($2, %5)
+
+ "4: \n\t"
+ " sar $1, %2 \n\t" /* prepare for processing 4 samples at a time */
+ " cmp $0, %2 \n\t"
+ " je 6f \n\t"
+
+ "5: \n\t" /* do samples in groups of 4 */
+ " movq (%q1, %3, 4), %%mm0 \n\t" /* | v1h | v1l | v0h | v0l | */
+ " movq 8(%q1, %3, 4), %%mm2 \n\t" /* | v3h | v3l | v2h | v2l | */
+ " movd (%0), %%mm1 \n\t" /* .. | p1 | p0 | */
+ " movd 4(%0), %%mm3 \n\t" /* .. | p3 | p2 | */
+ SWAP_16_2 (%%mm1, %%mm3)
+ VOLUME_32x16 (%%mm1, %%mm0)
+ VOLUME_32x16 (%%mm3, %%mm2)
+ SWAP_16_2 (%%mm0, %%mm2)
+ " movd %%mm0, (%0) \n\t" /* .. | p1*v1 | p0*v0 | */
+ " movd %%mm2, 4(%0) \n\t" /* .. | p3*v3 | p2*v2 | */
+ " add $8, %0 \n\t"
+ MOD_ADD ($4, %5)
+ " dec %2 \n\t"
+ " jne 5b \n\t"
+
+ "6: \n\t"
+ " emms \n\t"
+
+ : "+r" (samples), "+r" (volumes), "+r" (length), "=&D" (channel), "=&r" (temp)
+#if defined (__i386__)
+ : "m" (channels)
+#else
+ : "r" ((pa_reg_x86)channels)
+#endif
+ : "cc"
+ );
+}
+
+#endif /* (!defined(__APPLE__) && !defined(__FreeBSD__) && !defined(__FreeBSD_kernel__) && defined (__i386__)) || defined (__amd64__) */
+
+void pa_volume_func_init_mmx(pa_cpu_x86_flag_t flags) {
+#if (!defined(__APPLE__) && !defined(__FreeBSD__) && !defined(__FreeBSD_kernel__) && defined (__i386__)) || defined (__amd64__)
+ if ((flags & PA_CPU_X86_MMX) && (flags & PA_CPU_X86_CMOV)) {
+ pa_log_info("Initialising MMX optimized volume functions.");
+
+ pa_set_volume_func(PA_SAMPLE_S16NE, (pa_do_volume_func_t) pa_volume_s16ne_mmx);
+ pa_set_volume_func(PA_SAMPLE_S16RE, (pa_do_volume_func_t) pa_volume_s16re_mmx);
+ }
+#endif /* (!defined(__APPLE__) && !defined(__FreeBSD__) && !defined(__FreeBSD_kernel__) && defined (__i386__)) || defined (__amd64__) */
+}
diff --git a/src/pulsecore/svolume_orc.c b/src/pulsecore/svolume_orc.c
new file mode 100644
index 0000000..730f817
--- /dev/null
+++ b/src/pulsecore/svolume_orc.c
@@ -0,0 +1,50 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2009 Wim Taymans <wim.taymans@collabora.co.uk>
+ Copyright 2010 Arun Raghavan <arun.raghavan@collabora.co.uk>
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "cpu-orc.h"
+#include <pulse/rtclock.h>
+#include <pulsecore/sample-util.h>
+#include <pulsecore/random.h>
+#include <pulsecore/svolume-orc-gen.h>
+
+pa_do_volume_func_t fallback;
+
+static void
+pa_volume_s16ne_orc(int16_t *samples, const int32_t *volumes, unsigned channels, unsigned length) {
+ if (channels == 2) {
+ int64_t v = (int64_t)volumes[1] << 32 | volumes[0];
+ pa_volume_s16ne_orc_2ch (samples, v, ((length / (sizeof(int16_t))) / 2));
+ } else if (channels == 1)
+ pa_volume_s16ne_orc_1ch (samples, volumes[0], length / (sizeof(int16_t)));
+ else
+ fallback(samples, volumes, channels, length);
+}
+
+void pa_volume_func_init_orc(void) {
+ pa_log_info("Initialising ORC optimized volume functions.");
+
+ fallback = pa_get_volume_func(PA_SAMPLE_S16NE);
+ pa_set_volume_func(PA_SAMPLE_S16NE, (pa_do_volume_func_t) pa_volume_s16ne_orc);
+}
diff --git a/src/pulsecore/svolume_sse.c b/src/pulsecore/svolume_sse.c
new file mode 100644
index 0000000..222ff18
--- /dev/null
+++ b/src/pulsecore/svolume_sse.c
@@ -0,0 +1,263 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2009 Wim Taymans <wim.taymans@collabora.co.uk>
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulse/rtclock.h>
+
+#include <pulsecore/random.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/endianmacros.h>
+
+#include "cpu-x86.h"
+
+#include "sample-util.h"
+
+#if (!defined(__APPLE__) && !defined(__FreeBSD__) && !defined(__FreeBSD_kernel__) && defined (__i386__)) || defined (__amd64__)
+
+#define VOLUME_32x16(s,v) /* .. | vh | vl | */ \
+ " pxor %%xmm4, %%xmm4 \n\t" /* .. | 0 | 0 | */ \
+ " punpcklwd %%xmm4, "#s" \n\t" /* .. | 0 | p0 | */ \
+ " pcmpgtw "#s", %%xmm4 \n\t" /* .. | 0 | s(p0) | */ \
+ " pand "#v", %%xmm4 \n\t" /* .. | 0 | (vl) | */ \
+ " movdqa "#s", %%xmm5 \n\t" \
+ " pmulhuw "#v", "#s" \n\t" /* .. | 0 | vl*p0 | */ \
+ " psubd %%xmm4, "#s" \n\t" /* .. | 0 | vl*p0 | + sign correct */ \
+ " psrld $16, "#v" \n\t" /* .. | 0 | vh | */ \
+ " pmaddwd %%xmm5, "#v" \n\t" /* .. | p0 * vh | */ \
+ " paddd "#s", "#v" \n\t" /* .. | p0 * v0 | */ \
+ " packssdw "#v", "#v" \n\t" /* .. | p1*v1 | p0*v0 | */
+
+#define MOD_ADD(a,b) \
+ " add "#a", %3 \n\t" /* channel += inc */ \
+ " mov %3, %4 \n\t" \
+ " sub "#b", %4 \n\t" /* tmp = channel - channels */ \
+ " cmovae %4, %3 \n\t" /* if (tmp >= 0) channel = tmp */
+
+/* swap 16 bits */
+#define SWAP_16(s) \
+ " movdqa "#s", %%xmm4 \n\t" /* .. | h l | */ \
+ " psrlw $8, %%xmm4 \n\t" /* .. | 0 h | */ \
+ " psllw $8, "#s" \n\t" /* .. | l 0 | */ \
+ " por %%xmm4, "#s" \n\t" /* .. | l h | */
+
+/* swap 2 registers 16 bits for better pairing */
+#define SWAP_16_2(s1,s2) \
+ " movdqa "#s1", %%xmm4 \n\t" /* .. | h l | */ \
+ " movdqa "#s2", %%xmm5 \n\t" \
+ " psrlw $8, %%xmm4 \n\t" /* .. | 0 h | */ \
+ " psrlw $8, %%xmm5 \n\t" \
+ " psllw $8, "#s1" \n\t" /* .. | l 0 | */ \
+ " psllw $8, "#s2" \n\t" \
+ " por %%xmm4, "#s1" \n\t" /* .. | l h | */ \
+ " por %%xmm5, "#s2" \n\t"
+
+static int channel_overread_table[8] = {8,8,8,12,8,10,12,14};
+
+static void pa_volume_s16ne_sse2(int16_t *samples, const int32_t *volumes, unsigned channels, unsigned length) {
+ pa_reg_x86 channel, temp;
+
+ /* Channels must be at least 8 and always a multiple of the original number.
+ * This is also the max amount we overread the volume array, which should
+ * have enough padding. */
+ if (channels < 8)
+ channels = channel_overread_table[channels];
+
+ __asm__ __volatile__ (
+ " xor %3, %3 \n\t"
+ " sar $1, %2 \n\t" /* length /= sizeof (int16_t) */
+
+ " test $1, %2 \n\t" /* check for odd samples */
+ " je 2f \n\t"
+
+ " movd (%q1, %3, 4), %%xmm0 \n\t" /* | v0h | v0l | */
+ " movw (%0), %w4 \n\t" /* .. | p0 | */
+ " movd %4, %%xmm1 \n\t"
+ VOLUME_32x16 (%%xmm1, %%xmm0)
+ " movd %%xmm0, %4 \n\t" /* .. | p0*v0 | */
+ " movw %w4, (%0) \n\t"
+ " add $2, %0 \n\t"
+ MOD_ADD ($1, %5)
+
+ "2: \n\t"
+ " sar $1, %2 \n\t" /* prepare for processing 2 samples at a time */
+ " test $1, %2 \n\t"
+ " je 4f \n\t"
+
+ "3: \n\t" /* do samples in groups of 2 */
+ " movq (%q1, %3, 4), %%xmm0 \n\t" /* | v1h | v1l | v0h | v0l | */
+ " movd (%0), %%xmm1 \n\t" /* .. | p1 | p0 | */
+ VOLUME_32x16 (%%xmm1, %%xmm0)
+ " movd %%xmm0, (%0) \n\t" /* .. | p1*v1 | p0*v0 | */
+ " add $4, %0 \n\t"
+ MOD_ADD ($2, %5)
+
+ "4: \n\t"
+ " sar $1, %2 \n\t" /* prepare for processing 4 samples at a time */
+ " test $1, %2 \n\t"
+ " je 6f \n\t"
+
+ /* FIXME, we can do aligned access of the volume values if we can guarantee
+ * that the array is 16 bytes aligned, we probably have to do the odd values
+ * after this then. */
+ "5: \n\t" /* do samples in groups of 4 */
+ " movdqu (%q1, %3, 4), %%xmm0 \n\t" /* | v3h | v3l .. v0h | v0l | */
+ " movq (%0), %%xmm1 \n\t" /* .. | p3 .. p0 | */
+ VOLUME_32x16 (%%xmm1, %%xmm0)
+ " movq %%xmm0, (%0) \n\t" /* .. | p3*v3 .. p0*v0 | */
+ " add $8, %0 \n\t"
+ MOD_ADD ($4, %5)
+
+ "6: \n\t"
+ " sar $1, %2 \n\t" /* prepare for processing 8 samples at a time */
+ " cmp $0, %2 \n\t"
+ " je 8f \n\t"
+
+ "7: \n\t" /* do samples in groups of 8 */
+ " movdqu (%q1, %3, 4), %%xmm0 \n\t" /* | v3h | v3l .. v0h | v0l | */
+ " movdqu 16(%q1, %3, 4), %%xmm2 \n\t" /* | v7h | v7l .. v4h | v4l | */
+ " movq (%0), %%xmm1 \n\t" /* .. | p3 .. p0 | */
+ " movq 8(%0), %%xmm3 \n\t" /* .. | p7 .. p4 | */
+ VOLUME_32x16 (%%xmm1, %%xmm0)
+ VOLUME_32x16 (%%xmm3, %%xmm2)
+ " movq %%xmm0, (%0) \n\t" /* .. | p3*v3 .. p0*v0 | */
+ " movq %%xmm2, 8(%0) \n\t" /* .. | p7*v7 .. p4*v4 | */
+ " add $16, %0 \n\t"
+ MOD_ADD ($8, %5)
+ " dec %2 \n\t"
+ " jne 7b \n\t"
+ "8: \n\t"
+
+ : "+r" (samples), "+r" (volumes), "+r" (length), "=&D" (channel), "=&r" (temp)
+#if defined (__i386__)
+ : "m" (channels)
+#else
+ : "r" ((pa_reg_x86)channels)
+#endif
+ : "cc"
+ );
+}
+
+static void pa_volume_s16re_sse2(int16_t *samples, const int32_t *volumes, unsigned channels, unsigned length) {
+ pa_reg_x86 channel, temp;
+
+ /* Channels must be at least 8 and always a multiple of the original number.
+ * This is also the max amount we overread the volume array, which should
+ * have enough padding. */
+ if (channels < 8)
+ channels = channel_overread_table[channels];
+
+ __asm__ __volatile__ (
+ " xor %3, %3 \n\t"
+ " sar $1, %2 \n\t" /* length /= sizeof (int16_t) */
+
+ " test $1, %2 \n\t" /* check for odd samples */
+ " je 2f \n\t"
+
+ " movd (%q1, %3, 4), %%xmm0 \n\t" /* | v0h | v0l | */
+ " movw (%0), %w4 \n\t" /* .. | p0 | */
+ " rorw $8, %w4 \n\t"
+ " movd %4, %%xmm1 \n\t"
+ VOLUME_32x16 (%%xmm1, %%xmm0)
+ " movd %%xmm0, %4 \n\t" /* .. | p0*v0 | */
+ " rorw $8, %w4 \n\t"
+ " movw %w4, (%0) \n\t"
+ " add $2, %0 \n\t"
+ MOD_ADD ($1, %5)
+
+ "2: \n\t"
+ " sar $1, %2 \n\t" /* prepare for processing 2 samples at a time */
+ " test $1, %2 \n\t"
+ " je 4f \n\t"
+
+ "3: \n\t" /* do samples in groups of 2 */
+ " movq (%q1, %3, 4), %%xmm0 \n\t" /* | v1h | v1l | v0h | v0l | */
+ " movd (%0), %%xmm1 \n\t" /* .. | p1 | p0 | */
+ SWAP_16 (%%xmm1)
+ VOLUME_32x16 (%%xmm1, %%xmm0)
+ SWAP_16 (%%xmm0)
+ " movd %%xmm0, (%0) \n\t" /* .. | p1*v1 | p0*v0 | */
+ " add $4, %0 \n\t"
+ MOD_ADD ($2, %5)
+
+ "4: \n\t"
+ " sar $1, %2 \n\t" /* prepare for processing 4 samples at a time */
+ " test $1, %2 \n\t"
+ " je 6f \n\t"
+
+ /* FIXME, we can do aligned access of the volume values if we can guarantee
+ * that the array is 16 bytes aligned, we probably have to do the odd values
+ * after this then. */
+ "5: \n\t" /* do samples in groups of 4 */
+ " movdqu (%q1, %3, 4), %%xmm0 \n\t" /* | v3h | v3l .. v0h | v0l | */
+ " movq (%0), %%xmm1 \n\t" /* .. | p3 .. p0 | */
+ SWAP_16 (%%xmm1)
+ VOLUME_32x16 (%%xmm1, %%xmm0)
+ SWAP_16 (%%xmm0)
+ " movq %%xmm0, (%0) \n\t" /* .. | p3*v3 .. p0*v0 | */
+ " add $8, %0 \n\t"
+ MOD_ADD ($4, %5)
+
+ "6: \n\t"
+ " sar $1, %2 \n\t" /* prepare for processing 8 samples at a time */
+ " cmp $0, %2 \n\t"
+ " je 8f \n\t"
+
+ "7: \n\t" /* do samples in groups of 8 */
+ " movdqu (%q1, %3, 4), %%xmm0 \n\t" /* | v3h | v3l .. v0h | v0l | */
+ " movdqu 16(%q1, %3, 4), %%xmm2 \n\t" /* | v7h | v7l .. v4h | v4l | */
+ " movq (%0), %%xmm1 \n\t" /* .. | p3 .. p0 | */
+ " movq 8(%0), %%xmm3 \n\t" /* .. | p7 .. p4 | */
+ SWAP_16_2 (%%xmm1, %%xmm3)
+ VOLUME_32x16 (%%xmm1, %%xmm0)
+ VOLUME_32x16 (%%xmm3, %%xmm2)
+ SWAP_16_2 (%%xmm0, %%xmm2)
+ " movq %%xmm0, (%0) \n\t" /* .. | p3*v3 .. p0*v0 | */
+ " movq %%xmm2, 8(%0) \n\t" /* .. | p7*v7 .. p4*v4 | */
+ " add $16, %0 \n\t"
+ MOD_ADD ($8, %5)
+ " dec %2 \n\t"
+ " jne 7b \n\t"
+ "8: \n\t"
+
+ : "+r" (samples), "+r" (volumes), "+r" (length), "=&D" (channel), "=&r" (temp)
+#if defined (__i386__)
+ : "m" (channels)
+#else
+ : "r" ((pa_reg_x86)channels)
+#endif
+ : "cc"
+ );
+}
+
+#endif /* (!defined(__APPLE__) && !defined(__FreeBSD__) && !defined(__FreeBSD_kernel__) && defined (__i386__)) || defined (__amd64__) */
+
+void pa_volume_func_init_sse(pa_cpu_x86_flag_t flags) {
+#if (!defined(__APPLE__) && !defined(__FreeBSD__) && !defined(__FreeBSD_kernel__) && defined (__i386__)) || defined (__amd64__)
+ if (flags & PA_CPU_X86_SSE2) {
+ pa_log_info("Initialising SSE2 optimized volume functions.");
+
+ pa_set_volume_func(PA_SAMPLE_S16NE, (pa_do_volume_func_t) pa_volume_s16ne_sse2);
+ pa_set_volume_func(PA_SAMPLE_S16RE, (pa_do_volume_func_t) pa_volume_s16re_sse2);
+ }
+#endif /* (!defined(__APPLE__) && !defined(__FreeBSD__) && !defined(__FreeBSD_kernel__) && defined (__i386__)) || defined (__amd64__) */
+}
diff --git a/src/pulsecore/tagstruct.c b/src/pulsecore/tagstruct.c
new file mode 100644
index 0000000..6e9c7d1
--- /dev/null
+++ b/src/pulsecore/tagstruct.c
@@ -0,0 +1,800 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <stdarg.h>
+
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/socket.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/flist.h>
+
+#include "tagstruct.h"
+
+#define MAX_TAG_SIZE (64*1024)
+#define MAX_APPENDED_SIZE 128
+#define GROW_TAG_SIZE 100
+
+struct pa_tagstruct {
+ uint8_t *data;
+ size_t length, allocated;
+ size_t rindex;
+
+ enum {
+ PA_TAGSTRUCT_FIXED, /* The tagstruct does not own the data, buffer was provided by caller. */
+ PA_TAGSTRUCT_DYNAMIC, /* Buffer owned by tagstruct, data must be freed. */
+ PA_TAGSTRUCT_APPENDED, /* Data points to appended buffer, used for small tagstructs. Will change to dynamic if needed. */
+ } type;
+ union {
+ uint8_t appended[MAX_APPENDED_SIZE];
+ } per_type;
+};
+
+PA_STATIC_FLIST_DECLARE(tagstructs, 0, pa_xfree);
+
+pa_tagstruct *pa_tagstruct_new(void) {
+ pa_tagstruct*t;
+
+ if (!(t = pa_flist_pop(PA_STATIC_FLIST_GET(tagstructs))))
+ t = pa_xnew(pa_tagstruct, 1);
+ t->data = t->per_type.appended;
+ t->allocated = MAX_APPENDED_SIZE;
+ t->length = t->rindex = 0;
+ t->type = PA_TAGSTRUCT_APPENDED;
+
+ return t;
+}
+
+pa_tagstruct *pa_tagstruct_new_fixed(const uint8_t* data, size_t length) {
+ pa_tagstruct*t;
+
+ pa_assert(data && length);
+
+ if (!(t = pa_flist_pop(PA_STATIC_FLIST_GET(tagstructs))))
+ t = pa_xnew(pa_tagstruct, 1);
+ t->data = (uint8_t*) data;
+ t->allocated = t->length = length;
+ t->rindex = 0;
+ t->type = PA_TAGSTRUCT_FIXED;
+
+ return t;
+}
+
+void pa_tagstruct_free(pa_tagstruct*t) {
+ pa_assert(t);
+
+ if (t->type == PA_TAGSTRUCT_DYNAMIC)
+ pa_xfree(t->data);
+ if (pa_flist_push(PA_STATIC_FLIST_GET(tagstructs), t) < 0)
+ pa_xfree(t);
+}
+
+static inline void extend(pa_tagstruct*t, size_t l) {
+ pa_assert(t);
+ pa_assert(t->type != PA_TAGSTRUCT_FIXED);
+
+ if (t->length+l <= t->allocated)
+ return;
+
+ if (t->type == PA_TAGSTRUCT_DYNAMIC)
+ t->data = pa_xrealloc(t->data, t->allocated = t->length + l + GROW_TAG_SIZE);
+ else if (t->type == PA_TAGSTRUCT_APPENDED) {
+ t->type = PA_TAGSTRUCT_DYNAMIC;
+ t->data = pa_xmalloc(t->allocated = t->length + l + GROW_TAG_SIZE);
+ memcpy(t->data, t->per_type.appended, t->length);
+ }
+}
+
+static void write_u8(pa_tagstruct *t, uint8_t u) {
+ extend(t, 1);
+ t->data[t->length++] = u;
+}
+
+static int read_u8(pa_tagstruct *t, uint8_t *u) {
+ if (t->rindex + 1 > t->length)
+ return -1;
+
+ *u = t->data[t->rindex++];
+ return 0;
+}
+
+static void write_u32(pa_tagstruct *t, uint32_t u) {
+ extend(t, 4);
+ u = htonl(u);
+ memcpy(t->data + t->length, &u, 4);
+ t->length += 4;
+}
+
+static int read_u32(pa_tagstruct *t, uint32_t *u) {
+ if (t->rindex + 4 > t->length)
+ return -1;
+
+ memcpy(u, t->data + t->rindex, 4);
+ *u = ntohl(*u);
+ t->rindex += 4;
+
+ return 0;
+}
+
+static void write_u64(pa_tagstruct *t, uint64_t u) {
+ write_u32(t, u >> 32);
+ write_u32(t, u);
+}
+
+static int read_u64(pa_tagstruct *t, uint64_t *u) {
+ uint32_t tmp;
+
+ if (read_u32(t, &tmp) < 0)
+ return -1;
+
+ *u = ((uint64_t) tmp) << 32;
+
+ if (read_u32(t, &tmp) < 0)
+ return -1;
+
+ *u |= tmp;
+ return 0;
+}
+
+static int read_s64(pa_tagstruct *t, int64_t *u) {
+ uint32_t tmp;
+
+ if (read_u32(t, &tmp) < 0)
+ return -1;
+
+ *u = (int64_t) (((uint64_t) tmp) << 32);
+
+ if (read_u32(t, &tmp) < 0)
+ return -1;
+
+ *u |= (int64_t) tmp;
+ return 0;
+}
+
+static void write_arbitrary(pa_tagstruct *t, const void *p, size_t len) {
+ extend(t, len);
+
+ if (len > 0)
+ memcpy(t->data + t->length, p, len);
+
+ t->length += len;
+}
+
+static int read_arbitrary(pa_tagstruct *t, const void **p, size_t length) {
+ if (t->rindex + length > t->length)
+ return -1;
+
+ *p = t->data + t->rindex;
+ t->rindex += length;
+ return 0;
+}
+
+void pa_tagstruct_puts(pa_tagstruct*t, const char *s) {
+ size_t l;
+ pa_assert(t);
+
+ if (s) {
+ write_u8(t, PA_TAG_STRING);
+ l = strlen(s)+1;
+ write_arbitrary(t, s, l);
+ } else
+ write_u8(t, PA_TAG_STRING_NULL);
+}
+
+void pa_tagstruct_putu32(pa_tagstruct*t, uint32_t i) {
+ pa_assert(t);
+
+ write_u8(t, PA_TAG_U32);
+ write_u32(t, i);
+}
+
+void pa_tagstruct_putu8(pa_tagstruct*t, uint8_t c) {
+ pa_assert(t);
+
+ write_u8(t, PA_TAG_U8);
+ write_u8(t, c);
+}
+
+void pa_tagstruct_put_sample_spec(pa_tagstruct *t, const pa_sample_spec *ss) {
+ pa_assert(t);
+ pa_assert(ss);
+
+ write_u8(t, PA_TAG_SAMPLE_SPEC);
+ write_u8(t, ss->format);
+ write_u8(t, ss->channels);
+ write_u32(t, ss->rate);
+}
+
+void pa_tagstruct_put_arbitrary(pa_tagstruct *t, const void *p, size_t length) {
+ pa_assert(t);
+ pa_assert(p);
+
+ write_u8(t, PA_TAG_ARBITRARY);
+ write_u32(t, length);
+ write_arbitrary(t, p, length);
+}
+
+void pa_tagstruct_put_boolean(pa_tagstruct*t, bool b) {
+ pa_assert(t);
+
+ write_u8(t, b ? PA_TAG_BOOLEAN_TRUE : PA_TAG_BOOLEAN_FALSE);
+}
+
+void pa_tagstruct_put_timeval(pa_tagstruct*t, const struct timeval *tv) {
+ pa_assert(t);
+
+ write_u8(t, PA_TAG_TIMEVAL);
+ write_u32(t, tv->tv_sec);
+ write_u32(t, tv->tv_usec);
+}
+
+void pa_tagstruct_put_usec(pa_tagstruct*t, pa_usec_t u) {
+ pa_assert(t);
+
+ write_u8(t, PA_TAG_USEC);
+ write_u64(t, u);
+}
+
+void pa_tagstruct_putu64(pa_tagstruct*t, uint64_t u) {
+ pa_assert(t);
+
+ write_u8(t, PA_TAG_U64);
+ write_u64(t, u);
+}
+
+void pa_tagstruct_puts64(pa_tagstruct*t, int64_t u) {
+ pa_assert(t);
+
+ write_u8(t, PA_TAG_S64);
+ write_u64(t, u);
+}
+
+void pa_tagstruct_put_channel_map(pa_tagstruct *t, const pa_channel_map *map) {
+ unsigned i;
+
+ pa_assert(t);
+ pa_assert(map);
+
+ write_u8(t, PA_TAG_CHANNEL_MAP);
+ write_u8(t, map->channels);
+
+ for (i = 0; i < map->channels; i ++)
+ write_u8(t, map->map[i]);
+}
+
+void pa_tagstruct_put_cvolume(pa_tagstruct *t, const pa_cvolume *cvolume) {
+ unsigned i;
+
+ pa_assert(t);
+ pa_assert(cvolume);
+
+ write_u8(t, PA_TAG_CVOLUME);
+ write_u8(t, cvolume->channels);
+
+ for (i = 0; i < cvolume->channels; i ++)
+ write_u32(t, cvolume->values[i]);
+}
+
+void pa_tagstruct_put_volume(pa_tagstruct *t, pa_volume_t vol) {
+ pa_assert(t);
+
+ write_u8(t, PA_TAG_VOLUME);
+ write_u32(t, vol);
+}
+
+void pa_tagstruct_put_proplist(pa_tagstruct *t, const pa_proplist *p) {
+ void *state = NULL;
+ pa_assert(t);
+ pa_assert(p);
+
+ write_u8(t, PA_TAG_PROPLIST);
+
+ for (;;) {
+ const char *k;
+ const void *d;
+ size_t l;
+
+ if (!(k = pa_proplist_iterate(p, &state)))
+ break;
+
+ pa_tagstruct_puts(t, k);
+ pa_assert_se(pa_proplist_get(p, k, &d, &l) >= 0);
+ pa_tagstruct_putu32(t, (uint32_t) l);
+ pa_tagstruct_put_arbitrary(t, d, l);
+ }
+
+ pa_tagstruct_puts(t, NULL);
+}
+
+void pa_tagstruct_put_format_info(pa_tagstruct *t, const pa_format_info *f) {
+ pa_assert(t);
+ pa_assert(f);
+
+ write_u8(t, PA_TAG_FORMAT_INFO);
+ pa_tagstruct_putu8(t, (uint8_t) f->encoding);
+ pa_tagstruct_put_proplist(t, f->plist);
+}
+
+static int read_tag(pa_tagstruct *t, uint8_t type) {
+ if (t->rindex + 1 > t->length)
+ return -1;
+
+ if (t->data[t->rindex] != type)
+ return -1;
+
+ t->rindex++;
+
+ return 0;
+}
+
+int pa_tagstruct_gets(pa_tagstruct*t, const char **s) {
+ int error = 0;
+ size_t n;
+ char *c;
+
+ pa_assert(t);
+ pa_assert(s);
+
+ if (t->rindex+1 > t->length)
+ return -1;
+
+ if (t->data[t->rindex] == PA_TAG_STRING_NULL) {
+ t->rindex++;
+ *s = NULL;
+ return 0;
+ }
+
+ if (read_tag(t, PA_TAG_STRING) < 0)
+ return -1;
+
+ if (t->rindex + 1 > t->length)
+ return -1;
+
+ error = 1;
+ for (n = 0, c = (char*) (t->data + t->rindex); t->rindex + n < t->length; n++, c++)
+ if (!*c) {
+ error = 0;
+ break;
+ }
+
+ if (error)
+ return -1;
+
+ *s = (char*) (t->data + t->rindex);
+
+ t->rindex += n + 1;
+ return 0;
+}
+
+int pa_tagstruct_getu32(pa_tagstruct*t, uint32_t *i) {
+ pa_assert(t);
+ pa_assert(i);
+
+ if (read_tag(t, PA_TAG_U32) < 0)
+ return -1;
+
+ return read_u32(t, i);
+}
+
+int pa_tagstruct_getu8(pa_tagstruct*t, uint8_t *c) {
+ pa_assert(t);
+ pa_assert(c);
+
+ if (read_tag(t, PA_TAG_U8) < 0)
+ return -1;
+
+ return read_u8(t, c);
+}
+
+int pa_tagstruct_get_sample_spec(pa_tagstruct *t, pa_sample_spec *ss) {
+ uint8_t tmp;
+
+ pa_assert(t);
+ pa_assert(ss);
+
+ if (read_tag(t, PA_TAG_SAMPLE_SPEC) < 0)
+ return -1;
+
+ if (read_u8(t, &tmp) < 0)
+ return -1;
+
+ ss->format = tmp;
+
+ if (read_u8(t, &ss->channels) < 0)
+ return -1;
+
+ return read_u32(t, &ss->rate);
+}
+
+int pa_tagstruct_get_arbitrary(pa_tagstruct *t, const void **p, size_t length) {
+ uint32_t len;
+
+ pa_assert(t);
+ pa_assert(p);
+
+ if (read_tag(t, PA_TAG_ARBITRARY) < 0)
+ return -1;
+
+ if (read_u32(t, &len) < 0 || len != length)
+ return -1;
+
+ return read_arbitrary(t, p, length);
+}
+
+int pa_tagstruct_eof(pa_tagstruct*t) {
+ pa_assert(t);
+
+ return t->rindex >= t->length;
+}
+
+const uint8_t* pa_tagstruct_data(pa_tagstruct*t, size_t *l) {
+ pa_assert(t);
+ pa_assert(l);
+
+ *l = t->length;
+ return t->data;
+}
+
+int pa_tagstruct_get_boolean(pa_tagstruct*t, bool *b) {
+ pa_assert(t);
+ pa_assert(b);
+
+ if (t->rindex+1 > t->length)
+ return -1;
+
+ if (t->data[t->rindex] == PA_TAG_BOOLEAN_TRUE)
+ *b = true;
+ else if (t->data[t->rindex] == PA_TAG_BOOLEAN_FALSE)
+ *b = false;
+ else
+ return -1;
+
+ t->rindex +=1;
+ return 0;
+}
+
+int pa_tagstruct_get_timeval(pa_tagstruct*t, struct timeval *tv) {
+ uint32_t tmp;
+
+ pa_assert(t);
+ pa_assert(tv);
+
+ if (read_tag(t, PA_TAG_TIMEVAL) < 0)
+ return -1;
+
+ if (read_u32(t, &tmp) < 0)
+ return -1;
+
+ tv->tv_sec = tmp;
+
+ if (read_u32(t, &tmp) < 0)
+ return -1;
+
+ tv->tv_usec = tmp;
+
+ return 0;
+}
+
+int pa_tagstruct_get_usec(pa_tagstruct*t, pa_usec_t *u) {
+ pa_assert(t);
+ pa_assert(u);
+
+ if (read_tag(t, PA_TAG_USEC) < 0)
+ return -1;
+
+ return read_u64(t, u);
+}
+
+int pa_tagstruct_getu64(pa_tagstruct*t, uint64_t *u) {
+ pa_assert(t);
+ pa_assert(u);
+
+ if (read_tag(t, PA_TAG_U64) < 0)
+ return -1;
+
+ return read_u64(t, u);
+}
+
+int pa_tagstruct_gets64(pa_tagstruct*t, int64_t *u) {
+ pa_assert(t);
+ pa_assert(u);
+
+ if (read_tag(t, PA_TAG_S64) < 0)
+ return -1;
+
+ return read_s64(t, u);
+}
+
+int pa_tagstruct_get_channel_map(pa_tagstruct *t, pa_channel_map *map) {
+ unsigned i;
+
+ pa_assert(t);
+ pa_assert(map);
+
+ if (read_tag(t, PA_TAG_CHANNEL_MAP) < 0)
+ return -1;
+
+ if (read_u8(t, &map->channels) < 0 || map->channels > PA_CHANNELS_MAX)
+ return -1;
+
+ for (i = 0; i < map->channels; i ++) {
+ uint8_t tmp;
+
+ if (read_u8(t, &tmp) < 0)
+ return -1;
+
+ map->map[i] = tmp;
+ }
+
+ return 0;
+}
+
+int pa_tagstruct_get_cvolume(pa_tagstruct *t, pa_cvolume *cvolume) {
+ unsigned i;
+
+ pa_assert(t);
+ pa_assert(cvolume);
+
+ if (read_tag(t, PA_TAG_CVOLUME) < 0)
+ return -1;
+
+ if (read_u8(t, &cvolume->channels) < 0 || cvolume->channels > PA_CHANNELS_MAX)
+ return -1;
+
+ for (i = 0; i < cvolume->channels; i ++) {
+ if (read_u32(t, &cvolume->values[i]) < 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+int pa_tagstruct_get_volume(pa_tagstruct*t, pa_volume_t *vol) {
+ pa_assert(t);
+ pa_assert(vol);
+
+ if (read_tag(t, PA_TAG_VOLUME) < 0)
+ return -1;
+
+ return read_u32(t, vol);
+}
+
+int pa_tagstruct_get_proplist(pa_tagstruct *t, pa_proplist *p) {
+ pa_assert(t);
+
+ if (read_tag(t, PA_TAG_PROPLIST) < 0)
+ return -1;
+
+ for (;;) {
+ const char *k;
+ const void *d;
+ uint32_t length;
+
+ if (pa_tagstruct_gets(t, &k) < 0)
+ return -1;
+
+ if (!k)
+ break;
+
+ if (!pa_proplist_key_valid(k))
+ return -1;
+
+ if (pa_tagstruct_getu32(t, &length) < 0)
+ return -1;
+
+ if (length > MAX_TAG_SIZE)
+ return -1;
+
+ if (pa_tagstruct_get_arbitrary(t, &d, length) < 0)
+ return -1;
+
+ if (p)
+ pa_assert_se(pa_proplist_set(p, k, d, length) >= 0);
+ }
+
+ return 0;
+}
+
+int pa_tagstruct_get_format_info(pa_tagstruct *t, pa_format_info *f) {
+ uint8_t encoding;
+
+ pa_assert(t);
+ pa_assert(f);
+
+ if (read_tag(t, PA_TAG_FORMAT_INFO) < 0)
+ return -1;
+
+ if (pa_tagstruct_getu8(t, &encoding) < 0)
+ return -1;
+
+ f->encoding = encoding;
+
+ return pa_tagstruct_get_proplist(t, f->plist);
+}
+
+void pa_tagstruct_put(pa_tagstruct *t, ...) {
+ va_list va;
+ pa_assert(t);
+
+ va_start(va, t);
+
+ for (;;) {
+ int tag = va_arg(va, int);
+
+ if (tag == PA_TAG_INVALID)
+ break;
+
+ switch (tag) {
+ case PA_TAG_STRING:
+ case PA_TAG_STRING_NULL:
+ pa_tagstruct_puts(t, va_arg(va, char*));
+ break;
+
+ case PA_TAG_U32:
+ pa_tagstruct_putu32(t, va_arg(va, uint32_t));
+ break;
+
+ case PA_TAG_U8:
+ pa_tagstruct_putu8(t, (uint8_t) va_arg(va, int));
+ break;
+
+ case PA_TAG_U64:
+ pa_tagstruct_putu64(t, va_arg(va, uint64_t));
+ break;
+
+ case PA_TAG_SAMPLE_SPEC:
+ pa_tagstruct_put_sample_spec(t, va_arg(va, pa_sample_spec*));
+ break;
+
+ case PA_TAG_ARBITRARY: {
+ void *p = va_arg(va, void*);
+ size_t size = va_arg(va, size_t);
+ pa_tagstruct_put_arbitrary(t, p, size);
+ break;
+ }
+
+ case PA_TAG_BOOLEAN_TRUE:
+ case PA_TAG_BOOLEAN_FALSE:
+ pa_tagstruct_put_boolean(t, va_arg(va, int));
+ break;
+
+ case PA_TAG_TIMEVAL:
+ pa_tagstruct_put_timeval(t, va_arg(va, struct timeval*));
+ break;
+
+ case PA_TAG_USEC:
+ pa_tagstruct_put_usec(t, va_arg(va, pa_usec_t));
+ break;
+
+ case PA_TAG_CHANNEL_MAP:
+ pa_tagstruct_put_channel_map(t, va_arg(va, pa_channel_map *));
+ break;
+
+ case PA_TAG_CVOLUME:
+ pa_tagstruct_put_cvolume(t, va_arg(va, pa_cvolume *));
+ break;
+
+ case PA_TAG_VOLUME:
+ pa_tagstruct_put_volume(t, va_arg(va, pa_volume_t));
+ break;
+
+ case PA_TAG_PROPLIST:
+ pa_tagstruct_put_proplist(t, va_arg(va, pa_proplist *));
+ break;
+
+ default:
+ pa_assert_not_reached();
+ }
+ }
+
+ va_end(va);
+}
+
+int pa_tagstruct_get(pa_tagstruct *t, ...) {
+ va_list va;
+ int ret = 0;
+
+ pa_assert(t);
+
+ va_start(va, t);
+ while (ret == 0) {
+ int tag = va_arg(va, int);
+
+ if (tag == PA_TAG_INVALID)
+ break;
+
+ switch (tag) {
+ case PA_TAG_STRING:
+ case PA_TAG_STRING_NULL:
+ ret = pa_tagstruct_gets(t, va_arg(va, const char**));
+ break;
+
+ case PA_TAG_U32:
+ ret = pa_tagstruct_getu32(t, va_arg(va, uint32_t*));
+ break;
+
+ case PA_TAG_U8:
+ ret = pa_tagstruct_getu8(t, va_arg(va, uint8_t*));
+ break;
+
+ case PA_TAG_U64:
+ ret = pa_tagstruct_getu64(t, va_arg(va, uint64_t*));
+ break;
+
+ case PA_TAG_SAMPLE_SPEC:
+ ret = pa_tagstruct_get_sample_spec(t, va_arg(va, pa_sample_spec*));
+ break;
+
+ case PA_TAG_ARBITRARY: {
+ const void **p = va_arg(va, const void**);
+ size_t size = va_arg(va, size_t);
+ ret = pa_tagstruct_get_arbitrary(t, p, size);
+ break;
+ }
+
+ case PA_TAG_BOOLEAN_TRUE:
+ case PA_TAG_BOOLEAN_FALSE:
+ ret = pa_tagstruct_get_boolean(t, va_arg(va, bool*));
+ break;
+
+ case PA_TAG_TIMEVAL:
+ ret = pa_tagstruct_get_timeval(t, va_arg(va, struct timeval*));
+ break;
+
+ case PA_TAG_USEC:
+ ret = pa_tagstruct_get_usec(t, va_arg(va, pa_usec_t*));
+ break;
+
+ case PA_TAG_CHANNEL_MAP:
+ ret = pa_tagstruct_get_channel_map(t, va_arg(va, pa_channel_map *));
+ break;
+
+ case PA_TAG_CVOLUME:
+ ret = pa_tagstruct_get_cvolume(t, va_arg(va, pa_cvolume *));
+ break;
+
+ case PA_TAG_VOLUME:
+ ret = pa_tagstruct_get_volume(t, va_arg(va, pa_volume_t *));
+ break;
+
+ case PA_TAG_PROPLIST:
+ ret = pa_tagstruct_get_proplist(t, va_arg(va, pa_proplist *));
+ break;
+
+ default:
+ pa_assert_not_reached();
+ }
+
+ }
+
+ va_end(va);
+ return ret;
+}
diff --git a/src/pulsecore/tagstruct.h b/src/pulsecore/tagstruct.h
new file mode 100644
index 0000000..dcb51cb
--- /dev/null
+++ b/src/pulsecore/tagstruct.h
@@ -0,0 +1,106 @@
+#ifndef footagstructhfoo
+#define footagstructhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <inttypes.h>
+#include <sys/types.h>
+#include <sys/time.h>
+
+#include <pulse/sample.h>
+#include <pulse/format.h>
+#include <pulse/channelmap.h>
+#include <pulse/volume.h>
+#include <pulse/proplist.h>
+
+#include <pulsecore/macro.h>
+
+typedef struct pa_tagstruct pa_tagstruct;
+
+/* Due to a stupid design flaw, proplists may only be at the END of a
+ * packet or not before a STRING! Don't forget that! We can't really
+ * fix this without breaking compat. */
+
+enum {
+ PA_TAG_INVALID = 0,
+ PA_TAG_STRING = 't',
+ PA_TAG_STRING_NULL = 'N',
+ PA_TAG_U32 = 'L',
+ PA_TAG_U8 = 'B',
+ PA_TAG_U64 = 'R',
+ PA_TAG_S64 = 'r',
+ PA_TAG_SAMPLE_SPEC = 'a',
+ PA_TAG_ARBITRARY = 'x',
+ PA_TAG_BOOLEAN_TRUE = '1',
+ PA_TAG_BOOLEAN_FALSE = '0',
+ PA_TAG_BOOLEAN = PA_TAG_BOOLEAN_TRUE,
+ PA_TAG_TIMEVAL = 'T',
+ PA_TAG_USEC = 'U' /* 64bit unsigned */,
+ PA_TAG_CHANNEL_MAP = 'm',
+ PA_TAG_CVOLUME = 'v',
+ PA_TAG_PROPLIST = 'P',
+ PA_TAG_VOLUME = 'V',
+ PA_TAG_FORMAT_INFO = 'f',
+};
+
+pa_tagstruct *pa_tagstruct_new(void);
+pa_tagstruct *pa_tagstruct_new_fixed(const uint8_t* data, size_t length);
+void pa_tagstruct_free(pa_tagstruct*t);
+
+int pa_tagstruct_eof(pa_tagstruct*t);
+const uint8_t* pa_tagstruct_data(pa_tagstruct*t, size_t *l);
+
+void pa_tagstruct_put(pa_tagstruct *t, ...);
+
+void pa_tagstruct_puts(pa_tagstruct*t, const char *s);
+void pa_tagstruct_putu8(pa_tagstruct*t, uint8_t c);
+void pa_tagstruct_putu32(pa_tagstruct*t, uint32_t i);
+void pa_tagstruct_putu64(pa_tagstruct*t, uint64_t i);
+void pa_tagstruct_puts64(pa_tagstruct*t, int64_t i);
+void pa_tagstruct_put_sample_spec(pa_tagstruct *t, const pa_sample_spec *ss);
+void pa_tagstruct_put_arbitrary(pa_tagstruct*t, const void *p, size_t length);
+void pa_tagstruct_put_boolean(pa_tagstruct*t, bool b);
+void pa_tagstruct_put_timeval(pa_tagstruct*t, const struct timeval *tv);
+void pa_tagstruct_put_usec(pa_tagstruct*t, pa_usec_t u);
+void pa_tagstruct_put_channel_map(pa_tagstruct *t, const pa_channel_map *map);
+void pa_tagstruct_put_cvolume(pa_tagstruct *t, const pa_cvolume *cvolume);
+void pa_tagstruct_put_proplist(pa_tagstruct *t, const pa_proplist *p);
+void pa_tagstruct_put_volume(pa_tagstruct *t, pa_volume_t volume);
+void pa_tagstruct_put_format_info(pa_tagstruct *t, const pa_format_info *f);
+
+int pa_tagstruct_get(pa_tagstruct *t, ...);
+
+int pa_tagstruct_gets(pa_tagstruct*t, const char **s);
+int pa_tagstruct_getu8(pa_tagstruct*t, uint8_t *c);
+int pa_tagstruct_getu32(pa_tagstruct*t, uint32_t *i);
+int pa_tagstruct_getu64(pa_tagstruct*t, uint64_t *i);
+int pa_tagstruct_gets64(pa_tagstruct*t, int64_t *i);
+int pa_tagstruct_get_sample_spec(pa_tagstruct *t, pa_sample_spec *ss);
+int pa_tagstruct_get_arbitrary(pa_tagstruct *t, const void **p, size_t length);
+int pa_tagstruct_get_boolean(pa_tagstruct *t, bool *b);
+int pa_tagstruct_get_timeval(pa_tagstruct*t, struct timeval *tv);
+int pa_tagstruct_get_usec(pa_tagstruct*t, pa_usec_t *u);
+int pa_tagstruct_get_channel_map(pa_tagstruct *t, pa_channel_map *map);
+int pa_tagstruct_get_cvolume(pa_tagstruct *t, pa_cvolume *v);
+int pa_tagstruct_get_proplist(pa_tagstruct *t, pa_proplist *p);
+int pa_tagstruct_get_volume(pa_tagstruct *t, pa_volume_t *v);
+int pa_tagstruct_get_format_info(pa_tagstruct *t, pa_format_info *f);
+
+#endif
diff --git a/src/pulsecore/thread-mq.c b/src/pulsecore/thread-mq.c
new file mode 100644
index 0000000..ab3863b
--- /dev/null
+++ b/src/pulsecore/thread-mq.c
@@ -0,0 +1,223 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <unistd.h>
+#include <errno.h>
+
+#include <pulsecore/thread.h>
+#include <pulsecore/semaphore.h>
+#include <pulsecore/macro.h>
+
+#include <pulse/mainloop-api.h>
+
+#include "thread-mq.h"
+
+PA_STATIC_TLS_DECLARE_NO_FREE(thread_mq);
+
+static void asyncmsgq_read_cb(pa_mainloop_api *api, pa_io_event *e, int fd, pa_io_event_flags_t events, void *userdata) {
+ pa_thread_mq *q = userdata;
+ pa_asyncmsgq *aq;
+
+ pa_assert(events == PA_IO_EVENT_INPUT);
+
+ if (pa_asyncmsgq_read_fd(q->outq) == fd)
+ pa_asyncmsgq_ref(aq = q->outq);
+ else if (pa_asyncmsgq_read_fd(q->inq) == fd)
+ pa_asyncmsgq_ref(aq = q->inq);
+ else
+ pa_assert_not_reached();
+
+ pa_asyncmsgq_read_after_poll(aq);
+
+ for (;;) {
+ pa_msgobject *object;
+ int code;
+ void *data;
+ int64_t offset;
+ pa_memchunk chunk;
+
+ /* Check whether there is a message for us to process */
+ while (pa_asyncmsgq_get(aq, &object, &code, &data, &offset, &chunk, 0) >= 0) {
+ int ret;
+
+ if (!object && code == PA_MESSAGE_SHUTDOWN) {
+ pa_asyncmsgq_done(aq, 0);
+ api->quit(api, 0);
+ break;
+ }
+
+ ret = pa_asyncmsgq_dispatch(object, code, data, offset, &chunk);
+ pa_asyncmsgq_done(aq, ret);
+ }
+
+ if (pa_asyncmsgq_read_before_poll(aq) == 0)
+ break;
+ }
+
+ pa_asyncmsgq_unref(aq);
+}
+
+static void asyncmsgq_write_inq_cb(pa_mainloop_api *api, pa_io_event *e, int fd, pa_io_event_flags_t events, void *userdata) {
+ pa_thread_mq *q = userdata;
+
+ pa_assert(pa_asyncmsgq_write_fd(q->inq) == fd);
+ pa_assert(events == PA_IO_EVENT_INPUT);
+
+ pa_asyncmsgq_write_after_poll(q->inq);
+ pa_asyncmsgq_write_before_poll(q->inq);
+}
+
+static void asyncmsgq_write_outq_cb(pa_mainloop_api *api, pa_io_event *e, int fd, pa_io_event_flags_t events, void *userdata) {
+ pa_thread_mq *q = userdata;
+
+ pa_assert(pa_asyncmsgq_write_fd(q->outq) == fd);
+ pa_assert(events == PA_IO_EVENT_INPUT);
+
+ pa_asyncmsgq_write_after_poll(q->outq);
+ pa_asyncmsgq_write_before_poll(q->outq);
+}
+
+int pa_thread_mq_init_thread_mainloop(pa_thread_mq *q, pa_mainloop_api *main_mainloop, pa_mainloop_api *thread_mainloop) {
+ pa_assert(q);
+ pa_assert(main_mainloop);
+ pa_assert(thread_mainloop);
+
+ pa_zero(*q);
+
+ q->inq = pa_asyncmsgq_new(0);
+ if (!q->inq)
+ goto fail;
+
+ q->outq = pa_asyncmsgq_new(0);
+ if (!q->outq)
+ goto fail;
+
+ q->main_mainloop = main_mainloop;
+ q->thread_mainloop = thread_mainloop;
+
+ pa_assert_se(pa_asyncmsgq_read_before_poll(q->outq) == 0);
+ pa_asyncmsgq_write_before_poll(q->outq);
+ pa_assert_se(q->read_main_event = main_mainloop->io_new(main_mainloop, pa_asyncmsgq_read_fd(q->outq), PA_IO_EVENT_INPUT, asyncmsgq_read_cb, q));
+ pa_assert_se(q->write_thread_event = thread_mainloop->io_new(thread_mainloop, pa_asyncmsgq_write_fd(q->outq), PA_IO_EVENT_INPUT, asyncmsgq_write_outq_cb, q));
+
+ pa_asyncmsgq_read_before_poll(q->inq);
+ pa_asyncmsgq_write_before_poll(q->inq);
+ pa_assert_se(q->read_thread_event = thread_mainloop->io_new(thread_mainloop, pa_asyncmsgq_read_fd(q->inq), PA_IO_EVENT_INPUT, asyncmsgq_read_cb, q));
+ pa_assert_se(q->write_main_event = main_mainloop->io_new(main_mainloop, pa_asyncmsgq_write_fd(q->inq), PA_IO_EVENT_INPUT, asyncmsgq_write_inq_cb, q));
+
+ return 0;
+
+fail:
+ pa_thread_mq_done(q);
+
+ return -1;
+}
+
+int pa_thread_mq_init(pa_thread_mq *q, pa_mainloop_api *mainloop, pa_rtpoll *rtpoll) {
+ pa_assert(q);
+ pa_assert(mainloop);
+
+ pa_zero(*q);
+
+ q->main_mainloop = mainloop;
+ q->thread_mainloop = NULL;
+
+ q->inq = pa_asyncmsgq_new(0);
+ if (!q->inq)
+ goto fail;
+
+ q->outq = pa_asyncmsgq_new(0);
+ if (!q->outq)
+ goto fail;
+
+ pa_assert_se(pa_asyncmsgq_read_before_poll(q->outq) == 0);
+ pa_assert_se(q->read_main_event = mainloop->io_new(mainloop, pa_asyncmsgq_read_fd(q->outq), PA_IO_EVENT_INPUT, asyncmsgq_read_cb, q));
+
+ pa_asyncmsgq_write_before_poll(q->inq);
+ pa_assert_se(q->write_main_event = mainloop->io_new(mainloop, pa_asyncmsgq_write_fd(q->inq), PA_IO_EVENT_INPUT, asyncmsgq_write_inq_cb, q));
+
+ pa_rtpoll_item_new_asyncmsgq_read(rtpoll, PA_RTPOLL_EARLY, q->inq);
+ pa_rtpoll_item_new_asyncmsgq_write(rtpoll, PA_RTPOLL_LATE, q->outq);
+
+ return 0;
+
+fail:
+ pa_thread_mq_done(q);
+
+ return -1;
+}
+
+void pa_thread_mq_done(pa_thread_mq *q) {
+ pa_assert(q);
+
+ /* Since we are called from main context we can be sure that the
+ * inq is empty. However, the outq might still contain messages
+ * for the main loop, which we need to dispatch (e.g. release
+ * msgs, other stuff). Hence do so if we aren't currently
+ * dispatching anyway. */
+
+ if (q->outq && !pa_asyncmsgq_dispatching(q->outq)) {
+ /* Flushing the asyncmsgq can cause arbitrarily callbacks to run,
+ potentially causing recursion into pa_thread_mq_done again. */
+ pa_asyncmsgq *z = q->outq;
+ pa_asyncmsgq_ref(z);
+ pa_asyncmsgq_flush(z, true);
+ pa_asyncmsgq_unref(z);
+ }
+
+ if (q->main_mainloop) {
+ if (q->read_main_event)
+ q->main_mainloop->io_free(q->read_main_event);
+ if (q->write_main_event)
+ q->main_mainloop->io_free(q->write_main_event);
+ q->read_main_event = q->write_main_event = NULL;
+ }
+
+ if (q->thread_mainloop) {
+ if (q->read_thread_event)
+ q->thread_mainloop->io_free(q->read_thread_event);
+ if (q->write_thread_event)
+ q->thread_mainloop->io_free(q->write_thread_event);
+ q->read_thread_event = q->write_thread_event = NULL;
+ }
+
+ if (q->inq)
+ pa_asyncmsgq_unref(q->inq);
+ if (q->outq)
+ pa_asyncmsgq_unref(q->outq);
+ q->inq = q->outq = NULL;
+
+ q->main_mainloop = NULL;
+ q->thread_mainloop = NULL;
+}
+
+void pa_thread_mq_install(pa_thread_mq *q) {
+ pa_assert(q);
+
+ pa_assert(!(PA_STATIC_TLS_GET(thread_mq)));
+ PA_STATIC_TLS_SET(thread_mq, q);
+}
+
+pa_thread_mq *pa_thread_mq_get(void) {
+ return PA_STATIC_TLS_GET(thread_mq);
+}
diff --git a/src/pulsecore/thread-mq.h b/src/pulsecore/thread-mq.h
new file mode 100644
index 0000000..f6daa7f
--- /dev/null
+++ b/src/pulsecore/thread-mq.h
@@ -0,0 +1,57 @@
+#ifndef foopulsethreadmqhfoo
+#define foopulsethreadmqhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulse/mainloop-api.h>
+#include <pulsecore/asyncmsgq.h>
+#include <pulsecore/rtpoll.h>
+
+/* Two way communication between a thread and a mainloop. Before the
+ * thread is started a pa_thread_mq should be initialized and than
+ * attached to the thread using pa_thread_mq_install(). */
+
+typedef struct pa_thread_mq {
+ pa_mainloop_api *main_mainloop;
+ pa_mainloop_api *thread_mainloop;
+ pa_asyncmsgq *inq, *outq;
+ pa_io_event *read_main_event, *write_main_event;
+ pa_io_event *read_thread_event, *write_thread_event;
+} pa_thread_mq;
+
+int pa_thread_mq_init(pa_thread_mq *q, pa_mainloop_api *mainloop, pa_rtpoll *rtpoll);
+int pa_thread_mq_init_thread_mainloop(pa_thread_mq *q, pa_mainloop_api *main_mainloop, pa_mainloop_api *thread_mainloop);
+void pa_thread_mq_done(pa_thread_mq *q);
+
+/* Install the specified pa_thread_mq object for the current thread */
+void pa_thread_mq_install(pa_thread_mq *q);
+
+/* Return the pa_thread_mq object that is set for the current thread */
+pa_thread_mq *pa_thread_mq_get(void);
+
+/* Verify that we are in control context (aka 'main context'). */
+#define pa_assert_ctl_context(s) \
+ pa_assert(!pa_thread_mq_get())
+
+/* Verify that we are in IO context (aka 'thread context'). */
+#define pa_assert_io_context(s) \
+ pa_assert(pa_thread_mq_get())
+
+#endif
diff --git a/src/pulsecore/thread-posix.c b/src/pulsecore/thread-posix.c
new file mode 100644
index 0000000..62b0ec4
--- /dev/null
+++ b/src/pulsecore/thread-posix.c
@@ -0,0 +1,254 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pthread.h>
+#include <sched.h>
+#include <errno.h>
+
+#ifdef __linux__
+#include <sys/prctl.h>
+#endif
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/atomic.h>
+#include <pulsecore/macro.h>
+
+#include "thread.h"
+
+struct pa_thread {
+ pthread_t id;
+ pa_thread_func_t thread_func;
+ void *userdata;
+ pa_atomic_t running;
+ bool joined;
+ char *name;
+};
+
+struct pa_tls {
+ pthread_key_t key;
+};
+
+static void thread_free_cb(void *p) {
+ pa_thread *t = p;
+
+ pa_assert(t);
+
+ if (!t->thread_func) {
+ /* This is a foreign thread, we need to free the struct */
+ pa_xfree(t->name);
+ pa_xfree(t);
+ }
+}
+
+PA_STATIC_TLS_DECLARE(current_thread, thread_free_cb);
+
+static void* internal_thread_func(void *userdata) {
+ pa_thread *t = userdata;
+ pa_assert(t);
+
+#ifdef __linux__
+ prctl(PR_SET_NAME, t->name);
+#elif defined(HAVE_PTHREAD_SETNAME_NP) && defined(OS_IS_DARWIN)
+ pthread_setname_np(t->name);
+#endif
+
+ t->id = pthread_self();
+
+ PA_STATIC_TLS_SET(current_thread, t);
+
+ pa_atomic_inc(&t->running);
+ t->thread_func(t->userdata);
+ pa_atomic_sub(&t->running, 2);
+
+ return NULL;
+}
+
+pa_thread* pa_thread_new(const char *name, pa_thread_func_t thread_func, void *userdata) {
+ pa_thread *t;
+
+ pa_assert(thread_func);
+
+ t = pa_xnew0(pa_thread, 1);
+ t->name = pa_xstrdup(name);
+ t->thread_func = thread_func;
+ t->userdata = userdata;
+
+ if (pthread_create(&t->id, NULL, internal_thread_func, t) < 0) {
+ pa_xfree(t);
+ return NULL;
+ }
+
+ pa_atomic_inc(&t->running);
+
+ return t;
+}
+
+int pa_thread_is_running(pa_thread *t) {
+ pa_assert(t);
+
+ /* Unfortunately there is no way to tell whether a "foreign"
+ * thread is still running. See
+ * http://udrepper.livejournal.com/16844.html for more
+ * information */
+ pa_assert(t->thread_func);
+
+ return pa_atomic_load(&t->running) > 0;
+}
+
+void pa_thread_free(pa_thread *t) {
+ pa_assert(t);
+
+ pa_thread_join(t);
+
+ pa_xfree(t->name);
+ pa_xfree(t);
+}
+
+void pa_thread_free_nojoin(pa_thread *t) {
+ pa_assert(t);
+
+ pa_xfree(t->name);
+ pa_xfree(t);
+}
+
+int pa_thread_join(pa_thread *t) {
+ pa_assert(t);
+ pa_assert(t->thread_func);
+
+ if (t->joined)
+ return -1;
+
+ t->joined = true;
+ return pthread_join(t->id, NULL);
+}
+
+pa_thread* pa_thread_self(void) {
+ pa_thread *t;
+
+ if ((t = PA_STATIC_TLS_GET(current_thread)))
+ return t;
+
+ /* This is a foreign thread, let's create a pthread structure to
+ * make sure that we can always return a sensible pointer */
+
+ t = pa_xnew0(pa_thread, 1);
+ t->id = pthread_self();
+ t->joined = true;
+ pa_atomic_store(&t->running, 2);
+
+ PA_STATIC_TLS_SET(current_thread, t);
+
+ return t;
+}
+
+void* pa_thread_get_data(pa_thread *t) {
+ pa_assert(t);
+
+ return t->userdata;
+}
+
+void pa_thread_set_data(pa_thread *t, void *userdata) {
+ pa_assert(t);
+
+ t->userdata = userdata;
+}
+
+void pa_thread_set_name(pa_thread *t, const char *name) {
+ pa_assert(t);
+
+ pa_xfree(t->name);
+ t->name = pa_xstrdup(name);
+
+#ifdef __linux__
+ prctl(PR_SET_NAME, name);
+#elif defined(HAVE_PTHREAD_SETNAME_NP) && defined(OS_IS_DARWIN)
+ pthread_setname_np(name);
+#endif
+}
+
+const char *pa_thread_get_name(pa_thread *t) {
+ pa_assert(t);
+
+#ifdef __linux__
+ if (!t->name) {
+ t->name = pa_xmalloc(17);
+
+ if (prctl(PR_GET_NAME, t->name) >= 0)
+ t->name[16] = 0;
+ else {
+ pa_xfree(t->name);
+ t->name = NULL;
+ }
+ }
+#elif defined(HAVE_PTHREAD_GETNAME_NP) && defined(OS_IS_DARWIN)
+ if (!t->name) {
+ t->name = pa_xmalloc0(17);
+ pthread_getname_np(t->id, t->name, 16);
+ }
+#endif
+
+ return t->name;
+}
+
+void pa_thread_yield(void) {
+#ifdef HAVE_PTHREAD_YIELD
+ pthread_yield();
+#else
+ pa_assert_se(sched_yield() == 0);
+#endif
+}
+
+pa_tls* pa_tls_new(pa_free_cb_t free_cb) {
+ pa_tls *t;
+
+ t = pa_xnew(pa_tls, 1);
+
+ if (pthread_key_create(&t->key, free_cb) < 0) {
+ pa_xfree(t);
+ return NULL;
+ }
+
+ return t;
+}
+
+void pa_tls_free(pa_tls *t) {
+ pa_assert(t);
+
+ pa_assert_se(pthread_key_delete(t->key) == 0);
+ pa_xfree(t);
+}
+
+void *pa_tls_get(pa_tls *t) {
+ pa_assert(t);
+
+ return pthread_getspecific(t->key);
+}
+
+void *pa_tls_set(pa_tls *t, void *userdata) {
+ void *r;
+
+ r = pthread_getspecific(t->key);
+ pa_assert_se(pthread_setspecific(t->key, userdata) == 0);
+ return r;
+}
diff --git a/src/pulsecore/thread-win32.c b/src/pulsecore/thread-win32.c
new file mode 100644
index 0000000..310c04e
--- /dev/null
+++ b/src/pulsecore/thread-win32.c
@@ -0,0 +1,240 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+
+#include <windows.h>
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/once.h>
+
+#include "thread.h"
+
+struct pa_thread {
+ HANDLE thread;
+ pa_thread_func_t thread_func;
+ void *userdata;
+};
+
+struct pa_tls {
+ DWORD index;
+ pa_free_cb_t free_func;
+};
+
+struct pa_tls_monitor {
+ HANDLE thread;
+ pa_free_cb_t free_func;
+ void *data;
+};
+
+static pa_tls *thread_tls;
+static pa_once thread_tls_once = PA_ONCE_INIT;
+static pa_tls *monitor_tls;
+
+static void thread_tls_once_func(void) {
+ thread_tls = pa_tls_new(NULL);
+ assert(thread_tls);
+}
+
+static DWORD WINAPI internal_thread_func(LPVOID param) {
+ pa_thread *t = param;
+ assert(t);
+
+ pa_run_once(&thread_tls_once, thread_tls_once_func);
+ pa_tls_set(thread_tls, t);
+
+ t->thread_func(t->userdata);
+
+ return 0;
+}
+
+pa_thread* pa_thread_new(const char *name, pa_thread_func_t thread_func, void *userdata) {
+ pa_thread *t;
+ DWORD thread_id;
+
+ assert(thread_func);
+
+ t = pa_xnew(pa_thread, 1);
+ t->thread_func = thread_func;
+ t->userdata = userdata;
+
+ t->thread = CreateThread(NULL, 0, internal_thread_func, t, 0, &thread_id);
+
+ if (!t->thread) {
+ pa_xfree(t);
+ return NULL;
+ }
+
+ return t;
+}
+
+int pa_thread_is_running(pa_thread *t) {
+ DWORD code;
+
+ assert(t);
+
+ if (!GetExitCodeThread(t->thread, &code))
+ return 0;
+
+ return code == STILL_ACTIVE;
+}
+
+void pa_thread_free(pa_thread *t) {
+ assert(t);
+
+ pa_thread_join(t);
+ CloseHandle(t->thread);
+ pa_xfree(t);
+}
+
+void pa_thread_free_nojoin(pa_thread *t) {
+ pa_assert(t);
+
+ CloseHandle(t->thread);
+ pa_xfree(t);
+}
+
+int pa_thread_join(pa_thread *t) {
+ assert(t);
+
+ if (WaitForSingleObject(t->thread, INFINITE) == WAIT_FAILED)
+ return -1;
+
+ return 0;
+}
+
+pa_thread* pa_thread_self(void) {
+ pa_run_once(&thread_tls_once, thread_tls_once_func);
+ return pa_tls_get(thread_tls);
+}
+
+void* pa_thread_get_data(pa_thread *t) {
+ pa_assert(t);
+
+ return t->userdata;
+}
+
+void pa_thread_set_data(pa_thread *t, void *userdata) {
+ pa_assert(t);
+
+ t->userdata = userdata;
+}
+
+void pa_thread_set_name(pa_thread *t, const char *name) {
+ /* Not implemented */
+}
+
+const char *pa_thread_get_name(pa_thread *t) {
+ /* Not implemented */
+ return NULL;
+}
+
+void pa_thread_yield(void) {
+ Sleep(0);
+}
+
+static DWORD WINAPI monitor_thread_func(LPVOID param) {
+ struct pa_tls_monitor *m = param;
+ assert(m);
+
+ WaitForSingleObject(m->thread, INFINITE);
+
+ CloseHandle(m->thread);
+
+ m->free_func(m->data);
+
+ pa_xfree(m);
+
+ return 0;
+}
+
+pa_tls* pa_tls_new(pa_free_cb_t free_cb) {
+ pa_tls *t;
+
+ t = pa_xnew(pa_tls, 1);
+ t->index = TlsAlloc();
+ t->free_func = free_cb;
+
+ if (t->index == TLS_OUT_OF_INDEXES) {
+ pa_xfree(t);
+ return NULL;
+ }
+
+ return t;
+}
+
+void pa_tls_free(pa_tls *t) {
+ assert(t);
+
+ TlsFree(t->index);
+ pa_xfree(t);
+}
+
+void *pa_tls_get(pa_tls *t) {
+ assert(t);
+
+ return TlsGetValue(t->index);
+}
+
+void *pa_tls_set(pa_tls *t, void *userdata) {
+ void *r;
+
+ assert(t);
+
+ r = TlsGetValue(t->index);
+
+ TlsSetValue(t->index, userdata);
+
+ if (t->free_func) {
+ struct pa_tls_monitor *m;
+
+ PA_ONCE_BEGIN {
+ monitor_tls = pa_tls_new(NULL);
+ assert(monitor_tls);
+ pa_tls_set(monitor_tls, NULL);
+ } PA_ONCE_END;
+
+ m = pa_tls_get(monitor_tls);
+ if (!m) {
+ HANDLE thread;
+
+ m = pa_xnew(struct pa_tls_monitor, 1);
+
+ DuplicateHandle(GetCurrentProcess(), GetCurrentThread(),
+ GetCurrentProcess(), &m->thread, 0, FALSE,
+ DUPLICATE_SAME_ACCESS);
+
+ m->free_func = t->free_func;
+
+ pa_tls_set(monitor_tls, m);
+
+ thread = CreateThread(NULL, 0, monitor_thread_func, m, 0, NULL);
+ assert(thread);
+ CloseHandle(thread);
+ }
+
+ m->data = userdata;
+ }
+
+ return r;
+}
diff --git a/src/pulsecore/thread.h b/src/pulsecore/thread.h
new file mode 100644
index 0000000..8a14558
--- /dev/null
+++ b/src/pulsecore/thread.h
@@ -0,0 +1,117 @@
+#ifndef foopulsethreadhfoo
+#define foopulsethreadhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulse/def.h>
+#include <pulse/gccmacro.h>
+
+#include <pulsecore/once.h>
+#include <pulsecore/core-util.h>
+
+#ifndef PACKAGE
+#error "Please include config.h before including this file!"
+#endif
+
+typedef struct pa_thread pa_thread;
+
+typedef void (*pa_thread_func_t) (void *userdata);
+
+pa_thread* pa_thread_new(const char *name, pa_thread_func_t thread_func, void *userdata);
+void pa_thread_free(pa_thread *t);
+void pa_thread_free_nojoin(pa_thread *t);
+int pa_thread_join(pa_thread *t);
+int pa_thread_is_running(pa_thread *t);
+pa_thread *pa_thread_self(void);
+void pa_thread_yield(void);
+
+void* pa_thread_get_data(pa_thread *t);
+void pa_thread_set_data(pa_thread *t, void *userdata);
+
+const char *pa_thread_get_name(pa_thread *t);
+void pa_thread_set_name(pa_thread *t, const char *name);
+
+typedef struct pa_tls pa_tls;
+
+pa_tls* pa_tls_new(pa_free_cb_t free_cb);
+void pa_tls_free(pa_tls *t);
+void * pa_tls_get(pa_tls *t);
+void *pa_tls_set(pa_tls *t, void *userdata);
+
+#define PA_STATIC_TLS_DECLARE(name, free_cb) \
+ static struct { \
+ pa_once once; \
+ pa_tls *volatile tls; \
+ } name##_tls = { \
+ .once = PA_ONCE_INIT, \
+ .tls = NULL \
+ }; \
+ static void name##_tls_init(void) { \
+ name##_tls.tls = pa_tls_new(free_cb); \
+ } \
+ static inline pa_tls* name##_tls_obj(void) { \
+ pa_run_once(&name##_tls.once, name##_tls_init); \
+ return name##_tls.tls; \
+ } \
+ static void name##_tls_destructor(void) PA_GCC_DESTRUCTOR; \
+ static void name##_tls_destructor(void) { \
+ static void (*_free_cb)(void*) = free_cb; \
+ if (!pa_in_valgrind()) \
+ return; \
+ if (!name##_tls.tls) \
+ return; \
+ if (_free_cb) { \
+ void *p; \
+ if ((p = pa_tls_get(name##_tls.tls))) \
+ _free_cb(p); \
+ } \
+ pa_tls_free(name##_tls.tls); \
+ } \
+ static inline void* name##_tls_get(void) { \
+ return pa_tls_get(name##_tls_obj()); \
+ } \
+ static inline void* name##_tls_set(void *p) { \
+ return pa_tls_set(name##_tls_obj(), p); \
+ } \
+ struct __stupid_useless_struct_to_allow_trailing_semicolon
+
+#if defined(SUPPORT_TLS___THREAD) && !defined(OS_IS_WIN32)
+/* An optimized version of the above that requires no dynamic
+ * allocation if the compiler supports __thread */
+#define PA_STATIC_TLS_DECLARE_NO_FREE(name) \
+ static __thread void *name##_tls = NULL; \
+ static inline void* name##_tls_get(void) { \
+ return name##_tls; \
+ } \
+ static inline void* name##_tls_set(void *p) { \
+ void *r = name##_tls; \
+ name##_tls = p; \
+ return r; \
+ } \
+ struct __stupid_useless_struct_to_allow_trailing_semicolon
+#else
+#define PA_STATIC_TLS_DECLARE_NO_FREE(name) PA_STATIC_TLS_DECLARE(name, NULL)
+#endif
+
+#define PA_STATIC_TLS_GET(name) (name##_tls_get())
+#define PA_STATIC_TLS_SET(name, p) (name##_tls_set(p))
+
+#endif
diff --git a/src/pulsecore/time-smoother.c b/src/pulsecore/time-smoother.c
new file mode 100644
index 0000000..2c82ff6
--- /dev/null
+++ b/src/pulsecore/time-smoother.c
@@ -0,0 +1,524 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2007 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+
+#include <pulse/sample.h>
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/macro.h>
+
+#include "time-smoother.h"
+
+#define HISTORY_MAX 64
+
+/*
+ * Implementation of a time smoothing algorithm to synchronize remote
+ * clocks to a local one. Evens out noise, adjusts to clock skew and
+ * allows cheap estimations of the remote time while clock updates may
+ * be seldom and received in non-equidistant intervals.
+ *
+ * Basically, we estimate the gradient of received clock samples in a
+ * certain history window (of size 'history_time') with linear
+ * regression. With that info we estimate the remote time in
+ * 'adjust_time' ahead and smoothen our current estimation function
+ * towards that point with a 3rd order polynomial interpolation with
+ * fitting derivatives. (more or less a b-spline)
+ *
+ * The larger 'history_time' is chosen the better we will suppress
+ * noise -- but we'll adjust to clock skew slower..
+ *
+ * The larger 'adjust_time' is chosen the smoother our estimation
+ * function will be -- but we'll adjust to clock skew slower, too.
+ *
+ * If 'monotonic' is true the resulting estimation function is
+ * guaranteed to be monotonic.
+ */
+
+struct pa_smoother {
+ pa_usec_t adjust_time, history_time;
+
+ pa_usec_t time_offset;
+
+ pa_usec_t px, py; /* Point p, where we want to reach stability */
+ double dp; /* Gradient we want at point p */
+
+ pa_usec_t ex, ey; /* Point e, which we estimated before and need to smooth to */
+ double de; /* Gradient we estimated for point e */
+ pa_usec_t ry; /* The original y value for ex */
+
+ /* History of last measurements */
+ pa_usec_t history_x[HISTORY_MAX], history_y[HISTORY_MAX];
+ unsigned history_idx, n_history;
+
+ /* To even out for monotonicity */
+ pa_usec_t last_y, last_x;
+
+ /* Cached parameters for our interpolation polynomial y=ax^3+b^2+cx */
+ double a, b, c;
+ bool abc_valid:1;
+
+ bool monotonic:1;
+ bool paused:1;
+ bool smoothing:1; /* If false we skip the polynomial interpolation step */
+
+ pa_usec_t pause_time;
+
+ unsigned min_history;
+};
+
+pa_smoother* pa_smoother_new(
+ pa_usec_t adjust_time,
+ pa_usec_t history_time,
+ bool monotonic,
+ bool smoothing,
+ unsigned min_history,
+ pa_usec_t time_offset,
+ bool paused) {
+
+ pa_smoother *s;
+
+ pa_assert(adjust_time > 0);
+ pa_assert(history_time > 0);
+ pa_assert(min_history >= 2);
+ pa_assert(min_history <= HISTORY_MAX);
+
+ s = pa_xnew(pa_smoother, 1);
+ s->adjust_time = adjust_time;
+ s->history_time = history_time;
+ s->min_history = min_history;
+ s->monotonic = monotonic;
+ s->smoothing = smoothing;
+
+ pa_smoother_reset(s, time_offset, paused);
+
+ return s;
+}
+
+void pa_smoother_free(pa_smoother* s) {
+ pa_assert(s);
+
+ pa_xfree(s);
+}
+
+#define REDUCE(x) \
+ do { \
+ x = (x) % HISTORY_MAX; \
+ } while(false)
+
+#define REDUCE_INC(x) \
+ do { \
+ x = ((x)+1) % HISTORY_MAX; \
+ } while(false)
+
+static void drop_old(pa_smoother *s, pa_usec_t x) {
+
+ /* Drop items from history which are too old, but make sure to
+ * always keep min_history in the history */
+
+ while (s->n_history > s->min_history) {
+
+ if (s->history_x[s->history_idx] + s->history_time >= x)
+ /* This item is still valid, and thus all following ones
+ * are too, so let's quit this loop */
+ break;
+
+ /* Item is too old, let's drop it */
+ REDUCE_INC(s->history_idx);
+
+ s->n_history --;
+ }
+}
+
+static void add_to_history(pa_smoother *s, pa_usec_t x, pa_usec_t y) {
+ unsigned j, i;
+ pa_assert(s);
+
+ /* First try to update an existing history entry */
+ i = s->history_idx;
+ for (j = s->n_history; j > 0; j--) {
+
+ if (s->history_x[i] == x) {
+ s->history_y[i] = y;
+ return;
+ }
+
+ REDUCE_INC(i);
+ }
+
+ /* Drop old entries */
+ drop_old(s, x);
+
+ /* Calculate position for new entry */
+ j = s->history_idx + s->n_history;
+ REDUCE(j);
+
+ /* Fill in entry */
+ s->history_x[j] = x;
+ s->history_y[j] = y;
+
+ /* Adjust counter */
+ s->n_history ++;
+
+ /* And make sure we don't store more entries than fit in */
+ if (s->n_history > HISTORY_MAX) {
+ s->history_idx += s->n_history - HISTORY_MAX;
+ REDUCE(s->history_idx);
+ s->n_history = HISTORY_MAX;
+ }
+}
+
+static double avg_gradient(pa_smoother *s, pa_usec_t x) {
+ unsigned i, j, c = 0;
+ int64_t ax = 0, ay = 0, k, t;
+ double r;
+
+ /* FIXME: Optimization: Jason Newton suggested that instead of
+ * going through the history on each iteration we could calculated
+ * avg_gradient() as we go.
+ *
+ * Second idea: it might make sense to weight history entries:
+ * more recent entries should matter more than old ones. */
+
+ /* Too few measurements, assume gradient of 1 */
+ if (s->n_history < s->min_history)
+ return 1;
+
+ /* First, calculate average of all measurements */
+ i = s->history_idx;
+ for (j = s->n_history; j > 0; j--) {
+
+ ax += (int64_t) s->history_x[i];
+ ay += (int64_t) s->history_y[i];
+ c++;
+
+ REDUCE_INC(i);
+ }
+
+ pa_assert(c >= s->min_history);
+ ax /= c;
+ ay /= c;
+
+ /* Now, do linear regression */
+ k = t = 0;
+
+ i = s->history_idx;
+ for (j = s->n_history; j > 0; j--) {
+ int64_t dx, dy;
+
+ dx = (int64_t) s->history_x[i] - ax;
+ dy = (int64_t) s->history_y[i] - ay;
+
+ k += dx*dy;
+ t += dx*dx;
+
+ REDUCE_INC(i);
+ }
+
+ r = (double) k / (double) t;
+
+ return (s->monotonic && r < 0) ? 0 : r;
+}
+
+static void calc_abc(pa_smoother *s) {
+ pa_usec_t ex, ey, px, py;
+ int64_t kx, ky;
+ double de, dp;
+
+ pa_assert(s);
+
+ if (s->abc_valid)
+ return;
+
+ /* We have two points: (ex|ey) and (px|py) with two gradients at
+ * these points de and dp. We do a polynomial
+ * interpolation of degree 3 with these 6 values */
+
+ ex = s->ex; ey = s->ey;
+ px = s->px; py = s->py;
+ de = s->de; dp = s->dp;
+
+ pa_assert(ex < px);
+
+ /* To increase the dynamic range and simplify calculation, we
+ * move these values to the origin */
+ kx = (int64_t) px - (int64_t) ex;
+ ky = (int64_t) py - (int64_t) ey;
+
+ /* Calculate a, b, c for y=ax^3+bx^2+cx */
+ s->c = de;
+ s->b = (((double) (3*ky)/ (double) kx - dp - (double) (2*de))) / (double) kx;
+ s->a = (dp/(double) kx - 2*s->b - de/(double) kx) / (double) (3*kx);
+
+ s->abc_valid = true;
+}
+
+static void estimate(pa_smoother *s, pa_usec_t x, pa_usec_t *y, double *deriv) {
+ pa_assert(s);
+ pa_assert(y);
+
+ if (x >= s->px) {
+ /* Linear interpolation right from px */
+ int64_t t;
+
+ /* The requested point is right of the point where we wanted
+ * to be on track again, thus just linearly estimate */
+
+ t = (int64_t) s->py + (int64_t) llrint(s->dp * (double) (x - s->px));
+
+ if (t < 0)
+ t = 0;
+
+ *y = (pa_usec_t) t;
+
+ if (deriv)
+ *deriv = s->dp;
+
+ } else if (x <= s->ex) {
+ /* Linear interpolation left from ex */
+ int64_t t;
+
+ t = (int64_t) s->ey - (int64_t) llrint(s->de * (double) (s->ex - x));
+
+ if (t < 0)
+ t = 0;
+
+ *y = (pa_usec_t) t;
+
+ if (deriv)
+ *deriv = s->de;
+
+ } else {
+ /* Spline interpolation between ex and px */
+ double tx, ty;
+
+ /* Ok, we're not yet on track, thus let's interpolate, and
+ * make sure that the first derivative is smooth */
+
+ calc_abc(s);
+
+ /* Move to origin */
+ tx = (double) (x - s->ex);
+
+ /* Horner scheme */
+ ty = (tx * (s->c + tx * (s->b + tx * s->a)));
+
+ /* Move back from origin */
+ ty += (double) s->ey;
+
+ *y = ty >= 0 ? (pa_usec_t) llrint(ty) : 0;
+
+ /* Horner scheme */
+ if (deriv)
+ *deriv = s->c + (tx * (s->b*2 + tx * s->a*3));
+ }
+
+ /* Guarantee monotonicity */
+ if (s->monotonic) {
+
+ if (deriv && *deriv < 0)
+ *deriv = 0;
+ }
+}
+
+void pa_smoother_put(pa_smoother *s, pa_usec_t x, pa_usec_t y) {
+ pa_usec_t ney;
+ double nde;
+ bool is_new;
+
+ pa_assert(s);
+
+ /* Fix up x value */
+ if (s->paused)
+ x = s->pause_time;
+
+ x = PA_LIKELY(x >= s->time_offset) ? x - s->time_offset : 0;
+
+ is_new = x >= s->ex;
+
+ if (is_new) {
+ /* First, we calculate the position we'd estimate for x, so that
+ * we can adjust our position smoothly from this one */
+ estimate(s, x, &ney, &nde);
+ s->ex = x; s->ey = ney; s->de = nde;
+ s->ry = y;
+ }
+
+ /* Then, we add the new measurement to our history */
+ add_to_history(s, x, y);
+
+ /* And determine the average gradient of the history */
+ s->dp = avg_gradient(s, x);
+
+ /* And calculate when we want to be on track again */
+ if (s->smoothing) {
+ s->px = s->ex + s->adjust_time;
+ s->py = s->ry + (pa_usec_t) llrint(s->dp * (double) s->adjust_time);
+ } else {
+ s->px = s->ex;
+ s->py = s->ry;
+ }
+
+ s->abc_valid = false;
+
+#ifdef DEBUG_DATA
+ pa_log_debug("%p, put(%llu | %llu) = %llu", s, (unsigned long long) (x + s->time_offset), (unsigned long long) x, (unsigned long long) y);
+#endif
+}
+
+pa_usec_t pa_smoother_get(pa_smoother *s, pa_usec_t x) {
+ pa_usec_t y;
+
+ pa_assert(s);
+
+ /* Fix up x value */
+ if (s->paused)
+ x = s->pause_time;
+
+ x = PA_LIKELY(x >= s->time_offset) ? x - s->time_offset : 0;
+
+ if (s->monotonic)
+ if (x <= s->last_x)
+ x = s->last_x;
+
+ estimate(s, x, &y, NULL);
+
+ if (s->monotonic) {
+
+ /* Make sure the querier doesn't jump forth and back. */
+ s->last_x = x;
+
+ if (y < s->last_y)
+ y = s->last_y;
+ else
+ s->last_y = y;
+ }
+
+#ifdef DEBUG_DATA
+ pa_log_debug("%p, get(%llu | %llu) = %llu", s, (unsigned long long) (x + s->time_offset), (unsigned long long) x, (unsigned long long) y);
+#endif
+
+ return y;
+}
+
+void pa_smoother_set_time_offset(pa_smoother *s, pa_usec_t offset) {
+ pa_assert(s);
+
+ s->time_offset = offset;
+
+#ifdef DEBUG_DATA
+ pa_log_debug("offset(%llu)", (unsigned long long) offset);
+#endif
+}
+
+void pa_smoother_pause(pa_smoother *s, pa_usec_t x) {
+ pa_assert(s);
+
+ if (s->paused)
+ return;
+
+#ifdef DEBUG_DATA
+ pa_log_debug("pause(%llu)", (unsigned long long) x);
+#endif
+
+ s->paused = true;
+ s->pause_time = x;
+}
+
+void pa_smoother_resume(pa_smoother *s, pa_usec_t x, bool fix_now) {
+ pa_assert(s);
+
+ if (!s->paused)
+ return;
+
+ if (x < s->pause_time)
+ x = s->pause_time;
+
+#ifdef DEBUG_DATA
+ pa_log_debug("resume(%llu)", (unsigned long long) x);
+#endif
+
+ s->paused = false;
+ s->time_offset += x - s->pause_time;
+
+ if (fix_now)
+ pa_smoother_fix_now(s);
+}
+
+void pa_smoother_fix_now(pa_smoother *s) {
+ pa_assert(s);
+
+ s->px = s->ex;
+ s->py = s->ry;
+}
+
+pa_usec_t pa_smoother_translate(pa_smoother *s, pa_usec_t x, pa_usec_t y_delay) {
+ pa_usec_t ney;
+ double nde;
+
+ pa_assert(s);
+
+ /* Fix up x value */
+ if (s->paused)
+ x = s->pause_time;
+
+ x = PA_LIKELY(x >= s->time_offset) ? x - s->time_offset : 0;
+
+ estimate(s, x, &ney, &nde);
+
+ /* Play safe and take the larger gradient, so that we wakeup
+ * earlier when this is used for sleeping */
+ if (s->dp > nde)
+ nde = s->dp;
+
+#ifdef DEBUG_DATA
+ pa_log_debug("translate(%llu) = %llu (%0.2f)", (unsigned long long) y_delay, (unsigned long long) ((double) y_delay / nde), nde);
+#endif
+
+ return (pa_usec_t) llrint((double) y_delay / nde);
+}
+
+void pa_smoother_reset(pa_smoother *s, pa_usec_t time_offset, bool paused) {
+ pa_assert(s);
+
+ s->px = s->py = 0;
+ s->dp = 1;
+
+ s->ex = s->ey = s->ry = 0;
+ s->de = 1;
+
+ s->history_idx = 0;
+ s->n_history = 0;
+
+ s->last_y = s->last_x = 0;
+
+ s->abc_valid = false;
+
+ s->paused = paused;
+ s->time_offset = s->pause_time = time_offset;
+
+#ifdef DEBUG_DATA
+ pa_log_debug("reset()");
+#endif
+}
diff --git a/src/pulsecore/time-smoother.h b/src/pulsecore/time-smoother.h
new file mode 100644
index 0000000..49ae08c
--- /dev/null
+++ b/src/pulsecore/time-smoother.h
@@ -0,0 +1,57 @@
+#ifndef foopulsetimesmootherhfoo
+#define foopulsetimesmootherhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2007 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <pulsecore/macro.h>
+#include <pulse/sample.h>
+
+typedef struct pa_smoother pa_smoother;
+
+pa_smoother* pa_smoother_new(
+ pa_usec_t x_adjust_time,
+ pa_usec_t x_history_time,
+ bool monotonic,
+ bool smoothing,
+ unsigned min_history,
+ pa_usec_t x_offset,
+ bool paused);
+
+void pa_smoother_free(pa_smoother* s);
+
+/* Adds a new value to our dataset. x = local/system time, y = remote time */
+void pa_smoother_put(pa_smoother *s, pa_usec_t x, pa_usec_t y);
+
+/* Returns an interpolated value based on the dataset. x = local/system time, return value = remote time */
+pa_usec_t pa_smoother_get(pa_smoother *s, pa_usec_t x);
+
+/* Translates a time span from the remote time domain to the local one. x = local/system time when to estimate, y_delay = remote time span */
+pa_usec_t pa_smoother_translate(pa_smoother *s, pa_usec_t x, pa_usec_t y_delay);
+
+void pa_smoother_set_time_offset(pa_smoother *s, pa_usec_t x_offset);
+
+void pa_smoother_pause(pa_smoother *s, pa_usec_t x);
+void pa_smoother_resume(pa_smoother *s, pa_usec_t x, bool abrupt);
+
+void pa_smoother_reset(pa_smoother *s, pa_usec_t time_offset, bool paused);
+
+void pa_smoother_fix_now(pa_smoother *s);
+
+#endif
diff --git a/src/pulsecore/tokenizer.c b/src/pulsecore/tokenizer.c
new file mode 100644
index 0000000..e81dd6b
--- /dev/null
+++ b/src/pulsecore/tokenizer.c
@@ -0,0 +1,82 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/dynarray.h>
+#include <pulsecore/macro.h>
+
+#include "tokenizer.h"
+
+static void parse(pa_dynarray*a, const char *s, unsigned args) {
+ int infty = 0;
+ const char delimiter[] = " \t\n\r";
+ const char *p;
+
+ pa_assert(a);
+ pa_assert(s);
+
+ if (args == 0)
+ infty = 1;
+
+ p = s+strspn(s, delimiter);
+ while (*p && (infty || args >= 2)) {
+ size_t l = strcspn(p, delimiter);
+ char *n = pa_xstrndup(p, l);
+ pa_dynarray_append(a, n);
+ p += l;
+ p += strspn(p, delimiter);
+ args--;
+ }
+
+ if (args && *p) {
+ char *n = pa_xstrdup(p);
+ pa_dynarray_append(a, n);
+ }
+}
+
+pa_tokenizer* pa_tokenizer_new(const char *s, unsigned args) {
+ pa_dynarray *a;
+
+ a = pa_dynarray_new(pa_xfree);
+ parse(a, s, args);
+ return (pa_tokenizer*) a;
+}
+
+void pa_tokenizer_free(pa_tokenizer *t) {
+ pa_dynarray *a = (pa_dynarray*) t;
+
+ pa_assert(a);
+ pa_dynarray_free(a);
+}
+
+const char *pa_tokenizer_get(pa_tokenizer *t, unsigned i) {
+ pa_dynarray *a = (pa_dynarray*) t;
+
+ pa_assert(a);
+
+ return pa_dynarray_get(a, i);
+}
diff --git a/src/pulsecore/tokenizer.h b/src/pulsecore/tokenizer.h
new file mode 100644
index 0000000..806d40c
--- /dev/null
+++ b/src/pulsecore/tokenizer.h
@@ -0,0 +1,30 @@
+#ifndef footokenizerhfoo
+#define footokenizerhfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+typedef struct pa_tokenizer pa_tokenizer;
+
+pa_tokenizer* pa_tokenizer_new(const char *s, unsigned args);
+void pa_tokenizer_free(pa_tokenizer *t);
+
+const char *pa_tokenizer_get(pa_tokenizer *t, unsigned i);
+
+#endif
diff --git a/src/pulsecore/typedefs.h b/src/pulsecore/typedefs.h
new file mode 100644
index 0000000..3652f8f
--- /dev/null
+++ b/src/pulsecore/typedefs.h
@@ -0,0 +1,37 @@
+#ifndef footypedefshfoo
+#define footypedefshfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2015 Canonical Ltd.
+ Written by David Henningsson <david.henningsson@canonical.com>
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+typedef struct pa_card pa_card;
+typedef struct pa_card_profile pa_card_profile;
+typedef struct pa_client pa_client;
+typedef struct pa_core pa_core;
+typedef struct pa_device_port pa_device_port;
+typedef struct pa_sink pa_sink;
+typedef struct pa_sink_volume_change pa_sink_volume_change;
+typedef struct pa_sink_input pa_sink_input;
+typedef struct pa_source pa_source;
+typedef struct pa_source_volume_change pa_source_volume_change;
+typedef struct pa_source_output pa_source_output;
+
+
+#endif
diff --git a/src/pulsecore/usergroup.c b/src/pulsecore/usergroup.c
new file mode 100644
index 0000000..40dad28
--- /dev/null
+++ b/src/pulsecore/usergroup.c
@@ -0,0 +1,362 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2009 Ted Percival
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sys/types.h>
+#include <errno.h>
+
+#ifdef HAVE_PWD_H
+#include <pwd.h>
+#endif
+
+#ifdef HAVE_GRP_H
+#include <grp.h>
+#endif
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/macro.h>
+
+#include "usergroup.h"
+
+#ifdef HAVE_GRP_H
+
+/* Returns a suitable starting size for a getgrnam_r() or getgrgid_r() buffer,
+ plus the size of a struct group.
+ */
+static size_t starting_getgr_buflen(void) {
+ size_t full_size;
+ long n;
+#ifdef _SC_GETGR_R_SIZE_MAX
+ n = sysconf(_SC_GETGR_R_SIZE_MAX);
+#else
+ n = -1;
+#endif
+ if (n <= 0)
+ n = 512;
+
+ full_size = (size_t) n + sizeof(struct group);
+
+ if (full_size < (size_t) n) /* check for integer overflow */
+ return (size_t) n;
+
+ return full_size;
+}
+
+/* Returns a suitable starting size for a getpwnam_r() or getpwuid_r() buffer,
+ plus the size of a struct passwd.
+ */
+static size_t starting_getpw_buflen(void) {
+ long n;
+ size_t full_size;
+
+#ifdef _SC_GETPW_R_SIZE_MAX
+ n = sysconf(_SC_GETPW_R_SIZE_MAX);
+#else
+ n = -1;
+#endif
+ if (n <= 0)
+ n = 512;
+
+ full_size = (size_t) n + sizeof(struct passwd);
+
+ if (full_size < (size_t) n) /* check for integer overflow */
+ return (size_t) n;
+
+ return full_size;
+}
+
+/* Given a memory allocation (*bufptr) and its length (*buflenptr),
+ double the size of the allocation, updating the given buffer and length
+ arguments. This function should be used in conjunction with the pa_*alloc
+ and pa_xfree functions.
+
+ Unlike realloc(), this function does *not* retain the original buffer's
+ contents.
+
+ Returns 0 on success, nonzero on error. The error cause is indicated by
+ errno.
+ */
+static int expand_buffer_trashcontents(void **bufptr, size_t *buflenptr) {
+ size_t newlen;
+
+ if (!bufptr || !*bufptr || !buflenptr) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ newlen = *buflenptr * 2;
+
+ if (newlen < *buflenptr) {
+ errno = EOVERFLOW;
+ return -1;
+ }
+
+ /* Don't bother retaining memory contents; free & alloc anew */
+ pa_xfree(*bufptr);
+
+ *bufptr = pa_xmalloc(newlen);
+ *buflenptr = newlen;
+
+ return 0;
+}
+
+#ifdef HAVE_GETGRGID_R
+/* Thread-safe getgrgid() replacement.
+ Returned value should be freed using pa_getgrgid_free() when the caller is
+ finished with the returned group data.
+
+ API is the same as getgrgid(), errors are indicated by a NULL return;
+ consult errno for the error cause (zero it before calling).
+ */
+struct group *pa_getgrgid_malloc(gid_t gid) {
+ size_t buflen, getgr_buflen;
+ int err;
+ void *buf;
+ void *getgr_buf;
+ struct group *result = NULL;
+
+ buflen = starting_getgr_buflen();
+ buf = pa_xmalloc(buflen);
+
+ getgr_buflen = buflen - sizeof(struct group);
+ getgr_buf = (char *)buf + sizeof(struct group);
+
+ while ((err = getgrgid_r(gid, (struct group *)buf, getgr_buf, getgr_buflen, &result)) == ERANGE) {
+ if (expand_buffer_trashcontents(&buf, &buflen))
+ break;
+
+ getgr_buflen = buflen - sizeof(struct group);
+ getgr_buf = (char *)buf + sizeof(struct group);
+ }
+
+ if (err || !result) {
+ result = NULL;
+ if (buf) {
+ pa_xfree(buf);
+ buf = NULL;
+ }
+ }
+
+ pa_assert(result == buf || result == NULL);
+
+ return result;
+}
+
+void pa_getgrgid_free(struct group *grp) {
+ pa_xfree(grp);
+}
+
+#else /* !HAVE_GETGRGID_R */
+
+struct group *pa_getgrgid_malloc(gid_t gid) {
+ return getgrgid(gid);
+}
+
+void pa_getgrgid_free(struct group *grp) {
+ /* nothing */
+ return;
+}
+
+#endif /* !HAVE_GETGRGID_R */
+
+#ifdef HAVE_GETGRNAM_R
+/* Thread-safe getgrnam() function.
+ Returned value should be freed using pa_getgrnam_free() when the caller is
+ finished with the returned group data.
+
+ API is the same as getgrnam(), errors are indicated by a NULL return;
+ consult errno for the error cause (zero it before calling).
+ */
+struct group *pa_getgrnam_malloc(const char *name) {
+ size_t buflen, getgr_buflen;
+ int err;
+ void *buf;
+ void *getgr_buf;
+ struct group *result = NULL;
+
+ buflen = starting_getgr_buflen();
+ buf = pa_xmalloc(buflen);
+
+ getgr_buflen = buflen - sizeof(struct group);
+ getgr_buf = (char *)buf + sizeof(struct group);
+
+ while ((err = getgrnam_r(name, (struct group *)buf, getgr_buf, getgr_buflen, &result)) == ERANGE) {
+ if (expand_buffer_trashcontents(&buf, &buflen))
+ break;
+
+ getgr_buflen = buflen - sizeof(struct group);
+ getgr_buf = (char *)buf + sizeof(struct group);
+ }
+
+ if (err || !result) {
+ result = NULL;
+ if (buf) {
+ pa_xfree(buf);
+ buf = NULL;
+ }
+ }
+
+ pa_assert(result == buf || result == NULL);
+
+ return result;
+}
+
+void pa_getgrnam_free(struct group *group) {
+ pa_xfree(group);
+}
+
+#else /* !HAVE_GETGRNAM_R */
+
+struct group *pa_getgrnam_malloc(const char *name) {
+ return getgrnam(name);
+}
+
+void pa_getgrnam_free(struct group *group) {
+ /* nothing */
+ return;
+}
+
+#endif /* HAVE_GETGRNAM_R */
+
+#endif /* HAVE_GRP_H */
+
+#ifdef HAVE_PWD_H
+
+#ifdef HAVE_GETPWNAM_R
+/* Thread-safe getpwnam() function.
+ Returned value should be freed using pa_getpwnam_free() when the caller is
+ finished with the returned passwd data.
+
+ API is the same as getpwnam(), errors are indicated by a NULL return;
+ consult errno for the error cause (zero it before calling).
+ */
+struct passwd *pa_getpwnam_malloc(const char *name) {
+ size_t buflen, getpw_buflen;
+ int err;
+ void *buf;
+ void *getpw_buf;
+ struct passwd *result = NULL;
+
+ buflen = starting_getpw_buflen();
+ buf = pa_xmalloc(buflen);
+
+ getpw_buflen = buflen - sizeof(struct passwd);
+ getpw_buf = (char *)buf + sizeof(struct passwd);
+
+ while ((err = getpwnam_r(name, (struct passwd *)buf, getpw_buf, getpw_buflen, &result)) == ERANGE) {
+ if (expand_buffer_trashcontents(&buf, &buflen))
+ break;
+
+ getpw_buflen = buflen - sizeof(struct passwd);
+ getpw_buf = (char *)buf + sizeof(struct passwd);
+ }
+
+ if (err || !result) {
+ result = NULL;
+ if (buf) {
+ pa_xfree(buf);
+ buf = NULL;
+ }
+ }
+
+ pa_assert(result == buf || result == NULL);
+
+ return result;
+}
+
+void pa_getpwnam_free(struct passwd *passwd) {
+ pa_xfree(passwd);
+}
+
+#else /* !HAVE_GETPWNAM_R */
+
+struct passwd *pa_getpwnam_malloc(const char *name) {
+ return getpwnam(name);
+}
+
+void pa_getpwnam_free(struct passwd *passwd) {
+ /* nothing */
+ return;
+}
+
+#endif /* !HAVE_GETPWNAM_R */
+
+#ifdef HAVE_GETPWUID_R
+/* Thread-safe getpwuid() function.
+ Returned value should be freed using pa_getpwuid_free() when the caller is
+ finished with the returned group data.
+
+ API is the same as getpwuid(), errors are indicated by a NULL return;
+ consult errno for the error cause (zero it before calling).
+ */
+struct passwd *pa_getpwuid_malloc(uid_t uid) {
+ size_t buflen, getpw_buflen;
+ int err;
+ void *buf;
+ void *getpw_buf;
+ struct passwd *result = NULL;
+
+ buflen = starting_getpw_buflen();
+ buf = pa_xmalloc(buflen);
+
+ getpw_buflen = buflen - sizeof(struct passwd);
+ getpw_buf = (char *)buf + sizeof(struct passwd);
+
+ while ((err = getpwuid_r(uid, (struct passwd *)buf, getpw_buf, getpw_buflen, &result)) == ERANGE) {
+ if (expand_buffer_trashcontents(&buf, &buflen))
+ break;
+
+ getpw_buflen = buflen - sizeof(struct passwd);
+ getpw_buf = (char *)buf + sizeof(struct passwd);
+ }
+
+ if (err || !result) {
+ result = NULL;
+ if (buf) {
+ pa_xfree(buf);
+ buf = NULL;
+ }
+ }
+
+ pa_assert(result == buf || result == NULL);
+
+ return result;
+}
+
+void pa_getpwuid_free(struct passwd *passwd) {
+ pa_xfree(passwd);
+}
+
+#else /* !HAVE_GETPWUID_R */
+
+struct passwd *pa_getpwuid_malloc(uid_t uid) {
+ return getpwuid(uid);
+}
+
+void pa_getpwuid_free(struct passwd *passwd) {
+ /* nothing */
+ return;
+}
+
+#endif /* !HAVE_GETPWUID_R */
+
+#endif /* HAVE_PWD_H */
diff --git a/src/pulsecore/usergroup.h b/src/pulsecore/usergroup.h
new file mode 100644
index 0000000..0945059
--- /dev/null
+++ b/src/pulsecore/usergroup.h
@@ -0,0 +1,49 @@
+#ifndef foousergrouphfoo
+#define foousergrouphfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2009 Ted Percival
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/types.h>
+
+#ifndef PACKAGE
+#error "Please include config.h before including this file!"
+#endif
+
+#ifdef HAVE_GRP_H
+
+struct group *pa_getgrgid_malloc(gid_t gid);
+void pa_getgrgid_free(struct group *grp);
+
+struct group *pa_getgrnam_malloc(const char *name);
+void pa_getgrnam_free(struct group *group);
+
+#endif /* HAVE_GRP_H */
+
+#ifdef HAVE_PWD_H
+
+struct passwd *pa_getpwuid_malloc(uid_t uid);
+void pa_getpwuid_free(struct passwd *passwd);
+
+struct passwd *pa_getpwnam_malloc(const char *name);
+void pa_getpwnam_free(struct passwd *passwd);
+
+#endif /* HAVE_PWD_H */
+
+#endif /* foousergrouphfoo */
diff --git a/src/pulsecore/winerrno.h b/src/pulsecore/winerrno.h
new file mode 100644
index 0000000..052d4de
--- /dev/null
+++ b/src/pulsecore/winerrno.h
@@ -0,0 +1,89 @@
+
+/* Generated with:
+cat /usr/i686-pc-mingw32/sys-root/mingw/include/winerror.h \
+ | awk '/#define WSAE.*WSABASE/{gsub("WSA", ""); print "#undef " $2 "\n#define " $2 " WSA" $2}' \
+ | egrep -v 'EINTR|EBADF|EACCES|EFAULT|EINVAL|EMFILE|_QOS|PROVIDER|PROCTABLE'
+*/
+
+#undef EWOULDBLOCK
+#define EWOULDBLOCK WSAEWOULDBLOCK
+#undef EINPROGRESS
+#define EINPROGRESS WSAEINPROGRESS
+#undef EALREADY
+#define EALREADY WSAEALREADY
+#undef ENOTSOCK
+#define ENOTSOCK WSAENOTSOCK
+#undef EDESTADDRREQ
+#define EDESTADDRREQ WSAEDESTADDRREQ
+#undef EMSGSIZE
+#define EMSGSIZE WSAEMSGSIZE
+#undef EPROTOTYPE
+#define EPROTOTYPE WSAEPROTOTYPE
+#undef ENOPROTOOPT
+#define ENOPROTOOPT WSAENOPROTOOPT
+#undef EPROTONOSUPPORT
+#define EPROTONOSUPPORT WSAEPROTONOSUPPORT
+#undef ESOCKTNOSUPPORT
+#define ESOCKTNOSUPPORT WSAESOCKTNOSUPPORT
+#undef EOPNOTSUPP
+#define EOPNOTSUPP WSAEOPNOTSUPP
+#undef EPFNOSUPPORT
+#define EPFNOSUPPORT WSAEPFNOSUPPORT
+#undef EAFNOSUPPORT
+#define EAFNOSUPPORT WSAEAFNOSUPPORT
+#undef EADDRINUSE
+#define EADDRINUSE WSAEADDRINUSE
+#undef EADDRNOTAVAIL
+#define EADDRNOTAVAIL WSAEADDRNOTAVAIL
+#undef ENETDOWN
+#define ENETDOWN WSAENETDOWN
+#undef ENETUNREACH
+#define ENETUNREACH WSAENETUNREACH
+#undef ENETRESET
+#define ENETRESET WSAENETRESET
+#undef ECONNABORTED
+#define ECONNABORTED WSAECONNABORTED
+#undef ECONNRESET
+#define ECONNRESET WSAECONNRESET
+#undef ENOBUFS
+#define ENOBUFS WSAENOBUFS
+#undef EISCONN
+#define EISCONN WSAEISCONN
+#undef ENOTCONN
+#define ENOTCONN WSAENOTCONN
+#undef ESHUTDOWN
+#define ESHUTDOWN WSAESHUTDOWN
+#undef ETOOMANYREFS
+#define ETOOMANYREFS WSAETOOMANYREFS
+#undef ETIMEDOUT
+#define ETIMEDOUT WSAETIMEDOUT
+#undef ECONNREFUSED
+#define ECONNREFUSED WSAECONNREFUSED
+#undef ELOOP
+#define ELOOP WSAELOOP
+#undef ENAMETOOLONG
+#define ENAMETOOLONG WSAENAMETOOLONG
+#undef EHOSTDOWN
+#define EHOSTDOWN WSAEHOSTDOWN
+#undef EHOSTUNREACH
+#define EHOSTUNREACH WSAEHOSTUNREACH
+#undef ENOTEMPTY
+#define ENOTEMPTY WSAENOTEMPTY
+#undef EPROCLIM
+#define EPROCLIM WSAEPROCLIM
+#undef EUSERS
+#define EUSERS WSAEUSERS
+#undef EDQUOT
+#define EDQUOT WSAEDQUOT
+#undef ESTALE
+#define ESTALE WSAESTALE
+#undef EREMOTE
+#define EREMOTE WSAEREMOTE
+#undef EDISCON
+#define EDISCON WSAEDISCON
+#undef ENOMORE
+#define ENOMORE WSAENOMORE
+#undef ECANCELLED
+#define ECANCELLED WSAECANCELLED
+#undef EREFUSED
+#define EREFUSED WSAEREFUSED
diff --git a/src/pulsecore/x11prop.c b/src/pulsecore/x11prop.c
new file mode 100644
index 0000000..f3f7737
--- /dev/null
+++ b/src/pulsecore/x11prop.c
@@ -0,0 +1,145 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include "x11prop.h"
+
+#include <pulsecore/macro.h>
+
+#include <xcb/xproto.h>
+
+#define PA_XCB_FORMAT 8
+
+static xcb_screen_t *screen_of_display(xcb_connection_t *xcb, int screen) {
+ const xcb_setup_t *s;
+ xcb_screen_iterator_t iter;
+
+ if ((s = xcb_get_setup(xcb))) {
+ iter = xcb_setup_roots_iterator(s);
+ for (; iter.rem; --screen, xcb_screen_next(&iter))
+ if (0 == screen)
+ return iter.data;
+ }
+ return NULL;
+}
+
+void pa_x11_set_prop(xcb_connection_t *xcb, int screen, const char *name, const char *data) {
+ xcb_screen_t *xs;
+ xcb_intern_atom_reply_t *reply;
+
+ pa_assert(xcb);
+ pa_assert(name);
+ pa_assert(data);
+
+ if ((xs = screen_of_display(xcb, screen))) {
+ reply = xcb_intern_atom_reply(xcb,
+ xcb_intern_atom(xcb, 0, strlen(name), name),
+ NULL);
+
+ if (reply) {
+ xcb_change_property(xcb, XCB_PROP_MODE_REPLACE, xs->root, reply->atom,
+ XCB_ATOM_STRING, PA_XCB_FORMAT,
+ (int) strlen(data), (const void*) data);
+
+ free(reply);
+ }
+ }
+}
+
+void pa_x11_del_prop(xcb_connection_t *xcb, int screen, const char *name) {
+ xcb_screen_t *xs;
+ xcb_intern_atom_reply_t *reply;
+
+ pa_assert(xcb);
+ pa_assert(name);
+
+ if ((xs = screen_of_display(xcb, screen))) {
+ reply = xcb_intern_atom_reply(xcb,
+ xcb_intern_atom(xcb, 0, strlen(name), name),
+ NULL);
+
+ if (reply) {
+ xcb_delete_property(xcb, xs->root, reply->atom);
+ free(reply);
+ }
+ }
+}
+
+char* pa_x11_get_prop(xcb_connection_t *xcb, int screen, const char *name, char *p, size_t l) {
+ char *ret = NULL;
+ int len;
+ xcb_get_property_cookie_t req;
+ xcb_get_property_reply_t* prop = NULL;
+ xcb_screen_t *xs;
+ xcb_intern_atom_reply_t *reply;
+
+ pa_assert(xcb);
+ pa_assert(name);
+ pa_assert(p);
+
+ xs = screen_of_display(xcb, screen);
+ /*
+ * Also try and get the settings from the first screen.
+ * This allows for e.g. a Media Center to run on screen 1 (e.g. HDMI) and have
+ * different defaults (e.g. prefer the HDMI sink) than the primary screen 0
+ * which uses the Internal Audio sink.
+ */
+ if (!xs && 0 != screen)
+ xs = screen_of_display(xcb, 0);
+
+ if (xs) {
+ reply = xcb_intern_atom_reply(xcb,
+ xcb_intern_atom(xcb, 0, strlen(name), name),
+ NULL);
+
+ if (!reply)
+ goto finish;
+
+ req = xcb_get_property(xcb, 0, xs->root, reply->atom, XCB_ATOM_STRING, 0, (uint32_t)(l-1));
+ free(reply);
+ prop = xcb_get_property_reply(xcb, req, NULL);
+
+ if (!prop)
+ goto finish;
+
+ if (PA_XCB_FORMAT != prop->format)
+ goto finish;
+
+ len = xcb_get_property_value_length(prop);
+ if (len < 1 || len >= (int)l)
+ goto finish;
+
+ memcpy(p, xcb_get_property_value(prop), len);
+ p[len] = 0;
+
+ ret = p;
+ }
+
+finish:
+
+ if (prop)
+ free(prop);
+
+ return ret;
+}
diff --git a/src/pulsecore/x11prop.h b/src/pulsecore/x11prop.h
new file mode 100644
index 0000000..2b9d3f1
--- /dev/null
+++ b/src/pulsecore/x11prop.h
@@ -0,0 +1,32 @@
+#ifndef foox11prophfoo
+#define foox11prophfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2010 Colin Guthrie
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/types.h>
+
+#include <xcb/xcb.h>
+
+void pa_x11_set_prop(xcb_connection_t *xcb, int screen, const char *name, const char *data);
+void pa_x11_del_prop(xcb_connection_t *xcb, int screen, const char *name);
+char* pa_x11_get_prop(xcb_connection_t *xcb, int screen, const char *name, char *p, size_t l);
+
+#endif
diff --git a/src/pulsecore/x11wrap.c b/src/pulsecore/x11wrap.c
new file mode 100644
index 0000000..0c040cf
--- /dev/null
+++ b/src/pulsecore/x11wrap.c
@@ -0,0 +1,305 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/llist.h>
+#include <pulsecore/log.h>
+#include <pulsecore/shared.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/macro.h>
+
+#include "x11wrap.h"
+
+typedef struct pa_x11_internal pa_x11_internal;
+
+struct pa_x11_internal {
+ PA_LLIST_FIELDS(pa_x11_internal);
+ pa_x11_wrapper *wrapper;
+ pa_io_event* io_event;
+ int fd;
+};
+
+struct pa_x11_wrapper {
+ PA_REFCNT_DECLARE;
+ pa_core *core;
+
+ char *property_name;
+ Display *display;
+
+ pa_defer_event* defer_event;
+ pa_io_event* io_event;
+
+ PA_LLIST_HEAD(pa_x11_client, clients);
+ PA_LLIST_HEAD(pa_x11_internal, internals);
+};
+
+struct pa_x11_client {
+ PA_LLIST_FIELDS(pa_x11_client);
+ pa_x11_wrapper *wrapper;
+ pa_x11_event_cb_t event_cb;
+ pa_x11_kill_cb_t kill_cb;
+ void *userdata;
+};
+
+/* Dispatch all pending X11 events */
+static void work(pa_x11_wrapper *w) {
+ pa_assert(w);
+ pa_assert(PA_REFCNT_VALUE(w) >= 1);
+
+ pa_x11_wrapper_ref(w);
+
+ while (XPending(w->display)) {
+ pa_x11_client *c, *n;
+ XEvent e;
+ XNextEvent(w->display, &e);
+
+ for (c = w->clients; c; c = n) {
+ n = c->next;
+
+ if (c->event_cb)
+ if (c->event_cb(w, &e, c->userdata) != 0)
+ break;
+ }
+ }
+
+ pa_x11_wrapper_unref(w);
+}
+
+/* IO notification event for the X11 display connection */
+static void display_io_event(pa_mainloop_api *m, pa_io_event *e, int fd, pa_io_event_flags_t f, void *userdata) {
+ pa_x11_wrapper *w = userdata;
+
+ pa_assert(m);
+ pa_assert(e);
+ pa_assert(fd >= 0);
+ pa_assert(w);
+ pa_assert(PA_REFCNT_VALUE(w) >= 1);
+
+ work(w);
+}
+
+/* Deferred notification event. Called once each main loop iteration */
+static void defer_event(pa_mainloop_api *m, pa_defer_event *e, void *userdata) {
+ pa_x11_wrapper *w = userdata;
+
+ pa_assert(m);
+ pa_assert(e);
+ pa_assert(w);
+ pa_assert(PA_REFCNT_VALUE(w) >= 1);
+
+ m->defer_enable(e, 0);
+
+ work(w);
+}
+
+/* IO notification event for X11 internal connections */
+static void internal_io_event(pa_mainloop_api *m, pa_io_event *e, int fd, pa_io_event_flags_t f, void *userdata) {
+ pa_x11_wrapper *w = userdata;
+
+ pa_assert(m);
+ pa_assert(e);
+ pa_assert(fd >= 0);
+ pa_assert(w);
+ pa_assert(PA_REFCNT_VALUE(w) >= 1);
+
+ XProcessInternalConnection(w->display, fd);
+
+ work(w);
+}
+
+/* Add a new IO source for the specified X11 internal connection */
+static pa_x11_internal* x11_internal_add(pa_x11_wrapper *w, int fd) {
+ pa_x11_internal *i;
+ pa_assert(fd >= 0);
+
+ i = pa_xnew(pa_x11_internal, 1);
+ i->wrapper = w;
+ i->io_event = w->core->mainloop->io_new(w->core->mainloop, fd, PA_IO_EVENT_INPUT, internal_io_event, w);
+ i->fd = fd;
+
+ PA_LLIST_PREPEND(pa_x11_internal, w->internals, i);
+ return i;
+}
+
+/* Remove an IO source for an X11 internal connection */
+static void x11_internal_remove(pa_x11_wrapper *w, pa_x11_internal *i) {
+ pa_assert(i);
+
+ PA_LLIST_REMOVE(pa_x11_internal, w->internals, i);
+ w->core->mainloop->io_free(i->io_event);
+ pa_xfree(i);
+}
+
+/* Implementation of XConnectionWatchProc */
+static void x11_watch(Display *display, XPointer userdata, int fd, Bool opening, XPointer *watch_data) {
+ pa_x11_wrapper *w = (pa_x11_wrapper*) userdata;
+
+ pa_assert(display);
+ pa_assert(w);
+ pa_assert(fd >= 0);
+
+ if (opening)
+ *watch_data = (XPointer) x11_internal_add(w, fd);
+ else
+ x11_internal_remove(w, (pa_x11_internal*) *watch_data);
+}
+
+static pa_x11_wrapper* x11_wrapper_new(pa_core *c, const char *name, const char *t) {
+ pa_x11_wrapper*w;
+ Display *d;
+
+ if (!(d = XOpenDisplay(name))) {
+ pa_log("XOpenDisplay() failed");
+ return NULL;
+ }
+
+ w = pa_xnew(pa_x11_wrapper, 1);
+ PA_REFCNT_INIT(w);
+ w->core = c;
+ w->property_name = pa_xstrdup(t);
+ w->display = d;
+
+ PA_LLIST_HEAD_INIT(pa_x11_client, w->clients);
+ PA_LLIST_HEAD_INIT(pa_x11_internal, w->internals);
+
+ w->defer_event = c->mainloop->defer_new(c->mainloop, defer_event, w);
+ w->io_event = c->mainloop->io_new(c->mainloop, ConnectionNumber(d), PA_IO_EVENT_INPUT, display_io_event, w);
+
+ XAddConnectionWatch(d, x11_watch, (XPointer) w);
+
+ pa_assert_se(pa_shared_set(c, w->property_name, w) >= 0);
+
+ return w;
+}
+
+static void x11_wrapper_free(pa_x11_wrapper*w) {
+ pa_assert(w);
+
+ pa_assert_se(pa_shared_remove(w->core, w->property_name) >= 0);
+
+ pa_assert(!w->clients);
+
+ XRemoveConnectionWatch(w->display, x11_watch, (XPointer) w);
+ XCloseDisplay(w->display);
+
+ w->core->mainloop->io_free(w->io_event);
+ w->core->mainloop->defer_free(w->defer_event);
+
+ while (w->internals)
+ x11_internal_remove(w, w->internals);
+
+ pa_xfree(w->property_name);
+ pa_xfree(w);
+}
+
+pa_x11_wrapper* pa_x11_wrapper_get(pa_core *c, const char *name) {
+ char t[256];
+ pa_x11_wrapper *w;
+
+ pa_core_assert_ref(c);
+
+ pa_snprintf(t, sizeof(t), "x11-wrapper%s%s", name ? "@" : "", name ? name : "");
+
+ if ((w = pa_shared_get(c, t)))
+ return pa_x11_wrapper_ref(w);
+
+ return x11_wrapper_new(c, name, t);
+}
+
+pa_x11_wrapper* pa_x11_wrapper_ref(pa_x11_wrapper *w) {
+ pa_assert(w);
+ pa_assert(PA_REFCNT_VALUE(w) >= 1);
+
+ PA_REFCNT_INC(w);
+ return w;
+}
+
+void pa_x11_wrapper_unref(pa_x11_wrapper* w) {
+ pa_assert(w);
+ pa_assert(PA_REFCNT_VALUE(w) >= 1);
+
+ if (PA_REFCNT_DEC(w) > 0)
+ return;
+
+ x11_wrapper_free(w);
+}
+
+Display *pa_x11_wrapper_get_display(pa_x11_wrapper *w) {
+ pa_assert(w);
+ pa_assert(PA_REFCNT_VALUE(w) >= 1);
+
+ /* Somebody is using us, schedule a output buffer flush */
+ w->core->mainloop->defer_enable(w->defer_event, 1);
+
+ return w->display;
+}
+
+xcb_connection_t *pa_x11_wrapper_get_xcb_connection(pa_x11_wrapper *w) {
+ return XGetXCBConnection(pa_x11_wrapper_get_display(w));
+}
+
+void pa_x11_wrapper_kill(pa_x11_wrapper *w) {
+ pa_x11_client *c, *n;
+
+ pa_assert(w);
+
+ pa_x11_wrapper_ref(w);
+
+ for (c = w->clients; c; c = n) {
+ n = c->next;
+
+ if (c->kill_cb)
+ c->kill_cb(w, c->userdata);
+ }
+
+ pa_x11_wrapper_unref(w);
+}
+
+pa_x11_client* pa_x11_client_new(pa_x11_wrapper *w, pa_x11_event_cb_t event_cb, pa_x11_kill_cb_t kill_cb, void *userdata) {
+ pa_x11_client *c;
+
+ pa_assert(w);
+ pa_assert(PA_REFCNT_VALUE(w) >= 1);
+
+ c = pa_xnew(pa_x11_client, 1);
+ c->wrapper = w;
+ c->event_cb = event_cb;
+ c->kill_cb = kill_cb;
+ c->userdata = userdata;
+
+ PA_LLIST_PREPEND(pa_x11_client, w->clients, c);
+
+ return c;
+}
+
+void pa_x11_client_free(pa_x11_client *c) {
+ pa_assert(c);
+ pa_assert(c->wrapper);
+ pa_assert(PA_REFCNT_VALUE(c->wrapper) >= 1);
+
+ PA_LLIST_REMOVE(pa_x11_client, c->wrapper->clients, c);
+ pa_xfree(c);
+}
diff --git a/src/pulsecore/x11wrap.h b/src/pulsecore/x11wrap.h
new file mode 100644
index 0000000..0539303
--- /dev/null
+++ b/src/pulsecore/x11wrap.h
@@ -0,0 +1,60 @@
+#ifndef foox11wraphfoo
+#define foox11wraphfoo
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <X11/Xlib.h>
+#include <X11/Xlib-xcb.h>
+
+#include <pulsecore/core.h>
+
+typedef struct pa_x11_wrapper pa_x11_wrapper;
+
+typedef struct pa_x11_client pa_x11_client;
+
+typedef int (*pa_x11_event_cb_t)(pa_x11_wrapper *w, XEvent *e, void *userdata);
+typedef void (*pa_x11_kill_cb_t)(pa_x11_wrapper *w, void *userdata);
+
+/* Return the X11 wrapper for this core. In case no wrapper was
+ existent before, allocate a new one */
+pa_x11_wrapper* pa_x11_wrapper_get(pa_core *c, const char *name);
+
+/* Increase the wrapper's reference count by one */
+pa_x11_wrapper* pa_x11_wrapper_ref(pa_x11_wrapper *w);
+
+/* Decrease the reference counter of an X11 wrapper object */
+void pa_x11_wrapper_unref(pa_x11_wrapper* w);
+
+/* Return the X11 display object for this connection */
+Display *pa_x11_wrapper_get_display(pa_x11_wrapper *w);
+
+/* Return the XCB connection object for this connection */
+xcb_connection_t *pa_x11_wrapper_get_xcb_connection(pa_x11_wrapper *w);
+
+/* Kill the connection to the X11 display */
+void pa_x11_wrapper_kill(pa_x11_wrapper *w);
+
+/* Register an X11 client, that is called for each X11 event */
+pa_x11_client* pa_x11_client_new(pa_x11_wrapper *w, pa_x11_event_cb_t event_cb, pa_x11_kill_cb_t kill_cb, void *userdata);
+
+/* Free an X11 client object */
+void pa_x11_client_free(pa_x11_client *c);
+
+#endif