summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am1536
l---------src/daemon/Makefile1
-rw-r--r--src/daemon/PulseAudio.policy49
-rw-r--r--src/daemon/caps.c152
-rw-r--r--src/daemon/caps.h31
-rw-r--r--src/daemon/cmdline.c369
-rw-r--r--src/daemon/cmdline.h37
-rw-r--r--src/daemon/cpulimit.c247
-rw-r--r--src/daemon/cpulimit.h36
-rw-r--r--src/daemon/daemon-conf.c597
-rw-r--r--src/daemon/daemon-conf.h124
-rw-r--r--src/daemon/daemon.conf.in69
-rwxr-xr-xsrc/daemon/default.pa.in100
-rw-r--r--src/daemon/default.pa.win3243
-rw-r--r--src/daemon/dumpmodules.c158
-rw-r--r--src/daemon/dumpmodules.h33
-rwxr-xr-xsrc/daemon/esdcompat.in98
-rw-r--r--src/daemon/ltdl-bind-now.c197
-rw-r--r--src/daemon/ltdl-bind-now.h32
-rw-r--r--src/daemon/main.c850
-rw-r--r--src/daemon/polkit.c223
-rw-r--r--src/daemon/polkit.h29
-rw-r--r--src/daemon/pulseaudio-module-xsmp.desktop10
-rwxr-xr-xsrc/depmod.py73
-rw-r--r--src/map-file237
l---------src/modules/Makefile1
-rw-r--r--src/modules/alsa-util.c799
-rw-r--r--src/modules/alsa-util.h76
-rw-r--r--src/modules/bt-proximity-helper.c210
-rw-r--r--src/modules/dbus-util.c329
-rw-r--r--src/modules/dbus-util.h40
-rw-r--r--src/modules/gconf/Makefile13
-rw-r--r--src/modules/gconf/gconf-helper.c135
-rw-r--r--src/modules/gconf/module-gconf.c397
-rw-r--r--src/modules/ladspa.h603
-rw-r--r--src/modules/module-alsa-sink.c995
-rw-r--r--src/modules/module-alsa-source.c968
-rw-r--r--src/modules/module-bt-proximity.c492
-rw-r--r--src/modules/module-cli.c123
-rw-r--r--src/modules/module-combine.c1193
-rw-r--r--src/modules/module-default-device-restore.c101
-rw-r--r--src/modules/module-defs.h.m432
-rw-r--r--src/modules/module-detect.c272
-rw-r--r--src/modules/module-esound-compat-spawnfd.c80
-rw-r--r--src/modules/module-esound-compat-spawnpid.c77
-rw-r--r--src/modules/module-esound-sink.c661
-rw-r--r--src/modules/module-hal-detect.c851
-rw-r--r--src/modules/module-jack-sink.c456
-rw-r--r--src/modules/module-jack-source.c427
-rw-r--r--src/modules/module-ladspa-sink.c684
-rw-r--r--src/modules/module-lirc.c259
-rw-r--r--src/modules/module-match.c244
-rw-r--r--src/modules/module-mmkbd-evdev.c262
-rw-r--r--src/modules/module-native-protocol-fd.c89
-rw-r--r--src/modules/module-null-sink.c257
-rw-r--r--src/modules/module-oss.c1499
-rw-r--r--src/modules/module-pipe-sink.c333
-rw-r--r--src/modules/module-pipe-source.c309
-rw-r--r--src/modules/module-protocol-stub.c376
-rw-r--r--src/modules/module-remap-sink.c335
-rw-r--r--src/modules/module-rescue-streams.c164
-rw-r--r--src/modules/module-sine.c206
-rw-r--r--src/modules/module-solaris.c766
-rw-r--r--src/modules/module-suspend-on-idle.c446
-rw-r--r--src/modules/module-tunnel.c1509
-rw-r--r--src/modules/module-volume-restore.c580
-rw-r--r--src/modules/module-waveout.c649
-rw-r--r--src/modules/module-x11-bell.c171
-rw-r--r--src/modules/module-x11-publish.c198
-rw-r--r--src/modules/module-x11-xsmp.c196
-rw-r--r--src/modules/module-zeroconf-discover.c443
-rw-r--r--src/modules/module-zeroconf-publish.c650
-rw-r--r--src/modules/oss-util.c419
-rw-r--r--src/modules/oss-util.h43
-rw-r--r--src/modules/rtp/Makefile13
-rw-r--r--src/modules/rtp/module-rtp-recv.c600
-rw-r--r--src/modules/rtp/module-rtp-send.c397
-rw-r--r--src/modules/rtp/rfc2327.txt2355
-rw-r--r--src/modules/rtp/rfc2974.txt1011
-rw-r--r--src/modules/rtp/rfc3550.txt5827
-rw-r--r--src/modules/rtp/rfc3551.txt2467
-rw-r--r--src/modules/rtp/rtp.c364
-rw-r--r--src/modules/rtp/rtp.h59
-rw-r--r--src/modules/rtp/sap.c223
-rw-r--r--src/modules/rtp/sap.h48
-rw-r--r--src/modules/rtp/sdp.c261
-rw-r--r--src/modules/rtp/sdp.h52
-rw-r--r--src/pulse/Makefile13
-rw-r--r--src/pulse/browser.c453
-rw-r--r--src/pulse/browser.h100
-rw-r--r--src/pulse/cdecl.h62
-rw-r--r--src/pulse/channelmap.c533
-rw-r--r--src/pulse/channelmap.h197
-rw-r--r--src/pulse/client-conf-x11.c97
-rw-r--r--src/pulse/client-conf-x11.h33
-rw-r--r--src/pulse/client-conf.c185
-rw-r--r--src/pulse/client-conf.h54
-rw-r--r--src/pulse/client.conf.in34
-rw-r--r--src/pulse/context.c1041
-rw-r--r--src/pulse/context.h233
-rw-r--r--src/pulse/def.h397
-rw-r--r--src/pulse/error.c69
-rw-r--r--src/pulse/error.h41
-rw-r--r--src/pulse/glib-mainloop.c665
-rw-r--r--src/pulse/glib-mainloop.h65
-rw-r--r--src/pulse/internal.h230
-rw-r--r--src/pulse/introspect.c1473
-rw-r--r--src/pulse/introspect.h520
-rw-r--r--src/pulse/mainloop-api.c78
-rw-r--r--src/pulse/mainloop-api.h124
-rw-r--r--src/pulse/mainloop-signal.c230
-rw-r--r--src/pulse/mainloop-signal.h62
-rw-r--r--src/pulse/mainloop.c965
-rw-r--r--src/pulse/mainloop.h130
-rw-r--r--src/pulse/operation.c128
-rw-r--r--src/pulse/operation.h52
-rw-r--r--src/pulse/proplist.c257
-rw-r--r--src/pulse/proplist.h90
-rw-r--r--src/pulse/pulseaudio.h117
-rw-r--r--src/pulse/sample.c185
-rw-r--r--src/pulse/sample.h216
-rw-r--r--src/pulse/scache.c136
-rw-r--r--src/pulse/scache.h103
-rw-r--r--src/pulse/simple.c458
-rw-r--r--src/pulse/simple.h149
-rw-r--r--src/pulse/stream.c1839
-rw-r--r--src/pulse/stream.h515
-rw-r--r--src/pulse/subscribe.c92
-rw-r--r--src/pulse/subscribe.h64
-rw-r--r--src/pulse/thread-mainloop.c231
-rw-r--r--src/pulse/thread-mainloop.h305
-rw-r--r--src/pulse/timeval.c166
-rw-r--r--src/pulse/timeval.h67
-rw-r--r--src/pulse/utf8.c267
-rw-r--r--src/pulse/utf8.h50
-rw-r--r--src/pulse/util.c262
-rw-r--r--src/pulse/util.h62
-rw-r--r--src/pulse/version.h.in56
-rw-r--r--src/pulse/volume.c180
-rw-r--r--src/pulse/volume.h175
-rw-r--r--src/pulse/xmalloc.c130
-rw-r--r--src/pulse/xmalloc.h89
l---------src/pulsecore/Makefile1
-rw-r--r--src/pulsecore/asyncmsgq.c303
-rw-r--r--src/pulsecore/asyncmsgq.h75
-rw-r--r--src/pulsecore/asyncq.c213
-rw-r--r--src/pulsecore/asyncq.h56
-rw-r--r--src/pulsecore/atomic.h245
-rw-r--r--src/pulsecore/authkey-prop.c108
-rw-r--r--src/pulsecore/authkey-prop.h45
-rw-r--r--src/pulsecore/authkey.c238
-rw-r--r--src/pulsecore/authkey.h34
-rw-r--r--src/pulsecore/autoload.c204
-rw-r--r--src/pulsecore/autoload.h60
-rw-r--r--src/pulsecore/avahi-wrap.c197
-rw-r--r--src/pulsecore/avahi-wrap.h34
-rw-r--r--src/pulsecore/cli-command.c1423
-rw-r--r--src/pulsecore/cli-command.h45
-rw-r--r--src/pulsecore/cli-text.c450
-rw-r--r--src/pulsecore/cli-text.h44
-rw-r--r--src/pulsecore/cli.c155
-rw-r--r--src/pulsecore/cli.h40
-rw-r--r--src/pulsecore/client.c100
-rw-r--r--src/pulsecore/client.h61
-rw-r--r--src/pulsecore/conf-parser.c201
-rw-r--r--src/pulsecore/conf-parser.h49
-rw-r--r--src/pulsecore/core-def.h29
-rw-r--r--src/pulsecore/core-error.c80
-rw-r--r--src/pulsecore/core-error.h44
-rw-r--r--src/pulsecore/core-scache.c474
-rw-r--r--src/pulsecore/core-scache.h68
-rw-r--r--src/pulsecore/core-subscribe.c266
-rw-r--r--src/pulsecore/core-subscribe.h41
-rw-r--r--src/pulsecore/core-util.c1579
-rw-r--r--src/pulsecore/core-util.h132
-rw-r--r--src/pulsecore/core.c220
-rw-r--r--src/pulsecore/core.h142
-rw-r--r--src/pulsecore/creds.h56
-rw-r--r--src/pulsecore/dllmain.c57
-rw-r--r--src/pulsecore/dynarray.c111
-rw-r--r--src/pulsecore/dynarray.h51
-rw-r--r--src/pulsecore/endianmacros.h120
-rw-r--r--src/pulsecore/envelope.c783
-rw-r--r--src/pulsecore/envelope.h55
-rw-r--r--src/pulsecore/esound.h211
-rw-r--r--src/pulsecore/fdsem.c280
-rw-r--r--src/pulsecore/fdsem.h49
-rw-r--r--src/pulsecore/ffmpeg/Makefile13
-rw-r--r--src/pulsecore/ffmpeg/avcodec.h82
-rw-r--r--src/pulsecore/ffmpeg/dsputil.h1
-rw-r--r--src/pulsecore/ffmpeg/resample2.c324
-rw-r--r--src/pulsecore/flist.c234
-rw-r--r--src/pulsecore/flist.h68
-rw-r--r--src/pulsecore/g711.c2531
-rw-r--r--src/pulsecore/g711.h40
-rw-r--r--src/pulsecore/gccmacro.h90
-rw-r--r--src/pulsecore/hashmap.c234
-rw-r--r--src/pulsecore/hashmap.h63
-rw-r--r--src/pulsecore/hook-list.c120
-rw-r--r--src/pulsecore/hook-list.h69
-rw-r--r--src/pulsecore/idxset.c423
-rw-r--r--src/pulsecore/idxset.h100
-rw-r--r--src/pulsecore/inet_ntop.c81
-rw-r--r--src/pulsecore/inet_ntop.h12
-rw-r--r--src/pulsecore/inet_pton.c63
-rw-r--r--src/pulsecore/inet_pton.h12
-rw-r--r--src/pulsecore/iochannel.c426
-rw-r--r--src/pulsecore/iochannel.h93
-rw-r--r--src/pulsecore/ioline.c415
-rw-r--r--src/pulsecore/ioline.h53
-rw-r--r--src/pulsecore/ipacl.c239
-rw-r--r--src/pulsecore/ipacl.h34
-rw-r--r--src/pulsecore/llist.h109
-rw-r--r--src/pulsecore/log.c234
-rw-r--r--src/pulsecore/log.h107
-rw-r--r--src/pulsecore/ltdl-helper.c64
-rw-r--r--src/pulsecore/ltdl-helper.h34
-rw-r--r--src/pulsecore/macro.h159
-rw-r--r--src/pulsecore/mcalign.c213
-rw-r--r--src/pulsecore/mcalign.h82
-rw-r--r--src/pulsecore/memblock.c1117
-rw-r--r--src/pulsecore/memblock.h139
-rw-r--r--src/pulsecore/memblockq.c739
-rw-r--r--src/pulsecore/memblockq.h151
-rw-r--r--src/pulsecore/memchunk.c92
-rw-r--r--src/pulsecore/memchunk.h52
-rw-r--r--src/pulsecore/modargs.c324
-rw-r--r--src/pulsecore/modargs.h63
-rw-r--r--src/pulsecore/modinfo.c97
-rw-r--r--src/pulsecore/modinfo.h47
-rw-r--r--src/pulsecore/module.c308
-rw-r--r--src/pulsecore/module.h87
-rw-r--r--src/pulsecore/msgobject.c49
-rw-r--r--src/pulsecore/msgobject.h54
-rw-r--r--src/pulsecore/mutex-posix.c142
-rw-r--r--src/pulsecore/mutex-win32.c135
-rw-r--r--src/pulsecore/mutex.h50
-rw-r--r--src/pulsecore/namereg.c300
-rw-r--r--src/pulsecore/namereg.h50
-rw-r--r--src/pulsecore/native-common.h158
-rw-r--r--src/pulsecore/object.c72
-rw-r--r--src/pulsecore/object.h106
-rw-r--r--src/pulsecore/once.c96
-rw-r--r--src/pulsecore/once.h76
-rw-r--r--src/pulsecore/packet.c81
-rw-r--r--src/pulsecore/packet.h45
-rw-r--r--src/pulsecore/parseaddr.c124
-rw-r--r--src/pulsecore/parseaddr.h44
-rw-r--r--src/pulsecore/pdispatch.c348
-rw-r--r--src/pulsecore/pdispatch.h59
-rw-r--r--src/pulsecore/pid.c332
-rw-r--r--src/pulsecore/pid.h32
-rw-r--r--src/pulsecore/pipe.c162
-rw-r--r--src/pulsecore/pipe.h33
-rw-r--r--src/pulsecore/play-memblockq.c236
-rw-r--r--src/pulsecore/play-memblockq.h48
-rw-r--r--src/pulsecore/play-memchunk.c196
-rw-r--r--src/pulsecore/play-memchunk.h38
-rw-r--r--src/pulsecore/poll.c196
-rw-r--r--src/pulsecore/poll.h60
-rw-r--r--src/pulsecore/props.c140
-rw-r--r--src/pulsecore/props.h60
-rw-r--r--src/pulsecore/protocol-cli.c105
-rw-r--r--src/pulsecore/protocol-cli.h37
-rw-r--r--src/pulsecore/protocol-esound.c1471
-rw-r--r--src/pulsecore/protocol-esound.h38
-rw-r--r--src/pulsecore/protocol-http.c277
-rw-r--r--src/pulsecore/protocol-http.h37
-rw-r--r--src/pulsecore/protocol-native.c3458
-rw-r--r--src/pulsecore/protocol-native.h40
-rw-r--r--src/pulsecore/protocol-simple.c636
-rw-r--r--src/pulsecore/protocol-simple.h37
-rw-r--r--src/pulsecore/pstream-util.c64
-rw-r--r--src/pulsecore/pstream-util.h40
-rw-r--r--src/pulsecore/pstream.c1020
-rw-r--r--src/pulsecore/pstream.h68
-rw-r--r--src/pulsecore/queue.c125
-rw-r--r--src/pulsecore/queue.h42
-rw-r--r--src/pulsecore/random.c114
-rw-r--r--src/pulsecore/random.h33
-rw-r--r--src/pulsecore/refcnt.h44
-rw-r--r--src/pulsecore/resampler.c1527
-rw-r--r--src/pulsecore/resampler.h100
-rw-r--r--src/pulsecore/rtclock.c98
-rw-r--r--src/pulsecore/rtclock.h43
-rw-r--r--src/pulsecore/rtpoll.c753
-rw-r--r--src/pulsecore/rtpoll.h116
-rw-r--r--src/pulsecore/rtsig.c133
-rw-r--r--src/pulsecore/rtsig.h41
-rw-r--r--src/pulsecore/sample-util.c933
-rw-r--r--src/pulsecore/sample-util.h73
-rw-r--r--src/pulsecore/sconv-s16be.c61
-rw-r--r--src/pulsecore/sconv-s16be.h61
-rw-r--r--src/pulsecore/sconv-s16le.c251
-rw-r--r--src/pulsecore/sconv-s16le.h61
-rw-r--r--src/pulsecore/sconv.c271
-rw-r--r--src/pulsecore/sconv.h38
-rw-r--r--src/pulsecore/semaphore-posix.c69
-rw-r--r--src/pulsecore/semaphore-win32.c65
-rw-r--r--src/pulsecore/semaphore.h35
-rw-r--r--src/pulsecore/shm.c383
-rw-r--r--src/pulsecore/shm.h46
-rw-r--r--src/pulsecore/sink-input.c989
-rw-r--r--src/pulsecore/sink-input.h251
-rw-r--r--src/pulsecore/sink.c1066
-rw-r--r--src/pulsecore/sink.h188
-rw-r--r--src/pulsecore/sioman.c41
-rw-r--r--src/pulsecore/sioman.h30
-rw-r--r--src/pulsecore/socket-client.c569
-rw-r--r--src/pulsecore/socket-client.h50
-rw-r--r--src/pulsecore/socket-server.c532
-rw-r--r--src/pulsecore/socket-server.h54
-rw-r--r--src/pulsecore/socket-util.c286
-rw-r--r--src/pulsecore/socket-util.h42
-rw-r--r--src/pulsecore/sound-file-stream.c339
-rw-r--r--src/pulsecore/sound-file-stream.h31
-rw-r--r--src/pulsecore/sound-file.c211
-rw-r--r--src/pulsecore/sound-file.h35
-rw-r--r--src/pulsecore/source-output.c515
-rw-r--r--src/pulsecore/source-output.h191
-rw-r--r--src/pulsecore/source.c594
-rw-r--r--src/pulsecore/source.h179
-rw-r--r--src/pulsecore/speex/Makefile13
-rw-r--r--src/pulsecore/speex/arch.h241
-rw-r--r--src/pulsecore/speex/fixed_generic.h106
-rw-r--r--src/pulsecore/speex/resample.c1121
-rw-r--r--src/pulsecore/speex/speex_resampler.h328
-rw-r--r--src/pulsecore/speexwrap.h50
-rw-r--r--src/pulsecore/start-child.c162
-rw-r--r--src/pulsecore/start-child.h32
-rw-r--r--src/pulsecore/strbuf.c184
-rw-r--r--src/pulsecore/strbuf.h40
-rw-r--r--src/pulsecore/strlist.c163
-rw-r--r--src/pulsecore/strlist.h52
-rw-r--r--src/pulsecore/tagstruct.c673
-rw-r--r--src/pulsecore/tagstruct.h95
-rw-r--r--src/pulsecore/thread-mq.c112
-rw-r--r--src/pulsecore/thread-mq.h49
-rw-r--r--src/pulsecore/thread-posix.c197
-rw-r--r--src/pulsecore/thread-win32.c216
-rw-r--r--src/pulsecore/thread.h112
-rw-r--r--src/pulsecore/time-smoother.c382
-rw-r--r--src/pulsecore/time-smoother.h43
-rw-r--r--src/pulsecore/tokenizer.c90
-rw-r--r--src/pulsecore/tokenizer.h34
-rw-r--r--src/pulsecore/winsock.h26
-rw-r--r--src/pulsecore/x11prop.c71
-rw-r--r--src/pulsecore/x11prop.h35
-rw-r--r--src/pulsecore/x11wrap.c277
-rw-r--r--src/pulsecore/x11wrap.h54
l---------src/tests/Makefile1
-rw-r--r--src/tests/asyncmsgq-test.c110
-rw-r--r--src/tests/asyncq-test.c87
-rw-r--r--src/tests/channelmap-test.c33
-rw-r--r--src/tests/cpulimit-test.c93
-rw-r--r--src/tests/envelope-test.c248
-rw-r--r--src/tests/flist-test.c105
-rw-r--r--src/tests/get-binary-name-test.c36
-rw-r--r--src/tests/hook-list-test.c39
-rw-r--r--src/tests/interpol-test.c169
-rw-r--r--src/tests/ipacl-test.c136
-rw-r--r--src/tests/mainloop-test.c126
-rw-r--r--src/tests/mcalign-test.c112
-rw-r--r--src/tests/memblock-test.c176
-rw-r--r--src/tests/memblockq-test.c153
-rw-r--r--src/tests/mix-test.c261
-rw-r--r--src/tests/pacat-simple.c119
-rw-r--r--src/tests/parec-simple.c100
-rw-r--r--src/tests/proplist-test.c61
-rw-r--r--src/tests/queue-test.c69
-rw-r--r--src/tests/remix-test.c91
-rw-r--r--src/tests/resampler-test.c254
-rw-r--r--src/tests/rtpoll-test.c93
-rw-r--r--src/tests/sig2str-test.c39
-rw-r--r--src/tests/smoother-test.c80
-rw-r--r--src/tests/strlist-test.c42
-rw-r--r--src/tests/sync-playback.c192
-rw-r--r--src/tests/thread-mainloop-test.c79
-rw-r--r--src/tests/thread-test.c144
-rw-r--r--src/tests/utf8-test.c26
-rw-r--r--src/tests/voltest.c22
l---------src/utils/Makefile1
-rw-r--r--src/utils/pabrowse.c156
-rw-r--r--src/utils/pacat.c769
-rw-r--r--src/utils/pacmd.c188
-rw-r--r--src/utils/pactl.c930
-rwxr-xr-xsrc/utils/padsp88
-rw-r--r--src/utils/padsp.c2664
-rw-r--r--src/utils/paplay.c435
-rw-r--r--src/utils/pasuspender.c318
-rw-r--r--src/utils/pax11publish.c222
391 files changed, 109802 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 00000000..a3bb4d0e
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,1536 @@
+# $Id$
+#
+# This file is part of PulseAudio.
+#
+# Copyright 2004-2006 Lennart Poettering
+# Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+# Copyright 2006 Diego Pettenò
+#
+# PulseAudio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# PulseAudio is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with PulseAudio; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+# USA.
+
+
+###################################
+# Extra directories #
+###################################
+
+pulseincludedir=$(includedir)/pulse
+pulsecoreincludedir=$(includedir)/pulsecore
+pulseconfdir=$(sysconfdir)/pulse
+pulselibexecdir=$(libexecdir)/pulse
+xdgautostartdir=$(sysconfdir)/xdg/autostart
+
+###################################
+# Defines #
+###################################
+
+PA_BINARY=$(bindir)/pulseaudio$(EXEEXT)
+if OS_IS_WIN32
+PA_DEFAULT_CONFIG_DIR=%PULSE_ROOT%
+else
+PA_DEFAULT_CONFIG_DIR=$(pulseconfdir)
+endif
+
+###################################
+# Compiler/linker flags #
+###################################
+
+AM_CFLAGS = -I$(top_srcdir)/src -I$(top_builddir)/src/modules -I$(top_builddir)/src/modules/rtp -I$(top_builddir)/src/modules/gconf
+AM_CFLAGS += $(PTHREAD_CFLAGS) -D_POSIX_PTHREAD_SEMANTICS
+AM_CFLAGS += $(LTDLINCL)
+AM_CFLAGS += $(LIBSAMPLERATE_CFLAGS) $(LIBSNDFILE_CFLAGS)
+AM_CFLAGS += -DPA_DLSEARCHPATH=\"$(modlibexecdir)\"
+AM_CFLAGS += -DPA_DEFAULT_CONFIG_DIR=\"$(PA_DEFAULT_CONFIG_DIR)\"
+AM_CFLAGS += -DPA_BINARY=\"$(PA_BINARY)\"
+AM_CFLAGS += -DPA_SYSTEM_RUNTIME_PATH=\"$(PA_SYSTEM_RUNTIME_PATH)\"
+AM_CFLAGS += -DAO_REQUIRE_CAS
+
+# This cool debug trap works on i386/gcc only
+AM_CFLAGS += '-DDEBUG_TRAP=__asm__("int $$3")'
+
+AM_LIBADD = $(PTHREAD_LIBS)
+AM_LDADD = $(PTHREAD_LIBS)
+
+# Only required on some platforms but defined for all to avoid errors
+AM_LDFLAGS = -Wl,-no-undefined -ffunction-sections -fdata-sections -Wl,--gc-sections
+
+if STATIC_BINS
+BINLDFLAGS = -static
+endif
+
+if OS_IS_WIN32
+AM_LDFLAGS+=-Wl,--export-all-symbols
+WINSOCK_LIBS=-lwsock32 -lws2_32 -lwininet
+endif
+
+if OS_IS_WIN32
+PA_THREAD_OBJS = \
+ pulsecore/mutex-win32.c pulsecore/mutex.h \
+ pulsecore/thread-win32.c pulsecore/thread.h \
+ pulsecore/semaphore-win32.c pulsecore/semaphore.h
+else
+PA_THREAD_OBJS = \
+ pulsecore/mutex-posix.c pulsecore/mutex.h \
+ pulsecore/thread-posix.c pulsecore/thread.h \
+ pulsecore/semaphore-posix.c pulsecore/semaphore.h
+endif
+
+###################################
+# Extra files #
+###################################
+
+EXTRA_DIST = \
+ pulse/client.conf.in \
+ pulse/version.h.in \
+ daemon/daemon.conf.in \
+ daemon/default.pa.in \
+ daemon/default.pa.win32 \
+ depmod.py \
+ daemon/esdcompat.in \
+ utils/padsp \
+ modules/module-defs.h.m4 \
+ daemon/pulseaudio-module-xsmp.desktop \
+ map-file \
+ daemon/PulseAudio.policy
+
+pulseconf_DATA = \
+ default.pa \
+ daemon.conf \
+ client.conf
+
+if HAVE_X11
+xdgautostart_DATA = \
+ daemon/pulseaudio-module-xsmp.desktop
+endif
+
+BUILT_SOURCES = \
+ pulse/version.h
+
+###################################
+# Main daemon #
+###################################
+
+bin_PROGRAMS = pulseaudio
+
+pulseaudio_SOURCES = \
+ daemon/caps.h daemon/caps.c \
+ daemon/cmdline.c daemon/cmdline.h \
+ daemon/cpulimit.c daemon/cpulimit.h \
+ daemon/daemon-conf.c daemon/daemon-conf.h \
+ daemon/dumpmodules.c daemon/dumpmodules.h \
+ daemon/ltdl-bind-now.c daemon/ltdl-bind-now.h \
+ daemon/main.c \
+ pulsecore/gccmacro.h
+
+pulseaudio_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS) $(LIBSAMPLERATE_CFLAGS) $(LIBSNDFILE_CFLAGS) $(CAP_CFLAGS) $(LIBOIL_CFLAGS) $(DBUS_CFLAGS)
+pulseaudio_CPPFLAGS = $(AM_CPPFLAGS)
+pulseaudio_LDADD = $(AM_LDADD) libpulsecore.la $(LIBLTDL) $(LIBSAMPLERATE_LIBS) $(LIBSNDFILE_LIBS) $(CAP_LIBS) $(LIBOIL_LIBS) $(DBUS_LIBS)
+# This is needed because automake doesn't properly expand the foreach below
+pulseaudio_DEPENDENCIES = libpulsecore.la $(PREOPEN_LIBS)
+
+if PREOPEN_MODS
+PREOPEN_LIBS = $(PREOPEN_MODS)
+else
+PREOPEN_LIBS = $(modlibexec_LTLIBRARIES)
+endif
+
+if FORCE_PREOPEN
+pulseaudio_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) -dlpreopen force $(foreach f,$(PREOPEN_LIBS),-dlpreopen $(f))
+else
+pulseaudio_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) -dlopen force $(foreach f,$(PREOPEN_LIBS),-dlopen $(f))
+endif
+
+if HAVE_POLKIT
+
+policy_DATA = daemon/PulseAudio.policy
+
+pulseaudio_SOURCES += daemon/polkit.c daemon/polkit.h
+pulseaudio_CFLAGS += $(POLKIT_CFLAGS)
+pulseaudio_LDADD += $(POLKIT_LIBS)
+
+
+endif
+
+###################################
+# Utility programs #
+###################################
+
+bin_PROGRAMS += \
+ pacat \
+ pactl \
+ paplay \
+ pasuspender
+
+if HAVE_AF_UNIX
+bin_PROGRAMS += pacmd
+endif
+
+if HAVE_X11
+bin_PROGRAMS += pax11publish
+endif
+
+if HAVE_AVAHI
+bin_PROGRAMS += pabrowse
+endif
+
+bin_SCRIPTS = esdcompat
+
+pacat_SOURCES = utils/pacat.c
+pacat_LDADD = $(AM_LDADD) libpulse.la
+pacat_CFLAGS = $(AM_CFLAGS)
+pacat_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+paplay_SOURCES = utils/paplay.c
+paplay_LDADD = $(AM_LDADD) libpulse.la $(LIBSNDFILE_LIBS)
+paplay_CFLAGS = $(AM_CFLAGS) $(LIBSNDFILE_CFLAGS)
+paplay_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+pactl_SOURCES = utils/pactl.c pulsecore/core-util.c pulsecore/core-util.h pulsecore/core-error.c pulsecore/core-error.h pulsecore/log.c pulsecore/log.h pulsecore/once.c pulsecore/once.h $(PA_THREAD_OBJS)
+pactl_LDADD = $(AM_LDADD) libpulse.la $(LIBSNDFILE_LIBS)
+pactl_CFLAGS = $(AM_CFLAGS) $(LIBSNDFILE_CFLAGS)
+pactl_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+pasuspender_SOURCES = utils/pasuspender.c
+pasuspender_LDADD = $(AM_LDADD) libpulse.la $(LIBSNDFILE_LIBS)
+pasuspender_CFLAGS = $(AM_CFLAGS) $(LIBSNDFILE_CFLAGS)
+pasuspender_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+pacmd_SOURCES = utils/pacmd.c pulsecore/pid.c pulsecore/pid.h pulsecore/core-util.c pulsecore/core-util.h pulsecore/core-error.c pulsecore/core-error.h pulsecore/log.c pulsecore/log.h pulsecore/once.c pulsecore/once.h $(PA_THREAD_OBJS)
+pacmd_CFLAGS = $(AM_CFLAGS)
+pacmd_LDADD = $(AM_LDADD) libpulse.la
+pacmd_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+pax11publish_SOURCES = utils/pax11publish.c pulsecore/x11prop.c pulsecore/x11prop.h pulse/client-conf.c pulse/client-conf.h pulsecore/authkey.h pulsecore/authkey.c pulsecore/random.h pulsecore/random.c pulsecore/conf-parser.c pulsecore/conf-parser.h pulsecore/core-util.c pulsecore/core-util.h pulsecore/core-error.c pulsecore/core-error.h pulsecore/log.c pulsecore/log.h pulsecore/once.c pulsecore/once.h $(PA_THREAD_OBJS)
+pax11publish_CFLAGS = $(AM_CFLAGS) $(X_CFLAGS)
+pax11publish_LDADD = $(AM_LDADD) libpulse.la $(X_PRE_LIBS) -lX11 $(X_LIBS) $(X_EXTRA_LIBS)
+pax11publish_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+pabrowse_SOURCES = utils/pabrowse.c
+pabrowse_LDADD = $(AM_LDADD) libpulse.la libpulse-browse.la
+pabrowse_CFLAGS = $(AM_CFLAGS)
+pabrowse_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+###################################
+# Test programs #
+###################################
+
+check_PROGRAMS = \
+ mainloop-test \
+ mcalign-test \
+ pacat-simple \
+ parec-simple \
+ strlist-test \
+ voltest \
+ memblockq-test \
+ sync-playback \
+ interpol-test \
+ channelmap-test \
+ thread-mainloop-test \
+ utf8-test \
+ get-binary-name-test \
+ ipacl-test \
+ hook-list-test \
+ memblock-test \
+ thread-test \
+ flist-test \
+ asyncq-test \
+ asyncmsgq-test \
+ queue-test \
+ rtpoll-test \
+ sig2str-test \
+ resampler-test \
+ smoother-test \
+ mix-test \
+ remix-test \
+ envelope-test \
+ proplist-test
+
+if HAVE_SIGXCPU
+check_PROGRAMS += \
+ cpulimit-test \
+ cpulimit-test2
+endif
+
+if HAVE_GLIB20
+check_PROGRAMS += \
+ mainloop-test-glib
+endif
+
+mainloop_test_SOURCES = tests/mainloop-test.c
+mainloop_test_CFLAGS = $(AM_CFLAGS)
+mainloop_test_LDADD = $(AM_LDADD) libpulse.la
+mainloop_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+thread_mainloop_test_SOURCES = tests/thread-mainloop-test.c
+thread_mainloop_test_CFLAGS = $(AM_CFLAGS)
+thread_mainloop_test_LDADD = $(AM_LDADD) libpulse.la
+thread_mainloop_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+utf8_test_SOURCES = tests/utf8-test.c
+utf8_test_CFLAGS = $(AM_CFLAGS)
+utf8_test_LDADD = $(AM_LDADD) libpulsecore.la
+utf8_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+get_binary_name_test_SOURCES = tests/get-binary-name-test.c
+get_binary_name_test_CFLAGS = $(AM_CFLAGS)
+get_binary_name_test_LDADD = $(AM_LDADD) libpulse.la
+get_binary_name_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+ipacl_test_SOURCES = tests/ipacl-test.c \
+ pulsecore/ipacl.c pulsecore/ipacl.h \
+ pulsecore/inet_pton.c pulsecore/inet_pton.h
+ipacl_test_CFLAGS = $(AM_CFLAGS)
+ipacl_test_LDADD = $(AM_LDADD) libpulsecore.la
+ipacl_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+hook_list_test_SOURCES = tests/hook-list-test.c
+hook_list_test_CFLAGS = $(AM_CFLAGS)
+hook_list_test_LDADD = $(AM_LDADD) libpulsecore.la
+hook_list_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+memblock_test_SOURCES = tests/memblock-test.c
+memblock_test_CFLAGS = $(AM_CFLAGS)
+memblock_test_LDADD = $(AM_LDADD) libpulsecore.la
+memblock_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+thread_test_SOURCES = tests/thread-test.c
+thread_test_CFLAGS = $(AM_CFLAGS)
+thread_test_LDADD = $(AM_LDADD) libpulsecore.la
+thread_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+flist_test_SOURCES = tests/flist-test.c
+flist_test_CFLAGS = $(AM_CFLAGS)
+flist_test_LDADD = $(AM_LDADD) libpulsecore.la
+flist_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+asyncq_test_SOURCES = tests/asyncq-test.c
+asyncq_test_CFLAGS = $(AM_CFLAGS)
+asyncq_test_LDADD = $(AM_LDADD) libpulsecore.la
+asyncq_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+asyncmsgq_test_SOURCES = tests/asyncmsgq-test.c
+asyncmsgq_test_CFLAGS = $(AM_CFLAGS)
+asyncmsgq_test_LDADD = $(AM_LDADD) libpulsecore.la
+asyncmsgq_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+queue_test_SOURCES = tests/queue-test.c
+queue_test_CFLAGS = $(AM_CFLAGS)
+queue_test_LDADD = $(AM_LDADD) libpulsecore.la
+queue_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+rtpoll_test_SOURCES = tests/rtpoll-test.c
+rtpoll_test_CFLAGS = $(AM_CFLAGS)
+rtpoll_test_LDADD = $(AM_LDADD) libpulsecore.la
+rtpoll_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+mcalign_test_SOURCES = tests/mcalign-test.c
+mcalign_test_CFLAGS = $(AM_CFLAGS)
+mcalign_test_LDADD = $(AM_LDADD) $(WINSOCK_LIBS) libpulsecore.la
+mcalign_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+pacat_simple_SOURCES = tests/pacat-simple.c
+pacat_simple_LDADD = $(AM_LDADD) libpulse.la libpulse-simple.la
+pacat_simple_CFLAGS = $(AM_CFLAGS)
+pacat_simple_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+parec_simple_SOURCES = tests/parec-simple.c
+parec_simple_LDADD = $(AM_LDADD) libpulse.la libpulse-simple.la
+parec_simple_CFLAGS = $(AM_CFLAGS)
+parec_simple_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+strlist_test_SOURCES = tests/strlist-test.c
+strlist_test_CFLAGS = $(AM_CFLAGS)
+strlist_test_LDADD = $(AM_LDADD) $(WINSOCK_LIBS) libpulsecore.la libstrlist.la
+strlist_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+voltest_SOURCES = tests/voltest.c
+voltest_CFLAGS = $(AM_CFLAGS)
+voltest_LDADD = $(AM_LDADD) libpulse.la
+voltest_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+channelmap_test_SOURCES = tests/channelmap-test.c
+channelmap_test_CFLAGS = $(AM_CFLAGS)
+channelmap_test_LDADD = $(AM_LDADD) libpulse.la
+channelmap_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+cpulimit_test_SOURCES = tests/cpulimit-test.c daemon/cpulimit.c daemon/cpulimit.h
+cpulimit_test_CFLAGS = $(AM_CFLAGS)
+cpulimit_test_LDADD = $(AM_LDADD) libpulsecore.la
+cpulimit_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+cpulimit_test2_SOURCES = tests/cpulimit-test.c daemon/cpulimit.c daemon/cpulimit.h
+cpulimit_test2_CFLAGS = $(AM_CFLAGS) -DTEST2
+cpulimit_test2_LDADD = $(AM_LDADD) libpulsecore.la
+cpulimit_test2_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+mainloop_test_glib_SOURCES = $(mainloop_test_SOURCES)
+mainloop_test_glib_CFLAGS = $(mainloop_test_CFLAGS) $(GLIB20_CFLAGS) -DGLIB_MAIN_LOOP
+mainloop_test_glib_LDADD = $(mainloop_test_LDADD) $(GLIB20_LIBS) libpulse-mainloop-glib.la
+mainloop_test_glib_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+memblockq_test_SOURCES = tests/memblockq-test.c
+memblockq_test_CFLAGS = $(AM_CFLAGS)
+memblockq_test_LDADD = $(AM_LDADD) $(WINSOCK_LIBS) libpulsecore.la
+memblockq_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+sync_playback_SOURCES = tests/sync-playback.c
+sync_playback_LDADD = $(AM_LDADD) libpulse.la
+sync_playback_CFLAGS = $(AM_CFLAGS)
+sync_playback_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+interpol_test_SOURCES = tests/interpol-test.c
+interpol_test_LDADD = $(AM_LDADD) libpulse.la libpulsecore.la
+interpol_test_CFLAGS = $(AM_CFLAGS)
+interpol_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+sig2str_test_SOURCES = tests/sig2str-test.c
+sig2str_test_LDADD = $(AM_LDADD) libpulsecore.la
+sig2str_test_CFLAGS = $(AM_CFLAGS)
+sig2str_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+resampler_test_SOURCES = tests/resampler-test.c
+resampler_test_LDADD = $(AM_LDADD) libpulsecore.la
+resampler_test_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS)
+resampler_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBOIL_LIBS)
+
+mix_test_SOURCES = tests/mix-test.c
+mix_test_LDADD = $(AM_LDADD) libpulsecore.la
+mix_test_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS)
+mix_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBOIL_LIBS)
+
+remix_test_SOURCES = tests/remix-test.c
+remix_test_LDADD = $(AM_LDADD) libpulsecore.la
+remix_test_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS)
+remix_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBOIL_LIBS)
+
+smoother_test_SOURCES = tests/smoother-test.c
+smoother_test_LDADD = $(AM_LDADD) libpulsecore.la
+smoother_test_CFLAGS = $(AM_CFLAGS)
+smoother_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+envelope_test_SOURCES = tests/envelope-test.c
+envelope_test_LDADD = $(AM_LDADD) libpulsecore.la
+envelope_test_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS)
+envelope_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBOIL_LIBS)
+
+proplist_test_SOURCES = tests/proplist-test.c
+proplist_test_LDADD = $(AM_LDADD) libpulse.la
+proplist_test_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS)
+proplist_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBOIL_LIBS)
+
+###################################
+# Client library #
+###################################
+
+pulseinclude_HEADERS = \
+ pulse/cdecl.h \
+ pulse/channelmap.h \
+ pulse/context.h \
+ pulse/def.h \
+ pulse/error.h \
+ pulse/introspect.h \
+ pulse/mainloop.h \
+ pulse/mainloop-api.h \
+ pulse/mainloop-signal.h \
+ pulse/operation.h \
+ pulse/pulseaudio.h \
+ pulse/sample.h \
+ pulse/scache.h \
+ pulse/simple.h \
+ pulse/stream.h \
+ pulse/subscribe.h \
+ pulse/thread-mainloop.h \
+ pulse/timeval.h \
+ pulse/utf8.h \
+ pulse/util.h \
+ pulse/version.h \
+ pulse/volume.h \
+ pulse/xmalloc.h \
+ pulse/proplist.h
+
+if HAVE_AVAHI
+pulseinclude_HEADERS += \
+ pulse/browser.h
+endif
+
+if HAVE_GLIB20
+pulseinclude_HEADERS += \
+ pulse/glib-mainloop.h
+endif
+
+lib_LTLIBRARIES = \
+ libpulse.la \
+ libpulse-simple.la
+
+if HAVE_AVAHI
+lib_LTLIBRARIES += \
+ libpulse-browse.la
+endif
+
+if HAVE_GLIB20
+lib_LTLIBRARIES += \
+ libpulse-mainloop-glib.la
+endif
+
+# Public interface
+libpulse_la_SOURCES = \
+ pulse/cdecl.h \
+ pulse/channelmap.c pulse/channelmap.h \
+ pulse/client-conf.c pulse/client-conf.h \
+ pulse/context.c pulse/context.h \
+ pulse/def.h \
+ pulse/error.c pulse/error.h \
+ pulse/internal.h \
+ pulse/introspect.c pulse/introspect.h \
+ pulse/mainloop.c pulse/mainloop.h \
+ pulse/mainloop-api.c pulse/mainloop-api.h \
+ pulse/mainloop-signal.c pulse/mainloop-signal.h \
+ pulse/operation.c pulse/operation.h \
+ pulse/pulseaudio.h \
+ pulse/sample.c pulse/sample.h \
+ pulse/scache.c pulse/scache.h \
+ pulse/stream.c pulse/stream.h \
+ pulse/subscribe.c pulse/subscribe.h \
+ pulse/thread-mainloop.c pulse/thread-mainloop.h \
+ pulse/timeval.c pulse/timeval.h \
+ pulse/utf8.c pulse/utf8.h \
+ pulse/util.c pulse/util.h \
+ pulse/volume.c pulse/volume.h \
+ pulse/xmalloc.c pulse/xmalloc.h \
+ pulse/proplist.c pulse/proplist.h
+
+# Internal stuff that is shared with libpulsecore
+libpulse_la_SOURCES += \
+ pulsecore/authkey.c pulsecore/authkey.h \
+ pulsecore/conf-parser.c pulsecore/conf-parser.h \
+ pulsecore/core-util.c pulsecore/core-util.h \
+ pulsecore/dynarray.c pulsecore/dynarray.h \
+ pulsecore/gccmacro.h \
+ pulsecore/hashmap.c pulsecore/hashmap.h \
+ pulsecore/idxset.c pulsecore/idxset.h \
+ pulsecore/inet_ntop.c pulsecore/inet_ntop.h \
+ pulsecore/iochannel.c pulsecore/iochannel.h \
+ pulsecore/llist.h \
+ pulsecore/log.c pulsecore/log.h \
+ pulsecore/mcalign.c pulsecore/mcalign.h \
+ pulsecore/memblock.c pulsecore/memblock.h \
+ pulsecore/memblockq.c pulsecore/memblockq.h \
+ pulsecore/memchunk.c pulsecore/memchunk.h \
+ pulsecore/native-common.h \
+ pulsecore/packet.c pulsecore/packet.h \
+ pulsecore/parseaddr.c pulsecore/parseaddr.h \
+ pulsecore/pdispatch.c pulsecore/pdispatch.h \
+ pulsecore/pipe.c pulsecore/pipe.h \
+ pulsecore/poll.c pulsecore/poll.h \
+ pulsecore/pstream.c pulsecore/pstream.h \
+ pulsecore/pstream-util.c pulsecore/pstream-util.h \
+ pulsecore/queue.c pulsecore/queue.h \
+ pulsecore/random.c pulsecore/random.h \
+ pulsecore/socket-client.c pulsecore/socket-client.h \
+ pulsecore/socket-util.c pulsecore/socket-util.h \
+ pulsecore/strbuf.c pulsecore/strbuf.h \
+ pulsecore/strlist.c pulsecore/strlist.h \
+ pulsecore/tagstruct.c pulsecore/tagstruct.h \
+ pulsecore/core-error.c pulsecore/core-error.h \
+ pulsecore/winsock.h pulsecore/creds.h \
+ pulsecore/shm.c pulsecore/shm.h \
+ pulsecore/flist.c pulsecore/flist.h \
+ pulsecore/object.c pulsecore/object.h \
+ pulsecore/msgobject.c pulsecore/msgobject.h \
+ pulsecore/once.c pulsecore/once.h \
+ $(PA_THREAD_OBJS)
+
+if OS_IS_WIN32
+libpulse_la_SOURCES += \
+ pulsecore/dllmain.c
+endif
+
+if HAVE_X11
+libpulse_la_SOURCES += \
+ pulse/client-conf-x11.c pulse/client-conf-x11.h \
+ pulsecore/x11prop.c pulsecore/x11prop.h
+endif
+
+libpulse_la_CFLAGS = $(AM_CFLAGS)
+libpulse_la_LDFLAGS = -version-info $(LIBPULSE_VERSION_INFO) -Wl,-version-script=$(srcdir)/map-file
+libpulse_la_LIBADD = $(AM_LIBADD) $(WINSOCK_LIBS) $(LIBICONV)
+
+if HAVE_X11
+libpulse_la_CFLAGS += $(X_CFLAGS)
+libpulse_la_LDFLAGS += $(X_PRE_LIBS) -lX11 $(X_LIBS) $(X_EXTRA_LIBS)
+endif
+
+if HAVE_LIBASYNCNS
+libpulse_la_CFLAGS += $(LIBASYNCNS_CFLAGS)
+libpulse_la_LIBADD += $(LIBASYNCNS_LIBS)
+endif
+
+libpulse_simple_la_SOURCES = pulse/simple.c pulse/simple.h
+libpulse_simple_la_CFLAGS = $(AM_CFLAGS)
+libpulse_simple_la_LIBADD = $(AM_LIBADD) libpulse.la
+libpulse_simple_la_LDFLAGS = -version-info $(LIBPULSE_SIMPLE_VERSION_INFO) -Wl,-version-script=$(srcdir)/map-file
+
+libpulse_browse_la_SOURCES = pulse/browser.c pulse/browser.h pulsecore/avahi-wrap.c pulsecore/avahi-wrap.h pulsecore/core-util.c pulsecore/core-util.h pulsecore/core-error.c pulsecore/core-error.h pulsecore/log.c pulsecore/log.h pulsecore/once.c pulsecore/once.h $(PA_THREAD_OBJS)
+libpulse_browse_la_CFLAGS = $(AM_CFLAGS) $(AVAHI_CFLAGS)
+libpulse_browse_la_LIBADD = $(AM_LIBADD) libpulse.la $(AVAHI_LIBS)
+libpulse_browse_la_LDFLAGS = -version-info $(LIBPULSE_BROWSE_VERSION_INFO) -Wl,-version-script=$(srcdir)/map-file
+
+libpulse_mainloop_glib_la_SOURCES = pulse/glib-mainloop.h pulse/glib-mainloop.c
+libpulse_mainloop_glib_la_CFLAGS = $(AM_CFLAGS) $(GLIB20_CFLAGS)
+libpulse_mainloop_glib_la_LIBADD = $(AM_LIBADD) libpulse.la $(GLIB20_LIBS)
+libpulse_mainloop_glib_la_LDFLAGS = -version-info $(LIBPULSE_MAINLOOP_GLIB_VERSION_INFO) -Wl,-version-script=$(srcdir)/map-file
+
+###################################
+# OSS emulation #
+###################################
+
+if HAVE_OSS
+
+lib_LTLIBRARIES += libpulsedsp.la
+
+bin_SCRIPTS += utils/padsp
+
+endif
+
+libpulsedsp_la_SOURCES = utils/padsp.c
+libpulsedsp_la_CFLAGS = $(AM_CFLAGS)
+libpulsedsp_la_LIBADD = $(AM_LIBADD) libpulse.la
+libpulsedsp_la_LDFLAGS = -avoid-version
+
+###################################
+# Speex Resampler #
+###################################
+
+noinst_LTLIBRARIES = libspeex-resampler-fixed.la libspeex-resampler-float.la libffmpeg-resampler.la
+
+libspeex_resampler_fixed_la_CPPFLAGS = $(AM_CPPFLAGS) -DRANDOM_PREFIX=paspfx -DOUTSIDE_SPEEX -DFIXED_POINT
+libspeex_resampler_fixed_la_SOURCES = pulsecore/speex/resample.c pulsecore/speex/speex_resampler.h pulsecore/speex/arch.h pulsecore/speex/fixed_generic.h pulsecore/speexwrap.h
+
+libspeex_resampler_float_la_CPPFLAGS = $(AM_CPPFLAGS) -DRANDOM_PREFIX=paspfl -DOUTSIDE_SPEEX -DFLOATING_POINT
+libspeex_resampler_float_la_SOURCES = pulsecore/speex/resample.c pulsecore/speex/speex_resampler.h pulsecore/speex/arch.h
+
+libffmpeg_resampler_la_CPPFLAGS = $(AM_CPPFLAGS)
+libffmpeg_resampler_la_SOURCES = pulsecore/ffmpeg/resample2.c pulsecore/ffmpeg/avcodec.h pulsecore/ffmpeg/dsputil.h
+
+###################################
+# Daemon core library #
+###################################
+
+#pulsecoreinclude_HEADERS =
+noinst_HEADERS = \
+ pulsecore/autoload.h \
+ pulsecore/atomic.h \
+ pulsecore/cli-command.h \
+ pulsecore/cli-text.h \
+ pulsecore/client.h \
+ pulsecore/core.h \
+ pulsecore/core-def.h \
+ pulsecore/core-scache.h \
+ pulsecore/core-subscribe.h \
+ pulsecore/conf-parser.h \
+ pulsecore/core-util.h \
+ pulsecore/dynarray.h \
+ pulsecore/g711.h \
+ pulsecore/hashmap.h \
+ pulsecore/idxset.h \
+ pulsecore/log.h \
+ pulsecore/mcalign.h \
+ pulsecore/memblock.h \
+ pulsecore/memblockq.h \
+ pulsecore/memchunk.h \
+ pulsecore/modargs.h \
+ pulsecore/modinfo.h \
+ pulsecore/module.h \
+ pulsecore/namereg.h \
+ pulsecore/pid.h \
+ pulsecore/play-memchunk.h \
+ pulsecore/play-memblockq.h \
+ pulsecore/props.h \
+ pulsecore/queue.h \
+ pulsecore/random.h \
+ pulsecore/resampler.h \
+ pulsecore/sample-util.h \
+ pulsecore/sconv.h \
+ pulsecore/sink.h \
+ pulsecore/sink-input.h \
+ pulsecore/sioman.h \
+ pulsecore/sound-file.h \
+ pulsecore/sound-file-stream.h \
+ pulsecore/source.h \
+ pulsecore/source-output.h \
+ pulsecore/strbuf.h \
+ pulsecore/tokenizer.h \
+ pulsecore/creds.h \
+ pulsecore/shm.h \
+ pulsecore/llist.h \
+ pulsecore/refcnt.h \
+ pulsecore/mutex.h \
+ pulsecore/thread.h \
+ pulsecore/semaphore.h \
+ pulsecore/once.h
+
+lib_LTLIBRARIES += libpulsecore.la
+
+# Some public stuff is used even in the core
+libpulsecore_la_SOURCES = \
+ pulse/channelmap.c pulse/channelmap.h \
+ pulse/error.c pulse/error.h \
+ pulse/mainloop.c pulse/mainloop.h \
+ pulse/mainloop-api.c pulse/mainloop-api.h \
+ pulse/mainloop-signal.c pulse/mainloop-signal.h \
+ pulse/sample.c pulse/sample.h \
+ pulse/timeval.c pulse/timeval.h \
+ pulse/utf8.c pulse/utf8.h \
+ pulse/util.c pulse/util.h \
+ pulse/volume.c pulse/volume.h \
+ pulse/xmalloc.c pulse/xmalloc.h
+
+# Pure core stuff (some are shared in libpulse though).
+libpulsecore_la_SOURCES += \
+ pulsecore/autoload.c pulsecore/autoload.h \
+ pulsecore/cli-command.c pulsecore/cli-command.h \
+ pulsecore/cli-text.c pulsecore/cli-text.h \
+ pulsecore/client.c pulsecore/client.h \
+ pulsecore/conf-parser.c pulsecore/conf-parser.h \
+ pulsecore/core.c pulsecore/core.h \
+ pulsecore/core-scache.c pulsecore/core-scache.h \
+ pulsecore/core-subscribe.c pulsecore/core-subscribe.h \
+ pulsecore/core-util.c pulsecore/core-util.h \
+ pulsecore/dynarray.c pulsecore/dynarray.h \
+ pulsecore/endianmacros.h \
+ pulsecore/g711.c pulsecore/g711.h \
+ pulsecore/hashmap.c pulsecore/hashmap.h \
+ pulsecore/idxset.c pulsecore/idxset.h \
+ pulsecore/log.c pulsecore/log.h \
+ pulsecore/mcalign.c pulsecore/mcalign.h \
+ pulsecore/memblock.c pulsecore/memblock.h \
+ pulsecore/memblockq.c pulsecore/memblockq.h \
+ pulsecore/memchunk.c pulsecore/memchunk.h \
+ pulsecore/modargs.c pulsecore/modargs.h \
+ pulsecore/modinfo.c pulsecore/modinfo.h \
+ pulsecore/ltdl-helper.c pulsecore/ltdl-helper.h \
+ pulsecore/module.c pulsecore/module.h \
+ pulsecore/namereg.c pulsecore/namereg.h \
+ pulsecore/pid.c pulsecore/pid.h \
+ pulsecore/pipe.c pulsecore/pipe.h \
+ pulsecore/play-memchunk.c pulsecore/play-memchunk.h \
+ pulsecore/play-memblockq.c pulsecore/play-memblockq.h \
+ pulsecore/poll.c pulsecore/poll.h \
+ pulsecore/props.c pulsecore/props.h \
+ pulsecore/queue.c pulsecore/queue.h \
+ pulsecore/random.c pulsecore/random.h \
+ pulsecore/resampler.c pulsecore/resampler.h \
+ pulsecore/sample-util.c pulsecore/sample-util.h \
+ pulsecore/sconv.c pulsecore/sconv.h \
+ pulsecore/sconv-s16be.c pulsecore/sconv-s16be.h \
+ pulsecore/sconv-s16le.c pulsecore/sconv-s16le.h \
+ pulsecore/sink.c pulsecore/sink.h \
+ pulsecore/sink-input.c pulsecore/sink-input.h \
+ pulsecore/sioman.c pulsecore/sioman.h \
+ pulsecore/sound-file.c pulsecore/sound-file.h \
+ pulsecore/sound-file-stream.c pulsecore/sound-file-stream.h \
+ pulsecore/source.c pulsecore/source.h \
+ pulsecore/source-output.c pulsecore/source-output.h \
+ pulsecore/strbuf.c pulsecore/strbuf.h \
+ pulsecore/tokenizer.c pulsecore/tokenizer.h \
+ pulsecore/winsock.h \
+ pulsecore/core-error.c pulsecore/core-error.h \
+ pulsecore/hook-list.c pulsecore/hook-list.h \
+ pulsecore/shm.c pulsecore/shm.h \
+ pulsecore/flist.c pulsecore/flist.h \
+ pulsecore/asyncmsgq.c pulsecore/asyncmsgq.h \
+ pulsecore/asyncq.c pulsecore/asyncq.h \
+ pulsecore/thread-mq.c pulsecore/thread-mq.h \
+ pulsecore/fdsem.c pulsecore/fdsem.h \
+ pulsecore/object.c pulsecore/object.h \
+ pulsecore/msgobject.c pulsecore/msgobject.h \
+ pulsecore/rtsig.c pulsecore/rtsig.h \
+ pulsecore/rtpoll.c pulsecore/rtpoll.h \
+ pulsecore/rtclock.c pulsecore/rtclock.h \
+ pulsecore/macro.h \
+ pulsecore/once.c pulsecore/once.h \
+ pulsecore/time-smoother.c pulsecore/time-smoother.h \
+ pulsecore/start-child.c pulsecore/start-child.h \
+ pulsecore/envelope.c pulsecore/envelope.h \
+ $(PA_THREAD_OBJS)
+
+if OS_IS_WIN32
+libpulsecore_la_SOURCES += \
+ pulsecore/dllmain.c
+endif
+
+libpulsecore_la_CPPFLAGS = $(AM_CPPFLAGS) $(LIBOIL_CFLAGS)
+libpulsecore_la_LDFLAGS = -version-info $(LIBPULSECORE_VERSION_INFO)
+libpulsecore_la_LIBADD = $(AM_LIBADD) $(LIBLTDL) $(LIBSAMPLERATE_LIBS) $(LIBSNDFILE_LIBS) $(WINSOCK_LIBS) $(LIBOIL_LIBS) $(LIBICONV) libspeex-resampler-fixed.la libspeex-resampler-float.la libffmpeg-resampler.la
+
+###################################
+# Plug-in support libraries #
+###################################
+
+#pulsecoreinclude_HEADERS +=
+noinst_HEADERS += \
+ pulsecore/socket-util.h \
+ pulsecore/iochannel.h \
+ pulsecore/socket-server.h \
+ pulsecore/ipacl.h \
+ pulsecore/socket-client.h \
+ pulsecore/parseaddr.h \
+ pulsecore/packet.h \
+ pulsecore/pstream.h \
+ pulsecore/ioline.h \
+ pulsecore/cli.h \
+ pulsecore/protocol-cli.h \
+ pulsecore/tagstruct.h \
+ pulsecore/pstream-util.h \
+ pulsecore/pdispatch.h \
+ pulsecore/authkey.h \
+ pulsecore/authkey-prop.h \
+ pulsecore/strlist.h \
+ pulsecore/protocol-simple.h \
+ pulsecore/esound.h \
+ pulsecore/protocol-esound.h \
+ pulsecore/native-common.h \
+ pulsecore/protocol-native.h \
+ pulsecore/protocol-http.h
+
+### Warning! Due to an obscure bug in libtool/automake it is required
+### that the libraries in modlibexec_LTLIBRARIES are specified in-order,
+### i.e. libraries near the end of the list depend on libraries near
+### the head, and not the other way!
+
+modlibexec_LTLIBRARIES = \
+ libsocket-util.la \
+ libiochannel.la \
+ libsocket-server.la \
+ libipacl.la \
+ libparseaddr.la \
+ libsocket-client.la \
+ libpacket.la \
+ libpstream.la \
+ libioline.la \
+ libcli.la \
+ libprotocol-cli.la \
+ libtagstruct.la \
+ libpstream-util.la \
+ libpdispatch.la \
+ libauthkey.la \
+ libauthkey-prop.la \
+ libstrlist.la \
+ libprotocol-simple.la \
+ libprotocol-http.la \
+ libprotocol-native.la \
+ libprotocol-esound.la
+
+# We need to emulate sendmsg/recvmsg to support this on Win32
+if !OS_IS_WIN32
+modlibexec_LTLIBRARIES += \
+ librtp.la
+endif
+
+if HAVE_X11
+#pulsecoreinclude_HEADERS +=
+noinst_HEADERS += \
+ pulsecore/x11wrap.h \
+ pulsecore/x11prop.h
+
+modlibexec_LTLIBRARIES += \
+ libx11wrap.la \
+ libx11prop.la
+endif
+
+if HAVE_AVAHI
+#pulsecoreinclude_HEADERS +=
+noinst_HEADERS += \
+ pulsecore/avahi-wrap.h
+
+modlibexec_LTLIBRARIES += \
+ libavahi-wrap.la
+endif
+
+libprotocol_simple_la_SOURCES = pulsecore/protocol-simple.c pulsecore/protocol-simple.h
+libprotocol_simple_la_LDFLAGS = -avoid-version
+libprotocol_simple_la_LIBADD = $(AM_LIBADD) libpulsecore.la libsocket-server.la libiochannel.la
+
+libsocket_server_la_SOURCES = \
+ pulsecore/inet_ntop.c pulsecore/inet_ntop.h \
+ pulsecore/inet_pton.c pulsecore/inet_pton.h \
+ pulsecore/socket-server.c pulsecore/socket-server.h
+libsocket_server_la_LDFLAGS = -avoid-version
+libsocket_server_la_LIBADD = $(AM_LIBADD) libpulsecore.la libiochannel.la libsocket-util.la $(LIBWRAP_LIBS) $(WINSOCK_LIBS)
+
+libipacl_la_SOURCES = pulsecore/ipacl.h pulsecore/ipacl.c \
+ pulsecore/inet_pton.c pulsecore/inet_pton.h
+libipacl_la_LDFLAGS = -avoid-version
+libipacl_la_LIBADD = $(AM_LIBADD) libpulsecore.la $(WINSOCK_LIBS)
+
+libsocket_client_la_SOURCES = pulsecore/socket-client.c pulsecore/socket-client.h
+libsocket_client_la_LDFLAGS = -avoid-version
+libsocket_client_la_LIBADD = $(AM_LIBADD) libpulsecore.la libiochannel.la libsocket-util.la libparseaddr.la $(LIBASYNCNS_LIBS) $(WINSOCK_LIBS)
+libsocket_client_la_CFLAGS = $(AM_CFLAGS) $(LIBASYNCNS_CFLAGS)
+
+libparseaddr_la_SOURCES = pulsecore/parseaddr.c pulsecore/parseaddr.h
+libparseaddr_la_LDFLAGS = -avoid-version
+libparseaddr_la_LIBADD = $(AM_LIBADD) libpulsecore.la
+
+libpstream_la_SOURCES = pulsecore/pstream.c pulsecore/pstream.h
+libpstream_la_LDFLAGS = -avoid-version
+libpstream_la_LIBADD = $(AM_LIBADD) libpulsecore.la libpacket.la libiochannel.la $(WINSOCK_LIBS)
+
+libpstream_util_la_SOURCES = pulsecore/pstream-util.c pulsecore/pstream-util.h
+libpstream_util_la_LDFLAGS = -avoid-version
+libpstream_util_la_LIBADD = $(AM_LIBADD) libpacket.la libpstream.la libtagstruct.la
+
+libpdispatch_la_SOURCES = pulsecore/pdispatch.c pulsecore/pdispatch.h
+libpdispatch_la_LDFLAGS = -avoid-version
+libpdispatch_la_LIBADD = $(AM_LIBADD) libtagstruct.la libpulsecore.la
+
+libiochannel_la_SOURCES = pulsecore/iochannel.c pulsecore/iochannel.h
+libiochannel_la_LDFLAGS = -avoid-version
+libiochannel_la_LIBADD = $(AM_LIBADD) libsocket-util.la libpulsecore.la $(WINSOCK_LIBS)
+
+libpacket_la_SOURCES = pulsecore/packet.c pulsecore/packet.h
+libpacket_la_LDFLAGS = -avoid-version
+libpacket_la_LIBADD = $(AM_LIBADD) libpulsecore.la
+
+libioline_la_SOURCES = pulsecore/ioline.c pulsecore/ioline.h
+libioline_la_LDFLAGS = -avoid-version
+libioline_la_LIBADD = $(AM_LIBADD) libiochannel.la libpulsecore.la
+
+libcli_la_SOURCES = pulsecore/cli.c pulsecore/cli.h
+libcli_la_CPPFLAGS = $(AM_CPPFLAGS)
+libcli_la_LDFLAGS = -avoid-version
+libcli_la_LIBADD = $(AM_LIBADD) libiochannel.la libioline.la libpulsecore.la
+
+libstrlist_la_SOURCES = pulsecore/strlist.c pulsecore/strlist.h
+libstrlist_la_LDFLAGS = -avoid-version
+libstrlist_la_LIBADD = $(AM_LIBADD) libpulsecore.la
+
+libprotocol_cli_la_SOURCES = pulsecore/protocol-cli.c pulsecore/protocol-cli.h
+libprotocol_cli_la_LDFLAGS = -avoid-version
+libprotocol_cli_la_LIBADD = $(AM_LIBADD) libsocket-server.la libiochannel.la libcli.la libpulsecore.la
+
+libprotocol_http_la_SOURCES = pulsecore/protocol-http.c pulsecore/protocol-http.h
+libprotocol_http_la_LDFLAGS = -avoid-version
+libprotocol_http_la_LIBADD = $(AM_LIBADD) libsocket-server.la libioline.la libpulsecore.la libiochannel.la
+
+libprotocol_native_la_SOURCES = pulsecore/protocol-native.c pulsecore/protocol-native.h pulsecore/native-common.h
+libprotocol_native_la_LDFLAGS = -avoid-version
+libprotocol_native_la_LIBADD = $(AM_LIBADD) libsocket-server.la libpstream.la libpstream-util.la libpdispatch.la libtagstruct.la libauthkey.la libauthkey-prop.la libstrlist.la libpulsecore.la libiochannel.la libipacl.la
+
+libtagstruct_la_SOURCES = pulsecore/tagstruct.c pulsecore/tagstruct.h
+libtagstruct_la_LDFLAGS = -avoid-version
+libtagstruct_la_LIBADD = $(AM_LIBADD) libpulsecore.la $(WINSOCK_LIBS)
+
+libprotocol_esound_la_SOURCES = pulsecore/protocol-esound.c pulsecore/protocol-esound.h pulsecore/esound.h
+libprotocol_esound_la_LDFLAGS = -avoid-version
+libprotocol_esound_la_LIBADD = $(AM_LIBADD) libsocket-server.la libiochannel.la libauthkey.la libpulsecore.la libipacl.la
+
+libauthkey_la_SOURCES = pulsecore/authkey.c pulsecore/authkey.h
+libauthkey_la_LDFLAGS = -avoid-version
+libauthkey_la_LIBADD = $(AM_LIBADD) libpulsecore.la
+
+libauthkey_prop_la_SOURCES = pulsecore/authkey-prop.c pulsecore/authkey-prop.h
+libauthkey_prop_la_LDFLAGS = -avoid-version
+libauthkey_prop_la_LIBADD = $(AM_LIBADD) libpulsecore.la
+
+libsocket_util_la_SOURCES = \
+ pulsecore/inet_ntop.c pulsecore/inet_ntop.h \
+ pulsecore/socket-util.c pulsecore/socket-util.h
+libsocket_util_la_LDFLAGS = -avoid-version
+libsocket_util_la_LIBADD = $(AM_LIBADD) $(WINSOCK_LIBS) libpulsecore.la
+
+librtp_la_SOURCES = modules/rtp/rtp.c modules/rtp/rtp.h modules/rtp/sdp.c modules/rtp/sdp.h modules/rtp/sap.c modules/rtp/sap.h
+librtp_la_LDFLAGS = -avoid-version
+librtp_la_LIBADD = $(AM_LIBADD) libpulsecore.la
+
+# X11
+
+libx11wrap_la_SOURCES = pulsecore/x11wrap.c pulsecore/x11wrap.h
+libx11wrap_la_LDFLAGS = -avoid-version
+libx11wrap_la_CFLAGS = $(AM_CFLAGS) $(X_CFLAGS)
+libx11wrap_la_LIBADD = $(AM_LIBADD) $(X_PRE_LIBS) -lX11 $(X_LIBS) $(X_EXTRA_LIBS) libpulsecore.la
+
+libx11prop_la_SOURCES = pulsecore/x11prop.c pulsecore/x11prop.h
+libx11prop_la_LDFLAGS = -avoid-version
+libx11prop_la_CFLAGS = $(AM_CFLAGS) $(X_CFLAGS)
+libx11prop_la_LIBADD = $(AM_LIBADD) $(X_PRE_LIBS) -lX11 $(X_LIBS) $(X_EXTRA_LIBS)
+
+# Avahi
+
+libavahi_wrap_la_SOURCES = pulsecore/avahi-wrap.c pulsecore/avahi-wrap.h
+libavahi_wrap_la_LDFLAGS = -avoid-version
+libavahi_wrap_la_CFLAGS = $(AM_CFLAGS) $(AVAHI_CFLAGS)
+libavahi_wrap_la_LIBADD = $(AM_LIBADD) $(AVAHI_CFLAGS) libpulsecore.la
+
+###################################
+# Plug-in libraries #
+###################################
+
+modlibexec_LTLIBRARIES += \
+ module-cli.la \
+ module-cli-protocol-tcp.la \
+ module-simple-protocol-tcp.la \
+ module-null-sink.la \
+ module-detect.la \
+ module-volume-restore.la \
+ module-default-device-restore.la \
+ module-rescue-streams.la \
+ module-suspend-on-idle.la \
+ module-http-protocol-tcp.la \
+ module-sine.la \
+ module-native-protocol-tcp.la \
+ module-native-protocol-fd.la \
+ module-esound-protocol-tcp.la \
+ module-combine.la \
+ module-remap-sink.la \
+ module-ladspa-sink.la \
+ module-esound-sink.la \
+ module-tunnel-sink.la \
+ module-tunnel-source.la
+
+# See comment at librtp.la above
+if !OS_IS_WIN32
+modlibexec_LTLIBRARIES += \
+ module-rtp-send.la \
+ module-rtp-recv.la
+endif
+
+if HAVE_AF_UNIX
+modlibexec_LTLIBRARIES += \
+ module-cli-protocol-unix.la \
+ module-simple-protocol-unix.la \
+ module-http-protocol-unix.la \
+ module-native-protocol-unix.la \
+ module-esound-protocol-unix.la
+endif
+
+if HAVE_MKFIFO
+modlibexec_LTLIBRARIES += \
+ module-pipe-sink.la \
+ module-pipe-source.la
+endif
+
+if !OS_IS_WIN32
+modlibexec_LTLIBRARIES += \
+ module-esound-compat-spawnfd.la \
+ module-esound-compat-spawnpid.la
+endif
+
+if HAVE_REGEX
+modlibexec_LTLIBRARIES += \
+ module-match.la
+endif
+
+if HAVE_X11
+modlibexec_LTLIBRARIES += \
+ module-x11-bell.la \
+ module-x11-publish.la \
+ module-x11-xsmp.la
+endif
+
+if HAVE_OSS
+modlibexec_LTLIBRARIES += \
+ liboss-util.la \
+ module-oss.la
+endif
+
+if HAVE_ALSA
+modlibexec_LTLIBRARIES += \
+ libalsa-util.la \
+ module-alsa-sink.la \
+ module-alsa-source.la
+endif
+
+if HAVE_SOLARIS
+modlibexec_LTLIBRARIES += \
+ module-solaris.la
+endif
+
+if HAVE_AVAHI
+modlibexec_LTLIBRARIES += \
+ module-zeroconf-publish.la \
+ module-zeroconf-discover.la
+endif
+
+if HAVE_LIRC
+modlibexec_LTLIBRARIES += \
+ module-lirc.la
+endif
+
+if HAVE_EVDEV
+modlibexec_LTLIBRARIES += \
+ module-mmkbd-evdev.la
+endif
+
+if HAVE_JACK
+modlibexec_LTLIBRARIES += \
+ module-jack-sink.la \
+ module-jack-source.la
+endif
+
+pulselibexec_PROGRAMS =
+
+if HAVE_GCONF
+modlibexec_LTLIBRARIES += \
+ module-gconf.la
+
+pulselibexec_PROGRAMS += \
+ gconf-helper
+endif
+
+#if OS_IS_WIN32
+#modlibexec_LTLIBRARIES += \
+# module-waveout.la
+#endif
+
+if HAVE_HAL
+modlibexec_LTLIBRARIES += \
+ libdbus-util.la \
+ module-hal-detect.la
+endif
+
+if HAVE_BLUEZ
+modlibexec_LTLIBRARIES += \
+ module-bt-proximity.la
+
+pulselibexec_PROGRAMS += \
+ bt-proximity-helper
+endif
+
+# These are generated by a M4 script
+
+SYMDEF_FILES = \
+ modules/module-cli-symdef.h \
+ modules/module-cli-protocol-tcp-symdef.h \
+ modules/module-cli-protocol-unix-symdef.h \
+ modules/module-pipe-sink-symdef.h \
+ modules/module-pipe-source-symdef.h \
+ modules/module-simple-protocol-tcp-symdef.h \
+ modules/module-simple-protocol-unix-symdef.h \
+ modules/module-esound-protocol-tcp-symdef.h \
+ modules/module-esound-protocol-unix-symdef.h \
+ modules/module-native-protocol-tcp-symdef.h \
+ modules/module-native-protocol-unix-symdef.h \
+ modules/module-native-protocol-fd-symdef.h \
+ modules/module-sine-symdef.h \
+ modules/module-combine-symdef.h \
+ modules/module-remap-sink-symdef.h \
+ modules/module-ladspa-sink-symdef.h \
+ modules/module-esound-compat-spawnfd-symdef.h \
+ modules/module-esound-compat-spawnpid-symdef.h \
+ modules/module-match-symdef.h \
+ modules/module-tunnel-sink-symdef.h \
+ modules/module-tunnel-source-symdef.h \
+ modules/module-null-sink-symdef.h \
+ modules/module-esound-sink-symdef.h \
+ modules/module-zeroconf-publish-symdef.h \
+ modules/module-zeroconf-discover-symdef.h \
+ modules/module-lirc-symdef.h \
+ modules/module-mmkbd-evdev-symdef.h \
+ modules/module-http-protocol-tcp-symdef.h \
+ modules/module-http-protocol-unix-symdef.h \
+ modules/module-x11-bell-symdef.h \
+ modules/module-x11-publish-symdef.h \
+ modules/module-x11-xsmp-symdef.h \
+ modules/module-oss-symdef.h \
+ modules/module-alsa-sink-symdef.h \
+ modules/module-alsa-source-symdef.h \
+ modules/module-solaris-symdef.h \
+ modules/module-waveout-symdef.h \
+ modules/module-detect-symdef.h \
+ modules/rtp/module-rtp-send-symdef.h \
+ modules/rtp/module-rtp-recv-symdef.h \
+ modules/module-jack-sink-symdef.h \
+ modules/module-jack-source-symdef.h \
+ modules/module-volume-restore-symdef.h \
+ modules/module-default-device-restore-symdef.h \
+ modules/module-rescue-streams-symdef.h \
+ modules/module-suspend-on-idle-symdef.h \
+ modules/module-hal-detect-symdef.h \
+ modules/module-bt-proximity-symdef.h \
+ modules/gconf/module-gconf-symdef.h
+
+EXTRA_DIST += $(SYMDEF_FILES)
+BUILT_SOURCES += $(SYMDEF_FILES)
+
+$(SYMDEF_FILES): modules/module-defs.h.m4
+ $(MKDIR_P) modules
+ $(MKDIR_P) modules/gconf
+ $(MKDIR_P) modules/rtp
+ $(M4) -Dfname="$@" $< > $@
+
+# Simple protocol
+
+module_simple_protocol_tcp_la_SOURCES = modules/module-protocol-stub.c
+module_simple_protocol_tcp_la_CFLAGS = -DUSE_TCP_SOCKETS -DUSE_PROTOCOL_SIMPLE $(AM_CFLAGS)
+module_simple_protocol_tcp_la_LDFLAGS = -module -avoid-version
+module_simple_protocol_tcp_la_LIBADD = $(AM_LIBADD) libpulsecore.la libprotocol-simple.la libsocket-server.la
+
+module_simple_protocol_unix_la_SOURCES = modules/module-protocol-stub.c
+module_simple_protocol_unix_la_CFLAGS = -DUSE_UNIX_SOCKETS -DUSE_PROTOCOL_SIMPLE $(AM_CFLAGS)
+module_simple_protocol_unix_la_LDFLAGS = -module -avoid-version
+module_simple_protocol_unix_la_LIBADD = $(AM_LIBADD) libpulsecore.la libprotocol-simple.la libsocket-server.la libsocket-util.la
+
+# CLI protocol
+
+module_cli_la_SOURCES = modules/module-cli.c
+module_cli_la_LDFLAGS = -module -avoid-version
+module_cli_la_LIBADD = $(AM_LIBADD) libcli.la libiochannel.la libpulsecore.la
+
+module_cli_protocol_tcp_la_SOURCES = modules/module-protocol-stub.c
+module_cli_protocol_tcp_la_CFLAGS = -DUSE_TCP_SOCKETS -DUSE_PROTOCOL_CLI $(AM_CFLAGS)
+module_cli_protocol_tcp_la_LDFLAGS = -module -avoid-version
+module_cli_protocol_tcp_la_LIBADD = $(AM_LIBADD) libpulsecore.la libprotocol-cli.la libsocket-server.la
+
+module_cli_protocol_unix_la_SOURCES = modules/module-protocol-stub.c
+module_cli_protocol_unix_la_CFLAGS = -DUSE_UNIX_SOCKETS -DUSE_PROTOCOL_CLI $(AM_CFLAGS)
+module_cli_protocol_unix_la_LDFLAGS = -module -avoid-version
+module_cli_protocol_unix_la_LIBADD = $(AM_LIBADD) libpulsecore.la libprotocol-cli.la libsocket-server.la libsocket-util.la
+
+# HTTP protocol
+
+module_http_protocol_tcp_la_SOURCES = modules/module-protocol-stub.c
+module_http_protocol_tcp_la_CFLAGS = -DUSE_TCP_SOCKETS -DUSE_PROTOCOL_HTTP $(AM_CFLAGS)
+module_http_protocol_tcp_la_LDFLAGS = -module -avoid-version
+module_http_protocol_tcp_la_LIBADD = $(AM_LIBADD) libpulsecore.la libprotocol-http.la libsocket-server.la
+
+module_http_protocol_unix_la_SOURCES = modules/module-protocol-stub.c
+module_http_protocol_unix_la_CFLAGS = -DUSE_UNIX_SOCKETS -DUSE_PROTOCOL_HTTP $(AM_CFLAGS)
+module_http_protocol_unix_la_LDFLAGS = -module -avoid-version
+module_http_protocol_unix_la_LIBADD = $(AM_LIBADD) libpulsecore.la libprotocol-http.la libsocket-server.la libsocket-util.la
+
+# Native protocol
+
+module_native_protocol_tcp_la_SOURCES = modules/module-protocol-stub.c
+module_native_protocol_tcp_la_CFLAGS = -DUSE_TCP_SOCKETS -DUSE_PROTOCOL_NATIVE $(AM_CFLAGS)
+module_native_protocol_tcp_la_LDFLAGS = -module -avoid-version
+module_native_protocol_tcp_la_LIBADD = $(AM_LIBADD) libpulsecore.la libprotocol-native.la libsocket-server.la
+
+module_native_protocol_unix_la_SOURCES = modules/module-protocol-stub.c
+module_native_protocol_unix_la_CFLAGS = -DUSE_UNIX_SOCKETS -DUSE_PROTOCOL_NATIVE $(AM_CFLAGS)
+module_native_protocol_unix_la_LDFLAGS = -module -avoid-version
+module_native_protocol_unix_la_LIBADD = $(AM_LIBADD) libpulsecore.la libprotocol-native.la libsocket-server.la libsocket-util.la
+
+module_native_protocol_fd_la_SOURCES = modules/module-native-protocol-fd.c
+module_native_protocol_fd_la_CFLAGS = $(AM_CFLAGS)
+module_native_protocol_fd_la_LDFLAGS = -module -avoid-version
+module_native_protocol_fd_la_LIBADD = $(AM_LIBADD) libpulsecore.la libprotocol-native.la libsocket-server.la libsocket-util.la libiochannel.la
+
+# EsounD protocol
+
+module_esound_protocol_tcp_la_SOURCES = modules/module-protocol-stub.c
+module_esound_protocol_tcp_la_CFLAGS = -DUSE_TCP_SOCKETS -DUSE_PROTOCOL_ESOUND $(AM_CFLAGS)
+module_esound_protocol_tcp_la_LDFLAGS = -module -avoid-version
+module_esound_protocol_tcp_la_LIBADD = $(AM_LIBADD) libpulsecore.la libprotocol-esound.la libsocket-server.la
+
+module_esound_protocol_unix_la_SOURCES = modules/module-protocol-stub.c
+module_esound_protocol_unix_la_CFLAGS = -DUSE_UNIX_SOCKETS -DUSE_PROTOCOL_ESOUND $(AM_CFLAGS)
+module_esound_protocol_unix_la_LDFLAGS = -module -avoid-version
+module_esound_protocol_unix_la_LIBADD = $(AM_LIBADD) libpulsecore.la libprotocol-esound.la libsocket-server.la libsocket-util.la
+
+module_esound_compat_spawnfd_la_SOURCES = modules/module-esound-compat-spawnfd.c
+module_esound_compat_spawnfd_la_LDFLAGS = -module -avoid-version
+module_esound_compat_spawnfd_la_LIBADD = $(AM_LIBADD) libpulsecore.la
+
+module_esound_compat_spawnpid_la_SOURCES = modules/module-esound-compat-spawnpid.c
+module_esound_compat_spawnpid_la_LDFLAGS = -module -avoid-version
+module_esound_compat_spawnpid_la_LIBADD = $(AM_LIBADD) libpulsecore.la
+
+module_esound_sink_la_SOURCES = modules/module-esound-sink.c
+module_esound_sink_la_LDFLAGS = -module -avoid-version
+module_esound_sink_la_LIBADD = $(AM_LIBADD) libpulsecore.la libiochannel.la libsocket-client.la libauthkey.la libsocket-util.la
+
+# Pipes
+
+module_pipe_sink_la_SOURCES = modules/module-pipe-sink.c
+module_pipe_sink_la_LDFLAGS = -module -avoid-version
+module_pipe_sink_la_LIBADD = $(AM_LIBADD) libpulsecore.la libiochannel.la
+
+module_pipe_source_la_SOURCES = modules/module-pipe-source.c
+module_pipe_source_la_LDFLAGS = -module -avoid-version
+module_pipe_source_la_LIBADD = $(AM_LIBADD) libpulsecore.la libiochannel.la
+
+# Fake sources/sinks
+
+module_sine_la_SOURCES = modules/module-sine.c
+module_sine_la_LDFLAGS = -module -avoid-version
+module_sine_la_LIBADD = $(AM_LIBADD) libpulsecore.la
+
+module_null_sink_la_SOURCES = modules/module-null-sink.c
+module_null_sink_la_LDFLAGS = -module -avoid-version
+module_null_sink_la_LIBADD = $(AM_LIBADD) libpulsecore.la
+
+# Couplings
+
+module_combine_la_SOURCES = modules/module-combine.c
+module_combine_la_LDFLAGS = -module -avoid-version
+module_combine_la_LIBADD = $(AM_LIBADD) libpulsecore.la
+
+module_remap_sink_la_SOURCES = modules/module-remap-sink.c
+module_remap_sink_la_LDFLAGS = -module -avoid-version
+module_remap_sink_la_LIBADD = $(AM_LIBADD) libpulsecore.la
+
+module_ladspa_sink_la_SOURCES = modules/module-ladspa-sink.c modules/ladspa.h
+module_ladspa_sink_la_CFLAGS = -DLADSPA_PATH=\"$(libdir)/ladspa:/usr/local/lib/ladspa:/usr/lib/ladspa\" $(AM_CFLAGS)
+module_ladspa_sink_la_LDFLAGS = -module -avoid-version
+module_ladspa_sink_la_LIBADD = $(AM_LIBADD) $(LIBLTDL) libpulsecore.la
+
+module_match_la_SOURCES = modules/module-match.c
+module_match_la_LDFLAGS = -module -avoid-version
+module_match_la_LIBADD = $(AM_LIBADD) libpulsecore.la
+
+module_tunnel_sink_la_SOURCES = modules/module-tunnel.c
+module_tunnel_sink_la_CFLAGS = -DTUNNEL_SINK=1 $(AM_CFLAGS)
+module_tunnel_sink_la_LDFLAGS = -module -avoid-version
+module_tunnel_sink_la_LIBADD = $(AM_LIBADD) libpulsecore.la libsocket-client.la libpstream.la libpstream-util.la libpdispatch.la libtagstruct.la libauthkey.la libauthkey-prop.la libsocket-util.la libiochannel.la
+
+module_tunnel_source_la_SOURCES = modules/module-tunnel.c
+module_tunnel_source_la_LDFLAGS = -module -avoid-version
+module_tunnel_source_la_LIBADD = $(AM_LIBADD) libpulsecore.la libsocket-client.la libpstream.la libpstream-util.la libpdispatch.la libtagstruct.la libauthkey.la libauthkey-prop.la libsocket-util.la libiochannel.la
+
+# X11
+
+module_x11_bell_la_SOURCES = modules/module-x11-bell.c
+module_x11_bell_la_CFLAGS = $(AM_CFLAGS) $(X_CFLAGS)
+module_x11_bell_la_LDFLAGS = -module -avoid-version
+module_x11_bell_la_LIBADD = $(AM_LIBADD) $(X_PRE_LIBS) -lX11 $(X_LIBS) $(X_EXTRA_LIBS) libx11wrap.la libpulsecore.la
+
+module_x11_publish_la_SOURCES = modules/module-x11-publish.c
+module_x11_publish_la_CFLAGS = $(AM_CFLAGS) $(X_CFLAGS)
+module_x11_publish_la_LDFLAGS = -module -avoid-version
+module_x11_publish_la_LIBADD = $(AM_LIBADD) $(X_PRE_LIBS) -lX11 $(X_LIBS) $(X_EXTRA_LIBS) libx11wrap.la libauthkey.la libauthkey-prop.la libx11prop.la libstrlist.la libpulsecore.la
+
+module_x11_xsmp_la_SOURCES = modules/module-x11-xsmp.c
+module_x11_xsmp_la_CFLAGS = $(AM_CFLAGS) $(X_CFLAGS)
+module_x11_xsmp_la_LDFLAGS = -module -avoid-version
+module_x11_xsmp_la_LIBADD = $(AM_LIBADD) $(X_PRE_LIBS) -lX11 $(X_LIBS) $(X_EXTRA_LIBS) libpulsecore.la
+
+# OSS
+
+liboss_util_la_SOURCES = modules/oss-util.c modules/oss-util.h
+liboss_util_la_LDFLAGS = -avoid-version
+liboss_util_la_LIBADD = libpulsecore.la
+
+module_oss_la_SOURCES = modules/module-oss.c
+module_oss_la_LDFLAGS = -module -avoid-version
+module_oss_la_LIBADD = $(AM_LIBADD) libiochannel.la liboss-util.la libpulsecore.la
+
+# ALSA
+
+libalsa_util_la_SOURCES = modules/alsa-util.c modules/alsa-util.h
+libalsa_util_la_LDFLAGS = -avoid-version
+libalsa_util_la_LIBADD = $(AM_LIBADD) $(ASOUNDLIB_LIBS) libpulsecore.la
+libalsa_util_la_CFLAGS = $(AM_CFLAGS) $(ASOUNDLIB_CFLAGS)
+
+module_alsa_sink_la_SOURCES = modules/module-alsa-sink.c
+module_alsa_sink_la_LDFLAGS = -module -avoid-version
+module_alsa_sink_la_LIBADD = $(AM_LIBADD) $(ASOUNDLIB_LIBS) libalsa-util.la libpulsecore.la
+module_alsa_sink_la_CFLAGS = $(AM_CFLAGS) $(ASOUNDLIB_CFLAGS)
+
+module_alsa_source_la_SOURCES = modules/module-alsa-source.c
+module_alsa_source_la_LDFLAGS = -module -avoid-version
+module_alsa_source_la_LIBADD = $(AM_LIBADD) $(ASOUNDLIB_LIBS) libalsa-util.la libpulsecore.la
+module_alsa_source_la_CFLAGS = $(AM_CFLAGS) $(ASOUNDLIB_CFLAGS)
+
+# Solaris
+
+module_solaris_la_SOURCES = modules/module-solaris.c
+module_solaris_la_LDFLAGS = -module -avoid-version
+module_solaris_la_LIBADD = $(AM_LIBADD) libiochannel.la libpulsecore.la
+
+# Avahi
+
+module_zeroconf_publish_la_SOURCES = modules/module-zeroconf-publish.c
+module_zeroconf_publish_la_LDFLAGS = -module -avoid-version
+module_zeroconf_publish_la_LIBADD = $(AM_LIBADD) $(AVAHI_LIBS) libavahi-wrap.la libpulsecore.la
+module_zeroconf_publish_la_CFLAGS = $(AM_CFLAGS) $(AVAHI_CFLAGS)
+
+module_zeroconf_discover_la_SOURCES = modules/module-zeroconf-discover.c
+module_zeroconf_discover_la_LDFLAGS = -module -avoid-version
+module_zeroconf_discover_la_LIBADD = $(AM_LIBADD) $(AVAHI_LIBS) libavahi-wrap.la libpulsecore.la
+module_zeroconf_discover_la_CFLAGS = $(AM_CFLAGS) $(AVAHI_CFLAGS)
+
+# LIRC
+
+module_lirc_la_SOURCES = modules/module-lirc.c
+module_lirc_la_LDFLAGS = -module -avoid-version
+module_lirc_la_LIBADD = $(AM_LIBADD) $(LIRC_LIBS) libpulsecore.la
+module_lirc_la_CFLAGS = $(AM_CFLAGS) $(LIRC_CFLAGS)
+
+# Linux evdev
+
+module_mmkbd_evdev_la_SOURCES = modules/module-mmkbd-evdev.c
+module_mmkbd_evdev_la_LDFLAGS = -module -avoid-version
+module_mmkbd_evdev_la_LIBADD = $(AM_LIBADD) libpulsecore.la
+module_mmkbd_evdev_la_CFLAGS = $(AM_CFLAGS)
+
+# Windows waveout
+
+#module_waveout_la_SOURCES = modules/module-waveout.c
+#module_waveout_la_LDFLAGS = -module -avoid-version
+#module_waveout_la_LIBADD = $(AM_LIBADD) libpulsecore.la -lwinmm
+#module_waveout_la_CFLAGS = $(AM_CFLAGS)
+
+# Hardware autodetection module
+module_detect_la_SOURCES = modules/module-detect.c
+module_detect_la_LDFLAGS = -module -avoid-version
+module_detect_la_LIBADD = $(AM_LIBADD) libpulsecore.la
+module_detect_la_CFLAGS = $(AM_CFLAGS)
+
+# Volume restore module
+module_volume_restore_la_SOURCES = modules/module-volume-restore.c
+module_volume_restore_la_LDFLAGS = -module -avoid-version
+module_volume_restore_la_LIBADD = $(AM_LIBADD) libpulsecore.la
+module_volume_restore_la_CFLAGS = $(AM_CFLAGS)
+
+# Default sink/source restore module
+module_default_device_restore_la_SOURCES = modules/module-default-device-restore.c
+module_default_device_restore_la_LDFLAGS = -module -avoid-version
+module_default_device_restore_la_LIBADD = $(AM_LIBADD) libpulsecore.la
+module_default_device_restore_la_CFLAGS = $(AM_CFLAGS)
+
+# Rescue streams module
+module_rescue_streams_la_SOURCES = modules/module-rescue-streams.c
+module_rescue_streams_la_LDFLAGS = -module -avoid-version
+module_rescue_streams_la_LIBADD = $(AM_LIBADD) libpulsecore.la
+module_rescue_streams_la_CFLAGS = $(AM_CFLAGS)
+
+# Suspend-on-idle module
+module_suspend_on_idle_la_SOURCES = modules/module-suspend-on-idle.c
+module_suspend_on_idle_la_LDFLAGS = -module -avoid-version
+module_suspend_on_idle_la_LIBADD = $(AM_LIBADD) libpulsecore.la
+module_suspend_on_idle_la_CFLAGS = $(AM_CFLAGS)
+
+# RTP modules
+module_rtp_send_la_SOURCES = modules/rtp/module-rtp-send.c
+module_rtp_send_la_LDFLAGS = -module -avoid-version
+module_rtp_send_la_LIBADD = $(AM_LIBADD) libpulsecore.la librtp.la libsocket-util.la
+module_rtp_send_la_CFLAGS = $(AM_CFLAGS)
+
+module_rtp_recv_la_SOURCES = modules/rtp/module-rtp-recv.c
+module_rtp_recv_la_LDFLAGS = -module -avoid-version
+module_rtp_recv_la_LIBADD = $(AM_LIBADD) libpulsecore.la librtp.la
+module_rtp_recv_la_CFLAGS = $(AM_CFLAGS)
+
+# JACK
+
+module_jack_sink_la_SOURCES = modules/module-jack-sink.c
+module_jack_sink_la_LDFLAGS = -module -avoid-version
+module_jack_sink_la_LIBADD = $(AM_LIBADD) libpulsecore.la $(JACK_LIBS)
+module_jack_sink_la_CFLAGS = $(AM_CFLAGS) $(JACK_CFLAGS)
+
+module_jack_source_la_SOURCES = modules/module-jack-source.c
+module_jack_source_la_LDFLAGS = -module -avoid-version
+module_jack_source_la_LIBADD = $(AM_LIBADD) libpulsecore.la $(JACK_LIBS)
+module_jack_source_la_CFLAGS = $(AM_CFLAGS) $(JACK_CFLAGS)
+
+# HAL
+libdbus_util_la_SOURCES = modules/dbus-util.c modules/dbus-util.h
+libdbus_util_la_LDFLAGS = -avoid-version
+libdbus_util_la_LIBADD = $(AM_LIBADD) $(DBUS_LIBS) libpulsecore.la
+libdbus_util_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS)
+
+module_hal_detect_la_SOURCES = modules/module-hal-detect.c
+module_hal_detect_la_LDFLAGS = -module -avoid-version
+module_hal_detect_la_LIBADD = $(AM_LIBADD) $(HAL_LIBS) libpulsecore.la libdbus-util.la
+module_hal_detect_la_CFLAGS = $(AM_CFLAGS) $(HAL_CFLAGS)
+
+# GConf support
+module_gconf_la_SOURCES = modules/gconf/module-gconf.c
+module_gconf_la_LDFLAGS = -module -avoid-version
+module_gconf_la_LIBADD = $(AM_LIBADD) libpulsecore.la
+module_gconf_la_CFLAGS = $(AM_CFLAGS) -DPA_GCONF_HELPER=\"$(pulselibexecdir)/gconf-helper\"
+
+gconf_helper_SOURCES = modules/gconf/gconf-helper.c
+gconf_helper_LDADD = $(AM_LDADD) $(GCONF_LIBS) libpulsecore.la
+gconf_helper_CFLAGS = $(AM_CFLAGS) $(GCONF_CFLAGS)
+gconf_helper_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+# Bluetooth proximity
+module_bt_proximity_la_SOURCES = modules/module-bt-proximity.c
+module_bt_proximity_la_LDFLAGS = -module -avoid-version
+module_bt_proximity_la_LIBADD = $(AM_LIBADD) $(DBUS_LIBS) libpulsecore.la libdbus-util.la
+module_bt_proximity_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) -DPA_BT_PROXIMITY_HELPER=\"$(pulselibexecdir)/bt-proximity-helper\"
+
+bt_proximity_helper_SOURCES = modules/bt-proximity-helper.c
+bt_proximity_helper_LDADD = $(AM_LDADD) $(BLUEZ_LIBS)
+bt_proximity_helper_CFLAGS = $(AM_CFLAGS) $(BLUEZ_CFLAGS)
+bt_proximity_helper_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
+
+###################################
+# Some minor stuff #
+###################################
+
+suid: pulseaudio .libs/lt-pulseaudio
+ chown root $^
+ chmod u+s $^
+
+CLEANFILES = esdcompat client.conf default.pa daemon.conf
+
+esdcompat: daemon/esdcompat.in Makefile
+ sed -e 's,@PACKAGE_VERSION\@,$(PACKAGE_VERSION),g' \
+ -e 's,@PACKAGE_NAME\@,$(PACKAGE_NAME),g' \
+ -e 's,@PA_BINARY\@,$(PA_BINARY),g' < $< > $@
+ chmod +x esdcompat
+
+client.conf: pulse/client.conf.in Makefile
+ sed -e 's,@PA_BINARY\@,$(PA_BINARY),g' < $< > $@
+
+if OS_IS_WIN32
+default.pa: daemon/default.pa.win32
+ cp $< $@
+else
+default.pa: daemon/default.pa.in Makefile
+ sed -e 's,@PA_BINARY\@,$(PA_BINARY),g' \
+ -e 's,@PA_DLSEARCHPATH\@,$(modlibexecdir),g' \
+ -e 's,@PA_SOEXT\@,.so,g' < $< > $@
+endif
+
+daemon.conf: daemon/daemon.conf.in Makefile
+ sed -e 's,@PA_DLSEARCHPATH\@,$(modlibexecdir),g' \
+ -e 's,@PA_DEFAULT_CONFIG_FILE\@,$(DEFAULT_CONFIG_DIR),g' < $< > $@
+
+install-exec-hook:
+ chown root $(DESTDIR)$(bindir)/pulseaudio ; true
+ chmod u+s $(DESTDIR)$(bindir)/pulseaudio
+ -chmod u+s $(DESTDIR)$(pulselibexecdir)/bt-proximity-helper
+ ln -sf pacat $(DESTDIR)$(bindir)/parec
+ rm -f $(DESTDIR)$(modlibexecdir)/*.a
+ rm -f $(DESTDIR)$(libdir)/libpulsedsp.a
+ rm -f $(DESTDIR)$(libdir)/libpulsedsp.la
+ rm -f $(DESTDIR)$(modlibexecdir)/*.la
+
+massif: pulseaudio
+ libtool --mode=execute valgrind --tool=massif --depth=6 --alloc-fn=pa_xmalloc --alloc-fn=pa_xmalloc0 --alloc-fn=pa_xrealloc --alloc-fn=dbus_realloc --alloc-fn=pa_xnew0_internal --alloc-fn=pa_xnew_internal ./pulseaudio
+
+update-speex:
+ wget -O pulsecore/speex/speex_resampler.h http://svn.xiph.org/trunk/speex/include/speex/speex_resampler.h
+ wget -O pulsecore/speex/resample.c http://svn.xiph.org/trunk/speex/libspeex/resample.c
+ wget -O pulsecore/speex/arch.h http://svn.xiph.org/trunk/speex/libspeex/arch.h
+ wget -O pulsecore/speex/fixed_generic.h http://svn.xiph.org/trunk/speex/libspeex/fixed_generic.h
+
+update-ffmpeg:
+ wget -O pulsecore/ffmpeg/resample2.c http://svn.mplayerhq.hu/ffmpeg/trunk/libavcodec/resample2.c?view=co
+
+# Automatically generate linker version script. We use the same one for all public .sos
+update-map-file:
+ ( echo "PULSE_0 {" ; \
+ echo "global:" ; \
+ ctags -I PA_GCC_PURE,PA_GCC_CONST -f - --c-kinds=p $(pulseinclude_HEADERS) | awk '/^pa_/ { print $$1 ";" }' | sort ; \
+ echo "local:" ; \
+ echo "*;" ; \
+ echo "};" ) > $(srcdir)/map-file
+
+.PHONY: utils/padsp
diff --git a/src/daemon/Makefile b/src/daemon/Makefile
new file mode 120000
index 00000000..c110232d
--- /dev/null
+++ b/src/daemon/Makefile
@@ -0,0 +1 @@
+../pulse/Makefile \ No newline at end of file
diff --git a/src/daemon/PulseAudio.policy b/src/daemon/PulseAudio.policy
new file mode 100644
index 00000000..cf9499ee
--- /dev/null
+++ b/src/daemon/PulseAudio.policy
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?><!--*-nxml-*-->
+<!DOCTYPE policyconfig PUBLIC
+ "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd">
+
+<!-- $Id$ -->
+
+<!--
+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 Lesser General
+Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with PulseAudio; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+USA.
+-->
+
+<policyconfig>
+
+ <action id="org.pulseaudio.acquire-real-time">
+ <description>Real-time scheduling for the PulseAudio daemon</description>
+ <message>System policy prevents PulseAudio from acquiring real-time scheduling.</message>
+ <defaults>
+ <allow_any>no</allow_any>
+ <allow_inactive>no</allow_inactive>
+ <allow_active>no</allow_active>
+ </defaults>
+ </action>
+
+ <action id="org.pulseaudio.acquire-high-priority">
+ <description>High-priority scheduling (negative Unix nice level) for the PulseAudio daemon</description>
+ <message>System policy prevents PulseAudio from acquiring high-priority scheduling.</message>
+ <defaults>
+ <allow_any>no</allow_any>
+ <allow_inactive>no</allow_inactive>
+ <allow_active>no</allow_active>
+ </defaults>
+ </action>
+
+</policyconfig>
diff --git a/src/daemon/caps.c b/src/daemon/caps.c
new file mode 100644
index 00000000..44ee355e
--- /dev/null
+++ b/src/daemon/caps.c
@@ -0,0 +1,152 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/types.h>
+#include <pulsecore/macro.h>
+
+#ifdef HAVE_SYS_CAPABILITY_H
+#include <sys/capability.h>
+#endif
+#ifdef HAVE_SYS_PRCTL_H
+#include <sys/prctl.h>
+#endif
+
+#include <pulsecore/core-error.h>
+
+#include <pulsecore/log.h>
+
+#include "caps.h"
+
+/* Glibc <= 2.2 has broken unistd.h */
+#if defined(linux) && (__GLIBC__ <= 2 && __GLIBC_MINOR__ <= 2)
+int setresgid(gid_t r, gid_t e, gid_t s);
+int setresuid(uid_t r, uid_t e, uid_t s);
+#endif
+
+#ifdef HAVE_GETUID
+
+/* Drop root rights when called SUID root */
+void pa_drop_root(void) {
+ uid_t uid = getuid();
+
+ if (uid == 0 || geteuid() != 0)
+ return;
+
+ pa_log_info("Dropping root priviliges.");
+
+#if defined(HAVE_SETRESUID)
+ pa_assert_se(setresuid(uid, uid, uid) >= 0);
+#elif defined(HAVE_SETREUID)
+ pa_assert_se(setreuid(uid, uid) >= 0);
+#else
+ pa_assert_se(setuid(uid) >= 0);
+ pa_assert_se(seteuid(uid) >= 0);
+#endif
+
+ pa_assert_se(getuid() == uid);
+ pa_assert_se(geteuid() == uid);
+}
+
+#else
+
+void pa_drop_root(void) {
+}
+
+#endif
+
+#if defined(HAVE_SYS_CAPABILITY_H) && defined(HAVE_SYS_PRCTL_H)
+
+/* Limit permitted capabilities set to CAPSYS_NICE */
+int pa_limit_caps(void) {
+ int r = -1;
+ cap_t caps;
+ cap_value_t nice_cap = CAP_SYS_NICE;
+
+ caps = cap_init();
+ pa_assert(caps);
+ cap_clear(caps);
+ cap_set_flag(caps, CAP_EFFECTIVE, 1, &nice_cap, CAP_SET);
+ cap_set_flag(caps, CAP_PERMITTED, 1, &nice_cap, CAP_SET);
+
+ if (cap_set_proc(caps) < 0)
+ goto fail;
+
+ if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) < 0)
+ goto fail;
+
+ pa_log_info("Dropped capabilities successfully.");
+
+ r = 1;
+
+fail:
+ cap_free(caps);
+
+ return r;
+}
+
+/* Drop all capabilities, effectively becoming a normal user */
+int pa_drop_caps(void) {
+ cap_t caps;
+ int r = -1;
+
+ caps = cap_init();
+ pa_assert(caps);
+
+ cap_clear(caps);
+
+ prctl(PR_SET_KEEPCAPS, 0, 0, 0, 0);
+
+ if (cap_set_proc(caps) < 0) {
+ pa_log("Failed to drop capabilities: %s", pa_cstrerror(errno));
+ goto fail;
+ }
+
+ r = 0;
+
+fail:
+ cap_free(caps);
+
+ return r;
+}
+
+#else
+
+/* NOOPs in case capabilities are not available. */
+int pa_limit_caps(void) {
+ return 0;
+}
+
+int pa_drop_caps(void) {
+ pa_drop_root();
+ return 0;
+}
+
+#endif
diff --git a/src/daemon/caps.h b/src/daemon/caps.h
new file mode 100644
index 00000000..4cd09578
--- /dev/null
+++ b/src/daemon/caps.h
@@ -0,0 +1,31 @@
+#ifndef foocapshfoo
+#define foocapshfoo
+
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004 Lennart Poettering
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+void pa_drop_root(void);
+int pa_limit_caps(void);
+int pa_drop_caps(void);
+
+#endif
diff --git a/src/daemon/cmdline.c b/src/daemon/cmdline.c
new file mode 100644
index 00000000..f1e1282c
--- /dev/null
+++ b/src/daemon/cmdline.c
@@ -0,0 +1,369 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/stat.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/strbuf.h>
+#include <pulsecore/macro.h>
+
+#include "cmdline.h"
+
+/* Argument codes for getopt_long() */
+enum {
+ ARG_HELP = 256,
+ ARG_VERSION,
+ ARG_DUMP_CONF,
+ ARG_DUMP_MODULES,
+ ARG_DAEMONIZE,
+ ARG_FAIL,
+ ARG_LOG_LEVEL,
+ ARG_HIGH_PRIORITY,
+ ARG_REALTIME,
+ ARG_DISALLOW_MODULE_LOADING,
+ ARG_EXIT_IDLE_TIME,
+ ARG_MODULE_IDLE_TIME,
+ ARG_SCACHE_IDLE_TIME,
+ ARG_LOG_TARGET,
+ ARG_LOAD,
+ ARG_FILE,
+ ARG_DL_SEARCH_PATH,
+ ARG_RESAMPLE_METHOD,
+ ARG_KILL,
+ ARG_USE_PID_FILE,
+ ARG_CHECK,
+ ARG_NO_CPU_LIMIT,
+ ARG_DISABLE_SHM,
+ ARG_DUMP_RESAMPLE_METHODS,
+ ARG_SYSTEM,
+ ARG_CLEANUP_SHM
+};
+
+/* Tabel for getopt_long() */
+static const struct option long_options[] = {
+ {"help", 0, 0, ARG_HELP},
+ {"version", 0, 0, ARG_VERSION},
+ {"dump-conf", 0, 0, ARG_DUMP_CONF},
+ {"dump-modules", 0, 0, ARG_DUMP_MODULES},
+ {"daemonize", 2, 0, ARG_DAEMONIZE},
+ {"fail", 2, 0, ARG_FAIL},
+ {"verbose", 2, 0, ARG_LOG_LEVEL},
+ {"log-level", 2, 0, ARG_LOG_LEVEL},
+ {"high-priority", 2, 0, ARG_HIGH_PRIORITY},
+ {"realtime", 2, 0, ARG_REALTIME},
+ {"disallow-module-loading", 2, 0, ARG_DISALLOW_MODULE_LOADING},
+ {"exit-idle-time", 2, 0, ARG_EXIT_IDLE_TIME},
+ {"module-idle-time", 2, 0, ARG_MODULE_IDLE_TIME},
+ {"scache-idle-time", 2, 0, ARG_SCACHE_IDLE_TIME},
+ {"log-target", 1, 0, ARG_LOG_TARGET},
+ {"load", 1, 0, ARG_LOAD},
+ {"file", 1, 0, ARG_FILE},
+ {"dl-search-path", 1, 0, ARG_DL_SEARCH_PATH},
+ {"resample-method", 1, 0, ARG_RESAMPLE_METHOD},
+ {"kill", 0, 0, ARG_KILL},
+ {"use-pid-file", 2, 0, ARG_USE_PID_FILE},
+ {"check", 0, 0, ARG_CHECK},
+ {"system", 2, 0, ARG_SYSTEM},
+ {"no-cpu-limit", 2, 0, ARG_NO_CPU_LIMIT},
+ {"disable-shm", 2, 0, ARG_DISABLE_SHM},
+ {"dump-resample-methods", 2, 0, ARG_DUMP_RESAMPLE_METHODS},
+ {"cleanup-shm", 2, 0, ARG_CLEANUP_SHM},
+ {NULL, 0, 0, 0}
+};
+
+void pa_cmdline_help(const char *argv0) {
+ const char *e;
+
+ pa_assert(argv0);
+
+ if ((e = strrchr(argv0, '/')))
+ e++;
+ else
+ e = argv0;
+
+ printf("%s [options]\n\n"
+ "COMMANDS:\n"
+ " -h, --help Show this help\n"
+ " --version Show version\n"
+ " --dump-conf Dump default configuration\n"
+ " --dump-modules Dump list of available modules\n"
+ " --dump-resample-methods Dump available resample methods\n"
+ " --cleanup-shm Cleanup stale shared memory segments\n"
+ " -k --kill Kill a running daemon\n"
+ " --check Check for a running daemon\n\n"
+
+ "OPTIONS:\n"
+ " --system[=BOOL] Run as system-wide instance\n"
+ " -D, --daemonize[=BOOL] Daemonize after startup\n"
+ " --fail[=BOOL] Quit when startup fails\n"
+ " --high-priority[=BOOL] Try to set high nice level\n"
+ " (only available as root, when SUID or\n"
+ " with elevated RLIMIT_NICE)\n"
+ " --realtime[=BOOL] Try to enable realtime scheduling\n"
+ " (only available as root, when SUID or\n"
+ " with elevated RLIMIT_RTPRIO)\n"
+ " --disallow-module-loading[=BOOL] Disallow module loading after startup\n"
+ " --exit-idle-time=SECS Terminate the daemon when idle and this\n"
+ " time passed\n"
+ " --module-idle-time=SECS Unload autoloaded modules when idle and\n"
+ " this time passed\n"
+ " --scache-idle-time=SECS Unload autoloaded samples when idle and\n"
+ " this time passed\n"
+ " --log-level[=LEVEL] Increase or set verbosity level\n"
+ " -v Increase the verbosity level\n"
+ " --log-target={auto,syslog,stderr} Specify the log target\n"
+ " -p, --dl-search-path=PATH Set the search path for dynamic shared\n"
+ " objects (plugins)\n"
+ " --resample-method=METHOD Use the specified resampling method\n"
+ " (See --dump-resample-methods for\n"
+ " possible values)\n"
+ " --use-pid-file[=BOOL] Create a PID file\n"
+ " --no-cpu-limit[=BOOL] Do not install CPU load limiter on\n"
+ " platforms that support it.\n"
+ " --disable-shm[=BOOL] Disable shared memory support.\n\n"
+
+ "STARTUP SCRIPT:\n"
+ " -L, --load=\"MODULE ARGUMENTS\" Load the specified plugin module with\n"
+ " the specified argument\n"
+ " -F, --file=FILENAME Run the specified script\n"
+ " -C Open a command line on the running TTY\n"
+ " after startup\n\n"
+
+ " -n Don't load default script file\n", e);
+}
+
+int pa_cmdline_parse(pa_daemon_conf *conf, int argc, char *const argv [], int *d) {
+ pa_strbuf *buf = NULL;
+ int c;
+
+ pa_assert(conf);
+ pa_assert(argc > 0);
+ pa_assert(argv);
+
+ buf = pa_strbuf_new();
+
+ if (conf->script_commands)
+ pa_strbuf_puts(buf, conf->script_commands);
+
+ while ((c = getopt_long(argc, argv, "L:F:ChDnp:kv", long_options, NULL)) != -1) {
+ switch (c) {
+ case ARG_HELP:
+ case 'h':
+ conf->cmd = PA_CMD_HELP;
+ break;
+
+ case ARG_VERSION:
+ conf->cmd = PA_CMD_VERSION;
+ break;
+
+ case ARG_DUMP_CONF:
+ conf->cmd = PA_CMD_DUMP_CONF;
+ break;
+
+ case ARG_DUMP_MODULES:
+ conf->cmd = PA_CMD_DUMP_MODULES;
+ break;
+
+ case ARG_DUMP_RESAMPLE_METHODS:
+ conf->cmd = PA_CMD_DUMP_RESAMPLE_METHODS;
+ break;
+
+ case ARG_CLEANUP_SHM:
+ conf->cmd = PA_CMD_CLEANUP_SHM;
+ break;
+
+ case 'k':
+ case ARG_KILL:
+ conf->cmd = PA_CMD_KILL;
+ break;
+
+ case ARG_CHECK:
+ conf->cmd = PA_CMD_CHECK;
+ break;
+
+ case ARG_LOAD:
+ case 'L':
+ pa_strbuf_printf(buf, "load-module %s\n", optarg);
+ break;
+
+ case ARG_FILE:
+ case 'F': {
+ char *p;
+ pa_strbuf_printf(buf, ".include %s\n", p = pa_make_path_absolute(optarg));
+ pa_xfree(p);
+ break;
+ }
+
+ case 'C':
+ pa_strbuf_puts(buf, "load-module module-cli exit_on_eof=1\n");
+ break;
+
+ case ARG_DAEMONIZE:
+ case 'D':
+ if ((conf->daemonize = optarg ? pa_parse_boolean(optarg) : TRUE) < 0) {
+ pa_log("--daemonize expects boolean argument");
+ goto fail;
+ }
+ break;
+
+ case ARG_FAIL:
+ if ((conf->fail = optarg ? pa_parse_boolean(optarg) : TRUE) < 0) {
+ pa_log("--fail expects boolean argument");
+ goto fail;
+ }
+ break;
+
+ case 'v':
+ case ARG_LOG_LEVEL:
+
+ if (optarg) {
+ if (pa_daemon_conf_set_log_level(conf, optarg) < 0) {
+ pa_log("--log-level expects log level argument (either numeric in range 0..4 or one of debug, info, notice, warn, error).");
+ goto fail;
+ }
+ } else {
+ if (conf->log_level < PA_LOG_LEVEL_MAX-1)
+ conf->log_level++;
+ }
+
+ break;
+
+ case ARG_HIGH_PRIORITY:
+ if ((conf->high_priority = optarg ? pa_parse_boolean(optarg) : TRUE) < 0) {
+ pa_log("--high-priority expects boolean argument");
+ goto fail;
+ }
+ break;
+
+ case ARG_REALTIME:
+ if ((conf->realtime_scheduling = optarg ? pa_parse_boolean(optarg) : TRUE) < 0) {
+ pa_log("--realtime expects boolean argument");
+ goto fail;
+ }
+ break;
+
+ case ARG_DISALLOW_MODULE_LOADING:
+ if ((conf->disallow_module_loading = optarg ? pa_parse_boolean(optarg) : TRUE) < 0) {
+ pa_log("--disallow-module-loading expects boolean argument");
+ goto fail;
+ }
+ break;
+
+ case ARG_USE_PID_FILE:
+ if ((conf->use_pid_file = optarg ? pa_parse_boolean(optarg) : TRUE) < 0) {
+ pa_log("--use-pid-file expects boolean argument");
+ goto fail;
+ }
+ break;
+
+ case 'p':
+ case ARG_DL_SEARCH_PATH:
+ pa_xfree(conf->dl_search_path);
+ conf->dl_search_path = *optarg ? pa_xstrdup(optarg) : NULL;
+ break;
+
+ case 'n':
+ pa_xfree(conf->default_script_file);
+ conf->default_script_file = NULL;
+ break;
+
+ case ARG_LOG_TARGET:
+ if (pa_daemon_conf_set_log_target(conf, optarg) < 0) {
+ pa_log("Invalid log target: use either 'syslog', 'stderr' or 'auto'.");
+ goto fail;
+ }
+ break;
+
+ case ARG_EXIT_IDLE_TIME:
+ conf->exit_idle_time = atoi(optarg);
+ break;
+
+ case ARG_MODULE_IDLE_TIME:
+ conf->module_idle_time = atoi(optarg);
+ break;
+
+ case ARG_SCACHE_IDLE_TIME:
+ conf->scache_idle_time = atoi(optarg);
+ break;
+
+ case ARG_RESAMPLE_METHOD:
+ if (pa_daemon_conf_set_resample_method(conf, optarg) < 0) {
+ pa_log("Invalid resample method '%s'.", optarg);
+ goto fail;
+ }
+ break;
+
+ case ARG_SYSTEM:
+ if ((conf->system_instance = optarg ? pa_parse_boolean(optarg) : TRUE) < 0) {
+ pa_log("--system expects boolean argument");
+ goto fail;
+ }
+ break;
+
+ case ARG_NO_CPU_LIMIT:
+ if ((conf->no_cpu_limit = optarg ? pa_parse_boolean(optarg) : TRUE) < 0) {
+ pa_log("--no-cpu-limit expects boolean argument");
+ goto fail;
+ }
+ break;
+
+ case ARG_DISABLE_SHM:
+ if ((conf->disable_shm = optarg ? pa_parse_boolean(optarg) : TRUE) < 0) {
+ pa_log("--disable-shm expects boolean argument");
+ goto fail;
+ }
+ break;
+
+ default:
+ goto fail;
+ }
+ }
+
+ pa_xfree(conf->script_commands);
+ conf->script_commands = pa_strbuf_tostring_free(buf);
+
+ if (!conf->script_commands) {
+ pa_xfree(conf->script_commands);
+ conf->script_commands = NULL;
+ }
+
+ *d = optind;
+
+ return 0;
+
+fail:
+ if (buf)
+ pa_strbuf_free(buf);
+
+ return -1;
+}
diff --git a/src/daemon/cmdline.h b/src/daemon/cmdline.h
new file mode 100644
index 00000000..18418894
--- /dev/null
+++ b/src/daemon/cmdline.h
@@ -0,0 +1,37 @@
+#ifndef foocmdlinehfoo
+#define foocmdlinehfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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 "daemon-conf.h"
+
+/* Parese the command line and store its data in *c. Return the index
+ * of the first unparsed argument in *d. */
+int pa_cmdline_parse(pa_daemon_conf*c, int argc, char *const argv [], int *d);
+
+/* Show the command line help. The command name is extracted from
+ * argv[0] which should be passed in argv0. */
+void pa_cmdline_help(const char *argv0);
+
+#endif
diff --git a/src/daemon/cpulimit.c b/src/daemon/cpulimit.c
new file mode 100644
index 00000000..b77dd443
--- /dev/null
+++ b/src/daemon/cpulimit.c
@@ -0,0 +1,247 @@
+/* $Id$ */
+
+/***
+ 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 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulse/error.h>
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/core-error.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+
+#include "cpulimit.h"
+
+#ifdef HAVE_SIGXCPU
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <signal.h>
+
+#ifdef HAVE_SYS_RESOURCE_H
+#include <sys/resource.h>
+#endif
+
+/* This module implements a watchdog that makes sure that the current
+ * process doesn't consume more than 70% CPU time for 10 seconds. This
+ * is very useful when using SCHED_FIFO scheduling which effectively
+ * disables multitasking. */
+
+/* Method of operation: Using SIGXCPU a signal handler is called every
+ * 10s process CPU time. That function checks if less than 14s system
+ * time have passed. In that case, it tries to contact the main event
+ * loop through a pipe. After two additional seconds it is checked
+ * whether the main event loop contact was successful. If not, the
+ * program is terminated forcibly. */
+
+/* Utilize this much CPU time at maximum */
+#define CPUTIME_PERCENT 70
+
+/* Check every 10s */
+#define CPUTIME_INTERVAL_SOFT (10)
+
+/* Recheck after 5s */
+#define CPUTIME_INTERVAL_HARD (5)
+
+/* Time of the last CPU load check */
+static time_t last_time = 0;
+
+/* Pipe for communicating with the main loop */
+static int the_pipe[2] = {-1, -1};
+
+/* Main event loop and IO event for the FIFO */
+static pa_mainloop_api *api = NULL;
+static pa_io_event *io_event = NULL;
+
+/* Saved sigaction struct for SIGXCPU */
+static struct sigaction sigaction_prev;
+
+/* Nonzero after pa_cpu_limit_init() */
+static int installed = 0;
+
+/* The current state of operation */
+static enum {
+ PHASE_IDLE, /* Normal state */
+ PHASE_SOFT /* After CPU overload has been detected */
+} phase = PHASE_IDLE;
+
+/* Reset the SIGXCPU timer to the next t seconds */
+static void reset_cpu_time(int t) {
+ long n;
+ struct rlimit rl;
+ struct rusage ru;
+
+ /* Get the current CPU time of the current process */
+ pa_assert_se(getrusage(RUSAGE_SELF, &ru) >= 0);
+
+ n = ru.ru_utime.tv_sec + ru.ru_stime.tv_sec + t;
+ pa_assert_se(getrlimit(RLIMIT_CPU, &rl) >= 0);
+
+ rl.rlim_cur = n;
+ pa_assert_se(setrlimit(RLIMIT_CPU, &rl) >= 0);
+}
+
+/* A simple, thread-safe puts() work-alike */
+static void write_err(const char *p) {
+ pa_loop_write(2, p, strlen(p), NULL);
+}
+
+/* The signal handler, called on every SIGXCPU */
+static void signal_handler(int sig) {
+ int saved_errno;
+
+ saved_errno = errno;
+ pa_assert(sig == SIGXCPU);
+
+ if (phase == PHASE_IDLE) {
+ time_t now;
+
+#ifdef PRINT_CPU_LOAD
+ char t[256];
+#endif
+
+ time(&now);
+
+#ifdef PRINT_CPU_LOAD
+ pa_snprintf(t, sizeof(t), "Using %0.1f%% CPU\n", (double)CPUTIME_INTERVAL_SOFT/(now-last_time)*100);
+ write_err(t);
+#endif
+
+ if (CPUTIME_INTERVAL_SOFT >= ((now-last_time)*(double)CPUTIME_PERCENT/100)) {
+ static const char c = 'X';
+
+ write_err("Soft CPU time limit exhausted, terminating.\n");
+
+ /* Try a soft cleanup */
+ write(the_pipe[1], &c, sizeof(c));
+ phase = PHASE_SOFT;
+ reset_cpu_time(CPUTIME_INTERVAL_HARD);
+
+ } else {
+
+ /* Everything's fine */
+ reset_cpu_time(CPUTIME_INTERVAL_SOFT);
+ last_time = now;
+ }
+
+ } else if (phase == PHASE_SOFT) {
+ write_err("Hard CPU time limit exhausted, terminating forcibly.\n");
+ _exit(1); /* Forced exit */
+ }
+
+ errno = saved_errno;
+}
+
+/* Callback for IO events on the FIFO */
+static void callback(pa_mainloop_api*m, pa_io_event*e, int fd, pa_io_event_flags_t f, void *userdata) {
+ char c;
+ pa_assert(m);
+ pa_assert(e);
+ pa_assert(f == PA_IO_EVENT_INPUT);
+ pa_assert(e == io_event);
+ pa_assert(fd == the_pipe[0]);
+
+ pa_read(the_pipe[0], &c, sizeof(c), NULL);
+ m->quit(m, 1); /* Quit the main loop */
+}
+
+/* Initializes CPU load limiter */
+int pa_cpu_limit_init(pa_mainloop_api *m) {
+ struct sigaction sa;
+
+ pa_assert(m);
+ pa_assert(!api);
+ pa_assert(!io_event);
+ pa_assert(the_pipe[0] == -1);
+ pa_assert(the_pipe[1] == -1);
+ pa_assert(!installed);
+
+ time(&last_time);
+
+ /* Prepare the main loop pipe */
+ if (pipe(the_pipe) < 0) {
+ pa_log("pipe() failed: %s", pa_cstrerror(errno));
+ return -1;
+ }
+
+ pa_make_fd_nonblock(the_pipe[0]);
+ pa_make_fd_nonblock(the_pipe[1]);
+ pa_make_fd_cloexec(the_pipe[0]);
+ pa_make_fd_cloexec(the_pipe[1]);
+
+ api = m;
+ io_event = api->io_new(m, the_pipe[0], PA_IO_EVENT_INPUT, callback, NULL);
+
+ phase = PHASE_IDLE;
+
+ /* Install signal handler for SIGXCPU */
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = signal_handler;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+
+ if (sigaction(SIGXCPU, &sa, &sigaction_prev) < 0) {
+ pa_cpu_limit_done();
+ return -1;
+ }
+
+ installed = 1;
+
+ reset_cpu_time(CPUTIME_INTERVAL_SOFT);
+
+ return 0;
+}
+
+/* Shutdown CPU load limiter */
+void pa_cpu_limit_done(void) {
+
+ if (io_event) {
+ pa_assert(api);
+ api->io_free(io_event);
+ io_event = NULL;
+ api = NULL;
+ }
+
+ pa_close_pipe(the_pipe);
+
+ if (installed) {
+ pa_assert_se(sigaction(SIGXCPU, &sigaction_prev, NULL) >= 0);
+ installed = 0;
+ }
+}
+
+#else /* HAVE_SIGXCPU */
+
+int pa_cpu_limit_init(PA_GCC_UNUSED pa_mainloop_api *m) {
+ return 0;
+}
+
+void pa_cpu_limit_done(void) {
+}
+
+#endif
diff --git a/src/daemon/cpulimit.h b/src/daemon/cpulimit.h
new file mode 100644
index 00000000..271109b4
--- /dev/null
+++ b/src/daemon/cpulimit.h
@@ -0,0 +1,36 @@
+#ifndef foocpulimithfoo
+#define foocpulimithfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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/mainloop-api.h>
+
+/* This kills the pulseaudio process if it eats more than 70% of the
+ * CPU time. This is build around setrlimit() and SIGXCPU. It is handy
+ * in case of using SCHED_FIFO which may freeze the whole machine */
+
+int pa_cpu_limit_init(pa_mainloop_api *m);
+void pa_cpu_limit_done(void);
+
+#endif
diff --git a/src/daemon/daemon-conf.c b/src/daemon/daemon-conf.c
new file mode 100644
index 00000000..c98c0218
--- /dev/null
+++ b/src/daemon/daemon-conf.c
@@ -0,0 +1,597 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sched.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/core-error.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/strbuf.h>
+#include <pulsecore/conf-parser.h>
+#include <pulsecore/resampler.h>
+#include <pulsecore/macro.h>
+
+#include "daemon-conf.h"
+
+#define DEFAULT_SCRIPT_FILE PA_DEFAULT_CONFIG_DIR PA_PATH_SEP "default.pa"
+#define DEFAULT_SCRIPT_FILE_USER PA_PATH_SEP "default.pa"
+#define DEFAULT_CONFIG_FILE PA_DEFAULT_CONFIG_DIR PA_PATH_SEP "daemon.conf"
+#define DEFAULT_CONFIG_FILE_USER PA_PATH_SEP "daemon.conf"
+
+#define ENV_SCRIPT_FILE "PULSE_SCRIPT"
+#define ENV_CONFIG_FILE "PULSE_CONFIG"
+#define ENV_DL_SEARCH_PATH "PULSE_DLPATH"
+
+static const pa_daemon_conf default_conf = {
+ .cmd = PA_CMD_DAEMON,
+ .daemonize = FALSE,
+ .fail = TRUE,
+ .high_priority = TRUE,
+ .nice_level = -11,
+ .realtime_scheduling = FALSE,
+ .realtime_priority = 5, /* Half of JACK's default rtprio */
+ .disallow_module_loading = FALSE,
+ .exit_idle_time = -1,
+ .module_idle_time = 20,
+ .scache_idle_time = 20,
+ .auto_log_target = 1,
+ .script_commands = NULL,
+ .dl_search_path = NULL,
+ .default_script_file = NULL,
+ .log_target = PA_LOG_SYSLOG,
+ .log_level = PA_LOG_NOTICE,
+ .resample_method = PA_RESAMPLER_AUTO,
+ .disable_remixing = FALSE,
+ .config_file = NULL,
+ .use_pid_file = TRUE,
+ .system_instance = FALSE,
+ .no_cpu_limit = FALSE,
+ .disable_shm = FALSE,
+ .default_n_fragments = 4,
+ .default_fragment_size_msec = 25,
+ .default_sample_spec = { .format = PA_SAMPLE_S16NE, .rate = 44100, .channels = 2 }
+#ifdef HAVE_SYS_RESOURCE_H
+ , .rlimit_as = { .value = 0, .is_set = FALSE },
+ .rlimit_core = { .value = 0, .is_set = FALSE },
+ .rlimit_data = { .value = 0, .is_set = FALSE },
+ .rlimit_fsize = { .value = 0, .is_set = FALSE },
+ .rlimit_nofile = { .value = 256, .is_set = TRUE },
+ .rlimit_stack = { .value = 0, .is_set = FALSE }
+#ifdef RLIMIT_NPROC
+ , .rlimit_nproc = { .value = 0, .is_set = FALSE }
+#endif
+#ifdef RLIMIT_MEMLOCK
+ , .rlimit_memlock = { .value = 0, .is_set = FALSE }
+#endif
+#ifdef RLIMIT_NICE
+ , .rlimit_nice = { .value = 31, .is_set = TRUE } /* nice level of -11 */
+#endif
+#ifdef RLIMIT_RTPRIO
+ , .rlimit_rtprio = { .value = 9, .is_set = TRUE } /* One below JACK's default for the server */
+#endif
+#endif
+};
+
+pa_daemon_conf* pa_daemon_conf_new(void) {
+ FILE *f;
+ pa_daemon_conf *c = pa_xnewdup(pa_daemon_conf, &default_conf, 1);
+
+ if ((f = pa_open_config_file(DEFAULT_SCRIPT_FILE, DEFAULT_SCRIPT_FILE_USER, ENV_SCRIPT_FILE, &c->default_script_file, "r")))
+ fclose(f);
+
+ c->dl_search_path = pa_xstrdup(PA_DLSEARCHPATH);
+ return c;
+}
+
+void pa_daemon_conf_free(pa_daemon_conf *c) {
+ pa_assert(c);
+ pa_xfree(c->script_commands);
+ pa_xfree(c->dl_search_path);
+ pa_xfree(c->default_script_file);
+ pa_xfree(c->config_file);
+ pa_xfree(c);
+}
+
+int pa_daemon_conf_set_log_target(pa_daemon_conf *c, const char *string) {
+ pa_assert(c);
+ pa_assert(string);
+
+ if (!strcmp(string, "auto"))
+ c->auto_log_target = 1;
+ else if (!strcmp(string, "syslog")) {
+ c->auto_log_target = 0;
+ c->log_target = PA_LOG_SYSLOG;
+ } else if (!strcmp(string, "stderr")) {
+ c->auto_log_target = 0;
+ c->log_target = PA_LOG_STDERR;
+ } else
+ return -1;
+
+ return 0;
+}
+
+int pa_daemon_conf_set_log_level(pa_daemon_conf *c, const char *string) {
+ uint32_t u;
+ pa_assert(c);
+ pa_assert(string);
+
+ if (pa_atou(string, &u) >= 0) {
+ if (u >= PA_LOG_LEVEL_MAX)
+ return -1;
+
+ c->log_level = (pa_log_level_t) u;
+ } else if (pa_startswith(string, "debug"))
+ c->log_level = PA_LOG_DEBUG;
+ else if (pa_startswith(string, "info"))
+ c->log_level = PA_LOG_INFO;
+ else if (pa_startswith(string, "notice"))
+ c->log_level = PA_LOG_NOTICE;
+ else if (pa_startswith(string, "warn"))
+ c->log_level = PA_LOG_WARN;
+ else if (pa_startswith(string, "err"))
+ c->log_level = PA_LOG_ERROR;
+ else
+ return -1;
+
+ return 0;
+}
+
+int pa_daemon_conf_set_resample_method(pa_daemon_conf *c, const char *string) {
+ int m;
+ pa_assert(c);
+ pa_assert(string);
+
+ if ((m = pa_parse_resample_method(string)) < 0)
+ return -1;
+
+ c->resample_method = m;
+ return 0;
+}
+
+static int parse_log_target(const char *filename, unsigned line, const char *lvalue, const char *rvalue, void *data, PA_GCC_UNUSED void *userdata) {
+ pa_daemon_conf *c = data;
+
+ pa_assert(filename);
+ pa_assert(lvalue);
+ pa_assert(rvalue);
+ pa_assert(data);
+
+ if (pa_daemon_conf_set_log_target(c, rvalue) < 0) {
+ pa_log("[%s:%u] Invalid log target '%s'.", filename, line, rvalue);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int parse_log_level(const char *filename, unsigned line, const char *lvalue, const char *rvalue, void *data, PA_GCC_UNUSED void *userdata) {
+ pa_daemon_conf *c = data;
+
+ pa_assert(filename);
+ pa_assert(lvalue);
+ pa_assert(rvalue);
+ pa_assert(data);
+
+ if (pa_daemon_conf_set_log_level(c, rvalue) < 0) {
+ pa_log("[%s:%u] Invalid log level '%s'.", filename, line, rvalue);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int parse_resample_method(const char *filename, unsigned line, const char *lvalue, const char *rvalue, void *data, PA_GCC_UNUSED void *userdata) {
+ pa_daemon_conf *c = data;
+
+ pa_assert(filename);
+ pa_assert(lvalue);
+ pa_assert(rvalue);
+ pa_assert(data);
+
+ if (pa_daemon_conf_set_resample_method(c, rvalue) < 0) {
+ pa_log("[%s:%u] Invalid resample method '%s'.", filename, line, rvalue);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int parse_rlimit(const char *filename, unsigned line, const char *lvalue, const char *rvalue, void *data, PA_GCC_UNUSED void *userdata) {
+#ifdef HAVE_SYS_RESOURCE_H
+ struct pa_rlimit *r = data;
+
+ pa_assert(filename);
+ pa_assert(lvalue);
+ pa_assert(rvalue);
+ pa_assert(r);
+
+ if (rvalue[strspn(rvalue, "\t ")] == 0) {
+ /* Empty string */
+ r->is_set = 0;
+ r->value = 0;
+ } else {
+ int32_t k;
+ if (pa_atoi(rvalue, &k) < 0) {
+ pa_log("[%s:%u] Invalid rlimit '%s'.", filename, line, rvalue);
+ return -1;
+ }
+ r->is_set = k >= 0;
+ r->value = k >= 0 ? (rlim_t) k : 0;
+ }
+#else
+ pa_log_warn("[%s:%u] rlimit not supported on this platform.", filename, line);
+#endif
+
+ return 0;
+}
+
+static int parse_sample_format(const char *filename, unsigned line, const char *lvalue, const char *rvalue, void *data, PA_GCC_UNUSED void *userdata) {
+ pa_daemon_conf *c = data;
+ pa_sample_format_t f;
+
+ pa_assert(filename);
+ pa_assert(lvalue);
+ pa_assert(rvalue);
+ pa_assert(data);
+
+ if ((f = pa_parse_sample_format(rvalue)) < 0) {
+ pa_log("[%s:%u] Invalid sample format '%s'.", filename, line, rvalue);
+ return -1;
+ }
+
+ c->default_sample_spec.format = f;
+ return 0;
+}
+
+static int parse_sample_rate(const char *filename, unsigned line, const char *lvalue, const char *rvalue, void *data, PA_GCC_UNUSED void *userdata) {
+ pa_daemon_conf *c = data;
+ int32_t r;
+
+ pa_assert(filename);
+ pa_assert(lvalue);
+ pa_assert(rvalue);
+ pa_assert(data);
+
+ if (pa_atoi(rvalue, &r) < 0 || r > PA_RATE_MAX || r <= 0) {
+ pa_log("[%s:%u] Invalid sample rate '%s'.", filename, line, rvalue);
+ return -1;
+ }
+
+ c->default_sample_spec.rate = r;
+ return 0;
+}
+
+static int parse_sample_channels(const char *filename, unsigned line, const char *lvalue, const char *rvalue, void *data, PA_GCC_UNUSED void *userdata) {
+ pa_daemon_conf *c = data;
+ int32_t n;
+
+ pa_assert(filename);
+ pa_assert(lvalue);
+ pa_assert(rvalue);
+ pa_assert(data);
+
+ if (pa_atoi(rvalue, &n) < 0 || n > PA_CHANNELS_MAX || n <= 0) {
+ pa_log("[%s:%u] Invalid sample channels '%s'.", filename, line, rvalue);
+ return -1;
+ }
+
+ c->default_sample_spec.channels = (uint8_t) n;
+ return 0;
+}
+
+static int parse_fragments(const char *filename, unsigned line, const char *lvalue, const char *rvalue, void *data, PA_GCC_UNUSED void *userdata) {
+ pa_daemon_conf *c = data;
+ int32_t n;
+
+ pa_assert(filename);
+ pa_assert(lvalue);
+ pa_assert(rvalue);
+ pa_assert(data);
+
+ if (pa_atoi(rvalue, &n) < 0 || n < 2) {
+ pa_log("[%s:%u] Invalid number of fragments '%s'.", filename, line, rvalue);
+ return -1;
+ }
+
+ c->default_n_fragments = (unsigned) n;
+ return 0;
+}
+
+static int parse_fragment_size_msec(const char *filename, unsigned line, const char *lvalue, const char *rvalue, void *data, PA_GCC_UNUSED void *userdata) {
+ pa_daemon_conf *c = data;
+ int32_t n;
+
+ pa_assert(filename);
+ pa_assert(lvalue);
+ pa_assert(rvalue);
+ pa_assert(data);
+
+ if (pa_atoi(rvalue, &n) < 0 || n < 1) {
+ pa_log("[%s:%u] Invalid fragment size '%s'.", filename, line, rvalue);
+ return -1;
+ }
+
+ c->default_fragment_size_msec = (unsigned) n;
+ return 0;
+}
+
+static int parse_nice_level(const char *filename, unsigned line, const char *lvalue, const char *rvalue, void *data, PA_GCC_UNUSED void *userdata) {
+ pa_daemon_conf *c = data;
+ int32_t level;
+
+ pa_assert(filename);
+ pa_assert(lvalue);
+ pa_assert(rvalue);
+ pa_assert(data);
+
+ if (pa_atoi(rvalue, &level) < 0 || level < -20 || level > 19) {
+ pa_log("[%s:%u] Invalid nice level '%s'.", filename, line, rvalue);
+ return -1;
+ }
+
+ c->nice_level = (int) level;
+ return 0;
+}
+
+static int parse_rtprio(const char *filename, unsigned line, const char *lvalue, const char *rvalue, void *data, PA_GCC_UNUSED void *userdata) {
+ pa_daemon_conf *c = data;
+ int32_t rtprio;
+
+ pa_assert(filename);
+ pa_assert(lvalue);
+ pa_assert(rvalue);
+ pa_assert(data);
+
+ if (pa_atoi(rvalue, &rtprio) < 0 || rtprio < sched_get_priority_min(SCHED_FIFO) || rtprio > sched_get_priority_max(SCHED_FIFO)) {
+ pa_log("[%s:%u] Invalid realtime priority '%s'.", filename, line, rvalue);
+ return -1;
+ }
+
+ c->realtime_priority = (int) rtprio;
+ return 0;
+}
+
+int pa_daemon_conf_load(pa_daemon_conf *c, const char *filename) {
+ int r = -1;
+ FILE *f = NULL;
+
+ pa_config_item table[] = {
+ { "daemonize", pa_config_parse_bool, NULL },
+ { "fail", pa_config_parse_bool, NULL },
+ { "high-priority", pa_config_parse_bool, NULL },
+ { "realtime-scheduling", pa_config_parse_bool, NULL },
+ { "disallow-module-loading", pa_config_parse_bool, NULL },
+ { "use-pid-file", pa_config_parse_bool, NULL },
+ { "system-instance", pa_config_parse_bool, NULL },
+ { "no-cpu-limit", pa_config_parse_bool, NULL },
+ { "disable-shm", pa_config_parse_bool, NULL },
+ { "exit-idle-time", pa_config_parse_int, NULL },
+ { "module-idle-time", pa_config_parse_int, NULL },
+ { "scache-idle-time", pa_config_parse_int, NULL },
+ { "realtime-priority", parse_rtprio, NULL },
+ { "dl-search-path", pa_config_parse_string, NULL },
+ { "default-script-file", pa_config_parse_string, NULL },
+ { "log-target", parse_log_target, NULL },
+ { "log-level", parse_log_level, NULL },
+ { "verbose", parse_log_level, NULL },
+ { "resample-method", parse_resample_method, NULL },
+ { "default-sample-format", parse_sample_format, NULL },
+ { "default-sample-rate", parse_sample_rate, NULL },
+ { "default-sample-channels", parse_sample_channels, NULL },
+ { "default-fragments", parse_fragments, NULL },
+ { "default-fragment-size-msec", parse_fragment_size_msec, NULL },
+ { "nice-level", parse_nice_level, NULL },
+ { "disable-remixing", pa_config_parse_bool, NULL },
+#ifdef HAVE_SYS_RESOURCE_H
+ { "rlimit-as", parse_rlimit, NULL },
+ { "rlimit-core", parse_rlimit, NULL },
+ { "rlimit-data", parse_rlimit, NULL },
+ { "rlimit-fsize", parse_rlimit, NULL },
+ { "rlimit-nofile", parse_rlimit, NULL },
+ { "rlimit-stack", parse_rlimit, NULL },
+#ifdef RLIMIT_NPROC
+ { "rlimit-nproc", parse_rlimit, NULL },
+#endif
+#ifdef RLIMIT_MEMLOCK
+ { "rlimit-memlock", parse_rlimit, NULL },
+#endif
+#ifdef RLIMIT_NICE
+ { "rlimit-nice", parse_rlimit, NULL },
+#endif
+#ifdef RLIMIT_RTPRIO
+ { "rlimit-rtprio", parse_rlimit, NULL },
+#endif
+#endif
+ { NULL, NULL, NULL },
+ };
+
+ table[0].data = &c->daemonize;
+ table[1].data = &c->fail;
+ table[2].data = &c->high_priority;
+ table[3].data = &c->realtime_scheduling;
+ table[4].data = &c->disallow_module_loading;
+ table[5].data = &c->use_pid_file;
+ table[6].data = &c->system_instance;
+ table[7].data = &c->no_cpu_limit;
+ table[8].data = &c->disable_shm;
+ table[9].data = &c->exit_idle_time;
+ table[10].data = &c->module_idle_time;
+ table[11].data = &c->scache_idle_time;
+ table[12].data = c;
+ table[13].data = &c->dl_search_path;
+ table[14].data = &c->default_script_file;
+ table[15].data = c;
+ table[16].data = c;
+ table[17].data = c;
+ table[18].data = c;
+ table[19].data = c;
+ table[20].data = c;
+ table[21].data = c;
+ table[22].data = c;
+ table[23].data = c;
+ table[24].data = c;
+ table[25].data = &c->disable_remixing;
+#ifdef HAVE_SYS_RESOURCE_H
+ table[26].data = &c->rlimit_as;
+ table[27].data = &c->rlimit_core;
+ table[28].data = &c->rlimit_data;
+ table[29].data = &c->rlimit_fsize;
+ table[30].data = &c->rlimit_nofile;
+ table[31].data = &c->rlimit_stack;
+#ifdef RLIMIT_NPROC
+ table[32].data = &c->rlimit_nproc;
+#endif
+#ifdef RLIMIT_MEMLOCK
+#ifndef RLIMIT_NPROC
+#error "Houston, we have a numbering problem!"
+#endif
+ table[33].data = &c->rlimit_memlock;
+#endif
+#ifdef RLIMIT_NICE
+#ifndef RLIMIT_MEMLOCK
+#error "Houston, we have a numbering problem!"
+#endif
+ table[34].data = &c->rlimit_nice;
+#endif
+#ifdef RLIMIT_RTPRIO
+#ifndef RLIMIT_NICE
+#error "Houston, we have a numbering problem!"
+#endif
+ table[35].data = &c->rlimit_rtprio;
+#endif
+#endif
+
+ pa_xfree(c->config_file);
+ c->config_file = NULL;
+
+ f = filename ?
+ fopen(c->config_file = pa_xstrdup(filename), "r") :
+ pa_open_config_file(DEFAULT_CONFIG_FILE, DEFAULT_CONFIG_FILE_USER, ENV_CONFIG_FILE, &c->config_file, "r");
+
+ if (!f && errno != ENOENT) {
+ pa_log_warn("Failed to open configuration file '%s': %s", c->config_file, pa_cstrerror(errno));
+ goto finish;
+ }
+
+ r = f ? pa_config_parse(c->config_file, f, table, NULL) : 0;
+
+finish:
+ if (f)
+ fclose(f);
+
+ return r;
+}
+
+int pa_daemon_conf_env(pa_daemon_conf *c) {
+ char *e;
+
+ if ((e = getenv(ENV_DL_SEARCH_PATH))) {
+ pa_xfree(c->dl_search_path);
+ c->dl_search_path = pa_xstrdup(e);
+ }
+ if ((e = getenv(ENV_SCRIPT_FILE))) {
+ pa_xfree(c->default_script_file);
+ c->default_script_file = pa_xstrdup(e);
+ }
+
+ return 0;
+}
+
+static const char* const log_level_to_string[] = {
+ [PA_LOG_DEBUG] = "debug",
+ [PA_LOG_INFO] = "info",
+ [PA_LOG_NOTICE] = "notice",
+ [PA_LOG_WARN] = "warning",
+ [PA_LOG_ERROR] = "error"
+};
+
+char *pa_daemon_conf_dump(pa_daemon_conf *c) {
+ pa_strbuf *s;
+
+ pa_assert(c);
+
+ s = pa_strbuf_new();
+
+ if (c->config_file)
+ pa_strbuf_printf(s, "### Read from configuration file: %s ###\n", c->config_file);
+
+ pa_assert(c->log_level <= PA_LOG_LEVEL_MAX);
+
+ pa_strbuf_printf(s, "daemonize = %s\n", pa_yes_no(c->daemonize));
+ pa_strbuf_printf(s, "fail = %s\n", pa_yes_no(c->fail));
+ pa_strbuf_printf(s, "high-priority = %s\n", pa_yes_no(c->high_priority));
+ pa_strbuf_printf(s, "nice-level = %i\n", c->nice_level);
+ pa_strbuf_printf(s, "realtime-scheduling = %s\n", pa_yes_no(c->realtime_scheduling));
+ pa_strbuf_printf(s, "realtime-priority = %i\n", c->realtime_priority);
+ pa_strbuf_printf(s, "disallow-module-loading = %s\n", pa_yes_no(c->disallow_module_loading));
+ pa_strbuf_printf(s, "use-pid-file = %s\n", pa_yes_no(c->use_pid_file));
+ pa_strbuf_printf(s, "system-instance = %s\n", pa_yes_no(c->system_instance));
+ pa_strbuf_printf(s, "no-cpu-limit = %s\n", pa_yes_no(c->no_cpu_limit));
+ pa_strbuf_printf(s, "disable-shm = %s\n", pa_yes_no(c->disable_shm));
+ pa_strbuf_printf(s, "exit-idle-time = %i\n", c->exit_idle_time);
+ pa_strbuf_printf(s, "module-idle-time = %i\n", c->module_idle_time);
+ pa_strbuf_printf(s, "scache-idle-time = %i\n", c->scache_idle_time);
+ pa_strbuf_printf(s, "dl-search-path = %s\n", c->dl_search_path ? c->dl_search_path : "");
+ pa_strbuf_printf(s, "default-script-file = %s\n", c->default_script_file);
+ pa_strbuf_printf(s, "log-target = %s\n", c->auto_log_target ? "auto" : (c->log_target == PA_LOG_SYSLOG ? "syslog" : "stderr"));
+ pa_strbuf_printf(s, "log-level = %s\n", log_level_to_string[c->log_level]);
+ pa_strbuf_printf(s, "resample-method = %s\n", pa_resample_method_to_string(c->resample_method));
+ pa_strbuf_printf(s, "disable-remixing = %s\n", pa_yes_no(c->disable_remixing));
+ pa_strbuf_printf(s, "default-sample-format = %s\n", pa_sample_format_to_string(c->default_sample_spec.format));
+ pa_strbuf_printf(s, "default-sample-rate = %u\n", c->default_sample_spec.rate);
+ pa_strbuf_printf(s, "default-sample-channels = %u\n", c->default_sample_spec.channels);
+ pa_strbuf_printf(s, "default-fragments = %u\n", c->default_n_fragments);
+ pa_strbuf_printf(s, "default-fragment-size-msec = %u\n", c->default_fragment_size_msec);
+#ifdef HAVE_SYS_RESOURCE_H
+ pa_strbuf_printf(s, "rlimit-as = %li\n", c->rlimit_as.is_set ? (long int) c->rlimit_as.value : -1);
+ pa_strbuf_printf(s, "rlimit-core = %li\n", c->rlimit_core.is_set ? (long int) c->rlimit_core.value : -1);
+ pa_strbuf_printf(s, "rlimit-data = %li\n", c->rlimit_data.is_set ? (long int) c->rlimit_data.value : -1);
+ pa_strbuf_printf(s, "rlimit-fsize = %li\n", c->rlimit_fsize.is_set ? (long int) c->rlimit_fsize.value : -1);
+ pa_strbuf_printf(s, "rlimit-nofile = %li\n", c->rlimit_nofile.is_set ? (long int) c->rlimit_nofile.value : -1);
+ pa_strbuf_printf(s, "rlimit-stack = %li\n", c->rlimit_stack.is_set ? (long int) c->rlimit_stack.value : -1);
+#ifdef RLIMIT_NPROC
+ pa_strbuf_printf(s, "rlimit-nproc = %li\n", c->rlimit_nproc.is_set ? (long int) c->rlimit_nproc.value : -1);
+#endif
+#ifdef RLIMIT_MEMLOCK
+ pa_strbuf_printf(s, "rlimit-memlock = %li\n", c->rlimit_memlock.is_set ? (long int) c->rlimit_memlock.value : -1);
+#endif
+#ifdef RLIMIT_NICE
+ pa_strbuf_printf(s, "rlimit-nice = %li\n", c->rlimit_memlock.is_set ? (long int) c->rlimit_nice.value : -1);
+#endif
+#ifdef RLIMIT_RTPRIO
+ pa_strbuf_printf(s, "rlimit-rtprio = %li\n", c->rlimit_memlock.is_set ? (long int) c->rlimit_rtprio.value : -1);
+#endif
+#endif
+
+ return pa_strbuf_tostring_free(s);
+}
diff --git a/src/daemon/daemon-conf.h b/src/daemon/daemon-conf.h
new file mode 100644
index 00000000..3dcafbfe
--- /dev/null
+++ b/src/daemon/daemon-conf.h
@@ -0,0 +1,124 @@
+#ifndef foodaemonconfhfoo
+#define foodaemonconfhfoo
+
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+#include <pulse/sample.h>
+
+#ifdef HAVE_SYS_RESOURCE_H
+#include <sys/resource.h>
+#endif
+
+/* The actual command to execute */
+typedef enum pa_daemon_conf_cmd {
+ PA_CMD_DAEMON, /* the default */
+ PA_CMD_HELP,
+ PA_CMD_VERSION,
+ PA_CMD_DUMP_CONF,
+ PA_CMD_DUMP_MODULES,
+ PA_CMD_KILL,
+ PA_CMD_CHECK,
+ PA_CMD_DUMP_RESAMPLE_METHODS,
+ PA_CMD_CLEANUP_SHM
+} pa_daemon_conf_cmd_t;
+
+#ifdef HAVE_SYS_RESOURCE_H
+typedef struct pa_rlimit {
+ rlim_t value;
+ pa_bool_t is_set;
+} pa_rlimit;
+#endif
+
+/* A structure containing configuration data for the PulseAudio server . */
+typedef struct pa_daemon_conf {
+ pa_daemon_conf_cmd_t cmd;
+ pa_bool_t daemonize,
+ fail,
+ high_priority,
+ realtime_scheduling,
+ disallow_module_loading,
+ use_pid_file,
+ system_instance,
+ no_cpu_limit,
+ disable_shm,
+ disable_remixing;
+ int exit_idle_time,
+ module_idle_time,
+ scache_idle_time,
+ auto_log_target,
+ realtime_priority,
+ nice_level,
+ resample_method;
+ char *script_commands, *dl_search_path, *default_script_file;
+ pa_log_target_t log_target;
+ pa_log_level_t log_level;
+ char *config_file;
+
+#ifdef HAVE_SYS_RESOURCE_H
+ pa_rlimit rlimit_as, rlimit_core, rlimit_data, rlimit_fsize, rlimit_nofile, rlimit_stack;
+#ifdef RLIMIT_NPROC
+ pa_rlimit rlimit_nproc;
+#endif
+#ifdef RLIMIT_MEMLOCK
+ pa_rlimit rlimit_memlock;
+#endif
+#ifdef RLIMIT_NICE
+ pa_rlimit rlimit_nice;
+#endif
+#ifdef RLIMIT_RTPRIO
+ pa_rlimit rlimit_rtprio;
+#endif
+#endif
+
+ unsigned default_n_fragments, default_fragment_size_msec;
+ pa_sample_spec default_sample_spec;
+} pa_daemon_conf;
+
+/* Allocate a new structure and fill it with sane defaults */
+pa_daemon_conf* pa_daemon_conf_new(void);
+void pa_daemon_conf_free(pa_daemon_conf*c);
+
+/* Load configuration data from the specified file overwriting the
+ * current settings in *c. If filename is NULL load the default daemon
+ * configuration file */
+int pa_daemon_conf_load(pa_daemon_conf *c, const char *filename);
+
+/* Pretty print the current configuration data of the daemon. The
+ * returned string has to be freed manually. The output of this
+ * function may be parsed with pa_daemon_conf_load(). */
+char *pa_daemon_conf_dump(pa_daemon_conf *c);
+
+/* Load the configuration data from the process' environment
+ * overwriting the current settings in *c. */
+int pa_daemon_conf_env(pa_daemon_conf *c);
+
+/* Set these configuration variables in the structure by passing a string */
+int pa_daemon_conf_set_log_target(pa_daemon_conf *c, const char *string);
+int pa_daemon_conf_set_log_level(pa_daemon_conf *c, const char *string);
+int pa_daemon_conf_set_resample_method(pa_daemon_conf *c, const char *string);
+
+#endif
diff --git a/src/daemon/daemon.conf.in b/src/daemon/daemon.conf.in
new file mode 100644
index 00000000..d664962e
--- /dev/null
+++ b/src/daemon/daemon.conf.in
@@ -0,0 +1,69 @@
+# $Id$
+#
+# This file is part of PulseAudio.
+#
+# PulseAudio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# PulseAudio is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with PulseAudio; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+# USA.
+
+## Configuration file for the PulseAudio daemon. See pulse-daemon.conf(5) for
+## more information. Default values a commented out. Use either ; or # for
+## commenting.
+
+; daemonize = no
+; fail = yes
+; disallow-module-loading = no
+; use-pid-file = yes
+; system-instance = no
+; disable-shm = no
+
+; high-priority = yes
+; nice-level = -11
+
+; realtime-scheduling = no
+; realtime-priority = 5
+
+; exit-idle-time = -1
+; module-idle-time = 20
+; scache-idle-time = 20
+
+; dl-search-path = @PA_DLSEARCHPATH@
+
+; default-script-file = @PA_DEFAULT_CONFIG_FILE@
+
+; log-target = auto
+; log-level = notice
+
+; resample-method = speex-float-3
+; disable-remixing = no
+
+; no-cpu-limit = no
+
+; rlimit-as = -1
+; rlimit-core = -1
+; rlimit-data = -1
+; rlimit-fsize = -1
+; rlimit-nofile = 256
+; rlimit-stack = -1
+; rlimit-nproc = -1
+; rlimit-memlock = -1
+; rlimit-nice = 31
+; rlimit-rtprio = 9
+
+; default-sample-format = s16le
+; default-sample-rate = 44100
+; default-sample-channels = 2
+
+; default-fragments = 4
+; default-fragment-size-msec = 25
diff --git a/src/daemon/default.pa.in b/src/daemon/default.pa.in
new file mode 100755
index 00000000..597993c4
--- /dev/null
+++ b/src/daemon/default.pa.in
@@ -0,0 +1,100 @@
+#!@PA_BINARY@ -nF
+#
+# This file is part of PulseAudio.
+#
+# PulseAudio is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# PulseAudio is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with PulseAudio; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+
+.nofail
+
+### Load something into the sample cache
+#load-sample-lazy x11-bell /usr/share/sounds/gtk-events/activate.wav
+load-sample-lazy pulse-hotplug /usr/share/sounds/startup3.wav
+#load-sample-lazy pulse-coldplug /usr/share/sounds/startup3.wav
+#load-sample-lazy pulse-access /usr/share/sounds/generic.wav
+
+.fail
+
+### Load audio drivers statically (it's probably better to not load
+### these drivers manually, but instead use module-hal-detect --
+### see below -- for doing this automatically)
+#load-module module-alsa-sink
+#load-module module-alsa-source device=hw:1,0
+#load-module module-oss device="/dev/dsp" sink_name=output source_name=input
+#load-module module-oss-mmap device="/dev/dsp" sink_name=output source_name=input
+#load-module module-null-sink
+#load-module module-pipe-sink
+
+### Automatically load driver modules depending on the hardware available
+.ifexists @PA_DLSEARCHPATH@/module-hal-detect@PA_SOEXT@
+load-module module-hal-detect
+.else
+### Alternatively use the static hardware detection module (for systems that
+### lack HAL support)
+load-module module-detect
+.endif
+
+### Load several protocols
+load-module module-esound-protocol-unix
+load-module module-native-protocol-unix
+
+### Network access (may be configured with paprefs, so leave this commented
+### here if you plan to use paprefs)
+#load-module module-esound-protocol-tcp
+#load-module module-native-protocol-tcp
+#load-module module-zeroconf-publish
+
+### Load the RTP reciever module (also configured via paprefs, see above)
+#load-module module-rtp-recv
+
+### Load the RTP sender module (also configured via paprefs, see above)
+#load-module module-null-sink sink_name=rtp format=s16be channels=2 rate=44100 description="RTP Multicast Sink"
+#load-module module-rtp-send source=rtp.monitor
+
+### Automatically restore the volume of playback streams
+load-module module-volume-restore
+
+### Automatically restore the default sink/source when changed by the user during runtime
+load-module module-default-device-restore
+
+### Automatically move streams to the default sink if the sink they are
+### connected to dies, similar for sources
+load-module module-rescue-streams
+
+### Automatically suspend sinks/sources that become idle for too long
+load-module module-suspend-on-idle
+
+### Load X11 bell module
+#load-module module-x11-bell sample=x11-bell
+
+### Publish connection data in the X11 root window
+.ifexists @PA_DLSEARCHPATH@/module-x11-publish@PA_SOEXT@
+load-module module-x11-publish
+.endif
+
+### Register ourselves in the X11 session manager
+# Deactivated by default, to avoid deadlock when PA is started as esd from gnome-session
+# Instead we load this via /etc/xdg/autostart/ and "pactl load-module" now
+# load-module module-x11-xsmp
+
+### Load additional modules from GConf settings. This can be configured with the paprefs tool.
+### Please keep in mind that the modules configured by paprefs might conflict with manually
+### loaded modules.
+.ifexists @PA_DLSEARCHPATH@/module-gconf@PA_SOEXT@
+load-module module-gconf
+.endif
+
+### Make some devices default
+#set-default-sink output
+#set-default-source input
diff --git a/src/daemon/default.pa.win32 b/src/daemon/default.pa.win32
new file mode 100644
index 00000000..d5a1e183
--- /dev/null
+++ b/src/daemon/default.pa.win32
@@ -0,0 +1,43 @@
+#
+# This file is part of PulseAudio.
+#
+# PulseAudio is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# PulseAudio is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with PulseAudio; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+
+
+# Load audio drivers statically
+
+load-module module-waveout sink_name=output source_name=input
+load-module module-null-sink
+
+# Load audio drivers automatically on access
+
+#add-autoload-sink output module-waveout sink_name=output source_name=input
+#add-autoload-source input module-waveout sink_name=output source_name=input
+
+# Load several protocols
+#load-module module-esound-protocol-tcp
+#load-module module-native-protocol-tcp
+#load-module module-simple-protocol-tcp
+#load-module module-cli-protocol-tcp
+
+# Make some devices default
+set-default-sink output
+set-default-source input
+
+.nofail
+
+# Load something to the sample cache
+load-sample x11-bell %WINDIR%\Media\ding.wav
+load-sample-dir-lazy %WINDIR%\Media\*.wav
diff --git a/src/daemon/dumpmodules.c b/src/daemon/dumpmodules.c
new file mode 100644
index 00000000..68236c70
--- /dev/null
+++ b/src/daemon/dumpmodules.c
@@ -0,0 +1,158 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <ltdl.h>
+
+#include <pulse/util.h>
+
+#include <pulsecore/modinfo.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/macro.h>
+
+#include "dumpmodules.h"
+
+#define PREFIX "module-"
+
+static void short_info(const char *name, PA_GCC_UNUSED const char *path, pa_modinfo *i) {
+ pa_assert(name);
+ pa_assert(i);
+
+ printf("%-40s%s\n", name, i->description ? i->description : "n/a");
+}
+
+static void long_info(const char *name, const char *path, pa_modinfo *i) {
+ static int nl = 0;
+ pa_assert(name);
+ pa_assert(i);
+
+ if (nl)
+ printf("\n");
+
+ nl = 1;
+
+ printf("Name: %s\n", name);
+
+ if (!i->description && !i->version && !i->author && !i->usage)
+ printf("No module information available\n");
+ else {
+ if (i->version)
+ printf("Version: %s\n", i->version);
+ if (i->description)
+ printf("Description: %s\n", i->description);
+ if (i->author)
+ printf("Author: %s\n", i->author);
+ if (i->usage)
+ printf("Usage: %s\n", i->usage);
+ printf("Load Once: %s\n", pa_yes_no(i->load_once));
+ }
+
+ if (path)
+ printf("Path: %s\n", path);
+}
+
+static void show_info(const char *name, const char *path, void (*info)(const char *name, const char *path, pa_modinfo*i)) {
+ pa_modinfo *i;
+
+ pa_assert(name);
+
+ if ((i = pa_modinfo_get_by_name(path ? path : name))) {
+ info(name, path, i);
+ pa_modinfo_free(i);
+ }
+}
+
+extern const lt_dlsymlist lt_preloaded_symbols[];
+
+static int is_preloaded(const char *name) {
+ const lt_dlsymlist *l;
+
+ for (l = lt_preloaded_symbols; l->name; l++) {
+ char buf[64], *e;
+
+ if (l->address)
+ continue;
+
+ pa_snprintf(buf, sizeof(buf), "%s", l->name);
+ if ((e = strrchr(buf, '.')))
+ *e = 0;
+
+ if (!strcmp(name, buf))
+ return 1;
+ }
+
+ return 0;
+}
+
+static int callback(const char *path, lt_ptr data) {
+ const char *e;
+ pa_daemon_conf *c = (data);
+
+ e = pa_path_get_filename(path);
+
+ if (strlen(e) <= sizeof(PREFIX)-1 || strncmp(e, PREFIX, sizeof(PREFIX)-1))
+ return 0;
+
+ if (is_preloaded(e))
+ return 0;
+
+ show_info(e, path, c->log_level >= PA_LOG_INFO ? long_info : short_info);
+ return 0;
+}
+
+void pa_dump_modules(pa_daemon_conf *c, int argc, char * const argv[]) {
+ pa_assert(c);
+
+ if (argc > 0) {
+ int i;
+ for (i = 0; i < argc; i++)
+ show_info(argv[i], NULL, long_info);
+ } else {
+ const lt_dlsymlist *l;
+
+ for (l = lt_preloaded_symbols; l->name; l++) {
+ char buf[64], *e;
+
+ if (l->address)
+ continue;
+
+ if (strlen(l->name) <= sizeof(PREFIX)-1 || strncmp(l->name, PREFIX, sizeof(PREFIX)-1))
+ continue;
+
+ pa_snprintf(buf, sizeof(buf), "%s", l->name);
+ if ((e = strrchr(buf, '.')))
+ *e = 0;
+
+ show_info(buf, NULL, c->log_level >= PA_LOG_INFO ? long_info : short_info);
+ }
+
+ lt_dlforeachfile(NULL, callback, c);
+ }
+}
diff --git a/src/daemon/dumpmodules.h b/src/daemon/dumpmodules.h
new file mode 100644
index 00000000..ab2ddb64
--- /dev/null
+++ b/src/daemon/dumpmodules.h
@@ -0,0 +1,33 @@
+#ifndef foodumpmoduleshfoo
+#define foodumpmoduleshfoo
+
+/* $Id*/
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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 "daemon-conf.h"
+
+/* Dump all available modules to STDOUT. If argc > 0 print information
+ * about the modules specified in argv[] instead. */
+void pa_dump_modules(pa_daemon_conf *c, int argc, char * const argv[]);
+
+#endif
diff --git a/src/daemon/esdcompat.in b/src/daemon/esdcompat.in
new file mode 100755
index 00000000..942389d2
--- /dev/null
+++ b/src/daemon/esdcompat.in
@@ -0,0 +1,98 @@
+#!/bin/sh
+
+# $Id$
+#
+# This file is part of PulseAudio.
+#
+# PulseAudio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# PulseAudio is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with PulseAudio; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+# USA.
+
+VERSION_STRING="@PACKAGE_NAME@ esd wrapper @PACKAGE_VERSION@"
+
+fail() {
+ echo "ERROR: $1"
+ exit 1
+}
+
+ARGS=" --log-target=syslog"
+
+while [ "$#" -gt "0" ]; do
+
+ case "$1" in
+ "")
+ ;;
+
+ -v|--version)
+ echo "$VERSION_STRING"
+ exit 0
+ ;;
+
+ -h|--help)
+ cat <<EOF
+$VERSION_STRING
+
+Usage: $0 [options]
+
+ -v --version print version information
+ -h --help show this help
+
+Ignored directives:
+
+ -tcp use tcp/ip sockets in addition to unix domain
+ -promiscuous don't require authentication
+ -d DEVICE force esd to use sound device DEVICE
+ -b run server in 8 bit sound mode
+ -r RATE run server at sample rate of RATE
+ -as SECS free audio device after SECS of inactivity
+ -unix use unix domain sockets instead of tcp/ip
+ -public make tcp/ip access public (other than localhost)
+ -terminate terminate esd daemone after last client exits
+ -nobeeps disable startup beeps
+ -trust start esd even if use of /tmp/.esd can be insecure
+ -port PORT listen for connections at PORT (only for tcp/ip)
+ -bind ADDRESS binds to ADDRESS (only for tcp/ip)
+EOF
+ exit 0
+ ;;
+
+ -spawnpid)
+ shift
+ ARGS="$ARGS '-Lmodule-esound-compat-spawnpid pid=$1'"
+ ;;
+
+ -spawnfd)
+ shift
+ ARGS="$ARGS '-Lmodule-esound-compat-spawnfd fd=$1'"
+ ;;
+
+ -unix|-b|-public|-terminate|-nobeeps|-trust|-tcp|-promiscuous)
+ # Ignore these commands
+ ;;
+
+ -d|-r|-as|-port|-bind)
+ # Ignore these commands and their arguments
+ shift
+
+ ;;
+
+ *)
+ fail "Unknown command: $1"
+ ;;
+ esac
+
+ shift
+done
+
+eval "exec '@PA_BINARY@'$ARGS"
diff --git a/src/daemon/ltdl-bind-now.c b/src/daemon/ltdl-bind-now.c
new file mode 100644
index 00000000..6915fe0c
--- /dev/null
+++ b/src/daemon/ltdl-bind-now.c
@@ -0,0 +1,197 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#if HAVE_DLFCN_H
+#include <dlfcn.h>
+#endif
+
+#if HAVE_SYS_DL_H
+#include <sys/dl.h>
+#endif
+
+#ifndef HAVE_STRUCT_LT_USER_DLLOADER
+/* Only used with ltdl 2.2 */
+#include <string.h>
+#endif
+
+#include <ltdl.h>
+
+#include <pulsecore/macro.h>
+#include <pulsecore/mutex.h>
+#include <pulsecore/thread.h>
+#include <pulsecore/log.h>
+
+#include "ltdl-bind-now.h"
+
+#ifdef RTLD_NOW
+#define PA_BIND_NOW RTLD_NOW
+#elif defined(DL_NOW)
+#define PA_BIND_NOW DL_NOW
+#else
+#undef PA_BIND_NOW
+#endif
+
+static pa_mutex *libtool_mutex = NULL;
+
+PA_STATIC_TLS_DECLARE_NO_FREE(libtool_tls);
+
+static void libtool_lock(void) {
+ pa_mutex_lock(libtool_mutex);
+}
+
+static void libtool_unlock(void) {
+ pa_mutex_unlock(libtool_mutex);
+}
+
+static void libtool_set_error(const char *error) {
+ PA_STATIC_TLS_SET(libtool_tls, (char*) error);
+}
+
+static const char *libtool_get_error(void) {
+ return PA_STATIC_TLS_GET(libtool_tls);
+}
+
+#ifdef PA_BIND_NOW
+
+/*
+ To avoid lazy relocations during runtime in our RT threads we add
+ our own shared object loader with uses RTLD_NOW if it is
+ available. The standard ltdl loader prefers RTLD_LAZY.
+
+ Please note that this loader doesn't have any influence on
+ relocations on any libraries that are already loaded into our
+ process, i.e. because the pulseaudio binary links directly to
+ them. To disable lazy relocations for those libraries it is possible
+ to set $LT_BIND_NOW before starting the pulsaudio binary.
+*/
+
+#ifndef HAVE_LT_DLADVISE
+static lt_module bind_now_open(lt_user_data d, const char *fname) {
+#else
+ static lt_module bind_now_open(lt_user_data d, const char *fname, lt_dladvise advise) {
+#endif
+ lt_module m;
+
+ pa_assert(fname);
+
+ if (!(m = dlopen(fname, PA_BIND_NOW))) {
+ libtool_set_error(dlerror());
+ return NULL;
+ }
+
+ return m;
+}
+
+static int bind_now_close(lt_user_data d, lt_module m) {
+
+ pa_assert(m);
+
+ if (dlclose(m) != 0){
+ libtool_set_error(dlerror());
+ return 1;
+ }
+
+ return 0;
+}
+
+static lt_ptr bind_now_find_sym(lt_user_data d, lt_module m, const char *symbol) {
+ lt_ptr ptr;
+
+ pa_assert(m);
+ pa_assert(symbol);
+
+ if (!(ptr = dlsym(m, symbol))) {
+ libtool_set_error(dlerror());
+ return NULL;
+ }
+
+ return ptr;
+}
+
+#endif
+
+void pa_ltdl_init(void) {
+
+#ifdef PA_BIND_NOW
+# ifdef HAVE_STRUCT_LT_USER_DLLOADER
+ lt_dlloader *place;
+ static const struct lt_user_dlloader loader = {
+ .module_open = bind_now_open,
+ .module_close = bind_now_close,
+ .find_sym = bind_now_find_sym
+ };
+# else
+ static const lt_dlvtable *dlopen_loader;
+ static lt_dlvtable bindnow_loader;
+# endif
+#endif
+
+ pa_assert_se(lt_dlinit() == 0);
+ pa_assert_se(libtool_mutex = pa_mutex_new(TRUE, FALSE));
+#ifdef HAVE_LT_DLMUTEX_REGISTER
+ pa_assert_se(lt_dlmutex_register(libtool_lock, libtool_unlock, libtool_set_error, libtool_get_error) == 0);
+#endif
+
+#ifdef PA_BIND_NOW
+# ifdef HAVE_STRUCT_LT_USER_DLLOADER
+
+ if (!(place = lt_dlloader_find("dlopen")))
+ place = lt_dlloader_next(NULL);
+
+ /* Add our BIND_NOW loader as the default module loader. */
+ if (lt_dlloader_add(place, &loader, "bind-now-loader") != 0)
+ pa_log_warn("Failed to add bind-now-loader.");
+# else
+ /* Already initialised */
+ if ( dlopen_loader != NULL ) return;
+
+ if (!(dlopen_loader = lt_dlloader_find("dlopen"))) {
+ pa_log_warn("Failed to find original dlopen loader.");
+ return;
+ }
+
+ memcpy(&bindnow_loader, dlopen_loader, sizeof(bindnow_loader));
+ bindnow_loader.name = "bind-now-loader";
+ bindnow_loader.module_open = bind_now_open;
+ bindnow_loader.module_close = bind_now_close;
+ bindnow_loader.find_sym = bind_now_find_sym;
+ bindnow_loader.priority = LT_DLLOADER_PREPEND;
+
+ /* Add our BIND_NOW loader as the default module loader. */
+ if (lt_dlloader_add(&bindnow_loader) != 0)
+ pa_log_warn("Failed to add bind-now-loader.");
+# endif
+#endif
+}
+
+void pa_ltdl_done(void) {
+ pa_assert_se(lt_dlexit() == 0);
+ pa_mutex_free(libtool_mutex);
+ libtool_mutex = NULL;
+}
+
diff --git a/src/daemon/ltdl-bind-now.h b/src/daemon/ltdl-bind-now.h
new file mode 100644
index 00000000..e19c7bc1
--- /dev/null
+++ b/src/daemon/ltdl-bind-now.h
@@ -0,0 +1,32 @@
+#ifndef foopulsecoreltdlbindnowhfoo
+#define foopulsecoreltdlbindnowhfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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.
+***/
+
+
+void pa_ltdl_init(void);
+void pa_ltdl_done(void);
+
+#endif
+
diff --git a/src/daemon/main.c b/src/daemon/main.c
new file mode 100644
index 00000000..7823180a
--- /dev/null
+++ b/src/daemon/main.c
@@ -0,0 +1,850 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <signal.h>
+#include <stddef.h>
+#include <ltdl.h>
+#include <limits.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <locale.h>
+#include <sys/types.h>
+
+#include <liboil/liboil.h>
+
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+
+#ifdef HAVE_PWD_H
+#include <pwd.h>
+#endif
+#ifdef HAVE_GRP_H
+#include <grp.h>
+#endif
+
+#ifdef HAVE_LIBWRAP
+#include <syslog.h>
+#include <tcpd.h>
+#endif
+
+#ifdef HAVE_DBUS
+#include <dbus/dbus.h>
+#endif
+
+#include <pulse/mainloop.h>
+#include <pulse/mainloop-signal.h>
+#include <pulse/timeval.h>
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/winsock.h>
+#include <pulsecore/core-error.h>
+#include <pulsecore/core.h>
+#include <pulsecore/memblock.h>
+#include <pulsecore/module.h>
+#include <pulsecore/cli-command.h>
+#include <pulsecore/log.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/sioman.h>
+#include <pulsecore/cli-text.h>
+#include <pulsecore/pid.h>
+#include <pulsecore/namereg.h>
+#include <pulsecore/random.h>
+#include <pulsecore/rtsig.h>
+#include <pulsecore/rtclock.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/mutex.h>
+#include <pulsecore/thread.h>
+#include <pulsecore/once.h>
+#include <pulsecore/shm.h>
+
+#include "cmdline.h"
+#include "cpulimit.h"
+#include "daemon-conf.h"
+#include "dumpmodules.h"
+#include "caps.h"
+#include "ltdl-bind-now.h"
+#include "polkit.h"
+
+#ifdef HAVE_LIBWRAP
+/* Only one instance of these variables */
+int allow_severity = LOG_INFO;
+int deny_severity = LOG_WARNING;
+#endif
+
+#ifdef HAVE_OSS
+/* padsp looks for this symbol in the running process and disables
+ * itself if it finds it and it is set to 7 (which is actually a bit
+ * mask). For details see padsp. */
+int __padsp_disabled__ = 7;
+#endif
+
+#ifdef OS_IS_WIN32
+
+static void message_cb(pa_mainloop_api*a, pa_time_event*e, PA_GCC_UNUSED const struct timeval *tv, void *userdata) {
+ MSG msg;
+ struct timeval tvnext;
+
+ while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
+ if (msg.message == WM_QUIT)
+ raise(SIGTERM);
+ else {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+ }
+
+ pa_timeval_add(pa_gettimeofday(&tvnext), 100000);
+ a->time_restart(e, &tvnext);
+}
+
+#endif
+
+static void signal_callback(pa_mainloop_api*m, PA_GCC_UNUSED pa_signal_event *e, int sig, void *userdata) {
+ pa_log_info("Got signal %s.", pa_sig2str(sig));
+
+ switch (sig) {
+#ifdef SIGUSR1
+ case SIGUSR1:
+ pa_module_load(userdata, "module-cli", NULL);
+ break;
+#endif
+
+#ifdef SIGUSR2
+ case SIGUSR2:
+ pa_module_load(userdata, "module-cli-protocol-unix", NULL);
+ break;
+#endif
+
+#ifdef SIGHUP
+ case SIGHUP: {
+ char *c = pa_full_status_string(userdata);
+ pa_log_notice("%s", c);
+ pa_xfree(c);
+ return;
+ }
+#endif
+
+ case SIGINT:
+ case SIGTERM:
+ default:
+ pa_log_info("Exiting.");
+ m->quit(m, 1);
+ break;
+ }
+}
+
+#define set_env(key, value) putenv(pa_sprintf_malloc("%s=%s", (key), (value)))
+
+#if defined(HAVE_PWD_H) && defined(HAVE_GRP_H)
+
+static int change_user(void) {
+ struct passwd *pw;
+ struct group * gr;
+ int r;
+
+ /* This function is called only in system-wide mode. It creates a
+ * runtime dir in /var/run/ with proper UID/GID and drops privs
+ * afterwards. */
+
+ if (!(pw = getpwnam(PA_SYSTEM_USER))) {
+ pa_log("Failed to find user '%s'.", PA_SYSTEM_USER);
+ return -1;
+ }
+
+ if (!(gr = getgrnam(PA_SYSTEM_GROUP))) {
+ pa_log("Failed to find group '%s'.", PA_SYSTEM_GROUP);
+ return -1;
+ }
+
+ pa_log_info("Found user '%s' (UID %lu) and group '%s' (GID %lu).",
+ PA_SYSTEM_USER, (unsigned long) pw->pw_uid,
+ PA_SYSTEM_GROUP, (unsigned long) gr->gr_gid);
+
+ if (pw->pw_gid != gr->gr_gid) {
+ pa_log("GID of user '%s' and of group '%s' don't match.", PA_SYSTEM_USER, PA_SYSTEM_GROUP);
+ return -1;
+ }
+
+ if (strcmp(pw->pw_dir, PA_SYSTEM_RUNTIME_PATH) != 0)
+ pa_log_warn("Warning: home directory of user '%s' is not '%s', ignoring.", PA_SYSTEM_USER, PA_SYSTEM_RUNTIME_PATH);
+
+ if (pa_make_secure_dir(PA_SYSTEM_RUNTIME_PATH, 0755, pw->pw_uid, gr->gr_gid) < 0) {
+ pa_log("Failed to create '%s': %s", PA_SYSTEM_RUNTIME_PATH, pa_cstrerror(errno));
+ return -1;
+ }
+
+ if (initgroups(PA_SYSTEM_USER, gr->gr_gid) != 0) {
+ pa_log("Failed to change group list: %s", pa_cstrerror(errno));
+ return -1;
+ }
+
+#if defined(HAVE_SETRESGID)
+ r = setresgid(gr->gr_gid, gr->gr_gid, gr->gr_gid);
+#elif defined(HAVE_SETEGID)
+ if ((r = setgid(gr->gr_gid)) >= 0)
+ r = setegid(gr->gr_gid);
+#elif defined(HAVE_SETREGID)
+ r = setregid(gr->gr_gid, gr->gr_gid);
+#else
+#error "No API to drop priviliges"
+#endif
+
+ if (r < 0) {
+ pa_log("Failed to change GID: %s", pa_cstrerror(errno));
+ return -1;
+ }
+
+#if defined(HAVE_SETRESUID)
+ r = setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid);
+#elif defined(HAVE_SETEUID)
+ if ((r = setuid(pw->pw_uid)) >= 0)
+ r = seteuid(pw->pw_uid);
+#elif defined(HAVE_SETREUID)
+ r = setreuid(pw->pw_uid, pw->pw_uid);
+#else
+#error "No API to drop priviliges"
+#endif
+
+ if (r < 0) {
+ pa_log("Failed to change UID: %s", pa_cstrerror(errno));
+ return -1;
+ }
+
+ set_env("USER", PA_SYSTEM_USER);
+ set_env("LOGNAME", PA_SYSTEM_GROUP);
+ set_env("HOME", PA_SYSTEM_RUNTIME_PATH);
+
+ /* Relevant for pa_runtime_path() */
+ set_env("PULSE_RUNTIME_PATH", PA_SYSTEM_RUNTIME_PATH);
+ set_env("PULSE_CONFIG_PATH", PA_SYSTEM_RUNTIME_PATH);
+
+ pa_log_info("Successfully dropped root privileges.");
+
+ return 0;
+}
+
+#else /* HAVE_PWD_H && HAVE_GRP_H */
+
+static int change_user(void) {
+ pa_log("System wide mode unsupported on this platform.");
+ return -1;
+}
+
+#endif /* HAVE_PWD_H && HAVE_GRP_H */
+
+static int create_runtime_dir(void) {
+ char fn[PATH_MAX];
+
+ pa_runtime_path(NULL, fn, sizeof(fn));
+
+ /* This function is called only when the daemon is started in
+ * per-user mode. We create the runtime directory somewhere in
+ * /tmp/ with the current UID/GID */
+
+ if (pa_make_secure_dir(fn, 0700, (uid_t)-1, (gid_t)-1) < 0) {
+ pa_log("Failed to create '%s': %s", fn, pa_cstrerror(errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+#ifdef HAVE_SYS_RESOURCE_H
+
+static int set_one_rlimit(const pa_rlimit *r, int resource, const char *name) {
+ struct rlimit rl;
+ pa_assert(r);
+
+ if (!r->is_set)
+ return 0;
+
+ rl.rlim_cur = rl.rlim_max = r->value;
+
+ if (setrlimit(resource, &rl) < 0) {
+ pa_log_warn("setrlimit(%s, (%u, %u)) failed: %s", name, (unsigned) r->value, (unsigned) r->value, pa_cstrerror(errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+static void set_all_rlimits(const pa_daemon_conf *conf) {
+ set_one_rlimit(&conf->rlimit_as, RLIMIT_AS, "RLIMIT_AS");
+ set_one_rlimit(&conf->rlimit_core, RLIMIT_CORE, "RLIMIT_CORE");
+ set_one_rlimit(&conf->rlimit_data, RLIMIT_DATA, "RLIMIT_DATA");
+ set_one_rlimit(&conf->rlimit_fsize, RLIMIT_FSIZE, "RLIMIT_FSIZE");
+ set_one_rlimit(&conf->rlimit_nofile, RLIMIT_NOFILE, "RLIMIT_NOFILE");
+ set_one_rlimit(&conf->rlimit_stack, RLIMIT_STACK, "RLIMIT_STACK");
+#ifdef RLIMIT_NPROC
+ set_one_rlimit(&conf->rlimit_nproc, RLIMIT_NPROC, "RLIMIT_NPROC");
+#endif
+#ifdef RLIMIT_MEMLOCK
+ set_one_rlimit(&conf->rlimit_memlock, RLIMIT_MEMLOCK, "RLIMIT_MEMLOCK");
+#endif
+#ifdef RLIMIT_NICE
+ set_one_rlimit(&conf->rlimit_nice, RLIMIT_NICE, "RLIMIT_NICE");
+#endif
+#ifdef RLIMIT_RTPRIO
+ set_one_rlimit(&conf->rlimit_rtprio, RLIMIT_RTPRIO, "RLIMIT_RTPRIO");
+#endif
+}
+#endif
+
+int main(int argc, char *argv[]) {
+ pa_core *c = NULL;
+ pa_strbuf *buf = NULL;
+ pa_daemon_conf *conf = NULL;
+ pa_mainloop *mainloop = NULL;
+ char *s;
+ int r = 0, retval = 1, d = 0;
+ int daemon_pipe[2] = { -1, -1 };
+ pa_bool_t suid_root, real_root;
+ int valid_pid_file = 0;
+ gid_t gid = (gid_t) -1;
+ pa_bool_t allow_realtime, allow_high_priority;
+ pa_bool_t ltdl_init = FALSE;
+
+#ifdef OS_IS_WIN32
+ pa_time_event *timer;
+ struct timeval tv;
+#endif
+
+
+#if defined(__linux__) && defined(__OPTIMIZE__)
+ /*
+ Disable lazy relocations to make usage of external libraries
+ more deterministic for our RT threads. We abuse __OPTIMIZE__ as
+ a check whether we are a debug build or not.
+ */
+
+ if (!getenv("LD_BIND_NOW")) {
+ char *rp;
+
+ /* We have to execute ourselves, because the libc caches the
+ * value of $LD_BIND_NOW on initialization. */
+
+ putenv(pa_xstrdup("LD_BIND_NOW=1"));
+ pa_assert_se(rp = pa_readlink("/proc/self/exe"));
+ pa_assert_se(execv(rp, argv) == 0);
+ }
+#endif
+
+#ifdef HAVE_GETUID
+ real_root = getuid() == 0;
+ suid_root = !real_root && geteuid() == 0;
+#else
+ real_root = FALSE;
+ suid_root = FALSE;
+#endif
+
+ if (suid_root) {
+ /* Drop all capabilities except CAP_SYS_NICE */
+ pa_limit_caps();
+
+ /* Drop priviliges, but keep CAP_SYS_NICE */
+ pa_drop_root();
+
+ /* After dropping root, the effective set is reset, hence,
+ * let's raise it again */
+ pa_limit_caps();
+
+ /* When capabilities are not supported we will not be able to
+ * aquire RT sched anymore. But yes, that's the way it is. It
+ * is just too risky tun let PA run as root all the time. */
+ }
+
+ /* At this point, we are a normal user, possibly with CAP_NICE if
+ * we were started SUID. If we are started as normal root, than we
+ * still are normal root. */
+
+ setlocale(LC_ALL, "");
+ pa_log_set_maximal_level(PA_LOG_INFO);
+ pa_log_set_ident("pulseaudio");
+
+ conf = pa_daemon_conf_new();
+
+ if (pa_daemon_conf_load(conf, NULL) < 0)
+ goto finish;
+
+ if (pa_daemon_conf_env(conf) < 0)
+ goto finish;
+
+ if (pa_cmdline_parse(conf, argc, argv, &d) < 0) {
+ pa_log("Failed to parse command line.");
+ goto finish;
+ }
+
+ pa_log_set_maximal_level(conf->log_level);
+ pa_log_set_target(conf->auto_log_target ? PA_LOG_STDERR : conf->log_target, NULL);
+
+ if (suid_root) {
+ /* Ok, we're suid root, so let's better not enable high prio
+ * or RT by default */
+
+ allow_high_priority = allow_realtime = FALSE;
+
+#ifdef HAVE_POLKIT
+ if (conf->high_priority) {
+ if (pa_polkit_check("org.pulseaudio.acquire-high-priority") > 0) {
+ pa_log_info("PolicyKit grants us acquire-high-priority privilige.");
+ allow_high_priority = TRUE;
+ } else
+ pa_log_info("PolicyKit refuses acquire-high-priority privilige.");
+ }
+
+ if (conf->realtime_scheduling) {
+ if (pa_polkit_check("org.pulseaudio.acquire-real-time") > 0) {
+ pa_log_info("PolicyKit grants us acquire-real-time privilige.");
+ allow_realtime = TRUE;
+ } else
+ pa_log_info("PolicyKit refuses acquire-real-time privilige.");
+ }
+#endif
+
+ if ((conf->high_priority || conf->realtime_scheduling) && pa_own_uid_in_group(PA_REALTIME_GROUP, &gid) > 0) {
+ pa_log_info("We're in the group '"PA_REALTIME_GROUP"', allowing real-time and high-priority scheduling.");
+ allow_realtime = conf->realtime_scheduling;
+ allow_high_priority = conf->high_priority;
+ }
+
+ if (!allow_high_priority && !allow_realtime) {
+
+ /* OK, there's no further need to keep CAP_NICE. Hence
+ * let's give it up early */
+
+ pa_drop_caps();
+ pa_drop_root();
+ suid_root = real_root = FALSE;
+
+ if (conf->high_priority || conf->realtime_scheduling)
+ pa_log_notice("Called SUID root and real-time/high-priority scheduling was requested in the configuration. However, we lack the necessary priviliges:\n"
+ "We are not in group '"PA_REALTIME_GROUP"' and PolicyKit refuse to grant us priviliges. Dropping SUID again.\n"
+ "For enabling real-time scheduling please acquire the appropriate PolicyKit priviliges, or become a member of '"PA_REALTIME_GROUP"', or increase the RLIMIT_NICE/RLIMIT_RTPRIO resource limits for this user.");
+ }
+
+ } else {
+
+ /* OK, we're a normal user, so let's allow the user evrything
+ * he asks for, it's now the kernel's job to enforce limits,
+ * not ours anymore */
+ allow_high_priority = allow_realtime = TRUE;
+ }
+
+ if (conf->high_priority && !allow_high_priority) {
+ pa_log_info("High-priority scheduling enabled in configuration but now allowed by policy. Disabling forcibly.");
+ conf->high_priority = FALSE;
+ }
+
+ if (conf->realtime_scheduling && !allow_realtime) {
+ pa_log_info("Real-time scheduling enabled in configuration but now allowed by policy. Disabling forcibly.");
+ conf->realtime_scheduling = FALSE;
+ }
+
+ if (conf->high_priority && conf->cmd == PA_CMD_DAEMON)
+ pa_raise_priority(conf->nice_level);
+
+ if (suid_root) {
+ pa_bool_t drop;
+
+ drop = conf->cmd != PA_CMD_DAEMON || !conf->realtime_scheduling;
+
+#ifdef RLIMIT_RTPRIO
+ if (!drop) {
+
+ /* At this point we still have CAP_NICE if we were loaded
+ * SUID root. If possible let's acquire RLIMIT_RTPRIO
+ * instead and give CAP_NICE up. */
+
+ const pa_rlimit rl = { 9, TRUE };
+
+ if (set_one_rlimit(&rl, RLIMIT_RTPRIO, "RLIMIT_RTPRIO") >= 0) {
+ pa_log_info("Successfully increased RLIMIT_RTPRIO, giving up CAP_NICE.");
+ drop = TRUE;
+ } else
+ pa_log_warn("RLIMIT_RTPRIO failed: %s", pa_cstrerror(errno));
+ }
+#endif
+
+ if (drop) {
+ pa_drop_caps();
+ pa_drop_root();
+ suid_root = real_root = FALSE;
+ }
+ }
+
+ LTDL_SET_PRELOADED_SYMBOLS();
+ pa_ltdl_init();
+ ltdl_init = TRUE;
+
+ if (conf->dl_search_path)
+ lt_dlsetsearchpath(conf->dl_search_path);
+
+#ifdef OS_IS_WIN32
+ {
+ WSADATA data;
+ WSAStartup(MAKEWORD(2, 0), &data);
+ }
+#endif
+
+ pa_random_seed();
+
+ switch (conf->cmd) {
+ case PA_CMD_DUMP_MODULES:
+ pa_dump_modules(conf, argc-d, argv+d);
+ retval = 0;
+ goto finish;
+
+ case PA_CMD_DUMP_CONF: {
+ s = pa_daemon_conf_dump(conf);
+ fputs(s, stdout);
+ pa_xfree(s);
+ retval = 0;
+ goto finish;
+ }
+
+ case PA_CMD_DUMP_RESAMPLE_METHODS: {
+ int i;
+
+ for (i = 0; i < PA_RESAMPLER_MAX; i++)
+ if (pa_resample_method_supported(i))
+ printf("%s\n", pa_resample_method_to_string(i));
+
+ goto finish;
+ }
+
+ case PA_CMD_HELP :
+ pa_cmdline_help(argv[0]);
+ retval = 0;
+ goto finish;
+
+ case PA_CMD_VERSION :
+ printf(PACKAGE_NAME" "PACKAGE_VERSION"\n");
+ retval = 0;
+ goto finish;
+
+ case PA_CMD_CHECK: {
+ pid_t pid;
+
+ if (pa_pid_file_check_running(&pid, "pulseaudio") < 0)
+ pa_log_info("Daemon not running");
+ else {
+ pa_log_info("Daemon running as PID %u", pid);
+ retval = 0;
+ }
+
+ goto finish;
+
+ }
+ case PA_CMD_KILL:
+
+ if (pa_pid_file_kill(SIGINT, NULL, "pulseaudio") < 0)
+ pa_log("Failed to kill daemon.");
+ else
+ retval = 0;
+
+ goto finish;
+
+ case PA_CMD_CLEANUP_SHM:
+
+ if (pa_shm_cleanup() >= 0)
+ retval = 0;
+
+ goto finish;
+
+ default:
+ pa_assert(conf->cmd == PA_CMD_DAEMON);
+ }
+
+ if (real_root && !conf->system_instance)
+ pa_log_warn("This program is not intended to be run as root (unless --system is specified).");
+ else if (!real_root && conf->system_instance) {
+ pa_log("Root priviliges required.");
+ goto finish;
+ }
+
+ if (conf->daemonize) {
+ pid_t child;
+ int tty_fd;
+
+ if (pa_stdio_acquire() < 0) {
+ pa_log("Failed to acquire stdio.");
+ goto finish;
+ }
+
+#ifdef HAVE_FORK
+ if (pipe(daemon_pipe) < 0) {
+ pa_log("Failed to create pipe.");
+ goto finish;
+ }
+
+ if ((child = fork()) < 0) {
+ pa_log("fork() failed: %s", pa_cstrerror(errno));
+ goto finish;
+ }
+
+ if (child != 0) {
+ /* Father */
+
+ pa_assert_se(pa_close(daemon_pipe[1]) == 0);
+ daemon_pipe[1] = -1;
+
+ if (pa_loop_read(daemon_pipe[0], &retval, sizeof(retval), NULL) != sizeof(retval)) {
+ pa_log("read() failed: %s", pa_cstrerror(errno));
+ retval = 1;
+ }
+
+ if (retval)
+ pa_log("daemon startup failed.");
+ else
+ pa_log_info("daemon startup successful.");
+
+ goto finish;
+ }
+
+ pa_assert_se(pa_close(daemon_pipe[0]) == 0);
+ daemon_pipe[0] = -1;
+#endif
+
+ if (conf->auto_log_target)
+ pa_log_set_target(PA_LOG_SYSLOG, NULL);
+
+#ifdef HAVE_SETSID
+ setsid();
+#endif
+#ifdef HAVE_SETPGID
+ setpgid(0,0);
+#endif
+
+#ifndef OS_IS_WIN32
+ pa_close(0);
+ pa_close(1);
+ pa_close(2);
+
+ open("/dev/null", O_RDONLY);
+ open("/dev/null", O_WRONLY);
+ open("/dev/null", O_WRONLY);
+#else
+ FreeConsole();
+#endif
+
+#ifdef SIGTTOU
+ signal(SIGTTOU, SIG_IGN);
+#endif
+#ifdef SIGTTIN
+ signal(SIGTTIN, SIG_IGN);
+#endif
+#ifdef SIGTSTP
+ signal(SIGTSTP, SIG_IGN);
+#endif
+
+#ifdef TIOCNOTTY
+ if ((tty_fd = open("/dev/tty", O_RDWR)) >= 0) {
+ ioctl(tty_fd, TIOCNOTTY, (char*) 0);
+ pa_assert_se(pa_close(tty_fd) == 0);
+ }
+#endif
+ }
+
+ pa_assert_se(chdir("/") == 0);
+ umask(0022);
+
+ if (conf->system_instance) {
+ if (change_user() < 0)
+ goto finish;
+ } else if (create_runtime_dir() < 0)
+ goto finish;
+
+ if (conf->use_pid_file) {
+ if (pa_pid_file_create() < 0) {
+ pa_log("pa_pid_file_create() failed.");
+#ifdef HAVE_FORK
+ if (conf->daemonize)
+ pa_loop_write(daemon_pipe[1], &retval, sizeof(retval), NULL);
+#endif
+ goto finish;
+ }
+
+ valid_pid_file = 1;
+ }
+
+#ifdef HAVE_SYS_RESOURCE_H
+ set_all_rlimits(conf);
+#endif
+
+#ifdef SIGPIPE
+ signal(SIGPIPE, SIG_IGN);
+#endif
+
+ pa_log_info("This is PulseAudio " PACKAGE_VERSION);
+ pa_log_info("Page size is %lu bytes", (unsigned long) PA_PAGE_SIZE);
+
+ if (pa_rtclock_hrtimer())
+ pa_log_info("Fresh high-resolution timers available! Bon appetit!");
+ else
+ pa_log_info("Dude, your kernel stinks! The chef's recommendation today is Linux with high-resolution timers enabled!");
+
+#ifdef SIGRTMIN
+ /* Valgrind uses SIGRTMAX. To easy debugging we don't use it here */
+ pa_rtsig_configure(SIGRTMIN, SIGRTMAX-1);
+#endif
+
+ pa_assert_se(mainloop = pa_mainloop_new());
+
+ if (!(c = pa_core_new(pa_mainloop_get_api(mainloop), !conf->disable_shm))) {
+ pa_log("pa_core_new() failed.");
+ goto finish;
+ }
+
+ c->is_system_instance = !!conf->system_instance;
+ c->default_sample_spec = conf->default_sample_spec;
+ c->default_n_fragments = conf->default_n_fragments;
+ c->default_fragment_size_msec = conf->default_fragment_size_msec;
+ c->exit_idle_time = conf->exit_idle_time;
+ c->module_idle_time = conf->module_idle_time;
+ c->scache_idle_time = conf->scache_idle_time;
+ c->resample_method = conf->resample_method;
+ c->realtime_priority = conf->realtime_priority;
+ c->realtime_scheduling = !!conf->realtime_scheduling;
+ c->disable_remixing = !!conf->disable_remixing;
+
+ pa_assert_se(pa_signal_init(pa_mainloop_get_api(mainloop)) == 0);
+ pa_signal_new(SIGINT, signal_callback, c);
+ pa_signal_new(SIGTERM, signal_callback, c);
+
+#ifdef SIGUSR1
+ pa_signal_new(SIGUSR1, signal_callback, c);
+#endif
+#ifdef SIGUSR2
+ pa_signal_new(SIGUSR2, signal_callback, c);
+#endif
+#ifdef SIGHUP
+ pa_signal_new(SIGHUP, signal_callback, c);
+#endif
+
+#ifdef OS_IS_WIN32
+ pa_assert_se(timer = pa_mainloop_get_api(mainloop)->time_new(pa_mainloop_get_api(mainloop), pa_gettimeofday(&tv), message_cb, NULL));
+#endif
+
+ if (conf->daemonize)
+ c->running_as_daemon = TRUE;
+
+ oil_init();
+
+ if (!conf->no_cpu_limit)
+ pa_assert_se(pa_cpu_limit_init(pa_mainloop_get_api(mainloop)) == 0);
+
+ buf = pa_strbuf_new();
+ if (conf->default_script_file)
+ r = pa_cli_command_execute_file(c, conf->default_script_file, buf, &conf->fail);
+
+ if (r >= 0)
+ r = pa_cli_command_execute(c, conf->script_commands, buf, &conf->fail);
+ pa_log_error("%s", s = pa_strbuf_tostring_free(buf));
+ pa_xfree(s);
+
+ /* We completed the initial module loading, so let's disable it
+ * from now on, if requested */
+ c->disallow_module_loading = !!conf->disallow_module_loading;
+
+ if (r < 0 && conf->fail) {
+ pa_log("failed to initialize daemon.");
+#ifdef HAVE_FORK
+ if (conf->daemonize)
+ pa_loop_write(daemon_pipe[1], &retval, sizeof(retval), NULL);
+#endif
+ } else if (!c->modules || pa_idxset_size(c->modules) == 0) {
+ pa_log("daemon startup without any loaded modules, refusing to work.");
+#ifdef HAVE_FORK
+ if (conf->daemonize)
+ pa_loop_write(daemon_pipe[1], &retval, sizeof(retval), NULL);
+#endif
+ } else {
+
+ retval = 0;
+#ifdef HAVE_FORK
+ if (conf->daemonize)
+ pa_loop_write(daemon_pipe[1], &retval, sizeof(retval), NULL);
+#endif
+
+ if (c->default_sink_name &&
+ pa_namereg_get(c, c->default_sink_name, PA_NAMEREG_SINK, 1) == NULL) {
+ pa_log_error("%s : Fatal error. Default sink name (%s) does not exist in name register.", __FILE__, c->default_sink_name);
+ retval = 1;
+ } else {
+ pa_log_info("Daemon startup complete.");
+ if (pa_mainloop_run(mainloop, &retval) < 0)
+ retval = 1;
+ pa_log_info("Daemon shutdown initiated.");
+ }
+ }
+
+#ifdef OS_IS_WIN32
+ pa_mainloop_get_api(mainloop)->time_free(timer);
+#endif
+
+ pa_core_unref(c);
+
+ if (!conf->no_cpu_limit)
+ pa_cpu_limit_done();
+
+ pa_signal_done();
+
+ pa_log_info("Daemon terminated.");
+
+finish:
+
+ if (mainloop)
+ pa_mainloop_free(mainloop);
+
+ if (conf)
+ pa_daemon_conf_free(conf);
+
+ if (valid_pid_file)
+ pa_pid_file_remove();
+
+ pa_close_pipe(daemon_pipe);
+
+#ifdef OS_IS_WIN32
+ WSACleanup();
+#endif
+
+ if (ltdl_init)
+ pa_ltdl_done();
+
+#ifdef HAVE_DBUS
+ dbus_shutdown();
+#endif
+
+ return retval;
+}
diff --git a/src/daemon/polkit.c b/src/daemon/polkit.c
new file mode 100644
index 00000000..362c5194
--- /dev/null
+++ b/src/daemon/polkit.c
@@ -0,0 +1,223 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <inttypes.h>
+
+#include <dbus/dbus.h>
+#include <polkit-dbus/polkit-dbus.h>
+
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+
+#include "polkit.h"
+
+static pa_bool_t show_grant_dialog(const char *action_id) {
+ DBusError dbus_error;
+ DBusConnection *bus = NULL;
+ DBusMessage *m = NULL, *reply = NULL;
+ pa_bool_t r = FALSE;
+ uint32_t xid = 0;
+ int verdict;
+
+ dbus_error_init(&dbus_error);
+
+ if (!(bus = dbus_bus_get(DBUS_BUS_SESSION, &dbus_error))) {
+ pa_log_error("Cannot connect to session bus: %s", dbus_error.message);
+ goto finish;
+ }
+
+ if (!(m = dbus_message_new_method_call("org.gnome.PolicyKit", "/org/gnome/PolicyKit/Manager", "org.gnome.PolicyKit.Manager", "ShowDialog"))) {
+ pa_log_error("Failed to allocate D-Bus message.");
+ goto finish;
+ }
+
+ if (!(dbus_message_append_args(m, DBUS_TYPE_STRING, &action_id, DBUS_TYPE_UINT32, &xid, DBUS_TYPE_INVALID))) {
+ pa_log_error("Failed to append arguments to D-Bus message.");
+ goto finish;
+ }
+
+ if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &dbus_error))) {
+ pa_log_warn("Failed to show grant dialog: %s", dbus_error.message);
+ goto finish;
+ }
+
+ if (!(dbus_message_get_args(reply, &dbus_error, DBUS_TYPE_BOOLEAN, &verdict, DBUS_TYPE_INVALID))) {
+ pa_log_warn("Malformed response from grant manager: %s", dbus_error.message);
+ goto finish;
+ }
+
+ r = !!verdict;
+
+finish:
+
+ if (bus)
+ dbus_connection_unref(bus);
+
+ dbus_error_free(&dbus_error);
+
+ if (m)
+ dbus_message_unref(m);
+
+ if (reply)
+ dbus_message_unref(reply);
+
+ return r;
+}
+
+int pa_polkit_check(const char *action_id) {
+ int ret = -1;
+ DBusError dbus_error;
+ DBusConnection *bus = NULL;
+ PolKitCaller *caller = NULL;
+ PolKitAction *action = NULL;
+ PolKitContext *context = NULL;
+ PolKitError *polkit_error = NULL;
+ PolKitSession *session = NULL;
+ PolKitResult polkit_result;
+
+ dbus_error_init(&dbus_error);
+
+ if (!(bus = dbus_bus_get(DBUS_BUS_SYSTEM, &dbus_error))) {
+ pa_log_error("Cannot connect to system bus: %s", dbus_error.message);
+ goto finish;
+ }
+
+ if (!(caller = polkit_caller_new_from_pid(bus, getpid(), &dbus_error))) {
+ pa_log_error("Cannot get caller from PID: %s", dbus_error.message);
+ goto finish;
+ }
+
+ /* This function is called when PulseAudio is called SUID root. We
+ * want to authenticate the real user that called us and not the
+ * effective user we gained through being SUID root. Hence we
+ * overwrite the UID caller data here explicitly, just for
+ * paranoia. In fact PolicyKit should fill in the UID here anyway
+ * -- an not the EUID or any other user id. */
+
+ if (!(polkit_caller_set_uid(caller, getuid()))) {
+ pa_log_error("Cannot set UID on caller object.");
+ goto finish;
+ }
+
+ if (!(polkit_caller_get_ck_session(caller, &session))) {
+ pa_log_error("Failed to get CK session.");
+ goto finish;
+ }
+
+ /* We need to overwrite the UID in both the caller and the session
+ * object */
+
+ if (!(polkit_session_set_uid(session, getuid()))) {
+ pa_log_error("Cannot set UID on session object.");
+ goto finish;
+ }
+
+ if (!(action = polkit_action_new())) {
+ pa_log_error("Cannot allocate PolKitAction.");
+ goto finish;
+ }
+
+ if (!polkit_action_set_action_id(action, action_id)) {
+ pa_log_error("Cannot set action_id");
+ goto finish;
+ }
+
+ if (!(context = polkit_context_new())) {
+ pa_log_error("Cannot allocate PolKitContext.");
+ goto finish;
+ }
+
+ if (!polkit_context_init(context, &polkit_error)) {
+ pa_log_error("Cannot initialize PolKitContext: %s", polkit_error_get_error_message(polkit_error));
+ goto finish;
+ }
+
+ for (;;) {
+
+#ifdef HAVE_POLKIT_CONTEXT_IS_CALLER_AUTHORIZED
+ polkit_result = polkit_context_is_caller_authorized(context, action, caller, TRUE, &polkit_error);
+
+ if (polkit_error_is_set(polkit_error)) {
+ pa_log_error("Could not determine whether caller is authorized: %s", polkit_error_get_error_message(polkit_error));
+ goto finish;
+ }
+#else
+
+ polkit_result = polkit_context_can_caller_do_action(context, action, caller);
+
+#endif
+
+ if (polkit_result == POLKIT_RESULT_ONLY_VIA_ADMIN_AUTH ||
+ polkit_result == POLKIT_RESULT_ONLY_VIA_ADMIN_AUTH_KEEP_SESSION ||
+ polkit_result == POLKIT_RESULT_ONLY_VIA_ADMIN_AUTH_KEEP_ALWAYS ||
+#ifdef POLKIT_RESULT_ONLY_VIA_ADMIN_AUTH_ONE_SHOT
+ polkit_result == POLKIT_RESULT_ONLY_VIA_ADMIN_AUTH_ONE_SHOT ||
+#endif
+ polkit_result == POLKIT_RESULT_ONLY_VIA_SELF_AUTH ||
+ polkit_result == POLKIT_RESULT_ONLY_VIA_SELF_AUTH_KEEP_SESSION ||
+ polkit_result == POLKIT_RESULT_ONLY_VIA_SELF_AUTH_KEEP_ALWAYS
+#ifdef POLKIT_RESULT_ONLY_VIA_SELF_AUTH_ONE_SHOT
+ || polkit_result == POLKIT_RESULT_ONLY_VIA_SELF_AUTH_ONE_SHOT
+#endif
+ ) {
+
+ if (show_grant_dialog(action_id))
+ continue;
+ }
+
+ break;
+ }
+
+ if (polkit_result != POLKIT_RESULT_YES && polkit_result != POLKIT_RESULT_NO)
+ pa_log_warn("PolicyKit responded with '%s'", polkit_result_to_string_representation(polkit_result));
+
+ ret = polkit_result == POLKIT_RESULT_YES;
+
+finish:
+
+ if (caller)
+ polkit_caller_unref(caller);
+
+ if (action)
+ polkit_action_unref(action);
+
+ if (context)
+ polkit_context_unref(context);
+
+ if (bus)
+ dbus_connection_unref(bus);
+
+ dbus_error_free(&dbus_error);
+
+ if (polkit_error)
+ polkit_error_free(polkit_error);
+
+ return ret;
+}
diff --git a/src/daemon/polkit.h b/src/daemon/polkit.h
new file mode 100644
index 00000000..cbcf6a6a
--- /dev/null
+++ b/src/daemon/polkit.h
@@ -0,0 +1,29 @@
+#ifndef foopolkithfoo
+#define foopolkithfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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.
+***/
+
+int pa_polkit_check(const char *action);
+
+#endif
diff --git a/src/daemon/pulseaudio-module-xsmp.desktop b/src/daemon/pulseaudio-module-xsmp.desktop
new file mode 100644
index 00000000..fa719a73
--- /dev/null
+++ b/src/daemon/pulseaudio-module-xsmp.desktop
@@ -0,0 +1,10 @@
+[Desktop Entry]
+Version=1.0
+Encoding=UTF-8
+Name=PulseAudio Session Management
+Comment=Load module-x11-xsmp into PulseAudio
+Exec=pactl load-module module-x11-xsmp
+Terminal=false
+Type=Application
+Categories=
+GenericName=
diff --git a/src/depmod.py b/src/depmod.py
new file mode 100755
index 00000000..a20bc7c0
--- /dev/null
+++ b/src/depmod.py
@@ -0,0 +1,73 @@
+#!/usr/bin/python
+# $Id$
+#
+# This file is part of PulseAudio.
+#
+# PulseAudio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# PulseAudio is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with PulseAudio; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+# USA.
+
+import sys, os, string
+
+exported_symbols = {}
+imported_symbols = {}
+
+for fn in sys.argv[1:]:
+ f = os.popen("nm '%s'" % fn, "r")
+
+ imported_symbols[fn] = []
+
+ for line in f:
+ sym_address = line[:7].strip()
+ sym_type = line[9].strip()
+ sym_name = line[11:].strip()
+
+ if sym_name in ('_fini', '_init'):
+ continue
+
+ if sym_type in ('T', 'B', 'R', 'D' 'G', 'S', 'D'):
+ if exported_symbols.has_key(sym_name):
+ sys.stderr.write("CONFLICT: %s defined in both '%s' and '%s'.\n" % (sym_name, fn, exported_symbols[sym_name]))
+ else:
+ exported_symbols[sym_name] = fn
+ elif sym_type in ('U',):
+ if sym_name[:3] == 'pa_':
+ imported_symbols[fn].append(sym_name)
+
+ f.close()
+
+dependencies = {}
+unresolved_symbols = {}
+
+for fn in imported_symbols:
+ dependencies[fn] = []
+
+ for sym in imported_symbols[fn]:
+ if exported_symbols.has_key(sym):
+ if exported_symbols[sym] not in dependencies[fn]:
+ dependencies[fn].append(exported_symbols[sym])
+ else:
+ if unresolved_symbols.has_key(sym):
+ unresolved_symbols[sym].append(fn)
+ else:
+ unresolved_symbols[sym] = [fn]
+
+for sym, files in unresolved_symbols.iteritems():
+ print "WARNING: Unresolved symbol '%s' in %s" % (sym, `files`)
+
+k = dependencies.keys()
+k.sort()
+for fn in k:
+ dependencies[fn].sort()
+ print "%s: %s" % (fn, string.join(dependencies[fn], " "))
diff --git a/src/map-file b/src/map-file
new file mode 100644
index 00000000..ffa5d103
--- /dev/null
+++ b/src/map-file
@@ -0,0 +1,237 @@
+PULSE_0 {
+global:
+pa_browser_new;
+pa_browser_new_full;
+pa_browser_ref;
+pa_browser_set_callback;
+pa_browser_set_error_callback;
+pa_browser_unref;
+pa_bytes_per_second;
+pa_bytes_snprint;
+pa_bytes_to_usec;
+pa_channel_map_equal;
+pa_channel_map_init;
+pa_channel_map_init_auto;
+pa_channel_map_init_mono;
+pa_channel_map_init_stereo;
+pa_channel_map_parse;
+pa_channel_map_snprint;
+pa_channel_map_valid;
+pa_channel_position_to_pretty_string;
+pa_channel_position_to_string;
+pa_context_add_autoload;
+pa_context_connect;
+pa_context_disconnect;
+pa_context_drain;
+pa_context_errno;
+pa_context_exit_daemon;
+pa_context_get_autoload_info_by_index;
+pa_context_get_autoload_info_by_name;
+pa_context_get_autoload_info_list;
+pa_context_get_client_info;
+pa_context_get_client_info_list;
+pa_context_get_module_info;
+pa_context_get_module_info_list;
+pa_context_get_protocol_version;
+pa_context_get_sample_info_by_index;
+pa_context_get_sample_info_by_name;
+pa_context_get_sample_info_list;
+pa_context_get_server;
+pa_context_get_server_info;
+pa_context_get_server_protocol_version;
+pa_context_get_sink_info_by_index;
+pa_context_get_sink_info_by_name;
+pa_context_get_sink_info_list;
+pa_context_get_sink_input_info;
+pa_context_get_sink_input_info_list;
+pa_context_get_source_info_by_index;
+pa_context_get_source_info_by_name;
+pa_context_get_source_info_list;
+pa_context_get_source_output_info;
+pa_context_get_source_output_info_list;
+pa_context_get_state;
+pa_context_is_local;
+pa_context_is_pending;
+pa_context_kill_client;
+pa_context_kill_sink_input;
+pa_context_kill_source_output;
+pa_context_load_module;
+pa_context_move_sink_input_by_index;
+pa_context_move_sink_input_by_name;
+pa_context_move_source_output_by_index;
+pa_context_move_source_output_by_name;
+pa_context_new;
+pa_context_play_sample;
+pa_context_ref;
+pa_context_remove_autoload_by_index;
+pa_context_remove_autoload_by_name;
+pa_context_remove_sample;
+pa_context_set_default_sink;
+pa_context_set_default_source;
+pa_context_set_name;
+pa_context_set_sink_input_mute;
+pa_context_set_sink_input_volume;
+pa_context_set_sink_mute_by_index;
+pa_context_set_sink_mute_by_name;
+pa_context_set_sink_volume_by_index;
+pa_context_set_sink_volume_by_name;
+pa_context_set_source_mute_by_index;
+pa_context_set_source_mute_by_name;
+pa_context_set_source_volume_by_index;
+pa_context_set_source_volume_by_name;
+pa_context_set_state_callback;
+pa_context_set_subscribe_callback;
+pa_context_stat;
+pa_context_subscribe;
+pa_context_suspend_sink_by_index;
+pa_context_suspend_sink_by_name;
+pa_context_suspend_source_by_index;
+pa_context_suspend_source_by_name;
+pa_context_unload_module;
+pa_context_unref;
+pa_cvolume_avg;
+pa_cvolume_channels_equal_to;
+pa_cvolume_equal;
+pa_cvolume_set;
+pa_cvolume_snprint;
+pa_cvolume_valid;
+pa_frame_size;
+pa_get_binary_name;
+pa_get_fqdn;
+pa_get_home_dir;
+pa_get_host_name;
+pa_get_library_version;
+pa_gettimeofday;
+pa_get_user_name;
+pa_glib_mainloop_free;
+pa_glib_mainloop_get_api;
+pa_glib_mainloop_new;
+pa_locale_to_utf8;
+pa_mainloop_api_once;
+pa_mainloop_dispatch;
+pa_mainloop_free;
+pa_mainloop_get_api;
+pa_mainloop_get_retval;
+pa_mainloop_iterate;
+pa_mainloop_new;
+pa_mainloop_poll;
+pa_mainloop_prepare;
+pa_mainloop_quit;
+pa_mainloop_run;
+pa_mainloop_set_poll_func;
+pa_mainloop_wakeup;
+pa_msleep;
+pa_operation_cancel;
+pa_operation_get_state;
+pa_operation_ref;
+pa_operation_unref;
+pa_parse_sample_format;
+pa_path_get_filename;
+pa_proplist_free;
+pa_proplist_get;
+pa_proplist_gets;
+pa_proplist_iterate;
+pa_proplist_merge;
+pa_proplist_new;
+pa_proplist_put;
+pa_proplist_puts;
+pa_proplist_remove;
+pa_proplist_to_string;
+pa_sample_format_to_string;
+pa_sample_size;
+pa_sample_spec_equal;
+pa_sample_spec_snprint;
+pa_sample_spec_valid;
+pa_signal_done;
+pa_signal_free;
+pa_signal_init;
+pa_signal_new;
+pa_signal_set_destroy;
+pa_simple_drain;
+pa_simple_flush;
+pa_simple_free;
+pa_simple_get_latency;
+pa_simple_new;
+pa_simple_read;
+pa_simple_write;
+pa_stream_connect_playback;
+pa_stream_connect_record;
+pa_stream_connect_upload;
+pa_stream_cork;
+pa_stream_disconnect;
+pa_stream_drain;
+pa_stream_drop;
+pa_stream_finish_upload;
+pa_stream_flush;
+pa_stream_get_buffer_attr;
+pa_stream_get_channel_map;
+pa_stream_get_context;
+pa_stream_get_device_index;
+pa_stream_get_device_name;
+pa_stream_get_index;
+pa_stream_get_latency;
+pa_stream_get_sample_spec;
+pa_stream_get_state;
+pa_stream_get_time;
+pa_stream_get_timing_info;
+pa_stream_is_suspended;
+pa_stream_new;
+pa_stream_peek;
+pa_stream_prebuf;
+pa_stream_readable_size;
+pa_stream_ref;
+pa_stream_set_buffer_attr;
+pa_stream_set_latency_update_callback;
+pa_stream_set_moved_callback;
+pa_stream_set_name;
+pa_stream_set_overflow_callback;
+pa_stream_set_read_callback;
+pa_stream_set_state_callback;
+pa_stream_set_suspended_callback;
+pa_stream_set_underflow_callback;
+pa_stream_set_write_callback;
+pa_stream_trigger;
+pa_stream_unref;
+pa_stream_update_sample_rate;
+pa_stream_update_timing_info;
+pa_stream_writable_size;
+pa_stream_write;
+pa_strerror;
+pa_sw_cvolume_multiply;
+pa_sw_volume_from_dB;
+pa_sw_volume_from_linear;
+pa_sw_volume_multiply;
+pa_sw_volume_to_dB;
+pa_sw_volume_to_linear;
+pa_threaded_mainloop_accept;
+pa_threaded_mainloop_free;
+pa_threaded_mainloop_get_api;
+pa_threaded_mainloop_get_retval;
+pa_threaded_mainloop_in_thread;
+pa_threaded_mainloop_lock;
+pa_threaded_mainloop_new;
+pa_threaded_mainloop_signal;
+pa_threaded_mainloop_start;
+pa_threaded_mainloop_stop;
+pa_threaded_mainloop_unlock;
+pa_threaded_mainloop_wait;
+pa_timeval_add;
+pa_timeval_age;
+pa_timeval_cmp;
+pa_timeval_diff;
+pa_timeval_load;
+pa_timeval_store;
+pa_usec_to_bytes;
+pa_utf8_filter;
+pa_utf8_to_locale;
+pa_utf8_valid;
+pa_xfree;
+pa_xmalloc;
+pa_xmalloc0;
+pa_xmemdup;
+pa_xrealloc;
+pa_xstrdup;
+pa_xstrndup;
+local:
+*;
+};
diff --git a/src/modules/Makefile b/src/modules/Makefile
new file mode 120000
index 00000000..c110232d
--- /dev/null
+++ b/src/modules/Makefile
@@ -0,0 +1 @@
+../pulse/Makefile \ No newline at end of file
diff --git a/src/modules/alsa-util.c b/src/modules/alsa-util.c
new file mode 100644
index 00000000..6afec3bc
--- /dev/null
+++ b/src/modules/alsa-util.c
@@ -0,0 +1,799 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sys/types.h>
+#include <asoundlib.h>
+
+#include <pulse/sample.h>
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/core-util.h>
+
+#include "alsa-util.h"
+
+struct pa_alsa_fdlist {
+ int num_fds;
+ struct pollfd *fds;
+ /* This is a temporary buffer used to avoid lots of mallocs */
+ struct pollfd *work_fds;
+
+ snd_mixer_t *mixer;
+
+ pa_mainloop_api *m;
+ pa_defer_event *defer;
+ pa_io_event **ios;
+
+ int polled;
+
+ void (*cb)(void *userdata);
+ void *userdata;
+};
+
+static void io_cb(pa_mainloop_api*a, pa_io_event* e, PA_GCC_UNUSED int fd, pa_io_event_flags_t events, void *userdata) {
+
+ struct pa_alsa_fdlist *fdl = userdata;
+ int err, i;
+ unsigned short revents;
+
+ pa_assert(a);
+ pa_assert(fdl);
+ pa_assert(fdl->mixer);
+ pa_assert(fdl->fds);
+ pa_assert(fdl->work_fds);
+
+ if (fdl->polled)
+ return;
+
+ fdl->polled = 1;
+
+ memcpy(fdl->work_fds, fdl->fds, sizeof(struct pollfd) * fdl->num_fds);
+
+ for (i = 0;i < fdl->num_fds; i++) {
+ if (e == fdl->ios[i]) {
+ if (events & PA_IO_EVENT_INPUT)
+ fdl->work_fds[i].revents |= POLLIN;
+ if (events & PA_IO_EVENT_OUTPUT)
+ fdl->work_fds[i].revents |= POLLOUT;
+ if (events & PA_IO_EVENT_ERROR)
+ fdl->work_fds[i].revents |= POLLERR;
+ if (events & PA_IO_EVENT_HANGUP)
+ fdl->work_fds[i].revents |= POLLHUP;
+ break;
+ }
+ }
+
+ pa_assert(i != fdl->num_fds);
+
+ if ((err = snd_mixer_poll_descriptors_revents(fdl->mixer, fdl->work_fds, fdl->num_fds, &revents)) < 0) {
+ pa_log_error("Unable to get poll revent: %s", snd_strerror(err));
+ return;
+ }
+
+ a->defer_enable(fdl->defer, 1);
+
+ if (revents)
+ snd_mixer_handle_events(fdl->mixer);
+}
+
+static void defer_cb(pa_mainloop_api*a, PA_GCC_UNUSED pa_defer_event* e, void *userdata) {
+ struct pa_alsa_fdlist *fdl = userdata;
+ int num_fds, i, err;
+ struct pollfd *temp;
+
+ pa_assert(a);
+ pa_assert(fdl);
+ pa_assert(fdl->mixer);
+
+ a->defer_enable(fdl->defer, 0);
+
+ num_fds = snd_mixer_poll_descriptors_count(fdl->mixer);
+ pa_assert(num_fds > 0);
+
+ if (num_fds != fdl->num_fds) {
+ if (fdl->fds)
+ pa_xfree(fdl->fds);
+ if (fdl->work_fds)
+ pa_xfree(fdl->work_fds);
+ fdl->fds = pa_xnew0(struct pollfd, num_fds);
+ fdl->work_fds = pa_xnew(struct pollfd, num_fds);
+ }
+
+ memset(fdl->work_fds, 0, sizeof(struct pollfd) * num_fds);
+
+ if ((err = snd_mixer_poll_descriptors(fdl->mixer, fdl->work_fds, num_fds)) < 0) {
+ pa_log_error("Unable to get poll descriptors: %s", snd_strerror(err));
+ return;
+ }
+
+ fdl->polled = 0;
+
+ if (memcmp(fdl->fds, fdl->work_fds, sizeof(struct pollfd) * num_fds) == 0)
+ return;
+
+ if (fdl->ios) {
+ for (i = 0; i < fdl->num_fds; i++)
+ a->io_free(fdl->ios[i]);
+
+ if (num_fds != fdl->num_fds) {
+ pa_xfree(fdl->ios);
+ fdl->ios = NULL;
+ }
+ }
+
+ if (!fdl->ios)
+ fdl->ios = pa_xnew(pa_io_event*, num_fds);
+
+ /* Swap pointers */
+ temp = fdl->work_fds;
+ fdl->work_fds = fdl->fds;
+ fdl->fds = temp;
+
+ fdl->num_fds = num_fds;
+
+ for (i = 0;i < num_fds;i++)
+ fdl->ios[i] = a->io_new(a, fdl->fds[i].fd,
+ ((fdl->fds[i].events & POLLIN) ? PA_IO_EVENT_INPUT : 0) |
+ ((fdl->fds[i].events & POLLOUT) ? PA_IO_EVENT_OUTPUT : 0),
+ io_cb, fdl);
+}
+
+struct pa_alsa_fdlist *pa_alsa_fdlist_new(void) {
+ struct pa_alsa_fdlist *fdl;
+
+ fdl = pa_xnew0(struct pa_alsa_fdlist, 1);
+
+ fdl->num_fds = 0;
+ fdl->fds = NULL;
+ fdl->work_fds = NULL;
+ fdl->mixer = NULL;
+ fdl->m = NULL;
+ fdl->defer = NULL;
+ fdl->ios = NULL;
+ fdl->polled = 0;
+
+ return fdl;
+}
+
+void pa_alsa_fdlist_free(struct pa_alsa_fdlist *fdl) {
+ pa_assert(fdl);
+
+ if (fdl->defer) {
+ pa_assert(fdl->m);
+ fdl->m->defer_free(fdl->defer);
+ }
+
+ if (fdl->ios) {
+ int i;
+ pa_assert(fdl->m);
+ for (i = 0;i < fdl->num_fds;i++)
+ fdl->m->io_free(fdl->ios[i]);
+ pa_xfree(fdl->ios);
+ }
+
+ if (fdl->fds)
+ pa_xfree(fdl->fds);
+ if (fdl->work_fds)
+ pa_xfree(fdl->work_fds);
+
+ pa_xfree(fdl);
+}
+
+int pa_alsa_fdlist_set_mixer(struct pa_alsa_fdlist *fdl, snd_mixer_t *mixer_handle, pa_mainloop_api* m) {
+ pa_assert(fdl);
+ pa_assert(mixer_handle);
+ pa_assert(m);
+ pa_assert(!fdl->m);
+
+ fdl->mixer = mixer_handle;
+ fdl->m = m;
+ fdl->defer = m->defer_new(m, defer_cb, fdl);
+
+ return 0;
+}
+
+static int set_format(snd_pcm_t *pcm_handle, snd_pcm_hw_params_t *hwparams, pa_sample_format_t *f) {
+
+ static const snd_pcm_format_t format_trans[] = {
+ [PA_SAMPLE_U8] = SND_PCM_FORMAT_U8,
+ [PA_SAMPLE_ALAW] = SND_PCM_FORMAT_A_LAW,
+ [PA_SAMPLE_ULAW] = SND_PCM_FORMAT_MU_LAW,
+ [PA_SAMPLE_S16LE] = SND_PCM_FORMAT_S16_LE,
+ [PA_SAMPLE_S16BE] = SND_PCM_FORMAT_S16_BE,
+ [PA_SAMPLE_FLOAT32LE] = SND_PCM_FORMAT_FLOAT_LE,
+ [PA_SAMPLE_FLOAT32BE] = SND_PCM_FORMAT_FLOAT_BE,
+ [PA_SAMPLE_S32LE] = SND_PCM_FORMAT_S32_LE,
+ [PA_SAMPLE_S32BE] = SND_PCM_FORMAT_S32_BE,
+ };
+
+ static const pa_sample_format_t try_order[] = {
+ PA_SAMPLE_FLOAT32NE,
+ PA_SAMPLE_FLOAT32RE,
+ PA_SAMPLE_S32NE,
+ PA_SAMPLE_S32RE,
+ PA_SAMPLE_S16NE,
+ PA_SAMPLE_S16RE,
+ PA_SAMPLE_ALAW,
+ PA_SAMPLE_ULAW,
+ PA_SAMPLE_U8,
+ PA_SAMPLE_INVALID
+ };
+
+ int i, ret;
+
+ pa_assert(pcm_handle);
+ pa_assert(f);
+
+ if ((ret = snd_pcm_hw_params_set_format(pcm_handle, hwparams, format_trans[*f])) >= 0)
+ return ret;
+
+ if (*f == PA_SAMPLE_FLOAT32BE)
+ *f = PA_SAMPLE_FLOAT32LE;
+ else if (*f == PA_SAMPLE_FLOAT32LE)
+ *f = PA_SAMPLE_FLOAT32BE;
+ else if (*f == PA_SAMPLE_S16BE)
+ *f = PA_SAMPLE_S16LE;
+ else if (*f == PA_SAMPLE_S16LE)
+ *f = PA_SAMPLE_S16BE;
+ else if (*f == PA_SAMPLE_S32BE)
+ *f = PA_SAMPLE_S32LE;
+ else if (*f == PA_SAMPLE_S32LE)
+ *f = PA_SAMPLE_S32BE;
+ else
+ goto try_auto;
+
+ if ((ret = snd_pcm_hw_params_set_format(pcm_handle, hwparams, format_trans[*f])) >= 0)
+ return ret;
+
+try_auto:
+
+ for (i = 0; try_order[i] != PA_SAMPLE_INVALID; i++) {
+ *f = try_order[i];
+
+ if ((ret = snd_pcm_hw_params_set_format(pcm_handle, hwparams, format_trans[*f])) >= 0)
+ return ret;
+ }
+
+ return -1;
+}
+
+/* Set the hardware parameters of the given ALSA device. Returns the
+ * selected fragment settings in *period and *period_size */
+int pa_alsa_set_hw_params(
+ snd_pcm_t *pcm_handle,
+ pa_sample_spec *ss,
+ uint32_t *periods,
+ snd_pcm_uframes_t *period_size,
+ pa_bool_t *use_mmap,
+ pa_bool_t require_exact_channel_number) {
+
+ int ret = -1;
+ snd_pcm_uframes_t buffer_size;
+ unsigned int r = ss->rate;
+ unsigned int c = ss->channels;
+ pa_sample_format_t f = ss->format;
+ snd_pcm_hw_params_t *hwparams;
+ pa_bool_t _use_mmap = use_mmap && *use_mmap;
+
+ pa_assert(pcm_handle);
+ pa_assert(ss);
+ pa_assert(periods);
+ pa_assert(period_size);
+
+ snd_pcm_hw_params_alloca(&hwparams);
+
+ buffer_size = *periods * *period_size;
+
+ if ((ret = snd_pcm_hw_params_any(pcm_handle, hwparams)) < 0)
+ goto finish;
+
+ if ((ret = snd_pcm_hw_params_set_rate_resample(pcm_handle, hwparams, 0)) < 0)
+ goto finish;
+
+ if (_use_mmap) {
+ if ((ret = snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_MMAP_INTERLEAVED)) < 0) {
+
+ /* mmap() didn't work, fall back to interleaved */
+
+ if ((ret = snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
+ goto finish;
+
+ _use_mmap = FALSE;
+ }
+
+ } else if ((ret = snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
+ goto finish;
+
+ if ((ret = set_format(pcm_handle, hwparams, &f)) < 0)
+ goto finish;
+
+ if ((ret = snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &r, NULL)) < 0)
+ goto finish;
+
+ if (require_exact_channel_number) {
+ if ((ret = snd_pcm_hw_params_set_channels(pcm_handle, hwparams, c)) < 0)
+ goto finish;
+ } else {
+ if ((ret = snd_pcm_hw_params_set_channels_near(pcm_handle, hwparams, &c)) < 0)
+ goto finish;
+ }
+
+ if ((*period_size > 0 && (ret = snd_pcm_hw_params_set_period_size_near(pcm_handle, hwparams, period_size, NULL)) < 0) ||
+ (*periods > 0 && (ret = snd_pcm_hw_params_set_buffer_size_near(pcm_handle, hwparams, &buffer_size)) < 0))
+ goto finish;
+
+ if ((ret = snd_pcm_hw_params(pcm_handle, hwparams)) < 0)
+ goto finish;
+
+ if (ss->rate != r)
+ pa_log_warn("Device %s doesn't support %u Hz, changed to %u Hz.", snd_pcm_name(pcm_handle), ss->rate, r);
+
+ if (ss->channels != c)
+ pa_log_warn("Device %s doesn't support %u channels, changed to %u.", snd_pcm_name(pcm_handle), ss->channels, c);
+
+ if (ss->format != f)
+ pa_log_warn("Device %s doesn't support sample format %s, changed to %s.", snd_pcm_name(pcm_handle), pa_sample_format_to_string(ss->format), pa_sample_format_to_string(f));
+
+ if ((ret = snd_pcm_prepare(pcm_handle)) < 0)
+ goto finish;
+
+ if ((ret = snd_pcm_hw_params_get_buffer_size(hwparams, &buffer_size)) < 0 ||
+ (ret = snd_pcm_hw_params_get_period_size(hwparams, period_size, NULL)) < 0)
+ goto finish;
+
+ /* If the sample rate deviates too much, we need to resample */
+ if (r < ss->rate*.95 || r > ss->rate*1.05)
+ ss->rate = r;
+ ss->channels = c;
+ ss->format = f;
+
+ pa_assert(buffer_size > 0);
+ pa_assert(*period_size > 0);
+ *periods = buffer_size / *period_size;
+ pa_assert(*periods > 0);
+
+ if (use_mmap)
+ *use_mmap = _use_mmap;
+
+ ret = 0;
+
+finish:
+
+ return ret;
+}
+
+int pa_alsa_set_sw_params(snd_pcm_t *pcm) {
+ snd_pcm_sw_params_t *swparams;
+ int err;
+
+ pa_assert(pcm);
+
+ snd_pcm_sw_params_alloca(&swparams);
+
+ if ((err = snd_pcm_sw_params_current(pcm, swparams) < 0)) {
+ pa_log_warn("Unable to determine current swparams: %s\n", snd_strerror(err));
+ return err;
+ }
+
+ if ((err = snd_pcm_sw_params_set_stop_threshold(pcm, swparams, (snd_pcm_uframes_t) -1)) < 0) {
+ pa_log_warn("Unable to set stop threshold: %s\n", snd_strerror(err));
+ return err;
+ }
+
+ if ((err = snd_pcm_sw_params_set_start_threshold(pcm, swparams, (snd_pcm_uframes_t) -1)) < 0) {
+ pa_log_warn("Unable to set start threshold: %s\n", snd_strerror(err));
+ return err;
+ }
+
+ if ((err = snd_pcm_sw_params(pcm, swparams)) < 0) {
+ pa_log_warn("Unable to set sw params: %s\n", snd_strerror(err));
+ return err;
+ }
+
+ return 0;
+}
+
+struct device_info {
+ pa_channel_map map;
+ const char *name;
+};
+
+static const struct device_info device_table[] = {
+ {{ 2, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT } }, "front" },
+
+ {{ 4, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT,
+ PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT }}, "surround40" },
+
+ {{ 5, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT,
+ PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT,
+ PA_CHANNEL_POSITION_LFE }}, "surround41" },
+
+ {{ 5, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT,
+ PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT,
+ PA_CHANNEL_POSITION_CENTER }}, "surround50" },
+
+ {{ 6, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT,
+ PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT,
+ PA_CHANNEL_POSITION_CENTER, PA_CHANNEL_POSITION_LFE }}, "surround51" },
+
+ {{ 8, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT,
+ PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT,
+ PA_CHANNEL_POSITION_CENTER, PA_CHANNEL_POSITION_LFE,
+ PA_CHANNEL_POSITION_SIDE_LEFT, PA_CHANNEL_POSITION_SIDE_RIGHT }} , "surround71" },
+
+ {{ 0, { 0 }}, NULL }
+};
+
+static pa_bool_t channel_map_superset(const pa_channel_map *a, const pa_channel_map *b) {
+ pa_bool_t in_a[PA_CHANNEL_POSITION_MAX];
+ unsigned i;
+
+ pa_assert(a);
+ pa_assert(b);
+
+ memset(in_a, 0, sizeof(in_a));
+
+ for (i = 0; i < a->channels; i++)
+ in_a[a->map[i]] = TRUE;
+
+ for (i = 0; i < b->channels; i++)
+ if (!in_a[b->map[i]])
+ return FALSE;
+
+ return TRUE;
+}
+
+snd_pcm_t *pa_alsa_open_by_device_id(
+ const char *dev_id,
+ char **dev,
+ pa_sample_spec *ss,
+ pa_channel_map* map,
+ int mode,
+ uint32_t *nfrags,
+ snd_pcm_uframes_t *period_size,
+ pa_bool_t *use_mmap) {
+
+ int i;
+ int direction = 1;
+ int err;
+ char *d;
+ snd_pcm_t *pcm_handle;
+
+ pa_assert(dev_id);
+ pa_assert(dev);
+ pa_assert(ss);
+ pa_assert(map);
+ pa_assert(nfrags);
+ pa_assert(period_size);
+
+ /* First we try to find a device string with a superset of the
+ * requested channel map and open it without the plug: prefix. We
+ * iterate through our device table from top to bottom and take
+ * the first that matches. If we didn't find a working device that
+ * way, we iterate backwards, and check all devices that do not
+ * provide a superset of the requested channel map.*/
+
+ for (i = 0;; i += direction) {
+ pa_sample_spec try_ss;
+
+ if (i < 0) {
+ pa_assert(direction == -1);
+
+ /* OK, so we iterated backwards, and now are at the
+ * beginning of our list. */
+
+ break;
+
+ } else if (!device_table[i].name) {
+ pa_assert(direction == 1);
+
+ /* OK, so we are at the end of our list. at iterated
+ * forwards. */
+
+ i--;
+ direction = -1;
+ }
+
+ if ((direction > 0) == !channel_map_superset(&device_table[i].map, map))
+ continue;
+
+ d = pa_sprintf_malloc("%s:%s", device_table[i].name, dev_id);
+ pa_log_debug("Trying %s...", d);
+
+ if ((err = snd_pcm_open(&pcm_handle, d, mode, SND_PCM_NONBLOCK)) < 0) {
+ pa_log_info("Couldn't open PCM device %s: %s", d, snd_strerror(err));
+ pa_xfree(d);
+ continue;
+ }
+
+ try_ss.channels = device_table[i].map.channels;
+ try_ss.rate = ss->rate;
+ try_ss.format = ss->format;
+
+ if ((err = pa_alsa_set_hw_params(pcm_handle, &try_ss, nfrags, period_size, use_mmap, TRUE)) < 0) {
+ pa_log_info("PCM device %s refused our hw parameters: %s", d, snd_strerror(err));
+ pa_xfree(d);
+ snd_pcm_close(pcm_handle);
+ continue;
+ }
+
+ *ss = try_ss;
+ *map = device_table[i].map;
+ pa_assert(map->channels == ss->channels);
+ *dev = d;
+ return pcm_handle;
+ }
+
+ /* OK, we didn't find any good device, so let's try the raw hw: stuff */
+
+ d = pa_sprintf_malloc("hw:%s", dev_id);
+ pa_log_debug("Trying %s as last resort...", d);
+ pcm_handle = pa_alsa_open_by_device_string(d, dev, ss, map, mode, nfrags, period_size, use_mmap);
+ pa_xfree(d);
+
+ return pcm_handle;
+}
+
+snd_pcm_t *pa_alsa_open_by_device_string(
+ const char *device,
+ char **dev,
+ pa_sample_spec *ss,
+ pa_channel_map* map,
+ int mode,
+ uint32_t *nfrags,
+ snd_pcm_uframes_t *period_size,
+ pa_bool_t *use_mmap) {
+
+ int err;
+ char *d;
+ snd_pcm_t *pcm_handle;
+
+ pa_assert(device);
+ pa_assert(dev);
+ pa_assert(ss);
+ pa_assert(map);
+ pa_assert(nfrags);
+ pa_assert(period_size);
+
+ d = pa_xstrdup(device);
+
+ for (;;) {
+
+ if ((err = snd_pcm_open(&pcm_handle, d, mode, SND_PCM_NONBLOCK)) < 0) {
+ pa_log("Error opening PCM device %s: %s", d, snd_strerror(err));
+ pa_xfree(d);
+ return NULL;
+ }
+
+ if ((err = pa_alsa_set_hw_params(pcm_handle, ss, nfrags, period_size, use_mmap, FALSE)) < 0) {
+
+ if (err == -EPERM) {
+ /* Hmm, some hw is very exotic, so we retry with plug, if without it didn't work */
+
+ if (pa_startswith(d, "hw:")) {
+ char *t = pa_sprintf_malloc("plughw:%s", d+3);
+ pa_log_debug("Opening the device as '%s' didn't work, retrying with '%s'.", d, t);
+ pa_xfree(d);
+ d = t;
+
+ snd_pcm_close(pcm_handle);
+ continue;
+ }
+
+ pa_log("Failed to set hardware parameters on %s: %s", d, snd_strerror(err));
+ pa_xfree(d);
+ snd_pcm_close(pcm_handle);
+ return NULL;
+ }
+ }
+
+ *dev = d;
+
+ if (ss->channels != map->channels) {
+ pa_assert_se(pa_channel_map_init_auto(map, ss->channels, PA_CHANNEL_MAP_AUX));
+ pa_channel_map_init_auto(map, ss->channels, PA_CHANNEL_MAP_ALSA);
+ }
+
+ return pcm_handle;
+ }
+}
+
+int pa_alsa_prepare_mixer(snd_mixer_t *mixer, const char *dev) {
+ int err;
+
+ pa_assert(mixer);
+ pa_assert(dev);
+
+ if ((err = snd_mixer_attach(mixer, dev)) < 0) {
+ pa_log_info("Unable to attach to mixer %s: %s", dev, snd_strerror(err));
+ return -1;
+ }
+
+ if ((err = snd_mixer_selem_register(mixer, NULL, NULL)) < 0) {
+ pa_log_warn("Unable to register mixer: %s", snd_strerror(err));
+ return -1;
+ }
+
+ if ((err = snd_mixer_load(mixer)) < 0) {
+ pa_log_warn("Unable to load mixer: %s", snd_strerror(err));
+ return -1;
+ }
+
+ pa_log_info("Successfully attached to mixer '%s'", dev);
+
+ return 0;
+}
+
+snd_mixer_elem_t *pa_alsa_find_elem(snd_mixer_t *mixer, const char *name, const char *fallback) {
+ snd_mixer_elem_t *elem;
+ snd_mixer_selem_id_t *sid = NULL;
+
+ snd_mixer_selem_id_alloca(&sid);
+
+ pa_assert(mixer);
+ pa_assert(name);
+
+ snd_mixer_selem_id_set_name(sid, name);
+
+ if (!(elem = snd_mixer_find_selem(mixer, sid))) {
+ pa_log_info("Cannot find mixer control \"%s\".", snd_mixer_selem_id_get_name(sid));
+
+ if (fallback) {
+ snd_mixer_selem_id_set_name(sid, fallback);
+
+ if (!(elem = snd_mixer_find_selem(mixer, sid)))
+ pa_log_warn("Cannot find fallback mixer control \"%s\".", snd_mixer_selem_id_get_name(sid));
+ }
+ }
+
+ if (elem)
+ pa_log_info("Using mixer control \"%s\".", snd_mixer_selem_id_get_name(sid));
+
+ return elem;
+}
+
+static const snd_mixer_selem_channel_id_t alsa_channel_ids[PA_CHANNEL_POSITION_MAX] = {
+ [PA_CHANNEL_POSITION_MONO] = SND_MIXER_SCHN_MONO, /* The ALSA name is just an alias! */
+
+ [PA_CHANNEL_POSITION_FRONT_CENTER] = SND_MIXER_SCHN_FRONT_CENTER,
+ [PA_CHANNEL_POSITION_FRONT_LEFT] = SND_MIXER_SCHN_FRONT_LEFT,
+ [PA_CHANNEL_POSITION_FRONT_RIGHT] = SND_MIXER_SCHN_FRONT_RIGHT,
+
+ [PA_CHANNEL_POSITION_REAR_CENTER] = SND_MIXER_SCHN_REAR_CENTER,
+ [PA_CHANNEL_POSITION_REAR_LEFT] = SND_MIXER_SCHN_REAR_LEFT,
+ [PA_CHANNEL_POSITION_REAR_RIGHT] = SND_MIXER_SCHN_REAR_RIGHT,
+
+ [PA_CHANNEL_POSITION_LFE] = SND_MIXER_SCHN_WOOFER,
+
+ [PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER] = SND_MIXER_SCHN_UNKNOWN,
+
+ [PA_CHANNEL_POSITION_SIDE_LEFT] = SND_MIXER_SCHN_SIDE_LEFT,
+ [PA_CHANNEL_POSITION_SIDE_RIGHT] = SND_MIXER_SCHN_SIDE_RIGHT,
+
+ [PA_CHANNEL_POSITION_AUX0] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX1] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX2] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX3] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX4] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX5] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX6] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX7] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX8] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX9] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX10] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX11] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX12] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX13] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX14] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX15] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX16] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX17] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX18] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX19] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX20] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX21] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX22] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX23] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX24] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX25] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX26] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX27] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX28] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX29] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX30] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_AUX31] = SND_MIXER_SCHN_UNKNOWN,
+
+ [PA_CHANNEL_POSITION_TOP_CENTER] = SND_MIXER_SCHN_UNKNOWN,
+
+ [PA_CHANNEL_POSITION_TOP_FRONT_CENTER] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_TOP_FRONT_LEFT] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_TOP_FRONT_RIGHT] = SND_MIXER_SCHN_UNKNOWN,
+
+ [PA_CHANNEL_POSITION_TOP_REAR_CENTER] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_TOP_REAR_LEFT] = SND_MIXER_SCHN_UNKNOWN,
+ [PA_CHANNEL_POSITION_TOP_REAR_RIGHT] = SND_MIXER_SCHN_UNKNOWN
+};
+
+
+int pa_alsa_calc_mixer_map(snd_mixer_elem_t *elem, const pa_channel_map *channel_map, snd_mixer_selem_channel_id_t mixer_map[], pa_bool_t playback) {
+ unsigned i;
+ pa_bool_t alsa_channel_used[SND_MIXER_SCHN_LAST];
+ pa_bool_t mono_used = FALSE;
+
+ pa_assert(elem);
+ pa_assert(channel_map);
+ pa_assert(mixer_map);
+
+ memset(&alsa_channel_used, 0, sizeof(alsa_channel_used));
+
+ if (channel_map->channels > 1 &&
+ ((playback && snd_mixer_selem_has_playback_volume_joined(elem)) ||
+ (!playback && snd_mixer_selem_has_capture_volume_joined(elem)))) {
+ pa_log_info("ALSA device lacks independant volume controls for each channel, falling back to software volume control.");
+ return -1;
+ }
+
+ for (i = 0; i < channel_map->channels; i++) {
+ snd_mixer_selem_channel_id_t id;
+ pa_bool_t is_mono;
+
+ is_mono = channel_map->map[i] == PA_CHANNEL_POSITION_MONO;
+ id = alsa_channel_ids[channel_map->map[i]];
+
+ if (!is_mono && id == SND_MIXER_SCHN_UNKNOWN) {
+ pa_log_info("Configured channel map contains channel '%s' that is unknown to the ALSA mixer. Falling back to software volume control.", pa_channel_position_to_string(channel_map->map[i]));
+ return -1;
+ }
+
+ if ((is_mono && mono_used) || (!is_mono && alsa_channel_used[id])) {
+ pa_log_info("Channel map has duplicate channel '%s', failling back to software volume control.", pa_channel_position_to_string(channel_map->map[i]));
+ return -1;
+ }
+
+ if ((playback && (!snd_mixer_selem_has_playback_channel(elem, id) || (is_mono && !snd_mixer_selem_is_playback_mono(elem)))) ||
+ (!playback && (!snd_mixer_selem_has_capture_channel(elem, id) || (is_mono && !snd_mixer_selem_is_capture_mono(elem))))) {
+
+ pa_log_info("ALSA device lacks separate volumes control for channel '%s', falling back to software volume control.", pa_channel_position_to_string(channel_map->map[i]));
+ return -1;
+ }
+
+ if (is_mono) {
+ mixer_map[i] = SND_MIXER_SCHN_MONO;
+ mono_used = TRUE;
+ } else {
+ mixer_map[i] = id;
+ alsa_channel_used[id] = TRUE;
+ }
+ }
+
+ pa_log_info("All %u channels can be mapped to mixer channels. Using hardware volume control.", channel_map->channels);
+
+ return 0;
+}
diff --git a/src/modules/alsa-util.h b/src/modules/alsa-util.h
new file mode 100644
index 00000000..53d9a2fb
--- /dev/null
+++ b/src/modules/alsa-util.h
@@ -0,0 +1,76 @@
+#ifndef fooalsautilhfoo
+#define fooalsautilhfoo
+
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <asoundlib.h>
+
+#include <pulse/sample.h>
+#include <pulse/mainloop-api.h>
+
+#include <pulse/channelmap.h>
+
+typedef struct pa_alsa_fdlist pa_alsa_fdlist;
+
+struct pa_alsa_fdlist *pa_alsa_fdlist_new(void);
+void pa_alsa_fdlist_free(struct pa_alsa_fdlist *fdl);
+int pa_alsa_fdlist_set_mixer(struct pa_alsa_fdlist *fdl, snd_mixer_t *mixer_handle, pa_mainloop_api* m);
+
+int pa_alsa_set_hw_params(
+ snd_pcm_t *pcm_handle,
+ pa_sample_spec *ss,
+ uint32_t *periods,
+ snd_pcm_uframes_t *period_size,
+ pa_bool_t *use_mmap,
+ pa_bool_t require_exact_channel_number);
+
+int pa_alsa_set_sw_params(snd_pcm_t *pcm);
+
+int pa_alsa_prepare_mixer(snd_mixer_t *mixer, const char *dev);
+snd_mixer_elem_t *pa_alsa_find_elem(snd_mixer_t *mixer, const char *name, const char *fallback);
+
+snd_pcm_t *pa_alsa_open_by_device_id(
+ const char *dev_id,
+ char **dev,
+ pa_sample_spec *ss,
+ pa_channel_map* map,
+ int mode,
+ uint32_t *nfrags,
+ snd_pcm_uframes_t *period_size,
+ pa_bool_t *use_mmap);
+
+snd_pcm_t *pa_alsa_open_by_device_string(
+ const char *device,
+ char **dev,
+ pa_sample_spec *ss,
+ pa_channel_map* map,
+ int mode,
+ uint32_t *nfrags,
+ snd_pcm_uframes_t *period_size,
+ pa_bool_t *use_mmap);
+
+int pa_alsa_calc_mixer_map(snd_mixer_elem_t *elem, const pa_channel_map *channel_map, snd_mixer_selem_channel_id_t mixer_map[], pa_bool_t playback);
+
+#endif
diff --git a/src/modules/bt-proximity-helper.c b/src/modules/bt-proximity-helper.c
new file mode 100644
index 00000000..d80cc0c1
--- /dev/null
+++ b/src/modules/bt-proximity-helper.c
@@ -0,0 +1,210 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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.
+***/
+
+/*
+ * Small SUID helper that allows us to ping a BT device. Borrows
+ * heavily from bluez-utils' l2ping, which is licensed as GPL2+, too
+ * and comes with a copyright like this:
+ *
+ * Copyright (C) 2000-2001 Qualcomm Incorporated
+ * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com>
+ * Copyright (C) 2002-2007 Marcel Holtmann <marcel@holtmann.org>
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#undef NDEBUG
+
+#include <assert.h>
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/time.h>
+#include <sys/select.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/l2cap.h>
+
+#define PING_STRING "PulseAudio"
+#define IDENT 200
+#define TIMEOUT 4
+#define INTERVAL 2
+
+static void update_status(int found) {
+ static int status = -1;
+
+ if (!found && status != 0)
+ printf("-");
+ if (found && status <= 0)
+ printf("+");
+
+ fflush(stdout);
+ status = !!found;
+}
+
+int main(int argc, char *argv[]) {
+ struct sockaddr_l2 addr;
+ union {
+ l2cap_cmd_hdr hdr;
+ uint8_t buf[L2CAP_CMD_HDR_SIZE + sizeof(PING_STRING)];
+ } packet;
+ int fd = -1;
+ uint8_t id = IDENT;
+ int connected = 0;
+
+ assert(argc == 2);
+
+ for (;;) {
+ fd_set fds;
+ struct timeval end;
+ ssize_t r;
+
+ if (!connected) {
+
+ if (fd >= 0)
+ close(fd);
+
+ if ((fd = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_L2CAP)) < 0) {
+ fprintf(stderr, "socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_L2CAP) failed: %s", strerror(errno));
+ goto finish;
+ }
+
+ memset(&addr, 0, sizeof(addr));
+ addr.l2_family = AF_BLUETOOTH;
+ bacpy(&addr.l2_bdaddr, BDADDR_ANY);
+
+ if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+ fprintf(stderr, "bind() failed: %s", strerror(errno));
+ goto finish;
+ }
+
+ memset(&addr, 0, sizeof(addr));
+ addr.l2_family = AF_BLUETOOTH;
+ str2ba(argv[1], &addr.l2_bdaddr);
+
+ if (connect(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+
+ if (errno == EHOSTDOWN || errno == ECONNRESET || errno == ETIMEDOUT) {
+ update_status(0);
+ sleep(INTERVAL);
+ continue;
+ }
+
+ fprintf(stderr, "connect() failed: %s", strerror(errno));
+ goto finish;
+ }
+
+ connected = 1;
+ }
+
+ assert(connected);
+
+ memset(&packet, 0, sizeof(packet));
+ strcpy((char*) packet.buf + L2CAP_CMD_HDR_SIZE, PING_STRING);
+ packet.hdr.ident = id;
+ packet.hdr.len = htobs(sizeof(PING_STRING));
+ packet.hdr.code = L2CAP_ECHO_REQ;
+
+ if ((r = send(fd, &packet, sizeof(packet), 0)) < 0) {
+
+ if (errno == EHOSTDOWN || errno == ECONNRESET || errno == ETIMEDOUT) {
+ update_status(0);
+ connected = 0;
+ sleep(INTERVAL);
+ continue;
+ }
+
+ fprintf(stderr, "send() failed: %s", strerror(errno));
+ goto finish;
+ }
+
+ assert(r == sizeof(packet));
+
+ gettimeofday(&end, NULL);
+ end.tv_sec += TIMEOUT;
+
+ for (;;) {
+ struct timeval now, delta;
+
+ gettimeofday(&now, NULL);
+
+ if (timercmp(&end, &now, <=)) {
+ update_status(0);
+ connected = 0;
+ sleep(INTERVAL);
+ break;
+ }
+
+ timersub(&end, &now, &delta);
+
+ FD_ZERO(&fds);
+ FD_SET(fd, &fds);
+
+ if (select(fd+1, &fds, NULL, NULL, &delta) < 0) {
+ fprintf(stderr, "select() failed: %s", strerror(errno));
+ goto finish;
+ }
+
+ if ((r = recv(fd, &packet, sizeof(packet), 0)) <= 0) {
+
+ if (errno == EHOSTDOWN || errno == ECONNRESET || errno == ETIMEDOUT) {
+ update_status(0);
+ connected = 0;
+ sleep(INTERVAL);
+ break;
+ }
+
+ fprintf(stderr, "send() failed: %s", r == 0 ? "EOF" : strerror(errno));
+ goto finish;
+ }
+
+ assert(r >= L2CAP_CMD_HDR_SIZE);
+
+ if (packet.hdr.ident != id)
+ continue;
+
+ if (packet.hdr.code == L2CAP_ECHO_RSP || packet.hdr.code == L2CAP_COMMAND_REJ) {
+
+ if (++id >= 0xFF)
+ id = IDENT;
+
+ update_status(1);
+ sleep(INTERVAL);
+ break;
+ }
+ }
+ }
+
+finish:
+
+ if (fd >= 0)
+ close(fd);
+
+ return 1;
+}
diff --git a/src/modules/dbus-util.c b/src/modules/dbus-util.c
new file mode 100644
index 00000000..fc1e91ea
--- /dev/null
+++ b/src/modules/dbus-util.c
@@ -0,0 +1,329 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulse/xmalloc.h>
+#include <pulse/timeval.h>
+#include <pulsecore/log.h>
+#include <pulsecore/props.h>
+
+#include "dbus-util.h"
+
+struct pa_dbus_connection {
+ PA_REFCNT_DECLARE;
+
+ pa_core *core;
+ DBusConnection *connection;
+ const char *property_name;
+ pa_defer_event* dispatch_event;
+};
+
+static void dispatch_cb(pa_mainloop_api *ea, pa_defer_event *ev, void *userdata) {
+ DBusConnection *conn = userdata;
+
+ if (dbus_connection_dispatch(conn) == DBUS_DISPATCH_COMPLETE) {
+ /* no more data to process, disable the deferred */
+ ea->defer_enable(ev, 0);
+ }
+}
+
+/* DBusDispatchStatusFunction callback for the pa mainloop */
+static void dispatch_status(DBusConnection *conn, DBusDispatchStatus status, void *userdata) {
+ pa_dbus_connection *c = userdata;
+
+ pa_assert(c);
+
+ switch(status) {
+
+ case DBUS_DISPATCH_COMPLETE:
+ c->core->mainloop->defer_enable(c->dispatch_event, 0);
+ break;
+
+ case DBUS_DISPATCH_DATA_REMAINS:
+ case DBUS_DISPATCH_NEED_MEMORY:
+ default:
+ c->core->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_GCC_UNUSED pa_mainloop_api *ea, pa_io_event *e, int fd, pa_io_event_flags_t events, void *userdata) {
+ unsigned int flags = 0;
+ DBusWatch *watch = userdata;
+
+#if HAVE_DBUS_WATCH_GET_UNIX_FD
+ pa_assert(fd == dbus_watch_get_unix_fd(watch));
+#else
+ pa_assert(fd == dbus_watch_get_fd(watch));
+#endif
+
+ if (!dbus_watch_get_enabled(watch)) {
+ pa_log_warn("Asked to handle disabled watch: %p %i", (void*) watch, fd);
+ return;
+ }
+
+ if (events & PA_IO_EVENT_INPUT)
+ flags |= DBUS_WATCH_READABLE;
+ if (events & PA_IO_EVENT_OUTPUT)
+ flags |= DBUS_WATCH_WRITABLE;
+ if (events & PA_IO_EVENT_HANGUP)
+ flags |= DBUS_WATCH_HANGUP;
+ if (events & PA_IO_EVENT_ERROR)
+ flags |= DBUS_WATCH_ERROR;
+
+ dbus_watch_handle(watch, flags);
+}
+
+/* pa_time_event_cb_t timer event handler */
+static void handle_time_event(pa_mainloop_api *ea, pa_time_event* e, const struct timeval *tv, void *userdata) {
+ DBusTimeout *timeout = userdata;
+
+ if (dbus_timeout_get_enabled(timeout)) {
+ struct timeval next = *tv;
+ dbus_timeout_handle(timeout);
+
+ /* restart it for the next scheduled time */
+ pa_timeval_add(&next, dbus_timeout_get_interval(timeout) * 1000);
+ ea->time_restart(e, &next);
+ }
+}
+
+/* DBusAddWatchFunction callback for pa mainloop */
+static dbus_bool_t add_watch(DBusWatch *watch, void *data) {
+ pa_core *c = PA_CORE(data);
+ pa_io_event *ev;
+
+ pa_assert(watch);
+ pa_assert(c);
+
+ ev = c->mainloop->io_new(
+ c->mainloop,
+#if HAVE_DBUS_WATCH_GET_UNIX_FD
+ dbus_watch_get_unix_fd(watch),
+#else
+ dbus_watch_get_fd(watch),
+#endif
+ get_watch_flags(watch), handle_io_event, watch);
+
+ dbus_watch_set_data(watch, ev, NULL);
+
+ return TRUE;
+}
+
+/* DBusRemoveWatchFunction callback for pa mainloop */
+static void remove_watch(DBusWatch *watch, void *data) {
+ pa_core *c = PA_CORE(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_core *c = PA_CORE(data);
+ pa_io_event *ev;
+
+ pa_assert(watch);
+ pa_core_assert_ref(c);
+
+ pa_assert_se(ev = dbus_watch_get_data(watch));
+
+ /* get_watch_flags() checks if the watch is enabled */
+ c->mainloop->io_enable(ev, get_watch_flags(watch));
+}
+
+/* DBusAddTimeoutFunction callback for pa mainloop */
+static dbus_bool_t add_timeout(DBusTimeout *timeout, void *data) {
+ pa_core *c = PA_CORE(data);
+ pa_time_event *ev;
+ struct timeval tv;
+
+ pa_assert(timeout);
+ pa_assert(c);
+
+ if (!dbus_timeout_get_enabled(timeout))
+ return FALSE;
+
+ pa_gettimeofday(&tv);
+ pa_timeval_add(&tv, dbus_timeout_get_interval(timeout) * 1000);
+
+ ev = c->mainloop->time_new(c->mainloop, &tv, handle_time_event, timeout);
+
+ dbus_timeout_set_data(timeout, ev, NULL);
+
+ return TRUE;
+}
+
+/* DBusRemoveTimeoutFunction callback for pa mainloop */
+static void remove_timeout(DBusTimeout *timeout, void *data) {
+ pa_core *c = PA_CORE(data);
+ pa_time_event *ev;
+
+ pa_assert(timeout);
+ pa_assert(c);
+
+ if ((ev = dbus_timeout_get_data(timeout)))
+ c->mainloop->time_free(ev);
+}
+
+/* DBusTimeoutToggledFunction callback for pa mainloop */
+static void toggle_timeout(DBusTimeout *timeout, void *data) {
+ pa_core *c = PA_CORE(data);
+ pa_time_event *ev;
+
+ pa_assert(timeout);
+ pa_assert(c);
+
+ pa_assert_se(ev = dbus_timeout_get_data(timeout));
+
+ if (dbus_timeout_get_enabled(timeout)) {
+ struct timeval tv;
+
+ pa_gettimeofday(&tv);
+ pa_timeval_add(&tv, dbus_timeout_get_interval(timeout) * 1000);
+
+ c->mainloop->time_restart(ev, &tv);
+ } else
+ c->mainloop->time_restart(ev, NULL);
+}
+
+static void wakeup_main(void *userdata) {
+ pa_dbus_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->core->mainloop->defer_enable(c->dispatch_event, 1);
+}
+
+static pa_dbus_connection* pa_dbus_connection_new(pa_core* c, DBusConnection *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;
+ pconn->dispatch_event = c->mainloop->defer_new(c->mainloop, dispatch_cb, conn);
+
+ pa_property_set(c, name, pconn);
+
+ return pconn;
+}
+
+DBusConnection* pa_dbus_connection_get(pa_dbus_connection *c){
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) > 0);
+ pa_assert(c->connection);
+
+ return 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;
+
+ 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));
+ }
+
+ /* already disconnected, just free */
+ pa_property_remove(c->core, c->property_name);
+ c->core->mainloop->defer_free(c->dispatch_event);
+ dbus_connection_unref(c->connection);
+ 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;
+}
+
+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"
+ };
+ DBusConnection *conn;
+ pa_dbus_connection *pconn;
+
+ pa_assert(type == DBUS_BUS_SYSTEM || type == DBUS_BUS_SESSION || type == DBUS_BUS_STARTER);
+
+ if ((pconn = pa_property_get(c, prop_name[type])))
+ return pa_dbus_connection_ref(pconn);
+
+ if (!(conn = dbus_bus_get_private(type, error)))
+ return NULL;
+
+ pconn = pa_dbus_connection_new(c, conn, prop_name[type]);
+
+ 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, c, NULL);
+ dbus_connection_set_timeout_functions(conn, add_timeout, remove_timeout, toggle_timeout, c, NULL);
+ dbus_connection_set_wakeup_main_function(conn, wakeup_main, pconn, NULL);
+
+ return pconn;
+}
diff --git a/src/modules/dbus-util.h b/src/modules/dbus-util.h
new file mode 100644
index 00000000..8dca54fe
--- /dev/null
+++ b/src/modules/dbus-util.h
@@ -0,0 +1,40 @@
+#ifndef foodbusutilhfoo
+#define foodbusutilhfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <dbus/dbus.h>
+
+typedef struct pa_dbus_connection pa_dbus_connection;
+
+/* return the DBusConnection 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/modules/gconf/Makefile b/src/modules/gconf/Makefile
new file mode 100644
index 00000000..316beb72
--- /dev/null
+++ b/src/modules/gconf/Makefile
@@ -0,0 +1,13 @@
+# This is a dirty trick just to ease compilation with emacs
+#
+# This file is not intended to be distributed or anything
+#
+# So: don't touch it, even better ignore it!
+
+all:
+ $(MAKE) -C ../..
+
+clean:
+ $(MAKE) -C ../.. clean
+
+.PHONY: all clean
diff --git a/src/modules/gconf/gconf-helper.c b/src/modules/gconf/gconf-helper.c
new file mode 100644
index 00000000..abd13287
--- /dev/null
+++ b/src/modules/gconf/gconf-helper.c
@@ -0,0 +1,135 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <gconf/gconf-client.h>
+#include <glib.h>
+
+#include <pulsecore/core-util.h>
+
+#define PA_GCONF_ROOT "/system/pulseaudio"
+#define PA_GCONF_PATH_MODULES PA_GCONF_ROOT"/modules"
+
+static void handle_module(GConfClient *client, const char *name) {
+ gchar p[1024];
+ gboolean enabled, locked;
+ int i;
+
+ pa_snprintf(p, sizeof(p), PA_GCONF_PATH_MODULES"/%s/locked", name);
+ locked = gconf_client_get_bool(client, p, FALSE);
+
+ if (locked)
+ return;
+
+ pa_snprintf(p, sizeof(p), PA_GCONF_PATH_MODULES"/%s/enabled", name);
+ enabled = gconf_client_get_bool(client, p, FALSE);
+
+ printf("%c%s%c", enabled ? '+' : '-', name, 0);
+
+ if (enabled) {
+
+ for (i = 0; i < 10; i++) {
+ gchar *n, *a;
+
+ pa_snprintf(p, sizeof(p), PA_GCONF_PATH_MODULES"/%s/name%i", name, i);
+ if (!(n = gconf_client_get_string(client, p, NULL)) || !*n)
+ break;
+
+ pa_snprintf(p, sizeof(p), PA_GCONF_PATH_MODULES"/%s/args%i", name, i);
+ a = gconf_client_get_string(client, p, NULL);
+
+ printf("%s%c%s%c", n, 0, a ? a : "", 0);
+
+ g_free(n);
+ g_free(a);
+ }
+
+ printf("%c", 0);
+ }
+
+ fflush(stdout);
+}
+
+static void modules_callback(
+ GConfClient* client,
+ guint cnxn_id,
+ GConfEntry *entry,
+ gpointer user_data) {
+
+ const char *n;
+ char buf[128];
+
+ g_assert(strncmp(entry->key, PA_GCONF_PATH_MODULES"/", sizeof(PA_GCONF_PATH_MODULES)) == 0);
+
+ n = entry->key + sizeof(PA_GCONF_PATH_MODULES);
+
+ g_strlcpy(buf, n, sizeof(buf));
+ buf[strcspn(buf, "/")] = 0;
+
+ handle_module(client, buf);
+}
+
+int main(int argc, char *argv[]) {
+ GMainLoop *g;
+ GConfClient *client;
+ GSList *modules, *m;
+
+ g_type_init();
+
+ if (!(client = gconf_client_get_default()))
+ goto fail;
+
+ gconf_client_add_dir(client, PA_GCONF_ROOT, GCONF_CLIENT_PRELOAD_RECURSIVE, NULL);
+ gconf_client_notify_add(client, PA_GCONF_PATH_MODULES, modules_callback, NULL, NULL, NULL);
+
+ modules = gconf_client_all_dirs(client, PA_GCONF_PATH_MODULES, NULL);
+
+ for (m = modules; m; m = m->next) {
+ char *e = strrchr(m->data, '/');
+ handle_module(client, e ? e+1 : m->data);
+ }
+
+ g_slist_free(modules);
+
+ /* Signal the parent that we are now initialized */
+ printf("!");
+ fflush(stdout);
+
+ g = g_main_loop_new(NULL, FALSE);
+ g_main_loop_run(g);
+ g_main_loop_unref(g);
+
+ g_object_unref(G_OBJECT(client));
+
+ return 0;
+
+fail:
+ return 1;
+}
diff --git a/src/modules/gconf/module-gconf.c b/src/modules/gconf/module-gconf.c
new file mode 100644
index 00000000..836157d0
--- /dev/null
+++ b/src/modules/gconf/module-gconf.c
@@ -0,0 +1,397 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <fcntl.h>
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/module.h>
+#include <pulsecore/core.h>
+#include <pulsecore/llist.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/log.h>
+#include <pulse/mainloop-api.h>
+#include <pulsecore/core-error.h>
+#include <pulsecore/start-child.h>
+
+#include "module-gconf-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering");
+PA_MODULE_DESCRIPTION("GConf Adapter");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(TRUE);
+
+#define MAX_MODULES 10
+#define BUF_MAX 2048
+
+/* #undef PA_GCONF_HELPER */
+/* #define PA_GCONF_HELPER "/home/lennart/projects/pulseaudio/src/gconf-helper" */
+
+struct module_item {
+ char *name;
+ char *args;
+ uint32_t index;
+};
+
+struct module_info {
+ char *name;
+
+ struct module_item items[MAX_MODULES];
+ unsigned n_items;
+};
+
+struct userdata {
+ pa_core *core;
+ pa_module *module;
+
+ pa_hashmap *module_infos;
+
+ pid_t pid;
+
+ int fd;
+ int fd_type;
+ pa_io_event *io_event;
+
+ char buf[BUF_MAX];
+ size_t buf_fill;
+};
+
+static int fill_buf(struct userdata *u) {
+ ssize_t r;
+ pa_assert(u);
+
+ if (u->buf_fill >= BUF_MAX) {
+ pa_log("read buffer overflow");
+ return -1;
+ }
+
+ if ((r = pa_read(u->fd, u->buf + u->buf_fill, BUF_MAX - u->buf_fill, &u->fd_type)) <= 0)
+ return -1;
+
+ u->buf_fill += r;
+ return 0;
+}
+
+static int read_byte(struct userdata *u) {
+ int ret;
+ pa_assert(u);
+
+ if (u->buf_fill < 1)
+ if (fill_buf(u) < 0)
+ return -1;
+
+ ret = u->buf[0];
+ pa_assert(u->buf_fill > 0);
+ u->buf_fill--;
+ memmove(u->buf, u->buf+1, u->buf_fill);
+ return ret;
+}
+
+static char *read_string(struct userdata *u) {
+ pa_assert(u);
+
+ for (;;) {
+ char *e;
+
+ if ((e = memchr(u->buf, 0, u->buf_fill))) {
+ char *ret = pa_xstrdup(u->buf);
+ u->buf_fill -= e - u->buf +1;
+ memmove(u->buf, e+1, u->buf_fill);
+ return ret;
+ }
+
+ if (fill_buf(u) < 0)
+ return NULL;
+ }
+}
+
+static void unload_one_module(struct userdata *u, struct module_info*m, unsigned i) {
+ pa_assert(u);
+ pa_assert(m);
+ pa_assert(i < m->n_items);
+
+ if (m->items[i].index == PA_INVALID_INDEX)
+ return;
+
+ pa_log_debug("Unloading module #%i", m->items[i].index);
+ pa_module_unload_by_index(u->core, m->items[i].index);
+ m->items[i].index = PA_INVALID_INDEX;
+ pa_xfree(m->items[i].name);
+ pa_xfree(m->items[i].args);
+ m->items[i].name = m->items[i].args = NULL;
+}
+
+static void unload_all_modules(struct userdata *u, struct module_info*m) {
+ unsigned i;
+
+ pa_assert(u);
+ pa_assert(m);
+
+ for (i = 0; i < m->n_items; i++)
+ unload_one_module(u, m, i);
+
+ m->n_items = 0;
+}
+
+static void load_module(
+ struct userdata *u,
+ struct module_info *m,
+ int i,
+ const char *name,
+ const char *args,
+ int is_new) {
+
+ pa_module *mod;
+
+ pa_assert(u);
+ pa_assert(m);
+ pa_assert(name);
+ pa_assert(args);
+
+ if (!is_new) {
+ if (m->items[i].index != PA_INVALID_INDEX &&
+ strcmp(m->items[i].name, name) == 0 &&
+ strcmp(m->items[i].args, args) == 0)
+ return;
+
+ unload_one_module(u, m, i);
+ }
+
+ pa_log_debug("Loading module '%s' with args '%s' due to GConf configuration.", name, args);
+
+ m->items[i].name = pa_xstrdup(name);
+ m->items[i].args = pa_xstrdup(args);
+ m->items[i].index = PA_INVALID_INDEX;
+
+ if (!(mod = pa_module_load(u->core, name, args))) {
+ pa_log("pa_module_load() failed");
+ return;
+ }
+
+ m->items[i].index = mod->index;
+}
+
+static void module_info_free(void *p, void *userdata) {
+ struct module_info *m = p;
+ struct userdata *u = userdata;
+
+ pa_assert(m);
+ pa_assert(u);
+
+ unload_all_modules(u, m);
+ pa_xfree(m->name);
+ pa_xfree(m);
+}
+
+static int handle_event(struct userdata *u) {
+ int opcode;
+ int ret = 0;
+
+ do {
+ if ((opcode = read_byte(u)) < 0){
+ if (errno == EINTR || errno == EAGAIN)
+ break;
+ goto fail;
+ }
+
+ switch (opcode) {
+ case '!':
+ /* The helper tool is now initialized */
+ ret = 1;
+ break;
+
+ case '+': {
+ char *name;
+ struct module_info *m;
+ unsigned i, j;
+
+ if (!(name = read_string(u)))
+ goto fail;
+
+ if (!(m = pa_hashmap_get(u->module_infos, name))) {
+ m = pa_xnew(struct module_info, 1);
+ m->name = name;
+ m->n_items = 0;
+ pa_hashmap_put(u->module_infos, m->name, m);
+ } else
+ pa_xfree(name);
+
+ i = 0;
+ while (i < MAX_MODULES) {
+ char *module, *args;
+
+ if (!(module = read_string(u))) {
+ if (i > m->n_items) m->n_items = i;
+ goto fail;
+ }
+
+ if (!*module) {
+ pa_xfree(module);
+ break;
+ }
+
+ if (!(args = read_string(u))) {
+ pa_xfree(module);
+
+ if (i > m->n_items) m->n_items = i;
+ goto fail;
+ }
+
+ load_module(u, m, i, module, args, i >= m->n_items);
+
+ i++;
+
+ pa_xfree(module);
+ pa_xfree(args);
+ }
+
+ /* Unload all removed modules */
+ for (j = i; j < m->n_items; j++)
+ unload_one_module(u, m, j);
+
+ m->n_items = i;
+
+ break;
+ }
+
+ case '-': {
+ char *name;
+ struct module_info *m;
+
+ if (!(name = read_string(u)))
+ goto fail;
+
+ if ((m = pa_hashmap_get(u->module_infos, name))) {
+ pa_hashmap_remove(u->module_infos, name);
+ module_info_free(m, u);
+ }
+
+ pa_xfree(name);
+
+ break;
+ }
+ }
+ } while (u->buf_fill > 0 && ret == 0);
+
+ return ret;
+
+fail:
+ pa_log("Unable to read or parse data from client.");
+ return -1;
+}
+
+static void io_event_cb(
+ pa_mainloop_api*a,
+ pa_io_event* e,
+ int fd,
+ pa_io_event_flags_t events,
+ void *userdata) {
+
+ struct userdata *u = userdata;
+
+ if (handle_event(u) < 0) {
+
+ if (u->io_event) {
+ u->core->mainloop->io_free(u->io_event);
+ u->io_event = NULL;
+ }
+
+ pa_module_unload_request(u->module);
+ }
+}
+
+int pa__init(pa_module*m) {
+ struct userdata *u;
+ int r;
+
+ u = pa_xnew(struct userdata, 1);
+ u->core = m->core;
+ u->module = m;
+ m->userdata = u;
+ u->module_infos = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+ u->pid = (pid_t) -1;
+ u->fd = -1;
+ u->fd_type = 0;
+ u->io_event = NULL;
+ u->buf_fill = 0;
+
+ if ((u->fd = pa_start_child_for_read(PA_GCONF_HELPER, NULL, &u->pid)) < 0)
+ goto fail;
+
+ u->io_event = m->core->mainloop->io_new(
+ m->core->mainloop,
+ u->fd,
+ PA_IO_EVENT_INPUT,
+ io_event_cb,
+ u);
+
+ do {
+ if ((r = handle_event(u)) < 0)
+ goto fail;
+
+ /* Read until the client signalled us that it is ready with
+ * initialization */
+ } while (r != 1);
+
+ return 0;
+
+fail:
+ pa__done(m);
+ return -1;
+}
+
+void pa__done(pa_module*m) {
+ struct userdata *u;
+
+ pa_assert(m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->pid != (pid_t) -1) {
+ kill(u->pid, SIGTERM);
+ waitpid(u->pid, NULL, 0);
+ }
+
+ if (u->io_event)
+ m->core->mainloop->io_free(u->io_event);
+
+ if (u->fd >= 0)
+ pa_close(u->fd);
+
+
+ if (u->module_infos)
+ pa_hashmap_free(u->module_infos, module_info_free, u);
+
+ pa_xfree(u);
+}
diff --git a/src/modules/ladspa.h b/src/modules/ladspa.h
new file mode 100644
index 00000000..b1a9c4e5
--- /dev/null
+++ b/src/modules/ladspa.h
@@ -0,0 +1,603 @@
+/* ladspa.h
+
+ Linux Audio Developer's Simple Plugin API Version 1.1[LGPL].
+ Copyright (C) 2000-2002 Richard W.E. Furse, Paul Barton-Davis,
+ Stefan Westerfeld.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public License
+ as published by the Free Software Foundation; either version 2.1 of
+ the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA. */
+
+#ifndef LADSPA_INCLUDED
+#define LADSPA_INCLUDED
+
+#define LADSPA_VERSION "1.1"
+#define LADSPA_VERSION_MAJOR 1
+#define LADSPA_VERSION_MINOR 1
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*****************************************************************************/
+
+/* Overview:
+
+ There is a large number of synthesis packages in use or development
+ on the Linux platform at this time. This API (`The Linux Audio
+ Developer's Simple Plugin API') attempts to give programmers the
+ ability to write simple `plugin' audio processors in C/C++ and link
+ them dynamically (`plug') into a range of these packages (`hosts').
+ It should be possible for any host and any plugin to communicate
+ completely through this interface.
+
+ This API is deliberately short and simple. To achieve compatibility
+ with a range of promising Linux sound synthesis packages it
+ attempts to find the `greatest common divisor' in their logical
+ behaviour. Having said this, certain limiting decisions are
+ implicit, notably the use of a fixed type (LADSPA_Data) for all
+ data transfer and absence of a parameterised `initialisation'
+ phase. See below for the LADSPA_Data typedef.
+
+ Plugins are expected to distinguish between control and audio
+ data. Plugins have `ports' that are inputs or outputs for audio or
+ control data and each plugin is `run' for a `block' corresponding
+ to a short time interval measured in samples. Audio data is
+ communicated using arrays of LADSPA_Data, allowing a block of audio
+ to be processed by the plugin in a single pass. Control data is
+ communicated using single LADSPA_Data values. Control data has a
+ single value at the start of a call to the `run()' or `run_adding()'
+ function, and may be considered to remain this value for its
+ duration. The plugin may assume that all its input and output ports
+ have been connected to the relevant data location (see the
+ `connect_port()' function below) before it is asked to run.
+
+ Plugins will reside in shared object files suitable for dynamic
+ linking by dlopen() and family. The file will provide a number of
+ `plugin types' that can be used to instantiate actual plugins
+ (sometimes known as `plugin instances') that can be connected
+ together to perform tasks.
+
+ This API contains very limited error-handling. */
+
+/*****************************************************************************/
+
+/* Fundamental data type passed in and out of plugin. This data type
+ is used to communicate audio samples and control values. It is
+ assumed that the plugin will work sensibly given any numeric input
+ value although it may have a preferred range (see hints below).
+
+ For audio it is generally assumed that 1.0f is the `0dB' reference
+ amplitude and is a `normal' signal level. */
+
+typedef float LADSPA_Data;
+
+/*****************************************************************************/
+
+/* Special Plugin Properties:
+
+ Optional features of the plugin type are encapsulated in the
+ LADSPA_Properties type. This is assembled by ORing individual
+ properties together. */
+
+typedef int LADSPA_Properties;
+
+/* Property LADSPA_PROPERTY_REALTIME indicates that the plugin has a
+ real-time dependency (e.g. listens to a MIDI device) and so its
+ output must not be cached or subject to significant latency. */
+#define LADSPA_PROPERTY_REALTIME 0x1
+
+/* Property LADSPA_PROPERTY_INPLACE_BROKEN indicates that the plugin
+ may cease to work correctly if the host elects to use the same data
+ location for both input and output (see connect_port()). This
+ should be avoided as enabling this flag makes it impossible for
+ hosts to use the plugin to process audio `in-place.' */
+#define LADSPA_PROPERTY_INPLACE_BROKEN 0x2
+
+/* Property LADSPA_PROPERTY_HARD_RT_CAPABLE indicates that the plugin
+ is capable of running not only in a conventional host but also in a
+ `hard real-time' environment. To qualify for this the plugin must
+ satisfy all of the following:
+
+ (1) The plugin must not use malloc(), free() or other heap memory
+ management within its run() or run_adding() functions. All new
+ memory used in run() must be managed via the stack. These
+ restrictions only apply to the run() function.
+
+ (2) The plugin will not attempt to make use of any library
+ functions with the exceptions of functions in the ANSI standard C
+ and C maths libraries, which the host is expected to provide.
+
+ (3) The plugin will not access files, devices, pipes, sockets, IPC
+ or any other mechanism that might result in process or thread
+ blocking.
+
+ (4) The plugin will take an amount of time to execute a run() or
+ run_adding() call approximately of form (A+B*SampleCount) where A
+ and B depend on the machine and host in use. This amount of time
+ may not depend on input signals or plugin state. The host is left
+ the responsibility to perform timings to estimate upper bounds for
+ A and B. */
+#define LADSPA_PROPERTY_HARD_RT_CAPABLE 0x4
+
+#define LADSPA_IS_REALTIME(x) ((x) & LADSPA_PROPERTY_REALTIME)
+#define LADSPA_IS_INPLACE_BROKEN(x) ((x) & LADSPA_PROPERTY_INPLACE_BROKEN)
+#define LADSPA_IS_HARD_RT_CAPABLE(x) ((x) & LADSPA_PROPERTY_HARD_RT_CAPABLE)
+
+/*****************************************************************************/
+
+/* Plugin Ports:
+
+ Plugins have `ports' that are inputs or outputs for audio or
+ data. Ports can communicate arrays of LADSPA_Data (for audio
+ inputs/outputs) or single LADSPA_Data values (for control
+ input/outputs). This information is encapsulated in the
+ LADSPA_PortDescriptor type which is assembled by ORing individual
+ properties together.
+
+ Note that a port must be an input or an output port but not both
+ and that a port must be a control or audio port but not both. */
+
+typedef int LADSPA_PortDescriptor;
+
+/* Property LADSPA_PORT_INPUT indicates that the port is an input. */
+#define LADSPA_PORT_INPUT 0x1
+
+/* Property LADSPA_PORT_OUTPUT indicates that the port is an output. */
+#define LADSPA_PORT_OUTPUT 0x2
+
+/* Property LADSPA_PORT_CONTROL indicates that the port is a control
+ port. */
+#define LADSPA_PORT_CONTROL 0x4
+
+/* Property LADSPA_PORT_AUDIO indicates that the port is a audio
+ port. */
+#define LADSPA_PORT_AUDIO 0x8
+
+#define LADSPA_IS_PORT_INPUT(x) ((x) & LADSPA_PORT_INPUT)
+#define LADSPA_IS_PORT_OUTPUT(x) ((x) & LADSPA_PORT_OUTPUT)
+#define LADSPA_IS_PORT_CONTROL(x) ((x) & LADSPA_PORT_CONTROL)
+#define LADSPA_IS_PORT_AUDIO(x) ((x) & LADSPA_PORT_AUDIO)
+
+/*****************************************************************************/
+
+/* Plugin Port Range Hints:
+
+ The host may wish to provide a representation of data entering or
+ leaving a plugin (e.g. to generate a GUI automatically). To make
+ this more meaningful, the plugin should provide `hints' to the host
+ describing the usual values taken by the data.
+
+ Note that these are only hints. The host may ignore them and the
+ plugin must not assume that data supplied to it is meaningful. If
+ the plugin receives invalid input data it is expected to continue
+ to run without failure and, where possible, produce a sensible
+ output (e.g. a high-pass filter given a negative cutoff frequency
+ might switch to an all-pass mode).
+
+ Hints are meaningful for all input and output ports but hints for
+ input control ports are expected to be particularly useful.
+
+ More hint information is encapsulated in the
+ LADSPA_PortRangeHintDescriptor type which is assembled by ORing
+ individual hint types together. Hints may require further
+ LowerBound and UpperBound information.
+
+ All the hint information for a particular port is aggregated in the
+ LADSPA_PortRangeHint structure. */
+
+typedef int LADSPA_PortRangeHintDescriptor;
+
+/* Hint LADSPA_HINT_BOUNDED_BELOW indicates that the LowerBound field
+ of the LADSPA_PortRangeHint should be considered meaningful. The
+ value in this field should be considered the (inclusive) lower
+ bound of the valid range. If LADSPA_HINT_SAMPLE_RATE is also
+ specified then the value of LowerBound should be multiplied by the
+ sample rate. */
+#define LADSPA_HINT_BOUNDED_BELOW 0x1
+
+/* Hint LADSPA_HINT_BOUNDED_ABOVE indicates that the UpperBound field
+ of the LADSPA_PortRangeHint should be considered meaningful. The
+ value in this field should be considered the (inclusive) upper
+ bound of the valid range. If LADSPA_HINT_SAMPLE_RATE is also
+ specified then the value of UpperBound should be multiplied by the
+ sample rate. */
+#define LADSPA_HINT_BOUNDED_ABOVE 0x2
+
+/* Hint LADSPA_HINT_TOGGLED indicates that the data item should be
+ considered a Boolean toggle. Data less than or equal to zero should
+ be considered `off' or `false,' and data above zero should be
+ considered `on' or `true.' LADSPA_HINT_TOGGLED may not be used in
+ conjunction with any other hint except LADSPA_HINT_DEFAULT_0 or
+ LADSPA_HINT_DEFAULT_1. */
+#define LADSPA_HINT_TOGGLED 0x4
+
+/* Hint LADSPA_HINT_SAMPLE_RATE indicates that any bounds specified
+ should be interpreted as multiples of the sample rate. For
+ instance, a frequency range from 0Hz to the Nyquist frequency (half
+ the sample rate) could be requested by this hint in conjunction
+ with LowerBound = 0 and UpperBound = 0.5. Hosts that support bounds
+ at all must support this hint to retain meaning. */
+#define LADSPA_HINT_SAMPLE_RATE 0x8
+
+/* Hint LADSPA_HINT_LOGARITHMIC indicates that it is likely that the
+ user will find it more intuitive to view values using a logarithmic
+ scale. This is particularly useful for frequencies and gains. */
+#define LADSPA_HINT_LOGARITHMIC 0x10
+
+/* Hint LADSPA_HINT_INTEGER indicates that a user interface would
+ probably wish to provide a stepped control taking only integer
+ values. Any bounds set should be slightly wider than the actual
+ integer range required to avoid floating point rounding errors. For
+ instance, the integer set {0,1,2,3} might be described as [-0.1,
+ 3.1]. */
+#define LADSPA_HINT_INTEGER 0x20
+
+/* The various LADSPA_HINT_HAS_DEFAULT_* hints indicate a `normal'
+ value for the port that is sensible as a default. For instance,
+ this value is suitable for use as an initial value in a user
+ interface or as a value the host might assign to a control port
+ when the user has not provided one. Defaults are encoded using a
+ mask so only one default may be specified for a port. Some of the
+ hints make use of lower and upper bounds, in which case the
+ relevant bound or bounds must be available and
+ LADSPA_HINT_SAMPLE_RATE must be applied as usual. The resulting
+ default must be rounded if LADSPA_HINT_INTEGER is present. Default
+ values were introduced in LADSPA v1.1. */
+#define LADSPA_HINT_DEFAULT_MASK 0x3C0
+
+/* This default values indicates that no default is provided. */
+#define LADSPA_HINT_DEFAULT_NONE 0x0
+
+/* This default hint indicates that the suggested lower bound for the
+ port should be used. */
+#define LADSPA_HINT_DEFAULT_MINIMUM 0x40
+
+/* This default hint indicates that a low value between the suggested
+ lower and upper bounds should be chosen. For ports with
+ LADSPA_HINT_LOGARITHMIC, this should be exp(log(lower) * 0.75 +
+ log(upper) * 0.25). Otherwise, this should be (lower * 0.75 + upper
+ * 0.25). */
+#define LADSPA_HINT_DEFAULT_LOW 0x80
+
+/* This default hint indicates that a middle value between the
+ suggested lower and upper bounds should be chosen. For ports with
+ LADSPA_HINT_LOGARITHMIC, this should be exp(log(lower) * 0.5 +
+ log(upper) * 0.5). Otherwise, this should be (lower * 0.5 + upper *
+ 0.5). */
+#define LADSPA_HINT_DEFAULT_MIDDLE 0xC0
+
+/* This default hint indicates that a high value between the suggested
+ lower and upper bounds should be chosen. For ports with
+ LADSPA_HINT_LOGARITHMIC, this should be exp(log(lower) * 0.25 +
+ log(upper) * 0.75). Otherwise, this should be (lower * 0.25 + upper
+ * 0.75). */
+#define LADSPA_HINT_DEFAULT_HIGH 0x100
+
+/* This default hint indicates that the suggested upper bound for the
+ port should be used. */
+#define LADSPA_HINT_DEFAULT_MAXIMUM 0x140
+
+/* This default hint indicates that the number 0 should be used. Note
+ that this default may be used in conjunction with
+ LADSPA_HINT_TOGGLED. */
+#define LADSPA_HINT_DEFAULT_0 0x200
+
+/* This default hint indicates that the number 1 should be used. Note
+ that this default may be used in conjunction with
+ LADSPA_HINT_TOGGLED. */
+#define LADSPA_HINT_DEFAULT_1 0x240
+
+/* This default hint indicates that the number 100 should be used. */
+#define LADSPA_HINT_DEFAULT_100 0x280
+
+/* This default hint indicates that the Hz frequency of `concert A'
+ should be used. This will be 440 unless the host uses an unusual
+ tuning convention, in which case it may be within a few Hz. */
+#define LADSPA_HINT_DEFAULT_440 0x2C0
+
+#define LADSPA_IS_HINT_BOUNDED_BELOW(x) ((x) & LADSPA_HINT_BOUNDED_BELOW)
+#define LADSPA_IS_HINT_BOUNDED_ABOVE(x) ((x) & LADSPA_HINT_BOUNDED_ABOVE)
+#define LADSPA_IS_HINT_TOGGLED(x) ((x) & LADSPA_HINT_TOGGLED)
+#define LADSPA_IS_HINT_SAMPLE_RATE(x) ((x) & LADSPA_HINT_SAMPLE_RATE)
+#define LADSPA_IS_HINT_LOGARITHMIC(x) ((x) & LADSPA_HINT_LOGARITHMIC)
+#define LADSPA_IS_HINT_INTEGER(x) ((x) & LADSPA_HINT_INTEGER)
+
+#define LADSPA_IS_HINT_HAS_DEFAULT(x) ((x) & LADSPA_HINT_DEFAULT_MASK)
+#define LADSPA_IS_HINT_DEFAULT_MINIMUM(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
+ == LADSPA_HINT_DEFAULT_MINIMUM)
+#define LADSPA_IS_HINT_DEFAULT_LOW(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
+ == LADSPA_HINT_DEFAULT_LOW)
+#define LADSPA_IS_HINT_DEFAULT_MIDDLE(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
+ == LADSPA_HINT_DEFAULT_MIDDLE)
+#define LADSPA_IS_HINT_DEFAULT_HIGH(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
+ == LADSPA_HINT_DEFAULT_HIGH)
+#define LADSPA_IS_HINT_DEFAULT_MAXIMUM(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
+ == LADSPA_HINT_DEFAULT_MAXIMUM)
+#define LADSPA_IS_HINT_DEFAULT_0(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
+ == LADSPA_HINT_DEFAULT_0)
+#define LADSPA_IS_HINT_DEFAULT_1(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
+ == LADSPA_HINT_DEFAULT_1)
+#define LADSPA_IS_HINT_DEFAULT_100(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
+ == LADSPA_HINT_DEFAULT_100)
+#define LADSPA_IS_HINT_DEFAULT_440(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
+ == LADSPA_HINT_DEFAULT_440)
+
+typedef struct _LADSPA_PortRangeHint {
+
+ /* Hints about the port. */
+ LADSPA_PortRangeHintDescriptor HintDescriptor;
+
+ /* Meaningful when hint LADSPA_HINT_BOUNDED_BELOW is active. When
+ LADSPA_HINT_SAMPLE_RATE is also active then this value should be
+ multiplied by the relevant sample rate. */
+ LADSPA_Data LowerBound;
+
+ /* Meaningful when hint LADSPA_HINT_BOUNDED_ABOVE is active. When
+ LADSPA_HINT_SAMPLE_RATE is also active then this value should be
+ multiplied by the relevant sample rate. */
+ LADSPA_Data UpperBound;
+
+} LADSPA_PortRangeHint;
+
+/*****************************************************************************/
+
+/* Plugin Handles:
+
+ This plugin handle indicates a particular instance of the plugin
+ concerned. It is valid to compare this to NULL (0 for C++) but
+ otherwise the host should not attempt to interpret it. The plugin
+ may use it to reference internal instance data. */
+
+typedef void * LADSPA_Handle;
+
+/*****************************************************************************/
+
+/* Descriptor for a Type of Plugin:
+
+ This structure is used to describe a plugin type. It provides a
+ number of functions to examine the type, instantiate it, link it to
+ buffers and workspaces and to run it. */
+
+typedef struct _LADSPA_Descriptor {
+
+ /* This numeric identifier indicates the plugin type
+ uniquely. Plugin programmers may reserve ranges of IDs from a
+ central body to avoid clashes. Hosts may assume that IDs are
+ below 0x1000000. */
+ unsigned long UniqueID;
+
+ /* This identifier can be used as a unique, case-sensitive
+ identifier for the plugin type within the plugin file. Plugin
+ types should be identified by file and label rather than by index
+ or plugin name, which may be changed in new plugin
+ versions. Labels must not contain white-space characters. */
+ const char * Label;
+
+ /* This indicates a number of properties of the plugin. */
+ LADSPA_Properties Properties;
+
+ /* This member points to the null-terminated name of the plugin
+ (e.g. "Sine Oscillator"). */
+ const char * Name;
+
+ /* This member points to the null-terminated string indicating the
+ maker of the plugin. This can be an empty string but not NULL. */
+ const char * Maker;
+
+ /* This member points to the null-terminated string indicating any
+ copyright applying to the plugin. If no Copyright applies the
+ string "None" should be used. */
+ const char * Copyright;
+
+ /* This indicates the number of ports (input AND output) present on
+ the plugin. */
+ unsigned long PortCount;
+
+ /* This member indicates an array of port descriptors. Valid indices
+ vary from 0 to PortCount-1. */
+ const LADSPA_PortDescriptor * PortDescriptors;
+
+ /* This member indicates an array of null-terminated strings
+ describing ports (e.g. "Frequency (Hz)"). Valid indices vary from
+ 0 to PortCount-1. */
+ const char * const * PortNames;
+
+ /* This member indicates an array of range hints for each port (see
+ above). Valid indices vary from 0 to PortCount-1. */
+ const LADSPA_PortRangeHint * PortRangeHints;
+
+ /* This may be used by the plugin developer to pass any custom
+ implementation data into an instantiate call. It must not be used
+ or interpreted by the host. It is expected that most plugin
+ writers will not use this facility as LADSPA_Handle should be
+ used to hold instance data. */
+ void * ImplementationData;
+
+ /* This member is a function pointer that instantiates a plugin. A
+ handle is returned indicating the new plugin instance. The
+ instantiation function accepts a sample rate as a parameter. The
+ plugin descriptor from which this instantiate function was found
+ must also be passed. This function must return NULL if
+ instantiation fails.
+
+ Note that instance initialisation should generally occur in
+ activate() rather than here. */
+ LADSPA_Handle (*instantiate)(const struct _LADSPA_Descriptor * Descriptor,
+ unsigned long SampleRate);
+
+ /* This member is a function pointer that connects a port on an
+ instantiated plugin to a memory location at which a block of data
+ for the port will be read/written. The data location is expected
+ to be an array of LADSPA_Data for audio ports or a single
+ LADSPA_Data value for control ports. Memory issues will be
+ managed by the host. The plugin must read/write the data at these
+ locations every time run() or run_adding() is called and the data
+ present at the time of this connection call should not be
+ considered meaningful.
+
+ connect_port() may be called more than once for a plugin instance
+ to allow the host to change the buffers that the plugin is
+ reading or writing. These calls may be made before or after
+ activate() or deactivate() calls.
+
+ connect_port() must be called at least once for each port before
+ run() or run_adding() is called. When working with blocks of
+ LADSPA_Data the plugin should pay careful attention to the block
+ size passed to the run function as the block allocated may only
+ just be large enough to contain the block of samples.
+
+ Plugin writers should be aware that the host may elect to use the
+ same buffer for more than one port and even use the same buffer
+ for both input and output (see LADSPA_PROPERTY_INPLACE_BROKEN).
+ However, overlapped buffers or use of a single buffer for both
+ audio and control data may result in unexpected behaviour. */
+ void (*connect_port)(LADSPA_Handle Instance,
+ unsigned long Port,
+ LADSPA_Data * DataLocation);
+
+ /* This member is a function pointer that initialises a plugin
+ instance and activates it for use. This is separated from
+ instantiate() to aid real-time support and so that hosts can
+ reinitialise a plugin instance by calling deactivate() and then
+ activate(). In this case the plugin instance must reset all state
+ information dependent on the history of the plugin instance
+ except for any data locations provided by connect_port() and any
+ gain set by set_run_adding_gain(). If there is nothing for
+ activate() to do then the plugin writer may provide a NULL rather
+ than an empty function.
+
+ When present, hosts must call this function once before run() (or
+ run_adding()) is called for the first time. This call should be
+ made as close to the run() call as possible and indicates to
+ real-time plugins that they are now live. Plugins should not rely
+ on a prompt call to run() after activate(). activate() may not be
+ called again unless deactivate() is called first. Note that
+ connect_port() may be called before or after a call to
+ activate(). */
+ void (*activate)(LADSPA_Handle Instance);
+
+ /* This method is a function pointer that runs an instance of a
+ plugin for a block. Two parameters are required: the first is a
+ handle to the particular instance to be run and the second
+ indicates the block size (in samples) for which the plugin
+ instance may run.
+
+ Note that if an activate() function exists then it must be called
+ before run() or run_adding(). If deactivate() is called for a
+ plugin instance then the plugin instance may not be reused until
+ activate() has been called again.
+
+ If the plugin has the property LADSPA_PROPERTY_HARD_RT_CAPABLE
+ then there are various things that the plugin should not do
+ within the run() or run_adding() functions (see above). */
+ void (*run)(LADSPA_Handle Instance,
+ unsigned long SampleCount);
+
+ /* This method is a function pointer that runs an instance of a
+ plugin for a block. This has identical behaviour to run() except
+ in the way data is output from the plugin. When run() is used,
+ values are written directly to the memory areas associated with
+ the output ports. However when run_adding() is called, values
+ must be added to the values already present in the memory
+ areas. Furthermore, output values written must be scaled by the
+ current gain set by set_run_adding_gain() (see below) before
+ addition.
+
+ run_adding() is optional. When it is not provided by a plugin,
+ this function pointer must be set to NULL. When it is provided,
+ the function set_run_adding_gain() must be provided also. */
+ void (*run_adding)(LADSPA_Handle Instance,
+ unsigned long SampleCount);
+
+ /* This method is a function pointer that sets the output gain for
+ use when run_adding() is called (see above). If this function is
+ never called the gain is assumed to default to 1. Gain
+ information should be retained when activate() or deactivate()
+ are called.
+
+ This function should be provided by the plugin if and only if the
+ run_adding() function is provided. When it is absent this
+ function pointer must be set to NULL. */
+ void (*set_run_adding_gain)(LADSPA_Handle Instance,
+ LADSPA_Data Gain);
+
+ /* This is the counterpart to activate() (see above). If there is
+ nothing for deactivate() to do then the plugin writer may provide
+ a NULL rather than an empty function.
+
+ Hosts must deactivate all activated units after they have been
+ run() (or run_adding()) for the last time. This call should be
+ made as close to the last run() call as possible and indicates to
+ real-time plugins that they are no longer live. Plugins should
+ not rely on prompt deactivation. Note that connect_port() may be
+ called before or after a call to deactivate().
+
+ Deactivation is not similar to pausing as the plugin instance
+ will be reinitialised when activate() is called to reuse it. */
+ void (*deactivate)(LADSPA_Handle Instance);
+
+ /* Once an instance of a plugin has been finished with it can be
+ deleted using the following function. The instance handle passed
+ ceases to be valid after this call.
+
+ If activate() was called for a plugin instance then a
+ corresponding call to deactivate() must be made before cleanup()
+ is called. */
+ void (*cleanup)(LADSPA_Handle Instance);
+
+} LADSPA_Descriptor;
+
+/**********************************************************************/
+
+/* Accessing a Plugin: */
+
+/* The exact mechanism by which plugins are loaded is host-dependent,
+ however all most hosts will need to know is the name of shared
+ object file containing the plugin types. To allow multiple hosts to
+ share plugin types, hosts may wish to check for environment
+ variable LADSPA_PATH. If present, this should contain a
+ colon-separated path indicating directories that should be searched
+ (in order) when loading plugin types.
+
+ A plugin programmer must include a function called
+ "ladspa_descriptor" with the following function prototype within
+ the shared object file. This function will have C-style linkage (if
+ you are using C++ this is taken care of by the `extern "C"' clause
+ at the top of the file).
+
+ A host will find the plugin shared object file by one means or
+ another, find the ladspa_descriptor() function, call it, and
+ proceed from there.
+
+ Plugin types are accessed by index (not ID) using values from 0
+ upwards. Out of range indexes must result in this function
+ returning NULL, so the plugin count can be determined by checking
+ for the least index that results in NULL being returned. */
+
+const LADSPA_Descriptor * ladspa_descriptor(unsigned long Index);
+
+/* Datatype corresponding to the ladspa_descriptor() function. */
+typedef const LADSPA_Descriptor *
+(*LADSPA_Descriptor_Function)(unsigned long Index);
+
+/**********************************************************************/
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LADSPA_INCLUDED */
+
+/* EOF */
diff --git a/src/modules/module-alsa-sink.c b/src/modules/module-alsa-sink.c
new file mode 100644
index 00000000..14aef7c9
--- /dev/null
+++ b/src/modules/module-alsa-sink.c
@@ -0,0 +1,995 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+
+#include <asoundlib.h>
+
+#include <pulse/xmalloc.h>
+#include <pulse/util.h>
+
+#include <pulsecore/core.h>
+#include <pulsecore/module.h>
+#include <pulsecore/memchunk.h>
+#include <pulsecore/sink.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/sample-util.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/thread.h>
+#include <pulsecore/core-error.h>
+#include <pulsecore/thread-mq.h>
+#include <pulsecore/rtpoll.h>
+
+#include "alsa-util.h"
+#include "module-alsa-sink-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering");
+PA_MODULE_DESCRIPTION("ALSA Sink");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(FALSE);
+PA_MODULE_USAGE(
+ "sink_name=<name for the sink> "
+ "device=<ALSA device> "
+ "device_id=<ALSA device id> "
+ "format=<sample format> "
+ "channels=<number of channels> "
+ "rate=<sample rate> "
+ "fragments=<number of fragments> "
+ "fragment_size=<fragment size> "
+ "channel_map=<channel map> "
+ "mmap=<enable memory mapping?>");
+
+#define DEFAULT_DEVICE "default"
+
+struct userdata {
+ pa_core *core;
+ pa_module *module;
+ pa_sink *sink;
+
+ pa_thread *thread;
+ pa_thread_mq thread_mq;
+ pa_rtpoll *rtpoll;
+
+ snd_pcm_t *pcm_handle;
+
+ pa_alsa_fdlist *mixer_fdl;
+ snd_mixer_t *mixer_handle;
+ snd_mixer_elem_t *mixer_elem;
+ long hw_volume_max, hw_volume_min;
+
+ size_t frame_size, fragment_size, hwbuf_size;
+ unsigned nfragments;
+ pa_memchunk memchunk;
+
+ char *device_name;
+
+ pa_bool_t use_mmap;
+
+ pa_bool_t first;
+
+ pa_rtpoll_item *alsa_rtpoll_item;
+
+ snd_mixer_selem_channel_id_t mixer_map[SND_MIXER_SCHN_LAST];
+};
+
+static const char* const valid_modargs[] = {
+ "device",
+ "device_id",
+ "sink_name",
+ "format",
+ "channels",
+ "rate",
+ "fragments",
+ "fragment_size",
+ "channel_map",
+ "mmap",
+ NULL
+};
+
+static int mmap_write(struct userdata *u) {
+ int work_done = 0;
+
+ pa_assert(u);
+ pa_sink_assert_ref(u->sink);
+
+ for (;;) {
+ pa_memchunk chunk;
+ void *p;
+ snd_pcm_sframes_t n;
+ int err;
+ const snd_pcm_channel_area_t *areas;
+ snd_pcm_uframes_t offset, frames;
+
+ if ((n = snd_pcm_avail_update(u->pcm_handle)) < 0) {
+
+ if (n == -EPIPE) {
+ pa_log_debug("snd_pcm_avail_update: Buffer underrun!");
+ u->first = TRUE;
+ }
+
+ if ((err = snd_pcm_recover(u->pcm_handle, n, 1)) == 0)
+ continue;
+
+ if (err == -EAGAIN)
+ return work_done;
+
+ pa_log("snd_pcm_avail_update: %s", snd_strerror(err));
+ return -1;
+ }
+
+/* pa_log("Got request for %i samples", (int) n); */
+
+ if (n <= 0)
+ return work_done;
+
+ frames = n;
+
+ if ((err = snd_pcm_mmap_begin(u->pcm_handle, &areas, &offset, &frames)) < 0) {
+
+ if (err == -EPIPE) {
+ pa_log_debug("snd_pcm_mmap_begin: Buffer underrun!");
+ u->first = TRUE;
+ }
+
+ if ((err = snd_pcm_recover(u->pcm_handle, err, 1)) == 0)
+ continue;
+
+ if (err == -EAGAIN)
+ return work_done;
+
+ pa_log("Failed to write data to DSP: %s", snd_strerror(err));
+ return -1;
+ }
+
+ /* Check these are multiples of 8 bit */
+ pa_assert((areas[0].first & 7) == 0);
+ pa_assert((areas[0].step & 7)== 0);
+
+ /* We assume a single interleaved memory buffer */
+ pa_assert((areas[0].first >> 3) == 0);
+ pa_assert((areas[0].step >> 3) == u->frame_size);
+
+ p = (uint8_t*) areas[0].addr + (offset * u->frame_size);
+
+ chunk.memblock = pa_memblock_new_fixed(u->core->mempool, p, frames * u->frame_size, 1);
+ chunk.length = pa_memblock_get_length(chunk.memblock);
+ chunk.index = 0;
+
+ pa_sink_render_into_full(u->sink, &chunk);
+
+ /* FIXME: Maybe we can do something to keep this memory block
+ * a little bit longer around? */
+ pa_memblock_unref_fixed(chunk.memblock);
+
+ if ((err = snd_pcm_mmap_commit(u->pcm_handle, offset, frames)) < 0) {
+
+ if (err == -EPIPE) {
+ pa_log_debug("snd_pcm_mmap_commit: Buffer underrun!");
+ u->first = TRUE;
+ }
+
+ if ((err = snd_pcm_recover(u->pcm_handle, err, 1)) == 0)
+ continue;
+
+ if (err == -EAGAIN)
+ return work_done;
+
+ pa_log("Failed to write data to DSP: %s", snd_strerror(err));
+ return -1;
+ }
+
+ work_done = 1;
+
+ if (frames >= (snd_pcm_uframes_t) n)
+ return work_done;
+
+/* pa_log("wrote %i samples", (int) frames); */
+ }
+}
+
+static int unix_write(struct userdata *u) {
+ snd_pcm_status_t *status;
+ int work_done = 0;
+
+ snd_pcm_status_alloca(&status);
+
+ pa_assert(u);
+ pa_sink_assert_ref(u->sink);
+
+ for (;;) {
+ void *p;
+ snd_pcm_sframes_t t;
+ ssize_t l;
+ int err;
+
+ if ((err = snd_pcm_status(u->pcm_handle, status)) < 0) {
+ pa_log("Failed to query DSP status data: %s", snd_strerror(err));
+ return -1;
+ }
+
+ if (snd_pcm_status_get_avail_max(status)*u->frame_size >= u->hwbuf_size)
+ pa_log_debug("Buffer underrun!");
+
+ l = snd_pcm_status_get_avail(status) * u->frame_size;
+
+/* pa_log("%u bytes to write", l); */
+
+ if (l <= 0)
+ return work_done;
+
+ if (u->memchunk.length <= 0)
+ pa_sink_render(u->sink, l, &u->memchunk);
+
+ pa_assert(u->memchunk.length > 0);
+
+ p = pa_memblock_acquire(u->memchunk.memblock);
+ t = snd_pcm_writei(u->pcm_handle, (const uint8_t*) p + u->memchunk.index, u->memchunk.length / u->frame_size);
+ pa_memblock_release(u->memchunk.memblock);
+
+/* pa_log("wrote %i bytes of %u (%u)", t*u->frame_size, u->memchunk.length, l); */
+
+ pa_assert(t != 0);
+
+ if (t < 0) {
+
+ if ((t = snd_pcm_recover(u->pcm_handle, t, 1)) == 0)
+ continue;
+
+ if (t == -EAGAIN) {
+ pa_log_debug("EAGAIN");
+ return work_done;
+ } else {
+ pa_log("Failed to write data to DSP: %s", snd_strerror(t));
+ return -1;
+ }
+ }
+
+ u->memchunk.index += t * u->frame_size;
+ u->memchunk.length -= t * u->frame_size;
+
+ if (u->memchunk.length <= 0) {
+ pa_memblock_unref(u->memchunk.memblock);
+ pa_memchunk_reset(&u->memchunk);
+ }
+
+ work_done = 1;
+
+ if (t * u->frame_size >= (unsigned) l)
+ return work_done;
+ }
+}
+
+static pa_usec_t sink_get_latency(struct userdata *u) {
+ pa_usec_t r = 0;
+ snd_pcm_status_t *status;
+ snd_pcm_sframes_t frames = 0;
+ int err;
+
+ snd_pcm_status_alloca(&status);
+
+ pa_assert(u);
+ pa_assert(u->pcm_handle);
+
+ if ((err = snd_pcm_status(u->pcm_handle, status)) < 0)
+ pa_log("Failed to get delay: %s", snd_strerror(err));
+ else
+ frames = snd_pcm_status_get_delay(status);
+
+ if (frames > 0)
+ r = pa_bytes_to_usec(frames * u->frame_size, &u->sink->sample_spec);
+
+ if (u->memchunk.memblock)
+ r += pa_bytes_to_usec(u->memchunk.length, &u->sink->sample_spec);
+
+ return r;
+}
+
+static int build_pollfd(struct userdata *u) {
+ int err;
+ struct pollfd *pollfd;
+ int n;
+
+ pa_assert(u);
+ pa_assert(u->pcm_handle);
+
+ if ((n = snd_pcm_poll_descriptors_count(u->pcm_handle)) < 0) {
+ pa_log("snd_pcm_poll_descriptors_count() failed: %s", snd_strerror(n));
+ return -1;
+ }
+
+ if (u->alsa_rtpoll_item)
+ pa_rtpoll_item_free(u->alsa_rtpoll_item);
+
+ u->alsa_rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, n);
+ pollfd = pa_rtpoll_item_get_pollfd(u->alsa_rtpoll_item, NULL);
+
+ if ((err = snd_pcm_poll_descriptors(u->pcm_handle, pollfd, n)) < 0) {
+ pa_log("snd_pcm_poll_descriptors() failed: %s", snd_strerror(err));
+ return -1;
+ }
+
+ return 0;
+}
+
+static int suspend(struct userdata *u) {
+ pa_assert(u);
+ pa_assert(u->pcm_handle);
+
+ /* Let's suspend */
+ snd_pcm_drain(u->pcm_handle);
+ snd_pcm_close(u->pcm_handle);
+ u->pcm_handle = NULL;
+
+ if (u->alsa_rtpoll_item) {
+ pa_rtpoll_item_free(u->alsa_rtpoll_item);
+ u->alsa_rtpoll_item = NULL;
+ }
+
+ pa_log_info("Device suspended...");
+
+ return 0;
+}
+
+static int unsuspend(struct userdata *u) {
+ pa_sample_spec ss;
+ int err;
+ pa_bool_t b;
+ unsigned nfrags;
+ snd_pcm_uframes_t period_size;
+
+ pa_assert(u);
+ pa_assert(!u->pcm_handle);
+
+ pa_log_info("Trying resume...");
+
+ snd_config_update_free_global();
+ if ((err = snd_pcm_open(&u->pcm_handle, u->device_name, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) {
+ pa_log("Error opening PCM device %s: %s", u->device_name, snd_strerror(err));
+ goto fail;
+ }
+
+ ss = u->sink->sample_spec;
+ nfrags = u->nfragments;
+ period_size = u->fragment_size / u->frame_size;
+ b = u->use_mmap;
+
+ if ((err = pa_alsa_set_hw_params(u->pcm_handle, &ss, &nfrags, &period_size, &b, TRUE)) < 0) {
+ pa_log("Failed to set hardware parameters: %s", snd_strerror(err));
+ goto fail;
+ }
+
+ if (b != u->use_mmap) {
+ pa_log_warn("Resume failed, couldn't get original access mode.");
+ goto fail;
+ }
+
+ if (!pa_sample_spec_equal(&ss, &u->sink->sample_spec)) {
+ pa_log_warn("Resume failed, couldn't restore original sample settings.");
+ goto fail;
+ }
+
+ if (nfrags != u->nfragments || period_size*u->frame_size != u->fragment_size) {
+ pa_log_warn("Resume failed, couldn't restore original fragment settings.");
+ goto fail;
+ }
+
+ if ((err = pa_alsa_set_sw_params(u->pcm_handle)) < 0) {
+ pa_log("Failed to set software parameters: %s", snd_strerror(err));
+ goto fail;
+ }
+
+ if (build_pollfd(u) < 0)
+ goto fail;
+
+ /* FIXME: We need to reload the volume somehow */
+
+ u->first = TRUE;
+
+ pa_log_info("Resumed successfully...");
+
+ return 0;
+
+fail:
+ if (u->pcm_handle) {
+ snd_pcm_close(u->pcm_handle);
+ u->pcm_handle = NULL;
+ }
+
+ return -1;
+}
+
+static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+ struct userdata *u = PA_SINK(o)->userdata;
+
+ switch (code) {
+
+ case PA_SINK_MESSAGE_GET_LATENCY: {
+ pa_usec_t r = 0;
+
+ if (u->pcm_handle)
+ r = sink_get_latency(u);
+
+ *((pa_usec_t*) data) = r;
+
+ return 0;
+ }
+
+ case PA_SINK_MESSAGE_SET_STATE:
+
+ switch ((pa_sink_state_t) PA_PTR_TO_UINT(data)) {
+
+ case PA_SINK_SUSPENDED:
+ pa_assert(PA_SINK_OPENED(u->sink->thread_info.state));
+
+ if (suspend(u) < 0)
+ return -1;
+
+ break;
+
+ case PA_SINK_IDLE:
+ case PA_SINK_RUNNING:
+
+ if (u->sink->thread_info.state == PA_SINK_INIT) {
+ if (build_pollfd(u) < 0)
+ return -1;
+ }
+
+ if (u->sink->thread_info.state == PA_SINK_SUSPENDED) {
+ if (unsuspend(u) < 0)
+ return -1;
+ }
+
+ break;
+
+ case PA_SINK_UNLINKED:
+ case PA_SINK_INIT:
+ ;
+ }
+
+ break;
+ }
+
+ return pa_sink_process_msg(o, code, data, offset, chunk);
+}
+
+static int mixer_callback(snd_mixer_elem_t *elem, unsigned int mask) {
+ struct userdata *u = snd_mixer_elem_get_callback_private(elem);
+
+ pa_assert(u);
+ pa_assert(u->mixer_handle);
+
+ if (mask == SND_CTL_EVENT_MASK_REMOVE)
+ return 0;
+
+ if (mask & SND_CTL_EVENT_MASK_VALUE) {
+ pa_sink_get_volume(u->sink);
+ pa_sink_get_mute(u->sink);
+ }
+
+ return 0;
+}
+
+static int sink_get_volume_cb(pa_sink *s) {
+ struct userdata *u = s->userdata;
+ int err;
+ int i;
+
+ pa_assert(u);
+ pa_assert(u->mixer_elem);
+
+ for (i = 0; i < s->sample_spec.channels; i++) {
+ long set_vol, vol;
+
+ pa_assert(snd_mixer_selem_has_playback_channel(u->mixer_elem, u->mixer_map[i]));
+
+ if ((err = snd_mixer_selem_get_playback_volume(u->mixer_elem, u->mixer_map[i], &vol)) < 0)
+ goto fail;
+
+ set_vol = (long) roundf(((float) s->volume.values[i] * (u->hw_volume_max - u->hw_volume_min)) / PA_VOLUME_NORM) + u->hw_volume_min;
+
+ /* Try to avoid superfluous volume changes */
+ if (set_vol != vol)
+ s->volume.values[i] = (pa_volume_t) roundf(((float) (vol - u->hw_volume_min) * PA_VOLUME_NORM) / (u->hw_volume_max - u->hw_volume_min));
+ }
+
+ return 0;
+
+fail:
+ pa_log_error("Unable to read volume: %s", snd_strerror(err));
+
+ s->get_volume = NULL;
+ s->set_volume = NULL;
+ return -1;
+}
+
+static int sink_set_volume_cb(pa_sink *s) {
+ struct userdata *u = s->userdata;
+ int err;
+ int i;
+
+ pa_assert(u);
+ pa_assert(u->mixer_elem);
+
+ for (i = 0; i < s->sample_spec.channels; i++) {
+ long alsa_vol;
+ pa_volume_t vol;
+
+ pa_assert(snd_mixer_selem_has_playback_channel(u->mixer_elem, u->mixer_map[i]));
+
+ vol = s->volume.values[i];
+
+ if (vol > PA_VOLUME_NORM)
+ vol = PA_VOLUME_NORM;
+
+ alsa_vol = (long) roundf(((float) vol * (u->hw_volume_max - u->hw_volume_min)) / PA_VOLUME_NORM) + u->hw_volume_min;
+
+ if ((err = snd_mixer_selem_set_playback_volume(u->mixer_elem, u->mixer_map[i], alsa_vol)) < 0)
+ goto fail;
+ }
+
+ return 0;
+
+fail:
+ pa_log_error("Unable to set volume: %s", snd_strerror(err));
+
+ s->get_volume = NULL;
+ s->set_volume = NULL;
+ return -1;
+}
+
+static int sink_get_mute_cb(pa_sink *s) {
+ struct userdata *u = s->userdata;
+ int err, sw;
+
+ pa_assert(u);
+ pa_assert(u->mixer_elem);
+
+ if ((err = snd_mixer_selem_get_playback_switch(u->mixer_elem, 0, &sw)) < 0) {
+ pa_log_error("Unable to get switch: %s", snd_strerror(err));
+
+ s->get_mute = NULL;
+ s->set_mute = NULL;
+ return -1;
+ }
+
+ s->muted = !sw;
+
+ return 0;
+}
+
+static int sink_set_mute_cb(pa_sink *s) {
+ struct userdata *u = s->userdata;
+ int err;
+
+ pa_assert(u);
+ pa_assert(u->mixer_elem);
+
+ if ((err = snd_mixer_selem_set_playback_switch_all(u->mixer_elem, !s->muted)) < 0) {
+ pa_log_error("Unable to set switch: %s", snd_strerror(err));
+
+ s->get_mute = NULL;
+ s->set_mute = NULL;
+ return -1;
+ }
+
+ return 0;
+}
+
+static void thread_func(void *userdata) {
+ struct userdata *u = userdata;
+
+ pa_assert(u);
+
+ pa_log_debug("Thread starting up");
+
+ if (u->core->realtime_scheduling)
+ pa_make_realtime(u->core->realtime_priority);
+
+ pa_thread_mq_install(&u->thread_mq);
+ pa_rtpoll_install(u->rtpoll);
+
+ for (;;) {
+ int ret;
+
+ /* Render some data and write it to the dsp */
+ if (PA_SINK_OPENED(u->sink->thread_info.state)) {
+ int work_done = 0;
+
+ if (u->use_mmap) {
+ if ((work_done = mmap_write(u)) < 0)
+ goto fail;
+ } else {
+ if ((work_done = unix_write(u)) < 0)
+ goto fail;
+ }
+
+ if (work_done && u->first) {
+ pa_log_info("Starting playback.");
+ snd_pcm_start(u->pcm_handle);
+ u->first = FALSE;
+ continue;
+ }
+ }
+
+ /* Hmm, nothing to do. Let's sleep */
+ if ((ret = pa_rtpoll_run(u->rtpoll, 1)) < 0)
+ goto fail;
+
+ if (ret == 0)
+ goto finish;
+
+ /* Tell ALSA about this and process its response */
+ if (PA_SINK_OPENED(u->sink->thread_info.state)) {
+ struct pollfd *pollfd;
+ unsigned short revents = 0;
+ int err;
+ unsigned n;
+
+ pollfd = pa_rtpoll_item_get_pollfd(u->alsa_rtpoll_item, &n);
+
+ if ((err = snd_pcm_poll_descriptors_revents(u->pcm_handle, pollfd, n, &revents)) < 0) {
+ pa_log("snd_pcm_poll_descriptors_revents() failed: %s", snd_strerror(err));
+ goto fail;
+ }
+
+ if (revents & (POLLERR|POLLNVAL|POLLHUP)) {
+
+ if (revents & POLLERR)
+ pa_log_warn("Got POLLERR from ALSA");
+ if (revents & POLLNVAL)
+ pa_log_warn("Got POLLNVAL from ALSA");
+ if (revents & POLLHUP)
+ pa_log_warn("Got POLLHUP from ALSA");
+
+ /* Try to recover from this error */
+
+ switch (snd_pcm_state(u->pcm_handle)) {
+
+ case SND_PCM_STATE_XRUN:
+ if ((err = snd_pcm_recover(u->pcm_handle, -EPIPE, 1)) != 0) {
+ pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP and XRUN: %s", snd_strerror(err));
+ goto fail;
+ }
+ break;
+
+ case SND_PCM_STATE_SUSPENDED:
+ if ((err = snd_pcm_recover(u->pcm_handle, -ESTRPIPE, 1)) != 0) {
+ pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP and SUSPENDED: %s", snd_strerror(err));
+ goto fail;
+ }
+ break;
+
+ default:
+
+ snd_pcm_drop(u->pcm_handle);
+
+ if ((err = snd_pcm_prepare(u->pcm_handle)) < 0) {
+ pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP with snd_pcm_prepare(): %s", snd_strerror(err));
+ goto fail;
+ }
+ break;
+ }
+ }
+ }
+ }
+
+fail:
+ /* If this was no regular exit from the loop we have to continue
+ * processing messages until we received PA_MESSAGE_SHUTDOWN */
+ pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL);
+ pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN);
+
+finish:
+ pa_log_debug("Thread shutting down");
+}
+
+int pa__init(pa_module*m) {
+
+ pa_modargs *ma = NULL;
+ struct userdata *u = NULL;
+ const char *dev_id;
+ pa_sample_spec ss;
+ pa_channel_map map;
+ uint32_t nfrags, frag_size;
+ snd_pcm_uframes_t period_size;
+ size_t frame_size;
+ snd_pcm_info_t *pcm_info = NULL;
+ int err;
+ char *t;
+ const char *name;
+ char *name_buf = NULL;
+ int namereg_fail;
+ pa_bool_t use_mmap = TRUE, b;
+
+ snd_pcm_info_alloca(&pcm_info);
+
+ pa_assert(m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log("Failed to parse module arguments");
+ goto fail;
+ }
+
+ ss = m->core->default_sample_spec;
+ if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_ALSA) < 0) {
+ pa_log("Failed to parse sample specification and channel map");
+ goto fail;
+ }
+
+ frame_size = pa_frame_size(&ss);
+
+ nfrags = m->core->default_n_fragments;
+ frag_size = pa_usec_to_bytes(m->core->default_fragment_size_msec*1000, &ss);
+ if (frag_size <= 0)
+ frag_size = frame_size;
+
+ if (pa_modargs_get_value_u32(ma, "fragments", &nfrags) < 0 || pa_modargs_get_value_u32(ma, "fragment_size", &frag_size) < 0) {
+ pa_log("Failed to parse buffer metrics");
+ goto fail;
+ }
+ period_size = frag_size/frame_size;
+
+ if (pa_modargs_get_value_boolean(ma, "mmap", &use_mmap) < 0) {
+ pa_log("Failed to parse mmap argument.");
+ goto fail;
+ }
+
+ u = pa_xnew0(struct userdata, 1);
+ u->core = m->core;
+ u->module = m;
+ m->userdata = u;
+ u->use_mmap = use_mmap;
+ u->first = TRUE;
+ pa_thread_mq_init(&u->thread_mq, m->core->mainloop);
+ u->rtpoll = pa_rtpoll_new();
+ u->alsa_rtpoll_item = NULL;
+ pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq);
+
+ snd_config_update_free_global();
+
+ b = use_mmap;
+
+ if ((dev_id = pa_modargs_get_value(ma, "device_id", NULL))) {
+
+ if (!(u->pcm_handle = pa_alsa_open_by_device_id(
+ dev_id,
+ &u->device_name,
+ &ss, &map,
+ SND_PCM_STREAM_PLAYBACK,
+ &nfrags, &period_size,
+ &b)))
+
+ goto fail;
+
+ } else {
+
+ if (!(u->pcm_handle = pa_alsa_open_by_device_string(
+ pa_modargs_get_value(ma, "device", DEFAULT_DEVICE),
+ &u->device_name,
+ &ss, &map,
+ SND_PCM_STREAM_PLAYBACK,
+ &nfrags, &period_size,
+ &b)))
+ goto fail;
+
+ }
+
+ pa_assert(u->device_name);
+ pa_log_info("Successfully opened device %s.", u->device_name);
+
+ if (use_mmap && !b) {
+ pa_log_info("Device doesn't support mmap(), falling back to UNIX read/write mode.");
+ u->use_mmap = use_mmap = b;
+ }
+
+ if (u->use_mmap)
+ pa_log_info("Successfully enabled mmap() mode.");
+
+ if ((err = snd_pcm_info(u->pcm_handle, pcm_info)) < 0) {
+ pa_log("Error fetching PCM info: %s", snd_strerror(err));
+ goto fail;
+ }
+
+ if ((err = pa_alsa_set_sw_params(u->pcm_handle)) < 0) {
+ pa_log("Failed to set software parameters: %s", snd_strerror(err));
+ goto fail;
+ }
+
+ /* ALSA might tweak the sample spec, so recalculate the frame size */
+ frame_size = pa_frame_size(&ss);
+
+ if ((err = snd_mixer_open(&u->mixer_handle, 0)) < 0)
+ pa_log_warn("Error opening mixer: %s", snd_strerror(err));
+ else {
+ pa_bool_t found = FALSE;
+
+ if (pa_alsa_prepare_mixer(u->mixer_handle, u->device_name) >= 0)
+ found = TRUE;
+ else {
+ char *md = pa_sprintf_malloc("hw:%s", dev_id);
+
+ if (strcmp(u->device_name, md))
+ if (pa_alsa_prepare_mixer(u->mixer_handle, md) >= 0)
+ found = TRUE;
+
+ pa_xfree(md);
+ }
+
+ if (found)
+ if (!(u->mixer_elem = pa_alsa_find_elem(u->mixer_handle, "Master", "PCM")))
+ found = FALSE;
+
+ if (!found) {
+ snd_mixer_close(u->mixer_handle);
+ u->mixer_handle = NULL;
+ }
+ }
+
+ if ((name = pa_modargs_get_value(ma, "sink_name", NULL)))
+ namereg_fail = 1;
+ else {
+ name = name_buf = pa_sprintf_malloc("alsa_output.%s", u->device_name);
+ namereg_fail = 0;
+ }
+
+ u->sink = pa_sink_new(m->core, __FILE__, name, namereg_fail, &ss, &map);
+ pa_xfree(name_buf);
+
+ if (!u->sink) {
+ pa_log("Failed to create sink object");
+ goto fail;
+ }
+
+ u->sink->parent.process_msg = sink_process_msg;
+ u->sink->userdata = u;
+
+ pa_sink_set_module(u->sink, m);
+ pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq);
+ pa_sink_set_rtpoll(u->sink, u->rtpoll);
+ pa_sink_set_description(u->sink, t = pa_sprintf_malloc(
+ "ALSA PCM on %s (%s)%s",
+ u->device_name,
+ snd_pcm_info_get_name(pcm_info),
+ use_mmap ? " via DMA" : ""));
+ pa_xfree(t);
+
+ u->sink->flags = PA_SINK_HARDWARE|PA_SINK_LATENCY;
+
+ u->frame_size = frame_size;
+ u->fragment_size = frag_size = period_size * frame_size;
+ u->nfragments = nfrags;
+ u->hwbuf_size = u->fragment_size * nfrags;
+
+ pa_log_info("Using %u fragments of size %lu bytes.", nfrags, (long unsigned) u->fragment_size);
+
+ pa_memchunk_reset(&u->memchunk);
+
+ if (u->mixer_handle) {
+ pa_assert(u->mixer_elem);
+
+ if (snd_mixer_selem_has_playback_volume(u->mixer_elem))
+ if (pa_alsa_calc_mixer_map(u->mixer_elem, &map, u->mixer_map, TRUE) >= 0) {
+ u->sink->get_volume = sink_get_volume_cb;
+ u->sink->set_volume = sink_set_volume_cb;
+ snd_mixer_selem_get_playback_volume_range(u->mixer_elem, &u->hw_volume_min, &u->hw_volume_max);
+ u->sink->flags |= PA_SINK_HW_VOLUME_CTRL;
+ }
+
+ if (snd_mixer_selem_has_playback_switch(u->mixer_elem)) {
+ u->sink->get_mute = sink_get_mute_cb;
+ u->sink->set_mute = sink_set_mute_cb;
+ u->sink->flags |= PA_SINK_HW_VOLUME_CTRL;
+ }
+
+ u->mixer_fdl = pa_alsa_fdlist_new();
+
+ if (pa_alsa_fdlist_set_mixer(u->mixer_fdl, u->mixer_handle, m->core->mainloop) < 0) {
+ pa_log("Failed to initialize file descriptor monitoring");
+ goto fail;
+ }
+
+ snd_mixer_elem_set_callback(u->mixer_elem, mixer_callback);
+ snd_mixer_elem_set_callback_private(u->mixer_elem, u);
+ } else
+ u->mixer_fdl = NULL;
+
+ if (!(u->thread = pa_thread_new(thread_func, u))) {
+ pa_log("Failed to create thread.");
+ goto fail;
+ }
+
+ /* Get initial mixer settings */
+ if (u->sink->get_volume)
+ u->sink->get_volume(u->sink);
+ if (u->sink->get_mute)
+ u->sink->get_mute(u->sink);
+
+ pa_sink_put(u->sink);
+
+ pa_modargs_free(ma);
+
+ return 0;
+
+fail:
+
+ if (ma)
+ pa_modargs_free(ma);
+
+ pa__done(m);
+
+ return -1;
+}
+
+void pa__done(pa_module*m) {
+ struct userdata *u;
+
+ pa_assert(m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->sink)
+ pa_sink_unlink(u->sink);
+
+ if (u->thread) {
+ pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL);
+ pa_thread_free(u->thread);
+ }
+
+ pa_thread_mq_done(&u->thread_mq);
+
+ if (u->sink)
+ pa_sink_unref(u->sink);
+
+ if (u->memchunk.memblock)
+ pa_memblock_unref(u->memchunk.memblock);
+
+ if (u->alsa_rtpoll_item)
+ pa_rtpoll_item_free(u->alsa_rtpoll_item);
+
+ if (u->rtpoll)
+ pa_rtpoll_free(u->rtpoll);
+
+ if (u->mixer_fdl)
+ pa_alsa_fdlist_free(u->mixer_fdl);
+
+ if (u->mixer_handle)
+ snd_mixer_close(u->mixer_handle);
+
+ if (u->pcm_handle) {
+ snd_pcm_drop(u->pcm_handle);
+ snd_pcm_close(u->pcm_handle);
+ }
+
+ pa_xfree(u->device_name);
+ pa_xfree(u);
+
+ snd_config_update_free_global();
+}
diff --git a/src/modules/module-alsa-source.c b/src/modules/module-alsa-source.c
new file mode 100644
index 00000000..23a2f921
--- /dev/null
+++ b/src/modules/module-alsa-source.c
@@ -0,0 +1,968 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+
+#include <asoundlib.h>
+
+#include <pulse/xmalloc.h>
+#include <pulse/util.h>
+
+#include <pulsecore/core-error.h>
+#include <pulsecore/core.h>
+#include <pulsecore/module.h>
+#include <pulsecore/memchunk.h>
+#include <pulsecore/sink.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/sample-util.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/thread.h>
+#include <pulsecore/core-error.h>
+#include <pulsecore/thread-mq.h>
+#include <pulsecore/rtpoll.h>
+
+#include "alsa-util.h"
+#include "module-alsa-source-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering");
+PA_MODULE_DESCRIPTION("ALSA Source");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(FALSE);
+PA_MODULE_USAGE(
+ "source_name=<name for the source> "
+ "device=<ALSA device> "
+ "device_id=<ALSA device id> "
+ "format=<sample format> "
+ "channels=<number of channels> "
+ "rate=<sample rate> "
+ "fragments=<number of fragments> "
+ "fragment_size=<fragment size> "
+ "channel_map=<channel map> "
+ "mmap=<enable memory mapping?>");
+
+#define DEFAULT_DEVICE "default"
+
+struct userdata {
+ pa_core *core;
+ pa_module *module;
+ pa_source *source;
+
+ pa_thread *thread;
+ pa_thread_mq thread_mq;
+ pa_rtpoll *rtpoll;
+
+ snd_pcm_t *pcm_handle;
+
+ pa_alsa_fdlist *mixer_fdl;
+ snd_mixer_t *mixer_handle;
+ snd_mixer_elem_t *mixer_elem;
+ long hw_volume_max, hw_volume_min;
+
+ size_t frame_size, fragment_size, hwbuf_size;
+ unsigned nfragments;
+
+ char *device_name;
+
+ pa_bool_t use_mmap;
+
+ pa_rtpoll_item *alsa_rtpoll_item;
+
+ snd_mixer_selem_channel_id_t mixer_map[SND_MIXER_SCHN_LAST];
+};
+
+static const char* const valid_modargs[] = {
+ "device",
+ "device_id",
+ "source_name",
+ "channels",
+ "rate",
+ "format",
+ "fragments",
+ "fragment_size",
+ "channel_map",
+ "mmap",
+ NULL
+};
+
+static int mmap_read(struct userdata *u) {
+ int work_done = 0;
+
+ pa_assert(u);
+ pa_source_assert_ref(u->source);
+
+ for (;;) {
+ snd_pcm_sframes_t n;
+ int err;
+ const snd_pcm_channel_area_t *areas;
+ snd_pcm_uframes_t offset, frames;
+ pa_memchunk chunk;
+ void *p;
+
+ if ((n = snd_pcm_avail_update(u->pcm_handle)) < 0) {
+
+ if (n == -EPIPE)
+ pa_log_debug("snd_pcm_avail_update: Buffer underrun!");
+
+ if ((err = snd_pcm_recover(u->pcm_handle, n, 1)) == 0)
+ continue;
+
+ if (err == -EAGAIN)
+ return work_done;
+
+ pa_log("snd_pcm_avail_update: %s", snd_strerror(err));
+ return -1;
+ }
+
+/* pa_log("Got request for %i samples", (int) n); */
+
+ if (n <= 0)
+ return work_done;
+
+ frames = n;
+
+ if ((err = snd_pcm_mmap_begin(u->pcm_handle, &areas, &offset, &frames)) < 0) {
+
+ if (err == -EPIPE)
+ pa_log_debug("snd_pcm_mmap_begin: Buffer underrun!");
+
+ if ((err = snd_pcm_recover(u->pcm_handle, err, 1)) == 0)
+ continue;
+
+ if (err == -EAGAIN)
+ return work_done;
+
+ pa_log("Failed to write data to DSP: %s", snd_strerror(err));
+ return -1;
+ }
+
+ /* Check these are multiples of 8 bit */
+ pa_assert((areas[0].first & 7) == 0);
+ pa_assert((areas[0].step & 7)== 0);
+
+ /* We assume a single interleaved memory buffer */
+ pa_assert((areas[0].first >> 3) == 0);
+ pa_assert((areas[0].step >> 3) == u->frame_size);
+
+ p = (uint8_t*) areas[0].addr + (offset * u->frame_size);
+
+ chunk.memblock = pa_memblock_new_fixed(u->core->mempool, p, frames * u->frame_size, 1);
+ chunk.length = pa_memblock_get_length(chunk.memblock);
+ chunk.index = 0;
+
+ pa_source_post(u->source, &chunk);
+
+ /* FIXME: Maybe we can do something to keep this memory block
+ * a little bit longer around? */
+ pa_memblock_unref_fixed(chunk.memblock);
+
+ if ((err = snd_pcm_mmap_commit(u->pcm_handle, offset, frames)) < 0) {
+
+ if (err == -EPIPE)
+ pa_log_debug("snd_pcm_mmap_commit: Buffer underrun!");
+
+ if ((err = snd_pcm_recover(u->pcm_handle, err, 1)) == 0)
+ continue;
+
+ if (err == -EAGAIN)
+ return work_done;
+
+ pa_log("Failed to write data to DSP: %s", snd_strerror(err));
+ return -1;
+ }
+
+ work_done = 1;
+
+/* pa_log("wrote %i samples", (int) frames); */
+ }
+}
+
+static int unix_read(struct userdata *u) {
+ snd_pcm_status_t *status;
+ int work_done = 0;
+
+ snd_pcm_status_alloca(&status);
+
+ pa_assert(u);
+ pa_source_assert_ref(u->source);
+
+ for (;;) {
+ void *p;
+ snd_pcm_sframes_t t, k;
+ ssize_t l;
+ int err;
+ pa_memchunk chunk;
+
+ if ((err = snd_pcm_status(u->pcm_handle, status)) < 0) {
+ pa_log("Failed to query DSP status data: %s", snd_strerror(err));
+ return -1;
+ }
+
+ if (snd_pcm_status_get_avail_max(status)*u->frame_size >= u->hwbuf_size)
+ pa_log_debug("Buffer overrun!");
+
+ l = snd_pcm_status_get_avail(status) * u->frame_size;
+
+ if (l <= 0)
+ return work_done;
+
+ chunk.memblock = pa_memblock_new(u->core->mempool, (size_t) -1);
+
+ k = pa_memblock_get_length(chunk.memblock);
+
+ if (k > l)
+ k = l;
+
+ k = (k/u->frame_size)*u->frame_size;
+
+ p = pa_memblock_acquire(chunk.memblock);
+ t = snd_pcm_readi(u->pcm_handle, (uint8_t*) p, k / u->frame_size);
+ pa_memblock_release(chunk.memblock);
+
+/* pa_log("wrote %i bytes of %u (%u)", t*u->frame_size, u->memchunk.length, l); */
+
+ pa_assert(t != 0);
+
+ if (t < 0) {
+ pa_memblock_unref(chunk.memblock);
+
+ if ((t = snd_pcm_recover(u->pcm_handle, t, 1)) == 0)
+ continue;
+
+ if (t == -EAGAIN) {
+ pa_log_debug("EAGAIN");
+ return work_done;
+ } else {
+ pa_log("Failed to read data from DSP: %s", snd_strerror(t));
+ return -1;
+ }
+ }
+
+ chunk.index = 0;
+ chunk.length = t * u->frame_size;
+
+ pa_source_post(u->source, &chunk);
+ pa_memblock_unref(chunk.memblock);
+
+ work_done = 1;
+
+ if (t * u->frame_size >= (unsigned) l)
+ return work_done;
+ }
+}
+
+static pa_usec_t source_get_latency(struct userdata *u) {
+ pa_usec_t r = 0;
+ snd_pcm_status_t *status;
+ snd_pcm_sframes_t frames = 0;
+ int err;
+
+ snd_pcm_status_alloca(&status);
+
+ pa_assert(u);
+ pa_assert(u->pcm_handle);
+
+ if ((err = snd_pcm_status(u->pcm_handle, status)) < 0)
+ pa_log("Failed to get delay: %s", snd_strerror(err));
+ else
+ frames = snd_pcm_status_get_delay(status);
+
+ if (frames > 0)
+ r = pa_bytes_to_usec(frames * u->frame_size, &u->source->sample_spec);
+
+ return r;
+}
+
+static int build_pollfd(struct userdata *u) {
+ int err;
+ struct pollfd *pollfd;
+ int n;
+
+ pa_assert(u);
+ pa_assert(u->pcm_handle);
+
+ if ((n = snd_pcm_poll_descriptors_count(u->pcm_handle)) < 0) {
+ pa_log("snd_pcm_poll_descriptors_count() failed: %s", snd_strerror(n));
+ return -1;
+ }
+
+ if (u->alsa_rtpoll_item)
+ pa_rtpoll_item_free(u->alsa_rtpoll_item);
+
+ u->alsa_rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, n);
+ pollfd = pa_rtpoll_item_get_pollfd(u->alsa_rtpoll_item, NULL);
+
+ if ((err = snd_pcm_poll_descriptors(u->pcm_handle, pollfd, n)) < 0) {
+ pa_log("snd_pcm_poll_descriptors() failed: %s", snd_strerror(err));
+ return -1;
+ }
+
+ return 0;
+}
+
+static int suspend(struct userdata *u) {
+ pa_assert(u);
+ pa_assert(u->pcm_handle);
+
+ /* Let's suspend */
+ snd_pcm_close(u->pcm_handle);
+ u->pcm_handle = NULL;
+
+ if (u->alsa_rtpoll_item) {
+ pa_rtpoll_item_free(u->alsa_rtpoll_item);
+ u->alsa_rtpoll_item = NULL;
+ }
+
+ pa_log_info("Device suspended...");
+
+ return 0;
+}
+
+static int unsuspend(struct userdata *u) {
+ pa_sample_spec ss;
+ int err;
+ pa_bool_t b;
+ unsigned nfrags;
+ snd_pcm_uframes_t period_size;
+
+ pa_assert(u);
+ pa_assert(!u->pcm_handle);
+
+ pa_log_info("Trying resume...");
+
+ snd_config_update_free_global();
+ if ((err = snd_pcm_open(&u->pcm_handle, u->device_name, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)) < 0) {
+ pa_log("Error opening PCM device %s: %s", u->device_name, snd_strerror(err));
+ goto fail;
+ }
+
+ ss = u->source->sample_spec;
+ nfrags = u->nfragments;
+ period_size = u->fragment_size / u->frame_size;
+ b = u->use_mmap;
+
+ if ((err = pa_alsa_set_hw_params(u->pcm_handle, &ss, &nfrags, &period_size, &b, TRUE)) < 0) {
+ pa_log("Failed to set hardware parameters: %s", snd_strerror(err));
+ goto fail;
+ }
+
+ if (b != u->use_mmap) {
+ pa_log_warn("Resume failed, couldn't get original access mode.");
+ goto fail;
+ }
+
+ if (!pa_sample_spec_equal(&ss, &u->source->sample_spec)) {
+ pa_log_warn("Resume failed, couldn't restore original sample settings.");
+ goto fail;
+ }
+
+ if (nfrags != u->nfragments || period_size*u->frame_size != u->fragment_size) {
+ pa_log_warn("Resume failed, couldn't restore original fragment settings.");
+ goto fail;
+ }
+
+ if ((err = pa_alsa_set_sw_params(u->pcm_handle)) < 0) {
+ pa_log("Failed to set software parameters: %s", snd_strerror(err));
+ goto fail;
+ }
+
+ if (build_pollfd(u) < 0)
+ goto fail;
+
+ snd_pcm_start(u->pcm_handle);
+
+ /* FIXME: We need to reload the volume somehow */
+
+ pa_log_info("Resumed successfully...");
+
+ return 0;
+
+fail:
+ if (u->pcm_handle) {
+ snd_pcm_close(u->pcm_handle);
+ u->pcm_handle = NULL;
+ }
+
+ return -1;
+}
+
+static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+ struct userdata *u = PA_SOURCE(o)->userdata;
+
+ switch (code) {
+
+ case PA_SOURCE_MESSAGE_GET_LATENCY: {
+ pa_usec_t r = 0;
+
+ if (u->pcm_handle)
+ r = source_get_latency(u);
+
+ *((pa_usec_t*) data) = r;
+
+ return 0;
+ }
+
+ case PA_SOURCE_MESSAGE_SET_STATE:
+
+ switch ((pa_source_state_t) PA_PTR_TO_UINT(data)) {
+
+ case PA_SOURCE_SUSPENDED:
+ pa_assert(PA_SOURCE_OPENED(u->source->thread_info.state));
+
+ if (suspend(u) < 0)
+ return -1;
+
+ break;
+
+ case PA_SOURCE_IDLE:
+ case PA_SOURCE_RUNNING:
+
+ if (u->source->thread_info.state == PA_SOURCE_INIT) {
+ if (build_pollfd(u) < 0)
+ return -1;
+
+ snd_pcm_start(u->pcm_handle);
+ }
+
+ if (u->source->thread_info.state == PA_SOURCE_SUSPENDED) {
+ if (unsuspend(u) < 0)
+ return -1;
+ }
+
+ break;
+
+ case PA_SOURCE_UNLINKED:
+ case PA_SOURCE_INIT:
+ ;
+ }
+
+ break;
+ }
+
+ return pa_source_process_msg(o, code, data, offset, chunk);
+}
+
+static int mixer_callback(snd_mixer_elem_t *elem, unsigned int mask) {
+ struct userdata *u = snd_mixer_elem_get_callback_private(elem);
+
+ pa_assert(u);
+ pa_assert(u->mixer_handle);
+
+ if (mask == SND_CTL_EVENT_MASK_REMOVE)
+ return 0;
+
+ if (mask & SND_CTL_EVENT_MASK_VALUE) {
+ pa_source_get_volume(u->source);
+ pa_source_get_mute(u->source);
+ }
+
+ return 0;
+}
+
+static int source_get_volume_cb(pa_source *s) {
+ struct userdata *u = s->userdata;
+ int err;
+ int i;
+
+ pa_assert(u);
+ pa_assert(u->mixer_elem);
+
+ for (i = 0; i < s->sample_spec.channels; i++) {
+ long set_vol, vol;
+
+ pa_assert(snd_mixer_selem_has_capture_channel(u->mixer_elem, u->mixer_map[i]));
+
+ if ((err = snd_mixer_selem_get_capture_volume(u->mixer_elem, u->mixer_map[i], &vol)) < 0)
+ goto fail;
+
+ set_vol = (long) roundf(((float) s->volume.values[i] * (u->hw_volume_max - u->hw_volume_min)) / PA_VOLUME_NORM) + u->hw_volume_min;
+
+ /* Try to avoid superfluous volume changes */
+ if (set_vol != vol)
+ s->volume.values[i] = (pa_volume_t) roundf(((float) (vol - u->hw_volume_min) * PA_VOLUME_NORM) / (u->hw_volume_max - u->hw_volume_min));
+ }
+
+ return 0;
+
+fail:
+ pa_log_error("Unable to read volume: %s", snd_strerror(err));
+
+ s->get_volume = NULL;
+ s->set_volume = NULL;
+ return -1;
+}
+
+static int source_set_volume_cb(pa_source *s) {
+ struct userdata *u = s->userdata;
+ int err;
+ int i;
+
+ pa_assert(u);
+ pa_assert(u->mixer_elem);
+
+ for (i = 0; i < s->sample_spec.channels; i++) {
+ long alsa_vol;
+ pa_volume_t vol;
+
+ pa_assert(snd_mixer_selem_has_capture_channel(u->mixer_elem, u->mixer_map[i]));
+
+ vol = s->volume.values[i];
+
+ if (vol > PA_VOLUME_NORM)
+ vol = PA_VOLUME_NORM;
+
+ alsa_vol = (long) roundf(((float) vol * (u->hw_volume_max - u->hw_volume_min)) / PA_VOLUME_NORM) + u->hw_volume_min;
+
+ if ((err = snd_mixer_selem_set_capture_volume(u->mixer_elem, u->mixer_map[i], alsa_vol)) < 0)
+ goto fail;
+ }
+
+ return 0;
+
+fail:
+ pa_log_error("Unable to set volume: %s", snd_strerror(err));
+
+ s->get_volume = NULL;
+ s->set_volume = NULL;
+ return -1;
+}
+
+static int source_get_mute_cb(pa_source *s) {
+ struct userdata *u = s->userdata;
+ int err, sw;
+
+ pa_assert(u);
+ pa_assert(u->mixer_elem);
+
+ if ((err = snd_mixer_selem_get_capture_switch(u->mixer_elem, 0, &sw)) < 0) {
+ pa_log_error("Unable to get switch: %s", snd_strerror(err));
+
+ s->get_mute = NULL;
+ s->set_mute = NULL;
+ return -1;
+ }
+
+ s->muted = !sw;
+
+ return 0;
+}
+
+static int source_set_mute_cb(pa_source *s) {
+ struct userdata *u = s->userdata;
+ int err;
+
+ pa_assert(u);
+ pa_assert(u->mixer_elem);
+
+ if ((err = snd_mixer_selem_set_capture_switch_all(u->mixer_elem, !s->muted)) < 0) {
+ pa_log_error("Unable to set switch: %s", snd_strerror(err));
+
+ s->get_mute = NULL;
+ s->set_mute = NULL;
+ return -1;
+ }
+
+ return 0;
+}
+
+static void thread_func(void *userdata) {
+ struct userdata *u = userdata;
+
+ pa_assert(u);
+
+ pa_log_debug("Thread starting up");
+
+ if (u->core->realtime_scheduling)
+ pa_make_realtime(u->core->realtime_priority);
+
+ pa_thread_mq_install(&u->thread_mq);
+ pa_rtpoll_install(u->rtpoll);
+
+ for (;;) {
+ int ret;
+
+ /* Read some data and pass it to the sources */
+ if (PA_SOURCE_OPENED(u->source->thread_info.state)) {
+
+ if (u->use_mmap) {
+ if (mmap_read(u) < 0)
+ goto fail;
+
+ } else {
+ if (unix_read(u) < 0)
+ goto fail;
+ }
+ }
+
+ /* Hmm, nothing to do. Let's sleep */
+ if ((ret = pa_rtpoll_run(u->rtpoll, 1)) < 0)
+ goto fail;
+
+ if (ret == 0)
+ goto finish;
+
+ /* Tell ALSA about this and process its response */
+ if (PA_SOURCE_OPENED(u->source->thread_info.state)) {
+ struct pollfd *pollfd;
+ unsigned short revents = 0;
+ int err;
+ unsigned n;
+
+ pollfd = pa_rtpoll_item_get_pollfd(u->alsa_rtpoll_item, &n);
+
+ if ((err = snd_pcm_poll_descriptors_revents(u->pcm_handle, pollfd, n, &revents)) < 0) {
+ pa_log("snd_pcm_poll_descriptors_revents() failed: %s", snd_strerror(err));
+ goto fail;
+ }
+
+ if (revents & (POLLERR|POLLNVAL|POLLHUP)) {
+
+ if (revents & POLLERR)
+ pa_log_warn("Got POLLERR from ALSA");
+ if (revents & POLLNVAL)
+ pa_log_warn("Got POLLNVAL from ALSA");
+ if (revents & POLLHUP)
+ pa_log_warn("Got POLLHUP from ALSA");
+
+ /* Try to recover from this error */
+
+ switch (snd_pcm_state(u->pcm_handle)) {
+
+ case SND_PCM_STATE_XRUN:
+ if ((err = snd_pcm_recover(u->pcm_handle, -EPIPE, 1)) != 0) {
+ pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP and XRUN: %s", snd_strerror(err));
+ goto fail;
+ }
+ break;
+
+ case SND_PCM_STATE_SUSPENDED:
+ if ((err = snd_pcm_recover(u->pcm_handle, -ESTRPIPE, 1)) != 0) {
+ pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP and SUSPENDED: %s", snd_strerror(err));
+ goto fail;
+ }
+ break;
+
+ default:
+
+ snd_pcm_drop(u->pcm_handle);
+
+ if ((err = snd_pcm_prepare(u->pcm_handle)) < 0) {
+ pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP with snd_pcm_prepare(): %s", snd_strerror(err));
+ goto fail;
+ }
+ break;
+ }
+ }
+ }
+ }
+
+fail:
+ /* If this was no regular exit from the loop we have to continue
+ * processing messages until we received PA_MESSAGE_SHUTDOWN */
+ pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL);
+ pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN);
+
+finish:
+ pa_log_debug("Thread shutting down");
+}
+
+int pa__init(pa_module*m) {
+
+ pa_modargs *ma = NULL;
+ struct userdata *u = NULL;
+ const char *dev_id;
+ pa_sample_spec ss;
+ pa_channel_map map;
+ uint32_t nfrags, frag_size;
+ snd_pcm_uframes_t period_size;
+ size_t frame_size;
+ snd_pcm_info_t *pcm_info = NULL;
+ int err;
+ char *t;
+ const char *name;
+ char *name_buf = NULL;
+ int namereg_fail;
+ pa_bool_t use_mmap = TRUE, b;
+
+ snd_pcm_info_alloca(&pcm_info);
+
+ pa_assert(m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log("Failed to parse module arguments");
+ goto fail;
+ }
+
+ ss = m->core->default_sample_spec;
+ if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_ALSA) < 0) {
+ pa_log("Failed to parse sample specification");
+ goto fail;
+ }
+
+ frame_size = pa_frame_size(&ss);
+
+ nfrags = m->core->default_n_fragments;
+ frag_size = pa_usec_to_bytes(m->core->default_fragment_size_msec*1000, &ss);
+ if (frag_size <= 0)
+ frag_size = frame_size;
+
+ if (pa_modargs_get_value_u32(ma, "fragments", &nfrags) < 0 || pa_modargs_get_value_u32(ma, "fragment_size", &frag_size) < 0) {
+ pa_log("Failed to parse buffer metrics");
+ goto fail;
+ }
+ period_size = frag_size/frame_size;
+
+ if (pa_modargs_get_value_boolean(ma, "mmap", &use_mmap) < 0) {
+ pa_log("Failed to parse mmap argument.");
+ goto fail;
+ }
+
+ u = pa_xnew0(struct userdata, 1);
+ u->core = m->core;
+ u->module = m;
+ m->userdata = u;
+ u->use_mmap = use_mmap;
+ pa_thread_mq_init(&u->thread_mq, m->core->mainloop);
+ u->rtpoll = pa_rtpoll_new();
+ u->alsa_rtpoll_item = NULL;
+ pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq);
+
+ snd_config_update_free_global();
+
+ b = use_mmap;
+
+ if ((dev_id = pa_modargs_get_value(ma, "device_id", NULL))) {
+
+ if (!(u->pcm_handle = pa_alsa_open_by_device_id(
+ dev_id,
+ &u->device_name,
+ &ss, &map,
+ SND_PCM_STREAM_CAPTURE,
+ &nfrags, &period_size,
+ &b)))
+ goto fail;
+
+ } else {
+
+ if (!(u->pcm_handle = pa_alsa_open_by_device_string(
+ pa_modargs_get_value(ma, "device", DEFAULT_DEVICE),
+ &u->device_name,
+ &ss, &map,
+ SND_PCM_STREAM_CAPTURE,
+ &nfrags, &period_size,
+ &b)))
+ goto fail;
+ }
+
+ pa_assert(u->device_name);
+ pa_log_info("Successfully opened device %s.", u->device_name);
+
+ if (use_mmap && !b) {
+ pa_log_info("Device doesn't support mmap(), falling back to UNIX read/write mode.");
+ u->use_mmap = use_mmap = b;
+ }
+
+ if (u->use_mmap)
+ pa_log_info("Successfully enabled mmap() mode.");
+
+ if ((err = snd_pcm_info(u->pcm_handle, pcm_info)) < 0) {
+ pa_log("Error fetching PCM info: %s", snd_strerror(err));
+ goto fail;
+ }
+
+ if ((err = pa_alsa_set_sw_params(u->pcm_handle)) < 0) {
+ pa_log("Failed to set software parameters: %s", snd_strerror(err));
+ goto fail;
+ }
+
+ /* ALSA might tweak the sample spec, so recalculate the frame size */
+ frame_size = pa_frame_size(&ss);
+
+ if ((err = snd_mixer_open(&u->mixer_handle, 0)) < 0)
+ pa_log("Error opening mixer: %s", snd_strerror(err));
+ else {
+ pa_bool_t found = FALSE;
+
+ if (pa_alsa_prepare_mixer(u->mixer_handle, u->device_name) >= 0)
+ found = TRUE;
+ else {
+ char *md = pa_sprintf_malloc("hw:%s", dev_id);
+
+ if (strcmp(u->device_name, md))
+ if (pa_alsa_prepare_mixer(u->mixer_handle, md) >= 0)
+ found = TRUE;
+
+ pa_xfree(md);
+ }
+
+ if (found)
+ if (!(u->mixer_elem = pa_alsa_find_elem(u->mixer_handle, "Capture", "Mic")))
+ found = FALSE;
+
+ if (!found) {
+ snd_mixer_close(u->mixer_handle);
+ u->mixer_handle = NULL;
+ }
+ }
+
+ if ((name = pa_modargs_get_value(ma, "source_name", NULL)))
+ namereg_fail = 1;
+ else {
+ name = name_buf = pa_sprintf_malloc("alsa_input.%s", u->device_name);
+ namereg_fail = 0;
+ }
+
+ u->source = pa_source_new(m->core, __FILE__, name, namereg_fail, &ss, &map);
+ pa_xfree(name_buf);
+
+ if (!u->source) {
+ pa_log("Failed to create source object");
+ goto fail;
+ }
+
+ u->source->parent.process_msg = source_process_msg;
+ u->source->userdata = u;
+
+ pa_source_set_module(u->source, m);
+ pa_source_set_asyncmsgq(u->source, u->thread_mq.inq);
+ pa_source_set_rtpoll(u->source, u->rtpoll);
+ pa_source_set_description(u->source, t = pa_sprintf_malloc(
+ "ALSA PCM on %s (%s)%s",
+ u->device_name,
+ snd_pcm_info_get_name(pcm_info),
+ use_mmap ? " via DMA" : ""));
+ pa_xfree(t);
+
+ u->source->flags = PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY;
+
+ u->frame_size = frame_size;
+ u->fragment_size = frag_size = period_size * frame_size;
+ u->nfragments = nfrags;
+ u->hwbuf_size = u->fragment_size * nfrags;
+
+ pa_log_info("Using %u fragments of size %lu bytes.", nfrags, (long unsigned) u->fragment_size);
+
+ if (u->mixer_handle) {
+ pa_assert(u->mixer_elem);
+
+ if (snd_mixer_selem_has_capture_volume(u->mixer_elem))
+ if (pa_alsa_calc_mixer_map(u->mixer_elem, &map, u->mixer_map, FALSE) >= 0) {
+ u->source->get_volume = source_get_volume_cb;
+ u->source->set_volume = source_set_volume_cb;
+ snd_mixer_selem_get_capture_volume_range(u->mixer_elem, &u->hw_volume_min, &u->hw_volume_max);
+ u->source->flags |= PA_SOURCE_HW_VOLUME_CTRL;
+ }
+
+ if (snd_mixer_selem_has_capture_switch(u->mixer_elem)) {
+ u->source->get_mute = source_get_mute_cb;
+ u->source->set_mute = source_set_mute_cb;
+ u->source->flags |= PA_SOURCE_HW_VOLUME_CTRL;
+ }
+
+ u->mixer_fdl = pa_alsa_fdlist_new();
+
+ if (pa_alsa_fdlist_set_mixer(u->mixer_fdl, u->mixer_handle, m->core->mainloop) < 0) {
+ pa_log("Failed to initialize file descriptor monitoring");
+ goto fail;
+ }
+
+ snd_mixer_elem_set_callback(u->mixer_elem, mixer_callback);
+ snd_mixer_elem_set_callback_private(u->mixer_elem, u);
+ } else
+ u->mixer_fdl = NULL;
+
+ if (!(u->thread = pa_thread_new(thread_func, u))) {
+ pa_log("Failed to create thread.");
+ goto fail;
+ }
+ /* Get initial mixer settings */
+ if (u->source->get_volume)
+ u->source->get_volume(u->source);
+ if (u->source->get_mute)
+ u->source->get_mute(u->source);
+
+ pa_source_put(u->source);
+
+ pa_modargs_free(ma);
+
+ return 0;
+
+fail:
+
+ if (ma)
+ pa_modargs_free(ma);
+
+ pa__done(m);
+
+ return -1;
+}
+
+void pa__done(pa_module*m) {
+ struct userdata *u;
+
+ pa_assert(m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->source)
+ pa_source_unlink(u->source);
+
+ if (u->thread) {
+ pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL);
+ pa_thread_free(u->thread);
+ }
+
+ pa_thread_mq_done(&u->thread_mq);
+
+ if (u->source)
+ pa_source_unref(u->source);
+
+ if (u->alsa_rtpoll_item)
+ pa_rtpoll_item_free(u->alsa_rtpoll_item);
+
+ if (u->rtpoll)
+ pa_rtpoll_free(u->rtpoll);
+
+ if (u->mixer_fdl)
+ pa_alsa_fdlist_free(u->mixer_fdl);
+
+ if (u->mixer_handle)
+ snd_mixer_close(u->mixer_handle);
+
+ if (u->pcm_handle) {
+ snd_pcm_drop(u->pcm_handle);
+ snd_pcm_close(u->pcm_handle);
+ }
+
+ pa_xfree(u->device_name);
+ pa_xfree(u);
+
+ snd_config_update_free_global();
+}
diff --git a/src/modules/module-bt-proximity.c b/src/modules/module-bt-proximity.c
new file mode 100644
index 00000000..62d530d4
--- /dev/null
+++ b/src/modules/module-bt-proximity.c
@@ -0,0 +1,492 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/module.h>
+#include <pulsecore/log.h>
+#include <pulsecore/namereg.h>
+#include <pulsecore/sink.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/core-error.h>
+#include <pulsecore/start-child.h>
+
+#include "dbus-util.h"
+#include "module-bt-proximity-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering");
+PA_MODULE_DESCRIPTION("Bluetooth Proximity Volume Control");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(TRUE);
+PA_MODULE_USAGE(
+ "sink=<sink name> "
+ "hci=<hci device> "
+);
+
+#define DEFAULT_HCI "hci0"
+
+static const char* const valid_modargs[] = {
+ "sink",
+ "rssi",
+ "hci",
+ NULL,
+};
+
+struct bonding {
+ struct userdata *userdata;
+ char address[18];
+
+ pid_t pid;
+ int fd;
+
+ pa_io_event *io_event;
+
+ enum {
+ UNKNOWN,
+ FOUND,
+ NOT_FOUND
+ } state;
+};
+
+struct userdata {
+ pa_module *module;
+ pa_dbus_connection *dbus_connection;
+
+ char *sink_name;
+ char *hci, *hci_path;
+
+ pa_hashmap *bondings;
+
+ unsigned n_found;
+ unsigned n_unknown;
+
+ pa_bool_t muted;
+};
+
+static void update_volume(struct userdata *u) {
+ pa_assert(u);
+
+ if (u->muted && u->n_found > 0) {
+ pa_sink *s;
+
+ u->muted = FALSE;
+
+ if (!(s = pa_namereg_get(u->module->core, u->sink_name, PA_NAMEREG_SINK, FALSE))) {
+ pa_log_warn("Sink device '%s' not available for unmuting.", pa_strnull(u->sink_name));
+ return;
+ }
+
+ pa_log_info("Found %u BT devices, unmuting.", u->n_found);
+ pa_sink_set_mute(s, FALSE);
+
+ } else if (!u->muted && (u->n_found+u->n_unknown) <= 0) {
+ pa_sink *s;
+
+ u->muted = TRUE;
+
+ if (!(s = pa_namereg_get(u->module->core, u->sink_name, PA_NAMEREG_SINK, FALSE))) {
+ pa_log_warn("Sink device '%s' not available for muting.", pa_strnull(u->sink_name));
+ return;
+ }
+
+ pa_log_info("No BT devices found, muting.");
+ pa_sink_set_mute(s, TRUE);
+
+ } else
+ pa_log_info("%u devices now active, %u with unknown state.", u->n_found, u->n_unknown);
+}
+
+static void bonding_free(struct bonding *b) {
+ pa_assert(b);
+
+ if (b->state == FOUND)
+ pa_assert_se(b->userdata->n_found-- >= 1);
+
+ if (b->state == UNKNOWN)
+ pa_assert_se(b->userdata->n_unknown-- >= 1);
+
+ if (b->pid != (pid_t) -1) {
+ kill(b->pid, SIGTERM);
+ waitpid(b->pid, NULL, 0);
+ }
+
+ if (b->fd >= 0)
+ pa_close(b->fd);
+
+ if (b->io_event)
+ b->userdata->module->core->mainloop->io_free(b->io_event);
+
+ pa_xfree(b);
+}
+
+static void io_event_cb(
+ pa_mainloop_api*a,
+ pa_io_event* e,
+ int fd,
+ pa_io_event_flags_t events,
+ void *userdata) {
+
+ struct bonding *b = userdata;
+ char x;
+ ssize_t r;
+
+ pa_assert(b);
+
+ if ((r = read(fd, &x, 1)) <= 0) {
+ pa_log_warn("Child watching '%s' died abnormally: %s", b->address, r == 0 ? "EOF" : pa_cstrerror(errno));
+
+ pa_assert_se(pa_hashmap_remove(b->userdata->bondings, b->address) == b);
+ bonding_free(b);
+ return;
+ }
+
+ pa_assert_se(r == 1);
+
+ if (b->state == UNKNOWN)
+ pa_assert_se(b->userdata->n_unknown-- >= 1);
+
+ if (x == '+') {
+ pa_assert(b->state == UNKNOWN || b->state == NOT_FOUND);
+
+ b->state = FOUND;
+ b->userdata->n_found++;
+
+ pa_log_info("Device '%s' is alive.", b->address);
+
+ } else {
+ pa_assert(x == '-');
+ pa_assert(b->state == UNKNOWN || b->state == FOUND);
+
+ if (b->state == FOUND)
+ b->userdata->n_found--;
+
+ b->state = NOT_FOUND;
+
+ pa_log_info("Device '%s' is dead.", b->address);
+ }
+
+ update_volume(b->userdata);
+}
+
+static struct bonding* bonding_new(struct userdata *u, const char *a) {
+ struct bonding *b = NULL;
+ DBusMessage *m = NULL, *r = NULL;
+ DBusError e;
+ const char *class;
+
+ pa_assert(u);
+ pa_assert(a);
+
+ pa_return_val_if_fail(strlen(a) == 17, NULL);
+ pa_return_val_if_fail(!pa_hashmap_get(u->bondings, a), NULL);
+
+ dbus_error_init(&e);
+
+ pa_assert_se(m = dbus_message_new_method_call("org.bluez", u->hci_path, "org.bluez.Adapter", "GetRemoteMajorClass"));
+ pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_STRING, &a, DBUS_TYPE_INVALID));
+ r = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(u->dbus_connection), m, -1, &e);
+
+ if (!r) {
+ pa_log("org.bluez.Adapter.GetRemoteMajorClass(%s) failed: %s", a, e.message);
+ goto fail;
+ }
+
+ if (!(dbus_message_get_args(r, &e, DBUS_TYPE_STRING, &class, DBUS_TYPE_INVALID))) {
+ pa_log("Malformed org.bluez.Adapter.GetRemoteMajorClass signal: %s", e.message);
+ goto fail;
+ }
+
+ if (strcmp(class, "phone")) {
+ pa_log_info("Found device '%s' of class '%s', ignoring.", a, class);
+ goto fail;
+ }
+
+ b = pa_xnew(struct bonding, 1);
+ b->userdata = u;
+ pa_strlcpy(b->address, a, sizeof(b->address));
+ b->pid = (pid_t) -1;
+ b->fd = -1;
+ b->io_event = NULL;
+ b->state = UNKNOWN;
+ u->n_unknown ++;
+
+ pa_log_info("Watching device '%s' of class '%s'.", b->address, class);
+
+ if ((b->fd = pa_start_child_for_read(PA_BT_PROXIMITY_HELPER, a, &b->pid)) < 0) {
+ pa_log("Failed to start helper tool.");
+ goto fail;
+ }
+
+ b->io_event = u->module->core->mainloop->io_new(
+ u->module->core->mainloop,
+ b->fd,
+ PA_IO_EVENT_INPUT,
+ io_event_cb,
+ b);
+
+ dbus_message_unref(m);
+ dbus_message_unref(r);
+
+ pa_hashmap_put(u->bondings, b->address, b);
+
+ return b;
+
+fail:
+ if (m)
+ dbus_message_unref(m);
+ if (r)
+ dbus_message_unref(r);
+
+ if (b)
+ bonding_free(b);
+
+ dbus_error_free(&e);
+ return NULL;
+}
+
+static void bonding_remove(struct userdata *u, const char *a) {
+ struct bonding *b;
+ pa_assert(u);
+
+ pa_return_if_fail((b = pa_hashmap_remove(u->bondings, a)));
+
+ pa_log_info("No longer watching device '%s'", b->address);
+ bonding_free(b);
+}
+
+static DBusHandlerResult filter_func(DBusConnection *connection, DBusMessage *m, void *userdata) {
+ struct userdata *u = userdata;
+ DBusError e;
+
+ dbus_error_init(&e);
+
+ if (dbus_message_is_signal(m, "org.bluez.Adapter", "BondingCreated")) {
+ const char *a;
+
+ if (!(dbus_message_get_args(m, &e, DBUS_TYPE_STRING, &a, DBUS_TYPE_INVALID))) {
+ pa_log("Malformed org.bluez.Adapter.BondingCreated signal: %s", e.message);
+ goto finish;
+ }
+
+ bonding_new(u, a);
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+
+ } else if (dbus_message_is_signal(m, "org.bluez.Adapter", "BondingRemoved")) {
+
+ const char *a;
+
+ if (!(dbus_message_get_args(m, &e, DBUS_TYPE_STRING, &a, DBUS_TYPE_INVALID))) {
+ pa_log("Malformed org.bluez.Adapter.BondingRemoved signal: %s", e.message);
+ goto finish;
+ }
+
+ bonding_remove(u, a);
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+ }
+
+finish:
+
+ dbus_error_free(&e);
+
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+static int add_matches(struct userdata *u, pa_bool_t add) {
+ char *filter1, *filter2;
+ DBusError e;
+ int r = -1;
+
+ pa_assert(u);
+ dbus_error_init(&e);
+
+ filter1 = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='BondingCreated',path='%s'", u->hci_path);
+ filter2 = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='BondingRemoved',path='%s'", u->hci_path);
+
+ if (add) {
+ dbus_bus_add_match(pa_dbus_connection_get(u->dbus_connection), filter1, &e);
+
+ if (dbus_error_is_set(&e)) {
+ pa_log("dbus_bus_add_match(%s) failed: %s", filter1, e.message);
+ goto finish;
+ }
+ } else
+ dbus_bus_remove_match(pa_dbus_connection_get(u->dbus_connection), filter1, &e);
+
+
+ if (add) {
+ dbus_bus_add_match(pa_dbus_connection_get(u->dbus_connection), filter2, &e);
+
+ if (dbus_error_is_set(&e)) {
+ pa_log("dbus_bus_add_match(%s) failed: %s", filter2, e.message);
+ dbus_bus_remove_match(pa_dbus_connection_get(u->dbus_connection), filter2, &e);
+ goto finish;
+ }
+ } else
+ dbus_bus_remove_match(pa_dbus_connection_get(u->dbus_connection), filter2, &e);
+
+
+ if (add)
+ pa_assert_se(dbus_connection_add_filter(pa_dbus_connection_get(u->dbus_connection), filter_func, u, NULL));
+ else
+ dbus_connection_remove_filter(pa_dbus_connection_get(u->dbus_connection), filter_func, u);
+
+ r = 0;
+
+finish:
+ pa_xfree(filter1);
+ pa_xfree(filter2);
+ dbus_error_free(&e);
+
+ return r;
+}
+
+int pa__init(pa_module*m) {
+ pa_modargs *ma = NULL;
+ struct userdata *u;
+ DBusError e;
+ DBusMessage *msg = NULL, *r = NULL;
+ DBusMessageIter iter, sub;
+
+ pa_assert(m);
+ dbus_error_init(&e);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log("Failed to parse module arguments");
+ goto fail;
+ }
+
+ m->userdata = u = pa_xnew0(struct userdata, 1);
+ u->module = m;
+ u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL));
+ u->hci = pa_xstrdup(pa_modargs_get_value(ma, "hci", DEFAULT_HCI));
+ u->hci_path = pa_sprintf_malloc("/org/bluez/%s", u->hci);
+ u->n_found = u->n_unknown = 0;
+ u->muted = FALSE;
+
+ u->bondings = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+
+ if (!(u->dbus_connection = pa_dbus_bus_get(m->core, DBUS_BUS_SYSTEM, &e))) {
+ pa_log("Failed to get D-Bus connection: %s", e.message);
+ goto fail;
+ }
+
+ if (add_matches(u, TRUE) < 0)
+ goto fail;
+
+ pa_assert_se(msg = dbus_message_new_method_call("org.bluez", u->hci_path, "org.bluez.Adapter", "ListBondings"));
+
+ if (!(r = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(u->dbus_connection), msg, -1, &e))) {
+ pa_log("org.bluez.Adapter.ListBondings failed: %s", e.message);
+ goto fail;
+ }
+
+ dbus_message_iter_init(r, &iter);
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
+ pa_log("Malformed reply to org.bluez.Adapter.ListBondings.");
+ goto fail;
+ }
+
+ dbus_message_iter_recurse(&iter, &sub);
+
+ while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING) {
+ const char *a = NULL;
+
+ dbus_message_iter_get_basic(&sub, &a);
+ bonding_new(u, a);
+
+ dbus_message_iter_next(&sub);
+ }
+
+ dbus_message_unref(r);
+ dbus_message_unref(msg);
+
+ pa_modargs_free(ma);
+
+ if (pa_hashmap_size(u->bondings) == 0)
+ pa_log_warn("Warning: no phone device bonded.");
+
+ update_volume(u);
+
+ return 0;
+
+fail:
+
+ if (ma)
+ pa_modargs_free(ma);
+
+ pa__done(m);
+
+ dbus_error_free(&e);
+
+ if (msg)
+ dbus_message_unref(msg);
+
+ if (r)
+ dbus_message_unref(r);
+
+ return -1;
+}
+
+void pa__done(pa_module*m) {
+ struct userdata *u;
+ pa_assert(m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->bondings) {
+ struct bonding *b;
+
+ while ((b = pa_hashmap_steal_first(u->bondings)))
+ bonding_free(b);
+
+ pa_hashmap_free(u->bondings, NULL, NULL);
+ }
+
+ if (u->dbus_connection) {
+ add_matches(u, FALSE);
+ pa_dbus_connection_unref(u->dbus_connection);
+ }
+
+ pa_xfree(u->sink_name);
+ pa_xfree(u->hci_path);
+ pa_xfree(u->hci);
+ pa_xfree(u);
+}
diff --git a/src/modules/module-cli.c b/src/modules/module-cli.c
new file mode 100644
index 00000000..ab311a82
--- /dev/null
+++ b/src/modules/module-cli.c
@@ -0,0 +1,123 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <unistd.h>
+
+#include <pulsecore/module.h>
+#include <pulsecore/iochannel.h>
+#include <pulsecore/cli.h>
+#include <pulsecore/sioman.h>
+#include <pulsecore/log.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/macro.h>
+
+#include "module-cli-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering");
+PA_MODULE_DESCRIPTION("Command line interface");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(TRUE);
+PA_MODULE_USAGE("exit_on_eof=<exit daemon after EOF?>");
+
+static const char* const valid_modargs[] = {
+ "exit_on_eof",
+ NULL
+};
+
+static void eof_and_unload_cb(pa_cli*c, void *userdata) {
+ pa_module *m = userdata;
+
+ pa_assert(c);
+ pa_assert(m);
+
+ pa_module_unload_request(m);
+}
+
+static void eof_and_exit_cb(pa_cli*c, void *userdata) {
+ pa_module *m = userdata;
+
+ pa_assert(c);
+ pa_assert(m);
+
+ m->core->mainloop->quit(m->core->mainloop, 0);
+}
+
+int pa__init(pa_module*m) {
+ pa_iochannel *io;
+ pa_modargs *ma;
+ pa_bool_t exit_on_eof = FALSE;
+
+ pa_assert(m);
+
+ if (m->core->running_as_daemon) {
+ pa_log_info("Running as daemon, refusing to load this module.");
+ return 0;
+ }
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log("failed to parse module arguments.");
+ goto fail;
+ }
+
+ if (pa_modargs_get_value_boolean(ma, "exit_on_eof", &exit_on_eof) < 0) {
+ pa_log("exit_on_eof= expects boolean argument.");
+ goto fail;
+ }
+
+ if (pa_stdio_acquire() < 0) {
+ pa_log("STDIN/STDUSE already in use.");
+ goto fail;
+ }
+
+ io = pa_iochannel_new(m->core->mainloop, STDIN_FILENO, STDOUT_FILENO);
+ pa_iochannel_set_noclose(io, 1);
+
+ m->userdata = pa_cli_new(m->core, io, m);
+
+ pa_cli_set_eof_callback(m->userdata, exit_on_eof ? eof_and_exit_cb : eof_and_unload_cb, m);
+
+ pa_modargs_free(ma);
+
+ return 0;
+
+fail:
+
+ if (ma)
+ pa_modargs_free(ma);
+
+ return -1;
+}
+
+void pa__done(pa_module*m) {
+ pa_assert(m);
+
+ if (m->core->running_as_daemon == 0) {
+ pa_cli_free(m->userdata);
+ pa_stdio_release();
+ }
+}
diff --git a/src/modules/module-combine.c b/src/modules/module-combine.c
new file mode 100644
index 00000000..996cd4f6
--- /dev/null
+++ b/src/modules/module-combine.c
@@ -0,0 +1,1193 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+
+#include <pulse/timeval.h>
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/macro.h>
+#include <pulsecore/module.h>
+#include <pulsecore/llist.h>
+#include <pulsecore/sink.h>
+#include <pulsecore/sink-input.h>
+#include <pulsecore/memblockq.h>
+#include <pulsecore/log.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/namereg.h>
+#include <pulsecore/mutex.h>
+#include <pulsecore/thread.h>
+#include <pulsecore/thread-mq.h>
+#include <pulsecore/rtpoll.h>
+#include <pulsecore/rtclock.h>
+#include <pulsecore/core-error.h>
+
+#include "module-combine-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering");
+PA_MODULE_DESCRIPTION("Combine multiple sinks to one");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(FALSE);
+PA_MODULE_USAGE(
+ "sink_name=<name for the sink> "
+ "master=<master sink> "
+ "slaves=<slave sinks> "
+ "adjust_time=<seconds> "
+ "resample_method=<method> "
+ "format=<sample format> "
+ "channels=<number of channels> "
+ "rate=<sample rate> "
+ "channel_map=<channel map>");
+
+#define DEFAULT_SINK_NAME "combined"
+#define MEMBLOCKQ_MAXLENGTH (1024*170)
+
+#define DEFAULT_ADJUST_TIME 10
+
+static const char* const valid_modargs[] = {
+ "sink_name",
+ "master",
+ "slaves",
+ "adjust_time",
+ "resample_method",
+ "format",
+ "channels",
+ "rate",
+ "channel_map",
+ NULL
+};
+
+struct output {
+ struct userdata *userdata;
+
+ pa_sink *sink;
+ pa_sink_input *sink_input;
+
+ pa_asyncmsgq *inq, /* Message queue from the sink thread to this sink input */
+ *outq; /* Message queue from this sink input to the sink thread */
+ pa_rtpoll_item *inq_rtpoll_item, *outq_rtpoll_item;
+
+ pa_memblockq *memblockq;
+
+ pa_usec_t total_latency;
+
+ PA_LLIST_FIELDS(struct output);
+};
+
+struct userdata {
+ pa_core *core;
+ pa_module *module;
+ pa_sink *sink;
+
+ pa_thread *thread;
+ pa_thread_mq thread_mq;
+ pa_rtpoll *rtpoll;
+
+ pa_time_event *time_event;
+ uint32_t adjust_time;
+
+ pa_bool_t automatic;
+ size_t block_size;
+
+ pa_hook_slot *sink_new_slot, *sink_unlink_slot, *sink_state_changed_slot;
+
+ pa_resample_method_t resample_method;
+
+ struct timeval adjust_timestamp;
+
+ struct output *master;
+ pa_idxset* outputs; /* managed in main context */
+
+ struct {
+ PA_LLIST_HEAD(struct output, active_outputs); /* managed in IO thread context */
+ pa_atomic_t running; /* we cache that value here, so that every thread can query it cheaply */
+ struct timeval timestamp;
+ pa_bool_t in_null_mode;
+ } thread_info;
+};
+
+enum {
+ SINK_MESSAGE_ADD_OUTPUT = PA_SINK_MESSAGE_MAX,
+ SINK_MESSAGE_REMOVE_OUTPUT,
+ SINK_MESSAGE_NEED
+};
+
+enum {
+ SINK_INPUT_MESSAGE_POST = PA_SINK_INPUT_MESSAGE_MAX
+};
+
+static void output_free(struct output *o);
+static int output_create_sink_input(struct output *o);
+static void update_master(struct userdata *u, struct output *o);
+static void pick_master(struct userdata *u, struct output *except);
+
+static void adjust_rates(struct userdata *u) {
+ struct output *o;
+ pa_usec_t max_sink_latency = 0, min_total_latency = (pa_usec_t) -1, target_latency;
+ uint32_t base_rate;
+ uint32_t idx;
+
+ pa_assert(u);
+ pa_sink_assert_ref(u->sink);
+
+ if (pa_idxset_size(u->outputs) <= 0)
+ return;
+
+ if (!u->master)
+ return;
+
+ if (!PA_SINK_OPENED(pa_sink_get_state(u->sink)))
+ return;
+
+ for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) {
+ pa_usec_t sink_latency;
+
+ if (!o->sink_input || !PA_SINK_OPENED(pa_sink_get_state(o->sink)))
+ continue;
+
+ sink_latency = pa_sink_get_latency(o->sink);
+ o->total_latency = sink_latency + pa_sink_input_get_latency(o->sink_input);
+
+ if (sink_latency > max_sink_latency)
+ max_sink_latency = sink_latency;
+
+ if (min_total_latency == (pa_usec_t) -1 || o->total_latency < min_total_latency)
+ min_total_latency = o->total_latency;
+ }
+
+ if (min_total_latency == (pa_usec_t) -1)
+ return;
+
+ target_latency = max_sink_latency > min_total_latency ? max_sink_latency : min_total_latency;
+
+ pa_log_info("[%s] target latency is %0.0f usec.", u->sink->name, (float) target_latency);
+ pa_log_info("[%s] master %s latency %0.0f usec.", u->sink->name, u->master->sink->name, (float) u->master->total_latency);
+
+ base_rate = u->sink->sample_spec.rate;
+
+ for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) {
+ uint32_t r = base_rate;
+
+ if (!o->sink_input || !PA_SINK_OPENED(pa_sink_get_state(o->sink)))
+ continue;
+
+ if (o->total_latency < target_latency)
+ r -= (uint32_t) (((((double) target_latency - o->total_latency))/u->adjust_time)*r/PA_USEC_PER_SEC);
+ else if (o->total_latency > target_latency)
+ r += (uint32_t) (((((double) o->total_latency - target_latency))/u->adjust_time)*r/PA_USEC_PER_SEC);
+
+ if (r < (uint32_t) (base_rate*0.9) || r > (uint32_t) (base_rate*1.1)) {
+ pa_log_warn("[%s] sample rates too different, not adjusting (%u vs. %u).", o->sink_input->name, base_rate, r);
+ pa_sink_input_set_rate(o->sink_input, base_rate);
+ } else {
+ pa_log_info("[%s] new rate is %u Hz; ratio is %0.3f; latency is %0.0f usec.", o->sink_input->name, r, (double) r / base_rate, (float) o->total_latency);
+ pa_sink_input_set_rate(o->sink_input, r);
+ }
+ }
+}
+
+static void time_callback(pa_mainloop_api*a, pa_time_event* e, const struct timeval *tv, void *userdata) {
+ struct userdata *u = userdata;
+ struct timeval n;
+
+ pa_assert(u);
+ pa_assert(a);
+ pa_assert(u->time_event == e);
+
+ adjust_rates(u);
+
+ pa_gettimeofday(&n);
+ n.tv_sec += u->adjust_time;
+ u->sink->core->mainloop->time_restart(e, &n);
+}
+
+static void thread_func(void *userdata) {
+ struct userdata *u = userdata;
+
+ pa_assert(u);
+
+ pa_log_debug("Thread starting up");
+
+ if (u->core->realtime_scheduling)
+ pa_make_realtime(u->core->realtime_priority+1);
+
+ pa_thread_mq_install(&u->thread_mq);
+ pa_rtpoll_install(u->rtpoll);
+
+ pa_rtclock_get(&u->thread_info.timestamp);
+ u->thread_info.in_null_mode = FALSE;
+
+ for (;;) {
+ int ret;
+
+ /* If no outputs are connected, render some data and drop it immediately. */
+ if (u->sink->thread_info.state == PA_SINK_RUNNING && !u->thread_info.active_outputs) {
+ struct timeval now;
+
+ pa_rtclock_get(&now);
+
+ if (!u->thread_info.in_null_mode || pa_timeval_cmp(&u->thread_info.timestamp, &now) <= 0) {
+ pa_sink_skip(u->sink, u->block_size);
+
+ if (!u->thread_info.in_null_mode)
+ u->thread_info.timestamp = now;
+
+ pa_timeval_add(&u->thread_info.timestamp, pa_bytes_to_usec(u->block_size, &u->sink->sample_spec));
+ }
+
+ pa_rtpoll_set_timer_absolute(u->rtpoll, &u->thread_info.timestamp);
+ u->thread_info.in_null_mode = TRUE;
+
+ } else {
+ pa_rtpoll_set_timer_disabled(u->rtpoll);
+ u->thread_info.in_null_mode = FALSE;
+ }
+
+ /* Hmm, nothing to do. Let's sleep */
+ if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) {
+ pa_log_info("pa_rtpoll_run() = %i", ret);
+ goto fail;
+ }
+
+ if (ret == 0)
+ goto finish;
+ }
+
+fail:
+ /* If this was no regular exit from the loop we have to continue
+ * processing messages until we received PA_MESSAGE_SHUTDOWN */
+ pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL);
+ pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN);
+
+finish:
+ pa_log_debug("Thread shutting down");
+}
+
+/* Called from I/O thread context */
+static void render_memblock(struct userdata *u, struct output *o, size_t length) {
+ pa_assert(u);
+ pa_assert(o);
+
+ /* We are run by the sink thread, on behalf of an output (o). The
+ * other output is waiting for us, hence it is safe to access its
+ * mainblockq and asyncmsgq directly. */
+
+ /* If we are not running, we cannot produce any data */
+ if (!pa_atomic_load(&u->thread_info.running))
+ return;
+
+ /* Maybe there's some data in the requesting output's queue
+ * now? */
+ while (pa_asyncmsgq_process_one(o->inq) > 0)
+ ;
+
+ /* Ok, now let's prepare some data if we really have to */
+ while (!pa_memblockq_is_readable(o->memblockq)) {
+ struct output *j;
+ pa_memchunk chunk;
+
+ /* Render data! */
+ pa_sink_render(u->sink, length, &chunk);
+
+ /* OK, let's send this data to the other threads */
+ for (j = u->thread_info.active_outputs; j; j = j->next)
+
+ /* Send to other outputs, which are not the requesting
+ * one */
+
+ if (j != o)
+ pa_asyncmsgq_post(j->inq, PA_MSGOBJECT(j->sink_input), SINK_INPUT_MESSAGE_POST, NULL, 0, &chunk, NULL);
+
+ /* And place it directly into the requesting output's queue */
+ if (o)
+ pa_memblockq_push_align(o->memblockq, &chunk);
+
+ pa_memblock_unref(chunk.memblock);
+ }
+}
+
+/* Called from I/O thread context */
+static void request_memblock(struct output *o, size_t length) {
+ pa_assert(o);
+ pa_sink_input_assert_ref(o->sink_input);
+ pa_sink_assert_ref(o->userdata->sink);
+
+ /* If another thread already prepared some data we received
+ * the data over the asyncmsgq, hence let's first process
+ * it. */
+ while (pa_asyncmsgq_process_one(o->inq) > 0)
+ ;
+
+ /* Check whether we're now readable */
+ if (pa_memblockq_is_readable(o->memblockq))
+ return;
+
+ /* OK, we need to prepare new data, but only if the sink is actually running */
+ if (pa_atomic_load(&o->userdata->thread_info.running))
+ pa_asyncmsgq_send(o->outq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_NEED, o, length, NULL);
+}
+
+/* Called from I/O thread context */
+static int sink_input_peek_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) {
+ struct output *o;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(o = i->userdata);
+
+ /* If necessary, get some new data */
+ request_memblock(o, length);
+
+ return pa_memblockq_peek(o->memblockq, chunk);
+}
+
+/* Called from I/O thread context */
+static void sink_input_drop_cb(pa_sink_input *i, size_t length) {
+ struct output *o;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert(length > 0);
+ pa_assert_se(o = i->userdata);
+
+ pa_memblockq_drop(o->memblockq, length);
+}
+
+/* Called from I/O thread context */
+static void sink_input_attach_cb(pa_sink_input *i) {
+ struct output *o;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(o = i->userdata);
+
+ /* Set up the queue from the sink thread to us */
+ pa_assert(!o->inq_rtpoll_item);
+ o->inq_rtpoll_item = pa_rtpoll_item_new_asyncmsgq(
+ i->sink->rtpoll,
+ PA_RTPOLL_LATE, /* This one is not that important, since we check for data in _peek() anyway. */
+ o->inq);
+}
+
+/* Called from I/O thread context */
+static void sink_input_detach_cb(pa_sink_input *i) {
+ struct output *o;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(o = i->userdata);
+
+ /* Shut down the queue from the sink thread to us */
+ pa_assert(o->inq_rtpoll_item);
+ pa_rtpoll_item_free(o->inq_rtpoll_item);
+ o->inq_rtpoll_item = NULL;
+}
+
+/* Called from main context */
+static void sink_input_kill_cb(pa_sink_input *i) {
+ struct output *o;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert(o = i->userdata);
+
+ pa_module_unload_request(o->userdata->module);
+ output_free(o);
+}
+
+/* Called from thread context */
+static int sink_input_process_msg(pa_msgobject *obj, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+ struct output *o = PA_SINK_INPUT(obj)->userdata;
+
+ switch (code) {
+
+ case PA_SINK_INPUT_MESSAGE_GET_LATENCY: {
+ pa_usec_t *r = data;
+
+ *r = pa_bytes_to_usec(pa_memblockq_get_length(o->memblockq), &o->sink_input->sample_spec);
+
+ /* Fall through, the default handler will add in the extra
+ * latency added by the resampler */
+ break;
+ }
+
+ case SINK_INPUT_MESSAGE_POST:
+
+ if (PA_SINK_OPENED(o->sink_input->sink->thread_info.state))
+ pa_memblockq_push_align(o->memblockq, chunk);
+ else
+ pa_memblockq_flush(o->memblockq);
+
+ break;
+ }
+
+ return pa_sink_input_process_msg(obj, code, data, offset, chunk);
+}
+
+/* Called from main context */
+static void disable_output(struct output *o) {
+ pa_assert(o);
+
+ if (!o->sink_input)
+ return;
+
+ pa_asyncmsgq_send(o->userdata->sink->asyncmsgq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_REMOVE_OUTPUT, o, 0, NULL);
+ pa_sink_input_unlink(o->sink_input);
+ pa_sink_input_unref(o->sink_input);
+ o->sink_input = NULL;
+
+}
+
+/* Called from main context */
+static void enable_output(struct output *o) {
+ pa_assert(o);
+
+ if (o->sink_input)
+ return;
+
+ if (output_create_sink_input(o) >= 0) {
+
+ pa_memblockq_flush(o->memblockq);
+
+ pa_sink_input_put(o->sink_input);
+
+ if (o->userdata->sink && PA_SINK_LINKED(pa_sink_get_state(o->userdata->sink)))
+ pa_asyncmsgq_send(o->userdata->sink->asyncmsgq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_ADD_OUTPUT, o, 0, NULL);
+ }
+}
+
+/* Called from main context */
+static void suspend(struct userdata *u) {
+ struct output *o;
+ uint32_t idx;
+
+ pa_assert(u);
+
+ /* Let's suspend by unlinking all streams */
+ for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx))
+ disable_output(o);
+
+ pick_master(u, NULL);
+
+ pa_log_info("Device suspended...");
+}
+
+/* Called from main context */
+static void unsuspend(struct userdata *u) {
+ struct output *o;
+ uint32_t idx;
+
+ pa_assert(u);
+
+ /* Let's resume */
+ for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) {
+
+ pa_sink_suspend(o->sink, FALSE);
+
+ if (PA_SINK_OPENED(pa_sink_get_state(o->sink)))
+ enable_output(o);
+ }
+
+ pick_master(u, NULL);
+
+ pa_log_info("Resumed successfully...");
+}
+
+/* Called from main context */
+static int sink_set_state(pa_sink *sink, pa_sink_state_t state) {
+ struct userdata *u;
+
+ pa_sink_assert_ref(sink);
+ pa_assert_se(u = sink->userdata);
+
+ /* Please note that in contrast to the ALSA modules we call
+ * suspend/unsuspend from main context here! */
+
+ switch (state) {
+ case PA_SINK_SUSPENDED:
+ pa_assert(PA_SINK_OPENED(pa_sink_get_state(u->sink)));
+
+ suspend(u);
+ break;
+
+ case PA_SINK_IDLE:
+ case PA_SINK_RUNNING:
+
+ if (pa_sink_get_state(u->sink) == PA_SINK_SUSPENDED)
+ unsuspend(u);
+
+ break;
+
+ case PA_SINK_UNLINKED:
+ case PA_SINK_INIT:
+ ;
+ }
+
+ return 0;
+}
+
+/* Called from thread context of the master */
+static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+ struct userdata *u = PA_SINK(o)->userdata;
+
+ switch (code) {
+
+ case PA_SINK_MESSAGE_SET_STATE:
+ pa_atomic_store(&u->thread_info.running, PA_PTR_TO_UINT(data) == PA_SINK_RUNNING);
+ break;
+
+ case PA_SINK_MESSAGE_GET_LATENCY:
+
+ /* This code will only be called when running in NULL
+ * mode, i.e. when no output is attached. See
+ * sink_get_latency_cb() below */
+
+ if (u->thread_info.in_null_mode) {
+ struct timeval now;
+
+ if (pa_timeval_cmp(&u->thread_info.timestamp, pa_rtclock_get(&now)) > 0) {
+ *((pa_usec_t*) data) = pa_timeval_diff(&u->thread_info.timestamp, &now);
+ break;
+ }
+ }
+
+ *((pa_usec_t*) data) = 0;
+
+ break;
+
+ case SINK_MESSAGE_ADD_OUTPUT: {
+ struct output *op = data;
+
+ PA_LLIST_PREPEND(struct output, u->thread_info.active_outputs, op);
+
+ pa_assert(!op->outq_rtpoll_item);
+
+ /* Create pa_asyncmsgq to the sink thread */
+
+ op->outq_rtpoll_item = pa_rtpoll_item_new_asyncmsgq(
+ u->rtpoll,
+ PA_RTPOLL_EARLY-1, /* This item is very important */
+ op->outq);
+
+ return 0;
+ }
+
+ case SINK_MESSAGE_REMOVE_OUTPUT: {
+ struct output *op = data;
+
+ PA_LLIST_REMOVE(struct output, u->thread_info.active_outputs, op);
+
+ /* Remove the q that leads from this output to the sink thread */
+
+ pa_assert(op->outq_rtpoll_item);
+ pa_rtpoll_item_free(op->outq_rtpoll_item);
+ op->outq_rtpoll_item = NULL;
+
+ return 0;
+ }
+
+ case SINK_MESSAGE_NEED:
+ render_memblock(u, data, (size_t) offset);
+ return 0;
+ }
+
+ return pa_sink_process_msg(o, code, data, offset, chunk);
+}
+
+/* Called from main context */
+static pa_usec_t sink_get_latency_cb(pa_sink *s) {
+ struct userdata *u;
+
+ pa_sink_assert_ref(s);
+ pa_assert_se(u = s->userdata);
+
+ if (u->master) {
+ /* If we have a master sink, we just return the latency of it
+ * and add our own buffering on top */
+
+ if (!u->master->sink_input)
+ return 0;
+
+ return
+ pa_sink_input_get_latency(u->master->sink_input) +
+ pa_sink_get_latency(u->master->sink);
+
+ } else {
+ pa_usec_t usec = 0;
+
+ /* We have no master, hence let's ask our own thread which
+ * implements the NULL sink */
+
+ if (pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0)
+ return 0;
+
+ return usec;
+ }
+}
+
+static void update_description(struct userdata *u) {
+ int first = 1;
+ char *t;
+ struct output *o;
+ uint32_t idx;
+
+ pa_assert(u);
+
+ if (pa_idxset_isempty(u->outputs)) {
+ pa_sink_set_description(u->sink, "Simultaneous output");
+ return;
+ }
+
+ t = pa_xstrdup("Simultaneous output to");
+
+ for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) {
+ char *e;
+
+ if (first) {
+ e = pa_sprintf_malloc("%s %s", t, o->sink->description);
+ first = 0;
+ } else
+ e = pa_sprintf_malloc("%s, %s", t, o->sink->description);
+
+ pa_xfree(t);
+ t = e;
+ }
+
+ pa_sink_set_description(u->sink, t);
+ pa_xfree(t);
+}
+
+static void update_master(struct userdata *u, struct output *o) {
+ pa_assert(u);
+
+ if (u->master == o)
+ return;
+
+ if ((u->master = o))
+ pa_log_info("Master sink is now '%s'", o->sink_input->sink->name);
+ else
+ pa_log_info("No master selected, lacking suitable outputs.");
+}
+
+static void pick_master(struct userdata *u, struct output *except) {
+ struct output *o;
+ uint32_t idx;
+ pa_assert(u);
+
+ if (u->master &&
+ u->master != except &&
+ u->master->sink_input &&
+ PA_SINK_OPENED(pa_sink_get_state(u->master->sink))) {
+ update_master(u, u->master);
+ return;
+ }
+
+ for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx))
+ if (o != except &&
+ o->sink_input &&
+ PA_SINK_OPENED(pa_sink_get_state(o->sink))) {
+ update_master(u, o);
+ return;
+ }
+
+ update_master(u, NULL);
+}
+
+static int output_create_sink_input(struct output *o) {
+ pa_sink_input_new_data data;
+ char *t;
+
+ pa_assert(o);
+
+ if (o->sink_input)
+ return 0;
+
+ t = pa_sprintf_malloc("Simultaneous output on %s", o->sink->description);
+
+ pa_sink_input_new_data_init(&data);
+ data.sink = o->sink;
+ data.driver = __FILE__;
+ data.name = t;
+ pa_sink_input_new_data_set_sample_spec(&data, &o->userdata->sink->sample_spec);
+ pa_sink_input_new_data_set_channel_map(&data, &o->userdata->sink->channel_map);
+ data.module = o->userdata->module;
+ data.resample_method = o->userdata->resample_method;
+
+ o->sink_input = pa_sink_input_new(o->userdata->core, &data, PA_SINK_INPUT_VARIABLE_RATE|PA_SINK_INPUT_DONT_MOVE);
+
+ pa_xfree(t);
+
+ if (!o->sink_input)
+ return -1;
+
+ o->sink_input->parent.process_msg = sink_input_process_msg;
+ o->sink_input->peek = sink_input_peek_cb;
+ o->sink_input->drop = sink_input_drop_cb;
+ o->sink_input->attach = sink_input_attach_cb;
+ o->sink_input->detach = sink_input_detach_cb;
+ o->sink_input->kill = sink_input_kill_cb;
+ o->sink_input->userdata = o;
+
+
+ return 0;
+}
+
+static struct output *output_new(struct userdata *u, pa_sink *sink) {
+ struct output *o;
+
+ pa_assert(u);
+ pa_assert(sink);
+ pa_assert(u->sink);
+
+ o = pa_xnew(struct output, 1);
+ o->userdata = u;
+ o->inq = pa_asyncmsgq_new(0);
+ o->outq = pa_asyncmsgq_new(0);
+ o->inq_rtpoll_item = NULL;
+ o->outq_rtpoll_item = NULL;
+ o->sink = sink;
+ o->sink_input = NULL;
+ o->memblockq = pa_memblockq_new(
+ 0,
+ MEMBLOCKQ_MAXLENGTH,
+ MEMBLOCKQ_MAXLENGTH,
+ pa_frame_size(&u->sink->sample_spec),
+ 1,
+ 0,
+ NULL);
+
+ pa_assert_se(pa_idxset_put(u->outputs, o, NULL) == 0);
+
+ if (u->sink && PA_SINK_LINKED(pa_sink_get_state(u->sink)))
+ pa_asyncmsgq_send(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_ADD_OUTPUT, o, 0, NULL);
+ else {
+ /* If the sink is not yet started, we need to do the activation ourselves */
+ PA_LLIST_PREPEND(struct output, u->thread_info.active_outputs, o);
+
+ o->outq_rtpoll_item = pa_rtpoll_item_new_asyncmsgq(
+ u->rtpoll,
+ PA_RTPOLL_EARLY-1, /* This item is very important */
+ o->outq);
+ }
+
+ if (PA_SINK_OPENED(pa_sink_get_state(u->sink)) || pa_sink_get_state(u->sink) == PA_SINK_INIT) {
+ pa_sink_suspend(sink, FALSE);
+
+ if (PA_SINK_OPENED(pa_sink_get_state(sink)))
+ if (output_create_sink_input(o) < 0)
+ goto fail;
+ }
+
+
+ update_description(u);
+
+ return o;
+
+fail:
+
+ if (o) {
+ pa_idxset_remove_by_data(u->outputs, o, NULL);
+
+ if (o->sink_input) {
+ pa_sink_input_unlink(o->sink_input);
+ pa_sink_input_unref(o->sink_input);
+ }
+
+ if (o->memblockq)
+ pa_memblockq_free(o->memblockq);
+
+ if (o->inq)
+ pa_asyncmsgq_unref(o->inq);
+
+ if (o->outq)
+ pa_asyncmsgq_unref(o->outq);
+
+ pa_xfree(o);
+ }
+
+ return NULL;
+}
+
+static pa_hook_result_t sink_new_hook_cb(pa_core *c, pa_sink *s, struct userdata* u) {
+ struct output *o;
+
+ pa_core_assert_ref(c);
+ pa_sink_assert_ref(s);
+ pa_assert(u);
+ pa_assert(u->automatic);
+
+ if (!(s->flags & PA_SINK_HARDWARE) || s == u->sink)
+ return PA_HOOK_OK;
+
+ pa_log_info("Configuring new sink: %s", s->name);
+
+ if (!(o = output_new(u, s))) {
+ pa_log("Failed to create sink input on sink '%s'.", s->name);
+ return PA_HOOK_OK;
+ }
+
+ if (o->sink_input)
+ pa_sink_input_put(o->sink_input);
+
+ pick_master(u, NULL);
+
+ return PA_HOOK_OK;
+}
+
+static pa_hook_result_t sink_unlink_hook_cb(pa_core *c, pa_sink *s, struct userdata* u) {
+ struct output *o;
+ uint32_t idx;
+
+ pa_assert(c);
+ pa_sink_assert_ref(s);
+ pa_assert(u);
+
+ if (s == u->sink)
+ return PA_HOOK_OK;
+
+ for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx))
+ if (o->sink == s)
+ break;
+
+ if (!o)
+ return PA_HOOK_OK;
+
+ pa_log_info("Unconfiguring sink: %s", s->name);
+
+ output_free(o);
+
+ return PA_HOOK_OK;
+}
+
+static pa_hook_result_t sink_state_changed_hook_cb(pa_core *c, pa_sink *s, struct userdata* u) {
+ struct output *o;
+ uint32_t idx;
+ pa_sink_state_t state;
+
+ if (s == u->sink)
+ return PA_HOOK_OK;
+
+ for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx))
+ if (o->sink == s)
+ break;
+
+ if (!o)
+ return PA_HOOK_OK;
+
+ state = pa_sink_get_state(s);
+
+ if (PA_SINK_OPENED(state) && PA_SINK_OPENED(pa_sink_get_state(u->sink)) && !o->sink_input) {
+ enable_output(o);
+ pick_master(u, NULL);
+ }
+
+ if (state == PA_SINK_SUSPENDED && o->sink_input) {
+ disable_output(o);
+ pick_master(u, o);
+ }
+
+ return PA_HOOK_OK;
+}
+
+int pa__init(pa_module*m) {
+ struct userdata *u;
+ pa_modargs *ma = NULL;
+ const char *master_name, *slaves, *rm;
+ pa_sink *master_sink = NULL;
+ int resample_method = PA_RESAMPLER_TRIVIAL;
+ pa_sample_spec ss;
+ pa_channel_map map;
+ struct output *o;
+ uint32_t idx;
+
+ pa_assert(m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log("failed to parse module arguments");
+ goto fail;
+ }
+
+ if ((rm = pa_modargs_get_value(ma, "resample_method", NULL))) {
+ if ((resample_method = pa_parse_resample_method(rm)) < 0) {
+ pa_log("invalid resample method '%s'", rm);
+ goto fail;
+ }
+ }
+
+ u = pa_xnew(struct userdata, 1);
+ u->core = m->core;
+ u->module = m;
+ m->userdata = u;
+ u->sink = NULL;
+ u->master = NULL;
+ u->time_event = NULL;
+ u->adjust_time = DEFAULT_ADJUST_TIME;
+ pa_thread_mq_init(&u->thread_mq, m->core->mainloop);
+ u->rtpoll = pa_rtpoll_new();
+ u->thread = NULL;
+ u->resample_method = resample_method;
+ u->outputs = pa_idxset_new(NULL, NULL);
+ memset(&u->adjust_timestamp, 0, sizeof(u->adjust_timestamp));
+ u->sink_new_slot = u->sink_unlink_slot = u->sink_state_changed_slot = NULL;
+ PA_LLIST_HEAD_INIT(struct output, u->thread_info.active_outputs);
+ pa_atomic_store(&u->thread_info.running, FALSE);
+ u->thread_info.in_null_mode = FALSE;
+ pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq);
+
+ if (pa_modargs_get_value_u32(ma, "adjust_time", &u->adjust_time) < 0) {
+ pa_log("Failed to parse adjust_time value");
+ goto fail;
+ }
+
+ master_name = pa_modargs_get_value(ma, "master", NULL);
+ slaves = pa_modargs_get_value(ma, "slaves", NULL);
+ if (!master_name != !slaves) {
+ pa_log("No master or slave sinks specified");
+ goto fail;
+ }
+
+ if (master_name) {
+ if (!(master_sink = pa_namereg_get(m->core, master_name, PA_NAMEREG_SINK, 1))) {
+ pa_log("Invalid master sink '%s'", master_name);
+ goto fail;
+ }
+
+ ss = master_sink->sample_spec;
+ u->automatic = FALSE;
+ } else {
+ master_sink = NULL;
+ ss = m->core->default_sample_spec;
+ u->automatic = TRUE;
+ }
+
+ if ((pa_modargs_get_sample_spec(ma, &ss) < 0)) {
+ pa_log("Invalid sample specification.");
+ goto fail;
+ }
+
+ if (master_sink && ss.channels == master_sink->sample_spec.channels)
+ map = master_sink->channel_map;
+ else {
+ pa_assert_se(pa_channel_map_init_auto(&map, ss.channels, PA_CHANNEL_MAP_AUX));
+ pa_channel_map_init_auto(&map, ss.channels, PA_CHANNEL_MAP_DEFAULT);
+ }
+
+ if ((pa_modargs_get_channel_map(ma, NULL, &map) < 0)) {
+ pa_log("Invalid channel map.");
+ goto fail;
+ }
+
+ if (ss.channels != map.channels) {
+ pa_log("Channel map and sample specification don't match.");
+ goto fail;
+ }
+
+ if (!(u->sink = pa_sink_new(m->core, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, &map))) {
+ pa_log("Failed to create sink");
+ goto fail;
+ }
+
+ u->sink->parent.process_msg = sink_process_msg;
+ u->sink->get_latency = sink_get_latency_cb;
+ u->sink->set_state = sink_set_state;
+ u->sink->userdata = u;
+
+ u->sink->flags = PA_SINK_LATENCY;
+ pa_sink_set_module(u->sink, m);
+ pa_sink_set_description(u->sink, "Simultaneous output");
+ pa_sink_set_rtpoll(u->sink, u->rtpoll);
+ pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq);
+
+ u->block_size = pa_bytes_per_second(&ss) / 20; /* 50 ms */
+ if (u->block_size <= 0)
+ u->block_size = pa_frame_size(&ss);
+
+ if (!u->automatic) {
+ const char*split_state;
+ char *n = NULL;
+ pa_assert(slaves);
+
+ /* The master and slaves have been specified manually */
+
+ if (!(u->master = output_new(u, master_sink))) {
+ pa_log("Failed to create master sink input on sink '%s'.", master_sink->name);
+ goto fail;
+ }
+
+ split_state = NULL;
+ while ((n = pa_split(slaves, ",", &split_state))) {
+ pa_sink *slave_sink;
+
+ if (!(slave_sink = pa_namereg_get(m->core, n, PA_NAMEREG_SINK, 1)) || slave_sink == u->sink) {
+ pa_log("Invalid slave sink '%s'", n);
+ pa_xfree(n);
+ goto fail;
+ }
+
+ pa_xfree(n);
+
+ if (!output_new(u, slave_sink)) {
+ pa_log("Failed to create slave sink input on sink '%s'.", slave_sink->name);
+ goto fail;
+ }
+ }
+
+ if (pa_idxset_size(u->outputs) <= 1)
+ pa_log_warn("No slave sinks specified.");
+
+ u->sink_new_slot = NULL;
+
+ } else {
+ pa_sink *s;
+
+ /* We're in automatic mode, we elect one hw sink to the master
+ * and attach all other hw sinks as slaves to it */
+
+ for (s = pa_idxset_first(m->core->sinks, &idx); s; s = pa_idxset_next(m->core->sinks, &idx)) {
+
+ if (!(s->flags & PA_SINK_HARDWARE) || s == u->sink)
+ continue;
+
+ if (!output_new(u, s)) {
+ pa_log("Failed to create sink input on sink '%s'.", s->name);
+ goto fail;
+ }
+ }
+
+ u->sink_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_NEW_POST], (pa_hook_cb_t) sink_new_hook_cb, u);
+ }
+
+ u->sink_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], (pa_hook_cb_t) sink_unlink_hook_cb, u);
+ u->sink_state_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_STATE_CHANGED], (pa_hook_cb_t) sink_state_changed_hook_cb, u);
+
+ pick_master(u, NULL);
+
+ if (!(u->thread = pa_thread_new(thread_func, u))) {
+ pa_log("Failed to create thread.");
+ goto fail;
+ }
+
+ /* Activate the sink and the sink inputs */
+ pa_sink_put(u->sink);
+
+ for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx))
+ if (o->sink_input)
+ pa_sink_input_put(o->sink_input);
+
+ if (u->adjust_time > 0) {
+ struct timeval tv;
+ pa_gettimeofday(&tv);
+ tv.tv_sec += u->adjust_time;
+ u->time_event = m->core->mainloop->time_new(m->core->mainloop, &tv, time_callback, u);
+ }
+
+ pa_modargs_free(ma);
+
+ return 0;
+
+fail:
+
+ if (ma)
+ pa_modargs_free(ma);
+
+ pa__done(m);
+
+ return -1;
+}
+
+static void output_free(struct output *o) {
+ pa_assert(o);
+
+ pick_master(o->userdata, o);
+
+ disable_output(o);
+
+ pa_assert_se(pa_idxset_remove_by_data(o->userdata->outputs, o, NULL));
+
+ update_description(o->userdata);
+
+ if (o->inq_rtpoll_item)
+ pa_rtpoll_item_free(o->inq_rtpoll_item);
+
+ if (o->outq_rtpoll_item)
+ pa_rtpoll_item_free(o->outq_rtpoll_item);
+
+ if (o->inq)
+ pa_asyncmsgq_unref(o->inq);
+
+ if (o->outq)
+ pa_asyncmsgq_unref(o->outq);
+
+ if (o->memblockq)
+ pa_memblockq_free(o->memblockq);
+
+ pa_xfree(o);
+}
+
+void pa__done(pa_module*m) {
+ struct userdata *u;
+ struct output *o;
+
+ pa_assert(m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->sink_new_slot)
+ pa_hook_slot_free(u->sink_new_slot);
+
+ if (u->sink_unlink_slot)
+ pa_hook_slot_free(u->sink_unlink_slot);
+
+ if (u->sink_state_changed_slot)
+ pa_hook_slot_free(u->sink_state_changed_slot);
+
+ if (u->outputs) {
+ while ((o = pa_idxset_first(u->outputs, NULL)))
+ output_free(o);
+
+ pa_idxset_free(u->outputs, NULL, NULL);
+ }
+
+ if (u->sink)
+ pa_sink_unlink(u->sink);
+
+ if (u->thread) {
+ pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL);
+ pa_thread_free(u->thread);
+ }
+
+ pa_thread_mq_done(&u->thread_mq);
+
+ if (u->sink)
+ pa_sink_unref(u->sink);
+
+ if (u->rtpoll)
+ pa_rtpoll_free(u->rtpoll);
+
+ if (u->time_event)
+ u->core->mainloop->time_free(u->time_event);
+
+ pa_xfree(u);
+}
diff --git a/src/modules/module-default-device-restore.c b/src/modules/module-default-device-restore.c
new file mode 100644
index 00000000..b550ae78
--- /dev/null
+++ b/src/modules/module-default-device-restore.c
@@ -0,0 +1,101 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/module.h>
+#include <pulsecore/log.h>
+#include <pulsecore/namereg.h>
+
+#include "module-default-device-restore-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering");
+PA_MODULE_DESCRIPTION("Automatically restore the default sink and source");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(TRUE);
+
+#define DEFAULT_SINK_FILE "default-sink"
+#define DEFAULT_SOURCE_FILE "default-source"
+
+int pa__init(pa_module *m) {
+ FILE *f;
+
+ /* We never overwrite manually configured settings */
+
+ if (m->core->default_sink_name)
+ pa_log_info("Manually configured default sink, not overwriting.");
+ else if ((f = pa_open_config_file(NULL, DEFAULT_SINK_FILE, NULL, NULL, "r"))) {
+ char ln[256] = "";
+
+ fgets(ln, sizeof(ln)-1, f);
+ pa_strip_nl(ln);
+ fclose(f);
+
+ if (!ln[0])
+ pa_log_debug("No previous default sink setting, ignoring.");
+ else if (pa_namereg_get(m->core, ln, PA_NAMEREG_SINK, 1)) {
+ pa_namereg_set_default(m->core, ln, PA_NAMEREG_SINK);
+ pa_log_debug("Restored default sink '%s'.", ln);
+ } else
+ pa_log_info("Saved default sink '%s' not existant, not restoring default sink setting.", ln);
+ }
+
+ if (m->core->default_source_name)
+ pa_log_info("Manually configured default source, not overwriting.");
+ else if ((f = pa_open_config_file(NULL, DEFAULT_SOURCE_FILE, NULL, NULL, "r"))) {
+ char ln[256] = "";
+
+ fgets(ln, sizeof(ln)-1, f);
+ pa_strip_nl(ln);
+ fclose(f);
+
+ if (!ln[0])
+ pa_log_debug("No previous default source setting, ignoring.");
+ else if (pa_namereg_get(m->core, ln, PA_NAMEREG_SOURCE, 1)) {
+ pa_namereg_set_default(m->core, ln, PA_NAMEREG_SOURCE);
+ pa_log_debug("Restored default source '%s'.", ln);
+ } else
+ pa_log_info("Saved default source '%s' not existant, not restoring default source setting.", ln);
+ }
+
+ return 0;
+}
+
+void pa__done(pa_module*m) {
+ FILE *f;
+
+ if ((f = pa_open_config_file(NULL, DEFAULT_SINK_FILE, NULL, NULL, "w"))) {
+ const char *n = pa_namereg_get_default_sink_name(m->core);
+ fprintf(f, "%s\n", n ? n : "");
+ fclose(f);
+ }
+
+ if ((f = pa_open_config_file(NULL, DEFAULT_SOURCE_FILE, NULL, NULL, "w"))) {
+ const char *n = pa_namereg_get_default_source_name(m->core);
+ fprintf(f, "%s\n", n ? n : "");
+ fclose(f);
+ }
+}
diff --git a/src/modules/module-defs.h.m4 b/src/modules/module-defs.h.m4
new file mode 100644
index 00000000..a49e8329
--- /dev/null
+++ b/src/modules/module-defs.h.m4
@@ -0,0 +1,32 @@
+dnl $Id$
+changecom(`/*', `*/')dnl
+define(`module_name', patsubst(patsubst(patsubst(fname, `-symdef.h$'), `^.*/'), `[^0-9a-zA-Z]', `_'))dnl
+define(`c_symbol', patsubst(module_name, `[^0-9a-zA-Z]', `_'))dnl
+define(`c_macro', patsubst(module_name, `[^0-9a-zA-Z]', `'))dnl
+define(`incmacro', `foo'c_macro`symdeffoo')dnl
+define(`gen_symbol', `#define $1 'module_name`_LTX_$1')dnl
+#ifndef incmacro
+#define incmacro
+
+#include <pulsecore/core.h>
+#include <pulsecore/module.h>
+#include <pulsecore/macro.h>
+
+gen_symbol(pa__init)
+gen_symbol(pa__done)
+gen_symbol(pa__get_author)
+gen_symbol(pa__get_description)
+gen_symbol(pa__get_usage)
+gen_symbol(pa__get_version)
+gen_symbol(pa__load_once)
+
+int pa__init(pa_module*m);
+void pa__done(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);
+pa_bool_t pa__load_once(void);
+
+#endif
diff --git a/src/modules/module-detect.c b/src/modules/module-detect.c
new file mode 100644
index 00000000..ee650dfd
--- /dev/null
+++ b/src/modules/module-detect.c
@@ -0,0 +1,272 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+ Copyright 2006 Diego Pettenò
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/core-error.h>
+#include <pulsecore/module.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/log.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/macro.h>
+
+#include "module-detect-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering");
+PA_MODULE_DESCRIPTION("Detect available audio hardware and load matching drivers");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(TRUE);
+PA_MODULE_USAGE("just-one=<boolean>");
+
+static const char* const valid_modargs[] = {
+ "just-one",
+ NULL
+};
+
+#ifdef HAVE_ALSA
+
+static int detect_alsa(pa_core *c, int just_one) {
+ FILE *f;
+ int n = 0, n_sink = 0, n_source = 0;
+
+ if (!(f = fopen("/proc/asound/devices", "r"))) {
+
+ if (errno != ENOENT)
+ pa_log_error("open(\"/proc/asound/devices\") failed: %s", pa_cstrerror(errno));
+
+ return -1;
+ }
+
+ while (!feof(f)) {
+ char line[64], args[64];
+ unsigned device, subdevice;
+ int is_sink;
+
+ if (!fgets(line, sizeof(line), f))
+ break;
+
+ line[strcspn(line, "\r\n")] = 0;
+
+ if (pa_endswith(line, "digital audio playback"))
+ is_sink = 1;
+ else if (pa_endswith(line, "digital audio capture"))
+ is_sink = 0;
+ else
+ continue;
+
+ if (just_one && is_sink && n_sink >= 1)
+ continue;
+
+ if (just_one && !is_sink && n_source >= 1)
+ continue;
+
+ if (sscanf(line, " %*i: [%u- %u]: ", &device, &subdevice) != 2)
+ continue;
+
+ /* Only one sink per device */
+ if (subdevice != 0)
+ continue;
+
+ pa_snprintf(args, sizeof(args), "device=hw:%u", device);
+ if (!pa_module_load(c, is_sink ? "module-alsa-sink" : "module-alsa-source", args))
+ continue;
+
+ n++;
+
+ if (is_sink)
+ n_sink++;
+ else
+ n_source++;
+ }
+
+ fclose(f);
+
+ return n;
+}
+#endif
+
+#ifdef HAVE_OSS
+static int detect_oss(pa_core *c, int just_one) {
+ FILE *f;
+ int n = 0, b = 0;
+
+ if (!(f = fopen("/dev/sndstat", "r")) &&
+ !(f = fopen("/proc/sndstat", "r")) &&
+ !(f = fopen("/proc/asound/oss/sndstat", "r"))) {
+
+ if (errno != ENOENT)
+ pa_log_error("failed to open OSS sndstat device: %s", pa_cstrerror(errno));
+
+ return -1;
+ }
+
+ while (!feof(f)) {
+ char line[64], args[64];
+ unsigned device;
+
+ if (!fgets(line, sizeof(line), f))
+ break;
+
+ line[strcspn(line, "\r\n")] = 0;
+
+ if (!b) {
+ b = strcmp(line, "Audio devices:") == 0 || strcmp(line, "Installed devices:") == 0;
+ continue;
+ }
+
+ if (line[0] == 0)
+ break;
+
+ if (sscanf(line, "%u: ", &device) == 1) {
+ if (device == 0)
+ pa_snprintf(args, sizeof(args), "device=/dev/dsp");
+ else
+ pa_snprintf(args, sizeof(args), "device=/dev/dsp%u", device);
+
+ if (!pa_module_load(c, "module-oss", args))
+ continue;
+
+ } else if (sscanf(line, "pcm%u: ", &device) == 1) {
+ /* FreeBSD support, the devices are named /dev/dsp0.0, dsp0.1 and so on */
+ pa_snprintf(args, sizeof(args), "device=/dev/dsp%u.0", device);
+
+ if (!pa_module_load(c, "module-oss", args))
+ continue;
+ }
+
+ n++;
+
+ if (just_one)
+ break;
+ }
+
+ fclose(f);
+ return n;
+}
+#endif
+
+#ifdef HAVE_SOLARIS
+static int detect_solaris(pa_core *c, int just_one) {
+ struct stat s;
+ const char *dev;
+ char args[64];
+
+ dev = getenv("AUDIODEV");
+ if (!dev)
+ dev = "/dev/audio";
+
+ if (stat(dev, &s) < 0) {
+ if (errno != ENOENT)
+ pa_log_error("failed to open device %s: %s", dev, pa_cstrerror(errno));
+ return -1;
+ }
+
+ if (!S_ISCHR(s.st_mode))
+ return 0;
+
+ pa_snprintf(args, sizeof(args), "device=%s", dev);
+
+ if (!pa_module_load(c, "module-solaris", args))
+ return 0;
+
+ return 1;
+}
+#endif
+
+#ifdef OS_IS_WIN32
+static int detect_waveout(pa_core *c, int just_one) {
+ /*
+ * FIXME: No point in enumerating devices until the plugin supports
+ * selecting anything but the first.
+ */
+ if (!pa_module_load(c, "module-waveout", ""))
+ return 0;
+
+ return 1;
+}
+#endif
+
+int pa__init(pa_module*m) {
+ pa_bool_t just_one = FALSE;
+ int n = 0;
+ pa_modargs *ma;
+
+ pa_assert(m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log("Failed to parse module arguments");
+ goto fail;
+ }
+
+ if (pa_modargs_get_value_boolean(ma, "just-one", &just_one) < 0) {
+ pa_log("just_one= expects a boolean argument.");
+ goto fail;
+ }
+
+#if HAVE_ALSA
+ if ((n = detect_alsa(m->core, just_one)) <= 0)
+#endif
+#if HAVE_OSS
+ if ((n = detect_oss(m->core, just_one)) <= 0)
+#endif
+#if HAVE_SOLARIS
+ if ((n = detect_solaris(m->core, just_one)) <= 0)
+#endif
+#if OS_IS_WIN32
+ if ((n = detect_waveout(m->core, just_one)) <= 0)
+#endif
+ {
+ pa_log_warn("failed to detect any sound hardware.");
+ goto fail;
+ }
+
+ pa_log_info("loaded %i modules.", n);
+
+ /* We were successful and can unload ourselves now. */
+ pa_module_unload_request(m);
+
+ pa_modargs_free(ma);
+
+ return 0;
+
+fail:
+ if (ma)
+ pa_modargs_free(ma);
+
+ return -1;
+}
diff --git a/src/modules/module-esound-compat-spawnfd.c b/src/modules/module-esound-compat-spawnfd.c
new file mode 100644
index 00000000..8321192b
--- /dev/null
+++ b/src/modules/module-esound-compat-spawnfd.c
@@ -0,0 +1,80 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#include <pulsecore/core-error.h>
+#include <pulsecore/module.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/log.h>
+
+#include "module-esound-compat-spawnfd-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering");
+PA_MODULE_DESCRIPTION("ESOUND compatibility module: -spawnfd emulation");
+PA_MODULE_LOAD_ONCE(TRUE);
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_USAGE("fd=<file descriptor>");
+
+static const char* const valid_modargs[] = {
+ "fd",
+ NULL,
+};
+
+int pa__init(pa_module*m) {
+ pa_modargs *ma = NULL;
+ int ret = -1, fd = -1;
+ char x = 1;
+
+ pa_assert(m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs)) ||
+ pa_modargs_get_value_s32(ma, "fd", &fd) < 0 ||
+ fd < 0) {
+
+ pa_log("Failed to parse module arguments");
+ goto finish;
+ }
+
+ if (pa_loop_write(fd, &x, sizeof(x), NULL) != sizeof(x))
+ pa_log_warn("write(%u, 1, 1) failed: %s", fd, pa_cstrerror(errno));
+
+ pa_assert_se(pa_close(fd) == 0);
+
+ pa_module_unload_request(m);
+
+ ret = 0;
+
+finish:
+ if (ma)
+ pa_modargs_free(ma);
+
+ return ret;
+}
diff --git a/src/modules/module-esound-compat-spawnpid.c b/src/modules/module-esound-compat-spawnpid.c
new file mode 100644
index 00000000..67f0a231
--- /dev/null
+++ b/src/modules/module-esound-compat-spawnpid.c
@@ -0,0 +1,77 @@
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+
+#include <pulsecore/core-error.h>
+#include <pulsecore/module.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/log.h>
+
+#include "module-esound-compat-spawnpid-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering");
+PA_MODULE_DESCRIPTION("ESOUND compatibility module: -spawnpid emulation");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(TRUE);
+PA_MODULE_USAGE("pid=<process id>");
+
+static const char* const valid_modargs[] = {
+ "pid",
+ NULL,
+};
+
+int pa__init(pa_module*m) {
+ pa_modargs *ma = NULL;
+ int ret = -1;
+ uint32_t pid = 0;
+
+ pa_assert(m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs)) ||
+ pa_modargs_get_value_u32(ma, "pid", &pid) < 0 ||
+ !pid) {
+ pa_log("Failed to parse module arguments");
+ goto finish;
+ }
+
+ if (kill(pid, SIGUSR1) < 0)
+ pa_log_warn("kill(%u) failed: %s", pid, pa_cstrerror(errno));
+
+ pa_module_unload_request(m);
+
+ ret = 0;
+
+finish:
+ if (ma)
+ pa_modargs_free(ma);
+
+ return ret;
+}
diff --git a/src/modules/module-esound-sink.c b/src/modules/module-esound-sink.c
new file mode 100644
index 00000000..f9bea63d
--- /dev/null
+++ b/src/modules/module-esound-sink.c
@@ -0,0 +1,661 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+#include <poll.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <sys/ioctl.h>
+
+#ifdef HAVE_LINUX_SOCKIOS_H
+#include <linux/sockios.h>
+#endif
+
+#include <pulse/xmalloc.h>
+#include <pulse/timeval.h>
+
+#include <pulsecore/core-error.h>
+#include <pulsecore/iochannel.h>
+#include <pulsecore/sink.h>
+#include <pulsecore/module.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/log.h>
+#include <pulsecore/socket-client.h>
+#include <pulsecore/esound.h>
+#include <pulsecore/authkey.h>
+#include <pulsecore/thread-mq.h>
+#include <pulsecore/thread.h>
+#include <pulsecore/time-smoother.h>
+#include <pulsecore/rtclock.h>
+#include <pulsecore/socket-util.h>
+
+#include "module-esound-sink-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering");
+PA_MODULE_DESCRIPTION("ESOUND Sink");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(FALSE);
+PA_MODULE_USAGE(
+ "sink_name=<name for the sink> "
+ "server=<address> cookie=<filename> "
+ "format=<sample format> "
+ "channels=<number of channels> "
+ "rate=<sample rate>");
+
+#define DEFAULT_SINK_NAME "esound_out"
+
+struct userdata {
+ pa_core *core;
+ pa_module *module;
+ pa_sink *sink;
+
+ pa_thread_mq thread_mq;
+ pa_rtpoll *rtpoll;
+ pa_rtpoll_item *rtpoll_item;
+ pa_thread *thread;
+
+ pa_memchunk memchunk;
+
+ void *write_data;
+ size_t write_length, write_index;
+
+ void *read_data;
+ size_t read_length, read_index;
+
+ enum {
+ STATE_AUTH,
+ STATE_LATENCY,
+ STATE_PREPARE,
+ STATE_RUNNING,
+ STATE_DEAD
+ } state;
+
+ pa_usec_t latency;
+
+ esd_format_t format;
+ int32_t rate;
+
+ pa_smoother *smoother;
+ int fd;
+
+ int64_t offset;
+
+ pa_iochannel *io;
+ pa_socket_client *client;
+
+ size_t block_size;
+};
+
+static const char* const valid_modargs[] = {
+ "server",
+ "cookie",
+ "rate",
+ "format",
+ "channels",
+ "sink_name",
+ NULL
+};
+
+enum {
+ SINK_MESSAGE_PASS_SOCKET = PA_SINK_MESSAGE_MAX
+};
+
+static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+ struct userdata *u = PA_SINK(o)->userdata;
+
+ switch (code) {
+
+ case PA_SINK_MESSAGE_SET_STATE:
+
+ switch ((pa_sink_state_t) PA_PTR_TO_UINT(data)) {
+
+ case PA_SINK_SUSPENDED:
+ pa_assert(PA_SINK_OPENED(u->sink->thread_info.state));
+
+ pa_smoother_pause(u->smoother, pa_rtclock_usec());
+ break;
+
+ case PA_SINK_IDLE:
+ case PA_SINK_RUNNING:
+
+ if (u->sink->thread_info.state == PA_SINK_SUSPENDED)
+ pa_smoother_resume(u->smoother, pa_rtclock_usec());
+
+ break;
+
+ case PA_SINK_UNLINKED:
+ case PA_SINK_INIT:
+ ;
+ }
+
+ break;
+
+ case PA_SINK_MESSAGE_GET_LATENCY: {
+ pa_usec_t w, r;
+
+ r = pa_smoother_get(u->smoother, pa_rtclock_usec());
+ w = pa_bytes_to_usec(u->offset + u->memchunk.length, &u->sink->sample_spec);
+
+ *((pa_usec_t*) data) = w > r ? w - r : 0;
+ break;
+ }
+
+ case SINK_MESSAGE_PASS_SOCKET: {
+ struct pollfd *pollfd;
+
+ pa_assert(!u->rtpoll_item);
+
+ u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1);
+ pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
+ pollfd->fd = u->fd;
+ pollfd->events = pollfd->revents = 0;
+
+ return 0;
+ }
+ }
+
+ return pa_sink_process_msg(o, code, data, offset, chunk);
+}
+
+static void thread_func(void *userdata) {
+ struct userdata *u = userdata;
+ int write_type = 0;
+
+ pa_assert(u);
+
+ pa_log_debug("Thread starting up");
+
+ pa_thread_mq_install(&u->thread_mq);
+ pa_rtpoll_install(u->rtpoll);
+
+ pa_smoother_set_time_offset(u->smoother, pa_rtclock_usec());
+
+ for (;;) {
+ int ret;
+
+ if (u->rtpoll_item) {
+ struct pollfd *pollfd;
+ pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
+
+ /* Render some data and write it to the fifo */
+ if (PA_SINK_OPENED(u->sink->thread_info.state) && pollfd->revents) {
+ pa_usec_t usec;
+ int64_t n;
+
+ for (;;) {
+ ssize_t l;
+ void *p;
+
+ if (u->memchunk.length <= 0)
+ pa_sink_render(u->sink, u->block_size, &u->memchunk);
+
+ pa_assert(u->memchunk.length > 0);
+
+ p = pa_memblock_acquire(u->memchunk.memblock);
+ l = pa_write(u->fd, (uint8_t*) p + u->memchunk.index, u->memchunk.length, &write_type);
+ pa_memblock_release(u->memchunk.memblock);
+
+ pa_assert(l != 0);
+
+ if (l < 0) {
+
+ if (errno == EINTR)
+ continue;
+ else if (errno == EAGAIN) {
+
+ /* OK, we filled all socket buffers up
+ * now. */
+ goto filled_up;
+
+ } else {
+ pa_log("Failed to write data to FIFO: %s", pa_cstrerror(errno));
+ goto fail;
+ }
+
+ } else {
+ u->offset += l;
+
+ u->memchunk.index += l;
+ u->memchunk.length -= l;
+
+ if (u->memchunk.length <= 0) {
+ pa_memblock_unref(u->memchunk.memblock);
+ pa_memchunk_reset(&u->memchunk);
+ }
+
+ pollfd->revents = 0;
+
+ if (u->memchunk.length > 0)
+
+ /* OK, we wrote less that we asked for,
+ * hence we can assume that the socket
+ * buffers are full now */
+ goto filled_up;
+ }
+ }
+
+ filled_up:
+
+ /* At this spot we know that the socket buffers are
+ * fully filled up. This is the best time to estimate
+ * the playback position of the server */
+
+ n = u->offset;
+
+#ifdef SIOCOUTQ
+ {
+ int l;
+ if (ioctl(u->fd, SIOCOUTQ, &l) >= 0 && l > 0)
+ n -= l;
+ }
+#endif
+
+ usec = pa_bytes_to_usec(n, &u->sink->sample_spec);
+
+ if (usec > u->latency)
+ usec -= u->latency;
+ else
+ usec = 0;
+
+ pa_smoother_put(u->smoother, pa_rtclock_usec(), usec);
+ }
+
+ /* Hmm, nothing to do. Let's sleep */
+ pollfd->events = PA_SINK_OPENED(u->sink->thread_info.state) ? POLLOUT : 0;
+ }
+
+ if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0)
+ goto fail;
+
+ if (ret == 0)
+ goto finish;
+
+ if (u->rtpoll_item) {
+ struct pollfd* pollfd;
+
+ pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
+
+ if (pollfd->revents & ~POLLOUT) {
+ pa_log("FIFO shutdown.");
+ goto fail;
+ }
+ }
+ }
+
+fail:
+ /* If this was no regular exit from the loop we have to continue
+ * processing messages until we received PA_MESSAGE_SHUTDOWN */
+ pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL);
+ pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN);
+
+finish:
+ pa_log_debug("Thread shutting down");
+}
+
+static int do_write(struct userdata *u) {
+ ssize_t r;
+ pa_assert(u);
+
+ if (!pa_iochannel_is_writable(u->io))
+ return 0;
+
+ if (u->write_data) {
+ pa_assert(u->write_index < u->write_length);
+
+ if ((r = pa_iochannel_write(u->io, (uint8_t*) u->write_data + u->write_index, u->write_length - u->write_index)) <= 0) {
+ pa_log("write() failed: %s", pa_cstrerror(errno));
+ return -1;
+ }
+
+ u->write_index += r;
+ pa_assert(u->write_index <= u->write_length);
+
+ if (u->write_index == u->write_length) {
+ pa_xfree(u->write_data);
+ u->write_data = NULL;
+ u->write_index = u->write_length = 0;
+ }
+ }
+
+ if (!u->write_data && u->state == STATE_PREPARE) {
+ /* OK, we're done with sending all control data we need to, so
+ * let's hand the socket over to the IO thread now */
+
+ pa_assert(u->fd < 0);
+ u->fd = pa_iochannel_get_send_fd(u->io);
+
+ pa_iochannel_set_noclose(u->io, TRUE);
+ pa_iochannel_free(u->io);
+ u->io = NULL;
+
+ pa_make_tcp_socket_low_delay(u->fd);
+
+ pa_log_debug("Connection authenticated, handing fd to IO thread...");
+
+ pa_asyncmsgq_post(u->thread_mq.inq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_PASS_SOCKET, NULL, 0, NULL, NULL);
+ u->state = STATE_RUNNING;
+ }
+
+ return 0;
+}
+
+static int handle_response(struct userdata *u) {
+ pa_assert(u);
+
+ switch (u->state) {
+
+ case STATE_AUTH:
+ pa_assert(u->read_length == sizeof(int32_t));
+
+ /* Process auth data */
+ if (!*(int32_t*) u->read_data) {
+ pa_log("Authentication failed: %s", pa_cstrerror(errno));
+ return -1;
+ }
+
+ /* Request latency data */
+ pa_assert(!u->write_data);
+ *(int32_t*) (u->write_data = pa_xmalloc(u->write_length = sizeof(int32_t))) = ESD_PROTO_LATENCY;
+
+ u->write_index = 0;
+ u->state = STATE_LATENCY;
+
+ /* Space for next response */
+ pa_assert(u->read_length >= sizeof(int32_t));
+ u->read_index = 0;
+ u->read_length = sizeof(int32_t);
+
+ break;
+
+ case STATE_LATENCY: {
+ int32_t *p;
+ pa_assert(u->read_length == sizeof(int32_t));
+
+ /* Process latency info */
+ u->latency = (pa_usec_t) ((double) (*(int32_t*) u->read_data) * 1000000 / 44100);
+ if (u->latency > 10000000) {
+ pa_log_warn("Invalid latency information received from server");
+ u->latency = 0;
+ }
+
+ /* Create stream */
+ pa_assert(!u->write_data);
+ p = u->write_data = pa_xmalloc0(u->write_length = sizeof(int32_t)*3+ESD_NAME_MAX);
+ *(p++) = ESD_PROTO_STREAM_PLAY;
+ *(p++) = u->format;
+ *(p++) = u->rate;
+ pa_strlcpy((char*) p, "PulseAudio Tunnel", ESD_NAME_MAX);
+
+ u->write_index = 0;
+ u->state = STATE_PREPARE;
+
+ /* Don't read any further */
+ pa_xfree(u->read_data);
+ u->read_data = NULL;
+ u->read_index = u->read_length = 0;
+
+ break;
+ }
+
+ default:
+ pa_assert_not_reached();
+ }
+
+ return 0;
+}
+
+static int do_read(struct userdata *u) {
+ pa_assert(u);
+
+ if (!pa_iochannel_is_readable(u->io))
+ return 0;
+
+ if (u->state == STATE_AUTH || u->state == STATE_LATENCY) {
+ ssize_t r;
+
+ if (!u->read_data)
+ return 0;
+
+ pa_assert(u->read_index < u->read_length);
+
+ if ((r = pa_iochannel_read(u->io, (uint8_t*) u->read_data + u->read_index, u->read_length - u->read_index)) <= 0) {
+ pa_log("read() failed: %s", r < 0 ? pa_cstrerror(errno) : "EOF");
+ return -1;
+ }
+
+ u->read_index += r;
+ pa_assert(u->read_index <= u->read_length);
+
+ if (u->read_index == u->read_length)
+ return handle_response(u);
+ }
+
+ return 0;
+}
+
+static void io_callback(PA_GCC_UNUSED pa_iochannel *io, void*userdata) {
+ struct userdata *u = userdata;
+ pa_assert(u);
+
+ if (do_read(u) < 0 || do_write(u) < 0) {
+
+ if (u->io) {
+ pa_iochannel_free(u->io);
+ u->io = NULL;
+ }
+
+ pa_module_unload_request(u->module);
+ }
+}
+
+static void on_connection(PA_GCC_UNUSED pa_socket_client *c, pa_iochannel*io, void *userdata) {
+ struct userdata *u = userdata;
+
+ pa_socket_client_unref(u->client);
+ u->client = NULL;
+
+ if (!io) {
+ pa_log("Connection failed: %s", pa_cstrerror(errno));
+ pa_module_unload_request(u->module);
+ return;
+ }
+
+ pa_assert(!u->io);
+ u->io = io;
+ pa_iochannel_set_callback(u->io, io_callback, u);
+
+ pa_log_debug("Connection established, authenticating ...");
+}
+
+int pa__init(pa_module*m) {
+ struct userdata *u = NULL;
+ const char *p;
+ pa_sample_spec ss;
+ pa_modargs *ma = NULL;
+ char *t;
+ const char *espeaker;
+ uint32_t key;
+
+ pa_assert(m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log("failed to parse module arguments");
+ goto fail;
+ }
+
+ ss = m->core->default_sample_spec;
+ if (pa_modargs_get_sample_spec(ma, &ss) < 0) {
+ pa_log("invalid sample format specification");
+ goto fail;
+ }
+
+ if ((ss.format != PA_SAMPLE_U8 && ss.format != PA_SAMPLE_S16NE) ||
+ (ss.channels > 2)) {
+ pa_log("esound sample type support is limited to mono/stereo and U8 or S16NE sample data");
+ goto fail;
+ }
+
+ u = pa_xnew0(struct userdata, 1);
+ u->core = m->core;
+ u->module = m;
+ m->userdata = u;
+ u->fd = -1;
+ u->smoother = pa_smoother_new(PA_USEC_PER_SEC, PA_USEC_PER_SEC*2, TRUE);
+ pa_memchunk_reset(&u->memchunk);
+ u->offset = 0;
+
+ pa_thread_mq_init(&u->thread_mq, m->core->mainloop);
+ u->rtpoll = pa_rtpoll_new();
+ pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq);
+ u->rtpoll_item = NULL;
+
+ u->format =
+ (ss.format == PA_SAMPLE_U8 ? ESD_BITS8 : ESD_BITS16) |
+ (ss.channels == 2 ? ESD_STEREO : ESD_MONO);
+ u->rate = ss.rate;
+ u->block_size = pa_usec_to_bytes(PA_USEC_PER_SEC/20, &ss);
+
+ u->read_data = u->write_data = NULL;
+ u->read_index = u->write_index = u->read_length = u->write_length = 0;
+
+ u->state = STATE_AUTH;
+ u->latency = 0;
+
+ if (!(u->sink = pa_sink_new(m->core, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, NULL))) {
+ pa_log("Failed to create sink.");
+ goto fail;
+ }
+
+ u->sink->parent.process_msg = sink_process_msg;
+ u->sink->userdata = u;
+ u->sink->flags = PA_SINK_LATENCY|PA_SINK_NETWORK;
+
+ pa_sink_set_module(u->sink, m);
+ pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq);
+ pa_sink_set_rtpoll(u->sink, u->rtpoll);
+
+ if (!(espeaker = getenv("ESPEAKER")))
+ espeaker = ESD_UNIX_SOCKET_NAME;
+
+ if (!(u->client = pa_socket_client_new_string(u->core->mainloop, p = pa_modargs_get_value(ma, "server", espeaker), ESD_DEFAULT_PORT))) {
+ pa_log("Failed to connect to server.");
+ goto fail;
+ }
+
+ pa_sink_set_description(u->sink, t = pa_sprintf_malloc("Esound sink '%s'", p));
+ pa_xfree(t);
+
+ pa_socket_client_set_callback(u->client, on_connection, u);
+
+ /* Prepare the initial request */
+ u->write_data = pa_xmalloc(u->write_length = ESD_KEY_LEN + sizeof(int32_t));
+ if (pa_authkey_load_auto(pa_modargs_get_value(ma, "cookie", ".esd_auth"), u->write_data, ESD_KEY_LEN) < 0) {
+ pa_log("Failed to load cookie");
+ goto fail;
+ }
+
+ key = ESD_ENDIAN_KEY;
+ memcpy((uint8_t*) u->write_data + ESD_KEY_LEN, &key, sizeof(key));
+
+ /* Reserve space for the response */
+ u->read_data = pa_xmalloc(u->read_length = sizeof(int32_t));
+
+ if (!(u->thread = pa_thread_new(thread_func, u))) {
+ pa_log("Failed to create thread.");
+ goto fail;
+ }
+
+ pa_sink_put(u->sink);
+
+ pa_modargs_free(ma);
+
+ return 0;
+
+fail:
+ if (ma)
+ pa_modargs_free(ma);
+
+ pa__done(m);
+
+ return -1;
+}
+
+void pa__done(pa_module*m) {
+ struct userdata *u;
+ pa_assert(m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->sink)
+ pa_sink_unlink(u->sink);
+
+ if (u->thread) {
+ pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL);
+ pa_thread_free(u->thread);
+ }
+
+ pa_thread_mq_done(&u->thread_mq);
+
+ if (u->sink)
+ pa_sink_unref(u->sink);
+
+ if (u->io)
+ pa_iochannel_free(u->io);
+
+ if (u->rtpoll_item)
+ pa_rtpoll_item_free(u->rtpoll_item);
+
+ if (u->rtpoll)
+ pa_rtpoll_free(u->rtpoll);
+
+ if (u->memchunk.memblock)
+ pa_memblock_unref(u->memchunk.memblock);
+
+ if (u->client)
+ pa_socket_client_unref(u->client);
+
+ pa_xfree(u->read_data);
+ pa_xfree(u->write_data);
+
+ if (u->smoother)
+ pa_smoother_free(u->smoother);
+
+ if (u->fd >= 0)
+ pa_close(u->fd);
+
+ pa_xfree(u);
+}
diff --git a/src/modules/module-hal-detect.c b/src/modules/module-hal-detect.c
new file mode 100644
index 00000000..832bc73e
--- /dev/null
+++ b/src/modules/module-hal-detect.c
@@ -0,0 +1,851 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <pulse/xmalloc.h>
+#include <pulse/timeval.h>
+
+#include <pulsecore/core-error.h>
+#include <pulsecore/module.h>
+#include <pulsecore/log.h>
+#include <pulsecore/hashmap.h>
+#include <pulsecore/idxset.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/namereg.h>
+#include <pulsecore/core-scache.h>
+#include <pulsecore/modargs.h>
+
+#include <hal/libhal.h>
+
+#include "dbus-util.h"
+#include "module-hal-detect-symdef.h"
+
+PA_MODULE_AUTHOR("Shahms King");
+PA_MODULE_DESCRIPTION("Detect available audio hardware and load matching drivers");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(TRUE);
+#if defined(HAVE_ALSA) && defined(HAVE_OSS)
+PA_MODULE_USAGE("api=<alsa or oss>");
+#elif defined(HAVE_ALSA)
+PA_MODULE_USAGE("api=<alsa>");
+#elif defined(HAVE_OSS)
+PA_MODULE_USAGE("api=<oss>");
+#endif
+
+struct device {
+ uint32_t index;
+ char *udi;
+ char *sink_name, *source_name;
+ int acl_race_fix;
+};
+
+struct userdata {
+ pa_core *core;
+ LibHalContext *context;
+ pa_dbus_connection *connection;
+ pa_hashmap *devices;
+ const char *capability;
+};
+
+struct timerdata {
+ struct userdata *u;
+ char *udi;
+};
+
+#define CAPABILITY_ALSA "alsa"
+#define CAPABILITY_OSS "oss"
+
+static const char* const valid_modargs[] = {
+ "api",
+ NULL
+};
+
+static void hal_device_free(struct device* d) {
+ pa_assert(d);
+
+ pa_xfree(d->udi);
+ pa_xfree(d->sink_name);
+ pa_xfree(d->source_name);
+ pa_xfree(d);
+}
+
+static void hal_device_free_cb(void *d, PA_GCC_UNUSED void *data) {
+ hal_device_free(d);
+}
+
+static const char *strip_udi(const char *udi) {
+ const char *slash;
+
+ if ((slash = strrchr(udi, '/')))
+ return slash+1;
+
+ return udi;
+}
+
+#ifdef HAVE_ALSA
+
+typedef enum {
+ ALSA_TYPE_SINK,
+ ALSA_TYPE_SOURCE,
+ ALSA_TYPE_OTHER,
+ ALSA_TYPE_MAX
+} alsa_type_t;
+
+static alsa_type_t hal_alsa_device_get_type(LibHalContext *context, const char *udi, DBusError *error) {
+ char *type;
+ alsa_type_t t;
+
+ if (!(type = libhal_device_get_property_string(context, udi, "alsa.type", error)))
+ return ALSA_TYPE_OTHER;
+
+ if (!strcmp(type, "playback"))
+ t = ALSA_TYPE_SINK;
+ else if (!strcmp(type, "capture"))
+ t = ALSA_TYPE_SOURCE;
+ else
+ t = ALSA_TYPE_OTHER;
+
+ libhal_free_string(type);
+
+ return t;
+}
+
+static int hal_alsa_device_is_modem(LibHalContext *context, const char *udi, DBusError *error) {
+ char *class;
+ int r;
+
+ if (!(class = libhal_device_get_property_string(context, udi, "alsa.pcm_class", error)))
+ return 0;
+
+ r = strcmp(class, "modem") == 0;
+ pa_xfree(class);
+
+ return r;
+}
+
+static pa_module* hal_device_load_alsa(struct userdata *u, const char *udi, char **sink_name, char **source_name) {
+ char *args;
+ alsa_type_t type;
+ int device, card;
+ const char *module_name;
+ DBusError error;
+ pa_module *m;
+
+ dbus_error_init(&error);
+
+ pa_assert(u);
+ pa_assert(sink_name);
+ pa_assert(source_name);
+
+ *sink_name = *source_name = NULL;
+
+ type = hal_alsa_device_get_type(u->context, udi, &error);
+ if (dbus_error_is_set(&error) || type == ALSA_TYPE_OTHER)
+ goto fail;
+
+ device = libhal_device_get_property_int(u->context, udi, "alsa.device", &error);
+ if (dbus_error_is_set(&error) || device != 0)
+ goto fail;
+
+ card = libhal_device_get_property_int(u->context, udi, "alsa.card", &error);
+ if (dbus_error_is_set(&error))
+ goto fail;
+
+ if (hal_alsa_device_is_modem(u->context, udi, &error))
+ goto fail;
+
+ if (type == ALSA_TYPE_SINK) {
+ *sink_name = pa_sprintf_malloc("alsa_output.%s", strip_udi(udi));
+
+ module_name = "module-alsa-sink";
+ args = pa_sprintf_malloc("device_id=%u sink_name=%s", card, *sink_name);
+ } else {
+ *source_name = pa_sprintf_malloc("alsa_input.%s", strip_udi(udi));
+
+ module_name = "module-alsa-source";
+ args = pa_sprintf_malloc("device_id=%u source_name=%s", card, *source_name);
+ }
+
+ pa_log_debug("Loading %s with arguments '%s'", module_name, args);
+
+ m = pa_module_load(u->core, module_name, args);
+
+ pa_xfree(args);
+
+ if (!m) {
+ pa_xfree(*sink_name);
+ pa_xfree(*source_name);
+ *sink_name = *source_name = NULL;
+ }
+
+ return m;
+
+fail:
+ if (dbus_error_is_set(&error)) {
+ pa_log_error("D-Bus error while parsing ALSA data: %s: %s", error.name, error.message);
+ dbus_error_free(&error);
+ }
+
+ return NULL;
+}
+
+#endif
+
+#ifdef HAVE_OSS
+
+static int hal_oss_device_is_pcm(LibHalContext *context, const char *udi, DBusError *error) {
+ char *class = NULL, *dev = NULL, *e;
+ int device;
+ int r = 0;
+
+ class = libhal_device_get_property_string(context, udi, "oss.type", error);
+ if (dbus_error_is_set(error) || !class)
+ goto finish;
+
+ if (strcmp(class, "pcm"))
+ goto finish;
+
+ dev = libhal_device_get_property_string(context, udi, "oss.device_file", error);
+ if (dbus_error_is_set(error) || !dev)
+ goto finish;
+
+ if ((e = strrchr(dev, '/')))
+ if (pa_startswith(e + 1, "audio"))
+ goto finish;
+
+ device = libhal_device_get_property_int(context, udi, "oss.device", error);
+ if (dbus_error_is_set(error) || device != 0)
+ goto finish;
+
+ r = 1;
+
+finish:
+
+ libhal_free_string(class);
+ libhal_free_string(dev);
+
+ return r;
+}
+
+static pa_module* hal_device_load_oss(struct userdata *u, const char *udi, char **sink_name, char **source_name) {
+ char* args;
+ char* device;
+ DBusError error;
+ pa_module *m;
+
+ dbus_error_init(&error);
+
+ pa_assert(u);
+ pa_assert(sink_name);
+ pa_assert(source_name);
+
+ *sink_name = *source_name = NULL;
+
+ if (!hal_oss_device_is_pcm(u->context, udi, &error) || dbus_error_is_set(&error))
+ goto fail;
+
+ device = libhal_device_get_property_string(u->context, udi, "oss.device_file", &error);
+ if (!device || dbus_error_is_set(&error))
+ goto fail;
+
+ *sink_name = pa_sprintf_malloc("oss_output.%s", strip_udi(udi));
+ *source_name = pa_sprintf_malloc("oss_input.%s", strip_udi(udi));
+
+ args = pa_sprintf_malloc("device=%s sink_name=%s source_name=%s", device, *sink_name, *source_name);
+ libhal_free_string(device);
+
+ pa_log_debug("Loading module-oss with arguments '%s'", args);
+ m = pa_module_load(u->core, "module-oss", args);
+ pa_xfree(args);
+
+ if (!m) {
+ pa_xfree(*sink_name);
+ pa_xfree(*source_name);
+ *sink_name = *source_name = NULL;
+ }
+
+ return m;
+
+fail:
+ if (dbus_error_is_set(&error)) {
+ pa_log_error("D-Bus error while parsing OSS data: %s: %s", error.name, error.message);
+ dbus_error_free(&error);
+ }
+
+ return NULL;
+}
+#endif
+
+static struct device* hal_device_add(struct userdata *u, const char *udi) {
+ pa_module* m = NULL;
+ struct device *d;
+ char *sink_name = NULL, *source_name = NULL;
+
+ pa_assert(u);
+ pa_assert(u->capability);
+ pa_assert(!pa_hashmap_get(u->devices, udi));
+
+#ifdef HAVE_ALSA
+ if (strcmp(u->capability, CAPABILITY_ALSA) == 0)
+ m = hal_device_load_alsa(u, udi, &sink_name, &source_name);
+#endif
+#ifdef HAVE_OSS
+ if (strcmp(u->capability, CAPABILITY_OSS) == 0)
+ m = hal_device_load_oss(u, udi, &sink_name, &source_name);
+#endif
+
+ if (!m)
+ return NULL;
+
+ d = pa_xnew(struct device, 1);
+ d->acl_race_fix = 0;
+ d->udi = pa_xstrdup(udi);
+ d->index = m->index;
+ d->sink_name = sink_name;
+ d->source_name = source_name;
+ pa_hashmap_put(u->devices, d->udi, d);
+
+ return d;
+}
+
+static int hal_device_add_all(struct userdata *u, const char *capability) {
+ DBusError error;
+ int i, n, count = 0;
+ char** udis;
+
+ pa_assert(u);
+
+ dbus_error_init(&error);
+
+ if (u->capability && strcmp(u->capability, capability) != 0)
+ return 0;
+
+ pa_log_info("Trying capability %s", capability);
+
+ udis = libhal_find_device_by_capability(u->context, capability, &n, &error);
+ if (dbus_error_is_set(&error)) {
+ pa_log_error("Error finding devices: %s: %s", error.name, error.message);
+ dbus_error_free(&error);
+ return -1;
+ }
+
+ if (n > 0) {
+ u->capability = capability;
+
+ for (i = 0; i < n; i++) {
+ struct device *d;
+
+ if (!(d = hal_device_add(u, udis[i])))
+ pa_log_debug("Not loaded device %s", udis[i]);
+ else {
+ if (d->sink_name)
+ pa_scache_play_item_by_name(u->core, "pulse-coldplug", d->sink_name, PA_VOLUME_NORM, 0);
+ count++;
+ }
+ }
+ }
+
+ libhal_free_string_array(udis);
+ return count;
+}
+
+static dbus_bool_t device_has_capability(LibHalContext *context, const char *udi, const char* cap, DBusError *error){
+ dbus_bool_t has_prop;
+
+ has_prop = libhal_device_property_exists(context, udi, "info.capabilities", error);
+ if (!has_prop || dbus_error_is_set(error))
+ return FALSE;
+
+ return libhal_device_query_capability(context, udi, cap, error);
+}
+
+static void device_added_time_cb(pa_mainloop_api *ea, pa_time_event *ev, const struct timeval *tv, void *userdata) {
+ DBusError error;
+ struct timerdata *td = userdata;
+
+ dbus_error_init(&error);
+
+ if (!pa_hashmap_get(td->u->devices, td->udi)) {
+ int b;
+ struct device *d;
+
+ b = libhal_device_exists(td->u->context, td->udi, &error);
+
+ if (dbus_error_is_set(&error)) {
+ pa_log_error("Error adding device: %s: %s", error.name, error.message);
+ dbus_error_free(&error);
+ } else if (b) {
+ if (!(d = hal_device_add(td->u, td->udi)))
+ pa_log_debug("Not loaded device %s", td->udi);
+ else {
+ if (d->sink_name)
+ pa_scache_play_item_by_name(td->u->core, "pulse-hotplug", d->sink_name, PA_VOLUME_NORM, 0);
+ }
+ }
+ }
+
+ pa_xfree(td->udi);
+ pa_xfree(td);
+ ea->time_free(ev);
+}
+
+static void device_added_cb(LibHalContext *context, const char *udi) {
+ DBusError error;
+ struct timeval tv;
+ struct timerdata *t;
+ struct userdata *u;
+ int good = 0;
+
+ pa_assert_se(u = libhal_ctx_get_user_data(context));
+
+ if (pa_hashmap_get(u->devices, udi))
+ return;
+
+ pa_log_debug("HAL Device added: %s", udi);
+
+ dbus_error_init(&error);
+
+ if (u->capability) {
+
+ good = device_has_capability(context, udi, u->capability, &error);
+
+ if (dbus_error_is_set(&error)) {
+ pa_log_error("Error getting capability: %s: %s", error.name, error.message);
+ dbus_error_free(&error);
+ return;
+ }
+
+ } else {
+
+#ifdef HAVE_ALSA
+ good = device_has_capability(context, udi, CAPABILITY_ALSA, &error);
+
+ if (dbus_error_is_set(&error)) {
+ pa_log_error("Error getting capability: %s: %s", error.name, error.message);
+ dbus_error_free(&error);
+ return;
+ }
+
+ if (good)
+ u->capability = CAPABILITY_ALSA;
+#endif
+#if defined(HAVE_OSS) && defined(HAVE_ALSA)
+ if (!good) {
+#endif
+#ifdef HAS_OSS
+ good = device_has_capability(context, udi, CAPABILITY_OSS, &error);
+
+ if (dbus_error_is_set(&error)) {
+ pa_log_error("Error getting capability: %s: %s", error.name, error.message);
+ dbus_error_free(&error);
+ return;
+ }
+
+ if (good)
+ u->capability = CAPABILITY_OSS;
+
+#endif
+#if defined(HAVE_OSS) && defined(HAVE_ALSA)
+ }
+#endif
+ }
+
+ if (!good)
+ return;
+
+ /* actually add the device 1/2 second later */
+ t = pa_xnew(struct timerdata, 1);
+ t->u = u;
+ t->udi = pa_xstrdup(udi);
+
+ pa_gettimeofday(&tv);
+ pa_timeval_add(&tv, 500000);
+ u->core->mainloop->time_new(u->core->mainloop, &tv, device_added_time_cb, t);
+}
+
+static void device_removed_cb(LibHalContext* context, const char *udi) {
+ struct device *d;
+ struct userdata *u;
+
+ pa_assert_se(u = libhal_ctx_get_user_data(context));
+
+ pa_log_debug("Device removed: %s", udi);
+
+ if ((d = pa_hashmap_remove(u->devices, udi))) {
+ pa_module_unload_by_index(u->core, d->index);
+ hal_device_free(d);
+ }
+}
+
+static void new_capability_cb(LibHalContext *context, const char *udi, const char* capability) {
+ struct userdata *u;
+
+ pa_assert_se(u = libhal_ctx_get_user_data(context));
+
+ if (!u->capability || strcmp(u->capability, capability) == 0)
+ /* capability we care about, pretend it's a new device */
+ device_added_cb(context, udi);
+}
+
+static void lost_capability_cb(LibHalContext *context, const char *udi, const char* capability) {
+ struct userdata *u;
+
+ pa_assert_se(u = libhal_ctx_get_user_data(context));
+
+ if (u->capability && strcmp(u->capability, capability) == 0)
+ /* capability we care about, pretend it was removed */
+ device_removed_cb(context, udi);
+}
+
+static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, void *userdata) {
+ struct userdata*u = userdata;
+ DBusError error;
+
+ pa_assert(bus);
+ pa_assert(message);
+ pa_assert(u);
+
+ dbus_error_init(&error);
+
+ pa_log_debug("dbus: interface=%s, path=%s, member=%s\n",
+ dbus_message_get_interface(message),
+ dbus_message_get_path(message),
+ dbus_message_get_member(message));
+
+ if (dbus_message_is_signal(message, "org.freedesktop.Hal.Device.AccessControl", "ACLAdded") ||
+ dbus_message_is_signal(message, "org.freedesktop.Hal.Device.AccessControl", "ACLRemoved")) {
+ uint32_t uid;
+ int suspend = strcmp(dbus_message_get_member(message), "ACLRemoved") == 0;
+
+ if (!dbus_message_get_args(message, &error, DBUS_TYPE_UINT32, &uid, DBUS_TYPE_INVALID) || dbus_error_is_set(&error)) {
+ pa_log_error("Failed to parse ACL message: %s: %s", error.name, error.message);
+ goto finish;
+ }
+
+ if (uid == getuid() || uid == geteuid()) {
+ struct device *d;
+ const char *udi;
+
+ udi = dbus_message_get_path(message);
+
+ if ((d = pa_hashmap_get(u->devices, udi))) {
+ int send_acl_race_fix_message = 0;
+
+ d->acl_race_fix = 0;
+
+ if (d->sink_name) {
+ pa_sink *sink;
+
+ if ((sink = pa_namereg_get(u->core, d->sink_name, PA_NAMEREG_SINK, 0))) {
+ int prev_suspended = pa_sink_get_state(sink) == PA_SINK_SUSPENDED;
+
+ if (prev_suspended && !suspend) {
+ /* resume */
+ if (pa_sink_suspend(sink, 0) >= 0)
+ pa_scache_play_item_by_name(u->core, "pulse-access", d->sink_name, PA_VOLUME_NORM, 0);
+ else
+ d->acl_race_fix = 1;
+
+ } else if (!prev_suspended && suspend) {
+ /* suspend */
+ if (pa_sink_suspend(sink, 1) >= 0)
+ send_acl_race_fix_message = 1;
+ }
+ }
+ }
+
+ if (d->source_name) {
+ pa_source *source;
+
+ if ((source = pa_namereg_get(u->core, d->source_name, PA_NAMEREG_SOURCE, 0))) {
+ int prev_suspended = pa_source_get_state(source) == PA_SOURCE_SUSPENDED;
+
+ if (prev_suspended && !suspend) {
+ /* resume */
+ if (pa_source_suspend(source, 0) < 0)
+ d->acl_race_fix = 1;
+
+ } else if (!prev_suspended && suspend) {
+ /* suspend */
+ if (pa_source_suspend(source, 0) >= 0)
+ send_acl_race_fix_message = 1;
+ }
+ }
+ }
+
+ if (send_acl_race_fix_message) {
+ DBusMessage *msg;
+ msg = dbus_message_new_signal(udi, "org.pulseaudio.Server", "DirtyGiveUpMessage");
+ dbus_connection_send(pa_dbus_connection_get(u->connection), msg, NULL);
+ dbus_message_unref(msg);
+ }
+
+ } else if (!suspend)
+ device_added_cb(u->context, udi);
+ }
+
+ } else if (dbus_message_is_signal(message, "org.pulseaudio.Server", "DirtyGiveUpMessage")) {
+ /* We use this message to avoid a dirty race condition when we
+ get an ACLAdded message before the previously owning PA
+ sever has closed the device. We can remove this as
+ soon as HAL learns frevoke() */
+
+ const char *udi;
+ struct device *d;
+
+ udi = dbus_message_get_path(message);
+
+ if ((d = pa_hashmap_get(u->devices, udi)) && d->acl_race_fix) {
+ pa_log_debug("Got dirty give up message for '%s', trying resume ...", udi);
+
+ d->acl_race_fix = 0;
+
+ if (d->sink_name) {
+ pa_sink *sink;
+
+ if ((sink = pa_namereg_get(u->core, d->sink_name, PA_NAMEREG_SINK, 0))) {
+
+ int prev_suspended = pa_sink_get_state(sink) == PA_SINK_SUSPENDED;
+
+ if (prev_suspended) {
+ /* resume */
+ if (pa_sink_suspend(sink, 0) >= 0)
+ pa_scache_play_item_by_name(u->core, "pulse-access", d->sink_name, PA_VOLUME_NORM, 0);
+ }
+ }
+ }
+
+ if (d->source_name) {
+ pa_source *source;
+
+ if ((source = pa_namereg_get(u->core, d->source_name, PA_NAMEREG_SOURCE, 0))) {
+
+ int prev_suspended = pa_source_get_state(source) == PA_SOURCE_SUSPENDED;
+
+ if (prev_suspended)
+ pa_source_suspend(source, 0);
+ }
+ }
+
+ } else
+ /* Yes, we don't check the UDI for validity, but hopefully HAL will */
+ device_added_cb(u->context, udi);
+ }
+
+finish:
+ dbus_error_free(&error);
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static void hal_context_free(LibHalContext* hal_context) {
+ DBusError error;
+
+ dbus_error_init(&error);
+
+ libhal_ctx_shutdown(hal_context, &error);
+ libhal_ctx_free(hal_context);
+
+ dbus_error_free(&error);
+}
+
+static LibHalContext* hal_context_new(pa_core* c, DBusConnection *conn) {
+ DBusError error;
+ LibHalContext *hal_context = NULL;
+
+ dbus_error_init(&error);
+
+ if (!(hal_context = libhal_ctx_new())) {
+ pa_log_error("libhal_ctx_new() failed");
+ goto fail;
+ }
+
+ if (!libhal_ctx_set_dbus_connection(hal_context, conn)) {
+ pa_log_error("Error establishing DBUS connection: %s: %s", error.name, error.message);
+ goto fail;
+ }
+
+ if (!libhal_ctx_init(hal_context, &error)) {
+ pa_log_error("Couldn't connect to hald: %s: %s", error.name, error.message);
+ goto fail;
+ }
+
+ return hal_context;
+
+fail:
+ if (hal_context)
+ hal_context_free(hal_context);
+
+ dbus_error_free(&error);
+
+ return NULL;
+}
+
+int pa__init(pa_module*m) {
+ DBusError error;
+ pa_dbus_connection *conn;
+ struct userdata *u = NULL;
+ LibHalContext *hal_context = NULL;
+ int n = 0;
+ pa_modargs *ma;
+ const char *api;
+
+ pa_assert(m);
+
+ dbus_error_init(&error);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log("Failed to parse module arguments");
+ goto fail;
+ }
+
+ if ((api = pa_modargs_get_value(ma, "api", NULL))) {
+ int good = 0;
+
+#ifdef HAVE_ALSA
+ if (strcmp(api, CAPABILITY_ALSA) == 0) {
+ good = 1;
+ api = CAPABILITY_ALSA;
+ }
+#endif
+#ifdef HAVE_OSS
+ if (strcmp(api, CAPABILITY_OSS) == 0) {
+ good = 1;
+ api = CAPABILITY_OSS;
+ }
+#endif
+
+ if (!good) {
+ pa_log_error("Invalid API specification.");
+ goto fail;
+ }
+ }
+
+ if (!(conn = pa_dbus_bus_get(m->core, DBUS_BUS_SYSTEM, &error)) || dbus_error_is_set(&error)) {
+ if (conn)
+ pa_dbus_connection_unref(conn);
+ pa_log_error("Unable to contact DBUS system bus: %s: %s", error.name, error.message);
+ goto fail;
+ }
+
+ if (!(hal_context = hal_context_new(m->core, pa_dbus_connection_get(conn)))) {
+ /* pa_hal_context_new() logs appropriate errors */
+ pa_dbus_connection_unref(conn);
+ goto fail;
+ }
+
+ u = pa_xnew(struct userdata, 1);
+ u->core = m->core;
+ u->context = hal_context;
+ u->connection = conn;
+ u->devices = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+ u->capability = api;
+ m->userdata = u;
+
+#ifdef HAVE_ALSA
+ n = hal_device_add_all(u, CAPABILITY_ALSA);
+#endif
+#if defined(HAVE_ALSA) && defined(HAVE_OSS)
+ if (n <= 0)
+#endif
+#ifdef HAVE_OSS
+ n += hal_device_add_all(u, CAPABILITY_OSS);
+#endif
+
+ libhal_ctx_set_user_data(hal_context, u);
+ libhal_ctx_set_device_added(hal_context, device_added_cb);
+ libhal_ctx_set_device_removed(hal_context, device_removed_cb);
+ libhal_ctx_set_device_new_capability(hal_context, new_capability_cb);
+ libhal_ctx_set_device_lost_capability(hal_context, lost_capability_cb);
+
+ if (!libhal_device_property_watch_all(hal_context, &error)) {
+ pa_log_error("Error monitoring device list: %s: %s", error.name, error.message);
+ goto fail;
+ }
+
+ if (!dbus_connection_add_filter(pa_dbus_connection_get(conn), filter_cb, u, NULL)) {
+ pa_log_error("Failed to add filter function");
+ goto fail;
+ }
+
+ dbus_bus_add_match(pa_dbus_connection_get(conn), "type='signal',sender='org.freedesktop.Hal', interface='org.freedesktop.Hal.Device.AccessControl'", &error);
+ if (dbus_error_is_set(&error)) {
+ pa_log_error("Unable to subscribe to HAL ACL signals: %s: %s", error.name, error.message);
+ goto fail;
+ }
+
+ dbus_bus_add_match(pa_dbus_connection_get(conn), "type='signal',interface='org.pulseaudio.Server'", &error);
+ if (dbus_error_is_set(&error)) {
+ pa_log_error("Unable to subscribe to PulseAudio signals: %s: %s", error.name, error.message);
+ goto fail;
+ }
+
+ pa_log_info("Loaded %i modules.", n);
+
+ pa_modargs_free(ma);
+
+ return 0;
+
+fail:
+ if (ma)
+ pa_modargs_free(ma);
+
+ dbus_error_free(&error);
+ pa__done(m);
+
+ return -1;
+}
+
+
+void pa__done(pa_module *m) {
+ struct userdata *u;
+
+ pa_assert(m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->context)
+ hal_context_free(u->context);
+
+ if (u->devices)
+ pa_hashmap_free(u->devices, hal_device_free_cb, NULL);
+
+ if (u->connection)
+ pa_dbus_connection_unref(u->connection);
+
+ pa_xfree(u);
+}
diff --git a/src/modules/module-jack-sink.c b/src/modules/module-jack-sink.c
new file mode 100644
index 00000000..a42aa9ef
--- /dev/null
+++ b/src/modules/module-jack-sink.c
@@ -0,0 +1,456 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+
+#include <jack/jack.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/core-error.h>
+#include <pulsecore/sink.h>
+#include <pulsecore/module.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/log.h>
+#include <pulsecore/thread.h>
+#include <pulsecore/thread-mq.h>
+#include <pulsecore/rtpoll.h>
+#include <pulsecore/sample-util.h>
+
+#include "module-jack-sink-symdef.h"
+
+/* General overview:
+ *
+ * Because JACK has a very unflexible event loop management, which
+ * doesn't allow us to add our own event sources to the event thread
+ * we cannot use the JACK real-time thread for dispatching our PA
+ * work. Instead, we run an additional RT thread which does most of
+ * the PA handling, and have the JACK RT thread request data from it
+ * via pa_asyncmsgq. The cost is an additional context switch which
+ * should hopefully not be that expensive if RT scheduling is
+ * enabled. A better fix would only be possible with additional event
+ * source support in JACK.
+ */
+
+PA_MODULE_AUTHOR("Lennart Poettering");
+PA_MODULE_DESCRIPTION("JACK Sink");
+PA_MODULE_LOAD_ONCE(TRUE);
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_USAGE(
+ "sink_name=<name of sink> "
+ "server_name=<jack server name> "
+ "client_name=<jack client name> "
+ "channels=<number of channels> "
+ "connect=<connect ports?> "
+ "channel_map=<channel map>");
+
+#define DEFAULT_SINK_NAME "jack_out"
+
+struct userdata {
+ pa_core *core;
+ pa_module *module;
+ pa_sink *sink;
+
+ unsigned channels;
+
+ jack_port_t* port[PA_CHANNELS_MAX];
+ jack_client_t *client;
+
+ void *buffer[PA_CHANNELS_MAX];
+
+ pa_thread_mq thread_mq;
+ pa_asyncmsgq *jack_msgq;
+ pa_rtpoll *rtpoll;
+ pa_rtpoll_item *rtpoll_item;
+
+ pa_thread *thread;
+
+ jack_nframes_t frames_in_buffer;
+ jack_nframes_t saved_frame_time;
+ pa_bool_t saved_frame_time_valid;
+};
+
+static const char* const valid_modargs[] = {
+ "sink_name",
+ "server_name",
+ "client_name",
+ "channels",
+ "connect",
+ "channel_map",
+ NULL
+};
+
+enum {
+ SINK_MESSAGE_RENDER = PA_SINK_MESSAGE_MAX,
+ SINK_MESSAGE_ON_SHUTDOWN
+};
+
+static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *memchunk) {
+ struct userdata *u = PA_SINK(o)->userdata;
+
+ switch (code) {
+
+ case SINK_MESSAGE_RENDER:
+
+ /* Handle the request from the JACK thread */
+
+ if (u->sink->thread_info.state == PA_SINK_RUNNING) {
+ pa_memchunk chunk;
+ size_t nbytes;
+ void *p;
+
+ pa_assert(offset > 0);
+ nbytes = offset * pa_frame_size(&u->sink->sample_spec);
+
+ pa_sink_render_full(u->sink, nbytes, &chunk);
+
+ p = (uint8_t*) pa_memblock_acquire(chunk.memblock) + chunk.index;
+ pa_deinterleave(p, u->buffer, u->channels, sizeof(float), offset);
+ pa_memblock_release(chunk.memblock);
+
+ pa_memblock_unref(chunk.memblock);
+ } else {
+ unsigned c;
+ pa_sample_spec ss;
+
+ /* Humm, we're not RUNNING, hence let's write some silence */
+
+ ss = u->sink->sample_spec;
+ ss.channels = 1;
+
+ for (c = 0; c < u->channels; c++)
+ pa_silence_memory(u->buffer[c], offset * pa_sample_size(&ss), &ss);
+ }
+
+ u->frames_in_buffer = offset;
+ u->saved_frame_time = * (jack_nframes_t*) data;
+ u->saved_frame_time_valid = TRUE;
+
+ return 0;
+
+ case SINK_MESSAGE_ON_SHUTDOWN:
+ pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL);
+ return 0;
+
+ case PA_SINK_MESSAGE_GET_LATENCY: {
+ jack_nframes_t l, ft, d;
+ size_t n;
+
+ /* This is the "worst-case" latency */
+ l = jack_port_get_total_latency(u->client, u->port[0]) + u->frames_in_buffer;
+
+ if (u->saved_frame_time_valid) {
+ /* Adjust the worst case latency by the time that
+ * passed since we last handed data to JACK */
+
+ ft = jack_frame_time(u->client);
+ d = ft > u->saved_frame_time ? ft - u->saved_frame_time : 0;
+ l = l > d ? l - d : 0;
+ }
+
+ /* Convert it to usec */
+ n = l * pa_frame_size(&u->sink->sample_spec);
+ *((pa_usec_t*) data) = pa_bytes_to_usec(n, &u->sink->sample_spec);
+
+ return 0;
+ }
+ }
+
+ return pa_sink_process_msg(o, code, data, offset, memchunk);
+}
+
+static int jack_process(jack_nframes_t nframes, void *arg) {
+ struct userdata *u = arg;
+ unsigned c;
+ jack_nframes_t frame_time;
+ pa_assert(u);
+
+ /* We just forward the request to our other RT thread */
+
+ for (c = 0; c < u->channels; c++)
+ pa_assert_se(u->buffer[c] = jack_port_get_buffer(u->port[c], nframes));
+
+ frame_time = jack_frame_time(u->client);
+
+ pa_assert_se(pa_asyncmsgq_send(u->jack_msgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_RENDER, &frame_time, nframes, NULL) == 0);
+ return 0;
+}
+
+static void thread_func(void *userdata) {
+ struct userdata *u = userdata;
+
+ pa_assert(u);
+
+ pa_log_debug("Thread starting up");
+
+ if (u->core->realtime_scheduling)
+ pa_make_realtime(u->core->realtime_priority);
+
+ pa_thread_mq_install(&u->thread_mq);
+ pa_rtpoll_install(u->rtpoll);
+
+ for (;;) {
+ int ret;
+
+ if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0)
+ goto fail;
+
+ if (ret == 0)
+ goto finish;
+ }
+
+fail:
+ /* If this was no regular exit from the loop we have to continue
+ * processing messages until we received PA_MESSAGE_SHUTDOWN */
+ pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL);
+ pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN);
+
+finish:
+ pa_log_debug("Thread shutting down");
+}
+
+static void jack_error_func(const char*t) {
+ char *s;
+
+ s = pa_xstrndup(t, strcspn(t, "\n\r"));
+ pa_log_warn("JACK error >%s<", s);
+ pa_xfree(s);
+}
+
+static void jack_init(void *arg) {
+ struct userdata *u = arg;
+
+ pa_log_info("JACK thread starting up.");
+
+ if (u->core->realtime_scheduling)
+ pa_make_realtime(u->core->realtime_priority+4);
+}
+
+static void jack_shutdown(void* arg) {
+ struct userdata *u = arg;
+
+ pa_log_info("JACK thread shutting down..");
+ pa_asyncmsgq_post(u->jack_msgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_ON_SHUTDOWN, NULL, 0, NULL, NULL);
+}
+
+int pa__init(pa_module*m) {
+ struct userdata *u = NULL;
+ pa_sample_spec ss;
+ pa_channel_map map;
+ pa_modargs *ma = NULL;
+ jack_status_t status;
+ const char *server_name, *client_name;
+ uint32_t channels = 0;
+ pa_bool_t do_connect = TRUE;
+ unsigned i;
+ const char **ports = NULL, **p;
+ char *t;
+
+ pa_assert(m);
+
+ jack_set_error_function(jack_error_func);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log("Failed to parse module arguments.");
+ goto fail;
+ }
+
+ if (pa_modargs_get_value_boolean(ma, "connect", &do_connect) < 0) {
+ pa_log("Failed to parse connect= argument.");
+ goto fail;
+ }
+
+ server_name = pa_modargs_get_value(ma, "server_name", NULL);
+ client_name = pa_modargs_get_value(ma, "client_name", "PulseAudio JACK Sink");
+
+ u = pa_xnew0(struct userdata, 1);
+ u->core = m->core;
+ u->module = m;
+ m->userdata = u;
+ u->saved_frame_time_valid = FALSE;
+ pa_thread_mq_init(&u->thread_mq, m->core->mainloop);
+ u->rtpoll = pa_rtpoll_new();
+ pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq);
+
+ /* The queue linking the JACK thread and our RT thread */
+ u->jack_msgq = pa_asyncmsgq_new(0);
+
+ /* The msgq from the JACK RT thread should have an even higher
+ * priority than the normal message queues, to match the guarantee
+ * all other drivers make: supplying the audio device with data is
+ * the top priority -- and as long as that is possible we don't do
+ * anything else */
+ u->rtpoll_item = pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY-1, u->jack_msgq);
+
+ if (!(u->client = jack_client_open(client_name, server_name ? JackServerName : JackNullOption, &status, server_name))) {
+ pa_log("jack_client_open() failed.");
+ goto fail;
+ }
+
+ ports = jack_get_ports(u->client, NULL, NULL, JackPortIsPhysical|JackPortIsInput);
+
+ channels = 0;
+ for (p = ports; *p; p++)
+ channels++;
+
+ if (!channels)
+ channels = m->core->default_sample_spec.channels;
+
+ if (pa_modargs_get_value_u32(ma, "channels", &channels) < 0 || channels <= 0 || channels >= PA_CHANNELS_MAX) {
+ pa_log("Failed to parse channels= argument.");
+ goto fail;
+ }
+
+ pa_assert_se(pa_channel_map_init_auto(&map, channels, PA_CHANNEL_MAP_AUX));
+ pa_channel_map_init_auto(&map, channels, PA_CHANNEL_MAP_ALSA);
+ if (pa_modargs_get_channel_map(ma, NULL, &map) < 0 || map.channels != channels) {
+ pa_log("Failed to parse channel_map= argument.");
+ goto fail;
+ }
+
+ pa_log_info("Successfully connected as '%s'", jack_get_client_name(u->client));
+
+ ss.channels = u->channels = channels;
+ ss.rate = jack_get_sample_rate(u->client);
+ ss.format = PA_SAMPLE_FLOAT32NE;
+
+ pa_assert(pa_sample_spec_valid(&ss));
+
+ for (i = 0; i < ss.channels; i++) {
+ if (!(u->port[i] = jack_port_register(u->client, pa_channel_position_to_string(map.map[i]), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput|JackPortIsTerminal, 0))) {
+ pa_log("jack_port_register() failed.");
+ goto fail;
+ }
+ }
+
+ if (!(u->sink = pa_sink_new(m->core, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, &map))) {
+ pa_log("failed to create sink.");
+ goto fail;
+ }
+
+ u->sink->parent.process_msg = sink_process_msg;
+ u->sink->userdata = u;
+ u->sink->flags = PA_SINK_LATENCY;
+
+ pa_sink_set_module(u->sink, m);
+ pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq);
+ pa_sink_set_rtpoll(u->sink, u->rtpoll);
+ pa_sink_set_description(u->sink, t = pa_sprintf_malloc("Jack sink (%s)", jack_get_client_name(u->client)));
+ pa_xfree(t);
+
+ jack_set_process_callback(u->client, jack_process, u);
+ jack_on_shutdown(u->client, jack_shutdown, u);
+ jack_set_thread_init_callback(u->client, jack_init, u);
+
+ if (!(u->thread = pa_thread_new(thread_func, u))) {
+ pa_log("Failed to create thread.");
+ goto fail;
+ }
+
+ if (jack_activate(u->client)) {
+ pa_log("jack_activate() failed");
+ goto fail;
+ }
+
+ if (do_connect) {
+ for (i = 0, p = ports; i < ss.channels; i++, p++) {
+
+ if (!*p) {
+ pa_log("Not enough physical output ports, leaving unconnected.");
+ break;
+ }
+
+ pa_log_info("Connecting %s to %s", jack_port_name(u->port[i]), *p);
+
+ if (jack_connect(u->client, jack_port_name(u->port[i]), *p)) {
+ pa_log("Failed to connect %s to %s, leaving unconnected.", jack_port_name(u->port[i]), *p);
+ break;
+ }
+ }
+ }
+
+ pa_sink_put(u->sink);
+
+ free(ports);
+ pa_modargs_free(ma);
+
+ return 0;
+
+fail:
+ if (ma)
+ pa_modargs_free(ma);
+
+ free(ports);
+
+ pa__done(m);
+
+ return -1;
+}
+
+void pa__done(pa_module*m) {
+ struct userdata *u;
+
+ pa_assert(m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->client)
+ jack_client_close(u->client);
+
+ if (u->sink)
+ pa_sink_unlink(u->sink);
+
+ if (u->thread) {
+ pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL);
+ pa_thread_free(u->thread);
+ }
+
+ pa_thread_mq_done(&u->thread_mq);
+
+ if (u->sink)
+ pa_sink_unref(u->sink);
+
+ if (u->rtpoll_item)
+ pa_rtpoll_item_free(u->rtpoll_item);
+
+ if (u->jack_msgq)
+ pa_asyncmsgq_unref(u->jack_msgq);
+
+ if (u->rtpoll)
+ pa_rtpoll_free(u->rtpoll);
+
+ pa_xfree(u);
+}
diff --git a/src/modules/module-jack-source.c b/src/modules/module-jack-source.c
new file mode 100644
index 00000000..4ee08bf1
--- /dev/null
+++ b/src/modules/module-jack-source.c
@@ -0,0 +1,427 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+
+#include <jack/jack.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/core-error.h>
+#include <pulsecore/source.h>
+#include <pulsecore/module.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/log.h>
+#include <pulsecore/thread.h>
+#include <pulsecore/thread-mq.h>
+#include <pulsecore/rtpoll.h>
+#include <pulsecore/sample-util.h>
+
+#include "module-jack-source-symdef.h"
+
+/* See module-jack-sink for a few comments how this module basically
+ * works */
+
+PA_MODULE_AUTHOR("Lennart Poettering");
+PA_MODULE_DESCRIPTION("JACK Source");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(TRUE);
+PA_MODULE_USAGE(
+ "source_name=<name of source> "
+ "server_name=<jack server name> "
+ "client_name=<jack client name> "
+ "channels=<number of channels> "
+ "connect=<connect ports?>"
+ "channel_map=<channel map>");
+
+#define DEFAULT_SOURCE_NAME "jack_in"
+
+struct userdata {
+ pa_core *core;
+ pa_module *module;
+ pa_source *source;
+
+ unsigned channels;
+
+ jack_port_t* port[PA_CHANNELS_MAX];
+ jack_client_t *client;
+
+ pa_thread_mq thread_mq;
+ pa_asyncmsgq *jack_msgq;
+ pa_rtpoll *rtpoll;
+ pa_rtpoll_item *rtpoll_item;
+
+ pa_thread *thread;
+
+ jack_nframes_t saved_frame_time;
+ pa_bool_t saved_frame_time_valid;
+};
+
+static const char* const valid_modargs[] = {
+ "source_name",
+ "server_name",
+ "client_name",
+ "channels",
+ "connect",
+ "channel_map",
+ NULL
+};
+
+enum {
+ SOURCE_MESSAGE_POST = PA_SOURCE_MESSAGE_MAX,
+ SOURCE_MESSAGE_ON_SHUTDOWN
+};
+
+static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+ struct userdata *u = PA_SOURCE(o)->userdata;
+
+ switch (code) {
+
+ case SOURCE_MESSAGE_POST:
+
+ /* Handle the new block from the JACK thread */
+ pa_assert(chunk);
+ pa_assert(chunk->length > 0);
+
+ if (u->source->thread_info.state == PA_SOURCE_RUNNING)
+ pa_source_post(u->source, chunk);
+
+ u->saved_frame_time = offset;
+ u->saved_frame_time_valid = TRUE;
+
+ return 0;
+
+ case SOURCE_MESSAGE_ON_SHUTDOWN:
+ pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL);
+ return 0;
+
+ case PA_SOURCE_MESSAGE_GET_LATENCY: {
+ jack_nframes_t l, ft, d;
+ size_t n;
+
+ /* This is the "worst-case" latency */
+ l = jack_port_get_total_latency(u->client, u->port[0]);
+
+ if (u->saved_frame_time_valid) {
+ /* Adjust the worst case latency by the time that
+ * passed since we last handed data to JACK */
+
+ ft = jack_frame_time(u->client);
+ d = ft > u->saved_frame_time ? ft - u->saved_frame_time : 0;
+ l += d;
+ }
+
+ /* Convert it to usec */
+ n = l * pa_frame_size(&u->source->sample_spec);
+ *((pa_usec_t*) data) = pa_bytes_to_usec(n, &u->source->sample_spec);
+
+ return 0;
+ }
+ }
+
+ return pa_source_process_msg(o, code, data, offset, chunk);
+}
+
+static int jack_process(jack_nframes_t nframes, void *arg) {
+ unsigned c;
+ struct userdata *u = arg;
+ const void *buffer[PA_CHANNELS_MAX];
+ void *p;
+ jack_nframes_t frame_time;
+ pa_memchunk chunk;
+
+ pa_assert(u);
+
+ for (c = 0; c < u->channels; c++)
+ pa_assert(buffer[c] = jack_port_get_buffer(u->port[c], nframes));
+
+ /* We interleave the data and pass it on to the other RT thread */
+
+ pa_memchunk_reset(&chunk);
+ chunk.length = nframes * pa_frame_size(&u->source->sample_spec);
+ chunk.memblock = pa_memblock_new(u->core->mempool, chunk.length);
+ p = pa_memblock_acquire(chunk.memblock);
+ pa_interleave(buffer, u->channels, p, sizeof(float), nframes);
+ pa_memblock_release(chunk.memblock);
+
+ frame_time = jack_frame_time(u->client);
+
+ pa_asyncmsgq_post(u->jack_msgq, PA_MSGOBJECT(u->source), SOURCE_MESSAGE_POST, NULL, frame_time, &chunk, NULL);
+
+ pa_memblock_unref(chunk.memblock);
+
+ return 0;
+}
+
+static void thread_func(void *userdata) {
+ struct userdata *u = userdata;
+
+ pa_assert(u);
+
+ pa_log_debug("Thread starting up");
+
+ if (u->core->realtime_scheduling)
+ pa_make_realtime(u->core->realtime_priority);
+
+ pa_thread_mq_install(&u->thread_mq);
+ pa_rtpoll_install(u->rtpoll);
+
+ for (;;) {
+ int ret;
+
+ if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0)
+ goto fail;
+
+ if (ret == 0)
+ goto finish;
+ }
+
+fail:
+ /* If this was no regular exit from the loop we have to continue
+ * processing messages until we received PA_MESSAGE_SHUTDOWN */
+ pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL);
+ pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN);
+
+finish:
+ pa_log_debug("Thread shutting down");
+}
+
+static void jack_error_func(const char*t) {
+ char *s;
+
+ s = pa_xstrndup(t, strcspn(t, "\n\r"));
+ pa_log_warn("JACK error >%s<", s);
+ pa_xfree(s);
+}
+
+static void jack_init(void *arg) {
+ struct userdata *u = arg;
+
+ pa_log_info("JACK thread starting up.");
+
+ if (u->core->realtime_scheduling)
+ pa_make_realtime(u->core->realtime_priority+4);
+}
+
+static void jack_shutdown(void* arg) {
+ struct userdata *u = arg;
+
+ pa_log_info("JACK thread shutting down..");
+ pa_asyncmsgq_post(u->jack_msgq, PA_MSGOBJECT(u->source), SOURCE_MESSAGE_ON_SHUTDOWN, NULL, 0, NULL, NULL);
+}
+
+int pa__init(pa_module*m) {
+ struct userdata *u = NULL;
+ pa_sample_spec ss;
+ pa_channel_map map;
+ pa_modargs *ma = NULL;
+ jack_status_t status;
+ const char *server_name, *client_name;
+ uint32_t channels = 0;
+ pa_bool_t do_connect = TRUE;
+ unsigned i;
+ const char **ports = NULL, **p;
+ char *t;
+
+ pa_assert(m);
+
+ jack_set_error_function(jack_error_func);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log("Failed to parse module arguments.");
+ goto fail;
+ }
+
+ if (pa_modargs_get_value_boolean(ma, "connect", &do_connect) < 0) {
+ pa_log("Failed to parse connect= argument.");
+ goto fail;
+ }
+
+ server_name = pa_modargs_get_value(ma, "server_name", NULL);
+ client_name = pa_modargs_get_value(ma, "client_name", "PulseAudio JACK Source");
+
+ u = pa_xnew0(struct userdata, 1);
+ u->core = m->core;
+ u->module = m;
+ m->userdata = u;
+ u->saved_frame_time_valid = FALSE;
+
+ pa_thread_mq_init(&u->thread_mq, m->core->mainloop);
+ u->rtpoll = pa_rtpoll_new();
+ pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq);
+
+ u->jack_msgq = pa_asyncmsgq_new(0);
+ u->rtpoll_item = pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY-1, u->jack_msgq);
+
+ if (!(u->client = jack_client_open(client_name, server_name ? JackServerName : JackNullOption, &status, server_name))) {
+ pa_log("jack_client_open() failed.");
+ goto fail;
+ }
+
+ ports = jack_get_ports(u->client, NULL, NULL, JackPortIsPhysical|JackPortIsOutput);
+
+ channels = 0;
+ for (p = ports; *p; p++)
+ channels++;
+
+ if (!channels)
+ channels = m->core->default_sample_spec.channels;
+
+ if (pa_modargs_get_value_u32(ma, "channels", &channels) < 0 || channels <= 0 || channels >= PA_CHANNELS_MAX) {
+ pa_log("failed to parse channels= argument.");
+ goto fail;
+ }
+
+ pa_assert_se(pa_channel_map_init_auto(&map, channels, PA_CHANNEL_MAP_AUX));
+ pa_channel_map_init_auto(&map, channels, PA_CHANNEL_MAP_ALSA);
+ if (pa_modargs_get_channel_map(ma, NULL, &map) < 0 || map.channels != channels) {
+ pa_log("failed to parse channel_map= argument.");
+ goto fail;
+ }
+
+ pa_log_info("Successfully connected as '%s'", jack_get_client_name(u->client));
+
+ ss.channels = u->channels = channels;
+ ss.rate = jack_get_sample_rate(u->client);
+ ss.format = PA_SAMPLE_FLOAT32NE;
+
+ pa_assert(pa_sample_spec_valid(&ss));
+
+ for (i = 0; i < ss.channels; i++) {
+ if (!(u->port[i] = jack_port_register(u->client, pa_channel_position_to_string(map.map[i]), JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput|JackPortIsTerminal, 0))) {
+ pa_log("jack_port_register() failed.");
+ goto fail;
+ }
+ }
+
+ if (!(u->source = pa_source_new(m->core, __FILE__, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &ss, &map))) {
+ pa_log("failed to create source.");
+ goto fail;
+ }
+
+ u->source->parent.process_msg = source_process_msg;
+ u->source->userdata = u;
+ u->source->flags = PA_SOURCE_LATENCY;
+
+ pa_source_set_module(u->source, m);
+ pa_source_set_asyncmsgq(u->source, u->thread_mq.inq);
+ pa_source_set_rtpoll(u->source, u->rtpoll);
+ pa_source_set_description(u->source, t = pa_sprintf_malloc("Jack source (%s)", jack_get_client_name(u->client)));
+ pa_xfree(t);
+
+ jack_set_process_callback(u->client, jack_process, u);
+ jack_on_shutdown(u->client, jack_shutdown, u);
+ jack_set_thread_init_callback(u->client, jack_init, u);
+
+ if (!(u->thread = pa_thread_new(thread_func, u))) {
+ pa_log("Failed to create thread.");
+ goto fail;
+ }
+
+ if (jack_activate(u->client)) {
+ pa_log("jack_activate() failed");
+ goto fail;
+ }
+
+ if (do_connect) {
+ for (i = 0, p = ports; i < ss.channels; i++, p++) {
+
+ if (!*p) {
+ pa_log("not enough physical output ports, leaving unconnected.");
+ break;
+ }
+
+ pa_log_info("connecting %s to %s", jack_port_name(u->port[i]), *p);
+
+ if (jack_connect(u->client, *p, jack_port_name(u->port[i]))) {
+ pa_log("failed to connect %s to %s, leaving unconnected.", jack_port_name(u->port[i]), *p);
+ break;
+ }
+ }
+
+ }
+
+ pa_source_put(u->source);
+
+ free(ports);
+ pa_modargs_free(ma);
+
+ return 0;
+
+fail:
+ if (ma)
+ pa_modargs_free(ma);
+
+ free(ports);
+
+ pa__done(m);
+
+ return -1;
+}
+
+void pa__done(pa_module*m) {
+ struct userdata *u;
+ pa_assert(m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->client)
+ jack_client_close(u->client);
+
+ if (u->source)
+ pa_source_unlink(u->source);
+
+ if (u->thread) {
+ pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL);
+ pa_thread_free(u->thread);
+ }
+
+ pa_thread_mq_done(&u->thread_mq);
+
+ if (u->source)
+ pa_source_unref(u->source);
+
+ if (u->rtpoll_item)
+ pa_rtpoll_item_free(u->rtpoll_item);
+
+ if (u->jack_msgq)
+ pa_asyncmsgq_unref(u->jack_msgq);
+
+ if (u->rtpoll)
+ pa_rtpoll_free(u->rtpoll);
+
+ pa_xfree(u);
+}
diff --git a/src/modules/module-ladspa-sink.c b/src/modules/module-ladspa-sink.c
new file mode 100644
index 00000000..b31037b6
--- /dev/null
+++ b/src/modules/module-ladspa-sink.c
@@ -0,0 +1,684 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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.
+***/
+
+/* TODO: Some plugins cause latency, and some even report it by using a control
+ out port. We don't currently use the latency information. */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/core-error.h>
+#include <pulsecore/namereg.h>
+#include <pulsecore/sink.h>
+#include <pulsecore/module.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/log.h>
+#include <pulsecore/thread.h>
+#include <pulsecore/thread-mq.h>
+#include <pulsecore/rtpoll.h>
+#include <pulsecore/sample-util.h>
+
+#include "module-ladspa-sink-symdef.h"
+#include "ladspa.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering");
+PA_MODULE_DESCRIPTION("Virtual LADSPA sink");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(FALSE);
+PA_MODULE_USAGE(
+ "sink_name=<name for the sink> "
+ "master=<name of sink to remap> "
+ "format=<sample format> "
+ "channels=<number of channels> "
+ "rate=<sample rate> "
+ "channel_map=<channel map> "
+ "plugin=<ladspa plugin name> "
+ "label=<ladspa plugin label> "
+ "control=<comma seperated list of input control values>");
+
+struct userdata {
+ pa_core *core;
+ pa_module *module;
+
+ pa_sink *sink, *master;
+ pa_sink_input *sink_input;
+
+ const LADSPA_Descriptor *descriptor;
+ unsigned channels;
+ LADSPA_Handle handle[PA_CHANNELS_MAX];
+ LADSPA_Data *input, *output;
+ size_t block_size;
+ unsigned long input_port, output_port;
+ LADSPA_Data *control;
+
+ /* This is a dummy buffer. Every port must be connected, but we don't care
+ about control out ports. We connect them all to this single buffer. */
+ LADSPA_Data control_out;
+
+ pa_memchunk memchunk;
+};
+
+static const char* const valid_modargs[] = {
+ "sink_name",
+ "master",
+ "format",
+ "channels",
+ "rate",
+ "channel_map",
+ "plugin",
+ "label",
+ "control",
+ NULL
+};
+
+/* Called from I/O thread context */
+static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+ struct userdata *u = PA_SINK(o)->userdata;
+
+ switch (code) {
+
+ case PA_SINK_MESSAGE_GET_LATENCY: {
+ pa_usec_t usec = 0;
+
+ if (PA_MSGOBJECT(u->master)->process_msg(PA_MSGOBJECT(u->master), PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0)
+ usec = 0;
+
+ *((pa_usec_t*) data) = usec + pa_bytes_to_usec(u->memchunk.length, &u->sink->sample_spec);
+ return 0;
+ }
+ }
+
+ return pa_sink_process_msg(o, code, data, offset, chunk);
+}
+
+/* Called from main context */
+static int sink_set_state(pa_sink *s, pa_sink_state_t state) {
+ struct userdata *u;
+
+ pa_sink_assert_ref(s);
+ pa_assert_se(u = s->userdata);
+
+ if (PA_SINK_LINKED(state) && u->sink_input && PA_SINK_INPUT_LINKED(pa_sink_input_get_state(u->sink_input)))
+ pa_sink_input_cork(u->sink_input, state == PA_SINK_SUSPENDED);
+
+ return 0;
+}
+
+/* Called from I/O thread context */
+static int sink_input_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+ struct userdata *u = PA_SINK_INPUT(o)->userdata;
+
+ switch (code) {
+ case PA_SINK_INPUT_MESSAGE_GET_LATENCY:
+ *((pa_usec_t*) data) = pa_bytes_to_usec(u->memchunk.length, &u->sink_input->sample_spec);
+
+ /* Fall through, the default handler will add in the extra
+ * latency added by the resampler */
+ break;
+ }
+
+ return pa_sink_input_process_msg(o, code, data, offset, chunk);
+}
+
+/* Called from I/O thread context */
+static int sink_input_peek_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ if (!u->memchunk.memblock) {
+ pa_memchunk tchunk;
+ float *src, *dst;
+ size_t fs;
+ unsigned n, c;
+
+ pa_sink_render(u->sink, length, &tchunk);
+
+ fs = pa_frame_size(&i->sample_spec);
+ n = tchunk.length / fs;
+
+ pa_assert(n > 0);
+
+ u->memchunk.memblock = pa_memblock_new(i->sink->core->mempool, tchunk.length);
+ u->memchunk.index = 0;
+ u->memchunk.length = tchunk.length;
+
+ src = (float*) ((uint8_t*) pa_memblock_acquire(tchunk.memblock) + tchunk.index);
+ dst = (float*) pa_memblock_acquire(u->memchunk.memblock);
+
+ for (c = 0; c < u->channels; c++) {
+ unsigned j;
+ float *p, *q;
+
+ p = src + c;
+ q = u->input;
+ for (j = 0; j < n; j++, p += u->channels, q++)
+ *q = PA_CLAMP_UNLIKELY(*p, -1.0, 1.0);
+
+ u->descriptor->run(u->handle[c], n);
+
+ q = u->output;
+ p = dst + c;
+ for (j = 0; j < n; j++, q++, p += u->channels)
+ *p = PA_CLAMP_UNLIKELY(*q, -1.0, 1.0);
+ }
+
+ pa_memblock_release(tchunk.memblock);
+ pa_memblock_release(u->memchunk.memblock);
+
+ pa_memblock_unref(tchunk.memblock);
+ }
+
+ pa_assert(u->memchunk.length > 0);
+ pa_assert(u->memchunk.memblock);
+
+ *chunk = u->memchunk;
+ pa_memblock_ref(chunk->memblock);
+
+ return 0;
+}
+
+/* Called from I/O thread context */
+static void sink_input_drop_cb(pa_sink_input *i, size_t length) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+ pa_assert(length > 0);
+
+ if (u->memchunk.memblock) {
+
+ if (length < u->memchunk.length) {
+ u->memchunk.index += length;
+ u->memchunk.length -= length;
+ return;
+ }
+
+ pa_memblock_unref(u->memchunk.memblock);
+ length -= u->memchunk.length;
+ pa_memchunk_reset(&u->memchunk);
+ }
+
+ if (length > 0)
+ pa_sink_skip(u->sink, length);
+}
+
+/* Called from I/O thread context */
+static void sink_input_detach_cb(pa_sink_input *i) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ pa_sink_detach_within_thread(u->sink);
+}
+
+/* Called from I/O thread context */
+static void sink_input_attach_cb(pa_sink_input *i) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ pa_sink_set_asyncmsgq(u->sink, i->sink->asyncmsgq);
+ pa_sink_set_rtpoll(u->sink, i->sink->rtpoll);
+
+ pa_sink_attach_within_thread(u->sink);
+}
+
+/* Called from main context */
+static void sink_input_kill_cb(pa_sink_input *i) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ pa_sink_input_unlink(u->sink_input);
+ pa_sink_input_unref(u->sink_input);
+ u->sink_input = NULL;
+
+ pa_sink_unlink(u->sink);
+ pa_sink_unref(u->sink);
+ u->sink = NULL;
+
+ pa_module_unload_request(u->module);
+}
+
+int pa__init(pa_module*m) {
+ struct userdata *u;
+ pa_sample_spec ss;
+ pa_channel_map map;
+ pa_modargs *ma;
+ char *t;
+ pa_sink *master;
+ pa_sink_input_new_data data;
+ const char *plugin, *label;
+ LADSPA_Descriptor_Function descriptor_func;
+ const char *e, *cdata;
+ const LADSPA_Descriptor *d;
+ unsigned long input_port, output_port, p, j, n_control;
+ unsigned c;
+ pa_bool_t *use_default = NULL;
+ char *default_sink_name = NULL;
+
+ pa_assert(m);
+
+ pa_assert(sizeof(LADSPA_Data) == sizeof(float));
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log("Failed to parse module arguments.");
+ goto fail;
+ }
+
+ if (!(master = pa_namereg_get(m->core, pa_modargs_get_value(ma, "master", NULL), PA_NAMEREG_SINK, 1))) {
+ pa_log("Master sink not found");
+ goto fail;
+ }
+
+ ss = master->sample_spec;
+ ss.format = PA_SAMPLE_FLOAT32;
+ map = master->channel_map;
+ if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) {
+ pa_log("Invalid sample format specification or channel map");
+ goto fail;
+ }
+
+ if (!(plugin = pa_modargs_get_value(ma, "plugin", NULL))) {
+ pa_log("Missing LADSPA plugin name");
+ goto fail;
+ }
+
+ if (!(label = pa_modargs_get_value(ma, "label", NULL))) {
+ pa_log("Missing LADSPA plugin label");
+ goto fail;
+ }
+
+ cdata = pa_modargs_get_value(ma, "control", NULL);
+
+ u = pa_xnew0(struct userdata, 1);
+ u->core = m->core;
+ u->module = m;
+ m->userdata = u;
+ u->master = master;
+ pa_memchunk_reset(&u->memchunk);
+
+ if (!(e = getenv("LADSPA_PATH")))
+ e = LADSPA_PATH;
+
+ /* FIXME: This is not exactly thread safe */
+ t = pa_xstrdup(lt_dlgetsearchpath());
+ lt_dlsetsearchpath(e);
+ m->dl = lt_dlopenext(plugin);
+ lt_dlsetsearchpath(t);
+ pa_xfree(t);
+
+ if (!m->dl) {
+ pa_log("Failed to load LADSPA plugin: %s", lt_dlerror());
+ goto fail;
+ }
+
+ if (!(descriptor_func = (LADSPA_Descriptor_Function) lt_dlsym(m->dl, "ladspa_descriptor"))) {
+ pa_log("LADSPA module lacks ladspa_descriptor() symbol.");
+ goto fail;
+ }
+
+ for (j = 0;; j++) {
+
+ if (!(d = descriptor_func(j))) {
+ pa_log("Failed to find plugin label '%s' in plugin '%s'.", plugin, label);
+ goto fail;
+ }
+
+ if (strcmp(d->Label, label) == 0)
+ break;
+ }
+
+ u->descriptor = d;
+
+ pa_log_debug("Module: %s", plugin);
+ pa_log_debug("Label: %s", d->Label);
+ pa_log_debug("Unique ID: %lu", d->UniqueID);
+ pa_log_debug("Name: %s", d->Name);
+ pa_log_debug("Maker: %s", d->Maker);
+ pa_log_debug("Copyright: %s", d->Copyright);
+
+ input_port = output_port = (unsigned long) -1;
+ n_control = 0;
+
+ for (p = 0; p < d->PortCount; p++) {
+
+ if (LADSPA_IS_PORT_INPUT(d->PortDescriptors[p]) && LADSPA_IS_PORT_AUDIO(d->PortDescriptors[p])) {
+
+ if (strcmp(d->PortNames[p], "Input") == 0) {
+ pa_assert(input_port == (unsigned long) -1);
+ input_port = p;
+ } else {
+ pa_log("Found audio input port on plugin we cannot handle: %s", d->PortNames[p]);
+ goto fail;
+ }
+
+ } else if (LADSPA_IS_PORT_OUTPUT(d->PortDescriptors[p]) && LADSPA_IS_PORT_AUDIO(d->PortDescriptors[p])) {
+
+ if (strcmp(d->PortNames[p], "Output") == 0) {
+ pa_assert(output_port == (unsigned long) -1);
+ output_port = p;
+ } else {
+ pa_log("Found audio output port on plugin we cannot handle: %s", d->PortNames[p]);
+ goto fail;
+ }
+
+ } else if (LADSPA_IS_PORT_INPUT(d->PortDescriptors[p]) && LADSPA_IS_PORT_CONTROL(d->PortDescriptors[p]))
+ n_control++;
+ else {
+ pa_assert(LADSPA_IS_PORT_OUTPUT(d->PortDescriptors[p]) && LADSPA_IS_PORT_CONTROL(d->PortDescriptors[p]));
+ pa_log_debug("Ignored control output port \"%s\".", d->PortNames[p]);
+ }
+ }
+
+ if ((input_port == (unsigned long) -1) || (output_port == (unsigned long) -1)) {
+ pa_log("Failed to identify input and output ports. "
+ "Right now this module can only deal with plugins which provide an 'Input' and an 'Output' audio port. "
+ "Patches welcome!");
+ goto fail;
+ }
+
+ u->block_size = pa_frame_align(pa_mempool_block_size_max(m->core->mempool), &ss);
+
+ u->input = (LADSPA_Data*) pa_xnew(uint8_t, u->block_size);
+ if (LADSPA_IS_INPLACE_BROKEN(d->Properties))
+ u->output = (LADSPA_Data*) pa_xnew(uint8_t, u->block_size);
+ else
+ u->output = u->input;
+
+ u->channels = ss.channels;
+
+ for (c = 0; c < ss.channels; c++) {
+ if (!(u->handle[c] = d->instantiate(d, ss.rate))) {
+ pa_log("Failed to instantiate plugin %s with label %s for channel %i", plugin, d->Label, c);
+ goto fail;
+ }
+
+ d->connect_port(u->handle[c], input_port, u->input);
+ d->connect_port(u->handle[c], output_port, u->output);
+ }
+
+ if (!cdata && n_control > 0) {
+ pa_log("This plugin requires specification of %lu control parameters.", n_control);
+ goto fail;
+ }
+
+ if (n_control > 0) {
+ const char *state = NULL;
+ char *k;
+ unsigned long h;
+
+ u->control = pa_xnew(LADSPA_Data, n_control);
+ use_default = pa_xnew(pa_bool_t, n_control);
+ p = 0;
+
+ while ((k = pa_split(cdata, ",", &state)) && p < n_control) {
+ float f;
+
+ if (*k == 0) {
+ use_default[p++] = TRUE;
+ pa_xfree(k);
+ continue;
+ }
+
+ if (pa_atof(k, &f) < 0) {
+ pa_log("Failed to parse control value '%s'", k);
+ pa_xfree(k);
+ goto fail;
+ }
+
+ pa_xfree(k);
+
+ use_default[p] = FALSE;
+ u->control[p++] = f;
+ }
+
+ /* The previous loop doesn't take the last control value into account
+ if it is left empty, so we do it here. */
+ if (*cdata == 0 || cdata[strlen(cdata) - 1] == ',') {
+ if (p < n_control)
+ use_default[p] = TRUE;
+ p++;
+ }
+
+ if (p > n_control || k) {
+ pa_log("Too many control values passed, %lu expected.", n_control);
+ if (k)
+ pa_xfree(k);
+ goto fail;
+ }
+
+ if (p < n_control) {
+ pa_log("Not enough control values passed, %lu expected, %lu passed.", n_control, p);
+ goto fail;
+ }
+
+ h = 0;
+ for (p = 0; p < d->PortCount; p++) {
+ LADSPA_PortRangeHintDescriptor hint = d->PortRangeHints[p].HintDescriptor;
+
+ if (!LADSPA_IS_PORT_CONTROL(d->PortDescriptors[p]))
+ continue;
+
+ if (LADSPA_IS_PORT_OUTPUT(d->PortDescriptors[p])) {
+ for (c = 0; c < ss.channels; c++)
+ d->connect_port(u->handle[c], p, &u->control_out);
+ continue;
+ }
+
+ pa_assert(h < n_control);
+
+ if (use_default[h]) {
+ LADSPA_Data lower, upper;
+
+ if (!LADSPA_IS_HINT_HAS_DEFAULT(hint)) {
+ pa_log("Control port value left empty but plugin defines no default.");
+ goto fail;
+ }
+
+ lower = d->PortRangeHints[p].LowerBound;
+ upper = d->PortRangeHints[p].UpperBound;
+
+ if (LADSPA_IS_HINT_SAMPLE_RATE(hint)) {
+ lower *= ss.rate;
+ upper *= ss.rate;
+ }
+
+ switch (hint & LADSPA_HINT_DEFAULT_MASK) {
+
+ case LADSPA_HINT_DEFAULT_MINIMUM:
+ u->control[h] = lower;
+ break;
+
+ case LADSPA_HINT_DEFAULT_MAXIMUM:
+ u->control[h] = upper;
+ break;
+
+ case LADSPA_HINT_DEFAULT_LOW:
+ if (LADSPA_IS_HINT_LOGARITHMIC(hint))
+ u->control[h] = exp(log(lower) * 0.75 + log(upper) * 0.25);
+ else
+ u->control[h] = lower * 0.75 + upper * 0.25;
+ break;
+
+ case LADSPA_HINT_DEFAULT_MIDDLE:
+ if (LADSPA_IS_HINT_LOGARITHMIC(hint))
+ u->control[h] = exp(log(lower) * 0.5 + log(upper) * 0.5);
+ else
+ u->control[h] = lower * 0.5 + upper * 0.5;
+ break;
+
+ case LADSPA_HINT_DEFAULT_HIGH:
+ if (LADSPA_IS_HINT_LOGARITHMIC(hint))
+ u->control[h] = exp(log(lower) * 0.25 + log(upper) * 0.75);
+ else
+ u->control[h] = lower * 0.25 + upper * 0.75;
+ break;
+
+ case LADSPA_HINT_DEFAULT_0:
+ u->control[h] = 0;
+ break;
+
+ case LADSPA_HINT_DEFAULT_1:
+ u->control[h] = 1;
+ break;
+
+ case LADSPA_HINT_DEFAULT_100:
+ u->control[h] = 100;
+ break;
+
+ case LADSPA_HINT_DEFAULT_440:
+ u->control[h] = 440;
+ break;
+
+ default:
+ pa_assert_not_reached();
+ }
+ }
+
+ if (LADSPA_IS_HINT_INTEGER(hint))
+ u->control[h] = roundf(u->control[h]);
+
+ pa_log_debug("Binding %f to port %s", u->control[h], d->PortNames[p]);
+
+ for (c = 0; c < ss.channels; c++)
+ d->connect_port(u->handle[c], p, &u->control[h]);
+
+ h++;
+ }
+
+ pa_assert(h == n_control);
+ }
+
+ if (d->activate)
+ for (c = 0; c < u->channels; c++)
+ d->activate(u->handle[c]);
+
+ default_sink_name = pa_sprintf_malloc("%s.ladspa", master->name);
+
+ /* Create sink */
+ if (!(u->sink = pa_sink_new(m->core, __FILE__, pa_modargs_get_value(ma, "sink_name", default_sink_name), 0, &ss, &map))) {
+ pa_log("Failed to create sink.");
+ goto fail;
+ }
+
+ u->sink->parent.process_msg = sink_process_msg;
+ u->sink->set_state = sink_set_state;
+ u->sink->userdata = u;
+ u->sink->flags = PA_SINK_LATENCY;
+
+ pa_sink_set_module(u->sink, m);
+ pa_sink_set_description(u->sink, t = pa_sprintf_malloc("LADSPA plugin '%s' on '%s'", label, master->description));
+ pa_xfree(t);
+ pa_sink_set_asyncmsgq(u->sink, master->asyncmsgq);
+ pa_sink_set_rtpoll(u->sink, master->rtpoll);
+
+ /* Create sink input */
+ pa_sink_input_new_data_init(&data);
+ data.sink = u->master;
+ data.driver = __FILE__;
+ data.name = "LADSPA Stream";
+ pa_sink_input_new_data_set_sample_spec(&data, &ss);
+ pa_sink_input_new_data_set_channel_map(&data, &map);
+ data.module = m;
+
+ if (!(u->sink_input = pa_sink_input_new(m->core, &data, PA_SINK_INPUT_DONT_MOVE)))
+ goto fail;
+
+ u->sink_input->parent.process_msg = sink_input_process_msg;
+ u->sink_input->peek = sink_input_peek_cb;
+ u->sink_input->drop = sink_input_drop_cb;
+ u->sink_input->kill = sink_input_kill_cb;
+ u->sink_input->attach = sink_input_attach_cb;
+ u->sink_input->detach = sink_input_detach_cb;
+ u->sink_input->userdata = u;
+
+ pa_sink_put(u->sink);
+ pa_sink_input_put(u->sink_input);
+
+ pa_modargs_free(ma);
+
+ pa_xfree(use_default);
+ pa_xfree(default_sink_name);
+
+ return 0;
+
+fail:
+ if (ma)
+ pa_modargs_free(ma);
+
+ pa_xfree(use_default);
+ pa_xfree(default_sink_name);
+
+ pa__done(m);
+
+ return -1;
+}
+
+void pa__done(pa_module*m) {
+ struct userdata *u;
+ unsigned c;
+
+ pa_assert(m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->sink_input) {
+ pa_sink_input_unlink(u->sink_input);
+ pa_sink_input_unref(u->sink_input);
+ }
+
+ if (u->sink) {
+ pa_sink_unlink(u->sink);
+ pa_sink_unref(u->sink);
+ }
+
+ if (u->memchunk.memblock)
+ pa_memblock_unref(u->memchunk.memblock);
+
+ for (c = 0; c < u->channels; c++)
+ if (u->handle[c]) {
+ if (u->descriptor->deactivate)
+ u->descriptor->deactivate(u->handle[c]);
+ u->descriptor->cleanup(u->handle[c]);
+ }
+
+ if (u->output != u->input)
+ pa_xfree(u->output);
+
+ pa_xfree(u->input);
+
+ pa_xfree(u->control);
+
+ pa_xfree(u);
+}
diff --git a/src/modules/module-lirc.c b/src/modules/module-lirc.c
new file mode 100644
index 00000000..24542172
--- /dev/null
+++ b/src/modules/module-lirc.c
@@ -0,0 +1,259 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <lirc/lirc_client.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/module.h>
+#include <pulsecore/log.h>
+#include <pulsecore/namereg.h>
+#include <pulsecore/sink.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/macro.h>
+
+#include "module-lirc-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering");
+PA_MODULE_DESCRIPTION("LIRC volume control");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(TRUE);
+PA_MODULE_USAGE("config=<config file> sink=<sink name> appname=<lirc application name>");
+
+static const char* const valid_modargs[] = {
+ "config",
+ "sink",
+ "appname",
+ NULL,
+};
+
+struct userdata {
+ int lirc_fd;
+ pa_io_event *io;
+ struct lirc_config *config;
+ char *sink_name;
+ pa_module *module;
+ float mute_toggle_save;
+};
+
+static int lirc_in_use = 0;
+
+static void io_callback(pa_mainloop_api *io, PA_GCC_UNUSED pa_io_event *e, PA_GCC_UNUSED int fd, pa_io_event_flags_t events, void*userdata) {
+ struct userdata *u = userdata;
+ char *name = NULL, *code = NULL;
+
+ pa_assert(io);
+ pa_assert(u);
+
+ if (events & (PA_IO_EVENT_HANGUP|PA_IO_EVENT_ERROR)) {
+ pa_log("Lost connection to LIRC daemon.");
+ goto fail;
+ }
+
+ if (events & PA_IO_EVENT_INPUT) {
+ char *c;
+
+ if (lirc_nextcode(&code) != 0 || !code) {
+ pa_log("lirc_nextcode() failed.");
+ goto fail;
+ }
+
+ c = pa_xstrdup(code);
+ c[strcspn(c, "\n\r")] = 0;
+ pa_log_debug("Raw IR code '%s'", c);
+ pa_xfree(c);
+
+ while (lirc_code2char(u->config, code, &name) == 0 && name) {
+ enum {
+ INVALID,
+ UP,
+ DOWN,
+ MUTE,
+ RESET,
+ MUTE_TOGGLE
+ } volchange = INVALID;
+
+ pa_log_info("Translated IR code '%s'", name);
+
+ if (strcasecmp(name, "volume-up") == 0)
+ volchange = UP;
+ else if (strcasecmp(name, "volume-down") == 0)
+ volchange = DOWN;
+ else if (strcasecmp(name, "mute") == 0)
+ volchange = MUTE;
+ else if (strcasecmp(name, "mute-toggle") == 0)
+ volchange = MUTE_TOGGLE;
+ else if (strcasecmp(name, "reset") == 0)
+ volchange = RESET;
+
+ if (volchange == INVALID)
+ pa_log_warn("Recieved unknown IR code '%s'", name);
+ else {
+ pa_sink *s;
+
+ if (!(s = pa_namereg_get(u->module->core, u->sink_name, PA_NAMEREG_SINK, 1)))
+ pa_log("Failed to get sink '%s'", u->sink_name);
+ else {
+ int i;
+ pa_cvolume cv = *pa_sink_get_volume(s);
+
+#define DELTA (PA_VOLUME_NORM/20)
+
+ switch (volchange) {
+ case UP:
+ for (i = 0; i < cv.channels; i++) {
+ cv.values[i] += DELTA;
+
+ if (cv.values[i] > PA_VOLUME_NORM)
+ cv.values[i] = PA_VOLUME_NORM;
+ }
+
+ pa_sink_set_volume(s, &cv);
+ break;
+
+ case DOWN:
+ for (i = 0; i < cv.channels; i++) {
+ if (cv.values[i] >= DELTA)
+ cv.values[i] -= DELTA;
+ else
+ cv.values[i] = PA_VOLUME_MUTED;
+ }
+
+ pa_sink_set_volume(s, &cv);
+ break;
+
+ case MUTE:
+ pa_sink_set_mute(s, 0);
+ break;
+
+ case RESET:
+ pa_sink_set_mute(s, 1);
+ break;
+
+ case MUTE_TOGGLE:
+
+ pa_sink_set_mute(s, !pa_sink_get_mute(s));
+ break;
+
+ case INVALID:
+ ;
+ }
+ }
+ }
+ }
+ }
+
+ pa_xfree(code);
+
+ return;
+
+fail:
+ u->module->core->mainloop->io_free(u->io);
+ u->io = NULL;
+
+ pa_module_unload_request(u->module);
+
+ pa_xfree(code);
+}
+
+int pa__init(pa_module*m) {
+ pa_modargs *ma = NULL;
+ struct userdata *u;
+
+ pa_assert(m);
+
+ if (lirc_in_use) {
+ pa_log("module-lirc may no be loaded twice.");
+ return -1;
+ }
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log("Failed to parse module arguments");
+ goto fail;
+ }
+
+ m->userdata = u = pa_xnew(struct userdata, 1);
+ u->module = m;
+ u->io = NULL;
+ u->config = NULL;
+ u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL));
+ u->lirc_fd = -1;
+ u->mute_toggle_save = 0;
+
+ if ((u->lirc_fd = lirc_init((char*) pa_modargs_get_value(ma, "appname", "pulseaudio"), 1)) < 0) {
+ pa_log("lirc_init() failed.");
+ goto fail;
+ }
+
+ if (lirc_readconfig((char*) pa_modargs_get_value(ma, "config", NULL), &u->config, NULL) < 0) {
+ pa_log("lirc_readconfig() failed.");
+ goto fail;
+ }
+
+ u->io = m->core->mainloop->io_new(m->core->mainloop, u->lirc_fd, PA_IO_EVENT_INPUT|PA_IO_EVENT_HANGUP, io_callback, u);
+
+ lirc_in_use = 1;
+
+ pa_modargs_free(ma);
+
+ return 0;
+
+fail:
+
+ if (ma)
+ pa_modargs_free(ma);
+
+ pa__done(m);
+ return -1;
+}
+
+void pa__done(pa_module*m) {
+ struct userdata *u;
+ pa_assert(m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->io)
+ m->core->mainloop->io_free(u->io);
+
+ if (u->config)
+ lirc_freeconfig(u->config);
+
+ if (u->lirc_fd >= 0)
+ lirc_deinit();
+
+ pa_xfree(u->sink_name);
+ pa_xfree(u);
+
+ lirc_in_use = 0;
+}
diff --git a/src/modules/module-match.c b/src/modules/module-match.c
new file mode 100644
index 00000000..ed5f3076
--- /dev/null
+++ b/src/modules/module-match.c
@@ -0,0 +1,244 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <regex.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/core-error.h>
+#include <pulsecore/module.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/log.h>
+#include <pulsecore/core-subscribe.h>
+#include <pulsecore/sink-input.h>
+#include <pulsecore/core-util.h>
+
+#include "module-match-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering");
+PA_MODULE_DESCRIPTION("Playback stream expression matching module");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(TRUE);
+PA_MODULE_USAGE("table=<filename>");
+
+#define WHITESPACE "\n\r \t"
+
+#define DEFAULT_MATCH_TABLE_FILE PA_DEFAULT_CONFIG_DIR"/match.table"
+#define DEFAULT_MATCH_TABLE_FILE_USER "match.table"
+
+static const char* const valid_modargs[] = {
+ "table",
+ NULL,
+};
+
+struct rule {
+ regex_t regex;
+ pa_volume_t volume;
+ struct rule *next;
+};
+
+struct userdata {
+ struct rule *rules;
+ pa_subscription *subscription;
+};
+
+static int load_rules(struct userdata *u, const char *filename) {
+ FILE *f;
+ int n = 0;
+ int ret = -1;
+ struct rule *end = NULL;
+ char *fn = NULL;
+
+ pa_assert(u);
+
+ f = filename ?
+ fopen(fn = pa_xstrdup(filename), "r") :
+ pa_open_config_file(DEFAULT_MATCH_TABLE_FILE, DEFAULT_MATCH_TABLE_FILE_USER, NULL, &fn, "r");
+
+ if (!f) {
+ pa_log("failed to open file '%s': %s", fn, pa_cstrerror(errno));
+ goto finish;
+ }
+
+ pa_lock_fd(fileno(f), 1);
+
+ while (!feof(f)) {
+ char *d, *v;
+ pa_volume_t volume;
+ uint32_t k;
+ regex_t regex;
+ char ln[256];
+ struct rule *rule;
+
+ if (!fgets(ln, sizeof(ln), f))
+ break;
+
+ n++;
+
+ pa_strip_nl(ln);
+
+ if (ln[0] == '#' || !*ln )
+ continue;
+
+ d = ln+strcspn(ln, WHITESPACE);
+ v = d+strspn(d, WHITESPACE);
+
+
+ if (!*v) {
+ pa_log(__FILE__ ": [%s:%u] failed to parse line - too few words", filename, n);
+ goto finish;
+ }
+
+ *d = 0;
+ if (pa_atou(v, &k) < 0) {
+ pa_log("[%s:%u] failed to parse volume", filename, n);
+ goto finish;
+ }
+
+ volume = (pa_volume_t) k;
+
+
+ if (regcomp(&regex, ln, REG_EXTENDED|REG_NOSUB) != 0) {
+ pa_log("[%s:%u] invalid regular expression", filename, n);
+ goto finish;
+ }
+
+ rule = pa_xnew(struct rule, 1);
+ rule->regex = regex;
+ rule->volume = volume;
+ rule->next = NULL;
+
+ if (end)
+ end->next = rule;
+ else
+ u->rules = rule;
+ end = rule;
+
+ *d = 0;
+ }
+
+ ret = 0;
+
+finish:
+ if (f) {
+ pa_lock_fd(fileno(f), 0);
+ fclose(f);
+ }
+
+ if (fn)
+ pa_xfree(fn);
+
+ return ret;
+}
+
+static void callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) {
+ struct userdata *u = userdata;
+ pa_sink_input *si;
+ struct rule *r;
+
+ pa_assert(c);
+ pa_assert(u);
+
+ if (t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW))
+ return;
+
+ if (!(si = pa_idxset_get_by_index(c->sink_inputs, idx)))
+ return;
+
+ if (!si->name)
+ return;
+
+ for (r = u->rules; r; r = r->next) {
+ if (!regexec(&r->regex, si->name, 0, NULL, 0)) {
+ pa_cvolume cv;
+ pa_log_debug("changing volume of sink input '%s' to 0x%03x", si->name, r->volume);
+ pa_cvolume_set(&cv, si->sample_spec.channels, r->volume);
+ pa_sink_input_set_volume(si, &cv);
+ }
+ }
+}
+
+int pa__init(pa_module*m) {
+ pa_modargs *ma = NULL;
+ struct userdata *u;
+
+ pa_assert(m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log("Failed to parse module arguments");
+ goto fail;
+ }
+
+ u = pa_xnew(struct userdata, 1);
+ u->rules = NULL;
+ u->subscription = NULL;
+ m->userdata = u;
+
+ if (load_rules(u, pa_modargs_get_value(ma, "table", NULL)) < 0)
+ goto fail;
+
+ u->subscription = pa_subscription_new(m->core, PA_SUBSCRIPTION_MASK_SINK_INPUT, callback, u);
+
+ pa_modargs_free(ma);
+ return 0;
+
+fail:
+ pa__done(m);
+
+ if (ma)
+ pa_modargs_free(ma);
+ return -1;
+}
+
+void pa__done(pa_module*m) {
+ struct userdata* u;
+ struct rule *r, *n;
+
+ pa_assert(m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->subscription)
+ pa_subscription_free(u->subscription);
+
+ for (r = u->rules; r; r = n) {
+ n = r->next;
+
+ regfree(&r->regex);
+ pa_xfree(r);
+ }
+
+ pa_xfree(u);
+}
diff --git a/src/modules/module-mmkbd-evdev.c b/src/modules/module-mmkbd-evdev.c
new file mode 100644
index 00000000..03c0e973
--- /dev/null
+++ b/src/modules/module-mmkbd-evdev.c
@@ -0,0 +1,262 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include <linux/input.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/core-error.h>
+#include <pulsecore/module.h>
+#include <pulsecore/log.h>
+#include <pulsecore/namereg.h>
+#include <pulsecore/sink.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/core-util.h>
+
+#include "module-mmkbd-evdev-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering");
+PA_MODULE_DESCRIPTION("Multimedia keyboard support via Linux evdev");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(FALSE);
+PA_MODULE_USAGE("device=<evdev device> sink=<sink name>");
+
+#define DEFAULT_DEVICE "/dev/input/event0"
+
+/*
+ * This isn't defined in older kernel headers and there is no way of
+ * detecting it.
+ */
+struct _input_id {
+ __u16 bustype;
+ __u16 vendor;
+ __u16 product;
+ __u16 version;
+};
+
+static const char* const valid_modargs[] = {
+ "device",
+ "sink",
+ NULL,
+};
+
+struct userdata {
+ int fd, fd_type;
+ pa_io_event *io;
+ char *sink_name;
+ pa_module *module;
+};
+
+static void io_callback(pa_mainloop_api *io, PA_GCC_UNUSED pa_io_event *e, PA_GCC_UNUSED int fd, pa_io_event_flags_t events, void*userdata) {
+ struct userdata *u = userdata;
+
+ pa_assert(io);
+ pa_assert(u);
+
+ if (events & (PA_IO_EVENT_HANGUP|PA_IO_EVENT_ERROR)) {
+ pa_log("Lost connection to evdev device.");
+ goto fail;
+ }
+
+ if (events & PA_IO_EVENT_INPUT) {
+ struct input_event ev;
+
+ if (pa_loop_read(u->fd, &ev, sizeof(ev), &u->fd_type) <= 0) {
+ pa_log("Failed to read from event device: %s", pa_cstrerror(errno));
+ goto fail;
+ }
+
+ if (ev.type == EV_KEY && (ev.value == 1 || ev.value == 2)) {
+ enum { INVALID, UP, DOWN, MUTE_TOGGLE } volchange = INVALID;
+
+ pa_log_debug("Key code=%u, value=%u", ev.code, ev.value);
+
+ switch (ev.code) {
+ case KEY_VOLUMEDOWN: volchange = DOWN; break;
+ case KEY_VOLUMEUP: volchange = UP; break;
+ case KEY_MUTE: volchange = MUTE_TOGGLE; break;
+ }
+
+ if (volchange != INVALID) {
+ pa_sink *s;
+
+ if (!(s = pa_namereg_get(u->module->core, u->sink_name, PA_NAMEREG_SINK, 1)))
+ pa_log("Failed to get sink '%s'", u->sink_name);
+ else {
+ int i;
+ pa_cvolume cv = *pa_sink_get_volume(s);
+
+#define DELTA (PA_VOLUME_NORM/20)
+
+ switch (volchange) {
+ case UP:
+ for (i = 0; i < cv.channels; i++) {
+ cv.values[i] += DELTA;
+
+ if (cv.values[i] > PA_VOLUME_NORM)
+ cv.values[i] = PA_VOLUME_NORM;
+ }
+
+ pa_sink_set_volume(s, &cv);
+ break;
+
+ case DOWN:
+ for (i = 0; i < cv.channels; i++) {
+ if (cv.values[i] >= DELTA)
+ cv.values[i] -= DELTA;
+ else
+ cv.values[i] = PA_VOLUME_MUTED;
+ }
+
+ pa_sink_set_volume(s, &cv);
+ break;
+
+ case MUTE_TOGGLE:
+
+ pa_sink_set_mute(s, !pa_sink_get_mute(s));
+ break;
+
+ case INVALID:
+ ;
+ }
+ }
+ }
+ }
+ }
+
+ return;
+
+fail:
+ u->module->core->mainloop->io_free(u->io);
+ u->io = NULL;
+
+ pa_module_unload_request(u->module);
+}
+
+#define test_bit(bit, array) (array[bit/8] & (1<<(bit%8)))
+
+int pa__init(pa_module*m) {
+
+ pa_modargs *ma = NULL;
+ struct userdata *u;
+ int version;
+ struct _input_id input_id;
+ char name[256];
+ uint8_t evtype_bitmask[EV_MAX/8 + 1];
+
+ pa_assert(m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log("Failed to parse module arguments");
+ goto fail;
+ }
+
+ m->userdata = u = pa_xnew(struct userdata,1);
+ u->module = m;
+ u->io = NULL;
+ u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL));
+ u->fd = -1;
+ u->fd_type = 0;
+
+ if ((u->fd = open(pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), O_RDONLY)) < 0) {
+ pa_log("failed to open evdev device: %s", pa_cstrerror(errno));
+ goto fail;
+ }
+
+ if (ioctl(u->fd, EVIOCGVERSION, &version) < 0) {
+ pa_log("EVIOCGVERSION failed: %s", pa_cstrerror(errno));
+ goto fail;
+ }
+
+ pa_log_info("evdev driver version %i.%i.%i", version >> 16, (version >> 8) & 0xff, version & 0xff);
+
+ if(ioctl(u->fd, EVIOCGID, &input_id)) {
+ pa_log("EVIOCGID failed: %s", pa_cstrerror(errno));
+ goto fail;
+ }
+
+ pa_log_info("evdev vendor 0x%04hx product 0x%04hx version 0x%04hx bustype %u",
+ input_id.vendor, input_id.product, input_id.version, input_id.bustype);
+
+ memset(name, 0, sizeof(name));
+ if(ioctl(u->fd, EVIOCGNAME(sizeof(name)), name) < 0) {
+ pa_log("EVIOCGNAME failed: %s", pa_cstrerror(errno));
+ goto fail;
+ }
+
+ pa_log_info("evdev device name: %s", name);
+
+ memset(evtype_bitmask, 0, sizeof(evtype_bitmask));
+ if (ioctl(u->fd, EVIOCGBIT(0, EV_MAX), evtype_bitmask) < 0) {
+ pa_log("EVIOCGBIT failed: %s", pa_cstrerror(errno));
+ goto fail;
+ }
+
+ if (!test_bit(EV_KEY, evtype_bitmask)) {
+ pa_log("Device has no keys.");
+ goto fail;
+ }
+
+ u->io = m->core->mainloop->io_new(m->core->mainloop, u->fd, PA_IO_EVENT_INPUT|PA_IO_EVENT_HANGUP, io_callback, u);
+
+ pa_modargs_free(ma);
+
+ return 0;
+
+fail:
+
+ if (ma)
+ pa_modargs_free(ma);
+
+ pa__done(m);
+ return -1;
+}
+
+void pa__done(pa_module*m) {
+ struct userdata *u;
+
+ pa_assert(m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->io)
+ m->core->mainloop->io_free(u->io);
+
+ if (u->fd >= 0)
+ pa_assert_se(pa_close(u->fd) == 0);
+
+ pa_xfree(u->sink_name);
+ pa_xfree(u);
+}
diff --git a/src/modules/module-native-protocol-fd.c b/src/modules/module-native-protocol-fd.c
new file mode 100644
index 00000000..53f41896
--- /dev/null
+++ b/src/modules/module-native-protocol-fd.c
@@ -0,0 +1,89 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <unistd.h>
+
+#include <pulsecore/module.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/iochannel.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/protocol-native.h>
+#include <pulsecore/log.h>
+
+#include "module-native-protocol-fd-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering");
+PA_MODULE_DESCRIPTION("Native protocol autospawn helper");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(TRUE);
+
+static const char* const valid_modargs[] = {
+ "fd",
+ "public",
+ "cookie",
+ NULL,
+};
+
+int pa__init(pa_module*m) {
+ pa_iochannel *io;
+ pa_modargs *ma;
+ int fd, r = -1;
+
+ pa_assert(m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log("Failed to parse module arguments.");
+ goto finish;
+ }
+
+ if (pa_modargs_get_value_s32(ma, "fd", &fd) < 0) {
+ pa_log("Invalid file descriptor.");
+ goto finish;
+ }
+
+ io = pa_iochannel_new(m->core->mainloop, fd, fd);
+
+ if (!(m->userdata = pa_protocol_native_new_iochannel(m->core, io, m, ma))) {
+ pa_iochannel_free(io);
+ goto finish;
+ }
+
+ r = 0;
+
+finish:
+ if (ma)
+ pa_modargs_free(ma);
+
+ return r;
+}
+
+void pa__done(pa_module*m) {
+ pa_assert(m);
+
+ pa_protocol_native_free(m->userdata);
+}
diff --git a/src/modules/module-null-sink.c b/src/modules/module-null-sink.c
new file mode 100644
index 00000000..de35fff9
--- /dev/null
+++ b/src/modules/module-null-sink.c
@@ -0,0 +1,257 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+
+#include <pulse/timeval.h>
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/macro.h>
+#include <pulsecore/sink.h>
+#include <pulsecore/module.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/core-error.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/log.h>
+#include <pulsecore/thread.h>
+#include <pulsecore/thread-mq.h>
+#include <pulsecore/rtpoll.h>
+#include <pulsecore/rtclock.h>
+
+#include "module-null-sink-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering");
+PA_MODULE_DESCRIPTION("Clocked NULL sink");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(FALSE);
+PA_MODULE_USAGE(
+ "format=<sample format> "
+ "channels=<number of channels> "
+ "rate=<sample rate> "
+ "sink_name=<name of sink>"
+ "channel_map=<channel map>"
+ "description=<description for the sink>");
+
+#define DEFAULT_SINK_NAME "null"
+
+struct userdata {
+ pa_core *core;
+ pa_module *module;
+ pa_sink *sink;
+
+ pa_thread *thread;
+ pa_thread_mq thread_mq;
+ pa_rtpoll *rtpoll;
+
+ size_t block_size;
+
+ struct timeval timestamp;
+};
+
+static const char* const valid_modargs[] = {
+ "rate",
+ "format",
+ "channels",
+ "sink_name",
+ "channel_map",
+ "description",
+ NULL
+};
+
+static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+ struct userdata *u = PA_SINK(o)->userdata;
+
+ switch (code) {
+ case PA_SINK_MESSAGE_SET_STATE:
+
+ if (PA_PTR_TO_UINT(data) == PA_SINK_RUNNING)
+ pa_rtclock_get(&u->timestamp);
+
+ break;
+
+ case PA_SINK_MESSAGE_GET_LATENCY: {
+ struct timeval now;
+
+ pa_rtclock_get(&now);
+
+ if (pa_timeval_cmp(&u->timestamp, &now) > 0)
+ *((pa_usec_t*) data) = 0;
+ else
+ *((pa_usec_t*) data) = pa_timeval_diff(&u->timestamp, &now);
+ break;
+ }
+ }
+
+ return pa_sink_process_msg(o, code, data, offset, chunk);
+}
+
+static void thread_func(void *userdata) {
+ struct userdata *u = userdata;
+
+ pa_assert(u);
+
+ pa_log_debug("Thread starting up");
+
+ pa_thread_mq_install(&u->thread_mq);
+ pa_rtpoll_install(u->rtpoll);
+
+ pa_rtclock_get(&u->timestamp);
+
+ for (;;) {
+ int ret;
+
+ /* Render some data and drop it immediately */
+ if (u->sink->thread_info.state == PA_SINK_RUNNING) {
+ struct timeval now;
+
+ pa_rtclock_get(&now);
+
+ if (pa_timeval_cmp(&u->timestamp, &now) <= 0) {
+ pa_sink_skip(u->sink, u->block_size);
+ pa_timeval_add(&u->timestamp, pa_bytes_to_usec(u->block_size, &u->sink->sample_spec));
+ }
+
+ pa_rtpoll_set_timer_absolute(u->rtpoll, &u->timestamp);
+ } else
+ pa_rtpoll_set_timer_disabled(u->rtpoll);
+
+ /* Hmm, nothing to do. Let's sleep */
+ if ((ret = pa_rtpoll_run(u->rtpoll, 1)) < 0)
+ goto fail;
+
+ if (ret == 0)
+ goto finish;
+ }
+
+fail:
+ /* If this was no regular exit from the loop we have to continue
+ * processing messages until we received PA_MESSAGE_SHUTDOWN */
+ pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL);
+ pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN);
+
+finish:
+ pa_log_debug("Thread shutting down");
+}
+
+int pa__init(pa_module*m) {
+ struct userdata *u = NULL;
+ pa_sample_spec ss;
+ pa_channel_map map;
+ pa_modargs *ma = NULL;
+
+ pa_assert(m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log("Failed to parse module arguments.");
+ goto fail;
+ }
+
+ ss = m->core->default_sample_spec;
+ if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) {
+ pa_log("Invalid sample format specification or channel map");
+ goto fail;
+ }
+
+ u = pa_xnew0(struct userdata, 1);
+ u->core = m->core;
+ u->module = m;
+ m->userdata = u;
+ pa_thread_mq_init(&u->thread_mq, m->core->mainloop);
+ u->rtpoll = pa_rtpoll_new();
+ pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq);
+
+ if (!(u->sink = pa_sink_new(m->core, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, &map))) {
+ pa_log("Failed to create sink.");
+ goto fail;
+ }
+
+ u->sink->parent.process_msg = sink_process_msg;
+ u->sink->userdata = u;
+ u->sink->flags = PA_SINK_LATENCY;
+
+ pa_sink_set_module(u->sink, m);
+ pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq);
+ pa_sink_set_rtpoll(u->sink, u->rtpoll);
+ pa_sink_set_description(u->sink, pa_modargs_get_value(ma, "description", "NULL sink"));
+
+ u->block_size = pa_bytes_per_second(&ss) / 20; /* 50 ms */
+ if (u->block_size <= 0)
+ u->block_size = pa_frame_size(&ss);
+
+ if (!(u->thread = pa_thread_new(thread_func, u))) {
+ pa_log("Failed to create thread.");
+ goto fail;
+ }
+
+ pa_sink_put(u->sink);
+
+ pa_modargs_free(ma);
+
+ return 0;
+
+fail:
+ if (ma)
+ pa_modargs_free(ma);
+
+ pa__done(m);
+
+ return -1;
+}
+
+void pa__done(pa_module*m) {
+ struct userdata *u;
+
+ pa_assert(m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->sink)
+ pa_sink_unlink(u->sink);
+
+ if (u->thread) {
+ pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL);
+ pa_thread_free(u->thread);
+ }
+
+ pa_thread_mq_done(&u->thread_mq);
+
+ if (u->sink)
+ pa_sink_unref(u->sink);
+
+ if (u->rtpoll)
+ pa_rtpoll_free(u->rtpoll);
+
+ pa_xfree(u);
+}
diff --git a/src/modules/module-oss.c b/src/modules/module-oss.c
new file mode 100644
index 00000000..a7df8a0c
--- /dev/null
+++ b/src/modules/module-oss.c
@@ -0,0 +1,1499 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+/* General power management rules:
+ *
+ * When SUSPENDED we close the audio device.
+ *
+ * We make no difference between IDLE and RUNNING in our handling.
+ *
+ * As long as we are in RUNNING/IDLE state we will *always* write data to
+ * the device. If none is avilable from the inputs, we write silence
+ * instead.
+ *
+ * If power should be saved on IDLE module-suspend-on-idle should be used.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef HAVE_SYS_MMAN_H
+#include <sys/mman.h>
+#endif
+
+#include <sys/soundcard.h>
+#include <sys/ioctl.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+#include <signal.h>
+#include <poll.h>
+
+#include <pulse/xmalloc.h>
+#include <pulse/util.h>
+
+#include <pulsecore/core-error.h>
+#include <pulsecore/thread.h>
+#include <pulsecore/sink.h>
+#include <pulsecore/source.h>
+#include <pulsecore/module.h>
+#include <pulsecore/sample-util.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/thread-mq.h>
+#include <pulsecore/rtpoll.h>
+
+#include "oss-util.h"
+#include "module-oss-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering");
+PA_MODULE_DESCRIPTION("OSS Sink/Source");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(FALSE);
+PA_MODULE_USAGE(
+ "sink_name=<name for the sink> "
+ "source_name=<name for the source> "
+ "device=<OSS device> "
+ "record=<enable source?> "
+ "playback=<enable sink?> "
+ "format=<sample format> "
+ "channels=<number of channels> "
+ "rate=<sample rate> "
+ "fragments=<number of fragments> "
+ "fragment_size=<fragment size> "
+ "channel_map=<channel map> "
+ "mmap=<enable memory mapping?>");
+
+#define DEFAULT_DEVICE "/dev/dsp"
+
+struct userdata {
+ pa_core *core;
+ pa_module *module;
+ pa_sink *sink;
+ pa_source *source;
+
+ pa_thread *thread;
+ pa_thread_mq thread_mq;
+ pa_rtpoll *rtpoll;
+
+ char *device_name;
+
+ pa_memchunk memchunk;
+
+ size_t frame_size;
+ uint32_t in_fragment_size, out_fragment_size, in_nfrags, out_nfrags, in_hwbuf_size, out_hwbuf_size;
+ pa_bool_t use_getospace, use_getispace;
+ pa_bool_t use_getodelay;
+
+ pa_bool_t sink_suspended, source_suspended;
+
+ int fd;
+ int mode;
+
+ int mixer_fd;
+ int mixer_devmask;
+
+ int nfrags, frag_size;
+
+ pa_bool_t use_mmap;
+ unsigned out_mmap_current, in_mmap_current;
+ void *in_mmap, *out_mmap;
+ pa_memblock **in_mmap_memblocks, **out_mmap_memblocks;
+
+ int in_mmap_saved_nfrags, out_mmap_saved_nfrags;
+
+ pa_rtpoll_item *rtpoll_item;
+};
+
+static const char* const valid_modargs[] = {
+ "sink_name",
+ "source_name",
+ "device",
+ "record",
+ "playback",
+ "fragments",
+ "fragment_size",
+ "format",
+ "rate",
+ "channels",
+ "channel_map",
+ "mmap",
+ NULL
+};
+
+static void trigger(struct userdata *u, pa_bool_t quick) {
+ int enable_bits = 0, zero = 0;
+
+ pa_assert(u);
+
+ if (u->fd < 0)
+ return;
+
+ pa_log_debug("trigger");
+
+ if (u->source && PA_SOURCE_OPENED(u->source->thread_info.state))
+ enable_bits |= PCM_ENABLE_INPUT;
+
+ if (u->sink && PA_SINK_OPENED(u->sink->thread_info.state))
+ enable_bits |= PCM_ENABLE_OUTPUT;
+
+ pa_log_debug("trigger: %i", enable_bits);
+
+
+ if (u->use_mmap) {
+
+ if (!quick)
+ ioctl(u->fd, SNDCTL_DSP_SETTRIGGER, &zero);
+
+#ifdef SNDCTL_DSP_HALT
+ if (enable_bits == 0)
+ if (ioctl(u->fd, SNDCTL_DSP_HALT, NULL) < 0)
+ pa_log_warn("SNDCTL_DSP_HALT: %s", pa_cstrerror(errno));
+#endif
+
+ if (ioctl(u->fd, SNDCTL_DSP_SETTRIGGER, &enable_bits) < 0)
+ pa_log_warn("SNDCTL_DSP_SETTRIGGER: %s", pa_cstrerror(errno));
+
+ if (u->sink && !(enable_bits & PCM_ENABLE_OUTPUT)) {
+ pa_log_debug("clearing playback buffer");
+ pa_silence_memory(u->out_mmap, u->out_hwbuf_size, &u->sink->sample_spec);
+ }
+
+ } else {
+
+ if (enable_bits)
+ if (ioctl(u->fd, SNDCTL_DSP_POST, NULL) < 0)
+ pa_log_warn("SNDCTL_DSP_POST: %s", pa_cstrerror(errno));
+
+ if (!quick) {
+ /*
+ * Some crappy drivers do not start the recording until we
+ * read something. Without this snippet, poll will never
+ * register the fd as ready.
+ */
+
+ if (u->source && PA_SOURCE_OPENED(u->source->thread_info.state)) {
+ uint8_t *buf = pa_xnew(uint8_t, u->in_fragment_size);
+ pa_read(u->fd, buf, u->in_fragment_size, NULL);
+ pa_xfree(buf);
+ }
+ }
+ }
+}
+
+static void mmap_fill_memblocks(struct userdata *u, unsigned n) {
+ pa_assert(u);
+ pa_assert(u->out_mmap_memblocks);
+
+/* pa_log("Mmmap writing %u blocks", n); */
+
+ while (n > 0) {
+ pa_memchunk chunk;
+
+ if (u->out_mmap_memblocks[u->out_mmap_current])
+ pa_memblock_unref_fixed(u->out_mmap_memblocks[u->out_mmap_current]);
+
+ chunk.memblock = u->out_mmap_memblocks[u->out_mmap_current] =
+ pa_memblock_new_fixed(
+ u->core->mempool,
+ (uint8_t*) u->out_mmap + u->out_fragment_size * u->out_mmap_current,
+ u->out_fragment_size,
+ 1);
+
+ chunk.length = pa_memblock_get_length(chunk.memblock);
+ chunk.index = 0;
+
+ pa_sink_render_into_full(u->sink, &chunk);
+
+ u->out_mmap_current++;
+ while (u->out_mmap_current >= u->out_nfrags)
+ u->out_mmap_current -= u->out_nfrags;
+
+ n--;
+ }
+}
+
+static int mmap_write(struct userdata *u) {
+ struct count_info info;
+
+ pa_assert(u);
+ pa_assert(u->sink);
+
+/* pa_log("Mmmap writing..."); */
+
+ if (ioctl(u->fd, SNDCTL_DSP_GETOPTR, &info) < 0) {
+ pa_log("SNDCTL_DSP_GETOPTR: %s", pa_cstrerror(errno));
+ return -1;
+ }
+
+ info.blocks += u->out_mmap_saved_nfrags;
+ u->out_mmap_saved_nfrags = 0;
+
+ if (info.blocks > 0)
+ mmap_fill_memblocks(u, info.blocks);
+
+ return info.blocks;
+}
+
+static void mmap_post_memblocks(struct userdata *u, unsigned n) {
+ pa_assert(u);
+ pa_assert(u->in_mmap_memblocks);
+
+/* pa_log("Mmmap reading %u blocks", n); */
+
+ while (n > 0) {
+ pa_memchunk chunk;
+
+ if (!u->in_mmap_memblocks[u->in_mmap_current]) {
+
+ chunk.memblock = u->in_mmap_memblocks[u->in_mmap_current] =
+ pa_memblock_new_fixed(
+ u->core->mempool,
+ (uint8_t*) u->in_mmap + u->in_fragment_size*u->in_mmap_current,
+ u->in_fragment_size,
+ 1);
+
+ chunk.length = pa_memblock_get_length(chunk.memblock);
+ chunk.index = 0;
+
+ pa_source_post(u->source, &chunk);
+ }
+
+ u->in_mmap_current++;
+ while (u->in_mmap_current >= u->in_nfrags)
+ u->in_mmap_current -= u->in_nfrags;
+
+ n--;
+ }
+}
+
+static void mmap_clear_memblocks(struct userdata*u, unsigned n) {
+ unsigned i = u->in_mmap_current;
+
+ pa_assert(u);
+ pa_assert(u->in_mmap_memblocks);
+
+ if (n > u->in_nfrags)
+ n = u->in_nfrags;
+
+ while (n > 0) {
+ if (u->in_mmap_memblocks[i]) {
+ pa_memblock_unref_fixed(u->in_mmap_memblocks[i]);
+ u->in_mmap_memblocks[i] = NULL;
+ }
+
+ i++;
+ while (i >= u->in_nfrags)
+ i -= u->in_nfrags;
+
+ n--;
+ }
+}
+
+static int mmap_read(struct userdata *u) {
+ struct count_info info;
+ pa_assert(u);
+ pa_assert(u->source);
+
+/* pa_log("Mmmap reading..."); */
+
+ if (ioctl(u->fd, SNDCTL_DSP_GETIPTR, &info) < 0) {
+ pa_log("SNDCTL_DSP_GETIPTR: %s", pa_cstrerror(errno));
+ return -1;
+ }
+
+/* pa_log("... %i", info.blocks); */
+
+ info.blocks += u->in_mmap_saved_nfrags;
+ u->in_mmap_saved_nfrags = 0;
+
+ if (info.blocks > 0) {
+ mmap_post_memblocks(u, info.blocks);
+ mmap_clear_memblocks(u, u->in_nfrags/2);
+ }
+
+ return info.blocks;
+}
+
+static pa_usec_t mmap_sink_get_latency(struct userdata *u) {
+ struct count_info info;
+ size_t bpos, n;
+
+ pa_assert(u);
+
+ if (ioctl(u->fd, SNDCTL_DSP_GETOPTR, &info) < 0) {
+ pa_log("SNDCTL_DSP_GETOPTR: %s", pa_cstrerror(errno));
+ return 0;
+ }
+
+ u->out_mmap_saved_nfrags += info.blocks;
+
+ bpos = ((u->out_mmap_current + u->out_mmap_saved_nfrags) * u->out_fragment_size) % u->out_hwbuf_size;
+
+ if (bpos <= (size_t) info.ptr)
+ n = u->out_hwbuf_size - (info.ptr - bpos);
+ else
+ n = bpos - info.ptr;
+
+/* pa_log("n = %u, bpos = %u, ptr = %u, total=%u, fragsize = %u, n_frags = %u\n", n, bpos, (unsigned) info.ptr, total, u->out_fragment_size, u->out_fragments); */
+
+ return pa_bytes_to_usec(n, &u->sink->sample_spec);
+}
+
+static pa_usec_t mmap_source_get_latency(struct userdata *u) {
+ struct count_info info;
+ size_t bpos, n;
+
+ pa_assert(u);
+
+ if (ioctl(u->fd, SNDCTL_DSP_GETIPTR, &info) < 0) {
+ pa_log("SNDCTL_DSP_GETIPTR: %s", pa_cstrerror(errno));
+ return 0;
+ }
+
+ u->in_mmap_saved_nfrags += info.blocks;
+ bpos = ((u->in_mmap_current + u->in_mmap_saved_nfrags) * u->in_fragment_size) % u->in_hwbuf_size;
+
+ if (bpos <= (size_t) info.ptr)
+ n = info.ptr - bpos;
+ else
+ n = u->in_hwbuf_size - bpos + info.ptr;
+
+/* pa_log("n = %u, bpos = %u, ptr = %u, total=%u, fragsize = %u, n_frags = %u\n", n, bpos, (unsigned) info.ptr, total, u->in_fragment_size, u->in_fragments); */
+
+ return pa_bytes_to_usec(n, &u->source->sample_spec);
+}
+
+static pa_usec_t io_sink_get_latency(struct userdata *u) {
+ pa_usec_t r = 0;
+
+ pa_assert(u);
+
+ if (u->use_getodelay) {
+ int arg;
+
+ if (ioctl(u->fd, SNDCTL_DSP_GETODELAY, &arg) < 0) {
+ pa_log_info("Device doesn't support SNDCTL_DSP_GETODELAY: %s", pa_cstrerror(errno));
+ u->use_getodelay = 0;
+ } else
+ r = pa_bytes_to_usec(arg, &u->sink->sample_spec);
+
+ }
+
+ if (!u->use_getodelay && u->use_getospace) {
+ struct audio_buf_info info;
+
+ if (ioctl(u->fd, SNDCTL_DSP_GETOSPACE, &info) < 0) {
+ pa_log_info("Device doesn't support SNDCTL_DSP_GETOSPACE: %s", pa_cstrerror(errno));
+ u->use_getospace = 0;
+ } else
+ r = pa_bytes_to_usec(info.bytes, &u->sink->sample_spec);
+ }
+
+ if (u->memchunk.memblock)
+ r += pa_bytes_to_usec(u->memchunk.length, &u->sink->sample_spec);
+
+ return r;
+}
+
+
+static pa_usec_t io_source_get_latency(struct userdata *u) {
+ pa_usec_t r = 0;
+
+ pa_assert(u);
+
+ if (u->use_getispace) {
+ struct audio_buf_info info;
+
+ if (ioctl(u->fd, SNDCTL_DSP_GETISPACE, &info) < 0) {
+ pa_log_info("Device doesn't support SNDCTL_DSP_GETISPACE: %s", pa_cstrerror(errno));
+ u->use_getispace = 0;
+ } else
+ r = pa_bytes_to_usec(info.bytes, &u->source->sample_spec);
+ }
+
+ return r;
+}
+
+static void build_pollfd(struct userdata *u) {
+ struct pollfd *pollfd;
+
+ pa_assert(u);
+ pa_assert(u->fd >= 0);
+
+ if (u->rtpoll_item)
+ pa_rtpoll_item_free(u->rtpoll_item);
+
+ u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1);
+ pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
+ pollfd->fd = u->fd;
+ pollfd->events = 0;
+ pollfd->revents = 0;
+}
+
+static int suspend(struct userdata *u) {
+ pa_assert(u);
+ pa_assert(u->fd >= 0);
+
+ pa_log_info("Suspending...");
+
+ if (u->out_mmap_memblocks) {
+ unsigned i;
+ for (i = 0; i < u->out_nfrags; i++)
+ if (u->out_mmap_memblocks[i]) {
+ pa_memblock_unref_fixed(u->out_mmap_memblocks[i]);
+ u->out_mmap_memblocks[i] = NULL;
+ }
+ }
+
+ if (u->in_mmap_memblocks) {
+ unsigned i;
+ for (i = 0; i < u->in_nfrags; i++)
+ if (u->in_mmap_memblocks[i]) {
+ pa_memblock_unref_fixed(u->in_mmap_memblocks[i]);
+ u->in_mmap_memblocks[i] = NULL;
+ }
+ }
+
+ if (u->in_mmap && u->in_mmap != MAP_FAILED) {
+ munmap(u->in_mmap, u->in_hwbuf_size);
+ u->in_mmap = NULL;
+ }
+
+ if (u->out_mmap && u->out_mmap != MAP_FAILED) {
+ munmap(u->out_mmap, u->out_hwbuf_size);
+ u->out_mmap = NULL;
+ }
+
+ /* Let's suspend */
+ ioctl(u->fd, SNDCTL_DSP_SYNC, NULL);
+ pa_close(u->fd);
+ u->fd = -1;
+
+ if (u->rtpoll_item) {
+ pa_rtpoll_item_free(u->rtpoll_item);
+ u->rtpoll_item = NULL;
+ }
+
+ pa_log_info("Device suspended...");
+
+ return 0;
+}
+
+static int unsuspend(struct userdata *u) {
+ int m;
+ pa_sample_spec ss, *ss_original;
+ int frag_size, in_frag_size, out_frag_size;
+ int in_nfrags, out_nfrags;
+ struct audio_buf_info info;
+
+ pa_assert(u);
+ pa_assert(u->fd < 0);
+
+ m = u->mode;
+
+ pa_log_info("Trying resume...");
+
+ if ((u->fd = pa_oss_open(u->device_name, &m, NULL)) < 0) {
+ pa_log_warn("Resume failed, device busy (%s)", pa_cstrerror(errno));
+ return -1;
+
+ if (m != u->mode)
+ pa_log_warn("Resume failed, couldn't open device with original access mode.");
+ goto fail;
+ }
+
+ if (u->nfrags >= 2 && u->frag_size >= 1)
+ if (pa_oss_set_fragments(u->fd, u->nfrags, u->frag_size) < 0) {
+ pa_log_warn("Resume failed, couldn't set original fragment settings.");
+ goto fail;
+ }
+
+ ss = *(ss_original = u->sink ? &u->sink->sample_spec : &u->source->sample_spec);
+ if (pa_oss_auto_format(u->fd, &ss) < 0 || !pa_sample_spec_equal(&ss, ss_original)) {
+ pa_log_warn("Resume failed, couldn't set original sample format settings.");
+ goto fail;
+ }
+
+ if (ioctl(u->fd, SNDCTL_DSP_GETBLKSIZE, &frag_size) < 0) {
+ pa_log_warn("SNDCTL_DSP_GETBLKSIZE: %s", pa_cstrerror(errno));
+ goto fail;
+ }
+
+ in_frag_size = out_frag_size = frag_size;
+ in_nfrags = out_nfrags = u->nfrags;
+
+ if (ioctl(u->fd, SNDCTL_DSP_GETISPACE, &info) >= 0) {
+ in_frag_size = info.fragsize;
+ in_nfrags = info.fragstotal;
+ }
+
+ if (ioctl(u->fd, SNDCTL_DSP_GETOSPACE, &info) >= 0) {
+ out_frag_size = info.fragsize;
+ out_nfrags = info.fragstotal;
+ }
+
+ if ((u->source && (in_frag_size != (int) u->in_fragment_size || in_nfrags != (int) u->in_nfrags)) ||
+ (u->sink && (out_frag_size != (int) u->out_fragment_size || out_nfrags != (int) u->out_nfrags))) {
+ pa_log_warn("Resume failed, input fragment settings don't match.");
+ goto fail;
+ }
+
+ if (u->use_mmap) {
+ if (u->source) {
+ if ((u->in_mmap = mmap(NULL, u->in_hwbuf_size, PROT_READ, MAP_SHARED, u->fd, 0)) == MAP_FAILED) {
+ pa_log("Resume failed, mmap(): %s", pa_cstrerror(errno));
+ goto fail;
+ }
+ }
+
+ if (u->sink) {
+ if ((u->out_mmap = mmap(NULL, u->out_hwbuf_size, PROT_WRITE, MAP_SHARED, u->fd, 0)) == MAP_FAILED) {
+ pa_log("Resume failed, mmap(): %s", pa_cstrerror(errno));
+ if (u->in_mmap && u->in_mmap != MAP_FAILED) {
+ munmap(u->in_mmap, u->in_hwbuf_size);
+ u->in_mmap = NULL;
+ }
+
+ goto fail;
+ }
+
+ pa_silence_memory(u->out_mmap, u->out_hwbuf_size, &ss);
+ }
+ }
+
+ u->out_mmap_current = u->in_mmap_current = 0;
+ u->out_mmap_saved_nfrags = u->in_mmap_saved_nfrags = 0;
+
+ pa_assert(!u->rtpoll_item);
+
+ build_pollfd(u);
+
+ if (u->sink)
+ pa_sink_get_volume(u->sink);
+ if (u->source)
+ pa_source_get_volume(u->source);
+
+ pa_log_info("Resumed successfully...");
+
+ return 0;
+
+fail:
+ pa_close(u->fd);
+ u->fd = -1;
+ return -1;
+}
+
+static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+ struct userdata *u = PA_SINK(o)->userdata;
+ int ret;
+ pa_bool_t do_trigger = FALSE, quick = TRUE;
+
+ switch (code) {
+
+ case PA_SINK_MESSAGE_GET_LATENCY: {
+ pa_usec_t r = 0;
+
+ if (u->fd >= 0) {
+ if (u->use_mmap)
+ r = mmap_sink_get_latency(u);
+ else
+ r = io_sink_get_latency(u);
+ }
+
+ *((pa_usec_t*) data) = r;
+
+ return 0;
+ }
+
+ case PA_SINK_MESSAGE_SET_STATE:
+
+ switch ((pa_sink_state_t) PA_PTR_TO_UINT(data)) {
+
+ case PA_SINK_SUSPENDED:
+ pa_assert(PA_SINK_OPENED(u->sink->thread_info.state));
+
+ if (!u->source || u->source_suspended) {
+ if (suspend(u) < 0)
+ return -1;
+ }
+
+ do_trigger = TRUE;
+
+ u->sink_suspended = TRUE;
+ break;
+
+ case PA_SINK_IDLE:
+ case PA_SINK_RUNNING:
+
+ if (u->sink->thread_info.state == PA_SINK_INIT) {
+ do_trigger = TRUE;
+ quick = u->source && PA_SOURCE_OPENED(u->source->thread_info.state);
+ }
+
+ if (u->sink->thread_info.state == PA_SINK_SUSPENDED) {
+
+ if (!u->source || u->source_suspended) {
+ if (unsuspend(u) < 0)
+ return -1;
+ quick = FALSE;
+ }
+
+ do_trigger = TRUE;
+
+ u->out_mmap_current = 0;
+ u->out_mmap_saved_nfrags = 0;
+
+ u->sink_suspended = FALSE;
+ }
+
+ break;
+
+ case PA_SINK_UNLINKED:
+ case PA_SINK_INIT:
+ ;
+ }
+
+ break;
+
+ }
+
+ ret = pa_sink_process_msg(o, code, data, offset, chunk);
+
+ if (do_trigger)
+ trigger(u, quick);
+
+ return ret;
+}
+
+static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+ struct userdata *u = PA_SOURCE(o)->userdata;
+ int ret;
+ int do_trigger = FALSE, quick = TRUE;
+
+ switch (code) {
+
+ case PA_SOURCE_MESSAGE_GET_LATENCY: {
+ pa_usec_t r = 0;
+
+ if (u->fd >= 0) {
+ if (u->use_mmap)
+ r = mmap_source_get_latency(u);
+ else
+ r = io_source_get_latency(u);
+ }
+
+ *((pa_usec_t*) data) = r;
+ return 0;
+ }
+
+ case PA_SOURCE_MESSAGE_SET_STATE:
+
+ switch ((pa_source_state_t) PA_PTR_TO_UINT(data)) {
+ case PA_SOURCE_SUSPENDED:
+ pa_assert(PA_SOURCE_OPENED(u->source->thread_info.state));
+
+ if (!u->sink || u->sink_suspended) {
+ if (suspend(u) < 0)
+ return -1;
+ }
+
+ do_trigger = TRUE;
+
+ u->source_suspended = TRUE;
+ break;
+
+ case PA_SOURCE_IDLE:
+ case PA_SOURCE_RUNNING:
+
+ if (u->source->thread_info.state == PA_SOURCE_INIT) {
+ do_trigger = TRUE;
+ quick = u->sink && PA_SINK_OPENED(u->sink->thread_info.state);
+ }
+
+ if (u->source->thread_info.state == PA_SOURCE_SUSPENDED) {
+
+ if (!u->sink || u->sink_suspended) {
+ if (unsuspend(u) < 0)
+ return -1;
+ quick = FALSE;
+ }
+
+ do_trigger = TRUE;
+
+ u->in_mmap_current = 0;
+ u->in_mmap_saved_nfrags = 0;
+
+ u->source_suspended = FALSE;
+ }
+ break;
+
+ case PA_SOURCE_UNLINKED:
+ case PA_SOURCE_INIT:
+ ;
+
+ }
+ break;
+
+ }
+
+ ret = pa_source_process_msg(o, code, data, offset, chunk);
+
+ if (do_trigger)
+ trigger(u, quick);
+
+ return ret;
+}
+
+static int sink_get_volume(pa_sink *s) {
+ struct userdata *u;
+ int r;
+
+ pa_assert_se(u = s->userdata);
+
+ pa_assert(u->mixer_devmask & (SOUND_MASK_VOLUME|SOUND_MASK_PCM));
+
+ if (u->mixer_devmask & SOUND_MASK_VOLUME)
+ if ((r = pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_VOLUME, &s->sample_spec, &s->volume)) >= 0)
+ return r;
+
+ if (u->mixer_devmask & SOUND_MASK_PCM)
+ if ((r = pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_PCM, &s->sample_spec, &s->volume)) >= 0)
+ return r;
+
+ pa_log_info("Device doesn't support reading mixer settings: %s", pa_cstrerror(errno));
+ return -1;
+}
+
+static int sink_set_volume(pa_sink *s) {
+ struct userdata *u;
+ int r;
+
+ pa_assert_se(u = s->userdata);
+
+ pa_assert(u->mixer_devmask & (SOUND_MASK_VOLUME|SOUND_MASK_PCM));
+
+ if (u->mixer_devmask & SOUND_MASK_VOLUME)
+ if ((r = pa_oss_set_volume(u->mixer_fd, SOUND_MIXER_WRITE_VOLUME, &s->sample_spec, &s->volume)) >= 0)
+ return r;
+
+ if (u->mixer_devmask & SOUND_MASK_PCM)
+ if ((r = pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_WRITE_PCM, &s->sample_spec, &s->volume)) >= 0)
+ return r;
+
+ pa_log_info("Device doesn't support writing mixer settings: %s", pa_cstrerror(errno));
+ return -1;
+}
+
+static int source_get_volume(pa_source *s) {
+ struct userdata *u;
+ int r;
+
+ pa_assert_se(u = s->userdata);
+
+ pa_assert(u->mixer_devmask & (SOUND_MASK_IGAIN|SOUND_MASK_RECLEV));
+
+ if (u->mixer_devmask & SOUND_MASK_IGAIN)
+ if ((r = pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_IGAIN, &s->sample_spec, &s->volume)) >= 0)
+ return r;
+
+ if (u->mixer_devmask & SOUND_MASK_RECLEV)
+ if ((r = pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_RECLEV, &s->sample_spec, &s->volume)) >= 0)
+ return r;
+
+ pa_log_info("Device doesn't support reading mixer settings: %s", pa_cstrerror(errno));
+ return -1;
+}
+
+static int source_set_volume(pa_source *s) {
+ struct userdata *u;
+ int r;
+
+ pa_assert_se(u = s->userdata);
+
+ pa_assert(u->mixer_devmask & (SOUND_MASK_IGAIN|SOUND_MASK_RECLEV));
+
+ if (u->mixer_devmask & SOUND_MASK_IGAIN)
+ if ((r = pa_oss_set_volume(u->mixer_fd, SOUND_MIXER_WRITE_IGAIN, &s->sample_spec, &s->volume)) >= 0)
+ return r;
+
+ if (u->mixer_devmask & SOUND_MASK_RECLEV)
+ if ((r = pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_WRITE_RECLEV, &s->sample_spec, &s->volume)) >= 0)
+ return r;
+
+ pa_log_info("Device doesn't support writing mixer settings: %s", pa_cstrerror(errno));
+ return -1;
+}
+
+static void thread_func(void *userdata) {
+ struct userdata *u = userdata;
+ int write_type = 0, read_type = 0;
+ unsigned short revents = 0;
+
+ pa_assert(u);
+
+ pa_log_debug("Thread starting up");
+
+ if (u->core->realtime_scheduling)
+ pa_make_realtime(u->core->realtime_priority);
+
+ pa_thread_mq_install(&u->thread_mq);
+ pa_rtpoll_install(u->rtpoll);
+
+ for (;;) {
+ int ret;
+
+/* pa_log("loop"); */
+
+ /* Render some data and write it to the dsp */
+
+ if (u->sink && PA_SINK_OPENED(u->sink->thread_info.state) && ((revents & POLLOUT) || u->use_mmap || u->use_getospace)) {
+
+ if (u->use_mmap) {
+
+ if ((ret = mmap_write(u)) < 0)
+ goto fail;
+
+ revents &= ~POLLOUT;
+
+ if (ret > 0)
+ continue;
+
+ } else {
+ ssize_t l;
+ pa_bool_t loop = FALSE, work_done = FALSE;
+
+ l = u->out_fragment_size;
+
+ if (u->use_getospace) {
+ audio_buf_info info;
+
+ if (ioctl(u->fd, SNDCTL_DSP_GETOSPACE, &info) < 0) {
+ pa_log_info("Device doesn't support SNDCTL_DSP_GETOSPACE: %s", pa_cstrerror(errno));
+ u->use_getospace = FALSE;
+ } else {
+ l = info.bytes;
+
+ /* We loop only if GETOSPACE worked and we
+ * actually *know* that we can write more than
+ * one fragment at a time */
+ loop = TRUE;
+ }
+ }
+
+ /* Round down to multiples of the fragment size,
+ * because OSS needs that (at least some versions
+ * do) */
+ l = (l/u->out_fragment_size) * u->out_fragment_size;
+
+ /* Hmm, so poll() signalled us that we can read
+ * something, but GETOSPACE told us there was nothing?
+ * Hmm, make the best of it, try to read some data, to
+ * avoid spinning forever. */
+ if (l <= 0 && (revents & POLLOUT)) {
+ l = u->out_fragment_size;
+ loop = FALSE;
+ }
+
+ while (l > 0) {
+ void *p;
+ ssize_t t;
+
+ if (u->memchunk.length <= 0)
+ pa_sink_render(u->sink, l, &u->memchunk);
+
+ pa_assert(u->memchunk.length > 0);
+
+ p = pa_memblock_acquire(u->memchunk.memblock);
+ t = pa_write(u->fd, (uint8_t*) p + u->memchunk.index, u->memchunk.length, &write_type);
+ pa_memblock_release(u->memchunk.memblock);
+
+/* pa_log("wrote %i bytes of %u", t, l); */
+
+ pa_assert(t != 0);
+
+ if (t < 0) {
+
+ if (errno == EINTR)
+ continue;
+
+ else if (errno == EAGAIN) {
+ pa_log_debug("EAGAIN");
+
+ revents &= ~POLLOUT;
+ break;
+
+ } else {
+ pa_log("Failed to write data to DSP: %s", pa_cstrerror(errno));
+ goto fail;
+ }
+
+ } else {
+
+ u->memchunk.index += t;
+ u->memchunk.length -= t;
+
+ if (u->memchunk.length <= 0) {
+ pa_memblock_unref(u->memchunk.memblock);
+ pa_memchunk_reset(&u->memchunk);
+ }
+
+ l -= t;
+
+ revents &= ~POLLOUT;
+ work_done = TRUE;
+ }
+
+ if (!loop)
+ break;
+ }
+
+ if (work_done)
+ continue;
+ }
+ }
+
+ /* Try to read some data and pass it on to the source driver. */
+
+ if (u->source && PA_SOURCE_OPENED(u->source->thread_info.state) && ((revents & POLLIN) || u->use_mmap || u->use_getispace)) {
+
+ if (u->use_mmap) {
+
+ if ((ret = mmap_read(u)) < 0)
+ goto fail;
+
+ revents &= ~POLLIN;
+
+ if (ret > 0)
+ continue;
+
+ } else {
+
+ void *p;
+ ssize_t l;
+ pa_memchunk memchunk;
+ pa_bool_t loop = FALSE, work_done = FALSE;
+
+ l = u->in_fragment_size;
+
+ if (u->use_getispace) {
+ audio_buf_info info;
+
+ if (ioctl(u->fd, SNDCTL_DSP_GETISPACE, &info) < 0) {
+ pa_log_info("Device doesn't support SNDCTL_DSP_GETISPACE: %s", pa_cstrerror(errno));
+ u->use_getispace = FALSE;
+ } else {
+ l = info.bytes;
+ loop = TRUE;
+ }
+ }
+
+ l = (l/u->in_fragment_size) * u->in_fragment_size;
+
+ if (l <= 0 && (revents & POLLIN)) {
+ l = u->in_fragment_size;
+ loop = FALSE;
+ }
+
+ while (l > 0) {
+ ssize_t t, k;
+
+ pa_assert(l > 0);
+
+ memchunk.memblock = pa_memblock_new(u->core->mempool, (size_t) -1);
+
+ k = pa_memblock_get_length(memchunk.memblock);
+
+ if (k > l)
+ k = l;
+
+ k = (k/u->frame_size)*u->frame_size;
+
+ p = pa_memblock_acquire(memchunk.memblock);
+ t = pa_read(u->fd, p, k, &read_type);
+ pa_memblock_release(memchunk.memblock);
+
+ pa_assert(t != 0); /* EOF cannot happen */
+
+/* pa_log("read %i bytes of %u", t, l); */
+
+ if (t < 0) {
+ pa_memblock_unref(memchunk.memblock);
+
+ if (errno == EINTR)
+ continue;
+
+ else if (errno == EAGAIN) {
+ pa_log_debug("EAGAIN");
+
+ revents &= ~POLLIN;
+ break;
+
+ } else {
+ pa_log("Failed to read data from DSP: %s", pa_cstrerror(errno));
+ goto fail;
+ }
+
+ } else {
+ memchunk.index = 0;
+ memchunk.length = t;
+
+ pa_source_post(u->source, &memchunk);
+ pa_memblock_unref(memchunk.memblock);
+
+ l -= t;
+
+ revents &= ~POLLIN;
+ work_done = TRUE;
+ }
+
+ if (!loop)
+ break;
+ }
+
+ if (work_done)
+ continue;
+ }
+ }
+
+/* pa_log("loop2 revents=%i", revents); */
+
+ if (u->rtpoll_item) {
+ struct pollfd *pollfd;
+
+ pa_assert(u->fd >= 0);
+
+ pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
+ pollfd->events =
+ ((u->source && PA_SOURCE_OPENED(u->source->thread_info.state)) ? POLLIN : 0) |
+ ((u->sink && PA_SINK_OPENED(u->sink->thread_info.state)) ? POLLOUT : 0);
+ }
+
+ /* Hmm, nothing to do. Let's sleep */
+ if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0)
+ goto fail;
+
+ if (ret == 0)
+ goto finish;
+
+ if (u->rtpoll_item) {
+ struct pollfd *pollfd;
+
+ pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
+
+ if (pollfd->revents & ~(POLLOUT|POLLIN)) {
+ pa_log("DSP shutdown.");
+ goto fail;
+ }
+
+ revents = pollfd->revents;
+ } else
+ revents = 0;
+ }
+
+fail:
+ /* If this was no regular exit from the loop we have to continue
+ * processing messages until we received PA_MESSAGE_SHUTDOWN */
+ pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL);
+ pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN);
+
+finish:
+ pa_log_debug("Thread shutting down");
+}
+
+int pa__init(pa_module*m) {
+
+ struct audio_buf_info info;
+ struct userdata *u = NULL;
+ const char *dev;
+ int fd = -1;
+ int nfrags, frag_size;
+ int mode, caps;
+ pa_bool_t record = TRUE, playback = TRUE, use_mmap = TRUE;
+ pa_sample_spec ss;
+ pa_channel_map map;
+ pa_modargs *ma = NULL;
+ char hwdesc[64], *t;
+ const char *name;
+ int namereg_fail;
+
+ pa_assert(m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log("Failed to parse module arguments.");
+ goto fail;
+ }
+
+ if (pa_modargs_get_value_boolean(ma, "record", &record) < 0 || pa_modargs_get_value_boolean(ma, "playback", &playback) < 0) {
+ pa_log("record= and playback= expect boolean argument.");
+ goto fail;
+ }
+
+ if (!playback && !record) {
+ pa_log("Neither playback nor record enabled for device.");
+ goto fail;
+ }
+
+ mode = (playback && record) ? O_RDWR : (playback ? O_WRONLY : (record ? O_RDONLY : 0));
+
+ ss = m->core->default_sample_spec;
+ if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_OSS) < 0) {
+ pa_log("Failed to parse sample specification or channel map");
+ goto fail;
+ }
+
+ nfrags = m->core->default_n_fragments;
+ frag_size = pa_usec_to_bytes(m->core->default_fragment_size_msec*1000, &ss);
+ if (frag_size <= 0)
+ frag_size = pa_frame_size(&ss);
+
+ if (pa_modargs_get_value_s32(ma, "fragments", &nfrags) < 0 || pa_modargs_get_value_s32(ma, "fragment_size", &frag_size) < 0) {
+ pa_log("Failed to parse fragments arguments");
+ goto fail;
+ }
+
+ if (pa_modargs_get_value_boolean(ma, "mmap", &use_mmap) < 0) {
+ pa_log("Failed to parse mmap argument.");
+ goto fail;
+ }
+
+ if ((fd = pa_oss_open(dev = pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), &mode, &caps)) < 0)
+ goto fail;
+
+ if (use_mmap && (!(caps & DSP_CAP_MMAP) || !(caps & DSP_CAP_TRIGGER))) {
+ pa_log_info("OSS device not mmap capable, falling back to UNIX read/write mode.");
+ use_mmap = 0;
+ }
+
+ if (use_mmap && mode == O_WRONLY) {
+ pa_log_info("Device opened for playback only, cannot do memory mapping, falling back to UNIX write() mode.");
+ use_mmap = 0;
+ }
+
+ if (pa_oss_get_hw_description(dev, hwdesc, sizeof(hwdesc)) >= 0)
+ pa_log_info("Hardware name is '%s'.", hwdesc);
+ else
+ hwdesc[0] = 0;
+
+ pa_log_info("Device opened in %s mode.", mode == O_WRONLY ? "O_WRONLY" : (mode == O_RDONLY ? "O_RDONLY" : "O_RDWR"));
+
+ if (nfrags >= 2 && frag_size >= 1)
+ if (pa_oss_set_fragments(fd, nfrags, frag_size) < 0)
+ goto fail;
+
+ if (pa_oss_auto_format(fd, &ss) < 0)
+ goto fail;
+
+ if (ioctl(fd, SNDCTL_DSP_GETBLKSIZE, &frag_size) < 0) {
+ pa_log("SNDCTL_DSP_GETBLKSIZE: %s", pa_cstrerror(errno));
+ goto fail;
+ }
+ pa_assert(frag_size > 0);
+
+ u = pa_xnew0(struct userdata, 1);
+ u->core = m->core;
+ u->module = m;
+ m->userdata = u;
+ u->fd = fd;
+ u->mixer_fd = -1;
+ u->use_getospace = u->use_getispace = 1;
+ u->use_getodelay = 1;
+ u->mode = mode;
+ u->frame_size = pa_frame_size(&ss);
+ u->device_name = pa_xstrdup(dev);
+ u->in_nfrags = u->out_nfrags = u->nfrags = nfrags;
+ u->out_fragment_size = u->in_fragment_size = u->frag_size = frag_size;
+ u->use_mmap = use_mmap;
+ pa_thread_mq_init(&u->thread_mq, m->core->mainloop);
+ u->rtpoll = pa_rtpoll_new();
+ pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq);
+ u->rtpoll_item = NULL;
+ build_pollfd(u);
+
+ if (ioctl(fd, SNDCTL_DSP_GETISPACE, &info) >= 0) {
+ pa_log_info("Input -- %u fragments of size %u.", info.fragstotal, info.fragsize);
+ u->in_fragment_size = info.fragsize;
+ u->in_nfrags = info.fragstotal;
+ u->use_getispace = 1;
+ }
+
+ if (ioctl(fd, SNDCTL_DSP_GETOSPACE, &info) >= 0) {
+ pa_log_info("Output -- %u fragments of size %u.", info.fragstotal, info.fragsize);
+ u->out_fragment_size = info.fragsize;
+ u->out_nfrags = info.fragstotal;
+ u->use_getospace = 1;
+ }
+
+ u->in_hwbuf_size = u->in_nfrags * u->in_fragment_size;
+ u->out_hwbuf_size = u->out_nfrags * u->out_fragment_size;
+
+ if (mode != O_WRONLY) {
+ char *name_buf = NULL;
+
+ if (use_mmap) {
+ if ((u->in_mmap = mmap(NULL, u->in_hwbuf_size, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) {
+ pa_log_warn("mmap(PROT_READ) failed, reverting to non-mmap mode: %s", pa_cstrerror(errno));
+ use_mmap = u->use_mmap = 0;
+ u->in_mmap = NULL;
+ } else
+ pa_log_debug("Successfully mmap()ed input buffer.");
+ }
+
+ if ((name = pa_modargs_get_value(ma, "source_name", NULL)))
+ namereg_fail = 1;
+ else {
+ name = name_buf = pa_sprintf_malloc("oss_input.%s", pa_path_get_filename(dev));
+ namereg_fail = 0;
+ }
+
+ u->source = pa_source_new(m->core, __FILE__, name, namereg_fail, &ss, &map);
+ pa_xfree(name_buf);
+ if (!u->source) {
+ pa_log("Failed to create source object");
+ goto fail;
+ }
+
+ u->source->parent.process_msg = source_process_msg;
+ u->source->userdata = u;
+
+ pa_source_set_module(u->source, m);
+ pa_source_set_asyncmsgq(u->source, u->thread_mq.inq);
+ pa_source_set_rtpoll(u->source, u->rtpoll);
+ pa_source_set_description(u->source, t = pa_sprintf_malloc(
+ "OSS PCM on %s%s%s%s%s",
+ dev,
+ hwdesc[0] ? " (" : "",
+ hwdesc[0] ? hwdesc : "",
+ hwdesc[0] ? ")" : "",
+ use_mmap ? " via DMA" : ""));
+ pa_xfree(t);
+ u->source->flags = PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY;
+ u->source->refresh_volume = TRUE;
+
+ if (use_mmap)
+ u->in_mmap_memblocks = pa_xnew0(pa_memblock*, u->in_nfrags);
+ }
+
+ if (mode != O_RDONLY) {
+ char *name_buf = NULL;
+
+ if (use_mmap) {
+ if ((u->out_mmap = mmap(NULL, u->out_hwbuf_size, PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) {
+ if (mode == O_RDWR) {
+ pa_log_debug("mmap() failed for input. Changing to O_WRONLY mode.");
+ mode = O_WRONLY;
+ goto go_on;
+ } else {
+ pa_log_warn("mmap(PROT_WRITE) failed, reverting to non-mmap mode: %s", pa_cstrerror(errno));
+ u->use_mmap = (use_mmap = FALSE);
+ u->out_mmap = NULL;
+ }
+ } else {
+ pa_log_debug("Successfully mmap()ed output buffer.");
+ pa_silence_memory(u->out_mmap, u->out_hwbuf_size, &ss);
+ }
+ }
+
+ if ((name = pa_modargs_get_value(ma, "sink_name", NULL)))
+ namereg_fail = 1;
+ else {
+ name = name_buf = pa_sprintf_malloc("oss_output.%s", pa_path_get_filename(dev));
+ namereg_fail = 0;
+ }
+
+ u->sink = pa_sink_new(m->core, __FILE__, name, namereg_fail, &ss, &map);
+ pa_xfree(name_buf);
+ if (!u->sink) {
+ pa_log("Failed to create sink object");
+ goto fail;
+ }
+
+ u->sink->parent.process_msg = sink_process_msg;
+ u->sink->userdata = u;
+
+ pa_sink_set_module(u->sink, m);
+ pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq);
+ pa_sink_set_rtpoll(u->sink, u->rtpoll);
+ pa_sink_set_description(u->sink, t = pa_sprintf_malloc(
+ "OSS PCM on %s%s%s%s%s",
+ dev,
+ hwdesc[0] ? " (" : "",
+ hwdesc[0] ? hwdesc : "",
+ hwdesc[0] ? ")" : "",
+ use_mmap ? " via DMA" : ""));
+ pa_xfree(t);
+ u->sink->flags = PA_SINK_HARDWARE|PA_SINK_LATENCY;
+ u->sink->refresh_volume = TRUE;
+
+ if (use_mmap)
+ u->out_mmap_memblocks = pa_xnew0(pa_memblock*, u->out_nfrags);
+ }
+
+ if ((u->mixer_fd = pa_oss_open_mixer_for_device(u->device_name)) >= 0) {
+ int do_close = 1;
+ u->mixer_devmask = 0;
+
+ if (ioctl(fd, SOUND_MIXER_READ_DEVMASK, &u->mixer_devmask) < 0)
+ pa_log_warn("SOUND_MIXER_READ_DEVMASK failed: %s", pa_cstrerror(errno));
+
+ else {
+ if (u->sink && (u->mixer_devmask & (SOUND_MASK_VOLUME|SOUND_MASK_PCM))) {
+ pa_log_debug("Found hardware mixer track for playback.");
+ u->sink->flags |= PA_SINK_HW_VOLUME_CTRL;
+ u->sink->get_volume = sink_get_volume;
+ u->sink->set_volume = sink_set_volume;
+ do_close = 0;
+ }
+
+ if (u->source && (u->mixer_devmask & (SOUND_MASK_RECLEV|SOUND_MASK_IGAIN))) {
+ pa_log_debug("Found hardware mixer track for recording.");
+ u->source->flags |= PA_SOURCE_HW_VOLUME_CTRL;
+ u->source->get_volume = source_get_volume;
+ u->source->set_volume = source_set_volume;
+ do_close = 0;
+ }
+ }
+
+ if (do_close) {
+ pa_close(u->mixer_fd);
+ u->mixer_fd = -1;
+ }
+ }
+
+go_on:
+
+ pa_assert(u->source || u->sink);
+
+ pa_memchunk_reset(&u->memchunk);
+
+ if (!(u->thread = pa_thread_new(thread_func, u))) {
+ pa_log("Failed to create thread.");
+ goto fail;
+ }
+
+ /* Read mixer settings */
+ if (u->sink && u->sink->get_volume)
+ sink_get_volume(u->sink);
+ if (u->source && u->source->get_volume)
+ source_get_volume(u->source);
+
+ if (u->sink)
+ pa_sink_put(u->sink);
+ if (u->source)
+ pa_source_put(u->source);
+
+ pa_modargs_free(ma);
+
+ return 0;
+
+fail:
+
+ if (u)
+ pa__done(m);
+ else if (fd >= 0)
+ pa_close(fd);
+
+ if (ma)
+ pa_modargs_free(ma);
+
+ return -1;
+}
+
+void pa__done(pa_module*m) {
+ struct userdata *u;
+
+ pa_assert(m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->sink)
+ pa_sink_unlink(u->sink);
+
+ if (u->source)
+ pa_source_unlink(u->source);
+
+ if (u->thread) {
+ pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL);
+ pa_thread_free(u->thread);
+ }
+
+ pa_thread_mq_done(&u->thread_mq);
+
+ if (u->sink)
+ pa_sink_unref(u->sink);
+
+ if (u->source)
+ pa_source_unref(u->source);
+
+ if (u->memchunk.memblock)
+ pa_memblock_unref(u->memchunk.memblock);
+
+ if (u->rtpoll_item)
+ pa_rtpoll_item_free(u->rtpoll_item);
+
+ if (u->rtpoll)
+ pa_rtpoll_free(u->rtpoll);
+
+ if (u->out_mmap_memblocks) {
+ unsigned i;
+ for (i = 0; i < u->out_nfrags; i++)
+ if (u->out_mmap_memblocks[i])
+ pa_memblock_unref_fixed(u->out_mmap_memblocks[i]);
+ pa_xfree(u->out_mmap_memblocks);
+ }
+
+ if (u->in_mmap_memblocks) {
+ unsigned i;
+ for (i = 0; i < u->in_nfrags; i++)
+ if (u->in_mmap_memblocks[i])
+ pa_memblock_unref_fixed(u->in_mmap_memblocks[i]);
+ pa_xfree(u->in_mmap_memblocks);
+ }
+
+ if (u->in_mmap && u->in_mmap != MAP_FAILED)
+ munmap(u->in_mmap, u->in_hwbuf_size);
+
+ if (u->out_mmap && u->out_mmap != MAP_FAILED)
+ munmap(u->out_mmap, u->out_hwbuf_size);
+
+ if (u->fd >= 0)
+ pa_close(u->fd);
+
+ if (u->mixer_fd >= 0)
+ pa_close(u->mixer_fd);
+
+ pa_xfree(u->device_name);
+
+ pa_xfree(u);
+}
diff --git a/src/modules/module-pipe-sink.c b/src/modules/module-pipe-sink.c
new file mode 100644
index 00000000..e720c8ad
--- /dev/null
+++ b/src/modules/module-pipe-sink.c
@@ -0,0 +1,333 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+#include <sys/ioctl.h>
+#include <poll.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/core-error.h>
+#include <pulsecore/sink.h>
+#include <pulsecore/module.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/log.h>
+#include <pulsecore/thread.h>
+#include <pulsecore/thread-mq.h>
+#include <pulsecore/rtpoll.h>
+
+#include "module-pipe-sink-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering");
+PA_MODULE_DESCRIPTION("UNIX pipe sink");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(FALSE);
+PA_MODULE_USAGE(
+ "sink_name=<name for the sink> "
+ "file=<path of the FIFO> "
+ "format=<sample format> "
+ "channels=<number of channels> "
+ "rate=<sample rate>"
+ "channel_map=<channel map>");
+
+#define DEFAULT_FILE_NAME "/tmp/music.output"
+#define DEFAULT_SINK_NAME "fifo_output"
+
+struct userdata {
+ pa_core *core;
+ pa_module *module;
+ pa_sink *sink;
+
+ pa_thread *thread;
+ pa_thread_mq thread_mq;
+ pa_rtpoll *rtpoll;
+
+ char *filename;
+ int fd;
+
+ pa_memchunk memchunk;
+
+ pa_rtpoll_item *rtpoll_item;
+};
+
+static const char* const valid_modargs[] = {
+ "file",
+ "rate",
+ "format",
+ "channels",
+ "sink_name",
+ "channel_map",
+ NULL
+};
+
+static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+ struct userdata *u = PA_SINK(o)->userdata;
+
+ switch (code) {
+
+ case PA_SINK_MESSAGE_GET_LATENCY: {
+ size_t n = 0;
+ int l;
+
+#ifdef TIOCINQ
+ if (ioctl(u->fd, TIOCINQ, &l) >= 0 && l > 0)
+ n = (size_t) l;
+#endif
+
+ n += u->memchunk.length;
+
+ *((pa_usec_t*) data) = pa_bytes_to_usec(n, &u->sink->sample_spec);
+ break;
+ }
+ }
+
+ return pa_sink_process_msg(o, code, data, offset, chunk);
+}
+
+static void thread_func(void *userdata) {
+ struct userdata *u = userdata;
+ int write_type = 0;
+
+ pa_assert(u);
+
+ pa_log_debug("Thread starting up");
+
+ pa_thread_mq_install(&u->thread_mq);
+ pa_rtpoll_install(u->rtpoll);
+
+ for (;;) {
+ struct pollfd *pollfd;
+ int ret;
+
+ pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
+
+ /* Render some data and write it to the fifo */
+ if (u->sink->thread_info.state == PA_SINK_RUNNING && pollfd->revents) {
+ ssize_t l;
+ void *p;
+
+ if (u->memchunk.length <= 0)
+ pa_sink_render(u->sink, PIPE_BUF, &u->memchunk);
+
+ pa_assert(u->memchunk.length > 0);
+
+ p = pa_memblock_acquire(u->memchunk.memblock);
+ l = pa_write(u->fd, (uint8_t*) p + u->memchunk.index, u->memchunk.length, &write_type);
+ pa_memblock_release(u->memchunk.memblock);
+
+ pa_assert(l != 0);
+
+ if (l < 0) {
+
+ if (errno == EINTR)
+ continue;
+ else if (errno != EAGAIN) {
+ pa_log("Failed to write data to FIFO: %s", pa_cstrerror(errno));
+ goto fail;
+ }
+
+ } else {
+
+ u->memchunk.index += l;
+ u->memchunk.length -= l;
+
+ if (u->memchunk.length <= 0) {
+ pa_memblock_unref(u->memchunk.memblock);
+ pa_memchunk_reset(&u->memchunk);
+ }
+
+ pollfd->revents = 0;
+ }
+ }
+
+ /* Hmm, nothing to do. Let's sleep */
+ pollfd->events = u->sink->thread_info.state == PA_SINK_RUNNING ? POLLOUT : 0;
+
+ if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0)
+ goto fail;
+
+ if (ret == 0)
+ goto finish;
+
+ pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
+
+ if (pollfd->revents & ~POLLOUT) {
+ pa_log("FIFO shutdown.");
+ goto fail;
+ }
+ }
+
+fail:
+ /* If this was no regular exit from the loop we have to continue
+ * processing messages until we received PA_MESSAGE_SHUTDOWN */
+ pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL);
+ pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN);
+
+finish:
+ pa_log_debug("Thread shutting down");
+}
+
+int pa__init(pa_module*m) {
+ struct userdata *u;
+ struct stat st;
+ pa_sample_spec ss;
+ pa_channel_map map;
+ pa_modargs *ma;
+ char *t;
+ struct pollfd *pollfd;
+
+ pa_assert(m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log("Failed to parse module arguments.");
+ goto fail;
+ }
+
+ ss = m->core->default_sample_spec;
+ if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) {
+ pa_log("Invalid sample format specification or channel map");
+ goto fail;
+ }
+
+ u = pa_xnew0(struct userdata, 1);
+ u->core = m->core;
+ u->module = m;
+ m->userdata = u;
+ pa_memchunk_reset(&u->memchunk);
+ pa_thread_mq_init(&u->thread_mq, m->core->mainloop);
+ u->rtpoll = pa_rtpoll_new();
+ pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq);
+
+ u->filename = pa_xstrdup(pa_modargs_get_value(ma, "file", DEFAULT_FILE_NAME));
+
+ mkfifo(u->filename, 0666);
+ if ((u->fd = open(u->filename, O_RDWR|O_NOCTTY)) < 0) {
+ pa_log("open('%s'): %s", u->filename, pa_cstrerror(errno));
+ goto fail;
+ }
+
+ pa_make_fd_cloexec(u->fd);
+ pa_make_fd_nonblock(u->fd);
+
+ if (fstat(u->fd, &st) < 0) {
+ pa_log("fstat('%s'): %s", u->filename, pa_cstrerror(errno));
+ goto fail;
+ }
+
+ if (!S_ISFIFO(st.st_mode)) {
+ pa_log("'%s' is not a FIFO.", u->filename);
+ goto fail;
+ }
+
+ if (!(u->sink = pa_sink_new(m->core, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, &map))) {
+ pa_log("Failed to create sink.");
+ goto fail;
+ }
+
+ u->sink->parent.process_msg = sink_process_msg;
+ u->sink->userdata = u;
+ u->sink->flags = PA_SINK_LATENCY;
+
+ pa_sink_set_module(u->sink, m);
+ pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq);
+ pa_sink_set_rtpoll(u->sink, u->rtpoll);
+ pa_sink_set_description(u->sink, t = pa_sprintf_malloc("Unix FIFO sink '%s'", u->filename));
+ pa_xfree(t);
+
+ u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1);
+ pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
+ pollfd->fd = u->fd;
+ pollfd->events = pollfd->revents = 0;
+
+ if (!(u->thread = pa_thread_new(thread_func, u))) {
+ pa_log("Failed to create thread.");
+ goto fail;
+ }
+
+ pa_sink_put(u->sink);
+
+ pa_modargs_free(ma);
+
+ return 0;
+
+fail:
+ if (ma)
+ pa_modargs_free(ma);
+
+ pa__done(m);
+
+ return -1;
+}
+
+void pa__done(pa_module*m) {
+ struct userdata *u;
+
+ pa_assert(m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->sink)
+ pa_sink_unlink(u->sink);
+
+ if (u->thread) {
+ pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL);
+ pa_thread_free(u->thread);
+ }
+
+ pa_thread_mq_done(&u->thread_mq);
+
+ if (u->sink)
+ pa_sink_unref(u->sink);
+
+ if (u->memchunk.memblock)
+ pa_memblock_unref(u->memchunk.memblock);
+
+ if (u->rtpoll_item)
+ pa_rtpoll_item_free(u->rtpoll_item);
+
+ if (u->rtpoll)
+ pa_rtpoll_free(u->rtpoll);
+
+ if (u->filename) {
+ unlink(u->filename);
+ pa_xfree(u->filename);
+ }
+
+ if (u->fd >= 0)
+ pa_assert_se(pa_close(u->fd) == 0);
+
+ pa_xfree(u);
+}
diff --git a/src/modules/module-pipe-source.c b/src/modules/module-pipe-source.c
new file mode 100644
index 00000000..02935649
--- /dev/null
+++ b/src/modules/module-pipe-source.c
@@ -0,0 +1,309 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+#include <sys/poll.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/core-error.h>
+#include <pulsecore/source.h>
+#include <pulsecore/module.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/log.h>
+#include <pulsecore/thread.h>
+#include <pulsecore/thread-mq.h>
+#include <pulsecore/rtpoll.h>
+
+#include "module-pipe-source-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering");
+PA_MODULE_DESCRIPTION("UNIX pipe source");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(FALSE);
+PA_MODULE_USAGE(
+ "source_name=<name for the source> "
+ "file=<path of the FIFO> "
+ "format=<sample format> "
+ "channels=<number of channels> "
+ "rate=<sample rate> "
+ "channel_map=<channel map>");
+
+#define DEFAULT_FILE_NAME "/tmp/music.input"
+#define DEFAULT_SOURCE_NAME "fifo_input"
+
+struct userdata {
+ pa_core *core;
+ pa_module *module;
+ pa_source *source;
+
+ pa_thread *thread;
+ pa_thread_mq thread_mq;
+ pa_rtpoll *rtpoll;
+
+ char *filename;
+ int fd;
+
+ pa_memchunk memchunk;
+
+ pa_rtpoll_item *rtpoll_item;
+};
+
+static const char* const valid_modargs[] = {
+ "file",
+ "rate",
+ "channels",
+ "format",
+ "source_name",
+ "channel_map",
+ NULL
+};
+
+static void thread_func(void *userdata) {
+ struct userdata *u = userdata;
+ int read_type = 0;
+
+ pa_assert(u);
+
+ pa_log_debug("Thread starting up");
+
+ pa_thread_mq_install(&u->thread_mq);
+ pa_rtpoll_install(u->rtpoll);
+
+ for (;;) {
+ int ret;
+ struct pollfd *pollfd;
+
+ pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
+
+ /* Try to read some data and pass it on to the source driver */
+ if (u->source->thread_info.state == PA_SOURCE_RUNNING && pollfd->revents) {
+ ssize_t l;
+ void *p;
+
+ if (!u->memchunk.memblock) {
+ u->memchunk.memblock = pa_memblock_new(u->core->mempool, PIPE_BUF);
+ u->memchunk.index = u->memchunk.length = 0;
+ }
+
+ pa_assert(pa_memblock_get_length(u->memchunk.memblock) > u->memchunk.index);
+
+ p = pa_memblock_acquire(u->memchunk.memblock);
+ l = pa_read(u->fd, (uint8_t*) p + u->memchunk.index, pa_memblock_get_length(u->memchunk.memblock) - u->memchunk.index, &read_type);
+ pa_memblock_release(u->memchunk.memblock);
+
+ pa_assert(l != 0); /* EOF cannot happen, since we opened the fifo for both reading and writing */
+
+ if (l < 0) {
+
+ if (errno == EINTR)
+ continue;
+ else if (errno != EAGAIN) {
+ pa_log("Faile to read data from FIFO: %s", pa_cstrerror(errno));
+ goto fail;
+ }
+
+ } else {
+
+ u->memchunk.length = l;
+ pa_source_post(u->source, &u->memchunk);
+ u->memchunk.index += l;
+
+ if (u->memchunk.index >= pa_memblock_get_length(u->memchunk.memblock)) {
+ pa_memblock_unref(u->memchunk.memblock);
+ pa_memchunk_reset(&u->memchunk);
+ }
+
+ pollfd->revents = 0;
+ }
+ }
+
+ /* Hmm, nothing to do. Let's sleep */
+ pollfd->events = u->source->thread_info.state == PA_SOURCE_RUNNING ? POLLIN : 0;
+
+ if ((ret = pa_rtpoll_run(u->rtpoll, 1)) < 0)
+ goto fail;
+
+ if (ret == 0)
+ goto finish;
+
+ pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
+ if (pollfd->revents & ~POLLIN) {
+ pa_log("FIFO shutdown.");
+ goto fail;
+ }
+ }
+
+fail:
+ /* If this was no regular exit from the loop we have to continue
+ * processing messages until we received PA_MESSAGE_SHUTDOWN */
+ pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL);
+ pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN);
+
+finish:
+ pa_log_debug("Thread shutting down");
+}
+
+int pa__init(pa_module*m) {
+ struct userdata *u;
+ struct stat st;
+ pa_sample_spec ss;
+ pa_channel_map map;
+ pa_modargs *ma;
+ char *t;
+ struct pollfd *pollfd;
+
+ pa_assert(m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log("failed to parse module arguments.");
+ goto fail;
+ }
+
+ ss = m->core->default_sample_spec;
+ if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) {
+ pa_log("invalid sample format specification or channel map");
+ goto fail;
+ }
+
+ u = pa_xnew0(struct userdata, 1);
+ u->core = m->core;
+ u->module = m;
+ m->userdata = u;
+ pa_memchunk_reset(&u->memchunk);
+ pa_thread_mq_init(&u->thread_mq, m->core->mainloop);
+ u->rtpoll = pa_rtpoll_new();
+ pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq);
+
+ u->filename = pa_xstrdup(pa_modargs_get_value(ma, "file", DEFAULT_FILE_NAME));
+
+ mkfifo(u->filename, 0666);
+ if ((u->fd = open(u->filename, O_RDWR|O_NOCTTY)) < 0) {
+ pa_log("open('%s'): %s", u->filename, pa_cstrerror(errno));
+ goto fail;
+ }
+
+ pa_make_fd_cloexec(u->fd);
+ pa_make_fd_nonblock(u->fd);
+
+ if (fstat(u->fd, &st) < 0) {
+ pa_log("fstat('%s'): %s",u->filename, pa_cstrerror(errno));
+ goto fail;
+ }
+
+ if (!S_ISFIFO(st.st_mode)) {
+ pa_log("'%s' is not a FIFO.", u->filename);
+ goto fail;
+ }
+
+ if (!(u->source = pa_source_new(m->core, __FILE__, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &ss, &map))) {
+ pa_log("Failed to create source.");
+ goto fail;
+ }
+
+ u->source->userdata = u;
+ u->source->flags = 0;
+
+ pa_source_set_module(u->source, m);
+ pa_source_set_asyncmsgq(u->source, u->thread_mq.inq);
+ pa_source_set_rtpoll(u->source, u->rtpoll);
+ pa_source_set_description(u->source, t = pa_sprintf_malloc("Unix FIFO source '%s'", u->filename));
+ pa_xfree(t);
+
+ u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1);
+ pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
+ pollfd->fd = u->fd;
+ pollfd->events = pollfd->revents = 0;
+
+ if (!(u->thread = pa_thread_new(thread_func, u))) {
+ pa_log("Failed to create thread.");
+ goto fail;
+ }
+
+ pa_source_put(u->source);
+
+ pa_modargs_free(ma);
+
+ return 0;
+
+fail:
+ if (ma)
+ pa_modargs_free(ma);
+
+ pa__done(m);
+
+ return -1;
+}
+
+void pa__done(pa_module*m) {
+ struct userdata *u;
+
+ pa_assert(m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->source)
+ pa_source_unlink(u->source);
+
+ if (u->thread) {
+ pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL);
+ pa_thread_free(u->thread);
+ }
+
+ pa_thread_mq_done(&u->thread_mq);
+
+ if (u->source)
+ pa_source_unref(u->source);
+
+ if (u->memchunk.memblock)
+ pa_memblock_unref(u->memchunk.memblock);
+
+ if (u->rtpoll_item)
+ pa_rtpoll_item_free(u->rtpoll_item);
+
+ if (u->rtpoll)
+ pa_rtpoll_free(u->rtpoll);
+
+ if (u->filename) {
+ unlink(u->filename);
+ pa_xfree(u->filename);
+ }
+
+ if (u->fd >= 0)
+ pa_assert_se(pa_close(u->fd) == 0);
+
+ pa_xfree(u);
+}
diff --git a/src/modules/module-protocol-stub.c b/src/modules/module-protocol-stub.c
new file mode 100644
index 00000000..a9bd850b
--- /dev/null
+++ b/src/modules/module-protocol-stub.c
@@ -0,0 +1,376 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <limits.h>
+
+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+#ifdef HAVE_ARPA_INET_H
+#include <arpa/inet.h>
+#endif
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/winsock.h>
+#include <pulsecore/core-error.h>
+#include <pulsecore/module.h>
+#include <pulsecore/socket-server.h>
+#include <pulsecore/socket-util.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/log.h>
+#include <pulsecore/native-common.h>
+#include <pulsecore/creds.h>
+
+#ifdef USE_TCP_SOCKETS
+#define SOCKET_DESCRIPTION "(TCP sockets)"
+#define SOCKET_USAGE "port=<TCP port number> listen=<address to listen on>"
+#else
+#define SOCKET_DESCRIPTION "(UNIX sockets)"
+#define SOCKET_USAGE "socket=<path to UNIX socket>"
+#endif
+
+#if defined(USE_PROTOCOL_SIMPLE)
+ #include <pulsecore/protocol-simple.h>
+ #define protocol_new pa_protocol_simple_new
+ #define protocol_free pa_protocol_simple_free
+ #define TCPWRAP_SERVICE "pulseaudio-simple"
+ #define IPV4_PORT 4711
+ #define UNIX_SOCKET "simple"
+ #define MODULE_ARGUMENTS "rate", "format", "channels", "sink", "source", "playback", "record",
+ #if defined(USE_TCP_SOCKETS)
+ #include "module-simple-protocol-tcp-symdef.h"
+ #else
+ #include "module-simple-protocol-unix-symdef.h"
+ #endif
+PA_MODULE_DESCRIPTION("Simple protocol "SOCKET_DESCRIPTION);
+ PA_MODULE_USAGE("rate=<sample rate> "
+ "format=<sample format> "
+ "channels=<number of channels> "
+ "sink=<sink to connect to> "
+ "source=<source to connect to> "
+ "playback=<enable playback?> "
+ "record=<enable record?> "
+ SOCKET_USAGE);
+#elif defined(USE_PROTOCOL_CLI)
+ #include <pulsecore/protocol-cli.h>
+ #define protocol_new pa_protocol_cli_new
+ #define protocol_free pa_protocol_cli_free
+ #define TCPWRAP_SERVICE "pulseaudio-cli"
+ #define IPV4_PORT 4712
+ #define UNIX_SOCKET "cli"
+ #define MODULE_ARGUMENTS
+ #ifdef USE_TCP_SOCKETS
+ #include "module-cli-protocol-tcp-symdef.h"
+ #else
+ #include "module-cli-protocol-unix-symdef.h"
+ #endif
+ PA_MODULE_DESCRIPTION("Command line interface protocol "SOCKET_DESCRIPTION);
+ PA_MODULE_USAGE(SOCKET_USAGE);
+#elif defined(USE_PROTOCOL_HTTP)
+ #include <pulsecore/protocol-http.h>
+ #define protocol_new pa_protocol_http_new
+ #define protocol_free pa_protocol_http_free
+ #define TCPWRAP_SERVICE "pulseaudio-http"
+ #define IPV4_PORT 4714
+ #define UNIX_SOCKET "http"
+ #define MODULE_ARGUMENTS
+ #ifdef USE_TCP_SOCKETS
+ #include "module-http-protocol-tcp-symdef.h"
+ #else
+ #include "module-http-protocol-unix-symdef.h"
+ #endif
+ PA_MODULE_DESCRIPTION("HTTP "SOCKET_DESCRIPTION);
+ PA_MODULE_USAGE(SOCKET_USAGE);
+#elif defined(USE_PROTOCOL_NATIVE)
+ #include <pulsecore/protocol-native.h>
+ #define protocol_new pa_protocol_native_new
+ #define protocol_free pa_protocol_native_free
+ #define TCPWRAP_SERVICE "pulseaudio-native"
+ #define IPV4_PORT PA_NATIVE_DEFAULT_PORT
+ #define UNIX_SOCKET PA_NATIVE_DEFAULT_UNIX_SOCKET
+ #define MODULE_ARGUMENTS_COMMON "cookie", "auth-anonymous",
+ #ifdef USE_TCP_SOCKETS
+ #include "module-native-protocol-tcp-symdef.h"
+ #else
+ #include "module-native-protocol-unix-symdef.h"
+ #endif
+
+ #if defined(HAVE_CREDS) && !defined(USE_TCP_SOCKETS)
+ #define MODULE_ARGUMENTS MODULE_ARGUMENTS_COMMON "auth-group", "auth-group-enable",
+ #define AUTH_USAGE "auth-group=<system group to allow access> auth-group-enable=<enable auth by UNIX group?> "
+ #elif defined(USE_TCP_SOCKETS)
+ #define MODULE_ARGUMENTS MODULE_ARGUMENTS_COMMON "auth-ip-acl",
+ #define AUTH_USAGE "auth-ip-acl=<IP address ACL to allow access> "
+ #else
+ #define MODULE_ARGUMENTS MODULE_ARGUMENTS_COMMON
+ #define AUTH_USAGE
+ #endif
+
+ PA_MODULE_DESCRIPTION("Native protocol "SOCKET_DESCRIPTION);
+ PA_MODULE_USAGE("auth-anonymous=<don't check for cookies?> "
+ "cookie=<path to cookie file> "
+ AUTH_USAGE
+ SOCKET_USAGE);
+#elif defined(USE_PROTOCOL_ESOUND)
+ #include <pulsecore/protocol-esound.h>
+ #include <pulsecore/esound.h>
+ #define protocol_new pa_protocol_esound_new
+ #define protocol_free pa_protocol_esound_free
+ #define TCPWRAP_SERVICE "esound"
+ #define IPV4_PORT ESD_DEFAULT_PORT
+ #define MODULE_ARGUMENTS_COMMON "sink", "source", "auth-anonymous", "cookie",
+ #ifdef USE_TCP_SOCKETS
+ #include "module-esound-protocol-tcp-symdef.h"
+ #else
+ #include "module-esound-protocol-unix-symdef.h"
+ #endif
+
+ #if defined(USE_TCP_SOCKETS)
+ #define MODULE_ARGUMENTS MODULE_ARGUMENTS_COMMON "auth-ip-acl",
+ #define AUTH_USAGE "auth-ip-acl=<IP address ACL to allow access> "
+ #else
+ #define MODULE_ARGUMENTS MODULE_ARGUMENTS_COMMON
+ #define AUTH_USAGE
+ #endif
+
+ PA_MODULE_DESCRIPTION("ESOUND protocol "SOCKET_DESCRIPTION);
+ PA_MODULE_USAGE("sink=<sink to connect to> "
+ "source=<source to connect to> "
+ "auth-anonymous=<don't verify cookies?> "
+ "cookie=<path to cookie file> "
+ AUTH_USAGE
+ SOCKET_USAGE);
+#else
+ #error "Broken build system"
+#endif
+
+PA_MODULE_LOAD_ONCE(FALSE);
+PA_MODULE_AUTHOR("Lennart Poettering");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+
+static const char* const valid_modargs[] = {
+ MODULE_ARGUMENTS
+#if defined(USE_TCP_SOCKETS)
+ "port",
+ "listen",
+#else
+ "socket",
+#endif
+ NULL
+};
+
+struct userdata {
+#if defined(USE_TCP_SOCKETS)
+ void *protocol_ipv4;
+ void *protocol_ipv6;
+#else
+ void *protocol_unix;
+ char *socket_path;
+#endif
+};
+
+int pa__init(pa_module*m) {
+ pa_modargs *ma = NULL;
+ int ret = -1;
+ struct userdata *u = NULL;
+
+#if defined(USE_TCP_SOCKETS)
+ pa_socket_server *s_ipv4 = NULL, *s_ipv6 = NULL;
+ uint32_t port = IPV4_PORT;
+ const char *listen_on;
+#else
+ pa_socket_server *s;
+ int r;
+ char tmp[PATH_MAX];
+
+#if defined(USE_PROTOCOL_ESOUND)
+#if defined(USE_PERUSER_ESOUND_SOCKET)
+ char esdsocketpath[PATH_MAX];
+#else
+ const char esdsocketpath[] = "/tmp/.esd/socket";
+#endif
+#endif
+#endif
+
+ pa_assert(m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log("Failed to parse module arguments");
+ goto finish;
+ }
+
+ u = pa_xnew0(struct userdata, 1);
+
+#if defined(USE_TCP_SOCKETS)
+ if (pa_modargs_get_value_u32(ma, "port", &port) < 0 || port < 1 || port > 0xFFFF) {
+ pa_log("port= expects a numerical argument between 1 and 65535.");
+ goto fail;
+ }
+
+ listen_on = pa_modargs_get_value(ma, "listen", NULL);
+
+ if (listen_on) {
+ s_ipv6 = pa_socket_server_new_ipv6_string(m->core->mainloop, listen_on, port, TCPWRAP_SERVICE);
+ s_ipv4 = pa_socket_server_new_ipv4_string(m->core->mainloop, listen_on, port, TCPWRAP_SERVICE);
+ } else {
+ s_ipv6 = pa_socket_server_new_ipv6_any(m->core->mainloop, port, TCPWRAP_SERVICE);
+ s_ipv4 = pa_socket_server_new_ipv4_any(m->core->mainloop, port, TCPWRAP_SERVICE);
+ }
+
+ if (!s_ipv4 && !s_ipv6)
+ goto fail;
+
+ if (s_ipv4)
+ if (!(u->protocol_ipv4 = protocol_new(m->core, s_ipv4, m, ma)))
+ pa_socket_server_unref(s_ipv4);
+
+ if (s_ipv6)
+ if (!(u->protocol_ipv6 = protocol_new(m->core, s_ipv6, m, ma)))
+ pa_socket_server_unref(s_ipv6);
+
+ if (!u->protocol_ipv4 && !u->protocol_ipv6)
+ goto fail;
+
+#else
+
+#if defined(USE_PROTOCOL_ESOUND)
+
+#if defined(USE_PERUSER_ESOUND_SOCKET)
+ snprintf(esdsocketpath, sizeof(esdsocketpath), "/tmp/.esd-%lu/socket", (unsigned long) getuid());
+#endif
+ pa_runtime_path(pa_modargs_get_value(ma, "socket", esdsocketpath), tmp, sizeof(tmp));
+ u->socket_path = pa_xstrdup(tmp);
+
+ /* This socket doesn't reside in our own runtime dir but in
+ * /tmp/.esd/, hence we have to create the dir first */
+
+ if (pa_make_secure_parent_dir(u->socket_path, m->core->is_system_instance ? 0755 : 0700, (uid_t)-1, (gid_t)-1) < 0) {
+ pa_log("Failed to create socket directory '%s': %s\n", u->socket_path, pa_cstrerror(errno));
+ goto fail;
+ }
+
+#else
+ pa_runtime_path(pa_modargs_get_value(ma, "socket", UNIX_SOCKET), tmp, sizeof(tmp));
+ u->socket_path = pa_xstrdup(tmp);
+#endif
+
+ if ((r = pa_unix_socket_remove_stale(tmp)) < 0) {
+ pa_log("Failed to remove stale UNIX socket '%s': %s", tmp, pa_cstrerror(errno));
+ goto fail;
+ }
+
+ if (r)
+ pa_log("Removed stale UNIX socket '%s'.", tmp);
+
+ if (!(s = pa_socket_server_new_unix(m->core->mainloop, tmp)))
+ goto fail;
+
+ if (!(u->protocol_unix = protocol_new(m->core, s, m, ma)))
+ goto fail;
+
+#endif
+
+ m->userdata = u;
+
+ ret = 0;
+
+finish:
+ if (ma)
+ pa_modargs_free(ma);
+
+ return ret;
+
+fail:
+ if (u) {
+#if defined(USE_TCP_SOCKETS)
+ if (u->protocol_ipv4)
+ protocol_free(u->protocol_ipv4);
+ if (u->protocol_ipv6)
+ protocol_free(u->protocol_ipv6);
+#else
+ if (u->protocol_unix)
+ protocol_free(u->protocol_unix);
+
+ if (u->socket_path)
+ pa_xfree(u->socket_path);
+#endif
+
+ pa_xfree(u);
+ } else {
+#if defined(USE_TCP_SOCKETS)
+ if (s_ipv4)
+ pa_socket_server_unref(s_ipv4);
+ if (s_ipv6)
+ pa_socket_server_unref(s_ipv6);
+#else
+ if (s)
+ pa_socket_server_unref(s);
+#endif
+ }
+
+ goto finish;
+}
+
+void pa__done(pa_module*m) {
+ struct userdata *u;
+
+ pa_assert(m);
+
+ u = m->userdata;
+
+#if defined(USE_TCP_SOCKETS)
+ if (u->protocol_ipv4)
+ protocol_free(u->protocol_ipv4);
+ if (u->protocol_ipv6)
+ protocol_free(u->protocol_ipv6);
+#else
+ if (u->protocol_unix)
+ protocol_free(u->protocol_unix);
+
+#if defined(USE_PROTOCOL_ESOUND)
+ if (u->socket_path) {
+ char *p = pa_parent_dir(u->socket_path);
+ rmdir(p);
+ pa_xfree(p);
+ }
+#endif
+
+ pa_xfree(u->socket_path);
+#endif
+
+ pa_xfree(u);
+}
diff --git a/src/modules/module-remap-sink.c b/src/modules/module-remap-sink.c
new file mode 100644
index 00000000..39a9245d
--- /dev/null
+++ b/src/modules/module-remap-sink.c
@@ -0,0 +1,335 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/core-error.h>
+#include <pulsecore/namereg.h>
+#include <pulsecore/sink.h>
+#include <pulsecore/module.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/log.h>
+#include <pulsecore/thread.h>
+#include <pulsecore/thread-mq.h>
+#include <pulsecore/rtpoll.h>
+
+#include "module-remap-sink-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering");
+PA_MODULE_DESCRIPTION("Virtual channel remapping sink");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(FALSE);
+PA_MODULE_USAGE(
+ "sink_name=<name for the sink> "
+ "master=<name of sink to remap> "
+ "master_channel_map=<channel map> "
+ "format=<sample format> "
+ "channels=<number of channels> "
+ "rate=<sample rate> "
+ "channel_map=<channel map>");
+
+struct userdata {
+ pa_core *core;
+ pa_module *module;
+
+ pa_sink *sink, *master;
+ pa_sink_input *sink_input;
+
+ pa_memchunk memchunk;
+};
+
+static const char* const valid_modargs[] = {
+ "sink_name",
+ "master",
+ "master_channel_map",
+ "rate",
+ "format",
+ "channels",
+ "channel_map",
+ NULL
+};
+
+/* Called from I/O thread context */
+static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+ struct userdata *u = PA_SINK(o)->userdata;
+
+ switch (code) {
+
+ case PA_SINK_MESSAGE_GET_LATENCY: {
+ pa_usec_t usec = 0;
+
+ if (PA_MSGOBJECT(u->master)->process_msg(PA_MSGOBJECT(u->master), PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0)
+ usec = 0;
+
+ *((pa_usec_t*) data) = usec + pa_bytes_to_usec(u->memchunk.length, &u->sink->sample_spec);
+ return 0;
+ }
+ }
+
+ return pa_sink_process_msg(o, code, data, offset, chunk);
+}
+
+/* Called from main context */
+static int sink_set_state(pa_sink *s, pa_sink_state_t state) {
+ struct userdata *u;
+
+ pa_sink_assert_ref(s);
+ pa_assert_se(u = s->userdata);
+
+ if (PA_SINK_LINKED(state) && u->sink_input && PA_SINK_INPUT_LINKED(pa_sink_input_get_state(u->sink_input)))
+ pa_sink_input_cork(u->sink_input, state == PA_SINK_SUSPENDED);
+
+ return 0;
+}
+
+/* Called from I/O thread context */
+static int sink_input_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+ struct userdata *u = PA_SINK_INPUT(o)->userdata;
+
+ switch (code) {
+ case PA_SINK_INPUT_MESSAGE_GET_LATENCY:
+ *((pa_usec_t*) data) = pa_bytes_to_usec(u->memchunk.length, &u->sink_input->sample_spec);
+
+ /* Fall through, the default handler will add in the extra
+ * latency added by the resampler */
+ break;
+ }
+
+ return pa_sink_input_process_msg(o, code, data, offset, chunk);
+}
+
+/* Called from I/O thread context */
+static int sink_input_peek_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ if (!u->memchunk.memblock)
+ pa_sink_render(u->sink, length, &u->memchunk);
+
+ pa_assert(u->memchunk.memblock);
+ *chunk = u->memchunk;
+ pa_memblock_ref(chunk->memblock);
+ return 0;
+}
+
+/* Called from I/O thread context */
+static void sink_input_drop_cb(pa_sink_input *i, size_t length) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+ pa_assert(length > 0);
+
+ if (u->memchunk.memblock) {
+
+ if (length < u->memchunk.length) {
+ u->memchunk.index += length;
+ u->memchunk.length -= length;
+ return;
+ }
+
+ pa_memblock_unref(u->memchunk.memblock);
+ length -= u->memchunk.length;
+ pa_memchunk_reset(&u->memchunk);
+ }
+
+ if (length > 0)
+ pa_sink_skip(u->sink, length);
+}
+
+/* Called from I/O thread context */
+static void sink_input_detach_cb(pa_sink_input *i) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ pa_sink_detach_within_thread(u->sink);
+}
+
+/* Called from I/O thread context */
+static void sink_input_attach_cb(pa_sink_input *i) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ pa_sink_set_asyncmsgq(u->sink, i->sink->asyncmsgq);
+ pa_sink_set_rtpoll(u->sink, i->sink->rtpoll);
+
+ pa_sink_attach_within_thread(u->sink);
+}
+
+/* Called from main context */
+static void sink_input_kill_cb(pa_sink_input *i) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ pa_sink_input_unlink(u->sink_input);
+ pa_sink_input_unref(u->sink_input);
+ u->sink_input = NULL;
+
+ pa_sink_unlink(u->sink);
+ pa_sink_unref(u->sink);
+ u->sink = NULL;
+
+ pa_module_unload_request(u->module);
+}
+
+int pa__init(pa_module*m) {
+ struct userdata *u;
+ pa_sample_spec ss;
+ pa_channel_map sink_map, stream_map;
+ pa_modargs *ma;
+ char *t;
+ pa_sink *master;
+ pa_sink_input_new_data data;
+ char *default_sink_name = NULL;
+
+ pa_assert(m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log("Failed to parse module arguments.");
+ goto fail;
+ }
+
+ if (!(master = pa_namereg_get(m->core, pa_modargs_get_value(ma, "master", NULL), PA_NAMEREG_SINK, 1))) {
+ pa_log("Master sink not found");
+ goto fail;
+ }
+
+ ss = master->sample_spec;
+ sink_map = master->channel_map;
+ if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &sink_map, PA_CHANNEL_MAP_DEFAULT) < 0) {
+ pa_log("Invalid sample format specification or channel map");
+ goto fail;
+ }
+
+ stream_map = sink_map;
+ if (pa_modargs_get_channel_map(ma, "master_channel_map", &stream_map) < 0) {
+ pa_log("Invalid master hannel map");
+ goto fail;
+ }
+
+ if (stream_map.channels != ss.channels) {
+ pa_log("Number of channels doesn't match");
+ goto fail;
+ }
+
+ u = pa_xnew0(struct userdata, 1);
+ u->core = m->core;
+ u->module = m;
+ m->userdata = u;
+ u->master = master;
+ pa_memchunk_reset(&u->memchunk);
+
+ default_sink_name = pa_sprintf_malloc("%s.remapped", master->name);
+
+ /* Create sink */
+ if (!(u->sink = pa_sink_new(m->core, __FILE__, pa_modargs_get_value(ma, "sink_name", default_sink_name), 0, &ss, &sink_map))) {
+ pa_log("Failed to create sink.");
+ goto fail;
+ }
+
+ u->sink->parent.process_msg = sink_process_msg;
+ u->sink->set_state = sink_set_state;
+ u->sink->userdata = u;
+ u->sink->flags = PA_SINK_LATENCY;
+
+ pa_sink_set_module(u->sink, m);
+ pa_sink_set_description(u->sink, t = pa_sprintf_malloc("Remapped %s", master->description));
+ pa_xfree(t);
+ pa_sink_set_asyncmsgq(u->sink, master->asyncmsgq);
+ pa_sink_set_rtpoll(u->sink, master->rtpoll);
+
+ /* Create sink input */
+ pa_sink_input_new_data_init(&data);
+ data.sink = u->master;
+ data.driver = __FILE__;
+ data.name = "Remapped Stream";
+ pa_sink_input_new_data_set_sample_spec(&data, &ss);
+ pa_sink_input_new_data_set_channel_map(&data, &stream_map);
+ data.module = m;
+
+ if (!(u->sink_input = pa_sink_input_new(m->core, &data, PA_SINK_INPUT_DONT_MOVE)))
+ goto fail;
+
+ u->sink_input->parent.process_msg = sink_input_process_msg;
+ u->sink_input->peek = sink_input_peek_cb;
+ u->sink_input->drop = sink_input_drop_cb;
+ u->sink_input->kill = sink_input_kill_cb;
+ u->sink_input->attach = sink_input_attach_cb;
+ u->sink_input->detach = sink_input_detach_cb;
+ u->sink_input->userdata = u;
+
+ pa_sink_put(u->sink);
+ pa_sink_input_put(u->sink_input);
+
+ pa_modargs_free(ma);
+ pa_xfree(default_sink_name);
+
+ return 0;
+
+fail:
+ if (ma)
+ pa_modargs_free(ma);
+
+ pa__done(m);
+
+ pa_xfree(default_sink_name);
+
+ return -1;
+}
+
+void pa__done(pa_module*m) {
+ struct userdata *u;
+
+ pa_assert(m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->sink_input) {
+ pa_sink_input_unlink(u->sink_input);
+ pa_sink_input_unref(u->sink_input);
+ }
+
+ if (u->sink) {
+ pa_sink_unlink(u->sink);
+ pa_sink_unref(u->sink);
+ }
+
+ if (u->memchunk.memblock)
+ pa_memblock_unref(u->memchunk.memblock);
+
+ pa_xfree(u);
+}
diff --git a/src/modules/module-rescue-streams.c b/src/modules/module-rescue-streams.c
new file mode 100644
index 00000000..12957c9d
--- /dev/null
+++ b/src/modules/module-rescue-streams.c
@@ -0,0 +1,164 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/core.h>
+#include <pulsecore/sink-input.h>
+#include <pulsecore/source-output.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/log.h>
+#include <pulsecore/namereg.h>
+
+#include "module-rescue-streams-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering");
+PA_MODULE_DESCRIPTION("When a sink/source is removed, try to move their streams to the default sink/source");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(TRUE);
+
+static const char* const valid_modargs[] = {
+ NULL,
+};
+
+struct userdata {
+ pa_hook_slot *sink_slot, *source_slot;
+};
+
+static pa_hook_result_t sink_hook_callback(pa_core *c, pa_sink *sink, void* userdata) {
+ pa_sink_input *i;
+ pa_sink *target;
+
+ pa_assert(c);
+ pa_assert(sink);
+
+ if (!pa_idxset_size(sink->inputs)) {
+ pa_log_debug("No sink inputs to move away.");
+ return PA_HOOK_OK;
+ }
+
+ if (!(target = pa_namereg_get(c, NULL, PA_NAMEREG_SINK, 0)) || target == sink) {
+ uint32_t idx;
+
+ for (target = pa_idxset_first(c->sinks, &idx); target; target = pa_idxset_next(c->sinks, &idx))
+ if (target != sink)
+ break;
+
+ if (!target) {
+ pa_log_info("No evacuation sink found.");
+ return PA_HOOK_OK;
+ }
+ }
+
+ while ((i = pa_idxset_first(sink->inputs, NULL))) {
+ if (pa_sink_input_move_to(i, target, 1) < 0) {
+ pa_log_warn("Failed to move sink input %u \"%s\" to %s.", i->index, i->name, target->name);
+ return PA_HOOK_OK;
+ }
+
+ pa_log_info("Sucessfully moved sink input %u \"%s\" to %s.", i->index, i->name, target->name);
+ }
+
+
+ return PA_HOOK_OK;
+}
+
+static pa_hook_result_t source_hook_callback(pa_core *c, pa_source *source, void* userdata) {
+ pa_source_output *o;
+ pa_source *target;
+
+ pa_assert(c);
+ pa_assert(source);
+
+ if (!pa_idxset_size(source->outputs)) {
+ pa_log_debug("No source outputs to move away.");
+ return PA_HOOK_OK;
+ }
+
+ if (!(target = pa_namereg_get(c, NULL, PA_NAMEREG_SOURCE, 0)) || target == source) {
+ uint32_t idx;
+
+ for (target = pa_idxset_first(c->sources, &idx); target; target = pa_idxset_next(c->sources, &idx))
+ if (target != source && !target->monitor_of == !source->monitor_of)
+ break;
+
+ if (!target) {
+ pa_log_info("No evacuation source found.");
+ return PA_HOOK_OK;
+ }
+ }
+
+ pa_assert(target != source);
+
+ while ((o = pa_idxset_first(source->outputs, NULL))) {
+ if (pa_source_output_move_to(o, target) < 0) {
+ pa_log_warn("Failed to move source output %u \"%s\" to %s.", o->index, o->name, target->name);
+ return PA_HOOK_OK;
+ }
+
+ pa_log_info("Sucessfully moved source output %u \"%s\" to %s.", o->index, o->name, target->name);
+ }
+
+
+ return PA_HOOK_OK;
+}
+
+int pa__init(pa_module*m) {
+ pa_modargs *ma = NULL;
+ struct userdata *u;
+
+ pa_assert(m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log("Failed to parse module arguments");
+ return -1;
+ }
+
+ m->userdata = u = pa_xnew(struct userdata, 1);
+ u->sink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], (pa_hook_cb_t) sink_hook_callback, NULL);
+ u->source_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], (pa_hook_cb_t) source_hook_callback, NULL);
+
+ pa_modargs_free(ma);
+ return 0;
+}
+
+void pa__done(pa_module*m) {
+ struct userdata *u;
+
+ pa_assert(m);
+
+ if (!m->userdata)
+ return;
+
+ u = m->userdata;
+ if (u->sink_slot)
+ pa_hook_slot_free(u->sink_slot);
+ if (u->source_slot)
+ pa_hook_slot_free(u->source_slot);
+
+ pa_xfree(u);
+}
diff --git a/src/modules/module-sine.c b/src/modules/module-sine.c
new file mode 100644
index 00000000..41d9a51c
--- /dev/null
+++ b/src/modules/module-sine.c
@@ -0,0 +1,206 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/sink-input.h>
+#include <pulsecore/module.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/namereg.h>
+#include <pulsecore/log.h>
+#include <pulsecore/core-util.h>
+
+#include "module-sine-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering");
+PA_MODULE_DESCRIPTION("Sine wave generator");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(FALSE);
+PA_MODULE_USAGE("sink=<sink to connect to> frequency=<frequency in Hz>");
+
+struct userdata {
+ pa_core *core;
+ pa_module *module;
+ pa_sink_input *sink_input;
+ pa_memblock *memblock;
+ size_t peek_index;
+};
+
+static const char* const valid_modargs[] = {
+ "sink",
+ "frequency",
+ NULL,
+};
+
+static int sink_input_peek_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) {
+ struct userdata *u;
+
+ pa_assert(i);
+ u = i->userdata;
+ pa_assert(u);
+ pa_assert(chunk);
+
+ chunk->memblock = pa_memblock_ref(u->memblock);
+ chunk->index = u->peek_index;
+ chunk->length = pa_memblock_get_length(u->memblock) - u->peek_index;
+
+ return 0;
+}
+
+static void sink_input_drop_cb(pa_sink_input *i, size_t length) {
+ struct userdata *u;
+ size_t l;
+
+ pa_assert(i);
+ u = i->userdata;
+ pa_assert(u);
+ pa_assert(length > 0);
+
+ u->peek_index += length;
+
+ l = pa_memblock_get_length(u->memblock);
+
+ while (u->peek_index >= l)
+ u->peek_index -= l;
+}
+
+static void sink_input_kill_cb(pa_sink_input *i) {
+ struct userdata *u;
+
+ pa_assert(i);
+ u = i->userdata;
+ pa_assert(u);
+
+ pa_sink_input_unlink(u->sink_input);
+ pa_sink_input_unref(u->sink_input);
+ u->sink_input = NULL;
+
+ pa_module_unload_request(u->module);
+}
+
+static void calc_sine(float *f, size_t l, float freq) {
+ size_t i;
+
+ l /= sizeof(float);
+
+ for (i = 0; i < l; i++)
+ f[i] = (float) sin((double) i/l*M_PI*2*freq)/2;
+}
+
+int pa__init(pa_module*m) {
+ pa_modargs *ma = NULL;
+ struct userdata *u;
+ pa_sink *sink;
+ pa_sample_spec ss;
+ uint32_t frequency;
+ char t[256];
+ void *p;
+ pa_sink_input_new_data data;
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log("Failed to parse module arguments");
+ goto fail;
+ }
+
+ m->userdata = u = pa_xnew0(struct userdata, 1);
+ u->core = m->core;
+ u->module = m;
+ u->sink_input = NULL;
+ u->memblock = NULL;
+ u->peek_index = 0;
+
+ if (!(sink = pa_namereg_get(m->core, pa_modargs_get_value(ma, "sink", NULL), PA_NAMEREG_SINK, 1))) {
+ pa_log("No such sink.");
+ goto fail;
+ }
+
+ ss.format = PA_SAMPLE_FLOAT32;
+ ss.rate = sink->sample_spec.rate;
+ ss.channels = 1;
+
+ frequency = 440;
+ if (pa_modargs_get_value_u32(ma, "frequency", &frequency) < 0 || frequency < 1 || frequency > ss.rate/2) {
+ pa_log("Invalid frequency specification");
+ goto fail;
+ }
+
+ u->memblock = pa_memblock_new(m->core->mempool, pa_bytes_per_second(&ss));
+ p = pa_memblock_acquire(u->memblock);
+ calc_sine(p, pa_memblock_get_length(u->memblock), frequency);
+ pa_memblock_release(u->memblock);
+
+ pa_snprintf(t, sizeof(t), "Sine Generator at %u Hz", frequency);
+
+ pa_sink_input_new_data_init(&data);
+ data.sink = sink;
+ data.driver = __FILE__;
+ data.name = t;
+ pa_sink_input_new_data_set_sample_spec(&data, &ss);
+ data.module = m;
+
+ if (!(u->sink_input = pa_sink_input_new(m->core, &data, 0)))
+ goto fail;
+
+ u->sink_input->peek = sink_input_peek_cb;
+ u->sink_input->drop = sink_input_drop_cb;
+ u->sink_input->kill = sink_input_kill_cb;
+ u->sink_input->userdata = u;
+
+ pa_sink_input_put(u->sink_input);
+
+ pa_modargs_free(ma);
+ return 0;
+
+fail:
+ if (ma)
+ pa_modargs_free(ma);
+
+ pa__done(m);
+ return -1;
+}
+
+void pa__done(pa_module*m) {
+ struct userdata *u;
+
+ pa_assert(m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->sink_input) {
+ pa_sink_input_unlink(u->sink_input);
+ pa_sink_input_unref(u->sink_input);
+ }
+
+ if (u->memblock)
+ pa_memblock_unref(u->memblock);
+
+ pa_xfree(u);
+}
diff --git a/src/modules/module-solaris.c b/src/modules/module-solaris.c
new file mode 100644
index 00000000..4a5c88e4
--- /dev/null
+++ b/src/modules/module-solaris.c
@@ -0,0 +1,766 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <signal.h>
+#include <stropts.h>
+#include <sys/conf.h>
+#include <sys/audio.h>
+
+#include <pulse/error.h>
+#include <pulse/mainloop-signal.h>
+#include <pulse/xmalloc.h>
+#include <pulse/timeval.h>
+
+#include <pulsecore/iochannel.h>
+#include <pulsecore/sink.h>
+#include <pulsecore/source.h>
+#include <pulsecore/module.h>
+#include <pulsecore/sample-util.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/log.h>
+#include <pulsecore/core-error.h>
+#include <pulsecore/thread-mq.h>
+#include <pulsecore/rtpoll.h>
+#include <pulsecore/thread.h>
+
+#include "module-solaris-symdef.h"
+
+PA_MODULE_AUTHOR("Pierre Ossman")
+PA_MODULE_DESCRIPTION("Solaris Sink/Source")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+PA_MODULE_USAGE(
+ "sink_name=<name for the sink> "
+ "source_name=<name for the source> "
+ "device=<OSS device> record=<enable source?> "
+ "playback=<enable sink?> "
+ "format=<sample format> "
+ "channels=<number of channels> "
+ "rate=<sample rate> "
+ "buffer_size=<record buffer size> "
+ "channel_map=<channel map>")
+
+struct userdata {
+ pa_core *core;
+ pa_sink *sink;
+ pa_source *source;
+
+ pa_thread *thread;
+ pa_thread_mq thread_mq;
+ pa_rtpoll *rtpoll;
+
+ pa_signal_event *sig;
+
+ pa_memchunk memchunk;
+
+ unsigned int page_size;
+
+ uint32_t frame_size;
+ uint32_t buffer_size;
+ unsigned int written_bytes, read_bytes;
+
+ int fd;
+ pa_rtpoll_item *rtpoll_item;
+ pa_module *module;
+};
+
+static const char* const valid_modargs[] = {
+ "sink_name",
+ "source_name",
+ "device",
+ "record",
+ "playback",
+ "buffer_size",
+ "format",
+ "rate",
+ "channels",
+ "channel_map",
+ NULL
+};
+
+#define DEFAULT_SINK_NAME "solaris_output"
+#define DEFAULT_SOURCE_NAME "solaris_input"
+#define DEFAULT_DEVICE "/dev/audio"
+
+static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+ struct userdata *u = PA_SINK(o)->userdata;
+ int err;
+ audio_info_t info;
+
+ switch (code) {
+ case PA_SINK_MESSAGE_GET_LATENCY: {
+ pa_usec_t r = 0;
+
+ if (u->fd >= 0) {
+
+ err = ioctl(u->fd, AUDIO_GETINFO, &info);
+ pa_assert(err >= 0);
+
+ r += pa_bytes_to_usec(u->written_bytes, &PA_SINK(o)->sample_spec);
+ r -= pa_bytes_to_usec(info.play.samples * u->frame_size, &PA_SINK(o)->sample_spec);
+
+ if (u->memchunk.memblock)
+ r += pa_bytes_to_usec(u->memchunk.length, &PA_SINK(o)->sample_spec);
+ }
+
+ *((pa_usec_t*) data) = r;
+
+ return 0;
+ }
+
+ case PA_SINK_MESSAGE_SET_VOLUME:
+ if (u->fd >= 0) {
+ AUDIO_INITINFO(&info);
+
+ info.play.gain = pa_cvolume_avg((pa_cvolume*)data) * AUDIO_MAX_GAIN / PA_VOLUME_NORM;
+ assert(info.play.gain <= AUDIO_MAX_GAIN);
+
+ if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0) {
+ if (errno == EINVAL)
+ pa_log("AUDIO_SETINFO: Unsupported volume.");
+ else
+ pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno));
+ } else {
+ return 0;
+ }
+ }
+ break;
+
+ case PA_SINK_MESSAGE_GET_VOLUME:
+ if (u->fd >= 0) {
+ err = ioctl(u->fd, AUDIO_GETINFO, &info);
+ assert(err >= 0);
+
+ pa_cvolume_set((pa_cvolume*) data, ((pa_cvolume*) data)->channels,
+ info.play.gain * PA_VOLUME_NORM / AUDIO_MAX_GAIN);
+
+ return 0;
+ }
+ break;
+
+ case PA_SINK_MESSAGE_SET_MUTE:
+ if (u->fd >= 0) {
+ AUDIO_INITINFO(&info);
+
+ info.output_muted = !!PA_PTR_TO_UINT(data);
+
+ if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0)
+ pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno));
+ else
+ return 0;
+ }
+ break;
+
+ case PA_SINK_MESSAGE_GET_MUTE:
+ if (u->fd >= 0) {
+ err = ioctl(u->fd, AUDIO_GETINFO, &info);
+ pa_assert(err >= 0);
+
+ *(int*)data = !!info.output_muted;
+
+ return 0;
+ }
+ break;
+ }
+
+ return pa_sink_process_msg(o, code, data, offset, chunk);
+}
+
+static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+ struct userdata *u = PA_SOURCE(o)->userdata;
+ int err;
+ audio_info_t info;
+
+ switch (code) {
+ case PA_SOURCE_MESSAGE_GET_LATENCY: {
+ pa_usec_t r = 0;
+
+ if (u->fd) {
+ err = ioctl(u->fd, AUDIO_GETINFO, &info);
+ pa_assert(err >= 0);
+
+ r += pa_bytes_to_usec(info.record.samples * u->frame_size, &PA_SOURCE(o)->sample_spec);
+ r -= pa_bytes_to_usec(u->read_bytes, &PA_SOURCE(o)->sample_spec);
+ }
+
+ *((pa_usec_t*) data) = r;
+
+ return 0;
+ }
+
+ case PA_SOURCE_MESSAGE_SET_VOLUME:
+ if (u->fd >= 0) {
+ AUDIO_INITINFO(&info);
+
+ info.record.gain = pa_cvolume_avg((pa_cvolume*) data) * AUDIO_MAX_GAIN / PA_VOLUME_NORM;
+ assert(info.record.gain <= AUDIO_MAX_GAIN);
+
+ if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0) {
+ if (errno == EINVAL)
+ pa_log("AUDIO_SETINFO: Unsupported volume.");
+ else
+ pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno));
+ } else {
+ return 0;
+ }
+ }
+ break;
+
+ case PA_SOURCE_MESSAGE_GET_VOLUME:
+ if (u->fd >= 0) {
+ err = ioctl(u->fd, AUDIO_GETINFO, &info);
+ pa_assert(err >= 0);
+
+ pa_cvolume_set((pa_cvolume*) data, ((pa_cvolume*) data)->channels,
+ info.record.gain * PA_VOLUME_NORM / AUDIO_MAX_GAIN);
+
+ return 0;
+ }
+ break;
+ }
+
+ return pa_source_process_msg(o, code, data, offset, chunk);
+}
+
+static void clear_underflow(struct userdata *u)
+{
+ audio_info_t info;
+
+ AUDIO_INITINFO(&info);
+
+ info.play.error = 0;
+
+ if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0)
+ pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno));
+}
+
+static void clear_overflow(struct userdata *u)
+{
+ audio_info_t info;
+
+ AUDIO_INITINFO(&info);
+
+ info.record.error = 0;
+
+ if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0)
+ pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno));
+}
+
+static void thread_func(void *userdata) {
+ struct userdata *u = userdata;
+ unsigned short revents = 0;
+ int ret;
+
+ pa_assert(u);
+
+ pa_log_debug("Thread starting up");
+
+ if (u->core->high_priority)
+ pa_make_realtime();
+
+ pa_thread_mq_install(&u->thread_mq);
+ pa_rtpoll_install(u->rtpoll);
+
+ for (;;) {
+ /* Render some data and write it to the dsp */
+
+ if (u->sink && PA_SINK_OPENED(u->sink->thread_info.state)) {
+ audio_info_t info;
+ int err;
+ size_t len;
+
+ err = ioctl(u->fd, AUDIO_GETINFO, &info);
+ pa_assert(err >= 0);
+
+ /*
+ * Since we cannot modify the size of the output buffer we fake it
+ * by not filling it more than u->buffer_size.
+ */
+ len = u->buffer_size;
+ len -= u->written_bytes - (info.play.samples * u->frame_size);
+
+ /* The sample counter can sometimes go backwards :( */
+ if (len > u->buffer_size)
+ len = 0;
+
+ if (info.play.error) {
+ pa_log_debug("Solaris buffer underflow!");
+ clear_underflow(u);
+ }
+
+ len -= len % u->frame_size;
+
+ while (len) {
+ void *p;
+ ssize_t r;
+
+ if (!u->memchunk.length)
+ pa_sink_render(u->sink, len, &u->memchunk);
+
+ pa_assert(u->memchunk.length);
+
+ p = pa_memblock_acquire(u->memchunk.memblock);
+ r = pa_write(u->fd, (uint8_t*) p + u->memchunk.index, u->memchunk.length, NULL);
+ pa_memblock_release(u->memchunk.memblock);
+
+ if (r < 0) {
+ if (errno == EINTR)
+ continue;
+ else if (errno != EAGAIN) {
+ pa_log("Failed to read data from DSP: %s", pa_cstrerror(errno));
+ goto fail;
+ }
+ } else {
+ pa_assert(r % u->frame_size == 0);
+
+ u->memchunk.index += r;
+ u->memchunk.length -= r;
+
+ if (u->memchunk.length <= 0) {
+ pa_memblock_unref(u->memchunk.memblock);
+ pa_memchunk_reset(&u->memchunk);
+ }
+
+ len -= r;
+ u->written_bytes += r;
+ }
+ }
+ }
+
+ /* Try to read some data and pass it on to the source driver */
+
+ if (u->source && PA_SOURCE_OPENED(u->source->thread_info.state) && ((revents & POLLIN))) {
+ pa_memchunk memchunk;
+ int err;
+ size_t l;
+ void *p;
+ ssize_t r;
+ audio_info_t info;
+
+ err = ioctl(u->fd, AUDIO_GETINFO, &info);
+ pa_assert(err >= 0);
+
+ if (info.record.error) {
+ pa_log_debug("Solaris buffer overflow!");
+ clear_overflow(u);
+ }
+
+ err = ioctl(u->fd, I_NREAD, &l);
+ pa_assert(err >= 0);
+
+ if (l > 0) {
+ /* This is to make sure it fits in the memory pool. Also, a page
+ should be the most efficient transfer size. */
+ if (l > u->page_size)
+ l = u->page_size;
+
+ memchunk.memblock = pa_memblock_new(u->core->mempool, l);
+ pa_assert(memchunk.memblock);
+
+ p = pa_memblock_acquire(memchunk.memblock);
+ r = pa_read(u->fd, p, l, NULL);
+ pa_memblock_release(memchunk.memblock);
+
+ if (r < 0) {
+ pa_memblock_unref(memchunk.memblock);
+ if (errno != EAGAIN) {
+ pa_log("Failed to read data from DSP: %s", pa_cstrerror(errno));
+ goto fail;
+ }
+ } else {
+ memchunk.index = 0;
+ memchunk.length = r;
+
+ pa_source_post(u->source, &memchunk);
+ pa_memblock_unref(memchunk.memblock);
+
+ u->read_bytes += r;
+
+ revents &= ~POLLIN;
+ }
+ }
+ }
+
+ if (u->fd >= 0) {
+ struct pollfd *pollfd;
+
+ pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
+ pollfd->events =
+ ((u->source && PA_SOURCE_OPENED(u->source->thread_info.state)) ? POLLIN : 0);
+ }
+
+ /* Hmm, nothing to do. Let's sleep */
+ if ((ret = pa_rtpoll_run(u->rtpoll, 1)) < 0)
+ goto fail;
+
+ if (ret == 0)
+ goto finish;
+
+ if (u->fd >= 0) {
+ struct pollfd *pollfd;
+
+ pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
+
+ if (pollfd->revents & ~(POLLOUT|POLLIN)) {
+ pa_log("DSP shutdown.");
+ goto fail;
+ }
+
+ revents = pollfd->revents;
+ } else
+ revents = 0;
+ }
+
+fail:
+ /* We have to continue processing messages until we receive the
+ * SHUTDOWN message */
+ pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL);
+ pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN);
+
+finish:
+ pa_log_debug("Thread shutting down");
+}
+
+static void sig_callback(pa_mainloop_api *api, pa_signal_event*e, int sig, void *userdata) {
+ struct userdata *u = userdata;
+
+ assert(u);
+
+ if (u->sink) {
+ pa_sink_get_volume(u->sink);
+ pa_sink_get_mute(u->sink);
+ }
+
+ if (u->source)
+ pa_source_get_volume(u->source);
+}
+
+static int pa_solaris_auto_format(int fd, int mode, pa_sample_spec *ss) {
+ audio_info_t info;
+
+ AUDIO_INITINFO(&info);
+
+ if (mode != O_RDONLY) {
+ info.play.sample_rate = ss->rate;
+ info.play.channels = ss->channels;
+ switch (ss->format) {
+ case PA_SAMPLE_U8:
+ info.play.precision = 8;
+ info.play.encoding = AUDIO_ENCODING_LINEAR;
+ break;
+ case PA_SAMPLE_ALAW:
+ info.play.precision = 8;
+ info.play.encoding = AUDIO_ENCODING_ALAW;
+ break;
+ case PA_SAMPLE_ULAW:
+ info.play.precision = 8;
+ info.play.encoding = AUDIO_ENCODING_ULAW;
+ break;
+ case PA_SAMPLE_S16NE:
+ info.play.precision = 16;
+ info.play.encoding = AUDIO_ENCODING_LINEAR;
+ break;
+ default:
+ return -1;
+ }
+ }
+
+ if (mode != O_WRONLY) {
+ info.record.sample_rate = ss->rate;
+ info.record.channels = ss->channels;
+ switch (ss->format) {
+ case PA_SAMPLE_U8:
+ info.record.precision = 8;
+ info.record.encoding = AUDIO_ENCODING_LINEAR;
+ break;
+ case PA_SAMPLE_ALAW:
+ info.record.precision = 8;
+ info.record.encoding = AUDIO_ENCODING_ALAW;
+ break;
+ case PA_SAMPLE_ULAW:
+ info.record.precision = 8;
+ info.record.encoding = AUDIO_ENCODING_ULAW;
+ break;
+ case PA_SAMPLE_S16NE:
+ info.record.precision = 16;
+ info.record.encoding = AUDIO_ENCODING_LINEAR;
+ break;
+ default:
+ return -1;
+ }
+ }
+
+ if (ioctl(fd, AUDIO_SETINFO, &info) < 0) {
+ if (errno == EINVAL)
+ pa_log("AUDIO_SETINFO: Unsupported sample format.");
+ else
+ pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+static int pa_solaris_set_buffer(int fd, int buffer_size) {
+ audio_info_t info;
+
+ AUDIO_INITINFO(&info);
+
+ info.play.buffer_size = buffer_size;
+ info.record.buffer_size = buffer_size;
+
+ if (ioctl(fd, AUDIO_SETINFO, &info) < 0) {
+ if (errno == EINVAL)
+ pa_log("AUDIO_SETINFO: Unsupported buffer size.");
+ else
+ pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+int pa__init(pa_module *m) {
+ struct userdata *u = NULL;
+ const char *p;
+ int fd = -1;
+ int buffer_size;
+ int mode;
+ int record = 1, playback = 1;
+ pa_sample_spec ss;
+ pa_channel_map map;
+ pa_modargs *ma = NULL;
+ char *t;
+ struct pollfd *pollfd;
+
+ pa_assert(m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log("failed to parse module arguments.");
+ goto fail;
+ }
+
+ if (pa_modargs_get_value_boolean(ma, "record", &record) < 0 || pa_modargs_get_value_boolean(ma, "playback", &playback) < 0) {
+ pa_log("record= and playback= expect numeric argument.");
+ goto fail;
+ }
+
+ if (!playback && !record) {
+ pa_log("neither playback nor record enabled for device.");
+ goto fail;
+ }
+
+ mode = (playback&&record) ? O_RDWR : (playback ? O_WRONLY : (record ? O_RDONLY : 0));
+
+ buffer_size = 16384;
+ if (pa_modargs_get_value_s32(ma, "buffer_size", &buffer_size) < 0) {
+ pa_log("failed to parse buffer size argument");
+ goto fail;
+ }
+
+ ss = m->core->default_sample_spec;
+ if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) {
+ pa_log("failed to parse sample specification");
+ goto fail;
+ }
+
+ if ((fd = open(p = pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), mode | O_NONBLOCK)) < 0)
+ goto fail;
+
+ pa_log_info("device opened in %s mode.", mode == O_WRONLY ? "O_WRONLY" : (mode == O_RDONLY ? "O_RDONLY" : "O_RDWR"));
+
+ if (pa_solaris_auto_format(fd, mode, &ss) < 0)
+ goto fail;
+
+ if (pa_solaris_set_buffer(fd, buffer_size) < 0)
+ goto fail;
+
+ u = pa_xmalloc(sizeof(struct userdata));
+ u->core = m->core;
+
+ u->fd = fd;
+
+ pa_memchunk_reset(&u->memchunk);
+
+ /* We use this to get a reasonable chunk size */
+ u->page_size = PA_PAGE_SIZE;
+
+ u->frame_size = pa_frame_size(&ss);
+ u->buffer_size = buffer_size;
+
+ u->written_bytes = 0;
+ u->read_bytes = 0;
+
+ u->module = m;
+ m->userdata = u;
+
+ pa_thread_mq_init(&u->thread_mq, m->core->mainloop);
+
+ u->rtpoll = pa_rtpoll_new();
+ pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq);
+
+ pa_rtpoll_set_timer_periodic(u->rtpoll, pa_bytes_to_usec(u->buffer_size / 10, &ss));
+
+ u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1);
+ pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
+ pollfd->fd = fd;
+ pollfd->events = 0;
+ pollfd->revents = 0;
+
+ if (mode != O_WRONLY) {
+ u->source = pa_source_new(m->core, __FILE__, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &ss, &map);
+ pa_assert(u->source);
+
+ u->source->userdata = u;
+ u->source->parent.process_msg = source_process_msg;
+
+ pa_source_set_module(u->source, m);
+ pa_source_set_description(u->source, t = pa_sprintf_malloc("Solaris PCM on '%s'", p));
+ pa_xfree(t);
+ pa_source_set_asyncmsgq(u->source, u->thread_mq.inq);
+ pa_source_set_rtpoll(u->source, u->rtpoll);
+
+ u->source->flags = PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY|PA_SOURCE_HW_VOLUME_CTRL;
+ u->source->refresh_volume = 1;
+ } else
+ u->source = NULL;
+
+ if (mode != O_RDONLY) {
+ u->sink = pa_sink_new(m->core, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, &map);
+ pa_assert(u->sink);
+
+ u->sink->userdata = u;
+ u->sink->parent.process_msg = sink_process_msg;
+
+ pa_sink_set_module(u->sink, m);
+ pa_sink_set_description(u->sink, t = pa_sprintf_malloc("Solaris PCM on '%s'", p));
+ pa_xfree(t);
+ pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq);
+ pa_sink_set_rtpoll(u->sink, u->rtpoll);
+
+ u->sink->flags = PA_SINK_HARDWARE|PA_SINK_LATENCY|PA_SINK_HW_VOLUME_CTRL;
+ u->sink->refresh_volume = 1;
+ u->sink->refresh_mute = 1;
+ } else
+ u->sink = NULL;
+
+ pa_assert(u->source || u->sink);
+
+ u->sig = pa_signal_new(SIGPOLL, sig_callback, u);
+ pa_assert(u->sig);
+ ioctl(u->fd, I_SETSIG, S_MSG);
+
+ if (!(u->thread = pa_thread_new(thread_func, u))) {
+ pa_log("Failed to create thread.");
+ goto fail;
+ }
+
+ /* Read mixer settings */
+ if (u->source)
+ pa_asyncmsgq_send(u->thread_mq.inq, PA_MSGOBJECT(u->source), PA_SOURCE_MESSAGE_GET_VOLUME, &u->source->volume, 0, NULL);
+ if (u->sink) {
+ pa_asyncmsgq_send(u->thread_mq.inq, PA_MSGOBJECT(u->sink), PA_SINK_MESSAGE_GET_VOLUME, &u->sink->volume, 0, NULL);
+ pa_asyncmsgq_send(u->thread_mq.inq, PA_MSGOBJECT(u->sink), PA_SINK_MESSAGE_GET_MUTE, &u->sink->muted, 0, NULL);
+ }
+
+ if (u->sink)
+ pa_sink_put(u->sink);
+ if (u->source)
+ pa_source_put(u->source);
+
+ pa_modargs_free(ma);
+
+ return 0;
+
+fail:
+ if (u)
+ pa__done(m);
+ else if (fd >= 0)
+ close(fd);
+
+ if (ma)
+ pa_modargs_free(ma);
+
+ return -1;
+}
+
+void pa__done(pa_module *m) {
+ struct userdata *u;
+
+ pa_assert(m);
+
+ if (!(u = m->userdata))
+ return;
+
+ ioctl(u->fd, I_SETSIG, 0);
+ pa_signal_free(u->sig);
+
+ if (u->sink)
+ pa_sink_unlink(u->sink);
+
+ if (u->source)
+ pa_source_unlink(u->source);
+
+ if (u->thread) {
+ pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL);
+ pa_thread_free(u->thread);
+ }
+
+ pa_thread_mq_done(&u->thread_mq);
+
+ if (u->sink)
+ pa_sink_unref(u->sink);
+
+ if (u->source)
+ pa_source_unref(u->source);
+
+ if (u->memchunk.memblock)
+ pa_memblock_unref(u->memchunk.memblock);
+
+ if (u->rtpoll_item)
+ pa_rtpoll_item_free(u->rtpoll_item);
+
+ if (u->rtpoll)
+ pa_rtpoll_free(u->rtpoll);
+
+ if (u->fd >= 0)
+ close(u->fd);
+
+ pa_xfree(u);
+}
diff --git a/src/modules/module-suspend-on-idle.c b/src/modules/module-suspend-on-idle.c
new file mode 100644
index 00000000..4c260d76
--- /dev/null
+++ b/src/modules/module-suspend-on-idle.c
@@ -0,0 +1,446 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulse/xmalloc.h>
+#include <pulse/timeval.h>
+
+#include <pulsecore/core.h>
+#include <pulsecore/sink-input.h>
+#include <pulsecore/source-output.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/log.h>
+#include <pulsecore/namereg.h>
+
+#include "module-suspend-on-idle-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering");
+PA_MODULE_DESCRIPTION("When a sink/source is idle for too long, suspend it");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(TRUE);
+
+static const char* const valid_modargs[] = {
+ "timeout",
+ NULL,
+};
+
+struct userdata {
+ pa_core *core;
+ pa_usec_t timeout;
+ pa_hashmap *device_infos;
+ pa_hook_slot
+ *sink_new_slot,
+ *source_new_slot,
+ *sink_unlink_slot,
+ *source_unlink_slot,
+ *sink_state_changed_slot,
+ *source_state_changed_slot;
+
+ pa_hook_slot
+ *sink_input_new_slot,
+ *source_output_new_slot,
+ *sink_input_unlink_slot,
+ *source_output_unlink_slot,
+ *sink_input_move_slot,
+ *source_output_move_slot,
+ *sink_input_state_changed_slot,
+ *source_output_state_changed_slot;
+};
+
+struct device_info {
+ struct userdata *userdata;
+ pa_sink *sink;
+ pa_source *source;
+ struct timeval last_use;
+ pa_time_event *time_event;
+};
+
+static void timeout_cb(pa_mainloop_api*a, pa_time_event* e, const struct timeval *tv, void *userdata) {
+ struct device_info *d = userdata;
+
+ pa_assert(d);
+
+ d->userdata->core->mainloop->time_restart(d->time_event, NULL);
+
+ if (d->sink && pa_sink_used_by(d->sink) <= 0 && pa_sink_get_state(d->sink) != PA_SINK_SUSPENDED) {
+ pa_log_info("Sink %s idle for too long, suspending ...", d->sink->name);
+ pa_sink_suspend(d->sink, TRUE);
+ }
+
+ if (d->source && pa_source_used_by(d->source) <= 0 && pa_source_get_state(d->source) != PA_SOURCE_SUSPENDED) {
+ pa_log_info("Source %s idle for too long, suspending ...", d->source->name);
+ pa_source_suspend(d->source, TRUE);
+ }
+}
+
+static void restart(struct device_info *d) {
+ struct timeval tv;
+ pa_assert(d);
+
+ pa_gettimeofday(&tv);
+ d->last_use = tv;
+ pa_timeval_add(&tv, d->userdata->timeout*1000000);
+ d->userdata->core->mainloop->time_restart(d->time_event, &tv);
+
+ if (d->sink)
+ pa_log_debug("Sink %s becomes idle.", d->sink->name);
+ if (d->source)
+ pa_log_debug("Source %s becomes idle.", d->source->name);
+}
+
+static void resume(struct device_info *d) {
+ pa_assert(d);
+
+ d->userdata->core->mainloop->time_restart(d->time_event, NULL);
+
+ if (d->sink) {
+ pa_sink_suspend(d->sink, FALSE);
+
+ pa_log_debug("Sink %s becomes busy.", d->sink->name);
+ }
+
+ if (d->source) {
+ pa_source_suspend(d->source, FALSE);
+
+ pa_log_debug("Source %s becomes busy.", d->source->name);
+ }
+}
+
+static pa_hook_result_t sink_input_fixate_hook_cb(pa_core *c, pa_sink_input_new_data *data, struct userdata *u) {
+ struct device_info *d;
+
+ pa_assert(c);
+ pa_assert(data);
+ pa_assert(u);
+
+ if ((d = pa_hashmap_get(u->device_infos, data->sink)))
+ resume(d);
+
+ return PA_HOOK_OK;
+}
+
+static pa_hook_result_t source_output_fixate_hook_cb(pa_core *c, pa_source_output_new_data *data, struct userdata *u) {
+ struct device_info *d;
+
+ pa_assert(c);
+ pa_assert(data);
+ pa_assert(u);
+
+ if ((d = pa_hashmap_get(u->device_infos, data->source)))
+ resume(d);
+
+ return PA_HOOK_OK;
+}
+
+static pa_hook_result_t sink_input_unlink_hook_cb(pa_core *c, pa_sink_input *s, struct userdata *u) {
+ pa_assert(c);
+ pa_sink_input_assert_ref(s);
+ pa_assert(u);
+
+ if (pa_sink_used_by(s->sink) <= 0) {
+ struct device_info *d;
+ if ((d = pa_hashmap_get(u->device_infos, s->sink)))
+ restart(d);
+ }
+
+ return PA_HOOK_OK;
+}
+
+static pa_hook_result_t source_output_unlink_hook_cb(pa_core *c, pa_source_output *s, struct userdata *u) {
+ pa_assert(c);
+ pa_source_output_assert_ref(s);
+ pa_assert(u);
+
+ if (pa_source_used_by(s->source) <= 0) {
+ struct device_info *d;
+ if ((d = pa_hashmap_get(u->device_infos, s->source)))
+ restart(d);
+ }
+
+ return PA_HOOK_OK;
+}
+
+static pa_hook_result_t sink_input_move_hook_cb(pa_core *c, pa_sink_input_move_hook_data *data, struct userdata *u) {
+ struct device_info *d;
+
+ pa_assert(c);
+ pa_assert(data);
+ pa_assert(u);
+
+ if ((d = pa_hashmap_get(u->device_infos, data->destination)))
+ resume(d);
+
+ if (pa_sink_used_by(data->sink_input->sink) <= 1)
+ if ((d = pa_hashmap_get(u->device_infos, data->sink_input->sink)))
+ restart(d);
+
+ return PA_HOOK_OK;
+}
+
+static pa_hook_result_t source_output_move_hook_cb(pa_core *c, pa_source_output_move_hook_data *data, struct userdata *u) {
+ struct device_info *d;
+
+ pa_assert(c);
+ pa_assert(data);
+ pa_assert(u);
+
+ if ((d = pa_hashmap_get(u->device_infos, data->destination)))
+ resume(d);
+
+ if (pa_source_used_by(data->source_output->source) <= 1)
+ if ((d = pa_hashmap_get(u->device_infos, data->source_output->source)))
+ restart(d);
+
+ return PA_HOOK_OK;
+}
+
+static pa_hook_result_t sink_input_state_changed_hook_cb(pa_core *c, pa_sink_input *s, struct userdata *u) {
+ struct device_info *d;
+ pa_sink_input_state_t state;
+ pa_assert(c);
+ pa_sink_input_assert_ref(s);
+ pa_assert(u);
+
+ state = pa_sink_input_get_state(s);
+ if (state == PA_SINK_INPUT_RUNNING || state == PA_SINK_INPUT_DRAINED)
+ if ((d = pa_hashmap_get(u->device_infos, s->sink)))
+ resume(d);
+
+ return PA_HOOK_OK;
+}
+
+static pa_hook_result_t source_output_state_changed_hook_cb(pa_core *c, pa_source_output *s, struct userdata *u) {
+ struct device_info *d;
+ pa_source_output_state_t state;
+ pa_assert(c);
+ pa_source_output_assert_ref(s);
+ pa_assert(u);
+
+ state = pa_source_output_get_state(s);
+ if (state == PA_SOURCE_OUTPUT_RUNNING)
+ if ((d = pa_hashmap_get(u->device_infos, s->source)))
+ resume(d);
+
+ return PA_HOOK_OK;
+}
+
+static pa_hook_result_t device_new_hook_cb(pa_core *c, pa_object *o, struct userdata *u) {
+ struct device_info *d;
+ pa_source *source;
+ pa_sink *sink;
+
+ pa_assert(c);
+ pa_object_assert_ref(o);
+ pa_assert(u);
+
+ source = pa_source_isinstance(o) ? PA_SOURCE(o) : NULL;
+ sink = pa_sink_isinstance(o) ? PA_SINK(o) : NULL;
+
+ pa_assert(source || sink);
+
+ d = pa_xnew(struct device_info, 1);
+ d->userdata = u;
+ d->source = source ? pa_source_ref(source) : NULL;
+ d->sink = sink ? pa_sink_ref(sink) : NULL;
+ d->time_event = c->mainloop->time_new(c->mainloop, NULL, timeout_cb, d);
+ pa_hashmap_put(u->device_infos, o, d);
+
+ if ((d->sink && pa_sink_used_by(d->sink) <= 0) ||
+ (d->source && pa_source_used_by(d->source) <= 0))
+ restart(d);
+
+ return PA_HOOK_OK;
+}
+
+static void device_info_free(struct device_info *d) {
+ pa_assert(d);
+
+ if (d->source)
+ pa_source_unref(d->source);
+ if (d->sink)
+ pa_sink_unref(d->sink);
+
+ d->userdata->core->mainloop->time_free(d->time_event);
+
+ pa_xfree(d);
+}
+
+static pa_hook_result_t device_unlink_hook_cb(pa_core *c, pa_object *o, struct userdata *u) {
+ struct device_info *d;
+
+ pa_assert(c);
+ pa_object_assert_ref(o);
+ pa_assert(u);
+
+ if ((d = pa_hashmap_remove(u->device_infos, o)))
+ device_info_free(d);
+
+ return PA_HOOK_OK;
+}
+
+static pa_hook_result_t device_state_changed_hook_cb(pa_core *c, pa_object *o, struct userdata *u) {
+ struct device_info *d;
+
+ pa_assert(c);
+ pa_object_assert_ref(o);
+ pa_assert(u);
+
+ if (!(d = pa_hashmap_get(u->device_infos, o)))
+ return PA_HOOK_OK;
+
+ if (pa_sink_isinstance(o)) {
+ pa_sink *s = PA_SINK(o);
+ pa_sink_state_t state = pa_sink_get_state(s);
+
+ if (pa_sink_used_by(s) <= 0) {
+
+ if (PA_SINK_OPENED(state))
+ restart(d);
+
+ }
+
+ } else if (pa_source_isinstance(o)) {
+ pa_source *s = PA_SOURCE(o);
+ pa_source_state_t state = pa_source_get_state(s);
+
+ if (pa_source_used_by(s) <= 0) {
+
+ if (PA_SOURCE_OPENED(state))
+ restart(d);
+ }
+ }
+
+ return PA_HOOK_OK;
+}
+
+int pa__init(pa_module*m) {
+ pa_modargs *ma = NULL;
+ struct userdata *u;
+ uint32_t timeout = 1;
+ uint32_t idx;
+ pa_sink *sink;
+ pa_source *source;
+
+ pa_assert(m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log("Failed to parse module arguments.");
+ goto fail;
+ }
+
+ if (pa_modargs_get_value_u32(ma, "timeout", &timeout) < 0) {
+ pa_log("Failed to parse timeout value.");
+ goto fail;
+ }
+
+ m->userdata = u = pa_xnew(struct userdata, 1);
+ u->core = m->core;
+ u->timeout = timeout;
+ u->device_infos = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
+
+ for (sink = pa_idxset_first(m->core->sinks, &idx); sink; sink = pa_idxset_next(m->core->sinks, &idx))
+ device_new_hook_cb(m->core, PA_OBJECT(sink), u);
+
+ for (source = pa_idxset_first(m->core->sources, &idx); source; source = pa_idxset_next(m->core->sources, &idx))
+ device_new_hook_cb(m->core, PA_OBJECT(source), u);
+
+ u->sink_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_NEW_POST], (pa_hook_cb_t) device_new_hook_cb, u);
+ u->source_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_NEW_POST], (pa_hook_cb_t) device_new_hook_cb, u);
+ u->sink_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK_POST], (pa_hook_cb_t) device_unlink_hook_cb, u);
+ u->source_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK_POST], (pa_hook_cb_t) device_unlink_hook_cb, u);
+ u->sink_state_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_STATE_CHANGED], (pa_hook_cb_t) device_state_changed_hook_cb, u);
+ u->source_state_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_STATE_CHANGED], (pa_hook_cb_t) device_state_changed_hook_cb, u);
+
+ u->sink_input_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_FIXATE], (pa_hook_cb_t) sink_input_fixate_hook_cb, u);
+ u->source_output_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_FIXATE], (pa_hook_cb_t) source_output_fixate_hook_cb, u);
+ u->sink_input_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_UNLINK_POST], (pa_hook_cb_t) sink_input_unlink_hook_cb, u);
+ u->source_output_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK_POST], (pa_hook_cb_t) source_output_unlink_hook_cb, u);
+ u->sink_input_move_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE], (pa_hook_cb_t) sink_input_move_hook_cb, u);
+ u->source_output_move_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE], (pa_hook_cb_t) source_output_move_hook_cb, u);
+ u->sink_input_state_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_STATE_CHANGED], (pa_hook_cb_t) sink_input_state_changed_hook_cb, u);
+ u->source_output_state_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_STATE_CHANGED], (pa_hook_cb_t) source_output_state_changed_hook_cb, u);
+
+ pa_modargs_free(ma);
+ return 0;
+
+fail:
+
+ if (ma)
+ pa_modargs_free(ma);
+
+ return -1;
+}
+
+void pa__done(pa_module*m) {
+ struct userdata *u;
+ struct device_info *d;
+
+ pa_assert(m);
+
+ if (!m->userdata)
+ return;
+
+ u = m->userdata;
+
+ if (u->sink_new_slot)
+ pa_hook_slot_free(u->sink_new_slot);
+ if (u->sink_unlink_slot)
+ pa_hook_slot_free(u->sink_unlink_slot);
+ if (u->sink_state_changed_slot)
+ pa_hook_slot_free(u->sink_state_changed_slot);
+
+ if (u->source_new_slot)
+ pa_hook_slot_free(u->source_new_slot);
+ if (u->source_unlink_slot)
+ pa_hook_slot_free(u->source_unlink_slot);
+ if (u->source_state_changed_slot)
+ pa_hook_slot_free(u->source_state_changed_slot);
+
+ if (u->sink_input_new_slot)
+ pa_hook_slot_free(u->sink_input_new_slot);
+ if (u->sink_input_unlink_slot)
+ pa_hook_slot_free(u->sink_input_unlink_slot);
+ if (u->sink_input_move_slot)
+ pa_hook_slot_free(u->sink_input_move_slot);
+ if (u->sink_input_state_changed_slot)
+ pa_hook_slot_free(u->sink_input_state_changed_slot);
+
+ if (u->source_output_new_slot)
+ pa_hook_slot_free(u->source_output_new_slot);
+ if (u->source_output_unlink_slot)
+ pa_hook_slot_free(u->source_output_unlink_slot);
+ if (u->source_output_move_slot)
+ pa_hook_slot_free(u->source_output_move_slot);
+ if (u->source_output_state_changed_slot)
+ pa_hook_slot_free(u->source_output_state_changed_slot);
+
+ while ((d = pa_hashmap_steal_first(u->device_infos)))
+ device_info_free(d);
+
+ pa_hashmap_free(u->device_infos, NULL, NULL);
+
+ pa_xfree(u);
+}
diff --git a/src/modules/module-tunnel.c b/src/modules/module-tunnel.c
new file mode 100644
index 00000000..a53e3932
--- /dev/null
+++ b/src/modules/module-tunnel.c
@@ -0,0 +1,1509 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <pulse/timeval.h>
+#include <pulse/util.h>
+#include <pulse/version.h>
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/module.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/log.h>
+#include <pulsecore/core-subscribe.h>
+#include <pulsecore/sink-input.h>
+#include <pulsecore/pdispatch.h>
+#include <pulsecore/pstream.h>
+#include <pulsecore/pstream-util.h>
+#include <pulsecore/authkey.h>
+#include <pulsecore/socket-client.h>
+#include <pulsecore/socket-util.h>
+#include <pulsecore/authkey-prop.h>
+#include <pulsecore/time-smoother.h>
+#include <pulsecore/thread.h>
+#include <pulsecore/thread-mq.h>
+#include <pulsecore/rtclock.h>
+#include <pulsecore/core-error.h>
+
+#ifdef TUNNEL_SINK
+#include "module-tunnel-sink-symdef.h"
+PA_MODULE_DESCRIPTION("Tunnel module for sinks");
+PA_MODULE_LOAD_ONCE(FALSE);
+PA_MODULE_USAGE(
+ "server=<address> "
+ "sink=<remote sink name> "
+ "cookie=<filename> "
+ "format=<sample format> "
+ "channels=<number of channels> "
+ "rate=<sample rate> "
+ "sink_name=<name for the local sink> "
+ "channel_map=<channel map>");
+#else
+#include "module-tunnel-source-symdef.h"
+PA_MODULE_DESCRIPTION("Tunnel module for sources");
+PA_MODULE_USAGE(
+ "server=<address> "
+ "source=<remote source name> "
+ "cookie=<filename> "
+ "format=<sample format> "
+ "channels=<number of channels> "
+ "rate=<sample rate> "
+ "source_name=<name for the local source> "
+ "channel_map=<channel map>");
+#endif
+
+PA_MODULE_AUTHOR("Lennart Poettering");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+
+#define DEFAULT_TLENGTH_MSEC 100
+#define DEFAULT_MINREQ_MSEC 10
+#define DEFAULT_MAXLENGTH_MSEC ((DEFAULT_TLENGTH_MSEC*3)/2)
+#define DEFAULT_FRAGSIZE_MSEC 10
+
+#define DEFAULT_TIMEOUT 5
+
+#define LATENCY_INTERVAL 10
+
+static const char* const valid_modargs[] = {
+ "server",
+ "cookie",
+ "format",
+ "channels",
+ "rate",
+#ifdef TUNNEL_SINK
+ "sink_name",
+ "sink",
+#else
+ "source_name",
+ "source",
+#endif
+ "channel_map",
+ NULL,
+};
+
+enum {
+ SOURCE_MESSAGE_POST = PA_SOURCE_MESSAGE_MAX
+};
+
+enum {
+ SINK_MESSAGE_REQUEST = PA_SINK_MESSAGE_MAX,
+ SINK_MESSAGE_POST
+};
+
+#ifdef TUNNEL_SINK
+static void command_request(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+#endif
+static void command_subscribe_event(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+static void command_stream_killed(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+static void command_overflow(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+static void command_underflow(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+static void command_suspend(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+static void command_moved(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+
+static const pa_pdispatch_cb_t command_table[PA_COMMAND_MAX] = {
+#ifdef TUNNEL_SINK
+ [PA_COMMAND_REQUEST] = command_request,
+#endif
+ [PA_COMMAND_SUBSCRIBE_EVENT] = command_subscribe_event,
+ [PA_COMMAND_OVERFLOW] = command_overflow,
+ [PA_COMMAND_UNDERFLOW] = command_underflow,
+ [PA_COMMAND_PLAYBACK_STREAM_KILLED] = command_stream_killed,
+ [PA_COMMAND_RECORD_STREAM_KILLED] = command_stream_killed,
+ [PA_COMMAND_PLAYBACK_STREAM_SUSPENDED] = command_suspend,
+ [PA_COMMAND_RECORD_STREAM_SUSPENDED] = command_suspend,
+ [PA_COMMAND_PLAYBACK_STREAM_MOVED] = command_moved,
+ [PA_COMMAND_RECORD_STREAM_MOVED] = command_moved,
+};
+
+struct userdata {
+ pa_core *core;
+ pa_module *module;
+
+ pa_thread_mq thread_mq;
+ pa_rtpoll *rtpoll;
+ pa_thread *thread;
+
+ pa_socket_client *client;
+ pa_pstream *pstream;
+ pa_pdispatch *pdispatch;
+
+ char *server_name;
+#ifdef TUNNEL_SINK
+ char *sink_name;
+ pa_sink *sink;
+ uint32_t requested_bytes;
+#else
+ char *source_name;
+ pa_source *source;
+#endif
+
+ uint8_t auth_cookie[PA_NATIVE_COOKIE_LENGTH];
+
+ uint32_t version;
+ uint32_t ctag;
+ uint32_t device_index;
+ uint32_t channel;
+
+ int64_t counter, counter_delta;
+
+ pa_time_event *time_event;
+
+ pa_bool_t auth_cookie_in_property;
+
+ pa_smoother *smoother;
+
+ char *device_description;
+ char *server_fqdn;
+ char *user_name;
+
+ uint32_t maxlength;
+#ifdef TUNNEL_SINK
+ uint32_t tlength;
+ uint32_t minreq;
+ uint32_t prebuf;
+#else
+ uint32_t fragsize;
+#endif
+};
+
+static void command_stream_killed(pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ struct userdata *u = userdata;
+
+ pa_assert(pd);
+ pa_assert(t);
+ pa_assert(u);
+ pa_assert(u->pdispatch == pd);
+
+ pa_log_warn("Stream killed");
+ pa_module_unload_request(u->module);
+}
+
+static void command_overflow(pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ struct userdata *u = userdata;
+
+ pa_assert(pd);
+ pa_assert(t);
+ pa_assert(u);
+ pa_assert(u->pdispatch == pd);
+
+ pa_log_warn("Server signalled buffer overrun.");
+}
+
+static void command_underflow(pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ struct userdata *u = userdata;
+
+ pa_assert(pd);
+ pa_assert(t);
+ pa_assert(u);
+ pa_assert(u->pdispatch == pd);
+
+ pa_log_warn("Server signalled buffer underrun.");
+}
+
+static void command_suspend(pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ struct userdata *u = userdata;
+
+ pa_assert(pd);
+ pa_assert(t);
+ pa_assert(u);
+ pa_assert(u->pdispatch == pd);
+
+ pa_log_debug("Server reports a stream suspension.");
+}
+
+static void command_moved(pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ struct userdata *u = userdata;
+
+ pa_assert(pd);
+ pa_assert(t);
+ pa_assert(u);
+ pa_assert(u->pdispatch == pd);
+
+ pa_log_debug("Server reports a stream move.");
+}
+
+static void stream_cork(struct userdata *u, pa_bool_t cork) {
+ pa_tagstruct *t;
+ pa_assert(u);
+
+ if (cork)
+ pa_smoother_pause(u->smoother, pa_rtclock_usec());
+ else
+ pa_smoother_resume(u->smoother, pa_rtclock_usec());
+
+ if (!u->pstream)
+ return;
+
+ t = pa_tagstruct_new(NULL, 0);
+#ifdef TUNNEL_SINK
+ pa_tagstruct_putu32(t, PA_COMMAND_CORK_PLAYBACK_STREAM);
+#else
+ pa_tagstruct_putu32(t, PA_COMMAND_CORK_RECORD_STREAM);
+#endif
+ pa_tagstruct_putu32(t, u->ctag++);
+ pa_tagstruct_putu32(t, u->channel);
+ pa_tagstruct_put_boolean(t, !!cork);
+ pa_pstream_send_tagstruct(u->pstream, t);
+}
+
+#ifdef TUNNEL_SINK
+
+static void send_data(struct userdata *u) {
+ pa_assert(u);
+
+ while (u->requested_bytes > 0) {
+ pa_memchunk memchunk;
+ pa_sink_render(u->sink, u->requested_bytes, &memchunk);
+ pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_POST, NULL, 0, &memchunk, NULL);
+ pa_memblock_unref(memchunk.memblock);
+ u->requested_bytes -= memchunk.length;
+ }
+}
+
+/* This function is called from IO context -- except when it is not. */
+static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+ struct userdata *u = PA_SINK(o)->userdata;
+
+ switch (code) {
+
+ case PA_SINK_MESSAGE_SET_STATE: {
+ int r;
+
+ /* First, change the state, because otherwide pa_sink_render() would fail */
+ if ((r = pa_sink_process_msg(o, code, data, offset, chunk)) >= 0)
+ if (PA_SINK_OPENED((pa_sink_state_t) PA_PTR_TO_UINT(data)))
+ send_data(u);
+
+ return r;
+ }
+
+ case SINK_MESSAGE_REQUEST:
+
+ pa_assert(offset > 0);
+ u->requested_bytes += (size_t) offset;
+
+ if (PA_SINK_OPENED(u->sink->thread_info.state))
+ send_data(u);
+
+ return 0;
+
+ case SINK_MESSAGE_POST:
+
+ /* OK, This might be a bit confusing. This message is
+ * delivered to us from the main context -- NOT from the
+ * IO thread context where the rest of the messages are
+ * dispatched. Yeah, ugly, but I am a lazy bastard. */
+
+ pa_pstream_send_memblock(u->pstream, u->channel, 0, PA_SEEK_RELATIVE, chunk);
+ u->counter += chunk->length;
+ u->counter_delta += chunk->length;
+ return 0;
+ }
+
+ return pa_sink_process_msg(o, code, data, offset, chunk);
+}
+
+static int sink_set_state(pa_sink *s, pa_sink_state_t state) {
+ struct userdata *u;
+ pa_sink_assert_ref(s);
+ u = s->userdata;
+
+ switch ((pa_sink_state_t) state) {
+
+ case PA_SINK_SUSPENDED:
+ pa_assert(PA_SINK_OPENED(s->state));
+ stream_cork(u, TRUE);
+ break;
+
+ case PA_SINK_IDLE:
+ case PA_SINK_RUNNING:
+ if (s->state == PA_SINK_SUSPENDED)
+ stream_cork(u, FALSE);
+ break;
+
+ case PA_SINK_UNLINKED:
+ case PA_SINK_INIT:
+ ;
+ }
+
+ return 0;
+}
+
+#else
+
+static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+ struct userdata *u = PA_SOURCE(o)->userdata;
+
+ switch (code) {
+ case SOURCE_MESSAGE_POST:
+
+ if (PA_SOURCE_OPENED(u->source->thread_info.state))
+ pa_source_post(u->source, chunk);
+ return 0;
+ }
+
+ return pa_source_process_msg(o, code, data, offset, chunk);
+}
+
+static int source_set_state(pa_source *s, pa_source_state_t state) {
+ struct userdata *u;
+ pa_source_assert_ref(s);
+ u = s->userdata;
+
+ switch ((pa_source_state_t) state) {
+
+ case PA_SOURCE_SUSPENDED:
+ pa_assert(PA_SOURCE_OPENED(s->state));
+ stream_cork(u, TRUE);
+ break;
+
+ case PA_SOURCE_IDLE:
+ case PA_SOURCE_RUNNING:
+ if (s->state == PA_SOURCE_SUSPENDED)
+ stream_cork(u, FALSE);
+ break;
+
+ case PA_SOURCE_UNLINKED:
+ case PA_SOURCE_INIT:
+ ;
+ }
+
+ return 0;
+}
+
+#endif
+
+static void thread_func(void *userdata) {
+ struct userdata *u = userdata;
+
+ pa_assert(u);
+
+ pa_log_debug("Thread starting up");
+
+ pa_thread_mq_install(&u->thread_mq);
+ pa_rtpoll_install(u->rtpoll);
+
+ for (;;) {
+ int ret;
+
+ if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0)
+ goto fail;
+
+ if (ret == 0)
+ goto finish;
+ }
+
+fail:
+ /* If this was no regular exit from the loop we have to continue
+ * processing messages until we received PA_MESSAGE_SHUTDOWN */
+ pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL);
+ pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN);
+
+finish:
+ pa_log_debug("Thread shutting down");
+}
+
+#ifdef TUNNEL_SINK
+static void command_request(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ struct userdata *u = userdata;
+ uint32_t bytes, channel;
+
+ pa_assert(pd);
+ pa_assert(command == PA_COMMAND_REQUEST);
+ pa_assert(t);
+ pa_assert(u);
+ pa_assert(u->pdispatch == pd);
+
+ if (pa_tagstruct_getu32(t, &channel) < 0 ||
+ pa_tagstruct_getu32(t, &bytes) < 0) {
+ pa_log("Invalid protocol reply");
+ goto fail;
+ }
+
+ if (channel != u->channel) {
+ pa_log("Recieved data for invalid channel");
+ goto fail;
+ }
+
+ pa_asyncmsgq_send(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_REQUEST, NULL, bytes, NULL);
+ return;
+
+fail:
+ pa_module_unload_request(u->module);
+}
+
+#endif
+
+static void stream_get_latency_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ struct userdata *u = userdata;
+ pa_usec_t sink_usec, source_usec, transport_usec, host_usec, k;
+ int playing;
+ int64_t write_index, read_index;
+ struct timeval local, remote, now;
+
+ pa_assert(pd);
+ pa_assert(u);
+
+ if (command != PA_COMMAND_REPLY) {
+ if (command == PA_COMMAND_ERROR)
+ pa_log("Failed to get latency.");
+ else
+ pa_log("Protocol error 1.");
+ goto fail;
+ }
+
+ if (pa_tagstruct_get_usec(t, &sink_usec) < 0 ||
+ pa_tagstruct_get_usec(t, &source_usec) < 0 ||
+ pa_tagstruct_get_boolean(t, &playing) < 0 ||
+ pa_tagstruct_get_timeval(t, &local) < 0 ||
+ pa_tagstruct_get_timeval(t, &remote) < 0 ||
+ pa_tagstruct_gets64(t, &write_index) < 0 ||
+ pa_tagstruct_gets64(t, &read_index) < 0) {
+ pa_log("Invalid reply. (latency)");
+ goto fail;
+ }
+
+ pa_gettimeofday(&now);
+
+ if (pa_timeval_cmp(&local, &remote) < 0 && pa_timeval_cmp(&remote, &now)) {
+ /* local and remote seem to have synchronized clocks */
+#ifdef TUNNEL_SINK
+ transport_usec = pa_timeval_diff(&remote, &local);
+#else
+ transport_usec = pa_timeval_diff(&now, &remote);
+#endif
+ } else
+ transport_usec = pa_timeval_diff(&now, &local)/2;
+
+#ifdef TUNNEL_SINK
+ host_usec = sink_usec + transport_usec;
+#else
+ host_usec = source_usec + transport_usec;
+ if (host_usec > sink_usec)
+ host_usec -= sink_usec;
+ else
+ host_usec = 0;
+#endif
+
+#ifdef TUNNEL_SINK
+ k = pa_bytes_to_usec(u->counter - u->counter_delta, &u->sink->sample_spec);
+
+ if (k > host_usec)
+ k -= host_usec;
+ else
+ k = 0;
+#else
+ k = pa_bytes_to_usec(u->counter - u->counter_delta, &u->source->sample_spec);
+ k += host_usec;
+#endif
+
+ pa_smoother_put(u->smoother, pa_rtclock_usec(), k);
+
+ return;
+
+fail:
+ pa_module_unload_request(u->module);
+}
+
+static void request_latency(struct userdata *u) {
+ pa_tagstruct *t;
+ struct timeval now;
+ uint32_t tag;
+ pa_assert(u);
+
+ t = pa_tagstruct_new(NULL, 0);
+#ifdef TUNNEL_SINK
+ pa_tagstruct_putu32(t, PA_COMMAND_GET_PLAYBACK_LATENCY);
+#else
+ pa_tagstruct_putu32(t, PA_COMMAND_GET_RECORD_LATENCY);
+#endif
+ pa_tagstruct_putu32(t, tag = u->ctag++);
+ pa_tagstruct_putu32(t, u->channel);
+
+ pa_gettimeofday(&now);
+ pa_tagstruct_put_timeval(t, &now);
+
+ pa_pstream_send_tagstruct(u->pstream, t);
+ pa_pdispatch_register_reply(u->pdispatch, tag, DEFAULT_TIMEOUT, stream_get_latency_callback, u, NULL);
+
+ u->counter_delta = 0;
+}
+
+static void timeout_callback(pa_mainloop_api *m, pa_time_event*e, PA_GCC_UNUSED const struct timeval *tv, void *userdata) {
+ struct userdata *u = userdata;
+ struct timeval ntv;
+
+ pa_assert(m);
+ pa_assert(e);
+ pa_assert(u);
+
+ request_latency(u);
+
+ pa_gettimeofday(&ntv);
+ ntv.tv_sec += LATENCY_INTERVAL;
+ m->time_restart(e, &ntv);
+}
+
+#ifdef TUNNEL_SINK
+static pa_usec_t sink_get_latency(pa_sink *s) {
+ pa_usec_t t, c;
+ struct userdata *u = s->userdata;
+
+ pa_sink_assert_ref(s);
+
+ c = pa_bytes_to_usec(u->counter, &s->sample_spec);
+ t = pa_smoother_get(u->smoother, pa_rtclock_usec());
+
+ return c > t ? c - t : 0;
+}
+#else
+static pa_usec_t source_get_latency(pa_source *s) {
+ pa_usec_t t, c;
+ struct userdata *u = s->userdata;
+
+ pa_source_assert_ref(s);
+
+ c = pa_bytes_to_usec(u->counter, &s->sample_spec);
+ t = pa_smoother_get(u->smoother, pa_rtclock_usec());
+
+ return t > c ? t - c : 0;
+}
+#endif
+
+static void update_description(struct userdata *u) {
+ char *d;
+ char un[128], hn[128];
+ pa_tagstruct *t;
+
+ pa_assert(u);
+
+ if (!u->server_fqdn || !u->user_name || !u->device_description)
+ return;
+
+ d = pa_sprintf_malloc("%s on %s@%s", u->device_description, u->user_name, u->server_fqdn);
+
+#ifdef TUNNEL_SINK
+ pa_sink_set_description(u->sink, d);
+#else
+ pa_source_set_description(u->source, d);
+#endif
+
+ pa_xfree(d);
+
+ d = pa_sprintf_malloc("%s for %s@%s", u->device_description,
+ pa_get_user_name(un, sizeof(un)),
+ pa_get_host_name(hn, sizeof(hn)));
+
+ t = pa_tagstruct_new(NULL, 0);
+#ifdef TUNNEL_SINK
+ pa_tagstruct_putu32(t, PA_COMMAND_SET_PLAYBACK_STREAM_NAME);
+#else
+ pa_tagstruct_putu32(t, PA_COMMAND_SET_RECORD_STREAM_NAME);
+#endif
+ pa_tagstruct_putu32(t, u->ctag++);
+ pa_tagstruct_putu32(t, u->channel);
+ pa_tagstruct_puts(t, d);
+ pa_pstream_send_tagstruct(u->pstream, t);
+
+ pa_xfree(d);
+}
+
+static void server_info_cb(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ struct userdata *u = userdata;
+ pa_sample_spec ss;
+ const char *server_name, *server_version, *user_name, *host_name, *default_sink_name, *default_source_name;
+ uint32_t cookie;
+
+ pa_assert(pd);
+ pa_assert(u);
+
+ if (command != PA_COMMAND_REPLY) {
+ if (command == PA_COMMAND_ERROR)
+ pa_log("Failed to get info.");
+ else
+ pa_log("Protocol error 6.");
+ goto fail;
+ }
+
+ if (pa_tagstruct_gets(t, &server_name) < 0 ||
+ pa_tagstruct_gets(t, &server_version) < 0 ||
+ pa_tagstruct_gets(t, &user_name) < 0 ||
+ pa_tagstruct_gets(t, &host_name) < 0 ||
+ pa_tagstruct_get_sample_spec(t, &ss) < 0 ||
+ pa_tagstruct_gets(t, &default_sink_name) < 0 ||
+ pa_tagstruct_gets(t, &default_source_name) < 0 ||
+ pa_tagstruct_getu32(t, &cookie) < 0) {
+ pa_log("Invalid reply. (get_server_info)");
+ goto fail;
+ }
+
+ pa_xfree(u->server_fqdn);
+ u->server_fqdn = pa_xstrdup(host_name);
+
+ pa_xfree(u->user_name);
+ u->user_name = pa_xstrdup(user_name);
+
+ update_description(u);
+
+ return;
+
+fail:
+ pa_module_unload_request(u->module);
+}
+
+#ifdef TUNNEL_SINK
+
+static void sink_info_cb(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ struct userdata *u = userdata;
+ uint32_t idx, owner_module, monitor_source, flags;
+ const char *name, *description, *monitor_source_name, *driver;
+ pa_sample_spec ss;
+ pa_channel_map cm;
+ pa_cvolume volume;
+ int mute;
+ pa_usec_t latency;
+
+ pa_assert(pd);
+ pa_assert(u);
+
+ if (command != PA_COMMAND_REPLY) {
+ if (command == PA_COMMAND_ERROR)
+ pa_log("Failed to get info.");
+ else
+ pa_log("Protocol error 5.");
+ goto fail;
+ }
+
+ if (pa_tagstruct_getu32(t, &idx) < 0 ||
+ pa_tagstruct_gets(t, &name) < 0 ||
+ pa_tagstruct_gets(t, &description) < 0 ||
+ pa_tagstruct_get_sample_spec(t, &ss) < 0 ||
+ pa_tagstruct_get_channel_map(t, &cm) < 0 ||
+ pa_tagstruct_getu32(t, &owner_module) < 0 ||
+ pa_tagstruct_get_cvolume(t, &volume) < 0 ||
+ pa_tagstruct_get_boolean(t, &mute) < 0 ||
+ pa_tagstruct_getu32(t, &monitor_source) < 0 ||
+ pa_tagstruct_gets(t, &monitor_source_name) < 0 ||
+ pa_tagstruct_get_usec(t, &latency) < 0 ||
+ pa_tagstruct_gets(t, &driver) < 0 ||
+ pa_tagstruct_getu32(t, &flags) < 0) {
+ pa_log("Invalid reply. (get_sink_info)");
+ goto fail;
+ }
+
+ if (strcmp(name, u->sink_name))
+ return;
+
+ pa_xfree(u->device_description);
+ u->device_description = pa_xstrdup(description);
+
+ update_description(u);
+
+ return;
+
+fail:
+ pa_module_unload_request(u->module);
+}
+
+static void sink_input_info_cb(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ struct userdata *u = userdata;
+ uint32_t idx, owner_module, client, sink;
+ pa_usec_t buffer_usec, sink_usec;
+ const char *name, *driver, *resample_method;
+ int mute;
+ pa_sample_spec sample_spec;
+ pa_channel_map channel_map;
+ pa_cvolume volume;
+
+ pa_assert(pd);
+ pa_assert(u);
+
+ if (command != PA_COMMAND_REPLY) {
+ if (command == PA_COMMAND_ERROR)
+ pa_log("Failed to get info.");
+ else
+ pa_log("Protocol error 2.");
+ goto fail;
+ }
+
+ if (pa_tagstruct_getu32(t, &idx) < 0 ||
+ pa_tagstruct_gets(t, &name) < 0 ||
+ pa_tagstruct_getu32(t, &owner_module) < 0 ||
+ pa_tagstruct_getu32(t, &client) < 0 ||
+ pa_tagstruct_getu32(t, &sink) < 0 ||
+ pa_tagstruct_get_sample_spec(t, &sample_spec) < 0 ||
+ pa_tagstruct_get_channel_map(t, &channel_map) < 0 ||
+ pa_tagstruct_get_cvolume(t, &volume) < 0 ||
+ pa_tagstruct_get_usec(t, &buffer_usec) < 0 ||
+ pa_tagstruct_get_usec(t, &sink_usec) < 0 ||
+ pa_tagstruct_gets(t, &resample_method) < 0 ||
+ pa_tagstruct_gets(t, &driver) < 0 ||
+ (u->version >= 11 && pa_tagstruct_get_boolean(t, &mute) < 0)) {
+ pa_log("Invalid reply. (get_info)");
+ goto fail;
+ }
+
+ if (idx != u->device_index)
+ return;
+
+ pa_assert(u->sink);
+
+ if ((u->version < 11 || !!mute == !!u->sink->muted) &&
+ pa_cvolume_equal(&volume, &u->sink->volume))
+ return;
+
+ memcpy(&u->sink->volume, &volume, sizeof(pa_cvolume));
+
+ if (u->version >= 11)
+ u->sink->muted = !!mute;
+
+ pa_subscription_post(u->sink->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, u->sink->index);
+ return;
+
+fail:
+ pa_module_unload_request(u->module);
+}
+
+#else
+
+static void source_info_cb(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ struct userdata *u = userdata;
+ uint32_t idx, owner_module, monitor_of_sink, flags;
+ const char *name, *description, *monitor_of_sink_name, *driver;
+ pa_sample_spec ss;
+ pa_channel_map cm;
+ pa_cvolume volume;
+ int mute;
+ pa_usec_t latency;
+
+ pa_assert(pd);
+ pa_assert(u);
+
+ if (command != PA_COMMAND_REPLY) {
+ if (command == PA_COMMAND_ERROR)
+ pa_log("Failed to get info.");
+ else
+ pa_log("Protocol error 5.");
+ goto fail;
+ }
+
+ if (pa_tagstruct_getu32(t, &idx) < 0 ||
+ pa_tagstruct_gets(t, &name) < 0 ||
+ pa_tagstruct_gets(t, &description) < 0 ||
+ pa_tagstruct_get_sample_spec(t, &ss) < 0 ||
+ pa_tagstruct_get_channel_map(t, &cm) < 0 ||
+ pa_tagstruct_getu32(t, &owner_module) < 0 ||
+ pa_tagstruct_get_cvolume(t, &volume) < 0 ||
+ pa_tagstruct_get_boolean(t, &mute) < 0 ||
+ pa_tagstruct_getu32(t, &monitor_of_sink) < 0 ||
+ pa_tagstruct_gets(t, &monitor_of_sink_name) < 0 ||
+ pa_tagstruct_get_usec(t, &latency) < 0 ||
+ pa_tagstruct_gets(t, &driver) < 0 ||
+ pa_tagstruct_getu32(t, &flags) < 0) {
+ pa_log("Invalid reply. (get_source_info)");
+ goto fail;
+ }
+
+ if (strcmp(name, u->source_name))
+ return;
+
+ pa_xfree(u->device_description);
+ u->device_description = pa_xstrdup(description);
+
+ update_description(u);
+
+ return;
+
+fail:
+ pa_module_unload_request(u->module);
+}
+
+#endif
+
+static void request_info(struct userdata *u) {
+ pa_tagstruct *t;
+ uint32_t tag;
+ pa_assert(u);
+
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, PA_COMMAND_GET_SERVER_INFO);
+ pa_tagstruct_putu32(t, tag = u->ctag++);
+ pa_pstream_send_tagstruct(u->pstream, t);
+ pa_pdispatch_register_reply(u->pdispatch, tag, DEFAULT_TIMEOUT, server_info_cb, u, NULL);
+
+#ifdef TUNNEL_SINK
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, PA_COMMAND_GET_SINK_INPUT_INFO);
+ pa_tagstruct_putu32(t, tag = u->ctag++);
+ pa_tagstruct_putu32(t, u->device_index);
+ pa_pstream_send_tagstruct(u->pstream, t);
+ pa_pdispatch_register_reply(u->pdispatch, tag, DEFAULT_TIMEOUT, sink_input_info_cb, u, NULL);
+
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, PA_COMMAND_GET_SINK_INFO);
+ pa_tagstruct_putu32(t, tag = u->ctag++);
+ pa_tagstruct_putu32(t, PA_INVALID_INDEX);
+ pa_tagstruct_puts(t, u->sink_name);
+ pa_pstream_send_tagstruct(u->pstream, t);
+ pa_pdispatch_register_reply(u->pdispatch, tag, DEFAULT_TIMEOUT, sink_info_cb, u, NULL);
+#else
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, PA_COMMAND_GET_SOURCE_INFO);
+ pa_tagstruct_putu32(t, tag = u->ctag++);
+ pa_tagstruct_putu32(t, PA_INVALID_INDEX);
+ pa_tagstruct_puts(t, u->source_name);
+ pa_pstream_send_tagstruct(u->pstream, t);
+ pa_pdispatch_register_reply(u->pdispatch, tag, DEFAULT_TIMEOUT, source_info_cb, u, NULL);
+#endif
+}
+
+static void command_subscribe_event(pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ struct userdata *u = userdata;
+ pa_subscription_event_type_t e;
+ uint32_t idx;
+
+ pa_assert(pd);
+ pa_assert(t);
+ pa_assert(u);
+ pa_assert(command == PA_COMMAND_SUBSCRIBE_EVENT);
+
+ if (pa_tagstruct_getu32(t, &e) < 0 ||
+ pa_tagstruct_getu32(t, &idx) < 0) {
+ pa_log("Invalid protocol reply");
+ pa_module_unload_request(u->module);
+ return;
+ }
+
+ if (e != (PA_SUBSCRIPTION_EVENT_SERVER|PA_SUBSCRIPTION_EVENT_CHANGE) &&
+#ifdef TUNNEL_SINK
+ e != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE) &&
+ e != (PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE)
+#else
+ e != (PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE)
+#endif
+ )
+ return;
+
+ request_info(u);
+}
+
+static void start_subscribe(struct userdata *u) {
+ pa_tagstruct *t;
+ uint32_t tag;
+ pa_assert(u);
+
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, PA_COMMAND_SUBSCRIBE);
+ pa_tagstruct_putu32(t, tag = u->ctag++);
+ pa_tagstruct_putu32(t, PA_SUBSCRIPTION_MASK_SERVER|
+#ifdef TUNNEL_SINK
+ PA_SUBSCRIPTION_MASK_SINK_INPUT|PA_SUBSCRIPTION_MASK_SINK
+#else
+ PA_SUBSCRIPTION_MASK_SOURCE
+#endif
+ );
+
+ pa_pstream_send_tagstruct(u->pstream, t);
+}
+
+static void create_stream_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ struct userdata *u = userdata;
+ struct timeval ntv;
+#ifdef TUNNEL_SINK
+ uint32_t bytes;
+#endif
+
+ pa_assert(pd);
+ pa_assert(u);
+ pa_assert(u->pdispatch == pd);
+
+ if (command != PA_COMMAND_REPLY) {
+ if (command == PA_COMMAND_ERROR)
+ pa_log("Failed to create stream.");
+ else
+ pa_log("Protocol error 3.");
+ goto fail;
+ }
+
+ if (pa_tagstruct_getu32(t, &u->channel) < 0 ||
+ pa_tagstruct_getu32(t, &u->device_index) < 0
+#ifdef TUNNEL_SINK
+ || pa_tagstruct_getu32(t, &bytes) < 0
+#endif
+ )
+ goto parse_error;
+
+ if (u->version >= 9) {
+#ifdef TUNNEL_SINK
+ uint32_t maxlength, tlength, prebuf, minreq;
+
+ if (pa_tagstruct_getu32(t, &maxlength) < 0 ||
+ pa_tagstruct_getu32(t, &tlength) < 0 ||
+ pa_tagstruct_getu32(t, &prebuf) < 0 ||
+ pa_tagstruct_getu32(t, &minreq) < 0)
+ goto parse_error;
+#else
+ uint32_t maxlength, fragsize;
+
+ if (pa_tagstruct_getu32(t, &maxlength) < 0 ||
+ pa_tagstruct_getu32(t, &fragsize) < 0)
+ goto parse_error;
+#endif
+ }
+
+ start_subscribe(u);
+ request_info(u);
+
+ pa_assert(!u->time_event);
+ pa_gettimeofday(&ntv);
+ ntv.tv_sec += LATENCY_INTERVAL;
+ u->time_event = u->core->mainloop->time_new(u->core->mainloop, &ntv, timeout_callback, u);
+
+ request_latency(u);
+
+ pa_log_debug("Stream created.");
+
+#ifdef TUNNEL_SINK
+ pa_asyncmsgq_post(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_REQUEST, NULL, bytes, NULL, NULL);
+#endif
+
+ return;
+
+parse_error:
+ pa_log("Invalid reply. (Create stream)");
+
+fail:
+ pa_module_unload_request(u->module);
+}
+
+static void setup_complete_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ struct userdata *u = userdata;
+ pa_tagstruct *reply;
+ char name[256], un[128], hn[128];
+#ifdef TUNNEL_SINK
+ pa_cvolume volume;
+#endif
+
+ pa_assert(pd);
+ pa_assert(u);
+ pa_assert(u->pdispatch == pd);
+
+ if (command != PA_COMMAND_REPLY ||
+ pa_tagstruct_getu32(t, &u->version) < 0) {
+ if (command == PA_COMMAND_ERROR)
+ pa_log("Failed to authenticate");
+ else
+ pa_log("Protocol error 4.");
+
+ goto fail;
+ }
+
+ /* Minimum supported protocol version */
+ if (u->version < 8) {
+ pa_log("Incompatible protocol version");
+ goto fail;
+ }
+
+#ifdef TUNNEL_SINK
+ pa_snprintf(name, sizeof(name), "%s for %s@%s",
+ u->sink_name,
+ pa_get_user_name(un, sizeof(un)),
+ pa_get_host_name(hn, sizeof(hn)));
+#else
+ pa_snprintf(name, sizeof(name), "%s for %s@%s",
+ u->source_name,
+ pa_get_user_name(un, sizeof(un)),
+ pa_get_host_name(hn, sizeof(hn)));
+#endif
+
+ reply = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(reply, PA_COMMAND_SET_CLIENT_NAME);
+ pa_tagstruct_putu32(reply, tag = u->ctag++);
+ pa_tagstruct_puts(reply, "PulseAudio");
+ pa_pstream_send_tagstruct(u->pstream, reply);
+ /* We ignore the server's reply here */
+
+ reply = pa_tagstruct_new(NULL, 0);
+
+#ifdef TUNNEL_SINK
+ pa_tagstruct_putu32(reply, PA_COMMAND_CREATE_PLAYBACK_STREAM);
+ pa_tagstruct_putu32(reply, tag = u->ctag++);
+ pa_tagstruct_puts(reply, name);
+ pa_tagstruct_put_sample_spec(reply, &u->sink->sample_spec);
+ pa_tagstruct_put_channel_map(reply, &u->sink->channel_map);
+ pa_tagstruct_putu32(reply, PA_INVALID_INDEX);
+ pa_tagstruct_puts(reply, u->sink_name);
+ pa_tagstruct_putu32(reply, u->maxlength);
+ pa_tagstruct_put_boolean(reply, !PA_SINK_OPENED(pa_sink_get_state(u->sink)));
+ pa_tagstruct_putu32(reply, u->tlength);
+ pa_tagstruct_putu32(reply, u->prebuf);
+ pa_tagstruct_putu32(reply, u->minreq);
+ pa_tagstruct_putu32(reply, 0);
+ pa_cvolume_reset(&volume, u->sink->sample_spec.channels);
+ pa_tagstruct_put_cvolume(reply, &volume);
+#else
+ pa_tagstruct_putu32(reply, PA_COMMAND_CREATE_RECORD_STREAM);
+ pa_tagstruct_putu32(reply, tag = u->ctag++);
+ pa_tagstruct_puts(reply, name);
+ pa_tagstruct_put_sample_spec(reply, &u->source->sample_spec);
+ pa_tagstruct_put_channel_map(reply, &u->source->channel_map);
+ pa_tagstruct_putu32(reply, PA_INVALID_INDEX);
+ pa_tagstruct_puts(reply, u->source_name);
+ pa_tagstruct_putu32(reply, u->maxlength);
+ pa_tagstruct_put_boolean(reply, !PA_SOURCE_OPENED(pa_source_get_state(u->source)));
+ pa_tagstruct_putu32(reply, u->fragsize);
+#endif
+
+ /* New flags added in 0.9.8 */
+ if (u->version >= 12) {
+ /* TODO: set these to useful values */
+ pa_tagstruct_put_boolean(reply, FALSE); /*no_remap*/
+ pa_tagstruct_put_boolean(reply, FALSE); /*no_remix*/
+ pa_tagstruct_put_boolean(reply, FALSE); /*fix_format*/
+ pa_tagstruct_put_boolean(reply, FALSE); /*fix_rate*/
+ pa_tagstruct_put_boolean(reply, FALSE); /*fix_channels*/
+ pa_tagstruct_put_boolean(reply, FALSE); /*no_move*/
+ pa_tagstruct_put_boolean(reply, FALSE); /*variable_rate*/
+ }
+
+ pa_pstream_send_tagstruct(u->pstream, reply);
+ pa_pdispatch_register_reply(u->pdispatch, tag, DEFAULT_TIMEOUT, create_stream_callback, u, NULL);
+
+ pa_log_debug("Connection authenticated, creating stream ...");
+
+ return;
+
+fail:
+ pa_module_unload_request(u->module);
+}
+
+static void pstream_die_callback(pa_pstream *p, void *userdata) {
+ struct userdata *u = userdata;
+
+ pa_assert(p);
+ pa_assert(u);
+
+ pa_log_warn("Stream died.");
+ pa_module_unload_request(u->module);
+}
+
+static void pstream_packet_callback(pa_pstream *p, pa_packet *packet, const pa_creds *creds, void *userdata) {
+ struct userdata *u = userdata;
+
+ pa_assert(p);
+ pa_assert(packet);
+ pa_assert(u);
+
+ if (pa_pdispatch_run(u->pdispatch, packet, creds, u) < 0) {
+ pa_log("Invalid packet");
+ pa_module_unload_request(u->module);
+ return;
+ }
+}
+
+#ifndef TUNNEL_SINK
+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) {
+ struct userdata *u = userdata;
+
+ pa_assert(p);
+ pa_assert(chunk);
+ pa_assert(u);
+
+ if (channel != u->channel) {
+ pa_log("Recieved memory block on bad channel.");
+ pa_module_unload_request(u->module);
+ return;
+ }
+
+ pa_asyncmsgq_send(u->source->asyncmsgq, PA_MSGOBJECT(u->source), SOURCE_MESSAGE_POST, PA_UINT_TO_PTR(seek), offset, chunk);
+
+ u->counter += chunk->length;
+ u->counter_delta += chunk->length;
+}
+
+#endif
+
+static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata) {
+ struct userdata *u = userdata;
+ pa_tagstruct *t;
+ uint32_t tag;
+
+ pa_assert(sc);
+ pa_assert(u);
+ pa_assert(u->client == sc);
+
+ pa_socket_client_unref(u->client);
+ u->client = NULL;
+
+ if (!io) {
+ pa_log("Connection failed: %s", pa_cstrerror(errno));
+ pa_module_unload_request(u->module);
+ return;
+ }
+
+ u->pstream = pa_pstream_new(u->core->mainloop, io, u->core->mempool);
+ u->pdispatch = pa_pdispatch_new(u->core->mainloop, command_table, PA_COMMAND_MAX);
+
+ pa_pstream_set_die_callback(u->pstream, pstream_die_callback, u);
+ pa_pstream_set_recieve_packet_callback(u->pstream, pstream_packet_callback, u);
+#ifndef TUNNEL_SINK
+ pa_pstream_set_recieve_memblock_callback(u->pstream, pstream_memblock_callback, u);
+#endif
+
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, PA_COMMAND_AUTH);
+ pa_tagstruct_putu32(t, tag = u->ctag++);
+ pa_tagstruct_putu32(t, PA_PROTOCOL_VERSION);
+ pa_tagstruct_put_arbitrary(t, u->auth_cookie, sizeof(u->auth_cookie));
+
+#ifdef HAVE_CREDS
+{
+ pa_creds ucred;
+
+ if (pa_iochannel_creds_supported(io))
+ pa_iochannel_creds_enable(io);
+
+ ucred.uid = getuid();
+ ucred.gid = getgid();
+
+ pa_pstream_send_tagstruct_with_creds(u->pstream, t, &ucred);
+}
+#else
+ pa_pstream_send_tagstruct(u->pstream, t);
+#endif
+
+ pa_pdispatch_register_reply(u->pdispatch, tag, DEFAULT_TIMEOUT, setup_complete_callback, u, NULL);
+
+ pa_log_debug("Connection established, authenticating ...");
+}
+
+#ifdef TUNNEL_SINK
+
+static int sink_get_volume(pa_sink *sink) {
+ return 0;
+}
+
+static int sink_set_volume(pa_sink *sink) {
+ struct userdata *u;
+ pa_tagstruct *t;
+ uint32_t tag;
+
+ pa_assert(sink);
+ u = sink->userdata;
+ pa_assert(u);
+
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, PA_COMMAND_SET_SINK_INPUT_VOLUME);
+ pa_tagstruct_putu32(t, tag = u->ctag++);
+ pa_tagstruct_putu32(t, u->device_index);
+ pa_tagstruct_put_cvolume(t, &sink->volume);
+ pa_pstream_send_tagstruct(u->pstream, t);
+
+ return 0;
+}
+
+static int sink_get_mute(pa_sink *sink) {
+ return 0;
+}
+
+static int sink_set_mute(pa_sink *sink) {
+ struct userdata *u;
+ pa_tagstruct *t;
+ uint32_t tag;
+
+ pa_assert(sink);
+ u = sink->userdata;
+ pa_assert(u);
+
+ if (u->version < 11)
+ return -1;
+
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, PA_COMMAND_SET_SINK_INPUT_MUTE);
+ pa_tagstruct_putu32(t, tag = u->ctag++);
+ pa_tagstruct_putu32(t, u->device_index);
+ pa_tagstruct_put_boolean(t, !!sink->muted);
+ pa_pstream_send_tagstruct(u->pstream, t);
+
+ return 0;
+}
+
+#endif
+
+static int load_key(struct userdata *u, const char*fn) {
+ pa_assert(u);
+
+ u->auth_cookie_in_property = FALSE;
+
+ if (!fn && pa_authkey_prop_get(u->core, PA_NATIVE_COOKIE_PROPERTY_NAME, u->auth_cookie, sizeof(u->auth_cookie)) >= 0) {
+ pa_log_debug("Using already loaded auth cookie.");
+ pa_authkey_prop_ref(u->core, PA_NATIVE_COOKIE_PROPERTY_NAME);
+ u->auth_cookie_in_property = 1;
+ return 0;
+ }
+
+ if (!fn)
+ fn = PA_NATIVE_COOKIE_FILE;
+
+ if (pa_authkey_load_auto(fn, u->auth_cookie, sizeof(u->auth_cookie)) < 0)
+ return -1;
+
+ pa_log_debug("Loading cookie from disk.");
+
+ if (pa_authkey_prop_put(u->core, PA_NATIVE_COOKIE_PROPERTY_NAME, u->auth_cookie, sizeof(u->auth_cookie)) >= 0)
+ u->auth_cookie_in_property = TRUE;
+
+ return 0;
+}
+
+int pa__init(pa_module*m) {
+ pa_modargs *ma = NULL;
+ struct userdata *u = NULL;
+ pa_sample_spec ss;
+ pa_channel_map map;
+ char *t, *dn = NULL;
+
+ pa_assert(m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log("failed to parse module arguments");
+ goto fail;
+ }
+
+ u = pa_xnew0(struct userdata, 1);
+ m->userdata = u;
+ u->module = m;
+ u->core = m->core;
+ u->client = NULL;
+ u->pdispatch = NULL;
+ u->pstream = NULL;
+ u->server_name = NULL;
+#ifdef TUNNEL_SINK
+ u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL));;
+ u->sink = NULL;
+ u->requested_bytes = 0;
+#else
+ u->source_name = pa_xstrdup(pa_modargs_get_value(ma, "source", NULL));;
+ u->source = NULL;
+#endif
+ u->smoother = pa_smoother_new(PA_USEC_PER_SEC, PA_USEC_PER_SEC*2, TRUE);
+ u->ctag = 1;
+ u->device_index = u->channel = PA_INVALID_INDEX;
+ u->auth_cookie_in_property = FALSE;
+ u->time_event = NULL;
+
+ pa_thread_mq_init(&u->thread_mq, m->core->mainloop);
+ u->rtpoll = pa_rtpoll_new();
+ pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq);
+
+ if (load_key(u, pa_modargs_get_value(ma, "cookie", NULL)) < 0)
+ goto fail;
+
+ if (!(u->server_name = pa_xstrdup(pa_modargs_get_value(ma, "server", NULL)))) {
+ pa_log("no server specified.");
+ goto fail;
+ }
+
+ ss = m->core->default_sample_spec;
+ if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) {
+ pa_log("invalid sample format specification");
+ goto fail;
+ }
+
+ if (!(u->client = pa_socket_client_new_string(m->core->mainloop, u->server_name, PA_NATIVE_DEFAULT_PORT))) {
+ pa_log("failed to connect to server '%s'", u->server_name);
+ goto fail;
+ }
+
+ pa_socket_client_set_callback(u->client, on_connection, u);
+
+#ifdef TUNNEL_SINK
+
+ if (!(dn = pa_xstrdup(pa_modargs_get_value(ma, "sink_name", NULL))))
+ dn = pa_sprintf_malloc("tunnel.%s", u->server_name);
+
+ if (!(u->sink = pa_sink_new(m->core, __FILE__, dn, 1, &ss, &map))) {
+ pa_log("Failed to create sink.");
+ goto fail;
+ }
+
+ u->sink->parent.process_msg = sink_process_msg;
+ u->sink->userdata = u;
+ u->sink->set_state = sink_set_state;
+ u->sink->get_latency = sink_get_latency;
+ u->sink->get_volume = sink_get_volume;
+ u->sink->get_mute = sink_get_mute;
+ u->sink->set_volume = sink_set_volume;
+ u->sink->set_mute = sink_set_mute;
+ u->sink->flags = PA_SINK_NETWORK|PA_SINK_LATENCY|PA_SINK_HW_VOLUME_CTRL;
+
+ pa_sink_set_module(u->sink, m);
+ pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq);
+ pa_sink_set_rtpoll(u->sink, u->rtpoll);
+ pa_sink_set_description(u->sink, t = pa_sprintf_malloc("%s%s%s", u->sink_name ? u->sink_name : "", u->sink_name ? " on " : "", u->server_name));
+ pa_xfree(t);
+
+#else
+
+ if (!(dn = pa_xstrdup(pa_modargs_get_value(ma, "source_name", NULL))))
+ dn = pa_sprintf_malloc("tunnel.%s", u->server_name);
+
+ if (!(u->source = pa_source_new(m->core, __FILE__, dn, 1, &ss, &map))) {
+ pa_log("Failed to create source.");
+ goto fail;
+ }
+
+ u->source->parent.process_msg = source_process_msg;
+ u->source->userdata = u;
+ u->source->set_state = source_set_state;
+ u->source->get_latency = source_get_latency;
+ u->source->flags = PA_SOURCE_NETWORK|PA_SOURCE_LATENCY;
+
+ pa_source_set_module(u->source, m);
+ pa_source_set_asyncmsgq(u->source, u->thread_mq.inq);
+ pa_source_set_rtpoll(u->source, u->rtpoll);
+ pa_source_set_description(u->source, t = pa_sprintf_malloc("%s%s%s", u->source_name ? u->source_name : "", u->source_name ? " on " : "", u->server_name));
+ pa_xfree(t);
+#endif
+
+ pa_xfree(dn);
+
+ u->time_event = NULL;
+
+ u->maxlength = pa_usec_to_bytes(PA_USEC_PER_MSEC * DEFAULT_MAXLENGTH_MSEC, &ss);
+#ifdef TUNNEL_SINK
+ u->tlength = pa_usec_to_bytes(PA_USEC_PER_MSEC * DEFAULT_TLENGTH_MSEC, &ss);
+ u->minreq = pa_usec_to_bytes(PA_USEC_PER_MSEC * DEFAULT_MINREQ_MSEC, &ss);
+ u->prebuf = u->tlength;
+#else
+ u->fragsize = pa_usec_to_bytes(PA_USEC_PER_MSEC * DEFAULT_FRAGSIZE_MSEC, &ss);
+#endif
+
+ u->counter = u->counter_delta = 0;
+ pa_smoother_set_time_offset(u->smoother, pa_rtclock_usec());
+
+ if (!(u->thread = pa_thread_new(thread_func, u))) {
+ pa_log("Failed to create thread.");
+ goto fail;
+ }
+
+#ifdef TUNNEL_SINK
+ pa_sink_put(u->sink);
+#else
+ pa_source_put(u->source);
+#endif
+
+ pa_modargs_free(ma);
+
+ return 0;
+
+fail:
+ pa__done(m);
+
+ if (ma)
+ pa_modargs_free(ma);
+
+ pa_xfree(dn);
+
+ return -1;
+}
+
+void pa__done(pa_module*m) {
+ struct userdata* u;
+
+ pa_assert(m);
+
+ if (!(u = m->userdata))
+ return;
+
+#ifdef TUNNEL_SINK
+ if (u->sink)
+ pa_sink_unlink(u->sink);
+#else
+ if (u->source)
+ pa_source_unlink(u->source);
+#endif
+
+ if (u->thread) {
+ pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL);
+ pa_thread_free(u->thread);
+ }
+
+ pa_thread_mq_done(&u->thread_mq);
+
+#ifdef TUNNEL_SINK
+ if (u->sink)
+ pa_sink_unref(u->sink);
+#else
+ if (u->source)
+ pa_source_unref(u->source);
+#endif
+
+ if (u->rtpoll)
+ pa_rtpoll_free(u->rtpoll);
+
+ if (u->pstream) {
+ pa_pstream_unlink(u->pstream);
+ pa_pstream_unref(u->pstream);
+ }
+
+ if (u->pdispatch)
+ pa_pdispatch_unref(u->pdispatch);
+
+ if (u->client)
+ pa_socket_client_unref(u->client);
+
+ if (u->auth_cookie_in_property)
+ pa_authkey_prop_unref(m->core, PA_NATIVE_COOKIE_PROPERTY_NAME);
+
+ if (u->smoother)
+ pa_smoother_free(u->smoother);
+
+ if (u->time_event)
+ u->core->mainloop->time_free(u->time_event);
+
+#ifdef TUNNEL_SINK
+ pa_xfree(u->sink_name);
+#else
+ pa_xfree(u->source_name);
+#endif
+ pa_xfree(u->server_name);
+
+ pa_xfree(u->device_description);
+ pa_xfree(u->server_fqdn);
+ pa_xfree(u->user_name);
+
+ pa_xfree(u);
+}
diff --git a/src/modules/module-volume-restore.c b/src/modules/module-volume-restore.c
new file mode 100644
index 00000000..192a2a78
--- /dev/null
+++ b/src/modules/module-volume-restore.c
@@ -0,0 +1,580 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+#include <pulse/xmalloc.h>
+#include <pulse/volume.h>
+#include <pulse/timeval.h>
+
+#include <pulsecore/core-error.h>
+#include <pulsecore/module.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/log.h>
+#include <pulsecore/core-subscribe.h>
+#include <pulsecore/sink-input.h>
+#include <pulsecore/source-output.h>
+#include <pulsecore/namereg.h>
+
+#include "module-volume-restore-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering");
+PA_MODULE_DESCRIPTION("Automatically restore the volume and the devices of streams");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(TRUE);
+PA_MODULE_USAGE(
+ "table=<filename> "
+ "restore_device=<Restore the device for each stream?> "
+ "restore_volume=<Restore the volume for each stream?>"
+);
+
+#define WHITESPACE "\n\r \t"
+#define DEFAULT_VOLUME_TABLE_FILE "volume-restore.table"
+#define SAVE_INTERVAL 10
+
+static const char* const valid_modargs[] = {
+ "table",
+ "restore_device",
+ "restore_volume",
+ NULL,
+};
+
+struct rule {
+ char* name;
+ pa_bool_t volume_is_set;
+ pa_cvolume volume;
+ char *sink, *source;
+};
+
+struct userdata {
+ pa_core *core;
+ pa_hashmap *hashmap;
+ pa_subscription *subscription;
+ pa_hook_slot
+ *sink_input_new_hook_slot,
+ *sink_input_fixate_hook_slot,
+ *source_output_new_hook_slot;
+ pa_bool_t modified;
+ char *table_file;
+ pa_time_event *save_time_event;
+};
+
+static pa_cvolume* parse_volume(const char *s, pa_cvolume *v) {
+ char *p;
+ long k;
+ unsigned i;
+
+ pa_assert(s);
+ pa_assert(v);
+
+ if (!isdigit(*s))
+ return NULL;
+
+ k = strtol(s, &p, 0);
+ if (k <= 0 || k > PA_CHANNELS_MAX)
+ return NULL;
+
+ v->channels = (unsigned) k;
+
+ for (i = 0; i < v->channels; i++) {
+ p += strspn(p, WHITESPACE);
+
+ if (!isdigit(*p))
+ return NULL;
+
+ k = strtol(p, &p, 0);
+
+ if (k < PA_VOLUME_MUTED)
+ return NULL;
+
+ v->values[i] = (pa_volume_t) k;
+ }
+
+ if (*p != 0)
+ return NULL;
+
+ return v;
+}
+
+static int load_rules(struct userdata *u) {
+ FILE *f;
+ int n = 0;
+ int ret = -1;
+ char buf_name[256], buf_volume[256], buf_sink[256], buf_source[256];
+ char *ln = buf_name;
+
+ f = u->table_file ?
+ fopen(u->table_file, "r") :
+ pa_open_config_file(NULL, DEFAULT_VOLUME_TABLE_FILE, NULL, &u->table_file, "r");
+
+ if (!f) {
+ if (errno == ENOENT) {
+ pa_log_info("starting with empty ruleset.");
+ ret = 0;
+ } else
+ pa_log("failed to open file '%s': %s", u->table_file, pa_cstrerror(errno));
+
+ goto finish;
+ }
+
+ pa_lock_fd(fileno(f), 1);
+
+ while (!feof(f)) {
+ struct rule *rule;
+ pa_cvolume v;
+ pa_bool_t v_is_set;
+
+ if (!fgets(ln, sizeof(buf_name), f))
+ break;
+
+ n++;
+
+ pa_strip_nl(ln);
+
+ if (ln[0] == '#')
+ continue;
+
+ if (ln == buf_name) {
+ ln = buf_volume;
+ continue;
+ }
+
+ if (ln == buf_volume) {
+ ln = buf_sink;
+ continue;
+ }
+
+ if (ln == buf_sink) {
+ ln = buf_source;
+ continue;
+ }
+
+ pa_assert(ln == buf_source);
+
+ if (buf_volume[0]) {
+ if (!parse_volume(buf_volume, &v)) {
+ pa_log("parse failure in %s:%u, stopping parsing", u->table_file, n);
+ goto finish;
+ }
+
+ v_is_set = TRUE;
+ } else
+ v_is_set = FALSE;
+
+ ln = buf_name;
+
+ if (pa_hashmap_get(u->hashmap, buf_name)) {
+ pa_log("double entry in %s:%u, ignoring", u->table_file, n);
+ continue;
+ }
+
+ rule = pa_xnew(struct rule, 1);
+ rule->name = pa_xstrdup(buf_name);
+ if ((rule->volume_is_set = v_is_set))
+ rule->volume = v;
+ rule->sink = buf_sink[0] ? pa_xstrdup(buf_sink) : NULL;
+ rule->source = buf_source[0] ? pa_xstrdup(buf_source) : NULL;
+
+ pa_hashmap_put(u->hashmap, rule->name, rule);
+ }
+
+ if (ln != buf_name) {
+ pa_log("invalid number of lines in %s.", u->table_file);
+ goto finish;
+ }
+
+ ret = 0;
+
+finish:
+ if (f) {
+ pa_lock_fd(fileno(f), 0);
+ fclose(f);
+ }
+
+ return ret;
+}
+
+static int save_rules(struct userdata *u) {
+ FILE *f;
+ int ret = -1;
+ void *state = NULL;
+ struct rule *rule;
+
+ if (!u->modified)
+ return 0;
+
+ pa_log_info("Saving rules...");
+
+ f = u->table_file ?
+ fopen(u->table_file, "w") :
+ pa_open_config_file(NULL, DEFAULT_VOLUME_TABLE_FILE, NULL, &u->table_file, "w");
+
+ if (!f) {
+ pa_log("Failed to open file '%s': %s", u->table_file, pa_cstrerror(errno));
+ goto finish;
+ }
+
+ pa_lock_fd(fileno(f), 1);
+
+ while ((rule = pa_hashmap_iterate(u->hashmap, &state, NULL))) {
+ unsigned i;
+
+ fprintf(f, "%s\n", rule->name);
+
+ if (rule->volume_is_set) {
+ fprintf(f, "%u", rule->volume.channels);
+
+ for (i = 0; i < rule->volume.channels; i++)
+ fprintf(f, " %u", rule->volume.values[i]);
+ }
+
+ fprintf(f, "\n%s\n%s\n",
+ rule->sink ? rule->sink : "",
+ rule->source ? rule->source : "");
+ }
+
+ ret = 0;
+ u->modified = FALSE;
+ pa_log_debug("Successfully saved rules...");
+
+finish:
+ if (f) {
+ pa_lock_fd(fileno(f), 0);
+ fclose(f);
+ }
+
+ return ret;
+}
+
+static char* client_name(pa_client *c) {
+ char *t, *e;
+
+ if (!c->name || !c->driver)
+ return NULL;
+
+ t = pa_sprintf_malloc("%s$%s", c->driver, c->name);
+ t[strcspn(t, "\n\r#")] = 0;
+
+ if (!*t) {
+ pa_xfree(t);
+ return NULL;
+ }
+
+ if ((e = strrchr(t, '('))) {
+ char *k = e + 1 + strspn(e + 1, "0123456789-");
+
+ /* Dirty trick: truncate all trailing parens with numbers in
+ * between, since they are usually used to identify multiple
+ * sessions of the same application, which is something we
+ * explicitly don't want. Besides other stuff this makes xmms
+ * with esound work properly for us. */
+
+ if (*k == ')' && *(k+1) == 0)
+ *e = 0;
+ }
+
+ return t;
+}
+
+static void save_time_callback(pa_mainloop_api*a, pa_time_event* e, const struct timeval *tv, void *userdata) {
+ struct userdata *u = userdata;
+
+ pa_assert(a);
+ pa_assert(e);
+ pa_assert(tv);
+ pa_assert(u);
+
+ pa_assert(e == u->save_time_event);
+ u->core->mainloop->time_free(u->save_time_event);
+ u->save_time_event = NULL;
+
+ save_rules(u);
+}
+
+static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) {
+ struct userdata *u = userdata;
+ pa_sink_input *si = NULL;
+ pa_source_output *so = NULL;
+ struct rule *r;
+ char *name;
+
+ pa_assert(c);
+ pa_assert(u);
+
+ if (t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW) &&
+ t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE) &&
+ t != (PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_NEW) &&
+ t != (PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE))
+ return;
+
+ if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK_INPUT) {
+ if (!(si = pa_idxset_get_by_index(c->sink_inputs, idx)))
+ return;
+
+ if (!si->client || !(name = client_name(si->client)))
+ return;
+ } else {
+ pa_assert((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT);
+
+ if (!(so = pa_idxset_get_by_index(c->source_outputs, idx)))
+ return;
+
+ if (!so->client || !(name = client_name(so->client)))
+ return;
+ }
+
+ if ((r = pa_hashmap_get(u->hashmap, name))) {
+ pa_xfree(name);
+
+ if (si) {
+
+ if (!r->volume_is_set || !pa_cvolume_equal(pa_sink_input_get_volume(si), &r->volume)) {
+ pa_log_info("Saving volume for <%s>", r->name);
+ r->volume = *pa_sink_input_get_volume(si);
+ r->volume_is_set = TRUE;
+ u->modified = TRUE;
+ }
+
+ if (!r->sink || strcmp(si->sink->name, r->sink) != 0) {
+ pa_log_info("Saving sink for <%s>", r->name);
+ pa_xfree(r->sink);
+ r->sink = pa_xstrdup(si->sink->name);
+ u->modified = TRUE;
+ }
+ } else {
+ pa_assert(so);
+
+ if (!r->source || strcmp(so->source->name, r->source) != 0) {
+ pa_log_info("Saving source for <%s>", r->name);
+ pa_xfree(r->source);
+ r->source = pa_xstrdup(so->source->name);
+ u->modified = TRUE;
+ }
+ }
+
+ } else {
+ pa_log_info("Creating new entry for <%s>", name);
+
+ r = pa_xnew(struct rule, 1);
+ r->name = name;
+
+ if (si) {
+ r->volume = *pa_sink_input_get_volume(si);
+ r->volume_is_set = TRUE;
+ r->sink = pa_xstrdup(si->sink->name);
+ r->source = NULL;
+ } else {
+ pa_assert(so);
+ r->volume_is_set = FALSE;
+ r->sink = NULL;
+ r->source = pa_xstrdup(so->source->name);
+ }
+
+ pa_hashmap_put(u->hashmap, r->name, r);
+ u->modified = TRUE;
+ }
+
+ if (u->modified && !u->save_time_event) {
+ struct timeval tv;
+ pa_gettimeofday(&tv);
+ tv.tv_sec += SAVE_INTERVAL;
+ u->save_time_event = u->core->mainloop->time_new(u->core->mainloop, &tv, save_time_callback, u);
+ }
+}
+
+static pa_hook_result_t sink_input_new_hook_callback(pa_core *c, pa_sink_input_new_data *data, struct userdata *u) {
+ struct rule *r;
+ char *name;
+
+ pa_assert(data);
+
+ /* In the NEW hook we only adjust the device. Adjusting the volume
+ * is left for the FIXATE hook */
+
+ if (!data->client || !(name = client_name(data->client)))
+ return PA_HOOK_OK;
+
+ if ((r = pa_hashmap_get(u->hashmap, name))) {
+ if (!data->sink && r->sink) {
+ if ((data->sink = pa_namereg_get(c, r->sink, PA_NAMEREG_SINK, 1)))
+ pa_log_info("Restoring sink for <%s>", r->name);
+ }
+ }
+
+ pa_xfree(name);
+
+ return PA_HOOK_OK;
+}
+
+static pa_hook_result_t sink_input_fixate_hook_callback(pa_core *c, pa_sink_input_new_data *data, struct userdata *u) {
+ struct rule *r;
+ char *name;
+
+ pa_assert(data);
+
+ /* In the FIXATE hook we only adjust the volum. Adjusting the device
+ * is left for the NEW hook */
+
+ if (!data->client || !(name = client_name(data->client)))
+ return PA_HOOK_OK;
+
+ if ((r = pa_hashmap_get(u->hashmap, name))) {
+
+ if (r->volume_is_set && data->sample_spec.channels == r->volume.channels) {
+ pa_log_info("Restoring volume for <%s>", r->name);
+ pa_sink_input_new_data_set_volume(data, &r->volume);
+ }
+ }
+
+ pa_xfree(name);
+
+ return PA_HOOK_OK;
+}
+
+static pa_hook_result_t source_output_new_hook_callback(pa_core *c, pa_source_output_new_data *data, struct userdata *u) {
+ struct rule *r;
+ char *name;
+
+ pa_assert(data);
+
+ if (!data->client || !(name = client_name(data->client)))
+ return PA_HOOK_OK;
+
+ if ((r = pa_hashmap_get(u->hashmap, name))) {
+ if (!data->source && r->source) {
+ if ((data->source = pa_namereg_get(c, r->source, PA_NAMEREG_SOURCE, 1)))
+ pa_log_info("Restoring source for <%s>", r->name);
+ }
+ }
+
+ return PA_HOOK_OK;
+}
+
+int pa__init(pa_module*m) {
+ pa_modargs *ma = NULL;
+ struct userdata *u;
+ pa_bool_t restore_device = TRUE, restore_volume = TRUE;
+
+ pa_assert(m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log("Failed to parse module arguments");
+ goto fail;
+ }
+
+ u = pa_xnew(struct userdata, 1);
+ u->core = m->core;
+ u->hashmap = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+ u->table_file = pa_xstrdup(pa_modargs_get_value(ma, "table", NULL));
+ u->modified = FALSE;
+ u->subscription = NULL;
+ u->sink_input_new_hook_slot = u->sink_input_fixate_hook_slot = u->source_output_new_hook_slot = NULL;
+ u->save_time_event = NULL;
+
+ m->userdata = u;
+
+ if (pa_modargs_get_value_boolean(ma, "restore_device", &restore_device) < 0 ||
+ pa_modargs_get_value_boolean(ma, "restore_volume", &restore_volume) < 0) {
+ pa_log("restore_volume= and restore_device= expect boolean arguments");
+ goto fail;
+ }
+
+ if (!(restore_device || restore_volume)) {
+ pa_log("Both restrong the volume and restoring the device are disabled. There's no point in using this module at all then, failing.");
+ goto fail;
+ }
+
+ if (load_rules(u) < 0)
+ goto fail;
+
+ u->subscription = pa_subscription_new(m->core, PA_SUBSCRIPTION_MASK_SINK_INPUT|PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT, subscribe_callback, u);
+
+ if (restore_device) {
+ u->sink_input_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_NEW], (pa_hook_cb_t) sink_input_new_hook_callback, u);
+ u->source_output_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_NEW], (pa_hook_cb_t) source_output_new_hook_callback, u);
+ }
+
+ if (restore_volume)
+ u->sink_input_fixate_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_FIXATE], (pa_hook_cb_t) sink_input_fixate_hook_callback, u);
+
+ pa_modargs_free(ma);
+ return 0;
+
+fail:
+ pa__done(m);
+ if (ma)
+ pa_modargs_free(ma);
+
+ return -1;
+}
+
+static void free_func(void *p, void *userdata) {
+ struct rule *r = p;
+ pa_assert(r);
+
+ pa_xfree(r->name);
+ pa_xfree(r->sink);
+ pa_xfree(r->source);
+ pa_xfree(r);
+}
+
+void pa__done(pa_module*m) {
+ struct userdata* u;
+
+ pa_assert(m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->subscription)
+ pa_subscription_free(u->subscription);
+
+ if (u->sink_input_new_hook_slot)
+ pa_hook_slot_free(u->sink_input_new_hook_slot);
+ if (u->sink_input_fixate_hook_slot)
+ pa_hook_slot_free(u->sink_input_fixate_hook_slot);
+ if (u->source_output_new_hook_slot)
+ pa_hook_slot_free(u->source_output_new_hook_slot);
+
+ if (u->hashmap) {
+ save_rules(u);
+ pa_hashmap_free(u->hashmap, free_func, NULL);
+ }
+
+ if (u->save_time_event)
+ u->core->mainloop->time_free(u->save_time_event);
+
+ pa_xfree(u->table_file);
+ pa_xfree(u);
+}
diff --git a/src/modules/module-waveout.c b/src/modules/module-waveout.c
new file mode 100644
index 00000000..f8bae02f
--- /dev/null
+++ b/src/modules/module-waveout.c
@@ -0,0 +1,649 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <windows.h>
+#include <mmsystem.h>
+
+#include <pulse/mainloop-api.h>
+
+#include <pulse/xmalloc.h>
+#include <pulse/timeval.h>
+
+#include <pulsecore/sink.h>
+#include <pulsecore/source.h>
+#include <pulsecore/module.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/sample-util.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/log.h>
+
+#include "module-waveout-symdef.h"
+
+PA_MODULE_AUTHOR("Pierre Ossman")
+PA_MODULE_DESCRIPTION("Windows waveOut Sink/Source")
+PA_MODULE_VERSION(PACKAGE_VERSION)
+PA_MODULE_USAGE(
+ "sink_name=<name for the sink> "
+ "source_name=<name for the source> "
+ "device=<device number> "
+ "record=<enable source?> "
+ "playback=<enable sink?> "
+ "format=<sample format> "
+ "channels=<number of channels> "
+ "rate=<sample rate> "
+ "fragments=<number of fragments> "
+ "fragment_size=<fragment size> "
+ "channel_map=<channel map>")
+
+#define DEFAULT_SINK_NAME "wave_output"
+#define DEFAULT_SOURCE_NAME "wave_input"
+
+#define WAVEOUT_MAX_VOLUME 0xFFFF
+
+struct userdata {
+ pa_sink *sink;
+ pa_source *source;
+ pa_core *core;
+ pa_time_event *event;
+ pa_defer_event *defer;
+ pa_usec_t poll_timeout;
+
+ uint32_t fragments, fragment_size;
+
+ uint32_t free_ofrags, free_ifrags;
+
+ DWORD written_bytes;
+ int sink_underflow;
+
+ int cur_ohdr, cur_ihdr;
+ WAVEHDR *ohdrs, *ihdrs;
+
+ HWAVEOUT hwo;
+ HWAVEIN hwi;
+ pa_module *module;
+
+ CRITICAL_SECTION crit;
+};
+
+static const char* const valid_modargs[] = {
+ "sink_name",
+ "source_name",
+ "device",
+ "record",
+ "playback",
+ "fragments",
+ "fragment_size",
+ "format",
+ "rate",
+ "channels",
+ "channel_map",
+ NULL
+};
+
+static void update_usage(struct userdata *u) {
+ pa_module_set_used(u->module,
+ (u->sink ? pa_sink_used_by(u->sink) : 0) +
+ (u->source ? pa_source_used_by(u->source) : 0));
+}
+
+static void do_write(struct userdata *u)
+{
+ uint32_t free_frags;
+ pa_memchunk memchunk;
+ WAVEHDR *hdr;
+ MMRESULT res;
+
+ if (!u->sink)
+ return;
+
+ EnterCriticalSection(&u->crit);
+ free_frags = u->free_ofrags;
+ LeaveCriticalSection(&u->crit);
+
+ if (!u->sink_underflow && (free_frags == u->fragments))
+ pa_log_debug("WaveOut underflow!");
+
+ while (free_frags) {
+ hdr = &u->ohdrs[u->cur_ohdr];
+ if (hdr->dwFlags & WHDR_PREPARED)
+ waveOutUnprepareHeader(u->hwo, hdr, sizeof(WAVEHDR));
+
+ hdr->dwBufferLength = 0;
+ while (hdr->dwBufferLength < u->fragment_size) {
+ size_t len;
+
+ len = u->fragment_size - hdr->dwBufferLength;
+
+ if (pa_sink_render(u->sink, len, &memchunk) < 0)
+ break;
+
+ assert(memchunk.memblock);
+ assert(memchunk.memblock->data);
+ assert(memchunk.length);
+
+ if (memchunk.length < len)
+ len = memchunk.length;
+
+ memcpy(hdr->lpData + hdr->dwBufferLength,
+ (char*)memchunk.memblock->data + memchunk.index, len);
+
+ hdr->dwBufferLength += len;
+
+ pa_memblock_unref(memchunk.memblock);
+ memchunk.memblock = NULL;
+ }
+
+ /* Insufficient data in sink buffer? */
+ if (hdr->dwBufferLength == 0) {
+ u->sink_underflow = 1;
+ break;
+ }
+
+ u->sink_underflow = 0;
+
+ res = waveOutPrepareHeader(u->hwo, hdr, sizeof(WAVEHDR));
+ if (res != MMSYSERR_NOERROR) {
+ pa_log_error(__FILE__ ": ERROR: Unable to prepare waveOut block: %d",
+ res);
+ }
+ res = waveOutWrite(u->hwo, hdr, sizeof(WAVEHDR));
+ if (res != MMSYSERR_NOERROR) {
+ pa_log_error(__FILE__ ": ERROR: Unable to write waveOut block: %d",
+ res);
+ }
+
+ u->written_bytes += hdr->dwBufferLength;
+
+ EnterCriticalSection(&u->crit);
+ u->free_ofrags--;
+ LeaveCriticalSection(&u->crit);
+
+ free_frags--;
+ u->cur_ohdr++;
+ u->cur_ohdr %= u->fragments;
+ }
+}
+
+static void do_read(struct userdata *u)
+{
+ uint32_t free_frags;
+ pa_memchunk memchunk;
+ WAVEHDR *hdr;
+ MMRESULT res;
+
+ if (!u->source)
+ return;
+
+ EnterCriticalSection(&u->crit);
+
+ free_frags = u->free_ifrags;
+ u->free_ifrags = 0;
+
+ LeaveCriticalSection(&u->crit);
+
+ if (free_frags == u->fragments)
+ pa_log_debug("WaveIn overflow!");
+
+ while (free_frags) {
+ hdr = &u->ihdrs[u->cur_ihdr];
+ if (hdr->dwFlags & WHDR_PREPARED)
+ waveInUnprepareHeader(u->hwi, hdr, sizeof(WAVEHDR));
+
+ if (hdr->dwBytesRecorded) {
+ memchunk.memblock = pa_memblock_new(u->core->mempool, hdr->dwBytesRecorded);
+ assert(memchunk.memblock);
+
+ memcpy((char*)memchunk.memblock->data, hdr->lpData, hdr->dwBytesRecorded);
+
+ memchunk.length = memchunk.memblock->length = hdr->dwBytesRecorded;
+ memchunk.index = 0;
+
+ pa_source_post(u->source, &memchunk);
+ pa_memblock_unref(memchunk.memblock);
+ }
+
+ res = waveInPrepareHeader(u->hwi, hdr, sizeof(WAVEHDR));
+ if (res != MMSYSERR_NOERROR) {
+ pa_log_error(__FILE__ ": ERROR: Unable to prepare waveIn block: %d",
+ res);
+ }
+ res = waveInAddBuffer(u->hwi, hdr, sizeof(WAVEHDR));
+ if (res != MMSYSERR_NOERROR) {
+ pa_log_error(__FILE__ ": ERROR: Unable to add waveIn block: %d",
+ res);
+ }
+
+ free_frags--;
+ u->cur_ihdr++;
+ u->cur_ihdr %= u->fragments;
+ }
+}
+
+static void poll_cb(pa_mainloop_api*a, pa_time_event *e, const struct timeval *tv, void *userdata) {
+ struct userdata *u = userdata;
+ struct timeval ntv;
+
+ assert(u);
+
+ update_usage(u);
+
+ do_write(u);
+ do_read(u);
+
+ pa_gettimeofday(&ntv);
+ pa_timeval_add(&ntv, u->poll_timeout);
+
+ a->time_restart(e, &ntv);
+}
+
+static void defer_cb(pa_mainloop_api*a, pa_defer_event *e, void *userdata) {
+ struct userdata *u = userdata;
+
+ assert(u);
+
+ a->defer_enable(e, 0);
+
+ do_write(u);
+ do_read(u);
+}
+
+static void CALLBACK chunk_done_cb(HWAVEOUT hwo, UINT msg, DWORD_PTR inst, DWORD param1, DWORD param2) {
+ struct userdata *u = (struct userdata *)inst;
+
+ if (msg != WOM_DONE)
+ return;
+
+ EnterCriticalSection(&u->crit);
+
+ u->free_ofrags++;
+ assert(u->free_ofrags <= u->fragments);
+
+ LeaveCriticalSection(&u->crit);
+}
+
+static void CALLBACK chunk_ready_cb(HWAVEIN hwi, UINT msg, DWORD_PTR inst, DWORD param1, DWORD param2) {
+ struct userdata *u = (struct userdata *)inst;
+
+ if (msg != WIM_DATA)
+ return;
+
+ EnterCriticalSection(&u->crit);
+
+ u->free_ifrags++;
+ assert(u->free_ifrags <= u->fragments);
+
+ LeaveCriticalSection(&u->crit);
+}
+
+static pa_usec_t sink_get_latency_cb(pa_sink *s) {
+ struct userdata *u = s->userdata;
+ uint32_t free_frags;
+ MMTIME mmt;
+ assert(s && u && u->sink);
+
+ memset(&mmt, 0, sizeof(mmt));
+ mmt.wType = TIME_BYTES;
+ if (waveOutGetPosition(u->hwo, &mmt, sizeof(mmt)) == MMSYSERR_NOERROR)
+ return pa_bytes_to_usec(u->written_bytes - mmt.u.cb, &s->sample_spec);
+ else {
+ EnterCriticalSection(&u->crit);
+
+ free_frags = u->free_ofrags;
+
+ LeaveCriticalSection(&u->crit);
+
+ return pa_bytes_to_usec((u->fragments - free_frags) * u->fragment_size,
+ &s->sample_spec);
+ }
+}
+
+static pa_usec_t source_get_latency_cb(pa_source *s) {
+ pa_usec_t r = 0;
+ struct userdata *u = s->userdata;
+ uint32_t free_frags;
+ assert(s && u && u->sink);
+
+ EnterCriticalSection(&u->crit);
+
+ free_frags = u->free_ifrags;
+
+ LeaveCriticalSection(&u->crit);
+
+ r += pa_bytes_to_usec((free_frags + 1) * u->fragment_size, &s->sample_spec);
+
+ return r;
+}
+
+static void notify_sink_cb(pa_sink *s) {
+ struct userdata *u = s->userdata;
+ assert(u);
+
+ u->core->mainloop->defer_enable(u->defer, 1);
+}
+
+static void notify_source_cb(pa_source *s) {
+ struct userdata *u = s->userdata;
+ assert(u);
+
+ u->core->mainloop->defer_enable(u->defer, 1);
+}
+
+static int sink_get_hw_volume_cb(pa_sink *s) {
+ struct userdata *u = s->userdata;
+ DWORD vol;
+ pa_volume_t left, right;
+
+ if (waveOutGetVolume(u->hwo, &vol) != MMSYSERR_NOERROR)
+ return -1;
+
+ left = (vol & 0xFFFF) * PA_VOLUME_NORM / WAVEOUT_MAX_VOLUME;
+ right = ((vol >> 16) & 0xFFFF) * PA_VOLUME_NORM / WAVEOUT_MAX_VOLUME;
+
+ /* Windows supports > 2 channels, except for volume control */
+ if (s->hw_volume.channels > 2)
+ pa_cvolume_set(&s->hw_volume, s->hw_volume.channels, (left + right)/2);
+
+ s->hw_volume.values[0] = left;
+ if (s->hw_volume.channels > 1)
+ s->hw_volume.values[1] = right;
+
+ return 0;
+}
+
+static int sink_set_hw_volume_cb(pa_sink *s) {
+ struct userdata *u = s->userdata;
+ DWORD vol;
+
+ vol = s->hw_volume.values[0] * WAVEOUT_MAX_VOLUME / PA_VOLUME_NORM;
+ if (s->hw_volume.channels > 1)
+ vol |= (s->hw_volume.values[0] * WAVEOUT_MAX_VOLUME / PA_VOLUME_NORM) << 16;
+
+ if (waveOutSetVolume(u->hwo, vol) != MMSYSERR_NOERROR)
+ return -1;
+
+ return 0;
+}
+
+static int ss_to_waveformat(pa_sample_spec *ss, LPWAVEFORMATEX wf) {
+ wf->wFormatTag = WAVE_FORMAT_PCM;
+
+ if (ss->channels > 2) {
+ pa_log_error("ERROR: More than two channels not supported.");
+ return -1;
+ }
+
+ wf->nChannels = ss->channels;
+
+ switch (ss->rate) {
+ case 8000:
+ case 11025:
+ case 22005:
+ case 44100:
+ break;
+ default:
+ pa_log_error("ERROR: Unsupported sample rate.");
+ return -1;
+ }
+
+ wf->nSamplesPerSec = ss->rate;
+
+ if (ss->format == PA_SAMPLE_U8)
+ wf->wBitsPerSample = 8;
+ else if (ss->format == PA_SAMPLE_S16NE)
+ wf->wBitsPerSample = 16;
+ else {
+ pa_log_error("ERROR: Unsupported sample format.");
+ return -1;
+ }
+
+ wf->nBlockAlign = wf->nChannels * wf->wBitsPerSample/8;
+ wf->nAvgBytesPerSec = wf->nSamplesPerSec * wf->nBlockAlign;
+
+ wf->cbSize = 0;
+
+ return 0;
+}
+
+int pa__init(pa_core *c, pa_module*m) {
+ struct userdata *u = NULL;
+ HWAVEOUT hwo = INVALID_HANDLE_VALUE;
+ HWAVEIN hwi = INVALID_HANDLE_VALUE;
+ WAVEFORMATEX wf;
+ int nfrags, frag_size;
+ int record = 1, playback = 1;
+ unsigned int device;
+ pa_sample_spec ss;
+ pa_channel_map map;
+ pa_modargs *ma = NULL;
+ unsigned int i;
+ struct timeval tv;
+
+ assert(c && m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log("failed to parse module arguments.");
+ goto fail;
+ }
+
+ if (pa_modargs_get_value_boolean(ma, "record", &record) < 0 || pa_modargs_get_value_boolean(ma, "playback", &playback) < 0) {
+ pa_log("record= and playback= expect boolean argument.");
+ goto fail;
+ }
+
+ if (!playback && !record) {
+ pa_log("neither playback nor record enabled for device.");
+ goto fail;
+ }
+
+ device = WAVE_MAPPER;
+ if (pa_modargs_get_value_u32(ma, "device", &device) < 0) {
+ pa_log("failed to parse device argument");
+ goto fail;
+ }
+
+ nfrags = 5;
+ frag_size = 8192;
+ if (pa_modargs_get_value_s32(ma, "fragments", &nfrags) < 0 || pa_modargs_get_value_s32(ma, "fragment_size", &frag_size) < 0) {
+ pa_log("failed to parse fragments arguments");
+ goto fail;
+ }
+
+ ss = c->default_sample_spec;
+ if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_WAVEEX) < 0) {
+ pa_log("failed to parse sample specification");
+ goto fail;
+ }
+
+ if (ss_to_waveformat(&ss, &wf) < 0)
+ goto fail;
+
+ u = pa_xmalloc(sizeof(struct userdata));
+
+ if (record) {
+ if (waveInOpen(&hwi, device, &wf, (DWORD_PTR)chunk_ready_cb, (DWORD_PTR)u, CALLBACK_FUNCTION) != MMSYSERR_NOERROR) {
+ pa_log("failed to open waveIn");
+ goto fail;
+ }
+ if (waveInStart(hwi) != MMSYSERR_NOERROR) {
+ pa_log("failed to start waveIn");
+ goto fail;
+ }
+ pa_log_debug("Opened waveIn subsystem.");
+ }
+
+ if (playback) {
+ if (waveOutOpen(&hwo, device, &wf, (DWORD_PTR)chunk_done_cb, (DWORD_PTR)u, CALLBACK_FUNCTION) != MMSYSERR_NOERROR) {
+ pa_log("failed to open waveOut");
+ goto fail;
+ }
+ pa_log_debug("Opened waveOut subsystem.");
+ }
+
+ InitializeCriticalSection(&u->crit);
+
+ if (hwi != INVALID_HANDLE_VALUE) {
+ u->source = pa_source_new(c, __FILE__, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &ss, &map);
+ assert(u->source);
+ u->source->userdata = u;
+ u->source->notify = notify_source_cb;
+ u->source->get_latency = source_get_latency_cb;
+ pa_source_set_owner(u->source, m);
+ pa_source_set_description(u->source, "Windows waveIn PCM");
+ u->source->is_hardware = 1;
+ } else
+ u->source = NULL;
+
+ if (hwo != INVALID_HANDLE_VALUE) {
+ u->sink = pa_sink_new(c, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, &map);
+ assert(u->sink);
+ u->sink->notify = notify_sink_cb;
+ u->sink->get_latency = sink_get_latency_cb;
+ u->sink->get_hw_volume = sink_get_hw_volume_cb;
+ u->sink->set_hw_volume = sink_set_hw_volume_cb;
+ u->sink->userdata = u;
+ pa_sink_set_owner(u->sink, m);
+ pa_sink_set_description(u->sink, "Windows waveOut PCM");
+ u->sink->is_hardware = 1;
+ } else
+ u->sink = NULL;
+
+ assert(u->source || u->sink);
+
+ u->core = c;
+ u->hwi = hwi;
+ u->hwo = hwo;
+
+ u->fragments = nfrags;
+ u->free_ifrags = u->fragments;
+ u->free_ofrags = u->fragments;
+ u->fragment_size = frag_size - (frag_size % pa_frame_size(&ss));
+
+ u->written_bytes = 0;
+ u->sink_underflow = 1;
+
+ u->poll_timeout = pa_bytes_to_usec(u->fragments * u->fragment_size / 10, &ss);
+
+ pa_gettimeofday(&tv);
+ pa_timeval_add(&tv, u->poll_timeout);
+
+ u->event = c->mainloop->time_new(c->mainloop, &tv, poll_cb, u);
+ assert(u->event);
+
+ u->defer = c->mainloop->defer_new(c->mainloop, defer_cb, u);
+ assert(u->defer);
+ c->mainloop->defer_enable(u->defer, 0);
+
+ u->cur_ihdr = 0;
+ u->cur_ohdr = 0;
+ u->ihdrs = pa_xmalloc0(sizeof(WAVEHDR) * u->fragments);
+ assert(u->ihdrs);
+ u->ohdrs = pa_xmalloc0(sizeof(WAVEHDR) * u->fragments);
+ assert(u->ohdrs);
+ for (i = 0;i < u->fragments;i++) {
+ u->ihdrs[i].dwBufferLength = u->fragment_size;
+ u->ohdrs[i].dwBufferLength = u->fragment_size;
+ u->ihdrs[i].lpData = pa_xmalloc(u->fragment_size);
+ assert(u->ihdrs);
+ u->ohdrs[i].lpData = pa_xmalloc(u->fragment_size);
+ assert(u->ohdrs);
+ }
+
+ u->module = m;
+ m->userdata = u;
+
+ pa_modargs_free(ma);
+
+ /* Read mixer settings */
+ if (u->sink)
+ sink_get_hw_volume_cb(u->sink);
+
+ return 0;
+
+fail:
+ if (hwi != INVALID_HANDLE_VALUE)
+ waveInClose(hwi);
+
+ if (hwo != INVALID_HANDLE_VALUE)
+ waveOutClose(hwo);
+
+ if (u)
+ pa_xfree(u);
+
+ if (ma)
+ pa_modargs_free(ma);
+
+ return -1;
+}
+
+void pa__done(pa_core *c, pa_module*m) {
+ struct userdata *u;
+ unsigned int i;
+
+ assert(c && m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->event)
+ c->mainloop->time_free(u->event);
+
+ if (u->defer)
+ c->mainloop->defer_free(u->defer);
+
+ if (u->sink) {
+ pa_sink_disconnect(u->sink);
+ pa_sink_unref(u->sink);
+ }
+
+ if (u->source) {
+ pa_source_disconnect(u->source);
+ pa_source_unref(u->source);
+ }
+
+ if (u->hwi != INVALID_HANDLE_VALUE) {
+ waveInReset(u->hwi);
+ waveInClose(u->hwi);
+ }
+
+ if (u->hwo != INVALID_HANDLE_VALUE) {
+ waveOutReset(u->hwo);
+ waveOutClose(u->hwo);
+ }
+
+ for (i = 0;i < u->fragments;i++) {
+ pa_xfree(u->ihdrs[i].lpData);
+ pa_xfree(u->ohdrs[i].lpData);
+ }
+
+ pa_xfree(u->ihdrs);
+ pa_xfree(u->ohdrs);
+
+ DeleteCriticalSection(&u->crit);
+
+ pa_xfree(u);
+}
diff --git a/src/modules/module-x11-bell.c b/src/modules/module-x11-bell.c
new file mode 100644
index 00000000..87c6849d
--- /dev/null
+++ b/src/modules/module-x11-bell.c
@@ -0,0 +1,171 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <X11/Xlib.h>
+#include <X11/XKBlib.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/iochannel.h>
+#include <pulsecore/sink.h>
+#include <pulsecore/core-scache.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/namereg.h>
+#include <pulsecore/log.h>
+#include <pulsecore/x11wrap.h>
+
+#include "module-x11-bell-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering");
+PA_MODULE_DESCRIPTION("X11 Bell interceptor");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(FALSE);
+PA_MODULE_USAGE("sink=<sink to connect to> sample=<sample name> display=<X11 display>");
+
+struct userdata {
+ pa_core *core;
+ int xkb_event_base;
+ char *sink_name;
+ char *scache_item;
+
+ pa_x11_wrapper *x11_wrapper;
+ pa_x11_client *x11_client;
+};
+
+static const char* const valid_modargs[] = {
+ "sink",
+ "sample",
+ "display",
+ NULL
+};
+
+static int x11_event_callback(pa_x11_wrapper *w, XEvent *e, void *userdata) {
+ XkbBellNotifyEvent *bne;
+ struct userdata *u = userdata;
+
+ pa_assert(w);
+ pa_assert(e);
+ pa_assert(u);
+ pa_assert(u->x11_wrapper == w);
+
+ if (((XkbEvent*) e)->any.xkb_type != XkbBellNotify)
+ return 0;
+
+ bne = (XkbBellNotifyEvent*) e;
+
+ if (pa_scache_play_item_by_name(u->core, u->scache_item, u->sink_name, (bne->percent*PA_VOLUME_NORM)/100, 1) < 0) {
+ pa_log_info("Ringing bell failed, reverting to X11 device bell.");
+ XkbForceDeviceBell(pa_x11_wrapper_get_display(w), bne->device, bne->bell_class, bne->bell_id, bne->percent);
+ }
+
+ return 1;
+}
+
+int pa__init(pa_module*m) {
+
+ struct userdata *u = NULL;
+ pa_modargs *ma = NULL;
+ int major, minor;
+ unsigned int auto_ctrls, auto_values;
+
+ pa_assert(m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log("Failed to parse module arguments");
+ goto fail;
+ }
+
+ m->userdata = u = pa_xnew(struct userdata, 1);
+ u->core = m->core;
+ u->scache_item = pa_xstrdup(pa_modargs_get_value(ma, "sample", "x11-bell"));
+ u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL));
+ u->x11_client = NULL;
+
+ if (!(u->x11_wrapper = pa_x11_wrapper_get(m->core, pa_modargs_get_value(ma, "display", NULL))))
+ goto fail;
+
+ major = XkbMajorVersion;
+ minor = XkbMinorVersion;
+
+ if (!XkbLibraryVersion(&major, &minor)) {
+ pa_log("XkbLibraryVersion() failed");
+ goto fail;
+ }
+
+ major = XkbMajorVersion;
+ minor = XkbMinorVersion;
+
+ if (!XkbQueryExtension(pa_x11_wrapper_get_display(u->x11_wrapper), NULL, &u->xkb_event_base, NULL, &major, &minor)) {
+ pa_log("XkbQueryExtension() failed");
+ goto fail;
+ }
+
+ XkbSelectEvents(pa_x11_wrapper_get_display(u->x11_wrapper), XkbUseCoreKbd, XkbBellNotifyMask, XkbBellNotifyMask);
+ auto_ctrls = auto_values = XkbAudibleBellMask;
+ XkbSetAutoResetControls(pa_x11_wrapper_get_display(u->x11_wrapper), XkbAudibleBellMask, &auto_ctrls, &auto_values);
+ XkbChangeEnabledControls(pa_x11_wrapper_get_display(u->x11_wrapper), XkbUseCoreKbd, XkbAudibleBellMask, 0);
+
+ u->x11_client = pa_x11_client_new(u->x11_wrapper, x11_event_callback, u);
+
+ pa_modargs_free(ma);
+
+ return 0;
+
+fail:
+ if (ma)
+ pa_modargs_free(ma);
+
+ pa__done(m);
+
+ return -1;
+}
+
+void pa__done(pa_module*m) {
+ struct userdata *u;
+
+ pa_assert(m);
+
+ if (!m->userdata)
+ return;
+
+ u = m->userdata;
+
+ pa_xfree(u->scache_item);
+ pa_xfree(u->sink_name);
+
+ if (u->x11_client)
+ pa_x11_client_free(u->x11_client);
+
+ if (u->x11_wrapper)
+ pa_x11_wrapper_unref(u->x11_wrapper);
+
+ pa_xfree(u);
+}
diff --git a/src/modules/module-x11-publish.c b/src/modules/module-x11-publish.c
new file mode 100644
index 00000000..429c2a69
--- /dev/null
+++ b/src/modules/module-x11-publish.c
@@ -0,0 +1,198 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+
+#include <pulse/util.h>
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/module.h>
+#include <pulsecore/sink.h>
+#include <pulsecore/core-scache.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/namereg.h>
+#include <pulsecore/log.h>
+#include <pulsecore/x11wrap.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/native-common.h>
+#include <pulsecore/authkey-prop.h>
+#include <pulsecore/authkey.h>
+#include <pulsecore/x11prop.h>
+#include <pulsecore/strlist.h>
+#include <pulsecore/props.h>
+
+#include "module-x11-publish-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering");
+PA_MODULE_DESCRIPTION("X11 Credential Publisher");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(FALSE);
+PA_MODULE_USAGE("display=<X11 display>");
+
+static const char* const valid_modargs[] = {
+ "display",
+ "sink",
+ "source",
+ "cookie",
+ NULL
+};
+
+struct userdata {
+ pa_core *core;
+ pa_x11_wrapper *x11_wrapper;
+ char *id;
+ uint8_t auth_cookie[PA_NATIVE_COOKIE_LENGTH];
+ int auth_cookie_in_property;
+};
+
+static int load_key(struct userdata *u, const char*fn) {
+ pa_assert(u);
+
+ u->auth_cookie_in_property = 0;
+
+ if (!fn && pa_authkey_prop_get(u->core, PA_NATIVE_COOKIE_PROPERTY_NAME, u->auth_cookie, sizeof(u->auth_cookie)) >= 0) {
+ pa_log_debug("using already loaded auth cookie.");
+ pa_authkey_prop_ref(u->core, PA_NATIVE_COOKIE_PROPERTY_NAME);
+ u->auth_cookie_in_property = 1;
+ return 0;
+ }
+
+ if (!fn)
+ fn = PA_NATIVE_COOKIE_FILE;
+
+ if (pa_authkey_load_auto(fn, u->auth_cookie, sizeof(u->auth_cookie)) < 0)
+ return -1;
+
+ pa_log_debug("Loading cookie from disk.");
+
+ if (pa_authkey_prop_put(u->core, PA_NATIVE_COOKIE_PROPERTY_NAME, u->auth_cookie, sizeof(u->auth_cookie)) >= 0)
+ u->auth_cookie_in_property = 1;
+
+ return 0;
+}
+
+int pa__init(pa_module*m) {
+ struct userdata *u;
+ pa_modargs *ma = NULL;
+ char hn[256], un[128];
+ char hx[PA_NATIVE_COOKIE_LENGTH*2+1];
+ const char *t;
+ char *s;
+ pa_strlist *l;
+
+ pa_assert(m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log("failed to parse module arguments");
+ goto fail;
+ }
+
+ m->userdata = u = pa_xmalloc(sizeof(struct userdata));
+ u->core = m->core;
+ u->id = NULL;
+ u->auth_cookie_in_property = 0;
+
+ if (load_key(u, pa_modargs_get_value(ma, "cookie", NULL)) < 0)
+ goto fail;
+
+ if (!(u->x11_wrapper = pa_x11_wrapper_get(m->core, pa_modargs_get_value(ma, "display", NULL))))
+ goto fail;
+
+ if (!(l = pa_property_get(m->core, PA_NATIVE_SERVER_PROPERTY_NAME)))
+ goto fail;
+
+ l = pa_strlist_reverse(l);
+ s = pa_strlist_tostring(l);
+ l = pa_strlist_reverse(l);
+
+ pa_x11_set_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_SERVER", s);
+ pa_xfree(s);
+
+ if (!pa_get_fqdn(hn, sizeof(hn)) || !pa_get_user_name(un, sizeof(un)))
+ goto fail;
+
+ u->id = pa_sprintf_malloc("%s@%s/%u", un, hn, (unsigned) getpid());
+ pa_x11_set_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_ID", u->id);
+
+ if ((t = pa_modargs_get_value(ma, "source", NULL)))
+ pa_x11_set_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_SOURCE", t);
+
+ if ((t = pa_modargs_get_value(ma, "sink", NULL)))
+ pa_x11_set_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_SINK", t);
+
+ pa_x11_set_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_COOKIE", pa_hexstr(u->auth_cookie, sizeof(u->auth_cookie), hx, sizeof(hx)));
+
+ pa_modargs_free(ma);
+ return 0;
+
+fail:
+ if (ma)
+ pa_modargs_free(ma);
+
+ pa__done(m);
+ return -1;
+}
+
+void pa__done(pa_module*m) {
+ struct userdata*u;
+
+ pa_assert(m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->x11_wrapper) {
+ char t[256];
+
+ /* Yes, here is a race condition */
+ if (!pa_x11_get_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_ID", t, sizeof(t)) || strcmp(t, u->id))
+ pa_log_warn("PulseAudio information vanished from X11!");
+ else {
+ pa_x11_del_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_ID");
+ pa_x11_del_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_SERVER");
+ pa_x11_del_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_SINK");
+ pa_x11_del_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_SOURCE");
+ pa_x11_del_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_COOKIE");
+ XSync(pa_x11_wrapper_get_display(u->x11_wrapper), False);
+ }
+ }
+
+ if (u->x11_wrapper)
+ pa_x11_wrapper_unref(u->x11_wrapper);
+
+ if (u->auth_cookie_in_property)
+ pa_authkey_prop_unref(m->core, PA_NATIVE_COOKIE_PROPERTY_NAME);
+
+ pa_xfree(u->id);
+ pa_xfree(u);
+}
diff --git a/src/modules/module-x11-xsmp.c b/src/modules/module-x11-xsmp.c
new file mode 100644
index 00000000..e9efa096
--- /dev/null
+++ b/src/modules/module-x11-xsmp.c
@@ -0,0 +1,196 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <X11/Xlib.h>
+#include <X11/SM/SMlib.h>
+
+#include <pulse/xmalloc.h>
+#include <pulse/util.h>
+
+#include <pulsecore/iochannel.h>
+#include <pulsecore/sink.h>
+#include <pulsecore/core-scache.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/namereg.h>
+#include <pulsecore/log.h>
+#include <pulsecore/core-util.h>
+
+#include "module-x11-xsmp-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering");
+PA_MODULE_DESCRIPTION("X11 session management");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(TRUE);
+
+static int ice_in_use = 0;
+
+static const char* const valid_modargs[] = {
+ NULL
+};
+
+static void die_cb(SmcConn connection, SmPointer client_data){
+ pa_core *c = PA_CORE(client_data);
+
+ pa_log_debug("Got die message from XSM. Exiting...");
+
+ pa_core_assert_ref(c);
+ c->mainloop->quit(c->mainloop, 0);
+}
+
+static void save_complete_cb(SmcConn connection, SmPointer client_data) {
+}
+
+static void shutdown_cancelled_cb(SmcConn connection, SmPointer client_data) {
+ SmcSaveYourselfDone(connection, True);
+}
+
+static void save_yourself_cb(SmcConn connection, SmPointer client_data, int save_type, Bool _shutdown, int interact_style, Bool fast) {
+ SmcSaveYourselfDone(connection, True);
+}
+
+static void ice_io_cb(pa_mainloop_api*a, pa_io_event *e, int fd, pa_io_event_flags_t flags, void *userdata) {
+ IceConn connection = userdata;
+
+ if (IceProcessMessages(connection, NULL, NULL) == IceProcessMessagesIOError) {
+ IceSetShutdownNegotiation(connection, False);
+ IceCloseConnection(connection);
+ }
+}
+
+static void new_ice_connection(IceConn connection, IcePointer client_data, Bool opening, IcePointer *watch_data) {
+ pa_core *c = client_data;
+
+ pa_assert(c);
+
+ if (opening)
+ *watch_data = c->mainloop->io_new(c->mainloop, IceConnectionNumber(connection), PA_IO_EVENT_INPUT, ice_io_cb, connection);
+ else
+ c->mainloop->io_free(*watch_data);
+}
+
+int pa__init(pa_module*m) {
+
+ pa_modargs *ma = NULL;
+ char t[256], *vendor, *client_id;
+ SmcCallbacks callbacks;
+ SmProp prop_program, prop_user;
+ SmProp *prop_list[2];
+ SmPropValue val_program, val_user;
+ SmcConn connection;
+
+ pa_assert(m);
+
+ if (ice_in_use) {
+ pa_log("module-x11-xsmp may no be loaded twice.");
+ return -1;
+ }
+
+ IceAddConnectionWatch(new_ice_connection, m->core);
+ ice_in_use = 1;
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log("Failed to parse module arguments");
+ goto fail;
+ }
+
+ if (!getenv("SESSION_MANAGER")) {
+ pa_log("X11 session manager not running.");
+ goto fail;
+ }
+
+ memset(&callbacks, 0, sizeof(callbacks));
+ callbacks.die.callback = die_cb;
+ callbacks.die.client_data = m->core;
+ callbacks.save_yourself.callback = save_yourself_cb;
+ callbacks.save_yourself.client_data = m->core;
+ callbacks.save_complete.callback = save_complete_cb;
+ callbacks.save_complete.client_data = m->core;
+ callbacks.shutdown_cancelled.callback = shutdown_cancelled_cb;
+ callbacks.shutdown_cancelled.client_data = m->core;
+
+ if (!(m->userdata = connection = SmcOpenConnection(
+ NULL, m->core,
+ SmProtoMajor, SmProtoMinor,
+ SmcSaveYourselfProcMask | SmcDieProcMask | SmcSaveCompleteProcMask | SmcShutdownCancelledProcMask,
+ &callbacks, NULL, &client_id,
+ sizeof(t), t))) {
+
+ pa_log("Failed to open connection to session manager: %s", t);
+ goto fail;
+ }
+
+ prop_program.name = (char*) SmProgram;
+ prop_program.type = (char*) SmARRAY8;
+ val_program.value = (char*) PACKAGE_NAME;
+ val_program.length = strlen(val_program.value);
+ prop_program.num_vals = 1;
+ prop_program.vals = &val_program;
+ prop_list[0] = &prop_program;
+
+ prop_user.name = (char*) SmUserID;
+ prop_user.type = (char*) SmARRAY8;
+ pa_get_user_name(t, sizeof(t));
+ val_user.value = t;
+ val_user.length = strlen(val_user.value);
+ prop_user.num_vals = 1;
+ prop_user.vals = &val_user;
+ prop_list[1] = &prop_user;
+
+ SmcSetProperties(connection, PA_ELEMENTSOF(prop_list), prop_list);
+
+ pa_log_info("Connected to session manager '%s' as '%s'.", vendor = SmcVendor(connection), client_id);
+ free(vendor);
+ free(client_id);
+
+ pa_modargs_free(ma);
+
+ return 0;
+
+fail:
+ if (ma)
+ pa_modargs_free(ma);
+
+ pa__done(m);
+
+ return -1;
+}
+
+void pa__done(pa_module*m) {
+ pa_assert(m);
+
+ if (m->userdata)
+ SmcCloseConnection(m->userdata, 0, NULL);
+
+ if (ice_in_use) {
+ IceRemoveConnectionWatch(new_ice_connection, m->core);
+ ice_in_use = 0;
+ }
+}
diff --git a/src/modules/module-zeroconf-discover.c b/src/modules/module-zeroconf-discover.c
new file mode 100644
index 00000000..4e76f448
--- /dev/null
+++ b/src/modules/module-zeroconf-discover.c
@@ -0,0 +1,443 @@
+/* $Id$ */
+
+/***
+ 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 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <avahi-client/client.h>
+#include <avahi-client/lookup.h>
+#include <avahi-common/alternative.h>
+#include <avahi-common/error.h>
+#include <avahi-common/domain.h>
+#include <avahi-common/malloc.h>
+
+#include <pulse/xmalloc.h>
+#include <pulse/util.h>
+
+#include <pulsecore/sink.h>
+#include <pulsecore/source.h>
+#include <pulsecore/native-common.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/log.h>
+#include <pulsecore/core-subscribe.h>
+#include <pulsecore/hashmap.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/namereg.h>
+#include <pulsecore/avahi-wrap.h>
+
+#include "module-zeroconf-discover-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering");
+PA_MODULE_DESCRIPTION("mDNS/DNS-SD Service Discovery");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(TRUE);
+
+#define SERVICE_TYPE_SINK "_pulse-sink._tcp"
+#define SERVICE_TYPE_SOURCE "_non-monitor._sub._pulse-source._tcp"
+
+static const char* const valid_modargs[] = {
+ NULL
+};
+
+struct tunnel {
+ AvahiIfIndex interface;
+ AvahiProtocol protocol;
+ char *name, *type, *domain;
+ uint32_t module_index;
+};
+
+struct userdata {
+ pa_core *core;
+ pa_module *module;
+ AvahiPoll *avahi_poll;
+ AvahiClient *client;
+ AvahiServiceBrowser *source_browser, *sink_browser;
+
+ pa_hashmap *tunnels;
+};
+
+static unsigned tunnel_hash(const void *p) {
+ const struct tunnel *t = p;
+
+ return
+ (unsigned) t->interface +
+ (unsigned) t->protocol +
+ pa_idxset_string_hash_func(t->name) +
+ pa_idxset_string_hash_func(t->type) +
+ pa_idxset_string_hash_func(t->domain);
+}
+
+static int tunnel_compare(const void *a, const void *b) {
+ const struct tunnel *ta = a, *tb = b;
+ int r;
+
+ if (ta->interface != tb->interface)
+ return 1;
+ if (ta->protocol != tb->protocol)
+ return 1;
+ if ((r = strcmp(ta->name, tb->name)))
+ return r;
+ if ((r = strcmp(ta->type, tb->type)))
+ return r;
+ if ((r = strcmp(ta->domain, tb->domain)))
+ return r;
+
+ return 0;
+}
+
+static struct tunnel *tunnel_new(
+ AvahiIfIndex interface, AvahiProtocol protocol,
+ const char *name, const char *type, const char *domain) {
+
+ struct tunnel *t;
+ t = pa_xnew(struct tunnel, 1);
+ t->interface = interface;
+ t->protocol = protocol;
+ t->name = pa_xstrdup(name);
+ t->type = pa_xstrdup(type);
+ t->domain = pa_xstrdup(domain);
+ t->module_index = PA_IDXSET_INVALID;
+ return t;
+}
+
+static void tunnel_free(struct tunnel *t) {
+ pa_assert(t);
+ pa_xfree(t->name);
+ pa_xfree(t->type);
+ pa_xfree(t->domain);
+ pa_xfree(t);
+}
+
+static void resolver_cb(
+ AvahiServiceResolver *r,
+ AvahiIfIndex interface, AvahiProtocol protocol,
+ AvahiResolverEvent event,
+ const char *name, const char *type, const char *domain,
+ const char *host_name, const AvahiAddress *a, uint16_t port,
+ AvahiStringList *txt,
+ AvahiLookupResultFlags flags,
+ void *userdata) {
+
+ struct userdata *u = userdata;
+ struct tunnel *tnl;
+
+ pa_assert(u);
+
+ tnl = tunnel_new(interface, protocol, name, type, domain);
+
+ if (event != AVAHI_RESOLVER_FOUND)
+ pa_log("Resolving of '%s' failed: %s", name, avahi_strerror(avahi_client_errno(u->client)));
+ else {
+ char *device = NULL, *dname, *module_name, *args;
+ const char *t;
+ char at[AVAHI_ADDRESS_STR_MAX], cmt[PA_CHANNEL_MAP_SNPRINT_MAX];
+ pa_sample_spec ss;
+ pa_channel_map cm;
+ AvahiStringList *l;
+ pa_bool_t channel_map_set = FALSE;
+ pa_module *m;
+
+ ss = u->core->default_sample_spec;
+ pa_assert_se(pa_channel_map_init_auto(&cm, ss.channels, PA_CHANNEL_MAP_AUX));
+ pa_channel_map_init_auto(&cm, ss.channels, PA_CHANNEL_MAP_DEFAULT);
+
+ for (l = txt; l; l = l->next) {
+ char *key, *value;
+ pa_assert_se(avahi_string_list_get_pair(l, &key, &value, NULL) == 0);
+
+ if (strcmp(key, "device") == 0) {
+ pa_xfree(device);
+ device = value;
+ value = NULL;
+ } else if (strcmp(key, "rate") == 0)
+ ss.rate = atoi(value);
+ else if (strcmp(key, "channels") == 0)
+ ss.channels = atoi(value);
+ else if (strcmp(key, "format") == 0)
+ ss.format = pa_parse_sample_format(value);
+ else if (strcmp(key, "channel_map") == 0) {
+ pa_channel_map_parse(&cm, value);
+ channel_map_set = TRUE;
+ }
+
+ avahi_free(key);
+ avahi_free(value);
+ }
+
+ if (!channel_map_set && cm.channels != ss.channels) {
+ pa_assert_se(pa_channel_map_init_auto(&cm, ss.channels, PA_CHANNEL_MAP_AUX));
+ pa_channel_map_init_auto(&cm, ss.channels, PA_CHANNEL_MAP_DEFAULT);
+ }
+
+ if (!pa_sample_spec_valid(&ss)) {
+ pa_log("Service '%s' contains an invalid sample specification.", name);
+ avahi_free(device);
+ goto finish;
+ }
+
+ if (!pa_channel_map_valid(&cm) || cm.channels != ss.channels) {
+ pa_log("Service '%s' contains an invalid channel map.", name);
+ avahi_free(device);
+ goto finish;
+ }
+
+ if (device)
+ dname = pa_sprintf_malloc("tunnel.%s.%s", host_name, device);
+ else
+ dname = pa_sprintf_malloc("tunnel.%s", host_name);
+
+ if (!pa_namereg_is_valid_name(dname)) {
+ pa_log("Cannot construct valid device name from credentials of service '%s'.", dname);
+ avahi_free(device);
+ pa_xfree(dname);
+ goto finish;
+ }
+
+ t = strstr(type, "sink") ? "sink" : "source";
+
+ module_name = pa_sprintf_malloc("module-tunnel-%s", t);
+ args = pa_sprintf_malloc("server=[%s]:%u "
+ "%s=%s "
+ "format=%s "
+ "channels=%u "
+ "rate=%u "
+ "%s_name=%s "
+ "channel_map=%s",
+ avahi_address_snprint(at, sizeof(at), a), port,
+ t, device,
+ pa_sample_format_to_string(ss.format),
+ ss.channels,
+ ss.rate,
+ t, dname,
+ pa_channel_map_snprint(cmt, sizeof(cmt), &cm));
+
+ pa_log_debug("Loading module-tunnel-%s with arguments '%s'", module_name, args);
+
+ if ((m = pa_module_load(u->core, module_name, args))) {
+ tnl->module_index = m->index;
+ pa_hashmap_put(u->tunnels, tnl, tnl);
+ tnl = NULL;
+ }
+
+ pa_xfree(module_name);
+ pa_xfree(dname);
+ pa_xfree(args);
+ avahi_free(device);
+ }
+
+finish:
+
+ avahi_service_resolver_free(r);
+
+ if (tnl)
+ tunnel_free(tnl);
+}
+
+static void browser_cb(
+ AvahiServiceBrowser *b,
+ AvahiIfIndex interface, AvahiProtocol protocol,
+ AvahiBrowserEvent event,
+ const char *name, const char *type, const char *domain,
+ AvahiLookupResultFlags flags,
+ void *userdata) {
+
+ struct userdata *u = userdata;
+ struct tunnel *t;
+
+ pa_assert(u);
+
+ if (flags & AVAHI_LOOKUP_RESULT_LOCAL)
+ return;
+
+ t = tunnel_new(interface, protocol, name, type, domain);
+
+ if (event == AVAHI_BROWSER_NEW) {
+
+ if (!pa_hashmap_get(u->tunnels, t))
+ if (!(avahi_service_resolver_new(u->client, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, resolver_cb, u)))
+ pa_log("avahi_service_resolver_new() failed: %s", avahi_strerror(avahi_client_errno(u->client)));
+
+ /* We ignore the returned resolver object here, since the we don't
+ * need to attach any special data to it, and we can still destory
+ * it from the callback */
+
+ } else if (event == AVAHI_BROWSER_REMOVE) {
+ struct tunnel *t2;
+
+ if ((t2 = pa_hashmap_get(u->tunnels, t))) {
+ pa_module_unload_by_index(u->core, t2->module_index);
+ pa_hashmap_remove(u->tunnels, t2);
+ tunnel_free(t2);
+ }
+ }
+
+ tunnel_free(t);
+}
+
+static void client_callback(AvahiClient *c, AvahiClientState state, void *userdata) {
+ struct userdata *u = userdata;
+
+ pa_assert(c);
+ pa_assert(u);
+
+ u->client = c;
+
+ switch (state) {
+ case AVAHI_CLIENT_S_REGISTERING:
+ case AVAHI_CLIENT_S_RUNNING:
+ case AVAHI_CLIENT_S_COLLISION:
+
+ if (!u->sink_browser) {
+
+ if (!(u->sink_browser = avahi_service_browser_new(
+ c,
+ AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
+ SERVICE_TYPE_SINK,
+ NULL,
+ 0,
+ browser_cb, u))) {
+
+ pa_log("avahi_service_browser_new() failed: %s", avahi_strerror(avahi_client_errno(c)));
+ pa_module_unload_request(u->module);
+ }
+ }
+
+ if (!u->source_browser) {
+
+ if (!(u->source_browser = avahi_service_browser_new(
+ c,
+ AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
+ SERVICE_TYPE_SOURCE,
+ NULL,
+ 0,
+ browser_cb, u))) {
+
+ pa_log("avahi_service_browser_new() failed: %s", avahi_strerror(avahi_client_errno(c)));
+ pa_module_unload_request(u->module);
+ }
+ }
+
+ break;
+
+ case AVAHI_CLIENT_FAILURE:
+ if (avahi_client_errno(c) == AVAHI_ERR_DISCONNECTED) {
+ int error;
+
+ pa_log_debug("Avahi daemon disconnected.");
+
+ if (!(u->client = avahi_client_new(u->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, u, &error))) {
+ pa_log("avahi_client_new() failed: %s", avahi_strerror(error));
+ pa_module_unload_request(u->module);
+ }
+ }
+
+ /* Fall through */
+
+ case AVAHI_CLIENT_CONNECTING:
+
+ if (u->sink_browser) {
+ avahi_service_browser_free(u->sink_browser);
+ u->sink_browser = NULL;
+ }
+
+ if (u->source_browser) {
+ avahi_service_browser_free(u->source_browser);
+ u->source_browser = NULL;
+ }
+
+ break;
+
+ default: ;
+ }
+}
+
+int pa__init(pa_module*m) {
+
+ struct userdata *u;
+ pa_modargs *ma = NULL;
+ int error;
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log("Failed to parse module arguments.");
+ goto fail;
+ }
+
+ m->userdata = u = pa_xnew(struct userdata, 1);
+ u->core = m->core;
+ u->module = m;
+ u->sink_browser = u->source_browser = NULL;
+
+ u->tunnels = pa_hashmap_new(tunnel_hash, tunnel_compare);
+
+ u->avahi_poll = pa_avahi_poll_new(m->core->mainloop);
+
+ if (!(u->client = avahi_client_new(u->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, u, &error))) {
+ pa_log("pa_avahi_client_new() failed: %s", avahi_strerror(error));
+ goto fail;
+ }
+
+ pa_modargs_free(ma);
+
+ return 0;
+
+fail:
+ pa__done(m);
+
+ if (ma)
+ pa_modargs_free(ma);
+
+ return -1;
+}
+
+void pa__done(pa_module*m) {
+ struct userdata*u;
+ pa_assert(m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->client)
+ avahi_client_free(u->client);
+
+ if (u->avahi_poll)
+ pa_avahi_poll_free(u->avahi_poll);
+
+ if (u->tunnels) {
+ struct tunnel *t;
+
+ while ((t = pa_hashmap_steal_first(u->tunnels))) {
+ pa_module_unload_by_index(u->core, t->module_index);
+ tunnel_free(t);
+ }
+
+ pa_hashmap_free(u->tunnels, NULL, NULL);
+ }
+
+ pa_xfree(u);
+}
diff --git a/src/modules/module-zeroconf-publish.c b/src/modules/module-zeroconf-publish.c
new file mode 100644
index 00000000..46969a24
--- /dev/null
+++ b/src/modules/module-zeroconf-publish.c
@@ -0,0 +1,650 @@
+/* $Id$ */
+
+/***
+ 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 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <avahi-client/client.h>
+#include <avahi-client/publish.h>
+#include <avahi-common/alternative.h>
+#include <avahi-common/error.h>
+#include <avahi-common/domain.h>
+
+#include <pulse/xmalloc.h>
+#include <pulse/util.h>
+
+#include <pulsecore/sink.h>
+#include <pulsecore/source.h>
+#include <pulsecore/native-common.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/log.h>
+#include <pulsecore/core-subscribe.h>
+#include <pulsecore/dynarray.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/avahi-wrap.h>
+#include <pulsecore/endianmacros.h>
+
+#include "module-zeroconf-publish-symdef.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering");
+PA_MODULE_DESCRIPTION("mDNS/DNS-SD Service Publisher");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(TRUE);
+PA_MODULE_USAGE("port=<IP port number>");
+
+#define SERVICE_TYPE_SINK "_pulse-sink._tcp"
+#define SERVICE_TYPE_SOURCE "_pulse-source._tcp"
+#define SERVICE_TYPE_SERVER "_pulse-server._tcp"
+#define SERVICE_SUBTYPE_SINK_HARDWARE "_hardware._sub."SERVICE_TYPE_SINK
+#define SERVICE_SUBTYPE_SINK_VIRTUAL "_virtual._sub."SERVICE_TYPE_SINK
+#define SERVICE_SUBTYPE_SOURCE_HARDWARE "_hardware._sub."SERVICE_TYPE_SOURCE
+#define SERVICE_SUBTYPE_SOURCE_VIRTUAL "_virtual._sub."SERVICE_TYPE_SOURCE
+#define SERVICE_SUBTYPE_SOURCE_MONITOR "_monitor._sub."SERVICE_TYPE_SOURCE
+#define SERVICE_SUBTYPE_SOURCE_NON_MONITOR "_non-monitor._sub."SERVICE_TYPE_SOURCE
+
+static const char* const valid_modargs[] = {
+ "port",
+ NULL
+};
+
+enum service_subtype {
+ SUBTYPE_HARDWARE,
+ SUBTYPE_VIRTUAL,
+ SUBTYPE_MONITOR
+};
+
+struct service {
+ struct userdata *userdata;
+ AvahiEntryGroup *entry_group;
+ char *service_name;
+ pa_object *device;
+ enum service_subtype subtype;
+};
+
+struct userdata {
+ pa_core *core;
+ pa_module *module;
+ AvahiPoll *avahi_poll;
+ AvahiClient *client;
+
+ pa_hashmap *services;
+ char *service_name;
+
+ AvahiEntryGroup *main_entry_group;
+
+ uint16_t port;
+
+ pa_hook_slot *sink_new_slot, *source_new_slot, *sink_unlink_slot, *source_unlink_slot, *sink_changed_slot, *source_changed_slot;
+};
+
+static void get_service_data(struct service *s, pa_sample_spec *ret_ss, pa_channel_map *ret_map, const char **ret_name, const char **ret_description, enum service_subtype *ret_subtype) {
+ pa_assert(s);
+ pa_assert(ret_ss);
+ pa_assert(ret_description);
+ pa_assert(ret_subtype);
+
+ if (pa_sink_isinstance(s->device)) {
+ pa_sink *sink = PA_SINK(s->device);
+
+ *ret_ss = sink->sample_spec;
+ *ret_map = sink->channel_map;
+ *ret_name = sink->name;
+ *ret_description = sink->description;
+ *ret_subtype = sink->flags & PA_SINK_HARDWARE ? SUBTYPE_HARDWARE : SUBTYPE_VIRTUAL;
+
+ } else if (pa_source_isinstance(s->device)) {
+ pa_source *source = PA_SOURCE(s->device);
+
+ *ret_ss = source->sample_spec;
+ *ret_map = source->channel_map;
+ *ret_name = source->name;
+ *ret_description = source->description;
+ *ret_subtype = source->monitor_of ? SUBTYPE_MONITOR : (source->flags & PA_SOURCE_HARDWARE ? SUBTYPE_HARDWARE : SUBTYPE_VIRTUAL);
+
+ } else
+ pa_assert_not_reached();
+}
+
+static AvahiStringList* txt_record_server_data(pa_core *c, AvahiStringList *l) {
+ char s[128];
+
+ pa_assert(c);
+
+ l = avahi_string_list_add_pair(l, "server-version", PACKAGE_NAME" "PACKAGE_VERSION);
+ l = avahi_string_list_add_pair(l, "user-name", pa_get_user_name(s, sizeof(s)));
+ l = avahi_string_list_add_pair(l, "fqdn", pa_get_fqdn(s, sizeof(s)));
+ l = avahi_string_list_add_printf(l, "cookie=0x%08x", c->cookie);
+
+ return l;
+}
+
+static int publish_service(struct service *s);
+
+static void service_entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) {
+ struct service *s = userdata;
+
+ pa_assert(s);
+
+ switch (state) {
+
+ case AVAHI_ENTRY_GROUP_ESTABLISHED:
+ pa_log_info("Successfully established service %s.", s->service_name);
+ break;
+
+ case AVAHI_ENTRY_GROUP_COLLISION: {
+ char *t;
+
+ t = avahi_alternative_service_name(s->service_name);
+ pa_log_info("Name collision, renaming %s to %s.", s->service_name, t);
+ pa_xfree(s->service_name);
+ s->service_name = t;
+
+ publish_service(s);
+ break;
+ }
+
+ case AVAHI_ENTRY_GROUP_FAILURE: {
+ pa_log("Failed to register service: %s", avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g))));
+
+ avahi_entry_group_free(g);
+ s->entry_group = NULL;
+
+ break;
+ }
+
+ case AVAHI_ENTRY_GROUP_UNCOMMITED:
+ case AVAHI_ENTRY_GROUP_REGISTERING:
+ ;
+ }
+}
+
+static void service_free(struct service *s);
+
+static int publish_service(struct service *s) {
+ int r = -1;
+ AvahiStringList *txt = NULL;
+ const char *description = NULL, *name = NULL;
+ pa_sample_spec ss;
+ pa_channel_map map;
+ char cm[PA_CHANNEL_MAP_SNPRINT_MAX];
+ enum service_subtype subtype;
+
+ const char * const subtype_text[] = {
+ [SUBTYPE_HARDWARE] = "hardware",
+ [SUBTYPE_VIRTUAL] = "virtual",
+ [SUBTYPE_MONITOR] = "monitor"
+ };
+
+ pa_assert(s);
+
+ if (!s->userdata->client || avahi_client_get_state(s->userdata->client) != AVAHI_CLIENT_S_RUNNING)
+ return 0;
+
+ if (!s->entry_group) {
+ if (!(s->entry_group = avahi_entry_group_new(s->userdata->client, service_entry_group_callback, s))) {
+ pa_log("avahi_entry_group_new(): %s", avahi_strerror(avahi_client_errno(s->userdata->client)));
+ goto finish;
+ }
+ } else
+ avahi_entry_group_reset(s->entry_group);
+
+ txt = txt_record_server_data(s->userdata->core, txt);
+
+ get_service_data(s, &ss, &map, &name, &description, &subtype);
+ txt = avahi_string_list_add_pair(txt, "device", name);
+ txt = avahi_string_list_add_printf(txt, "rate=%u", ss.rate);
+ txt = avahi_string_list_add_printf(txt, "channels=%u", ss.channels);
+ txt = avahi_string_list_add_pair(txt, "format", pa_sample_format_to_string(ss.format));
+ txt = avahi_string_list_add_pair(txt, "channel_map", pa_channel_map_snprint(cm, sizeof(cm), &map));
+ txt = avahi_string_list_add_pair(txt, "subtype", subtype_text[subtype]);
+
+ if (avahi_entry_group_add_service_strlst(
+ s->entry_group,
+ AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
+ 0,
+ s->service_name,
+ pa_sink_isinstance(s->device) ? SERVICE_TYPE_SINK : SERVICE_TYPE_SOURCE,
+ NULL,
+ NULL,
+ s->userdata->port,
+ txt) < 0) {
+
+ pa_log("avahi_entry_group_add_service_strlst(): %s", avahi_strerror(avahi_client_errno(s->userdata->client)));
+ goto finish;
+ }
+
+ if (avahi_entry_group_add_service_subtype(
+ s->entry_group,
+ AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
+ 0,
+ s->service_name,
+ pa_sink_isinstance(s->device) ? SERVICE_TYPE_SINK : SERVICE_TYPE_SOURCE,
+ NULL,
+ pa_sink_isinstance(s->device) ? (subtype == SUBTYPE_HARDWARE ? SERVICE_SUBTYPE_SINK_HARDWARE : SERVICE_SUBTYPE_SINK_VIRTUAL) :
+ (subtype == SUBTYPE_HARDWARE ? SERVICE_SUBTYPE_SOURCE_HARDWARE : (subtype == SUBTYPE_VIRTUAL ? SERVICE_SUBTYPE_SOURCE_VIRTUAL : SERVICE_SUBTYPE_SOURCE_MONITOR))) < 0) {
+
+ pa_log("avahi_entry_group_add_service_subtype(): %s", avahi_strerror(avahi_client_errno(s->userdata->client)));
+ goto finish;
+ }
+
+ if (pa_source_isinstance(s->device) && subtype != SUBTYPE_MONITOR) {
+ if (avahi_entry_group_add_service_subtype(
+ s->entry_group,
+ AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
+ 0,
+ s->service_name,
+ SERVICE_TYPE_SOURCE,
+ NULL,
+ SERVICE_SUBTYPE_SOURCE_NON_MONITOR) < 0) {
+
+ pa_log("avahi_entry_group_add_service_subtype(): %s", avahi_strerror(avahi_client_errno(s->userdata->client)));
+ goto finish;
+ }
+ }
+
+ if (avahi_entry_group_commit(s->entry_group) < 0) {
+ pa_log("avahi_entry_group_commit(): %s", avahi_strerror(avahi_client_errno(s->userdata->client)));
+ goto finish;
+ }
+
+ r = 0;
+ pa_log_debug("Successfully created entry group for %s.", s->service_name);
+
+finish:
+
+ /* Remove this service */
+ if (r < 0)
+ service_free(s);
+
+ avahi_string_list_free(txt);
+
+ return r;
+}
+
+static struct service *get_service(struct userdata *u, pa_object *device) {
+ struct service *s;
+ char hn[64], un[64];
+ const char *n;
+
+ pa_assert(u);
+ pa_object_assert_ref(device);
+
+ if ((s = pa_hashmap_get(u->services, device)))
+ return s;
+
+ s = pa_xnew(struct service, 1);
+ s->userdata = u;
+ s->entry_group = NULL;
+ s->device = device;
+
+ if (pa_sink_isinstance(device)) {
+ if (!(n = PA_SINK(device)->description))
+ n = PA_SINK(device)->name;
+ } else {
+ if (!(n = PA_SOURCE(device)->description))
+ n = PA_SOURCE(device)->name;
+ }
+
+ s->service_name = pa_truncate_utf8(pa_sprintf_malloc("%s@%s: %s",
+ pa_get_user_name(un, sizeof(un)),
+ pa_get_host_name(hn, sizeof(hn)),
+ n),
+ AVAHI_LABEL_MAX-1);
+
+ pa_hashmap_put(u->services, s->device, s);
+
+ return s;
+}
+
+static void service_free(struct service *s) {
+ pa_assert(s);
+
+ pa_hashmap_remove(s->userdata->services, s->device);
+
+ if (s->entry_group) {
+ pa_log_debug("Removing entry group for %s.", s->service_name);
+ avahi_entry_group_free(s->entry_group);
+ }
+
+ pa_xfree(s->service_name);
+ pa_xfree(s);
+}
+
+static pa_bool_t shall_ignore(pa_object *o) {
+ pa_object_assert_ref(o);
+
+ if (pa_sink_isinstance(o))
+ return !!(PA_SINK(o)->flags & PA_SINK_NETWORK);
+
+ if (pa_source_isinstance(o))
+ return PA_SOURCE(o)->monitor_of || (PA_SOURCE(o)->flags & PA_SOURCE_NETWORK);
+
+ pa_assert_not_reached();
+}
+
+static pa_hook_result_t device_new_or_changed_cb(pa_core *c, pa_object *o, struct userdata *u) {
+ pa_assert(c);
+ pa_object_assert_ref(o);
+
+ if (!shall_ignore(o))
+ publish_service(get_service(u, o));
+
+ return PA_HOOK_OK;
+}
+
+static pa_hook_result_t device_unlink_cb(pa_core *c, pa_object *o, struct userdata *u) {
+ struct service *s;
+
+ pa_assert(c);
+ pa_object_assert_ref(o);
+
+ if ((s = pa_hashmap_get(u->services, o)))
+ service_free(s);
+
+ return PA_HOOK_OK;
+}
+
+static int publish_main_service(struct userdata *u);
+
+static void main_entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) {
+ struct userdata *u = userdata;
+ pa_assert(u);
+
+ switch (state) {
+
+ case AVAHI_ENTRY_GROUP_ESTABLISHED:
+ pa_log_info("Successfully established main service.");
+ break;
+
+ case AVAHI_ENTRY_GROUP_COLLISION: {
+ char *t;
+
+ t = avahi_alternative_service_name(u->service_name);
+ pa_log_info("Name collision: renaming main service %s to %s.", u->service_name, t);
+ pa_xfree(u->service_name);
+ u->service_name = t;
+
+ publish_main_service(u);
+ break;
+ }
+
+ case AVAHI_ENTRY_GROUP_FAILURE: {
+ pa_log("Failed to register main service: %s", avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g))));
+
+ avahi_entry_group_free(g);
+ u->main_entry_group = NULL;
+ break;
+ }
+
+ case AVAHI_ENTRY_GROUP_UNCOMMITED:
+ case AVAHI_ENTRY_GROUP_REGISTERING:
+ break;
+ }
+}
+
+static int publish_main_service(struct userdata *u) {
+ AvahiStringList *txt = NULL;
+ int r = -1;
+
+ pa_assert(u);
+
+ if (!u->main_entry_group) {
+ if (!(u->main_entry_group = avahi_entry_group_new(u->client, main_entry_group_callback, u))) {
+ pa_log("avahi_entry_group_new() failed: %s", avahi_strerror(avahi_client_errno(u->client)));
+ goto fail;
+ }
+ } else
+ avahi_entry_group_reset(u->main_entry_group);
+
+ txt = txt_record_server_data(u->core, txt);
+
+ if (avahi_entry_group_add_service_strlst(
+ u->main_entry_group,
+ AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
+ 0,
+ u->service_name,
+ SERVICE_TYPE_SERVER,
+ NULL,
+ NULL,
+ u->port,
+ txt) < 0) {
+
+ pa_log("avahi_entry_group_add_service_strlst() failed: %s", avahi_strerror(avahi_client_errno(u->client)));
+ goto fail;
+ }
+
+ if (avahi_entry_group_commit(u->main_entry_group) < 0) {
+ pa_log("avahi_entry_group_commit() failed: %s", avahi_strerror(avahi_client_errno(u->client)));
+ goto fail;
+ }
+
+ r = 0;
+
+fail:
+ avahi_string_list_free(txt);
+
+ return r;
+}
+
+static int publish_all_services(struct userdata *u) {
+ pa_sink *sink;
+ pa_source *source;
+ int r = -1;
+ uint32_t idx;
+
+ pa_assert(u);
+
+ pa_log_debug("Publishing services in Zeroconf");
+
+ for (sink = PA_SINK(pa_idxset_first(u->core->sinks, &idx)); sink; sink = PA_SINK(pa_idxset_next(u->core->sinks, &idx)))
+ if (!shall_ignore(PA_OBJECT(sink)))
+ publish_service(get_service(u, PA_OBJECT(sink)));
+
+ for (source = PA_SOURCE(pa_idxset_first(u->core->sources, &idx)); source; source = PA_SOURCE(pa_idxset_next(u->core->sources, &idx)))
+ if (!shall_ignore(PA_OBJECT(source)))
+ publish_service(get_service(u, PA_OBJECT(source)));
+
+ if (publish_main_service(u) < 0)
+ goto fail;
+
+ r = 0;
+
+fail:
+ return r;
+}
+
+static void unpublish_all_services(struct userdata *u, pa_bool_t rem) {
+ void *state = NULL;
+ struct service *s;
+
+ pa_assert(u);
+
+ pa_log_debug("Unpublishing services in Zeroconf");
+
+ while ((s = pa_hashmap_iterate(u->services, &state, NULL))) {
+ if (s->entry_group) {
+ if (rem) {
+ pa_log_debug("Removing entry group for %s.", s->service_name);
+ avahi_entry_group_free(s->entry_group);
+ s->entry_group = NULL;
+ } else {
+ avahi_entry_group_reset(s->entry_group);
+ pa_log_debug("Resetting entry group for %s.", s->service_name);
+ }
+ }
+ }
+
+ if (u->main_entry_group) {
+ if (rem) {
+ pa_log_debug("Removing main entry group.");
+ avahi_entry_group_free(u->main_entry_group);
+ u->main_entry_group = NULL;
+ } else {
+ avahi_entry_group_reset(u->main_entry_group);
+ pa_log_debug("Resetting main entry group.");
+ }
+ }
+}
+
+static void client_callback(AvahiClient *c, AvahiClientState state, void *userdata) {
+ struct userdata *u = userdata;
+
+ pa_assert(c);
+ pa_assert(u);
+
+ u->client = c;
+
+ switch (state) {
+ case AVAHI_CLIENT_S_RUNNING:
+ publish_all_services(u);
+ break;
+
+ case AVAHI_CLIENT_S_COLLISION:
+ pa_log_debug("Host name collision");
+ unpublish_all_services(u, FALSE);
+ break;
+
+ case AVAHI_CLIENT_FAILURE:
+ if (avahi_client_errno(c) == AVAHI_ERR_DISCONNECTED) {
+ int error;
+
+ pa_log_debug("Avahi daemon disconnected.");
+
+ unpublish_all_services(u, TRUE);
+ avahi_client_free(u->client);
+
+ if (!(u->client = avahi_client_new(u->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, u, &error))) {
+ pa_log("avahi_client_new() failed: %s", avahi_strerror(error));
+ pa_module_unload_request(u->module);
+ }
+ }
+
+ break;
+
+ default: ;
+ }
+}
+
+int pa__init(pa_module*m) {
+
+ struct userdata *u;
+ uint32_t port = PA_NATIVE_DEFAULT_PORT;
+ pa_modargs *ma = NULL;
+ char hn[256], un[256];
+ int error;
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log("Failed to parse module arguments.");
+ goto fail;
+ }
+
+ if (pa_modargs_get_value_u32(ma, "port", &port) < 0 || port <= 0 || port > 0xFFFF) {
+ pa_log("Invalid port specified.");
+ goto fail;
+ }
+
+ m->userdata = u = pa_xnew(struct userdata, 1);
+ u->core = m->core;
+ u->module = m;
+ u->port = (uint16_t) port;
+
+ u->avahi_poll = pa_avahi_poll_new(m->core->mainloop);
+
+ u->services = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
+
+ u->sink_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_NEW_POST], (pa_hook_cb_t) device_new_or_changed_cb, u);
+ u->sink_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_DESCRIPTION_CHANGED], (pa_hook_cb_t) device_new_or_changed_cb, u);
+ u->sink_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], (pa_hook_cb_t) device_unlink_cb, u);
+ u->source_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_NEW_POST], (pa_hook_cb_t) device_new_or_changed_cb, u);
+ u->source_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_DESCRIPTION_CHANGED], (pa_hook_cb_t) device_new_or_changed_cb, u);
+ u->source_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], (pa_hook_cb_t) device_unlink_cb, u);
+
+ u->main_entry_group = NULL;
+
+ u->service_name = pa_truncate_utf8(pa_sprintf_malloc("%s@%s", pa_get_user_name(un, sizeof(un)), pa_get_host_name(hn, sizeof(hn))), AVAHI_LABEL_MAX);
+
+ if (!(u->client = avahi_client_new(u->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, u, &error))) {
+ pa_log("avahi_client_new() failed: %s", avahi_strerror(error));
+ goto fail;
+ }
+
+ pa_modargs_free(ma);
+
+ return 0;
+
+fail:
+ pa__done(m);
+
+ if (ma)
+ pa_modargs_free(ma);
+
+ return -1;
+}
+
+void pa__done(pa_module*m) {
+ struct userdata*u;
+ pa_assert(m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->services) {
+ struct service *s;
+
+ while ((s = pa_hashmap_get_first(u->services)))
+ service_free(s);
+
+ pa_hashmap_free(u->services, NULL, NULL);
+ }
+
+ if (u->sink_new_slot)
+ pa_hook_slot_free(u->sink_new_slot);
+ if (u->source_new_slot)
+ pa_hook_slot_free(u->source_new_slot);
+ if (u->sink_changed_slot)
+ pa_hook_slot_free(u->sink_changed_slot);
+ if (u->source_changed_slot)
+ pa_hook_slot_free(u->source_changed_slot);
+ if (u->sink_unlink_slot)
+ pa_hook_slot_free(u->sink_unlink_slot);
+ if (u->source_unlink_slot)
+ pa_hook_slot_free(u->source_unlink_slot);
+
+ if (u->main_entry_group)
+ avahi_entry_group_free(u->main_entry_group);
+
+ if (u->client)
+ avahi_client_free(u->client);
+
+ if (u->avahi_poll)
+ pa_avahi_poll_free(u->avahi_poll);
+
+ pa_xfree(u->service_name);
+ pa_xfree(u);
+}
diff --git a/src/modules/oss-util.c b/src/modules/oss-util.c
new file mode 100644
index 00000000..9598feee
--- /dev/null
+++ b/src/modules/oss-util.c
@@ -0,0 +1,419 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sys/soundcard.h>
+#include <sys/ioctl.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/core-error.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+
+#include "oss-util.h"
+
+int pa_oss_open(const char *device, int *mode, int* pcaps) {
+ int fd = -1;
+ int caps;
+
+ pa_assert(device);
+ pa_assert(mode);
+ pa_assert(*mode == O_RDWR || *mode == O_RDONLY || *mode == O_WRONLY);
+
+ if(!pcaps)
+ pcaps = &caps;
+
+ if (*mode == O_RDWR) {
+ if ((fd = open(device, O_RDWR|O_NDELAY|O_NOCTTY)) >= 0) {
+ ioctl(fd, SNDCTL_DSP_SETDUPLEX, 0);
+
+ if (ioctl(fd, SNDCTL_DSP_GETCAPS, pcaps) < 0) {
+ pa_log("SNDCTL_DSP_GETCAPS: %s", pa_cstrerror(errno));
+ goto fail;
+ }
+
+ if (*pcaps & DSP_CAP_DUPLEX)
+ goto success;
+
+ pa_log_warn("'%s' doesn't support full duplex", device);
+
+ pa_close(fd);
+ }
+
+ if ((fd = open(device, (*mode = O_WRONLY)|O_NDELAY|O_NOCTTY)) < 0) {
+ if ((fd = open(device, (*mode = O_RDONLY)|O_NDELAY|O_NOCTTY)) < 0) {
+ pa_log("open('%s'): %s", device, pa_cstrerror(errno));
+ goto fail;
+ }
+ }
+ } else {
+ if ((fd = open(device, *mode|O_NDELAY|O_NOCTTY)) < 0) {
+ pa_log("open('%s'): %s", device, pa_cstrerror(errno));
+ goto fail;
+ }
+ }
+
+ *pcaps = 0;
+
+ if (ioctl(fd, SNDCTL_DSP_GETCAPS, pcaps) < 0) {
+ pa_log("SNDCTL_DSP_GETCAPS: %s", pa_cstrerror(errno));
+ goto fail;
+ }
+
+success:
+
+ pa_log_debug("capabilities:%s%s%s%s%s%s%s%s%s%s%s%s%s%s",
+ *pcaps & DSP_CAP_BATCH ? " BATCH" : "",
+#ifdef DSP_CAP_BIND
+ *pcaps & DSP_CAP_BIND ? " BIND" : "",
+#else
+ "",
+#endif
+ *pcaps & DSP_CAP_COPROC ? " COPROC" : "",
+ *pcaps & DSP_CAP_DUPLEX ? " DUPLEX" : "",
+#ifdef DSP_CAP_FREERATE
+ *pcaps & DSP_CAP_FREERATE ? " FREERATE" : "",
+#else
+ "",
+#endif
+#ifdef DSP_CAP_INPUT
+ *pcaps & DSP_CAP_INPUT ? " INPUT" : "",
+#else
+ "",
+#endif
+ *pcaps & DSP_CAP_MMAP ? " MMAP" : "",
+#ifdef DSP_CAP_MODEM
+ *pcaps & DSP_CAP_MODEM ? " MODEM" : "",
+#else
+ "",
+#endif
+#ifdef DSP_CAP_MULTI
+ *pcaps & DSP_CAP_MULTI ? " MULTI" : "",
+#else
+ "",
+#endif
+#ifdef DSP_CAP_OUTPUT
+ *pcaps & DSP_CAP_OUTPUT ? " OUTPUT" : "",
+#else
+ "",
+#endif
+ *pcaps & DSP_CAP_REALTIME ? " REALTIME" : "",
+#ifdef DSP_CAP_SHADOW
+ *pcaps & DSP_CAP_SHADOW ? " SHADOW" : "",
+#else
+ "",
+#endif
+#ifdef DSP_CAP_VIRTUAL
+ *pcaps & DSP_CAP_VIRTUAL ? " VIRTUAL" : "",
+#else
+ "",
+#endif
+ *pcaps & DSP_CAP_TRIGGER ? " TRIGGER" : "");
+
+ pa_make_fd_cloexec(fd);
+
+ return fd;
+
+fail:
+ if (fd >= 0)
+ pa_close(fd);
+ return -1;
+}
+
+int pa_oss_auto_format(int fd, pa_sample_spec *ss) {
+ int format, channels, speed, reqformat;
+ pa_sample_format_t orig_format;
+
+ static const int format_trans[PA_SAMPLE_MAX] = {
+ [PA_SAMPLE_U8] = AFMT_U8,
+ [PA_SAMPLE_ALAW] = AFMT_A_LAW,
+ [PA_SAMPLE_ULAW] = AFMT_MU_LAW,
+ [PA_SAMPLE_S16LE] = AFMT_S16_LE,
+ [PA_SAMPLE_S16BE] = AFMT_S16_BE,
+ [PA_SAMPLE_FLOAT32LE] = AFMT_QUERY, /* not supported */
+ [PA_SAMPLE_FLOAT32BE] = AFMT_QUERY, /* not supported */
+ [PA_SAMPLE_S32LE] = AFMT_QUERY, /* not supported */
+ [PA_SAMPLE_S32BE] = AFMT_QUERY, /* not supported */
+ };
+
+ pa_assert(fd >= 0);
+ pa_assert(ss);
+
+ orig_format = ss->format;
+
+ reqformat = format = format_trans[ss->format];
+ if (reqformat == AFMT_QUERY || ioctl(fd, SNDCTL_DSP_SETFMT, &format) < 0 || format != reqformat) {
+ format = AFMT_S16_NE;
+ if (ioctl(fd, SNDCTL_DSP_SETFMT, &format) < 0 || format != AFMT_S16_NE) {
+ int f = AFMT_S16_NE == AFMT_S16_LE ? AFMT_S16_BE : AFMT_S16_LE;
+ format = f;
+ if (ioctl(fd, SNDCTL_DSP_SETFMT, &format) < 0 || format != f) {
+ format = AFMT_U8;
+ if (ioctl(fd, SNDCTL_DSP_SETFMT, &format) < 0 || format != AFMT_U8) {
+ pa_log("SNDCTL_DSP_SETFMT: %s", format != AFMT_U8 ? "No supported sample format" : pa_cstrerror(errno));
+ return -1;
+ } else
+ ss->format = PA_SAMPLE_U8;
+ } else
+ ss->format = f == AFMT_S16_LE ? PA_SAMPLE_S16LE : PA_SAMPLE_S16BE;
+ } else
+ ss->format = PA_SAMPLE_S16NE;
+ }
+
+ if (orig_format != ss->format)
+ pa_log_warn("device doesn't support sample format %s, changed to %s.",
+ pa_sample_format_to_string(orig_format),
+ pa_sample_format_to_string(ss->format));
+
+ channels = ss->channels;
+ if (ioctl(fd, SNDCTL_DSP_CHANNELS, &channels) < 0) {
+ pa_log("SNDCTL_DSP_CHANNELS: %s", pa_cstrerror(errno));
+ return -1;
+ }
+ pa_assert(channels > 0);
+
+ if (ss->channels != channels) {
+ pa_log_warn("device doesn't support %i channels, using %i channels.", ss->channels, channels);
+ ss->channels = channels;
+ }
+
+ speed = ss->rate;
+ if (ioctl(fd, SNDCTL_DSP_SPEED, &speed) < 0) {
+ pa_log("SNDCTL_DSP_SPEED: %s", pa_cstrerror(errno));
+ return -1;
+ }
+ pa_assert(speed > 0);
+
+ if (ss->rate != (unsigned) speed) {
+ pa_log_warn("device doesn't support %i Hz, changed to %i Hz.", ss->rate, speed);
+
+ /* If the sample rate deviates too much, we need to resample */
+ if (speed < ss->rate*.95 || speed > ss->rate*1.05)
+ ss->rate = speed;
+ }
+
+ return 0;
+}
+
+static int simple_log2(int v) {
+ int k = 0;
+
+ for (;;) {
+ v >>= 1;
+ if (!v) break;
+ k++;
+ }
+
+ return k;
+}
+
+int pa_oss_set_fragments(int fd, int nfrags, int frag_size) {
+ int arg;
+ arg = ((int) nfrags << 16) | simple_log2(frag_size);
+
+ if (ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &arg) < 0) {
+ pa_log("SNDCTL_DSP_SETFRAGMENT: %s", pa_cstrerror(errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+int pa_oss_get_volume(int fd, int mixer, const pa_sample_spec *ss, pa_cvolume *volume) {
+ char cv[PA_CVOLUME_SNPRINT_MAX];
+ unsigned vol;
+
+ pa_assert(fd >= 0);
+ pa_assert(ss);
+ pa_assert(volume);
+
+ if (ioctl(fd, mixer, &vol) < 0)
+ return -1;
+
+ pa_cvolume_reset(volume, ss->channels);
+
+ volume->values[0] = ((vol & 0xFF) * PA_VOLUME_NORM) / 100;
+
+ if (volume->channels >= 2)
+ volume->values[1] = (((vol >> 8) & 0xFF) * PA_VOLUME_NORM) / 100;
+
+ pa_log_debug("Read mixer settings: %s", pa_cvolume_snprint(cv, sizeof(cv), volume));
+ return 0;
+}
+
+int pa_oss_set_volume(int fd, long mixer, const pa_sample_spec *ss, const pa_cvolume *volume) {
+ char cv[PA_CVOLUME_SNPRINT_MAX];
+ unsigned vol;
+ pa_volume_t l, r;
+
+ l = volume->values[0] > PA_VOLUME_NORM ? PA_VOLUME_NORM : volume->values[0];
+
+ vol = (l*100)/PA_VOLUME_NORM;
+
+ if (ss->channels >= 2) {
+ r = volume->values[1] > PA_VOLUME_NORM ? PA_VOLUME_NORM : volume->values[1];
+ vol |= ((r*100)/PA_VOLUME_NORM) << 8;
+ }
+
+ if (ioctl(fd, mixer, &vol) < 0)
+ return -1;
+
+ pa_log_debug("Wrote mixer settings: %s", pa_cvolume_snprint(cv, sizeof(cv), volume));
+ return 0;
+}
+
+static int get_device_number(const char *dev) {
+ const char *p, *e;
+ char *rp = NULL;
+ int r;
+
+ if (!(p = rp = pa_readlink(dev))) {
+ if (errno != EINVAL && errno != ENOLINK) {
+ r = -1;
+ goto finish;
+ }
+
+ p = dev;
+ }
+
+ if ((e = strrchr(p, '/')))
+ p = e+1;
+
+ if (p == 0) {
+ r = 0;
+ goto finish;
+ }
+
+ p = strchr(p, 0) -1;
+
+ if (*p >= '0' && *p <= '9') {
+ r = *p - '0';
+ goto finish;
+ }
+
+ r = -1;
+
+finish:
+ pa_xfree(rp);
+ return r;
+}
+
+int pa_oss_get_hw_description(const char *dev, char *name, size_t l) {
+ FILE *f;
+ int n, r = -1;
+ int b = 0;
+
+ if ((n = get_device_number(dev)) < 0)
+ return -1;
+
+ if (!(f = fopen("/dev/sndstat", "r")) &&
+ !(f = fopen("/proc/sndstat", "r")) &&
+ !(f = fopen("/proc/asound/oss/sndstat", "r"))) {
+
+ if (errno != ENOENT)
+ pa_log_warn("failed to open OSS sndstat device: %s", pa_cstrerror(errno));
+
+ return -1;
+ }
+
+ while (!feof(f)) {
+ char line[64];
+ int device;
+
+ if (!fgets(line, sizeof(line), f))
+ break;
+
+ line[strcspn(line, "\r\n")] = 0;
+
+ if (!b) {
+ b = strcmp(line, "Audio devices:") == 0;
+ continue;
+ }
+
+ if (line[0] == 0)
+ break;
+
+ if (sscanf(line, "%i: ", &device) != 1)
+ continue;
+
+ if (device == n) {
+ char *k = strchr(line, ':');
+ pa_assert(k);
+ k++;
+ k += strspn(k, " ");
+
+ if (pa_endswith(k, " (DUPLEX)"))
+ k[strlen(k)-9] = 0;
+
+ pa_strlcpy(name, k, l);
+ r = 0;
+ break;
+ }
+ }
+
+ fclose(f);
+ return r;
+}
+
+static int open_mixer(const char *mixer) {
+ int fd;
+
+ if ((fd = open(mixer, O_RDWR|O_NDELAY|O_NOCTTY)) >= 0)
+ return fd;
+
+ return -1;
+}
+
+int pa_oss_open_mixer_for_device(const char *device) {
+ int n;
+ char *fn;
+ int fd;
+
+ if ((n = get_device_number(device)) < 0)
+ return -1;
+
+ if (n == 0)
+ if ((fd = open_mixer("/dev/mixer")) >= 0)
+ return fd;
+
+ fn = pa_sprintf_malloc("/dev/mixer%i", n);
+ fd = open_mixer(fn);
+ pa_xfree(fn);
+
+ if (fd < 0)
+ pa_log_warn("Failed to open mixer '%s': %s", device, pa_cstrerror(errno));
+
+ return fd;
+}
diff --git a/src/modules/oss-util.h b/src/modules/oss-util.h
new file mode 100644
index 00000000..259a622a
--- /dev/null
+++ b/src/modules/oss-util.h
@@ -0,0 +1,43 @@
+#ifndef fooossutilhfoo
+#define fooossutilhfoo
+
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <pulse/sample.h>
+#include <pulse/volume.h>
+
+int pa_oss_open(const char *device, int *mode, int* pcaps);
+int pa_oss_auto_format(int fd, pa_sample_spec *ss);
+
+int pa_oss_set_fragments(int fd, int frags, int frag_size);
+
+int pa_oss_set_volume(int fd, long mixer, const pa_sample_spec *ss, const pa_cvolume *volume);
+int pa_oss_get_volume(int fd, int mixer, const pa_sample_spec *ss, pa_cvolume *volume);
+
+int pa_oss_get_hw_description(const char *dev, char *name, size_t l);
+
+int pa_oss_open_mixer_for_device(const char *device);
+
+#endif
diff --git a/src/modules/rtp/Makefile b/src/modules/rtp/Makefile
new file mode 100644
index 00000000..316beb72
--- /dev/null
+++ b/src/modules/rtp/Makefile
@@ -0,0 +1,13 @@
+# This is a dirty trick just to ease compilation with emacs
+#
+# This file is not intended to be distributed or anything
+#
+# So: don't touch it, even better ignore it!
+
+all:
+ $(MAKE) -C ../..
+
+clean:
+ $(MAKE) -C ../.. clean
+
+.PHONY: all clean
diff --git a/src/modules/rtp/module-rtp-recv.c b/src/modules/rtp/module-rtp-recv.c
new file mode 100644
index 00000000..d8e7a781
--- /dev/null
+++ b/src/modules/rtp/module-rtp-recv.c
@@ -0,0 +1,600 @@
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <poll.h>
+
+#include <pulse/timeval.h>
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/core-error.h>
+#include <pulsecore/module.h>
+#include <pulsecore/llist.h>
+#include <pulsecore/sink.h>
+#include <pulsecore/sink-input.h>
+#include <pulsecore/memblockq.h>
+#include <pulsecore/log.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/namereg.h>
+#include <pulsecore/sample-util.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/atomic.h>
+#include <pulsecore/rtclock.h>
+#include <pulsecore/atomic.h>
+
+#include "module-rtp-recv-symdef.h"
+
+#include "rtp.h"
+#include "sdp.h"
+#include "sap.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering");
+PA_MODULE_DESCRIPTION("Recieve data from a network via RTP/SAP/SDP");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(FALSE);
+PA_MODULE_USAGE(
+ "sink=<name of the sink> "
+ "sap_address=<multicast address to listen on> "
+);
+
+#define SAP_PORT 9875
+#define DEFAULT_SAP_ADDRESS "224.0.0.56"
+#define MEMBLOCKQ_MAXLENGTH (1024*170)
+#define MAX_SESSIONS 16
+#define DEATH_TIMEOUT 20
+
+static const char* const valid_modargs[] = {
+ "sink",
+ "sap_address",
+ NULL
+};
+
+struct session {
+ struct userdata *userdata;
+ PA_LLIST_FIELDS(struct session);
+
+ pa_sink_input *sink_input;
+ pa_memblockq *memblockq;
+
+ pa_bool_t first_packet;
+ uint32_t ssrc;
+ uint32_t offset;
+
+ struct pa_sdp_info sdp_info;
+
+ pa_rtp_context rtp_context;
+
+ pa_rtpoll_item *rtpoll_item;
+
+ pa_atomic_t timestamp;
+};
+
+struct userdata {
+ pa_module *module;
+
+ pa_sap_context sap_context;
+ pa_io_event* sap_event;
+
+ pa_time_event *check_death_event;
+
+ char *sink_name;
+
+ PA_LLIST_HEAD(struct session, sessions);
+ pa_hashmap *by_origin;
+ int n_sessions;
+};
+
+static void session_free(struct session *s);
+
+/* Called from I/O thread context */
+static int sink_input_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+ struct session *s = PA_SINK_INPUT(o)->userdata;
+
+ switch (code) {
+ case PA_SINK_INPUT_MESSAGE_GET_LATENCY:
+ *((pa_usec_t*) data) = pa_bytes_to_usec(pa_memblockq_get_length(s->memblockq), &s->sink_input->sample_spec);
+
+ /* Fall through, the default handler will add in the extra
+ * latency added by the resampler */
+ break;
+ }
+
+ return pa_sink_input_process_msg(o, code, data, offset, chunk);
+}
+
+/* Called from I/O thread context */
+static int sink_input_peek(pa_sink_input *i, size_t length, pa_memchunk *chunk) {
+ struct session *s;
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(s = i->userdata);
+
+ return pa_memblockq_peek(s->memblockq, chunk);
+}
+
+/* Called from I/O thread context */
+static void sink_input_drop(pa_sink_input *i, size_t length) {
+ struct session *s;
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(s = i->userdata);
+
+ pa_memblockq_drop(s->memblockq, length);
+}
+
+/* Called from main context */
+static void sink_input_kill(pa_sink_input* i) {
+ struct session *s;
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(s = i->userdata);
+
+ session_free(s);
+}
+
+/* Called from I/O thread context */
+static int rtpoll_work_cb(pa_rtpoll_item *i) {
+ pa_memchunk chunk;
+ int64_t k, j, delta;
+ struct timeval now;
+ struct session *s;
+ struct pollfd *p;
+
+ pa_assert_se(s = pa_rtpoll_item_get_userdata(i));
+
+ p = pa_rtpoll_item_get_pollfd(i, NULL);
+
+ if (p->revents & (POLLERR|POLLNVAL|POLLHUP|POLLOUT)) {
+ pa_log("poll() signalled bad revents.");
+ return -1;
+ }
+
+ if ((p->revents & POLLIN) == 0)
+ return 0;
+
+ p->revents = 0;
+
+ if (pa_rtp_recv(&s->rtp_context, &chunk, s->userdata->module->core->mempool) < 0)
+ return 0;
+
+ if (s->sdp_info.payload != s->rtp_context.payload) {
+ pa_memblock_unref(chunk.memblock);
+ return 0;
+ }
+
+ if (!s->first_packet) {
+ s->first_packet = TRUE;
+
+ s->ssrc = s->rtp_context.ssrc;
+ s->offset = s->rtp_context.timestamp;
+
+ if (s->ssrc == s->userdata->module->core->cookie)
+ pa_log_warn("Detected RTP packet loop!");
+ } else {
+ if (s->ssrc != s->rtp_context.ssrc) {
+ pa_memblock_unref(chunk.memblock);
+ return 0;
+ }
+ }
+
+ /* Check wheter there was a timestamp overflow */
+ k = (int64_t) s->rtp_context.timestamp - (int64_t) s->offset;
+ j = (int64_t) 0x100000000LL - (int64_t) s->offset + (int64_t) s->rtp_context.timestamp;
+
+ if ((k < 0 ? -k : k) < (j < 0 ? -j : j))
+ delta = k;
+ else
+ delta = j;
+
+ pa_memblockq_seek(s->memblockq, delta * s->rtp_context.frame_size, PA_SEEK_RELATIVE);
+
+ if (pa_memblockq_push(s->memblockq, &chunk) < 0) {
+ /* queue overflow, let's flush it and try again */
+ pa_memblockq_flush(s->memblockq);
+ pa_memblockq_push(s->memblockq, &chunk);
+ }
+
+ /* The next timestamp we expect */
+ s->offset = s->rtp_context.timestamp + (chunk.length / s->rtp_context.frame_size);
+
+ pa_memblock_unref(chunk.memblock);
+
+ pa_rtclock_get(&now);
+ pa_atomic_store(&s->timestamp, now.tv_sec);
+
+ return 1;
+}
+
+/* Called from I/O thread context */
+static void sink_input_attach(pa_sink_input *i) {
+ struct session *s;
+ struct pollfd *p;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(s = i->userdata);
+
+ pa_assert(!s->rtpoll_item);
+ s->rtpoll_item = pa_rtpoll_item_new(i->sink->rtpoll, PA_RTPOLL_LATE, 1);
+
+ p = pa_rtpoll_item_get_pollfd(s->rtpoll_item, NULL);
+ p->fd = s->rtp_context.fd;
+ p->events = POLLIN;
+ p->revents = 0;
+
+ pa_rtpoll_item_set_work_callback(s->rtpoll_item, rtpoll_work_cb);
+ pa_rtpoll_item_set_userdata(s->rtpoll_item, s);
+}
+
+/* Called from I/O thread context */
+static void sink_input_detach(pa_sink_input *i) {
+ struct session *s;
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(s = i->userdata);
+
+ pa_assert(s->rtpoll_item);
+ pa_rtpoll_item_free(s->rtpoll_item);
+ s->rtpoll_item = NULL;
+}
+
+static int mcast_socket(const struct sockaddr* sa, socklen_t salen) {
+ int af, fd = -1, r, one;
+
+ pa_assert(sa);
+ pa_assert(salen > 0);
+
+ af = sa->sa_family;
+ if ((fd = socket(af, SOCK_DGRAM, 0)) < 0) {
+ pa_log("Failed to create socket: %s", pa_cstrerror(errno));
+ goto fail;
+ }
+
+ one = 1;
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0) {
+ pa_log("SO_REUSEADDR failed: %s", pa_cstrerror(errno));
+ goto fail;
+ }
+
+ if (af == AF_INET) {
+ struct ip_mreq mr4;
+ memset(&mr4, 0, sizeof(mr4));
+ mr4.imr_multiaddr = ((const struct sockaddr_in*) sa)->sin_addr;
+ r = setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mr4, sizeof(mr4));
+ } else {
+ struct ipv6_mreq mr6;
+ memset(&mr6, 0, sizeof(mr6));
+ mr6.ipv6mr_multiaddr = ((const struct sockaddr_in6*) sa)->sin6_addr;
+ r = setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mr6, sizeof(mr6));
+ }
+
+ if (r < 0) {
+ pa_log_info("Joining mcast group failed: %s", pa_cstrerror(errno));
+ goto fail;
+ }
+
+ if (bind(fd, sa, salen) < 0) {
+ pa_log("bind() failed: %s", pa_cstrerror(errno));
+ goto fail;
+ }
+
+ return fd;
+
+fail:
+ if (fd >= 0)
+ close(fd);
+
+ return -1;
+}
+
+static struct session *session_new(struct userdata *u, const pa_sdp_info *sdp_info) {
+ struct session *s = NULL;
+ char *c;
+ pa_sink *sink;
+ int fd = -1;
+ pa_memblock *silence;
+ pa_sink_input_new_data data;
+ struct timeval now;
+
+ pa_assert(u);
+ pa_assert(sdp_info);
+
+ if (u->n_sessions >= MAX_SESSIONS) {
+ pa_log("Session limit reached.");
+ goto fail;
+ }
+
+ if (!(sink = pa_namereg_get(u->module->core, u->sink_name, PA_NAMEREG_SINK, 1))) {
+ pa_log("Sink does not exist.");
+ goto fail;
+ }
+
+ s = pa_xnew0(struct session, 1);
+ s->userdata = u;
+ s->first_packet = FALSE;
+ s->sdp_info = *sdp_info;
+ s->rtpoll_item = NULL;
+
+ pa_rtclock_get(&now);
+ pa_atomic_store(&s->timestamp, now.tv_sec);
+
+ if ((fd = mcast_socket((const struct sockaddr*) &sdp_info->sa, sdp_info->salen)) < 0)
+ goto fail;
+
+ c = pa_sprintf_malloc("RTP Stream%s%s%s",
+ sdp_info->session_name ? " (" : "",
+ sdp_info->session_name ? sdp_info->session_name : "",
+ sdp_info->session_name ? ")" : "");
+
+ pa_sink_input_new_data_init(&data);
+ data.sink = sink;
+ data.driver = __FILE__;
+ data.name = c;
+ data.module = u->module;
+ pa_sink_input_new_data_set_sample_spec(&data, &sdp_info->sample_spec);
+
+ s->sink_input = pa_sink_input_new(u->module->core, &data, 0);
+ pa_xfree(c);
+
+ if (!s->sink_input) {
+ pa_log("Failed to create sink input.");
+ goto fail;
+ }
+
+ s->sink_input->userdata = s;
+
+ s->sink_input->parent.process_msg = sink_input_process_msg;
+ s->sink_input->peek = sink_input_peek;
+ s->sink_input->drop = sink_input_drop;
+ s->sink_input->kill = sink_input_kill;
+ s->sink_input->attach = sink_input_attach;
+ s->sink_input->detach = sink_input_detach;
+
+ silence = pa_silence_memblock_new(
+ s->userdata->module->core->mempool,
+ &s->sink_input->sample_spec,
+ pa_frame_align(pa_bytes_per_second(&s->sink_input->sample_spec)/128, &s->sink_input->sample_spec));
+
+ s->memblockq = pa_memblockq_new(
+ 0,
+ MEMBLOCKQ_MAXLENGTH,
+ MEMBLOCKQ_MAXLENGTH,
+ pa_frame_size(&s->sink_input->sample_spec),
+ pa_bytes_per_second(&s->sink_input->sample_spec)/10+1,
+ 0,
+ silence);
+
+ pa_memblock_unref(silence);
+
+ pa_rtp_context_init_recv(&s->rtp_context, fd, pa_frame_size(&s->sdp_info.sample_spec));
+
+ pa_hashmap_put(s->userdata->by_origin, s->sdp_info.origin, s);
+ u->n_sessions++;
+ PA_LLIST_PREPEND(struct session, s->userdata->sessions, s);
+
+ pa_sink_input_put(s->sink_input);
+
+ pa_log_info("New session '%s'", s->sdp_info.session_name);
+
+ return s;
+
+fail:
+ pa_xfree(s);
+
+ if (fd >= 0)
+ pa_close(fd);
+
+ return NULL;
+}
+
+static void session_free(struct session *s) {
+ pa_assert(s);
+
+ pa_log_info("Freeing session '%s'", s->sdp_info.session_name);
+
+ pa_sink_input_unlink(s->sink_input);
+ pa_sink_input_unref(s->sink_input);
+
+ PA_LLIST_REMOVE(struct session, s->userdata->sessions, s);
+ pa_assert(s->userdata->n_sessions >= 1);
+ s->userdata->n_sessions--;
+ pa_hashmap_remove(s->userdata->by_origin, s->sdp_info.origin);
+
+ pa_memblockq_free(s->memblockq);
+ pa_sdp_info_destroy(&s->sdp_info);
+ pa_rtp_context_destroy(&s->rtp_context);
+
+ pa_xfree(s);
+}
+
+static void sap_event_cb(pa_mainloop_api *m, pa_io_event *e, int fd, pa_io_event_flags_t flags, void *userdata) {
+ struct userdata *u = userdata;
+ int goodbye;
+ pa_sdp_info info;
+ struct session *s;
+
+ pa_assert(m);
+ pa_assert(e);
+ pa_assert(u);
+ pa_assert(fd == u->sap_context.fd);
+ pa_assert(flags == PA_IO_EVENT_INPUT);
+
+ if (pa_sap_recv(&u->sap_context, &goodbye) < 0)
+ return;
+
+ if (!pa_sdp_parse(u->sap_context.sdp_data, &info, goodbye))
+ return;
+
+ if (goodbye) {
+
+ if ((s = pa_hashmap_get(u->by_origin, info.origin)))
+ session_free(s);
+
+ pa_sdp_info_destroy(&info);
+ } else {
+
+ if (!(s = pa_hashmap_get(u->by_origin, info.origin))) {
+ if (!(s = session_new(u, &info)))
+ pa_sdp_info_destroy(&info);
+
+ } else {
+ struct timeval now;
+ pa_rtclock_get(&now);
+ pa_atomic_store(&s->timestamp, now.tv_sec);
+
+ pa_sdp_info_destroy(&info);
+ }
+ }
+}
+
+static void check_death_event_cb(pa_mainloop_api *m, pa_time_event *t, const struct timeval *ptv, void *userdata) {
+ struct session *s, *n;
+ struct userdata *u = userdata;
+ struct timeval now;
+ struct timeval tv;
+
+ pa_assert(m);
+ pa_assert(t);
+ pa_assert(ptv);
+ pa_assert(u);
+
+ pa_rtclock_get(&now);
+
+ pa_log_debug("Checking for dead streams ...");
+
+ for (s = u->sessions; s; s = n) {
+ int k;
+ n = s->next;
+
+ k = pa_atomic_load(&s->timestamp);
+
+ if (k + DEATH_TIMEOUT < now.tv_sec)
+ session_free(s);
+ }
+
+ /* Restart timer */
+ pa_gettimeofday(&tv);
+ pa_timeval_add(&tv, DEATH_TIMEOUT*PA_USEC_PER_SEC);
+ m->time_restart(t, &tv);
+}
+
+int pa__init(pa_module*m) {
+ struct userdata *u;
+ pa_modargs *ma = NULL;
+ struct sockaddr_in sa4;
+ struct sockaddr_in6 sa6;
+ struct sockaddr *sa;
+ socklen_t salen;
+ const char *sap_address;
+ int fd = -1;
+ struct timeval tv;
+
+ pa_assert(m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log("failed to parse module arguments");
+ goto fail;
+ }
+
+ sap_address = pa_modargs_get_value(ma, "sap_address", DEFAULT_SAP_ADDRESS);
+
+ if (inet_pton(AF_INET6, sap_address, &sa6.sin6_addr) > 0) {
+ sa6.sin6_family = AF_INET6;
+ sa6.sin6_port = htons(SAP_PORT);
+ sa = (struct sockaddr*) &sa6;
+ salen = sizeof(sa6);
+ } else if (inet_pton(AF_INET, sap_address, &sa4.sin_addr) > 0) {
+ sa4.sin_family = AF_INET;
+ sa4.sin_port = htons(SAP_PORT);
+ sa = (struct sockaddr*) &sa4;
+ salen = sizeof(sa4);
+ } else {
+ pa_log("Invalid SAP address '%s'", sap_address);
+ goto fail;
+ }
+
+ if ((fd = mcast_socket(sa, salen)) < 0)
+ goto fail;
+
+ u = pa_xnew(struct userdata, 1);
+ m->userdata = u;
+ u->module = m;
+ u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL));
+
+ u->sap_event = m->core->mainloop->io_new(m->core->mainloop, fd, PA_IO_EVENT_INPUT, sap_event_cb, u);
+ pa_sap_context_init_recv(&u->sap_context, fd);
+
+ PA_LLIST_HEAD_INIT(struct session, u->sessions);
+ u->n_sessions = 0;
+ u->by_origin = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+
+ pa_gettimeofday(&tv);
+ pa_timeval_add(&tv, DEATH_TIMEOUT * PA_USEC_PER_SEC);
+ u->check_death_event = m->core->mainloop->time_new(m->core->mainloop, &tv, check_death_event_cb, u);
+
+ pa_modargs_free(ma);
+
+ return 0;
+
+fail:
+ if (ma)
+ pa_modargs_free(ma);
+
+ if (fd >= 0)
+ pa_close(fd);
+
+ return -1;
+}
+
+void pa__done(pa_module*m) {
+ struct userdata *u;
+ struct session *s;
+
+ pa_assert(m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->sap_event)
+ m->core->mainloop->io_free(u->sap_event);
+
+ if (u->check_death_event)
+ m->core->mainloop->time_free(u->check_death_event);
+
+ pa_sap_context_destroy(&u->sap_context);
+
+ if (u->by_origin) {
+ while ((s = pa_hashmap_get_first(u->by_origin)))
+ session_free(s);
+
+ pa_hashmap_free(u->by_origin, NULL, NULL);
+ }
+
+ pa_xfree(u->sink_name);
+ pa_xfree(u);
+}
diff --git a/src/modules/rtp/module-rtp-send.c b/src/modules/rtp/module-rtp-send.c
new file mode 100644
index 00000000..95ff15de
--- /dev/null
+++ b/src/modules/rtp/module-rtp-send.c
@@ -0,0 +1,397 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <pulse/timeval.h>
+#include <pulse/util.h>
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/core-error.h>
+#include <pulsecore/module.h>
+#include <pulsecore/llist.h>
+#include <pulsecore/source.h>
+#include <pulsecore/source-output.h>
+#include <pulsecore/memblockq.h>
+#include <pulsecore/log.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/namereg.h>
+#include <pulsecore/sample-util.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/socket-util.h>
+
+#include "module-rtp-send-symdef.h"
+
+#include "rtp.h"
+#include "sdp.h"
+#include "sap.h"
+
+PA_MODULE_AUTHOR("Lennart Poettering");
+PA_MODULE_DESCRIPTION("Read data from source and send it to the network via RTP/SAP/SDP");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(FALSE);
+PA_MODULE_USAGE(
+ "source=<name of the source> "
+ "format=<sample format> "
+ "channels=<number of channels> "
+ "rate=<sample rate> "
+ "destination=<destination IP address> "
+ "port=<port number> "
+ "mtu=<maximum transfer unit> "
+ "loop=<loopback to local host?>"
+);
+
+#define DEFAULT_PORT 46000
+#define SAP_PORT 9875
+#define DEFAULT_DESTINATION "224.0.0.56"
+#define MEMBLOCKQ_MAXLENGTH (1024*170)
+#define DEFAULT_MTU 1280
+#define SAP_INTERVAL 5
+
+static const char* const valid_modargs[] = {
+ "source",
+ "format",
+ "channels",
+ "rate",
+ "destination",
+ "port",
+ "mtu" ,
+ "loop",
+ NULL
+};
+
+struct userdata {
+ pa_module *module;
+
+ pa_source_output *source_output;
+ pa_memblockq *memblockq;
+
+ pa_rtp_context rtp_context;
+ pa_sap_context sap_context;
+ size_t mtu;
+
+ pa_time_event *sap_event;
+};
+
+/* Called from I/O thread context */
+static int source_output_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+ struct userdata *u;
+ pa_assert_se(u = PA_SOURCE_OUTPUT(o)->userdata);
+
+ switch (code) {
+ case PA_SOURCE_OUTPUT_MESSAGE_GET_LATENCY:
+ *((pa_usec_t*) data) = pa_bytes_to_usec(pa_memblockq_get_length(u->memblockq), &u->source_output->sample_spec);
+
+ /* Fall through, the default handler will add in the extra
+ * latency added by the resampler */
+ break;
+ }
+
+ return pa_source_output_process_msg(o, code, data, offset, chunk);
+}
+
+/* Called from I/O thread context */
+static void source_output_push(pa_source_output *o, const pa_memchunk *chunk) {
+ struct userdata *u;
+ pa_source_output_assert_ref(o);
+ pa_assert_se(u = o->userdata);
+
+ if (pa_memblockq_push(u->memblockq, chunk) < 0) {
+ pa_log_warn("Failed to push chunk into memblockq.");
+ return;
+ }
+
+ pa_rtp_send(&u->rtp_context, u->mtu, u->memblockq);
+}
+
+/* Called from main context */
+static void source_output_kill(pa_source_output* o) {
+ struct userdata *u;
+ pa_source_output_assert_ref(o);
+ pa_assert_se(u = o->userdata);
+
+ pa_module_unload_request(u->module);
+
+ pa_source_output_unlink(u->source_output);
+ pa_source_output_unref(u->source_output);
+ u->source_output = NULL;
+}
+
+static void sap_event_cb(pa_mainloop_api *m, pa_time_event *t, const struct timeval *tv, void *userdata) {
+ struct userdata *u = userdata;
+ struct timeval next;
+
+ pa_assert(m);
+ pa_assert(t);
+ pa_assert(tv);
+ pa_assert(u);
+
+ pa_sap_send(&u->sap_context, 0);
+
+ pa_gettimeofday(&next);
+ pa_timeval_add(&next, SAP_INTERVAL * PA_USEC_PER_SEC);
+ m->time_restart(t, &next);
+}
+
+int pa__init(pa_module*m) {
+ struct userdata *u;
+ pa_modargs *ma = NULL;
+ const char *dest;
+ uint32_t port = DEFAULT_PORT, mtu;
+ int af, fd = -1, sap_fd = -1;
+ pa_source *s;
+ pa_sample_spec ss;
+ pa_channel_map cm;
+ struct sockaddr_in sa4, sap_sa4;
+ struct sockaddr_in6 sa6, sap_sa6;
+ struct sockaddr_storage sa_dst;
+ pa_source_output *o = NULL;
+ uint8_t payload;
+ char *p;
+ int r, j;
+ socklen_t k;
+ struct timeval tv;
+ char hn[128], *n;
+ pa_bool_t loop = FALSE;
+ pa_source_output_new_data data;
+
+ pa_assert(m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log("Failed to parse module arguments");
+ goto fail;
+ }
+
+ if (!(s = pa_namereg_get(m->core, pa_modargs_get_value(ma, "source", NULL), PA_NAMEREG_SOURCE, 1))) {
+ pa_log("Source does not exist.");
+ goto fail;
+ }
+
+ if (pa_modargs_get_value_boolean(ma, "loop", &loop) < 0) {
+ pa_log("Failed to parse \"loop\" parameter.");
+ goto fail;
+ }
+
+ ss = s->sample_spec;
+ pa_rtp_sample_spec_fixup(&ss);
+ cm = s->channel_map;
+ if (pa_modargs_get_sample_spec(ma, &ss) < 0) {
+ pa_log("Failed to parse sample specification");
+ goto fail;
+ }
+
+ if (!pa_rtp_sample_spec_valid(&ss)) {
+ pa_log("Specified sample type not compatible with RTP");
+ goto fail;
+ }
+
+ if (ss.channels != cm.channels)
+ pa_channel_map_init_auto(&cm, ss.channels, PA_CHANNEL_MAP_AIFF);
+
+ payload = pa_rtp_payload_from_sample_spec(&ss);
+
+ mtu = pa_frame_align(DEFAULT_MTU, &ss);
+
+ if (pa_modargs_get_value_u32(ma, "mtu", &mtu) < 0 || mtu < 1 || mtu % pa_frame_size(&ss) != 0) {
+ pa_log("Invalid MTU.");
+ goto fail;
+ }
+
+ port = DEFAULT_PORT + ((rand() % 512) << 1);
+ if (pa_modargs_get_value_u32(ma, "port", &port) < 0 || port < 1 || port > 0xFFFF) {
+ pa_log("port= expects a numerical argument between 1 and 65535.");
+ goto fail;
+ }
+
+ if (port & 1)
+ pa_log_warn("Port number not even as suggested in RFC3550!");
+
+ dest = pa_modargs_get_value(ma, "destination", DEFAULT_DESTINATION);
+
+ if (inet_pton(AF_INET6, dest, &sa6.sin6_addr) > 0) {
+ sa6.sin6_family = af = AF_INET6;
+ sa6.sin6_port = htons(port);
+ sap_sa6 = sa6;
+ sap_sa6.sin6_port = htons(SAP_PORT);
+ } else if (inet_pton(AF_INET, dest, &sa4.sin_addr) > 0) {
+ sa4.sin_family = af = AF_INET;
+ sa4.sin_port = htons(port);
+ sap_sa4 = sa4;
+ sap_sa4.sin_port = htons(SAP_PORT);
+ } else {
+ pa_log("Invalid destination '%s'", dest);
+ goto fail;
+ }
+
+ if ((fd = socket(af, SOCK_DGRAM, 0)) < 0) {
+ pa_log("socket() failed: %s", pa_cstrerror(errno));
+ goto fail;
+ }
+
+ if (connect(fd, af == AF_INET ? (struct sockaddr*) &sa4 : (struct sockaddr*) &sa6, af == AF_INET ? sizeof(sa4) : sizeof(sa6)) < 0) {
+ pa_log("connect() failed: %s", pa_cstrerror(errno));
+ goto fail;
+ }
+
+ if ((sap_fd = socket(af, SOCK_DGRAM, 0)) < 0) {
+ pa_log("socket() failed: %s", pa_cstrerror(errno));
+ goto fail;
+ }
+
+ if (connect(sap_fd, af == AF_INET ? (struct sockaddr*) &sap_sa4 : (struct sockaddr*) &sap_sa6, af == AF_INET ? sizeof(sap_sa4) : sizeof(sap_sa6)) < 0) {
+ pa_log("connect() failed: %s", pa_cstrerror(errno));
+ goto fail;
+ }
+
+ j = !!loop;
+ if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, &j, sizeof(j)) < 0 ||
+ setsockopt(sap_fd, IPPROTO_IP, IP_MULTICAST_LOOP, &j, sizeof(j)) < 0) {
+ pa_log("IP_MULTICAST_LOOP failed: %s", pa_cstrerror(errno));
+ goto fail;
+ }
+
+ /* If the socket queue is full, let's drop packets */
+ pa_make_fd_nonblock(fd);
+ pa_make_udp_socket_low_delay(fd);
+ pa_make_fd_cloexec(fd);
+ pa_make_fd_cloexec(sap_fd);
+
+ pa_source_output_new_data_init(&data);
+ data.name = "RTP Monitor Stream";
+ data.driver = __FILE__;
+ data.module = m;
+ data.source = s;
+ pa_source_output_new_data_set_sample_spec(&data, &ss);
+ pa_source_output_new_data_set_channel_map(&data, &cm);
+
+ if (!(o = pa_source_output_new(m->core, &data, 0))) {
+ pa_log("failed to create source output.");
+ goto fail;
+ }
+
+ o->parent.process_msg = source_output_process_msg;
+ o->push = source_output_push;
+ o->kill = source_output_kill;
+
+ u = pa_xnew(struct userdata, 1);
+ m->userdata = u;
+ o->userdata = u;
+
+ u->module = m;
+ u->source_output = o;
+
+ u->memblockq = pa_memblockq_new(
+ 0,
+ MEMBLOCKQ_MAXLENGTH,
+ MEMBLOCKQ_MAXLENGTH,
+ pa_frame_size(&ss),
+ 1,
+ 0,
+ NULL);
+
+ u->mtu = mtu;
+
+ k = sizeof(sa_dst);
+ pa_assert_se((r = getsockname(fd, (struct sockaddr*) &sa_dst, &k)) >= 0);
+
+ n = pa_sprintf_malloc("PulseAudio RTP Stream on %s", pa_get_fqdn(hn, sizeof(hn)));
+
+ p = pa_sdp_build(af,
+ af == AF_INET ? (void*) &((struct sockaddr_in*) &sa_dst)->sin_addr : (void*) &((struct sockaddr_in6*) &sa_dst)->sin6_addr,
+ af == AF_INET ? (void*) &sa4.sin_addr : (void*) &sa6.sin6_addr,
+ n, port, payload, &ss);
+
+ pa_xfree(n);
+
+ pa_rtp_context_init_send(&u->rtp_context, fd, m->core->cookie, payload, pa_frame_size(&ss));
+ pa_sap_context_init_send(&u->sap_context, sap_fd, p);
+
+ pa_log_info("RTP stream initialized with mtu %u on %s:%u, SSRC=0x%08x, payload=%u, initial sequence #%u", mtu, dest, port, u->rtp_context.ssrc, payload, u->rtp_context.sequence);
+ pa_log_info("SDP-Data:\n%s\nEOF", p);
+
+ pa_sap_send(&u->sap_context, 0);
+
+ pa_gettimeofday(&tv);
+ pa_timeval_add(&tv, SAP_INTERVAL * PA_USEC_PER_SEC);
+ u->sap_event = m->core->mainloop->time_new(m->core->mainloop, &tv, sap_event_cb, u);
+
+ pa_source_output_put(u->source_output);
+
+ pa_modargs_free(ma);
+
+ return 0;
+
+fail:
+ if (ma)
+ pa_modargs_free(ma);
+
+ if (fd >= 0)
+ pa_close(fd);
+
+ if (sap_fd >= 0)
+ pa_close(sap_fd);
+
+ if (o) {
+ pa_source_output_unlink(o);
+ pa_source_output_unref(o);
+ }
+
+ return -1;
+}
+
+void pa__done(pa_module*m) {
+ struct userdata *u;
+ pa_assert(m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->sap_event)
+ m->core->mainloop->time_free(u->sap_event);
+
+ if (u->source_output) {
+ pa_source_output_unlink(u->source_output);
+ pa_source_output_unref(u->source_output);
+ }
+
+ pa_rtp_context_destroy(&u->rtp_context);
+
+ pa_sap_send(&u->sap_context, 1);
+ pa_sap_context_destroy(&u->sap_context);
+
+ if (u->memblockq)
+ pa_memblockq_free(u->memblockq);
+
+ pa_xfree(u);
+}
diff --git a/src/modules/rtp/rfc2327.txt b/src/modules/rtp/rfc2327.txt
new file mode 100644
index 00000000..ce77de61
--- /dev/null
+++ b/src/modules/rtp/rfc2327.txt
@@ -0,0 +1,2355 @@
+
+
+
+
+
+
+Network Working Group M. Handley
+Request for Comments: 2327 V. Jacobson
+Category: Standards Track ISI/LBNL
+ April 1998
+
+
+ SDP: Session Description Protocol
+
+Status of this Memo
+
+ This document specifies an Internet standards track protocol for the
+ Internet community, and requests discussion and suggestions for
+ improvements. Please refer to the current edition of the "Internet
+ Official Protocol Standards" (STD 1) for the standardization state
+ and status of this protocol. Distribution of this memo is unlimited.
+
+Copyright Notice
+
+ Copyright (C) The Internet Society (1998). All Rights Reserved.
+
+Abstract
+
+ This document defines the Session Description Protocol, SDP. SDP is
+ intended for describing multimedia sessions for the purposes of
+ session announcement, session invitation, and other forms of
+ multimedia session initiation.
+
+ This document is a product of the Multiparty Multimedia Session
+ Control (MMUSIC) working group of the Internet Engineering Task
+ Force. Comments are solicited and should be addressed to the working
+ group's mailing list at confctrl@isi.edu and/or the authors.
+
+1. Introduction
+
+ On the Internet multicast backbone (Mbone), a session directory tool
+ is used to advertise multimedia conferences and communicate the
+ conference addresses and conference tool-specific information
+ necessary for participation. This document defines a session
+ description protocol for this purpose, and for general real-time
+ multimedia session description purposes. This memo does not describe
+ multicast address allocation or the distribution of SDP messages in
+ detail. These are described in accompanying memos. SDP is not
+ intended for negotiation of media encodings.
+
+
+
+
+
+
+
+
+Handley & Jacobson Standards Track [Page 1]
+
+RFC 2327 SDP April 1998
+
+
+2. Background
+
+ The Mbone is the part of the internet that supports IP multicast, and
+ thus permits efficient many-to-many communication. It is used
+ extensively for multimedia conferencing. Such conferences usually
+ have the property that tight coordination of conference membership is
+ not necessary; to receive a conference, a user at an Mbone site only
+ has to know the conference's multicast group address and the UDP
+ ports for the conference data streams.
+
+ Session directories assist the advertisement of conference sessions
+ and communicate the relevant conference setup information to
+ prospective participants. SDP is designed to convey such information
+ to recipients. SDP is purely a format for session description - it
+ does not incorporate a transport protocol, and is intended to use
+ different transport protocols as appropriate including the Session
+ Announcement Protocol [4], Session Initiation Protocol [11], Real-
+ Time Streaming Protocol [12], electronic mail using the MIME
+ extensions, and the Hypertext Transport Protocol.
+
+ SDP is intended to be general purpose so that it can be used for a
+ wider range of network environments and applications than just
+ multicast session directories. However, it is not intended to
+ support negotiation of session content or media encodings - this is
+ viewed as outside the scope of session description.
+
+3. Glossary of Terms
+
+ The following terms are used in this document, and have specific
+ meaning within the context of this document.
+
+ Conference
+ A multimedia conference is a set of two or more communicating users
+ along with the software they are using to communicate.
+
+ Session
+ A multimedia session is a set of multimedia senders and receivers
+ and the data streams flowing from senders to receivers. A
+ multimedia conference is an example of a multimedia session.
+
+ Session Advertisement
+ See session announcement.
+
+ Session Announcement
+ A session announcement is a mechanism by which a session
+ description is conveyed to users in a proactive fashion, i.e., the
+ session description was not explicitly requested by the user.
+
+
+
+
+Handley & Jacobson Standards Track [Page 2]
+
+RFC 2327 SDP April 1998
+
+
+ Session Description
+ A well defined format for conveying sufficient information to
+ discover and participate in a multimedia session.
+
+3.1. Terminology
+
+ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
+ "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
+ document are to be interpreted as described in RFC 2119.
+
+4. SDP Usage
+
+4.1. Multicast Announcements
+
+ SDP is a session description protocol for multimedia sessions. A
+ common mode of usage is for a client to announce a conference session
+ by periodically multicasting an announcement packet to a well known
+ multicast address and port using the Session Announcement Protocol
+ (SAP).
+
+ SAP packets are UDP packets with the following format:
+
+ |--------------------|
+ | SAP header |
+ |--------------------|
+ | text payload |
+ |//////////
+
+
+ The header is the Session Announcement Protocol header. SAP is
+ described in more detail in a companion memo [4]
+
+ The text payload is an SDP session description, as described in this
+ memo. The text payload should be no greater than 1 Kbyte in length.
+ If announced by SAP, only one session announcement is permitted in a
+ single packet.
+
+4.2. Email and WWW Announcements
+
+ Alternative means of conveying session descriptions include
+ electronic mail and the World Wide Web. For both email and WWW
+ distribution, the use of the MIME content type "application/sdp"
+ should be used. This enables the automatic launching of applications
+ for participation in the session from the WWW client or mail reader
+ in a standard manner.
+
+
+
+
+
+
+Handley & Jacobson Standards Track [Page 3]
+
+RFC 2327 SDP April 1998
+
+
+ Note that announcements of multicast sessions made only via email or
+ the World Wide Web (WWW) do not have the property that the receiver
+ of a session announcement can necessarily receive the session because
+ the multicast sessions may be restricted in scope, and access to the
+ WWW server or reception of email is possible outside this scope. SAP
+ announcements do not suffer from this mismatch.
+
+5. Requirements and Recommendations
+
+ The purpose of SDP is to convey information about media streams in
+ multimedia sessions to allow the recipients of a session description
+ to participate in the session. SDP is primarily intended for use in
+ an internetwork, although it is sufficiently general that it can
+ describe conferences in other network environments.
+
+ A multimedia session, for these purposes, is defined as a set of
+ media streams that exist for some duration of time. Media streams
+ can be many-to-many. The times during which the session is active
+ need not be continuous.
+
+ Thus far, multicast based sessions on the Internet have differed from
+ many other forms of conferencing in that anyone receiving the traffic
+ can join the session (unless the session traffic is encrypted). In
+ such an environment, SDP serves two primary purposes. It is a means
+ to communicate the existence of a session, and is a means to convey
+ sufficient information to enable joining and participating in the
+ session. In a unicast environment, only the latter purpose is likely
+ to be relevant.
+
+ Thus SDP includes:
+
+ o Session name and purpose
+
+ o Time(s) the session is active
+
+ o The media comprising the session
+
+ o Information to receive those media (addresses, ports, formats and
+ so on)
+
+ As resources necessary to participate in a session may be limited,
+ some additional information may also be desirable:
+
+ o Information about the bandwidth to be used by the conference
+
+ o Contact information for the person responsible for the session
+
+
+
+
+
+Handley & Jacobson Standards Track [Page 4]
+
+RFC 2327 SDP April 1998
+
+
+ In general, SDP must convey sufficient information to be able to join
+ a session (with the possible exception of encryption keys) and to
+ announce the resources to be used to non-participants that may need
+ to know.
+
+5.1. Media Information
+
+ SDP includes:
+
+ o The type of media (video, audio, etc)
+
+ o The transport protocol (RTP/UDP/IP, H.320, etc)
+
+ o The format of the media (H.261 video, MPEG video, etc)
+
+ For an IP multicast session, the following are also conveyed:
+
+ o Multicast address for media
+
+ o Transport Port for media
+
+ This address and port are the destination address and destination
+ port of the multicast stream, whether being sent, received, or both.
+
+ For an IP unicast session, the following are conveyed:
+
+ o Remote address for media
+
+ o Transport port for contact address
+
+ The semantics of this address and port depend on the media and
+ transport protocol defined. By default, this is the remote address
+ and remote port to which data is sent, and the remote address and
+ local port on which to receive data. However, some media may define
+ to use these to establish a control channel for the actual media
+ flow.
+
+5.2. Timing Information
+
+ Sessions may either be bounded or unbounded in time. Whether or not
+ they are bounded, they may be only active at specific times.
+
+ SDP can convey:
+
+ o An arbitrary list of start and stop times bounding the session
+
+ o For each bound, repeat times such as "every Wednesday at 10am for
+ one hour"
+
+
+
+Handley & Jacobson Standards Track [Page 5]
+
+RFC 2327 SDP April 1998
+
+
+ This timing information is globally consistent, irrespective of local
+ time zone or daylight saving time.
+
+5.3. Private Sessions
+
+ It is possible to create both public sessions and private sessions.
+ Private sessions will typically be conveyed by encrypting the session
+ description to distribute it. The details of how encryption is
+ performed are dependent on the mechanism used to convey SDP - see [4]
+ for how this is done for session announcements.
+
+ If a session announcement is private it is possible to use that
+ private announcement to convey encryption keys necessary to decode
+ each of the media in a conference, including enough information to
+ know which encryption scheme is used for each media.
+
+5.4. Obtaining Further Information about a Session
+
+ A session description should convey enough information to decide
+ whether or not to participate in a session. SDP may include
+ additional pointers in the form of Universal Resources Identifiers
+ (URIs) for more information about the session.
+
+5.5. Categorisation
+
+ When many session descriptions are being distributed by SAP or any
+ other advertisement mechanism, it may be desirable to filter
+ announcements that are of interest from those that are not. SDP
+ supports a categorisation mechanism for sessions that is capable of
+ being automated.
+
+5.6. Internationalization
+
+ The SDP specification recommends the use of the ISO 10646 character
+ sets in the UTF-8 encoding (RFC 2044) to allow many different
+ languages to be represented. However, to assist in compact
+ representations, SDP also allows other character sets such as ISO
+ 8859-1 to be used when desired. Internationalization only applies to
+ free-text fields (session name and background information), and not
+ to SDP as a whole.
+
+6. SDP Specification
+
+ SDP session descriptions are entirely textual using the ISO 10646
+ character set in UTF-8 encoding. SDP field names and attributes names
+ use only the US-ASCII subset of UTF-8, but textual fields and
+ attribute values may use the full ISO 10646 character set. The
+ textual form, as opposed to a binary encoding such as ASN/1 or XDR,
+
+
+
+Handley & Jacobson Standards Track [Page 6]
+
+RFC 2327 SDP April 1998
+
+
+ was chosen to enhance portability, to enable a variety of transports
+ to be used (e.g, session description in a MIME email message) and to
+ allow flexible, text-based toolkits (e.g., Tcl/Tk ) to be used to
+ generate and to process session descriptions. However, since the
+ total bandwidth allocated to all SAP announcements is strictly
+ limited, the encoding is deliberately compact. Also, since
+ announcements may be transported via very unreliable means (e.g.,
+ email) or damaged by an intermediate caching server, the encoding was
+ designed with strict order and formatting rules so that most errors
+ would result in malformed announcements which could be detected
+ easily and discarded. This also allows rapid discarding of encrypted
+ announcements for which a receiver does not have the correct key.
+
+ An SDP session description consists of a number of lines of text of
+ the form <type>=<value> <type> is always exactly one character and is
+ case-significant. <value> is a structured text string whose format
+ depends on <type>. It also will be case-significant unless a
+ specific field defines otherwise. Whitespace is not permitted either
+ side of the `=' sign. In general <value> is either a number of fields
+ delimited by a single space character or a free format string.
+
+ A session description consists of a session-level description
+ (details that apply to the whole session and all media streams) and
+ optionally several media-level descriptions (details that apply onto
+ to a single media stream).
+
+ An announcement consists of a session-level section followed by zero
+ or more media-level sections. The session-level part starts with a
+ `v=' line and continues to the first media-level section. The media
+ description starts with an `m=' line and continues to the next media
+ description or end of the whole session description. In general,
+ session-level values are the default for all media unless overridden
+ by an equivalent media-level value.
+
+ When SDP is conveyed by SAP, only one session description is allowed
+ per packet. When SDP is conveyed by other means, many SDP session
+ descriptions may be concatenated together (the `v=' line indicating
+ the start of a session description terminates the previous
+ description). Some lines in each description are required and some
+ are optional but all must appear in exactly the order given here (the
+ fixed order greatly enhances error detection and allows for a simple
+ parser). Optional items are marked with a `*'.
+
+Session description
+ v= (protocol version)
+ o= (owner/creator and session identifier).
+ s= (session name)
+ i=* (session information)
+
+
+
+Handley & Jacobson Standards Track [Page 7]
+
+RFC 2327 SDP April 1998
+
+
+ u=* (URI of description)
+ e=* (email address)
+ p=* (phone number)
+ c=* (connection information - not required if included in all media)
+ b=* (bandwidth information)
+ One or more time descriptions (see below)
+ z=* (time zone adjustments)
+ k=* (encryption key)
+ a=* (zero or more session attribute lines)
+ Zero or more media descriptions (see below)
+
+Time description
+ t= (time the session is active)
+ r=* (zero or more repeat times)
+
+Media description
+ m= (media name and transport address)
+ i=* (media title)
+ c=* (connection information - optional if included at session-level)
+ b=* (bandwidth information)
+ k=* (encryption key)
+ a=* (zero or more media attribute lines)
+
+ The set of `type' letters is deliberately small and not intended to
+ be extensible -- SDP parsers must completely ignore any announcement
+ that contains a `type' letter that it does not understand. The
+ `attribute' mechanism ("a=" described below) is the primary means for
+ extending SDP and tailoring it to particular applications or media.
+ Some attributes (the ones listed in this document) have a defined
+ meaning but others may be added on an application-, media- or
+ session-specific basis. A session directory must ignore any
+ attribute it doesn't understand.
+
+ The connection (`c=') and attribute (`a=') information in the
+ session-level section applies to all the media of that session unless
+ overridden by connection information or an attribute of the same name
+ in the media description. For instance, in the example below, each
+ media behaves as if it were given a `recvonly' attribute.
+
+ An example SDP description is:
+
+ v=0
+ o=mhandley 2890844526 2890842807 IN IP4 126.16.64.4
+ s=SDP Seminar
+ i=A Seminar on the session description protocol
+ u=http://www.cs.ucl.ac.uk/staff/M.Handley/sdp.03.ps
+ e=mjh@isi.edu (Mark Handley)
+ c=IN IP4 224.2.17.12/127
+
+
+
+Handley & Jacobson Standards Track [Page 8]
+
+RFC 2327 SDP April 1998
+
+
+ t=2873397496 2873404696
+ a=recvonly
+ m=audio 49170 RTP/AVP 0
+ m=video 51372 RTP/AVP 31
+ m=application 32416 udp wb
+ a=orient:portrait
+
+ Text records such as the session name and information are bytes
+ strings which may contain any byte with the exceptions of 0x00 (Nul),
+ 0x0a (ASCII newline) and 0x0d (ASCII carriage return). The sequence
+ CRLF (0x0d0a) is used to end a record, although parsers should be
+ tolerant and also accept records terminated with a single newline
+ character. By default these byte strings contain ISO-10646
+ characters in UTF-8 encoding, but this default may be changed using
+ the `charset' attribute.
+
+ Protocol Version
+
+ v=0
+
+ The "v=" field gives the version of the Session Description Protocol.
+ There is no minor version number.
+
+ Origin
+
+ o=<username> <session id> <version> <network type> <address type>
+ <address>
+
+ The "o=" field gives the originator of the session (their username
+ and the address of the user's host) plus a session id and session
+ version number.
+
+ <username> is the user's login on the originating host, or it is "-"
+ if the originating host does not support the concept of user ids.
+ <username> must not contain spaces. <session id> is a numeric string
+ such that the tuple of <username>, <session id>, <network type>,
+ <address type> and <address> form a globally unique identifier for
+ the session.
+
+ The method of <session id> allocation is up to the creating tool, but
+ it has been suggested that a Network Time Protocol (NTP) timestamp be
+ used to ensure uniqueness [1].
+
+ <version> is a version number for this announcement. It is needed
+ for proxy announcements to detect which of several announcements for
+ the same session is the most recent. Again its usage is up to the
+
+
+
+
+
+Handley & Jacobson Standards Track [Page 9]
+
+RFC 2327 SDP April 1998
+
+
+ creating tool, so long as <version> is increased when a modification
+ is made to the session data. Again, it is recommended (but not
+ mandatory) that an NTP timestamp is used.
+
+ <network type> is a text string giving the type of network.
+ Initially "IN" is defined to have the meaning "Internet". <address
+ type> is a text string giving the type of the address that follows.
+ Initially "IP4" and "IP6" are defined. <address> is the globally
+ unique address of the machine from which the session was created.
+ For an address type of IP4, this is either the fully-qualified domain
+ name of the machine, or the dotted-decimal representation of the IP
+ version 4 address of the machine. For an address type of IP6, this
+ is either the fully-qualified domain name of the machine, or the
+ compressed textual representation of the IP version 6 address of the
+ machine. For both IP4 and IP6, the fully-qualified domain name is
+ the form that SHOULD be given unless this is unavailable, in which
+ case the globally unique address may be substituted. A local IP
+ address MUST NOT be used in any context where the SDP description
+ might leave the scope in which the address is meaningful.
+
+ In general, the "o=" field serves as a globally unique identifier for
+ this version of this session description, and the subfields excepting
+ the version taken together identify the session irrespective of any
+ modifications.
+
+ Session Name
+
+ s=<session name>
+
+ The "s=" field is the session name. There must be one and only one
+ "s=" field per session description, and it must contain ISO 10646
+ characters (but see also the `charset' attribute below).
+
+ Session and Media Information
+
+ i=<session description>
+
+ The "i=" field is information about the session. There may be at
+ most one session-level "i=" field per session description, and at
+ most one "i=" field per media. Although it may be omitted, this is
+ discouraged for session announcements, and user interfaces for
+ composing sessions should require text to be entered. If it is
+ present it must contain ISO 10646 characters (but see also the
+ `charset' attribute below).
+
+ A single "i=" field can also be used for each media definition. In
+ media definitions, "i=" fields are primarily intended for labeling
+ media streams. As such, they are most likely to be useful when a
+
+
+
+Handley & Jacobson Standards Track [Page 10]
+
+RFC 2327 SDP April 1998
+
+
+ single session has more than one distinct media stream of the same
+ media type. An example would be two different whiteboards, one for
+ slides and one for feedback and questions.
+
+ URI
+
+ u=<URI>
+
+ o A URI is a Universal Resource Identifier as used by WWW clients
+
+ o The URI should be a pointer to additional information about the
+ conference
+
+ o This field is optional, but if it is present it should be specified
+ before the first media field
+
+ o No more than one URI field is allowed per session description
+
+
+ Email Address and Phone Number
+
+ e=<email address>
+ p=<phone number>
+
+ o These specify contact information for the person responsible for
+ the conference. This is not necessarily the same person that
+ created the conference announcement.
+
+ o Either an email field or a phone field must be specified.
+ Additional email and phone fields are allowed.
+
+ o If these are present, they should be specified before the first
+ media field.
+
+ o More than one email or phone field can be given for a session
+ description.
+
+ o Phone numbers should be given in the conventional international
+
+ format - preceded by a "+ and the international country code.
+ There must be a space or a hyphen ("-") between the country code
+ and the rest of the phone number. Spaces and hyphens may be used
+ to split up a phone field to aid readability if desired. For
+ example:
+
+ p=+44-171-380-7777 or p=+1 617 253 6011
+
+
+
+
+
+Handley & Jacobson Standards Track [Page 11]
+
+RFC 2327 SDP April 1998
+
+
+ o Both email addresses and phone numbers can have an optional free
+ text string associated with them, normally giving the name of the
+ person who may be contacted. This should be enclosed in
+ parenthesis if it is present. For example:
+
+ e=mjh@isi.edu (Mark Handley)
+
+ The alternative RFC822 name quoting convention is also allowed for
+ both email addresses and phone numbers. For example,
+
+ e=Mark Handley <mjh@isi.edu>
+
+ The free text string should be in the ISO-10646 character set with
+ UTF-8 encoding, or alternatively in ISO-8859-1 or other encodings
+ if the appropriate charset session-level attribute is set.
+
+ Connection Data
+
+ c=<network type> <address type> <connection address>
+
+ The "c=" field contains connection data.
+
+ A session announcement must contain one "c=" field in each media
+ description (see below) or a "c=" field at the session-level. It may
+ contain a session-level "c=" field and one additional "c=" field per
+ media description, in which case the per-media values override the
+ session-level settings for the relevant media.
+
+ The first sub-field is the network type, which is a text string
+ giving the type of network. Initially "IN" is defined to have the
+ meaning "Internet".
+
+ The second sub-field is the address type. This allows SDP to be used
+ for sessions that are not IP based. Currently only IP4 is defined.
+
+ The third sub-field is the connection address. Optional extra
+ subfields may be added after the connection address depending on the
+ value of the <address type> field.
+
+ For IP4 addresses, the connection address is defined as follows:
+
+ o Typically the connection address will be a class-D IP multicast
+
+ group address. If the session is not multicast, then the
+ connection address contains the fully-qualified domain name or the
+ unicast IP address of the expected data source or data relay or
+ data sink as determined by additional attribute fields. It is not
+ expected that fully-qualified domain names or unicast addresses
+
+
+
+Handley & Jacobson Standards Track [Page 12]
+
+RFC 2327 SDP April 1998
+
+
+ will be given in a session description that is communicated by a
+ multicast announcement, though this is not prohibited. If a
+ unicast data stream is to pass through a network address
+ translator, the use of a fully-qualified domain name rather than an
+ unicast IP address is RECOMMENDED. In other cases, the use of an
+ IP address to specify a particular interface on a multi-homed host
+ might be required. Thus this specification leaves the decision as
+ to which to use up to the individual application, but all
+ applications MUST be able to cope with receiving both formats.
+
+ o Conferences using an IP multicast connection address must also have
+ a time to live (TTL) value present in addition to the multicast
+ address. The TTL and the address together define the scope with
+ which multicast packets sent in this conference will be sent. TTL
+ values must be in the range 0-255.
+
+ The TTL for the session is appended to the address using a slash as
+ a separator. An example is:
+
+ c=IN IP4 224.2.1.1/127
+
+ Hierarchical or layered encoding schemes are data streams where the
+ encoding from a single media source is split into a number of
+ layers. The receiver can choose the desired quality (and hence
+ bandwidth) by only subscribing to a subset of these layers. Such
+ layered encodings are normally transmitted in multiple multicast
+ groups to allow multicast pruning. This technique keeps unwanted
+ traffic from sites only requiring certain levels of the hierarchy.
+ For applications requiring multiple multicast groups, we allow the
+ following notation to be used for the connection address:
+
+ <base multicast address>/<ttl>/<number of addresses>
+
+ If the number of addresses is not given it is assumed to be one.
+ Multicast addresses so assigned are contiguously allocated above
+ the base address, so that, for example:
+
+ c=IN IP4 224.2.1.1/127/3
+
+ would state that addresses 224.2.1.1, 224.2.1.2 and 224.2.1.3 are
+ to be used at a ttl of 127. This is semantically identical to
+ including multiple "c=" lines in a media description:
+
+ c=IN IP4 224.2.1.1/127
+ c=IN IP4 224.2.1.2/127
+ c=IN IP4 224.2.1.3/127
+
+
+
+
+
+Handley & Jacobson Standards Track [Page 13]
+
+RFC 2327 SDP April 1998
+
+
+ Multiple addresses or "c=" lines can only be specified on a per-
+ media basis, and not for a session-level "c=" field.
+
+ It is illegal for the slash notation described above to be used for
+ IP unicast addresses.
+
+ Bandwidth
+
+ b=<modifier>:<bandwidth-value>
+
+ o This specifies the proposed bandwidth to be used by the session or
+ media, and is optional.
+
+ o <bandwidth-value> is in kilobits per second
+
+ o <modifier> is a single alphanumeric word giving the meaning of the
+ bandwidth figure.
+
+ o Two modifiers are initially defined:
+
+ CT Conference Total: An implicit maximum bandwidth is associated with
+ each TTL on the Mbone or within a particular multicast
+ administrative scope region (the Mbone bandwidth vs. TTL limits are
+ given in the MBone FAQ). If the bandwidth of a session or media in
+ a session is different from the bandwidth implicit from the scope,
+ a `b=CT:...' line should be supplied for the session giving the
+ proposed upper limit to the bandwidth used. The primary purpose of
+ this is to give an approximate idea as to whether two or more
+ conferences can co-exist simultaneously.
+
+ AS Application-Specific Maximum: The bandwidth is interpreted to be
+ application-specific, i.e., will be the application's concept of
+ maximum bandwidth. Normally this will coincide with what is set on
+ the application's "maximum bandwidth" control if applicable.
+
+ Note that CT gives a total bandwidth figure for all the media at
+ all sites. AS gives a bandwidth figure for a single media at a
+ single site, although there may be many sites sending
+ simultaneously.
+
+ o Extension Mechanism: Tool writers can define experimental bandwidth
+ modifiers by prefixing their modifier with "X-". For example:
+
+ b=X-YZ:128
+
+ SDP parsers should ignore bandwidth fields with unknown modifiers.
+ Modifiers should be alpha-numeric and, although no length limit is
+ given, they are recommended to be short.
+
+
+
+Handley & Jacobson Standards Track [Page 14]
+
+RFC 2327 SDP April 1998
+
+
+ Times, Repeat Times and Time Zones
+
+ t=<start time> <stop time>
+
+ o "t=" fields specify the start and stop times for a conference
+ session. Multiple "t=" fields may be used if a session is active
+ at multiple irregularly spaced times; each additional "t=" field
+ specifies an additional period of time for which the session will
+ be active. If the session is active at regular times, an "r="
+ field (see below) should be used in addition to and following a
+ "t=" field - in which case the "t=" field specifies the start and
+ stop times of the repeat sequence.
+
+ o The first and second sub-fields give the start and stop times for
+ the conference respectively. These values are the decimal
+ representation of Network Time Protocol (NTP) time values in
+ seconds [1]. To convert these values to UNIX time, subtract
+ decimal 2208988800.
+
+ o If the stop-time is set to zero, then the session is not bounded,
+ though it will not become active until after the start-time. If
+ the start-time is also zero, the session is regarded as permanent.
+
+ User interfaces should strongly discourage the creation of
+ unbounded and permanent sessions as they give no information about
+ when the session is actually going to terminate, and so make
+ scheduling difficult.
+
+ The general assumption may be made, when displaying unbounded
+ sessions that have not timed out to the user, that an unbounded
+ session will only be active until half an hour from the current
+ time or the session start time, whichever is the later. If
+ behaviour other than this is required, an end-time should be given
+ and modified as appropriate when new information becomes available
+ about when the session should really end.
+
+ Permanent sessions may be shown to the user as never being active
+ unless there are associated repeat times which state precisely when
+ the session will be active. In general, permanent sessions should
+ not be created for any session expected to have a duration of less
+ than 2 months, and should be discouraged for sessions expected to
+ have a duration of less than 6 months.
+
+ r=<repeat interval> <active duration> <list of offsets from start-
+ time>
+
+ o "r=" fields specify repeat times for a session. For example, if
+ a session is active at 10am on Monday and 11am on Tuesday for one
+
+
+
+Handley & Jacobson Standards Track [Page 15]
+
+RFC 2327 SDP April 1998
+
+
+ hour each week for three months, then the <start time> in the
+ corresponding "t=" field would be the NTP representation of 10am on
+ the first Monday, the <repeat interval> would be 1 week, the
+ <active duration> would be 1 hour, and the offsets would be zero
+ and 25 hours. The corresponding "t=" field stop time would be the
+ NTP representation of the end of the last session three months
+ later. By default all fields are in seconds, so the "r=" and "t="
+ fields might be:
+
+ t=3034423619 3042462419
+ r=604800 3600 0 90000
+
+ To make announcements more compact, times may also be given in units
+ of days, hours or minutes. The syntax for these is a number
+ immediately followed by a single case-sensitive character.
+ Fractional units are not allowed - a smaller unit should be used
+ instead. The following unit specification characters are allowed:
+
+ d - days (86400 seconds)
+ h - minutes (3600 seconds)
+ m - minutes (60 seconds)
+ s - seconds (allowed for completeness but not recommended)
+
+ Thus, the above announcement could also have been written:
+
+ r=7d 1h 0 25h
+
+ Monthly and yearly repeats cannot currently be directly specified
+ with a single SDP repeat time - instead separate "t" fields should
+ be used to explicitly list the session times.
+
+ z=<adjustment time> <offset> <adjustment time> <offset> ....
+
+ o To schedule a repeated session which spans a change from daylight-
+ saving time to standard time or vice-versa, it is necessary to
+ specify offsets from the base repeat times. This is required
+ because different time zones change time at different times of day,
+ different countries change to or from daylight time on different
+ dates, and some countries do not have daylight saving time at all.
+
+ Thus in order to schedule a session that is at the same time winter
+ and summer, it must be possible to specify unambiguously by whose
+ time zone a session is scheduled. To simplify this task for
+ receivers, we allow the sender to specify the NTP time that a time
+ zone adjustment happens and the offset from the time when the
+ session was first scheduled. The "z" field allows the sender to
+ specify a list of these adjustment times and offsets from the base
+ time.
+
+
+
+Handley & Jacobson Standards Track [Page 16]
+
+RFC 2327 SDP April 1998
+
+
+ An example might be:
+
+ z=2882844526 -1h 2898848070 0
+
+ This specifies that at time 2882844526 the time base by which the
+ session's repeat times are calculated is shifted back by 1 hour,
+ and that at time 2898848070 the session's original time base is
+ restored. Adjustments are always relative to the specified start
+ time - they are not cumulative.
+
+ o If a session is likely to last several years, it is expected
+ that
+ the session announcement will be modified periodically rather than
+ transmit several years worth of adjustments in one announcement.
+
+ Encryption Keys
+
+ k=<method>
+ k=<method>:<encryption key>
+
+ o The session description protocol may be used to convey encryption
+ keys. A key field is permitted before the first media entry (in
+ which case it applies to all media in the session), or for each
+ media entry as required.
+
+ o The format of keys and their usage is outside the scope of this
+ document, but see [3].
+
+ o The method indicates the mechanism to be used to obtain a usable
+ key by external means, or from the encoded encryption key given.
+
+ The following methods are defined:
+
+ k=clear:<encryption key>
+ The encryption key (as described in [3] for RTP media streams
+ under the AV profile) is included untransformed in this key
+ field.
+
+ k=base64:<encoded encryption key>
+ The encryption key (as described in [3] for RTP media streams
+ under the AV profile) is included in this key field but has been
+ base64 encoded because it includes characters that are
+ prohibited in SDP.
+
+ k=uri:<URI to obtain key>
+ A Universal Resource Identifier as used by WWW clients is
+ included in this key field. The URI refers to the data
+ containing the key, and may require additional authentication
+
+
+
+Handley & Jacobson Standards Track [Page 17]
+
+RFC 2327 SDP April 1998
+
+
+ before the key can be returned. When a request is made to the
+ given URI, the MIME content-type of the reply specifies the
+ encoding for the key in the reply. The key should not be
+ obtained until the user wishes to join the session to reduce
+ synchronisation of requests to the WWW server(s).
+
+ k=prompt
+ No key is included in this SDP description, but the session or
+ media stream referred to by this key field is encrypted. The
+ user should be prompted for the key when attempting to join the
+ session, and this user-supplied key should then be used to
+ decrypt the media streams.
+
+ Attributes
+
+ a=<attribute>
+ a=<attribute>:<value>
+
+ Attributes are the primary means for extending SDP. Attributes may
+ be defined to be used as "session-level" attributes, "media-level"
+ attributes, or both.
+
+ A media description may have any number of attributes ("a=" fields)
+ which are media specific. These are referred to as "media-level"
+ attributes and add information about the media stream. Attribute
+ fields can also be added before the first media field; these
+ "session-level" attributes convey additional information that applies
+ to the conference as a whole rather than to individual media; an
+ example might be the conference's floor control policy.
+
+ Attribute fields may be of two forms:
+
+ o property attributes. A property attribute is simply of the form
+ "a=<flag>". These are binary attributes, and the presence of the
+ attribute conveys that the attribute is a property of the session.
+ An example might be "a=recvonly".
+
+ o value attributes. A value attribute is of the form
+ "a=<attribute>:<value>". An example might be that a whiteboard
+ could have the value attribute "a=orient:landscape"
+
+ Attribute interpretation depends on the media tool being invoked.
+ Thus receivers of session descriptions should be configurable in
+ their interpretation of announcements in general and of attributes in
+ particular.
+
+ Attribute names must be in the US-ASCII subset of ISO-10646/UTF-8.
+
+
+
+
+Handley & Jacobson Standards Track [Page 18]
+
+RFC 2327 SDP April 1998
+
+
+ Attribute values are byte strings, and MAY use any byte value except
+ 0x00 (Nul), 0x0A (LF), and 0x0D (CR). By default, attribute values
+ are to be interpreted as in ISO-10646 character set with UTF-8
+ encoding. Unlike other text fields, attribute values are NOT
+ normally affected by the `charset' attribute as this would make
+ comparisons against known values problematic. However, when an
+ attribute is defined, it can be defined to be charset-dependent, in
+ which case it's value should be interpreted in the session charset
+ rather than in ISO-10646.
+
+ Attributes that will be commonly used can be registered with IANA
+ (see Appendix B). Unregistered attributes should begin with "X-" to
+ prevent inadvertent collision with registered attributes. In either
+ case, if an attribute is received that is not understood, it should
+ simply be ignored by the receiver.
+
+ Media Announcements
+
+ m=<media> <port> <transport> <fmt list>
+
+ A session description may contain a number of media descriptions.
+ Each media description starts with an "m=" field, and is terminated
+ by either the next "m=" field or by the end of the session
+ description. A media field also has several sub-fields:
+
+ o The first sub-field is the media type. Currently defined media are
+ "audio", "video", "application", "data" and "control", though this
+ list may be extended as new communication modalities emerge (e.g.,
+ telepresense). The difference between "application" and "data" is
+ that the former is a media flow such as whiteboard information, and
+ the latter is bulk-data transfer such as multicasting of program
+ executables which will not typically be displayed to the user.
+ "control" is used to specify an additional conference control
+ channel for the session.
+
+ o The second sub-field is the transport port to which the media
+ stream will be sent. The meaning of the transport port depends on
+ the network being used as specified in the relevant "c" field and
+ on the transport protocol defined in the third sub-field. Other
+ ports used by the media application (such as the RTCP port, see
+ [2]) should be derived algorithmically from the base media port.
+
+ Note: For transports based on UDP, the value should be in the range
+ 1024 to 65535 inclusive. For RTP compliance it should be an even
+ number.
+
+
+
+
+
+
+Handley & Jacobson Standards Track [Page 19]
+
+RFC 2327 SDP April 1998
+
+
+ For applications where hierarchically encoded streams are being
+ sent to a unicast address, it may be necessary to specify multiple
+ transport ports. This is done using a similar notation to that
+ used for IP multicast addresses in the "c=" field:
+
+ m=<media> <port>/<number of ports> <transport> <fmt list>
+
+ In such a case, the ports used depend on the transport protocol.
+ For RTP, only the even ports are used for data and the
+ corresponding one-higher odd port is used for RTCP. For example:
+
+ m=video 49170/2 RTP/AVP 31
+
+ would specify that ports 49170 and 49171 form one RTP/RTCP pair and
+ 49172 and 49173 form the second RTP/RTCP pair. RTP/AVP is the
+ transport protocol and 31 is the format (see below).
+
+ It is illegal for both multiple addresses to be specified in the
+ "c=" field and for multiple ports to be specified in the "m=" field
+ in the same session description.
+
+ o The third sub-field is the transport protocol. The transport
+ protocol values are dependent on the address-type field in the "c="
+ fields. Thus a "c=" field of IP4 defines that the transport
+ protocol runs over IP4. For IP4, it is normally expected that most
+ media traffic will be carried as RTP over UDP. The following
+ transport protocols are preliminarily defined, but may be extended
+ through registration of new protocols with IANA:
+
+ - RTP/AVP - the IETF's Realtime Transport Protocol using the
+ Audio/Video profile carried over UDP.
+
+ - udp - User Datagram Protocol
+
+ If an application uses a single combined proprietary media format
+ and transport protocol over UDP, then simply specifying the
+ transport protocol as udp and using the format field to distinguish
+ the combined protocol is recommended. If a transport protocol is
+ used over UDP to carry several distinct media types that need to be
+ distinguished by a session directory, then specifying the transport
+ protocol and media format separately is necessary. RTP is an
+ example of a transport-protocol that carries multiple payload
+ formats that must be distinguished by the session directory for it
+ to know how to start appropriate tools, relays, mixers or
+ recorders.
+
+
+
+
+
+
+Handley & Jacobson Standards Track [Page 20]
+
+RFC 2327 SDP April 1998
+
+
+ The main reason to specify the transport-protocol in addition to
+ the media format is that the same standard media formats may be
+ carried over different transport protocols even when the network
+ protocol is the same - a historical example is vat PCM audio and
+ RTP PCM audio. In addition, relays and monitoring tools that are
+ transport-protocol-specific but format-independent are possible.
+
+ For RTP media streams operating under the RTP Audio/Video Profile
+ [3], the protocol field is "RTP/AVP". Should other RTP profiles be
+ defined in the future, their profiles will be specified in the same
+ way. For example, the protocol field "RTP/XYZ" would specify RTP
+ operating under a profile whose short name is "XYZ".
+
+ o The fourth and subsequent sub-fields are media formats. For audio
+ and video, these will normally be a media payload type as defined
+ in the RTP Audio/Video Profile.
+
+ When a list of payload formats is given, this implies that all of
+ these formats may be used in the session, but the first of these
+ formats is the default format for the session.
+
+ For media whose transport protocol is not RTP or UDP the format
+ field is protocol specific. Such formats should be defined in an
+ additional specification document.
+
+ For media whose transport protocol is RTP, SDP can be used to
+ provide a dynamic binding of media encoding to RTP payload type.
+ The encoding names in the RTP AV Profile do not specify unique
+ audio encodings (in terms of clock rate and number of audio
+ channels), and so they are not used directly in SDP format fields.
+ Instead, the payload type number should be used to specify the
+ format for static payload types and the payload type number along
+ with additional encoding information should be used for dynamically
+ allocated payload types.
+
+ An example of a static payload type is u-law PCM coded single
+ channel audio sampled at 8KHz. This is completely defined in the
+ RTP Audio/Video profile as payload type 0, so the media field for
+ such a stream sent to UDP port 49232 is:
+
+ m=video 49232 RTP/AVP 0
+
+ An example of a dynamic payload type is 16 bit linear encoded
+ stereo audio sampled at 16KHz. If we wish to use dynamic RTP/AVP
+ payload type 98 for such a stream, additional information is
+ required to decode it:
+
+ m=video 49232 RTP/AVP 98
+
+
+
+Handley & Jacobson Standards Track [Page 21]
+
+RFC 2327 SDP April 1998
+
+
+ a=rtpmap:98 L16/16000/2
+
+ The general form of an rtpmap attribute is:
+
+ a=rtpmap:<payload type> <encoding name>/<clock rate>[/<encoding
+ parameters>]
+
+ For audio streams, <encoding parameters> may specify the number of
+ audio channels. This parameter may be omitted if the number of
+ channels is one provided no additional parameters are needed. For
+ video streams, no encoding parameters are currently specified.
+
+ Additional parameters may be defined in the future, but
+ codecspecific parameters should not be added. Parameters added to
+ an rtpmap attribute should only be those required for a session
+ directory to make the choice of appropriate media too to
+ participate in a session. Codec-specific parameters should be
+ added in other attributes.
+
+ Up to one rtpmap attribute can be defined for each media format
+ specified. Thus we might have:
+
+ m=audio 49230 RTP/AVP 96 97 98
+ a=rtpmap:96 L8/8000
+ a=rtpmap:97 L16/8000
+ a=rtpmap:98 L16/11025/2
+
+ RTP profiles that specify the use of dynamic payload types must
+ define the set of valid encoding names and/or a means to register
+ encoding names if that profile is to be used with SDP.
+
+ Experimental encoding formats can also be specified using rtpmap.
+ RTP formats that are not registered as standard format names must
+ be preceded by "X-". Thus a new experimental redundant audio
+ stream called GSMLPC using dynamic payload type 99 could be
+ specified as:
+
+ m=video 49232 RTP/AVP 99
+ a=rtpmap:99 X-GSMLPC/8000
+
+ Such an experimental encoding requires that any site wishing to
+ receive the media stream has relevant configured state in its
+ session directory to know which tools are appropriate.
+
+ Note that RTP audio formats typically do not include information
+ about the number of samples per packet. If a non-default (as
+ defined in the RTP Audio/Video Profile) packetisation is required,
+ the "ptime" attribute is used as given below.
+
+
+
+Handley & Jacobson Standards Track [Page 22]
+
+RFC 2327 SDP April 1998
+
+
+ For more details on RTP audio and video formats, see [3].
+
+ o Formats for non-RTP media should be registered as MIME content
+ types as described in Appendix B. For example, the LBL whiteboard
+ application might be registered as MIME content-type application/wb
+ with encoding considerations specifying that it operates over UDP,
+ with no appropriate file format. In SDP this would then be
+ expressed using a combination of the "media" field and the "fmt"
+ field, as follows:
+
+ m=application 32416 udp wb
+
+ Suggested Attributes
+
+ The following attributes are suggested. Since application writers
+ may add new attributes as they are required, this list is not
+ exhaustive.
+
+ a=cat:<category>
+ This attribute gives the dot-separated hierarchical category of
+ the session. This is to enable a receiver to filter unwanted
+ sessions by category. It would probably have been a compulsory
+ separate field, except for its experimental nature at this time.
+ It is a session-level attribute, and is not dependent on charset.
+
+ a=keywds:<keywords>
+ Like the cat attribute, this is to assist identifying wanted
+ sessions at the receiver. This allows a receiver to select
+ interesting session based on keywords describing the purpose of
+ the session. It is a session-level attribute. It is a charset
+ dependent attribute, meaning that its value should be interpreted
+ in the charset specified for the session description if one is
+ specified, or by default in ISO 10646/UTF-8.
+
+ a=tool:<name and version of tool>
+ This gives the name and version number of the tool used to create
+ the session description. It is a session-level attribute, and is
+ not dependent on charset.
+
+ a=ptime:<packet time>
+ This gives the length of time in milliseconds represented by the
+ media in a packet. This is probably only meaningful for audio
+ data. It should not be necessary to know ptime to decode RTP or
+ vat audio, and it is intended as a recommendation for the
+ encoding/packetisation of audio. It is a media attribute, and is
+ not dependent on charset.
+
+
+
+
+
+Handley & Jacobson Standards Track [Page 23]
+
+RFC 2327 SDP April 1998
+
+
+ a=recvonly
+ This specifies that the tools should be started in receive-only
+ mode where applicable. It can be either a session or media
+ attribute, and is not dependent on charset.
+
+ a=sendrecv
+ This specifies that the tools should be started in send and
+ receive mode. This is necessary for interactive conferences with
+ tools such as wb which defaults to receive only mode. It can be
+ either a session or media attribute, and is not dependent on
+ charset.
+
+ a=sendonly
+ This specifies that the tools should be started in send-only
+ mode. An example may be where a different unicast address is to
+ be used for a traffic destination than for a traffic source. In
+ such a case, two media descriptions may be use, one sendonly and
+ one recvonly. It can be either a session or media attribute, but
+ would normally only be used as a media attribute, and is not
+ dependent on charset.
+
+ a=orient:<whiteboard orientation>
+ Normally this is only used in a whiteboard media specification.
+ It specifies the orientation of a the whiteboard on the screen.
+ It is a media attribute. Permitted values are `portrait',
+ `landscape' and `seascape' (upside down landscape). It is not
+ dependent on charset
+
+ a=type:<conference type>
+ This specifies the type of the conference. Suggested values are
+ `broadcast', `meeting', `moderated', `test' and `H332'.
+ `recvonly' should be the default for `type:broadcast' sessions,
+ `type:meeting' should imply `sendrecv' and `type:moderated'
+ should indicate the use of a floor control tool and that the
+ media tools are started so as to "mute" new sites joining the
+ conference.
+
+ Specifying the attribute type:H332 indicates that this loosely
+ coupled session is part of a H.332 session as defined in the ITU
+ H.332 specification [10]. Media tools should be started
+ `recvonly'.
+
+ Specifying the attribute type:test is suggested as a hint that,
+ unless explicitly requested otherwise, receivers can safely avoid
+ displaying this session description to users.
+
+ The type attribute is a session-level attribute, and is not
+ dependent on charset.
+
+
+
+Handley & Jacobson Standards Track [Page 24]
+
+RFC 2327 SDP April 1998
+
+
+ a=charset:<character set>
+ This specifies the character set to be used to display the
+ session name and information data. By default, the ISO-10646
+ character set in UTF-8 encoding is used. If a more compact
+ representation is required, other character sets may be used such
+ as ISO-8859-1 for Northern European languages. In particular,
+ the ISO 8859-1 is specified with the following SDP attribute:
+
+ a=charset:ISO-8859-1
+
+ This is a session-level attribute; if this attribute is present,
+ it must be before the first media field. The charset specified
+ MUST be one of those registered with IANA, such as ISO-8859-1.
+ The character set identifier is a US-ASCII string and MUST be
+ compared against the IANA identifiers using a case-insensitive
+ comparison. If the identifier is not recognised or not
+ supported, all strings that are affected by it SHOULD be regarded
+ as byte strings.
+
+ Note that a character set specified MUST still prohibit the use
+ of bytes 0x00 (Nul), 0x0A (LF) and 0x0d (CR). Character sets
+ requiring the use of these characters MUST define a quoting
+ mechanism that prevents these bytes appearing within text fields.
+
+ a=sdplang:<language tag>
+ This can be a session level attribute or a media level attribute.
+ As a session level attribute, it specifies the language for the
+ session description. As a media level attribute, it specifies
+ the language for any media-level SDP information field associated
+ with that media. Multiple sdplang attributes can be provided
+ either at session or media level if multiple languages in the
+ session description or media use multiple languages, in which
+ case the order of the attributes indicates the order of
+ importance of the various languages in the session or media from
+ most important to least important.
+
+ In general, sending session descriptions consisting of multiple
+ languages should be discouraged. Instead, multiple descriptions
+ should be sent describing the session, one in each language.
+ However this is not possible with all transport mechanisms, and
+ so multiple sdplang attributes are allowed although not
+ recommended.
+
+ The sdplang attribute value must be a single RFC 1766 language
+ tag in US-ASCII. It is not dependent on the charset attribute.
+ An sdplang attribute SHOULD be specified when a session is of
+
+
+
+
+
+Handley & Jacobson Standards Track [Page 25]
+
+RFC 2327 SDP April 1998
+
+
+ sufficient scope to cross geographic boundaries where the
+ language of recipients cannot be assumed, or where the session is
+ in a different language from the locally assumed norm.
+
+ a=lang:<language tag>
+ This can be a session level attribute or a media level attribute.
+ As a session level attribute, it specifies the default language
+ for the session being described. As a media level attribute, it
+ specifies the language for that media, overriding any session-
+ level language specified. Multiple lang attributes can be
+ provided either at session or media level if multiple languages
+ if the session description or media use multiple languages, in
+ which case the order of the attributes indicates the order of
+ importance of the various languages in the session or media from
+ most important to least important.
+
+ The lang attribute value must be a single RFC 1766 language tag
+ in US-ASCII. It is not dependent on the charset attribute. A
+ lang attribute SHOULD be specified when a session is of
+ sufficient scope to cross geographic boundaries where the
+ language of recipients cannot be assumed, or where the session is
+ in a different language from the locally assumed norm.
+
+ a=framerate:<frame rate>
+ This gives the maximum video frame rate in frames/sec. It is
+ intended as a recommendation for the encoding of video data.
+ Decimal representations of fractional values using the notation
+ "<integer>.<fraction>" are allowed. It is a media attribute, is
+ only defined for video media, and is not dependent on charset.
+
+ a=quality:<quality>
+ This gives a suggestion for the quality of the encoding as an
+ integer value.
+
+ The intention of the quality attribute for video is to specify a
+ non-default trade-off between frame-rate and still-image quality.
+ For video, the value in the range 0 to 10, with the following
+ suggested meaning:
+
+ 10 - the best still-image quality the compression scheme can
+ give.
+
+ 5 - the default behaviour given no quality suggestion.
+
+ 0 - the worst still-image quality the codec designer thinks is
+ still usable.
+
+ It is a media attribute, and is not dependent on charset.
+
+
+
+Handley & Jacobson Standards Track [Page 26]
+
+RFC 2327 SDP April 1998
+
+
+ a=fmtp:<format> <format specific parameters>
+ This attribute allows parameters that are specific to a
+ particular format to be conveyed in a way that SDP doesn't have
+ to understand them. The format must be one of the formats
+ specified for the media. Format-specific parameters may be any
+ set of parameters required to be conveyed by SDP and given
+ unchanged to the media tool that will use this format.
+
+ It is a media attribute, and is not dependent on charset.
+
+6.1. Communicating Conference Control Policy
+
+ There is some debate over the way conference control policy should be
+ communicated. In general, the authors believe that an implicit
+ declarative style of specifying conference control is desirable where
+ possible.
+
+ A simple declarative style uses a single conference attribute field
+ before the first media field, possibly supplemented by properties
+ such as `recvonly' for some of the media tools. This conference
+ attribute conveys the conference control policy. An example might be:
+
+ a=type:moderated
+
+ In some cases, however, it is possible that this may be insufficient
+ to communicate the details of an unusual conference control policy.
+ If this is the case, then a conference attribute specifying external
+ control might be set, and then one or more "media" fields might be
+ used to specify the conference control tools and configuration data
+ for those tools. An example is an ITU H.332 session:
+
+ c=IN IP4 224.5.6.7
+ a=type:H332
+ m=audio 49230 RTP/AVP 0
+ m=video 49232 RTP/AVP 31
+ m=application 12349 udp wb
+ m=control 49234 H323 mc
+ c=IN IP4 134.134.157.81
+
+ In this example, a general conference attribute (type:H332) is
+ specified stating that conference control will be provided by an
+ external H.332 tool, and a contact addresses for the H.323 session
+ multipoint controller is given.
+
+ In this document, only the declarative style of conference control
+ declaration is specified. Other forms of conference control should
+ specify an appropriate type attribute, and should define the
+ implications this has for control media.
+
+
+
+Handley & Jacobson Standards Track [Page 27]
+
+RFC 2327 SDP April 1998
+
+
+7. Security Considerations
+
+ SDP is a session description format that describes multimedia
+ sessions. A session description should not be trusted unless it has
+ been obtained by an authenticated transport protocol from a trusted
+ source. Many different transport protocols may be used to distribute
+ session description, and the nature of the authentication will differ
+ from transport to transport.
+
+ One transport that will frequently be used to distribute session
+ descriptions is the Session Announcement Protocol (SAP). SAP
+ provides both encryption and authentication mechanisms but due to the
+ nature of session announcements it is likely that there are many
+ occasions where the originator of a session announcement cannot be
+ authenticated because they are previously unknown to the receiver of
+ the announcement and because no common public key infrastructure is
+ available.
+
+ On receiving a session description over an unauthenticated transport
+ mechanism or from an untrusted party, software parsing the session
+ should take a few precautions. Session description contain
+ information required to start software on the receivers system.
+ Software that parses a session description MUST not be able to start
+ other software except that which is specifically configured as
+ appropriate software to participate in multimedia sessions. It is
+ normally considered INAPPROPRIATE for software parsing a session
+ description to start, on a user's system, software that is
+ appropriate to participate in multimedia sessions, without the user
+ first being informed that such software will be started and giving
+ their consent. Thus a session description arriving by session
+ announcement, email, session invitation, or WWW page SHOULD not
+ deliver the user into an {it interactive} multimedia session without
+ the user being aware that this will happen. As it is not always
+ simple to tell whether a session is interactive or not, applications
+ that are unsure should assume sessions are interactive.
+
+ In this specification, there are no attributes which would allow the
+ recipient of a session description to be informed to start multimedia
+ tools in a mode where they default to transmitting. Under some
+ circumstances it might be appropriate to define such attributes. If
+ this is done an application parsing a session description containing
+ such attributes SHOULD either ignore them, or inform the user that
+ joining this session will result in the automatic transmission of
+ multimedia data. The default behaviour for an unknown attribute is
+ to ignore it.
+
+
+
+
+
+
+Handley & Jacobson Standards Track [Page 28]
+
+RFC 2327 SDP April 1998
+
+
+ Session descriptions may be parsed at intermediate systems such as
+ firewalls for the purposes of opening a hole in the firewall to allow
+ the participation in multimedia sessions. It is considered
+ INAPPROPRIATE for a firewall to open such holes for unicast data
+ streams unless the session description comes in a request from inside
+ the firewall.
+
+ For multicast sessions, it is likely that local administrators will
+ apply their own policies, but the exclusive use of "local" or "site-
+ local" administrative scope within the firewall and the refusal of
+ the firewall to open a hole for such scopes will provide separation
+ of global multicast sessions from local ones.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Handley & Jacobson Standards Track [Page 29]
+
+RFC 2327 SDP April 1998
+
+
+Appendix A: SDP Grammar
+
+ This appendix provides an Augmented BNF grammar for SDP. ABNF is
+ defined in RFC 2234.
+
+
+ announcement = proto-version
+ origin-field
+ session-name-field
+ information-field
+ uri-field
+ email-fields
+ phone-fields
+ connection-field
+ bandwidth-fields
+ time-fields
+ key-field
+ attribute-fields
+ media-descriptions
+
+ proto-version = "v=" 1*DIGIT CRLF
+ ;this memo describes version 0
+
+ origin-field = "o=" username space
+ sess-id space sess-version space
+ nettype space addrtype space
+ addr CRLF
+
+ session-name-field = "s=" text CRLF
+
+ information-field = ["i=" text CRLF]
+
+ uri-field = ["u=" uri CRLF]
+
+ email-fields = *("e=" email-address CRLF)
+
+ phone-fields = *("p=" phone-number CRLF)
+
+
+ connection-field = ["c=" nettype space addrtype space
+ connection-address CRLF]
+ ;a connection field must be present
+ ;in every media description or at the
+ ;session-level
+
+
+ bandwidth-fields = *("b=" bwtype ":" bandwidth CRLF)
+
+
+
+
+Handley & Jacobson Standards Track [Page 30]
+
+RFC 2327 SDP April 1998
+
+
+ time-fields = 1*( "t=" start-time space stop-time
+ *(CRLF repeat-fields) CRLF)
+ [zone-adjustments CRLF]
+
+
+ repeat-fields = "r=" repeat-interval space typed-time
+ 1*(space typed-time)
+
+
+ zone-adjustments = time space ["-"] typed-time
+ *(space time space ["-"] typed-time)
+
+
+ key-field = ["k=" key-type CRLF]
+
+
+ key-type = "prompt" |
+ "clear:" key-data |
+ "base64:" key-data |
+ "uri:" uri
+
+
+ key-data = email-safe | "~" | "
+
+
+ attribute-fields = *("a=" attribute CRLF)
+
+
+ media-descriptions = *( media-field
+ information-field
+ *(connection-field)
+ bandwidth-fields
+ key-field
+ attribute-fields )
+
+
+ media-field = "m=" media space port ["/" integer]
+ space proto 1*(space fmt) CRLF
+
+
+ media = 1*(alpha-numeric)
+ ;typically "audio", "video", "application"
+ ;or "data"
+
+ fmt = 1*(alpha-numeric)
+ ;typically an RTP payload type for audio
+ ;and video media
+
+
+
+
+Handley & Jacobson Standards Track [Page 31]
+
+RFC 2327 SDP April 1998
+
+
+ proto = 1*(alpha-numeric)
+ ;typically "RTP/AVP" or "udp" for IP4
+
+
+ port = 1*(DIGIT)
+ ;should in the range "1024" to "65535" inclusive
+ ;for UDP based media
+
+
+ attribute = (att-field ":" att-value) | att-field
+
+
+ att-field = 1*(alpha-numeric)
+
+
+ att-value = byte-string
+
+
+ sess-id = 1*(DIGIT)
+ ;should be unique for this originating username/host
+
+
+ sess-version = 1*(DIGIT)
+ ;0 is a new session
+
+
+ connection-address = multicast-address
+ | addr
+
+
+ multicast-address = 3*(decimal-uchar ".") decimal-uchar "/" ttl
+ [ "/" integer ]
+ ;multicast addresses may be in the range
+ ;224.0.0.0 to 239.255.255.255
+
+ ttl = decimal-uchar
+
+ start-time = time | "0"
+
+ stop-time = time | "0"
+
+ time = POS-DIGIT 9*(DIGIT)
+ ;sufficient for 2 more centuries
+
+
+ repeat-interval = typed-time
+
+
+
+
+
+Handley & Jacobson Standards Track [Page 32]
+
+RFC 2327 SDP April 1998
+
+
+ typed-time = 1*(DIGIT) [fixed-len-time-unit]
+
+
+ fixed-len-time-unit = "d" | "h" | "m" | "s"
+
+
+ bwtype = 1*(alpha-numeric)
+
+ bandwidth = 1*(DIGIT)
+
+
+ username = safe
+ ;pretty wide definition, but doesn't include space
+
+
+ email-address = email | email "(" email-safe ")" |
+ email-safe "<" email ">"
+
+
+ email = ;defined in RFC822
+
+
+ uri= ;defined in RFC1630
+
+
+ phone-number = phone | phone "(" email-safe ")" |
+ email-safe "<" phone ">"
+
+
+ phone = "+" POS-DIGIT 1*(space | "-" | DIGIT)
+ ;there must be a space or hyphen between the
+ ;international code and the rest of the number.
+
+
+ nettype = "IN"
+ ;list to be extended
+
+
+ addrtype = "IP4" | "IP6"
+ ;list to be extended
+
+
+ addr = FQDN | unicast-address
+
+
+ FQDN = 4*(alpha-numeric|"-"|".")
+ ;fully qualified domain name as specified in RFC1035
+
+
+
+
+Handley & Jacobson Standards Track [Page 33]
+
+RFC 2327 SDP April 1998
+
+
+ unicast-address = IP4-address | IP6-address
+
+
+ IP4-address = b1 "." decimal-uchar "." decimal-uchar "." b4
+ b1 = decimal-uchar
+ ;less than "224"; not "0" or "127"
+ b4 = decimal-uchar
+ ;not "0"
+
+ IP6-address = ;to be defined
+
+
+ text = byte-string
+ ;default is to interpret this as IS0-10646 UTF8
+ ;ISO 8859-1 requires a "a=charset:ISO-8859-1"
+ ;session-level attribute to be used
+
+
+ byte-string = 1*(0x01..0x09|0x0b|0x0c|0x0e..0xff)
+ ;any byte except NUL, CR or LF
+
+
+ decimal-uchar = DIGIT
+ | POS-DIGIT DIGIT
+ | ("1" 2*(DIGIT))
+ | ("2" ("0"|"1"|"2"|"3"|"4") DIGIT)
+ | ("2" "5" ("0"|"1"|"2"|"3"|"4"|"5"))
+
+
+ integer = POS-DIGIT *(DIGIT)
+
+
+ alpha-numeric = ALPHA | DIGIT
+
+
+ DIGIT = "0" | POS-DIGIT
+
+
+ POS-DIGIT = "1"|"2"|"3"|"4"|"5"|"6"|"7"|"8"|"9"
+
+
+ ALPHA = "a"|"b"|"c"|"d"|"e"|"f"|"g"|"h"|"i"|"j"|"k"|
+ "l"|"m"|"n"|"o "|"p"|"q"|"r"|"s"|"t"|"u"|"v"|
+ "w"|"x"|"y"|"z"|"A"|"B"|"C "|"D"|"E"|"F"|"G"|
+ "H"|"I"|"J"|"K"|"L"|"M"|"N"|"O"|"P"|" Q"|"R"|
+ "S"|"T"|"U"|"V"|"W"|"X"|"Y"|"Z"
+
+
+
+
+
+Handley & Jacobson Standards Track [Page 34]
+
+RFC 2327 SDP April 1998
+
+
+ email-safe = safe | space | tab
+
+
+ safe = alpha-numeric |
+ "'" | "'" | "-" | "." | "/" | ":" | "?" | """ |
+ "#" | "$" | "&" | "*" | ";" | "=" | "@" | "[" |
+ "]" | "^" | "_" | "`" | "{" | "|" | "}" | "+" |
+ "~" | "
+
+
+ space = %d32
+ tab = %d9
+ CRLF = %d13.10
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Handley & Jacobson Standards Track [Page 35]
+
+RFC 2327 SDP April 1998
+
+
+Appendix B: Guidelines for registering SDP names with IANA
+
+ There are seven field names that may be registered with IANA. Using
+ the terminology in the SDP specification BNF, they are "media",
+ "proto", "fmt", "att-field", "bwtype", "nettype" and "addrtype".
+
+ "media" (eg, audio, video, application, data).
+
+ Packetized media types, such as those used by RTP, share the
+ namespace used by media types registry [RFC 2048] (i.e. "MIME
+ types"). The list of valid media names is the set of top-level
+ MIME content types. The set of media is intended to be small and
+ not to be extended except under rare circumstances. (The MIME
+ subtype corresponds to the "fmt" parameter below).
+
+ "proto"
+
+ In general this should be an IETF standards-track transport
+ protocol identifier such as RTP/AVP (rfc 1889 under the rfc 1890
+ profile).
+
+ However, people will want to invent their own proprietary
+ transport protocols. Some of these should be registered as a
+ "fmt" using "udp" as the protocol and some of which probably
+ can't be.
+
+ Where the protocol and the application are intimately linked,
+ such as with the LBL whiteboard wb which used a proprietary and
+ special purpose protocol over UDP, the protocol name should be
+ "udp" and the format name that should be registered is "wb". The
+ rules for formats (see below) apply to such registrations.
+
+ Where the proprietary transport protocol really carries many
+ different data formats, it is possible to register a new protocol
+ name with IANA. In such a case, an RFC MUST be produced
+ describing the protocol and referenced in the registration. Such
+ an RFC MAY be informational, although it is preferable if it is
+ standards-track.
+
+ "fmt"
+
+ The format namespace is dependent on the context of the "proto"
+ field, so a format cannot be registered without specifying one or
+ more transport protocols that it applies to.
+
+ Formats cover all the possible encodings that might want to be
+ transported in a multimedia session.
+
+
+
+
+Handley & Jacobson Standards Track [Page 36]
+
+RFC 2327 SDP April 1998
+
+
+ For RTP formats that have been assigned static payload types, the
+ payload type number is used. For RTP formats using a dynamic
+ payload type number, the dynamic payload type number is given as
+ the format and an additional "rtpmap" attribute specifies the
+ format and parameters.
+
+ For non-RTP formats, any unregistered format name may be
+ registered through the MIME-type registration process [RFC 2048].
+ The type given here is the MIME subtype only (the top-level MIME
+ content type is specified by the media parameter). The MIME type
+ registration SHOULD reference a standards-track RFC which
+ describes the transport protocol for this media type. If there
+ is an existing MIME type for this format, the MIME registration
+ should be augmented to reference the transport specification for
+ this media type. If there is not an existing MIME type for this
+ format, and there exists no appropriate file format, this should
+ be noted in the encoding considerations as "no appropriate file
+ format".
+
+ "att-field" (Attribute names)
+
+ Attribute field names MAY be registered with IANA, although this
+ is not compulsory, and unknown attributes are simply ignored.
+
+ When an attribute is registered, it must be accompanied by a
+ brief specification stating the following:
+
+ o contact name, email address and telephone number
+
+ o attribute-name (as it will appear in SDP)
+
+ o long-form attribute name in English
+
+ o type of attribute (session level, media level, or both)
+
+ o whether the attribute value is subject to the charset
+ attribute.
+
+ o a one paragraph explanation of the purpose of the attribute.
+
+ o a specification of appropriate attribute values for this
+ attribute.
+
+ IANA will not sanity check such attribute registrations except to
+ ensure that they do not clash with existing registrations.
+
+
+
+
+
+
+Handley & Jacobson Standards Track [Page 37]
+
+RFC 2327 SDP April 1998
+
+
+ Although the above is the minimum that IANA will accept, if the
+ attribute is expected to see widespread use and interoperability
+ is an issue, authors are encouraged to produce a standards-track
+ RFC that specifies the attribute more precisely.
+
+ Submitters of registrations should ensure that the specification
+ is in the spirit of SDP attributes, most notably that the
+ attribute is platform independent in the sense that it makes no
+ implicit assumptions about operating systems and does not name
+ specific pieces of software in a manner that might inhibit
+ interoperability.
+
+ "bwtype" (bandwidth specifiers)
+
+ A proliferation of bandwidth specifiers is strongly discouraged.
+
+ New bandwidth specifiers may be registered with IANA. The
+ submission MUST reference a standards-track RFC specifying the
+ semantics of the bandwidth specifier precisely, and indicating
+ when it should be used, and why the existing registered bandwidth
+ specifiers do not suffice.
+
+ "nettype" (Network Type)
+
+ New network types may be registered with IANA if SDP needs to be
+ used in the context of non-internet environments. Whilst these
+ are not normally the preserve of IANA, there may be circumstances
+ when an Internet application needs to interoperate with a non-
+ internet application, such as when gatewaying an internet
+ telephony call into the PSTN. The number of network types should
+ be small and should be rarely extended. A new network type
+ cannot be registered without registering at least one address
+ type to be used with that network type. A new network type
+ registration MUST reference an RFC which gives details of the
+ network type and address type and specifies how and when they
+ would be used. Such an RFC MAY be Informational.
+
+ "addrtype" (Address Type)
+
+ New address types may be registered with IANA. An address type
+ is only meaningful in the context of a network type, and any
+ registration of an address type MUST specify a registered network
+ type, or be submitted along with a network type registration. A
+ new address type registration MUST reference an RFC giving
+ details of the syntax of the address type. Such an RFC MAY be
+ Informational. Address types are not expected to be registered
+ frequently.
+
+
+
+
+Handley & Jacobson Standards Track [Page 38]
+
+RFC 2327 SDP April 1998
+
+
+ Registration Procedure
+
+ To register a name the above guidelines should be followed regarding
+ the required level of documentation that is required. The
+ registration itself should be sent to IANA. Attribute registrations
+ should include the information given above. Other registrations
+ should include the following additional information:
+
+ o contact name, email address and telephone number
+
+ o name being registered (as it will appear in SDP)
+
+ o long-form name in English
+
+ o type of name ("media", "proto", "fmt", "bwtype", "nettype", or
+ "addrtype")
+
+ o a one paragraph explanation of the purpose of the registered name.
+
+ o a reference to the specification (eg RFC number) of the registered
+ name.
+
+ IANA may refer any registration to the IESG or to any appropriate
+ IETF working group for review, and may request revisions to be made
+ before a registration will be made.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Handley & Jacobson Standards Track [Page 39]
+
+RFC 2327 SDP April 1998
+
+
+Appendix C: Authors' Addresses
+
+ Mark Handley
+ Information Sciences Institute
+ c/o MIT Laboratory for Computer Science
+ 545 Technology Square
+ Cambridge, MA 02139
+ United States
+ electronic mail: mjh@isi.edu
+
+ Van Jacobson
+ MS 46a-1121
+ Lawrence Berkeley Laboratory
+ Berkeley, CA 94720
+ United States
+ electronic mail: van@ee.lbl.gov
+
+Acknowledgments
+
+ Many people in the IETF MMUSIC working group have made comments and
+ suggestions contributing to this document. In particular, we would
+ like to thank Eve Schooler, Steve Casner, Bill Fenner, Allison
+ Mankin, Ross Finlayson, Peter Parnes, Joerg Ott, Carsten Bormann, Rob
+ Lanphier and Steve Hanna.
+
+References
+
+ [1] Mills, D., "Network Time Protocol (version 3) specification and
+ implementation", RFC 1305, March 1992.
+
+ [2] Schulzrinne, H., Casner, S., Frederick, R. and V. Jacobson, "RTP:
+ A Transport Protocol for Real-Time Applications", RFC 1889, January
+ 1996.
+
+ [3] Schulzrinne, H., "RTP Profile for Audio and Video Conferences
+ with Minimal Control", RFC 1890, January 1996
+
+ [4] Handley, M., "SAP - Session Announcement Protocol", Work in
+ Progress.
+
+ [5] V. Jacobson, S. McCanne, "vat - X11-based audio teleconferencing
+ tool" vat manual page, Lawrence Berkeley Laboratory, 1994.
+
+ [6] The Unicode Consortium, "The Unicode Standard -- Version 2.0",
+ Addison-Wesley, 1996.
+
+
+
+
+
+
+Handley & Jacobson Standards Track [Page 40]
+
+RFC 2327 SDP April 1998
+
+
+ [7] ISO/IEC 10646-1:1993. International Standard -- Information
+ technol- ogy -- Universal Multiple-Octet Coded Character Set (UCS) --
+ Part 1: Architecture and Basic Multilingual Plane. Five amendments
+ and a techn- ical corrigendum have been published up to now. UTF-8
+ is described in Annex R, published as Amendment 2.
+
+ [8] Goldsmith, D., and M. Davis, "Using Unicode with MIME", RFC 1641,
+ July 1994.
+
+ [9] Yergeau, F., "UTF-8, a transformation format of Unicode and ISO
+ 10646", RFC 2044, October 1996.
+
+ [10] ITU-T Recommendation H.332 (1998): "Multimedia Terminal for
+ Receiving Internet-based H.323 Conferences", ITU, Geneva.
+
+ [11] Handley, M., Schooler, E., and H. Schulzrinne, "Session
+ Initiation Protocol (SIP)", Work in Progress.
+
+ [12] Schulzrinne, H., Rao, A., and R. Lanphier, "Real Time Streaming
+ Protocol (RTSP)", RFC 2326, April 1998.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Handley & Jacobson Standards Track [Page 41]
+
+RFC 2327 SDP April 1998
+
+
+Full Copyright Statement
+
+ Copyright (C) The Internet Society (1998). All Rights Reserved.
+
+ This document and translations of it may be copied and furnished to
+ others, and derivative works that comment on or otherwise explain it
+ or assist in its implementation may be prepared, copied, published
+ and distributed, in whole or in part, without restriction of any
+ kind, provided that the above copyright notice and this paragraph are
+ included on all such copies and derivative works. However, this
+ document itself may not be modified in any way, such as by removing
+ the copyright notice or references to the Internet Society or other
+ Internet organizations, except as needed for the purpose of
+ developing Internet standards in which case the procedures for
+ copyrights defined in the Internet Standards process must be
+ followed, or as required to translate it into languages other than
+ English.
+
+ The limited permissions granted above are perpetual and will not be
+ revoked by the Internet Society or its successors or assigns.
+
+ This document and the information contained herein is provided on an
+ "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING
+ TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING
+ BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION
+ HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF
+ MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Handley & Jacobson Standards Track [Page 42]
+
diff --git a/src/modules/rtp/rfc2974.txt b/src/modules/rtp/rfc2974.txt
new file mode 100644
index 00000000..4a5aa626
--- /dev/null
+++ b/src/modules/rtp/rfc2974.txt
@@ -0,0 +1,1011 @@
+
+
+
+
+
+
+Network Working Group M. Handley
+Request for Comments: 2974 ACIRI
+Category: Experimental C. Perkins
+ USC/ISI
+ E. Whelan
+ UCL
+ October 2000
+
+
+ Session Announcement Protocol
+
+Status of this Memo
+
+ This memo defines an Experimental Protocol for the Internet
+ community. It does not specify an Internet standard of any kind.
+ Discussion and suggestions for improvement are requested.
+ Distribution of this memo is unlimited.
+
+Copyright Notice
+
+ Copyright (C) The Internet Society (2000). All Rights Reserved.
+
+Abstract
+
+ This document describes version 2 of the multicast session directory
+ announcement protocol, Session Announcement Protocol (SAP), and the
+ related issues affecting security and scalability that should be
+ taken into account by implementors.
+
+1 Introduction
+
+ In order to assist the advertisement of multicast multimedia
+ conferences and other multicast sessions, and to communicate the
+ relevant session setup information to prospective participants, a
+ distributed session directory may be used. An instance of such a
+ session directory periodically multicasts packets containing a
+ description of the session, and these advertisements are received by
+ other session directories such that potential remote participants can
+ use the session description to start the tools required to
+ participate in the session.
+
+ This memo describes the issues involved in the multicast announcement
+ of session description information and defines an announcement
+ protocol to be used. Sessions are described using the session
+ description protocol which is described in a companion memo [4].
+
+
+
+
+
+
+Handley, et al. Experimental [Page 1]
+
+RFC 2974 Session Announcement Protocol October 2000
+
+
+2 Terminology
+
+ A SAP announcer periodically multicasts an announcement packet to a
+ well known multicast address and port. The announcement is multicast
+ with the same scope as the session it is announcing, ensuring that
+ the recipients of the announcement are within the scope of the
+ session the announcement describes (bandwidth and other such
+ constraints permitting). This is also important for the scalability
+ of the protocol, as it keeps local session announcements local.
+
+ A SAP listener learns of the multicast scopes it is within (for
+ example, using the Multicast-Scope Zone Announcement Protocol [5])
+ and listens on the well known SAP address and port for those scopes.
+ In this manner, it will eventually learn of all the sessions being
+ announced, allowing those sessions to be joined.
+
+ The key words `MUST', `MUST NOT', `REQUIRED', `SHALL', `SHALL NOT',
+ `SHOULD', `SHOULD NOT', `RECOMMENDED', `MAY', and `OPTIONAL' in this
+ document are to be interpreted as described in [1].
+
+3 Session Announcement
+
+ As noted previously, a SAP announcer periodically sends an
+ announcement packet to a well known multicast address and port.
+ There is no rendezvous mechanism - the SAP announcer is not aware of
+ the presence or absence of any SAP listeners - and no additional
+ reliability is provided over the standard best-effort UDP/IP
+ semantics.
+
+ That announcement contains a session description and SHOULD contain
+ an authentication header. The session description MAY be encrypted
+ although this is NOT RECOMMENDED (see section 7).
+
+ A SAP announcement is multicast with the same scope as the session it
+ is announcing, ensuring that the recipients of the announcement are
+ within the scope of the session the announcement describes. There are
+ a number of possibilities:
+
+ IPv4 global scope sessions use multicast addresses in the range
+ 224.2.128.0 - 224.2.255.255 with SAP announcements being sent to
+ 224.2.127.254 (note that 224.2.127.255 is used by the obsolete
+ SAPv0 and MUST NOT be used).
+
+
+
+
+
+
+
+
+
+Handley, et al. Experimental [Page 2]
+
+RFC 2974 Session Announcement Protocol October 2000
+
+
+ IPv4 administrative scope sessions using administratively scoped IP
+ multicast as defined in [7]. The multicast address to be used for
+ announcements is the highest multicast address in the relevant
+ administrative scope zone. For example, if the scope range is
+ 239.16.32.0 - 239.16.33.255, then 239.16.33.255 is used for SAP
+ announcements.
+
+ IPv6 sessions are announced on the address FF0X:0:0:0:0:0:2:7FFE
+ where X is the 4-bit scope value. For example, an announcement
+ for a link-local session assigned the address
+ FF02:0:0:0:0:0:1234:5678, should be advertised on SAP address
+ FF02:0:0:0:0:0:2:7FFE.
+
+ Ensuring that a description is not used by a potential participant
+ outside the session scope is not addressed in this memo.
+
+ SAP announcements MUST be sent on port 9875 and SHOULD be sent with
+ an IP time-to-live of 255 (the use of TTL scoping for multicast is
+ discouraged [7]).
+
+ If a session uses addresses in multiple administrative scope ranges,
+ it is necessary for the announcer to send identical copies of the
+ announcement to each administrative scope range. It is up to the
+ listeners to parse such multiple announcements as the same session
+ (as identified by the SDP origin field, for example). The
+ announcement rate for each administrative scope range MUST be
+ calculated separately, as if the multiple announcements were
+ separate.
+
+ Multiple announcers may announce a single session, as an aid to
+ robustness in the face of packet loss and failure of one or more
+ announcers. The rate at which each announcer repeats its
+ announcement MUST be scaled back such that the total announcement
+ rate is equal to that which a single server would choose.
+ Announcements made in this manner MUST be identical.
+
+ If multiple announcements are being made for a session, then each
+ announcement MUST carry an authentication header signed by the same
+ key, or be treated as a completely separate announcement by
+ listeners.
+
+ An IPv4 SAP listener SHOULD listen on the IPv4 global scope SAP
+ address and on the SAP addresses for each IPv4 administrative scope
+ zone it is within. The discovery of administrative scope zones is
+ outside the scope of this memo, but it is assumed that each SAP
+ listener within a particular scope zone is aware of that scope zone.
+ A SAP listener which supports IPv6 SHOULD also listen to the IPv6 SAP
+ addresses.
+
+
+
+Handley, et al. Experimental [Page 3]
+
+RFC 2974 Session Announcement Protocol October 2000
+
+
+3.1 Announcement Interval
+
+ The time period between repetitions of an announcement is chosen such
+ that the total bandwidth used by all announcements on a single SAP
+ group remains below a preconfigured limit. If not otherwise
+ specified, the bandwidth limit SHOULD be assumed to be 4000 bits per
+ second.
+
+ Each announcer is expected to listen to other announcements in order
+ to determine the total number of sessions being announced on a
+ particular group. Sessions are uniquely identified by the
+ combination of the message identifier hash and originating source
+ fields of the SAP header (note that SAP v0 announcers always set the
+ message identifier hash to zero, and if such an announcement is
+ received the entire message MUST be compared to determine
+ uniqueness).
+
+ Announcements are made by periodic multicast to the group. The base
+ interval between announcements is derived from the number of
+ announcements being made in that group, the size of the announcement
+ and the configured bandwidth limit. The actual transmission time is
+ derived from this base interval as follows:
+
+ 1. The announcer initializes the variable tp to be the last time a
+ particular announcement was transmitted (or the current time if
+ this is the first time this announcement is to be made).
+
+ 2. Given a configured bandwidth limit in bits/second and an
+ announcement of ad_size bytes, the base announcement interval
+ in seconds is
+
+ interval =max(300; (8*no_of_ads*ad_size)/limit)
+
+ 3. An offset is calculated based on the base announcement interval
+
+ offset= rand(interval* 2/3)-(interval/3)
+
+ 4. The next transmission time for an announcement derived as
+
+ tn =tp+ interval+ offset
+
+ The announcer then sets a timer to expire at tn and waits. At time
+ tn the announcer SHOULD recalculate the next transmission time. If
+ the new value of tn is before the current time, the announcement is
+ sent immediately. Otherwise the transmission is rescheduled for the
+ new tn. This reconsideration prevents transient packet bursts on
+ startup and when a network partition heals.
+
+
+
+
+Handley, et al. Experimental [Page 4]
+
+RFC 2974 Session Announcement Protocol October 2000
+
+
+4 Session Deletion
+
+ Sessions may be deleted in one of several ways:
+
+ Explicit Timeout The session description payload may contain
+ timestamp information specifying the start- and end-times of the
+ session. If the current time is later than the end-time of the
+ session, then the session SHOULD be deleted from the receiver's
+ session cache.
+
+ Implicit Timeout A session announcement message should be received
+ periodically for each session description in a receiver's session
+ cache. The announcement period can be predicted by the receiver
+ from the set of sessions currently being announced. If a session
+ announcement message has not been received for ten times the
+ announcement period, or one hour, whichever is the greater, then
+ the session is deleted from the receiver's session cache. The one
+ hour minimum is to allow for transient network partitionings.
+
+ Explicit Deletion A session deletion packet is received specifying
+ the session to be deleted. Session deletion packets SHOULD have a
+ valid authentication header, matching that used to authenticate
+ previous announcement packets. If this authentication is missing,
+ the deletion message SHOULD be ignored.
+
+5 Session Modification
+
+ A pre-announced session can be modified by simply announcing the
+ modified session description. In this case, the version hash in the
+ SAP header MUST be changed to indicate to receivers that the packet
+ contents should be parsed (or decrypted and parsed if it is
+ encrypted). The session itself, as distinct from the session
+ announcement, is uniquely identified by the payload and not by the
+ message identifier hash in the header.
+
+ The same rules apply for session modification as for session
+ deletion:
+
+ o Either the modified announcement must contain an authentication
+ header signed by the same key as the cached session announcement
+ it is modifying, or:
+
+ o The cached session announcement must not contain an authentication
+ header, and the session modification announcement must originate
+ from the same host as the session it is modifying.
+
+
+
+
+
+
+Handley, et al. Experimental [Page 5]
+
+RFC 2974 Session Announcement Protocol October 2000
+
+
+ If an announcement is received containing an authentication header
+ and the cached announcement did not contain an authentication header,
+ or it contained a different authentication header, then the modified
+ announcement MUST be treated as a new and different announcement, and
+ displayed in addition to the un-authenticated announcement. The same
+ should happen if a modified packet without an authentication header
+ is received from a different source than the original announcement.
+
+ These rules prevent an announcement having an authentication header
+ added by a malicious user and then being deleted using that header,
+ and it also prevents a denial-of-service attack by someone putting
+ out a spoof announcement which, due to packet loss, reaches some
+ participants before the original announcement. Note that under such
+ circumstances, being able to authenticate the message originator is
+ the only way to discover which session is the correct session.
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | V=1 |A|R|T|E|C| auth len | msg id hash |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | |
+ : originating source (32 or 128 bits) :
+ : :
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | optional authentication data |
+ : .... :
+ *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*
+ | optional payload type |
+ + +-+- - - - - - - - - -+
+ | |0| |
+ + - - - - - - - - - - - - - - - - - - - - +-+ |
+ | |
+ : payload :
+ | |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ Figure 1: Packet format
+
+6 Packet Format
+
+ SAP data packets have the format described in figure 1.
+
+ V: Version Number. The version number field MUST be set to 1 (SAPv2
+ announcements which use only SAPv1 features are backwards
+ compatible, those which use new features can be detected by other
+ means, so the SAP version number doesn't need to change).
+
+
+
+
+Handley, et al. Experimental [Page 6]
+
+RFC 2974 Session Announcement Protocol October 2000
+
+
+ A: Address type. If the A bit is 0, the originating source field
+ contains a 32-bit IPv4 address. If the A bit is 1, the
+ originating source contains a 128-bit IPv6 address.
+
+ R: Reserved. SAP announcers MUST set this to 0, SAP listeners MUST
+ ignore the contents of this field.
+
+ T: Message Type. If the T field is set to 0 this is a session
+ announcement packet, if 1 this is a session deletion packet.
+
+ E: Encryption Bit. If the encryption bit is set to 1, the payload of
+ the SAP packet is encrypted. If this bit is 0 the packet is not
+ encrypted. See section 7 for details of the encryption process.
+
+ C: Compressed bit. If the compressed bit is set to 1, the payload is
+ compressed using the zlib compression algorithm [3]. If the
+ payload is to be compressed and encrypted, the compression MUST be
+ performed first.
+
+ Authentication Length. An 8 bit unsigned quantity giving the number
+ of 32 bit words following the main SAP header that contain
+ authentication data. If it is zero, no authentication header is
+ present.
+
+ Authentication data containing a digital signature of the packet,
+ with length as specified by the authentication length header
+ field. See section 8 for details of the authentication process.
+
+ Message Identifier Hash. A 16 bit quantity that, used in combination
+ with the originating source, provides a globally unique identifier
+ indicating the precise version of this announcement. The choice
+ of value for this field is not specified here, except that it MUST
+ be unique for each session announced by a particular SAP announcer
+ and it MUST be changed if the session description is modified (and
+ a session deletion message SHOULD be sent for the old version of
+ the session).
+
+ Earlier versions of SAP used a value of zero to mean that the hash
+ should be ignored and the payload should always be parsed. This
+ had the unfortunate side-effect that SAP announcers had to study
+ the payload data to determine how many unique sessions were being
+ advertised, making the calculation of the announcement interval
+ more complex that necessary. In order to decouple the session
+ announcement process from the contents of those announcements, SAP
+ announcers SHOULD NOT set the message identifier hash to zero.
+
+ SAP listeners MAY silently discard messages if the message
+ identifier hash is set to zero.
+
+
+
+Handley, et al. Experimental [Page 7]
+
+RFC 2974 Session Announcement Protocol October 2000
+
+
+ Originating Source. This gives the IP address of the original source
+ of the message. This is an IPv4 address if the A field is set to
+ zero, else it is an IPv6 address. The address is stored in
+ network byte order.
+
+ SAPv0 permitted the originating source to be zero if the message
+ identifier hash was also zero. This practise is no longer legal,
+ and SAP announcers SHOULD NOT set the originating source to zero.
+ SAP listeners MAY silently discard packets with the originating
+ source set to zero.
+
+ The header is followed by an optional payload type field and the
+ payload data itself. If the E or C bits are set in the header both
+ the payload type and payload are encrypted and/or compressed.
+
+ The payload type field is a MIME content type specifier, describing
+ the format of the payload. This is a variable length ASCII text
+ string, followed by a single zero byte (ASCII NUL). The payload type
+ SHOULD be included in all packets. If the payload type is
+ `application/sdp' both the payload type and its terminating zero byte
+ MAY be omitted, although this is intended for backwards compatibility
+ with SAP v1 listeners only.
+
+ The absence of a payload type field may be noted since the payload
+ section of such a packet will start with an SDP `v=0' field, which is
+ not a legal MIME content type specifier.
+
+ All implementations MUST support payloads of type `application/sdp'
+ [4]. Other formats MAY be supported although since there is no
+ negotiation in SAP an announcer which chooses to use a session
+ description format other than SDP cannot know that the listeners are
+ able to understand the announcement. A proliferation of payload
+ types in announcements has the potential to lead to severe
+ interoperability problems, and for this reason, the use of non-SDP
+ payloads is NOT RECOMMENDED.
+
+ If the packet is an announcement packet, the payload contains a
+ session description.
+
+ If the packet is a session deletion packet, the payload contains a
+ session deletion message. If the payload format is `application/sdp'
+ the deletion message is a single SDP line consisting of the origin
+ field of the announcement to be deleted.
+
+ It is desirable for the payload to be sufficiently small that SAP
+ packets do not get fragmented by the underlying network.
+ Fragmentation has a loss multiplier effect, which is known to
+ significantly affect the reliability of announcements. It is
+
+
+
+Handley, et al. Experimental [Page 8]
+
+RFC 2974 Session Announcement Protocol October 2000
+
+
+ RECOMMENDED that SAP packets are smaller than 1kByte in length,
+ although if it is known that announcements will use a network with a
+ smaller MTU than this, then that SHOULD be used as the maximum
+ recommended packet size.
+
+7 Encrypted Announcements
+
+ An announcement is received by all listeners in the scope to which it
+ is sent. If an announcement is encrypted, and many of the receivers
+ do not have the encryption key, there is a considerable waste of
+ bandwidth since those receivers cannot use the announcement they have
+ received. For this reason, the use of encrypted SAP announcements is
+ NOT RECOMMENDED on the global scope SAP group or on administrative
+ scope groups which may have many receivers which cannot decrypt those
+ announcements.
+
+ The opinion of the authors is that encrypted SAP is useful in special
+ cases only, and that the vast majority of scenarios where encrypted
+ SAP has been proposed may be better served by distributing session
+ details using another mechanism. There are, however, certain
+ scenarios where encrypted announcements may be useful. For this
+ reason, the encryption bit is included in the SAP header to allow
+ experimentation with encrypted announcements.
+
+ This memo does not specify details of the encryption algorithm to be
+ used or the means by which keys are generated and distributed. An
+ additional specification should define these, if it is desired to use
+ encrypted SAP.
+
+ Note that if an encrypted announcement is being announced via a
+ proxy, then there may be no way for the proxy to discover that the
+ announcement has been superseded, and so it may continue to relay the
+ old announcement in addition to the new announcement. SAP provides
+ no mechanism to chain modified encrypted announcements, so it is
+ advisable to announce the unmodified session as deleted for a short
+ time after the modification has occurred. This does not guarantee
+ that all proxies have deleted the session, and so receivers of
+ encrypted sessions should be prepared to discard old versions of
+ session announcements that they may receive. In most cases however,
+ the only stateful proxy will be local to (and known to) the sender,
+ and an additional (local-area) protocol involving a handshake for
+ such session modifications can be used to avoid this problem.
+
+ Session announcements that are encrypted with a symmetric algorithm
+ may allow a degree of privacy in the announcement of a session, but
+ it should be recognized that a user in possession of such a key can
+ pass it on to other users who should not be in possession of such a
+ key. Thus announcements to such a group of key holders cannot be
+
+
+
+Handley, et al. Experimental [Page 9]
+
+RFC 2974 Session Announcement Protocol October 2000
+
+
+ assumed to have come from an authorized key holder unless there is an
+ appropriate authentication header signed by an authorized key holder.
+ In addition the recipients of such encrypted announcements cannot be
+ assumed to only be authorized key holders. Such encrypted
+ announcements do not provide any real security unless all of the
+ authorized key holders are trusted to maintain security of such
+ session directory keys. This property is shared by the multicast
+ session tools themselves, where it is possible for an un-trustworthy
+ member of the session to pass on encryption keys to un-authorized
+ users. However it is likely that keys used for the session tools
+ will be more short lived than those used for session directories.
+
+ Similar considerations should apply when session announcements are
+ encrypted with an asymmetric algorithm, but then it is possible to
+ restrict the possessor(s) of the private key, so that announcements
+ to a key-holder group can not be made, even if one of the untrusted
+ members of the group proves to be un-trustworthy.
+
+ 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | V=1 |P| Auth | |
+ +-+-+-+-+-+-+-+-+ |
+ | Format specific authentication subheader |
+ : .................. :
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ Figure 2: Format of the authentication data in the SAP header
+
+8 Authenticated Announcements
+
+ The authentication header can be used for two purposes:
+
+ o Verification that changes to a session description or deletion of
+ a session are permitted.
+
+ o Authentication of the identity of the session creator.
+
+ In some circumstances only verification is possible because a
+ certificate signed by a mutually trusted person or authority is not
+ available. However, under such circumstances, the session originator
+ may still be authenticated to be the same as the session originator
+ of previous sessions claiming to be from the same person. This may
+ or may not be sufficient depending on the purpose of the session and
+ the people involved.
+
+
+
+
+
+
+Handley, et al. Experimental [Page 10]
+
+RFC 2974 Session Announcement Protocol October 2000
+
+
+ Clearly the key used for the authentication should not be trusted to
+ belong to the session originator unless it has been separately
+ authenticated by some other means, such as being certified by a
+ trusted third party. Such certificates are not normally included in
+ an SAP header because they take more space than can normally be
+ afforded in an SAP packet, and such verification must therefore take
+ place by some other mechanism. However, as certified public keys are
+ normally locally cached, authentication of a particular key only has
+ to take place once, rather than every time the session directory
+ retransmits the announcement.
+
+ SAP is not tied to any single authentication mechanism.
+ Authentication data in the header is self-describing, but the precise
+ format depends on the authentication mechanism in use. The generic
+ format of the authentication data is given in figure 2. The
+ structure of the format specific authentication subheader, using both
+ the PGP and the CMS formats, is discussed in sections 8.1 and 8.2
+ respectively. Additional formats may be added in future.
+
+ Version Number, V: The version number of the authentication format
+ specified by this memo is 1.
+
+ Padding Bit, P: If necessary the authentication data is padded to be
+ a multiple of 32 bits and the padding bit is set. In this case
+ the last byte of the authentication data contains the number of
+ padding bytes (including the last byte) that must be discarded.
+
+ Authentication Type, Auth: The authentication type is a 4 bit
+ encoded field that denotes the authentication infrastructure the
+ sender expects the recipients to use to check the authenticity and
+ integrity of the information. This defines the format of the
+ authentication subheader and can take the values: 0 = PGP format,
+ 1 = CMS format. All other values are undefined and SHOULD be
+ ignored.
+
+ If a SAP packet is to be compressed or encrypted, this MUST be done
+ before the authentication is added.
+
+ The digital signature in the authentication data MUST be calculated
+ over the entire packet, including the header. The authentication
+ length MUST be set to zero and the authentication data excluded when
+ calculating the digital signature.
+
+ It is to be expected that sessions may be announced by a number of
+ different mechanisms, not only SAP. For example, a session
+ description may placed on a web page, sent by email or conveyed in a
+
+
+
+
+
+Handley, et al. Experimental [Page 11]
+
+RFC 2974 Session Announcement Protocol October 2000
+
+
+ session initiation protocol. To ease interoperability with these
+ other mechanisms, application level security is employed, rather than
+ using IPsec authentication headers.
+
+8.1 PGP Authentication
+
+ A full description of the PGP protocol can be found in [2]. When
+ using PGP for SAP authentication the basic format specific
+ authentication subheader comprises a digital signature packet as
+ described in [2]. The signature type MUST be 0x01 which means the
+ signature is that of a canonical text document.
+
+8.2 CMS Authentication
+
+ A full description of the Cryptographic Message Syntax can be found
+ in [6]. The format specific authentication subheader will, in the
+ CMS case, have an ASN.1 ContentInfo type with the ContentType being
+ signedData.
+
+ Use is made of the option available in PKCS#7 to leave the content
+ itself blank as the content which is signed is already present in the
+ packet. Inclusion of it within the SignedData type would duplicate
+ this data and increase the packet length unnecessarily. In addition
+ this allows recipients with either no interest in the authentication,
+ or with no mechanism for checking it, to more easily skip the
+ authentication information.
+
+ There SHOULD be only one signerInfo and related fields corresponding
+ to the originator of the SAP announcement. The signingTime SHOULD be
+ present as a signedAttribute. However, due to the strict size
+ limitations on the size of SAP packets, certificates and CRLs SHOULD
+ NOT be included in the signedData structure. It is expected that
+ users of the protocol will have other methods for certificate and CRL
+ distribution.
+
+9 Scalability and caching
+
+ SAP is intended to announce the existence of long-lived wide-area
+ multicast sessions. It is not an especially timely protocol:
+ sessions are announced by periodic multicast with a repeat rate on
+ the order of tens of minutes, and no enhanced reliability over UDP.
+ This leads to a long startup delay before a complete set of
+ announcements is heard by a listener. This delay is clearly
+ undesirable for interactive browsing of announced sessions.
+
+ In order to reduce the delays inherent in SAP, it is recommended that
+ proxy caches are deployed. A SAP proxy cache is expected to listen
+ to all SAP groups in its scope, and to maintain an up-to-date list of
+
+
+
+Handley, et al. Experimental [Page 12]
+
+RFC 2974 Session Announcement Protocol October 2000
+
+
+ all announced sessions along with the time each announcement was last
+ received. When a new SAP listeners starts, it should contact its
+ local proxy to download this information, which is then sufficient
+ for it to process future announcements directly, as if it has been
+ continually listening.
+
+ The protocol by which a SAP listener contacts its local proxy cache
+ is not specified here.
+
+10 Security Considerations
+
+ SAP contains mechanisms for ensuring integrity of session
+ announcements, for authenticating the origin of an announcement and
+ for encrypting such announcements (sections 7 and 8).
+
+ As stated in section 5, if a session modification announcement is
+ received that contains a valid authentication header, but which is
+ not signed by the original creator of the session, then the session
+ must be treated as a new session in addition to the original session
+ with the same SDP origin information unless the originator of one of
+ the session descriptions can be authenticated using a certificate
+ signed by a trusted third party. If this were not done, there would
+ be a possible denial of service attack whereby a party listens for
+ new announcements, strips off the original authentication header,
+ modifies the session description, adds a new authentication header
+ and re-announces the session. If a rule was imposed that such spoof
+ announcements were ignored, then if packet loss or late starting of a
+ session directory instance caused the original announcement to fail
+ to arrive at a site, but the spoof announcement did so, this would
+ then prevent the original announcement from being accepted at that
+ site.
+
+ A similar denial-of-service attack is possible if a session
+ announcement receiver relies completely on the originating source and
+ hash fields to indicate change, and fails to parse the remainder of
+ announcements for which it has seen the origin/hash combination
+ before.
+
+ A denial of service attack is possible from a malicious site close to
+ a legitimate site which is making a session announcement. This can
+ happen if the malicious site floods the legitimate site with huge
+ numbers of (illegal) low TTL announcements describing high TTL
+ sessions. This may reduce the session announcement rate of the
+ legitimate announcement to below a tenth of the rate expected at
+ remote sites and therefore cause the session to time out. Such an
+ attack is likely to be easily detectable, and we do not provide any
+ mechanism here to prevent it.
+
+
+
+
+Handley, et al. Experimental [Page 13]
+
+RFC 2974 Session Announcement Protocol October 2000
+
+
+A. Summary of differences between SAPv0 and SAPv1
+
+ For this purpose SAPv0 is defined as the protocol in use by version
+ 2.2 of the session directory tool, sdr. SAPv1 is the protocol
+ described in the 19 November 1996 version of this memo. The packet
+ headers of SAP messages are the same in V0 and V1 in that a V1 tool
+ can parse a V0 announcement header but not vice-versa. In SAPv0, the
+ fields have the following values:
+
+ o Version Number: 0
+
+ o Message Type: 0 (Announcement)
+
+ o Authentication Type: 0 (No Authentication)
+
+ o Encryption Bit: 0 (No Encryption)
+
+ o Compression Bit: 0 (No compression)
+
+ o Message Id Hash: 0 (No Hash Specified)
+
+ o Originating Source: 0 (No source specified, announcement has
+ not been relayed)
+
+B. Summary of differences between SAPv1 and SAPv2
+
+ The packet headers of SAP messages are the same in V1 and V2 in that
+ a V2 tool can parse a V1 announcement header but not necessarily
+ vice-versa.
+
+ o The A bit has been added to the SAP header, replacing one of the
+ bits of the SAPv1 message type field. If set to zero the
+ announcement is of an IPv4 session, and the packet is backwards
+ compatible with SAPv1. If set to one the announcement is of an
+ IPv6 session, and SAPv1 listeners (which do not support IPv6) will
+ see this as an illegal message type (MT) field.
+
+ o The second bit of the message type field in SAPv1 has been
+ replaced by a reserved, must-be-zero, bit. This bit was unused in
+ SAPv1, so this change just codifies existing usage.
+
+ o SAPv1 specified encryption of the payload. SAPv2 includes the E
+ bit in the SAP header to indicate that the payload is encrypted,
+ but does not specify any details of the encryption.
+
+ o SAPv1 allowed the message identifier hash and originating source
+ fields to be set to zero, for backwards compatibility. This is no
+ longer legal.
+
+
+
+Handley, et al. Experimental [Page 14]
+
+RFC 2974 Session Announcement Protocol October 2000
+
+
+ o SAPv1 specified gzip compression. SAPv2 uses zlib (the only known
+ implementation of SAP compression used zlib, and gzip compression
+ was a mistake).
+
+ o SAPv2 provides a more complete specification for authentication.
+
+ o SAPv2 allows for non-SDP payloads to be transported. SAPv1
+ required that the payload was SDP.
+
+ o SAPv1 included a timeout field for encrypted announcement, SAPv2
+ does not (and relies of explicit deletion messages or implicit
+ timeouts).
+
+C. Acknowledgements
+
+ SAP and SDP were originally based on the protocol used by the sd
+ session directory from Van Jacobson at LBNL. Version 1 of SAP was
+ designed by Mark Handley as part of the European Commission MICE
+ (Esprit 7602) and MERCI (Telematics 1007) projects. Version 2
+ includes authentication features developed by Edmund Whelan, Goli
+ Montasser-Kohsari and Peter Kirstein as part of the European
+ Commission ICE-TEL project (Telematics 1005), and support for IPv6
+ developed by Maryann P. Maher and Colin Perkins.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Handley, et al. Experimental [Page 15]
+
+RFC 2974 Session Announcement Protocol October 2000
+
+
+D. Authors' Addresses
+
+ Mark Handley
+ AT&T Center for Internet Research at ICSI,
+ International Computer Science Institute,
+ 1947 Center Street, Suite 600,
+ Berkeley, CA 94704, USA
+
+ EMail: mjh@aciri.org
+
+
+ Colin Perkins
+ USC Information Sciences Institute
+ 4350 N. Fairfax Drive, Suite 620
+ Arlington, VA 22203, USA
+
+ EMail: csp@isi.edu
+
+
+ Edmund Whelan
+ Department of Computer Science,
+ University College London,
+ Gower Street,
+ London, WC1E 6BT, UK
+
+ EMail: e.whelan@cs.ucl.ac.uk
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Handley, et al. Experimental [Page 16]
+
+RFC 2974 Session Announcement Protocol October 2000
+
+
+References
+
+ [1] Bradner, S., "Key words for use in RFCs to indicate requirement
+ levels", BCP 14, RFC 2119, March 1997.
+
+ [2] Callas, J., Donnerhacke, L., Finney, H. and R. Thayer. "OpenPGP
+ message format", RFC 2440, November 1998.
+
+ [3] Deutsch, P. and J.-L. Gailly, "Zlib compressed data format
+ specification version 3.3", RFC 1950, May 1996.
+
+ [4] Handley, M. and V. Jacobson, "SDP: Session Description Protocol",
+ RFC 2327, April 1998.
+
+ [5] Handley, M., Thaler, D. and R. Kermode, "Multicast-scope zone
+ announcement protocol (MZAP)", RFC 2776, February 2000.
+
+ [6] Housley, R., "Cryptographic message syntax", RFC 2630, June 1999.
+
+ [7] Mayer, D., "Administratively scoped IP multicast", RFC 2365, July
+ 1998.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Handley, et al. Experimental [Page 17]
+
+RFC 2974 Session Announcement Protocol October 2000
+
+
+Full Copyright Statement
+
+ Copyright (C) The Internet Society (2000). All Rights Reserved.
+
+ This document and translations of it may be copied and furnished to
+ others, and derivative works that comment on or otherwise explain it
+ or assist in its implementation may be prepared, copied, published
+ and distributed, in whole or in part, without restriction of any
+ kind, provided that the above copyright notice and this paragraph are
+ included on all such copies and derivative works. However, this
+ document itself may not be modified in any way, such as by removing
+ the copyright notice or references to the Internet Society or other
+ Internet organizations, except as needed for the purpose of
+ developing Internet standards in which case the procedures for
+ copyrights defined in the Internet Standards process must be
+ followed, or as required to translate it into languages other than
+ English.
+
+ The limited permissions granted above are perpetual and will not be
+ revoked by the Internet Society or its successors or assigns.
+
+ This document and the information contained herein is provided on an
+ "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING
+ TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING
+ BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION
+ HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF
+ MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
+
+Acknowledgement
+
+ Funding for the RFC Editor function is currently provided by the
+ Internet Society.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Handley, et al. Experimental [Page 18]
+
diff --git a/src/modules/rtp/rfc3550.txt b/src/modules/rtp/rfc3550.txt
new file mode 100644
index 00000000..165736cf
--- /dev/null
+++ b/src/modules/rtp/rfc3550.txt
@@ -0,0 +1,5827 @@
+
+
+
+
+
+
+Network Working Group H. Schulzrinne
+Request for Comments: 3550 Columbia University
+Obsoletes: 1889 S. Casner
+Category: Standards Track Packet Design
+ R. Frederick
+ Blue Coat Systems Inc.
+ V. Jacobson
+ Packet Design
+ July 2003
+
+
+ RTP: A Transport Protocol for Real-Time Applications
+
+Status of this Memo
+
+ This document specifies an Internet standards track protocol for the
+ Internet community, and requests discussion and suggestions for
+ improvements. Please refer to the current edition of the "Internet
+ Official Protocol Standards" (STD 1) for the standardization state
+ and status of this protocol. Distribution of this memo is unlimited.
+
+Copyright Notice
+
+ Copyright (C) The Internet Society (2003). All Rights Reserved.
+
+Abstract
+
+ This memorandum describes RTP, the real-time transport protocol. RTP
+ provides end-to-end network transport functions suitable for
+ applications transmitting real-time data, such as audio, video or
+ simulation data, over multicast or unicast network services. RTP
+ does not address resource reservation and does not guarantee
+ quality-of-service for real-time services. The data transport is
+ augmented by a control protocol (RTCP) to allow monitoring of the
+ data delivery in a manner scalable to large multicast networks, and
+ to provide minimal control and identification functionality. RTP and
+ RTCP are designed to be independent of the underlying transport and
+ network layers. The protocol supports the use of RTP-level
+ translators and mixers.
+
+ Most of the text in this memorandum is identical to RFC 1889 which it
+ obsoletes. There are no changes in the packet formats on the wire,
+ only changes to the rules and algorithms governing how the protocol
+ is used. The biggest change is an enhancement to the scalable timer
+ algorithm for calculating when to send RTCP packets in order to
+ minimize transmission in excess of the intended rate when many
+ participants join a session simultaneously.
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 1]
+
+RFC 3550 RTP July 2003
+
+
+Table of Contents
+
+ 1. Introduction ................................................ 4
+ 1.1 Terminology ............................................ 5
+ 2. RTP Use Scenarios ........................................... 5
+ 2.1 Simple Multicast Audio Conference ...................... 6
+ 2.2 Audio and Video Conference ............................. 7
+ 2.3 Mixers and Translators ................................. 7
+ 2.4 Layered Encodings ...................................... 8
+ 3. Definitions ................................................. 8
+ 4. Byte Order, Alignment, and Time Format ...................... 12
+ 5. RTP Data Transfer Protocol .................................. 13
+ 5.1 RTP Fixed Header Fields ................................ 13
+ 5.2 Multiplexing RTP Sessions .............................. 16
+ 5.3 Profile-Specific Modifications to the RTP Header ....... 18
+ 5.3.1 RTP Header Extension ............................ 18
+ 6. RTP Control Protocol -- RTCP ................................ 19
+ 6.1 RTCP Packet Format ..................................... 21
+ 6.2 RTCP Transmission Interval ............................. 24
+ 6.2.1 Maintaining the Number of Session Members ....... 28
+ 6.3 RTCP Packet Send and Receive Rules ..................... 28
+ 6.3.1 Computing the RTCP Transmission Interval ........ 29
+ 6.3.2 Initialization .................................. 30
+ 6.3.3 Receiving an RTP or Non-BYE RTCP Packet ......... 31
+ 6.3.4 Receiving an RTCP BYE Packet .................... 31
+ 6.3.5 Timing Out an SSRC .............................. 32
+ 6.3.6 Expiration of Transmission Timer ................ 32
+ 6.3.7 Transmitting a BYE Packet ....................... 33
+ 6.3.8 Updating we_sent ................................ 34
+ 6.3.9 Allocation of Source Description Bandwidth ...... 34
+ 6.4 Sender and Receiver Reports ............................ 35
+ 6.4.1 SR: Sender Report RTCP Packet ................... 36
+ 6.4.2 RR: Receiver Report RTCP Packet ................. 42
+ 6.4.3 Extending the Sender and Receiver Reports ....... 42
+ 6.4.4 Analyzing Sender and Receiver Reports ........... 43
+ 6.5 SDES: Source Description RTCP Packet ................... 45
+ 6.5.1 CNAME: Canonical End-Point Identifier SDES Item . 46
+ 6.5.2 NAME: User Name SDES Item ....................... 48
+ 6.5.3 EMAIL: Electronic Mail Address SDES Item ........ 48
+ 6.5.4 PHONE: Phone Number SDES Item ................... 49
+ 6.5.5 LOC: Geographic User Location SDES Item ......... 49
+ 6.5.6 TOOL: Application or Tool Name SDES Item ........ 49
+ 6.5.7 NOTE: Notice/Status SDES Item ................... 50
+ 6.5.8 PRIV: Private Extensions SDES Item .............. 50
+ 6.6 BYE: Goodbye RTCP Packet ............................... 51
+ 6.7 APP: Application-Defined RTCP Packet ................... 52
+ 7. RTP Translators and Mixers .................................. 53
+ 7.1 General Description .................................... 53
+
+
+
+Schulzrinne, et al. Standards Track [Page 2]
+
+RFC 3550 RTP July 2003
+
+
+ 7.2 RTCP Processing in Translators ......................... 55
+ 7.3 RTCP Processing in Mixers .............................. 57
+ 7.4 Cascaded Mixers ........................................ 58
+ 8. SSRC Identifier Allocation and Use .......................... 59
+ 8.1 Probability of Collision ............................... 59
+ 8.2 Collision Resolution and Loop Detection ................ 60
+ 8.3 Use with Layered Encodings ............................. 64
+ 9. Security .................................................... 65
+ 9.1 Confidentiality ........................................ 65
+ 9.2 Authentication and Message Integrity ................... 67
+ 10. Congestion Control .......................................... 67
+ 11. RTP over Network and Transport Protocols .................... 68
+ 12. Summary of Protocol Constants ............................... 69
+ 12.1 RTCP Packet Types ...................................... 70
+ 12.2 SDES Types ............................................. 70
+ 13. RTP Profiles and Payload Format Specifications .............. 71
+ 14. Security Considerations ..................................... 73
+ 15. IANA Considerations ......................................... 73
+ 16. Intellectual Property Rights Statement ...................... 74
+ 17. Acknowledgments ............................................. 74
+ Appendix A. Algorithms ........................................ 75
+ Appendix A.1 RTP Data Header Validity Checks ................... 78
+ Appendix A.2 RTCP Header Validity Checks ....................... 82
+ Appendix A.3 Determining Number of Packets Expected and Lost ... 83
+ Appendix A.4 Generating RTCP SDES Packets ...................... 84
+ Appendix A.5 Parsing RTCP SDES Packets ......................... 85
+ Appendix A.6 Generating a Random 32-bit Identifier ............. 85
+ Appendix A.7 Computing the RTCP Transmission Interval .......... 87
+ Appendix A.8 Estimating the Interarrival Jitter ................ 94
+ Appendix B. Changes from RFC 1889 ............................. 95
+ References ...................................................... 100
+ Normative References ............................................ 100
+ Informative References .......................................... 100
+ Authors' Addresses .............................................. 103
+ Full Copyright Statement ........................................ 104
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 3]
+
+RFC 3550 RTP July 2003
+
+
+1. Introduction
+
+ This memorandum specifies the real-time transport protocol (RTP),
+ which provides end-to-end delivery services for data with real-time
+ characteristics, such as interactive audio and video. Those services
+ include payload type identification, sequence numbering, timestamping
+ and delivery monitoring. Applications typically run RTP on top of
+ UDP to make use of its multiplexing and checksum services; both
+ protocols contribute parts of the transport protocol functionality.
+ However, RTP may be used with other suitable underlying network or
+ transport protocols (see Section 11). RTP supports data transfer to
+ multiple destinations using multicast distribution if provided by the
+ underlying network.
+
+ Note that RTP itself does not provide any mechanism to ensure timely
+ delivery or provide other quality-of-service guarantees, but relies
+ on lower-layer services to do so. It does not guarantee delivery or
+ prevent out-of-order delivery, nor does it assume that the underlying
+ network is reliable and delivers packets in sequence. The sequence
+ numbers included in RTP allow the receiver to reconstruct the
+ sender's packet sequence, but sequence numbers might also be used to
+ determine the proper location of a packet, for example in video
+ decoding, without necessarily decoding packets in sequence.
+
+ While RTP is primarily designed to satisfy the needs of multi-
+ participant multimedia conferences, it is not limited to that
+ particular application. Storage of continuous data, interactive
+ distributed simulation, active badge, and control and measurement
+ applications may also find RTP applicable.
+
+ This document defines RTP, consisting of two closely-linked parts:
+
+ o the real-time transport protocol (RTP), to carry data that has
+ real-time properties.
+
+ o the RTP control protocol (RTCP), to monitor the quality of service
+ and to convey information about the participants in an on-going
+ session. The latter aspect of RTCP may be sufficient for "loosely
+ controlled" sessions, i.e., where there is no explicit membership
+ control and set-up, but it is not necessarily intended to support
+ all of an application's control communication requirements. This
+ functionality may be fully or partially subsumed by a separate
+ session control protocol, which is beyond the scope of this
+ document.
+
+ RTP represents a new style of protocol following the principles of
+ application level framing and integrated layer processing proposed by
+ Clark and Tennenhouse [10]. That is, RTP is intended to be malleable
+
+
+
+Schulzrinne, et al. Standards Track [Page 4]
+
+RFC 3550 RTP July 2003
+
+
+ to provide the information required by a particular application and
+ will often be integrated into the application processing rather than
+ being implemented as a separate layer. RTP is a protocol framework
+ that is deliberately not complete. This document specifies those
+ functions expected to be common across all the applications for which
+ RTP would be appropriate. Unlike conventional protocols in which
+ additional functions might be accommodated by making the protocol
+ more general or by adding an option mechanism that would require
+ parsing, RTP is intended to be tailored through modifications and/or
+ additions to the headers as needed. Examples are given in Sections
+ 5.3 and 6.4.3.
+
+ Therefore, in addition to this document, a complete specification of
+ RTP for a particular application will require one or more companion
+ documents (see Section 13):
+
+ o a profile specification document, which defines a set of payload
+ type codes and their mapping to payload formats (e.g., media
+ encodings). A profile may also define extensions or modifications
+ to RTP that are specific to a particular class of applications.
+ Typically an application will operate under only one profile. A
+ profile for audio and video data may be found in the companion RFC
+ 3551 [1].
+
+ o payload format specification documents, which define how a
+ particular payload, such as an audio or video encoding, is to be
+ carried in RTP.
+
+ A discussion of real-time services and algorithms for their
+ implementation as well as background discussion on some of the RTP
+ design decisions can be found in [11].
+
+1.1 Terminology
+
+ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
+ "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
+ document are to be interpreted as described in BCP 14, RFC 2119 [2]
+ and indicate requirement levels for compliant RTP implementations.
+
+2. RTP Use Scenarios
+
+ The following sections describe some aspects of the use of RTP. The
+ examples were chosen to illustrate the basic operation of
+ applications using RTP, not to limit what RTP may be used for. In
+ these examples, RTP is carried on top of IP and UDP, and follows the
+ conventions established by the profile for audio and video specified
+ in the companion RFC 3551.
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 5]
+
+RFC 3550 RTP July 2003
+
+
+2.1 Simple Multicast Audio Conference
+
+ A working group of the IETF meets to discuss the latest protocol
+ document, using the IP multicast services of the Internet for voice
+ communications. Through some allocation mechanism the working group
+ chair obtains a multicast group address and pair of ports. One port
+ is used for audio data, and the other is used for control (RTCP)
+ packets. This address and port information is distributed to the
+ intended participants. If privacy is desired, the data and control
+ packets may be encrypted as specified in Section 9.1, in which case
+ an encryption key must also be generated and distributed. The exact
+ details of these allocation and distribution mechanisms are beyond
+ the scope of RTP.
+
+ The audio conferencing application used by each conference
+ participant sends audio data in small chunks of, say, 20 ms duration.
+ Each chunk of audio data is preceded by an RTP header; RTP header and
+ data are in turn contained in a UDP packet. The RTP header indicates
+ what type of audio encoding (such as PCM, ADPCM or LPC) is contained
+ in each packet so that senders can change the encoding during a
+ conference, for example, to accommodate a new participant that is
+ connected through a low-bandwidth link or react to indications of
+ network congestion.
+
+ The Internet, like other packet networks, occasionally loses and
+ reorders packets and delays them by variable amounts of time. To
+ cope with these impairments, the RTP header contains timing
+ information and a sequence number that allow the receivers to
+ reconstruct the timing produced by the source, so that in this
+ example, chunks of audio are contiguously played out the speaker
+ every 20 ms. This timing reconstruction is performed separately for
+ each source of RTP packets in the conference. The sequence number
+ can also be used by the receiver to estimate how many packets are
+ being lost.
+
+ Since members of the working group join and leave during the
+ conference, it is useful to know who is participating at any moment
+ and how well they are receiving the audio data. For that purpose,
+ each instance of the audio application in the conference periodically
+ multicasts a reception report plus the name of its user on the RTCP
+ (control) port. The reception report indicates how well the current
+ speaker is being received and may be used to control adaptive
+ encodings. In addition to the user name, other identifying
+ information may also be included subject to control bandwidth limits.
+ A site sends the RTCP BYE packet (Section 6.6) when it leaves the
+ conference.
+
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 6]
+
+RFC 3550 RTP July 2003
+
+
+2.2 Audio and Video Conference
+
+ If both audio and video media are used in a conference, they are
+ transmitted as separate RTP sessions. That is, separate RTP and RTCP
+ packets are transmitted for each medium using two different UDP port
+ pairs and/or multicast addresses. There is no direct coupling at the
+ RTP level between the audio and video sessions, except that a user
+ participating in both sessions should use the same distinguished
+ (canonical) name in the RTCP packets for both so that the sessions
+ can be associated.
+
+ One motivation for this separation is to allow some participants in
+ the conference to receive only one medium if they choose. Further
+ explanation is given in Section 5.2. Despite the separation,
+ synchronized playback of a source's audio and video can be achieved
+ using timing information carried in the RTCP packets for both
+ sessions.
+
+2.3 Mixers and Translators
+
+ So far, we have assumed that all sites want to receive media data in
+ the same format. However, this may not always be appropriate.
+ Consider the case where participants in one area are connected
+ through a low-speed link to the majority of the conference
+ participants who enjoy high-speed network access. Instead of forcing
+ everyone to use a lower-bandwidth, reduced-quality audio encoding, an
+ RTP-level relay called a mixer may be placed near the low-bandwidth
+ area. This mixer resynchronizes incoming audio packets to
+ reconstruct the constant 20 ms spacing generated by the sender, mixes
+ these reconstructed audio streams into a single stream, translates
+ the audio encoding to a lower-bandwidth one and forwards the lower-
+ bandwidth packet stream across the low-speed link. These packets
+ might be unicast to a single recipient or multicast on a different
+ address to multiple recipients. The RTP header includes a means for
+ mixers to identify the sources that contributed to a mixed packet so
+ that correct talker indication can be provided at the receivers.
+
+ Some of the intended participants in the audio conference may be
+ connected with high bandwidth links but might not be directly
+ reachable via IP multicast. For example, they might be behind an
+ application-level firewall that will not let any IP packets pass.
+ For these sites, mixing may not be necessary, in which case another
+ type of RTP-level relay called a translator may be used. Two
+ translators are installed, one on either side of the firewall, with
+ the outside one funneling all multicast packets received through a
+ secure connection to the translator inside the firewall. The
+ translator inside the firewall sends them again as multicast packets
+ to a multicast group restricted to the site's internal network.
+
+
+
+Schulzrinne, et al. Standards Track [Page 7]
+
+RFC 3550 RTP July 2003
+
+
+ Mixers and translators may be designed for a variety of purposes. An
+ example is a video mixer that scales the images of individual people
+ in separate video streams and composites them into one video stream
+ to simulate a group scene. Other examples of translation include the
+ connection of a group of hosts speaking only IP/UDP to a group of
+ hosts that understand only ST-II, or the packet-by-packet encoding
+ translation of video streams from individual sources without
+ resynchronization or mixing. Details of the operation of mixers and
+ translators are given in Section 7.
+
+2.4 Layered Encodings
+
+ Multimedia applications should be able to adjust the transmission
+ rate to match the capacity of the receiver or to adapt to network
+ congestion. Many implementations place the responsibility of rate-
+ adaptivity at the source. This does not work well with multicast
+ transmission because of the conflicting bandwidth requirements of
+ heterogeneous receivers. The result is often a least-common
+ denominator scenario, where the smallest pipe in the network mesh
+ dictates the quality and fidelity of the overall live multimedia
+ "broadcast".
+
+ Instead, responsibility for rate-adaptation can be placed at the
+ receivers by combining a layered encoding with a layered transmission
+ system. In the context of RTP over IP multicast, the source can
+ stripe the progressive layers of a hierarchically represented signal
+ across multiple RTP sessions each carried on its own multicast group.
+ Receivers can then adapt to network heterogeneity and control their
+ reception bandwidth by joining only the appropriate subset of the
+ multicast groups.
+
+ Details of the use of RTP with layered encodings are given in
+ Sections 6.3.9, 8.3 and 11.
+
+3. Definitions
+
+ RTP payload: The data transported by RTP in a packet, for
+ example audio samples or compressed video data. The payload
+ format and interpretation are beyond the scope of this document.
+
+ RTP packet: A data packet consisting of the fixed RTP header, a
+ possibly empty list of contributing sources (see below), and the
+ payload data. Some underlying protocols may require an
+ encapsulation of the RTP packet to be defined. Typically one
+ packet of the underlying protocol contains a single RTP packet,
+ but several RTP packets MAY be contained if permitted by the
+ encapsulation method (see Section 11).
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 8]
+
+RFC 3550 RTP July 2003
+
+
+ RTCP packet: A control packet consisting of a fixed header part
+ similar to that of RTP data packets, followed by structured
+ elements that vary depending upon the RTCP packet type. The
+ formats are defined in Section 6. Typically, multiple RTCP
+ packets are sent together as a compound RTCP packet in a single
+ packet of the underlying protocol; this is enabled by the length
+ field in the fixed header of each RTCP packet.
+
+ Port: The "abstraction that transport protocols use to
+ distinguish among multiple destinations within a given host
+ computer. TCP/IP protocols identify ports using small positive
+ integers." [12] The transport selectors (TSEL) used by the OSI
+ transport layer are equivalent to ports. RTP depends upon the
+ lower-layer protocol to provide some mechanism such as ports to
+ multiplex the RTP and RTCP packets of a session.
+
+ Transport address: The combination of a network address and port
+ that identifies a transport-level endpoint, for example an IP
+ address and a UDP port. Packets are transmitted from a source
+ transport address to a destination transport address.
+
+ RTP media type: An RTP media type is the collection of payload
+ types which can be carried within a single RTP session. The RTP
+ Profile assigns RTP media types to RTP payload types.
+
+ Multimedia session: A set of concurrent RTP sessions among a
+ common group of participants. For example, a videoconference
+ (which is a multimedia session) may contain an audio RTP session
+ and a video RTP session.
+
+ RTP session: An association among a set of participants
+ communicating with RTP. A participant may be involved in multiple
+ RTP sessions at the same time. In a multimedia session, each
+ medium is typically carried in a separate RTP session with its own
+ RTCP packets unless the the encoding itself multiplexes multiple
+ media into a single data stream. A participant distinguishes
+ multiple RTP sessions by reception of different sessions using
+ different pairs of destination transport addresses, where a pair
+ of transport addresses comprises one network address plus a pair
+ of ports for RTP and RTCP. All participants in an RTP session may
+ share a common destination transport address pair, as in the case
+ of IP multicast, or the pairs may be different for each
+ participant, as in the case of individual unicast network
+ addresses and port pairs. In the unicast case, a participant may
+ receive from all other participants in the session using the same
+ pair of ports, or may use a distinct pair of ports for each.
+
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 9]
+
+RFC 3550 RTP July 2003
+
+
+ The distinguishing feature of an RTP session is that each
+ maintains a full, separate space of SSRC identifiers (defined
+ next). The set of participants included in one RTP session
+ consists of those that can receive an SSRC identifier transmitted
+ by any one of the participants either in RTP as the SSRC or a CSRC
+ (also defined below) or in RTCP. For example, consider a three-
+ party conference implemented using unicast UDP with each
+ participant receiving from the other two on separate port pairs.
+ If each participant sends RTCP feedback about data received from
+ one other participant only back to that participant, then the
+ conference is composed of three separate point-to-point RTP
+ sessions. If each participant provides RTCP feedback about its
+ reception of one other participant to both of the other
+ participants, then the conference is composed of one multi-party
+ RTP session. The latter case simulates the behavior that would
+ occur with IP multicast communication among the three
+ participants.
+
+ The RTP framework allows the variations defined here, but a
+ particular control protocol or application design will usually
+ impose constraints on these variations.
+
+ Synchronization source (SSRC): The source of a stream of RTP
+ packets, identified by a 32-bit numeric SSRC identifier carried in
+ the RTP header so as not to be dependent upon the network address.
+ All packets from a synchronization source form part of the same
+ timing and sequence number space, so a receiver groups packets by
+ synchronization source for playback. Examples of synchronization
+ sources include the sender of a stream of packets derived from a
+ signal source such as a microphone or a camera, or an RTP mixer
+ (see below). A synchronization source may change its data format,
+ e.g., audio encoding, over time. The SSRC identifier is a
+ randomly chosen value meant to be globally unique within a
+ particular RTP session (see Section 8). A participant need not
+ use the same SSRC identifier for all the RTP sessions in a
+ multimedia session; the binding of the SSRC identifiers is
+ provided through RTCP (see Section 6.5.1). If a participant
+ generates multiple streams in one RTP session, for example from
+ separate video cameras, each MUST be identified as a different
+ SSRC.
+
+ Contributing source (CSRC): A source of a stream of RTP packets
+ that has contributed to the combined stream produced by an RTP
+ mixer (see below). The mixer inserts a list of the SSRC
+ identifiers of the sources that contributed to the generation of a
+ particular packet into the RTP header of that packet. This list
+ is called the CSRC list. An example application is audio
+ conferencing where a mixer indicates all the talkers whose speech
+
+
+
+Schulzrinne, et al. Standards Track [Page 10]
+
+RFC 3550 RTP July 2003
+
+
+ was combined to produce the outgoing packet, allowing the receiver
+ to indicate the current talker, even though all the audio packets
+ contain the same SSRC identifier (that of the mixer).
+
+ End system: An application that generates the content to be sent
+ in RTP packets and/or consumes the content of received RTP
+ packets. An end system can act as one or more synchronization
+ sources in a particular RTP session, but typically only one.
+
+ Mixer: An intermediate system that receives RTP packets from one
+ or more sources, possibly changes the data format, combines the
+ packets in some manner and then forwards a new RTP packet. Since
+ the timing among multiple input sources will not generally be
+ synchronized, the mixer will make timing adjustments among the
+ streams and generate its own timing for the combined stream.
+ Thus, all data packets originating from a mixer will be identified
+ as having the mixer as their synchronization source.
+
+ Translator: An intermediate system that forwards RTP packets
+ with their synchronization source identifier intact. Examples of
+ translators include devices that convert encodings without mixing,
+ replicators from multicast to unicast, and application-level
+ filters in firewalls.
+
+ Monitor: An application that receives RTCP packets sent by
+ participants in an RTP session, in particular the reception
+ reports, and estimates the current quality of service for
+ distribution monitoring, fault diagnosis and long-term statistics.
+ The monitor function is likely to be built into the application(s)
+ participating in the session, but may also be a separate
+ application that does not otherwise participate and does not send
+ or receive the RTP data packets (since they are on a separate
+ port). These are called third-party monitors. It is also
+ acceptable for a third-party monitor to receive the RTP data
+ packets but not send RTCP packets or otherwise be counted in the
+ session.
+
+ Non-RTP means: Protocols and mechanisms that may be needed in
+ addition to RTP to provide a usable service. In particular, for
+ multimedia conferences, a control protocol may distribute
+ multicast addresses and keys for encryption, negotiate the
+ encryption algorithm to be used, and define dynamic mappings
+ between RTP payload type values and the payload formats they
+ represent for formats that do not have a predefined payload type
+ value. Examples of such protocols include the Session Initiation
+ Protocol (SIP) (RFC 3261 [13]), ITU Recommendation H.323 [14] and
+ applications using SDP (RFC 2327 [15]), such as RTSP (RFC 2326
+ [16]). For simple
+
+
+
+Schulzrinne, et al. Standards Track [Page 11]
+
+RFC 3550 RTP July 2003
+
+
+ applications, electronic mail or a conference database may also be
+ used. The specification of such protocols and mechanisms is
+ outside the scope of this document.
+
+4. Byte Order, Alignment, and Time Format
+
+ All integer fields are carried in network byte order, that is, most
+ significant byte (octet) first. This byte order is commonly known as
+ big-endian. The transmission order is described in detail in [3].
+ Unless otherwise noted, numeric constants are in decimal (base 10).
+
+ All header data is aligned to its natural length, i.e., 16-bit fields
+ are aligned on even offsets, 32-bit fields are aligned at offsets
+ divisible by four, etc. Octets designated as padding have the value
+ zero.
+
+ Wallclock time (absolute date and time) is represented using the
+ timestamp format of the Network Time Protocol (NTP), which is in
+ seconds relative to 0h UTC on 1 January 1900 [4]. The full
+ resolution NTP timestamp is a 64-bit unsigned fixed-point number with
+ the integer part in the first 32 bits and the fractional part in the
+ last 32 bits. In some fields where a more compact representation is
+ appropriate, only the middle 32 bits are used; that is, the low 16
+ bits of the integer part and the high 16 bits of the fractional part.
+ The high 16 bits of the integer part must be determined
+ independently.
+
+ An implementation is not required to run the Network Time Protocol in
+ order to use RTP. Other time sources, or none at all, may be used
+ (see the description of the NTP timestamp field in Section 6.4.1).
+ However, running NTP may be useful for synchronizing streams
+ transmitted from separate hosts.
+
+ The NTP timestamp will wrap around to zero some time in the year
+ 2036, but for RTP purposes, only differences between pairs of NTP
+ timestamps are used. So long as the pairs of timestamps can be
+ assumed to be within 68 years of each other, using modular arithmetic
+ for subtractions and comparisons makes the wraparound irrelevant.
+
+
+
+
+
+
+
+
+
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 12]
+
+RFC 3550 RTP July 2003
+
+
+5. RTP Data Transfer Protocol
+
+5.1 RTP Fixed Header Fields
+
+ The RTP header has the following format:
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ |V=2|P|X| CC |M| PT | sequence number |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | timestamp |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | synchronization source (SSRC) identifier |
+ +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+ | contributing source (CSRC) identifiers |
+ | .... |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ The first twelve octets are present in every RTP packet, while the
+ list of CSRC identifiers is present only when inserted by a mixer.
+ The fields have the following meaning:
+
+ version (V): 2 bits
+ This field identifies the version of RTP. The version defined by
+ this specification is two (2). (The value 1 is used by the first
+ draft version of RTP and the value 0 is used by the protocol
+ initially implemented in the "vat" audio tool.)
+
+ padding (P): 1 bit
+ If the padding bit is set, the packet contains one or more
+ additional padding octets at the end which are not part of the
+ payload. The last octet of the padding contains a count of how
+ many padding octets should be ignored, including itself. Padding
+ may be needed by some encryption algorithms with fixed block sizes
+ or for carrying several RTP packets in a lower-layer protocol data
+ unit.
+
+ extension (X): 1 bit
+ If the extension bit is set, the fixed header MUST be followed by
+ exactly one header extension, with a format defined in Section
+ 5.3.1.
+
+ CSRC count (CC): 4 bits
+ The CSRC count contains the number of CSRC identifiers that follow
+ the fixed header.
+
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 13]
+
+RFC 3550 RTP July 2003
+
+
+ marker (M): 1 bit
+ The interpretation of the marker is defined by a profile. It is
+ intended to allow significant events such as frame boundaries to
+ be marked in the packet stream. A profile MAY define additional
+ marker bits or specify that there is no marker bit by changing the
+ number of bits in the payload type field (see Section 5.3).
+
+ payload type (PT): 7 bits
+ This field identifies the format of the RTP payload and determines
+ its interpretation by the application. A profile MAY specify a
+ default static mapping of payload type codes to payload formats.
+ Additional payload type codes MAY be defined dynamically through
+ non-RTP means (see Section 3). A set of default mappings for
+ audio and video is specified in the companion RFC 3551 [1]. An
+ RTP source MAY change the payload type during a session, but this
+ field SHOULD NOT be used for multiplexing separate media streams
+ (see Section 5.2).
+
+ A receiver MUST ignore packets with payload types that it does not
+ understand.
+
+ sequence number: 16 bits
+ The sequence number increments by one for each RTP data packet
+ sent, and may be used by the receiver to detect packet loss and to
+ restore packet sequence. The initial value of the sequence number
+ SHOULD be random (unpredictable) to make known-plaintext attacks
+ on encryption more difficult, even if the source itself does not
+ encrypt according to the method in Section 9.1, because the
+ packets may flow through a translator that does. Techniques for
+ choosing unpredictable numbers are discussed in [17].
+
+ timestamp: 32 bits
+ The timestamp reflects the sampling instant of the first octet in
+ the RTP data packet. The sampling instant MUST be derived from a
+ clock that increments monotonically and linearly in time to allow
+ synchronization and jitter calculations (see Section 6.4.1). The
+ resolution of the clock MUST be sufficient for the desired
+ synchronization accuracy and for measuring packet arrival jitter
+ (one tick per video frame is typically not sufficient). The clock
+ frequency is dependent on the format of data carried as payload
+ and is specified statically in the profile or payload format
+ specification that defines the format, or MAY be specified
+ dynamically for payload formats defined through non-RTP means. If
+ RTP packets are generated periodically, the nominal sampling
+ instant as determined from the sampling clock is to be used, not a
+ reading of the system clock. As an example, for fixed-rate audio
+ the timestamp clock would likely increment by one for each
+ sampling period. If an audio application reads blocks covering
+
+
+
+Schulzrinne, et al. Standards Track [Page 14]
+
+RFC 3550 RTP July 2003
+
+
+ 160 sampling periods from the input device, the timestamp would be
+ increased by 160 for each such block, regardless of whether the
+ block is transmitted in a packet or dropped as silent.
+
+ The initial value of the timestamp SHOULD be random, as for the
+ sequence number. Several consecutive RTP packets will have equal
+ timestamps if they are (logically) generated at once, e.g., belong
+ to the same video frame. Consecutive RTP packets MAY contain
+ timestamps that are not monotonic if the data is not transmitted
+ in the order it was sampled, as in the case of MPEG interpolated
+ video frames. (The sequence numbers of the packets as transmitted
+ will still be monotonic.)
+
+ RTP timestamps from different media streams may advance at
+ different rates and usually have independent, random offsets.
+ Therefore, although these timestamps are sufficient to reconstruct
+ the timing of a single stream, directly comparing RTP timestamps
+ from different media is not effective for synchronization.
+ Instead, for each medium the RTP timestamp is related to the
+ sampling instant by pairing it with a timestamp from a reference
+ clock (wallclock) that represents the time when the data
+ corresponding to the RTP timestamp was sampled. The reference
+ clock is shared by all media to be synchronized. The timestamp
+ pairs are not transmitted in every data packet, but at a lower
+ rate in RTCP SR packets as described in Section 6.4.
+
+ The sampling instant is chosen as the point of reference for the
+ RTP timestamp because it is known to the transmitting endpoint and
+ has a common definition for all media, independent of encoding
+ delays or other processing. The purpose is to allow synchronized
+ presentation of all media sampled at the same time.
+
+ Applications transmitting stored data rather than data sampled in
+ real time typically use a virtual presentation timeline derived
+ from wallclock time to determine when the next frame or other unit
+ of each medium in the stored data should be presented. In this
+ case, the RTP timestamp would reflect the presentation time for
+ each unit. That is, the RTP timestamp for each unit would be
+ related to the wallclock time at which the unit becomes current on
+ the virtual presentation timeline. Actual presentation occurs
+ some time later as determined by the receiver.
+
+ An example describing live audio narration of prerecorded video
+ illustrates the significance of choosing the sampling instant as
+ the reference point. In this scenario, the video would be
+ presented locally for the narrator to view and would be
+ simultaneously transmitted using RTP. The "sampling instant" of a
+ video frame transmitted in RTP would be established by referencing
+
+
+
+Schulzrinne, et al. Standards Track [Page 15]
+
+RFC 3550 RTP July 2003
+
+
+ its timestamp to the wallclock time when that video frame was
+ presented to the narrator. The sampling instant for the audio RTP
+ packets containing the narrator's speech would be established by
+ referencing the same wallclock time when the audio was sampled.
+ The audio and video may even be transmitted by different hosts if
+ the reference clocks on the two hosts are synchronized by some
+ means such as NTP. A receiver can then synchronize presentation
+ of the audio and video packets by relating their RTP timestamps
+ using the timestamp pairs in RTCP SR packets.
+
+ SSRC: 32 bits
+ The SSRC field identifies the synchronization source. This
+ identifier SHOULD be chosen randomly, with the intent that no two
+ synchronization sources within the same RTP session will have the
+ same SSRC identifier. An example algorithm for generating a
+ random identifier is presented in Appendix A.6. Although the
+ probability of multiple sources choosing the same identifier is
+ low, all RTP implementations must be prepared to detect and
+ resolve collisions. Section 8 describes the probability of
+ collision along with a mechanism for resolving collisions and
+ detecting RTP-level forwarding loops based on the uniqueness of
+ the SSRC identifier. If a source changes its source transport
+ address, it must also choose a new SSRC identifier to avoid being
+ interpreted as a looped source (see Section 8.2).
+
+ CSRC list: 0 to 15 items, 32 bits each
+ The CSRC list identifies the contributing sources for the payload
+ contained in this packet. The number of identifiers is given by
+ the CC field. If there are more than 15 contributing sources,
+ only 15 can be identified. CSRC identifiers are inserted by
+ mixers (see Section 7.1), using the SSRC identifiers of
+ contributing sources. For example, for audio packets the SSRC
+ identifiers of all sources that were mixed together to create a
+ packet are listed, allowing correct talker indication at the
+ receiver.
+
+5.2 Multiplexing RTP Sessions
+
+ For efficient protocol processing, the number of multiplexing points
+ should be minimized, as described in the integrated layer processing
+ design principle [10]. In RTP, multiplexing is provided by the
+ destination transport address (network address and port number) which
+ is different for each RTP session. For example, in a teleconference
+ composed of audio and video media encoded separately, each medium
+ SHOULD be carried in a separate RTP session with its own destination
+ transport address.
+
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 16]
+
+RFC 3550 RTP July 2003
+
+
+ Separate audio and video streams SHOULD NOT be carried in a single
+ RTP session and demultiplexed based on the payload type or SSRC
+ fields. Interleaving packets with different RTP media types but
+ using the same SSRC would introduce several problems:
+
+ 1. If, say, two audio streams shared the same RTP session and the
+ same SSRC value, and one were to change encodings and thus acquire
+ a different RTP payload type, there would be no general way of
+ identifying which stream had changed encodings.
+
+ 2. An SSRC is defined to identify a single timing and sequence number
+ space. Interleaving multiple payload types would require
+ different timing spaces if the media clock rates differ and would
+ require different sequence number spaces to tell which payload
+ type suffered packet loss.
+
+ 3. The RTCP sender and receiver reports (see Section 6.4) can only
+ describe one timing and sequence number space per SSRC and do not
+ carry a payload type field.
+
+ 4. An RTP mixer would not be able to combine interleaved streams of
+ incompatible media into one stream.
+
+ 5. Carrying multiple media in one RTP session precludes: the use of
+ different network paths or network resource allocations if
+ appropriate; reception of a subset of the media if desired, for
+ example just audio if video would exceed the available bandwidth;
+ and receiver implementations that use separate processes for the
+ different media, whereas using separate RTP sessions permits
+ either single- or multiple-process implementations.
+
+ Using a different SSRC for each medium but sending them in the same
+ RTP session would avoid the first three problems but not the last
+ two.
+
+ On the other hand, multiplexing multiple related sources of the same
+ medium in one RTP session using different SSRC values is the norm for
+ multicast sessions. The problems listed above don't apply: an RTP
+ mixer can combine multiple audio sources, for example, and the same
+ treatment is applicable for all of them. It may also be appropriate
+ to multiplex streams of the same medium using different SSRC values
+ in other scenarios where the last two problems do not apply.
+
+
+
+
+
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 17]
+
+RFC 3550 RTP July 2003
+
+
+5.3 Profile-Specific Modifications to the RTP Header
+
+ The existing RTP data packet header is believed to be complete for
+ the set of functions required in common across all the application
+ classes that RTP might support. However, in keeping with the ALF
+ design principle, the header MAY be tailored through modifications or
+ additions defined in a profile specification while still allowing
+ profile-independent monitoring and recording tools to function.
+
+ o The marker bit and payload type field carry profile-specific
+ information, but they are allocated in the fixed header since many
+ applications are expected to need them and might otherwise have to
+ add another 32-bit word just to hold them. The octet containing
+ these fields MAY be redefined by a profile to suit different
+ requirements, for example with more or fewer marker bits. If
+ there are any marker bits, one SHOULD be located in the most
+ significant bit of the octet since profile-independent monitors
+ may be able to observe a correlation between packet loss patterns
+ and the marker bit.
+
+ o Additional information that is required for a particular payload
+ format, such as a video encoding, SHOULD be carried in the payload
+ section of the packet. This might be in a header that is always
+ present at the start of the payload section, or might be indicated
+ by a reserved value in the data pattern.
+
+ o If a particular class of applications needs additional
+ functionality independent of payload format, the profile under
+ which those applications operate SHOULD define additional fixed
+ fields to follow immediately after the SSRC field of the existing
+ fixed header. Those applications will be able to quickly and
+ directly access the additional fields while profile-independent
+ monitors or recorders can still process the RTP packets by
+ interpreting only the first twelve octets.
+
+ If it turns out that additional functionality is needed in common
+ across all profiles, then a new version of RTP should be defined to
+ make a permanent change to the fixed header.
+
+5.3.1 RTP Header Extension
+
+ An extension mechanism is provided to allow individual
+ implementations to experiment with new payload-format-independent
+ functions that require additional information to be carried in the
+ RTP data packet header. This mechanism is designed so that the
+ header extension may be ignored by other interoperating
+ implementations that have not been extended.
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 18]
+
+RFC 3550 RTP July 2003
+
+
+ Note that this header extension is intended only for limited use.
+ Most potential uses of this mechanism would be better done another
+ way, using the methods described in the previous section. For
+ example, a profile-specific extension to the fixed header is less
+ expensive to process because it is not conditional nor in a variable
+ location. Additional information required for a particular payload
+ format SHOULD NOT use this header extension, but SHOULD be carried in
+ the payload section of the packet.
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | defined by profile | length |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | header extension |
+ | .... |
+
+ If the X bit in the RTP header is one, a variable-length header
+ extension MUST be appended to the RTP header, following the CSRC list
+ if present. The header extension contains a 16-bit length field that
+ counts the number of 32-bit words in the extension, excluding the
+ four-octet extension header (therefore zero is a valid length). Only
+ a single extension can be appended to the RTP data header. To allow
+ multiple interoperating implementations to each experiment
+ independently with different header extensions, or to allow a
+ particular implementation to experiment with more than one type of
+ header extension, the first 16 bits of the header extension are left
+ open for distinguishing identifiers or parameters. The format of
+ these 16 bits is to be defined by the profile specification under
+ which the implementations are operating. This RTP specification does
+ not define any header extensions itself.
+
+6. RTP Control Protocol -- RTCP
+
+ The RTP control protocol (RTCP) is based on the periodic transmission
+ of control packets to all participants in the session, using the same
+ distribution mechanism as the data packets. The underlying protocol
+ MUST provide multiplexing of the data and control packets, for
+ example using separate port numbers with UDP. RTCP performs four
+ functions:
+
+ 1. The primary function is to provide feedback on the quality of the
+ data distribution. This is an integral part of the RTP's role as
+ a transport protocol and is related to the flow and congestion
+ control functions of other transport protocols (see Section 10 on
+ the requirement for congestion control). The feedback may be
+ directly useful for control of adaptive encodings [18,19], but
+ experiments with IP multicasting have shown that it is also
+
+
+
+Schulzrinne, et al. Standards Track [Page 19]
+
+RFC 3550 RTP July 2003
+
+
+ critical to get feedback from the receivers to diagnose faults in
+ the distribution. Sending reception feedback reports to all
+ participants allows one who is observing problems to evaluate
+ whether those problems are local or global. With a distribution
+ mechanism like IP multicast, it is also possible for an entity
+ such as a network service provider who is not otherwise involved
+ in the session to receive the feedback information and act as a
+ third-party monitor to diagnose network problems. This feedback
+ function is performed by the RTCP sender and receiver reports,
+ described below in Section 6.4.
+
+ 2. RTCP carries a persistent transport-level identifier for an RTP
+ source called the canonical name or CNAME, Section 6.5.1. Since
+ the SSRC identifier may change if a conflict is discovered or a
+ program is restarted, receivers require the CNAME to keep track of
+ each participant. Receivers may also require the CNAME to
+ associate multiple data streams from a given participant in a set
+ of related RTP sessions, for example to synchronize audio and
+ video. Inter-media synchronization also requires the NTP and RTP
+ timestamps included in RTCP packets by data senders.
+
+ 3. The first two functions require that all participants send RTCP
+ packets, therefore the rate must be controlled in order for RTP to
+ scale up to a large number of participants. By having each
+ participant send its control packets to all the others, each can
+ independently observe the number of participants. This number is
+ used to calculate the rate at which the packets are sent, as
+ explained in Section 6.2.
+
+ 4. A fourth, OPTIONAL function is to convey minimal session control
+ information, for example participant identification to be
+ displayed in the user interface. This is most likely to be useful
+ in "loosely controlled" sessions where participants enter and
+ leave without membership control or parameter negotiation. RTCP
+ serves as a convenient channel to reach all the participants, but
+ it is not necessarily expected to support all the control
+ communication requirements of an application. A higher-level
+ session control protocol, which is beyond the scope of this
+ document, may be needed.
+
+ Functions 1-3 SHOULD be used in all environments, but particularly in
+ the IP multicast environment. RTP application designers SHOULD avoid
+ mechanisms that can only work in unicast mode and will not scale to
+ larger numbers. Transmission of RTCP MAY be controlled separately
+ for senders and receivers, as described in Section 6.2, for cases
+ such as unidirectional links where feedback from receivers is not
+ possible.
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 20]
+
+RFC 3550 RTP July 2003
+
+
+ Non-normative note: In the multicast routing approach
+ called Source-Specific Multicast (SSM), there is only one sender
+ per "channel" (a source address, group address pair), and
+ receivers (except for the channel source) cannot use multicast to
+ communicate directly with other channel members. The
+ recommendations here accommodate SSM only through Section 6.2's
+ option of turning off receivers' RTCP entirely. Future work will
+ specify adaptation of RTCP for SSM so that feedback from receivers
+ can be maintained.
+
+6.1 RTCP Packet Format
+
+ This specification defines several RTCP packet types to carry a
+ variety of control information:
+
+ SR: Sender report, for transmission and reception statistics from
+ participants that are active senders
+
+ RR: Receiver report, for reception statistics from participants
+ that are not active senders and in combination with SR for
+ active senders reporting on more than 31 sources
+
+ SDES: Source description items, including CNAME
+
+ BYE: Indicates end of participation
+
+ APP: Application-specific functions
+
+ Each RTCP packet begins with a fixed part similar to that of RTP data
+ packets, followed by structured elements that MAY be of variable
+ length according to the packet type but MUST end on a 32-bit
+ boundary. The alignment requirement and a length field in the fixed
+ part of each packet are included to make RTCP packets "stackable".
+ Multiple RTCP packets can be concatenated without any intervening
+ separators to form a compound RTCP packet that is sent in a single
+ packet of the lower layer protocol, for example UDP. There is no
+ explicit count of individual RTCP packets in the compound packet
+ since the lower layer protocols are expected to provide an overall
+ length to determine the end of the compound packet.
+
+ Each individual RTCP packet in the compound packet may be processed
+ independently with no requirements upon the order or combination of
+ packets. However, in order to perform the functions of the protocol,
+ the following constraints are imposed:
+
+
+
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 21]
+
+RFC 3550 RTP July 2003
+
+
+ o Reception statistics (in SR or RR) should be sent as often as
+ bandwidth constraints will allow to maximize the resolution of the
+ statistics, therefore each periodically transmitted compound RTCP
+ packet MUST include a report packet.
+
+ o New receivers need to receive the CNAME for a source as soon as
+ possible to identify the source and to begin associating media for
+ purposes such as lip-sync, so each compound RTCP packet MUST also
+ include the SDES CNAME except when the compound RTCP packet is
+ split for partial encryption as described in Section 9.1.
+
+ o The number of packet types that may appear first in the compound
+ packet needs to be limited to increase the number of constant bits
+ in the first word and the probability of successfully validating
+ RTCP packets against misaddressed RTP data packets or other
+ unrelated packets.
+
+ Thus, all RTCP packets MUST be sent in a compound packet of at least
+ two individual packets, with the following format:
+
+ Encryption prefix: If and only if the compound packet is to be
+ encrypted according to the method in Section 9.1, it MUST be
+ prefixed by a random 32-bit quantity redrawn for every compound
+ packet transmitted. If padding is required for the encryption, it
+ MUST be added to the last packet of the compound packet.
+
+ SR or RR: The first RTCP packet in the compound packet MUST
+ always be a report packet to facilitate header validation as
+ described in Appendix A.2. This is true even if no data has been
+ sent or received, in which case an empty RR MUST be sent, and even
+ if the only other RTCP packet in the compound packet is a BYE.
+
+ Additional RRs: If the number of sources for which reception
+ statistics are being reported exceeds 31, the number that will fit
+ into one SR or RR packet, then additional RR packets SHOULD follow
+ the initial report packet.
+
+ SDES: An SDES packet containing a CNAME item MUST be included
+ in each compound RTCP packet, except as noted in Section 9.1.
+ Other source description items MAY optionally be included if
+ required by a particular application, subject to bandwidth
+ constraints (see Section 6.3.9).
+
+ BYE or APP: Other RTCP packet types, including those yet to be
+ defined, MAY follow in any order, except that BYE SHOULD be the
+ last packet sent with a given SSRC/CSRC. Packet types MAY appear
+ more than once.
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 22]
+
+RFC 3550 RTP July 2003
+
+
+ An individual RTP participant SHOULD send only one compound RTCP
+ packet per report interval in order for the RTCP bandwidth per
+ participant to be estimated correctly (see Section 6.2), except when
+ the compound RTCP packet is split for partial encryption as described
+ in Section 9.1. If there are too many sources to fit all the
+ necessary RR packets into one compound RTCP packet without exceeding
+ the maximum transmission unit (MTU) of the network path, then only
+ the subset that will fit into one MTU SHOULD be included in each
+ interval. The subsets SHOULD be selected round-robin across multiple
+ intervals so that all sources are reported.
+
+ It is RECOMMENDED that translators and mixers combine individual RTCP
+ packets from the multiple sources they are forwarding into one
+ compound packet whenever feasible in order to amortize the packet
+ overhead (see Section 7). An example RTCP compound packet as might
+ be produced by a mixer is shown in Fig. 1. If the overall length of
+ a compound packet would exceed the MTU of the network path, it SHOULD
+ be segmented into multiple shorter compound packets to be transmitted
+ in separate packets of the underlying protocol. This does not impair
+ the RTCP bandwidth estimation because each compound packet represents
+ at least one distinct participant. Note that each of the compound
+ packets MUST begin with an SR or RR packet.
+
+ An implementation SHOULD ignore incoming RTCP packets with types
+ unknown to it. Additional RTCP packet types may be registered with
+ the Internet Assigned Numbers Authority (IANA) as described in
+ Section 15.
+
+ if encrypted: random 32-bit integer
+ |
+ |[--------- packet --------][---------- packet ----------][-packet-]
+ |
+ | receiver chunk chunk
+ V reports item item item item
+ --------------------------------------------------------------------
+ R[SR #sendinfo #site1#site2][SDES #CNAME PHONE #CNAME LOC][BYE##why]
+ --------------------------------------------------------------------
+ | |
+ |<----------------------- compound packet ----------------------->|
+ |<-------------------------- UDP packet ------------------------->|
+
+ #: SSRC/CSRC identifier
+
+ Figure 1: Example of an RTCP compound packet
+
+
+
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 23]
+
+RFC 3550 RTP July 2003
+
+
+6.2 RTCP Transmission Interval
+
+ RTP is designed to allow an application to scale automatically over
+ session sizes ranging from a few participants to thousands. For
+ example, in an audio conference the data traffic is inherently self-
+ limiting because only one or two people will speak at a time, so with
+ multicast distribution the data rate on any given link remains
+ relatively constant independent of the number of participants.
+ However, the control traffic is not self-limiting. If the reception
+ reports from each participant were sent at a constant rate, the
+ control traffic would grow linearly with the number of participants.
+ Therefore, the rate must be scaled down by dynamically calculating
+ the interval between RTCP packet transmissions.
+
+ For each session, it is assumed that the data traffic is subject to
+ an aggregate limit called the "session bandwidth" to be divided among
+ the participants. This bandwidth might be reserved and the limit
+ enforced by the network. If there is no reservation, there may be
+ other constraints, depending on the environment, that establish the
+ "reasonable" maximum for the session to use, and that would be the
+ session bandwidth. The session bandwidth may be chosen based on some
+ cost or a priori knowledge of the available network bandwidth for the
+ session. It is somewhat independent of the media encoding, but the
+ encoding choice may be limited by the session bandwidth. Often, the
+ session bandwidth is the sum of the nominal bandwidths of the senders
+ expected to be concurrently active. For teleconference audio, this
+ number would typically be one sender's bandwidth. For layered
+ encodings, each layer is a separate RTP session with its own session
+ bandwidth parameter.
+
+ The session bandwidth parameter is expected to be supplied by a
+ session management application when it invokes a media application,
+ but media applications MAY set a default based on the single-sender
+ data bandwidth for the encoding selected for the session. The
+ application MAY also enforce bandwidth limits based on multicast
+ scope rules or other criteria. All participants MUST use the same
+ value for the session bandwidth so that the same RTCP interval will
+ be calculated.
+
+ Bandwidth calculations for control and data traffic include lower-
+ layer transport and network protocols (e.g., UDP and IP) since that
+ is what the resource reservation system would need to know. The
+ application can also be expected to know which of these protocols are
+ in use. Link level headers are not included in the calculation since
+ the packet will be encapsulated with different link level headers as
+ it travels.
+
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 24]
+
+RFC 3550 RTP July 2003
+
+
+ The control traffic should be limited to a small and known fraction
+ of the session bandwidth: small so that the primary function of the
+ transport protocol to carry data is not impaired; known so that the
+ control traffic can be included in the bandwidth specification given
+ to a resource reservation protocol, and so that each participant can
+ independently calculate its share. The control traffic bandwidth is
+ in addition to the session bandwidth for the data traffic. It is
+ RECOMMENDED that the fraction of the session bandwidth added for RTCP
+ be fixed at 5%. It is also RECOMMENDED that 1/4 of the RTCP
+ bandwidth be dedicated to participants that are sending data so that
+ in sessions with a large number of receivers but a small number of
+ senders, newly joining participants will more quickly receive the
+ CNAME for the sending sites. When the proportion of senders is
+ greater than 1/4 of the participants, the senders get their
+ proportion of the full RTCP bandwidth. While the values of these and
+ other constants in the interval calculation are not critical, all
+ participants in the session MUST use the same values so the same
+ interval will be calculated. Therefore, these constants SHOULD be
+ fixed for a particular profile.
+
+ A profile MAY specify that the control traffic bandwidth may be a
+ separate parameter of the session rather than a strict percentage of
+ the session bandwidth. Using a separate parameter allows rate-
+ adaptive applications to set an RTCP bandwidth consistent with a
+ "typical" data bandwidth that is lower than the maximum bandwidth
+ specified by the session bandwidth parameter.
+
+ The profile MAY further specify that the control traffic bandwidth
+ may be divided into two separate session parameters for those
+ participants which are active data senders and those which are not;
+ let us call the parameters S and R. Following the recommendation
+ that 1/4 of the RTCP bandwidth be dedicated to data senders, the
+ RECOMMENDED default values for these two parameters would be 1.25%
+ and 3.75%, respectively. When the proportion of senders is greater
+ than S/(S+R) of the participants, the senders get their proportion of
+ the sum of these parameters. Using two parameters allows RTCP
+ reception reports to be turned off entirely for a particular session
+ by setting the RTCP bandwidth for non-data-senders to zero while
+ keeping the RTCP bandwidth for data senders non-zero so that sender
+ reports can still be sent for inter-media synchronization. Turning
+ off RTCP reception reports is NOT RECOMMENDED because they are needed
+ for the functions listed at the beginning of Section 6, particularly
+ reception quality feedback and congestion control. However, doing so
+ may be appropriate for systems operating on unidirectional links or
+ for sessions that don't require feedback on the quality of reception
+ or liveness of receivers and that have other means to avoid
+ congestion.
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 25]
+
+RFC 3550 RTP July 2003
+
+
+ The calculated interval between transmissions of compound RTCP
+ packets SHOULD also have a lower bound to avoid having bursts of
+ packets exceed the allowed bandwidth when the number of participants
+ is small and the traffic isn't smoothed according to the law of large
+ numbers. It also keeps the report interval from becoming too small
+ during transient outages like a network partition such that
+ adaptation is delayed when the partition heals. At application
+ startup, a delay SHOULD be imposed before the first compound RTCP
+ packet is sent to allow time for RTCP packets to be received from
+ other participants so the report interval will converge to the
+ correct value more quickly. This delay MAY be set to half the
+ minimum interval to allow quicker notification that the new
+ participant is present. The RECOMMENDED value for a fixed minimum
+ interval is 5 seconds.
+
+ An implementation MAY scale the minimum RTCP interval to a smaller
+ value inversely proportional to the session bandwidth parameter with
+ the following limitations:
+
+ o For multicast sessions, only active data senders MAY use the
+ reduced minimum value to calculate the interval for transmission
+ of compound RTCP packets.
+
+ o For unicast sessions, the reduced value MAY be used by
+ participants that are not active data senders as well, and the
+ delay before sending the initial compound RTCP packet MAY be zero.
+
+ o For all sessions, the fixed minimum SHOULD be used when
+ calculating the participant timeout interval (see Section 6.3.5)
+ so that implementations which do not use the reduced value for
+ transmitting RTCP packets are not timed out by other participants
+ prematurely.
+
+ o The RECOMMENDED value for the reduced minimum in seconds is 360
+ divided by the session bandwidth in kilobits/second. This minimum
+ is smaller than 5 seconds for bandwidths greater than 72 kb/s.
+
+ The algorithm described in Section 6.3 and Appendix A.7 was designed
+ to meet the goals outlined in this section. It calculates the
+ interval between sending compound RTCP packets to divide the allowed
+ control traffic bandwidth among the participants. This allows an
+ application to provide fast response for small sessions where, for
+ example, identification of all participants is important, yet
+ automatically adapt to large sessions. The algorithm incorporates
+ the following characteristics:
+
+
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 26]
+
+RFC 3550 RTP July 2003
+
+
+ o The calculated interval between RTCP packets scales linearly with
+ the number of members in the group. It is this linear factor
+ which allows for a constant amount of control traffic when summed
+ across all members.
+
+ o The interval between RTCP packets is varied randomly over the
+ range [0.5,1.5] times the calculated interval to avoid unintended
+ synchronization of all participants [20]. The first RTCP packet
+ sent after joining a session is also delayed by a random variation
+ of half the minimum RTCP interval.
+
+ o A dynamic estimate of the average compound RTCP packet size is
+ calculated, including all those packets received and sent, to
+ automatically adapt to changes in the amount of control
+ information carried.
+
+ o Since the calculated interval is dependent on the number of
+ observed group members, there may be undesirable startup effects
+ when a new user joins an existing session, or many users
+ simultaneously join a new session. These new users will initially
+ have incorrect estimates of the group membership, and thus their
+ RTCP transmission interval will be too short. This problem can be
+ significant if many users join the session simultaneously. To
+ deal with this, an algorithm called "timer reconsideration" is
+ employed. This algorithm implements a simple back-off mechanism
+ which causes users to hold back RTCP packet transmission if the
+ group sizes are increasing.
+
+ o When users leave a session, either with a BYE or by timeout, the
+ group membership decreases, and thus the calculated interval
+ should decrease. A "reverse reconsideration" algorithm is used to
+ allow members to more quickly reduce their intervals in response
+ to group membership decreases.
+
+ o BYE packets are given different treatment than other RTCP packets.
+ When a user leaves a group, and wishes to send a BYE packet, it
+ may do so before its next scheduled RTCP packet. However,
+ transmission of BYEs follows a back-off algorithm which avoids
+ floods of BYE packets should a large number of members
+ simultaneously leave the session.
+
+ This algorithm may be used for sessions in which all participants are
+ allowed to send. In that case, the session bandwidth parameter is
+ the product of the individual sender's bandwidth times the number of
+ participants, and the RTCP bandwidth is 5% of that.
+
+ Details of the algorithm's operation are given in the sections that
+ follow. Appendix A.7 gives an example implementation.
+
+
+
+Schulzrinne, et al. Standards Track [Page 27]
+
+RFC 3550 RTP July 2003
+
+
+6.2.1 Maintaining the Number of Session Members
+
+ Calculation of the RTCP packet interval depends upon an estimate of
+ the number of sites participating in the session. New sites are
+ added to the count when they are heard, and an entry for each SHOULD
+ be created in a table indexed by the SSRC or CSRC identifier (see
+ Section 8.2) to keep track of them. New entries MAY be considered
+ not valid until multiple packets carrying the new SSRC have been
+ received (see Appendix A.1), or until an SDES RTCP packet containing
+ a CNAME for that SSRC has been received. Entries MAY be deleted from
+ the table when an RTCP BYE packet with the corresponding SSRC
+ identifier is received, except that some straggler data packets might
+ arrive after the BYE and cause the entry to be recreated. Instead,
+ the entry SHOULD be marked as having received a BYE and then deleted
+ after an appropriate delay.
+
+ A participant MAY mark another site inactive, or delete it if not yet
+ valid, if no RTP or RTCP packet has been received for a small number
+ of RTCP report intervals (5 is RECOMMENDED). This provides some
+ robustness against packet loss. All sites must have the same value
+ for this multiplier and must calculate roughly the same value for the
+ RTCP report interval in order for this timeout to work properly.
+ Therefore, this multiplier SHOULD be fixed for a particular profile.
+
+ For sessions with a very large number of participants, it may be
+ impractical to maintain a table to store the SSRC identifier and
+ state information for all of them. An implementation MAY use SSRC
+ sampling, as described in [21], to reduce the storage requirements.
+ An implementation MAY use any other algorithm with similar
+ performance. A key requirement is that any algorithm considered
+ SHOULD NOT substantially underestimate the group size, although it
+ MAY overestimate.
+
+6.3 RTCP Packet Send and Receive Rules
+
+ The rules for how to send, and what to do when receiving an RTCP
+ packet are outlined here. An implementation that allows operation in
+ a multicast environment or a multipoint unicast environment MUST meet
+ the requirements in Section 6.2. Such an implementation MAY use the
+ algorithm defined in this section to meet those requirements, or MAY
+ use some other algorithm so long as it provides equivalent or better
+ performance. An implementation which is constrained to two-party
+ unicast operation SHOULD still use randomization of the RTCP
+ transmission interval to avoid unintended synchronization of multiple
+ instances operating in the same environment, but MAY omit the "timer
+ reconsideration" and "reverse reconsideration" algorithms in Sections
+ 6.3.3, 6.3.6 and 6.3.7.
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 28]
+
+RFC 3550 RTP July 2003
+
+
+ To execute these rules, a session participant must maintain several
+ pieces of state:
+
+ tp: the last time an RTCP packet was transmitted;
+
+ tc: the current time;
+
+ tn: the next scheduled transmission time of an RTCP packet;
+
+ pmembers: the estimated number of session members at the time tn
+ was last recomputed;
+
+ members: the most current estimate for the number of session
+ members;
+
+ senders: the most current estimate for the number of senders in
+ the session;
+
+ rtcp_bw: The target RTCP bandwidth, i.e., the total bandwidth
+ that will be used for RTCP packets by all members of this session,
+ in octets per second. This will be a specified fraction of the
+ "session bandwidth" parameter supplied to the application at
+ startup.
+
+ we_sent: Flag that is true if the application has sent data
+ since the 2nd previous RTCP report was transmitted.
+
+ avg_rtcp_size: The average compound RTCP packet size, in octets,
+ over all RTCP packets sent and received by this participant. The
+ size includes lower-layer transport and network protocol headers
+ (e.g., UDP and IP) as explained in Section 6.2.
+
+ initial: Flag that is true if the application has not yet sent
+ an RTCP packet.
+
+ Many of these rules make use of the "calculated interval" between
+ packet transmissions. This interval is described in the following
+ section.
+
+6.3.1 Computing the RTCP Transmission Interval
+
+ To maintain scalability, the average interval between packets from a
+ session participant should scale with the group size. This interval
+ is called the calculated interval. It is obtained by combining a
+ number of the pieces of state described above. The calculated
+ interval T is then determined as follows:
+
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 29]
+
+RFC 3550 RTP July 2003
+
+
+ 1. If the number of senders is less than or equal to 25% of the
+ membership (members), the interval depends on whether the
+ participant is a sender or not (based on the value of we_sent).
+ If the participant is a sender (we_sent true), the constant C is
+ set to the average RTCP packet size (avg_rtcp_size) divided by 25%
+ of the RTCP bandwidth (rtcp_bw), and the constant n is set to the
+ number of senders. If we_sent is not true, the constant C is set
+ to the average RTCP packet size divided by 75% of the RTCP
+ bandwidth. The constant n is set to the number of receivers
+ (members - senders). If the number of senders is greater than
+ 25%, senders and receivers are treated together. The constant C
+ is set to the average RTCP packet size divided by the total RTCP
+ bandwidth and n is set to the total number of members. As stated
+ in Section 6.2, an RTP profile MAY specify that the RTCP bandwidth
+ may be explicitly defined by two separate parameters (call them S
+ and R) for those participants which are senders and those which
+ are not. In that case, the 25% fraction becomes S/(S+R) and the
+ 75% fraction becomes R/(S+R). Note that if R is zero, the
+ percentage of senders is never greater than S/(S+R), and the
+ implementation must avoid division by zero.
+
+ 2. If the participant has not yet sent an RTCP packet (the variable
+ initial is true), the constant Tmin is set to 2.5 seconds, else it
+ is set to 5 seconds.
+
+ 3. The deterministic calculated interval Td is set to max(Tmin, n*C).
+
+ 4. The calculated interval T is set to a number uniformly distributed
+ between 0.5 and 1.5 times the deterministic calculated interval.
+
+ 5. The resulting value of T is divided by e-3/2=1.21828 to compensate
+ for the fact that the timer reconsideration algorithm converges to
+ a value of the RTCP bandwidth below the intended average.
+
+ This procedure results in an interval which is random, but which, on
+ average, gives at least 25% of the RTCP bandwidth to senders and the
+ rest to receivers. If the senders constitute more than one quarter
+ of the membership, this procedure splits the bandwidth equally among
+ all participants, on average.
+
+6.3.2 Initialization
+
+ Upon joining the session, the participant initializes tp to 0, tc to
+ 0, senders to 0, pmembers to 1, members to 1, we_sent to false,
+ rtcp_bw to the specified fraction of the session bandwidth, initial
+ to true, and avg_rtcp_size to the probable size of the first RTCP
+ packet that the application will later construct. The calculated
+ interval T is then computed, and the first packet is scheduled for
+
+
+
+Schulzrinne, et al. Standards Track [Page 30]
+
+RFC 3550 RTP July 2003
+
+
+ time tn = T. This means that a transmission timer is set which
+ expires at time T. Note that an application MAY use any desired
+ approach for implementing this timer.
+
+ The participant adds its own SSRC to the member table.
+
+6.3.3 Receiving an RTP or Non-BYE RTCP Packet
+
+ When an RTP or RTCP packet is received from a participant whose SSRC
+ is not in the member table, the SSRC is added to the table, and the
+ value for members is updated once the participant has been validated
+ as described in Section 6.2.1. The same processing occurs for each
+ CSRC in a validated RTP packet.
+
+ When an RTP packet is received from a participant whose SSRC is not
+ in the sender table, the SSRC is added to the table, and the value
+ for senders is updated.
+
+ For each compound RTCP packet received, the value of avg_rtcp_size is
+ updated:
+
+ avg_rtcp_size = (1/16) * packet_size + (15/16) * avg_rtcp_size
+
+ where packet_size is the size of the RTCP packet just received.
+
+6.3.4 Receiving an RTCP BYE Packet
+
+ Except as described in Section 6.3.7 for the case when an RTCP BYE is
+ to be transmitted, if the received packet is an RTCP BYE packet, the
+ SSRC is checked against the member table. If present, the entry is
+ removed from the table, and the value for members is updated. The
+ SSRC is then checked against the sender table. If present, the entry
+ is removed from the table, and the value for senders is updated.
+
+ Furthermore, to make the transmission rate of RTCP packets more
+ adaptive to changes in group membership, the following "reverse
+ reconsideration" algorithm SHOULD be executed when a BYE packet is
+ received that reduces members to a value less than pmembers:
+
+ o The value for tn is updated according to the following formula:
+
+ tn = tc + (members/pmembers) * (tn - tc)
+
+ o The value for tp is updated according the following formula:
+
+ tp = tc - (members/pmembers) * (tc - tp).
+
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 31]
+
+RFC 3550 RTP July 2003
+
+
+ o The next RTCP packet is rescheduled for transmission at time tn,
+ which is now earlier.
+
+ o The value of pmembers is set equal to members.
+
+ This algorithm does not prevent the group size estimate from
+ incorrectly dropping to zero for a short time due to premature
+ timeouts when most participants of a large session leave at once but
+ some remain. The algorithm does make the estimate return to the
+ correct value more rapidly. This situation is unusual enough and the
+ consequences are sufficiently harmless that this problem is deemed
+ only a secondary concern.
+
+6.3.5 Timing Out an SSRC
+
+ At occasional intervals, the participant MUST check to see if any of
+ the other participants time out. To do this, the participant
+ computes the deterministic (without the randomization factor)
+ calculated interval Td for a receiver, that is, with we_sent false.
+ Any other session member who has not sent an RTP or RTCP packet since
+ time tc - MTd (M is the timeout multiplier, and defaults to 5) is
+ timed out. This means that its SSRC is removed from the member list,
+ and members is updated. A similar check is performed on the sender
+ list. Any member on the sender list who has not sent an RTP packet
+ since time tc - 2T (within the last two RTCP report intervals) is
+ removed from the sender list, and senders is updated.
+
+ If any members time out, the reverse reconsideration algorithm
+ described in Section 6.3.4 SHOULD be performed.
+
+ The participant MUST perform this check at least once per RTCP
+ transmission interval.
+
+6.3.6 Expiration of Transmission Timer
+
+ When the packet transmission timer expires, the participant performs
+ the following operations:
+
+ o The transmission interval T is computed as described in Section
+ 6.3.1, including the randomization factor.
+
+ o If tp + T is less than or equal to tc, an RTCP packet is
+ transmitted. tp is set to tc, then another value for T is
+ calculated as in the previous step and tn is set to tc + T. The
+ transmission timer is set to expire again at time tn. If tp + T
+ is greater than tc, tn is set to tp + T. No RTCP packet is
+ transmitted. The transmission timer is set to expire at time tn.
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 32]
+
+RFC 3550 RTP July 2003
+
+
+ o pmembers is set to members.
+
+ If an RTCP packet is transmitted, the value of initial is set to
+ FALSE. Furthermore, the value of avg_rtcp_size is updated:
+
+ avg_rtcp_size = (1/16) * packet_size + (15/16) * avg_rtcp_size
+
+ where packet_size is the size of the RTCP packet just transmitted.
+
+6.3.7 Transmitting a BYE Packet
+
+ When a participant wishes to leave a session, a BYE packet is
+ transmitted to inform the other participants of the event. In order
+ to avoid a flood of BYE packets when many participants leave the
+ system, a participant MUST execute the following algorithm if the
+ number of members is more than 50 when the participant chooses to
+ leave. This algorithm usurps the normal role of the members variable
+ to count BYE packets instead:
+
+ o When the participant decides to leave the system, tp is reset to
+ tc, the current time, members and pmembers are initialized to 1,
+ initial is set to 1, we_sent is set to false, senders is set to 0,
+ and avg_rtcp_size is set to the size of the compound BYE packet.
+ The calculated interval T is computed. The BYE packet is then
+ scheduled for time tn = tc + T.
+
+ o Every time a BYE packet from another participant is received,
+ members is incremented by 1 regardless of whether that participant
+ exists in the member table or not, and when SSRC sampling is in
+ use, regardless of whether or not the BYE SSRC would be included
+ in the sample. members is NOT incremented when other RTCP packets
+ or RTP packets are received, but only for BYE packets. Similarly,
+ avg_rtcp_size is updated only for received BYE packets. senders
+ is NOT updated when RTP packets arrive; it remains 0.
+
+ o Transmission of the BYE packet then follows the rules for
+ transmitting a regular RTCP packet, as above.
+
+ This allows BYE packets to be sent right away, yet controls their
+ total bandwidth usage. In the worst case, this could cause RTCP
+ control packets to use twice the bandwidth as normal (10%) -- 5% for
+ non-BYE RTCP packets and 5% for BYE.
+
+ A participant that does not want to wait for the above mechanism to
+ allow transmission of a BYE packet MAY leave the group without
+ sending a BYE at all. That participant will eventually be timed out
+ by the other group members.
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 33]
+
+RFC 3550 RTP July 2003
+
+
+ If the group size estimate members is less than 50 when the
+ participant decides to leave, the participant MAY send a BYE packet
+ immediately. Alternatively, the participant MAY choose to execute
+ the above BYE backoff algorithm.
+
+ In either case, a participant which never sent an RTP or RTCP packet
+ MUST NOT send a BYE packet when they leave the group.
+
+6.3.8 Updating we_sent
+
+ The variable we_sent contains true if the participant has sent an RTP
+ packet recently, false otherwise. This determination is made by
+ using the same mechanisms as for managing the set of other
+ participants listed in the senders table. If the participant sends
+ an RTP packet when we_sent is false, it adds itself to the sender
+ table and sets we_sent to true. The reverse reconsideration
+ algorithm described in Section 6.3.4 SHOULD be performed to possibly
+ reduce the delay before sending an SR packet. Every time another RTP
+ packet is sent, the time of transmission of that packet is maintained
+ in the table. The normal sender timeout algorithm is then applied to
+ the participant -- if an RTP packet has not been transmitted since
+ time tc - 2T, the participant removes itself from the sender table,
+ decrements the sender count, and sets we_sent to false.
+
+6.3.9 Allocation of Source Description Bandwidth
+
+ This specification defines several source description (SDES) items in
+ addition to the mandatory CNAME item, such as NAME (personal name)
+ and EMAIL (email address). It also provides a means to define new
+ application-specific RTCP packet types. Applications should exercise
+ caution in allocating control bandwidth to this additional
+ information because it will slow down the rate at which reception
+ reports and CNAME are sent, thus impairing the performance of the
+ protocol. It is RECOMMENDED that no more than 20% of the RTCP
+ bandwidth allocated to a single participant be used to carry the
+ additional information. Furthermore, it is not intended that all
+ SDES items will be included in every application. Those that are
+ included SHOULD be assigned a fraction of the bandwidth according to
+ their utility. Rather than estimate these fractions dynamically, it
+ is recommended that the percentages be translated statically into
+ report interval counts based on the typical length of an item.
+
+ For example, an application may be designed to send only CNAME, NAME
+ and EMAIL and not any others. NAME might be given much higher
+ priority than EMAIL because the NAME would be displayed continuously
+ in the application's user interface, whereas EMAIL would be displayed
+ only when requested. At every RTCP interval, an RR packet and an
+ SDES packet with the CNAME item would be sent. For a small session
+
+
+
+Schulzrinne, et al. Standards Track [Page 34]
+
+RFC 3550 RTP July 2003
+
+
+ operating at the minimum interval, that would be every 5 seconds on
+ the average. Every third interval (15 seconds), one extra item would
+ be included in the SDES packet. Seven out of eight times this would
+ be the NAME item, and every eighth time (2 minutes) it would be the
+ EMAIL item.
+
+ When multiple applications operate in concert using cross-application
+ binding through a common CNAME for each participant, for example in a
+ multimedia conference composed of an RTP session for each medium, the
+ additional SDES information MAY be sent in only one RTP session. The
+ other sessions would carry only the CNAME item. In particular, this
+ approach should be applied to the multiple sessions of a layered
+ encoding scheme (see Section 2.4).
+
+6.4 Sender and Receiver Reports
+
+ RTP receivers provide reception quality feedback using RTCP report
+ packets which may take one of two forms depending upon whether or not
+ the receiver is also a sender. The only difference between the
+ sender report (SR) and receiver report (RR) forms, besides the packet
+ type code, is that the sender report includes a 20-byte sender
+ information section for use by active senders. The SR is issued if a
+ site has sent any data packets during the interval since issuing the
+ last report or the previous one, otherwise the RR is issued.
+
+ Both the SR and RR forms include zero or more reception report
+ blocks, one for each of the synchronization sources from which this
+ receiver has received RTP data packets since the last report.
+ Reports are not issued for contributing sources listed in the CSRC
+ list. Each reception report block provides statistics about the data
+ received from the particular source indicated in that block. Since a
+ maximum of 31 reception report blocks will fit in an SR or RR packet,
+ additional RR packets SHOULD be stacked after the initial SR or RR
+ packet as needed to contain the reception reports for all sources
+ heard during the interval since the last report. If there are too
+ many sources to fit all the necessary RR packets into one compound
+ RTCP packet without exceeding the MTU of the network path, then only
+ the subset that will fit into one MTU SHOULD be included in each
+ interval. The subsets SHOULD be selected round-robin across multiple
+ intervals so that all sources are reported.
+
+ The next sections define the formats of the two reports, how they may
+ be extended in a profile-specific manner if an application requires
+ additional feedback information, and how the reports may be used.
+ Details of reception reporting by translators and mixers is given in
+ Section 7.
+
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 35]
+
+RFC 3550 RTP July 2003
+
+
+6.4.1 SR: Sender Report RTCP Packet
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+header |V=2|P| RC | PT=SR=200 | length |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | SSRC of sender |
+ +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+sender | NTP timestamp, most significant word |
+info +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | NTP timestamp, least significant word |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | RTP timestamp |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | sender's packet count |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | sender's octet count |
+ +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+report | SSRC_1 (SSRC of first source) |
+block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ 1 | fraction lost | cumulative number of packets lost |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | extended highest sequence number received |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | interarrival jitter |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | last SR (LSR) |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | delay since last SR (DLSR) |
+ +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+report | SSRC_2 (SSRC of second source) |
+block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ 2 : ... :
+ +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+ | profile-specific extensions |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ The sender report packet consists of three sections, possibly
+ followed by a fourth profile-specific extension section if defined.
+ The first section, the header, is 8 octets long. The fields have the
+ following meaning:
+
+ version (V): 2 bits
+ Identifies the version of RTP, which is the same in RTCP packets
+ as in RTP data packets. The version defined by this specification
+ is two (2).
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 36]
+
+RFC 3550 RTP July 2003
+
+
+ padding (P): 1 bit
+ If the padding bit is set, this individual RTCP packet contains
+ some additional padding octets at the end which are not part of
+ the control information but are included in the length field. The
+ last octet of the padding is a count of how many padding octets
+ should be ignored, including itself (it will be a multiple of
+ four). Padding may be needed by some encryption algorithms with
+ fixed block sizes. In a compound RTCP packet, padding is only
+ required on one individual packet because the compound packet is
+ encrypted as a whole for the method in Section 9.1. Thus, padding
+ MUST only be added to the last individual packet, and if padding
+ is added to that packet, the padding bit MUST be set only on that
+ packet. This convention aids the header validity checks described
+ in Appendix A.2 and allows detection of packets from some early
+ implementations that incorrectly set the padding bit on the first
+ individual packet and add padding to the last individual packet.
+
+ reception report count (RC): 5 bits
+ The number of reception report blocks contained in this packet. A
+ value of zero is valid.
+
+ packet type (PT): 8 bits
+ Contains the constant 200 to identify this as an RTCP SR packet.
+
+ length: 16 bits
+ The length of this RTCP packet in 32-bit words minus one,
+ including the header and any padding. (The offset of one makes
+ zero a valid length and avoids a possible infinite loop in
+ scanning a compound RTCP packet, while counting 32-bit words
+ avoids a validity check for a multiple of 4.)
+
+ SSRC: 32 bits
+ The synchronization source identifier for the originator of this
+ SR packet.
+
+ The second section, the sender information, is 20 octets long and is
+ present in every sender report packet. It summarizes the data
+ transmissions from this sender. The fields have the following
+ meaning:
+
+ NTP timestamp: 64 bits
+ Indicates the wallclock time (see Section 4) when this report was
+ sent so that it may be used in combination with timestamps
+ returned in reception reports from other receivers to measure
+ round-trip propagation to those receivers. Receivers should
+ expect that the measurement accuracy of the timestamp may be
+ limited to far less than the resolution of the NTP timestamp. The
+ measurement uncertainty of the timestamp is not indicated as it
+
+
+
+Schulzrinne, et al. Standards Track [Page 37]
+
+RFC 3550 RTP July 2003
+
+
+ may not be known. On a system that has no notion of wallclock
+ time but does have some system-specific clock such as "system
+ uptime", a sender MAY use that clock as a reference to calculate
+ relative NTP timestamps. It is important to choose a commonly
+ used clock so that if separate implementations are used to produce
+ the individual streams of a multimedia session, all
+ implementations will use the same clock. Until the year 2036,
+ relative and absolute timestamps will differ in the high bit so
+ (invalid) comparisons will show a large difference; by then one
+ hopes relative timestamps will no longer be needed. A sender that
+ has no notion of wallclock or elapsed time MAY set the NTP
+ timestamp to zero.
+
+ RTP timestamp: 32 bits
+ Corresponds to the same time as the NTP timestamp (above), but in
+ the same units and with the same random offset as the RTP
+ timestamps in data packets. This correspondence may be used for
+ intra- and inter-media synchronization for sources whose NTP
+ timestamps are synchronized, and may be used by media-independent
+ receivers to estimate the nominal RTP clock frequency. Note that
+ in most cases this timestamp will not be equal to the RTP
+ timestamp in any adjacent data packet. Rather, it MUST be
+ calculated from the corresponding NTP timestamp using the
+ relationship between the RTP timestamp counter and real time as
+ maintained by periodically checking the wallclock time at a
+ sampling instant.
+
+ sender's packet count: 32 bits
+ The total number of RTP data packets transmitted by the sender
+ since starting transmission up until the time this SR packet was
+ generated. The count SHOULD be reset if the sender changes its
+ SSRC identifier.
+
+ sender's octet count: 32 bits
+ The total number of payload octets (i.e., not including header or
+ padding) transmitted in RTP data packets by the sender since
+ starting transmission up until the time this SR packet was
+ generated. The count SHOULD be reset if the sender changes its
+ SSRC identifier. This field can be used to estimate the average
+ payload data rate.
+
+ The third section contains zero or more reception report blocks
+ depending on the number of other sources heard by this sender since
+ the last report. Each reception report block conveys statistics on
+ the reception of RTP packets from a single synchronization source.
+ Receivers SHOULD NOT carry over statistics when a source changes its
+ SSRC identifier due to a collision. These statistics are:
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 38]
+
+RFC 3550 RTP July 2003
+
+
+ SSRC_n (source identifier): 32 bits
+ The SSRC identifier of the source to which the information in this
+ reception report block pertains.
+
+ fraction lost: 8 bits
+ The fraction of RTP data packets from source SSRC_n lost since the
+ previous SR or RR packet was sent, expressed as a fixed point
+ number with the binary point at the left edge of the field. (That
+ is equivalent to taking the integer part after multiplying the
+ loss fraction by 256.) This fraction is defined to be the number
+ of packets lost divided by the number of packets expected, as
+ defined in the next paragraph. An implementation is shown in
+ Appendix A.3. If the loss is negative due to duplicates, the
+ fraction lost is set to zero. Note that a receiver cannot tell
+ whether any packets were lost after the last one received, and
+ that there will be no reception report block issued for a source
+ if all packets from that source sent during the last reporting
+ interval have been lost.
+
+ cumulative number of packets lost: 24 bits
+ The total number of RTP data packets from source SSRC_n that have
+ been lost since the beginning of reception. This number is
+ defined to be the number of packets expected less the number of
+ packets actually received, where the number of packets received
+ includes any which are late or duplicates. Thus, packets that
+ arrive late are not counted as lost, and the loss may be negative
+ if there are duplicates. The number of packets expected is
+ defined to be the extended last sequence number received, as
+ defined next, less the initial sequence number received. This may
+ be calculated as shown in Appendix A.3.
+
+ extended highest sequence number received: 32 bits
+ The low 16 bits contain the highest sequence number received in an
+ RTP data packet from source SSRC_n, and the most significant 16
+ bits extend that sequence number with the corresponding count of
+ sequence number cycles, which may be maintained according to the
+ algorithm in Appendix A.1. Note that different receivers within
+ the same session will generate different extensions to the
+ sequence number if their start times differ significantly.
+
+ interarrival jitter: 32 bits
+ An estimate of the statistical variance of the RTP data packet
+ interarrival time, measured in timestamp units and expressed as an
+ unsigned integer. The interarrival jitter J is defined to be the
+ mean deviation (smoothed absolute value) of the difference D in
+ packet spacing at the receiver compared to the sender for a pair
+ of packets. As shown in the equation below, this is equivalent to
+ the difference in the "relative transit time" for the two packets;
+
+
+
+Schulzrinne, et al. Standards Track [Page 39]
+
+RFC 3550 RTP July 2003
+
+
+ the relative transit time is the difference between a packet's RTP
+ timestamp and the receiver's clock at the time of arrival,
+ measured in the same units.
+
+ If Si is the RTP timestamp from packet i, and Ri is the time of
+ arrival in RTP timestamp units for packet i, then for two packets
+ i and j, D may be expressed as
+
+ D(i,j) = (Rj - Ri) - (Sj - Si) = (Rj - Sj) - (Ri - Si)
+
+ The interarrival jitter SHOULD be calculated continuously as each
+ data packet i is received from source SSRC_n, using this
+ difference D for that packet and the previous packet i-1 in order
+ of arrival (not necessarily in sequence), according to the formula
+
+ J(i) = J(i-1) + (|D(i-1,i)| - J(i-1))/16
+
+ Whenever a reception report is issued, the current value of J is
+ sampled.
+
+ The jitter calculation MUST conform to the formula specified here
+ in order to allow profile-independent monitors to make valid
+ interpretations of reports coming from different implementations.
+ This algorithm is the optimal first-order estimator and the gain
+ parameter 1/16 gives a good noise reduction ratio while
+ maintaining a reasonable rate of convergence [22]. A sample
+ implementation is shown in Appendix A.8. See Section 6.4.4 for a
+ discussion of the effects of varying packet duration and delay
+ before transmission.
+
+ last SR timestamp (LSR): 32 bits
+ The middle 32 bits out of 64 in the NTP timestamp (as explained in
+ Section 4) received as part of the most recent RTCP sender report
+ (SR) packet from source SSRC_n. If no SR has been received yet,
+ the field is set to zero.
+
+ delay since last SR (DLSR): 32 bits
+ The delay, expressed in units of 1/65536 seconds, between
+ receiving the last SR packet from source SSRC_n and sending this
+ reception report block. If no SR packet has been received yet
+ from SSRC_n, the DLSR field is set to zero.
+
+ Let SSRC_r denote the receiver issuing this receiver report.
+ Source SSRC_n can compute the round-trip propagation delay to
+ SSRC_r by recording the time A when this reception report block is
+ received. It calculates the total round-trip time A-LSR using the
+ last SR timestamp (LSR) field, and then subtracting this field to
+ leave the round-trip propagation delay as (A - LSR - DLSR). This
+
+
+
+Schulzrinne, et al. Standards Track [Page 40]
+
+RFC 3550 RTP July 2003
+
+
+ is illustrated in Fig. 2. Times are shown in both a hexadecimal
+ representation of the 32-bit fields and the equivalent floating-
+ point decimal representation. Colons indicate a 32-bit field
+ divided into a 16-bit integer part and 16-bit fraction part.
+
+ This may be used as an approximate measure of distance to cluster
+ receivers, although some links have very asymmetric delays.
+
+ [10 Nov 1995 11:33:25.125 UTC] [10 Nov 1995 11:33:36.5 UTC]
+ n SR(n) A=b710:8000 (46864.500 s)
+ ---------------------------------------------------------------->
+ v ^
+ ntp_sec =0xb44db705 v ^ dlsr=0x0005:4000 ( 5.250s)
+ ntp_frac=0x20000000 v ^ lsr =0xb705:2000 (46853.125s)
+ (3024992005.125 s) v ^
+ r v ^ RR(n)
+ ---------------------------------------------------------------->
+ |<-DLSR->|
+ (5.250 s)
+
+ A 0xb710:8000 (46864.500 s)
+ DLSR -0x0005:4000 ( 5.250 s)
+ LSR -0xb705:2000 (46853.125 s)
+ -------------------------------
+ delay 0x0006:2000 ( 6.125 s)
+
+ Figure 2: Example for round-trip time computation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 41]
+
+RFC 3550 RTP July 2003
+
+
+6.4.2 RR: Receiver Report RTCP Packet
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+header |V=2|P| RC | PT=RR=201 | length |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | SSRC of packet sender |
+ +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+report | SSRC_1 (SSRC of first source) |
+block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ 1 | fraction lost | cumulative number of packets lost |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | extended highest sequence number received |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | interarrival jitter |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | last SR (LSR) |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | delay since last SR (DLSR) |
+ +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+report | SSRC_2 (SSRC of second source) |
+block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ 2 : ... :
+ +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+ | profile-specific extensions |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ The format of the receiver report (RR) packet is the same as that of
+ the SR packet except that the packet type field contains the constant
+ 201 and the five words of sender information are omitted (these are
+ the NTP and RTP timestamps and sender's packet and octet counts).
+ The remaining fields have the same meaning as for the SR packet.
+
+ An empty RR packet (RC = 0) MUST be put at the head of a compound
+ RTCP packet when there is no data transmission or reception to
+ report.
+
+6.4.3 Extending the Sender and Receiver Reports
+
+ A profile SHOULD define profile-specific extensions to the sender
+ report and receiver report if there is additional information that
+ needs to be reported regularly about the sender or receivers. This
+ method SHOULD be used in preference to defining another RTCP packet
+ type because it requires less overhead:
+
+ o fewer octets in the packet (no RTCP header or SSRC field);
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 42]
+
+RFC 3550 RTP July 2003
+
+
+ o simpler and faster parsing because applications running under that
+ profile would be programmed to always expect the extension fields
+ in the directly accessible location after the reception reports.
+
+ The extension is a fourth section in the sender- or receiver-report
+ packet which comes at the end after the reception report blocks, if
+ any. If additional sender information is required, then for sender
+ reports it would be included first in the extension section, but for
+ receiver reports it would not be present. If information about
+ receivers is to be included, that data SHOULD be structured as an
+ array of blocks parallel to the existing array of reception report
+ blocks; that is, the number of blocks would be indicated by the RC
+ field.
+
+6.4.4 Analyzing Sender and Receiver Reports
+
+ It is expected that reception quality feedback will be useful not
+ only for the sender but also for other receivers and third-party
+ monitors. The sender may modify its transmissions based on the
+ feedback; receivers can determine whether problems are local,
+ regional or global; network managers may use profile-independent
+ monitors that receive only the RTCP packets and not the corresponding
+ RTP data packets to evaluate the performance of their networks for
+ multicast distribution.
+
+ Cumulative counts are used in both the sender information and
+ receiver report blocks so that differences may be calculated between
+ any two reports to make measurements over both short and long time
+ periods, and to provide resilience against the loss of a report. The
+ difference between the last two reports received can be used to
+ estimate the recent quality of the distribution. The NTP timestamp
+ is included so that rates may be calculated from these differences
+ over the interval between two reports. Since that timestamp is
+ independent of the clock rate for the data encoding, it is possible
+ to implement encoding- and profile-independent quality monitors.
+
+ An example calculation is the packet loss rate over the interval
+ between two reception reports. The difference in the cumulative
+ number of packets lost gives the number lost during that interval.
+ The difference in the extended last sequence numbers received gives
+ the number of packets expected during the interval. The ratio of
+ these two is the packet loss fraction over the interval. This ratio
+ should equal the fraction lost field if the two reports are
+ consecutive, but otherwise it may not. The loss rate per second can
+ be obtained by dividing the loss fraction by the difference in NTP
+ timestamps, expressed in seconds. The number of packets received is
+ the number of packets expected minus the number lost. The number of
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 43]
+
+RFC 3550 RTP July 2003
+
+
+ packets expected may also be used to judge the statistical validity
+ of any loss estimates. For example, 1 out of 5 packets lost has a
+ lower significance than 200 out of 1000.
+
+ From the sender information, a third-party monitor can calculate the
+ average payload data rate and the average packet rate over an
+ interval without receiving the data. Taking the ratio of the two
+ gives the average payload size. If it can be assumed that packet
+ loss is independent of packet size, then the number of packets
+ received by a particular receiver times the average payload size (or
+ the corresponding packet size) gives the apparent throughput
+ available to that receiver.
+
+ In addition to the cumulative counts which allow long-term packet
+ loss measurements using differences between reports, the fraction
+ lost field provides a short-term measurement from a single report.
+ This becomes more important as the size of a session scales up enough
+ that reception state information might not be kept for all receivers
+ or the interval between reports becomes long enough that only one
+ report might have been received from a particular receiver.
+
+ The interarrival jitter field provides a second short-term measure of
+ network congestion. Packet loss tracks persistent congestion while
+ the jitter measure tracks transient congestion. The jitter measure
+ may indicate congestion before it leads to packet loss. The
+ interarrival jitter field is only a snapshot of the jitter at the
+ time of a report and is not intended to be taken quantitatively.
+ Rather, it is intended for comparison across a number of reports from
+ one receiver over time or from multiple receivers, e.g., within a
+ single network, at the same time. To allow comparison across
+ receivers, it is important the the jitter be calculated according to
+ the same formula by all receivers.
+
+ Because the jitter calculation is based on the RTP timestamp which
+ represents the instant when the first data in the packet was sampled,
+ any variation in the delay between that sampling instant and the time
+ the packet is transmitted will affect the resulting jitter that is
+ calculated. Such a variation in delay would occur for audio packets
+ of varying duration. It will also occur for video encodings because
+ the timestamp is the same for all the packets of one frame but those
+ packets are not all transmitted at the same time. The variation in
+ delay until transmission does reduce the accuracy of the jitter
+ calculation as a measure of the behavior of the network by itself,
+ but it is appropriate to include considering that the receiver buffer
+ must accommodate it. When the jitter calculation is used as a
+ comparative measure, the (constant) component due to variation in
+ delay until transmission subtracts out so that a change in the
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 44]
+
+RFC 3550 RTP July 2003
+
+
+ network jitter component can then be observed unless it is relatively
+ small. If the change is small, then it is likely to be
+ inconsequential.
+
+6.5 SDES: Source Description RTCP Packet
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+header |V=2|P| SC | PT=SDES=202 | length |
+ +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+chunk | SSRC/CSRC_1 |
+ 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | SDES items |
+ | ... |
+ +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+chunk | SSRC/CSRC_2 |
+ 2 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | SDES items |
+ | ... |
+ +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+
+ The SDES packet is a three-level structure composed of a header and
+ zero or more chunks, each of which is composed of items describing
+ the source identified in that chunk. The items are described
+ individually in subsequent sections.
+
+ version (V), padding (P), length:
+ As described for the SR packet (see Section 6.4.1).
+
+ packet type (PT): 8 bits
+ Contains the constant 202 to identify this as an RTCP SDES packet.
+
+ source count (SC): 5 bits
+ The number of SSRC/CSRC chunks contained in this SDES packet. A
+ value of zero is valid but useless.
+
+ Each chunk consists of an SSRC/CSRC identifier followed by a list of
+ zero or more items, which carry information about the SSRC/CSRC.
+ Each chunk starts on a 32-bit boundary. Each item consists of an 8-
+ bit type field, an 8-bit octet count describing the length of the
+ text (thus, not including this two-octet header), and the text
+ itself. Note that the text can be no longer than 255 octets, but
+ this is consistent with the need to limit RTCP bandwidth consumption.
+
+
+
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 45]
+
+RFC 3550 RTP July 2003
+
+
+ The text is encoded according to the UTF-8 encoding specified in RFC
+ 2279 [5]. US-ASCII is a subset of this encoding and requires no
+ additional encoding. The presence of multi-octet encodings is
+ indicated by setting the most significant bit of a character to a
+ value of one.
+
+ Items are contiguous, i.e., items are not individually padded to a
+ 32-bit boundary. Text is not null terminated because some multi-
+ octet encodings include null octets. The list of items in each chunk
+ MUST be terminated by one or more null octets, the first of which is
+ interpreted as an item type of zero to denote the end of the list.
+ No length octet follows the null item type octet, but additional null
+ octets MUST be included if needed to pad until the next 32-bit
+ boundary. Note that this padding is separate from that indicated by
+ the P bit in the RTCP header. A chunk with zero items (four null
+ octets) is valid but useless.
+
+ End systems send one SDES packet containing their own source
+ identifier (the same as the SSRC in the fixed RTP header). A mixer
+ sends one SDES packet containing a chunk for each contributing source
+ from which it is receiving SDES information, or multiple complete
+ SDES packets in the format above if there are more than 31 such
+ sources (see Section 7).
+
+ The SDES items currently defined are described in the next sections.
+ Only the CNAME item is mandatory. Some items shown here may be
+ useful only for particular profiles, but the item types are all
+ assigned from one common space to promote shared use and to simplify
+ profile-independent applications. Additional items may be defined in
+ a profile by registering the type numbers with IANA as described in
+ Section 15.
+
+6.5.1 CNAME: Canonical End-Point Identifier SDES Item
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | CNAME=1 | length | user and domain name ...
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ The CNAME identifier has the following properties:
+
+ o Because the randomly allocated SSRC identifier may change if a
+ conflict is discovered or if a program is restarted, the CNAME
+ item MUST be included to provide the binding from the SSRC
+ identifier to an identifier for the source (sender or receiver)
+ that remains constant.
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 46]
+
+RFC 3550 RTP July 2003
+
+
+ o Like the SSRC identifier, the CNAME identifier SHOULD also be
+ unique among all participants within one RTP session.
+
+ o To provide a binding across multiple media tools used by one
+ participant in a set of related RTP sessions, the CNAME SHOULD be
+ fixed for that participant.
+
+ o To facilitate third-party monitoring, the CNAME SHOULD be suitable
+ for either a program or a person to locate the source.
+
+ Therefore, the CNAME SHOULD be derived algorithmically and not
+ entered manually, when possible. To meet these requirements, the
+ following format SHOULD be used unless a profile specifies an
+ alternate syntax or semantics. The CNAME item SHOULD have the format
+ "user@host", or "host" if a user name is not available as on single-
+ user systems. For both formats, "host" is either the fully qualified
+ domain name of the host from which the real-time data originates,
+ formatted according to the rules specified in RFC 1034 [6], RFC 1035
+ [7] and Section 2.1 of RFC 1123 [8]; or the standard ASCII
+ representation of the host's numeric address on the interface used
+ for the RTP communication. For example, the standard ASCII
+ representation of an IP Version 4 address is "dotted decimal", also
+ known as dotted quad, and for IP Version 6, addresses are textually
+ represented as groups of hexadecimal digits separated by colons (with
+ variations as detailed in RFC 3513 [23]). Other address types are
+ expected to have ASCII representations that are mutually unique. The
+ fully qualified domain name is more convenient for a human observer
+ and may avoid the need to send a NAME item in addition, but it may be
+ difficult or impossible to obtain reliably in some operating
+ environments. Applications that may be run in such environments
+ SHOULD use the ASCII representation of the address instead.
+
+ Examples are "doe@sleepy.example.com", "doe@192.0.2.89" or
+ "doe@2201:056D::112E:144A:1E24" for a multi-user system. On a system
+ with no user name, examples would be "sleepy.example.com",
+ "192.0.2.89" or "2201:056D::112E:144A:1E24".
+
+ The user name SHOULD be in a form that a program such as "finger" or
+ "talk" could use, i.e., it typically is the login name rather than
+ the personal name. The host name is not necessarily identical to the
+ one in the participant's electronic mail address.
+
+ This syntax will not provide unique identifiers for each source if an
+ application permits a user to generate multiple sources from one
+ host. Such an application would have to rely on the SSRC to further
+ identify the source, or the profile for that application would have
+ to specify additional syntax for the CNAME identifier.
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 47]
+
+RFC 3550 RTP July 2003
+
+
+ If each application creates its CNAME independently, the resulting
+ CNAMEs may not be identical as would be required to provide a binding
+ across multiple media tools belonging to one participant in a set of
+ related RTP sessions. If cross-media binding is required, it may be
+ necessary for the CNAME of each tool to be externally configured with
+ the same value by a coordination tool.
+
+ Application writers should be aware that private network address
+ assignments such as the Net-10 assignment proposed in RFC 1918 [24]
+ may create network addresses that are not globally unique. This
+ would lead to non-unique CNAMEs if hosts with private addresses and
+ no direct IP connectivity to the public Internet have their RTP
+ packets forwarded to the public Internet through an RTP-level
+ translator. (See also RFC 1627 [25].) To handle this case,
+ applications MAY provide a means to configure a unique CNAME, but the
+ burden is on the translator to translate CNAMEs from private
+ addresses to public addresses if necessary to keep private addresses
+ from being exposed.
+
+6.5.2 NAME: User Name SDES Item
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | NAME=2 | length | common name of source ...
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ This is the real name used to describe the source, e.g., "John Doe,
+ Bit Recycler". It may be in any form desired by the user. For
+ applications such as conferencing, this form of name may be the most
+ desirable for display in participant lists, and therefore might be
+ sent most frequently of those items other than CNAME. Profiles MAY
+ establish such priorities. The NAME value is expected to remain
+ constant at least for the duration of a session. It SHOULD NOT be
+ relied upon to be unique among all participants in the session.
+
+6.5.3 EMAIL: Electronic Mail Address SDES Item
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | EMAIL=3 | length | email address of source ...
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ The email address is formatted according to RFC 2822 [9], for
+ example, "John.Doe@example.com". The EMAIL value is expected to
+ remain constant for the duration of a session.
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 48]
+
+RFC 3550 RTP July 2003
+
+
+6.5.4 PHONE: Phone Number SDES Item
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | PHONE=4 | length | phone number of source ...
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ The phone number SHOULD be formatted with the plus sign replacing the
+ international access code. For example, "+1 908 555 1212" for a
+ number in the United States.
+
+6.5.5 LOC: Geographic User Location SDES Item
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | LOC=5 | length | geographic location of site ...
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ Depending on the application, different degrees of detail are
+ appropriate for this item. For conference applications, a string
+ like "Murray Hill, New Jersey" may be sufficient, while, for an
+ active badge system, strings like "Room 2A244, AT&T BL MH" might be
+ appropriate. The degree of detail is left to the implementation
+ and/or user, but format and content MAY be prescribed by a profile.
+ The LOC value is expected to remain constant for the duration of a
+ session, except for mobile hosts.
+
+6.5.6 TOOL: Application or Tool Name SDES Item
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | TOOL=6 | length |name/version of source appl. ...
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ A string giving the name and possibly version of the application
+ generating the stream, e.g., "videotool 1.2". This information may
+ be useful for debugging purposes and is similar to the Mailer or
+ Mail-System-Version SMTP headers. The TOOL value is expected to
+ remain constant for the duration of the session.
+
+
+
+
+
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 49]
+
+RFC 3550 RTP July 2003
+
+
+6.5.7 NOTE: Notice/Status SDES Item
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | NOTE=7 | length | note about the source ...
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ The following semantics are suggested for this item, but these or
+ other semantics MAY be explicitly defined by a profile. The NOTE
+ item is intended for transient messages describing the current state
+ of the source, e.g., "on the phone, can't talk". Or, during a
+ seminar, this item might be used to convey the title of the talk. It
+ should be used only to carry exceptional information and SHOULD NOT
+ be included routinely by all participants because this would slow
+ down the rate at which reception reports and CNAME are sent, thus
+ impairing the performance of the protocol. In particular, it SHOULD
+ NOT be included as an item in a user's configuration file nor
+ automatically generated as in a quote-of-the-day.
+
+ Since the NOTE item may be important to display while it is active,
+ the rate at which other non-CNAME items such as NAME are transmitted
+ might be reduced so that the NOTE item can take that part of the RTCP
+ bandwidth. When the transient message becomes inactive, the NOTE
+ item SHOULD continue to be transmitted a few times at the same
+ repetition rate but with a string of length zero to signal the
+ receivers. However, receivers SHOULD also consider the NOTE item
+ inactive if it is not received for a small multiple of the repetition
+ rate, or perhaps 20-30 RTCP intervals.
+
+6.5.8 PRIV: Private Extensions SDES Item
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | PRIV=8 | length | prefix length |prefix string...
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ ... | value string ...
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ This item is used to define experimental or application-specific SDES
+ extensions. The item contains a prefix consisting of a length-string
+ pair, followed by the value string filling the remainder of the item
+ and carrying the desired information. The prefix length field is 8
+ bits long. The prefix string is a name chosen by the person defining
+ the PRIV item to be unique with respect to other PRIV items this
+ application might receive. The application creator might choose to
+ use the application name plus an additional subtype identification if
+
+
+
+Schulzrinne, et al. Standards Track [Page 50]
+
+RFC 3550 RTP July 2003
+
+
+ needed. Alternatively, it is RECOMMENDED that others choose a name
+ based on the entity they represent, then coordinate the use of the
+ name within that entity.
+
+ Note that the prefix consumes some space within the item's total
+ length of 255 octets, so the prefix should be kept as short as
+ possible. This facility and the constrained RTCP bandwidth SHOULD
+ NOT be overloaded; it is not intended to satisfy all the control
+ communication requirements of all applications.
+
+ SDES PRIV prefixes will not be registered by IANA. If some form of
+ the PRIV item proves to be of general utility, it SHOULD instead be
+ assigned a regular SDES item type registered with IANA so that no
+ prefix is required. This simplifies use and increases transmission
+ efficiency.
+
+6.6 BYE: Goodbye RTCP Packet
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ |V=2|P| SC | PT=BYE=203 | length |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | SSRC/CSRC |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ : ... :
+ +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+(opt) | length | reason for leaving ...
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ The BYE packet indicates that one or more sources are no longer
+ active.
+
+ version (V), padding (P), length:
+ As described for the SR packet (see Section 6.4.1).
+
+ packet type (PT): 8 bits
+ Contains the constant 203 to identify this as an RTCP BYE packet.
+
+ source count (SC): 5 bits
+ The number of SSRC/CSRC identifiers included in this BYE packet.
+ A count value of zero is valid, but useless.
+
+ The rules for when a BYE packet should be sent are specified in
+ Sections 6.3.7 and 8.2.
+
+
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 51]
+
+RFC 3550 RTP July 2003
+
+
+ If a BYE packet is received by a mixer, the mixer SHOULD forward the
+ BYE packet with the SSRC/CSRC identifier(s) unchanged. If a mixer
+ shuts down, it SHOULD send a BYE packet listing all contributing
+ sources it handles, as well as its own SSRC identifier. Optionally,
+ the BYE packet MAY include an 8-bit octet count followed by that many
+ octets of text indicating the reason for leaving, e.g., "camera
+ malfunction" or "RTP loop detected". The string has the same
+ encoding as that described for SDES. If the string fills the packet
+ to the next 32-bit boundary, the string is not null terminated. If
+ not, the BYE packet MUST be padded with null octets to the next 32-
+ bit boundary. This padding is separate from that indicated by the P
+ bit in the RTCP header.
+
+6.7 APP: Application-Defined RTCP Packet
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ |V=2|P| subtype | PT=APP=204 | length |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | SSRC/CSRC |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | name (ASCII) |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | application-dependent data ...
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ The APP packet is intended for experimental use as new applications
+ and new features are developed, without requiring packet type value
+ registration. APP packets with unrecognized names SHOULD be ignored.
+ After testing and if wider use is justified, it is RECOMMENDED that
+ each APP packet be redefined without the subtype and name fields and
+ registered with IANA using an RTCP packet type.
+
+ version (V), padding (P), length:
+ As described for the SR packet (see Section 6.4.1).
+
+ subtype: 5 bits
+ May be used as a subtype to allow a set of APP packets to be
+ defined under one unique name, or for any application-dependent
+ data.
+
+ packet type (PT): 8 bits
+ Contains the constant 204 to identify this as an RTCP APP packet.
+
+
+
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 52]
+
+RFC 3550 RTP July 2003
+
+
+ name: 4 octets
+ A name chosen by the person defining the set of APP packets to be
+ unique with respect to other APP packets this application might
+ receive. The application creator might choose to use the
+ application name, and then coordinate the allocation of subtype
+ values to others who want to define new packet types for the
+ application. Alternatively, it is RECOMMENDED that others choose
+ a name based on the entity they represent, then coordinate the use
+ of the name within that entity. The name is interpreted as a
+ sequence of four ASCII characters, with uppercase and lowercase
+ characters treated as distinct.
+
+ application-dependent data: variable length
+ Application-dependent data may or may not appear in an APP packet.
+ It is interpreted by the application and not RTP itself. It MUST
+ be a multiple of 32 bits long.
+
+7. RTP Translators and Mixers
+
+ In addition to end systems, RTP supports the notion of "translators"
+ and "mixers", which could be considered as "intermediate systems" at
+ the RTP level. Although this support adds some complexity to the
+ protocol, the need for these functions has been clearly established
+ by experiments with multicast audio and video applications in the
+ Internet. Example uses of translators and mixers given in Section
+ 2.3 stem from the presence of firewalls and low bandwidth
+ connections, both of which are likely to remain.
+
+7.1 General Description
+
+ An RTP translator/mixer connects two or more transport-level
+ "clouds". Typically, each cloud is defined by a common network and
+ transport protocol (e.g., IP/UDP) plus a multicast address and
+ transport level destination port or a pair of unicast addresses and
+ ports. (Network-level protocol translators, such as IP version 4 to
+ IP version 6, may be present within a cloud invisibly to RTP.) One
+ system may serve as a translator or mixer for a number of RTP
+ sessions, but each is considered a logically separate entity.
+
+ In order to avoid creating a loop when a translator or mixer is
+ installed, the following rules MUST be observed:
+
+ o Each of the clouds connected by translators and mixers
+ participating in one RTP session either MUST be distinct from all
+ the others in at least one of these parameters (protocol, address,
+ port), or MUST be isolated at the network level from the others.
+
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 53]
+
+RFC 3550 RTP July 2003
+
+
+ o A derivative of the first rule is that there MUST NOT be multiple
+ translators or mixers connected in parallel unless by some
+ arrangement they partition the set of sources to be forwarded.
+
+ Similarly, all RTP end systems that can communicate through one or
+ more RTP translators or mixers share the same SSRC space, that is,
+ the SSRC identifiers MUST be unique among all these end systems.
+ Section 8.2 describes the collision resolution algorithm by which
+ SSRC identifiers are kept unique and loops are detected.
+
+ There may be many varieties of translators and mixers designed for
+ different purposes and applications. Some examples are to add or
+ remove encryption, change the encoding of the data or the underlying
+ protocols, or replicate between a multicast address and one or more
+ unicast addresses. The distinction between translators and mixers is
+ that a translator passes through the data streams from different
+ sources separately, whereas a mixer combines them to form one new
+ stream:
+
+ Translator: Forwards RTP packets with their SSRC identifier
+ intact; this makes it possible for receivers to identify
+ individual sources even though packets from all the sources pass
+ through the same translator and carry the translator's network
+ source address. Some kinds of translators will pass through the
+ data untouched, but others MAY change the encoding of the data and
+ thus the RTP data payload type and timestamp. If multiple data
+ packets are re-encoded into one, or vice versa, a translator MUST
+ assign new sequence numbers to the outgoing packets. Losses in
+ the incoming packet stream may induce corresponding gaps in the
+ outgoing sequence numbers. Receivers cannot detect the presence
+ of a translator unless they know by some other means what payload
+ type or transport address was used by the original source.
+
+ Mixer: Receives streams of RTP data packets from one or more
+ sources, possibly changes the data format, combines the streams in
+ some manner and then forwards the combined stream. Since the
+ timing among multiple input sources will not generally be
+ synchronized, the mixer will make timing adjustments among the
+ streams and generate its own timing for the combined stream, so it
+ is the synchronization source. Thus, all data packets forwarded
+ by a mixer MUST be marked with the mixer's own SSRC identifier.
+ In order to preserve the identity of the original sources
+ contributing to the mixed packet, the mixer SHOULD insert their
+ SSRC identifiers into the CSRC identifier list following the fixed
+ RTP header of the packet. A mixer that is also itself a
+ contributing source for some packet SHOULD explicitly include its
+ own SSRC identifier in the CSRC list for that packet.
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 54]
+
+RFC 3550 RTP July 2003
+
+
+ For some applications, it MAY be acceptable for a mixer not to
+ identify sources in the CSRC list. However, this introduces the
+ danger that loops involving those sources could not be detected.
+
+ The advantage of a mixer over a translator for applications like
+ audio is that the output bandwidth is limited to that of one source
+ even when multiple sources are active on the input side. This may be
+ important for low-bandwidth links. The disadvantage is that
+ receivers on the output side don't have any control over which
+ sources are passed through or muted, unless some mechanism is
+ implemented for remote control of the mixer. The regeneration of
+ synchronization information by mixers also means that receivers can't
+ do inter-media synchronization of the original streams. A multi-
+ media mixer could do it.
+
+ [E1] [E6]
+ | |
+ E1:17 | E6:15 |
+ | | E6:15
+ V M1:48 (1,17) M1:48 (1,17) V M1:48 (1,17)
+ (M1)-------------><T1>-----------------><T2>-------------->[E7]
+ ^ ^ E4:47 ^ E4:47
+ E2:1 | E4:47 | | M3:89 (64,45)
+ | | |
+ [E2] [E4] M3:89 (64,45) |
+ | legend:
+ [E3] --------->(M2)----------->(M3)------------| [End system]
+ E3:64 M2:12 (64) ^ (Mixer)
+ | E5:45 <Translator>
+ |
+ [E5] source: SSRC (CSRCs)
+ ------------------->
+
+ Figure 3: Sample RTP network with end systems, mixers and translators
+
+ A collection of mixers and translators is shown in Fig. 3 to
+ illustrate their effect on SSRC and CSRC identifiers. In the figure,
+ end systems are shown as rectangles (named E), translators as
+ triangles (named T) and mixers as ovals (named M). The notation "M1:
+ 48(1,17)" designates a packet originating a mixer M1, identified by
+ M1's (random) SSRC value of 48 and two CSRC identifiers, 1 and 17,
+ copied from the SSRC identifiers of packets from E1 and E2.
+
+7.2 RTCP Processing in Translators
+
+ In addition to forwarding data packets, perhaps modified, translators
+ and mixers MUST also process RTCP packets. In many cases, they will
+ take apart the compound RTCP packets received from end systems to
+
+
+
+Schulzrinne, et al. Standards Track [Page 55]
+
+RFC 3550 RTP July 2003
+
+
+ aggregate SDES information and to modify the SR or RR packets.
+ Retransmission of this information may be triggered by the packet
+ arrival or by the RTCP interval timer of the translator or mixer
+ itself.
+
+ A translator that does not modify the data packets, for example one
+ that just replicates between a multicast address and a unicast
+ address, MAY simply forward RTCP packets unmodified as well. A
+ translator that transforms the payload in some way MUST make
+ corresponding transformations in the SR and RR information so that it
+ still reflects the characteristics of the data and the reception
+ quality. These translators MUST NOT simply forward RTCP packets. In
+ general, a translator SHOULD NOT aggregate SR and RR packets from
+ different sources into one packet since that would reduce the
+ accuracy of the propagation delay measurements based on the LSR and
+ DLSR fields.
+
+ SR sender information: A translator does not generate its own
+ sender information, but forwards the SR packets received from one
+ cloud to the others. The SSRC is left intact but the sender
+ information MUST be modified if required by the translation. If a
+ translator changes the data encoding, it MUST change the "sender's
+ byte count" field. If it also combines several data packets into
+ one output packet, it MUST change the "sender's packet count"
+ field. If it changes the timestamp frequency, it MUST change the
+ "RTP timestamp" field in the SR packet.
+
+ SR/RR reception report blocks: A translator forwards reception
+ reports received from one cloud to the others. Note that these
+ flow in the direction opposite to the data. The SSRC is left
+ intact. If a translator combines several data packets into one
+ output packet, and therefore changes the sequence numbers, it MUST
+ make the inverse manipulation for the packet loss fields and the
+ "extended last sequence number" field. This may be complex. In
+ the extreme case, there may be no meaningful way to translate the
+ reception reports, so the translator MAY pass on no reception
+ report at all or a synthetic report based on its own reception.
+ The general rule is to do what makes sense for a particular
+ translation.
+
+ A translator does not require an SSRC identifier of its own, but
+ MAY choose to allocate one for the purpose of sending reports
+ about what it has received. These would be sent to all the
+ connected clouds, each corresponding to the translation of the
+ data stream as sent to that cloud, since reception reports are
+ normally multicast to all participants.
+
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 56]
+
+RFC 3550 RTP July 2003
+
+
+ SDES: Translators typically forward without change the SDES
+ information they receive from one cloud to the others, but MAY,
+ for example, decide to filter non-CNAME SDES information if
+ bandwidth is limited. The CNAMEs MUST be forwarded to allow SSRC
+ identifier collision detection to work. A translator that
+ generates its own RR packets MUST send SDES CNAME information
+ about itself to the same clouds that it sends those RR packets.
+
+ BYE: Translators forward BYE packets unchanged. A translator
+ that is about to cease forwarding packets SHOULD send a BYE packet
+ to each connected cloud containing all the SSRC identifiers that
+ were previously being forwarded to that cloud, including the
+ translator's own SSRC identifier if it sent reports of its own.
+
+ APP: Translators forward APP packets unchanged.
+
+7.3 RTCP Processing in Mixers
+
+ Since a mixer generates a new data stream of its own, it does not
+ pass through SR or RR packets at all and instead generates new
+ information for both sides.
+
+ SR sender information: A mixer does not pass through sender
+ information from the sources it mixes because the characteristics
+ of the source streams are lost in the mix. As a synchronization
+ source, the mixer SHOULD generate its own SR packets with sender
+ information about the mixed data stream and send them in the same
+ direction as the mixed stream.
+
+ SR/RR reception report blocks: A mixer generates its own
+ reception reports for sources in each cloud and sends them out
+ only to the same cloud. It MUST NOT send these reception reports
+ to the other clouds and MUST NOT forward reception reports from
+ one cloud to the others because the sources would not be SSRCs
+ there (only CSRCs).
+
+ SDES: Mixers typically forward without change the SDES
+ information they receive from one cloud to the others, but MAY,
+ for example, decide to filter non-CNAME SDES information if
+ bandwidth is limited. The CNAMEs MUST be forwarded to allow SSRC
+ identifier collision detection to work. (An identifier in a CSRC
+ list generated by a mixer might collide with an SSRC identifier
+ generated by an end system.) A mixer MUST send SDES CNAME
+ information about itself to the same clouds that it sends SR or RR
+ packets.
+
+
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 57]
+
+RFC 3550 RTP July 2003
+
+
+ Since mixers do not forward SR or RR packets, they will typically
+ be extracting SDES packets from a compound RTCP packet. To
+ minimize overhead, chunks from the SDES packets MAY be aggregated
+ into a single SDES packet which is then stacked on an SR or RR
+ packet originating from the mixer. A mixer which aggregates SDES
+ packets will use more RTCP bandwidth than an individual source
+ because the compound packets will be longer, but that is
+ appropriate since the mixer represents multiple sources.
+ Similarly, a mixer which passes through SDES packets as they are
+ received will be transmitting RTCP packets at higher than the
+ single source rate, but again that is correct since the packets
+ come from multiple sources. The RTCP packet rate may be different
+ on each side of the mixer.
+
+ A mixer that does not insert CSRC identifiers MAY also refrain
+ from forwarding SDES CNAMEs. In this case, the SSRC identifier
+ spaces in the two clouds are independent. As mentioned earlier,
+ this mode of operation creates a danger that loops can't be
+ detected.
+
+ BYE: Mixers MUST forward BYE packets. A mixer that is about to
+ cease forwarding packets SHOULD send a BYE packet to each
+ connected cloud containing all the SSRC identifiers that were
+ previously being forwarded to that cloud, including the mixer's
+ own SSRC identifier if it sent reports of its own.
+
+ APP: The treatment of APP packets by mixers is application-specific.
+
+7.4 Cascaded Mixers
+
+ An RTP session may involve a collection of mixers and translators as
+ shown in Fig. 3. If two mixers are cascaded, such as M2 and M3 in
+ the figure, packets received by a mixer may already have been mixed
+ and may include a CSRC list with multiple identifiers. The second
+ mixer SHOULD build the CSRC list for the outgoing packet using the
+ CSRC identifiers from already-mixed input packets and the SSRC
+ identifiers from unmixed input packets. This is shown in the output
+ arc from mixer M3 labeled M3:89(64,45) in the figure. As in the case
+ of mixers that are not cascaded, if the resulting CSRC list has more
+ than 15 identifiers, the remainder cannot be included.
+
+
+
+
+
+
+
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 58]
+
+RFC 3550 RTP July 2003
+
+
+8. SSRC Identifier Allocation and Use
+
+ The SSRC identifier carried in the RTP header and in various fields
+ of RTCP packets is a random 32-bit number that is required to be
+ globally unique within an RTP session. It is crucial that the number
+ be chosen with care in order that participants on the same network or
+ starting at the same time are not likely to choose the same number.
+
+ It is not sufficient to use the local network address (such as an
+ IPv4 address) for the identifier because the address may not be
+ unique. Since RTP translators and mixers enable interoperation among
+ multiple networks with different address spaces, the allocation
+ patterns for addresses within two spaces might result in a much
+ higher rate of collision than would occur with random allocation.
+
+ Multiple sources running on one host would also conflict.
+
+ It is also not sufficient to obtain an SSRC identifier simply by
+ calling random() without carefully initializing the state. An
+ example of how to generate a random identifier is presented in
+ Appendix A.6.
+
+8.1 Probability of Collision
+
+ Since the identifiers are chosen randomly, it is possible that two or
+ more sources will choose the same number. Collision occurs with the
+ highest probability when all sources are started simultaneously, for
+ example when triggered automatically by some session management
+ event. If N is the number of sources and L the length of the
+ identifier (here, 32 bits), the probability that two sources
+ independently pick the same value can be approximated for large N
+ [26] as 1 - exp(-N**2 / 2**(L+1)). For N=1000, the probability is
+ roughly 10**-4.
+
+ The typical collision probability is much lower than the worst-case
+ above. When one new source joins an RTP session in which all the
+ other sources already have unique identifiers, the probability of
+ collision is just the fraction of numbers used out of the space.
+ Again, if N is the number of sources and L the length of the
+ identifier, the probability of collision is N / 2**L. For N=1000,
+ the probability is roughly 2*10**-7.
+
+ The probability of collision is further reduced by the opportunity
+ for a new source to receive packets from other participants before
+ sending its first packet (either data or control). If the new source
+ keeps track of the other participants (by SSRC identifier), then
+
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 59]
+
+RFC 3550 RTP July 2003
+
+
+ before transmitting its first packet the new source can verify that
+ its identifier does not conflict with any that have been received, or
+ else choose again.
+
+8.2 Collision Resolution and Loop Detection
+
+ Although the probability of SSRC identifier collision is low, all RTP
+ implementations MUST be prepared to detect collisions and take the
+ appropriate actions to resolve them. If a source discovers at any
+ time that another source is using the same SSRC identifier as its
+ own, it MUST send an RTCP BYE packet for the old identifier and
+ choose another random one. (As explained below, this step is taken
+ only once in case of a loop.) If a receiver discovers that two other
+ sources are colliding, it MAY keep the packets from one and discard
+ the packets from the other when this can be detected by different
+ source transport addresses or CNAMEs. The two sources are expected
+ to resolve the collision so that the situation doesn't last.
+
+ Because the random SSRC identifiers are kept globally unique for each
+ RTP session, they can also be used to detect loops that may be
+ introduced by mixers or translators. A loop causes duplication of
+ data and control information, either unmodified or possibly mixed, as
+ in the following examples:
+
+ o A translator may incorrectly forward a packet to the same
+ multicast group from which it has received the packet, either
+ directly or through a chain of translators. In that case, the
+ same packet appears several times, originating from different
+ network sources.
+
+ o Two translators incorrectly set up in parallel, i.e., with the
+ same multicast groups on both sides, would both forward packets
+ from one multicast group to the other. Unidirectional translators
+ would produce two copies; bidirectional translators would form a
+ loop.
+
+ o A mixer can close a loop by sending to the same transport
+ destination upon which it receives packets, either directly or
+ through another mixer or translator. In this case a source might
+ show up both as an SSRC on a data packet and a CSRC in a mixed
+ data packet.
+
+ A source may discover that its own packets are being looped, or that
+ packets from another source are being looped (a third-party loop).
+ Both loops and collisions in the random selection of a source
+ identifier result in packets arriving with the same SSRC identifier
+ but a different source transport address, which may be that of the
+ end system originating the packet or an intermediate system.
+
+
+
+Schulzrinne, et al. Standards Track [Page 60]
+
+RFC 3550 RTP July 2003
+
+
+ Therefore, if a source changes its source transport address, it MAY
+ also choose a new SSRC identifier to avoid being interpreted as a
+ looped source. (This is not MUST because in some applications of RTP
+ sources may be expected to change addresses during a session.) Note
+ that if a translator restarts and consequently changes the source
+ transport address (e.g., changes the UDP source port number) on which
+ it forwards packets, then all those packets will appear to receivers
+ to be looped because the SSRC identifiers are applied by the original
+ source and will not change. This problem can be avoided by keeping
+ the source transport address fixed across restarts, but in any case
+ will be resolved after a timeout at the receivers.
+
+ Loops or collisions occurring on the far side of a translator or
+ mixer cannot be detected using the source transport address if all
+ copies of the packets go through the translator or mixer, however,
+ collisions may still be detected when chunks from two RTCP SDES
+ packets contain the same SSRC identifier but different CNAMEs.
+
+ To detect and resolve these conflicts, an RTP implementation MUST
+ include an algorithm similar to the one described below, though the
+ implementation MAY choose a different policy for which packets from
+ colliding third-party sources are kept. The algorithm described
+ below ignores packets from a new source or loop that collide with an
+ established source. It resolves collisions with the participant's
+ own SSRC identifier by sending an RTCP BYE for the old identifier and
+ choosing a new one. However, when the collision was induced by a
+ loop of the participant's own packets, the algorithm will choose a
+ new identifier only once and thereafter ignore packets from the
+ looping source transport address. This is required to avoid a flood
+ of BYE packets.
+
+ This algorithm requires keeping a table indexed by the source
+ identifier and containing the source transport addresses from the
+ first RTP packet and first RTCP packet received with that identifier,
+ along with other state for that source. Two source transport
+ addresses are required since, for example, the UDP source port
+ numbers may be different on RTP and RTCP packets. However, it may be
+ assumed that the network address is the same in both source transport
+ addresses.
+
+ Each SSRC or CSRC identifier received in an RTP or RTCP packet is
+ looked up in the source identifier table in order to process that
+ data or control information. The source transport address from the
+ packet is compared to the corresponding source transport address in
+ the table to detect a loop or collision if they don't match. For
+ control packets, each element with its own SSRC identifier, for
+ example an SDES chunk, requires a separate lookup. (The SSRC
+ identifier in a reception report block is an exception because it
+
+
+
+Schulzrinne, et al. Standards Track [Page 61]
+
+RFC 3550 RTP July 2003
+
+
+ identifies a source heard by the reporter, and that SSRC identifier
+ is unrelated to the source transport address of the RTCP packet sent
+ by the reporter.) If the SSRC or CSRC is not found, a new entry is
+ created. These table entries are removed when an RTCP BYE packet is
+ received with the corresponding SSRC identifier and validated by a
+ matching source transport address, or after no packets have arrived
+ for a relatively long time (see Section 6.2.1).
+
+ Note that if two sources on the same host are transmitting with the
+ same source identifier at the time a receiver begins operation, it
+ would be possible that the first RTP packet received came from one of
+ the sources while the first RTCP packet received came from the other.
+ This would cause the wrong RTCP information to be associated with the
+ RTP data, but this situation should be sufficiently rare and harmless
+ that it may be disregarded.
+
+ In order to track loops of the participant's own data packets, the
+ implementation MUST also keep a separate list of source transport
+ addresses (not identifiers) that have been found to be conflicting.
+ As in the source identifier table, two source transport addresses
+ MUST be kept to separately track conflicting RTP and RTCP packets.
+ Note that the conflicting address list should be short, usually
+ empty. Each element in this list stores the source addresses plus
+ the time when the most recent conflicting packet was received. An
+ element MAY be removed from the list when no conflicting packet has
+ arrived from that source for a time on the order of 10 RTCP report
+ intervals (see Section 6.2).
+
+ For the algorithm as shown, it is assumed that the participant's own
+ source identifier and state are included in the source identifier
+ table. The algorithm could be restructured to first make a separate
+ comparison against the participant's own source identifier.
+
+ if (SSRC or CSRC identifier is not found in the source
+ identifier table) {
+ create a new entry storing the data or control source
+ transport address, the SSRC or CSRC and other state;
+ }
+
+ /* Identifier is found in the table */
+
+ else if (table entry was created on receipt of a control packet
+ and this is the first data packet or vice versa) {
+ store the source transport address from this packet;
+ }
+ else if (source transport address from the packet does not match
+ the one saved in the table entry for this identifier) {
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 62]
+
+RFC 3550 RTP July 2003
+
+
+ /* An identifier collision or a loop is indicated */
+
+ if (source identifier is not the participant's own) {
+ /* OPTIONAL error counter step */
+ if (source identifier is from an RTCP SDES chunk
+ containing a CNAME item that differs from the CNAME
+ in the table entry) {
+ count a third-party collision;
+ } else {
+ count a third-party loop;
+ }
+ abort processing of data packet or control element;
+ /* MAY choose a different policy to keep new source */
+ }
+
+ /* A collision or loop of the participant's own packets */
+
+ else if (source transport address is found in the list of
+ conflicting data or control source transport
+ addresses) {
+ /* OPTIONAL error counter step */
+ if (source identifier is not from an RTCP SDES chunk
+ containing a CNAME item or CNAME is the
+ participant's own) {
+ count occurrence of own traffic looped;
+ }
+ mark current time in conflicting address list entry;
+ abort processing of data packet or control element;
+ }
+
+ /* New collision, change SSRC identifier */
+
+ else {
+ log occurrence of a collision;
+ create a new entry in the conflicting data or control
+ source transport address list and mark current time;
+ send an RTCP BYE packet with the old SSRC identifier;
+ choose a new SSRC identifier;
+ create a new entry in the source identifier table with
+ the old SSRC plus the source transport address from
+ the data or control packet being processed;
+ }
+ }
+
+ In this algorithm, packets from a newly conflicting source address
+ will be ignored and packets from the original source address will be
+ kept. If no packets arrive from the original source for an extended
+ period, the table entry will be timed out and the new source will be
+
+
+
+Schulzrinne, et al. Standards Track [Page 63]
+
+RFC 3550 RTP July 2003
+
+
+ able to take over. This might occur if the original source detects
+ the collision and moves to a new source identifier, but in the usual
+ case an RTCP BYE packet will be received from the original source to
+ delete the state without having to wait for a timeout.
+
+ If the original source address was received through a mixer (i.e.,
+ learned as a CSRC) and later the same source is received directly,
+ the receiver may be well advised to switch to the new source address
+ unless other sources in the mix would be lost. Furthermore, for
+ applications such as telephony in which some sources such as mobile
+ entities may change addresses during the course of an RTP session,
+ the RTP implementation SHOULD modify the collision detection
+ algorithm to accept packets from the new source transport address.
+ To guard against flip-flopping between addresses if a genuine
+ collision does occur, the algorithm SHOULD include some means to
+ detect this case and avoid switching.
+
+ When a new SSRC identifier is chosen due to a collision, the
+ candidate identifier SHOULD first be looked up in the source
+ identifier table to see if it was already in use by some other
+ source. If so, another candidate MUST be generated and the process
+ repeated.
+
+ A loop of data packets to a multicast destination can cause severe
+ network flooding. All mixers and translators MUST implement a loop
+ detection algorithm like the one here so that they can break loops.
+ This should limit the excess traffic to no more than one duplicate
+ copy of the original traffic, which may allow the session to continue
+ so that the cause of the loop can be found and fixed. However, in
+ extreme cases where a mixer or translator does not properly break the
+ loop and high traffic levels result, it may be necessary for end
+ systems to cease transmitting data or control packets entirely. This
+ decision may depend upon the application. An error condition SHOULD
+ be indicated as appropriate. Transmission MAY be attempted again
+ periodically after a long, random time (on the order of minutes).
+
+8.3 Use with Layered Encodings
+
+ For layered encodings transmitted on separate RTP sessions (see
+ Section 2.4), a single SSRC identifier space SHOULD be used across
+ the sessions of all layers and the core (base) layer SHOULD be used
+ for SSRC identifier allocation and collision resolution. When a
+ source discovers that it has collided, it transmits an RTCP BYE
+ packet on only the base layer but changes the SSRC identifier to the
+ new value in all layers.
+
+
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 64]
+
+RFC 3550 RTP July 2003
+
+
+9. Security
+
+ Lower layer protocols may eventually provide all the security
+ services that may be desired for applications of RTP, including
+ authentication, integrity, and confidentiality. These services have
+ been specified for IP in [27]. Since the initial audio and video
+ applications using RTP needed a confidentiality service before such
+ services were available for the IP layer, the confidentiality service
+ described in the next section was defined for use with RTP and RTCP.
+ That description is included here to codify existing practice. New
+ applications of RTP MAY implement this RTP-specific confidentiality
+ service for backward compatibility, and/or they MAY implement
+ alternative security services. The overhead on the RTP protocol for
+ this confidentiality service is low, so the penalty will be minimal
+ if this service is obsoleted by other services in the future.
+
+ Alternatively, other services, other implementations of services and
+ other algorithms may be defined for RTP in the future. In
+ particular, an RTP profile called Secure Real-time Transport Protocol
+ (SRTP) [28] is being developed to provide confidentiality of the RTP
+ payload while leaving the RTP header in the clear so that link-level
+ header compression algorithms can still operate. It is expected that
+ SRTP will be the correct choice for many applications. SRTP is based
+ on the Advanced Encryption Standard (AES) and provides stronger
+ security than the service described here. No claim is made that the
+ methods presented here are appropriate for a particular security
+ need. A profile may specify which services and algorithms should be
+ offered by applications, and may provide guidance as to their
+ appropriate use.
+
+ Key distribution and certificates are outside the scope of this
+ document.
+
+9.1 Confidentiality
+
+ Confidentiality means that only the intended receiver(s) can decode
+ the received packets; for others, the packet contains no useful
+ information. Confidentiality of the content is achieved by
+ encryption.
+
+ When it is desired to encrypt RTP or RTCP according to the method
+ specified in this section, all the octets that will be encapsulated
+ for transmission in a single lower-layer packet are encrypted as a
+ unit. For RTCP, a 32-bit random number redrawn for each unit MUST be
+ prepended to the unit before encryption. For RTP, no prefix is
+ prepended; instead, the sequence number and timestamp fields are
+ initialized with random offsets. This is considered to be a weak
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 65]
+
+RFC 3550 RTP July 2003
+
+
+ initialization vector (IV) because of poor randomness properties. In
+ addition, if the subsequent field, the SSRC, can be manipulated by an
+ enemy, there is further weakness of the encryption method.
+
+ For RTCP, an implementation MAY segregate the individual RTCP packets
+ in a compound RTCP packet into two separate compound RTCP packets,
+ one to be encrypted and one to be sent in the clear. For example,
+ SDES information might be encrypted while reception reports were sent
+ in the clear to accommodate third-party monitors that are not privy
+ to the encryption key. In this example, depicted in Fig. 4, the SDES
+ information MUST be appended to an RR packet with no reports (and the
+ random number) to satisfy the requirement that all compound RTCP
+ packets begin with an SR or RR packet. The SDES CNAME item is
+ required in either the encrypted or unencrypted packet, but not both.
+ The same SDES information SHOULD NOT be carried in both packets as
+ this may compromise the encryption.
+
+ UDP packet UDP packet
+ ----------------------------- ------------------------------
+ [random][RR][SDES #CNAME ...] [SR #senderinfo #site1 #site2]
+ ----------------------------- ------------------------------
+ encrypted not encrypted
+
+ #: SSRC identifier
+
+ Figure 4: Encrypted and non-encrypted RTCP packets
+
+ The presence of encryption and the use of the correct key are
+ confirmed by the receiver through header or payload validity checks.
+ Examples of such validity checks for RTP and RTCP headers are given
+ in Appendices A.1 and A.2.
+
+ To be consistent with existing implementations of the initial
+ specification of RTP in RFC 1889, the default encryption algorithm is
+ the Data Encryption Standard (DES) algorithm in cipher block chaining
+ (CBC) mode, as described in Section 1.1 of RFC 1423 [29], except that
+ padding to a multiple of 8 octets is indicated as described for the P
+ bit in Section 5.1. The initialization vector is zero because random
+ values are supplied in the RTP header or by the random prefix for
+ compound RTCP packets. For details on the use of CBC initialization
+ vectors, see [30].
+
+ Implementations that support the encryption method specified here
+ SHOULD always support the DES algorithm in CBC mode as the default
+ cipher for this method to maximize interoperability. This method was
+ chosen because it has been demonstrated to be easy and practical to
+ use in experimental audio and video tools in operation on the
+ Internet. However, DES has since been found to be too easily broken.
+
+
+
+Schulzrinne, et al. Standards Track [Page 66]
+
+RFC 3550 RTP July 2003
+
+
+ It is RECOMMENDED that stronger encryption algorithms such as
+ Triple-DES be used in place of the default algorithm. Furthermore,
+ secure CBC mode requires that the first block of each packet be XORed
+ with a random, independent IV of the same size as the cipher's block
+ size. For RTCP, this is (partially) achieved by prepending each
+ packet with a 32-bit random number, independently chosen for each
+ packet. For RTP, the timestamp and sequence number start from random
+ values, but consecutive packets will not be independently randomized.
+ It should be noted that the randomness in both cases (RTP and RTCP)
+ is limited. High-security applications SHOULD consider other, more
+ conventional, protection means. Other encryption algorithms MAY be
+ specified dynamically for a session by non-RTP means. In particular,
+ the SRTP profile [28] based on AES is being developed to take into
+ account known plaintext and CBC plaintext manipulation concerns, and
+ will be the correct choice in the future.
+
+ As an alternative to encryption at the IP level or at the RTP level
+ as described above, profiles MAY define additional payload types for
+ encrypted encodings. Those encodings MUST specify how padding and
+ other aspects of the encryption are to be handled. This method
+ allows encrypting only the data while leaving the headers in the
+ clear for applications where that is desired. It may be particularly
+ useful for hardware devices that will handle both decryption and
+ decoding. It is also valuable for applications where link-level
+ compression of RTP and lower-layer headers is desired and
+ confidentiality of the payload (but not addresses) is sufficient
+ since encryption of the headers precludes compression.
+
+9.2 Authentication and Message Integrity
+
+ Authentication and message integrity services are not defined at the
+ RTP level since these services would not be directly feasible without
+ a key management infrastructure. It is expected that authentication
+ and integrity services will be provided by lower layer protocols.
+
+10. Congestion Control
+
+ All transport protocols used on the Internet need to address
+ congestion control in some way [31]. RTP is not an exception, but
+ because the data transported over RTP is often inelastic (generated
+ at a fixed or controlled rate), the means to control congestion in
+ RTP may be quite different from those for other transport protocols
+ such as TCP. In one sense, inelasticity reduces the risk of
+ congestion because the RTP stream will not expand to consume all
+ available bandwidth as a TCP stream can. However, inelasticity also
+ means that the RTP stream cannot arbitrarily reduce its load on the
+ network to eliminate congestion when it occurs.
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 67]
+
+RFC 3550 RTP July 2003
+
+
+ Since RTP may be used for a wide variety of applications in many
+ different contexts, there is no single congestion control mechanism
+ that will work for all. Therefore, congestion control SHOULD be
+ defined in each RTP profile as appropriate. For some profiles, it
+ may be sufficient to include an applicability statement restricting
+ the use of that profile to environments where congestion is avoided
+ by engineering. For other profiles, specific methods such as data
+ rate adaptation based on RTCP feedback may be required.
+
+11. RTP over Network and Transport Protocols
+
+ This section describes issues specific to carrying RTP packets within
+ particular network and transport protocols. The following rules
+ apply unless superseded by protocol-specific definitions outside this
+ specification.
+
+ RTP relies on the underlying protocol(s) to provide demultiplexing of
+ RTP data and RTCP control streams. For UDP and similar protocols,
+ RTP SHOULD use an even destination port number and the corresponding
+ RTCP stream SHOULD use the next higher (odd) destination port number.
+ For applications that take a single port number as a parameter and
+ derive the RTP and RTCP port pair from that number, if an odd number
+ is supplied then the application SHOULD replace that number with the
+ next lower (even) number to use as the base of the port pair. For
+ applications in which the RTP and RTCP destination port numbers are
+ specified via explicit, separate parameters (using a signaling
+ protocol or other means), the application MAY disregard the
+ restrictions that the port numbers be even/odd and consecutive
+ although the use of an even/odd port pair is still encouraged. The
+ RTP and RTCP port numbers MUST NOT be the same since RTP relies on
+ the port numbers to demultiplex the RTP data and RTCP control
+ streams.
+
+ In a unicast session, both participants need to identify a port pair
+ for receiving RTP and RTCP packets. Both participants MAY use the
+ same port pair. A participant MUST NOT assume that the source port
+ of the incoming RTP or RTCP packet can be used as the destination
+ port for outgoing RTP or RTCP packets. When RTP data packets are
+ being sent in both directions, each participant's RTCP SR packets
+ MUST be sent to the port that the other participant has specified for
+ reception of RTCP. The RTCP SR packets combine sender information
+ for the outgoing data plus reception report information for the
+ incoming data. If a side is not actively sending data (see Section
+ 6.4), an RTCP RR packet is sent instead.
+
+ It is RECOMMENDED that layered encoding applications (see Section
+ 2.4) use a set of contiguous port numbers. The port numbers MUST be
+ distinct because of a widespread deficiency in existing operating
+
+
+
+Schulzrinne, et al. Standards Track [Page 68]
+
+RFC 3550 RTP July 2003
+
+
+ systems that prevents use of the same port with multiple multicast
+ addresses, and for unicast, there is only one permissible address.
+ Thus for layer n, the data port is P + 2n, and the control port is P
+ + 2n + 1. When IP multicast is used, the addresses MUST also be
+ distinct because multicast routing and group membership are managed
+ on an address granularity. However, allocation of contiguous IP
+ multicast addresses cannot be assumed because some groups may require
+ different scopes and may therefore be allocated from different
+ address ranges.
+
+ The previous paragraph conflicts with the SDP specification, RFC 2327
+ [15], which says that it is illegal for both multiple addresses and
+ multiple ports to be specified in the same session description
+ because the association of addresses with ports could be ambiguous.
+ It is intended that this restriction will be relaxed in a revision of
+ RFC 2327 to allow an equal number of addresses and ports to be
+ specified with a one-to-one mapping implied.
+
+ RTP data packets contain no length field or other delineation,
+ therefore RTP relies on the underlying protocol(s) to provide a
+ length indication. The maximum length of RTP packets is limited only
+ by the underlying protocols.
+
+ If RTP packets are to be carried in an underlying protocol that
+ provides the abstraction of a continuous octet stream rather than
+ messages (packets), an encapsulation of the RTP packets MUST be
+ defined to provide a framing mechanism. Framing is also needed if
+ the underlying protocol may contain padding so that the extent of the
+ RTP payload cannot be determined. The framing mechanism is not
+ defined here.
+
+ A profile MAY specify a framing method to be used even when RTP is
+ carried in protocols that do provide framing in order to allow
+ carrying several RTP packets in one lower-layer protocol data unit,
+ such as a UDP packet. Carrying several RTP packets in one network or
+ transport packet reduces header overhead and may simplify
+ synchronization between different streams.
+
+12. Summary of Protocol Constants
+
+ This section contains a summary listing of the constants defined in
+ this specification.
+
+ The RTP payload type (PT) constants are defined in profiles rather
+ than this document. However, the octet of the RTP header which
+ contains the marker bit(s) and payload type MUST avoid the reserved
+ values 200 and 201 (decimal) to distinguish RTP packets from the RTCP
+ SR and RR packet types for the header validation procedure described
+
+
+
+Schulzrinne, et al. Standards Track [Page 69]
+
+RFC 3550 RTP July 2003
+
+
+ in Appendix A.1. For the standard definition of one marker bit and a
+ 7-bit payload type field as shown in this specification, this
+ restriction means that payload types 72 and 73 are reserved.
+
+12.1 RTCP Packet Types
+
+ abbrev. name value
+ SR sender report 200
+ RR receiver report 201
+ SDES source description 202
+ BYE goodbye 203
+ APP application-defined 204
+
+ These type values were chosen in the range 200-204 for improved
+ header validity checking of RTCP packets compared to RTP packets or
+ other unrelated packets. When the RTCP packet type field is compared
+ to the corresponding octet of the RTP header, this range corresponds
+ to the marker bit being 1 (which it usually is not in data packets)
+ and to the high bit of the standard payload type field being 1 (since
+ the static payload types are typically defined in the low half).
+ This range was also chosen to be some distance numerically from 0 and
+ 255 since all-zeros and all-ones are common data patterns.
+
+ Since all compound RTCP packets MUST begin with SR or RR, these codes
+ were chosen as an even/odd pair to allow the RTCP validity check to
+ test the maximum number of bits with mask and value.
+
+ Additional RTCP packet types may be registered through IANA (see
+ Section 15).
+
+12.2 SDES Types
+
+ abbrev. name value
+ END end of SDES list 0
+ CNAME canonical name 1
+ NAME user name 2
+ EMAIL user's electronic mail address 3
+ PHONE user's phone number 4
+ LOC geographic user location 5
+ TOOL name of application or tool 6
+ NOTE notice about the source 7
+ PRIV private extensions 8
+
+ Additional SDES types may be registered through IANA (see Section
+ 15).
+
+
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 70]
+
+RFC 3550 RTP July 2003
+
+
+13. RTP Profiles and Payload Format Specifications
+
+ A complete specification of RTP for a particular application will
+ require one or more companion documents of two types described here:
+ profiles, and payload format specifications.
+
+ RTP may be used for a variety of applications with somewhat differing
+ requirements. The flexibility to adapt to those requirements is
+ provided by allowing multiple choices in the main protocol
+ specification, then selecting the appropriate choices or defining
+ extensions for a particular environment and class of applications in
+ a separate profile document. Typically an application will operate
+ under only one profile in a particular RTP session, so there is no
+ explicit indication within the RTP protocol itself as to which
+ profile is in use. A profile for audio and video applications may be
+ found in the companion RFC 3551. Profiles are typically titled "RTP
+ Profile for ...".
+
+ The second type of companion document is a payload format
+ specification, which defines how a particular kind of payload data,
+ such as H.261 encoded video, should be carried in RTP. These
+ documents are typically titled "RTP Payload Format for XYZ
+ Audio/Video Encoding". Payload formats may be useful under multiple
+ profiles and may therefore be defined independently of any particular
+ profile. The profile documents are then responsible for assigning a
+ default mapping of that format to a payload type value if needed.
+
+ Within this specification, the following items have been identified
+ for possible definition within a profile, but this list is not meant
+ to be exhaustive:
+
+ RTP data header: The octet in the RTP data header that contains
+ the marker bit and payload type field MAY be redefined by a
+ profile to suit different requirements, for example with more or
+ fewer marker bits (Section 5.3, p. 18).
+
+ Payload types: Assuming that a payload type field is included,
+ the profile will usually define a set of payload formats (e.g.,
+ media encodings) and a default static mapping of those formats to
+ payload type values. Some of the payload formats may be defined
+ by reference to separate payload format specifications. For each
+ payload type defined, the profile MUST specify the RTP timestamp
+ clock rate to be used (Section 5.1, p. 14).
+
+ RTP data header additions: Additional fields MAY be appended to
+ the fixed RTP data header if some additional functionality is
+ required across the profile's class of applications independent of
+ payload type (Section 5.3, p. 18).
+
+
+
+Schulzrinne, et al. Standards Track [Page 71]
+
+RFC 3550 RTP July 2003
+
+
+ RTP data header extensions: The contents of the first 16 bits of
+ the RTP data header extension structure MUST be defined if use of
+ that mechanism is to be allowed under the profile for
+ implementation-specific extensions (Section 5.3.1, p. 18).
+
+ RTCP packet types: New application-class-specific RTCP packet
+ types MAY be defined and registered with IANA.
+
+ RTCP report interval: A profile SHOULD specify that the values
+ suggested in Section 6.2 for the constants employed in the
+ calculation of the RTCP report interval will be used. Those are
+ the RTCP fraction of session bandwidth, the minimum report
+ interval, and the bandwidth split between senders and receivers.
+ A profile MAY specify alternate values if they have been
+ demonstrated to work in a scalable manner.
+
+ SR/RR extension: An extension section MAY be defined for the
+ RTCP SR and RR packets if there is additional information that
+ should be reported regularly about the sender or receivers
+ (Section 6.4.3, p. 42 and 43).
+
+ SDES use: The profile MAY specify the relative priorities for
+ RTCP SDES items to be transmitted or excluded entirely (Section
+ 6.3.9); an alternate syntax or semantics for the CNAME item
+ (Section 6.5.1); the format of the LOC item (Section 6.5.5); the
+ semantics and use of the NOTE item (Section 6.5.7); or new SDES
+ item types to be registered with IANA.
+
+ Security: A profile MAY specify which security services and
+ algorithms should be offered by applications, and MAY provide
+ guidance as to their appropriate use (Section 9, p. 65).
+
+ String-to-key mapping: A profile MAY specify how a user-provided
+ password or pass phrase is mapped into an encryption key.
+
+ Congestion: A profile SHOULD specify the congestion control
+ behavior appropriate for that profile.
+
+ Underlying protocol: Use of a particular underlying network or
+ transport layer protocol to carry RTP packets MAY be required.
+
+ Transport mapping: A mapping of RTP and RTCP to transport-level
+ addresses, e.g., UDP ports, other than the standard mapping
+ defined in Section 11, p. 68 may be specified.
+
+
+
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 72]
+
+RFC 3550 RTP July 2003
+
+
+ Encapsulation: An encapsulation of RTP packets may be defined to
+ allow multiple RTP data packets to be carried in one lower-layer
+ packet or to provide framing over underlying protocols that do not
+ already do so (Section 11, p. 69).
+
+ It is not expected that a new profile will be required for every
+ application. Within one application class, it would be better to
+ extend an existing profile rather than make a new one in order to
+ facilitate interoperation among the applications since each will
+ typically run under only one profile. Simple extensions such as the
+ definition of additional payload type values or RTCP packet types may
+ be accomplished by registering them through IANA and publishing their
+ descriptions in an addendum to the profile or in a payload format
+ specification.
+
+14. Security Considerations
+
+ RTP suffers from the same security liabilities as the underlying
+ protocols. For example, an impostor can fake source or destination
+ network addresses, or change the header or payload. Within RTCP, the
+ CNAME and NAME information may be used to impersonate another
+ participant. In addition, RTP may be sent via IP multicast, which
+ provides no direct means for a sender to know all the receivers of
+ the data sent and therefore no measure of privacy. Rightly or not,
+ users may be more sensitive to privacy concerns with audio and video
+ communication than they have been with more traditional forms of
+ network communication [33]. Therefore, the use of security
+ mechanisms with RTP is important. These mechanisms are discussed in
+ Section 9.
+
+ RTP-level translators or mixers may be used to allow RTP traffic to
+ reach hosts behind firewalls. Appropriate firewall security
+ principles and practices, which are beyond the scope of this
+ document, should be followed in the design and installation of these
+ devices and in the admission of RTP applications for use behind the
+ firewall.
+
+15. IANA Considerations
+
+ Additional RTCP packet types and SDES item types may be registered
+ through the Internet Assigned Numbers Authority (IANA). Since these
+ number spaces are small, allowing unconstrained registration of new
+ values would not be prudent. To facilitate review of requests and to
+ promote shared use of new types among multiple applications, requests
+ for registration of new values must be documented in an RFC or other
+ permanent and readily available reference such as the product of
+ another cooperative standards body (e.g., ITU-T). Other requests may
+ also be accepted, under the advice of a "designated expert."
+
+
+
+Schulzrinne, et al. Standards Track [Page 73]
+
+RFC 3550 RTP July 2003
+
+
+ (Contact the IANA for the contact information of the current expert.)
+
+ RTP profile specifications SHOULD register with IANA a name for the
+ profile in the form "RTP/xxx", where xxx is a short abbreviation of
+ the profile title. These names are for use by higher-level control
+ protocols, such as the Session Description Protocol (SDP), RFC 2327
+ [15], to refer to transport methods.
+
+16. Intellectual Property Rights Statement
+
+ The IETF takes no position regarding the validity or scope of any
+ intellectual property or other rights that might be claimed to
+ pertain to the implementation or use of the technology described in
+ this document or the extent to which any license under such rights
+ might or might not be available; neither does it represent that it
+ has made any effort to identify any such rights. Information on the
+ IETF's procedures with respect to rights in standards-track and
+ standards-related documentation can be found in BCP-11. Copies of
+ claims of rights made available for publication and any assurances of
+ licenses to be made available, or the result of an attempt made to
+ obtain a general license or permission for the use of such
+ proprietary rights by implementors or users of this specification can
+ be obtained from the IETF Secretariat.
+
+ The IETF invites any interested party to bring to its attention any
+ copyrights, patents or patent applications, or other proprietary
+ rights which may cover technology that may be required to practice
+ this standard. Please address the information to the IETF Executive
+ Director.
+
+17. Acknowledgments
+
+ This memorandum is based on discussions within the IETF Audio/Video
+ Transport working group chaired by Stephen Casner and Colin Perkins.
+ The current protocol has its origins in the Network Voice Protocol
+ and the Packet Video Protocol (Danny Cohen and Randy Cole) and the
+ protocol implemented by the vat application (Van Jacobson and Steve
+ McCanne). Christian Huitema provided ideas for the random identifier
+ generator. Extensive analysis and simulation of the timer
+ reconsideration algorithm was done by Jonathan Rosenberg. The
+ additions for layered encodings were specified by Michael Speer and
+ Steve McCanne.
+
+
+
+
+
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 74]
+
+RFC 3550 RTP July 2003
+
+
+Appendix A - Algorithms
+
+ We provide examples of C code for aspects of RTP sender and receiver
+ algorithms. There may be other implementation methods that are
+ faster in particular operating environments or have other advantages.
+ These implementation notes are for informational purposes only and
+ are meant to clarify the RTP specification.
+
+ The following definitions are used for all examples; for clarity and
+ brevity, the structure definitions are only valid for 32-bit big-
+ endian (most significant octet first) architectures. Bit fields are
+ assumed to be packed tightly in big-endian bit order, with no
+ additional padding. Modifications would be required to construct a
+ portable implementation.
+
+ /*
+ * rtp.h -- RTP header file
+ */
+ #include <sys/types.h>
+
+ /*
+ * The type definitions below are valid for 32-bit architectures and
+ * may have to be adjusted for 16- or 64-bit architectures.
+ */
+ typedef unsigned char u_int8;
+ typedef unsigned short u_int16;
+ typedef unsigned int u_int32;
+ typedef short int16;
+
+ /*
+ * Current protocol version.
+ */
+ #define RTP_VERSION 2
+
+ #define RTP_SEQ_MOD (1<<16)
+ #define RTP_MAX_SDES 255 /* maximum text length for SDES */
+
+ typedef enum {
+ RTCP_SR = 200,
+ RTCP_RR = 201,
+ RTCP_SDES = 202,
+ RTCP_BYE = 203,
+ RTCP_APP = 204
+ } rtcp_type_t;
+
+ typedef enum {
+ RTCP_SDES_END = 0,
+ RTCP_SDES_CNAME = 1,
+
+
+
+Schulzrinne, et al. Standards Track [Page 75]
+
+RFC 3550 RTP July 2003
+
+
+ RTCP_SDES_NAME = 2,
+ RTCP_SDES_EMAIL = 3,
+ RTCP_SDES_PHONE = 4,
+ RTCP_SDES_LOC = 5,
+ RTCP_SDES_TOOL = 6,
+ RTCP_SDES_NOTE = 7,
+ RTCP_SDES_PRIV = 8
+ } rtcp_sdes_type_t;
+
+ /*
+ * RTP data header
+ */
+ typedef struct {
+ unsigned int version:2; /* protocol version */
+ unsigned int p:1; /* padding flag */
+ unsigned int x:1; /* header extension flag */
+ unsigned int cc:4; /* CSRC count */
+ unsigned int m:1; /* marker bit */
+ unsigned int pt:7; /* payload type */
+ unsigned int seq:16; /* sequence number */
+ u_int32 ts; /* timestamp */
+ u_int32 ssrc; /* synchronization source */
+ u_int32 csrc[1]; /* optional CSRC list */
+ } rtp_hdr_t;
+
+ /*
+ * RTCP common header word
+ */
+ typedef struct {
+ unsigned int version:2; /* protocol version */
+ unsigned int p:1; /* padding flag */
+ unsigned int count:5; /* varies by packet type */
+ unsigned int pt:8; /* RTCP packet type */
+ u_int16 length; /* pkt len in words, w/o this word */
+ } rtcp_common_t;
+
+ /*
+ * Big-endian mask for version, padding bit and packet type pair
+ */
+ #define RTCP_VALID_MASK (0xc000 | 0x2000 | 0xfe)
+ #define RTCP_VALID_VALUE ((RTP_VERSION << 14) | RTCP_SR)
+
+ /*
+ * Reception report block
+ */
+ typedef struct {
+ u_int32 ssrc; /* data source being reported */
+ unsigned int fraction:8; /* fraction lost since last SR/RR */
+
+
+
+Schulzrinne, et al. Standards Track [Page 76]
+
+RFC 3550 RTP July 2003
+
+
+ int lost:24; /* cumul. no. pkts lost (signed!) */
+ u_int32 last_seq; /* extended last seq. no. received */
+ u_int32 jitter; /* interarrival jitter */
+ u_int32 lsr; /* last SR packet from this source */
+ u_int32 dlsr; /* delay since last SR packet */
+ } rtcp_rr_t;
+
+ /*
+ * SDES item
+ */
+ typedef struct {
+ u_int8 type; /* type of item (rtcp_sdes_type_t) */
+ u_int8 length; /* length of item (in octets) */
+ char data[1]; /* text, not null-terminated */
+ } rtcp_sdes_item_t;
+
+ /*
+ * One RTCP packet
+ */
+ typedef struct {
+ rtcp_common_t common; /* common header */
+ union {
+ /* sender report (SR) */
+ struct {
+ u_int32 ssrc; /* sender generating this report */
+ u_int32 ntp_sec; /* NTP timestamp */
+ u_int32 ntp_frac;
+ u_int32 rtp_ts; /* RTP timestamp */
+ u_int32 psent; /* packets sent */
+ u_int32 osent; /* octets sent */
+ rtcp_rr_t rr[1]; /* variable-length list */
+ } sr;
+
+ /* reception report (RR) */
+ struct {
+ u_int32 ssrc; /* receiver generating this report */
+ rtcp_rr_t rr[1]; /* variable-length list */
+ } rr;
+
+ /* source description (SDES) */
+ struct rtcp_sdes {
+ u_int32 src; /* first SSRC/CSRC */
+ rtcp_sdes_item_t item[1]; /* list of SDES items */
+ } sdes;
+
+ /* BYE */
+ struct {
+ u_int32 src[1]; /* list of sources */
+
+
+
+Schulzrinne, et al. Standards Track [Page 77]
+
+RFC 3550 RTP July 2003
+
+
+ /* can't express trailing text for reason */
+ } bye;
+ } r;
+ } rtcp_t;
+
+ typedef struct rtcp_sdes rtcp_sdes_t;
+
+ /*
+ * Per-source state information
+ */
+ typedef struct {
+ u_int16 max_seq; /* highest seq. number seen */
+ u_int32 cycles; /* shifted count of seq. number cycles */
+ u_int32 base_seq; /* base seq number */
+ u_int32 bad_seq; /* last 'bad' seq number + 1 */
+ u_int32 probation; /* sequ. packets till source is valid */
+ u_int32 received; /* packets received */
+ u_int32 expected_prior; /* packet expected at last interval */
+ u_int32 received_prior; /* packet received at last interval */
+ u_int32 transit; /* relative trans time for prev pkt */
+ u_int32 jitter; /* estimated jitter */
+ /* ... */
+ } source;
+
+A.1 RTP Data Header Validity Checks
+
+ An RTP receiver should check the validity of the RTP header on
+ incoming packets since they might be encrypted or might be from a
+ different application that happens to be misaddressed. Similarly, if
+ encryption according to the method described in Section 9 is enabled,
+ the header validity check is needed to verify that incoming packets
+ have been correctly decrypted, although a failure of the header
+ validity check (e.g., unknown payload type) may not necessarily
+ indicate decryption failure.
+
+ Only weak validity checks are possible on an RTP data packet from a
+ source that has not been heard before:
+
+ o RTP version field must equal 2.
+
+ o The payload type must be known, and in particular it must not be
+ equal to SR or RR.
+
+ o If the P bit is set, then the last octet of the packet must
+ contain a valid octet count, in particular, less than the total
+ packet length minus the header size.
+
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 78]
+
+RFC 3550 RTP July 2003
+
+
+ o The X bit must be zero if the profile does not specify that the
+ header extension mechanism may be used. Otherwise, the extension
+ length field must be less than the total packet size minus the
+ fixed header length and padding.
+
+ o The length of the packet must be consistent with CC and payload
+ type (if payloads have a known length).
+
+ The last three checks are somewhat complex and not always possible,
+ leaving only the first two which total just a few bits. If the SSRC
+ identifier in the packet is one that has been received before, then
+ the packet is probably valid and checking if the sequence number is
+ in the expected range provides further validation. If the SSRC
+ identifier has not been seen before, then data packets carrying that
+ identifier may be considered invalid until a small number of them
+ arrive with consecutive sequence numbers. Those invalid packets MAY
+ be discarded or they MAY be stored and delivered once validation has
+ been achieved if the resulting delay is acceptable.
+
+ The routine update_seq shown below ensures that a source is declared
+ valid only after MIN_SEQUENTIAL packets have been received in
+ sequence. It also validates the sequence number seq of a newly
+ received packet and updates the sequence state for the packet's
+ source in the structure to which s points.
+
+ When a new source is heard for the first time, that is, its SSRC
+ identifier is not in the table (see Section 8.2), and the per-source
+ state is allocated for it, s->probation is set to the number of
+ sequential packets required before declaring a source valid
+ (parameter MIN_SEQUENTIAL) and other variables are initialized:
+
+ init_seq(s, seq);
+ s->max_seq = seq - 1;
+ s->probation = MIN_SEQUENTIAL;
+
+ A non-zero s->probation marks the source as not yet valid so the
+ state may be discarded after a short timeout rather than a long one,
+ as discussed in Section 6.2.1.
+
+ After a source is considered valid, the sequence number is considered
+ valid if it is no more than MAX_DROPOUT ahead of s->max_seq nor more
+ than MAX_MISORDER behind. If the new sequence number is ahead of
+ max_seq modulo the RTP sequence number range (16 bits), but is
+ smaller than max_seq, it has wrapped around and the (shifted) count
+ of sequence number cycles is incremented. A value of one is returned
+ to indicate a valid sequence number.
+
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 79]
+
+RFC 3550 RTP July 2003
+
+
+ Otherwise, the value zero is returned to indicate that the validation
+ failed, and the bad sequence number plus 1 is stored. If the next
+ packet received carries the next higher sequence number, it is
+ considered the valid start of a new packet sequence presumably caused
+ by an extended dropout or a source restart. Since multiple complete
+ sequence number cycles may have been missed, the packet loss
+ statistics are reset.
+
+ Typical values for the parameters are shown, based on a maximum
+ misordering time of 2 seconds at 50 packets/second and a maximum
+ dropout of 1 minute. The dropout parameter MAX_DROPOUT should be a
+ small fraction of the 16-bit sequence number space to give a
+ reasonable probability that new sequence numbers after a restart will
+ not fall in the acceptable range for sequence numbers from before the
+ restart.
+
+ void init_seq(source *s, u_int16 seq)
+ {
+ s->base_seq = seq;
+ s->max_seq = seq;
+ s->bad_seq = RTP_SEQ_MOD + 1; /* so seq == bad_seq is false */
+ s->cycles = 0;
+ s->received = 0;
+ s->received_prior = 0;
+ s->expected_prior = 0;
+ /* other initialization */
+ }
+
+ int update_seq(source *s, u_int16 seq)
+ {
+ u_int16 udelta = seq - s->max_seq;
+ const int MAX_DROPOUT = 3000;
+ const int MAX_MISORDER = 100;
+ const int MIN_SEQUENTIAL = 2;
+
+ /*
+ * Source is not valid until MIN_SEQUENTIAL packets with
+ * sequential sequence numbers have been received.
+ */
+ if (s->probation) {
+ /* packet is in sequence */
+ if (seq == s->max_seq + 1) {
+ s->probation--;
+ s->max_seq = seq;
+ if (s->probation == 0) {
+ init_seq(s, seq);
+ s->received++;
+ return 1;
+
+
+
+Schulzrinne, et al. Standards Track [Page 80]
+
+RFC 3550 RTP July 2003
+
+
+ }
+ } else {
+ s->probation = MIN_SEQUENTIAL - 1;
+ s->max_seq = seq;
+ }
+ return 0;
+ } else if (udelta < MAX_DROPOUT) {
+ /* in order, with permissible gap */
+ if (seq < s->max_seq) {
+ /*
+ * Sequence number wrapped - count another 64K cycle.
+ */
+ s->cycles += RTP_SEQ_MOD;
+ }
+ s->max_seq = seq;
+ } else if (udelta <= RTP_SEQ_MOD - MAX_MISORDER) {
+ /* the sequence number made a very large jump */
+ if (seq == s->bad_seq) {
+ /*
+ * Two sequential packets -- assume that the other side
+ * restarted without telling us so just re-sync
+ * (i.e., pretend this was the first packet).
+ */
+ init_seq(s, seq);
+ }
+ else {
+ s->bad_seq = (seq + 1) & (RTP_SEQ_MOD-1);
+ return 0;
+ }
+ } else {
+ /* duplicate or reordered packet */
+ }
+ s->received++;
+ return 1;
+ }
+
+ The validity check can be made stronger requiring more than two
+ packets in sequence. The disadvantages are that a larger number of
+ initial packets will be discarded (or delayed in a queue) and that
+ high packet loss rates could prevent validation. However, because
+ the RTCP header validation is relatively strong, if an RTCP packet is
+ received from a source before the data packets, the count could be
+ adjusted so that only two packets are required in sequence. If
+ initial data loss for a few seconds can be tolerated, an application
+ MAY choose to discard all data packets from a source until a valid
+ RTCP packet has been received from that source.
+
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 81]
+
+RFC 3550 RTP July 2003
+
+
+ Depending on the application and encoding, algorithms may exploit
+ additional knowledge about the payload format for further validation.
+ For payload types where the timestamp increment is the same for all
+ packets, the timestamp values can be predicted from the previous
+ packet received from the same source using the sequence number
+ difference (assuming no change in payload type).
+
+ A strong "fast-path" check is possible since with high probability
+ the first four octets in the header of a newly received RTP data
+ packet will be just the same as that of the previous packet from the
+ same SSRC except that the sequence number will have increased by one.
+ Similarly, a single-entry cache may be used for faster SSRC lookups
+ in applications where data is typically received from one source at a
+ time.
+
+A.2 RTCP Header Validity Checks
+
+ The following checks should be applied to RTCP packets.
+
+ o RTP version field must equal 2.
+
+ o The payload type field of the first RTCP packet in a compound
+ packet must be equal to SR or RR.
+
+ o The padding bit (P) should be zero for the first packet of a
+ compound RTCP packet because padding should only be applied, if it
+ is needed, to the last packet.
+
+ o The length fields of the individual RTCP packets must add up to
+ the overall length of the compound RTCP packet as received. This
+ is a fairly strong check.
+
+ The code fragment below performs all of these checks. The packet
+ type is not checked for subsequent packets since unknown packet types
+ may be present and should be ignored.
+
+ u_int32 len; /* length of compound RTCP packet in words */
+ rtcp_t *r; /* RTCP header */
+ rtcp_t *end; /* end of compound RTCP packet */
+
+ if ((*(u_int16 *)r & RTCP_VALID_MASK) != RTCP_VALID_VALUE) {
+ /* something wrong with packet format */
+ }
+ end = (rtcp_t *)((u_int32 *)r + len);
+
+ do r = (rtcp_t *)((u_int32 *)r + r->common.length + 1);
+ while (r < end && r->common.version == 2);
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 82]
+
+RFC 3550 RTP July 2003
+
+
+ if (r != end) {
+ /* something wrong with packet format */
+ }
+
+A.3 Determining Number of Packets Expected and Lost
+
+ In order to compute packet loss rates, the number of RTP packets
+ expected and actually received from each source needs to be known,
+ using per-source state information defined in struct source
+ referenced via pointer s in the code below. The number of packets
+ received is simply the count of packets as they arrive, including any
+ late or duplicate packets. The number of packets expected can be
+ computed by the receiver as the difference between the highest
+ sequence number received (s->max_seq) and the first sequence number
+ received (s->base_seq). Since the sequence number is only 16 bits
+ and will wrap around, it is necessary to extend the highest sequence
+ number with the (shifted) count of sequence number wraparounds
+ (s->cycles). Both the received packet count and the count of cycles
+ are maintained the RTP header validity check routine in Appendix A.1.
+
+ extended_max = s->cycles + s->max_seq;
+ expected = extended_max - s->base_seq + 1;
+
+ The number of packets lost is defined to be the number of packets
+ expected less the number of packets actually received:
+
+ lost = expected - s->received;
+
+ Since this signed number is carried in 24 bits, it should be clamped
+ at 0x7fffff for positive loss or 0x800000 for negative loss rather
+ than wrapping around.
+
+ The fraction of packets lost during the last reporting interval
+ (since the previous SR or RR packet was sent) is calculated from
+ differences in the expected and received packet counts across the
+ interval, where expected_prior and received_prior are the values
+ saved when the previous reception report was generated:
+
+ expected_interval = expected - s->expected_prior;
+ s->expected_prior = expected;
+ received_interval = s->received - s->received_prior;
+ s->received_prior = s->received;
+ lost_interval = expected_interval - received_interval;
+ if (expected_interval == 0 || lost_interval <= 0) fraction = 0;
+ else fraction = (lost_interval << 8) / expected_interval;
+
+ The resulting fraction is an 8-bit fixed point number with the binary
+ point at the left edge.
+
+
+
+Schulzrinne, et al. Standards Track [Page 83]
+
+RFC 3550 RTP July 2003
+
+
+A.4 Generating RTCP SDES Packets
+
+ This function builds one SDES chunk into buffer b composed of argc
+ items supplied in arrays type, value and length. It returns a
+ pointer to the next available location within b.
+
+ char *rtp_write_sdes(char *b, u_int32 src, int argc,
+ rtcp_sdes_type_t type[], char *value[],
+ int length[])
+ {
+ rtcp_sdes_t *s = (rtcp_sdes_t *)b;
+ rtcp_sdes_item_t *rsp;
+ int i;
+ int len;
+ int pad;
+
+ /* SSRC header */
+ s->src = src;
+ rsp = &s->item[0];
+
+ /* SDES items */
+ for (i = 0; i < argc; i++) {
+ rsp->type = type[i];
+ len = length[i];
+ if (len > RTP_MAX_SDES) {
+ /* invalid length, may want to take other action */
+ len = RTP_MAX_SDES;
+ }
+ rsp->length = len;
+ memcpy(rsp->data, value[i], len);
+ rsp = (rtcp_sdes_item_t *)&rsp->data[len];
+ }
+
+ /* terminate with end marker and pad to next 4-octet boundary */
+ len = ((char *) rsp) - b;
+ pad = 4 - (len & 0x3);
+ b = (char *) rsp;
+ while (pad--) *b++ = RTCP_SDES_END;
+
+ return b;
+ }
+
+
+
+
+
+
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 84]
+
+RFC 3550 RTP July 2003
+
+
+A.5 Parsing RTCP SDES Packets
+
+ This function parses an SDES packet, calling functions find_member()
+ to find a pointer to the information for a session member given the
+ SSRC identifier and member_sdes() to store the new SDES information
+ for that member. This function expects a pointer to the header of
+ the RTCP packet.
+
+ void rtp_read_sdes(rtcp_t *r)
+ {
+ int count = r->common.count;
+ rtcp_sdes_t *sd = &r->r.sdes;
+ rtcp_sdes_item_t *rsp, *rspn;
+ rtcp_sdes_item_t *end = (rtcp_sdes_item_t *)
+ ((u_int32 *)r + r->common.length + 1);
+ source *s;
+
+ while (--count >= 0) {
+ rsp = &sd->item[0];
+ if (rsp >= end) break;
+ s = find_member(sd->src);
+
+ for (; rsp->type; rsp = rspn ) {
+ rspn = (rtcp_sdes_item_t *)((char*)rsp+rsp->length+2);
+ if (rspn >= end) {
+ rsp = rspn;
+ break;
+ }
+ member_sdes(s, rsp->type, rsp->data, rsp->length);
+ }
+ sd = (rtcp_sdes_t *)
+ ((u_int32 *)sd + (((char *)rsp - (char *)sd) >> 2)+1);
+ }
+ if (count >= 0) {
+ /* invalid packet format */
+ }
+ }
+
+A.6 Generating a Random 32-bit Identifier
+
+ The following subroutine generates a random 32-bit identifier using
+ the MD5 routines published in RFC 1321 [32]. The system routines may
+ not be present on all operating systems, but they should serve as
+ hints as to what kinds of information may be used. Other system
+ calls that may be appropriate include
+
+
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 85]
+
+RFC 3550 RTP July 2003
+
+
+ o getdomainname(),
+
+ o getwd(), or
+
+ o getrusage().
+
+ "Live" video or audio samples are also a good source of random
+ numbers, but care must be taken to avoid using a turned-off
+ microphone or blinded camera as a source [17].
+
+ Use of this or a similar routine is recommended to generate the
+ initial seed for the random number generator producing the RTCP
+ period (as shown in Appendix A.7), to generate the initial values for
+ the sequence number and timestamp, and to generate SSRC values.
+ Since this routine is likely to be CPU-intensive, its direct use to
+ generate RTCP periods is inappropriate because predictability is not
+ an issue. Note that this routine produces the same result on
+ repeated calls until the value of the system clock changes unless
+ different values are supplied for the type argument.
+
+ /*
+ * Generate a random 32-bit quantity.
+ */
+ #include <sys/types.h> /* u_long */
+ #include <sys/time.h> /* gettimeofday() */
+ #include <unistd.h> /* get..() */
+ #include <stdio.h> /* printf() */
+ #include <time.h> /* clock() */
+ #include <sys/utsname.h> /* uname() */
+ #include "global.h" /* from RFC 1321 */
+ #include "md5.h" /* from RFC 1321 */
+
+ #define MD_CTX MD5_CTX
+ #define MDInit MD5Init
+ #define MDUpdate MD5Update
+ #define MDFinal MD5Final
+
+ static u_long md_32(char *string, int length)
+ {
+ MD_CTX context;
+ union {
+ char c[16];
+ u_long x[4];
+ } digest;
+ u_long r;
+ int i;
+
+ MDInit (&context);
+
+
+
+Schulzrinne, et al. Standards Track [Page 86]
+
+RFC 3550 RTP July 2003
+
+
+ MDUpdate (&context, string, length);
+ MDFinal ((unsigned char *)&digest, &context);
+ r = 0;
+ for (i = 0; i < 3; i++) {
+ r ^= digest.x[i];
+ }
+ return r;
+ } /* md_32 */
+
+ /*
+ * Return random unsigned 32-bit quantity. Use 'type' argument if
+ * you need to generate several different values in close succession.
+ */
+ u_int32 random32(int type)
+ {
+ struct {
+ int type;
+ struct timeval tv;
+ clock_t cpu;
+ pid_t pid;
+ u_long hid;
+ uid_t uid;
+ gid_t gid;
+ struct utsname name;
+ } s;
+
+ gettimeofday(&s.tv, 0);
+ uname(&s.name);
+ s.type = type;
+ s.cpu = clock();
+ s.pid = getpid();
+ s.hid = gethostid();
+ s.uid = getuid();
+ s.gid = getgid();
+ /* also: system uptime */
+
+ return md_32((char *)&s, sizeof(s));
+ } /* random32 */
+
+A.7 Computing the RTCP Transmission Interval
+
+ The following functions implement the RTCP transmission and reception
+ rules described in Section 6.2. These rules are coded in several
+ functions:
+
+ o rtcp_interval() computes the deterministic calculated interval,
+ measured in seconds. The parameters are defined in Section 6.3.
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 87]
+
+RFC 3550 RTP July 2003
+
+
+ o OnExpire() is called when the RTCP transmission timer expires.
+
+ o OnReceive() is called whenever an RTCP packet is received.
+
+ Both OnExpire() and OnReceive() have event e as an argument. This is
+ the next scheduled event for that participant, either an RTCP report
+ or a BYE packet. It is assumed that the following functions are
+ available:
+
+ o Schedule(time t, event e) schedules an event e to occur at time t.
+ When time t arrives, the function OnExpire is called with e as an
+ argument.
+
+ o Reschedule(time t, event e) reschedules a previously scheduled
+ event e for time t.
+
+ o SendRTCPReport(event e) sends an RTCP report.
+
+ o SendBYEPacket(event e) sends a BYE packet.
+
+ o TypeOfEvent(event e) returns EVENT_BYE if the event being
+ processed is for a BYE packet to be sent, else it returns
+ EVENT_REPORT.
+
+ o PacketType(p) returns PACKET_RTCP_REPORT if packet p is an RTCP
+ report (not BYE), PACKET_BYE if its a BYE RTCP packet, and
+ PACKET_RTP if its a regular RTP data packet.
+
+ o ReceivedPacketSize() and SentPacketSize() return the size of the
+ referenced packet in octets.
+
+ o NewMember(p) returns a 1 if the participant who sent packet p is
+ not currently in the member list, 0 otherwise. Note this function
+ is not sufficient for a complete implementation because each CSRC
+ identifier in an RTP packet and each SSRC in a BYE packet should
+ be processed.
+
+ o NewSender(p) returns a 1 if the participant who sent packet p is
+ not currently in the sender sublist of the member list, 0
+ otherwise.
+
+ o AddMember() and RemoveMember() to add and remove participants from
+ the member list.
+
+ o AddSender() and RemoveSender() to add and remove participants from
+ the sender sublist of the member list.
+
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 88]
+
+RFC 3550 RTP July 2003
+
+
+ These functions would have to be extended for an implementation that
+ allows the RTCP bandwidth fractions for senders and non-senders to be
+ specified as explicit parameters rather than fixed values of 25% and
+ 75%. The extended implementation of rtcp_interval() would need to
+ avoid division by zero if one of the parameters was zero.
+
+ double rtcp_interval(int members,
+ int senders,
+ double rtcp_bw,
+ int we_sent,
+ double avg_rtcp_size,
+ int initial)
+ {
+ /*
+ * Minimum average time between RTCP packets from this site (in
+ * seconds). This time prevents the reports from `clumping' when
+ * sessions are small and the law of large numbers isn't helping
+ * to smooth out the traffic. It also keeps the report interval
+ * from becoming ridiculously small during transient outages like
+ * a network partition.
+ */
+ double const RTCP_MIN_TIME = 5.;
+ /*
+ * Fraction of the RTCP bandwidth to be shared among active
+ * senders. (This fraction was chosen so that in a typical
+ * session with one or two active senders, the computed report
+ * time would be roughly equal to the minimum report time so that
+ * we don't unnecessarily slow down receiver reports.) The
+ * receiver fraction must be 1 - the sender fraction.
+ */
+ double const RTCP_SENDER_BW_FRACTION = 0.25;
+ double const RTCP_RCVR_BW_FRACTION = (1-RTCP_SENDER_BW_FRACTION);
+ /*
+ /* To compensate for "timer reconsideration" converging to a
+ * value below the intended average.
+ */
+ double const COMPENSATION = 2.71828 - 1.5;
+
+ double t; /* interval */
+ double rtcp_min_time = RTCP_MIN_TIME;
+ int n; /* no. of members for computation */
+
+ /*
+ * Very first call at application start-up uses half the min
+ * delay for quicker notification while still allowing some time
+ * before reporting for randomization and to learn about other
+ * sources so the report interval will converge to the correct
+ * interval more quickly.
+
+
+
+Schulzrinne, et al. Standards Track [Page 89]
+
+RFC 3550 RTP July 2003
+
+
+ */
+ if (initial) {
+ rtcp_min_time /= 2;
+ }
+ /*
+ * Dedicate a fraction of the RTCP bandwidth to senders unless
+ * the number of senders is large enough that their share is
+ * more than that fraction.
+ */
+ n = members;
+ if (senders <= members * RTCP_SENDER_BW_FRACTION) {
+ if (we_sent) {
+ rtcp_bw *= RTCP_SENDER_BW_FRACTION;
+ n = senders;
+ } else {
+ rtcp_bw *= RTCP_RCVR_BW_FRACTION;
+ n -= senders;
+ }
+ }
+
+ /*
+ * The effective number of sites times the average packet size is
+ * the total number of octets sent when each site sends a report.
+ * Dividing this by the effective bandwidth gives the time
+ * interval over which those packets must be sent in order to
+ * meet the bandwidth target, with a minimum enforced. In that
+ * time interval we send one report so this time is also our
+ * average time between reports.
+ */
+ t = avg_rtcp_size * n / rtcp_bw;
+ if (t < rtcp_min_time) t = rtcp_min_time;
+
+ /*
+ * To avoid traffic bursts from unintended synchronization with
+ * other sites, we then pick our actual next report interval as a
+ * random number uniformly distributed between 0.5*t and 1.5*t.
+ */
+ t = t * (drand48() + 0.5);
+ t = t / COMPENSATION;
+ return t;
+ }
+
+ void OnExpire(event e,
+ int members,
+ int senders,
+ double rtcp_bw,
+ int we_sent,
+ double *avg_rtcp_size,
+
+
+
+Schulzrinne, et al. Standards Track [Page 90]
+
+RFC 3550 RTP July 2003
+
+
+ int *initial,
+ time_tp tc,
+ time_tp *tp,
+ int *pmembers)
+ {
+ /* This function is responsible for deciding whether to send an
+ * RTCP report or BYE packet now, or to reschedule transmission.
+ * It is also responsible for updating the pmembers, initial, tp,
+ * and avg_rtcp_size state variables. This function should be
+ * called upon expiration of the event timer used by Schedule().
+ */
+
+ double t; /* Interval */
+ double tn; /* Next transmit time */
+
+ /* In the case of a BYE, we use "timer reconsideration" to
+ * reschedule the transmission of the BYE if necessary */
+
+ if (TypeOfEvent(e) == EVENT_BYE) {
+ t = rtcp_interval(members,
+ senders,
+ rtcp_bw,
+ we_sent,
+ *avg_rtcp_size,
+ *initial);
+ tn = *tp + t;
+ if (tn <= tc) {
+ SendBYEPacket(e);
+ exit(1);
+ } else {
+ Schedule(tn, e);
+ }
+
+ } else if (TypeOfEvent(e) == EVENT_REPORT) {
+ t = rtcp_interval(members,
+ senders,
+ rtcp_bw,
+ we_sent,
+ *avg_rtcp_size,
+ *initial);
+ tn = *tp + t;
+ if (tn <= tc) {
+ SendRTCPReport(e);
+ *avg_rtcp_size = (1./16.)*SentPacketSize(e) +
+ (15./16.)*(*avg_rtcp_size);
+ *tp = tc;
+
+ /* We must redraw the interval. Don't reuse the
+
+
+
+Schulzrinne, et al. Standards Track [Page 91]
+
+RFC 3550 RTP July 2003
+
+
+ one computed above, since its not actually
+ distributed the same, as we are conditioned
+ on it being small enough to cause a packet to
+ be sent */
+
+ t = rtcp_interval(members,
+ senders,
+ rtcp_bw,
+ we_sent,
+ *avg_rtcp_size,
+ *initial);
+
+ Schedule(t+tc,e);
+ *initial = 0;
+ } else {
+ Schedule(tn, e);
+ }
+ *pmembers = members;
+ }
+ }
+
+ void OnReceive(packet p,
+ event e,
+ int *members,
+ int *pmembers,
+ int *senders,
+ double *avg_rtcp_size,
+ double *tp,
+ double tc,
+ double tn)
+ {
+ /* What we do depends on whether we have left the group, and are
+ * waiting to send a BYE (TypeOfEvent(e) == EVENT_BYE) or an RTCP
+ * report. p represents the packet that was just received. */
+
+ if (PacketType(p) == PACKET_RTCP_REPORT) {
+ if (NewMember(p) && (TypeOfEvent(e) == EVENT_REPORT)) {
+ AddMember(p);
+ *members += 1;
+ }
+ *avg_rtcp_size = (1./16.)*ReceivedPacketSize(p) +
+ (15./16.)*(*avg_rtcp_size);
+ } else if (PacketType(p) == PACKET_RTP) {
+ if (NewMember(p) && (TypeOfEvent(e) == EVENT_REPORT)) {
+ AddMember(p);
+ *members += 1;
+ }
+ if (NewSender(p) && (TypeOfEvent(e) == EVENT_REPORT)) {
+
+
+
+Schulzrinne, et al. Standards Track [Page 92]
+
+RFC 3550 RTP July 2003
+
+
+ AddSender(p);
+ *senders += 1;
+ }
+ } else if (PacketType(p) == PACKET_BYE) {
+ *avg_rtcp_size = (1./16.)*ReceivedPacketSize(p) +
+ (15./16.)*(*avg_rtcp_size);
+
+ if (TypeOfEvent(e) == EVENT_REPORT) {
+ if (NewSender(p) == FALSE) {
+ RemoveSender(p);
+ *senders -= 1;
+ }
+
+ if (NewMember(p) == FALSE) {
+ RemoveMember(p);
+ *members -= 1;
+ }
+
+ if (*members < *pmembers) {
+ tn = tc +
+ (((double) *members)/(*pmembers))*(tn - tc);
+ *tp = tc -
+ (((double) *members)/(*pmembers))*(tc - *tp);
+
+ /* Reschedule the next report for time tn */
+
+ Reschedule(tn, e);
+ *pmembers = *members;
+ }
+
+ } else if (TypeOfEvent(e) == EVENT_BYE) {
+ *members += 1;
+ }
+ }
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 93]
+
+RFC 3550 RTP July 2003
+
+
+A.8 Estimating the Interarrival Jitter
+
+ The code fragments below implement the algorithm given in Section
+ 6.4.1 for calculating an estimate of the statistical variance of the
+ RTP data interarrival time to be inserted in the interarrival jitter
+ field of reception reports. The inputs are r->ts, the timestamp from
+ the incoming packet, and arrival, the current time in the same units.
+ Here s points to state for the source; s->transit holds the relative
+ transit time for the previous packet, and s->jitter holds the
+ estimated jitter. The jitter field of the reception report is
+ measured in timestamp units and expressed as an unsigned integer, but
+ the jitter estimate is kept in a floating point. As each data packet
+ arrives, the jitter estimate is updated:
+
+ int transit = arrival - r->ts;
+ int d = transit - s->transit;
+ s->transit = transit;
+ if (d < 0) d = -d;
+ s->jitter += (1./16.) * ((double)d - s->jitter);
+
+ When a reception report block (to which rr points) is generated for
+ this member, the current jitter estimate is returned:
+
+ rr->jitter = (u_int32) s->jitter;
+
+ Alternatively, the jitter estimate can be kept as an integer, but
+ scaled to reduce round-off error. The calculation is the same except
+ for the last line:
+
+ s->jitter += d - ((s->jitter + 8) >> 4);
+
+ In this case, the estimate is sampled for the reception report as:
+
+ rr->jitter = s->jitter >> 4;
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 94]
+
+RFC 3550 RTP July 2003
+
+
+Appendix B - Changes from RFC 1889
+
+ Most of this RFC is identical to RFC 1889. There are no changes in
+ the packet formats on the wire, only changes to the rules and
+ algorithms governing how the protocol is used. The biggest change is
+ an enhancement to the scalable timer algorithm for calculating when
+ to send RTCP packets:
+
+ o The algorithm for calculating the RTCP transmission interval
+ specified in Sections 6.2 and 6.3 and illustrated in Appendix A.7
+ is augmented to include "reconsideration" to minimize transmission
+ in excess of the intended rate when many participants join a
+ session simultaneously, and "reverse reconsideration" to reduce
+ the incidence and duration of false participant timeouts when the
+ number of participants drops rapidly. Reverse reconsideration is
+ also used to possibly shorten the delay before sending RTCP SR
+ when transitioning from passive receiver to active sender mode.
+
+ o Section 6.3.7 specifies new rules controlling when an RTCP BYE
+ packet should be sent in order to avoid a flood of packets when
+ many participants leave a session simultaneously.
+
+ o The requirement to retain state for inactive participants for a
+ period long enough to span typical network partitions was removed
+ from Section 6.2.1. In a session where many participants join for
+ a brief time and fail to send BYE, this requirement would cause a
+ significant overestimate of the number of participants. The
+ reconsideration algorithm added in this revision compensates for
+ the large number of new participants joining simultaneously when a
+ partition heals.
+
+ It should be noted that these enhancements only have a significant
+ effect when the number of session participants is large (thousands)
+ and most of the participants join or leave at the same time. This
+ makes testing in a live network difficult. However, the algorithm
+ was subjected to a thorough analysis and simulation to verify its
+ performance. Furthermore, the enhanced algorithm was designed to
+ interoperate with the algorithm in RFC 1889 such that the degree of
+ reduction in excess RTCP bandwidth during a step join is proportional
+ to the fraction of participants that implement the enhanced
+ algorithm. Interoperation of the two algorithms has been verified
+ experimentally on live networks.
+
+ Other functional changes were:
+
+ o Section 6.2.1 specifies that implementations may store only a
+ sampling of the participants' SSRC identifiers to allow scaling to
+ very large sessions. Algorithms are specified in RFC 2762 [21].
+
+
+
+Schulzrinne, et al. Standards Track [Page 95]
+
+RFC 3550 RTP July 2003
+
+
+ o In Section 6.2 it is specified that RTCP sender and non-sender
+ bandwidths may be set as separate parameters of the session rather
+ than a strict percentage of the session bandwidth, and may be set
+ to zero. The requirement that RTCP was mandatory for RTP sessions
+ using IP multicast was relaxed. However, a clarification was also
+ added that turning off RTCP is NOT RECOMMENDED.
+
+ o In Sections 6.2, 6.3.1 and Appendix A.7, it is specified that the
+ fraction of participants below which senders get dedicated RTCP
+ bandwidth changes from the fixed 1/4 to a ratio based on the RTCP
+ sender and non-sender bandwidth parameters when those are given.
+ The condition that no bandwidth is dedicated to senders when there
+ are no senders was removed since that is expected to be a
+ transitory state. It also keeps non-senders from using sender
+ RTCP bandwidth when that is not intended.
+
+ o Also in Section 6.2 it is specified that the minimum RTCP interval
+ may be scaled to smaller values for high bandwidth sessions, and
+ that the initial RTCP delay may be set to zero for unicast
+ sessions.
+
+ o Timing out a participant is to be based on inactivity for a number
+ of RTCP report intervals calculated using the receiver RTCP
+ bandwidth fraction even for active senders.
+
+ o Sections 7.2 and 7.3 specify that translators and mixers should
+ send BYE packets for the sources they are no longer forwarding.
+
+ o Rule changes for layered encodings are defined in Sections 2.4,
+ 6.3.9, 8.3 and 11. In the last of these, it is noted that the
+ address and port assignment rule conflicts with the SDP
+ specification, RFC 2327 [15], but it is intended that this
+ restriction will be relaxed in a revision of RFC 2327.
+
+ o The convention for using even/odd port pairs for RTP and RTCP in
+ Section 11 was clarified to refer to destination ports. The
+ requirement to use an even/odd port pair was removed if the two
+ ports are specified explicitly. For unicast RTP sessions,
+ distinct port pairs may be used for the two ends (Sections 3, 7.1
+ and 11).
+
+ o A new Section 10 was added to explain the requirement for
+ congestion control in applications using RTP.
+
+ o In Section 8.2, the requirement that a new SSRC identifier MUST be
+ chosen whenever the source transport address is changed has been
+ relaxed to say that a new SSRC identifier MAY be chosen.
+ Correspondingly, it was clarified that an implementation MAY
+
+
+
+Schulzrinne, et al. Standards Track [Page 96]
+
+RFC 3550 RTP July 2003
+
+
+ choose to keep packets from the new source address rather than the
+ existing source address when an SSRC collision occurs between two
+ other participants, and SHOULD do so for applications such as
+ telephony in which some sources such as mobile entities may change
+ addresses during the course of an RTP session.
+
+ o An indentation bug in the RFC 1889 printing of the pseudo-code for
+ the collision detection and resolution algorithm in Section 8.2
+ has been corrected by translating the syntax to pseudo C language,
+ and the algorithm has been modified to remove the restriction that
+ both RTP and RTCP must be sent from the same source port number.
+
+ o The description of the padding mechanism for RTCP packets was
+ clarified and it is specified that padding MUST only be applied to
+ the last packet of a compound RTCP packet.
+
+ o In Section A.1, initialization of base_seq was corrected to be seq
+ rather than seq - 1, and the text was corrected to say the bad
+ sequence number plus 1 is stored. The initialization of max_seq
+ and other variables for the algorithm was separated from the text
+ to make clear that this initialization must be done in addition to
+ calling the init_seq() function (and a few words lost in RFC 1889
+ when processing the document from source to output form were
+ restored).
+
+ o Clamping of number of packets lost in Section A.3 was corrected to
+ use both positive and negative limits.
+
+ o The specification of "relative" NTP timestamp in the RTCP SR
+ section now defines these timestamps to be based on the most
+ common system-specific clock, such as system uptime, rather than
+ on session elapsed time which would not be the same for multiple
+ applications started on the same machine at different times.
+
+ Non-functional changes:
+
+ o It is specified that a receiver MUST ignore packets with payload
+ types it does not understand.
+
+ o In Fig. 2, the floating point NTP timestamp value was corrected,
+ some missing leading zeros were added in a hex number, and the UTC
+ timezone was specified.
+
+ o The inconsequence of NTP timestamps wrapping around in the year
+ 2036 is explained.
+
+
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 97]
+
+RFC 3550 RTP July 2003
+
+
+ o The policy for registration of RTCP packet types and SDES types
+ was clarified in a new Section 15, IANA Considerations. The
+ suggestion that experimenters register the numbers they need and
+ then unregister those which prove to be unneeded has been removed
+ in favor of using APP and PRIV. Registration of profile names was
+ also specified.
+
+ o The reference for the UTF-8 character set was changed from an
+ X/Open Preliminary Specification to be RFC 2279.
+
+ o The reference for RFC 1597 was updated to RFC 1918 and the
+ reference for RFC 2543 was updated to RFC 3261.
+
+ o The last paragraph of the introduction in RFC 1889, which
+ cautioned implementors to limit deployment in the Internet, was
+ removed because it was deemed no longer relevant.
+
+ o A non-normative note regarding the use of RTP with Source-Specific
+ Multicast (SSM) was added in Section 6.
+
+ o The definition of "RTP session" in Section 3 was expanded to
+ acknowledge that a single session may use multiple destination
+ transport addresses (as was always the case for a translator or
+ mixer) and to explain that the distinguishing feature of an RTP
+ session is that each corresponds to a separate SSRC identifier
+ space. A new definition of "multimedia session" was added to
+ reduce confusion about the word "session".
+
+ o The meaning of "sampling instant" was explained in more detail as
+ part of the definition of the timestamp field of the RTP header in
+ Section 5.1.
+
+ o Small clarifications of the text have been made in several places,
+ some in response to questions from readers. In particular:
+
+ - In RFC 1889, the first five words of the second sentence of
+ Section 2.2 were lost in processing the document from source to
+ output form, but are now restored.
+
+ - A definition for "RTP media type" was added in Section 3 to
+ allow the explanation of multiplexing RTP sessions in Section
+ 5.2 to be more clear regarding the multiplexing of multiple
+ media. That section also now explains that multiplexing
+ multiple sources of the same medium based on SSRC identifiers
+ may be appropriate and is the norm for multicast sessions.
+
+ - The definition for "non-RTP means" was expanded to include
+ examples of other protocols constituting non-RTP means.
+
+
+
+Schulzrinne, et al. Standards Track [Page 98]
+
+RFC 3550 RTP July 2003
+
+
+ - The description of the session bandwidth parameter is expanded
+ in Section 6.2, including a clarification that the control
+ traffic bandwidth is in addition to the session bandwidth for
+ the data traffic.
+
+ - The effect of varying packet duration on the jitter calculation
+ was explained in Section 6.4.4.
+
+ - The method for terminating and padding a sequence of SDES items
+ was clarified in Section 6.5.
+
+ - IPv6 address examples were added in the description of SDES
+ CNAME in Section 6.5.1, and "example.com" was used in place of
+ other example domain names.
+
+ - The Security section added a formal reference to IPSEC now that
+ it is available, and says that the confidentiality method
+ defined in this specification is primarily to codify existing
+ practice. It is RECOMMENDED that stronger encryption
+ algorithms such as Triple-DES be used in place of the default
+ algorithm, and noted that the SRTP profile based on AES will be
+ the correct choice in the future. A caution about the weakness
+ of the RTP header as an initialization vector was added. It
+ was also noted that payload-only encryption is necessary to
+ allow for header compression.
+
+ - The method for partial encryption of RTCP was clarified; in
+ particular, SDES CNAME is carried in only one part when the
+ compound RTCP packet is split.
+
+ - It is clarified that only one compound RTCP packet should be
+ sent per reporting interval and that if there are too many
+ active sources for the reports to fit in the MTU, then a subset
+ of the sources should be selected round-robin over multiple
+ intervals.
+
+ - A note was added in Appendix A.1 that packets may be saved
+ during RTP header validation and delivered upon success.
+
+ - Section 7.3 now explains that a mixer aggregating SDES packets
+ uses more RTCP bandwidth due to longer packets, and a mixer
+ passing through RTCP naturally sends packets at higher than the
+ single source rate, but both behaviors are valid.
+
+ - Section 13 clarifies that an RTP application may use multiple
+ profiles but typically only one in a given session.
+
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 99]
+
+RFC 3550 RTP July 2003
+
+
+ - The terms MUST, SHOULD, MAY, etc. are used as defined in RFC
+ 2119.
+
+ - The bibliography was divided into normative and informative
+ references.
+
+References
+
+Normative References
+
+ [1] Schulzrinne, H. and S. Casner, "RTP Profile for Audio and Video
+ Conferences with Minimal Control", RFC 3551, July 2003.
+
+ [2] Bradner, S., "Key Words for Use in RFCs to Indicate Requirement
+ Levels", BCP 14, RFC 2119, March 1997.
+
+ [3] Postel, J., "Internet Protocol", STD 5, RFC 791, September 1981.
+
+ [4] Mills, D., "Network Time Protocol (Version 3) Specification,
+ Implementation and Analysis", RFC 1305, March 1992.
+
+ [5] Yergeau, F., "UTF-8, a Transformation Format of ISO 10646", RFC
+ 2279, January 1998.
+
+ [6] Mockapetris, P., "Domain Names - Concepts and Facilities", STD
+ 13, RFC 1034, November 1987.
+
+ [7] Mockapetris, P., "Domain Names - Implementation and
+ Specification", STD 13, RFC 1035, November 1987.
+
+ [8] Braden, R., "Requirements for Internet Hosts - Application and
+ Support", STD 3, RFC 1123, October 1989.
+
+ [9] Resnick, P., "Internet Message Format", RFC 2822, April 2001.
+
+Informative References
+
+ [10] Clark, D. and D. Tennenhouse, "Architectural Considerations for
+ a New Generation of Protocols," in SIGCOMM Symposium on
+ Communications Architectures and Protocols , (Philadelphia,
+ Pennsylvania), pp. 200--208, IEEE Computer Communications
+ Review, Vol. 20(4), September 1990.
+
+ [11] Schulzrinne, H., "Issues in designing a transport protocol for
+ audio and video conferences and other multiparticipant real-time
+ applications." expired Internet Draft, October 1993.
+
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 100]
+
+RFC 3550 RTP July 2003
+
+
+ [12] Comer, D., Internetworking with TCP/IP , vol. 1. Englewood
+ Cliffs, New Jersey: Prentice Hall, 1991.
+
+ [13] Rosenberg, J., Schulzrinne, H., Camarillo, G., Johnston, A.,
+ Peterson, J., Sparks, R., Handley, M. and E. Schooler, "SIP:
+ Session Initiation Protocol", RFC 3261, June 2002.
+
+ [14] International Telecommunication Union, "Visual telephone systems
+ and equipment for local area networks which provide a non-
+ guaranteed quality of service", Recommendation H.323,
+ Telecommunication Standardization Sector of ITU, Geneva,
+ Switzerland, July 2003.
+
+ [15] Handley, M. and V. Jacobson, "SDP: Session Description
+ Protocol", RFC 2327, April 1998.
+
+ [16] Schulzrinne, H., Rao, A. and R. Lanphier, "Real Time Streaming
+ Protocol (RTSP)", RFC 2326, April 1998.
+
+ [17] Eastlake 3rd, D., Crocker, S. and J. Schiller, "Randomness
+ Recommendations for Security", RFC 1750, December 1994.
+
+ [18] Bolot, J.-C., Turletti, T. and I. Wakeman, "Scalable Feedback
+ Control for Multicast Video Distribution in the Internet", in
+ SIGCOMM Symposium on Communications Architectures and Protocols,
+ (London, England), pp. 58--67, ACM, August 1994.
+
+ [19] Busse, I., Deffner, B. and H. Schulzrinne, "Dynamic QoS Control
+ of Multimedia Applications Based on RTP", Computer
+ Communications , vol. 19, pp. 49--58, January 1996.
+
+ [20] Floyd, S. and V. Jacobson, "The Synchronization of Periodic
+ Routing Messages", in SIGCOMM Symposium on Communications
+ Architectures and Protocols (D. P. Sidhu, ed.), (San Francisco,
+ California), pp. 33--44, ACM, September 1993. Also in [34].
+
+ [21] Rosenberg, J. and H. Schulzrinne, "Sampling of the Group
+ Membership in RTP", RFC 2762, February 2000.
+
+ [22] Cadzow, J., Foundations of Digital Signal Processing and Data
+ Analysis New York, New York: Macmillan, 1987.
+
+ [23] Hinden, R. and S. Deering, "Internet Protocol Version 6 (IPv6)
+ Addressing Architecture", RFC 3513, April 2003.
+
+ [24] Rekhter, Y., Moskowitz, B., Karrenberg, D., de Groot, G. and E.
+ Lear, "Address Allocation for Private Internets", RFC 1918,
+ February 1996.
+
+
+
+Schulzrinne, et al. Standards Track [Page 101]
+
+RFC 3550 RTP July 2003
+
+
+ [25] Lear, E., Fair, E., Crocker, D. and T. Kessler, "Network 10
+ Considered Harmful (Some Practices Shouldn't be Codified)", RFC
+ 1627, July 1994.
+
+ [26] Feller, W., An Introduction to Probability Theory and its
+ Applications, vol. 1. New York, New York: John Wiley and Sons,
+ third ed., 1968.
+
+ [27] Kent, S. and R. Atkinson, "Security Architecture for the
+ Internet Protocol", RFC 2401, November 1998.
+
+ [28] Baugher, M., Blom, R., Carrara, E., McGrew, D., Naslund, M.,
+ Norrman, K. and D. Oran, "Secure Real-time Transport Protocol",
+ Work in Progress, April 2003.
+
+ [29] Balenson, D., "Privacy Enhancement for Internet Electronic Mail:
+ Part III", RFC 1423, February 1993.
+
+ [30] Voydock, V. and S. Kent, "Security Mechanisms in High-Level
+ Network Protocols", ACM Computing Surveys, vol. 15, pp. 135-171,
+ June 1983.
+
+ [31] Floyd, S., "Congestion Control Principles", BCP 41, RFC 2914,
+ September 2000.
+
+ [32] Rivest, R., "The MD5 Message-Digest Algorithm", RFC 1321, April
+ 1992.
+
+ [33] Stubblebine, S., "Security Services for Multimedia
+ Conferencing", in 16th National Computer Security Conference,
+ (Baltimore, Maryland), pp. 391--395, September 1993.
+
+ [34] Floyd, S. and V. Jacobson, "The Synchronization of Periodic
+ Routing Messages", IEEE/ACM Transactions on Networking, vol. 2,
+ pp. 122--136, April 1994.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 102]
+
+RFC 3550 RTP July 2003
+
+
+Authors' Addresses
+
+ Henning Schulzrinne
+ Department of Computer Science
+ Columbia University
+ 1214 Amsterdam Avenue
+ New York, NY 10027
+ United States
+
+ EMail: schulzrinne@cs.columbia.edu
+
+
+ Stephen L. Casner
+ Packet Design
+ 3400 Hillview Avenue, Building 3
+ Palo Alto, CA 94304
+ United States
+
+ EMail: casner@acm.org
+
+
+ Ron Frederick
+ Blue Coat Systems Inc.
+ 650 Almanor Avenue
+ Sunnyvale, CA 94085
+ United States
+
+ EMail: ronf@bluecoat.com
+
+
+ Van Jacobson
+ Packet Design
+ 3400 Hillview Avenue, Building 3
+ Palo Alto, CA 94304
+ United States
+
+ EMail: van@packetdesign.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 103]
+
+RFC 3550 RTP July 2003
+
+
+Full Copyright Statement
+
+ Copyright (C) The Internet Society (2003). All Rights Reserved.
+
+ This document and translations of it may be copied and furnished to
+ others, and derivative works that comment on or otherwise explain it
+ or assist in its implementation may be prepared, copied, published
+ and distributed, in whole or in part, without restriction of any
+ kind, provided that the above copyright notice and this paragraph are
+ included on all such copies and derivative works. However, this
+ document itself may not be modified in any way, such as by removing
+ the copyright notice or references to the Internet Society or other
+ Internet organizations, except as needed for the purpose of
+ developing Internet standards in which case the procedures for
+ copyrights defined in the Internet Standards process must be
+ followed, or as required to translate it into languages other than
+ English.
+
+ The limited permissions granted above are perpetual and will not be
+ revoked by the Internet Society or its successors or assigns.
+
+ This document and the information contained herein is provided on an
+ "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING
+ TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING
+ BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION
+ HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF
+ MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
+
+Acknowledgement
+
+ Funding for the RFC Editor function is currently provided by the
+ Internet Society.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Schulzrinne, et al. Standards Track [Page 104]
+
diff --git a/src/modules/rtp/rfc3551.txt b/src/modules/rtp/rfc3551.txt
new file mode 100644
index 00000000..c43ff34d
--- /dev/null
+++ b/src/modules/rtp/rfc3551.txt
@@ -0,0 +1,2467 @@
+
+
+
+
+
+
+Network Working Group H. Schulzrinne
+Request for Comments: 3551 Columbia University
+Obsoletes: 1890 S. Casner
+Category: Standards Track Packet Design
+ July 2003
+
+
+ RTP Profile for Audio and Video Conferences
+ with Minimal Control
+
+Status of this Memo
+
+ This document specifies an Internet standards track protocol for the
+ Internet community, and requests discussion and suggestions for
+ improvements. Please refer to the current edition of the "Internet
+ Official Protocol Standards" (STD 1) for the standardization state
+ and status of this protocol. Distribution of this memo is unlimited.
+
+Copyright Notice
+
+ Copyright (C) The Internet Society (2003). All Rights Reserved.
+
+Abstract
+
+ This document describes a profile called "RTP/AVP" for the use of the
+ real-time transport protocol (RTP), version 2, and the associated
+ control protocol, RTCP, within audio and video multiparticipant
+ conferences with minimal control. It provides interpretations of
+ generic fields within the RTP specification suitable for audio and
+ video conferences. In particular, this document defines a set of
+ default mappings from payload type numbers to encodings.
+
+ This document also describes how audio and video data may be carried
+ within RTP. It defines a set of standard encodings and their names
+ when used within RTP. The descriptions provide pointers to reference
+ implementations and the detailed standards. This document is meant
+ as an aid for implementors of audio, video and other real-time
+ multimedia applications.
+
+ This memorandum obsoletes RFC 1890. It is mostly backwards-
+ compatible except for functions removed because two interoperable
+ implementations were not found. The additions to RFC 1890 codify
+ existing practice in the use of payload formats under this profile
+ and include new payload formats defined since RFC 1890 was published.
+
+
+
+
+
+
+
+Schulzrinne & Casner Standards Track [Page 1]
+
+RFC 3551 RTP A/V Profile July 2003
+
+
+Table of Contents
+
+ 1. Introduction ................................................. 3
+ 1.1 Terminology ............................................. 3
+ 2. RTP and RTCP Packet Forms and Protocol Behavior .............. 4
+ 3. Registering Additional Encodings ............................. 6
+ 4. Audio ........................................................ 8
+ 4.1 Encoding-Independent Rules .............................. 8
+ 4.2 Operating Recommendations ............................... 9
+ 4.3 Guidelines for Sample-Based Audio Encodings ............. 10
+ 4.4 Guidelines for Frame-Based Audio Encodings .............. 11
+ 4.5 Audio Encodings ......................................... 12
+ 4.5.1 DVI4 ............................................ 13
+ 4.5.2 G722 ............................................ 14
+ 4.5.3 G723 ............................................ 14
+ 4.5.4 G726-40, G726-32, G726-24, and G726-16 .......... 18
+ 4.5.5 G728 ............................................ 19
+ 4.5.6 G729 ............................................ 20
+ 4.5.7 G729D and G729E ................................. 22
+ 4.5.8 GSM ............................................. 24
+ 4.5.9 GSM-EFR ......................................... 27
+ 4.5.10 L8 .............................................. 27
+ 4.5.11 L16 ............................................. 27
+ 4.5.12 LPC ............................................. 27
+ 4.5.13 MPA ............................................. 28
+ 4.5.14 PCMA and PCMU ................................... 28
+ 4.5.15 QCELP ........................................... 28
+ 4.5.16 RED ............................................. 29
+ 4.5.17 VDVI ............................................ 29
+ 5. Video ........................................................ 30
+ 5.1 CelB .................................................... 30
+ 5.2 JPEG .................................................... 30
+ 5.3 H261 .................................................... 30
+ 5.4 H263 .................................................... 31
+ 5.5 H263-1998 ............................................... 31
+ 5.6 MPV ..................................................... 31
+ 5.7 MP2T .................................................... 31
+ 5.8 nv ...................................................... 32
+ 6. Payload Type Definitions ..................................... 32
+ 7. RTP over TCP and Similar Byte Stream Protocols ............... 34
+ 8. Port Assignment .............................................. 34
+ 9. Changes from RFC 1890 ........................................ 35
+ 10. Security Considerations ...................................... 38
+ 11. IANA Considerations .......................................... 39
+ 12. References ................................................... 39
+ 12.1 Normative References .................................... 39
+ 12.2 Informative References .................................. 39
+ 13. Current Locations of Related Resources ....................... 41
+
+
+
+Schulzrinne & Casner Standards Track [Page 2]
+
+RFC 3551 RTP A/V Profile July 2003
+
+
+ 14. Acknowledgments .............................................. 42
+ 15. Intellectual Property Rights Statement ....................... 43
+ 16. Authors' Addresses ........................................... 43
+ 17. Full Copyright Statement ..................................... 44
+
+1. Introduction
+
+ This profile defines aspects of RTP left unspecified in the RTP
+ Version 2 protocol definition (RFC 3550) [1]. This profile is
+ intended for the use within audio and video conferences with minimal
+ session control. In particular, no support for the negotiation of
+ parameters or membership control is provided. The profile is
+ expected to be useful in sessions where no negotiation or membership
+ control are used (e.g., using the static payload types and the
+ membership indications provided by RTCP), but this profile may also
+ be useful in conjunction with a higher-level control protocol.
+
+ Use of this profile may be implicit in the use of the appropriate
+ applications; there may be no explicit indication by port number,
+ protocol identifier or the like. Applications such as session
+ directories may use the name for this profile specified in Section
+ 11.
+
+ Other profiles may make different choices for the items specified
+ here.
+
+ This document also defines a set of encodings and payload formats for
+ audio and video. These payload format descriptions are included here
+ only as a matter of convenience since they are too small to warrant
+ separate documents. Use of these payload formats is NOT REQUIRED to
+ use this profile. Only the binding of some of the payload formats to
+ static payload type numbers in Tables 4 and 5 is normative.
+
+1.1 Terminology
+
+ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
+ "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
+ document are to be interpreted as described in RFC 2119 [2] and
+ indicate requirement levels for implementations compliant with this
+ RTP profile.
+
+ This document defines the term media type as dividing encodings of
+ audio and video content into three classes: audio, video and
+ audio/video (interleaved).
+
+
+
+
+
+
+
+Schulzrinne & Casner Standards Track [Page 3]
+
+RFC 3551 RTP A/V Profile July 2003
+
+
+2. RTP and RTCP Packet Forms and Protocol Behavior
+
+ The section "RTP Profiles and Payload Format Specifications" of RFC
+ 3550 enumerates a number of items that can be specified or modified
+ in a profile. This section addresses these items. Generally, this
+ profile follows the default and/or recommended aspects of the RTP
+ specification.
+
+ RTP data header: The standard format of the fixed RTP data
+ header is used (one marker bit).
+
+ Payload types: Static payload types are defined in Section 6.
+
+ RTP data header additions: No additional fixed fields are
+ appended to the RTP data header.
+
+ RTP data header extensions: No RTP header extensions are
+ defined, but applications operating under this profile MAY use
+ such extensions. Thus, applications SHOULD NOT assume that the
+ RTP header X bit is always zero and SHOULD be prepared to ignore
+ the header extension. If a header extension is defined in the
+ future, that definition MUST specify the contents of the first 16
+ bits in such a way that multiple different extensions can be
+ identified.
+
+ RTCP packet types: No additional RTCP packet types are defined
+ by this profile specification.
+
+ RTCP report interval: The suggested constants are to be used for
+ the RTCP report interval calculation. Sessions operating under
+ this profile MAY specify a separate parameter for the RTCP traffic
+ bandwidth rather than using the default fraction of the session
+ bandwidth. The RTCP traffic bandwidth MAY be divided into two
+ separate session parameters for those participants which are
+ active data senders and those which are not. Following the
+ recommendation in the RTP specification [1] that 1/4 of the RTCP
+ bandwidth be dedicated to data senders, the RECOMMENDED default
+ values for these two parameters would be 1.25% and 3.75%,
+ respectively. For a particular session, the RTCP bandwidth for
+ non-data-senders MAY be set to zero when operating on
+ unidirectional links or for sessions that don't require feedback
+ on the quality of reception. The RTCP bandwidth for data senders
+ SHOULD be kept non-zero so that sender reports can still be sent
+ for inter-media synchronization and to identify the source by
+ CNAME. The means by which the one or two session parameters for
+ RTCP bandwidth are specified is beyond the scope of this memo.
+
+
+
+
+
+Schulzrinne & Casner Standards Track [Page 4]
+
+RFC 3551 RTP A/V Profile July 2003
+
+
+ SR/RR extension: No extension section is defined for the RTCP SR
+ or RR packet.
+
+ SDES use: Applications MAY use any of the SDES items described
+ in the RTP specification. While CNAME information MUST be sent
+ every reporting interval, other items SHOULD only be sent every
+ third reporting interval, with NAME sent seven out of eight times
+ within that slot and the remaining SDES items cyclically taking up
+ the eighth slot, as defined in Section 6.2.2 of the RTP
+ specification. In other words, NAME is sent in RTCP packets 1, 4,
+ 7, 10, 13, 16, 19, while, say, EMAIL is used in RTCP packet 22.
+
+ Security: The RTP default security services are also the default
+ under this profile.
+
+ String-to-key mapping: No mapping is specified by this profile.
+
+ Congestion: RTP and this profile may be used in the context of
+ enhanced network service, for example, through Integrated Services
+ (RFC 1633) [4] or Differentiated Services (RFC 2475) [5], or they
+ may be used with best effort service.
+
+ If enhanced service is being used, RTP receivers SHOULD monitor
+ packet loss to ensure that the service that was requested is
+ actually being delivered. If it is not, then they SHOULD assume
+ that they are receiving best-effort service and behave
+ accordingly.
+
+ If best-effort service is being used, RTP receivers SHOULD monitor
+ packet loss to ensure that the packet loss rate is within
+ acceptable parameters. Packet loss is considered acceptable if a
+ TCP flow across the same network path and experiencing the same
+ network conditions would achieve an average throughput, measured
+ on a reasonable timescale, that is not less than the RTP flow is
+ achieving. This condition can be satisfied by implementing
+ congestion control mechanisms to adapt the transmission rate (or
+ the number of layers subscribed for a layered multicast session),
+ or by arranging for a receiver to leave the session if the loss
+ rate is unacceptably high.
+
+ The comparison to TCP cannot be specified exactly, but is intended
+ as an "order-of-magnitude" comparison in timescale and throughput.
+ The timescale on which TCP throughput is measured is the round-
+ trip time of the connection. In essence, this requirement states
+ that it is not acceptable to deploy an application (using RTP or
+ any other transport protocol) on the best-effort Internet which
+ consumes bandwidth arbitrarily and does not compete fairly with
+ TCP within an order of magnitude.
+
+
+
+Schulzrinne & Casner Standards Track [Page 5]
+
+RFC 3551 RTP A/V Profile July 2003
+
+
+ Underlying protocol: The profile specifies the use of RTP over
+ unicast and multicast UDP as well as TCP. (This does not preclude
+ the use of these definitions when RTP is carried by other lower-
+ layer protocols.)
+
+ Transport mapping: The standard mapping of RTP and RTCP to
+ transport-level addresses is used.
+
+ Encapsulation: This profile leaves to applications the
+ specification of RTP encapsulation in protocols other than UDP.
+
+3. Registering Additional Encodings
+
+ This profile lists a set of encodings, each of which is comprised of
+ a particular media data compression or representation plus a payload
+ format for encapsulation within RTP. Some of those payload formats
+ are specified here, while others are specified in separate RFCs. It
+ is expected that additional encodings beyond the set listed here will
+ be created in the future and specified in additional payload format
+ RFCs.
+
+ This profile also assigns to each encoding a short name which MAY be
+ used by higher-level control protocols, such as the Session
+ Description Protocol (SDP), RFC 2327 [6], to identify encodings
+ selected for a particular RTP session.
+
+ In some contexts it may be useful to refer to these encodings in the
+ form of a MIME content-type. To facilitate this, RFC 3555 [7]
+ provides registrations for all of the encodings names listed here as
+ MIME subtype names under the "audio" and "video" MIME types through
+ the MIME registration procedure as specified in RFC 2048 [8].
+
+ Any additional encodings specified for use under this profile (or
+ others) may also be assigned names registered as MIME subtypes with
+ the Internet Assigned Numbers Authority (IANA). This registry
+ provides a means to insure that the names assigned to the additional
+ encodings are kept unique. RFC 3555 specifies the information that
+ is required for the registration of RTP encodings.
+
+ In addition to assigning names to encodings, this profile also
+ assigns static RTP payload type numbers to some of them. However,
+ the payload type number space is relatively small and cannot
+ accommodate assignments for all existing and future encodings.
+ During the early stages of RTP development, it was necessary to use
+ statically assigned payload types because no other mechanism had been
+ specified to bind encodings to payload types. It was anticipated
+ that non-RTP means beyond the scope of this memo (such as directory
+ services or invitation protocols) would be specified to establish a
+
+
+
+Schulzrinne & Casner Standards Track [Page 6]
+
+RFC 3551 RTP A/V Profile July 2003
+
+
+ dynamic mapping between a payload type and an encoding. Now,
+ mechanisms for defining dynamic payload type bindings have been
+ specified in the Session Description Protocol (SDP) and in other
+ protocols such as ITU-T Recommendation H.323/H.245. These mechanisms
+ associate the registered name of the encoding/payload format, along
+ with any additional required parameters, such as the RTP timestamp
+ clock rate and number of channels, with a payload type number. This
+ association is effective only for the duration of the RTP session in
+ which the dynamic payload type binding is made. This association
+ applies only to the RTP session for which it is made, thus the
+ numbers can be re-used for different encodings in different sessions
+ so the number space limitation is avoided.
+
+ This profile reserves payload type numbers in the range 96-127
+ exclusively for dynamic assignment. Applications SHOULD first use
+ values in this range for dynamic payload types. Those applications
+ which need to define more than 32 dynamic payload types MAY bind
+ codes below 96, in which case it is RECOMMENDED that unassigned
+ payload type numbers be used first. However, the statically assigned
+ payload types are default bindings and MAY be dynamically bound to
+ new encodings if needed. Redefining payload types below 96 may cause
+ incorrect operation if an attempt is made to join a session without
+ obtaining session description information that defines the dynamic
+ payload types.
+
+ Dynamic payload types SHOULD NOT be used without a well-defined
+ mechanism to indicate the mapping. Systems that expect to
+ interoperate with others operating under this profile SHOULD NOT make
+ their own assignments of proprietary encodings to particular, fixed
+ payload types.
+
+ This specification establishes the policy that no additional static
+ payload types will be assigned beyond the ones defined in this
+ document. Establishing this policy avoids the problem of trying to
+ create a set of criteria for accepting static assignments and
+ encourages the implementation and deployment of the dynamic payload
+ type mechanisms.
+
+ The final set of static payload type assignments is provided in
+ Tables 4 and 5.
+
+
+
+
+
+
+
+
+
+
+
+Schulzrinne & Casner Standards Track [Page 7]
+
+RFC 3551 RTP A/V Profile July 2003
+
+
+4. Audio
+
+4.1 Encoding-Independent Rules
+
+ Since the ability to suppress silence is one of the primary
+ motivations for using packets to transmit voice, the RTP header
+ carries both a sequence number and a timestamp to allow a receiver to
+ distinguish between lost packets and periods of time when no data was
+ transmitted. Discontiguous transmission (silence suppression) MAY be
+ used with any audio payload format. Receivers MUST assume that
+ senders may suppress silence unless this is restricted by signaling
+ specified elsewhere. (Even if the transmitter does not suppress
+ silence, the receiver should be prepared to handle periods when no
+ data is present since packets may be lost.)
+
+ Some payload formats (see Sections 4.5.3 and 4.5.6) define a "silence
+ insertion descriptor" or "comfort noise" frame to specify parameters
+ for artificial noise that may be generated during a period of silence
+ to approximate the background noise at the source. For other payload
+ formats, a generic Comfort Noise (CN) payload format is specified in
+ RFC 3389 [9]. When the CN payload format is used with another
+ payload format, different values in the RTP payload type field
+ distinguish comfort-noise packets from those of the selected payload
+ format.
+
+ For applications which send either no packets or occasional comfort-
+ noise packets during silence, the first packet of a talkspurt, that
+ is, the first packet after a silence period during which packets have
+ not been transmitted contiguously, SHOULD be distinguished by setting
+ the marker bit in the RTP data header to one. The marker bit in all
+ other packets is zero. The beginning of a talkspurt MAY be used to
+ adjust the playout delay to reflect changing network delays.
+ Applications without silence suppression MUST set the marker bit to
+ zero.
+
+ The RTP clock rate used for generating the RTP timestamp is
+ independent of the number of channels and the encoding; it usually
+ equals the number of sampling periods per second. For N-channel
+ encodings, each sampling period (say, 1/8,000 of a second) generates
+ N samples. (This terminology is standard, but somewhat confusing, as
+ the total number of samples generated per second is then the sampling
+ rate times the channel count.)
+
+ If multiple audio channels are used, channels are numbered left-to-
+ right, starting at one. In RTP audio packets, information from
+ lower-numbered channels precedes that from higher-numbered channels.
+
+
+
+
+
+Schulzrinne & Casner Standards Track [Page 8]
+
+RFC 3551 RTP A/V Profile July 2003
+
+
+ For more than two channels, the convention followed by the AIFF-C
+ audio interchange format SHOULD be followed [3], using the following
+ notation, unless some other convention is specified for a particular
+ encoding or payload format:
+
+ l left
+ r right
+ c center
+ S surround
+ F front
+ R rear
+
+ channels description channel
+ 1 2 3 4 5 6
+ _________________________________________________
+ 2 stereo l r
+ 3 l r c
+ 4 l c r S
+ 5 Fl Fr Fc Sl Sr
+ 6 l lc c r rc S
+
+ Note: RFC 1890 defined two conventions for the ordering of four
+ audio channels. Since the ordering is indicated implicitly by
+ the number of channels, this was ambiguous. In this revision,
+ the order described as "quadrophonic" has been eliminated to
+ remove the ambiguity. This choice was based on the observation
+ that quadrophonic consumer audio format did not become popular
+ whereas surround-sound subsequently has.
+
+ Samples for all channels belonging to a single sampling instant MUST
+ be within the same packet. The interleaving of samples from
+ different channels depends on the encoding. General guidelines are
+ given in Section 4.3 and 4.4.
+
+ The sampling frequency SHOULD be drawn from the set: 8,000, 11,025,
+ 16,000, 22,050, 24,000, 32,000, 44,100 and 48,000 Hz. (Older Apple
+ Macintosh computers had a native sample rate of 22,254.54 Hz, which
+ can be converted to 22,050 with acceptable quality by dropping 4
+ samples in a 20 ms frame.) However, most audio encodings are defined
+ for a more restricted set of sampling frequencies. Receivers SHOULD
+ be prepared to accept multi-channel audio, but MAY choose to only
+ play a single channel.
+
+4.2 Operating Recommendations
+
+ The following recommendations are default operating parameters.
+ Applications SHOULD be prepared to handle other values. The ranges
+ given are meant to give guidance to application writers, allowing a
+
+
+
+Schulzrinne & Casner Standards Track [Page 9]
+
+RFC 3551 RTP A/V Profile July 2003
+
+
+ set of applications conforming to these guidelines to interoperate
+ without additional negotiation. These guidelines are not intended to
+ restrict operating parameters for applications that can negotiate a
+ set of interoperable parameters, e.g., through a conference control
+ protocol.
+
+ For packetized audio, the default packetization interval SHOULD have
+ a duration of 20 ms or one frame, whichever is longer, unless
+ otherwise noted in Table 1 (column "ms/packet"). The packetization
+ interval determines the minimum end-to-end delay; longer packets
+ introduce less header overhead but higher delay and make packet loss
+ more noticeable. For non-interactive applications such as lectures
+ or for links with severe bandwidth constraints, a higher
+ packetization delay MAY be used. A receiver SHOULD accept packets
+ representing between 0 and 200 ms of audio data. (For framed audio
+ encodings, a receiver SHOULD accept packets with a number of frames
+ equal to 200 ms divided by the frame duration, rounded up.) This
+ restriction allows reasonable buffer sizing for the receiver.
+
+4.3 Guidelines for Sample-Based Audio Encodings
+
+ In sample-based encodings, each audio sample is represented by a
+ fixed number of bits. Within the compressed audio data, codes for
+ individual samples may span octet boundaries. An RTP audio packet
+ may contain any number of audio samples, subject to the constraint
+ that the number of bits per sample times the number of samples per
+ packet yields an integral octet count. Fractional encodings produce
+ less than one octet per sample.
+
+ The duration of an audio packet is determined by the number of
+ samples in the packet.
+
+ For sample-based encodings producing one or more octets per sample,
+ samples from different channels sampled at the same sampling instant
+ SHOULD be packed in consecutive octets. For example, for a two-
+ channel encoding, the octet sequence is (left channel, first sample),
+ (right channel, first sample), (left channel, second sample), (right
+ channel, second sample), .... For multi-octet encodings, octets
+ SHOULD be transmitted in network byte order (i.e., most significant
+ octet first).
+
+ The packing of sample-based encodings producing less than one octet
+ per sample is encoding-specific.
+
+ The RTP timestamp reflects the instant at which the first sample in
+ the packet was sampled, that is, the oldest information in the
+ packet.
+
+
+
+
+Schulzrinne & Casner Standards Track [Page 10]
+
+RFC 3551 RTP A/V Profile July 2003
+
+
+4.4 Guidelines for Frame-Based Audio Encodings
+
+ Frame-based encodings encode a fixed-length block of audio into
+ another block of compressed data, typically also of fixed length.
+ For frame-based encodings, the sender MAY choose to combine several
+ such frames into a single RTP packet. The receiver can tell the
+ number of frames contained in an RTP packet, if all the frames have
+ the same length, by dividing the RTP payload length by the audio
+ frame size which is defined as part of the encoding. This does not
+ work when carrying frames of different sizes unless the frame sizes
+ are relatively prime. If not, the frames MUST indicate their size.
+
+ For frame-based codecs, the channel order is defined for the whole
+ block. That is, for two-channel audio, right and left samples SHOULD
+ be coded independently, with the encoded frame for the left channel
+ preceding that for the right channel.
+
+ All frame-oriented audio codecs SHOULD be able to encode and decode
+ several consecutive frames within a single packet. Since the frame
+ size for the frame-oriented codecs is given, there is no need to use
+ a separate designation for the same encoding, but with different
+ number of frames per packet.
+
+ RTP packets SHALL contain a whole number of frames, with frames
+ inserted according to age within a packet, so that the oldest frame
+ (to be played first) occurs immediately after the RTP packet header.
+ The RTP timestamp reflects the instant at which the first sample in
+ the first frame was sampled, that is, the oldest information in the
+ packet.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Schulzrinne & Casner Standards Track [Page 11]
+
+RFC 3551 RTP A/V Profile July 2003
+
+
+4.5 Audio Encodings
+
+ name of sampling default
+ encoding sample/frame bits/sample rate ms/frame ms/packet
+ __________________________________________________________________
+ DVI4 sample 4 var. 20
+ G722 sample 8 16,000 20
+ G723 frame N/A 8,000 30 30
+ G726-40 sample 5 8,000 20
+ G726-32 sample 4 8,000 20
+ G726-24 sample 3 8,000 20
+ G726-16 sample 2 8,000 20
+ G728 frame N/A 8,000 2.5 20
+ G729 frame N/A 8,000 10 20
+ G729D frame N/A 8,000 10 20
+ G729E frame N/A 8,000 10 20
+ GSM frame N/A 8,000 20 20
+ GSM-EFR frame N/A 8,000 20 20
+ L8 sample 8 var. 20
+ L16 sample 16 var. 20
+ LPC frame N/A 8,000 20 20
+ MPA frame N/A var. var.
+ PCMA sample 8 var. 20
+ PCMU sample 8 var. 20
+ QCELP frame N/A 8,000 20 20
+ VDVI sample var. var. 20
+
+ Table 1: Properties of Audio Encodings (N/A: not applicable; var.:
+ variable)
+
+ The characteristics of the audio encodings described in this document
+ are shown in Table 1; they are listed in order of their payload type
+ in Table 4. While most audio codecs are only specified for a fixed
+ sampling rate, some sample-based algorithms (indicated by an entry of
+ "var." in the sampling rate column of Table 1) may be used with
+ different sampling rates, resulting in different coded bit rates.
+ When used with a sampling rate other than that for which a static
+ payload type is defined, non-RTP means beyond the scope of this memo
+ MUST be used to define a dynamic payload type and MUST indicate the
+ selected RTP timestamp clock rate, which is usually the same as the
+ sampling rate for audio.
+
+
+
+
+
+
+
+
+
+
+Schulzrinne & Casner Standards Track [Page 12]
+
+RFC 3551 RTP A/V Profile July 2003
+
+
+4.5.1 DVI4
+
+ DVI4 uses an adaptive delta pulse code modulation (ADPCM) encoding
+ scheme that was specified by the Interactive Multimedia Association
+ (IMA) as the "IMA ADPCM wave type". However, the encoding defined
+ here as DVI4 differs in three respects from the IMA specification:
+
+ o The RTP DVI4 header contains the predicted value rather than the
+ first sample value contained the IMA ADPCM block header.
+
+ o IMA ADPCM blocks contain an odd number of samples, since the first
+ sample of a block is contained just in the header (uncompressed),
+ followed by an even number of compressed samples. DVI4 has an
+ even number of compressed samples only, using the `predict' word
+ from the header to decode the first sample.
+
+ o For DVI4, the 4-bit samples are packed with the first sample in
+ the four most significant bits and the second sample in the four
+ least significant bits. In the IMA ADPCM codec, the samples are
+ packed in the opposite order.
+
+ Each packet contains a single DVI block. This profile only defines
+ the 4-bit-per-sample version, while IMA also specified a 3-bit-per-
+ sample encoding.
+
+ The "header" word for each channel has the following structure:
+
+ int16 predict; /* predicted value of first sample
+ from the previous block (L16 format) */
+ u_int8 index; /* current index into stepsize table */
+ u_int8 reserved; /* set to zero by sender, ignored by receiver */
+
+ Each octet following the header contains two 4-bit samples, thus the
+ number of samples per packet MUST be even because there is no means
+ to indicate a partially filled last octet.
+
+ Packing of samples for multiple channels is for further study.
+
+ The IMA ADPCM algorithm was described in the document IMA Recommended
+ Practices for Enhancing Digital Audio Compatibility in Multimedia
+ Systems (version 3.0). However, the Interactive Multimedia
+ Association ceased operations in 1997. Resources for an archived
+ copy of that document and a software implementation of the RTP DVI4
+ encoding are listed in Section 13.
+
+
+
+
+
+
+
+Schulzrinne & Casner Standards Track [Page 13]
+
+RFC 3551 RTP A/V Profile July 2003
+
+
+4.5.2 G722
+
+ G722 is specified in ITU-T Recommendation G.722, "7 kHz audio-coding
+ within 64 kbit/s". The G.722 encoder produces a stream of octets,
+ each of which SHALL be octet-aligned in an RTP packet. The first bit
+ transmitted in the G.722 octet, which is the most significant bit of
+ the higher sub-band sample, SHALL correspond to the most significant
+ bit of the octet in the RTP packet.
+
+ Even though the actual sampling rate for G.722 audio is 16,000 Hz,
+ the RTP clock rate for the G722 payload format is 8,000 Hz because
+ that value was erroneously assigned in RFC 1890 and must remain
+ unchanged for backward compatibility. The octet rate or sample-pair
+ rate is 8,000 Hz.
+
+4.5.3 G723
+
+ G723 is specified in ITU Recommendation G.723.1, "Dual-rate speech
+ coder for multimedia communications transmitting at 5.3 and 6.3
+ kbit/s". The G.723.1 5.3/6.3 kbit/s codec was defined by the ITU-T
+ as a mandatory codec for ITU-T H.324 GSTN videophone terminal
+ applications. The algorithm has a floating point specification in
+ Annex B to G.723.1, a silence compression algorithm in Annex A to
+ G.723.1 and a scalable channel coding scheme for wireless
+ applications in G.723.1 Annex C.
+
+ This Recommendation specifies a coded representation that can be used
+ for compressing the speech signal component of multi-media services
+ at a very low bit rate. Audio is encoded in 30 ms frames, with an
+ additional delay of 7.5 ms due to look-ahead. A G.723.1 frame can be
+ one of three sizes: 24 octets (6.3 kb/s frame), 20 octets (5.3 kb/s
+ frame), or 4 octets. These 4-octet frames are called SID frames
+ (Silence Insertion Descriptor) and are used to specify comfort noise
+ parameters. There is no restriction on how 4, 20, and 24 octet
+ frames are intermixed. The least significant two bits of the first
+ octet in the frame determine the frame size and codec type:
+
+ bits content octets/frame
+ 00 high-rate speech (6.3 kb/s) 24
+ 01 low-rate speech (5.3 kb/s) 20
+ 10 SID frame 4
+ 11 reserved
+
+
+
+
+
+
+
+
+
+Schulzrinne & Casner Standards Track [Page 14]
+
+RFC 3551 RTP A/V Profile July 2003
+
+
+ It is possible to switch between the two rates at any 30 ms frame
+ boundary. Both (5.3 kb/s and 6.3 kb/s) rates are a mandatory part of
+ the encoder and decoder. Receivers MUST accept both data rates and
+ MUST accept SID frames unless restriction of these capabilities has
+ been signaled. The MIME registration for G723 in RFC 3555 [7]
+ specifies parameters that MAY be used with MIME or SDP to restrict to
+ a single data rate or to restrict the use of SID frames. This coder
+ was optimized to represent speech with near-toll quality at the above
+ rates using a limited amount of complexity.
+
+ The packing of the encoded bit stream into octets and the
+ transmission order of the octets is specified in Rec. G.723.1 and is
+ the same as that produced by the G.723 C code reference
+ implementation. For the 6.3 kb/s data rate, this packing is
+ illustrated as follows, where the header (HDR) bits are always "0 0"
+ as shown in Fig. 1 to indicate operation at 6.3 kb/s, and the Z bit
+ is always set to zero. The diagrams show the bit packing in "network
+ byte order", also known as big-endian order. The bits of each 32-bit
+ word are numbered 0 to 31, with the most significant bit on the left
+ and numbered 0. The octets (bytes) of each word are transmitted most
+ significant octet first. The bits of each data field are numbered in
+ the order of the bit stream representation of the encoding (least
+ significant bit first). The vertical bars indicate the boundaries
+ between field fragments.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Schulzrinne & Casner Standards Track [Page 15]
+
+RFC 3551 RTP A/V Profile July 2003
+
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | LPC |HDR| LPC | LPC | ACL0 |LPC|
+ | | | | | | |
+ |0 0 0 0 0 0|0 0|1 1 1 1 0 0 0 0|2 2 1 1 1 1 1 1|0 0 0 0 0 0|2 2|
+ |5 4 3 2 1 0| |3 2 1 0 9 8 7 6|1 0 9 8 7 6 5 4|5 4 3 2 1 0|3 2|
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | ACL2 |ACL|A| GAIN0 |ACL|ACL| GAIN0 | GAIN1 |
+ | | 1 |C| | 3 | 2 | | |
+ |0 0 0 0 0|0 0|0|0 0 0 0|0 0|0 0|1 1 0 0 0 0 0 0|0 0 0 0 0 0 0 0|
+ |4 3 2 1 0|1 0|6|3 2 1 0|1 0|6 5|1 0 9 8 7 6 5 4|7 6 5 4 3 2 1 0|
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | GAIN2 | GAIN1 | GAIN2 | GAIN3 | GRID | GAIN3 |
+ | | | | | | |
+ |0 0 0 0|1 1 0 0|1 1 0 0 0 0 0 0|0 0 0 0 0 0 0 0|0 0 0 0|1 1 0 0|
+ |3 2 1 0|1 0 9 8|1 0 9 8 7 6 5 4|7 6 5 4 3 2 1 0|3 2 1 0|1 0 9 8|
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | MSBPOS |Z|POS| MSBPOS | POS0 |POS| POS0 |
+ | | | 0 | | | 1 | |
+ |0 0 0 0 0 0 0|0|0 0|1 1 1 0 0 0|0 0 0 0 0 0 0 0|0 0|1 1 1 1 1 1|
+ |6 5 4 3 2 1 0| |1 0|2 1 0 9 8 7|9 8 7 6 5 4 3 2|1 0|5 4 3 2 1 0|
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | POS1 | POS2 | POS1 | POS2 | POS3 | POS2 |
+ | | | | | | |
+ |0 0 0 0 0 0 0 0|0 0 0 0|1 1 1 1|1 1 0 0 0 0 0 0|0 0 0 0|1 1 1 1|
+ |9 8 7 6 5 4 3 2|3 2 1 0|3 2 1 0|1 0 9 8 7 6 5 4|3 2 1 0|5 4 3 2|
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | POS3 | PSIG0 |POS|PSIG2| PSIG1 | PSIG3 |PSIG2|
+ | | | 3 | | | | |
+ |1 1 0 0 0 0 0 0|0 0 0 0 0 0|1 1|0 0 0|0 0 0 0 0|0 0 0 0 0|0 0 0|
+ |1 0 9 8 7 6 5 4|5 4 3 2 1 0|3 2|2 1 0|4 3 2 1 0|4 3 2 1 0|5 4 3|
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ Figure 1: G.723 (6.3 kb/s) bit packing
+
+ For the 5.3 kb/s data rate, the header (HDR) bits are always "0 1",
+ as shown in Fig. 2, to indicate operation at 5.3 kb/s.
+
+
+
+
+
+
+
+
+
+
+
+
+
+Schulzrinne & Casner Standards Track [Page 16]
+
+RFC 3551 RTP A/V Profile July 2003
+
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | LPC |HDR| LPC | LPC | ACL0 |LPC|
+ | | | | | | |
+ |0 0 0 0 0 0|0 1|1 1 1 1 0 0 0 0|2 2 1 1 1 1 1 1|0 0 0 0 0 0|2 2|
+ |5 4 3 2 1 0| |3 2 1 0 9 8 7 6|1 0 9 8 7 6 5 4|5 4 3 2 1 0|3 2|
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | ACL2 |ACL|A| GAIN0 |ACL|ACL| GAIN0 | GAIN1 |
+ | | 1 |C| | 3 | 2 | | |
+ |0 0 0 0 0|0 0|0|0 0 0 0|0 0|0 0|1 1 0 0 0 0 0 0|0 0 0 0 0 0 0 0|
+ |4 3 2 1 0|1 0|6|3 2 1 0|1 0|6 5|1 0 9 8 7 6 5 4|7 6 5 4 3 2 1 0|
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | GAIN2 | GAIN1 | GAIN2 | GAIN3 | GRID | GAIN3 |
+ | | | | | | |
+ |0 0 0 0|1 1 0 0|1 1 0 0 0 0 0 0|0 0 0 0 0 0 0 0|0 0 0 0|1 1 0 0|
+ |3 2 1 0|1 0 9 8|1 0 9 8 7 6 5 4|7 6 5 4 3 2 1 0|4 3 2 1|1 0 9 8|
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | POS0 | POS1 | POS0 | POS1 | POS2 |
+ | | | | | |
+ |0 0 0 0 0 0 0 0|0 0 0 0|1 1 0 0|1 1 0 0 0 0 0 0|0 0 0 0 0 0 0 0|
+ |7 6 5 4 3 2 1 0|3 2 1 0|1 0 9 8|1 0 9 8 7 6 5 4|7 6 5 4 3 2 1 0|
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | POS3 | POS2 | POS3 | PSIG1 | PSIG0 | PSIG3 | PSIG2 |
+ | | | | | | | |
+ |0 0 0 0|1 1 0 0|1 1 0 0 0 0 0 0|0 0 0 0|0 0 0 0|0 0 0 0|0 0 0 0|
+ |3 2 1 0|1 0 9 8|1 0 9 8 7 6 5 4|3 2 1 0|3 2 1 0|3 2 1 0|3 2 1 0|
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ Figure 2: G.723 (5.3 kb/s) bit packing
+
+ The packing of G.723.1 SID (silence) frames, which are indicated by
+ the header (HDR) bits having the pattern "1 0", is depicted in Fig.
+ 3.
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | LPC |HDR| LPC | LPC | GAIN |LPC|
+ | | | | | | |
+ |0 0 0 0 0 0|1 0|1 1 1 1 0 0 0 0|2 2 1 1 1 1 1 1|0 0 0 0 0 0|2 2|
+ |5 4 3 2 1 0| |3 2 1 0 9 8 7 6|1 0 9 8 7 6 5 4|5 4 3 2 1 0|3 2|
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ Figure 3: G.723 SID mode bit packing
+
+
+
+
+
+
+Schulzrinne & Casner Standards Track [Page 17]
+
+RFC 3551 RTP A/V Profile July 2003
+
+
+4.5.4 G726-40, G726-32, G726-24, and G726-16
+
+ ITU-T Recommendation G.726 describes, among others, the algorithm
+ recommended for conversion of a single 64 kbit/s A-law or mu-law PCM
+ channel encoded at 8,000 samples/sec to and from a 40, 32, 24, or 16
+ kbit/s channel. The conversion is applied to the PCM stream using an
+ Adaptive Differential Pulse Code Modulation (ADPCM) transcoding
+ technique. The ADPCM representation consists of a series of
+ codewords with a one-to-one correspondence to the samples in the PCM
+ stream. The G726 data rates of 40, 32, 24, and 16 kbit/s have
+ codewords of 5, 4, 3, and 2 bits, respectively.
+
+ The 16 and 24 kbit/s encodings do not provide toll quality speech.
+ They are designed for used in overloaded Digital Circuit
+ Multiplication Equipment (DCME). ITU-T G.726 recommends that the 16
+ and 24 kbit/s encodings should be alternated with higher data rate
+ encodings to provide an average sample size of between 3.5 and 3.7
+ bits per sample.
+
+ The encodings of G.726 are here denoted as G726-40, G726-32, G726-24,
+ and G726-16. Prior to 1990, G721 described the 32 kbit/s ADPCM
+ encoding, and G723 described the 40, 32, and 16 kbit/s encodings.
+ Thus, G726-32 designates the same algorithm as G721 in RFC 1890.
+
+ A stream of G726 codewords contains no information on the encoding
+ being used, therefore transitions between G726 encoding types are not
+ permitted within a sequence of packed codewords. Applications MUST
+ determine the encoding type of packed codewords from the RTP payload
+ identifier.
+
+ No payload-specific header information SHALL be included as part of
+ the audio data. A stream of G726 codewords MUST be packed into
+ octets as follows: the first codeword is placed into the first octet
+ such that the least significant bit of the codeword aligns with the
+ least significant bit in the octet, the second codeword is then
+ packed so that its least significant bit coincides with the least
+ significant unoccupied bit in the octet. When a complete codeword
+ cannot be placed into an octet, the bits overlapping the octet
+ boundary are placed into the least significant bits of the next
+ octet. Packing MUST end with a completely packed final octet. The
+ number of codewords packed will therefore be a multiple of 8, 2, 8,
+ and 4 for G726-40, G726-32, G726-24, and G726-16, respectively. An
+ example of the packing scheme for G726-32 codewords is as shown,
+ where bit 7 is the least significant bit of the first octet, and bit
+ A3 is the least significant bit of the first codeword:
+
+
+
+
+
+
+Schulzrinne & Casner Standards Track [Page 18]
+
+RFC 3551 RTP A/V Profile July 2003
+
+
+ 0 1
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
+ |B B B B|A A A A|D D D D|C C C C| ...
+ |0 1 2 3|0 1 2 3|0 1 2 3|0 1 2 3|
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
+
+ An example of the packing scheme for G726-24 codewords follows, where
+ again bit 7 is the least significant bit of the first octet, and bit
+ A2 is the least significant bit of the first codeword:
+
+ 0 1 2
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
+ |C C|B B B|A A A|F|E E E|D D D|C|H H H|G G G|F F| ...
+ |1 2|0 1 2|0 1 2|2|0 1 2|0 1 2|0|0 1 2|0 1 2|0 1|
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
+
+ Note that the "little-endian" direction in which samples are packed
+ into octets in the G726-16, -24, -32 and -40 payload formats
+ specified here is consistent with ITU-T Recommendation X.420, but is
+ the opposite of what is specified in ITU-T Recommendation I.366.2
+ Annex E for ATM AAL2 transport. A second set of RTP payload formats
+ matching the packetization of I.366.2 Annex E and identified by MIME
+ subtypes AAL2-G726-16, -24, -32 and -40 will be specified in a
+ separate document.
+
+4.5.5 G728
+
+ G728 is specified in ITU-T Recommendation G.728, "Coding of speech at
+ 16 kbit/s using low-delay code excited linear prediction".
+
+ A G.278 encoder translates 5 consecutive audio samples into a 10-bit
+ codebook index, resulting in a bit rate of 16 kb/s for audio sampled
+ at 8,000 samples per second. The group of five consecutive samples
+ is called a vector. Four consecutive vectors, labeled V1 to V4
+ (where V1 is to be played first by the receiver), build one G.728
+ frame. The four vectors of 40 bits are packed into 5 octets, labeled
+ B1 through B5. B1 SHALL be placed first in the RTP packet.
+
+ Referring to the figure below, the principle for bit order is
+ "maintenance of bit significance". Bits from an older vector are
+ more significant than bits from newer vectors. The MSB of the frame
+ goes to the MSB of B1 and the LSB of the frame goes to LSB of B5.
+
+
+
+
+
+
+
+Schulzrinne & Casner Standards Track [Page 19]
+
+RFC 3551 RTP A/V Profile July 2003
+
+
+ 1 2 3 3
+ 0 0 0 0 9
+ ++++++++++++++++++++++++++++++++++++++++
+ <---V1---><---V2---><---V3---><---V4---> vectors
+ <--B1--><--B2--><--B3--><--B4--><--B5--> octets
+ <------------- frame 1 ---------------->
+
+ In particular, B1 contains the eight most significant bits of V1,
+ with the MSB of V1 being the MSB of B1. B2 contains the two least
+ significant bits of V1, the more significant of the two in its MSB,
+ and the six most significant bits of V2. B1 SHALL be placed first in
+ the RTP packet and B5 last.
+
+4.5.6 G729
+
+ G729 is specified in ITU-T Recommendation G.729, "Coding of speech at
+ 8 kbit/s using conjugate structure-algebraic code excited linear
+ prediction (CS-ACELP)". A reduced-complexity version of the G.729
+ algorithm is specified in Annex A to Rec. G.729. The speech coding
+ algorithms in the main body of G.729 and in G.729 Annex A are fully
+ interoperable with each other, so there is no need to further
+ distinguish between them. An implementation that signals or accepts
+ use of G729 payload format may implement either G.729 or G.729A
+ unless restricted by additional signaling specified elsewhere related
+ specifically to the encoding rather than the payload format. The
+ G.729 and G.729 Annex A codecs were optimized to represent speech
+ with high quality, where G.729 Annex A trades some speech quality for
+ an approximate 50% complexity reduction [10]. See the next Section
+ (4.5.7) for other data rates added in later G.729 Annexes. For all
+ data rates, the sampling frequency (and RTP timestamp clock rate) is
+ 8,000 Hz.
+
+ A voice activity detector (VAD) and comfort noise generator (CNG)
+ algorithm in Annex B of G.729 is RECOMMENDED for digital simultaneous
+ voice and data applications and can be used in conjunction with G.729
+ or G.729 Annex A. A G.729 or G.729 Annex A frame contains 10 octets,
+ while the G.729 Annex B comfort noise frame occupies 2 octets.
+ Receivers MUST accept comfort noise frames if restriction of their
+ use has not been signaled. The MIME registration for G729 in RFC
+ 3555 [7] specifies a parameter that MAY be used with MIME or SDP to
+ restrict the use of comfort noise frames.
+
+ A G729 RTP packet may consist of zero or more G.729 or G.729 Annex A
+ frames, followed by zero or one G.729 Annex B frames. The presence
+ of a comfort noise frame can be deduced from the length of the RTP
+ payload. The default packetization interval is 20 ms (two frames),
+ but in some situations it may be desirable to send 10 ms packets. An
+
+
+
+
+Schulzrinne & Casner Standards Track [Page 20]
+
+RFC 3551 RTP A/V Profile July 2003
+
+
+ example would be a transition from speech to comfort noise in the
+ first 10 ms of the packet. For some applications, a longer
+ packetization interval may be required to reduce the packet rate.
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ |L| L1 | L2 | L3 | P1 |P| C1 |
+ |0| | | | |0| |
+ | |0 1 2 3 4 5 6|0 1 2 3 4|0 1 2 3 4|0 1 2 3 4 5 6 7| |0 1 2 3 4|
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | C1 | S1 | GA1 | GB1 | P2 | C2 |
+ | 1 1 1| | | | | |
+ |5 6 7 8 9 0 1 2|0 1 2 3|0 1 2|0 1 2 3|0 1 2 3 4|0 1 2 3 4 5 6 7|
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | C2 | S2 | GA2 | GB2 |
+ | 1 1 1| | | |
+ |8 9 0 1 2|0 1 2 3|0 1 2|0 1 2 3|
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ Figure 4: G.729 and G.729A bit packing
+
+ The transmitted parameters of a G.729/G.729A 10-ms frame, consisting
+ of 80 bits, are defined in Recommendation G.729, Table 8/G.729. The
+ mapping of the these parameters is given below in Fig. 4. The
+ diagrams show the bit packing in "network byte order", also known as
+ big-endian order. The bits of each 32-bit word are numbered 0 to 31,
+ with the most significant bit on the left and numbered 0. The octets
+ (bytes) of each word are transmitted most significant octet first.
+ The bits of each data field are numbered in the order as produced by
+ the G.729 C code reference implementation.
+
+ The packing of the G.729 Annex B comfort noise frame is shown in Fig.
+ 5.
+
+ 0 1
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ |L| LSF1 | LSF2 | GAIN |R|
+ |S| | | |E|
+ |F| | | |S|
+ |0|0 1 2 3 4|0 1 2 3|0 1 2 3 4|V| RESV = Reserved (zero)
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ Figure 5: G.729 Annex B bit packing
+
+
+
+
+
+
+Schulzrinne & Casner Standards Track [Page 21]
+
+RFC 3551 RTP A/V Profile July 2003
+
+
+4.5.7 G729D and G729E
+
+ Annexes D and E to ITU-T Recommendation G.729 provide additional data
+ rates. Because the data rate is not signaled in the bitstream, the
+ different data rates are given distinct RTP encoding names which are
+ mapped to distinct payload type numbers. G729D indicates a 6.4
+ kbit/s coding mode (G.729 Annex D, for momentary reduction in channel
+ capacity), while G729E indicates an 11.8 kbit/s mode (G.729 Annex E,
+ for improved performance with a wide range of narrow-band input
+ signals, e.g., music and background noise). Annex E has two
+ operating modes, backward adaptive and forward adaptive, which are
+ signaled by the first two bits in each frame (the most significant
+ two bits of the first octet).
+
+ The voice activity detector (VAD) and comfort noise generator (CNG)
+ algorithm specified in Annex B of G.729 may be used with Annex D and
+ Annex E frames in addition to G.729 and G.729 Annex A frames. The
+ algorithm details for the operation of Annexes D and E with the Annex
+ B CNG are specified in G.729 Annexes F and G. Note that Annexes F
+ and G do not introduce any new encodings. Receivers MUST accept
+ comfort noise frames if restriction of their use has not been
+ signaled. The MIME registrations for G729D and G729E in RFC 3555 [7]
+ specify a parameter that MAY be used with MIME or SDP to restrict the
+ use of comfort noise frames.
+
+ For G729D, an RTP packet may consist of zero or more G.729 Annex D
+ frames, followed by zero or one G.729 Annex B frame. Similarly, for
+ G729E, an RTP packet may consist of zero or more G.729 Annex E
+ frames, followed by zero or one G.729 Annex B frame. The presence of
+ a comfort noise frame can be deduced from the length of the RTP
+ payload.
+
+ A single RTP packet must contain frames of only one data rate,
+ optionally followed by one comfort noise frame. The data rate may be
+ changed from packet to packet by changing the payload type number.
+ G.729 Annexes D, E and H describe what the encoding and decoding
+ algorithms must do to accommodate a change in data rate.
+
+ For G729D, the bits of a G.729 Annex D frame are formatted as shown
+ below in Fig. 6 (cf. Table D.1/G.729). The frame length is 64 bits.
+
+
+
+
+
+
+
+
+
+
+
+Schulzrinne & Casner Standards Track [Page 22]
+
+RFC 3551 RTP A/V Profile July 2003
+
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ |L| L1 | L2 | L3 | P1 | C1 |
+ |0| | | | | |
+ | |0 1 2 3 4 5 6|0 1 2 3 4|0 1 2 3 4|0 1 2 3 4 5 6 7|0 1 2 3 4 5|
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | C1 |S1 | GA1 | GB1 | P2 | C2 |S2 | GA2 | GB2 |
+ | | | | | | | | | |
+ |6 7 8|0 1|0 1 2|0 1 2|0 1 2 3|0 1 2 3 4 5 6 7 8|0 1|0 1 2|0 1 2|
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ Figure 6: G.729 Annex D bit packing
+
+ The net bit rate for the G.729 Annex E algorithm is 11.8 kbit/s and a
+ total of 118 bits are used. Two bits are appended as "don't care"
+ bits to complete an integer number of octets for the frame. For
+ G729E, the bits of a data frame are formatted as shown in the next
+ two diagrams (cf. Table E.1/G.729). The fields for the G729E forward
+ adaptive mode are packed as shown in Fig. 7.
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ |0 0|L| L1 | L2 | L3 | P1 |P| C0_1|
+ | |0| | | | |0| |
+ | | |0 1 2 3 4 5 6|0 1 2 3 4|0 1 2 3 4|0 1 2 3 4 5 6 7| |0 1 2|
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | | C1_1 | C2_1 | C3_1 | C4_1 |
+ | | | | | |
+ |3 4 5 6|0 1 2 3 4 5 6|0 1 2 3 4 5 6|0 1 2 3 4 5 6|0 1 2 3 4 5 6|
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | GA1 | GB1 | P2 | C0_2 | C1_2 | C2_2 |
+ | | | | | | |
+ |0 1 2|0 1 2 3|0 1 2 3 4|0 1 2 3 4 5 6|0 1 2 3 4 5 6|0 1 2 3 4 5|
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | | C3_2 | C4_2 | GA2 | GB2 |DC |
+ | | | | | | |
+ |6|0 1 2 3 4 5 6|0 1 2 3 4 5 6|0 1 2|0 1 2 3|0 1|
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ Figure 7: G.729 Annex E (forward adaptive mode) bit packing
+
+ The fields for the G729E backward adaptive mode are packed as shown
+ in Fig. 8.
+
+
+
+
+
+
+Schulzrinne & Casner Standards Track [Page 23]
+
+RFC 3551 RTP A/V Profile July 2003
+
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ |1 1| P1 |P| C0_1 | C1_1 |
+ | | |0| 1 1 1| |
+ | |0 1 2 3 4 5 6 7|0|0 1 2 3 4 5 6 7 8 9 0 1 2|0 1 2 3 4 5 6 7|
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | | C2_1 | C3_1 | C4_1 |GA1 | GB1 |P2 |
+ | | | | | | | |
+ |8 9|0 1 2 3 4 5 6|0 1 2 3 4 5 6|0 1 2 3 4 5 6|0 1 2|0 1 2 3|0 1|
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | | C0_2 | C1_2 | C2_2 |
+ | | 1 1 1| | |
+ |2 3 4|0 1 2 3 4 5 6 7 8 9 0 1 2|0 1 2 3 4 5 6 7 8 9|0 1 2 3 4 5|
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | | C3_2 | C4_2 | GA2 | GB2 |DC |
+ | | | | | | |
+ |6|0 1 2 3 4 5 6|0 1 2 3 4 5 6|0 1 2|0 1 2 3|0 1|
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ Figure 8: G.729 Annex E (backward adaptive mode) bit packing
+
+4.5.8 GSM
+
+ GSM (Group Speciale Mobile) denotes the European GSM 06.10 standard
+ for full-rate speech transcoding, ETS 300 961, which is based on
+ RPE/LTP (residual pulse excitation/long term prediction) coding at a
+ rate of 13 kb/s [11,12,13]. The text of the standard can be obtained
+ from:
+
+ ETSI (European Telecommunications Standards Institute)
+ ETSI Secretariat: B.P.152
+ F-06561 Valbonne Cedex
+ France
+ Phone: +33 92 94 42 00
+ Fax: +33 93 65 47 16
+
+ Blocks of 160 audio samples are compressed into 33 octets, for an
+ effective data rate of 13,200 b/s.
+
+4.5.8.1 General Packaging Issues
+
+ The GSM standard (ETS 300 961) specifies the bit stream produced by
+ the codec, but does not specify how these bits should be packed for
+ transmission. The packetization specified here has subsequently been
+ adopted in ETSI Technical Specification TS 101 318. Some software
+ implementations of the GSM codec use a different packing than that
+ specified here.
+
+
+
+Schulzrinne & Casner Standards Track [Page 24]
+
+RFC 3551 RTP A/V Profile July 2003
+
+
+ field field name bits field field name bits
+ ________________________________________________
+ 1 LARc[0] 6 39 xmc[22] 3
+ 2 LARc[1] 6 40 xmc[23] 3
+ 3 LARc[2] 5 41 xmc[24] 3
+ 4 LARc[3] 5 42 xmc[25] 3
+ 5 LARc[4] 4 43 Nc[2] 7
+ 6 LARc[5] 4 44 bc[2] 2
+ 7 LARc[6] 3 45 Mc[2] 2
+ 8 LARc[7] 3 46 xmaxc[2] 6
+ 9 Nc[0] 7 47 xmc[26] 3
+ 10 bc[0] 2 48 xmc[27] 3
+ 11 Mc[0] 2 49 xmc[28] 3
+ 12 xmaxc[0] 6 50 xmc[29] 3
+ 13 xmc[0] 3 51 xmc[30] 3
+ 14 xmc[1] 3 52 xmc[31] 3
+ 15 xmc[2] 3 53 xmc[32] 3
+ 16 xmc[3] 3 54 xmc[33] 3
+ 17 xmc[4] 3 55 xmc[34] 3
+ 18 xmc[5] 3 56 xmc[35] 3
+ 19 xmc[6] 3 57 xmc[36] 3
+ 20 xmc[7] 3 58 xmc[37] 3
+ 21 xmc[8] 3 59 xmc[38] 3
+ 22 xmc[9] 3 60 Nc[3] 7
+ 23 xmc[10] 3 61 bc[3] 2
+ 24 xmc[11] 3 62 Mc[3] 2
+ 25 xmc[12] 3 63 xmaxc[3] 6
+ 26 Nc[1] 7 64 xmc[39] 3
+ 27 bc[1] 2 65 xmc[40] 3
+ 28 Mc[1] 2 66 xmc[41] 3
+ 29 xmaxc[1] 6 67 xmc[42] 3
+ 30 xmc[13] 3 68 xmc[43] 3
+ 31 xmc[14] 3 69 xmc[44] 3
+ 32 xmc[15] 3 70 xmc[45] 3
+ 33 xmc[16] 3 71 xmc[46] 3
+ 34 xmc[17] 3 72 xmc[47] 3
+ 35 xmc[18] 3 73 xmc[48] 3
+ 36 xmc[19] 3 74 xmc[49] 3
+ 37 xmc[20] 3 75 xmc[50] 3
+ 38 xmc[21] 3 76 xmc[51] 3
+
+ Table 2: Ordering of GSM variables
+
+
+
+
+
+
+
+
+
+Schulzrinne & Casner Standards Track [Page 25]
+
+RFC 3551 RTP A/V Profile July 2003
+
+
+ Octet Bit 0 Bit 1 Bit 2 Bit 3 Bit 4 Bit 5 Bit 6 Bit 7
+ _____________________________________________________________________
+ 0 1 1 0 1 LARc0.0 LARc0.1 LARc0.2 LARc0.3
+ 1 LARc0.4 LARc0.5 LARc1.0 LARc1.1 LARc1.2 LARc1.3 LARc1.4 LARc1.5
+ 2 LARc2.0 LARc2.1 LARc2.2 LARc2.3 LARc2.4 LARc3.0 LARc3.1 LARc3.2
+ 3 LARc3.3 LARc3.4 LARc4.0 LARc4.1 LARc4.2 LARc4.3 LARc5.0 LARc5.1
+ 4 LARc5.2 LARc5.3 LARc6.0 LARc6.1 LARc6.2 LARc7.0 LARc7.1 LARc7.2
+ 5 Nc0.0 Nc0.1 Nc0.2 Nc0.3 Nc0.4 Nc0.5 Nc0.6 bc0.0
+ 6 bc0.1 Mc0.0 Mc0.1 xmaxc00 xmaxc01 xmaxc02 xmaxc03 xmaxc04
+ 7 xmaxc05 xmc0.0 xmc0.1 xmc0.2 xmc1.0 xmc1.1 xmc1.2 xmc2.0
+ 8 xmc2.1 xmc2.2 xmc3.0 xmc3.1 xmc3.2 xmc4.0 xmc4.1 xmc4.2
+ 9 xmc5.0 xmc5.1 xmc5.2 xmc6.0 xmc6.1 xmc6.2 xmc7.0 xmc7.1
+ 10 xmc7.2 xmc8.0 xmc8.1 xmc8.2 xmc9.0 xmc9.1 xmc9.2 xmc10.0
+ 11 xmc10.1 xmc10.2 xmc11.0 xmc11.1 xmc11.2 xmc12.0 xmc12.1 xcm12.2
+ 12 Nc1.0 Nc1.1 Nc1.2 Nc1.3 Nc1.4 Nc1.5 Nc1.6 bc1.0
+ 13 bc1.1 Mc1.0 Mc1.1 xmaxc10 xmaxc11 xmaxc12 xmaxc13 xmaxc14
+ 14 xmax15 xmc13.0 xmc13.1 xmc13.2 xmc14.0 xmc14.1 xmc14.2 xmc15.0
+ 15 xmc15.1 xmc15.2 xmc16.0 xmc16.1 xmc16.2 xmc17.0 xmc17.1 xmc17.2
+ 16 xmc18.0 xmc18.1 xmc18.2 xmc19.0 xmc19.1 xmc19.2 xmc20.0 xmc20.1
+ 17 xmc20.2 xmc21.0 xmc21.1 xmc21.2 xmc22.0 xmc22.1 xmc22.2 xmc23.0
+ 18 xmc23.1 xmc23.2 xmc24.0 xmc24.1 xmc24.2 xmc25.0 xmc25.1 xmc25.2
+ 19 Nc2.0 Nc2.1 Nc2.2 Nc2.3 Nc2.4 Nc2.5 Nc2.6 bc2.0
+ 20 bc2.1 Mc2.0 Mc2.1 xmaxc20 xmaxc21 xmaxc22 xmaxc23 xmaxc24
+ 21 xmaxc25 xmc26.0 xmc26.1 xmc26.2 xmc27.0 xmc27.1 xmc27.2 xmc28.0
+ 22 xmc28.1 xmc28.2 xmc29.0 xmc29.1 xmc29.2 xmc30.0 xmc30.1 xmc30.2
+ 23 xmc31.0 xmc31.1 xmc31.2 xmc32.0 xmc32.1 xmc32.2 xmc33.0 xmc33.1
+ 24 xmc33.2 xmc34.0 xmc34.1 xmc34.2 xmc35.0 xmc35.1 xmc35.2 xmc36.0
+ 25 Xmc36.1 xmc36.2 xmc37.0 xmc37.1 xmc37.2 xmc38.0 xmc38.1 xmc38.2
+ 26 Nc3.0 Nc3.1 Nc3.2 Nc3.3 Nc3.4 Nc3.5 Nc3.6 bc3.0
+ 27 bc3.1 Mc3.0 Mc3.1 xmaxc30 xmaxc31 xmaxc32 xmaxc33 xmaxc34
+ 28 xmaxc35 xmc39.0 xmc39.1 xmc39.2 xmc40.0 xmc40.1 xmc40.2 xmc41.0
+ 29 xmc41.1 xmc41.2 xmc42.0 xmc42.1 xmc42.2 xmc43.0 xmc43.1 xmc43.2
+ 30 xmc44.0 xmc44.1 xmc44.2 xmc45.0 xmc45.1 xmc45.2 xmc46.0 xmc46.1
+ 31 xmc46.2 xmc47.0 xmc47.1 xmc47.2 xmc48.0 xmc48.1 xmc48.2 xmc49.0
+ 32 xmc49.1 xmc49.2 xmc50.0 xmc50.1 xmc50.2 xmc51.0 xmc51.1 xmc51.2
+
+ Table 3: GSM payload format
+
+ In the GSM packing used by RTP, the bits SHALL be packed beginning
+ from the most significant bit. Every 160 sample GSM frame is coded
+ into one 33 octet (264 bit) buffer. Every such buffer begins with a
+ 4 bit signature (0xD), followed by the MSB encoding of the fields of
+ the frame. The first octet thus contains 1101 in the 4 most
+ significant bits (0-3) and the 4 most significant bits of F1 (0-3) in
+ the 4 least significant bits (4-7). The second octet contains the 2
+ least significant bits of F1 in bits 0-1, and F2 in bits 2-7, and so
+ on. The order of the fields in the frame is described in Table 2.
+
+
+
+
+Schulzrinne & Casner Standards Track [Page 26]
+
+RFC 3551 RTP A/V Profile July 2003
+
+
+4.5.8.2 GSM Variable Names and Numbers
+
+ In the RTP encoding we have the bit pattern described in Table 3,
+ where F.i signifies the ith bit of the field F, bit 0 is the most
+ significant bit, and the bits of every octet are numbered from 0 to 7
+ from most to least significant.
+
+4.5.9 GSM-EFR
+
+ GSM-EFR denotes GSM 06.60 enhanced full rate speech transcoding,
+ specified in ETS 300 726 which is available from ETSI at the address
+ given in Section 4.5.8. This codec has a frame length of 244 bits.
+ For transmission in RTP, each codec frame is packed into a 31 octet
+ (248 bit) buffer beginning with a 4-bit signature 0xC in a manner
+ similar to that specified here for the original GSM 06.10 codec. The
+ packing is specified in ETSI Technical Specification TS 101 318.
+
+4.5.10 L8
+
+ L8 denotes linear audio data samples, using 8-bits of precision with
+ an offset of 128, that is, the most negative signal is encoded as
+ zero.
+
+4.5.11 L16
+
+ L16 denotes uncompressed audio data samples, using 16-bit signed
+ representation with 65,535 equally divided steps between minimum and
+ maximum signal level, ranging from -32,768 to 32,767. The value is
+ represented in two's complement notation and transmitted in network
+ byte order (most significant byte first).
+
+ The MIME registration for L16 in RFC 3555 [7] specifies parameters
+ that MAY be used with MIME or SDP to indicate that analog pre-
+ emphasis was applied to the signal before quantization or to indicate
+ that a multiple-channel audio stream follows a different channel
+ ordering convention than is specified in Section 4.1.
+
+4.5.12 LPC
+
+ LPC designates an experimental linear predictive encoding contributed
+ by Ron Frederick, which is based on an implementation written by Ron
+ Zuckerman posted to the Usenet group comp.dsp on June 26, 1992. The
+ codec generates 14 octets for every frame. The framesize is set to
+ 20 ms, resulting in a bit rate of 5,600 b/s.
+
+
+
+
+
+
+
+Schulzrinne & Casner Standards Track [Page 27]
+
+RFC 3551 RTP A/V Profile July 2003
+
+
+4.5.13 MPA
+
+ MPA denotes MPEG-1 or MPEG-2 audio encapsulated as elementary
+ streams. The encoding is defined in ISO standards ISO/IEC 11172-3
+ and 13818-3. The encapsulation is specified in RFC 2250 [14].
+
+ The encoding may be at any of three levels of complexity, called
+ Layer I, II and III. The selected layer as well as the sampling rate
+ and channel count are indicated in the payload. The RTP timestamp
+ clock rate is always 90,000, independent of the sampling rate.
+ MPEG-1 audio supports sampling rates of 32, 44.1, and 48 kHz (ISO/IEC
+ 11172-3, section 1.1; "Scope"). MPEG-2 supports sampling rates of
+ 16, 22.05 and 24 kHz. The number of samples per frame is fixed, but
+ the frame size will vary with the sampling rate and bit rate.
+
+ The MIME registration for MPA in RFC 3555 [7] specifies parameters
+ that MAY be used with MIME or SDP to restrict the selection of layer,
+ channel count, sampling rate, and bit rate.
+
+4.5.14 PCMA and PCMU
+
+ PCMA and PCMU are specified in ITU-T Recommendation G.711. Audio
+ data is encoded as eight bits per sample, after logarithmic scaling.
+ PCMU denotes mu-law scaling, PCMA A-law scaling. A detailed
+ description is given by Jayant and Noll [15]. Each G.711 octet SHALL
+ be octet-aligned in an RTP packet. The sign bit of each G.711 octet
+ SHALL correspond to the most significant bit of the octet in the RTP
+ packet (i.e., assuming the G.711 samples are handled as octets on the
+ host machine, the sign bit SHALL be the most significant bit of the
+ octet as defined by the host machine format). The 56 kb/s and 48
+ kb/s modes of G.711 are not applicable to RTP, since PCMA and PCMU
+ MUST always be transmitted as 8-bit samples.
+
+ See Section 4.1 regarding silence suppression.
+
+4.5.15 QCELP
+
+ The Electronic Industries Association (EIA) & Telecommunications
+ Industry Association (TIA) standard IS-733, "TR45: High Rate Speech
+ Service Option for Wideband Spread Spectrum Communications Systems",
+ defines the QCELP audio compression algorithm for use in wireless
+ CDMA applications. The QCELP CODEC compresses each 20 milliseconds
+ of 8,000 Hz, 16-bit sampled input speech into one of four different
+ size output frames: Rate 1 (266 bits), Rate 1/2 (124 bits), Rate 1/4
+ (54 bits) or Rate 1/8 (20 bits). For typical speech patterns, this
+ results in an average output of 6.8 kb/s for normal mode and 4.7 kb/s
+ for reduced rate mode. The packetization of the QCELP audio codec is
+ described in [16].
+
+
+
+Schulzrinne & Casner Standards Track [Page 28]
+
+RFC 3551 RTP A/V Profile July 2003
+
+
+4.5.16 RED
+
+ The redundant audio payload format "RED" is specified by RFC 2198
+ [17]. It defines a means by which multiple redundant copies of an
+ audio packet may be transmitted in a single RTP stream. Each packet
+ in such a stream contains, in addition to the audio data for that
+ packetization interval, a (more heavily compressed) copy of the data
+ from a previous packetization interval. This allows an approximation
+ of the data from lost packets to be recovered upon decoding of a
+ subsequent packet, giving much improved sound quality when compared
+ with silence substitution for lost packets.
+
+4.5.17 VDVI
+
+ VDVI is a variable-rate version of DVI4, yielding speech bit rates of
+ between 10 and 25 kb/s. It is specified for single-channel operation
+ only. Samples are packed into octets starting at the most-
+ significant bit. The last octet is padded with 1 bits if the last
+ sample does not fill the last octet. This padding is distinct from
+ the valid codewords. The receiver needs to detect the padding
+ because there is no explicit count of samples in the packet.
+
+ It uses the following encoding:
+
+ DVI4 codeword VDVI bit pattern
+ _______________________________
+ 0 00
+ 1 010
+ 2 1100
+ 3 11100
+ 4 111100
+ 5 1111100
+ 6 11111100
+ 7 11111110
+ 8 10
+ 9 011
+ 10 1101
+ 11 11101
+ 12 111101
+ 13 1111101
+ 14 11111101
+ 15 11111111
+
+
+
+
+
+
+
+
+
+Schulzrinne & Casner Standards Track [Page 29]
+
+RFC 3551 RTP A/V Profile July 2003
+
+
+5. Video
+
+ The following sections describe the video encodings that are defined
+ in this memo and give their abbreviated names used for
+ identification. These video encodings and their payload types are
+ listed in Table 5.
+
+ All of these video encodings use an RTP timestamp frequency of 90,000
+ Hz, the same as the MPEG presentation time stamp frequency. This
+ frequency yields exact integer timestamp increments for the typical
+ 24 (HDTV), 25 (PAL), and 29.97 (NTSC) and 30 Hz (HDTV) frame rates
+ and 50, 59.94 and 60 Hz field rates. While 90 kHz is the RECOMMENDED
+ rate for future video encodings used within this profile, other rates
+ MAY be used. However, it is not sufficient to use the video frame
+ rate (typically between 15 and 30 Hz) because that does not provide
+ adequate resolution for typical synchronization requirements when
+ calculating the RTP timestamp corresponding to the NTP timestamp in
+ an RTCP SR packet. The timestamp resolution MUST also be sufficient
+ for the jitter estimate contained in the receiver reports.
+
+ For most of these video encodings, the RTP timestamp encodes the
+ sampling instant of the video image contained in the RTP data packet.
+ If a video image occupies more than one packet, the timestamp is the
+ same on all of those packets. Packets from different video images
+ are distinguished by their different timestamps.
+
+ Most of these video encodings also specify that the marker bit of the
+ RTP header SHOULD be set to one in the last packet of a video frame
+ and otherwise set to zero. Thus, it is not necessary to wait for a
+ following packet with a different timestamp to detect that a new
+ frame should be displayed.
+
+5.1 CelB
+
+ The CELL-B encoding is a proprietary encoding proposed by Sun
+ Microsystems. The byte stream format is described in RFC 2029 [18].
+
+5.2 JPEG
+
+ The encoding is specified in ISO Standards 10918-1 and 10918-2. The
+ RTP payload format is as specified in RFC 2435 [19].
+
+5.3 H261
+
+ The encoding is specified in ITU-T Recommendation H.261, "Video codec
+ for audiovisual services at p x 64 kbit/s". The packetization and
+ RTP-specific properties are described in RFC 2032 [20].
+
+
+
+
+Schulzrinne & Casner Standards Track [Page 30]
+
+RFC 3551 RTP A/V Profile July 2003
+
+
+5.4 H263
+
+ The encoding is specified in the 1996 version of ITU-T Recommendation
+ H.263, "Video coding for low bit rate communication". The
+ packetization and RTP-specific properties are described in RFC 2190
+ [21]. The H263-1998 payload format is RECOMMENDED over this one for
+ use by new implementations.
+
+5.5 H263-1998
+
+ The encoding is specified in the 1998 version of ITU-T Recommendation
+ H.263, "Video coding for low bit rate communication". The
+ packetization and RTP-specific properties are described in RFC 2429
+ [22]. Because the 1998 version of H.263 is a superset of the 1996
+ syntax, this payload format can also be used with the 1996 version of
+ H.263, and is RECOMMENDED for this use by new implementations. This
+ payload format does not replace RFC 2190, which continues to be used
+ by existing implementations, and may be required for backward
+ compatibility in new implementations. Implementations using the new
+ features of the 1998 version of H.263 MUST use the payload format
+ described in RFC 2429.
+
+5.6 MPV
+
+ MPV designates the use of MPEG-1 and MPEG-2 video encoding elementary
+ streams as specified in ISO Standards ISO/IEC 11172 and 13818-2,
+ respectively. The RTP payload format is as specified in RFC 2250
+ [14], Section 3.
+
+ The MIME registration for MPV in RFC 3555 [7] specifies a parameter
+ that MAY be used with MIME or SDP to restrict the selection of the
+ type of MPEG video.
+
+5.7 MP2T
+
+ MP2T designates the use of MPEG-2 transport streams, for either audio
+ or video. The RTP payload format is described in RFC 2250 [14],
+ Section 2.
+
+
+
+
+
+
+
+
+
+
+
+
+
+Schulzrinne & Casner Standards Track [Page 31]
+
+RFC 3551 RTP A/V Profile July 2003
+
+
+5.8 nv
+
+ The encoding is implemented in the program `nv', version 4, developed
+ at Xerox PARC by Ron Frederick. Further information is available
+ from the author:
+
+ Ron Frederick
+ Blue Coat Systems Inc.
+ 650 Almanor Avenue
+ Sunnyvale, CA 94085
+ United States
+ EMail: ronf@bluecoat.com
+
+6. Payload Type Definitions
+
+ Tables 4 and 5 define this profile's static payload type values for
+ the PT field of the RTP data header. In addition, payload type
+ values in the range 96-127 MAY be defined dynamically through a
+ conference control protocol, which is beyond the scope of this
+ document. For example, a session directory could specify that for a
+ given session, payload type 96 indicates PCMU encoding, 8,000 Hz
+ sampling rate, 2 channels. Entries in Tables 4 and 5 with payload
+ type "dyn" have no static payload type assigned and are only used
+ with a dynamic payload type. Payload type 2 was assigned to G721 in
+ RFC 1890 and to its equivalent successor G726-32 in draft versions of
+ this specification, but its use is now deprecated and that static
+ payload type is marked reserved due to conflicting use for the
+ payload formats G726-32 and AAL2-G726-32 (see Section 4.5.4).
+ Payload type 13 indicates the Comfort Noise (CN) payload format
+ specified in RFC 3389 [9]. Payload type 19 is marked "reserved"
+ because some draft versions of this specification assigned that
+ number to an earlier version of the comfort noise payload format.
+ The payload type range 72-76 is marked "reserved" so that RTCP and
+ RTP packets can be reliably distinguished (see Section "Summary of
+ Protocol Constants" of the RTP protocol specification).
+
+ The payload types currently defined in this profile are assigned to
+ exactly one of three categories or media types: audio only, video
+ only and those combining audio and video. The media types are marked
+ in Tables 4 and 5 as "A", "V" and "AV", respectively. Payload types
+ of different media types SHALL NOT be interleaved or multiplexed
+ within a single RTP session, but multiple RTP sessions MAY be used in
+ parallel to send multiple media types. An RTP source MAY change
+ payload types within the same media type during a session. See the
+ section "Multiplexing RTP Sessions" of RFC 3550 for additional
+ explanation.
+
+
+
+
+
+Schulzrinne & Casner Standards Track [Page 32]
+
+RFC 3551 RTP A/V Profile July 2003
+
+
+ PT encoding media type clock rate channels
+ name (Hz)
+ ___________________________________________________
+ 0 PCMU A 8,000 1
+ 1 reserved A
+ 2 reserved A
+ 3 GSM A 8,000 1
+ 4 G723 A 8,000 1
+ 5 DVI4 A 8,000 1
+ 6 DVI4 A 16,000 1
+ 7 LPC A 8,000 1
+ 8 PCMA A 8,000 1
+ 9 G722 A 8,000 1
+ 10 L16 A 44,100 2
+ 11 L16 A 44,100 1
+ 12 QCELP A 8,000 1
+ 13 CN A 8,000 1
+ 14 MPA A 90,000 (see text)
+ 15 G728 A 8,000 1
+ 16 DVI4 A 11,025 1
+ 17 DVI4 A 22,050 1
+ 18 G729 A 8,000 1
+ 19 reserved A
+ 20 unassigned A
+ 21 unassigned A
+ 22 unassigned A
+ 23 unassigned A
+ dyn G726-40 A 8,000 1
+ dyn G726-32 A 8,000 1
+ dyn G726-24 A 8,000 1
+ dyn G726-16 A 8,000 1
+ dyn G729D A 8,000 1
+ dyn G729E A 8,000 1
+ dyn GSM-EFR A 8,000 1
+ dyn L8 A var. var.
+ dyn RED A (see text)
+ dyn VDVI A var. 1
+
+ Table 4: Payload types (PT) for audio encodings
+
+
+
+
+
+
+
+
+
+
+
+
+Schulzrinne & Casner Standards Track [Page 33]
+
+RFC 3551 RTP A/V Profile July 2003
+
+
+ PT encoding media type clock rate
+ name (Hz)
+ _____________________________________________
+ 24 unassigned V
+ 25 CelB V 90,000
+ 26 JPEG V 90,000
+ 27 unassigned V
+ 28 nv V 90,000
+ 29 unassigned V
+ 30 unassigned V
+ 31 H261 V 90,000
+ 32 MPV V 90,000
+ 33 MP2T AV 90,000
+ 34 H263 V 90,000
+ 35-71 unassigned ?
+ 72-76 reserved N/A N/A
+ 77-95 unassigned ?
+ 96-127 dynamic ?
+ dyn H263-1998 V 90,000
+
+ Table 5: Payload types (PT) for video and combined
+ encodings
+
+ Session participants agree through mechanisms beyond the scope of
+ this specification on the set of payload types allowed in a given
+ session. This set MAY, for example, be defined by the capabilities
+ of the applications used, negotiated by a conference control protocol
+ or established by agreement between the human participants.
+
+ Audio applications operating under this profile SHOULD, at a minimum,
+ be able to send and/or receive payload types 0 (PCMU) and 5 (DVI4).
+ This allows interoperability without format negotiation and ensures
+ successful negotiation with a conference control protocol.
+
+7. RTP over TCP and Similar Byte Stream Protocols
+
+ Under special circumstances, it may be necessary to carry RTP in
+ protocols offering a byte stream abstraction, such as TCP, possibly
+ multiplexed with other data. The application MUST define its own
+ method of delineating RTP and RTCP packets (RTSP [23] provides an
+ example of such an encapsulation specification).
+
+8. Port Assignment
+
+ As specified in the RTP protocol definition, RTP data SHOULD be
+ carried on an even UDP port number and the corresponding RTCP packets
+ SHOULD be carried on the next higher (odd) port number.
+
+
+
+
+Schulzrinne & Casner Standards Track [Page 34]
+
+RFC 3551 RTP A/V Profile July 2003
+
+
+ Applications operating under this profile MAY use any such UDP port
+ pair. For example, the port pair MAY be allocated randomly by a
+ session management program. A single fixed port number pair cannot
+ be required because multiple applications using this profile are
+ likely to run on the same host, and there are some operating systems
+ that do not allow multiple processes to use the same UDP port with
+ different multicast addresses.
+
+ However, port numbers 5004 and 5005 have been registered for use with
+ this profile for those applications that choose to use them as the
+ default pair. Applications that operate under multiple profiles MAY
+ use this port pair as an indication to select this profile if they
+ are not subject to the constraint of the previous paragraph.
+ Applications need not have a default and MAY require that the port
+ pair be explicitly specified. The particular port numbers were
+ chosen to lie in the range above 5000 to accommodate port number
+ allocation practice within some versions of the Unix operating
+ system, where port numbers below 1024 can only be used by privileged
+ processes and port numbers between 1024 and 5000 are automatically
+ assigned by the operating system.
+
+9. Changes from RFC 1890
+
+ This RFC revises RFC 1890. It is mostly backwards-compatible with
+ RFC 1890 except for functions removed because two interoperable
+ implementations were not found. The additions to RFC 1890 codify
+ existing practice in the use of payload formats under this profile.
+ Since this profile may be used without using any of the payload
+ formats listed here, the addition of new payload formats in this
+ revision does not affect backwards compatibility. The changes are
+ listed below, categorized into functional and non-functional changes.
+
+ Functional changes:
+
+ o Section 11, "IANA Considerations" was added to specify the
+ registration of the name for this profile. That appendix also
+ references a new Section 3 "Registering Additional Encodings"
+ which establishes a policy that no additional registration of
+ static payload types for this profile will be made beyond those
+ added in this revision and included in Tables 4 and 5. Instead,
+ additional encoding names may be registered as MIME subtypes for
+ binding to dynamic payload types. Non-normative references were
+ added to RFC 3555 [7] where MIME subtypes for all the listed
+ payload formats are registered, some with optional parameters for
+ use of the payload formats.
+
+
+
+
+
+
+Schulzrinne & Casner Standards Track [Page 35]
+
+RFC 3551 RTP A/V Profile July 2003
+
+
+ o Static payload types 4, 16, 17 and 34 were added to incorporate
+ IANA registrations made since the publication of RFC 1890, along
+ with the corresponding payload format descriptions for G723 and
+ H263.
+
+ o Following working group discussion, static payload types 12 and 18
+ were added along with the corresponding payload format
+ descriptions for QCELP and G729. Static payload type 13 was
+ assigned to the Comfort Noise (CN) payload format defined in RFC
+ 3389. Payload type 19 was marked reserved because it had been
+ temporarily allocated to an earlier version of Comfort Noise
+ present in some draft revisions of this document.
+
+ o The payload format for G721 was renamed to G726-32 following the
+ ITU-T renumbering, and the payload format description for G726 was
+ expanded to include the -16, -24 and -40 data rates. Because of
+ confusion regarding draft revisions of this document, some
+ implementations of these G726 payload formats packed samples into
+ octets starting with the most significant bit rather than the
+ least significant bit as specified here. To partially resolve
+ this incompatibility, new payload formats named AAL2-G726-16, -24,
+ -32 and -40 will be specified in a separate document (see note in
+ Section 4.5.4), and use of static payload type 2 is deprecated as
+ explained in Section 6.
+
+ o Payload formats G729D and G729E were added following the ITU-T
+ addition of Annexes D and E to Recommendation G.729. Listings
+ were added for payload formats GSM-EFR, RED, and H263-1998
+ published in other documents subsequent to RFC 1890. These
+ additional payload formats are referenced only by dynamic payload
+ type numbers.
+
+ o The descriptions of the payload formats for G722, G728, GSM, VDVI
+ were expanded.
+
+ o The payload format for 1016 audio was removed and its static
+ payload type assignment 1 was marked "reserved" because two
+ interoperable implementations were not found.
+
+ o Requirements for congestion control were added in Section 2.
+
+ o This profile follows the suggestion in the revised RTP spec that
+ RTCP bandwidth may be specified separately from the session
+ bandwidth and separately for active senders and passive receivers.
+
+ o The mapping of a user pass-phrase string into an encryption key
+ was deleted from Section 2 because two interoperable
+ implementations were not found.
+
+
+
+Schulzrinne & Casner Standards Track [Page 36]
+
+RFC 3551 RTP A/V Profile July 2003
+
+
+ o The "quadrophonic" sample ordering convention for four-channel
+ audio was removed to eliminate an ambiguity as noted in Section
+ 4.1.
+
+ Non-functional changes:
+
+ o In Section 4.1, it is now explicitly stated that silence
+ suppression is allowed for all audio payload formats. (This has
+ always been the case and derives from a fundamental aspect of
+ RTP's design and the motivations for packet audio, but was not
+ explicit stated before.) The use of comfort noise is also
+ explained.
+
+ o In Section 4.1, the requirement level for setting of the marker
+ bit on the first packet after silence for audio was changed from
+ "is" to "SHOULD be", and clarified that the marker bit is set only
+ when packets are intentionally not sent.
+
+ o Similarly, text was added to specify that the marker bit SHOULD be
+ set to one on the last packet of a video frame, and that video
+ frames are distinguished by their timestamps.
+
+ o RFC references are added for payload formats published after RFC
+ 1890.
+
+ o The security considerations and full copyright sections were
+ added.
+
+ o According to Peter Hoddie of Apple, only pre-1994 Macintosh used
+ the 22254.54 rate and none the 11127.27 rate, so the latter was
+ dropped from the discussion of suggested sampling frequencies.
+
+ o Table 1 was corrected to move some values from the "ms/packet"
+ column to the "default ms/packet" column where they belonged.
+
+ o Since the Interactive Multimedia Association ceased operations, an
+ alternate resource was provided for a referenced IMA document.
+
+ o A note has been added for G722 to clarify a discrepancy between
+ the actual sampling rate and the RTP timestamp clock rate.
+
+ o Small clarifications of the text have been made in several places,
+ some in response to questions from readers. In particular:
+
+ - A definition for "media type" is given in Section 1.1 to allow
+ the explanation of multiplexing RTP sessions in Section 6 to be
+ more clear regarding the multiplexing of multiple media.
+
+
+
+
+Schulzrinne & Casner Standards Track [Page 37]
+
+RFC 3551 RTP A/V Profile July 2003
+
+
+ - The explanation of how to determine the number of audio frames
+ in a packet from the length was expanded.
+
+ - More description of the allocation of bandwidth to SDES items
+ is given.
+
+ - A note was added that the convention for the order of channels
+ specified in Section 4.1 may be overridden by a particular
+ encoding or payload format specification.
+
+ - The terms MUST, SHOULD, MAY, etc. are used as defined in RFC
+ 2119.
+
+ o A second author for this document was added.
+
+10. Security Considerations
+
+ Implementations using the profile defined in this specification are
+ subject to the security considerations discussed in the RTP
+ specification [1]. This profile does not specify any different
+ security services. The primary function of this profile is to list a
+ set of data compression encodings for audio and video media.
+
+ Confidentiality of the media streams is achieved by encryption.
+ Because the data compression used with the payload formats described
+ in this profile is applied end-to-end, encryption may be performed
+ after compression so there is no conflict between the two operations.
+
+ A potential denial-of-service threat exists for data encodings using
+ compression techniques that have non-uniform receiver-end
+ computational load. The attacker can inject pathological datagrams
+ into the stream which are complex to decode and cause the receiver to
+ be overloaded.
+
+ As with any IP-based protocol, in some circumstances a receiver may
+ be overloaded simply by the receipt of too many packets, either
+ desired or undesired. Network-layer authentication MAY be used to
+ discard packets from undesired sources, but the processing cost of
+ the authentication itself may be too high. In a multicast
+ environment, source pruning is implemented in IGMPv3 (RFC 3376) [24]
+ and in multicast routing protocols to allow a receiver to select
+ which sources are allowed to reach it.
+
+
+
+
+
+
+
+
+
+Schulzrinne & Casner Standards Track [Page 38]
+
+RFC 3551 RTP A/V Profile July 2003
+
+
+11. IANA Considerations
+
+ The RTP specification establishes a registry of profile names for use
+ by higher-level control protocols, such as the Session Description
+ Protocol (SDP), RFC 2327 [6], to refer to transport methods. This
+ profile registers the name "RTP/AVP".
+
+ Section 3 establishes the policy that no additional registration of
+ static RTP payload types for this profile will be made beyond those
+ added in this document revision and included in Tables 4 and 5. IANA
+ may reference that section in declining to accept any additional
+ registration requests. In Tables 4 and 5, note that types 1 and 2
+ have been marked reserved and the set of "dyn" payload types included
+ has been updated. These changes are explained in Sections 6 and 9.
+
+12. References
+
+12.1 Normative References
+
+ [1] Schulzrinne, H., Casner, S., Frederick, R. and V. Jacobson,
+ "RTP: A Transport Protocol for Real-Time Applications", RFC
+ 3550, July 2003.
+
+ [2] Bradner, S., "Key Words for Use in RFCs to Indicate Requirement
+ Levels", BCP 14, RFC 2119, March 1997.
+
+ [3] Apple Computer, "Audio Interchange File Format AIFF-C", August
+ 1991. (also ftp://ftp.sgi.com/sgi/aiff-c.9.26.91.ps.Z).
+
+12.2 Informative References
+
+ [4] Braden, R., Clark, D. and S. Shenker, "Integrated Services in
+ the Internet Architecture: an Overview", RFC 1633, June 1994.
+
+ [5] Blake, S., Black, D., Carlson, M., Davies, E., Wang, Z. and W.
+ Weiss, "An Architecture for Differentiated Service", RFC 2475,
+ December 1998.
+
+ [6] Handley, M. and V. Jacobson, "SDP: Session Description
+ Protocol", RFC 2327, April 1998.
+
+ [7] Casner, S. and P. Hoschka, "MIME Type Registration of RTP
+ Payload Types", RFC 3555, July 2003.
+
+ [8] Freed, N., Klensin, J. and J. Postel, "Multipurpose Internet
+ Mail Extensions (MIME) Part Four: Registration Procedures", BCP
+ 13, RFC 2048, November 1996.
+
+
+
+
+Schulzrinne & Casner Standards Track [Page 39]
+
+RFC 3551 RTP A/V Profile July 2003
+
+
+ [9] Zopf, R., "Real-time Transport Protocol (RTP) Payload for
+ Comfort Noise (CN)", RFC 3389, September 2002.
+
+ [10] Deleam, D. and J.-P. Petit, "Real-time implementations of the
+ recent ITU-T low bit rate speech coders on the TI TMS320C54X
+ DSP: results, methodology, and applications", in Proc. of
+ International Conference on Signal Processing, Technology, and
+ Applications (ICSPAT) , (Boston, Massachusetts), pp. 1656--1660,
+ October 1996.
+
+ [11] Mouly, M. and M.-B. Pautet, The GSM system for mobile
+ communications Lassay-les-Chateaux, France: Europe Media
+ Duplication, 1993.
+
+ [12] Degener, J., "Digital Speech Compression", Dr. Dobb's Journal,
+ December 1994.
+
+ [13] Redl, S., Weber, M. and M. Oliphant, An Introduction to GSM
+ Boston: Artech House, 1995.
+
+ [14] Hoffman, D., Fernando, G., Goyal, V. and M. Civanlar, "RTP
+ Payload Format for MPEG1/MPEG2 Video", RFC 2250, January 1998.
+
+ [15] Jayant, N. and P. Noll, Digital Coding of Waveforms--Principles
+ and Applications to Speech and Video Englewood Cliffs, New
+ Jersey: Prentice-Hall, 1984.
+
+ [16] McKay, K., "RTP Payload Format for PureVoice(tm) Audio", RFC
+ 2658, August 1999.
+
+ [17] Perkins, C., Kouvelas, I., Hodson, O., Hardman, V., Handley, M.,
+ Bolot, J.-C., Vega-Garcia, A. and S. Fosse-Parisis, "RTP Payload
+ for Redundant Audio Data", RFC 2198, September 1997.
+
+ [18] Speer, M. and D. Hoffman, "RTP Payload Format of Sun's CellB
+ Video Encoding", RFC 2029, October 1996.
+
+ [19] Berc, L., Fenner, W., Frederick, R., McCanne, S. and P. Stewart,
+ "RTP Payload Format for JPEG-Compressed Video", RFC 2435,
+ October 1998.
+
+ [20] Turletti, T. and C. Huitema, "RTP Payload Format for H.261 Video
+ Streams", RFC 2032, October 1996.
+
+ [21] Zhu, C., "RTP Payload Format for H.263 Video Streams", RFC 2190,
+ September 1997.
+
+
+
+
+
+Schulzrinne & Casner Standards Track [Page 40]
+
+RFC 3551 RTP A/V Profile July 2003
+
+
+ [22] Bormann, C., Cline, L., Deisher, G., Gardos, T., Maciocco, C.,
+ Newell, D., Ott, J., Sullivan, G., Wenger, S. and C. Zhu, "RTP
+ Payload Format for the 1998 Version of ITU-T Rec. H.263 Video
+ (H.263+)", RFC 2429, October 1998.
+
+ [23] Schulzrinne, H., Rao, A. and R. Lanphier, "Real Time Streaming
+ Protocol (RTSP)", RFC 2326, April 1998.
+
+ [24] Cain, B., Deering, S., Kouvelas, I., Fenner, B. and A.
+ Thyagarajan, "Internet Group Management Protocol, Version 3",
+ RFC 3376, October 2002.
+
+13. Current Locations of Related Resources
+
+ Note: Several sections below refer to the ITU-T Software Tool
+ Library (STL). It is available from the ITU Sales Service, Place des
+ Nations, CH-1211 Geneve 20, Switzerland (also check
+ http://www.itu.int). The ITU-T STL is covered by a license defined
+ in ITU-T Recommendation G.191, "Software tools for speech and audio
+ coding standardization".
+
+ DVI4
+
+ An archived copy of the document IMA Recommended Practices for
+ Enhancing Digital Audio Compatibility in Multimedia Systems (version
+ 3.0), which describes the IMA ADPCM algorithm, is available at:
+
+ http://www.cs.columbia.edu/~hgs/audio/dvi/
+
+ An implementation is available from Jack Jansen at
+
+ ftp://ftp.cwi.nl/local/pub/audio/adpcm.shar
+
+ G722
+
+ An implementation of the G.722 algorithm is available as part of the
+ ITU-T STL, described above.
+
+ G723
+
+ The reference C code implementation defining the G.723.1 algorithm
+ and its Annexes A, B, and C are available as an integral part of
+ Recommendation G.723.1 from the ITU Sales Service, address listed
+ above. Both the algorithm and C code are covered by a specific
+ license. The ITU-T Secretariat should be contacted to obtain such
+ licensing information.
+
+
+
+
+
+Schulzrinne & Casner Standards Track [Page 41]
+
+RFC 3551 RTP A/V Profile July 2003
+
+
+ G726
+
+ G726 is specified in the ITU-T Recommendation G.726, "40, 32, 24, and
+ 16 kb/s Adaptive Differential Pulse Code Modulation (ADPCM)". An
+ implementation of the G.726 algorithm is available as part of the
+ ITU-T STL, described above.
+
+ G729
+
+ The reference C code implementation defining the G.729 algorithm and
+ its Annexes A through I are available as an integral part of
+ Recommendation G.729 from the ITU Sales Service, listed above. Annex
+ I contains the integrated C source code for all G.729 operating
+ modes. The G.729 algorithm and associated C code are covered by a
+ specific license. The contact information for obtaining the license
+ is available from the ITU-T Secretariat.
+
+ GSM
+
+ A reference implementation was written by Carsten Bormann and Jutta
+ Degener (then at TU Berlin, Germany). It is available at
+
+ http://www.dmn.tzi.org/software/gsm/
+
+ Although the RPE-LTP algorithm is not an ITU-T standard, there is a C
+ code implementation of the RPE-LTP algorithm available as part of the
+ ITU-T STL. The STL implementation is an adaptation of the TU Berlin
+ version.
+
+ LPC
+
+ An implementation is available at
+
+ ftp://parcftp.xerox.com/pub/net-research/lpc.tar.Z
+
+ PCMU, PCMA
+
+ An implementation of these algorithms is available as part of the
+ ITU-T STL, described above.
+
+14. Acknowledgments
+
+ The comments and careful review of Simao Campos, Richard Cox and AVT
+ Working Group participants are gratefully acknowledged. The GSM
+ description was adopted from the IMTC Voice over IP Forum Service
+ Interoperability Implementation Agreement (January 1997). Fred Burg
+ and Terry Lyons helped with the G.729 description.
+
+
+
+
+Schulzrinne & Casner Standards Track [Page 42]
+
+RFC 3551 RTP A/V Profile July 2003
+
+
+15. Intellectual Property Rights Statement
+
+ The IETF takes no position regarding the validity or scope of any
+ intellectual property or other rights that might be claimed to
+ pertain to the implementation or use of the technology described in
+ this document or the extent to which any license under such rights
+ might or might not be available; neither does it represent that it
+ has made any effort to identify any such rights. Information on the
+ IETF's procedures with respect to rights in standards-track and
+ standards-related documentation can be found in BCP-11. Copies of
+ claims of rights made available for publication and any assurances of
+ licenses to be made available, or the result of an attempt made to
+ obtain a general license or permission for the use of such
+ proprietary rights by implementors or users of this specification can
+ be obtained from the IETF Secretariat.
+
+ The IETF invites any interested party to bring to its attention any
+ copyrights, patents or patent applications, or other proprietary
+ rights which may cover technology that may be required to practice
+ this standard. Please address the information to the IETF Executive
+ Director.
+
+16. Authors' Addresses
+
+ Henning Schulzrinne
+ Department of Computer Science
+ Columbia University
+ 1214 Amsterdam Avenue
+ New York, NY 10027
+ United States
+
+ EMail: schulzrinne@cs.columbia.edu
+
+
+ Stephen L. Casner
+ Packet Design
+ 3400 Hillview Avenue, Building 3
+ Palo Alto, CA 94304
+ United States
+
+ EMail: casner@acm.org
+
+
+
+
+
+
+
+
+
+
+Schulzrinne & Casner Standards Track [Page 43]
+
+RFC 3551 RTP A/V Profile July 2003
+
+
+17. Full Copyright Statement
+
+ Copyright (C) The Internet Society (2003). All Rights Reserved.
+
+ This document and translations of it may be copied and furnished to
+ others, and derivative works that comment on or otherwise explain it
+ or assist in its implementation may be prepared, copied, published
+ and distributed, in whole or in part, without restriction of any
+ kind, provided that the above copyright notice and this paragraph are
+ included on all such copies and derivative works. However, this
+ document itself may not be modified in any way, such as by removing
+ the copyright notice or references to the Internet Society or other
+ Internet organizations, except as needed for the purpose of
+ developing Internet standards in which case the procedures for
+ copyrights defined in the Internet Standards process must be
+ followed, or as required to translate it into languages other than
+ English.
+
+ The limited permissions granted above are perpetual and will not be
+ revoked by the Internet Society or its successors or assigns.
+
+ This document and the information contained herein is provided on an
+ "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING
+ TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING
+ BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION
+ HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF
+ MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
+
+Acknowledgement
+
+ Funding for the RFC Editor function is currently provided by the
+ Internet Society.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Schulzrinne & Casner Standards Track [Page 44]
+
diff --git a/src/modules/rtp/rtp.c b/src/modules/rtp/rtp.c
new file mode 100644
index 00000000..997fcc34
--- /dev/null
+++ b/src/modules/rtp/rtp.c
@@ -0,0 +1,364 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+
+#ifdef HAVE_SYS_FILIO_H
+#include <sys/filio.h>
+#endif
+
+#include <pulsecore/core-error.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/core-util.h>
+
+#include "rtp.h"
+
+pa_rtp_context* pa_rtp_context_init_send(pa_rtp_context *c, int fd, uint32_t ssrc, uint8_t payload, size_t frame_size) {
+ pa_assert(c);
+ pa_assert(fd >= 0);
+
+ c->fd = fd;
+ c->sequence = (uint16_t) (rand()*rand());
+ c->timestamp = 0;
+ c->ssrc = ssrc ? ssrc : (uint32_t) (rand()*rand());
+ c->payload = payload & 127;
+ c->frame_size = frame_size;
+
+ return c;
+}
+
+#define MAX_IOVECS 16
+
+int pa_rtp_send(pa_rtp_context *c, size_t size, pa_memblockq *q) {
+ struct iovec iov[MAX_IOVECS];
+ pa_memblock* mb[MAX_IOVECS];
+ int iov_idx = 1;
+ size_t n = 0;
+
+ pa_assert(c);
+ pa_assert(size > 0);
+ pa_assert(q);
+
+ if (pa_memblockq_get_length(q) < size)
+ return 0;
+
+ for (;;) {
+ int r;
+ pa_memchunk chunk;
+
+ pa_memchunk_reset(&chunk);
+
+ if ((r = pa_memblockq_peek(q, &chunk)) >= 0) {
+
+ size_t k = n + chunk.length > size ? size - n : chunk.length;
+
+ pa_assert(chunk.memblock);
+
+ iov[iov_idx].iov_base = ((uint8_t*) pa_memblock_acquire(chunk.memblock) + chunk.index);
+ iov[iov_idx].iov_len = k;
+ mb[iov_idx] = chunk.memblock;
+ iov_idx ++;
+
+ n += k;
+ pa_memblockq_drop(q, k);
+ }
+
+ pa_assert(n % c->frame_size == 0);
+
+ if (r < 0 || n >= size || iov_idx >= MAX_IOVECS) {
+ uint32_t header[3];
+ struct msghdr m;
+ int k, i;
+
+ if (n > 0) {
+ header[0] = htonl(((uint32_t) 2 << 30) | ((uint32_t) c->payload << 16) | ((uint32_t) c->sequence));
+ header[1] = htonl(c->timestamp);
+ header[2] = htonl(c->ssrc);
+
+ iov[0].iov_base = (void*)header;
+ iov[0].iov_len = sizeof(header);
+
+ m.msg_name = NULL;
+ m.msg_namelen = 0;
+ m.msg_iov = iov;
+ m.msg_iovlen = iov_idx;
+ m.msg_control = NULL;
+ m.msg_controllen = 0;
+ m.msg_flags = 0;
+
+ k = sendmsg(c->fd, &m, MSG_DONTWAIT);
+
+ for (i = 1; i < iov_idx; i++) {
+ pa_memblock_release(mb[i]);
+ pa_memblock_unref(mb[i]);
+ }
+
+ c->sequence++;
+ } else
+ k = 0;
+
+ c->timestamp += n/c->frame_size;
+
+ if (k < 0) {
+ if (errno != EAGAIN && errno != EINTR) /* If the queue is full, just ignore it */
+ pa_log("sendmsg() failed: %s", pa_cstrerror(errno));
+ return -1;
+ }
+
+ if (r < 0 || pa_memblockq_get_length(q) < size)
+ break;
+
+ n = 0;
+ iov_idx = 1;
+ }
+ }
+
+ return 0;
+}
+
+pa_rtp_context* pa_rtp_context_init_recv(pa_rtp_context *c, int fd, size_t frame_size) {
+ pa_assert(c);
+
+ c->fd = fd;
+ c->frame_size = frame_size;
+ return c;
+}
+
+int pa_rtp_recv(pa_rtp_context *c, pa_memchunk *chunk, pa_mempool *pool) {
+ int size;
+ struct msghdr m;
+ struct iovec iov;
+ uint32_t header;
+ int cc;
+ ssize_t r;
+
+ pa_assert(c);
+ pa_assert(chunk);
+
+ pa_memchunk_reset(chunk);
+
+ if (ioctl(c->fd, FIONREAD, &size) < 0) {
+ pa_log_warn("FIONREAD failed: %s", pa_cstrerror(errno));
+ goto fail;
+ }
+
+ if (!size)
+ return 0;
+
+ chunk->memblock = pa_memblock_new(pool, size);
+
+ iov.iov_base = pa_memblock_acquire(chunk->memblock);
+ iov.iov_len = size;
+
+ m.msg_name = NULL;
+ m.msg_namelen = 0;
+ m.msg_iov = &iov;
+ m.msg_iovlen = 1;
+ m.msg_control = NULL;
+ m.msg_controllen = 0;
+ m.msg_flags = 0;
+
+ r = recvmsg(c->fd, &m, 0);
+ pa_memblock_release(chunk->memblock);
+
+ if (r != size) {
+ if (r < 0 && errno != EAGAIN && errno != EINTR)
+ pa_log_warn("recvmsg() failed: %s", r < 0 ? pa_cstrerror(errno) : "size mismatch");
+
+ goto fail;
+ }
+
+ if (size < 12) {
+ pa_log_warn("RTP packet too short.");
+ goto fail;
+ }
+
+ memcpy(&header, iov.iov_base, sizeof(uint32_t));
+ memcpy(&c->timestamp, (uint8_t*) iov.iov_base + 4, sizeof(uint32_t));
+ memcpy(&c->ssrc, (uint8_t*) iov.iov_base + 8, sizeof(uint32_t));
+
+ header = ntohl(header);
+ c->timestamp = ntohl(c->timestamp);
+ c->ssrc = ntohl(c->ssrc);
+
+ if ((header >> 30) != 2) {
+ pa_log_warn("Unsupported RTP version.");
+ goto fail;
+ }
+
+ if ((header >> 29) & 1) {
+ pa_log_warn("RTP padding not supported.");
+ goto fail;
+ }
+
+ if ((header >> 28) & 1) {
+ pa_log_warn("RTP header extensions not supported.");
+ goto fail;
+ }
+
+ cc = (header >> 24) & 0xF;
+ c->payload = (header >> 16) & 127;
+ c->sequence = header & 0xFFFF;
+
+ if (12 + cc*4 > size) {
+ pa_log_warn("RTP packet too short. (CSRC)");
+ goto fail;
+ }
+
+ chunk->index = 12 + cc*4;
+ chunk->length = size - chunk->index;
+
+ if (chunk->length % c->frame_size != 0) {
+ pa_log_warn("Bad RTP packet size.");
+ goto fail;
+ }
+
+ return 0;
+
+fail:
+ if (chunk->memblock)
+ pa_memblock_unref(chunk->memblock);
+
+ return -1;
+}
+
+uint8_t pa_rtp_payload_from_sample_spec(const pa_sample_spec *ss) {
+ pa_assert(ss);
+
+ if (ss->format == PA_SAMPLE_ULAW && ss->rate == 8000 && ss->channels == 1)
+ return 0;
+ if (ss->format == PA_SAMPLE_ALAW && ss->rate == 8000 && ss->channels == 1)
+ return 8;
+ if (ss->format == PA_SAMPLE_S16BE && ss->rate == 44100 && ss->channels == 2)
+ return 10;
+ if (ss->format == PA_SAMPLE_S16BE && ss->rate == 44100 && ss->channels == 1)
+ return 11;
+
+ return 127;
+}
+
+pa_sample_spec *pa_rtp_sample_spec_from_payload(uint8_t payload, pa_sample_spec *ss) {
+ pa_assert(ss);
+
+ switch (payload) {
+ case 0:
+ ss->channels = 1;
+ ss->format = PA_SAMPLE_ULAW;
+ ss->rate = 8000;
+ break;
+
+ case 8:
+ ss->channels = 1;
+ ss->format = PA_SAMPLE_ALAW;
+ ss->rate = 8000;
+ break;
+
+ case 10:
+ ss->channels = 2;
+ ss->format = PA_SAMPLE_S16BE;
+ ss->rate = 44100;
+ break;
+
+ case 11:
+ ss->channels = 1;
+ ss->format = PA_SAMPLE_S16BE;
+ ss->rate = 44100;
+ break;
+
+ default:
+ return NULL;
+ }
+
+ return ss;
+}
+
+pa_sample_spec *pa_rtp_sample_spec_fixup(pa_sample_spec * ss) {
+ pa_assert(ss);
+
+ if (!pa_rtp_sample_spec_valid(ss))
+ ss->format = PA_SAMPLE_S16BE;
+
+ pa_assert(pa_rtp_sample_spec_valid(ss));
+ return ss;
+}
+
+int pa_rtp_sample_spec_valid(const pa_sample_spec *ss) {
+ pa_assert(ss);
+
+ if (!pa_sample_spec_valid(ss))
+ return 0;
+
+ return
+ ss->format == PA_SAMPLE_U8 ||
+ ss->format == PA_SAMPLE_ALAW ||
+ ss->format == PA_SAMPLE_ULAW ||
+ ss->format == PA_SAMPLE_S16BE;
+}
+
+void pa_rtp_context_destroy(pa_rtp_context *c) {
+ pa_assert(c);
+
+ pa_close(c->fd);
+}
+
+const char* pa_rtp_format_to_string(pa_sample_format_t f) {
+ switch (f) {
+ case PA_SAMPLE_S16BE:
+ return "L16";
+ case PA_SAMPLE_U8:
+ return "L8";
+ case PA_SAMPLE_ALAW:
+ return "PCMA";
+ case PA_SAMPLE_ULAW:
+ return "PCMU";
+ default:
+ return NULL;
+ }
+}
+
+pa_sample_format_t pa_rtp_string_to_format(const char *s) {
+ pa_assert(s);
+
+ if (!(strcmp(s, "L16")))
+ return PA_SAMPLE_S16BE;
+ else if (!strcmp(s, "L8"))
+ return PA_SAMPLE_U8;
+ else if (!strcmp(s, "PCMA"))
+ return PA_SAMPLE_ALAW;
+ else if (!strcmp(s, "PCMU"))
+ return PA_SAMPLE_ULAW;
+ else
+ return PA_SAMPLE_INVALID;
+}
+
diff --git a/src/modules/rtp/rtp.h b/src/modules/rtp/rtp.h
new file mode 100644
index 00000000..ad7175ca
--- /dev/null
+++ b/src/modules/rtp/rtp.h
@@ -0,0 +1,59 @@
+#ifndef foortphfoo
+#define foortphfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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 <inttypes.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <pulsecore/memblockq.h>
+#include <pulsecore/memchunk.h>
+
+typedef struct pa_rtp_context {
+ int fd;
+ uint16_t sequence;
+ uint32_t timestamp;
+ uint32_t ssrc;
+ uint8_t payload;
+ size_t frame_size;
+} pa_rtp_context;
+
+pa_rtp_context* pa_rtp_context_init_send(pa_rtp_context *c, int fd, uint32_t ssrc, uint8_t payload, size_t frame_size);
+int pa_rtp_send(pa_rtp_context *c, size_t size, pa_memblockq *q);
+
+pa_rtp_context* pa_rtp_context_init_recv(pa_rtp_context *c, int fd, size_t frame_size);
+int pa_rtp_recv(pa_rtp_context *c, pa_memchunk *chunk, pa_mempool *pool);
+
+void pa_rtp_context_destroy(pa_rtp_context *c);
+
+pa_sample_spec* pa_rtp_sample_spec_fixup(pa_sample_spec *ss);
+int pa_rtp_sample_spec_valid(const pa_sample_spec *ss);
+
+uint8_t pa_rtp_payload_from_sample_spec(const pa_sample_spec *ss);
+pa_sample_spec *pa_rtp_sample_spec_from_payload(uint8_t payload, pa_sample_spec *ss);
+
+const char* pa_rtp_format_to_string(pa_sample_format_t f);
+pa_sample_format_t pa_rtp_string_to_format(const char *s);
+
+#endif
diff --git a/src/modules/rtp/sap.c b/src/modules/rtp/sap.c
new file mode 100644
index 00000000..ed7eb0be
--- /dev/null
+++ b/src/modules/rtp/sap.c
@@ -0,0 +1,223 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <time.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+
+#ifdef HAVE_SYS_FILIO_H
+#include <sys/filio.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 "sap.h"
+#include "sdp.h"
+
+#define MIME_TYPE "application/sdp"
+
+pa_sap_context* pa_sap_context_init_send(pa_sap_context *c, int fd, char *sdp_data) {
+ pa_assert(c);
+ pa_assert(fd >= 0);
+ pa_assert(sdp_data);
+
+ c->fd = fd;
+ c->sdp_data = sdp_data;
+ c->msg_id_hash = (uint16_t) (rand()*rand());
+
+ return c;
+}
+
+void pa_sap_context_destroy(pa_sap_context *c) {
+ pa_assert(c);
+
+ pa_close(c->fd);
+ pa_xfree(c->sdp_data);
+}
+
+int pa_sap_send(pa_sap_context *c, int goodbye) {
+ uint32_t header;
+ struct sockaddr_storage sa_buf;
+ struct sockaddr *sa = (struct sockaddr*) &sa_buf;
+ socklen_t salen = sizeof(sa_buf);
+ struct iovec iov[4];
+ struct msghdr m;
+ int k;
+
+ if (getsockname(c->fd, sa, &salen) < 0) {
+ pa_log("getsockname() failed: %s\n", pa_cstrerror(errno));
+ return -1;
+ }
+
+ pa_assert(sa->sa_family == AF_INET || sa->sa_family == AF_INET6);
+
+ header = htonl(((uint32_t) 1 << 29) |
+ (sa->sa_family == AF_INET6 ? (uint32_t) 1 << 28 : 0) |
+ (goodbye ? (uint32_t) 1 << 26 : 0) |
+ (c->msg_id_hash));
+
+ iov[0].iov_base = &header;
+ iov[0].iov_len = sizeof(header);
+
+ iov[1].iov_base = sa->sa_family == AF_INET ? (void*) &((struct sockaddr_in*) sa)->sin_addr : (void*) &((struct sockaddr_in6*) sa)->sin6_addr;
+ iov[1].iov_len = sa->sa_family == AF_INET ? 4 : 16;
+
+ iov[2].iov_base = (char*) MIME_TYPE;
+ iov[2].iov_len = sizeof(MIME_TYPE);
+
+ iov[3].iov_base = c->sdp_data;
+ iov[3].iov_len = strlen(c->sdp_data);
+
+ m.msg_name = NULL;
+ m.msg_namelen = 0;
+ m.msg_iov = iov;
+ m.msg_iovlen = 4;
+ m.msg_control = NULL;
+ m.msg_controllen = 0;
+ m.msg_flags = 0;
+
+ if ((k = sendmsg(c->fd, &m, MSG_DONTWAIT)) < 0)
+ pa_log_warn("sendmsg() failed: %s\n", pa_cstrerror(errno));
+
+ return k;
+}
+
+pa_sap_context* pa_sap_context_init_recv(pa_sap_context *c, int fd) {
+ pa_assert(c);
+ pa_assert(fd >= 0);
+
+ c->fd = fd;
+ c->sdp_data = NULL;
+ return c;
+}
+
+int pa_sap_recv(pa_sap_context *c, int *goodbye) {
+ struct msghdr m;
+ struct iovec iov;
+ int size, k;
+ char *buf = NULL, *e;
+ uint32_t header;
+ int six, ac;
+ ssize_t r;
+
+ pa_assert(c);
+ pa_assert(goodbye);
+
+ if (ioctl(c->fd, FIONREAD, &size) < 0) {
+ pa_log_warn("FIONREAD failed: %s", pa_cstrerror(errno));
+ goto fail;
+ }
+
+ buf = pa_xnew(char, size+1);
+ buf[size] = 0;
+
+ iov.iov_base = buf;
+ iov.iov_len = size;
+
+ m.msg_name = NULL;
+ m.msg_namelen = 0;
+ m.msg_iov = &iov;
+ m.msg_iovlen = 1;
+ m.msg_control = NULL;
+ m.msg_controllen = 0;
+ m.msg_flags = 0;
+
+ if ((r = recvmsg(c->fd, &m, 0)) != size) {
+ pa_log_warn("recvmsg() failed: %s", r < 0 ? pa_cstrerror(errno) : "size mismatch");
+ goto fail;
+ }
+
+ if (size < 4) {
+ pa_log_warn("SAP packet too short.");
+ goto fail;
+ }
+
+ memcpy(&header, buf, sizeof(uint32_t));
+ header = ntohl(header);
+
+ if (header >> 29 != 1) {
+ pa_log_warn("Unsupported SAP version.");
+ goto fail;
+ }
+
+ if ((header >> 25) & 1) {
+ pa_log_warn("Encrypted SAP not supported.");
+ goto fail;
+ }
+
+ if ((header >> 24) & 1) {
+ pa_log_warn("Compressed SAP not supported.");
+ goto fail;
+ }
+
+ six = (header >> 28) & 1;
+ ac = (header >> 16) & 0xFF;
+
+ k = 4 + (six ? 16 : 4) + ac*4;
+ if (size < k) {
+ pa_log_warn("SAP packet too short (AD).");
+ goto fail;
+ }
+
+ e = buf + k;
+ size -= k;
+
+ if ((unsigned) size >= sizeof(MIME_TYPE) && !strcmp(e, MIME_TYPE)) {
+ e += sizeof(MIME_TYPE);
+ size -= sizeof(MIME_TYPE);
+ } else if ((unsigned) size < sizeof(PA_SDP_HEADER)-1 || strncmp(e, PA_SDP_HEADER, sizeof(PA_SDP_HEADER)-1)) {
+ pa_log_warn("Invalid SDP header.");
+ goto fail;
+ }
+
+ if (c->sdp_data)
+ pa_xfree(c->sdp_data);
+
+ c->sdp_data = pa_xstrndup(e, size);
+ pa_xfree(buf);
+
+ *goodbye = !!((header >> 26) & 1);
+
+ return 0;
+
+fail:
+ pa_xfree(buf);
+
+ return -1;
+}
diff --git a/src/modules/rtp/sap.h b/src/modules/rtp/sap.h
new file mode 100644
index 00000000..f906a32b
--- /dev/null
+++ b/src/modules/rtp/sap.h
@@ -0,0 +1,48 @@
+#ifndef foosaphfoo
+#define foosaphfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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 <inttypes.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <pulsecore/memblockq.h>
+#include <pulsecore/memchunk.h>
+
+typedef struct pa_sap_context {
+ int fd;
+ char *sdp_data;
+
+ uint16_t msg_id_hash;
+} pa_sap_context;
+
+pa_sap_context* pa_sap_context_init_send(pa_sap_context *c, int fd, char *sdp_data);
+void pa_sap_context_destroy(pa_sap_context *c);
+
+int pa_sap_send(pa_sap_context *c, int goodbye);
+
+pa_sap_context* pa_sap_context_init_recv(pa_sap_context *c, int fd);
+int pa_sap_recv(pa_sap_context *c, int *goodbye);
+
+#endif
diff --git a/src/modules/rtp/sdp.c b/src/modules/rtp/sdp.c
new file mode 100644
index 00000000..50ac157a
--- /dev/null
+++ b/src/modules/rtp/sdp.c
@@ -0,0 +1,261 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <time.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+
+#include <pulse/xmalloc.h>
+#include <pulse/util.h>
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+
+#include "sdp.h"
+#include "rtp.h"
+
+char *pa_sdp_build(int af, const void *src, const void *dst, const char *name, uint16_t port, uint8_t payload, const pa_sample_spec *ss) {
+ uint32_t ntp;
+ char buf_src[64], buf_dst[64], un[64];
+ const char *u, *f, *a;
+
+ pa_assert(src);
+ pa_assert(dst);
+ pa_assert(af == AF_INET || af == AF_INET6);
+
+ pa_assert_se(f = pa_rtp_format_to_string(ss->format));
+
+ if (!(u = pa_get_user_name(un, sizeof(un))))
+ u = "-";
+
+ ntp = time(NULL) + 2208988800U;
+
+ pa_assert_se(a = inet_ntop(af, src, buf_src, sizeof(buf_src)));
+ pa_assert_se(a = inet_ntop(af, dst, buf_dst, sizeof(buf_dst)));
+
+ return pa_sprintf_malloc(
+ PA_SDP_HEADER
+ "o=%s %lu 0 IN %s %s\n"
+ "s=%s\n"
+ "c=IN %s %s\n"
+ "t=%lu 0\n"
+ "a=recvonly\n"
+ "m=audio %u RTP/AVP %i\n"
+ "a=rtpmap:%i %s/%u/%u\n"
+ "a=type:broadcast\n",
+ u, (unsigned long) ntp, af == AF_INET ? "IP4" : "IP6", buf_src,
+ name,
+ af == AF_INET ? "IP4" : "IP6", buf_dst,
+ (unsigned long) ntp,
+ port, payload,
+ payload, f, ss->rate, ss->channels);
+}
+
+static pa_sample_spec *parse_sdp_sample_spec(pa_sample_spec *ss, char *c) {
+ unsigned rate, channels;
+ pa_assert(ss);
+ pa_assert(c);
+
+ if (pa_startswith(c, "L16/")) {
+ ss->format = PA_SAMPLE_S16BE;
+ c += 4;
+ } else if (pa_startswith(c, "L8/")) {
+ ss->format = PA_SAMPLE_U8;
+ c += 3;
+ } else if (pa_startswith(c, "PCMA/")) {
+ ss->format = PA_SAMPLE_ALAW;
+ c += 5;
+ } else if (pa_startswith(c, "PCMU/")) {
+ ss->format = PA_SAMPLE_ULAW;
+ c += 5;
+ } else
+ return NULL;
+
+ if (sscanf(c, "%u/%u", &rate, &channels) == 2) {
+ ss->rate = rate;
+ ss->channels = channels;
+ } else if (sscanf(c, "%u", &rate) == 2) {
+ ss->rate = rate;
+ ss->channels = 1;
+ } else
+ return NULL;
+
+ if (!pa_sample_spec_valid(ss))
+ return NULL;
+
+ return ss;
+}
+
+pa_sdp_info *pa_sdp_parse(const char *t, pa_sdp_info *i, int is_goodbye) {
+ uint16_t port = 0;
+ int ss_valid = 0;
+
+ pa_assert(t);
+ pa_assert(i);
+
+ i->origin = i->session_name = NULL;
+ i->salen = 0;
+ i->payload = 255;
+
+ if (!pa_startswith(t, PA_SDP_HEADER)) {
+ pa_log("Failed to parse SDP data: invalid header.");
+ goto fail;
+ }
+
+ t += sizeof(PA_SDP_HEADER)-1;
+
+ while (*t) {
+ size_t l;
+
+ l = strcspn(t, "\n");
+
+ if (l <= 2) {
+ pa_log("Failed to parse SDP data: line too short: >%s<.", t);
+ goto fail;
+ }
+
+ if (pa_startswith(t, "o="))
+ i->origin = pa_xstrndup(t+2, l-2);
+ else if (pa_startswith(t, "s="))
+ i->session_name = pa_xstrndup(t+2, l-2);
+ else if (pa_startswith(t, "c=IN IP4 ")) {
+ char a[64];
+ size_t k;
+
+ k = l-8 > sizeof(a) ? sizeof(a) : l-8;
+
+ pa_strlcpy(a, t+9, k);
+ a[strcspn(a, "/")] = 0;
+
+ if (inet_pton(AF_INET, a, &((struct sockaddr_in*) &i->sa)->sin_addr) <= 0) {
+ pa_log("Failed to parse SDP data: bad address: >%s<.", a);
+ goto fail;
+ }
+
+ ((struct sockaddr_in*) &i->sa)->sin_family = AF_INET;
+ ((struct sockaddr_in*) &i->sa)->sin_port = 0;
+ i->salen = sizeof(struct sockaddr_in);
+ } else if (pa_startswith(t, "c=IN IP6 ")) {
+ char a[64];
+ size_t k;
+
+ k = l-8 > sizeof(a) ? sizeof(a) : l-8;
+
+ pa_strlcpy(a, t+9, k);
+ a[strcspn(a, "/")] = 0;
+
+ if (inet_pton(AF_INET6, a, &((struct sockaddr_in6*) &i->sa)->sin6_addr) <= 0) {
+ pa_log("Failed to parse SDP data: bad address: >%s<.", a);
+ goto fail;
+ }
+
+ ((struct sockaddr_in6*) &i->sa)->sin6_family = AF_INET6;
+ ((struct sockaddr_in6*) &i->sa)->sin6_port = 0;
+ i->salen = sizeof(struct sockaddr_in6);
+ } else if (pa_startswith(t, "m=audio ")) {
+
+ if (i->payload > 127) {
+ int _port, _payload;
+
+ if (sscanf(t+8, "%i RTP/AVP %i", &_port, &_payload) == 2) {
+
+ if (_port <= 0 || _port > 0xFFFF) {
+ pa_log("Failed to parse SDP data: invalid port %i.", _port);
+ goto fail;
+ }
+
+ if (_payload < 0 || _payload > 127) {
+ pa_log("Failed to parse SDP data: invalid payload %i.", _payload);
+ goto fail;
+ }
+
+ port = (uint16_t) _port;
+ i->payload = (uint8_t) _payload;
+
+ if (pa_rtp_sample_spec_from_payload(i->payload, &i->sample_spec))
+ ss_valid = 1;
+ }
+ }
+ } else if (pa_startswith(t, "a=rtpmap:")) {
+
+ if (i->payload <= 127) {
+ char c[64];
+ int _payload;
+
+ if (sscanf(t+9, "%i %64c", &_payload, c) == 2) {
+
+ if (_payload < 0 || _payload > 127) {
+ pa_log("Failed to parse SDP data: invalid payload %i.", _payload);
+ goto fail;
+ }
+ if (_payload == i->payload) {
+
+ c[strcspn(c, "\n")] = 0;
+
+ if (parse_sdp_sample_spec(&i->sample_spec, c))
+ ss_valid = 1;
+ }
+ }
+ }
+ }
+
+ t += l;
+
+ if (*t == '\n')
+ t++;
+ }
+
+ if (!i->origin || (!is_goodbye && (!i->salen || i->payload > 127 || !ss_valid || port == 0))) {
+ pa_log("Failed to parse SDP data: missing data.");
+ goto fail;
+ }
+
+ if (((struct sockaddr*) &i->sa)->sa_family == AF_INET)
+ ((struct sockaddr_in*) &i->sa)->sin_port = htons(port);
+ else
+ ((struct sockaddr_in6*) &i->sa)->sin6_port = htons(port);
+
+ return i;
+
+fail:
+ pa_xfree(i->origin);
+ pa_xfree(i->session_name);
+
+ return NULL;
+}
+
+void pa_sdp_info_destroy(pa_sdp_info *i) {
+ pa_assert(i);
+
+ pa_xfree(i->origin);
+ pa_xfree(i->session_name);
+}
diff --git a/src/modules/rtp/sdp.h b/src/modules/rtp/sdp.h
new file mode 100644
index 00000000..7c91fca6
--- /dev/null
+++ b/src/modules/rtp/sdp.h
@@ -0,0 +1,52 @@
+#ifndef foosdphfoo
+#define foosdphfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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 <inttypes.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <pulse/sample.h>
+
+#define PA_SDP_HEADER "v=0\n"
+
+typedef struct pa_sdp_info {
+ char *origin;
+ char *session_name;
+
+ struct sockaddr_storage sa;
+ socklen_t salen;
+
+ pa_sample_spec sample_spec;
+ uint8_t payload;
+} pa_sdp_info;
+
+char *pa_sdp_build(int af, const void *src, const void *dst, const char *name, uint16_t port, uint8_t payload, const pa_sample_spec *ss);
+
+pa_sdp_info *pa_sdp_parse(const char *t, pa_sdp_info *info, int is_goodbye);
+
+void pa_sdp_info_destroy(pa_sdp_info *i);
+
+#endif
diff --git a/src/pulse/Makefile b/src/pulse/Makefile
new file mode 100644
index 00000000..7c8875f3
--- /dev/null
+++ b/src/pulse/Makefile
@@ -0,0 +1,13 @@
+# This is a dirty trick just to ease compilation with emacs
+#
+# This file is not intended to be distributed or anything
+#
+# So: don't touch it, even better ignore it!
+
+all:
+ $(MAKE) -C ..
+
+clean:
+ $(MAKE) -C .. clean
+
+.PHONY: all clean
diff --git a/src/pulse/browser.c b/src/pulse/browser.c
new file mode 100644
index 00000000..55e0b2cd
--- /dev/null
+++ b/src/pulse/browser.c
@@ -0,0 +1,453 @@
+/* $Id$ */
+
+/***
+ 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 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+
+#include <avahi-client/lookup.h>
+#include <avahi-common/domain.h>
+#include <avahi-common/error.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/log.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/avahi-wrap.h>
+#include <pulsecore/refcnt.h>
+#include <pulsecore/macro.h>
+
+#include "browser.h"
+
+#define SERVICE_TYPE_SINK "_pulse-sink._tcp."
+#define SERVICE_TYPE_SOURCE "_pulse-source._tcp."
+#define SERVICE_TYPE_SERVER "_pulse-server._tcp."
+
+struct pa_browser {
+ PA_REFCNT_DECLARE;
+
+ pa_mainloop_api *mainloop;
+ AvahiPoll* avahi_poll;
+
+ pa_browse_cb_t callback;
+ void *userdata;
+
+ pa_browser_error_cb_t error_callback;
+ void *error_userdata;
+
+ AvahiClient *client;
+ AvahiServiceBrowser *server_browser, *sink_browser, *source_browser;
+
+};
+
+static int map_to_opcode(const char *type, int new) {
+
+ if (avahi_domain_equal(type, SERVICE_TYPE_SINK))
+ return new ? PA_BROWSE_NEW_SINK : PA_BROWSE_REMOVE_SINK;
+ else if (avahi_domain_equal(type, SERVICE_TYPE_SOURCE))
+ return new ? PA_BROWSE_NEW_SOURCE : PA_BROWSE_REMOVE_SOURCE;
+ else if (avahi_domain_equal(type, SERVICE_TYPE_SERVER))
+ return new ? PA_BROWSE_NEW_SERVER : PA_BROWSE_REMOVE_SERVER;
+
+ return -1;
+}
+
+static void resolve_callback(
+ AvahiServiceResolver *r,
+ AvahiIfIndex interface,
+ AvahiProtocol protocol,
+ AvahiResolverEvent event,
+ const char *name,
+ const char *type,
+ const char *domain,
+ const char *host_name,
+ const AvahiAddress *aa,
+ uint16_t port,
+ AvahiStringList *txt,
+ AvahiLookupResultFlags flags,
+ void *userdata) {
+
+ pa_browser *b = userdata;
+ pa_browse_info i;
+ char ip[256], a[256];
+ int opcode;
+ int device_found = 0;
+ uint32_t cookie;
+ pa_sample_spec ss;
+ int ss_valid = 0;
+ char *key = NULL, *value = NULL;
+
+ pa_assert(b);
+ pa_assert(PA_REFCNT_VALUE(b) >= 1);
+
+ memset(&i, 0, sizeof(i));
+ i.name = name;
+
+ if (event != AVAHI_RESOLVER_FOUND)
+ goto fail;
+
+ if (!b->callback)
+ goto fail;
+
+ opcode = map_to_opcode(type, 1);
+ pa_assert(opcode >= 0);
+
+ if (aa->proto == AVAHI_PROTO_INET)
+ pa_snprintf(a, sizeof(a), "tcp:%s:%u", avahi_address_snprint(ip, sizeof(ip), aa), port);
+ else {
+ pa_assert(aa->proto == AVAHI_PROTO_INET6);
+ pa_snprintf(a, sizeof(a), "tcp6:%s:%u", avahi_address_snprint(ip, sizeof(ip), aa), port);
+ }
+ i.server = a;
+
+
+ while (txt) {
+
+ if (avahi_string_list_get_pair(txt, &key, &value, NULL) < 0)
+ break;
+
+ if (!strcmp(key, "device")) {
+ device_found = 1;
+ pa_xfree((char*) i.device);
+ i.device = value;
+ value = NULL;
+ } else if (!strcmp(key, "server-version")) {
+ pa_xfree((char*) i.server_version);
+ i.server_version = value;
+ value = NULL;
+ } else if (!strcmp(key, "user-name")) {
+ pa_xfree((char*) i.user_name);
+ i.user_name = value;
+ value = NULL;
+ } else if (!strcmp(key, "fqdn")) {
+ size_t l;
+
+ pa_xfree((char*) i.fqdn);
+ i.fqdn = value;
+ value = NULL;
+
+ l = strlen(a);
+ pa_assert(l+1 <= sizeof(a));
+ strncat(a, " ", sizeof(a)-l-1);
+ strncat(a, i.fqdn, sizeof(a)-l-2);
+ } else if (!strcmp(key, "cookie")) {
+
+ if (pa_atou(value, &cookie) < 0)
+ goto fail;
+
+ i.cookie = &cookie;
+ } else if (!strcmp(key, "description")) {
+ pa_xfree((char*) i.description);
+ i.description = value;
+ value = NULL;
+ } else if (!strcmp(key, "channels")) {
+ uint32_t ch;
+
+ if (pa_atou(value, &ch) < 0 || ch <= 0 || ch > 255)
+ goto fail;
+
+ ss.channels = (uint8_t) ch;
+ ss_valid |= 1;
+
+ } else if (!strcmp(key, "rate")) {
+ if (pa_atou(value, &ss.rate) < 0)
+ goto fail;
+ ss_valid |= 2;
+ } else if (!strcmp(key, "format")) {
+
+ if ((ss.format = pa_parse_sample_format(value)) == PA_SAMPLE_INVALID)
+ goto fail;
+
+ ss_valid |= 4;
+ }
+
+ pa_xfree(key);
+ pa_xfree(value);
+ key = value = NULL;
+
+ txt = avahi_string_list_get_next(txt);
+ }
+
+ /* No device txt record was sent for a sink or source service */
+ if (opcode != PA_BROWSE_NEW_SERVER && !device_found)
+ goto fail;
+
+ if (ss_valid == 7)
+ i.sample_spec = &ss;
+
+ b->callback(b, opcode, &i, b->userdata);
+
+fail:
+ pa_xfree((void*) i.device);
+ pa_xfree((void*) i.fqdn);
+ pa_xfree((void*) i.server_version);
+ pa_xfree((void*) i.user_name);
+ pa_xfree((void*) i.description);
+
+ pa_xfree(key);
+ pa_xfree(value);
+
+ avahi_service_resolver_free(r);
+}
+
+static void handle_failure(pa_browser *b) {
+ const char *e = NULL;
+
+ pa_assert(b);
+ pa_assert(PA_REFCNT_VALUE(b) >= 1);
+
+ if (b->sink_browser)
+ avahi_service_browser_free(b->sink_browser);
+ if (b->source_browser)
+ avahi_service_browser_free(b->source_browser);
+ if (b->server_browser)
+ avahi_service_browser_free(b->server_browser);
+
+ b->sink_browser = b->source_browser = b->server_browser = NULL;
+
+ if (b->client) {
+ e = avahi_strerror(avahi_client_errno(b->client));
+ avahi_client_free(b->client);
+ }
+
+ b->client = NULL;
+
+ if (b->error_callback)
+ b->error_callback(b, e, b->error_userdata);
+}
+
+static void browse_callback(
+ AvahiServiceBrowser *sb,
+ AvahiIfIndex interface,
+ AvahiProtocol protocol,
+ AvahiBrowserEvent event,
+ const char *name,
+ const char *type,
+ const char *domain,
+ AvahiLookupResultFlags flags,
+ void *userdata) {
+
+ pa_browser *b = userdata;
+
+ pa_assert(b);
+ pa_assert(PA_REFCNT_VALUE(b) >= 1);
+
+ switch (event) {
+ case AVAHI_BROWSER_NEW: {
+
+ if (!avahi_service_resolver_new(
+ b->client,
+ interface,
+ protocol,
+ name,
+ type,
+ domain,
+ AVAHI_PROTO_UNSPEC,
+ 0,
+ resolve_callback,
+ b))
+ handle_failure(b);
+
+ break;
+ }
+
+ case AVAHI_BROWSER_REMOVE: {
+
+ if (b->callback) {
+ pa_browse_info i;
+ int opcode;
+
+ memset(&i, 0, sizeof(i));
+ i.name = name;
+
+ opcode = map_to_opcode(type, 0);
+ pa_assert(opcode >= 0);
+
+ b->callback(b, opcode, &i, b->userdata);
+ }
+ break;
+ }
+
+ case AVAHI_BROWSER_FAILURE: {
+ handle_failure(b);
+ break;
+ }
+
+ default:
+ ;
+ }
+}
+
+static void client_callback(AvahiClient *s, AvahiClientState state, void *userdata) {
+ pa_browser *b = userdata;
+
+ pa_assert(s);
+ pa_assert(b);
+ pa_assert(PA_REFCNT_VALUE(b) >= 1);
+
+ if (state == AVAHI_CLIENT_FAILURE)
+ handle_failure(b);
+}
+
+static void browser_free(pa_browser *b);
+
+pa_browser *pa_browser_new(pa_mainloop_api *mainloop) {
+ return pa_browser_new_full(mainloop, PA_BROWSE_FOR_SERVERS|PA_BROWSE_FOR_SINKS|PA_BROWSE_FOR_SOURCES, NULL);
+}
+
+pa_browser *pa_browser_new_full(pa_mainloop_api *mainloop, pa_browse_flags_t flags, const char **error_string) {
+ pa_browser *b;
+ int error;
+
+ pa_assert(mainloop);
+
+ if (flags & ~(PA_BROWSE_FOR_SERVERS|PA_BROWSE_FOR_SINKS|PA_BROWSE_FOR_SOURCES) || flags == 0)
+ return NULL;
+
+ b = pa_xnew(pa_browser, 1);
+ b->mainloop = mainloop;
+ PA_REFCNT_INIT(b);
+ b->callback = NULL;
+ b->userdata = NULL;
+ b->error_callback = NULL;
+ b->error_userdata = NULL;
+ b->sink_browser = b->source_browser = b->server_browser = NULL;
+
+ b->avahi_poll = pa_avahi_poll_new(mainloop);
+
+ if (!(b->client = avahi_client_new(b->avahi_poll, 0, client_callback, b, &error))) {
+ if (error_string)
+ *error_string = avahi_strerror(error);
+ goto fail;
+ }
+
+ if ((flags & PA_BROWSE_FOR_SERVERS) &&
+ !(b->server_browser = avahi_service_browser_new(
+ b->client,
+ AVAHI_IF_UNSPEC,
+ AVAHI_PROTO_INET,
+ SERVICE_TYPE_SERVER,
+ NULL,
+ 0,
+ browse_callback,
+ b))) {
+
+ if (error_string)
+ *error_string = avahi_strerror(avahi_client_errno(b->client));
+ goto fail;
+ }
+
+ if ((flags & PA_BROWSE_FOR_SINKS) &&
+ !(b->sink_browser = avahi_service_browser_new(
+ b->client,
+ AVAHI_IF_UNSPEC,
+ AVAHI_PROTO_UNSPEC,
+ SERVICE_TYPE_SINK,
+ NULL,
+ 0,
+ browse_callback,
+ b))) {
+
+ if (error_string)
+ *error_string = avahi_strerror(avahi_client_errno(b->client));
+ goto fail;
+ }
+
+ if ((flags & PA_BROWSE_FOR_SOURCES) &&
+ !(b->source_browser = avahi_service_browser_new(
+ b->client,
+ AVAHI_IF_UNSPEC,
+ AVAHI_PROTO_UNSPEC,
+ SERVICE_TYPE_SOURCE,
+ NULL,
+ 0,
+ browse_callback,
+ b))) {
+
+ if (error_string)
+ *error_string = avahi_strerror(avahi_client_errno(b->client));
+ goto fail;
+ }
+
+ return b;
+
+fail:
+ if (b)
+ browser_free(b);
+
+ return NULL;
+}
+
+static void browser_free(pa_browser *b) {
+ pa_assert(b);
+ pa_assert(b->mainloop);
+
+ if (b->sink_browser)
+ avahi_service_browser_free(b->sink_browser);
+ if (b->source_browser)
+ avahi_service_browser_free(b->source_browser);
+ if (b->server_browser)
+ avahi_service_browser_free(b->server_browser);
+
+ if (b->client)
+ avahi_client_free(b->client);
+
+ if (b->avahi_poll)
+ pa_avahi_poll_free(b->avahi_poll);
+
+ pa_xfree(b);
+}
+
+pa_browser *pa_browser_ref(pa_browser *b) {
+ pa_assert(b);
+ pa_assert(PA_REFCNT_VALUE(b) >= 1);
+
+ PA_REFCNT_INC(b);
+ return b;
+}
+
+void pa_browser_unref(pa_browser *b) {
+ pa_assert(b);
+ pa_assert(PA_REFCNT_VALUE(b) >= 1);
+
+ if (PA_REFCNT_DEC(b) <= 0)
+ browser_free(b);
+}
+
+void pa_browser_set_callback(pa_browser *b, pa_browse_cb_t cb, void *userdata) {
+ pa_assert(b);
+ pa_assert(PA_REFCNT_VALUE(b) >= 1);
+
+ b->callback = cb;
+ b->userdata = userdata;
+}
+
+void pa_browser_set_error_callback(pa_browser *b, pa_browser_error_cb_t cb, void *userdata) {
+ pa_assert(b);
+ pa_assert(PA_REFCNT_VALUE(b) >= 1);
+
+ b->error_callback = cb;
+ b->error_userdata = userdata;
+}
diff --git a/src/pulse/browser.h b/src/pulse/browser.h
new file mode 100644
index 00000000..b039ca33
--- /dev/null
+++ b/src/pulse/browser.h
@@ -0,0 +1,100 @@
+#ifndef foobrowserhfoo
+#define foobrowserhfoo
+
+/* $Id$ */
+
+/***
+ 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 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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/mainloop-api.h>
+#include <pulse/sample.h>
+#include <pulse/channelmap.h>
+#include <pulse/cdecl.h>
+
+/** \file
+ * An abstract interface for Zeroconf browsing of PulseAudio servers */
+
+PA_C_DECL_BEGIN
+
+/** An opaque Zeroconf service browser object */
+typedef struct pa_browser pa_browser;
+
+/** Opcodes for pa_browser_cb_t callbacks */
+typedef enum pa_browse_opcode {
+ PA_BROWSE_NEW_SERVER = 0, /**< New server found */
+ PA_BROWSE_NEW_SINK, /**< New sink found */
+ PA_BROWSE_NEW_SOURCE, /**< New source found */
+ PA_BROWSE_REMOVE_SERVER, /**< Server disappeared */
+ PA_BROWSE_REMOVE_SINK, /**< Sink disappeared */
+ PA_BROWSE_REMOVE_SOURCE /**< Source disappeared */
+} pa_browse_opcode_t;
+
+typedef enum pa_browse_flags {
+ PA_BROWSE_FOR_SERVERS = 1, /**< Browse for servers */
+ PA_BROWSE_FOR_SINKS = 2, /**< Browse for sinks */
+ PA_BROWSE_FOR_SOURCES = 4 /** Browse for sources */
+} pa_browse_flags_t;
+
+/** Create a new browser object on the specified main loop */
+pa_browser *pa_browser_new(pa_mainloop_api *mainloop);
+
+/** Same pa_browser_new, but pass additional flags parameter. */
+pa_browser *pa_browser_new_full(pa_mainloop_api *mainloop, pa_browse_flags_t flags, const char **error_string);
+
+/** Increase reference counter of the specified browser object */
+pa_browser *pa_browser_ref(pa_browser *z);
+
+/** Decrease reference counter of the specified browser object */
+void pa_browser_unref(pa_browser *z);
+
+/** Information about a sink/source/server found with Zeroconf */
+typedef struct pa_browse_info {
+ const char *name; /**< Unique service name; always available */
+
+ const char *server; /**< Server name; always available */
+ const char *server_version; /**< Server version string; optional */
+ const char *user_name; /**< User name of the server process; optional */
+ const char *fqdn; /* Server version; optional */
+ const uint32_t *cookie; /* Server cookie; optional */
+
+ const char *device; /* Device name; always available when this information is of a sink/source */
+ const char *description; /* Device description; optional */
+ const pa_sample_spec *sample_spec; /* Sample specification of the device; optional */
+} pa_browse_info;
+
+/** Callback prototype */
+typedef void (*pa_browse_cb_t)(pa_browser *z, pa_browse_opcode_t c, const pa_browse_info *i, void *userdata);
+
+/** Set the callback pointer for the browser object */
+void pa_browser_set_callback(pa_browser *z, pa_browse_cb_t cb, void *userdata);
+
+/** Callback prototype for errors */
+typedef void (*pa_browser_error_cb_t)(pa_browser *z, const char *error_string, void *userdata);
+
+/** Set a callback function that is called whenever the browser object
+ * becomes invalid due to an error. After this function has been
+ * called the browser object has become invalid and should be
+ * freed. */
+void pa_browser_set_error_callback(pa_browser *z, pa_browser_error_cb_t, void *userdata);
+
+PA_C_DECL_END
+
+#endif
diff --git a/src/pulse/cdecl.h b/src/pulse/cdecl.h
new file mode 100644
index 00000000..e1f23d25
--- /dev/null
+++ b/src/pulse/cdecl.h
@@ -0,0 +1,62 @@
+#ifndef foopulsecdeclhfoo
+#define foopulsecdeclhfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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.
+***/
+
+/** \file
+ * C++ compatibility support */
+
+#ifdef __cplusplus
+/** If using C++ this macro enables C mode, otherwise does nothing */
+#define PA_C_DECL_BEGIN extern "C" {
+/** If using C++ this macros switches back to C++ mode, otherwise does nothing */
+#define PA_C_DECL_END }
+
+#else
+/** If using C++ this macro enables C mode, otherwise does nothing */
+#define PA_C_DECL_BEGIN
+/** If using C++ this macros switches back to C++ mode, otherwise does nothing */
+#define PA_C_DECL_END
+
+#endif
+
+#ifndef PA_GCC_PURE
+#ifdef __GNUCC__
+#define PA_GCC_PURE __attribute__ ((pure))
+#else
+/** This function's return value depends only the arguments list and global state **/
+#define PA_GCC_PURE
+#endif
+#endif
+
+#ifndef PA_GCC_CONST
+#ifdef __GNUCC__
+#define PA_GCC_CONST __attribute__ ((pure))
+#else
+/** This function's return value depends only the arguments list (stricter version of PA_GCC_CONST) **/
+#define PA_GCC_CONST
+#endif
+#endif
+
+#endif
diff --git a/src/pulse/channelmap.c b/src/pulse/channelmap.c
new file mode 100644
index 00000000..2b35ee75
--- /dev/null
+++ b/src/pulse/channelmap.c
@@ -0,0 +1,533 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2005-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/macro.h>
+
+#include "channelmap.h"
+
+const char *const table[PA_CHANNEL_POSITION_MAX] = {
+ [PA_CHANNEL_POSITION_MONO] = "mono",
+
+ [PA_CHANNEL_POSITION_FRONT_CENTER] = "front-center",
+ [PA_CHANNEL_POSITION_FRONT_LEFT] = "front-left",
+ [PA_CHANNEL_POSITION_FRONT_RIGHT] = "front-right",
+
+ [PA_CHANNEL_POSITION_REAR_CENTER] = "rear-center",
+ [PA_CHANNEL_POSITION_REAR_LEFT] = "rear-left",
+ [PA_CHANNEL_POSITION_REAR_RIGHT] = "rear-right",
+
+ [PA_CHANNEL_POSITION_LFE] = "lfe",
+
+ [PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER] = "front-left-of-center",
+ [PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER] = "front-right-of-center",
+
+ [PA_CHANNEL_POSITION_SIDE_LEFT] = "side-left",
+ [PA_CHANNEL_POSITION_SIDE_RIGHT] = "side-right",
+
+ [PA_CHANNEL_POSITION_AUX0] = "aux0",
+ [PA_CHANNEL_POSITION_AUX1] = "aux1",
+ [PA_CHANNEL_POSITION_AUX2] = "aux2",
+ [PA_CHANNEL_POSITION_AUX3] = "aux3",
+ [PA_CHANNEL_POSITION_AUX4] = "aux4",
+ [PA_CHANNEL_POSITION_AUX5] = "aux5",
+ [PA_CHANNEL_POSITION_AUX6] = "aux6",
+ [PA_CHANNEL_POSITION_AUX7] = "aux7",
+ [PA_CHANNEL_POSITION_AUX8] = "aux8",
+ [PA_CHANNEL_POSITION_AUX9] = "aux9",
+ [PA_CHANNEL_POSITION_AUX10] = "aux10",
+ [PA_CHANNEL_POSITION_AUX11] = "aux11",
+ [PA_CHANNEL_POSITION_AUX12] = "aux12",
+ [PA_CHANNEL_POSITION_AUX13] = "aux13",
+ [PA_CHANNEL_POSITION_AUX14] = "aux14",
+ [PA_CHANNEL_POSITION_AUX15] = "aux15",
+ [PA_CHANNEL_POSITION_AUX16] = "aux16",
+ [PA_CHANNEL_POSITION_AUX17] = "aux17",
+ [PA_CHANNEL_POSITION_AUX18] = "aux18",
+ [PA_CHANNEL_POSITION_AUX19] = "aux19",
+ [PA_CHANNEL_POSITION_AUX20] = "aux20",
+ [PA_CHANNEL_POSITION_AUX21] = "aux21",
+ [PA_CHANNEL_POSITION_AUX22] = "aux22",
+ [PA_CHANNEL_POSITION_AUX23] = "aux23",
+ [PA_CHANNEL_POSITION_AUX24] = "aux24",
+ [PA_CHANNEL_POSITION_AUX25] = "aux25",
+ [PA_CHANNEL_POSITION_AUX26] = "aux26",
+ [PA_CHANNEL_POSITION_AUX27] = "aux27",
+ [PA_CHANNEL_POSITION_AUX28] = "aux28",
+ [PA_CHANNEL_POSITION_AUX29] = "aux29",
+ [PA_CHANNEL_POSITION_AUX30] = "aux30",
+ [PA_CHANNEL_POSITION_AUX31] = "aux31",
+
+ [PA_CHANNEL_POSITION_TOP_CENTER] = "top-center",
+
+ [PA_CHANNEL_POSITION_TOP_FRONT_CENTER] = "top-front-center",
+ [PA_CHANNEL_POSITION_TOP_FRONT_LEFT] = "top-front-left",
+ [PA_CHANNEL_POSITION_TOP_FRONT_RIGHT] = "top-front-right",
+
+ [PA_CHANNEL_POSITION_TOP_REAR_CENTER] = "top-rear-center",
+ [PA_CHANNEL_POSITION_TOP_REAR_LEFT] = "top-rear-left",
+ [PA_CHANNEL_POSITION_TOP_REAR_RIGHT] = "top-rear-right"
+};
+
+const char *const pretty_table[PA_CHANNEL_POSITION_MAX] = {
+ [PA_CHANNEL_POSITION_MONO] = "Mono",
+
+ [PA_CHANNEL_POSITION_FRONT_CENTER] = "Front Center",
+ [PA_CHANNEL_POSITION_FRONT_LEFT] = "Front Left",
+ [PA_CHANNEL_POSITION_FRONT_RIGHT] = "Front Right",
+
+ [PA_CHANNEL_POSITION_REAR_CENTER] = "Rear Center",
+ [PA_CHANNEL_POSITION_REAR_LEFT] = "Rear Left",
+ [PA_CHANNEL_POSITION_REAR_RIGHT] = "Rear Right",
+
+ [PA_CHANNEL_POSITION_LFE] = "Low Frequency Emmiter",
+
+ [PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER] = "Front Left-of-center",
+ [PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER] = "Front Right-of-center",
+
+ [PA_CHANNEL_POSITION_SIDE_LEFT] = "Side Left",
+ [PA_CHANNEL_POSITION_SIDE_RIGHT] = "Side Right",
+
+ [PA_CHANNEL_POSITION_AUX0] = "Auxiliary 0",
+ [PA_CHANNEL_POSITION_AUX1] = "Auxiliary 1",
+ [PA_CHANNEL_POSITION_AUX2] = "Auxiliary 2",
+ [PA_CHANNEL_POSITION_AUX3] = "Auxiliary 3",
+ [PA_CHANNEL_POSITION_AUX4] = "Auxiliary 4",
+ [PA_CHANNEL_POSITION_AUX5] = "Auxiliary 5",
+ [PA_CHANNEL_POSITION_AUX6] = "Auxiliary 6",
+ [PA_CHANNEL_POSITION_AUX7] = "Auxiliary 7",
+ [PA_CHANNEL_POSITION_AUX8] = "Auxiliary 8",
+ [PA_CHANNEL_POSITION_AUX9] = "Auxiliary 9",
+ [PA_CHANNEL_POSITION_AUX10] = "Auxiliary 10",
+ [PA_CHANNEL_POSITION_AUX11] = "Auxiliary 11",
+ [PA_CHANNEL_POSITION_AUX12] = "Auxiliary 12",
+ [PA_CHANNEL_POSITION_AUX13] = "Auxiliary 13",
+ [PA_CHANNEL_POSITION_AUX14] = "Auxiliary 14",
+ [PA_CHANNEL_POSITION_AUX15] = "Auxiliary 15",
+ [PA_CHANNEL_POSITION_AUX16] = "Auxiliary 16",
+ [PA_CHANNEL_POSITION_AUX17] = "Auxiliary 17",
+ [PA_CHANNEL_POSITION_AUX18] = "Auxiliary 18",
+ [PA_CHANNEL_POSITION_AUX19] = "Auxiliary 19",
+ [PA_CHANNEL_POSITION_AUX20] = "Auxiliary 20",
+ [PA_CHANNEL_POSITION_AUX21] = "Auxiliary 21",
+ [PA_CHANNEL_POSITION_AUX22] = "Auxiliary 22",
+ [PA_CHANNEL_POSITION_AUX23] = "Auxiliary 23",
+ [PA_CHANNEL_POSITION_AUX24] = "Auxiliary 24",
+ [PA_CHANNEL_POSITION_AUX25] = "Auxiliary 25",
+ [PA_CHANNEL_POSITION_AUX26] = "Auxiliary 26",
+ [PA_CHANNEL_POSITION_AUX27] = "Auxiliary 27",
+ [PA_CHANNEL_POSITION_AUX28] = "Auxiliary 28",
+ [PA_CHANNEL_POSITION_AUX29] = "Auxiliary 29",
+ [PA_CHANNEL_POSITION_AUX30] = "Auxiliary 30",
+ [PA_CHANNEL_POSITION_AUX31] = "Auxiliary 31",
+
+ [PA_CHANNEL_POSITION_TOP_CENTER] = "Top Center",
+
+ [PA_CHANNEL_POSITION_TOP_FRONT_CENTER] = "Top Front Center",
+ [PA_CHANNEL_POSITION_TOP_FRONT_LEFT] = "Top Front Left",
+ [PA_CHANNEL_POSITION_TOP_FRONT_RIGHT] = "Top Front Right",
+
+ [PA_CHANNEL_POSITION_TOP_REAR_CENTER] = "Top Rear Center",
+ [PA_CHANNEL_POSITION_TOP_REAR_LEFT] = "Top Rear left",
+ [PA_CHANNEL_POSITION_TOP_REAR_RIGHT] = "Top Rear Right"
+};
+
+pa_channel_map* pa_channel_map_init(pa_channel_map *m) {
+ unsigned c;
+ pa_assert(m);
+
+ m->channels = 0;
+
+ for (c = 0; c < PA_CHANNELS_MAX; c++)
+ m->map[c] = PA_CHANNEL_POSITION_INVALID;
+
+ return m;
+}
+
+pa_channel_map* pa_channel_map_init_mono(pa_channel_map *m) {
+ pa_assert(m);
+
+ pa_channel_map_init(m);
+
+ m->channels = 1;
+ m->map[0] = PA_CHANNEL_POSITION_MONO;
+ return m;
+}
+
+pa_channel_map* pa_channel_map_init_stereo(pa_channel_map *m) {
+ pa_assert(m);
+
+ pa_channel_map_init(m);
+
+ m->channels = 2;
+ m->map[0] = PA_CHANNEL_POSITION_LEFT;
+ m->map[1] = PA_CHANNEL_POSITION_RIGHT;
+ return m;
+}
+
+pa_channel_map* pa_channel_map_init_auto(pa_channel_map *m, unsigned channels, pa_channel_map_def_t def) {
+ pa_assert(m);
+ pa_assert(channels > 0);
+ pa_assert(channels <= PA_CHANNELS_MAX);
+
+ pa_channel_map_init(m);
+
+ m->channels = channels;
+
+ switch (def) {
+ case PA_CHANNEL_MAP_AIFF:
+
+ /* This is somewhat compatible with RFC3551 */
+
+ switch (channels) {
+ case 1:
+ m->map[0] = PA_CHANNEL_POSITION_MONO;
+ return m;
+
+ case 6:
+ m->map[0] = PA_CHANNEL_POSITION_FRONT_LEFT;
+ m->map[1] = PA_CHANNEL_POSITION_SIDE_LEFT;
+ m->map[2] = PA_CHANNEL_POSITION_FRONT_CENTER;
+ m->map[3] = PA_CHANNEL_POSITION_FRONT_RIGHT;
+ m->map[4] = PA_CHANNEL_POSITION_SIDE_RIGHT;
+ m->map[5] = PA_CHANNEL_POSITION_LFE;
+ return m;
+
+ case 5:
+ m->map[2] = PA_CHANNEL_POSITION_FRONT_CENTER;
+ m->map[3] = PA_CHANNEL_POSITION_REAR_LEFT;
+ m->map[4] = PA_CHANNEL_POSITION_REAR_RIGHT;
+ /* Fall through */
+
+ case 2:
+ m->map[0] = PA_CHANNEL_POSITION_FRONT_LEFT;
+ m->map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT;
+ return m;
+
+ case 3:
+ m->map[0] = PA_CHANNEL_POSITION_LEFT;
+ m->map[1] = PA_CHANNEL_POSITION_RIGHT;
+ m->map[2] = PA_CHANNEL_POSITION_CENTER;
+ return m;
+
+ case 4:
+ m->map[0] = PA_CHANNEL_POSITION_LEFT;
+ m->map[1] = PA_CHANNEL_POSITION_CENTER;
+ m->map[2] = PA_CHANNEL_POSITION_RIGHT;
+ m->map[3] = PA_CHANNEL_POSITION_LFE;
+ return m;
+
+ default:
+ return NULL;
+ }
+
+ case PA_CHANNEL_MAP_ALSA:
+
+ switch (channels) {
+ case 1:
+ m->map[0] = PA_CHANNEL_POSITION_MONO;
+ return m;
+
+ case 8:
+ m->map[6] = PA_CHANNEL_POSITION_SIDE_LEFT;
+ m->map[7] = PA_CHANNEL_POSITION_SIDE_RIGHT;
+ /* Fall through */
+
+ case 6:
+ m->map[5] = PA_CHANNEL_POSITION_LFE;
+ /* Fall through */
+
+ case 5:
+ m->map[4] = PA_CHANNEL_POSITION_FRONT_CENTER;
+ /* Fall through */
+
+ case 4:
+ m->map[2] = PA_CHANNEL_POSITION_REAR_LEFT;
+ m->map[3] = PA_CHANNEL_POSITION_REAR_RIGHT;
+ /* Fall through */
+
+ case 2:
+ m->map[0] = PA_CHANNEL_POSITION_FRONT_LEFT;
+ m->map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT;
+ return m;
+
+ default:
+ return NULL;
+ }
+
+ case PA_CHANNEL_MAP_AUX: {
+ unsigned i;
+
+ if (channels >= PA_CHANNELS_MAX)
+ return NULL;
+
+ for (i = 0; i < channels; i++)
+ m->map[i] = PA_CHANNEL_POSITION_AUX0 + i;
+
+ return m;
+ }
+
+ case PA_CHANNEL_MAP_WAVEEX:
+
+ switch (channels) {
+ case 1:
+ m->map[0] = PA_CHANNEL_POSITION_MONO;
+ return m;
+
+ case 18:
+ m->map[15] = PA_CHANNEL_POSITION_TOP_REAR_LEFT;
+ m->map[16] = PA_CHANNEL_POSITION_TOP_REAR_CENTER;
+ m->map[17] = PA_CHANNEL_POSITION_TOP_REAR_RIGHT;
+ /* Fall through */
+
+ case 15:
+ m->map[12] = PA_CHANNEL_POSITION_TOP_FRONT_LEFT;
+ m->map[13] = PA_CHANNEL_POSITION_TOP_FRONT_CENTER;
+ m->map[14] = PA_CHANNEL_POSITION_TOP_FRONT_RIGHT;
+ /* Fall through */
+
+ case 12:
+ m->map[11] = PA_CHANNEL_POSITION_TOP_CENTER;
+ /* Fall through */
+
+ case 11:
+ m->map[9] = PA_CHANNEL_POSITION_SIDE_LEFT;
+ m->map[10] = PA_CHANNEL_POSITION_SIDE_RIGHT;
+ /* Fall through */
+
+ case 9:
+ m->map[8] = PA_CHANNEL_POSITION_REAR_CENTER;
+ /* Fall through */
+
+ case 8:
+ m->map[6] = PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER;
+ m->map[7] = PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER;
+ /* Fall through */
+
+ case 6:
+ m->map[4] = PA_CHANNEL_POSITION_REAR_LEFT;
+ m->map[5] = PA_CHANNEL_POSITION_REAR_RIGHT;
+ /* Fall through */
+
+ case 4:
+ m->map[3] = PA_CHANNEL_POSITION_LFE;
+ /* Fall through */
+
+ case 3:
+ m->map[2] = PA_CHANNEL_POSITION_FRONT_CENTER;
+ /* Fall through */
+
+ case 2:
+ m->map[0] = PA_CHANNEL_POSITION_FRONT_LEFT;
+ m->map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT;
+ return m;
+
+ default:
+ return NULL;
+ }
+
+ case PA_CHANNEL_MAP_OSS:
+
+ switch (channels) {
+ case 1:
+ m->map[0] = PA_CHANNEL_POSITION_MONO;
+ return m;
+
+ case 8:
+ m->map[6] = PA_CHANNEL_POSITION_REAR_LEFT;
+ m->map[7] = PA_CHANNEL_POSITION_REAR_RIGHT;
+ /* Fall through */
+
+ case 6:
+ m->map[4] = PA_CHANNEL_POSITION_SIDE_LEFT;
+ m->map[5] = PA_CHANNEL_POSITION_SIDE_RIGHT;
+ /* Fall through */
+
+ case 4:
+ m->map[3] = PA_CHANNEL_POSITION_LFE;
+ /* Fall through */
+
+ case 3:
+ m->map[2] = PA_CHANNEL_POSITION_FRONT_CENTER;
+ /* Fall through */
+
+ case 2:
+ m->map[0] = PA_CHANNEL_POSITION_FRONT_LEFT;
+ m->map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT;
+ return m;
+
+ default:
+ return NULL;
+ }
+
+
+ default:
+ return NULL;
+ }
+}
+
+
+const char* pa_channel_position_to_string(pa_channel_position_t pos) {
+
+ if (pos < 0 || pos >= PA_CHANNEL_POSITION_MAX)
+ return NULL;
+
+ return table[pos];
+}
+
+const char* pa_channel_position_to_pretty_string(pa_channel_position_t pos) {
+ if (pos < 0 || pos >= PA_CHANNEL_POSITION_MAX)
+ return NULL;
+
+ return pretty_table[pos];
+}
+
+int pa_channel_map_equal(const pa_channel_map *a, const pa_channel_map *b) {
+ unsigned c;
+
+ pa_assert(a);
+ pa_assert(b);
+
+ if (a->channels != b->channels)
+ return 0;
+
+ for (c = 0; c < a->channels; c++)
+ if (a->map[c] != b->map[c])
+ return 0;
+
+ return 1;
+}
+
+char* pa_channel_map_snprint(char *s, size_t l, const pa_channel_map *map) {
+ unsigned channel;
+ int first = 1;
+ char *e;
+
+ pa_assert(s);
+ pa_assert(l > 0);
+ pa_assert(map);
+
+ *(e = s) = 0;
+
+ for (channel = 0; channel < map->channels && l > 1; channel++) {
+ l -= pa_snprintf(e, l, "%s%s",
+ first ? "" : ",",
+ pa_channel_position_to_string(map->map[channel]));
+
+ e = strchr(e, 0);
+ first = 0;
+ }
+
+ return s;
+}
+
+pa_channel_map *pa_channel_map_parse(pa_channel_map *rmap, const char *s) {
+ const char *state;
+ pa_channel_map map;
+ char *p;
+
+ pa_assert(rmap);
+ pa_assert(s);
+
+ memset(&map, 0, sizeof(map));
+
+ if (strcmp(s, "stereo") == 0) {
+ map.channels = 2;
+ map.map[0] = PA_CHANNEL_POSITION_LEFT;
+ map.map[1] = PA_CHANNEL_POSITION_RIGHT;
+ goto finish;
+ }
+
+ state = NULL;
+ map.channels = 0;
+
+ while ((p = pa_split(s, ",", &state))) {
+
+ if (map.channels >= PA_CHANNELS_MAX) {
+ pa_xfree(p);
+ return NULL;
+ }
+
+ /* Some special aliases */
+ if (strcmp(p, "left") == 0)
+ map.map[map.channels++] = PA_CHANNEL_POSITION_LEFT;
+ else if (strcmp(p, "right") == 0)
+ map.map[map.channels++] = PA_CHANNEL_POSITION_RIGHT;
+ else if (strcmp(p, "center") == 0)
+ map.map[map.channels++] = PA_CHANNEL_POSITION_CENTER;
+ else if (strcmp(p, "subwoofer") == 0)
+ map.map[map.channels++] = PA_CHANNEL_POSITION_SUBWOOFER;
+ else {
+ pa_channel_position_t i;
+
+ for (i = 0; i < PA_CHANNEL_POSITION_MAX; i++)
+ if (strcmp(p, table[i]) == 0) {
+ map.map[map.channels++] = i;
+ break;
+ }
+
+ if (i >= PA_CHANNEL_POSITION_MAX) {
+ pa_xfree(p);
+ return NULL;
+ }
+ }
+
+ pa_xfree(p);
+ }
+
+finish:
+
+ if (!pa_channel_map_valid(&map))
+ return NULL;
+
+ *rmap = map;
+ return rmap;
+}
+
+int pa_channel_map_valid(const pa_channel_map *map) {
+ unsigned c;
+
+ pa_assert(map);
+
+ if (map->channels <= 0 || map->channels > PA_CHANNELS_MAX)
+ return 0;
+
+ for (c = 0; c < map->channels; c++) {
+
+ if (map->map[c] < 0 ||map->map[c] >= PA_CHANNEL_POSITION_MAX)
+ return 0;
+
+ }
+
+ return 1;
+}
diff --git a/src/pulse/channelmap.h b/src/pulse/channelmap.h
new file mode 100644
index 00000000..a05e1911
--- /dev/null
+++ b/src/pulse/channelmap.h
@@ -0,0 +1,197 @@
+#ifndef foochannelmaphfoo
+#define foochannelmaphfoo
+
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2005-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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/cdecl.h>
+
+/** \page channelmap Channel Maps
+ *
+ * \section overv_sec Overview
+ *
+ * Channel maps provide a way to associate channels in a stream with a
+ * specific speaker position. This relieves applications of having to
+ * make sure their channel order is identical to the final output.
+ *
+ * \section init_sec Initialisation
+ *
+ * A channel map consists of an array of \ref pa_channel_position values,
+ * one for each channel. This array is stored together with a channel count
+ * in a pa_channel_map structure.
+ *
+ * Before filling the structure, the application must initialise it using
+ * pa_channel_map_init(). There are also a number of convenience functions
+ * for standard channel mappings:
+ *
+ * \li pa_channel_map_init_mono() - Create a channel map with only mono audio.
+ * \li pa_channel_map_init_stereo() - Create a standard stereo mapping.
+ * \li pa_channel_map_init_auto() - Create a standard channel map for up to
+ * six channels.
+ *
+ * \section conv_sec Convenience Functions
+ *
+ * The library contains a number of convenience functions for dealing with
+ * channel maps:
+ *
+ * \li pa_channel_map_valid() - Tests if a channel map is valid.
+ * \li pa_channel_map_equal() - Tests if two channel maps are identical.
+ * \li pa_channel_map_snprint() - Creates a textual description of a channel
+ * map.
+ */
+
+/** \file
+ * Constants and routines for channel mapping handling */
+
+PA_C_DECL_BEGIN
+
+/** A list of channel labels */
+typedef enum pa_channel_position {
+ PA_CHANNEL_POSITION_INVALID = -1,
+ PA_CHANNEL_POSITION_MONO = 0,
+
+ PA_CHANNEL_POSITION_LEFT,
+ PA_CHANNEL_POSITION_RIGHT,
+ PA_CHANNEL_POSITION_CENTER,
+
+ PA_CHANNEL_POSITION_FRONT_LEFT = PA_CHANNEL_POSITION_LEFT,
+ PA_CHANNEL_POSITION_FRONT_RIGHT = PA_CHANNEL_POSITION_RIGHT,
+ PA_CHANNEL_POSITION_FRONT_CENTER = PA_CHANNEL_POSITION_CENTER,
+
+ PA_CHANNEL_POSITION_REAR_CENTER,
+ PA_CHANNEL_POSITION_REAR_LEFT,
+ PA_CHANNEL_POSITION_REAR_RIGHT,
+
+ PA_CHANNEL_POSITION_LFE,
+ PA_CHANNEL_POSITION_SUBWOOFER = PA_CHANNEL_POSITION_LFE,
+
+ PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER,
+ PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER,
+
+ PA_CHANNEL_POSITION_SIDE_LEFT,
+ PA_CHANNEL_POSITION_SIDE_RIGHT,
+
+ PA_CHANNEL_POSITION_AUX0,
+ PA_CHANNEL_POSITION_AUX1,
+ PA_CHANNEL_POSITION_AUX2,
+ PA_CHANNEL_POSITION_AUX3,
+ PA_CHANNEL_POSITION_AUX4,
+ PA_CHANNEL_POSITION_AUX5,
+ PA_CHANNEL_POSITION_AUX6,
+ PA_CHANNEL_POSITION_AUX7,
+ PA_CHANNEL_POSITION_AUX8,
+ PA_CHANNEL_POSITION_AUX9,
+ PA_CHANNEL_POSITION_AUX10,
+ PA_CHANNEL_POSITION_AUX11,
+ PA_CHANNEL_POSITION_AUX12,
+ PA_CHANNEL_POSITION_AUX13,
+ PA_CHANNEL_POSITION_AUX14,
+ PA_CHANNEL_POSITION_AUX15,
+ PA_CHANNEL_POSITION_AUX16,
+ PA_CHANNEL_POSITION_AUX17,
+ PA_CHANNEL_POSITION_AUX18,
+ PA_CHANNEL_POSITION_AUX19,
+ PA_CHANNEL_POSITION_AUX20,
+ PA_CHANNEL_POSITION_AUX21,
+ PA_CHANNEL_POSITION_AUX22,
+ PA_CHANNEL_POSITION_AUX23,
+ PA_CHANNEL_POSITION_AUX24,
+ PA_CHANNEL_POSITION_AUX25,
+ PA_CHANNEL_POSITION_AUX26,
+ PA_CHANNEL_POSITION_AUX27,
+ PA_CHANNEL_POSITION_AUX28,
+ PA_CHANNEL_POSITION_AUX29,
+ PA_CHANNEL_POSITION_AUX30,
+ PA_CHANNEL_POSITION_AUX31,
+
+ PA_CHANNEL_POSITION_TOP_CENTER,
+
+ PA_CHANNEL_POSITION_TOP_FRONT_LEFT,
+ PA_CHANNEL_POSITION_TOP_FRONT_RIGHT,
+ PA_CHANNEL_POSITION_TOP_FRONT_CENTER,
+
+ PA_CHANNEL_POSITION_TOP_REAR_LEFT,
+ PA_CHANNEL_POSITION_TOP_REAR_RIGHT,
+ PA_CHANNEL_POSITION_TOP_REAR_CENTER,
+
+ PA_CHANNEL_POSITION_MAX
+} pa_channel_position_t;
+
+/** A list of channel mapping definitions for pa_channel_map_init_auto() */
+typedef enum pa_channel_map_def {
+ PA_CHANNEL_MAP_AIFF, /**< The mapping from RFC3551, which is based on AIFF-C */
+ PA_CHANNEL_MAP_ALSA, /**< The default mapping used by ALSA */
+ PA_CHANNEL_MAP_AUX, /**< Only aux channels */
+ PA_CHANNEL_MAP_WAVEEX, /**< Microsoft's WAVEFORMATEXTENSIBLE mapping */
+ PA_CHANNEL_MAP_OSS, /**< The default channel mapping used by OSS as defined in the OSS 4.0 API specs */
+
+ PA_CHANNEL_MAP_DEFAULT = PA_CHANNEL_MAP_AIFF /**< The default channel map */
+} pa_channel_map_def_t;
+
+/** A channel map which can be used to attach labels to specific
+ * channels of a stream. These values are relevant for conversion and
+ * mixing of streams */
+typedef struct pa_channel_map {
+ uint8_t channels; /**< Number of channels */
+ pa_channel_position_t map[PA_CHANNELS_MAX]; /**< Channel labels */
+} pa_channel_map;
+
+/** Initialize the specified channel map and return a pointer to it */
+pa_channel_map* pa_channel_map_init(pa_channel_map *m);
+
+/** Initialize the specified channel map for monoaural audio and return a pointer to it */
+pa_channel_map* pa_channel_map_init_mono(pa_channel_map *m);
+
+/** Initialize the specified channel map for stereophonic audio and return a pointer to it */
+pa_channel_map* pa_channel_map_init_stereo(pa_channel_map *m);
+
+/** Initialize the specified channel map for the specified number
+ * of channels using default labels and return a pointer to it. */
+pa_channel_map* pa_channel_map_init_auto(pa_channel_map *m, unsigned channels, pa_channel_map_def_t def);
+
+/** Return a text label for the specified channel position */
+const char* pa_channel_position_to_string(pa_channel_position_t pos) PA_GCC_PURE;
+
+/** Return a human readable text label for the specified channel position. \since 0.9.7 */
+const char* pa_channel_position_to_pretty_string(pa_channel_position_t pos);
+
+/** The maximum length of strings returned by pa_channel_map_snprint() */
+#define PA_CHANNEL_MAP_SNPRINT_MAX 336
+
+/** Make a humand readable string from the specified channel map */
+char* pa_channel_map_snprint(char *s, size_t l, const pa_channel_map *map);
+
+/** Parse a channel position list into a channel map structure. \since 0.8.1 */
+pa_channel_map *pa_channel_map_parse(pa_channel_map *map, const char *s);
+
+/** Compare two channel maps. Return 1 if both match. */
+int pa_channel_map_equal(const pa_channel_map *a, const pa_channel_map *b) PA_GCC_PURE;
+
+/** Return non-zero of the specified channel map is considered valid */
+int pa_channel_map_valid(const pa_channel_map *map) PA_GCC_PURE;
+
+PA_C_DECL_END
+
+#endif
diff --git a/src/pulse/client-conf-x11.c b/src/pulse/client-conf-x11.c
new file mode 100644
index 00000000..e240ba88
--- /dev/null
+++ b/src/pulse/client-conf-x11.c
@@ -0,0 +1,97 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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-13071
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/x11prop.h>
+#include <pulsecore/log.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/macro.h>
+
+#include "client-conf-x11.h"
+
+int pa_client_conf_from_x11(pa_client_conf *c, const char *dname) {
+ Display *d = NULL;
+ int ret = -1;
+ char t[1024];
+
+ pa_assert(c);
+
+ if (!dname && (!(dname = getenv("DISPLAY")) || *dname == '\0'))
+ goto finish;
+
+ if (!(d = XOpenDisplay(dname))) {
+ pa_log("XOpenDisplay() failed");
+ goto finish;
+ }
+
+ if (pa_x11_get_prop(d, "PULSE_SERVER", t, sizeof(t))) {
+ pa_xfree(c->default_server);
+ c->default_server = pa_xstrdup(t);
+ }
+
+ if (pa_x11_get_prop(d, "PULSE_SINK", t, sizeof(t))) {
+ pa_xfree(c->default_sink);
+ c->default_sink = pa_xstrdup(t);
+ }
+
+ if (pa_x11_get_prop(d, "PULSE_SOURCE", t, sizeof(t))) {
+ pa_xfree(c->default_source);
+ c->default_source = pa_xstrdup(t);
+ }
+
+ if (pa_x11_get_prop(d, "PULSE_COOKIE", t, sizeof(t))) {
+ uint8_t cookie[PA_NATIVE_COOKIE_LENGTH];
+
+ if (pa_parsehex(t, cookie, sizeof(cookie)) != sizeof(cookie)) {
+ pa_log("failed to parse cookie data");
+ goto finish;
+ }
+
+ pa_assert(sizeof(cookie) == sizeof(c->cookie));
+ memcpy(c->cookie, cookie, sizeof(cookie));
+
+ c->cookie_valid = 1;
+
+ pa_xfree(c->cookie_file);
+ c->cookie_file = NULL;
+ }
+
+ ret = 0;
+
+finish:
+ if (d)
+ XCloseDisplay(d);
+
+ return ret;
+
+}
diff --git a/src/pulse/client-conf-x11.h b/src/pulse/client-conf-x11.h
new file mode 100644
index 00000000..56cd406d
--- /dev/null
+++ b/src/pulse/client-conf-x11.h
@@ -0,0 +1,33 @@
+#ifndef fooclientconfx11hfoo
+#define fooclientconfx11hfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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 "client-conf.h"
+
+/* Load client configuration data from the specified X11 display,
+ * overwriting the current settings in *c */
+int pa_client_conf_from_x11(pa_client_conf *c, const char *display);
+
+#endif
diff --git a/src/pulse/client-conf.c b/src/pulse/client-conf.c
new file mode 100644
index 00000000..c054f663
--- /dev/null
+++ b/src/pulse/client-conf.c
@@ -0,0 +1,185 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/macro.h>
+#include <pulsecore/core-error.h>
+#include <pulsecore/log.h>
+#include <pulsecore/conf-parser.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/authkey.h>
+
+#include "client-conf.h"
+
+#define DEFAULT_CLIENT_CONFIG_FILE PA_DEFAULT_CONFIG_DIR PA_PATH_SEP "client.conf"
+#define DEFAULT_CLIENT_CONFIG_FILE_USER "client.conf"
+
+#define ENV_CLIENT_CONFIG_FILE "PULSE_CLIENTCONFIG"
+#define ENV_DEFAULT_SINK "PULSE_SINK"
+#define ENV_DEFAULT_SOURCE "PULSE_SOURCE"
+#define ENV_DEFAULT_SERVER "PULSE_SERVER"
+#define ENV_DAEMON_BINARY "PULSE_BINARY"
+#define ENV_COOKIE_FILE "PULSE_COOKIE"
+
+static const pa_client_conf default_conf = {
+ .daemon_binary = NULL,
+ .extra_arguments = NULL,
+ .default_sink = NULL,
+ .default_source = NULL,
+ .default_server = NULL,
+ .autospawn = FALSE,
+ .disable_shm = FALSE,
+ .cookie_file = NULL,
+ .cookie_valid = FALSE,
+};
+
+pa_client_conf *pa_client_conf_new(void) {
+ pa_client_conf *c = pa_xmemdup(&default_conf, sizeof(default_conf));
+
+ c->daemon_binary = pa_xstrdup(PA_BINARY);
+ c->extra_arguments = pa_xstrdup("--log-target=syslog --exit-idle-time=5");
+ c->cookie_file = pa_xstrdup(PA_NATIVE_COOKIE_FILE);
+
+ return c;
+}
+
+void pa_client_conf_free(pa_client_conf *c) {
+ pa_assert(c);
+ pa_xfree(c->daemon_binary);
+ pa_xfree(c->extra_arguments);
+ pa_xfree(c->default_sink);
+ pa_xfree(c->default_source);
+ pa_xfree(c->default_server);
+ pa_xfree(c->cookie_file);
+ pa_xfree(c);
+}
+
+int pa_client_conf_load(pa_client_conf *c, const char *filename) {
+ FILE *f = NULL;
+ char *fn = NULL;
+ int r = -1;
+
+ /* Prepare the configuration parse table */
+ pa_config_item table[] = {
+ { "daemon-binary", pa_config_parse_string, NULL },
+ { "extra-arguments", pa_config_parse_string, NULL },
+ { "default-sink", pa_config_parse_string, NULL },
+ { "default-source", pa_config_parse_string, NULL },
+ { "default-server", pa_config_parse_string, NULL },
+ { "autospawn", pa_config_parse_bool, NULL },
+ { "cookie-file", pa_config_parse_string, NULL },
+ { "disable-shm", pa_config_parse_bool, NULL },
+ { NULL, NULL, NULL },
+ };
+
+ table[0].data = &c->daemon_binary;
+ table[1].data = &c->extra_arguments;
+ table[2].data = &c->default_sink;
+ table[3].data = &c->default_source;
+ table[4].data = &c->default_server;
+ table[5].data = &c->autospawn;
+ table[6].data = &c->cookie_file;
+ table[7].data = &c->disable_shm;
+
+ f = filename ?
+ fopen((fn = pa_xstrdup(filename)), "r") :
+ pa_open_config_file(DEFAULT_CLIENT_CONFIG_FILE, DEFAULT_CLIENT_CONFIG_FILE_USER, ENV_CLIENT_CONFIG_FILE, &fn, "r");
+
+ if (!f && errno != EINTR) {
+ pa_log_warn("Failed to open configuration file '%s': %s", fn, pa_cstrerror(errno));
+ goto finish;
+ }
+
+ r = f ? pa_config_parse(fn, f, table, NULL) : 0;
+
+ if (!r)
+ r = pa_client_conf_load_cookie(c);
+
+
+finish:
+ pa_xfree(fn);
+
+ if (f)
+ fclose(f);
+
+ return r;
+}
+
+int pa_client_conf_env(pa_client_conf *c) {
+ char *e;
+
+ if ((e = getenv(ENV_DEFAULT_SINK))) {
+ pa_xfree(c->default_sink);
+ c->default_sink = pa_xstrdup(e);
+ }
+
+ if ((e = getenv(ENV_DEFAULT_SOURCE))) {
+ pa_xfree(c->default_source);
+ c->default_source = pa_xstrdup(e);
+ }
+
+ if ((e = getenv(ENV_DEFAULT_SERVER))) {
+ pa_xfree(c->default_server);
+ c->default_server = pa_xstrdup(e);
+ }
+
+ if ((e = getenv(ENV_DAEMON_BINARY))) {
+ pa_xfree(c->daemon_binary);
+ c->daemon_binary = pa_xstrdup(e);
+ }
+
+ if ((e = getenv(ENV_COOKIE_FILE))) {
+ pa_xfree(c->cookie_file);
+ c->cookie_file = pa_xstrdup(e);
+
+ return pa_client_conf_load_cookie(c);
+ }
+
+ return 0;
+}
+
+int pa_client_conf_load_cookie(pa_client_conf* c) {
+ pa_assert(c);
+
+ c->cookie_valid = FALSE;
+
+ if (!c->cookie_file)
+ return -1;
+
+ if (pa_authkey_load_auto(c->cookie_file, c->cookie, sizeof(c->cookie)) < 0)
+ return -1;
+
+ c->cookie_valid = TRUE;
+ return 0;
+}
diff --git a/src/pulse/client-conf.h b/src/pulse/client-conf.h
new file mode 100644
index 00000000..7cc975e6
--- /dev/null
+++ b/src/pulse/client-conf.h
@@ -0,0 +1,54 @@
+#ifndef fooclientconfhfoo
+#define fooclientconfhfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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 <pulsecore/native-common.h>
+
+/* A structure containing configuration data for PulseAudio clients. */
+
+typedef struct pa_client_conf {
+ char *daemon_binary, *extra_arguments, *default_sink, *default_source, *default_server, *cookie_file;
+ pa_bool_t autospawn, disable_shm;
+ uint8_t cookie[PA_NATIVE_COOKIE_LENGTH];
+ pa_bool_t cookie_valid; /* non-zero, when cookie is valid */
+} pa_client_conf;
+
+/* Create a new configuration data object and reset it to defaults */
+pa_client_conf *pa_client_conf_new(void);
+void pa_client_conf_free(pa_client_conf *c);
+
+/* Load the configuration data from the speicified file, overwriting
+ * the current settings in *c. When the filename is NULL, the
+ * default client configuration file name is used. */
+int pa_client_conf_load(pa_client_conf *c, const char *filename);
+
+/* Load the configuration data from the environment of the current
+ process, overwriting the current settings in *c. */
+int pa_client_conf_env(pa_client_conf *c);
+
+/* Load cookie data from c->cookie_file into c->cookie */
+int pa_client_conf_load_cookie(pa_client_conf* c);
+
+#endif
diff --git a/src/pulse/client.conf.in b/src/pulse/client.conf.in
new file mode 100644
index 00000000..2bc8a7c8
--- /dev/null
+++ b/src/pulse/client.conf.in
@@ -0,0 +1,34 @@
+# $Id$
+#
+# This file is part of PulseAudio.
+#
+# PulseAudio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# PulseAudio is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with PulseAudio; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+# USA.
+
+## Configuration file for PulseAudio clients. See pulse-client.conf(5) for
+## more information. Default values a commented out. Use either ; or # for
+## commenting.
+
+; default-sink =
+; default-source =
+; default-server =
+
+; autospawn = no
+; daemon-binary = @PA_BINARY@
+; extra-arguments = --log-target=syslog --exit-idle-time=5
+
+; cookie-file =
+
+; disable-shm = no
diff --git a/src/pulse/context.c b/src/pulse/context.c
new file mode 100644
index 00000000..7243a29d
--- /dev/null
+++ b/src/pulse/context.c
@@ -0,0 +1,1041 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <signal.h>
+#include <limits.h>
+
+#ifdef HAVE_SYS_WAIT_H
+#include <sys/wait.h>
+#endif
+
+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+#ifdef HAVE_SYS_UN_H
+#include <sys/un.h>
+#endif
+#ifdef HAVE_NETDB_H
+#include <netdb.h>
+#endif
+
+#include <pulse/version.h>
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/winsock.h>
+#include <pulsecore/core-error.h>
+
+#include <pulsecore/native-common.h>
+#include <pulsecore/pdispatch.h>
+#include <pulsecore/pstream.h>
+#include <pulsecore/dynarray.h>
+#include <pulsecore/socket-client.h>
+#include <pulsecore/pstream-util.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/log.h>
+#include <pulsecore/socket-util.h>
+#include <pulsecore/creds.h>
+#include <pulsecore/macro.h>
+
+#include "internal.h"
+
+#include "client-conf.h"
+
+#ifdef HAVE_X11
+#include "client-conf-x11.h"
+#endif
+
+#include "context.h"
+
+#define AUTOSPAWN_LOCK "autospawn.lock"
+
+static const pa_pdispatch_cb_t command_table[PA_COMMAND_MAX] = {
+ [PA_COMMAND_REQUEST] = pa_command_request,
+ [PA_COMMAND_OVERFLOW] = pa_command_overflow_or_underflow,
+ [PA_COMMAND_UNDERFLOW] = pa_command_overflow_or_underflow,
+ [PA_COMMAND_PLAYBACK_STREAM_KILLED] = pa_command_stream_killed,
+ [PA_COMMAND_RECORD_STREAM_KILLED] = pa_command_stream_killed,
+ [PA_COMMAND_PLAYBACK_STREAM_MOVED] = pa_command_stream_moved,
+ [PA_COMMAND_RECORD_STREAM_MOVED] = pa_command_stream_moved,
+ [PA_COMMAND_PLAYBACK_STREAM_SUSPENDED] = pa_command_stream_suspended,
+ [PA_COMMAND_RECORD_STREAM_SUSPENDED] = pa_command_stream_suspended,
+ [PA_COMMAND_SUBSCRIBE_EVENT] = pa_command_subscribe_event
+};
+
+static void unlock_autospawn_lock_file(pa_context *c) {
+ pa_assert(c);
+
+ if (c->autospawn_lock_fd >= 0) {
+ char lf[PATH_MAX];
+ pa_runtime_path(AUTOSPAWN_LOCK, lf, sizeof(lf));
+
+ pa_unlock_lockfile(lf, c->autospawn_lock_fd);
+ c->autospawn_lock_fd = -1;
+ }
+}
+
+static void context_free(pa_context *c);
+
+pa_context *pa_context_new(pa_mainloop_api *mainloop, const char *name) {
+ pa_context *c;
+
+ pa_assert(mainloop);
+ pa_assert(name);
+
+ c = pa_xnew(pa_context, 1);
+ PA_REFCNT_INIT(c);
+ c->name = pa_xstrdup(name);
+ c->mainloop = mainloop;
+ c->client = NULL;
+ c->pstream = NULL;
+ c->pdispatch = NULL;
+ c->playback_streams = pa_dynarray_new();
+ c->record_streams = pa_dynarray_new();
+
+ PA_LLIST_HEAD_INIT(pa_stream, c->streams);
+ PA_LLIST_HEAD_INIT(pa_operation, c->operations);
+
+ c->error = PA_OK;
+ c->state = PA_CONTEXT_UNCONNECTED;
+ c->ctag = 0;
+ c->csyncid = 0;
+
+ c->state_callback = NULL;
+ c->state_userdata = NULL;
+
+ c->subscribe_callback = NULL;
+ c->subscribe_userdata = NULL;
+
+ c->is_local = -1;
+ c->server_list = NULL;
+ c->server = NULL;
+ c->autospawn_lock_fd = -1;
+ memset(&c->spawn_api, 0, sizeof(c->spawn_api));
+ c->do_autospawn = 0;
+
+#ifndef MSG_NOSIGNAL
+#ifdef SIGPIPE
+ pa_check_signal_is_blocked(SIGPIPE);
+#endif
+#endif
+
+ c->conf = pa_client_conf_new();
+ pa_client_conf_load(c->conf, NULL);
+#ifdef HAVE_X11
+ pa_client_conf_from_x11(c->conf, NULL);
+#endif
+ pa_client_conf_env(c->conf);
+
+ if (!(c->mempool = pa_mempool_new(!c->conf->disable_shm))) {
+
+ if (!c->conf->disable_shm)
+ c->mempool = pa_mempool_new(0);
+
+ if (!c->mempool) {
+ context_free(c);
+ return NULL;
+ }
+ }
+
+ return c;
+}
+
+static void context_free(pa_context *c) {
+ pa_assert(c);
+
+ unlock_autospawn_lock_file(c);
+
+ while (c->operations)
+ pa_operation_cancel(c->operations);
+
+ while (c->streams)
+ pa_stream_set_state(c->streams, PA_STREAM_TERMINATED);
+
+ if (c->client)
+ pa_socket_client_unref(c->client);
+ if (c->pdispatch)
+ pa_pdispatch_unref(c->pdispatch);
+ if (c->pstream) {
+ pa_pstream_unlink(c->pstream);
+ pa_pstream_unref(c->pstream);
+ }
+
+ if (c->record_streams)
+ pa_dynarray_free(c->record_streams, NULL, NULL);
+ if (c->playback_streams)
+ pa_dynarray_free(c->playback_streams, NULL, NULL);
+
+ if (c->mempool)
+ pa_mempool_free(c->mempool);
+
+ if (c->conf)
+ pa_client_conf_free(c->conf);
+
+ pa_strlist_free(c->server_list);
+
+ pa_xfree(c->name);
+ pa_xfree(c->server);
+ pa_xfree(c);
+}
+
+pa_context* pa_context_ref(pa_context *c) {
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ PA_REFCNT_INC(c);
+ return c;
+}
+
+void pa_context_unref(pa_context *c) {
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ if (PA_REFCNT_DEC(c) <= 0)
+ context_free(c);
+}
+
+void pa_context_set_state(pa_context *c, pa_context_state_t st) {
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ if (c->state == st)
+ return;
+
+ pa_context_ref(c);
+
+ c->state = st;
+ if (c->state_callback)
+ c->state_callback(c, c->state_userdata);
+
+ if (st == PA_CONTEXT_FAILED || st == PA_CONTEXT_TERMINATED) {
+ pa_stream *s;
+
+ s = c->streams ? pa_stream_ref(c->streams) : NULL;
+ while (s) {
+ pa_stream *n = s->next ? pa_stream_ref(s->next) : NULL;
+ pa_stream_set_state(s, st == PA_CONTEXT_FAILED ? PA_STREAM_FAILED : PA_STREAM_TERMINATED);
+ pa_stream_unref(s);
+ s = n;
+ }
+
+ if (c->pdispatch)
+ pa_pdispatch_unref(c->pdispatch);
+ c->pdispatch = NULL;
+
+ if (c->pstream) {
+ pa_pstream_unlink(c->pstream);
+ pa_pstream_unref(c->pstream);
+ }
+ c->pstream = NULL;
+
+ if (c->client)
+ pa_socket_client_unref(c->client);
+ c->client = NULL;
+ }
+
+ pa_context_unref(c);
+}
+
+void pa_context_fail(pa_context *c, int error) {
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ pa_context_set_error(c, error);
+ pa_context_set_state(c, PA_CONTEXT_FAILED);
+}
+
+int pa_context_set_error(pa_context *c, int error) {
+ pa_assert(error >= 0);
+ pa_assert(error < PA_ERR_MAX);
+
+ if (c)
+ c->error = error;
+
+ return error;
+}
+
+static void pstream_die_callback(pa_pstream *p, void *userdata) {
+ pa_context *c = userdata;
+
+ pa_assert(p);
+ pa_assert(c);
+
+ pa_context_fail(c, PA_ERR_CONNECTIONTERMINATED);
+}
+
+static void pstream_packet_callback(pa_pstream *p, pa_packet *packet, const pa_creds *creds, void *userdata) {
+ pa_context *c = userdata;
+
+ pa_assert(p);
+ pa_assert(packet);
+ pa_assert(c);
+
+ pa_context_ref(c);
+
+ if (pa_pdispatch_run(c->pdispatch, packet, creds, c) < 0)
+ pa_context_fail(c, PA_ERR_PROTOCOL);
+
+ pa_context_unref(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_context *c = userdata;
+ pa_stream *s;
+
+ pa_assert(p);
+ pa_assert(chunk);
+ pa_assert(chunk->memblock);
+ pa_assert(chunk->length);
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ pa_context_ref(c);
+
+ if ((s = pa_dynarray_get(c->record_streams, channel))) {
+
+ pa_assert(seek == PA_SEEK_RELATIVE);
+ pa_assert(offset == 0);
+
+ pa_memblockq_seek(s->record_memblockq, offset, seek);
+ pa_memblockq_push_align(s->record_memblockq, chunk);
+
+ if (s->read_callback) {
+ size_t l;
+
+ if ((l = pa_memblockq_get_length(s->record_memblockq)) > 0)
+ s->read_callback(s, l, s->read_userdata);
+ }
+ }
+
+ pa_context_unref(c);
+}
+
+int pa_context_handle_error(pa_context *c, uint32_t command, pa_tagstruct *t) {
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ if (command == PA_COMMAND_ERROR) {
+ pa_assert(t);
+
+ if (pa_tagstruct_getu32(t, &c->error) < 0) {
+ pa_context_fail(c, PA_ERR_PROTOCOL);
+ return -1;
+
+ }
+ } else if (command == PA_COMMAND_TIMEOUT)
+ c->error = PA_ERR_TIMEOUT;
+ else {
+ pa_context_fail(c, PA_ERR_PROTOCOL);
+ return -1;
+ }
+
+ return 0;
+}
+
+static void setup_complete_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_context *c = userdata;
+
+ pa_assert(pd);
+ pa_assert(c);
+ pa_assert(c->state == PA_CONTEXT_AUTHORIZING || c->state == PA_CONTEXT_SETTING_NAME);
+
+ pa_context_ref(c);
+
+ if (command != PA_COMMAND_REPLY) {
+
+ if (pa_context_handle_error(c, command, t) < 0)
+ pa_context_fail(c, PA_ERR_PROTOCOL);
+
+ pa_context_fail(c, c->error);
+ goto finish;
+ }
+
+ switch(c->state) {
+ case PA_CONTEXT_AUTHORIZING: {
+ pa_tagstruct *reply;
+
+ if (pa_tagstruct_getu32(t, &c->version) < 0 ||
+ !pa_tagstruct_eof(t)) {
+ pa_context_fail(c, PA_ERR_PROTOCOL);
+ goto finish;
+ }
+
+ /* Minimum supported version */
+ if (c->version < 8) {
+ pa_context_fail(c, PA_ERR_VERSION);
+ goto finish;
+ }
+
+ /* Enable shared memory support if possible */
+ if (c->version >= 10 &&
+ pa_mempool_is_shared(c->mempool) &&
+ c->is_local > 0) {
+
+ /* Only enable SHM if both sides are owned by the same
+ * user. This is a security measure because otherwise
+ * data private to the user might leak. */
+
+#ifdef HAVE_CREDS
+ const pa_creds *creds;
+ if ((creds = pa_pdispatch_creds(pd)))
+ if (getuid() == creds->uid)
+ pa_pstream_use_shm(c->pstream, 1);
+#endif
+ }
+
+ reply = pa_tagstruct_command(c, PA_COMMAND_SET_CLIENT_NAME, &tag);
+ pa_tagstruct_puts(reply, c->name);
+ pa_pstream_send_tagstruct(c->pstream, reply);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, setup_complete_callback, c, NULL);
+
+ pa_context_set_state(c, PA_CONTEXT_SETTING_NAME);
+ break;
+ }
+
+ case PA_CONTEXT_SETTING_NAME :
+ pa_context_set_state(c, PA_CONTEXT_READY);
+ break;
+
+ default:
+ pa_assert(0);
+ }
+
+finish:
+ pa_context_unref(c);
+}
+
+static void setup_context(pa_context *c, pa_iochannel *io) {
+ pa_tagstruct *t;
+ uint32_t tag;
+
+ pa_assert(c);
+ pa_assert(io);
+
+ pa_context_ref(c);
+
+ pa_assert(!c->pstream);
+ c->pstream = pa_pstream_new(c->mainloop, io, c->mempool);
+
+ pa_pstream_set_die_callback(c->pstream, pstream_die_callback, c);
+ pa_pstream_set_recieve_packet_callback(c->pstream, pstream_packet_callback, c);
+ pa_pstream_set_recieve_memblock_callback(c->pstream, pstream_memblock_callback, c);
+
+ pa_assert(!c->pdispatch);
+ c->pdispatch = pa_pdispatch_new(c->mainloop, command_table, PA_COMMAND_MAX);
+
+ if (!c->conf->cookie_valid)
+ pa_log_warn("No cookie loaded. Attempting to connect without.");
+
+ t = pa_tagstruct_command(c, PA_COMMAND_AUTH, &tag);
+ pa_tagstruct_putu32(t, PA_PROTOCOL_VERSION);
+ pa_tagstruct_put_arbitrary(t, c->conf->cookie, sizeof(c->conf->cookie));
+
+#ifdef HAVE_CREDS
+{
+ pa_creds ucred;
+
+ if (pa_iochannel_creds_supported(io))
+ pa_iochannel_creds_enable(io);
+
+ ucred.uid = getuid();
+ ucred.gid = getgid();
+
+ pa_pstream_send_tagstruct_with_creds(c->pstream, t, &ucred);
+}
+#else
+ pa_pstream_send_tagstruct(c->pstream, t);
+#endif
+
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, setup_complete_callback, c, NULL);
+
+ pa_context_set_state(c, PA_CONTEXT_AUTHORIZING);
+
+ pa_context_unref(c);
+}
+
+static void on_connection(pa_socket_client *client, pa_iochannel*io, void *userdata);
+
+#ifndef OS_IS_WIN32
+
+static int context_connect_spawn(pa_context *c) {
+ pid_t pid;
+ int status, r;
+ int fds[2] = { -1, -1} ;
+ pa_iochannel *io;
+
+ pa_context_ref(c);
+
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) < 0) {
+ pa_log("socketpair(): %s", pa_cstrerror(errno));
+ pa_context_fail(c, PA_ERR_INTERNAL);
+ goto fail;
+ }
+
+ pa_make_fd_cloexec(fds[0]);
+
+ pa_make_socket_low_delay(fds[0]);
+ pa_make_socket_low_delay(fds[1]);
+
+ if (c->spawn_api.prefork)
+ c->spawn_api.prefork();
+
+ if ((pid = fork()) < 0) {
+ pa_log("fork(): %s", pa_cstrerror(errno));
+ pa_context_fail(c, PA_ERR_INTERNAL);
+
+ if (c->spawn_api.postfork)
+ c->spawn_api.postfork();
+
+ goto fail;
+ } else if (!pid) {
+ /* Child */
+
+ char t[128];
+ const char *state = NULL;
+#define MAX_ARGS 64
+ const char * argv[MAX_ARGS+1];
+ int n;
+
+ /* Not required, since fds[0] has CLOEXEC enabled anyway */
+ pa_assert_se(pa_close(fds[0]) == 0);
+
+ if (c->spawn_api.atfork)
+ c->spawn_api.atfork();
+
+ /* Setup argv */
+
+ n = 0;
+
+ argv[n++] = c->conf->daemon_binary;
+ argv[n++] = "--daemonize=yes";
+
+ pa_snprintf(t, sizeof(t), "-Lmodule-native-protocol-fd fd=%i", fds[1]);
+ argv[n++] = strdup(t);
+
+ while (n < MAX_ARGS) {
+ char *a;
+
+ if (!(a = pa_split_spaces(c->conf->extra_arguments, &state)))
+ break;
+
+ argv[n++] = a;
+ }
+
+ argv[n++] = NULL;
+
+ execv(argv[0], (char * const *) argv);
+ _exit(1);
+#undef MAX_ARGS
+ }
+
+ /* Parent */
+
+ r = waitpid(pid, &status, 0);
+
+ if (c->spawn_api.postfork)
+ c->spawn_api.postfork();
+
+ if (r < 0) {
+ pa_log("waitpid(): %s", pa_cstrerror(errno));
+ pa_context_fail(c, PA_ERR_INTERNAL);
+ goto fail;
+ } else if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
+ pa_context_fail(c, PA_ERR_CONNECTIONREFUSED);
+ goto fail;
+ }
+
+ pa_assert_se(pa_close(fds[1]) == 0);
+
+ c->is_local = 1;
+
+ io = pa_iochannel_new(c->mainloop, fds[0], fds[0]);
+
+ setup_context(c, io);
+ unlock_autospawn_lock_file(c);
+
+ pa_context_unref(c);
+
+ return 0;
+
+fail:
+ pa_close_pipe(fds);
+
+ unlock_autospawn_lock_file(c);
+
+ pa_context_unref(c);
+
+ return -1;
+}
+
+#endif /* OS_IS_WIN32 */
+
+static int try_next_connection(pa_context *c) {
+ char *u = NULL;
+ int r = -1;
+
+ pa_assert(c);
+ pa_assert(!c->client);
+
+ for (;;) {
+ pa_xfree(u);
+ u = NULL;
+
+ c->server_list = pa_strlist_pop(c->server_list, &u);
+
+ if (!u) {
+
+#ifndef OS_IS_WIN32
+ if (c->do_autospawn) {
+ r = context_connect_spawn(c);
+ goto finish;
+ }
+#endif
+
+ pa_context_fail(c, PA_ERR_CONNECTIONREFUSED);
+ goto finish;
+ }
+
+ pa_log_debug("Trying to connect to %s...", u);
+
+ pa_xfree(c->server);
+ c->server = pa_xstrdup(u);
+
+ if (!(c->client = pa_socket_client_new_string(c->mainloop, u, PA_NATIVE_DEFAULT_PORT)))
+ continue;
+
+ c->is_local = pa_socket_client_is_local(c->client);
+ pa_socket_client_set_callback(c->client, on_connection, c);
+ break;
+ }
+
+ r = 0;
+
+finish:
+ pa_xfree(u);
+
+ return r;
+}
+
+static void on_connection(pa_socket_client *client, pa_iochannel*io, void *userdata) {
+ pa_context *c = userdata;
+
+ pa_assert(client);
+ pa_assert(c);
+ pa_assert(c->state == PA_CONTEXT_CONNECTING);
+
+ pa_context_ref(c);
+
+ pa_socket_client_unref(client);
+ c->client = NULL;
+
+ if (!io) {
+ /* Try the item in the list */
+ if (errno == ECONNREFUSED || errno == ETIMEDOUT || errno == EHOSTUNREACH) {
+ try_next_connection(c);
+ goto finish;
+ }
+
+ pa_context_fail(c, PA_ERR_CONNECTIONREFUSED);
+ goto finish;
+ }
+
+ unlock_autospawn_lock_file(c);
+ setup_context(c, io);
+
+finish:
+ pa_context_unref(c);
+}
+
+int pa_context_connect(
+ pa_context *c,
+ const char *server,
+ pa_context_flags_t flags,
+ const pa_spawn_api *api) {
+
+ int r = -1;
+
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ PA_CHECK_VALIDITY(c, c->state == PA_CONTEXT_UNCONNECTED, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY(c, !(flags & ~PA_CONTEXT_NOAUTOSPAWN), PA_ERR_INVALID);
+ PA_CHECK_VALIDITY(c, !server || *server, PA_ERR_INVALID);
+
+ if (!server)
+ server = c->conf->default_server;
+
+ pa_context_ref(c);
+
+ pa_assert(!c->server_list);
+
+ if (server) {
+ if (!(c->server_list = pa_strlist_parse(server))) {
+ pa_context_fail(c, PA_ERR_INVALIDSERVER);
+ goto finish;
+ }
+ } else {
+ char *d;
+ char ufn[PATH_MAX];
+
+ /* Prepend in reverse order */
+
+ if ((d = getenv("DISPLAY"))) {
+ char *e;
+ d = pa_xstrdup(d);
+ if ((e = strchr(d, ':')))
+ *e = 0;
+
+ if (*d)
+ c->server_list = pa_strlist_prepend(c->server_list, d);
+
+ pa_xfree(d);
+ }
+
+ c->server_list = pa_strlist_prepend(c->server_list, "tcp6:localhost");
+ c->server_list = pa_strlist_prepend(c->server_list, "tcp4:localhost");
+
+ /* The system wide instance */
+ c->server_list = pa_strlist_prepend(c->server_list, PA_SYSTEM_RUNTIME_PATH "/" PA_NATIVE_DEFAULT_UNIX_SOCKET);
+
+ /* The per-user instance */
+ c->server_list = pa_strlist_prepend(c->server_list, pa_runtime_path(PA_NATIVE_DEFAULT_UNIX_SOCKET, ufn, sizeof(ufn)));
+
+ /* Wrap the connection attempts in a single transaction for sane autospawn locking */
+ if (!(flags & PA_CONTEXT_NOAUTOSPAWN) && c->conf->autospawn) {
+ char lf[PATH_MAX];
+
+ pa_runtime_path(AUTOSPAWN_LOCK, lf, sizeof(lf));
+ pa_make_secure_parent_dir(lf, 0700, (uid_t)-1, (gid_t)-1);
+ pa_assert(c->autospawn_lock_fd <= 0);
+ c->autospawn_lock_fd = pa_lock_lockfile(lf);
+
+ if (api)
+ c->spawn_api = *api;
+ c->do_autospawn = 1;
+ }
+
+ }
+
+ pa_context_set_state(c, PA_CONTEXT_CONNECTING);
+ r = try_next_connection(c);
+
+finish:
+ pa_context_unref(c);
+
+ return r;
+}
+
+void pa_context_disconnect(pa_context *c) {
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ pa_context_set_state(c, PA_CONTEXT_TERMINATED);
+}
+
+pa_context_state_t pa_context_get_state(pa_context *c) {
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ return c->state;
+}
+
+int pa_context_errno(pa_context *c) {
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ return c->error;
+}
+
+void pa_context_set_state_callback(pa_context *c, pa_context_notify_cb_t cb, void *userdata) {
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ c->state_callback = cb;
+ c->state_userdata = userdata;
+}
+
+int pa_context_is_pending(pa_context *c) {
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ PA_CHECK_VALIDITY(c,
+ c->state == PA_CONTEXT_CONNECTING ||
+ c->state == PA_CONTEXT_AUTHORIZING ||
+ c->state == PA_CONTEXT_SETTING_NAME ||
+ c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+
+ return (c->pstream && pa_pstream_is_pending(c->pstream)) ||
+ (c->pdispatch && pa_pdispatch_is_pending(c->pdispatch)) ||
+ c->client;
+}
+
+static void set_dispatch_callbacks(pa_operation *o);
+
+static void pdispatch_drain_callback(PA_GCC_UNUSED pa_pdispatch*pd, void *userdata) {
+ set_dispatch_callbacks(userdata);
+}
+
+static void pstream_drain_callback(PA_GCC_UNUSED pa_pstream *s, void *userdata) {
+ set_dispatch_callbacks(userdata);
+}
+
+static void set_dispatch_callbacks(pa_operation *o) {
+ int done = 1;
+
+ pa_assert(o);
+ pa_assert(PA_REFCNT_VALUE(o) >= 1);
+ pa_assert(o->context);
+ pa_assert(PA_REFCNT_VALUE(o->context) >= 1);
+ pa_assert(o->context->state == PA_CONTEXT_READY);
+
+ pa_pstream_set_drain_callback(o->context->pstream, NULL, NULL);
+ pa_pdispatch_set_drain_callback(o->context->pdispatch, NULL, NULL);
+
+ if (pa_pdispatch_is_pending(o->context->pdispatch)) {
+ pa_pdispatch_set_drain_callback(o->context->pdispatch, pdispatch_drain_callback, o);
+ done = 0;
+ }
+
+ if (pa_pstream_is_pending(o->context->pstream)) {
+ pa_pstream_set_drain_callback(o->context->pstream, pstream_drain_callback, o);
+ done = 0;
+ }
+
+ if (done) {
+ if (o->callback) {
+ pa_context_notify_cb_t cb = (pa_context_notify_cb_t) o->callback;
+ cb(o->context, o->userdata);
+ }
+
+ pa_operation_done(o);
+ pa_operation_unref(o);
+ }
+}
+
+pa_operation* pa_context_drain(pa_context *c, pa_context_notify_cb_t cb, void *userdata) {
+ pa_operation *o;
+
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, pa_context_is_pending(c), PA_ERR_BADSTATE);
+
+ o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+ set_dispatch_callbacks(pa_operation_ref(o));
+
+ return o;
+}
+
+void pa_context_simple_ack_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_operation *o = userdata;
+ int success = 1;
+
+ pa_assert(pd);
+ pa_assert(o);
+ pa_assert(PA_REFCNT_VALUE(o) >= 1);
+
+ if (!o->context)
+ goto finish;
+
+ if (command != PA_COMMAND_REPLY) {
+ if (pa_context_handle_error(o->context, command, t) < 0)
+ goto finish;
+
+ success = 0;
+ } else if (!pa_tagstruct_eof(t)) {
+ pa_context_fail(o->context, PA_ERR_PROTOCOL);
+ goto finish;
+ }
+
+ if (o->callback) {
+ pa_context_success_cb_t cb = (pa_context_success_cb_t) o->callback;
+ cb(o->context, success, o->userdata);
+ }
+
+finish:
+ pa_operation_done(o);
+ pa_operation_unref(o);
+}
+
+pa_operation* pa_context_exit_daemon(pa_context *c, pa_context_success_cb_t cb, void *userdata) {
+ pa_tagstruct *t;
+ pa_operation *o;
+ uint32_t tag;
+
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+
+ o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+
+ t = pa_tagstruct_command(c, PA_COMMAND_EXIT, &tag);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+}
+
+pa_operation* pa_context_send_simple_command(pa_context *c, uint32_t command, pa_pdispatch_cb_t internal_cb, pa_operation_cb_t cb, void *userdata) {
+ pa_tagstruct *t;
+ pa_operation *o;
+ uint32_t tag;
+
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+
+ o = pa_operation_new(c, NULL, cb, userdata);
+
+ t = pa_tagstruct_command(c, command, &tag);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, internal_cb, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+}
+
+pa_operation* pa_context_set_default_sink(pa_context *c, const char *name, pa_context_success_cb_t cb, void *userdata) {
+ pa_tagstruct *t;
+ pa_operation *o;
+ uint32_t tag;
+
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+
+ o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+
+ t = pa_tagstruct_command(c, PA_COMMAND_SET_DEFAULT_SINK, &tag);
+ pa_tagstruct_puts(t, name);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+}
+
+pa_operation* pa_context_set_default_source(pa_context *c, const char *name, pa_context_success_cb_t cb, void *userdata) {
+ pa_tagstruct *t;
+ pa_operation *o;
+ uint32_t tag;
+
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+
+ o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+
+ t = pa_tagstruct_command(c, PA_COMMAND_SET_DEFAULT_SOURCE, &tag);
+ pa_tagstruct_puts(t, name);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+}
+
+int pa_context_is_local(pa_context *c) {
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ PA_CHECK_VALIDITY(c, c->is_local >= 0, PA_ERR_BADSTATE);
+
+ return c->is_local;
+}
+
+pa_operation* pa_context_set_name(pa_context *c, const char *name, pa_context_success_cb_t cb, void *userdata) {
+ pa_tagstruct *t;
+ pa_operation *o;
+ uint32_t tag;
+
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+ pa_assert(name);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+
+ o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+
+ t = pa_tagstruct_command(c, PA_COMMAND_SET_CLIENT_NAME, &tag);
+ pa_tagstruct_puts(t, name);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+}
+
+const char* pa_get_library_version(void) {
+ return PACKAGE_VERSION;
+}
+
+const char* pa_context_get_server(pa_context *c) {
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ if (!c->server)
+ return NULL;
+
+ if (*c->server == '{') {
+ char *e = strchr(c->server+1, '}');
+ return e ? e+1 : c->server;
+ }
+
+ return c->server;
+}
+
+uint32_t pa_context_get_protocol_version(PA_GCC_UNUSED pa_context *c) {
+ return PA_PROTOCOL_VERSION;
+}
+
+uint32_t pa_context_get_server_protocol_version(pa_context *c) {
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ return c->version;
+}
+
+pa_tagstruct *pa_tagstruct_command(pa_context *c, uint32_t command, uint32_t *tag) {
+ pa_tagstruct *t;
+
+ pa_assert(c);
+ pa_assert(tag);
+
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, command);
+ pa_tagstruct_putu32(t, *tag = c->ctag++);
+
+ return t;
+}
diff --git a/src/pulse/context.h b/src/pulse/context.h
new file mode 100644
index 00000000..1de3abad
--- /dev/null
+++ b/src/pulse/context.h
@@ -0,0 +1,233 @@
+#ifndef foocontexthfoo
+#define foocontexthfoo
+
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <pulse/sample.h>
+#include <pulse/def.h>
+#include <pulse/mainloop-api.h>
+#include <pulse/cdecl.h>
+#include <pulse/operation.h>
+
+/** \page async Asynchronous API
+ *
+ * \section overv_sec Overview
+ *
+ * The asynchronous API is the native interface to the PulseAudio library.
+ * It allows full access to all available functions. This also means that
+ * it is rather complex and can take some time to fully master.
+ *
+ * \section mainloop_sec Main Loop Abstraction
+ *
+ * The API is based around an asynchronous event loop, or main loop,
+ * abstraction. This abstraction contains three basic elements:
+ *
+ * \li Deferred events - Events that will trigger as soon as possible. Note
+ * that some implementations may block all other events
+ * when a deferred event is active.
+ * \li I/O events - Events that trigger on file descriptor activities.
+ * \li Times events - Events that trigger after a fixed ammount of time.
+ *
+ * The abstraction is represented as a number of function pointers in the
+ * pa_mainloop_api structure.
+ *
+ * To actually be able to use these functions, an implementation needs to
+ * be coupled to the abstraction. There are three of these shipped with
+ * PulseAudio, but any other can be used with a minimal ammount of work,
+ * provided it supports the three basic events listed above.
+ *
+ * The implementations shipped with PulseAudio are:
+ *
+ * \li \subpage mainloop - A minimal but fast implementation based on poll().
+ * \li \subpage threaded_mainloop - A special version of the previous
+ * implementation where all of PulseAudio's
+ * internal handling runs in a separate
+ * thread.
+ * \li \subpage glib-mainloop - A wrapper around GLIB's main loop. Available
+ * for both GLIB 1.2 and GLIB 2.x.
+ *
+ * UNIX signals may be hooked to a main loop using the functions from
+ * \ref mainloop-signal.h. These rely only on the main loop abstraction
+ * and can therefore be used with any of the implementations.
+ *
+ * \section refcnt_sec Reference Counting
+ *
+ * Almost all objects in PulseAudio are reference counted. What that means
+ * is that you rarely malloc() or free() any objects. Instead you increase
+ * and decrease their reference counts. Whenever an object's reference
+ * count reaches zero, that object gets destroy and any resources it uses
+ * get freed.
+ *
+ * The benefit of this design is that an application need not worry about
+ * whether or not it needs to keep an object around in case the library is
+ * using it internally. If it is, then it has made sure it has its own
+ * reference to it.
+ *
+ * Whenever the library creates an object, it will have an initial
+ * reference count of one. Most of the time, this single reference will be
+ * sufficient for the application, so all required reference count
+ * interaction will be a single call to the objects unref function.
+ *
+ * \section context_sec Context
+ *
+ * A context is the basic object for a connection to a PulseAudio server.
+ * It multiplexes commands, data streams and events through a single
+ * channel.
+ *
+ * There is no need for more than one context per application, unless
+ * connections to multiple servers are needed.
+ *
+ * \subsection ops_subsec Operations
+ *
+ * All operations on the context are performed asynchronously. I.e. the
+ * client will not wait for the server to complete the request. To keep
+ * track of all these in-flight operations, the application is given a
+ * pa_operation object for each asynchronous operation.
+ *
+ * There are only two actions (besides reference counting) that can be
+ * performed on a pa_operation: querying its state with
+ * pa_operation_get_state() and aborting it with pa_operation_cancel().
+ *
+ * A pa_operation object is reference counted, so an application must
+ * make sure to unreference it, even if it has no intention of using it.
+ *
+ * \subsection conn_subsec Connecting
+ *
+ * A context must be connected to a server before any operation can be
+ * issued. Calling pa_context_connect() will initiate the connection
+ * procedure. Unlike most asynchronous operations, connecting does not
+ * result in a pa_operation object. Instead, the application should
+ * register a callback using pa_context_set_state_callback().
+ *
+ * \subsection disc_subsec Disconnecting
+ *
+ * When the sound support is no longer needed, the connection needs to be
+ * closed using pa_context_disconnect(). This is an immediate function that
+ * works synchronously.
+ *
+ * Since the context object has references to other objects it must be
+ * disconnected after use or there is a high risk of memory leaks. If the
+ * connection has terminated by itself, then there is no need to explicitly
+ * disconnect the context using pa_context_disconnect().
+ *
+ * \section Functions
+ *
+ * The sound server's functionality can be divided into a number of
+ * subsections:
+ *
+ * \li \subpage streams
+ * \li \subpage scache
+ * \li \subpage introspect
+ * \li \subpage subscribe
+ */
+
+/** \file
+ * Connection contexts for asynchrononous communication with a
+ * server. A pa_context object wraps a connection to a PulseAudio
+ * server using its native protocol. */
+
+/** \example pacat.c
+ * A playback and recording tool using the asynchronous API */
+
+/** \example paplay.c
+ * A sound file playback tool using the asynchronous API, based on libsndfile */
+
+PA_C_DECL_BEGIN
+
+/** An opaque connection context to a daemon */
+typedef struct pa_context pa_context;
+
+/** Generic notification callback prototype */
+typedef void (*pa_context_notify_cb_t)(pa_context *c, void *userdata);
+
+/** A generic callback for operation completion */
+typedef void (*pa_context_success_cb_t) (pa_context *c, int success, void *userdata);
+
+/** Instantiate a new connection context with an abstract mainloop API
+ * and an application name */
+pa_context *pa_context_new(pa_mainloop_api *mainloop, const char *name);
+
+/** Decrease the reference counter of the context by one */
+void pa_context_unref(pa_context *c);
+
+/** Increase the reference counter of the context by one */
+pa_context* pa_context_ref(pa_context *c);
+
+/** Set a callback function that is called whenever the context status changes */
+void pa_context_set_state_callback(pa_context *c, pa_context_notify_cb_t cb, void *userdata);
+
+/** Return the error number of the last failed operation */
+int pa_context_errno(pa_context *c);
+
+/** Return non-zero if some data is pending to be written to the connection */
+int pa_context_is_pending(pa_context *c);
+
+/** Return the current context status */
+pa_context_state_t pa_context_get_state(pa_context *c);
+
+/** Connect the context to the specified server. If server is NULL,
+connect to the default server. This routine may but will not always
+return synchronously on error. Use pa_context_set_state_callback() to
+be notified when the connection is established. If flags doesn't have
+PA_NOAUTOSPAWN set and no specific server is specified or accessible a
+new daemon is spawned. If api is non-NULL, the functions specified in
+the structure are used when forking a new child process. */
+int pa_context_connect(pa_context *c, const char *server, pa_context_flags_t flags, const pa_spawn_api *api);
+
+/** Terminate the context connection immediately */
+void pa_context_disconnect(pa_context *c);
+
+/** Drain the context. If there is nothing to drain, the function returns NULL */
+pa_operation* pa_context_drain(pa_context *c, pa_context_notify_cb_t cb, void *userdata);
+
+/** Tell the daemon to exit. The returned operation is unlikely to
+ * complete succesfully, since the daemon probably died before
+ * returning a success notification */
+pa_operation* pa_context_exit_daemon(pa_context *c, pa_context_success_cb_t cb, void *userdata);
+
+/** Set the name of the default sink. \since 0.4 */
+pa_operation* pa_context_set_default_sink(pa_context *c, const char *name, pa_context_success_cb_t cb, void *userdata);
+
+/** Set the name of the default source. \since 0.4 */
+pa_operation* pa_context_set_default_source(pa_context *c, const char *name, pa_context_success_cb_t cb, void *userdata);
+
+/** Returns 1 when the connection is to a local daemon. Returns negative when no connection has been made yet. \since 0.5 */
+int pa_context_is_local(pa_context *c);
+
+/** Set a different application name for context on the server. \since 0.5 */
+pa_operation* pa_context_set_name(pa_context *c, const char *name, pa_context_success_cb_t cb, void *userdata);
+
+/** Return the server name this context is connected to. \since 0.7 */
+const char* pa_context_get_server(pa_context *c);
+
+/** Return the protocol version of the library. \since 0.8 */
+uint32_t pa_context_get_protocol_version(pa_context *c);
+
+/** Return the protocol version of the connected server. \since 0.8 */
+uint32_t pa_context_get_server_protocol_version(pa_context *c);
+
+PA_C_DECL_END
+
+#endif
diff --git a/src/pulse/def.h b/src/pulse/def.h
new file mode 100644
index 00000000..dabbc5eb
--- /dev/null
+++ b/src/pulse/def.h
@@ -0,0 +1,397 @@
+#ifndef foodefhfoo
+#define foodefhfoo
+
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <inttypes.h>
+#include <sys/time.h>
+#include <time.h>
+
+#include <pulse/cdecl.h>
+#include <pulse/sample.h>
+
+/** \file
+ * Global definitions */
+
+PA_C_DECL_BEGIN
+
+/** The state of a connection context */
+typedef enum pa_context_state {
+ PA_CONTEXT_UNCONNECTED, /**< The context hasn't been connected yet */
+ PA_CONTEXT_CONNECTING, /**< A connection is being established */
+ PA_CONTEXT_AUTHORIZING, /**< The client is authorizing itself to the daemon */
+ PA_CONTEXT_SETTING_NAME, /**< The client is passing its application name to the daemon */
+ PA_CONTEXT_READY, /**< The connection is established, the context is ready to execute operations */
+ PA_CONTEXT_FAILED, /**< The connection failed or was disconnected */
+ PA_CONTEXT_TERMINATED /**< The connection was terminated cleanly */
+} pa_context_state_t;
+
+/** The state of a stream */
+typedef enum pa_stream_state {
+ PA_STREAM_UNCONNECTED, /**< The stream is not yet connected to any sink or source */
+ PA_STREAM_CREATING, /**< The stream is being created */
+ PA_STREAM_READY, /**< The stream is established, you may pass audio data to it now */
+ PA_STREAM_FAILED, /**< An error occured that made the stream invalid */
+ PA_STREAM_TERMINATED /**< The stream has been terminated cleanly */
+} pa_stream_state_t;
+
+/** The state of an operation */
+typedef enum pa_operation_state {
+ PA_OPERATION_RUNNING, /**< The operation is still running */
+ PA_OPERATION_DONE, /**< The operation has been completed */
+ PA_OPERATION_CANCELED /**< The operation has been canceled */
+} pa_operation_state_t;
+
+/** An invalid index */
+#define PA_INVALID_INDEX ((uint32_t) -1)
+
+/** Some special flags for contexts. \since 0.8 */
+typedef enum pa_context_flags {
+ PA_CONTEXT_NOAUTOSPAWN = 1 /**< Disabled autospawning of the PulseAudio daemon if required */
+} pa_context_flags_t;
+
+/** The direction of a pa_stream object */
+typedef enum pa_stream_direction {
+ PA_STREAM_NODIRECTION, /**< Invalid direction */
+ PA_STREAM_PLAYBACK, /**< Playback stream */
+ PA_STREAM_RECORD, /**< Record stream */
+ PA_STREAM_UPLOAD /**< Sample upload stream */
+} pa_stream_direction_t;
+
+/** Some special flags for stream connections. \since 0.6 */
+typedef enum pa_stream_flags {
+ PA_STREAM_START_CORKED = 1, /**< Create the stream corked, requiring an explicit pa_stream_cork() call to uncork it. */
+ PA_STREAM_INTERPOLATE_TIMING = 2, /**< Interpolate the latency for
+ * this stream. When enabled,
+ * pa_stream_get_latency() and
+ * pa_stream_get_time() will try
+ * to estimate the current
+ * record/playback time based on
+ * the local time that passed
+ * since the last timing info
+ * update. Using this option
+ * has the advantage of not
+ * requiring a whole roundtrip
+ * when the current
+ * playback/recording time is
+ * needed. Consider using this
+ * option when requesting
+ * latency information
+ * frequently. This is
+ * especially useful on long
+ * latency network
+ * connections. It makes a lot
+ * of sense to combine this
+ * option with
+ * PA_STREAM_AUTO_TIMING_UPDATE. */
+ PA_STREAM_NOT_MONOTONOUS = 4, /**< Don't force the time to
+ * increase monotonically. If
+ * this option is enabled,
+ * pa_stream_get_time() will not
+ * necessarily return always
+ * monotonically increasing time
+ * values on each call. This may
+ * confuse applications which
+ * cannot deal with time going
+ * 'backwards', but has the
+ * advantage that bad transport
+ * latency estimations that
+ * caused the time to to jump
+ * ahead can be corrected
+ * quickly, without the need to
+ * wait. */
+ PA_STREAM_AUTO_TIMING_UPDATE = 8, /**< If set timing update requests
+ * are issued periodically
+ * automatically. Combined with
+ * PA_STREAM_INTERPOLATE_TIMING
+ * you will be able to query the
+ * current time and latency with
+ * pa_stream_get_time() and
+ * pa_stream_get_latency() at
+ * all times without a packet
+ * round trip.*/
+ PA_STREAM_NO_REMAP_CHANNELS = 16, /**< Don't remap channels by
+ * their name, instead map them
+ * simply by their
+ * index. Implies
+ * PA_STREAM_NO_REMIX_CHANNELS. Only
+ * supported when the server is
+ * at least PA 0.9.8. It is
+ * ignored on older
+ * servers.\since 0.9.8 */
+ PA_STREAM_NO_REMIX_CHANNELS = 32, /**< When remapping channels by
+ * name, don't upmix or downmix
+ * them to related
+ * channels. Copy them into
+ * matching channels of the
+ * device 1:1. Only supported
+ * when the server is at least
+ * PA 0.9.8. It is ignored on
+ * older servers. \since
+ * 0.9.8 */
+ PA_STREAM_FIX_FORMAT = 64, /**< Use the sample format of the
+ * sink/device this stream is being
+ * connected to, and possibly ignore
+ * the format the sample spec contains
+ * -- but you still have to pass a
+ * valid value in it as a hint to
+ * PulseAudio what would suit your
+ * stream best. If this is used you
+ * should query the used sample format
+ * after creating the stream by using
+ * pa_stream_get_sample_spec(). Also,
+ * if you specified manual buffer
+ * metrics it is recommended to update
+ * them with
+ * pa_stream_set_buffer_attr() to
+ * compensate for the changed frame
+ * sizes. Only supported when the
+ * server is at least PA 0.9.8. It is
+ * ignored on older servers. \since
+ * 0.9.8 */
+
+ PA_STREAM_FIX_RATE = 128, /**< Use the sample rate of the sink,
+ * and possibly ignore the rate the
+ * sample spec contains. Usage similar
+ * to PA_STREAM_FIX_FORMAT.Only
+ * supported when the server is at least
+ * PA 0.9.8. It is ignored on older
+ * servers. \since 0.9.8 */
+
+ PA_STREAM_FIX_CHANNELS = 256, /**< Use the number of channels and
+ * the channel map of the sink, and
+ * possibly ignore the number of
+ * channels and the map the sample spec
+ * and the passed channel map
+ * contains. Usage similar to
+ * PA_STREAM_FIX_FORMAT. Only supported
+ * when the server is at least PA
+ * 0.9.8. It is ignored on older
+ * servers. \since 0.9.8 */
+ PA_STREAM_DONT_MOVE = 512, /**< Don't allow moving of this stream to
+ * another sink/device. Useful if you use
+ * any of the PA_STREAM_FIX_ flags and
+ * want to make sure that resampling
+ * never takes place -- which might
+ * happen if the stream is moved to
+ * another sink/source whith a different
+ * sample spec/channel map. Only
+ * supported when the server is at least
+ * PA 0.9.8. It is ignored on older
+ * servers. \since 0.9.8 */
+ PA_STREAM_VARIABLE_RATE = 1024, /**< Allow dynamic changing of the
+ * sampling rate during playback
+ * with
+ * pa_stream_update_sample_rate(). Only
+ * supported when the server is at
+ * least PA 0.9.8. It is ignored
+ * on older servers. \since
+ * 0.9.8 */
+} pa_stream_flags_t;
+
+/** Playback and record buffer metrics */
+typedef struct pa_buffer_attr {
+ uint32_t maxlength; /**< Maximum length of the buffer */
+ uint32_t tlength; /**< Playback only: target length of the buffer. The server tries to assure that at least tlength bytes are always available in the buffer */
+ uint32_t prebuf; /**< Playback only: pre-buffering. The server does not start with playback before at least prebug bytes are available in the buffer */
+ uint32_t minreq; /**< Playback only: minimum request. The server does not request less than minreq bytes from the client, instead waints until the buffer is free enough to request more bytes at once */
+ uint32_t fragsize; /**< Recording only: fragment size. The server sends data in blocks of fragsize bytes size. Large values deminish interactivity with other operations on the connection context but decrease control overhead. */
+} pa_buffer_attr;
+
+/** Error values as used by pa_context_errno(). Use pa_strerror() to convert these values to human readable strings */
+enum {
+ PA_OK = 0, /**< No error */
+ PA_ERR_ACCESS, /**< Access failure */
+ PA_ERR_COMMAND, /**< Unknown command */
+ PA_ERR_INVALID, /**< Invalid argument */
+ PA_ERR_EXIST, /**< Entity exists */
+ PA_ERR_NOENTITY, /**< No such entity */
+ PA_ERR_CONNECTIONREFUSED, /**< Connection refused */
+ PA_ERR_PROTOCOL, /**< Protocol error */
+ PA_ERR_TIMEOUT, /**< Timeout */
+ PA_ERR_AUTHKEY, /**< No authorization key */
+ PA_ERR_INTERNAL, /**< Internal error */
+ PA_ERR_CONNECTIONTERMINATED, /**< Connection terminated */
+ PA_ERR_KILLED, /**< Entity killed */
+ PA_ERR_INVALIDSERVER, /**< Invalid server */
+ PA_ERR_MODINITFAILED, /**< Module initialization failed */
+ PA_ERR_BADSTATE, /**< Bad state */
+ PA_ERR_NODATA, /**< No data */
+ PA_ERR_VERSION, /**< Incompatible protocol version \since 0.8 */
+ PA_ERR_TOOLARGE, /**< Data too large \since 0.8.1 */
+ PA_ERR_NOTSUPPORTED, /**< Operation not supported \since 0.9.5 */
+ PA_ERR_MAX /**< Not really an error but the first invalid error code */
+};
+
+/** Subscription event mask, as used by pa_context_subscribe() */
+typedef enum pa_subscription_mask {
+ PA_SUBSCRIPTION_MASK_NULL = 0, /**< No events */
+ PA_SUBSCRIPTION_MASK_SINK = 1, /**< Sink events */
+ PA_SUBSCRIPTION_MASK_SOURCE = 2, /**< Source events */
+ PA_SUBSCRIPTION_MASK_SINK_INPUT = 4, /**< Sink input events */
+ PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT = 8, /**< Source output events */
+ PA_SUBSCRIPTION_MASK_MODULE = 16, /**< Module events */
+ PA_SUBSCRIPTION_MASK_CLIENT = 32, /**< Client events */
+ PA_SUBSCRIPTION_MASK_SAMPLE_CACHE = 64, /**< Sample cache events */
+ PA_SUBSCRIPTION_MASK_SERVER = 128, /**< Other global server changes. \since 0.4 */
+ PA_SUBSCRIPTION_MASK_AUTOLOAD = 256, /**< Autoload table events. \since 0.5 */
+ PA_SUBSCRIPTION_MASK_ALL = 511 /**< Catch all events \since 0.8 */
+} pa_subscription_mask_t;
+
+/** Subscription event types, as used by pa_context_subscribe() */
+typedef enum pa_subscription_event_type {
+ PA_SUBSCRIPTION_EVENT_SINK = 0, /**< Event type: Sink */
+ PA_SUBSCRIPTION_EVENT_SOURCE = 1, /**< Event type: Source */
+ PA_SUBSCRIPTION_EVENT_SINK_INPUT = 2, /**< Event type: Sink input */
+ PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT = 3, /**< Event type: Source output */
+ PA_SUBSCRIPTION_EVENT_MODULE = 4, /**< Event type: Module */
+ PA_SUBSCRIPTION_EVENT_CLIENT = 5, /**< Event type: Client */
+ PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE = 6, /**< Event type: Sample cache item */
+ PA_SUBSCRIPTION_EVENT_SERVER = 7, /**< Event type: Global server change, only occuring with PA_SUBSCRIPTION_EVENT_CHANGE. \since 0.4 */
+ PA_SUBSCRIPTION_EVENT_AUTOLOAD = 8, /**< Event type: Autoload table changes. \since 0.5 */
+ PA_SUBSCRIPTION_EVENT_FACILITY_MASK = 15, /**< A mask to extract the event type from an event value */
+
+ PA_SUBSCRIPTION_EVENT_NEW = 0, /**< A new object was created */
+ PA_SUBSCRIPTION_EVENT_CHANGE = 16, /**< A property of the object was modified */
+ PA_SUBSCRIPTION_EVENT_REMOVE = 32, /**< An object was removed */
+ PA_SUBSCRIPTION_EVENT_TYPE_MASK = 16+32 /**< A mask to extract the event operation from an event value */
+} pa_subscription_event_type_t;
+
+/** Return one if an event type t matches an event mask bitfield */
+#define pa_subscription_match_flags(m, t) (!!((m) & (1 << ((t) & PA_SUBSCRIPTION_EVENT_FACILITY_MASK))))
+
+/** A structure for all kinds of timing information of a stream. See
+ * pa_stream_update_timing_info() and pa_stream_get_timing_info(). The
+ * total output latency a sample that is written with
+ * pa_stream_write() takes to be played may be estimated by
+ * sink_usec+buffer_usec+transport_usec. (where buffer_usec is defined
+ * as pa_bytes_to_usec(write_index-read_index)) The output buffer
+ * which buffer_usec relates to may be manipulated freely (with
+ * pa_stream_write()'s seek argument, pa_stream_flush() and friends),
+ * the buffers sink_usec and source_usec relate to are first-in
+ * first-out (FIFO) buffers which cannot be flushed or manipulated in
+ * any way. The total input latency a sample that is recorded takes to
+ * be delivered to the application is:
+ * source_usec+buffer_usec+transport_usec-sink_usec. (Take care of
+ * sign issues!) When connected to a monitor source sink_usec contains
+ * the latency of the owning sink. The two latency estimations
+ * described here are implemented in pa_stream_get_latency().*/
+typedef struct pa_timing_info {
+ struct timeval timestamp; /**< The time when this timing info structure was current */
+ int synchronized_clocks; /**< Non-zero if the local and the
+ * remote machine have synchronized
+ * clocks. If synchronized clocks are
+ * detected transport_usec becomes much
+ * more reliable. However, the code that
+ * detects synchronized clocks is very
+ * limited und unreliable itself. \since
+ * 0.5 */
+
+ pa_usec_t sink_usec; /**< Time in usecs a sample takes to be played on the sink. For playback streams and record streams connected to a monitor source. */
+ pa_usec_t source_usec; /**< Time in usecs a sample takes from being recorded to being delivered to the application. Only for record streams. \since 0.5*/
+ pa_usec_t transport_usec; /**< Estimated time in usecs a sample takes to be transferred to/from the daemon. For both playback and record streams. \since 0.5 */
+
+ int playing; /**< Non-zero when the stream is currently playing. Only for playback streams. */
+
+ int write_index_corrupt; /**< Non-zero if write_index is not
+ * up-to-date because a local write
+ * command that corrupted it has been
+ * issued in the time since this latency
+ * info was current . Only write
+ * commands with SEEK_RELATIVE_ON_READ
+ * and SEEK_RELATIVE_END can corrupt
+ * write_index. \since 0.8 */
+ int64_t write_index; /**< Current write index into the
+ * playback buffer in bytes. Think twice before
+ * using this for seeking purposes: it
+ * might be out of date a the time you
+ * want to use it. Consider using
+ * PA_SEEK_RELATIVE instead. \since
+ * 0.8 */
+
+ int read_index_corrupt; /**< Non-zero if read_index is not
+ * up-to-date because a local pause or
+ * flush request that corrupted it has
+ * been issued in the time since this
+ * latency info was current. \since 0.8 */
+
+ int64_t read_index; /**< Current read index into the
+ * playback buffer in bytes. Think twice before
+ * using this for seeking purposes: it
+ * might be out of date a the time you
+ * want to use it. Consider using
+ * PA_SEEK_RELATIVE_ON_READ
+ * instead. \since 0.8 */
+} pa_timing_info;
+
+/** A structure for the spawn api. This may be used to integrate auto
+ * spawned daemons into your application. For more information see
+ * pa_context_connect(). When spawning a new child process the
+ * waitpid() is used on the child's PID. The spawn routine will not
+ * block or ignore SIGCHLD signals, since this cannot be done in a
+ * thread compatible way. You might have to do this in
+ * prefork/postfork. \since 0.4 */
+typedef struct pa_spawn_api {
+ void (*prefork)(void); /**< Is called just before the fork in the parent process. May be NULL. */
+ void (*postfork)(void); /**< Is called immediately after the fork in the parent process. May be NULL.*/
+ void (*atfork)(void); /**< Is called immediately after the
+ * fork in the child process. May be
+ * NULL. It is not safe to close all
+ * file descriptors in this function
+ * unconditionally, since a UNIX socket
+ * (created using socketpair()) is
+ * passed to the new process. */
+} pa_spawn_api;
+
+/** Seek type for pa_stream_write(). \since 0.8*/
+typedef enum pa_seek_mode {
+ PA_SEEK_RELATIVE = 0, /**< Seek relatively to the write index */
+ PA_SEEK_ABSOLUTE = 1, /**< Seek relatively to the start of the buffer queue */
+ PA_SEEK_RELATIVE_ON_READ = 2, /**< Seek relatively to the read index. */
+ PA_SEEK_RELATIVE_END = 3 /**< Seek relatively to the current end of the buffer queue. */
+} pa_seek_mode_t;
+
+/** Special sink flags. \since 0.8 */
+typedef enum pa_sink_flags {
+ PA_SINK_HW_VOLUME_CTRL = 1, /**< Supports hardware volume control */
+ PA_SINK_LATENCY = 2, /**< Supports latency querying */
+ PA_SINK_HARDWARE = 4, /**< Is a hardware sink of some kind, in contrast to "virtual"/software sinks \since 0.9.3 */
+ PA_SINK_NETWORK = 8 /**< Is a networked sink of some kind. \since 0.9.7 */
+} pa_sink_flags_t;
+
+/** Special source flags. \since 0.8 */
+typedef enum pa_source_flags {
+ PA_SOURCE_HW_VOLUME_CTRL = 1, /**< Supports hardware volume control */
+ PA_SOURCE_LATENCY = 2, /**< Supports latency querying */
+ PA_SOURCE_HARDWARE = 4, /**< Is a hardware source of some kind, in contrast to "virtual"/software source \since 0.9.3 */
+ PA_SOURCE_NETWORK = 8 /**< Is a networked sink of some kind. \since 0.9.7 */
+} pa_source_flags_t;
+
+/** A generic free() like callback prototype */
+typedef void (*pa_free_cb_t)(void *p);
+
+PA_C_DECL_END
+
+#endif
diff --git a/src/pulse/error.c b/src/pulse/error.c
new file mode 100644
index 00000000..78f0da95
--- /dev/null
+++ b/src/pulse/error.c
@@ -0,0 +1,69 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/native-common.h>
+
+#include "error.h"
+
+const char*pa_strerror(int error) {
+
+ static const char* const errortab[PA_ERR_MAX] = {
+ [PA_OK] = "OK",
+ [PA_ERR_ACCESS] = "Access denied",
+ [PA_ERR_COMMAND] = "Unknown command",
+ [PA_ERR_INVALID] = "Invalid argument",
+ [PA_ERR_EXIST] = "Entity exists",
+ [PA_ERR_NOENTITY] = "No such entity",
+ [PA_ERR_CONNECTIONREFUSED] = "Connection refused",
+ [PA_ERR_PROTOCOL] = "Protocol error",
+ [PA_ERR_TIMEOUT] = "Timeout",
+ [PA_ERR_AUTHKEY] = "No authorization key",
+ [PA_ERR_INTERNAL] = "Internal error",
+ [PA_ERR_CONNECTIONTERMINATED] = "Connection terminated",
+ [PA_ERR_KILLED] = "Entity killed",
+ [PA_ERR_INVALIDSERVER] = "Invalid server",
+ [PA_ERR_MODINITFAILED] = "Module initalization failed",
+ [PA_ERR_BADSTATE] = "Bad state",
+ [PA_ERR_NODATA] = "No data",
+ [PA_ERR_VERSION] = "Incompatible protocol version",
+ [PA_ERR_TOOLARGE] = "Too large"
+ };
+
+ if (error < 0 || error >= PA_ERR_MAX)
+ return NULL;
+
+ return errortab[error];
+}
diff --git a/src/pulse/error.h b/src/pulse/error.h
new file mode 100644
index 00000000..44a2e5ec
--- /dev/null
+++ b/src/pulse/error.h
@@ -0,0 +1,41 @@
+#ifndef fooerrorhfoo
+#define fooerrorhfoo
+
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <inttypes.h>
+#include <pulse/cdecl.h>
+
+/** \file
+ * Error management */
+
+PA_C_DECL_BEGIN
+
+/** Return a human readable error message for the specified numeric error code */
+const char* pa_strerror(int error);
+
+PA_C_DECL_END
+
+#endif
diff --git a/src/pulse/glib-mainloop.c b/src/pulse/glib-mainloop.c
new file mode 100644
index 00000000..b7a7537a
--- /dev/null
+++ b/src/pulse/glib-mainloop.c
@@ -0,0 +1,665 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulse/xmalloc.h>
+#include <pulse/timeval.h>
+
+#include <pulsecore/idxset.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/log.h>
+#include <pulsecore/llist.h>
+
+#include <glib.h>
+#include "glib-mainloop.h"
+
+struct pa_io_event {
+ pa_glib_mainloop *mainloop;
+ int dead;
+
+ GPollFD poll_fd;
+ int poll_fd_added;
+
+ pa_io_event_cb_t callback;
+ void *userdata;
+ pa_io_event_destroy_cb_t destroy_callback;
+
+ PA_LLIST_FIELDS(pa_io_event);
+};
+
+struct pa_time_event {
+ pa_glib_mainloop *mainloop;
+ int dead;
+
+ int enabled;
+ struct timeval timeval;
+
+ pa_time_event_cb_t callback;
+ void *userdata;
+ pa_time_event_destroy_cb_t destroy_callback;
+
+ PA_LLIST_FIELDS(pa_time_event);
+};
+
+struct pa_defer_event {
+ pa_glib_mainloop *mainloop;
+ int dead;
+
+ int enabled;
+
+ pa_defer_event_cb_t callback;
+ void *userdata;
+ pa_defer_event_destroy_cb_t destroy_callback;
+
+ PA_LLIST_FIELDS(pa_defer_event);
+};
+
+struct pa_glib_mainloop {
+ GSource source;
+
+ pa_mainloop_api api;
+ GMainContext *context;
+
+ PA_LLIST_HEAD(pa_io_event, io_events);
+ PA_LLIST_HEAD(pa_time_event, time_events);
+ PA_LLIST_HEAD(pa_defer_event, defer_events);
+
+ int n_enabled_defer_events, n_enabled_time_events;
+ int io_events_please_scan, time_events_please_scan, defer_events_please_scan;
+
+ pa_time_event *cached_next_time_event;
+};
+
+static void cleanup_io_events(pa_glib_mainloop *g, int force) {
+ pa_io_event *e;
+
+ e = g->io_events;
+ while (e) {
+ pa_io_event *n = e->next;
+
+ if (!force && g->io_events_please_scan <= 0)
+ break;
+
+ if (force || e->dead) {
+ PA_LLIST_REMOVE(pa_io_event, g->io_events, e);
+
+ if (e->dead) {
+ g_assert(g->io_events_please_scan > 0);
+ g->io_events_please_scan--;
+ }
+
+ if (e->poll_fd_added)
+ g_source_remove_poll(&g->source, &e->poll_fd);
+
+ if (e->destroy_callback)
+ e->destroy_callback(&g->api, e, e->userdata);
+
+ pa_xfree(e);
+ }
+
+ e = n;
+ }
+
+ g_assert(g->io_events_please_scan == 0);
+}
+
+static void cleanup_time_events(pa_glib_mainloop *g, int force) {
+ pa_time_event *e;
+
+ e = g->time_events;
+ while (e) {
+ pa_time_event *n = e->next;
+
+ if (!force && g->time_events_please_scan <= 0)
+ break;
+
+ if (force || e->dead) {
+ PA_LLIST_REMOVE(pa_time_event, g->time_events, e);
+
+ if (e->dead) {
+ g_assert(g->time_events_please_scan > 0);
+ g->time_events_please_scan--;
+ }
+
+ if (!e->dead && e->enabled) {
+ g_assert(g->n_enabled_time_events > 0);
+ g->n_enabled_time_events--;
+ }
+
+ if (e->destroy_callback)
+ e->destroy_callback(&g->api, e, e->userdata);
+
+ pa_xfree(e);
+ }
+
+ e = n;
+ }
+
+ g_assert(g->time_events_please_scan == 0);
+}
+
+static void cleanup_defer_events(pa_glib_mainloop *g, int force) {
+ pa_defer_event *e;
+
+ e = g->defer_events;
+ while (e) {
+ pa_defer_event *n = e->next;
+
+ if (!force && g->defer_events_please_scan <= 0)
+ break;
+
+ if (force || e->dead) {
+ PA_LLIST_REMOVE(pa_defer_event, g->defer_events, e);
+
+ if (e->dead) {
+ g_assert(g->defer_events_please_scan > 0);
+ g->defer_events_please_scan--;
+ }
+
+ if (!e->dead && e->enabled) {
+ g_assert(g->n_enabled_defer_events > 0);
+ g->n_enabled_defer_events--;
+ }
+
+ if (e->destroy_callback)
+ e->destroy_callback(&g->api, e, e->userdata);
+
+ pa_xfree(e);
+ }
+
+ e = n;
+ }
+
+ g_assert(g->defer_events_please_scan == 0);
+}
+
+static gushort map_flags_to_glib(pa_io_event_flags_t flags) {
+ return
+ (flags & PA_IO_EVENT_INPUT ? G_IO_IN : 0) |
+ (flags & PA_IO_EVENT_OUTPUT ? G_IO_OUT : 0) |
+ (flags & PA_IO_EVENT_ERROR ? G_IO_ERR : 0) |
+ (flags & PA_IO_EVENT_HANGUP ? G_IO_HUP : 0);
+}
+
+static pa_io_event_flags_t map_flags_from_glib(gushort flags) {
+ return
+ (flags & G_IO_IN ? PA_IO_EVENT_INPUT : 0) |
+ (flags & G_IO_OUT ? PA_IO_EVENT_OUTPUT : 0) |
+ (flags & G_IO_ERR ? PA_IO_EVENT_ERROR : 0) |
+ (flags & G_IO_HUP ? PA_IO_EVENT_HANGUP : 0);
+}
+
+static pa_io_event* glib_io_new(
+ pa_mainloop_api*m,
+ int fd,
+ pa_io_event_flags_t f,
+ pa_io_event_cb_t cb,
+ void *userdata) {
+
+ pa_io_event *e;
+ pa_glib_mainloop *g;
+
+ g_assert(m);
+ g_assert(m->userdata);
+ g_assert(fd >= 0);
+ g_assert(cb);
+
+ g = m->userdata;
+
+ e = pa_xnew(pa_io_event, 1);
+ e->mainloop = g;
+ e->dead = 0;
+
+ e->poll_fd.fd = fd;
+ e->poll_fd.events = map_flags_to_glib(f);
+ e->poll_fd.revents = 0;
+
+ e->callback = cb;
+ e->userdata = userdata;
+ e->destroy_callback = NULL;
+
+ PA_LLIST_PREPEND(pa_io_event, g->io_events, e);
+
+ g_source_add_poll(&g->source, &e->poll_fd);
+ e->poll_fd_added = 1;
+
+ return e;
+}
+
+static void glib_io_enable(pa_io_event*e, pa_io_event_flags_t f) {
+ g_assert(e);
+ g_assert(!e->dead);
+
+ e->poll_fd.events = map_flags_to_glib(f);
+}
+
+static void glib_io_free(pa_io_event*e) {
+ g_assert(e);
+ g_assert(!e->dead);
+
+ e->dead = 1;
+ e->mainloop->io_events_please_scan++;
+
+ if (e->poll_fd_added) {
+ g_source_remove_poll(&e->mainloop->source, &e->poll_fd);
+ e->poll_fd_added = 0;
+ }
+}
+
+static void glib_io_set_destroy(pa_io_event*e, pa_io_event_destroy_cb_t cb) {
+ g_assert(e);
+ g_assert(!e->dead);
+
+ e->destroy_callback = cb;
+}
+
+/* Time sources */
+
+static pa_time_event* glib_time_new(
+ pa_mainloop_api*m,
+ const struct timeval *tv,
+ pa_time_event_cb_t cb,
+ void *userdata) {
+
+ pa_glib_mainloop *g;
+ pa_time_event *e;
+
+ g_assert(m);
+ g_assert(m->userdata);
+ g_assert(cb);
+
+ g = m->userdata;
+
+ e = pa_xnew(pa_time_event, 1);
+ e->mainloop = g;
+ e->dead = 0;
+
+ if ((e->enabled = !!tv)) {
+ e->timeval = *tv;
+ g->n_enabled_time_events++;
+
+ if (g->cached_next_time_event) {
+ g_assert(g->cached_next_time_event->enabled);
+
+ if (pa_timeval_cmp(tv, &g->cached_next_time_event->timeval) < 0)
+ g->cached_next_time_event = e;
+ }
+ }
+
+ e->callback = cb;
+ e->userdata = userdata;
+ e->destroy_callback = NULL;
+
+ PA_LLIST_PREPEND(pa_time_event, g->time_events, e);
+
+ return e;
+}
+
+static void glib_time_restart(pa_time_event*e, const struct timeval *tv) {
+ g_assert(e);
+ g_assert(!e->dead);
+
+ if (e->enabled && !tv) {
+ g_assert(e->mainloop->n_enabled_time_events > 0);
+ e->mainloop->n_enabled_time_events--;
+ } else if (!e->enabled && tv)
+ e->mainloop->n_enabled_time_events++;
+
+ if ((e->enabled = !!tv))
+ e->timeval = *tv;
+
+ if (e->mainloop->cached_next_time_event && e->enabled) {
+ g_assert(e->mainloop->cached_next_time_event->enabled);
+
+ if (pa_timeval_cmp(tv, &e->mainloop->cached_next_time_event->timeval) < 0)
+ e->mainloop->cached_next_time_event = e;
+ } else if (e->mainloop->cached_next_time_event == e)
+ e->mainloop->cached_next_time_event = NULL;
+ }
+
+static void glib_time_free(pa_time_event *e) {
+ g_assert(e);
+ g_assert(!e->dead);
+
+ e->dead = 1;
+ e->mainloop->time_events_please_scan++;
+
+ if (e->enabled)
+ e->mainloop->n_enabled_time_events--;
+
+ if (e->mainloop->cached_next_time_event == e)
+ e->mainloop->cached_next_time_event = NULL;
+}
+
+static void glib_time_set_destroy(pa_time_event *e, pa_time_event_destroy_cb_t cb) {
+ g_assert(e);
+ g_assert(!e->dead);
+
+ e->destroy_callback = cb;
+}
+
+/* Deferred sources */
+
+static pa_defer_event* glib_defer_new(
+ pa_mainloop_api*m,
+ pa_defer_event_cb_t cb,
+ void *userdata) {
+
+ pa_defer_event *e;
+ pa_glib_mainloop *g;
+
+ g_assert(m);
+ g_assert(m->userdata);
+ g_assert(cb);
+
+ g = m->userdata;
+
+ e = pa_xnew(pa_defer_event, 1);
+ e->mainloop = g;
+ e->dead = 0;
+
+ e->enabled = 1;
+ g->n_enabled_defer_events++;
+
+ e->callback = cb;
+ e->userdata = userdata;
+ e->destroy_callback = NULL;
+
+ PA_LLIST_PREPEND(pa_defer_event, g->defer_events, e);
+ return e;
+}
+
+static void glib_defer_enable(pa_defer_event *e, int b) {
+ g_assert(e);
+ g_assert(!e->dead);
+
+ if (e->enabled && !b) {
+ g_assert(e->mainloop->n_enabled_defer_events > 0);
+ e->mainloop->n_enabled_defer_events--;
+ } else if (!e->enabled && b)
+ e->mainloop->n_enabled_defer_events++;
+
+ e->enabled = b;
+}
+
+static void glib_defer_free(pa_defer_event *e) {
+ g_assert(e);
+ g_assert(!e->dead);
+
+ e->dead = 1;
+ e->mainloop->defer_events_please_scan++;
+
+ if (e->enabled) {
+ g_assert(e->mainloop->n_enabled_defer_events > 0);
+ e->mainloop->n_enabled_defer_events--;
+ }
+}
+
+static void glib_defer_set_destroy(pa_defer_event *e, pa_defer_event_destroy_cb_t cb) {
+ g_assert(e);
+ g_assert(!e->dead);
+
+ e->destroy_callback = cb;
+}
+
+/* quit() */
+
+static void glib_quit(pa_mainloop_api*a, PA_GCC_UNUSED int retval) {
+
+ g_warning("quit() ignored");
+
+ /* NOOP */
+}
+
+static pa_time_event* find_next_time_event(pa_glib_mainloop *g) {
+ pa_time_event *t, *n = NULL;
+ g_assert(g);
+
+ if (g->cached_next_time_event)
+ return g->cached_next_time_event;
+
+ for (t = g->time_events; t; t = t->next) {
+
+ if (t->dead || !t->enabled)
+ continue;
+
+ if (!n || pa_timeval_cmp(&t->timeval, &n->timeval) < 0) {
+ n = t;
+
+ /* Shortcut for tv = { 0, 0 } */
+ if (n->timeval.tv_sec <= 0)
+ break;
+ }
+ }
+
+ g->cached_next_time_event = n;
+ return n;
+}
+
+static void scan_dead(pa_glib_mainloop *g) {
+ g_assert(g);
+
+ if (g->io_events_please_scan)
+ cleanup_io_events(g, 0);
+
+ if (g->time_events_please_scan)
+ cleanup_time_events(g, 0);
+
+ if (g->defer_events_please_scan)
+ cleanup_defer_events(g, 0);
+}
+
+static gboolean prepare_func(GSource *source, gint *timeout) {
+ pa_glib_mainloop *g = (pa_glib_mainloop*) source;
+
+ g_assert(g);
+ g_assert(timeout);
+
+ scan_dead(g);
+
+ if (g->n_enabled_defer_events) {
+ *timeout = 0;
+ return TRUE;
+ } else if (g->n_enabled_time_events) {
+ pa_time_event *t;
+ GTimeVal now;
+ struct timeval tvnow;
+ pa_usec_t usec;
+
+ t = find_next_time_event(g);
+ g_assert(t);
+
+ g_source_get_current_time(source, &now);
+ tvnow.tv_sec = now.tv_sec;
+ tvnow.tv_usec = now.tv_usec;
+
+ if (pa_timeval_cmp(&t->timeval, &tvnow) <= 0) {
+ *timeout = 0;
+ return TRUE;
+ }
+ usec = pa_timeval_diff(&t->timeval, &tvnow);
+ *timeout = (gint) (usec / 1000);
+ } else
+ *timeout = -1;
+
+ return FALSE;
+}
+static gboolean check_func(GSource *source) {
+ pa_glib_mainloop *g = (pa_glib_mainloop*) source;
+ pa_io_event *e;
+
+ g_assert(g);
+
+ if (g->n_enabled_defer_events)
+ return TRUE;
+ else if (g->n_enabled_time_events) {
+ pa_time_event *t;
+ GTimeVal now;
+ struct timeval tvnow;
+
+ t = find_next_time_event(g);
+ g_assert(t);
+
+ g_source_get_current_time(source, &now);
+ tvnow.tv_sec = now.tv_sec;
+ tvnow.tv_usec = now.tv_usec;
+
+ if (pa_timeval_cmp(&t->timeval, &tvnow) <= 0)
+ return TRUE;
+ }
+
+ for (e = g->io_events; e; e = e->next)
+ if (!e->dead && e->poll_fd.revents != 0)
+ return TRUE;
+
+ return FALSE;
+}
+
+static gboolean dispatch_func(GSource *source, PA_GCC_UNUSED GSourceFunc callback, PA_GCC_UNUSED gpointer userdata) {
+ pa_glib_mainloop *g = (pa_glib_mainloop*) source;
+ pa_io_event *e;
+
+ g_assert(g);
+
+ if (g->n_enabled_defer_events) {
+ pa_defer_event *d;
+
+ for (d = g->defer_events; d; d = d->next) {
+ if (d->dead || !d->enabled)
+ continue;
+
+ break;
+ }
+
+ g_assert(d);
+
+ d->callback(&g->api, d, d->userdata);
+ return TRUE;
+ }
+
+ if (g->n_enabled_time_events) {
+ GTimeVal now;
+ struct timeval tvnow;
+ pa_time_event *t;
+
+ t = find_next_time_event(g);
+ g_assert(t);
+
+ g_source_get_current_time(source, &now);
+ tvnow.tv_sec = now.tv_sec;
+ tvnow.tv_usec = now.tv_usec;
+
+ if (pa_timeval_cmp(&t->timeval, &tvnow) <= 0) {
+
+ /* Disable time event */
+ glib_time_restart(t, NULL);
+
+ t->callback(&g->api, t, &t->timeval, t->userdata);
+ return TRUE;
+ }
+ }
+
+ for (e = g->io_events; e; e = e->next)
+ if (!e->dead && e->poll_fd.revents != 0) {
+ e->callback(&g->api, e, e->poll_fd.fd, map_flags_from_glib(e->poll_fd.revents), e->userdata);
+ e->poll_fd.revents = 0;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static const pa_mainloop_api vtable = {
+ .userdata = NULL,
+
+ .io_new = glib_io_new,
+ .io_enable = glib_io_enable,
+ .io_free = glib_io_free,
+ .io_set_destroy= glib_io_set_destroy,
+
+ .time_new = glib_time_new,
+ .time_restart = glib_time_restart,
+ .time_free = glib_time_free,
+ .time_set_destroy = glib_time_set_destroy,
+
+ .defer_new = glib_defer_new,
+ .defer_enable = glib_defer_enable,
+ .defer_free = glib_defer_free,
+ .defer_set_destroy = glib_defer_set_destroy,
+
+ .quit = glib_quit,
+};
+
+pa_glib_mainloop *pa_glib_mainloop_new(GMainContext *c) {
+ pa_glib_mainloop *g;
+
+ static GSourceFuncs source_funcs = {
+ prepare_func,
+ check_func,
+ dispatch_func,
+ NULL,
+ NULL,
+ NULL
+ };
+
+ g = (pa_glib_mainloop*) g_source_new(&source_funcs, sizeof(pa_glib_mainloop));
+ g_main_context_ref(g->context = c ? c : g_main_context_default());
+
+ g->api = vtable;
+ g->api.userdata = g;
+
+ PA_LLIST_HEAD_INIT(pa_io_event, g->io_events);
+ PA_LLIST_HEAD_INIT(pa_time_event, g->time_events);
+ PA_LLIST_HEAD_INIT(pa_defer_event, g->defer_events);
+
+ g->n_enabled_defer_events = g->n_enabled_time_events = 0;
+ g->io_events_please_scan = g->time_events_please_scan = g->defer_events_please_scan = 0;
+
+ g->cached_next_time_event = NULL;
+
+ g_source_attach(&g->source, g->context);
+ g_source_set_can_recurse(&g->source, FALSE);
+
+ return g;
+}
+
+void pa_glib_mainloop_free(pa_glib_mainloop* g) {
+ g_assert(g);
+
+ cleanup_io_events(g, 1);
+ cleanup_defer_events(g, 1);
+ cleanup_time_events(g, 1);
+
+ g_main_context_unref(g->context);
+ g_source_destroy(&g->source);
+ g_source_unref(&g->source);
+}
+
+pa_mainloop_api* pa_glib_mainloop_get_api(pa_glib_mainloop *g) {
+ g_assert(g);
+
+ return &g->api;
+}
diff --git a/src/pulse/glib-mainloop.h b/src/pulse/glib-mainloop.h
new file mode 100644
index 00000000..a4e06ea0
--- /dev/null
+++ b/src/pulse/glib-mainloop.h
@@ -0,0 +1,65 @@
+#ifndef fooglibmainloophfoo
+#define fooglibmainloophfoo
+
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <glib.h>
+
+#include <pulse/mainloop-api.h>
+#include <pulse/cdecl.h>
+
+/** \page glib-mainloop GLIB Main Loop Bindings
+ *
+ * \section overv_sec Overview
+ *
+ * The GLIB main loop bindings are extremely easy to use. All that is
+ * required is to create a pa_glib_mainloop object using
+ * pa_glib_mainloop_new(). When the main loop abstraction is needed, it is
+ * provided by pa_glib_mainloop_get_api().
+ *
+ */
+
+/** \file
+ * GLIB main loop support */
+
+PA_C_DECL_BEGIN
+
+/** An opaque GLIB main loop object */
+typedef struct pa_glib_mainloop pa_glib_mainloop;
+
+/** Create a new GLIB main loop object for the specified GLIB main
+ * loop context. Takes an argument c for the
+ * GMainContext to use. If c is NULL the default context is used. */
+pa_glib_mainloop *pa_glib_mainloop_new(GMainContext *c);
+
+/** Free the GLIB main loop object */
+void pa_glib_mainloop_free(pa_glib_mainloop* g);
+
+/** Return the abstract main loop API vtable for the GLIB main loop object */
+pa_mainloop_api* pa_glib_mainloop_get_api(pa_glib_mainloop *g);
+
+PA_C_DECL_END
+
+#endif
diff --git a/src/pulse/internal.h b/src/pulse/internal.h
new file mode 100644
index 00000000..873f1363
--- /dev/null
+++ b/src/pulse/internal.h
@@ -0,0 +1,230 @@
+#ifndef foointernalhfoo
+#define foointernalhfoo
+
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <pulse/mainloop-api.h>
+#include <pulse/context.h>
+#include <pulse/stream.h>
+#include <pulse/operation.h>
+#include <pulse/subscribe.h>
+
+#include <pulsecore/socket-client.h>
+#include <pulsecore/pstream.h>
+#include <pulsecore/pdispatch.h>
+#include <pulsecore/dynarray.h>
+#include <pulsecore/llist.h>
+#include <pulsecore/native-common.h>
+#include <pulsecore/strlist.h>
+#include <pulsecore/mcalign.h>
+#include <pulsecore/memblockq.h>
+#include <pulsecore/hashmap.h>
+#include <pulsecore/refcnt.h>
+
+#include "client-conf.h"
+
+#define DEFAULT_TIMEOUT (30)
+
+struct pa_context {
+ PA_REFCNT_DECLARE;
+
+ char *name;
+ pa_mainloop_api* mainloop;
+
+ pa_socket_client *client;
+ pa_pstream *pstream;
+ pa_pdispatch *pdispatch;
+
+ pa_dynarray *record_streams, *playback_streams;
+ PA_LLIST_HEAD(pa_stream, streams);
+ PA_LLIST_HEAD(pa_operation, operations);
+
+ uint32_t version;
+ uint32_t ctag;
+ uint32_t csyncid;
+ uint32_t error;
+ pa_context_state_t state;
+
+ pa_context_notify_cb_t state_callback;
+ void *state_userdata;
+
+ pa_context_subscribe_cb_t subscribe_callback;
+ void *subscribe_userdata;
+
+ pa_mempool *mempool;
+
+ int is_local;
+ int do_autospawn;
+ int autospawn_lock_fd;
+ pa_spawn_api spawn_api;
+
+ pa_strlist *server_list;
+
+ char *server;
+
+ pa_client_conf *conf;
+};
+
+#define PA_MAX_WRITE_INDEX_CORRECTIONS 10
+
+typedef struct pa_index_correction {
+ uint32_t tag;
+ int valid;
+ int64_t value;
+ int absolute, corrupt;
+} pa_index_correction;
+
+struct pa_stream {
+ PA_REFCNT_DECLARE;
+ pa_context *context;
+ pa_mainloop_api *mainloop;
+ PA_LLIST_FIELDS(pa_stream);
+
+ char *name;
+ pa_bool_t manual_buffer_attr;
+ pa_buffer_attr buffer_attr;
+ pa_sample_spec sample_spec;
+ pa_channel_map channel_map;
+ pa_stream_flags_t flags;
+ uint32_t channel;
+ uint32_t syncid;
+ int channel_valid;
+ uint32_t stream_index;
+ pa_stream_direction_t direction;
+ pa_stream_state_t state;
+ pa_bool_t buffer_attr_not_ready, timing_info_not_ready;
+
+ uint32_t requested_bytes;
+
+ uint32_t device_index;
+ char *device_name;
+ pa_bool_t suspended;
+
+ pa_memchunk peek_memchunk;
+ void *peek_data;
+ pa_memblockq *record_memblockq;
+
+ int corked;
+
+ /* Store latest latency info */
+ pa_timing_info timing_info;
+ int timing_info_valid;
+
+ /* Use to make sure that time advances monotonically */
+ pa_usec_t previous_time;
+
+ /* time updates with tags older than these are invalid */
+ uint32_t write_index_not_before;
+ uint32_t read_index_not_before;
+
+ /* Data about individual timing update correctoins */
+ pa_index_correction write_index_corrections[PA_MAX_WRITE_INDEX_CORRECTIONS];
+ int current_write_index_correction;
+
+ /* Latency interpolation stuff */
+ pa_time_event *auto_timing_update_event;
+ int auto_timing_update_requested;
+
+ pa_usec_t cached_time;
+ int cached_time_valid;
+
+ /* Callbacks */
+ pa_stream_notify_cb_t state_callback;
+ void *state_userdata;
+ pa_stream_request_cb_t read_callback;
+ void *read_userdata;
+ pa_stream_request_cb_t write_callback;
+ void *write_userdata;
+ pa_stream_notify_cb_t overflow_callback;
+ void *overflow_userdata;
+ pa_stream_notify_cb_t underflow_callback;
+ void *underflow_userdata;
+ pa_stream_notify_cb_t latency_update_callback;
+ void *latency_update_userdata;
+ pa_stream_notify_cb_t moved_callback;
+ void *moved_userdata;
+ pa_stream_notify_cb_t suspended_callback;
+ void *suspended_userdata;
+};
+
+typedef void (*pa_operation_cb_t)(void);
+
+struct pa_operation {
+ PA_REFCNT_DECLARE;
+
+ pa_context *context;
+ pa_stream *stream;
+
+ PA_LLIST_FIELDS(pa_operation);
+
+ pa_operation_state_t state;
+ void *userdata;
+ pa_operation_cb_t callback;
+
+ void *private; /* some operations might need this */
+};
+
+void pa_command_request(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+void pa_command_stream_killed(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+void pa_command_subscribe_event(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+void pa_command_overflow_or_underflow(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+void pa_command_stream_suspended(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+void pa_command_stream_moved(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+
+pa_operation *pa_operation_new(pa_context *c, pa_stream *s, pa_operation_cb_t callback, void *userdata);
+void pa_operation_done(pa_operation *o);
+
+void pa_create_stream_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+void pa_stream_disconnect_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+void pa_context_simple_ack_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+void pa_stream_simple_ack_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+
+void pa_context_fail(pa_context *c, int error);
+int pa_context_set_error(pa_context *c, int error);
+void pa_context_set_state(pa_context *c, pa_context_state_t st);
+int pa_context_handle_error(pa_context *c, uint32_t command, pa_tagstruct *t);
+pa_operation* pa_context_send_simple_command(pa_context *c, uint32_t command, void (*internal_callback)(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata), void (*cb)(void), void *userdata);
+
+void pa_stream_set_state(pa_stream *s, pa_stream_state_t st);
+
+pa_tagstruct *pa_tagstruct_command(pa_context *c, uint32_t command, uint32_t *tag);
+
+#define PA_CHECK_VALIDITY(context, expression, error) do { \
+ if (!(expression)) \
+ return -pa_context_set_error((context), (error)); \
+} while(0)
+
+
+#define PA_CHECK_VALIDITY_RETURN_ANY(context, expression, error, value) do { \
+ if (!(expression)) { \
+ pa_context_set_error((context), (error)); \
+ return value; \
+ } \
+} while(0)
+
+#define PA_CHECK_VALIDITY_RETURN_NULL(context, expression, error) PA_CHECK_VALIDITY_RETURN_ANY(context, expression, error, NULL)
+
+
+#endif
diff --git a/src/pulse/introspect.c b/src/pulse/introspect.c
new file mode 100644
index 00000000..6610a724
--- /dev/null
+++ b/src/pulse/introspect.c
@@ -0,0 +1,1473 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulse/context.h>
+
+#include <pulsecore/gccmacro.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/pstream-util.h>
+
+#include "internal.h"
+
+#include "introspect.h"
+
+/*** Statistics ***/
+
+static void context_stat_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_operation *o = userdata;
+ pa_stat_info i, *p = &i;
+
+ pa_assert(pd);
+ pa_assert(o);
+ pa_assert(PA_REFCNT_VALUE(o) >= 1);
+
+ memset(&i, 0, sizeof(i));
+
+ if (!o->context)
+ goto finish;
+
+ if (command != PA_COMMAND_REPLY) {
+ if (pa_context_handle_error(o->context, command, t) < 0)
+ goto finish;
+
+ p = NULL;
+ } else if (pa_tagstruct_getu32(t, &i.memblock_total) < 0 ||
+ pa_tagstruct_getu32(t, &i.memblock_total_size) < 0 ||
+ pa_tagstruct_getu32(t, &i.memblock_allocated) < 0 ||
+ pa_tagstruct_getu32(t, &i.memblock_allocated_size) < 0 ||
+ pa_tagstruct_getu32(t, &i.scache_size) < 0) {
+ pa_context_fail(o->context, PA_ERR_PROTOCOL);
+ goto finish;
+ }
+
+ if (o->callback) {
+ pa_stat_info_cb_t cb = (pa_stat_info_cb_t) o->callback;
+ cb(o->context, p, o->userdata);
+ }
+
+finish:
+ pa_operation_done(o);
+ pa_operation_unref(o);
+}
+
+pa_operation* pa_context_stat(pa_context *c, pa_stat_info_cb_t cb, void *userdata) {
+ return pa_context_send_simple_command(c, PA_COMMAND_STAT, context_stat_callback, (pa_operation_cb_t) cb, userdata);
+}
+
+/*** Server Info ***/
+
+static void context_get_server_info_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_operation *o = userdata;
+ pa_server_info i, *p = &i;
+
+ pa_assert(pd);
+ pa_assert(o);
+ pa_assert(PA_REFCNT_VALUE(o) >= 1);
+
+ memset(&i, 0, sizeof(i));
+
+ if (!o->context)
+ goto finish;
+
+ if (command != PA_COMMAND_REPLY) {
+ if (pa_context_handle_error(o->context, command, t) < 0)
+ goto finish;
+
+ p = NULL;
+ } else if (pa_tagstruct_gets(t, &i.server_name) < 0 ||
+ pa_tagstruct_gets(t, &i.server_version) < 0 ||
+ pa_tagstruct_gets(t, &i.user_name) < 0 ||
+ pa_tagstruct_gets(t, &i.host_name) < 0 ||
+ pa_tagstruct_get_sample_spec(t, &i.sample_spec) < 0 ||
+ pa_tagstruct_gets(t, &i.default_sink_name) < 0 ||
+ pa_tagstruct_gets(t, &i.default_source_name) < 0 ||
+ pa_tagstruct_getu32(t, &i.cookie) < 0) {
+
+ pa_context_fail(o->context, PA_ERR_PROTOCOL);
+ goto finish;
+ }
+
+ if (o->callback) {
+ pa_server_info_cb_t cb = (pa_server_info_cb_t) o->callback;
+ cb(o->context, p, o->userdata);
+ }
+
+finish:
+ pa_operation_done(o);
+ pa_operation_unref(o);
+}
+
+pa_operation* pa_context_get_server_info(pa_context *c, pa_server_info_cb_t cb, void *userdata) {
+ return pa_context_send_simple_command(c, PA_COMMAND_GET_SERVER_INFO, context_get_server_info_callback, (pa_operation_cb_t) cb, userdata);
+}
+
+/*** Sink Info ***/
+
+static void context_get_sink_info_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_operation *o = userdata;
+ int eol = 1;
+
+ pa_assert(pd);
+ pa_assert(o);
+ pa_assert(PA_REFCNT_VALUE(o) >= 1);
+
+ if (!o->context)
+ goto finish;
+
+ if (command != PA_COMMAND_REPLY) {
+ if (pa_context_handle_error(o->context, command, t) < 0)
+ goto finish;
+
+ eol = -1;
+ } else {
+ uint32_t flags;
+
+ while (!pa_tagstruct_eof(t)) {
+ pa_sink_info i;
+ memset(&i, 0, sizeof(i));
+
+ if (pa_tagstruct_getu32(t, &i.index) < 0 ||
+ pa_tagstruct_gets(t, &i.name) < 0 ||
+ pa_tagstruct_gets(t, &i.description) < 0 ||
+ pa_tagstruct_get_sample_spec(t, &i.sample_spec) < 0 ||
+ pa_tagstruct_get_channel_map(t, &i.channel_map) < 0 ||
+ pa_tagstruct_getu32(t, &i.owner_module) < 0 ||
+ pa_tagstruct_get_cvolume(t, &i.volume) < 0 ||
+ pa_tagstruct_get_boolean(t, &i.mute) < 0 ||
+ pa_tagstruct_getu32(t, &i.monitor_source) < 0 ||
+ pa_tagstruct_gets(t, &i.monitor_source_name) < 0 ||
+ pa_tagstruct_get_usec(t, &i.latency) < 0 ||
+ pa_tagstruct_gets(t, &i.driver) < 0 ||
+ pa_tagstruct_getu32(t, &flags) < 0) {
+
+ pa_context_fail(o->context, PA_ERR_PROTOCOL);
+ goto finish;
+ }
+
+ i.flags = (pa_sink_flags_t) flags;
+
+ if (o->callback) {
+ pa_sink_info_cb_t cb = (pa_sink_info_cb_t) o->callback;
+ cb(o->context, &i, 0, o->userdata);
+ }
+ }
+ }
+
+ if (o->callback) {
+ pa_sink_info_cb_t cb = (pa_sink_info_cb_t) o->callback;
+ cb(o->context, NULL, eol, o->userdata);
+ }
+
+finish:
+ pa_operation_done(o);
+ pa_operation_unref(o);
+}
+
+pa_operation* pa_context_get_sink_info_list(pa_context *c, pa_sink_info_cb_t cb, void *userdata) {
+ return pa_context_send_simple_command(c, PA_COMMAND_GET_SINK_INFO_LIST, context_get_sink_info_callback, (pa_operation_cb_t) cb, userdata);
+}
+
+pa_operation* pa_context_get_sink_info_by_index(pa_context *c, uint32_t idx, pa_sink_info_cb_t cb, void *userdata) {
+ pa_tagstruct *t;
+ pa_operation *o;
+ uint32_t tag;
+
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+ pa_assert(cb);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+
+ o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+
+ t = pa_tagstruct_command(c, PA_COMMAND_GET_SINK_INFO, &tag);
+ pa_tagstruct_putu32(t, idx);
+ pa_tagstruct_puts(t, NULL);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_get_sink_info_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+}
+
+pa_operation* pa_context_get_sink_info_by_name(pa_context *c, const char *name, pa_sink_info_cb_t cb, void *userdata) {
+ pa_tagstruct *t;
+ pa_operation *o;
+ uint32_t tag;
+
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+ pa_assert(cb);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, !name || *name, PA_ERR_INVALID);
+
+ o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+
+ t = pa_tagstruct_command(c, PA_COMMAND_GET_SINK_INFO, &tag);
+ pa_tagstruct_putu32(t, PA_INVALID_INDEX);
+ pa_tagstruct_puts(t, name);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_get_sink_info_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+}
+
+/*** Source info ***/
+
+static void context_get_source_info_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_operation *o = userdata;
+ int eol = 1;
+
+ pa_assert(pd);
+ pa_assert(o);
+ pa_assert(PA_REFCNT_VALUE(o) >= 1);
+
+ if (!o->context)
+ goto finish;
+
+ if (command != PA_COMMAND_REPLY) {
+ if (pa_context_handle_error(o->context, command, t) < 0)
+ goto finish;
+
+ eol = -1;
+ } else {
+
+ while (!pa_tagstruct_eof(t)) {
+ pa_source_info i;
+ uint32_t flags;
+ memset(&i, 0, sizeof(i));
+
+ if (pa_tagstruct_getu32(t, &i.index) < 0 ||
+ pa_tagstruct_gets(t, &i.name) < 0 ||
+ pa_tagstruct_gets(t, &i.description) < 0 ||
+ pa_tagstruct_get_sample_spec(t, &i.sample_spec) < 0 ||
+ pa_tagstruct_get_channel_map(t, &i.channel_map) < 0 ||
+ pa_tagstruct_getu32(t, &i.owner_module) < 0 ||
+ pa_tagstruct_get_cvolume(t, &i.volume) < 0 ||
+ pa_tagstruct_get_boolean(t, &i.mute) < 0 ||
+ pa_tagstruct_getu32(t, &i.monitor_of_sink) < 0 ||
+ pa_tagstruct_gets(t, &i.monitor_of_sink_name) < 0 ||
+ pa_tagstruct_get_usec(t, &i.latency) < 0 ||
+ pa_tagstruct_gets(t, &i.driver) < 0 ||
+ pa_tagstruct_getu32(t, &flags) < 0) {
+
+ pa_context_fail(o->context, PA_ERR_PROTOCOL);
+ goto finish;
+ }
+
+ i.flags = (pa_source_flags_t) flags;
+
+ if (o->callback) {
+ pa_source_info_cb_t cb = (pa_source_info_cb_t) o->callback;
+ cb(o->context, &i, 0, o->userdata);
+ }
+ }
+ }
+
+ if (o->callback) {
+ pa_source_info_cb_t cb = (pa_source_info_cb_t) o->callback;
+ cb(o->context, NULL, eol, o->userdata);
+ }
+
+finish:
+ pa_operation_done(o);
+ pa_operation_unref(o);
+}
+
+pa_operation* pa_context_get_source_info_list(pa_context *c, pa_source_info_cb_t cb, void *userdata) {
+ return pa_context_send_simple_command(c, PA_COMMAND_GET_SOURCE_INFO_LIST, context_get_source_info_callback, (pa_operation_cb_t) cb, userdata);
+}
+
+pa_operation* pa_context_get_source_info_by_index(pa_context *c, uint32_t idx, pa_source_info_cb_t cb, void *userdata) {
+ pa_tagstruct *t;
+ pa_operation *o;
+ uint32_t tag;
+
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+ pa_assert(cb);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+
+ o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+
+ t = pa_tagstruct_command(c, PA_COMMAND_GET_SOURCE_INFO, &tag);
+ pa_tagstruct_putu32(t, idx);
+ pa_tagstruct_puts(t, NULL);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_get_source_info_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+}
+
+pa_operation* pa_context_get_source_info_by_name(pa_context *c, const char *name, pa_source_info_cb_t cb, void *userdata) {
+ pa_tagstruct *t;
+ pa_operation *o;
+ uint32_t tag;
+
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+ pa_assert(cb);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, !name || *name, PA_ERR_INVALID);
+
+ o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+
+ t = pa_tagstruct_command(c, PA_COMMAND_GET_SOURCE_INFO, &tag);
+ pa_tagstruct_putu32(t, PA_INVALID_INDEX);
+ pa_tagstruct_puts(t, name);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_get_source_info_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+}
+
+/*** Client info ***/
+
+static void context_get_client_info_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_operation *o = userdata;
+ int eol = 1;
+
+ pa_assert(pd);
+ pa_assert(o);
+ pa_assert(PA_REFCNT_VALUE(o) >= 1);
+
+ if (!o->context)
+ goto finish;
+
+ if (command != PA_COMMAND_REPLY) {
+ if (pa_context_handle_error(o->context, command, t) < 0)
+ goto finish;
+
+ eol = -1;
+ } else {
+
+ while (!pa_tagstruct_eof(t)) {
+ pa_client_info i;
+ memset(&i, 0, sizeof(i));
+
+ if (pa_tagstruct_getu32(t, &i.index) < 0 ||
+ pa_tagstruct_gets(t, &i.name) < 0 ||
+ pa_tagstruct_getu32(t, &i.owner_module) < 0 ||
+ pa_tagstruct_gets(t, &i.driver) < 0 ) {
+ pa_context_fail(o->context, PA_ERR_PROTOCOL);
+ goto finish;
+ }
+
+ if (o->callback) {
+ pa_client_info_cb_t cb = (pa_client_info_cb_t) o->callback;
+ cb(o->context, &i, 0, o->userdata);
+ }
+ }
+ }
+
+ if (o->callback) {
+ pa_client_info_cb_t cb = (pa_client_info_cb_t) o->callback;
+ cb(o->context, NULL, eol, o->userdata);
+ }
+
+finish:
+ pa_operation_done(o);
+ pa_operation_unref(o);
+}
+
+pa_operation* pa_context_get_client_info(pa_context *c, uint32_t idx, pa_client_info_cb_t cb, void *userdata) {
+ pa_tagstruct *t;
+ pa_operation *o;
+ uint32_t tag;
+
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+ pa_assert(cb);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID);
+
+ o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+
+ t = pa_tagstruct_command(c, PA_COMMAND_GET_CLIENT_INFO, &tag);
+ pa_tagstruct_putu32(t, idx);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_get_client_info_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+}
+
+pa_operation* pa_context_get_client_info_list(pa_context *c, pa_client_info_cb_t cb, void *userdata) {
+ return pa_context_send_simple_command(c, PA_COMMAND_GET_CLIENT_INFO_LIST, context_get_client_info_callback, (pa_operation_cb_t) cb, userdata);
+}
+
+/*** Module info ***/
+
+static void context_get_module_info_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_operation *o = userdata;
+ int eol = 1;
+
+ pa_assert(pd);
+ pa_assert(o);
+ pa_assert(PA_REFCNT_VALUE(o) >= 1);
+
+ if (!o->context)
+ goto finish;
+
+ if (command != PA_COMMAND_REPLY) {
+ if (pa_context_handle_error(o->context, command, t) < 0)
+ goto finish;
+
+ eol = -1;
+ } else {
+
+ while (!pa_tagstruct_eof(t)) {
+ pa_module_info i;
+ memset(&i, 0, sizeof(i));
+
+ if (pa_tagstruct_getu32(t, &i.index) < 0 ||
+ pa_tagstruct_gets(t, &i.name) < 0 ||
+ pa_tagstruct_gets(t, &i.argument) < 0 ||
+ pa_tagstruct_getu32(t, &i.n_used) < 0 ||
+ pa_tagstruct_get_boolean(t, &i.auto_unload) < 0) {
+ pa_context_fail(o->context, PA_ERR_PROTOCOL);
+ goto finish;
+ }
+
+ if (o->callback) {
+ pa_module_info_cb_t cb = (pa_module_info_cb_t) o->callback;
+ cb(o->context, &i, 0, o->userdata);
+ }
+ }
+ }
+
+ if (o->callback) {
+ pa_module_info_cb_t cb = (pa_module_info_cb_t) o->callback;
+ cb(o->context, NULL, eol, o->userdata);
+ }
+
+finish:
+ pa_operation_done(o);
+ pa_operation_unref(o);
+}
+
+pa_operation* pa_context_get_module_info(pa_context *c, uint32_t idx, pa_module_info_cb_t cb, void *userdata) {
+ pa_tagstruct *t;
+ pa_operation *o;
+ uint32_t tag;
+
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+ pa_assert(cb);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID);
+
+ o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+
+ t = pa_tagstruct_command(c, PA_COMMAND_GET_MODULE_INFO, &tag);
+ pa_tagstruct_putu32(t, idx);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_get_module_info_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+}
+
+pa_operation* pa_context_get_module_info_list(pa_context *c, pa_module_info_cb_t cb, void *userdata) {
+ return pa_context_send_simple_command(c, PA_COMMAND_GET_MODULE_INFO_LIST, context_get_module_info_callback, (pa_operation_cb_t) cb, userdata);
+}
+
+/*** Sink input info ***/
+
+static void context_get_sink_input_info_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_operation *o = userdata;
+ int eol = 1;
+
+ pa_assert(pd);
+ pa_assert(o);
+ pa_assert(PA_REFCNT_VALUE(o) >= 1);
+
+ if (!o->context)
+ goto finish;
+
+ if (command != PA_COMMAND_REPLY) {
+ if (pa_context_handle_error(o->context, command, t) < 0)
+ goto finish;
+
+ eol = -1;
+ } else {
+
+ while (!pa_tagstruct_eof(t)) {
+ pa_sink_input_info i;
+ memset(&i, 0, sizeof(i));
+
+ if (pa_tagstruct_getu32(t, &i.index) < 0 ||
+ pa_tagstruct_gets(t, &i.name) < 0 ||
+ pa_tagstruct_getu32(t, &i.owner_module) < 0 ||
+ pa_tagstruct_getu32(t, &i.client) < 0 ||
+ pa_tagstruct_getu32(t, &i.sink) < 0 ||
+ pa_tagstruct_get_sample_spec(t, &i.sample_spec) < 0 ||
+ pa_tagstruct_get_channel_map(t, &i.channel_map) < 0 ||
+ pa_tagstruct_get_cvolume(t, &i.volume) < 0 ||
+ pa_tagstruct_get_usec(t, &i.buffer_usec) < 0 ||
+ pa_tagstruct_get_usec(t, &i.sink_usec) < 0 ||
+ pa_tagstruct_gets(t, &i.resample_method) < 0 ||
+ pa_tagstruct_gets(t, &i.driver) < 0 ||
+ (o->context->version >= 11 && pa_tagstruct_get_boolean(t, &i.mute) < 0)) {
+
+ pa_context_fail(o->context, PA_ERR_PROTOCOL);
+ goto finish;
+ }
+
+ if (o->callback) {
+ pa_sink_input_info_cb_t cb = (pa_sink_input_info_cb_t) o->callback;
+ cb(o->context, &i, 0, o->userdata);
+ }
+ }
+ }
+
+ if (o->callback) {
+ pa_sink_input_info_cb_t cb = (pa_sink_input_info_cb_t) o->callback;
+ cb(o->context, NULL, eol, o->userdata);
+ }
+
+finish:
+ pa_operation_done(o);
+ pa_operation_unref(o);
+}
+
+pa_operation* pa_context_get_sink_input_info(pa_context *c, uint32_t idx, pa_sink_input_info_cb_t cb, void *userdata) {
+ pa_tagstruct *t;
+ pa_operation *o;
+ uint32_t tag;
+
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+ pa_assert(cb);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID);
+
+ o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+
+ t = pa_tagstruct_command(c, PA_COMMAND_GET_SINK_INPUT_INFO, &tag);
+ pa_tagstruct_putu32(t, idx);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_get_sink_input_info_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+}
+
+pa_operation* pa_context_get_sink_input_info_list(pa_context *c, void (*cb)(pa_context *c, const pa_sink_input_info*i, int is_last, void *userdata), void *userdata) {
+ return pa_context_send_simple_command(c, PA_COMMAND_GET_SINK_INPUT_INFO_LIST, context_get_sink_input_info_callback, (pa_operation_cb_t) cb, userdata);
+}
+
+/*** Source output info ***/
+
+static void context_get_source_output_info_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_operation *o = userdata;
+ int eol = 1;
+
+ pa_assert(pd);
+ pa_assert(o);
+ pa_assert(PA_REFCNT_VALUE(o) >= 1);
+
+ if (!o->context)
+ goto finish;
+
+ if (command != PA_COMMAND_REPLY) {
+ if (pa_context_handle_error(o->context, command, t) < 0)
+ goto finish;
+
+ eol = -1;
+ } else {
+
+ while (!pa_tagstruct_eof(t)) {
+ pa_source_output_info i;
+
+ memset(&i, 0, sizeof(i));
+
+ if (pa_tagstruct_getu32(t, &i.index) < 0 ||
+ pa_tagstruct_gets(t, &i.name) < 0 ||
+ pa_tagstruct_getu32(t, &i.owner_module) < 0 ||
+ pa_tagstruct_getu32(t, &i.client) < 0 ||
+ pa_tagstruct_getu32(t, &i.source) < 0 ||
+ pa_tagstruct_get_sample_spec(t, &i.sample_spec) < 0 ||
+ pa_tagstruct_get_channel_map(t, &i.channel_map) < 0 ||
+ pa_tagstruct_get_usec(t, &i.buffer_usec) < 0 ||
+ pa_tagstruct_get_usec(t, &i.source_usec) < 0 ||
+ pa_tagstruct_gets(t, &i.resample_method) < 0 ||
+ pa_tagstruct_gets(t, &i.driver) < 0) {
+
+ pa_context_fail(o->context, PA_ERR_PROTOCOL);
+ goto finish;
+ }
+
+ if (o->callback) {
+ pa_source_output_info_cb_t cb = (pa_source_output_info_cb_t) o->callback;
+ cb(o->context, &i, 0, o->userdata);
+ }
+ }
+ }
+
+ if (o->callback) {
+ pa_source_output_info_cb_t cb = (pa_source_output_info_cb_t) o->callback;
+ cb(o->context, NULL, eol, o->userdata);
+ }
+
+finish:
+ pa_operation_done(o);
+ pa_operation_unref(o);
+}
+
+pa_operation* pa_context_get_source_output_info(pa_context *c, uint32_t idx, pa_source_output_info_cb_t cb, void *userdata) {
+ pa_tagstruct *t;
+ pa_operation *o;
+ uint32_t tag;
+
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+ pa_assert(cb);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID);
+
+ o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+
+ t = pa_tagstruct_command(c, PA_COMMAND_GET_SOURCE_OUTPUT_INFO, &tag);
+ pa_tagstruct_putu32(t, idx);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_get_source_output_info_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+}
+
+pa_operation* pa_context_get_source_output_info_list(pa_context *c, pa_source_output_info_cb_t cb, void *userdata) {
+ return pa_context_send_simple_command(c, PA_COMMAND_GET_SOURCE_OUTPUT_INFO_LIST, context_get_source_output_info_callback, (pa_operation_cb_t) cb, userdata);
+}
+
+/*** Volume manipulation ***/
+
+pa_operation* pa_context_set_sink_volume_by_index(pa_context *c, uint32_t idx, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata) {
+ pa_operation *o;
+ pa_tagstruct *t;
+ uint32_t tag;
+
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+ pa_assert(volume);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, pa_cvolume_valid(volume), PA_ERR_INVALID);
+
+ o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+
+ t = pa_tagstruct_command(c, PA_COMMAND_SET_SINK_VOLUME, &tag);
+ pa_tagstruct_putu32(t, idx);
+ pa_tagstruct_puts(t, NULL);
+ pa_tagstruct_put_cvolume(t, volume);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+}
+
+pa_operation* pa_context_set_sink_volume_by_name(pa_context *c, const char *name, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata) {
+ pa_operation *o;
+ pa_tagstruct *t;
+ uint32_t tag;
+
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+ pa_assert(name);
+ pa_assert(volume);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, pa_cvolume_valid(volume), PA_ERR_INVALID);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, !name || *name, PA_ERR_INVALID);
+
+ o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+
+ t = pa_tagstruct_command(c, PA_COMMAND_SET_SINK_VOLUME, &tag);
+ pa_tagstruct_putu32(t, PA_INVALID_INDEX);
+ pa_tagstruct_puts(t, name);
+ pa_tagstruct_put_cvolume(t, volume);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+}
+
+pa_operation* pa_context_set_sink_mute_by_index(pa_context *c, uint32_t idx, int mute, pa_context_success_cb_t cb, void *userdata) {
+ pa_operation *o;
+ pa_tagstruct *t;
+ uint32_t tag;
+
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+
+ o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+
+ t = pa_tagstruct_command(c, PA_COMMAND_SET_SINK_MUTE, &tag);
+ pa_tagstruct_putu32(t, idx);
+ pa_tagstruct_puts(t, NULL);
+ pa_tagstruct_put_boolean(t, mute);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+}
+
+pa_operation* pa_context_set_sink_mute_by_name(pa_context *c, const char *name, int mute, pa_context_success_cb_t cb, void *userdata) {
+ pa_operation *o;
+ pa_tagstruct *t;
+ uint32_t tag;
+
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+ pa_assert(name);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, !name || *name, PA_ERR_INVALID);
+
+ o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+
+ t = pa_tagstruct_command(c, PA_COMMAND_SET_SINK_MUTE, &tag);
+ pa_tagstruct_putu32(t, PA_INVALID_INDEX);
+ pa_tagstruct_puts(t, name);
+ pa_tagstruct_put_boolean(t, mute);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+}
+
+pa_operation* pa_context_set_sink_input_volume(pa_context *c, uint32_t idx, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata) {
+ pa_operation *o;
+ pa_tagstruct *t;
+ uint32_t tag;
+
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+ pa_assert(volume);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, pa_cvolume_valid(volume), PA_ERR_INVALID);
+
+ o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+
+ t = pa_tagstruct_command(c, PA_COMMAND_SET_SINK_INPUT_VOLUME, &tag);
+ pa_tagstruct_putu32(t, idx);
+ pa_tagstruct_put_cvolume(t, volume);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+}
+
+pa_operation* pa_context_set_sink_input_mute(pa_context *c, uint32_t idx, int mute, pa_context_success_cb_t cb, void *userdata) {
+ pa_operation *o;
+ pa_tagstruct *t;
+ uint32_t tag;
+
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 11, PA_ERR_NOTSUPPORTED);
+
+ o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+
+ t = pa_tagstruct_command(c, PA_COMMAND_SET_SINK_INPUT_MUTE, &tag);
+ pa_tagstruct_putu32(t, idx);
+ pa_tagstruct_put_boolean(t, mute);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+}
+
+pa_operation* pa_context_set_source_volume_by_index(pa_context *c, uint32_t idx, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata) {
+ pa_operation *o;
+ pa_tagstruct *t;
+ uint32_t tag;
+
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+ pa_assert(volume);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, pa_cvolume_valid(volume), PA_ERR_INVALID);
+
+ o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+
+ t = pa_tagstruct_command(c, PA_COMMAND_SET_SOURCE_VOLUME, &tag);
+ pa_tagstruct_putu32(t, idx);
+ pa_tagstruct_puts(t, NULL);
+ pa_tagstruct_put_cvolume(t, volume);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+}
+
+pa_operation* pa_context_set_source_volume_by_name(pa_context *c, const char *name, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata) {
+ pa_operation *o;
+ pa_tagstruct *t;
+ uint32_t tag;
+
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+ pa_assert(name);
+ pa_assert(volume);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, pa_cvolume_valid(volume), PA_ERR_INVALID);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, !name || *name, PA_ERR_INVALID);
+
+ o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+
+ t = pa_tagstruct_command(c, PA_COMMAND_SET_SOURCE_VOLUME, &tag);
+ pa_tagstruct_putu32(t, PA_INVALID_INDEX);
+ pa_tagstruct_puts(t, name);
+ pa_tagstruct_put_cvolume(t, volume);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+}
+
+pa_operation* pa_context_set_source_mute_by_index(pa_context *c, uint32_t idx, int mute, pa_context_success_cb_t cb, void *userdata) {
+ pa_operation *o;
+ pa_tagstruct *t;
+ uint32_t tag;
+
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+
+ o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+
+ t = pa_tagstruct_command(c, PA_COMMAND_SET_SOURCE_MUTE, &tag);
+ pa_tagstruct_putu32(t, idx);
+ pa_tagstruct_puts(t, NULL);
+ pa_tagstruct_put_boolean(t, mute);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+}
+
+pa_operation* pa_context_set_source_mute_by_name(pa_context *c, const char *name, int mute, pa_context_success_cb_t cb, void *userdata) {
+ pa_operation *o;
+ pa_tagstruct *t;
+ uint32_t tag;
+
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+ pa_assert(name);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, !name || *name, PA_ERR_INVALID);
+
+ o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+
+ t = pa_tagstruct_command(c, PA_COMMAND_SET_SOURCE_MUTE, &tag);
+ pa_tagstruct_putu32(t, PA_INVALID_INDEX);
+ pa_tagstruct_puts(t, name);
+ pa_tagstruct_put_boolean(t, mute);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+}
+
+/** Sample Cache **/
+
+static void context_get_sample_info_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_operation *o = userdata;
+ int eol = 1;
+
+ pa_assert(pd);
+ pa_assert(o);
+ pa_assert(PA_REFCNT_VALUE(o) >= 1);
+
+ if (!o->context)
+ goto finish;
+
+ if (command != PA_COMMAND_REPLY) {
+ if (pa_context_handle_error(o->context, command, t) < 0)
+ goto finish;
+
+ eol = -1;
+ } else {
+
+ while (!pa_tagstruct_eof(t)) {
+ pa_sample_info i;
+
+ memset(&i, 0, sizeof(i));
+
+ if (pa_tagstruct_getu32(t, &i.index) < 0 ||
+ pa_tagstruct_gets(t, &i.name) < 0 ||
+ pa_tagstruct_get_cvolume(t, &i.volume) < 0 ||
+ pa_tagstruct_get_usec(t, &i.duration) < 0 ||
+ pa_tagstruct_get_sample_spec(t, &i.sample_spec) < 0 ||
+ pa_tagstruct_get_channel_map(t, &i.channel_map) < 0 ||
+ pa_tagstruct_getu32(t, &i.bytes) < 0 ||
+ pa_tagstruct_get_boolean(t, &i.lazy) < 0 ||
+ pa_tagstruct_gets(t, &i.filename) < 0) {
+
+ pa_context_fail(o->context, PA_ERR_PROTOCOL);
+ goto finish;
+ }
+
+ if (o->callback) {
+ pa_sample_info_cb_t cb = (pa_sample_info_cb_t) o->callback;
+ cb(o->context, &i, 0, o->userdata);
+ }
+ }
+ }
+
+ if (o->callback) {
+ pa_sample_info_cb_t cb = (pa_sample_info_cb_t) o->callback;
+ cb(o->context, NULL, eol, o->userdata);
+ }
+
+finish:
+ pa_operation_done(o);
+ pa_operation_unref(o);
+}
+
+pa_operation* pa_context_get_sample_info_by_name(pa_context *c, const char *name, pa_sample_info_cb_t cb, void *userdata) {
+ pa_tagstruct *t;
+ pa_operation *o;
+ uint32_t tag;
+
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+ pa_assert(cb);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, name && *name, PA_ERR_INVALID);
+
+ o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+
+ t = pa_tagstruct_command(c, PA_COMMAND_GET_SAMPLE_INFO, &tag);
+ pa_tagstruct_putu32(t, PA_INVALID_INDEX);
+ pa_tagstruct_puts(t, name);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_get_sample_info_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+}
+
+pa_operation* pa_context_get_sample_info_by_index(pa_context *c, uint32_t idx, pa_sample_info_cb_t cb, void *userdata) {
+ pa_tagstruct *t;
+ pa_operation *o;
+ uint32_t tag;
+
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+ pa_assert(cb);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID);
+
+ o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+
+ t = pa_tagstruct_command(c, PA_COMMAND_GET_SAMPLE_INFO, &tag);
+ pa_tagstruct_putu32(t, idx);
+ pa_tagstruct_puts(t, NULL);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_get_sample_info_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+}
+
+pa_operation* pa_context_get_sample_info_list(pa_context *c, pa_sample_info_cb_t cb, void *userdata) {
+ return pa_context_send_simple_command(c, PA_COMMAND_GET_SAMPLE_INFO_LIST, context_get_sample_info_callback, (pa_operation_cb_t) cb, userdata);
+}
+
+static pa_operation* command_kill(pa_context *c, uint32_t command, uint32_t idx, pa_context_success_cb_t cb, void *userdata) {
+ pa_operation *o;
+ pa_tagstruct *t;
+ uint32_t tag;
+
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID);
+
+ o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+
+ t = pa_tagstruct_command(c, command, &tag);
+ pa_tagstruct_putu32(t, idx);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+}
+
+pa_operation* pa_context_kill_client(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void *userdata) {
+ return command_kill(c, PA_COMMAND_KILL_CLIENT, idx, cb, userdata);
+}
+
+pa_operation* pa_context_kill_sink_input(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void *userdata) {
+ return command_kill(c, PA_COMMAND_KILL_SINK_INPUT, idx, cb, userdata);
+}
+
+pa_operation* pa_context_kill_source_output(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void *userdata) {
+ return command_kill(c, PA_COMMAND_KILL_SOURCE_OUTPUT, idx, cb, userdata);
+}
+
+static void context_index_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_operation *o = userdata;
+ uint32_t idx;
+
+ pa_assert(pd);
+ pa_assert(o);
+ pa_assert(PA_REFCNT_VALUE(o) >= 1);
+
+ if (!o->context)
+ goto finish;
+
+ if (command != PA_COMMAND_REPLY) {
+ if (pa_context_handle_error(o->context, command, t) < 0)
+ goto finish;
+
+ idx = PA_INVALID_INDEX;
+ } else if (pa_tagstruct_getu32(t, &idx) ||
+ !pa_tagstruct_eof(t)) {
+ pa_context_fail(o->context, PA_ERR_PROTOCOL);
+ goto finish;
+ }
+
+ if (o->callback) {
+ pa_context_index_cb_t cb = (pa_context_index_cb_t) o->callback;
+ cb(o->context, idx, o->userdata);
+ }
+
+
+finish:
+ pa_operation_done(o);
+ pa_operation_unref(o);
+}
+
+pa_operation* pa_context_load_module(pa_context *c, const char*name, const char *argument, pa_context_index_cb_t cb, void *userdata) {
+ pa_operation *o;
+ pa_tagstruct *t;
+ uint32_t tag;
+
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, name && *name, PA_ERR_INVALID);
+
+ o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+
+ t = pa_tagstruct_command(c, PA_COMMAND_LOAD_MODULE, &tag);
+ pa_tagstruct_puts(t, name);
+ pa_tagstruct_puts(t, argument);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_index_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+}
+
+pa_operation* pa_context_unload_module(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void *userdata) {
+ return command_kill(c, PA_COMMAND_UNLOAD_MODULE, idx, cb, userdata);
+}
+
+/*** Autoload stuff ***/
+
+static void context_get_autoload_info_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_operation *o = userdata;
+ int eol = 1;
+
+ pa_assert(pd);
+ pa_assert(o);
+ pa_assert(PA_REFCNT_VALUE(o) >= 1);
+
+ if (!o->context)
+ goto finish;
+
+ if (command != PA_COMMAND_REPLY) {
+ if (pa_context_handle_error(o->context, command, t) < 0)
+ goto finish;
+
+ eol = -1;
+ } else {
+
+ while (!pa_tagstruct_eof(t)) {
+ pa_autoload_info i;
+
+ memset(&i, 0, sizeof(i));
+
+ if (pa_tagstruct_getu32(t, &i.index) < 0 ||
+ pa_tagstruct_gets(t, &i.name) < 0 ||
+ pa_tagstruct_getu32(t, &i.type) < 0 ||
+ pa_tagstruct_gets(t, &i.module) < 0 ||
+ pa_tagstruct_gets(t, &i.argument) < 0) {
+ pa_context_fail(o->context, PA_ERR_PROTOCOL);
+ goto finish;
+ }
+
+ if (o->callback) {
+ pa_autoload_info_cb_t cb = (pa_autoload_info_cb_t) o->callback;
+ cb(o->context, &i, 0, o->userdata);
+ }
+ }
+ }
+
+ if (o->callback) {
+ pa_autoload_info_cb_t cb = (pa_autoload_info_cb_t) o->callback;
+ cb(o->context, NULL, eol, o->userdata);
+ }
+
+finish:
+ pa_operation_done(o);
+ pa_operation_unref(o);
+}
+
+pa_operation* pa_context_get_autoload_info_by_name(pa_context *c, const char *name, pa_autoload_type_t type, pa_autoload_info_cb_t cb, void *userdata) {
+ pa_tagstruct *t;
+ pa_operation *o;
+ uint32_t tag;
+
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+ pa_assert(cb);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, name && *name, PA_ERR_INVALID);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, type == PA_AUTOLOAD_SINK || type == PA_AUTOLOAD_SOURCE, PA_ERR_INVALID);
+
+ o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+
+ t = pa_tagstruct_command(c, PA_COMMAND_GET_AUTOLOAD_INFO, &tag);
+ pa_tagstruct_puts(t, name);
+ pa_tagstruct_putu32(t, type);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_get_autoload_info_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+}
+
+pa_operation* pa_context_get_autoload_info_by_index(pa_context *c, uint32_t idx, pa_autoload_info_cb_t cb, void *userdata) {
+ pa_tagstruct *t;
+ pa_operation *o;
+ uint32_t tag;
+
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+ pa_assert(cb);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID);
+
+ o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+
+ t = pa_tagstruct_command(c, PA_COMMAND_GET_AUTOLOAD_INFO, &tag);
+ pa_tagstruct_putu32(t, idx);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_get_autoload_info_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+}
+
+pa_operation* pa_context_get_autoload_info_list(pa_context *c, pa_autoload_info_cb_t cb, void *userdata) {
+ return pa_context_send_simple_command(c, PA_COMMAND_GET_AUTOLOAD_INFO_LIST, context_get_autoload_info_callback, (pa_operation_cb_t) cb, userdata);
+}
+
+pa_operation* pa_context_add_autoload(pa_context *c, const char *name, pa_autoload_type_t type, const char *module, const char*argument, pa_context_index_cb_t cb, void* userdata) {
+ pa_operation *o;
+ pa_tagstruct *t;
+ uint32_t tag;
+
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, name && *name, PA_ERR_INVALID);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, type == PA_AUTOLOAD_SINK || type == PA_AUTOLOAD_SOURCE, PA_ERR_INVALID);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, module && *module, PA_ERR_INVALID);
+
+ o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+
+ t = pa_tagstruct_command(c, PA_COMMAND_ADD_AUTOLOAD, &tag);
+ pa_tagstruct_puts(t, name);
+ pa_tagstruct_putu32(t, type);
+ pa_tagstruct_puts(t, module);
+ pa_tagstruct_puts(t, argument);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_index_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+}
+
+pa_operation* pa_context_remove_autoload_by_name(pa_context *c, const char *name, pa_autoload_type_t type, pa_context_success_cb_t cb, void* userdata) {
+ pa_operation *o;
+ pa_tagstruct *t;
+ uint32_t tag;
+
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, name && *name, PA_ERR_INVALID);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, type == PA_AUTOLOAD_SINK || type == PA_AUTOLOAD_SOURCE, PA_ERR_INVALID);
+
+ o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+
+ t = pa_tagstruct_command(c, PA_COMMAND_REMOVE_AUTOLOAD, &tag);
+ pa_tagstruct_puts(t, name);
+ pa_tagstruct_putu32(t, type);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+}
+
+pa_operation* pa_context_remove_autoload_by_index(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void* userdata) {
+ pa_operation *o;
+ pa_tagstruct *t;
+ uint32_t tag;
+
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID);
+
+ o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+
+ t = pa_tagstruct_command(c, PA_COMMAND_REMOVE_AUTOLOAD, &tag);
+ pa_tagstruct_putu32(t, idx);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+}
+
+pa_operation* pa_context_move_sink_input_by_name(pa_context *c, uint32_t idx, char *sink_name, pa_context_success_cb_t cb, void* userdata) {
+ pa_operation *o;
+ pa_tagstruct *t;
+ uint32_t tag;
+
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 10, PA_ERR_NOTSUPPORTED);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, sink_name && *sink_name, PA_ERR_INVALID);
+
+ o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+
+ t = pa_tagstruct_command(c, PA_COMMAND_MOVE_SINK_INPUT, &tag);
+ pa_tagstruct_putu32(t, idx);
+ pa_tagstruct_putu32(t, PA_INVALID_INDEX);
+ pa_tagstruct_puts(t, sink_name);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+}
+
+pa_operation* pa_context_move_sink_input_by_index(pa_context *c, uint32_t idx, uint32_t sink_idx, pa_context_success_cb_t cb, void* userdata) {
+ pa_operation *o;
+ pa_tagstruct *t;
+ uint32_t tag;
+
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 10, PA_ERR_NOTSUPPORTED);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, sink_idx != PA_INVALID_INDEX, PA_ERR_INVALID);
+
+ o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+
+ t = pa_tagstruct_command(c, PA_COMMAND_MOVE_SINK_INPUT, &tag);
+ pa_tagstruct_putu32(t, idx);
+ pa_tagstruct_putu32(t, sink_idx);
+ pa_tagstruct_puts(t, NULL);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+}
+
+pa_operation* pa_context_move_source_output_by_name(pa_context *c, uint32_t idx, char *source_name, pa_context_success_cb_t cb, void* userdata) {
+ pa_operation *o;
+ pa_tagstruct *t;
+ uint32_t tag;
+
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 10, PA_ERR_NOTSUPPORTED);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, source_name && *source_name, PA_ERR_INVALID);
+
+ o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+
+ t = pa_tagstruct_command(c, PA_COMMAND_MOVE_SOURCE_OUTPUT, &tag);
+ pa_tagstruct_putu32(t, idx);
+ pa_tagstruct_putu32(t, PA_INVALID_INDEX);
+ pa_tagstruct_puts(t, source_name);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+}
+
+pa_operation* pa_context_move_source_output_by_index(pa_context *c, uint32_t idx, uint32_t source_idx, pa_context_success_cb_t cb, void* userdata) {
+ pa_operation *o;
+ pa_tagstruct *t;
+ uint32_t tag;
+
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 10, PA_ERR_NOTSUPPORTED);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, source_idx != PA_INVALID_INDEX, PA_ERR_INVALID);
+
+ o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+
+ t = pa_tagstruct_command(c, PA_COMMAND_MOVE_SOURCE_OUTPUT, &tag);
+ pa_tagstruct_putu32(t, idx);
+ pa_tagstruct_putu32(t, source_idx);
+ pa_tagstruct_puts(t, NULL);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+}
+
+pa_operation* pa_context_suspend_sink_by_name(pa_context *c, char *sink_name, int suspend, pa_context_success_cb_t cb, void* userdata) {
+ pa_operation *o;
+ pa_tagstruct *t;
+ uint32_t tag;
+
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 11, PA_ERR_NOTSUPPORTED);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, !sink_name || *sink_name, PA_ERR_INVALID);
+
+ o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+
+ t = pa_tagstruct_command(c, PA_COMMAND_SUSPEND_SINK, &tag);
+ pa_tagstruct_putu32(t, PA_INVALID_INDEX);
+ pa_tagstruct_puts(t, sink_name);
+ pa_tagstruct_put_boolean(t, suspend);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+}
+
+pa_operation* pa_context_suspend_sink_by_index(pa_context *c, uint32_t idx, int suspend, pa_context_success_cb_t cb, void* userdata) {
+ pa_operation *o;
+ pa_tagstruct *t;
+ uint32_t tag;
+
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 11, PA_ERR_NOTSUPPORTED);
+
+ o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+
+ t = pa_tagstruct_command(c, PA_COMMAND_SUSPEND_SINK, &tag);
+ pa_tagstruct_putu32(t, idx);
+ pa_tagstruct_puts(t, idx == PA_INVALID_INDEX ? "" : NULL);
+ pa_tagstruct_put_boolean(t, suspend);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+}
+
+pa_operation* pa_context_suspend_source_by_name(pa_context *c, char *source_name, int suspend, pa_context_success_cb_t cb, void* userdata) {
+ pa_operation *o;
+ pa_tagstruct *t;
+ uint32_t tag;
+
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 11, PA_ERR_NOTSUPPORTED);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, !source_name || *source_name, PA_ERR_INVALID);
+
+ o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+
+ t = pa_tagstruct_command(c, PA_COMMAND_SUSPEND_SOURCE, &tag);
+ pa_tagstruct_putu32(t, PA_INVALID_INDEX);
+ pa_tagstruct_puts(t, source_name);
+ pa_tagstruct_put_boolean(t, suspend);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+}
+
+pa_operation* pa_context_suspend_source_by_index(pa_context *c, uint32_t idx, int suspend, pa_context_success_cb_t cb, void* userdata) {
+ pa_operation *o;
+ pa_tagstruct *t;
+ uint32_t tag;
+
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 11, PA_ERR_NOTSUPPORTED);
+
+ o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+
+ t = pa_tagstruct_command(c, PA_COMMAND_SUSPEND_SOURCE, &tag);
+ pa_tagstruct_putu32(t, idx);
+ pa_tagstruct_puts(t, idx == PA_INVALID_INDEX ? "" : NULL);
+ pa_tagstruct_put_boolean(t, suspend);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+}
diff --git a/src/pulse/introspect.h b/src/pulse/introspect.h
new file mode 100644
index 00000000..c148ee5e
--- /dev/null
+++ b/src/pulse/introspect.h
@@ -0,0 +1,520 @@
+#ifndef foointrospecthfoo
+#define foointrospecthfoo
+
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <inttypes.h>
+
+#include <pulse/operation.h>
+#include <pulse/context.h>
+#include <pulse/cdecl.h>
+#include <pulse/channelmap.h>
+#include <pulse/volume.h>
+
+/** \page introspect Server Query and Control
+ *
+ * \section overv_sec Overview
+ *
+ * Sometimes it is necessary to query and modify global settings in the
+ * server. For this, PulseAudio has the introspection API. It can list sinks,
+ * sources, samples and other aspects of the server. It can also modify the
+ * attributes of the server that will affect operations on a global level,
+ * and not just the application's context.
+ *
+ * \section query_sec Querying
+ *
+ * All querying is done through callbacks. This design is necessary to
+ * maintain an asynchronous design. The client will request the information
+ * and some time later, the server will respond with the desired data.
+ *
+ * Some objects can have multiple entries at the server. When requesting all
+ * of these at once, the callback will be called multiple times, once for
+ * each object. When the list has been exhausted, the callback will be called
+ * without an information structure and the eol parameter set to a non-zero
+ * value.
+ *
+ * Note that even if a single object is requested, and not the entire list,
+ * the terminating call will still be made.
+ *
+ * If an error occurs, the callback will be called without and information
+ * structure and eol set to zero.
+ *
+ * Data members in the information structures are only valid during the
+ * duration of the callback. If they are required after the callback is
+ * finished, a deep copy must be performed.
+ *
+ * \subsection server_subsec Server Information
+ *
+ * The server can be queried about its name, the environment it's running on
+ * and the currently active global defaults. Calling
+ * pa_context_get_server_info() will get access to a pa_server_info structure
+ * containing all of these.
+ *
+ * \subsection memstat_subsec Memory Usage
+ *
+ * Statistics about memory usage can be fetched using pa_context_stat(),
+ * giving a pa_stat_info structure.
+ *
+ * \subsection sinksrc_subsec Sinks and Sources
+ *
+ * The server can have an arbitrary number of sinks and sources. Each sink
+ * and source have both an index and a name associated with it. As such
+ * there are three ways to get access to them:
+ *
+ * \li By index - pa_context_get_sink_info_by_index() /
+ * pa_context_get_source_info_by_index()
+ * \li By name - pa_context_get_sink_info_by_name() /
+ * pa_context_get_source_info_by_name()
+ * \li All - pa_context_get_sink_info_list() /
+ * pa_context_get_source_info_list()
+ *
+ * All three method use the same callback and will provide a pa_sink_info or
+ * pa_source_info structure.
+ *
+ * \subsection siso_subsec Sink Inputs and Source Outputs
+ *
+ * Sink inputs and source outputs are the representations of the client ends
+ * of streams inside the server. I.e. they connect a client stream to one of
+ * the global sinks or sources.
+ *
+ * Sink inputs and source outputs only have an index to identify them. As
+ * such, there are only two ways to get information about them:
+ *
+ * \li By index - pa_context_get_sink_input_info() /
+ * pa_context_get_source_output_info()
+ * \li All - pa_context_get_sink_input_info_list() /
+ * pa_context_get_source_output_info_list()
+ *
+ * The structure returned is the pa_sink_input_info or pa_source_output_info
+ * structure.
+ *
+ * \subsection samples_subsec Samples
+ *
+ * The list of cached samples can be retrieved from the server. Three methods
+ * exist for querying the sample cache list:
+ *
+ * \li By index - pa_context_get_sample_info_by_index()
+ * \li By name - pa_context_get_sample_info_by_name()
+ * \li All - pa_context_get_sample_info_list()
+ *
+ * Note that this only retrieves information about the sample, not the sample
+ * data itself.
+ *
+ * \subsection module_subsec Driver Modules
+ *
+ * PulseAudio driver modules are identified by index and are retrieved using either
+ * pa_context_get_module_info() or pa_context_get_module_info_list(). The
+ * information structure is called pa_module_info.
+ *
+ * \subsection autoload_subsec Autoload Entries
+ *
+ * Modules can be autoloaded as a result of a client requesting a certain
+ * sink or source. This mapping between sink/source names and modules can be
+ * queried from the server:
+ *
+ * \li By index - pa_context_get_autoload_info_by_index()
+ * \li By sink/source name - pa_context_get_autoload_info_by_name()
+ * \li All - pa_context_get_autoload_info_list()
+ *
+ * \subsection client_subsec Clients
+ *
+ * PulseAudio clients are also identified by index and are retrieved using
+ * either pa_context_get_client_info() or pa_context_get_client_info_list().
+ * The information structure is called pa_client_info.
+ *
+ * \section ctrl_sec Control
+ *
+ * Some parts of the server are only possible to read, but most can also be
+ * modified in different ways. Note that these changes will affect all
+ * connected clients and not just the one issuing the request.
+ *
+ * \subsection sinksrc_subsec Sinks and Sources
+ *
+ * The most common change one would want to do to sinks and sources is to
+ * modify the volume of the audio. Identical to how sinks and sources can
+ * be queried, there are two ways of identifying them:
+ *
+ * \li By index - pa_context_set_sink_volume_by_index() /
+ * pa_context_set_source_volume_by_index()
+ * \li By name - pa_context_set_sink_volume_by_name() /
+ * pa_context_set_source_volume_by_name()
+ *
+ * It is also possible to mute a sink or source:
+ *
+ * \li By index - pa_context_set_sink_mute_by_index() /
+ * pa_context_set_source_mute_by_index()
+ * \li By name - pa_context_set_sink_mute_by_name() /
+ * pa_context_set_source_mute_by_name()
+ *
+ * \subsection siso_subsec Sink Inputs and Source Outputs
+ *
+ * If an application desires to modify the volume of just a single stream
+ * (commonly one of its own streams), this can be done by setting the volume
+ * of its associated sink input, using pa_context_set_sink_input_volume().
+ *
+ * There is no support for modifying the volume of source outputs.
+ *
+ * It is also possible to remove sink inputs and source outputs, terminating
+ * the streams associated with them:
+ *
+ * \li Sink input - pa_context_kill_sink_input()
+ * \li Source output - pa_context_kill_source_output()
+ *
+ * \subsection module_subsec Modules
+ *
+ * Server modules can be remotely loaded and unloaded using
+ * pa_context_load_module() and pa_context_unload_module().
+ *
+ * \subsection autoload_subsec Autoload Entries
+ *
+ * New module autoloading rules can be added, and existing can be removed
+ * using pa_context_add_autoload() and pa_context_remove_autoload_by_index()
+ * / pa_context_remove_autoload_by_name().
+ *
+ * \subsection client_subsec Clients
+ *
+ * The only operation supported on clients, is the possibility of kicking
+ * them off the server using pa_context_kill_client().
+ */
+
+/** \file
+ *
+ * Routines for daemon introspection.
+ */
+
+PA_C_DECL_BEGIN
+
+/** Stores information about sinks */
+typedef struct pa_sink_info {
+ const char *name; /**< Name of the sink */
+ uint32_t index; /**< Index of the sink */
+ const char *description; /**< Description of this sink */
+ pa_sample_spec sample_spec; /**< Sample spec of this sink */
+ pa_channel_map channel_map; /**< Channel map \since 0.8 */
+ uint32_t owner_module; /**< Index of the owning module of this sink, or PA_INVALID_INDEX */
+ pa_cvolume volume; /**< Volume of the sink */
+ int mute; /**< Mute switch of the sink \since 0.8 */
+ uint32_t monitor_source; /**< Index of the monitor source connected to this sink */
+ const char *monitor_source_name; /**< The name of the monitor source */
+ pa_usec_t latency; /**< Length of filled playback buffer of this sink */
+ const char *driver; /**< Driver name. \since 0.8 */
+ pa_sink_flags_t flags; /**< Flags \since 0.8 */
+} pa_sink_info;
+
+/** Callback prototype for pa_context_get_sink_info_by_name() and friends */
+typedef void (*pa_sink_info_cb_t)(pa_context *c, const pa_sink_info *i, int eol, void *userdata);
+
+/** Get information about a sink by its name */
+pa_operation* pa_context_get_sink_info_by_name(pa_context *c, const char *name, pa_sink_info_cb_t cb, void *userdata);
+
+/** Get information about a sink by its index */
+pa_operation* pa_context_get_sink_info_by_index(pa_context *c, uint32_t id, pa_sink_info_cb_t cb, void *userdata);
+
+/** Get the complete sink list */
+pa_operation* pa_context_get_sink_info_list(pa_context *c, pa_sink_info_cb_t cb, void *userdata);
+
+/** Stores information about sources */
+typedef struct pa_source_info {
+ const char *name ; /**< Name of the source */
+ uint32_t index; /**< Index of the source */
+ const char *description; /**< Description of this source */
+ pa_sample_spec sample_spec; /**< Sample spec of this source */
+ pa_channel_map channel_map; /**< Channel map \since 0.8 */
+ uint32_t owner_module; /**< Owning module index, or PA_INVALID_INDEX */
+ pa_cvolume volume; /**< Volume of the source \since 0.8 */
+ int mute; /**< Mute switch of the sink \since 0.8 */
+ uint32_t monitor_of_sink; /**< If this is a monitor source the index of the owning sink, otherwise PA_INVALID_INDEX */
+ const char *monitor_of_sink_name; /**< Name of the owning sink, or PA_INVALID_INDEX */
+ pa_usec_t latency; /**< Length of filled record buffer of this source. \since 0.5 */
+ const char *driver; /**< Driver name \since 0.8 */
+ pa_source_flags_t flags; /**< Flags \since 0.8 */
+} pa_source_info;
+
+/** Callback prototype for pa_context_get_source_info_by_name() and friends */
+typedef void (*pa_source_info_cb_t)(pa_context *c, const pa_source_info *i, int eol, void *userdata);
+
+/** Get information about a source by its name */
+pa_operation* pa_context_get_source_info_by_name(pa_context *c, const char *name, pa_source_info_cb_t cb, void *userdata);
+
+/** Get information about a source by its index */
+pa_operation* pa_context_get_source_info_by_index(pa_context *c, uint32_t id, pa_source_info_cb_t cb, void *userdata);
+
+/** Get the complete source list */
+pa_operation* pa_context_get_source_info_list(pa_context *c, pa_source_info_cb_t cb, void *userdata);
+
+/** Server information */
+typedef struct pa_server_info {
+ const char *user_name; /**< User name of the daemon process */
+ const char *host_name; /**< Host name the daemon is running on */
+ const char *server_version; /**< Version string of the daemon */
+ const char *server_name; /**< Server package name (usually "pulseaudio") */
+ pa_sample_spec sample_spec; /**< Default sample specification */
+ const char *default_sink_name; /**< Name of default sink. \since 0.4 */
+ const char *default_source_name; /**< Name of default sink. \since 0.4*/
+ uint32_t cookie; /**< A random cookie for identifying this instance of PulseAudio. \since 0.8 */
+} pa_server_info;
+
+/** Callback prototype for pa_context_get_server_info() */
+typedef void (*pa_server_info_cb_t) (pa_context *c, const pa_server_info*i, void *userdata);
+
+/** Get some information about the server */
+pa_operation* pa_context_get_server_info(pa_context *c, pa_server_info_cb_t cb, void *userdata);
+
+/** Stores information about modules */
+typedef struct pa_module_info {
+ uint32_t index; /**< Index of the module */
+ const char*name, /**< Name of the module */
+ *argument; /**< Argument string of the module */
+ uint32_t n_used; /**< Usage counter or PA_INVALID_INDEX */
+ int auto_unload; /**< Non-zero if this is an autoloaded module */
+} pa_module_info;
+
+/** Callback prototype for pa_context_get_module_info() and firends*/
+typedef void (*pa_module_info_cb_t) (pa_context *c, const pa_module_info*i, int eol, void *userdata);
+
+/** Get some information about a module by its index */
+pa_operation* pa_context_get_module_info(pa_context *c, uint32_t idx, pa_module_info_cb_t cb, void *userdata);
+
+/** Get the complete list of currently loaded modules */
+pa_operation* pa_context_get_module_info_list(pa_context *c, pa_module_info_cb_t cb, void *userdata);
+
+/** Stores information about clients */
+typedef struct pa_client_info {
+ uint32_t index; /**< Index of this client */
+ const char *name; /**< Name of this client */
+ uint32_t owner_module; /**< Index of the owning module, or PA_INVALID_INDEX */
+ const char *driver; /**< Driver name \since 0.8 */
+} pa_client_info;
+
+/** Callback prototype for pa_context_get_client_info() and firends*/
+typedef void (*pa_client_info_cb_t) (pa_context *c, const pa_client_info*i, int eol, void *userdata);
+
+/** Get information about a client by its index */
+pa_operation* pa_context_get_client_info(pa_context *c, uint32_t idx, pa_client_info_cb_t cb, void *userdata);
+
+/** Get the complete client list */
+pa_operation* pa_context_get_client_info_list(pa_context *c, pa_client_info_cb_t cb, void *userdata);
+
+/** Stores information about sink inputs */
+typedef struct pa_sink_input_info {
+ uint32_t index; /**< Index of the sink input */
+ const char *name; /**< Name of the sink input */
+ uint32_t owner_module; /**< Index of the module this sink input belongs to, or PA_INVALID_INDEX when it does not belong to any module */
+ uint32_t client; /**< Index of the client this sink input belongs to, or PA_INVALID_INDEX when it does not belong to any client */
+ uint32_t sink; /**< Index of the connected sink */
+ pa_sample_spec sample_spec; /**< The sample specification of the sink input */
+ pa_channel_map channel_map; /**< Channel map */
+ pa_cvolume volume; /**< The volume of this sink input */
+ pa_usec_t buffer_usec; /**< Latency due to buffering in sink input, see pa_latency_info for details */
+ pa_usec_t sink_usec; /**< Latency of the sink device, see pa_latency_info for details */
+ const char *resample_method; /**< Thre resampling method used by this sink input. \since 0.7 */
+ const char *driver; /**< Driver name \since 0.8 */
+ int mute; /**< Stream muted \since 0.9.7 */
+} pa_sink_input_info;
+
+/** Callback prototype for pa_context_get_sink_input_info() and firends*/
+typedef void (*pa_sink_input_info_cb_t) (pa_context *c, const pa_sink_input_info *i, int eol, void *userdata);
+
+/** Get some information about a sink input by its index */
+pa_operation* pa_context_get_sink_input_info(pa_context *c, uint32_t idx, pa_sink_input_info_cb_t cb, void *userdata);
+
+/** Get the complete sink input list */
+pa_operation* pa_context_get_sink_input_info_list(pa_context *c, pa_sink_input_info_cb_t cb, void *userdata);
+
+/** Stores information about source outputs */
+typedef struct pa_source_output_info {
+ uint32_t index; /**< Index of the sink input */
+ const char *name; /**< Name of the sink input */
+ uint32_t owner_module; /**< Index of the module this sink input belongs to, or PA_INVALID_INDEX when it does not belong to any module */
+ uint32_t client; /**< Index of the client this sink input belongs to, or PA_INVALID_INDEX when it does not belong to any client */
+ uint32_t source; /**< Index of the connected source */
+ pa_sample_spec sample_spec; /**< The sample specification of the source output */
+ pa_channel_map channel_map; /**< Channel map */
+ pa_usec_t buffer_usec; /**< Latency due to buffering in the source output, see pa_latency_info for details. \since 0.5 */
+ pa_usec_t source_usec; /**< Latency of the source device, see pa_latency_info for details. \since 0.5 */
+ const char *resample_method; /**< Thre resampling method used by this source output. \since 0.7 */
+ const char *driver; /**< Driver name \since 0.8 */
+} pa_source_output_info;
+
+/** Callback prototype for pa_context_get_source_output_info() and firends*/
+typedef void (*pa_source_output_info_cb_t) (pa_context *c, const pa_source_output_info *i, int eol, void *userdata);
+
+/** Get information about a source output by its index */
+pa_operation* pa_context_get_source_output_info(pa_context *c, uint32_t idx, pa_source_output_info_cb_t cb, void *userdata);
+
+/** Get the complete list of source outputs */
+pa_operation* pa_context_get_source_output_info_list(pa_context *c, pa_source_output_info_cb_t cb, void *userdata);
+
+/** Set the volume of a sink device specified by its index */
+pa_operation* pa_context_set_sink_volume_by_index(pa_context *c, uint32_t idx, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata);
+
+/** Set the volume of a sink device specified by its name */
+pa_operation* pa_context_set_sink_volume_by_name(pa_context *c, const char *name, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata);
+
+/** Set the mute switch of a sink device specified by its index \since 0.8 */
+pa_operation* pa_context_set_sink_mute_by_index(pa_context *c, uint32_t idx, int mute, pa_context_success_cb_t cb, void *userdata);
+
+/** Set the mute switch of a sink device specified by its name \since 0.8 */
+pa_operation* pa_context_set_sink_mute_by_name(pa_context *c, const char *name, int mute, pa_context_success_cb_t cb, void *userdata);
+
+/** Set the volume of a sink input stream */
+pa_operation* pa_context_set_sink_input_volume(pa_context *c, uint32_t idx, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata);
+
+/** Set the mute switch of a sink input stream \since 0.9.7 */
+pa_operation* pa_context_set_sink_input_mute(pa_context *c, uint32_t idx, int mute, pa_context_success_cb_t cb, void *userdata);
+
+/** Set the volume of a source device specified by its index \since 0.8 */
+pa_operation* pa_context_set_source_volume_by_index(pa_context *c, uint32_t idx, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata);
+
+/** Set the volume of a source device specified by its name \since 0.8 */
+pa_operation* pa_context_set_source_volume_by_name(pa_context *c, const char *name, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata);
+
+/** Set the mute switch of a source device specified by its index \since 0.8 */
+pa_operation* pa_context_set_source_mute_by_index(pa_context *c, uint32_t idx, int mute, pa_context_success_cb_t cb, void *userdata);
+
+/** Set the mute switch of a source device specified by its name \since 0.8 */
+pa_operation* pa_context_set_source_mute_by_name(pa_context *c, const char *name, int mute, pa_context_success_cb_t cb, void *userdata);
+
+/** Memory block statistics */
+typedef struct pa_stat_info {
+ uint32_t memblock_total; /**< Currently allocated memory blocks */
+ uint32_t memblock_total_size; /**< Currentl total size of allocated memory blocks */
+ uint32_t memblock_allocated; /**< Allocated memory blocks during the whole lifetime of the daemon */
+ uint32_t memblock_allocated_size; /**< Total size of all memory blocks allocated during the whole lifetime of the daemon */
+ uint32_t scache_size; /**< Total size of all sample cache entries. \since 0.4 */
+} pa_stat_info;
+
+/** Callback prototype for pa_context_stat() */
+typedef void (*pa_stat_info_cb_t) (pa_context *c, const pa_stat_info *i, void *userdata);
+
+/** Get daemon memory block statistics */
+pa_operation* pa_context_stat(pa_context *c, pa_stat_info_cb_t cb, void *userdata);
+
+/** Stores information about sample cache entries */
+typedef struct pa_sample_info {
+ uint32_t index; /**< Index of this entry */
+ const char *name; /**< Name of this entry */
+ pa_cvolume volume; /**< Default volume of this entry */
+ pa_sample_spec sample_spec; /**< Sample specification of the sample */
+ pa_channel_map channel_map; /**< The channel map */
+ pa_usec_t duration; /**< Duration of this entry */
+ uint32_t bytes; /**< Length of this sample in bytes. \since 0.4 */
+ int lazy; /**< Non-zero when this is a lazy cache entry. \since 0.5 */
+ const char *filename; /**< In case this is a lazy cache entry, the filename for the sound file to be loaded on demand. \since 0.5 */
+} pa_sample_info;
+
+/** Callback prototype for pa_context_get_sample_info_by_name() and firends */
+typedef void (*pa_sample_info_cb_t)(pa_context *c, const pa_sample_info *i, int eol, void *userdata);
+
+/** Get information about a sample by its name */
+pa_operation* pa_context_get_sample_info_by_name(pa_context *c, const char *name, pa_sample_info_cb_t cb, void *userdata);
+
+/** Get information about a sample by its index */
+pa_operation* pa_context_get_sample_info_by_index(pa_context *c, uint32_t idx, pa_sample_info_cb_t cb, void *userdata);
+
+/** Get the complete list of samples stored in the daemon. */
+pa_operation* pa_context_get_sample_info_list(pa_context *c, pa_sample_info_cb_t cb, void *userdata);
+
+/** Kill a client. \since 0.5 */
+pa_operation* pa_context_kill_client(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void *userdata);
+
+/** Kill a sink input. \since 0.5 */
+pa_operation* pa_context_kill_sink_input(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void *userdata);
+
+/** Kill a source output. \since 0.5 */
+pa_operation* pa_context_kill_source_output(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void *userdata);
+
+/** Callback prototype for pa_context_load_module() and pa_context_add_autoload() */
+typedef void (*pa_context_index_cb_t)(pa_context *c, uint32_t idx, void *userdata);
+
+/** Load a module. \since 0.5 */
+pa_operation* pa_context_load_module(pa_context *c, const char*name, const char *argument, pa_context_index_cb_t cb, void *userdata);
+
+/** Unload a module. \since 0.5 */
+pa_operation* pa_context_unload_module(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void *userdata);
+
+/** Type of an autoload entry. \since 0.5 */
+typedef enum pa_autoload_type {
+ PA_AUTOLOAD_SINK = 0,
+ PA_AUTOLOAD_SOURCE = 1
+} pa_autoload_type_t;
+
+/** Stores information about autoload entries. \since 0.5 */
+typedef struct pa_autoload_info {
+ uint32_t index; /**< Index of this autoload entry */
+ const char *name; /**< Name of the sink or source */
+ pa_autoload_type_t type; /**< Type of the autoload entry */
+ const char *module; /**< Module name to load */
+ const char *argument; /**< Argument string for module */
+} pa_autoload_info;
+
+/** Callback prototype for pa_context_get_autoload_info_by_name() and firends */
+typedef void (*pa_autoload_info_cb_t)(pa_context *c, const pa_autoload_info *i, int eol, void *userdata);
+
+/** Get info about a specific autoload entry. \since 0.6 */
+pa_operation* pa_context_get_autoload_info_by_name(pa_context *c, const char *name, pa_autoload_type_t type, pa_autoload_info_cb_t cb, void *userdata);
+
+/** Get info about a specific autoload entry. \since 0.6 */
+pa_operation* pa_context_get_autoload_info_by_index(pa_context *c, uint32_t idx, pa_autoload_info_cb_t cb, void *userdata);
+
+/** Get the complete list of autoload entries. \since 0.5 */
+pa_operation* pa_context_get_autoload_info_list(pa_context *c, pa_autoload_info_cb_t cb, void *userdata);
+
+/** Add a new autoload entry. \since 0.5 */
+pa_operation* pa_context_add_autoload(pa_context *c, const char *name, pa_autoload_type_t type, const char *module, const char*argument, pa_context_index_cb_t, void* userdata);
+
+/** Remove an autoload entry. \since 0.6 */
+pa_operation* pa_context_remove_autoload_by_name(pa_context *c, const char *name, pa_autoload_type_t type, pa_context_success_cb_t cb, void* userdata);
+
+/** Remove an autoload entry. \since 0.6 */
+pa_operation* pa_context_remove_autoload_by_index(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void* userdata);
+
+/** Move the specified sink input to a different sink. \since 0.9.5 */
+pa_operation* pa_context_move_sink_input_by_name(pa_context *c, uint32_t idx, char *sink_name, pa_context_success_cb_t cb, void* userdata);
+
+/** Move the specified sink input to a different sink. \since 0.9.5 */
+pa_operation* pa_context_move_sink_input_by_index(pa_context *c, uint32_t idx, uint32_t sink_idx, pa_context_success_cb_t cb, void* userdata);
+
+/** Move the specified source output to a different source. \since 0.9.5 */
+pa_operation* pa_context_move_source_output_by_name(pa_context *c, uint32_t idx, char *source_name, pa_context_success_cb_t cb, void* userdata);
+
+/** Move the specified source output to a different source. \since 0.9.5 */
+pa_operation* pa_context_move_source_output_by_index(pa_context *c, uint32_t idx, uint32_t source_idx, pa_context_success_cb_t cb, void* userdata);
+
+/** Suspend/Resume a sink. \since 0.9.7 */
+pa_operation* pa_context_suspend_sink_by_name(pa_context *c, char *sink_name, int suspend, pa_context_success_cb_t cb, void* userdata);
+
+/** Suspend/Resume a sink. If idx is PA_INVALID_INDEX all sinks will be suspended. \since 0.9.7 */
+pa_operation* pa_context_suspend_sink_by_index(pa_context *c, uint32_t idx, int suspend, pa_context_success_cb_t cb, void* userdata);
+
+/** Suspend/Resume a source. \since 0.9.7 */
+pa_operation* pa_context_suspend_source_by_name(pa_context *c, char *source_name, int suspend, pa_context_success_cb_t cb, void* userdata);
+
+/** Suspend/Resume a source. If idx is PA_INVALID_INDEX all sources will be suspended. \since 0.9.7 */
+pa_operation* pa_context_suspend_source_by_index(pa_context *c, uint32_t idx, int suspend, pa_context_success_cb_t cb, void* userdata);
+
+PA_C_DECL_END
+
+#endif
diff --git a/src/pulse/mainloop-api.c b/src/pulse/mainloop-api.c
new file mode 100644
index 00000000..b2ed3434
--- /dev/null
+++ b/src/pulse/mainloop-api.c
@@ -0,0 +1,78 @@
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/gccmacro.h>
+#include <pulsecore/macro.h>
+
+#include "mainloop-api.h"
+
+struct once_info {
+ void (*callback)(pa_mainloop_api*m, void *userdata);
+ void *userdata;
+};
+
+static void once_callback(pa_mainloop_api *m, pa_defer_event *e, void *userdata) {
+ struct once_info *i = userdata;
+
+ pa_assert(m);
+ pa_assert(i);
+
+ pa_assert(i->callback);
+ i->callback(m, i->userdata);
+
+ pa_assert(m->defer_free);
+ m->defer_free(e);
+}
+
+static void free_callback(pa_mainloop_api *m, PA_GCC_UNUSED pa_defer_event *e, void *userdata) {
+ struct once_info *i = userdata;
+
+ pa_assert(m);
+ pa_assert(i);
+ pa_xfree(i);
+}
+
+void pa_mainloop_api_once(pa_mainloop_api* m, void (*callback)(pa_mainloop_api *m, void *userdata), void *userdata) {
+ struct once_info *i;
+ pa_defer_event *e;
+
+ pa_assert(m);
+ pa_assert(callback);
+
+ i = pa_xnew(struct once_info, 1);
+ i->callback = callback;
+ i->userdata = userdata;
+
+ pa_assert(m->defer_new);
+ pa_assert_se(e = m->defer_new(m, once_callback, i));
+ m->defer_set_destroy(e, free_callback);
+}
+
diff --git a/src/pulse/mainloop-api.h b/src/pulse/mainloop-api.h
new file mode 100644
index 00000000..985806e6
--- /dev/null
+++ b/src/pulse/mainloop-api.h
@@ -0,0 +1,124 @@
+#ifndef foomainloopapihfoo
+#define foomainloopapihfoo
+
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <sys/time.h>
+#include <time.h>
+
+#include <pulse/cdecl.h>
+
+/** \file
+ *
+ * Main loop abstraction layer. Both the PulseAudio core and the
+ * PulseAudio client library use a main loop abstraction layer. Due to
+ * this it is possible to embed PulseAudio into other
+ * applications easily. Two main loop implemenations are
+ * currently available:
+ * \li A minimal implementation based on the C library's poll() function (See \ref mainloop.h)
+ * \li A wrapper around the GLIB main loop. Use this to embed PulseAudio into your GLIB/GTK+/GNOME programs (See \ref glib-mainloop.h)
+ *
+ * The structure pa_mainloop_api is used as vtable for the main loop abstraction.
+ *
+ * This mainloop abstraction layer has no direct support for UNIX signals. Generic, mainloop implementation agnostic support is available throught \ref mainloop-signal.h.
+ * */
+
+PA_C_DECL_BEGIN
+
+/** An abstract mainloop API vtable */
+typedef struct pa_mainloop_api pa_mainloop_api;
+
+/** A bitmask for IO events */
+typedef enum pa_io_event_flags {
+ PA_IO_EVENT_NULL = 0, /**< No event */
+ PA_IO_EVENT_INPUT = 1, /**< Input event */
+ PA_IO_EVENT_OUTPUT = 2, /**< Output event */
+ PA_IO_EVENT_HANGUP = 4, /**< Hangup event */
+ PA_IO_EVENT_ERROR = 8 /**< Error event */
+} pa_io_event_flags_t;
+
+/** An opaque IO event source object */
+typedef struct pa_io_event pa_io_event;
+/** An IO event callback protoype \since 0.9.3 */
+typedef void (*pa_io_event_cb_t)(pa_mainloop_api*ea, pa_io_event* e, int fd, pa_io_event_flags_t events, void *userdata);
+/** A IO event destroy callback prototype \ since 0.9.3 */
+typedef void (*pa_io_event_destroy_cb_t)(pa_mainloop_api*a, pa_io_event *e, void *userdata);
+
+/** An opaque timer event source object */
+typedef struct pa_time_event pa_time_event;
+/** A time event callback prototype \since 0.9.3 */
+typedef void (*pa_time_event_cb_t)(pa_mainloop_api*a, pa_time_event* e, const struct timeval *tv, void *userdata);
+/** A time event destroy callback prototype \ since 0.9.3 */
+typedef void (*pa_time_event_destroy_cb_t)(pa_mainloop_api*a, pa_time_event *e, void *userdata);
+
+/** An opaque deferred event source object. Events of this type are triggered once in every main loop iteration */
+typedef struct pa_defer_event pa_defer_event;
+/** A defer event callback protoype \since 0.9.3 */
+typedef void (*pa_defer_event_cb_t)(pa_mainloop_api*a, pa_defer_event* e, void *userdata);
+/** A defer event destroy callback prototype \ since 0.9.3 */
+typedef void (*pa_defer_event_destroy_cb_t)(pa_mainloop_api*a, pa_defer_event *e, void *userdata);
+
+/** An abstract mainloop API vtable */
+struct pa_mainloop_api {
+ /** A pointer to some private, arbitrary data of the main loop implementation */
+ void *userdata;
+
+ /** Create a new IO event source object */
+ pa_io_event* (*io_new)(pa_mainloop_api*a, int fd, pa_io_event_flags_t events, pa_io_event_cb_t cb, void *userdata);
+ /** Enable or disable IO events on this object */
+ void (*io_enable)(pa_io_event* e, pa_io_event_flags_t events);
+ /** Free a IO event source object */
+ void (*io_free)(pa_io_event* e);
+ /** Set a function that is called when the IO event source is destroyed. Use this to free the userdata argument if required */
+ void (*io_set_destroy)(pa_io_event *e, pa_io_event_destroy_cb_t cb);
+
+ /** Create a new timer event source object for the specified Unix time */
+ pa_time_event* (*time_new)(pa_mainloop_api*a, const struct timeval *tv, pa_time_event_cb_t cb, void *userdata);
+ /** Restart a running or expired timer event source with a new Unix time */
+ void (*time_restart)(pa_time_event* e, const struct timeval *tv);
+ /** Free a deferred timer event source object */
+ void (*time_free)(pa_time_event* e);
+ /** Set a function that is called when the timer event source is destroyed. Use this to free the userdata argument if required */
+ void (*time_set_destroy)(pa_time_event *e, pa_time_event_destroy_cb_t cb);
+
+ /** Create a new deferred event source object */
+ pa_defer_event* (*defer_new)(pa_mainloop_api*a, pa_defer_event_cb_t cb, void *userdata);
+ /** Enable or disable a deferred event source temporarily */
+ void (*defer_enable)(pa_defer_event* e, int b);
+ /** Free a deferred event source object */
+ void (*defer_free)(pa_defer_event* e);
+ /** Set a function that is called when the deferred event source is destroyed. Use this to free the userdata argument if required */
+ void (*defer_set_destroy)(pa_defer_event *e, pa_defer_event_destroy_cb_t cb);
+
+ /** Exit the main loop and return the specfied retval*/
+ void (*quit)(pa_mainloop_api*a, int retval);
+};
+
+/** Run the specified callback function once from the main loop using an anonymous defer event. */
+void pa_mainloop_api_once(pa_mainloop_api*m, void (*callback)(pa_mainloop_api*m, void *userdata), void *userdata);
+
+PA_C_DECL_END
+
+#endif
diff --git a/src/pulse/mainloop-signal.c b/src/pulse/mainloop-signal.c
new file mode 100644
index 00000000..e41ed14c
--- /dev/null
+++ b/src/pulse/mainloop-signal.c
@@ -0,0 +1,230 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <signal.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.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/gccmacro.h>
+#include <pulsecore/macro.h>
+
+#include "mainloop-signal.h"
+
+struct pa_signal_event {
+ int sig;
+#ifdef HAVE_SIGACTION
+ struct sigaction saved_sigaction;
+#else
+ void (*saved_handler)(int sig);
+#endif
+ void (*callback) (pa_mainloop_api*a, pa_signal_event *e, int sig, void *userdata);
+ void *userdata;
+ void (*destroy_callback) (pa_mainloop_api*a, pa_signal_event*e, void *userdata);
+ pa_signal_event *previous, *next;
+};
+
+static pa_mainloop_api *api = NULL;
+static int signal_pipe[2] = { -1, -1 };
+static pa_io_event* io_event = NULL;
+static pa_signal_event *signals = NULL;
+
+static void signal_handler(int sig) {
+ int saved_errno;
+
+ saved_errno = errno;
+
+#ifndef HAVE_SIGACTION
+ signal(sig, signal_handler);
+#endif
+ pa_write(signal_pipe[1], &sig, sizeof(sig), NULL);
+
+ errno = saved_errno;
+}
+
+static void dispatch(pa_mainloop_api*a, int sig) {
+ pa_signal_event *s;
+
+ for (s = signals; s; s = s->next)
+ if (s->sig == sig) {
+ pa_assert(s->callback);
+ s->callback(a, s, sig, s->userdata);
+ break;
+ }
+}
+
+static void callback(pa_mainloop_api*a, pa_io_event*e, int fd, pa_io_event_flags_t f, PA_GCC_UNUSED void *userdata) {
+ ssize_t r;
+ int sig;
+
+ pa_assert(a);
+ pa_assert(e);
+ pa_assert(f == PA_IO_EVENT_INPUT);
+ pa_assert(e == io_event);
+ pa_assert(fd == signal_pipe[0]);
+
+ if ((r = pa_read(signal_pipe[0], &sig, sizeof(sig), NULL)) < 0) {
+ if (errno == EAGAIN)
+ return;
+
+ pa_log("read(): %s", pa_cstrerror(errno));
+ return;
+ }
+
+ if (r != sizeof(sig)) {
+ pa_log("short read()");
+ return;
+ }
+
+ dispatch(a, sig);
+}
+
+int pa_signal_init(pa_mainloop_api *a) {
+
+ pa_assert(a);
+ pa_assert(!api);
+ pa_assert(signal_pipe[0] == -1);
+ pa_assert(signal_pipe[1] == -1);
+ pa_assert(!io_event);
+
+ if (pipe(signal_pipe) < 0) {
+ pa_log("pipe(): %s", pa_cstrerror(errno));
+ return -1;
+ }
+
+ pa_make_fd_nonblock(signal_pipe[0]);
+ pa_make_fd_nonblock(signal_pipe[1]);
+ pa_make_fd_cloexec(signal_pipe[0]);
+ pa_make_fd_cloexec(signal_pipe[1]);
+
+ api = a;
+
+ pa_assert_se(io_event = api->io_new(api, signal_pipe[0], PA_IO_EVENT_INPUT, callback, NULL));
+
+ return 0;
+}
+
+void pa_signal_done(void) {
+ pa_assert(api);
+ pa_assert(signal_pipe[0] >= 0);
+ pa_assert(signal_pipe[1] >= 0);
+ pa_assert(io_event);
+
+ while (signals)
+ pa_signal_free(signals);
+
+ api->io_free(io_event);
+ io_event = NULL;
+
+ pa_close_pipe(signal_pipe);
+
+ api = NULL;
+}
+
+pa_signal_event* pa_signal_new(int sig, void (*_callback) (pa_mainloop_api *api, pa_signal_event*e, int sig, void *userdata), void *userdata) {
+ pa_signal_event *e = NULL;
+
+#ifdef HAVE_SIGACTION
+ struct sigaction sa;
+#endif
+
+ pa_assert(sig > 0);
+ pa_assert(_callback);
+
+ for (e = signals; e; e = e->next)
+ if (e->sig == sig)
+ goto fail;
+
+ e = pa_xnew(pa_signal_event, 1);
+ e->sig = sig;
+ e->callback = _callback;
+ e->userdata = userdata;
+ e->destroy_callback = NULL;
+
+#ifdef HAVE_SIGACTION
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = signal_handler;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+
+ if (sigaction(sig, &sa, &e->saved_sigaction) < 0)
+#else
+ if ((e->saved_handler = signal(sig, signal_handler)) == SIG_ERR)
+#endif
+ goto fail;
+
+ e->previous = NULL;
+ e->next = signals;
+ signals = e;
+
+ return e;
+fail:
+ if (e)
+ pa_xfree(e);
+ return NULL;
+}
+
+void pa_signal_free(pa_signal_event *e) {
+ pa_assert(e);
+
+ if (e->next)
+ e->next->previous = e->previous;
+ if (e->previous)
+ e->previous->next = e->next;
+ else
+ signals = e->next;
+
+#ifdef HAVE_SIGACTION
+ pa_assert_se(sigaction(e->sig, &e->saved_sigaction, NULL) == 0);
+#else
+ pa_assert_se(signal(e->sig, e->saved_handler) == signal_handler);
+#endif
+
+ if (e->destroy_callback)
+ e->destroy_callback(api, e, e->userdata);
+
+ pa_xfree(e);
+}
+
+void pa_signal_set_destroy(pa_signal_event *e, void (*_callback) (pa_mainloop_api *api, pa_signal_event*e, void *userdata)) {
+ pa_assert(e);
+
+ e->destroy_callback = _callback;
+}
diff --git a/src/pulse/mainloop-signal.h b/src/pulse/mainloop-signal.h
new file mode 100644
index 00000000..50aa99ce
--- /dev/null
+++ b/src/pulse/mainloop-signal.h
@@ -0,0 +1,62 @@
+#ifndef foomainloopsignalhfoo
+#define foomainloopsignalhfoo
+
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <pulse/mainloop-api.h>
+#include <pulse/cdecl.h>
+
+PA_C_DECL_BEGIN
+
+/** \file
+ * UNIX signal support for main loops. In contrast to other
+ * main loop event sources such as timer and IO events, UNIX signal
+ * support requires modification of the global process
+ * environment. Due to this the generic main loop abstraction layer as
+ * defined in \ref mainloop-api.h doesn't have direct support for UNIX
+ * signals. However, you may hook signal support into an abstract main loop via the routines defined herein.
+ */
+
+/** Initialize the UNIX signal subsystem and bind it to the specified main loop */
+int pa_signal_init(pa_mainloop_api *api);
+
+/** Cleanup the signal subsystem */
+void pa_signal_done(void);
+
+/** An opaque UNIX signal event source object */
+typedef struct pa_signal_event pa_signal_event;
+
+/** Create a new UNIX signal event source object */
+pa_signal_event* pa_signal_new(int sig, void (*callback) (pa_mainloop_api *api, pa_signal_event*e, int sig, void *userdata), void *userdata);
+
+/** Free a UNIX signal event source object */
+void pa_signal_free(pa_signal_event *e);
+
+/** Set a function that is called when the signal event source is destroyed. Use this to free the userdata argument if required */
+void pa_signal_set_destroy(pa_signal_event *e, void (*callback) (pa_mainloop_api *api, pa_signal_event*e, void *userdata));
+
+PA_C_DECL_END
+
+#endif
diff --git a/src/pulse/mainloop.c b/src/pulse/mainloop.c
new file mode 100644
index 00000000..ad4e4e97
--- /dev/null
+++ b/src/pulse/mainloop.c
@@ -0,0 +1,965 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <signal.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#ifdef HAVE_POLL_H
+#include <poll.h>
+#else
+#include <pulsecore/poll.h>
+#endif
+
+#ifndef HAVE_PIPE
+#include <pulsecore/pipe.h>
+#endif
+
+#include <pulse/timeval.h>
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/llist.h>
+#include <pulsecore/log.h>
+#include <pulsecore/core-error.h>
+#include <pulsecore/winsock.h>
+#include <pulsecore/macro.h>
+
+#include "mainloop.h"
+
+struct pa_io_event {
+ pa_mainloop *mainloop;
+ int dead;
+
+ int fd;
+ pa_io_event_flags_t events;
+ struct pollfd *pollfd;
+
+ pa_io_event_cb_t callback;
+ void *userdata;
+ pa_io_event_destroy_cb_t destroy_callback;
+
+ PA_LLIST_FIELDS(pa_io_event);
+};
+
+struct pa_time_event {
+ pa_mainloop *mainloop;
+ int dead;
+
+ int enabled;
+ struct timeval timeval;
+
+ pa_time_event_cb_t callback;
+ void *userdata;
+ pa_time_event_destroy_cb_t destroy_callback;
+
+ PA_LLIST_FIELDS(pa_time_event);
+};
+
+struct pa_defer_event {
+ pa_mainloop *mainloop;
+ int dead;
+
+ int enabled;
+
+ pa_defer_event_cb_t callback;
+ void *userdata;
+ pa_defer_event_destroy_cb_t destroy_callback;
+
+ PA_LLIST_FIELDS(pa_defer_event);
+};
+
+struct pa_mainloop {
+ PA_LLIST_HEAD(pa_io_event, io_events);
+ PA_LLIST_HEAD(pa_time_event, time_events);
+ PA_LLIST_HEAD(pa_defer_event, defer_events);
+
+ int n_enabled_defer_events, n_enabled_time_events, n_io_events;
+ int io_events_please_scan, time_events_please_scan, defer_events_please_scan;
+
+ struct pollfd *pollfds;
+ unsigned max_pollfds, n_pollfds;
+ int rebuild_pollfds;
+
+ int prepared_timeout;
+ pa_time_event *cached_next_time_event;
+
+ int quit, retval;
+ pa_mainloop_api api;
+
+ int wakeup_pipe[2];
+ int wakeup_pipe_type;
+ int wakeup_requested;
+
+ enum {
+ STATE_PASSIVE,
+ STATE_PREPARED,
+ STATE_POLLING,
+ STATE_POLLED,
+ STATE_QUIT
+ } state;
+
+ pa_poll_func poll_func;
+ void *poll_func_userdata;
+ int poll_func_ret;
+};
+
+static short map_flags_to_libc(pa_io_event_flags_t flags) {
+ return
+ (flags & PA_IO_EVENT_INPUT ? POLLIN : 0) |
+ (flags & PA_IO_EVENT_OUTPUT ? POLLOUT : 0) |
+ (flags & PA_IO_EVENT_ERROR ? POLLERR : 0) |
+ (flags & PA_IO_EVENT_HANGUP ? POLLHUP : 0);
+}
+
+static pa_io_event_flags_t map_flags_from_libc(short flags) {
+ return
+ (flags & POLLIN ? PA_IO_EVENT_INPUT : 0) |
+ (flags & POLLOUT ? PA_IO_EVENT_OUTPUT : 0) |
+ (flags & POLLERR ? PA_IO_EVENT_ERROR : 0) |
+ (flags & POLLHUP ? PA_IO_EVENT_HANGUP : 0);
+}
+
+/* IO events */
+static pa_io_event* mainloop_io_new(
+ pa_mainloop_api*a,
+ int fd,
+ pa_io_event_flags_t events,
+ pa_io_event_cb_t callback,
+ void *userdata) {
+
+ pa_mainloop *m;
+ pa_io_event *e;
+
+ pa_assert(a);
+ pa_assert(a->userdata);
+ pa_assert(fd >= 0);
+ pa_assert(callback);
+
+ m = a->userdata;
+ pa_assert(a == &m->api);
+
+ e = pa_xnew(pa_io_event, 1);
+ e->mainloop = m;
+ e->dead = 0;
+
+ e->fd = fd;
+ e->events = events;
+ e->pollfd = NULL;
+
+ e->callback = callback;
+ e->userdata = userdata;
+ e->destroy_callback = NULL;
+
+#ifdef OS_IS_WIN32
+ {
+ fd_set xset;
+ struct timeval tv;
+
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+
+ FD_ZERO (&xset);
+ FD_SET (fd, &xset);
+
+ if ((select((SELECT_TYPE_ARG1) fd, NULL, NULL, SELECT_TYPE_ARG234 &xset,
+ SELECT_TYPE_ARG5 &tv) == -1) &&
+ (WSAGetLastError() == WSAENOTSOCK)) {
+ pa_log_warn("Cannot monitor non-socket file descriptors.");
+ e->dead = 1;
+ }
+ }
+#endif
+
+ PA_LLIST_PREPEND(pa_io_event, m->io_events, e);
+ m->rebuild_pollfds = 1;
+ m->n_io_events ++;
+
+ pa_mainloop_wakeup(m);
+
+ return e;
+}
+
+static void mainloop_io_enable(pa_io_event *e, pa_io_event_flags_t events) {
+ pa_assert(e);
+ pa_assert(!e->dead);
+
+ if (e->events == events)
+ return;
+
+ e->events = events;
+
+ if (e->pollfd)
+ e->pollfd->events = map_flags_to_libc(events);
+ else
+ e->mainloop->rebuild_pollfds = 1;
+
+ pa_mainloop_wakeup(e->mainloop);
+}
+
+static void mainloop_io_free(pa_io_event *e) {
+ pa_assert(e);
+ pa_assert(!e->dead);
+
+ e->dead = 1;
+ e->mainloop->io_events_please_scan ++;
+
+ e->mainloop->n_io_events --;
+ e->mainloop->rebuild_pollfds = 1;
+
+ pa_mainloop_wakeup(e->mainloop);
+}
+
+static void mainloop_io_set_destroy(pa_io_event *e, pa_io_event_destroy_cb_t callback) {
+ pa_assert(e);
+
+ e->destroy_callback = callback;
+}
+
+/* Defer events */
+static pa_defer_event* mainloop_defer_new(
+ pa_mainloop_api*a,
+ pa_defer_event_cb_t callback,
+ void *userdata) {
+
+ pa_mainloop *m;
+ pa_defer_event *e;
+
+ pa_assert(a);
+ pa_assert(a->userdata);
+ pa_assert(callback);
+
+ m = a->userdata;
+ pa_assert(a == &m->api);
+
+ e = pa_xnew(pa_defer_event, 1);
+ e->mainloop = m;
+ e->dead = 0;
+
+ e->enabled = 1;
+ m->n_enabled_defer_events++;
+
+ e->callback = callback;
+ e->userdata = userdata;
+ e->destroy_callback = NULL;
+
+ PA_LLIST_PREPEND(pa_defer_event, m->defer_events, e);
+
+ pa_mainloop_wakeup(e->mainloop);
+
+ return e;
+}
+
+static void mainloop_defer_enable(pa_defer_event *e, int b) {
+ pa_assert(e);
+ pa_assert(!e->dead);
+
+ if (e->enabled && !b) {
+ pa_assert(e->mainloop->n_enabled_defer_events > 0);
+ e->mainloop->n_enabled_defer_events--;
+ } else if (!e->enabled && b) {
+ e->mainloop->n_enabled_defer_events++;
+ pa_mainloop_wakeup(e->mainloop);
+ }
+
+ e->enabled = b;
+}
+
+static void mainloop_defer_free(pa_defer_event *e) {
+ pa_assert(e);
+ pa_assert(!e->dead);
+
+ e->dead = 1;
+ e->mainloop->defer_events_please_scan ++;
+
+ if (e->enabled) {
+ pa_assert(e->mainloop->n_enabled_defer_events > 0);
+ e->mainloop->n_enabled_defer_events--;
+ e->enabled = 0;
+ }
+}
+
+static void mainloop_defer_set_destroy(pa_defer_event *e, pa_defer_event_destroy_cb_t callback) {
+ pa_assert(e);
+ pa_assert(!e->dead);
+
+ e->destroy_callback = callback;
+}
+
+/* Time events */
+static pa_time_event* mainloop_time_new(
+ pa_mainloop_api*a,
+ const struct timeval *tv,
+ pa_time_event_cb_t callback,
+ void *userdata) {
+
+ pa_mainloop *m;
+ pa_time_event *e;
+
+ pa_assert(a);
+ pa_assert(a->userdata);
+ pa_assert(callback);
+
+ m = a->userdata;
+ pa_assert(a == &m->api);
+
+ e = pa_xnew(pa_time_event, 1);
+ e->mainloop = m;
+ e->dead = 0;
+
+ if ((e->enabled = !!tv)) {
+ e->timeval = *tv;
+
+ m->n_enabled_time_events++;
+
+ if (m->cached_next_time_event) {
+ pa_assert(m->cached_next_time_event->enabled);
+
+ if (pa_timeval_cmp(tv, &m->cached_next_time_event->timeval) < 0)
+ m->cached_next_time_event = e;
+ }
+ }
+
+ e->callback = callback;
+ e->userdata = userdata;
+ e->destroy_callback = NULL;
+
+ PA_LLIST_PREPEND(pa_time_event, m->time_events, e);
+
+ if (e->enabled)
+ pa_mainloop_wakeup(m);
+
+ return e;
+}
+
+static void mainloop_time_restart(pa_time_event *e, const struct timeval *tv) {
+ pa_assert(e);
+ pa_assert(!e->dead);
+
+ if (e->enabled && !tv) {
+ pa_assert(e->mainloop->n_enabled_time_events > 0);
+ e->mainloop->n_enabled_time_events--;
+ } else if (!e->enabled && tv)
+ e->mainloop->n_enabled_time_events++;
+
+ if ((e->enabled = !!tv)) {
+ e->timeval = *tv;
+ pa_mainloop_wakeup(e->mainloop);
+ }
+
+ if (e->mainloop->cached_next_time_event && e->enabled) {
+ pa_assert(e->mainloop->cached_next_time_event->enabled);
+
+ if (pa_timeval_cmp(tv, &e->mainloop->cached_next_time_event->timeval) < 0)
+ e->mainloop->cached_next_time_event = e;
+ } else if (e->mainloop->cached_next_time_event == e)
+ e->mainloop->cached_next_time_event = NULL;
+}
+
+static void mainloop_time_free(pa_time_event *e) {
+ pa_assert(e);
+ pa_assert(!e->dead);
+
+ e->dead = 1;
+ e->mainloop->time_events_please_scan ++;
+
+ if (e->enabled) {
+ pa_assert(e->mainloop->n_enabled_time_events > 0);
+ e->mainloop->n_enabled_time_events--;
+ e->enabled = 0;
+ }
+
+ if (e->mainloop->cached_next_time_event == e)
+ e->mainloop->cached_next_time_event = NULL;
+
+ /* no wakeup needed here. Think about it! */
+}
+
+static void mainloop_time_set_destroy(pa_time_event *e, pa_time_event_destroy_cb_t callback) {
+ pa_assert(e);
+ pa_assert(!e->dead);
+
+ e->destroy_callback = callback;
+}
+
+/* quit() */
+
+static void mainloop_quit(pa_mainloop_api*a, int retval) {
+ pa_mainloop *m;
+
+ pa_assert(a);
+ pa_assert(a->userdata);
+ m = a->userdata;
+ pa_assert(a == &m->api);
+
+ pa_mainloop_quit(m, retval);
+}
+
+static const pa_mainloop_api vtable = {
+ .userdata = NULL,
+
+ .io_new= mainloop_io_new,
+ .io_enable= mainloop_io_enable,
+ .io_free= mainloop_io_free,
+ .io_set_destroy= mainloop_io_set_destroy,
+
+ .time_new = mainloop_time_new,
+ .time_restart = mainloop_time_restart,
+ .time_free = mainloop_time_free,
+ .time_set_destroy = mainloop_time_set_destroy,
+
+ .defer_new = mainloop_defer_new,
+ .defer_enable = mainloop_defer_enable,
+ .defer_free = mainloop_defer_free,
+ .defer_set_destroy = mainloop_defer_set_destroy,
+
+ .quit = mainloop_quit,
+};
+
+pa_mainloop *pa_mainloop_new(void) {
+ pa_mainloop *m;
+
+ m = pa_xnew(pa_mainloop, 1);
+
+ m->wakeup_pipe_type = 0;
+ if (pipe(m->wakeup_pipe) < 0) {
+ pa_log_error("ERROR: cannot create wakeup pipe");
+ pa_xfree(m);
+ return NULL;
+ }
+
+ pa_make_fd_nonblock(m->wakeup_pipe[0]);
+ pa_make_fd_nonblock(m->wakeup_pipe[1]);
+ pa_make_fd_cloexec(m->wakeup_pipe[0]);
+ pa_make_fd_cloexec(m->wakeup_pipe[1]);
+ m->wakeup_requested = 0;
+
+ PA_LLIST_HEAD_INIT(pa_io_event, m->io_events);
+ PA_LLIST_HEAD_INIT(pa_time_event, m->time_events);
+ PA_LLIST_HEAD_INIT(pa_defer_event, m->defer_events);
+
+ m->n_enabled_defer_events = m->n_enabled_time_events = m->n_io_events = 0;
+ m->io_events_please_scan = m->time_events_please_scan = m->defer_events_please_scan = 0;
+
+ m->cached_next_time_event = NULL;
+ m->prepared_timeout = 0;
+
+ m->pollfds = NULL;
+ m->max_pollfds = m->n_pollfds = 0;
+ m->rebuild_pollfds = 1;
+
+ m->quit = m->retval = 0;
+
+ m->api = vtable;
+ m->api.userdata = m;
+
+ m->state = STATE_PASSIVE;
+
+ m->poll_func = NULL;
+ m->poll_func_userdata = NULL;
+ m->poll_func_ret = -1;
+
+ return m;
+}
+
+static void cleanup_io_events(pa_mainloop *m, int force) {
+ pa_io_event *e;
+
+ e = m->io_events;
+ while (e) {
+ pa_io_event *n = e->next;
+
+ if (!force && m->io_events_please_scan <= 0)
+ break;
+
+ if (force || e->dead) {
+ PA_LLIST_REMOVE(pa_io_event, m->io_events, e);
+
+ if (e->dead) {
+ pa_assert(m->io_events_please_scan > 0);
+ m->io_events_please_scan--;
+ }
+
+ if (e->destroy_callback)
+ e->destroy_callback(&m->api, e, e->userdata);
+
+ pa_xfree(e);
+
+ m->rebuild_pollfds = 1;
+ }
+
+ e = n;
+ }
+
+ pa_assert(m->io_events_please_scan == 0);
+}
+
+static void cleanup_time_events(pa_mainloop *m, int force) {
+ pa_time_event *e;
+
+ e = m->time_events;
+ while (e) {
+ pa_time_event *n = e->next;
+
+ if (!force && m->time_events_please_scan <= 0)
+ break;
+
+ if (force || e->dead) {
+ PA_LLIST_REMOVE(pa_time_event, m->time_events, e);
+
+ if (e->dead) {
+ pa_assert(m->time_events_please_scan > 0);
+ m->time_events_please_scan--;
+ }
+
+ if (!e->dead && e->enabled) {
+ pa_assert(m->n_enabled_time_events > 0);
+ m->n_enabled_time_events--;
+ e->enabled = 0;
+ }
+
+ if (e->destroy_callback)
+ e->destroy_callback(&m->api, e, e->userdata);
+
+ pa_xfree(e);
+ }
+
+ e = n;
+ }
+
+ pa_assert(m->time_events_please_scan == 0);
+}
+
+static void cleanup_defer_events(pa_mainloop *m, int force) {
+ pa_defer_event *e;
+
+ e = m->defer_events;
+ while (e) {
+ pa_defer_event *n = e->next;
+
+ if (!force && m->defer_events_please_scan <= 0)
+ break;
+
+ if (force || e->dead) {
+ PA_LLIST_REMOVE(pa_defer_event, m->defer_events, e);
+
+ if (e->dead) {
+ pa_assert(m->defer_events_please_scan > 0);
+ m->defer_events_please_scan--;
+ }
+
+ if (!e->dead && e->enabled) {
+ pa_assert(m->n_enabled_defer_events > 0);
+ m->n_enabled_defer_events--;
+ e->enabled = 0;
+ }
+
+ if (e->destroy_callback)
+ e->destroy_callback(&m->api, e, e->userdata);
+
+ pa_xfree(e);
+ }
+
+ e = n;
+ }
+
+ pa_assert(m->defer_events_please_scan == 0);
+}
+
+
+void pa_mainloop_free(pa_mainloop* m) {
+ pa_assert(m);
+
+ cleanup_io_events(m, 1);
+ cleanup_defer_events(m, 1);
+ cleanup_time_events(m, 1);
+
+ pa_xfree(m->pollfds);
+
+ pa_close_pipe(m->wakeup_pipe);
+
+ pa_xfree(m);
+}
+
+static void scan_dead(pa_mainloop *m) {
+ pa_assert(m);
+
+ if (m->io_events_please_scan)
+ cleanup_io_events(m, 0);
+
+ if (m->time_events_please_scan)
+ cleanup_time_events(m, 0);
+
+ if (m->defer_events_please_scan)
+ cleanup_defer_events(m, 0);
+}
+
+static void rebuild_pollfds(pa_mainloop *m) {
+ pa_io_event*e;
+ struct pollfd *p;
+ unsigned l;
+
+ l = m->n_io_events + 1;
+ if (m->max_pollfds < l) {
+ l *= 2;
+ m->pollfds = pa_xrealloc(m->pollfds, sizeof(struct pollfd)*l);
+ m->max_pollfds = l;
+ }
+
+ m->n_pollfds = 0;
+ p = m->pollfds;
+
+ if (m->wakeup_pipe[0] >= 0) {
+ m->pollfds[0].fd = m->wakeup_pipe[0];
+ m->pollfds[0].events = POLLIN;
+ m->pollfds[0].revents = 0;
+ p++;
+ m->n_pollfds++;
+ }
+
+ for (e = m->io_events; e; e = e->next) {
+ if (e->dead) {
+ e->pollfd = NULL;
+ continue;
+ }
+
+ e->pollfd = p;
+ p->fd = e->fd;
+ p->events = map_flags_to_libc(e->events);
+ p->revents = 0;
+
+ p++;
+ m->n_pollfds++;
+ }
+
+ m->rebuild_pollfds = 0;
+}
+
+static int dispatch_pollfds(pa_mainloop *m) {
+ pa_io_event *e;
+ int r = 0, k;
+
+ pa_assert(m->poll_func_ret > 0);
+
+ for (e = m->io_events, k = m->poll_func_ret; e && !m->quit && k > 0; e = e->next) {
+ if (e->dead || !e->pollfd || !e->pollfd->revents)
+ continue;
+
+ pa_assert(e->pollfd->fd == e->fd);
+ pa_assert(e->callback);
+ e->callback(&m->api, e, e->fd, map_flags_from_libc(e->pollfd->revents), e->userdata);
+ e->pollfd->revents = 0;
+ r++;
+
+ k--;
+ }
+
+ return r;
+}
+
+static int dispatch_defer(pa_mainloop *m) {
+ pa_defer_event *e;
+ int r = 0;
+
+ if (m->n_enabled_defer_events <= 0)
+ return 0;
+
+ for (e = m->defer_events; e && !m->quit; e = e->next) {
+ if (e->dead || !e->enabled)
+ continue;
+
+ pa_assert(e->callback);
+ e->callback(&m->api, e, e->userdata);
+ r++;
+ }
+
+ return r;
+}
+
+static pa_time_event* find_next_time_event(pa_mainloop *m) {
+ pa_time_event *t, *n = NULL;
+ pa_assert(m);
+
+ if (m->cached_next_time_event)
+ return m->cached_next_time_event;
+
+ for (t = m->time_events; t; t = t->next) {
+
+ if (t->dead || !t->enabled)
+ continue;
+
+ if (!n || pa_timeval_cmp(&t->timeval, &n->timeval) < 0) {
+ n = t;
+
+ /* Shortcut for tv = { 0, 0 } */
+ if (n->timeval.tv_sec <= 0)
+ break;
+ }
+ }
+
+ m->cached_next_time_event = n;
+ return n;
+}
+
+static int calc_next_timeout(pa_mainloop *m) {
+ pa_time_event *t;
+ struct timeval now;
+ pa_usec_t usec;
+
+ if (!m->n_enabled_time_events)
+ return -1;
+
+ t = find_next_time_event(m);
+ pa_assert(t);
+
+ if (t->timeval.tv_sec <= 0)
+ return 0;
+
+ pa_gettimeofday(&now);
+
+ if (pa_timeval_cmp(&t->timeval, &now) <= 0)
+ return 0;
+
+ usec = pa_timeval_diff(&t->timeval, &now);
+ return (int) (usec / 1000);
+}
+
+static int dispatch_timeout(pa_mainloop *m) {
+ pa_time_event *e;
+ struct timeval now;
+ int r = 0;
+ pa_assert(m);
+
+ if (m->n_enabled_time_events <= 0)
+ return 0;
+
+ pa_gettimeofday(&now);
+
+ for (e = m->time_events; e && !m->quit; e = e->next) {
+
+ if (e->dead || !e->enabled)
+ continue;
+
+ if (pa_timeval_cmp(&e->timeval, &now) <= 0) {
+ pa_assert(e->callback);
+
+ /* Disable time event */
+ mainloop_time_restart(e, NULL);
+
+ e->callback(&m->api, e, &e->timeval, e->userdata);
+
+ r++;
+ }
+ }
+
+ return r;
+}
+
+void pa_mainloop_wakeup(pa_mainloop *m) {
+ char c = 'W';
+ pa_assert(m);
+
+ if (m->wakeup_pipe[1] >= 0 && m->state == STATE_POLLING) {
+ pa_write(m->wakeup_pipe[1], &c, sizeof(c), &m->wakeup_pipe_type);
+ m->wakeup_requested++;
+ }
+}
+
+static void clear_wakeup(pa_mainloop *m) {
+ char c[10];
+
+ pa_assert(m);
+
+ if (m->wakeup_pipe[0] < 0)
+ return;
+
+ if (m->wakeup_requested) {
+ while (pa_read(m->wakeup_pipe[0], &c, sizeof(c), &m->wakeup_pipe_type) == sizeof(c));
+ m->wakeup_requested = 0;
+ }
+}
+
+int pa_mainloop_prepare(pa_mainloop *m, int timeout) {
+ pa_assert(m);
+ pa_assert(m->state == STATE_PASSIVE);
+
+ clear_wakeup(m);
+ scan_dead(m);
+
+ if (m->quit)
+ goto quit;
+
+ if (m->n_enabled_defer_events <= 0) {
+ if (m->rebuild_pollfds)
+ rebuild_pollfds(m);
+
+ m->prepared_timeout = calc_next_timeout(m);
+ if (timeout >= 0 && (timeout < m->prepared_timeout || m->prepared_timeout < 0))
+ m->prepared_timeout = timeout;
+ }
+
+ m->state = STATE_PREPARED;
+ return 0;
+
+quit:
+ m->state = STATE_QUIT;
+ return -2;
+}
+
+int pa_mainloop_poll(pa_mainloop *m) {
+ pa_assert(m);
+ pa_assert(m->state == STATE_PREPARED);
+
+ if (m->quit)
+ goto quit;
+
+ m->state = STATE_POLLING;
+
+ if (m->n_enabled_defer_events )
+ m->poll_func_ret = 0;
+ else {
+ pa_assert(!m->rebuild_pollfds);
+
+ if (m->poll_func)
+ m->poll_func_ret = m->poll_func(m->pollfds, m->n_pollfds, m->prepared_timeout, m->poll_func_userdata);
+ else
+ m->poll_func_ret = poll(m->pollfds, m->n_pollfds, m->prepared_timeout);
+
+ if (m->poll_func_ret < 0) {
+ if (errno == EINTR)
+ m->poll_func_ret = 0;
+ else
+ pa_log("poll(): %s", pa_cstrerror(errno));
+ }
+ }
+
+ m->state = m->poll_func_ret < 0 ? STATE_PASSIVE : STATE_POLLED;
+ return m->poll_func_ret;
+
+quit:
+ m->state = STATE_QUIT;
+ return -2;
+}
+
+int pa_mainloop_dispatch(pa_mainloop *m) {
+ int dispatched = 0;
+
+ pa_assert(m);
+ pa_assert(m->state == STATE_POLLED);
+
+ if (m->quit)
+ goto quit;
+
+ if (m->n_enabled_defer_events)
+ dispatched += dispatch_defer(m);
+ else {
+ if (m->n_enabled_time_events)
+ dispatched += dispatch_timeout(m);
+
+ if (m->quit)
+ goto quit;
+
+ if (m->poll_func_ret > 0)
+ dispatched += dispatch_pollfds(m);
+ }
+
+ if (m->quit)
+ goto quit;
+
+ m->state = STATE_PASSIVE;
+
+ return dispatched;
+
+quit:
+ m->state = STATE_QUIT;
+ return -2;
+}
+
+int pa_mainloop_get_retval(pa_mainloop *m) {
+ pa_assert(m);
+ return m->retval;
+}
+
+int pa_mainloop_iterate(pa_mainloop *m, int block, int *retval) {
+ int r;
+ pa_assert(m);
+
+ if ((r = pa_mainloop_prepare(m, block ? -1 : 0)) < 0)
+ goto quit;
+
+ if ((r = pa_mainloop_poll(m)) < 0)
+ goto quit;
+
+ if ((r = pa_mainloop_dispatch(m)) < 0)
+ goto quit;
+
+ return r;
+
+quit:
+
+ if ((r == -2) && retval)
+ *retval = pa_mainloop_get_retval(m);
+ return r;
+}
+
+int pa_mainloop_run(pa_mainloop *m, int *retval) {
+ int r;
+
+ while ((r = pa_mainloop_iterate(m, 1, retval)) >= 0);
+
+ if (r == -2)
+ return 1;
+ else if (r < 0)
+ return -1;
+ else
+ return 0;
+}
+
+void pa_mainloop_quit(pa_mainloop *m, int retval) {
+ pa_assert(m);
+
+ m->quit = 1;
+ m->retval = retval;
+ pa_mainloop_wakeup(m);
+}
+
+pa_mainloop_api* pa_mainloop_get_api(pa_mainloop*m) {
+ pa_assert(m);
+ return &m->api;
+}
+
+void pa_mainloop_set_poll_func(pa_mainloop *m, pa_poll_func poll_func, void *userdata) {
+ pa_assert(m);
+
+ m->poll_func = poll_func;
+ m->poll_func_userdata = userdata;
+}
diff --git a/src/pulse/mainloop.h b/src/pulse/mainloop.h
new file mode 100644
index 00000000..db2797fb
--- /dev/null
+++ b/src/pulse/mainloop.h
@@ -0,0 +1,130 @@
+#ifndef foomainloophfoo
+#define foomainloophfoo
+
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <pulse/mainloop-api.h>
+#include <pulse/cdecl.h>
+
+PA_C_DECL_BEGIN
+
+struct pollfd;
+
+/** \page mainloop Main Loop
+ *
+ * \section overv_sec Overview
+ *
+ * The built-in main loop implementation is based on the poll() system call.
+ * It supports the functions defined in the main loop abstraction and very
+ * little else.
+ *
+ * The main loop is created using pa_mainloop_new() and destroyed using
+ * pa_mainloop_free(). To get access to the main loop abstraction,
+ * pa_mainloop_get_api() is used.
+ *
+ * \section iter_sec Iteration
+ *
+ * The main loop is designed around the concept of iterations. Each iteration
+ * consists of three steps that repeat during the application's entire
+ * lifetime:
+ *
+ * -# Prepare - Build a list of file descriptors
+ * that need to be monitored and calculate the next timeout.
+ * -# Poll - Execute the actuall poll() system call.
+ * -# Dispatch - Dispatch any events that have fired.
+ *
+ * When using the main loop, the application can either execute each
+ * iteration, one at a time, using pa_mainloop_iterate(), or let the library
+ * iterate automatically using pa_mainloop_run().
+ *
+ * \section thread_sec Threads
+ *
+ * The main loop functions are designed to be thread safe, but the objects
+ * are not. What this means is that multiple main loops can be used, but only
+ * one object per thread.
+ *
+ */
+
+/** \file
+ *
+ * A minimal main loop implementation based on the C library's poll()
+ * function. Using the routines defined herein you may create a simple
+ * main loop supporting the generic main loop abstraction layer as
+ * defined in \ref mainloop-api.h. This implementation is thread safe
+ * as long as you access the main loop object from a single thread only.*/
+
+/** An opaque main loop object */
+typedef struct pa_mainloop pa_mainloop;
+
+/** Allocate a new main loop object */
+pa_mainloop *pa_mainloop_new(void);
+
+/** Free a main loop object */
+void pa_mainloop_free(pa_mainloop* m);
+
+/** Prepare for a single iteration of the main loop. Returns a negative value
+on error or exit request. timeout specifies a maximum timeout for the subsequent
+poll, or -1 for blocking behaviour. .*/
+int pa_mainloop_prepare(pa_mainloop *m, int timeout);
+
+/** Execute the previously prepared poll. Returns a negative value on error.*/
+int pa_mainloop_poll(pa_mainloop *m);
+
+/** Dispatch timeout, io and deferred events from the previously executed poll. Returns
+a negative value on error. On success returns the number of source dispatched. */
+int pa_mainloop_dispatch(pa_mainloop *m);
+
+/** Return the return value as specified with the main loop's quit() routine. */
+int pa_mainloop_get_retval(pa_mainloop *m);
+
+/** Run a single iteration of the main loop. This is a convenience function
+for pa_mainloop_prepare(), pa_mainloop_poll() and pa_mainloop_dispatch().
+Returns a negative value on error or exit request. If block is nonzero,
+block for events if none are queued. Optionally return the return value as
+specified with the main loop's quit() routine in the integer variable retval points
+to. On success returns the number of sources dispatched in this iteration. */
+int pa_mainloop_iterate(pa_mainloop *m, int block, int *retval);
+
+/** Run unlimited iterations of the main loop object until the main loop's quit() routine is called. */
+int pa_mainloop_run(pa_mainloop *m, int *retval);
+
+/** Return the abstract main loop abstraction layer vtable for this main loop. */
+pa_mainloop_api* pa_mainloop_get_api(pa_mainloop*m);
+
+/** Shutdown the main loop */
+void pa_mainloop_quit(pa_mainloop *m, int r);
+
+/** Interrupt a running poll (for threaded systems) */
+void pa_mainloop_wakeup(pa_mainloop *m);
+
+/** Generic prototype of a poll() like function */
+typedef int (*pa_poll_func)(struct pollfd *ufds, unsigned long nfds, int timeout, void*userdata);
+
+/** Change the poll() implementation */
+void pa_mainloop_set_poll_func(pa_mainloop *m, pa_poll_func poll_func, void *userdata);
+
+PA_C_DECL_END
+
+#endif
diff --git a/src/pulse/operation.c b/src/pulse/operation.c
new file mode 100644
index 00000000..5d2da5b8
--- /dev/null
+++ b/src/pulse/operation.c
@@ -0,0 +1,128 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/flist.h>
+
+#include "internal.h"
+#include "operation.h"
+
+PA_STATIC_FLIST_DECLARE(operations, 0, pa_xfree);
+
+pa_operation *pa_operation_new(pa_context *c, pa_stream *s, pa_operation_cb_t cb, void *userdata) {
+ pa_operation *o;
+ pa_assert(c);
+
+ if (!(o = pa_flist_pop(PA_STATIC_FLIST_GET(operations))))
+ o = pa_xnew(pa_operation, 1);
+
+ PA_REFCNT_INIT(o);
+ o->context = c;
+ o->stream = s;
+ o->private = NULL;
+
+ o->state = PA_OPERATION_RUNNING;
+ o->callback = cb;
+ o->userdata = userdata;
+
+ /* Refcounting is strictly one-way: from the "bigger" to the "smaller" object. */
+ PA_LLIST_PREPEND(pa_operation, c->operations, o);
+ pa_operation_ref(o);
+
+ return o;
+}
+
+pa_operation *pa_operation_ref(pa_operation *o) {
+ pa_assert(o);
+ pa_assert(PA_REFCNT_VALUE(o) >= 1);
+
+ PA_REFCNT_INC(o);
+ return o;
+}
+void pa_operation_unref(pa_operation *o) {
+ pa_assert(o);
+ pa_assert(PA_REFCNT_VALUE(o) >= 1);
+
+ if (PA_REFCNT_DEC(o) <= 0) {
+ pa_assert(!o->context);
+ pa_assert(!o->stream);
+
+ if (pa_flist_push(PA_STATIC_FLIST_GET(operations), o) < 0)
+ pa_xfree(o);
+ }
+}
+
+static void operation_set_state(pa_operation *o, pa_operation_state_t st) {
+ pa_assert(o);
+ pa_assert(PA_REFCNT_VALUE(o) >= 1);
+
+ if (st == o->state)
+ return;
+
+ pa_operation_ref(o);
+
+ o->state = st;
+
+ if ((o->state == PA_OPERATION_DONE) || (o->state == PA_OPERATION_CANCELED)) {
+
+ if (o->context) {
+ pa_assert(PA_REFCNT_VALUE(o) >= 2);
+
+ PA_LLIST_REMOVE(pa_operation, o->context->operations, o);
+ pa_operation_unref(o);
+ }
+
+ o->context = NULL;
+ o->stream = NULL;
+ o->callback = NULL;
+ o->userdata = NULL;
+ }
+
+ pa_operation_unref(o);
+}
+
+void pa_operation_cancel(pa_operation *o) {
+ pa_assert(o);
+ pa_assert(PA_REFCNT_VALUE(o) >= 1);
+
+ operation_set_state(o, PA_OPERATION_CANCELED);
+}
+
+void pa_operation_done(pa_operation *o) {
+ pa_assert(o);
+ pa_assert(PA_REFCNT_VALUE(o) >= 1);
+
+ operation_set_state(o, PA_OPERATION_DONE);
+}
+
+pa_operation_state_t pa_operation_get_state(pa_operation *o) {
+ pa_assert(o);
+ pa_assert(PA_REFCNT_VALUE(o) >= 1);
+
+ return o->state;
+}
diff --git a/src/pulse/operation.h b/src/pulse/operation.h
new file mode 100644
index 00000000..97d1c6b8
--- /dev/null
+++ b/src/pulse/operation.h
@@ -0,0 +1,52 @@
+#ifndef foooperationhfoo
+#define foooperationhfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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/cdecl.h>
+#include <pulse/def.h>
+
+/** \file
+ * Asynchronous operations */
+
+PA_C_DECL_BEGIN
+
+/** An asynchronous operation object */
+typedef struct pa_operation pa_operation;
+
+/** Increase the reference count by one */
+pa_operation *pa_operation_ref(pa_operation *o);
+
+/** Decrease the reference count by one */
+void pa_operation_unref(pa_operation *o);
+
+/** Cancel the operation. Beware! This will not necessarily cancel the execution of the operation on the server side. */
+void pa_operation_cancel(pa_operation *o);
+
+/** Return the current status of the operation */
+pa_operation_state_t pa_operation_get_state(pa_operation *o);
+
+PA_C_DECL_END
+
+#endif
diff --git a/src/pulse/proplist.c b/src/pulse/proplist.c
new file mode 100644
index 00000000..c27c9d84
--- /dev/null
+++ b/src/pulse/proplist.c
@@ -0,0 +1,257 @@
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include <pulse/xmalloc.h>
+#include <pulse/utf8.h>
+
+#include <pulsecore/hashmap.h>
+#include <pulsecore/strbuf.h>
+#include <pulsecore/core-util.h>
+
+#include "proplist.h"
+
+struct property {
+ char *key;
+ void *value;
+ size_t nbytes;
+};
+
+#define MAKE_HASHMAP(p) ((pa_hashmap*) (p))
+#define MAKE_PROPLIST(p) ((pa_proplist*) (p))
+
+static pa_bool_t property_name_valid(const char *key) {
+
+ if (!pa_utf8_valid(key))
+ return FALSE;
+
+ if (strlen(key) <= 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+static void property_free(struct property *prop) {
+ pa_assert(prop);
+
+ pa_xfree(prop->key);
+ pa_xfree(prop->value);
+ pa_xfree(prop);
+}
+
+pa_proplist* pa_proplist_new(void) {
+ return MAKE_PROPLIST(pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func));
+}
+
+void pa_proplist_free(pa_proplist* p) {
+ struct property *prop;
+
+ while ((prop = pa_hashmap_steal_first(MAKE_HASHMAP(p))))
+ property_free(prop);
+
+ pa_hashmap_free(MAKE_HASHMAP(p), NULL, NULL);
+}
+
+/** Will accept only valid UTF-8 */
+int pa_proplist_puts(pa_proplist *p, const char *key, const char *value) {
+ struct property *prop;
+ pa_bool_t add = FALSE;
+
+ pa_assert(p);
+ pa_assert(key);
+
+ if (!property_name_valid(key) || !pa_utf8_valid(value))
+ return -1;
+
+ if (!(prop = pa_hashmap_get(MAKE_HASHMAP(p), key))) {
+ prop = pa_xnew(struct property, 1);
+ prop->key = pa_xstrdup(key);
+ add = TRUE;
+ } else
+ pa_xfree(prop->value);
+
+ prop->value = pa_xstrdup(value);
+ prop->nbytes = strlen(value)+1;
+
+ if (add)
+ pa_hashmap_put(MAKE_HASHMAP(p), prop->key, prop);
+
+ return 0;
+}
+
+int pa_proplist_put(pa_proplist *p, const char *key, const void *data, size_t nbytes) {
+ struct property *prop;
+ pa_bool_t add = FALSE;
+
+ pa_assert(p);
+ pa_assert(key);
+
+ if (!property_name_valid(key))
+ return -1;
+
+ if (!(prop = pa_hashmap_get(MAKE_HASHMAP(p), key))) {
+ prop = pa_xnew(struct property, 1);
+ prop->key = pa_xstrdup(key);
+ add = TRUE;
+ } else
+ pa_xfree(prop->value);
+
+ prop->value = pa_xmemdup(data, nbytes);
+ prop->nbytes = nbytes;
+
+ if (add)
+ pa_hashmap_put(MAKE_HASHMAP(p), prop->key, prop);
+
+ return 0;
+}
+
+const char *pa_proplist_gets(pa_proplist *p, const char *key) {
+ struct property *prop;
+
+ pa_assert(p);
+ pa_assert(key);
+
+ if (!property_name_valid(key))
+ return NULL;
+
+ if (!(prop = pa_hashmap_get(MAKE_HASHMAP(p), key)))
+ return NULL;
+
+ if (prop->nbytes <= 0)
+ return NULL;
+
+ if (((char*) prop->value)[prop->nbytes-1] != 0)
+ return NULL;
+
+ if (strlen((char*) prop->value) != prop->nbytes-1)
+ return NULL;
+
+ if (!pa_utf8_valid((char*) prop->value))
+ return NULL;
+
+ return (char*) prop->value;
+}
+
+int pa_proplist_get(pa_proplist *p, const char *key, const void **data, size_t *nbytes) {
+ struct property *prop;
+
+ pa_assert(p);
+ pa_assert(key);
+
+ if (!property_name_valid(key))
+ return -1;
+
+ if (!(prop = pa_hashmap_get(MAKE_HASHMAP(p), key)))
+ return -1;
+
+ *data = prop->value;
+ *nbytes = prop->nbytes;
+
+ return 0;
+}
+
+void pa_proplist_merge(pa_proplist *p, pa_proplist *other) {
+ struct property *prop;
+ void *state = NULL;
+
+ pa_assert(p);
+ pa_assert(other);
+
+ while ((prop = pa_hashmap_iterate(MAKE_HASHMAP(other), &state, NULL)))
+ pa_assert_se(pa_proplist_put(p, prop->key, prop->value, prop->nbytes) == 0);
+}
+
+int pa_proplist_remove(pa_proplist *p, const char *key) {
+ struct property *prop;
+
+ pa_assert(p);
+ pa_assert(key);
+
+ if (!property_name_valid(key))
+ return -1;
+
+ if (!(prop = pa_hashmap_remove(MAKE_HASHMAP(p), key)))
+ return -1;
+
+ property_free(prop);
+ return 0;
+}
+
+const char *pa_proplist_iterate(pa_proplist *p, void **state) {
+ struct property *prop;
+
+ if (!(prop = pa_hashmap_iterate(MAKE_HASHMAP(p), state, NULL)))
+ return NULL;
+
+ return prop->key;
+}
+
+char *pa_proplist_to_string(pa_proplist *p) {
+ const char *key;
+ void *state = NULL;
+ pa_strbuf *buf;
+
+ pa_assert(p);
+
+ buf = pa_strbuf_new();
+
+ while ((key = pa_proplist_iterate(p, &state))) {
+
+ const char *v;
+
+ if ((v = pa_proplist_gets(p, key)))
+ pa_strbuf_printf(buf, "%s = \"%s\"\n", key, v);
+ else {
+ const void *value;
+ size_t nbytes;
+ char *c;
+
+ pa_assert_se(pa_proplist_get(p, key, &value, &nbytes) == 0);
+ c = pa_xnew(char, nbytes*2+1);
+ pa_hexstr((const uint8_t*) value, nbytes, c, nbytes*2+1);
+
+ pa_strbuf_printf(buf, "%s = hex:%s\n", key, c);
+ pa_xfree(c);
+ }
+ }
+
+ return pa_strbuf_tostring_free(buf);
+}
+
+int pa_proplist_contains(pa_proplist *p, const char *key) {
+ pa_assert(p);
+ pa_assert(key);
+
+ if (!property_name_valid(key))
+ return -1;
+
+ if (!(pa_hashmap_get(MAKE_HASHMAP(p), key)))
+ return 0;
+
+ return 1;
+}
diff --git a/src/pulse/proplist.h b/src/pulse/proplist.h
new file mode 100644
index 00000000..c4cf9ac9
--- /dev/null
+++ b/src/pulse/proplist.h
@@ -0,0 +1,90 @@
+#ifndef foopulseproplisthfoo
+#define foopulseproplisthfoo
+
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <pulsecore/macro.h>
+
+/* Defined properties:
+ *
+ * x11.xid
+ * x11.display
+ * x11.x_pointer
+ * x11.y_pointer
+ * x11.button
+ * media.name
+ * media.title
+ * media.artist
+ * media.language
+ * media.filename
+ * media.icon
+ * media.icon_name
+ * media.role video, music, game, event, phone, production
+ * application.name
+ * application.version
+ * application.icon
+ * application.icon_name
+ */
+
+#define PA_PROP_X11_XID "x11.xid"
+#define PA_PROP_X11_DISPLAY "x11.display"
+#define PA_PROP_X11_X_POINTER "x11.x_pointer"
+#define PA_PROP_X11_Y_POINTER "x11.y_pointer"
+#define PA_PROP_X11_BUTTON "x11.button"
+#define PA_PROP_MEDIA_NAME "media.name"
+#define PA_PROP_MEDIA_TITLE "media.title"
+#define PA_PROP_MEDIA_ARTIST "media.artist"
+#define PA_PROP_MEDIA_LANGUAGE "media.language"
+#define PA_PROP_MEDIA_FILENAME "media.filename"
+#define PA_PROP_MEDIA_ICON "media.icon"
+#define PA_PROP_MEDIA_ICON_NAME "media.icon_name"
+#define PA_PROP_MEDIA_ROLE "media.role"
+#define PA_PROP_APPLICATION_NAME "application.name"
+#define PA_PROP_APPLICATION_VERSION "application.version"
+#define PA_PROP_APPLICATION_ICON "application.icon"
+#define PA_PROP_APPLICATION_ICON_NAME "application.icon_name"
+
+typedef struct pa_proplist pa_proplist;
+
+pa_proplist* pa_proplist_new(void);
+void pa_proplist_free(pa_proplist* p);
+
+/** Will accept only valid UTF-8 */
+int pa_proplist_puts(pa_proplist *p, const char *key, const char *value);
+int pa_proplist_put(pa_proplist *p, const char *key, const void *data, size_t nbytes);
+
+/* Will return NULL if the data is not valid UTF-8 */
+const char *pa_proplist_gets(pa_proplist *p, const char *key);
+int pa_proplist_get(pa_proplist *p, const char *key, const void **data, size_t *nbytes);
+
+void pa_proplist_merge(pa_proplist *p, pa_proplist *other);
+int pa_proplist_remove(pa_proplist *p, const char *key);
+
+const char *pa_proplist_iterate(pa_proplist *p, void **state);
+
+char *pa_proplist_to_string(pa_proplist *p);
+
+int pa_proplist_contains(pa_proplist *p, const char *key);
+
+#endif
diff --git a/src/pulse/pulseaudio.h b/src/pulse/pulseaudio.h
new file mode 100644
index 00000000..88d1275b
--- /dev/null
+++ b/src/pulse/pulseaudio.h
@@ -0,0 +1,117 @@
+#ifndef foopulseaudiohfoo
+#define foopulseaudiohfoo
+
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <pulse/mainloop-api.h>
+#include <pulse/sample.h>
+#include <pulse/def.h>
+#include <pulse/context.h>
+#include <pulse/stream.h>
+#include <pulse/introspect.h>
+#include <pulse/subscribe.h>
+#include <pulse/scache.h>
+#include <pulse/version.h>
+#include <pulse/error.h>
+#include <pulse/operation.h>
+#include <pulse/channelmap.h>
+#include <pulse/volume.h>
+#include <pulse/xmalloc.h>
+#include <pulse/utf8.h>
+#include <pulse/thread-mainloop.h>
+#include <pulse/mainloop.h>
+#include <pulse/mainloop-signal.h>
+#include <pulse/util.h>
+#include <pulse/timeval.h>
+
+/** \file
+ * Include all libpulse header files at once. The following
+ * files are included: \ref mainloop-api.h, \ref sample.h, \ref def.h,
+ * \ref context.h, \ref stream.h, \ref introspect.h, \ref subscribe.h,
+ * \ref scache.h, \ref version.h, \ref error.h, \ref channelmap.h,
+ * \ref operation.h,\ref volume.h, \ref xmalloc.h, \ref utf8.h, \ref
+ * thread-mainloop.h, \ref mainloop.h, \ref util.h, \ref timeval.h and
+ * \ref mainloop-signal.h at once */
+
+/** \mainpage
+ *
+ * \section intro_sec Introduction
+ *
+ * This document describes the client API for the PulseAudio sound
+ * server. The API comes in two flavours to accomodate different styles
+ * of applications and different needs in complexity:
+ *
+ * \li The complete but somewhat complicated to use asynchronous API
+ * \li The simplified, easy to use, but limited synchronous API
+ *
+ * All strings in PulseAudio are in the UTF-8 encoding, regardless of current
+ * locale. Some functions will filter invalid sequences from the string, some
+ * will simply fail. To ensure reliable behaviour, make sure everything you
+ * pass to the API is already in UTF-8.
+
+ * \section simple_sec Simple API
+ *
+ * Use this if you develop your program in synchronous style and just
+ * need a way to play or record data on the sound server. See
+ * \subpage simple for more details.
+ *
+ * \section async_sec Asynchronous API
+ *
+ * Use this if you develop your programs in asynchronous, event loop
+ * based style or if you want to use the advanced features of the
+ * PulseAudio API. A guide can be found in \subpage async.
+ *
+ * By using the built-in threaded main loop, it is possible to acheive a
+ * pseudo-synchronous API, which can be useful in synchronous applications
+ * where the simple API is insufficient. See the \ref async page for
+ * details.
+ *
+ * \section thread_sec Threads
+ *
+ * The PulseAudio client libraries are not designed to be used in a
+ * heavily threaded environment. They are however designed to be reentrant
+ * safe.
+ *
+ * To use a the libraries in a threaded environment, you must assure that
+ * all objects are only used in one thread at a time. Normally, this means
+ * that all objects belonging to a single context must be accessed from the
+ * same thread.
+ *
+ * The included main loop implementation is also not thread safe. Take care
+ * to make sure event lists are not manipulated when any other code is
+ * using the main loop.
+ *
+ * \section pkgconfig pkg-config
+ *
+ * The PulseAudio libraries provide pkg-config snippets for the different
+ * modules:
+ *
+ * \li libpulse - The asynchronous API and the internal main loop implementation.
+ * \li libpulse-mainloop-glib12 - GLIB 1.2 main loop bindings.
+ * \li libpulse-mainloop-glib - GLIB 2.x main loop bindings.
+ * \li libpulse-simple - The simple PulseAudio API.
+ */
+
+#endif
diff --git a/src/pulse/sample.c b/src/pulse/sample.c
new file mode 100644
index 00000000..27c0df03
--- /dev/null
+++ b/src/pulse/sample.c
@@ -0,0 +1,185 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/macro.h>
+
+#include "sample.h"
+
+size_t pa_sample_size(const pa_sample_spec *spec) {
+
+ static const size_t table[] = {
+ [PA_SAMPLE_U8] = 1,
+ [PA_SAMPLE_ULAW] = 1,
+ [PA_SAMPLE_ALAW] = 1,
+ [PA_SAMPLE_S16LE] = 2,
+ [PA_SAMPLE_S16BE] = 2,
+ [PA_SAMPLE_FLOAT32LE] = 4,
+ [PA_SAMPLE_FLOAT32BE] = 4,
+ [PA_SAMPLE_S32LE] = 4,
+ [PA_SAMPLE_S32BE] = 4,
+ };
+
+ pa_assert(spec);
+ pa_assert(spec->format >= 0);
+ pa_assert(spec->format < PA_SAMPLE_MAX);
+
+ return table[spec->format];
+}
+
+size_t pa_frame_size(const pa_sample_spec *spec) {
+ pa_assert(spec);
+
+ return pa_sample_size(spec) * spec->channels;
+}
+
+size_t pa_bytes_per_second(const pa_sample_spec *spec) {
+ pa_assert(spec);
+ return spec->rate*pa_frame_size(spec);
+}
+
+pa_usec_t pa_bytes_to_usec(uint64_t length, const pa_sample_spec *spec) {
+ pa_assert(spec);
+
+ return (pa_usec_t) (((double) length/pa_frame_size(spec)*1000000)/spec->rate);
+}
+
+size_t pa_usec_to_bytes(pa_usec_t t, const pa_sample_spec *spec) {
+ pa_assert(spec);
+
+ return (size_t) (((double) t * spec->rate / 1000000))*pa_frame_size(spec);
+}
+
+int pa_sample_spec_valid(const pa_sample_spec *spec) {
+ pa_assert(spec);
+
+ if (spec->rate <= 0 ||
+ spec->rate > PA_RATE_MAX ||
+ spec->channels <= 0 ||
+ spec->channels > PA_CHANNELS_MAX ||
+ spec->format >= PA_SAMPLE_MAX ||
+ spec->format < 0)
+ return 0;
+
+ return 1;
+}
+
+int pa_sample_spec_equal(const pa_sample_spec*a, const pa_sample_spec*b) {
+ pa_assert(a);
+ pa_assert(b);
+
+ return (a->format == b->format) && (a->rate == b->rate) && (a->channels == b->channels);
+}
+
+const char *pa_sample_format_to_string(pa_sample_format_t f) {
+ static const char* const table[]= {
+ [PA_SAMPLE_U8] = "u8",
+ [PA_SAMPLE_ALAW] = "aLaw",
+ [PA_SAMPLE_ULAW] = "uLaw",
+ [PA_SAMPLE_S16LE] = "s16le",
+ [PA_SAMPLE_S16BE] = "s16be",
+ [PA_SAMPLE_FLOAT32LE] = "float32le",
+ [PA_SAMPLE_FLOAT32BE] = "float32be",
+ [PA_SAMPLE_S32LE] = "s32le",
+ [PA_SAMPLE_S32BE] = "s32be",
+ };
+
+ if (f < 0 || f >= PA_SAMPLE_MAX)
+ return NULL;
+
+ return table[f];
+}
+
+char *pa_sample_spec_snprint(char *s, size_t l, const pa_sample_spec *spec) {
+ pa_assert(s);
+ pa_assert(l);
+ pa_assert(spec);
+
+ if (!pa_sample_spec_valid(spec))
+ pa_snprintf(s, l, "Invalid");
+ else
+ pa_snprintf(s, l, "%s %uch %uHz", pa_sample_format_to_string(spec->format), spec->channels, spec->rate);
+
+ return s;
+}
+
+char* pa_bytes_snprint(char *s, size_t l, unsigned v) {
+ pa_assert(s);
+
+ if (v >= ((unsigned) 1024)*1024*1024)
+ pa_snprintf(s, l, "%0.1f GiB", ((double) v)/1024/1024/1024);
+ else if (v >= ((unsigned) 1024)*1024)
+ pa_snprintf(s, l, "%0.1f MiB", ((double) v)/1024/1024);
+ else if (v >= (unsigned) 1024)
+ pa_snprintf(s, l, "%0.1f KiB", ((double) v)/1024);
+ else
+ pa_snprintf(s, l, "%u B", (unsigned) v);
+
+ return s;
+}
+
+pa_sample_format_t pa_parse_sample_format(const char *format) {
+ pa_assert(format);
+
+ if (strcasecmp(format, "s16le") == 0)
+ return PA_SAMPLE_S16LE;
+ else if (strcasecmp(format, "s16be") == 0)
+ return PA_SAMPLE_S16BE;
+ else if (strcasecmp(format, "s16ne") == 0 || strcasecmp(format, "s16") == 0 || strcasecmp(format, "16") == 0)
+ return PA_SAMPLE_S16NE;
+ else if (strcasecmp(format, "s16re") == 0)
+ return PA_SAMPLE_S16RE;
+ else if (strcasecmp(format, "u8") == 0 || strcasecmp(format, "8") == 0)
+ return PA_SAMPLE_U8;
+ else if (strcasecmp(format, "float32") == 0 || strcasecmp(format, "float32ne") == 0 || strcasecmp(format, "float") == 0)
+ return PA_SAMPLE_FLOAT32NE;
+ else if (strcasecmp(format, "float32re") == 0)
+ return PA_SAMPLE_FLOAT32RE;
+ else if (strcasecmp(format, "float32le") == 0)
+ return PA_SAMPLE_FLOAT32LE;
+ else if (strcasecmp(format, "float32be") == 0)
+ return PA_SAMPLE_FLOAT32BE;
+ else if (strcasecmp(format, "ulaw") == 0 || strcasecmp(format, "mulaw") == 0)
+ return PA_SAMPLE_ULAW;
+ else if (strcasecmp(format, "alaw") == 0)
+ return PA_SAMPLE_ALAW;
+ else if (strcasecmp(format, "s32le") == 0)
+ return PA_SAMPLE_S32LE;
+ else if (strcasecmp(format, "s32be") == 0)
+ return PA_SAMPLE_S32BE;
+ else if (strcasecmp(format, "s32ne") == 0 || strcasecmp(format, "s32") == 0 || strcasecmp(format, "32") == 0)
+ return PA_SAMPLE_S32NE;
+ else if (strcasecmp(format, "s32re") == 0)
+ return PA_SAMPLE_S32RE;
+
+ return -1;
+}
diff --git a/src/pulse/sample.h b/src/pulse/sample.h
new file mode 100644
index 00000000..f0b839fd
--- /dev/null
+++ b/src/pulse/sample.h
@@ -0,0 +1,216 @@
+#ifndef foosamplehfoo
+#define foosamplehfoo
+
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <inttypes.h>
+#include <sys/types.h>
+#include <sys/param.h>
+#include <math.h>
+
+#include <pulse/cdecl.h>
+
+/** \page sample Sample Format Specifications
+ *
+ * \section overv_sec Overview
+ *
+ * PulseAudio is capable of handling a multitude of sample formats, rates
+ * and channels, transparently converting and mixing them as needed.
+ *
+ * \section format_sec Sample Format
+ *
+ * PulseAudio supports the following sample formats:
+ *
+ * \li PA_SAMPLE_U8 - Unsigned 8 bit integer PCM.
+ * \li PA_SAMPLE_S16LE - Signed 16 integer bit PCM, little endian.
+ * \li PA_SAMPLE_S16BE - Signed 16 integer bit PCM, big endian.
+ * \li PA_SAMPLE_FLOAT32LE - 32 bit IEEE floating point PCM, little endian.
+ * \li PA_SAMPLE_FLOAT32BE - 32 bit IEEE floating point PCM, big endian.
+ * \li PA_SAMPLE_ALAW - 8 bit a-Law.
+ * \li PA_SAMPLE_ULAW - 8 bit mu-Law.
+ * \li PA_SAMPLE_S32LE - Signed 32 bit integer PCM, little endian.
+ * \li PA_SAMPLE_S32BE - Signed 32 bit integer PCM, big endian.
+ *
+ * The floating point sample formats have the range from -1 to 1.
+ *
+ * The sample formats that are sensitive to endianness have convenience
+ * macros for native endian (NE), and reverse endian (RE).
+ *
+ * \section rate_sec Sample Rates
+ *
+ * PulseAudio supports any sample rate between 1 Hz and 4 GHz. There is no
+ * point trying to exceed the sample rate of the output device though as the
+ * signal will only get downsampled, consuming CPU on the machine running the
+ * server.
+ *
+ * \section chan_sec Channels
+ *
+ * PulseAudio supports up to 16 individiual channels. The order of the
+ * channels is up to the application, but they must be continous. To map
+ * channels to speakers, see \ref channelmap.
+ *
+ * \section calc_sec Calculations
+ *
+ * The PulseAudio library contains a number of convenience functions to do
+ * calculations on sample formats:
+ *
+ * \li pa_bytes_per_second() - The number of bytes one second of audio will
+ * take given a sample format.
+ * \li pa_frame_size() - The size, in bytes, of one frame (i.e. one set of
+ * samples, one for each channel).
+ * \li pa_sample_size() - The size, in bytes, of one sample.
+ * \li pa_bytes_to_usec() - Calculate the time it would take to play a buffer
+ * of a certain size.
+ *
+ * \section util_sec Convenience Functions
+ *
+ * The library also contains a couple of other convenience functions:
+ *
+ * \li pa_sample_spec_valid() - Tests if a sample format specification is
+ * valid.
+ * \li pa_sample_spec_equal() - Tests if the sample format specifications are
+ * identical.
+ * \li pa_sample_format_to_string() - Return a textual description of a
+ * sample format.
+ * \li pa_parse_sample_format() - Parse a text string into a sample format.
+ * \li pa_sample_spec_snprint() - Create a textual description of a complete
+ * sample format specification.
+ * \li pa_bytes_snprint() - Pretty print a byte value (e.g. 2.5 MiB).
+ */
+
+/** \file
+ * Constants and routines for sample type handling */
+
+PA_C_DECL_BEGIN
+
+#if !defined(WORDS_BIGENDIAN)
+#if defined(__BYTE_ORDER)
+#if __BYTE_ORDER == __BIG_ENDIAN
+#define WORDS_BIGENDIAN
+#endif
+#endif
+#endif
+
+/** Maximum number of allowed channels */
+#define PA_CHANNELS_MAX 32
+
+/** Maximum allowed sample rate */
+#define PA_RATE_MAX (48000*4)
+
+/** Sample format */
+typedef enum pa_sample_format {
+ PA_SAMPLE_U8, /**< Unsigned 8 Bit PCM */
+ PA_SAMPLE_ALAW, /**< 8 Bit a-Law */
+ PA_SAMPLE_ULAW, /**< 8 Bit mu-Law */
+ PA_SAMPLE_S16LE, /**< Signed 16 Bit PCM, little endian (PC) */
+ PA_SAMPLE_S16BE, /**< Signed 16 Bit PCM, big endian */
+ PA_SAMPLE_FLOAT32LE, /**< 32 Bit IEEE floating point, little endian, range -1 to 1 */
+ PA_SAMPLE_FLOAT32BE, /**< 32 Bit IEEE floating point, big endian, range -1 to 1 */
+ PA_SAMPLE_S32LE, /**< Signed 32 Bit PCM, little endian (PC) */
+ PA_SAMPLE_S32BE, /**< Signed 32 Bit PCM, big endian (PC) */
+ PA_SAMPLE_MAX, /**< Upper limit of valid sample types */
+ PA_SAMPLE_INVALID = -1 /**< An invalid value */
+} pa_sample_format_t;
+
+#ifdef WORDS_BIGENDIAN
+/** Signed 16 Bit PCM, native endian */
+#define PA_SAMPLE_S16NE PA_SAMPLE_S16BE
+/** 32 Bit IEEE floating point, native endian */
+#define PA_SAMPLE_FLOAT32NE PA_SAMPLE_FLOAT32BE
+/** Signed 32 Bit PCM, native endian */
+#define PA_SAMPLE_S32NE PA_SAMPLE_S32BE
+/** Signed 16 Bit PCM reverse endian */
+#define PA_SAMPLE_S16RE PA_SAMPLE_S16LE
+/** 32 Bit IEEE floating point, reverse endian */
+#define PA_SAMPLE_FLOAT32RE PA_SAMPLE_FLOAT32LE
+/** Signed 32 Bit PCM reverse endian */
+#define PA_SAMPLE_S32RE PA_SAMPLE_S32LE
+#else
+/** Signed 16 Bit PCM, native endian */
+#define PA_SAMPLE_S16NE PA_SAMPLE_S16LE
+/** 32 Bit IEEE floating point, native endian */
+#define PA_SAMPLE_FLOAT32NE PA_SAMPLE_FLOAT32LE
+/** Signed 32 Bit PCM, native endian */
+#define PA_SAMPLE_S32NE PA_SAMPLE_S32LE
+/** Signed 16 Bit PCM reverse endian */
+#define PA_SAMPLE_S16RE PA_SAMPLE_S16BE
+/** 32 Bit IEEE floating point, reverse endian */
+#define PA_SAMPLE_FLOAT32RE PA_SAMPLE_FLOAT32BE
+/** Signed 32 Bit PCM reverse endian */
+#define PA_SAMPLE_S32RE PA_SAMPLE_S32BE
+#endif
+
+/** A Shortcut for PA_SAMPLE_FLOAT32NE */
+#define PA_SAMPLE_FLOAT32 PA_SAMPLE_FLOAT32NE
+
+/** A sample format and attribute specification */
+typedef struct pa_sample_spec {
+ pa_sample_format_t format; /**< The sample format */
+ uint32_t rate; /**< The sample rate. (e.g. 44100) */
+ uint8_t channels; /**< Audio channels. (1 for mono, 2 for stereo, ...) */
+} pa_sample_spec;
+
+/** Type for usec specifications (unsigned). May be either 32 or 64 bit, depending on the architecture */
+typedef uint64_t pa_usec_t;
+
+/** Return the amount of bytes playback of a second of audio with the specified sample type takes */
+size_t pa_bytes_per_second(const pa_sample_spec *spec) PA_GCC_PURE;
+
+/** Return the size of a frame with the specific sample type */
+size_t pa_frame_size(const pa_sample_spec *spec) PA_GCC_PURE;
+
+/** Return the size of a sample with the specific sample type */
+size_t pa_sample_size(const pa_sample_spec *spec) PA_GCC_PURE;
+
+/** Calculate the time the specified bytes take to play with the specified sample type */
+pa_usec_t pa_bytes_to_usec(uint64_t length, const pa_sample_spec *spec) PA_GCC_PURE;
+
+/** Calculates the number of bytes that are required for the specified time. \since 0.9 */
+size_t pa_usec_to_bytes(pa_usec_t t, const pa_sample_spec *spec) PA_GCC_PURE;
+
+/** Return non-zero when the sample type specification is valid */
+int pa_sample_spec_valid(const pa_sample_spec *spec) PA_GCC_PURE;
+
+/** Return non-zero when the two sample type specifications match */
+int pa_sample_spec_equal(const pa_sample_spec*a, const pa_sample_spec*b) PA_GCC_PURE;
+
+/** Return a descriptive string for the specified sample format. \since 0.8 */
+const char *pa_sample_format_to_string(pa_sample_format_t f) PA_GCC_PURE;
+
+/** Parse a sample format text. Inverse of pa_sample_format_to_string() */
+pa_sample_format_t pa_parse_sample_format(const char *format) PA_GCC_PURE;
+
+/** Maximum required string length for pa_sample_spec_snprint() */
+#define PA_SAMPLE_SPEC_SNPRINT_MAX 32
+
+/** Pretty print a sample type specification to a string */
+char* pa_sample_spec_snprint(char *s, size_t l, const pa_sample_spec *spec);
+
+/** Pretty print a byte size value. (i.e. "2.5 MiB") */
+char* pa_bytes_snprint(char *s, size_t l, unsigned v);
+
+PA_C_DECL_END
+
+#endif
diff --git a/src/pulse/scache.c b/src/pulse/scache.c
new file mode 100644
index 00000000..186b0a3e
--- /dev/null
+++ b/src/pulse/scache.c
@@ -0,0 +1,136 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <pulsecore/pstream-util.h>
+#include <pulsecore/macro.h>
+
+#include "internal.h"
+
+#include "scache.h"
+
+int pa_stream_connect_upload(pa_stream *s, size_t length) {
+ pa_tagstruct *t;
+ uint32_t tag;
+
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+ PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_UNCONNECTED, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY(s->context, length > 0, PA_ERR_INVALID);
+
+ pa_stream_ref(s);
+
+ s->direction = PA_STREAM_UPLOAD;
+
+ t = pa_tagstruct_command(s->context, PA_COMMAND_CREATE_UPLOAD_STREAM, &tag);
+ pa_tagstruct_puts(t, s->name);
+ pa_tagstruct_put_sample_spec(t, &s->sample_spec);
+ pa_tagstruct_put_channel_map(t, &s->channel_map);
+ pa_tagstruct_putu32(t, length);
+ pa_pstream_send_tagstruct(s->context->pstream, t);
+ pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_create_stream_callback, s, NULL);
+
+ pa_stream_set_state(s, PA_STREAM_CREATING);
+
+ pa_stream_unref(s);
+ return 0;
+}
+
+int pa_stream_finish_upload(pa_stream *s) {
+ pa_tagstruct *t;
+ uint32_t tag;
+
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+ PA_CHECK_VALIDITY(s->context, s->channel_valid, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY(s->context, s->context->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+
+ pa_stream_ref(s);
+
+ t = pa_tagstruct_command(s->context, PA_COMMAND_FINISH_UPLOAD_STREAM, &tag);
+ pa_tagstruct_putu32(t, s->channel);
+ pa_pstream_send_tagstruct(s->context->pstream, t);
+ pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_stream_disconnect_callback, s, NULL);
+
+ pa_stream_unref(s);
+ return 0;
+}
+
+pa_operation *pa_context_play_sample(pa_context *c, const char *name, const char *dev, pa_volume_t volume, pa_context_success_cb_t cb, void *userdata) {
+ pa_operation *o;
+ pa_tagstruct *t;
+ uint32_t tag;
+
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, name && *name, PA_ERR_INVALID);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, !dev || *dev, PA_ERR_INVALID);
+
+ o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+
+ if (!dev)
+ dev = c->conf->default_sink;
+
+ t = pa_tagstruct_command(c, PA_COMMAND_PLAY_SAMPLE, &tag);
+ pa_tagstruct_putu32(t, PA_INVALID_INDEX);
+ pa_tagstruct_puts(t, dev);
+ pa_tagstruct_putu32(t, volume);
+ pa_tagstruct_puts(t, name);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+}
+
+pa_operation* pa_context_remove_sample(pa_context *c, const char *name, pa_context_success_cb_t cb, void *userdata) {
+ pa_operation *o;
+ pa_tagstruct *t;
+ uint32_t tag;
+
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, name && *name, PA_ERR_INVALID);
+
+ o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+
+ t = pa_tagstruct_command(c, PA_COMMAND_REMOVE_SAMPLE, &tag);
+ pa_tagstruct_puts(t, name);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+}
+
diff --git a/src/pulse/scache.h b/src/pulse/scache.h
new file mode 100644
index 00000000..31fd8956
--- /dev/null
+++ b/src/pulse/scache.h
@@ -0,0 +1,103 @@
+#ifndef fooscachehfoo
+#define fooscachehfoo
+
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <sys/types.h>
+
+#include <pulse/context.h>
+#include <pulse/stream.h>
+#include <pulse/cdecl.h>
+
+/** \page scache Sample Cache
+ *
+ * \section overv_sec Overview
+ *
+ * The sample cache provides a simple way of overcoming high network latencies
+ * and reducing bandwidth. Instead of streaming a sound precisely when it
+ * should be played, it is stored on the server and only the command to start
+ * playing it needs to be sent.
+ *
+ * \section create_sec Creation
+ *
+ * To create a sample, the normal stream API is used (see \ref streams). The
+ * function pa_stream_connect_upload() will make sure the stream is stored as
+ * a sample on the server.
+ *
+ * To complete the upload, pa_stream_finish_upload() is called and the sample
+ * will receive the same name as the stream. If the upload should be aborted,
+ * simply call pa_stream_disconnect().
+ *
+ * \section play_sec Playing samples
+ *
+ * To play back a sample, simply call pa_context_play_sample():
+ *
+ * \code
+ * pa_operation *o;
+ *
+ * o = pa_context_play_sample(my_context,
+ * "sample2", // Name of my sample
+ * NULL, // Use default sink
+ * PA_VOLUME_NORM, // Full volume
+ * NULL, // Don't need a callback
+ * NULL
+ * );
+ * if (o)
+ * pa_operation_unref(o);
+ * \endcode
+ *
+ * \section rem_sec Removing samples
+ *
+ * When a sample is no longer needed, it should be removed on the server to
+ * save resources. The sample is deleted using pa_context_remove_sample().
+ */
+
+/** \file
+ * All sample cache related routines */
+
+PA_C_DECL_BEGIN
+
+/** Make this stream a sample upload stream */
+int pa_stream_connect_upload(pa_stream *s, size_t length);
+
+/** Finish the sample upload, the stream name will become the sample name. You cancel a samp
+ * le upload by issuing pa_stream_disconnect() */
+int pa_stream_finish_upload(pa_stream *s);
+
+/** Play a sample from the sample cache to the specified device. If the latter is NULL use the default sink. Returns an operation object */
+pa_operation* pa_context_play_sample(
+ pa_context *c /**< Context */,
+ const char *name /**< Name of the sample to play */,
+ const char *dev /**< Sink to play this sample on */,
+ pa_volume_t volume /**< Volume to play this sample with */ ,
+ pa_context_success_cb_t cb /**< Call this function after successfully starting playback, or NULL */,
+ void *userdata /**< Userdata to pass to the callback */);
+
+/** Remove a sample from the sample cache. Returns an operation object which may be used to cancel the operation while it is running */
+pa_operation* pa_context_remove_sample(pa_context *c, const char *name, pa_context_success_cb_t, void *userdata);
+
+PA_C_DECL_END
+
+#endif
diff --git a/src/pulse/simple.c b/src/pulse/simple.c
new file mode 100644
index 00000000..1072fb4d
--- /dev/null
+++ b/src/pulse/simple.c
@@ -0,0 +1,458 @@
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <pulse/pulseaudio.h>
+#include <pulse/thread-mainloop.h>
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/native-common.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+
+#include "simple.h"
+
+struct pa_simple {
+ pa_threaded_mainloop *mainloop;
+ pa_context *context;
+ pa_stream *stream;
+ pa_stream_direction_t direction;
+
+ const void *read_data;
+ size_t read_index, read_length;
+
+ int operation_success;
+};
+
+#define CHECK_VALIDITY_RETURN_ANY(rerror, expression, error, ret) do { \
+if (!(expression)) { \
+ if (rerror) \
+ *(rerror) = error; \
+ return (ret); \
+ } \
+} while(0);
+
+#define CHECK_SUCCESS_GOTO(p, rerror, expression, label) do { \
+if (!(expression)) { \
+ if (rerror) \
+ *(rerror) = pa_context_errno((p)->context); \
+ goto label; \
+ } \
+} while(0);
+
+#define CHECK_DEAD_GOTO(p, rerror, label) do { \
+if (!(p)->context || pa_context_get_state((p)->context) != PA_CONTEXT_READY || \
+ !(p)->stream || pa_stream_get_state((p)->stream) != PA_STREAM_READY) { \
+ if (((p)->context && pa_context_get_state((p)->context) == PA_CONTEXT_FAILED) || \
+ ((p)->stream && pa_stream_get_state((p)->stream) == PA_STREAM_FAILED)) { \
+ if (rerror) \
+ *(rerror) = pa_context_errno((p)->context); \
+ } else \
+ if (rerror) \
+ *(rerror) = PA_ERR_BADSTATE; \
+ goto label; \
+ } \
+} while(0);
+
+static void context_state_cb(pa_context *c, void *userdata) {
+ pa_simple *p = userdata;
+ pa_assert(c);
+ pa_assert(p);
+
+ switch (pa_context_get_state(c)) {
+ case PA_CONTEXT_READY:
+ case PA_CONTEXT_TERMINATED:
+ case PA_CONTEXT_FAILED:
+ pa_threaded_mainloop_signal(p->mainloop, 0);
+ break;
+
+ case PA_CONTEXT_UNCONNECTED:
+ case PA_CONTEXT_CONNECTING:
+ case PA_CONTEXT_AUTHORIZING:
+ case PA_CONTEXT_SETTING_NAME:
+ break;
+ }
+}
+
+static void stream_state_cb(pa_stream *s, void * userdata) {
+ pa_simple *p = userdata;
+ pa_assert(s);
+ pa_assert(p);
+
+ switch (pa_stream_get_state(s)) {
+
+ case PA_STREAM_READY:
+ case PA_STREAM_FAILED:
+ case PA_STREAM_TERMINATED:
+ pa_threaded_mainloop_signal(p->mainloop, 0);
+ break;
+
+ case PA_STREAM_UNCONNECTED:
+ case PA_STREAM_CREATING:
+ break;
+ }
+}
+
+static void stream_request_cb(pa_stream *s, size_t length, void *userdata) {
+ pa_simple *p = userdata;
+ pa_assert(p);
+
+ pa_threaded_mainloop_signal(p->mainloop, 0);
+}
+
+static void stream_latency_update_cb(pa_stream *s, void *userdata) {
+ pa_simple *p = userdata;
+
+ pa_assert(p);
+
+ pa_threaded_mainloop_signal(p->mainloop, 0);
+}
+
+pa_simple* pa_simple_new(
+ const char *server,
+ const char *name,
+ pa_stream_direction_t dir,
+ const char *dev,
+ const char *stream_name,
+ const pa_sample_spec *ss,
+ const pa_channel_map *map,
+ const pa_buffer_attr *attr,
+ int *rerror) {
+
+ pa_simple *p;
+ int error = PA_ERR_INTERNAL, r;
+
+ CHECK_VALIDITY_RETURN_ANY(rerror, !server || *server, PA_ERR_INVALID, NULL);
+ CHECK_VALIDITY_RETURN_ANY(rerror, dir == PA_STREAM_PLAYBACK || dir == PA_STREAM_RECORD, PA_ERR_INVALID, NULL);
+ CHECK_VALIDITY_RETURN_ANY(rerror, !dev || *dev, PA_ERR_INVALID, NULL);
+ CHECK_VALIDITY_RETURN_ANY(rerror, ss && pa_sample_spec_valid(ss), PA_ERR_INVALID, NULL);
+ CHECK_VALIDITY_RETURN_ANY(rerror, !map || (pa_channel_map_valid(map) && map->channels == ss->channels), PA_ERR_INVALID, NULL)
+
+ p = pa_xnew(pa_simple, 1);
+ p->context = NULL;
+ p->stream = NULL;
+ p->direction = dir;
+ p->read_data = NULL;
+ p->read_index = p->read_length = 0;
+
+ if (!(p->mainloop = pa_threaded_mainloop_new()))
+ goto fail;
+
+ if (!(p->context = pa_context_new(pa_threaded_mainloop_get_api(p->mainloop), name)))
+ goto fail;
+
+ pa_context_set_state_callback(p->context, context_state_cb, p);
+
+ if (pa_context_connect(p->context, server, 0, NULL) < 0) {
+ error = pa_context_errno(p->context);
+ goto fail;
+ }
+
+ pa_threaded_mainloop_lock(p->mainloop);
+
+ if (pa_threaded_mainloop_start(p->mainloop) < 0)
+ goto unlock_and_fail;
+
+ /* Wait until the context is ready */
+ pa_threaded_mainloop_wait(p->mainloop);
+
+ if (pa_context_get_state(p->context) != PA_CONTEXT_READY) {
+ error = pa_context_errno(p->context);
+ goto unlock_and_fail;
+ }
+
+ if (!(p->stream = pa_stream_new(p->context, stream_name, ss, map))) {
+ error = pa_context_errno(p->context);
+ goto unlock_and_fail;
+ }
+
+ pa_stream_set_state_callback(p->stream, stream_state_cb, p);
+ pa_stream_set_read_callback(p->stream, stream_request_cb, p);
+ pa_stream_set_write_callback(p->stream, stream_request_cb, p);
+ pa_stream_set_latency_update_callback(p->stream, stream_latency_update_cb, p);
+
+ if (dir == PA_STREAM_PLAYBACK)
+ r = pa_stream_connect_playback(p->stream, dev, attr, PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_AUTO_TIMING_UPDATE, NULL, NULL);
+ else
+ r = pa_stream_connect_record(p->stream, dev, attr, PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_AUTO_TIMING_UPDATE);
+
+ if (r < 0) {
+ error = pa_context_errno(p->context);
+ goto unlock_and_fail;
+ }
+
+ /* Wait until the stream is ready */
+ pa_threaded_mainloop_wait(p->mainloop);
+
+ /* Wait until the stream is ready */
+ if (pa_stream_get_state(p->stream) != PA_STREAM_READY) {
+ error = pa_context_errno(p->context);
+ goto unlock_and_fail;
+ }
+
+ pa_threaded_mainloop_unlock(p->mainloop);
+
+ return p;
+
+unlock_and_fail:
+ pa_threaded_mainloop_unlock(p->mainloop);
+
+fail:
+ if (rerror)
+ *rerror = error;
+ pa_simple_free(p);
+ return NULL;
+}
+
+void pa_simple_free(pa_simple *s) {
+ pa_assert(s);
+
+ if (s->mainloop)
+ pa_threaded_mainloop_stop(s->mainloop);
+
+ if (s->stream)
+ pa_stream_unref(s->stream);
+
+ if (s->context)
+ pa_context_unref(s->context);
+
+ if (s->mainloop)
+ pa_threaded_mainloop_free(s->mainloop);
+
+ pa_xfree(s);
+}
+
+int pa_simple_write(pa_simple *p, const void*data, size_t length, int *rerror) {
+ pa_assert(p);
+
+ CHECK_VALIDITY_RETURN_ANY(rerror, p->direction == PA_STREAM_PLAYBACK, PA_ERR_BADSTATE, -1);
+ CHECK_VALIDITY_RETURN_ANY(rerror, data && length, PA_ERR_INVALID, -1);
+
+ pa_threaded_mainloop_lock(p->mainloop);
+
+ CHECK_DEAD_GOTO(p, rerror, unlock_and_fail);
+
+ while (length > 0) {
+ size_t l;
+ int r;
+
+ while (!(l = pa_stream_writable_size(p->stream))) {
+ pa_threaded_mainloop_wait(p->mainloop);
+ CHECK_DEAD_GOTO(p, rerror, unlock_and_fail);
+ }
+
+ CHECK_SUCCESS_GOTO(p, rerror, l != (size_t) -1, unlock_and_fail);
+
+ if (l > length)
+ l = length;
+
+ r = pa_stream_write(p->stream, data, l, NULL, 0, PA_SEEK_RELATIVE);
+ CHECK_SUCCESS_GOTO(p, rerror, r >= 0, unlock_and_fail);
+
+ data = (const uint8_t*) data + l;
+ length -= l;
+ }
+
+ pa_threaded_mainloop_unlock(p->mainloop);
+ return 0;
+
+unlock_and_fail:
+ pa_threaded_mainloop_unlock(p->mainloop);
+ return -1;
+}
+
+int pa_simple_read(pa_simple *p, void*data, size_t length, int *rerror) {
+ pa_assert(p);
+
+ CHECK_VALIDITY_RETURN_ANY(rerror, p->direction == PA_STREAM_RECORD, PA_ERR_BADSTATE, -1);
+ CHECK_VALIDITY_RETURN_ANY(rerror, data && length, PA_ERR_INVALID, -1);
+
+ pa_threaded_mainloop_lock(p->mainloop);
+
+ CHECK_DEAD_GOTO(p, rerror, unlock_and_fail);
+
+ while (length > 0) {
+ size_t l;
+
+ while (!p->read_data) {
+ int r;
+
+ r = pa_stream_peek(p->stream, &p->read_data, &p->read_length);
+ CHECK_SUCCESS_GOTO(p, rerror, r == 0, unlock_and_fail);
+
+ if (!p->read_data) {
+ pa_threaded_mainloop_wait(p->mainloop);
+ CHECK_DEAD_GOTO(p, rerror, unlock_and_fail);
+ } else
+ p->read_index = 0;
+ }
+
+ l = p->read_length < length ? p->read_length : length;
+ memcpy(data, (const uint8_t*) p->read_data+p->read_index, l);
+
+ data = (uint8_t*) data + l;
+ length -= l;
+
+ p->read_index += l;
+ p->read_length -= l;
+
+ if (!p->read_length) {
+ int r;
+
+ r = pa_stream_drop(p->stream);
+ p->read_data = NULL;
+ p->read_length = 0;
+ p->read_index = 0;
+
+ CHECK_SUCCESS_GOTO(p, rerror, r == 0, unlock_and_fail);
+ }
+ }
+
+ pa_threaded_mainloop_unlock(p->mainloop);
+ return 0;
+
+unlock_and_fail:
+ pa_threaded_mainloop_unlock(p->mainloop);
+ return -1;
+}
+
+static void success_cb(pa_stream *s, int success, void *userdata) {
+ pa_simple *p = userdata;
+
+ pa_assert(s);
+ pa_assert(p);
+
+ p->operation_success = success;
+ pa_threaded_mainloop_signal(p->mainloop, 0);
+}
+
+int pa_simple_drain(pa_simple *p, int *rerror) {
+ pa_operation *o = NULL;
+
+ pa_assert(p);
+
+ CHECK_VALIDITY_RETURN_ANY(rerror, p->direction == PA_STREAM_PLAYBACK, PA_ERR_BADSTATE, -1);
+
+ pa_threaded_mainloop_lock(p->mainloop);
+ CHECK_DEAD_GOTO(p, rerror, unlock_and_fail);
+
+ o = pa_stream_drain(p->stream, success_cb, p);
+ CHECK_SUCCESS_GOTO(p, rerror, o, unlock_and_fail);
+
+ p->operation_success = 0;
+ while (pa_operation_get_state(o) != PA_OPERATION_DONE) {
+ pa_threaded_mainloop_wait(p->mainloop);
+ CHECK_DEAD_GOTO(p, rerror, unlock_and_fail);
+ }
+ CHECK_SUCCESS_GOTO(p, rerror, p->operation_success, unlock_and_fail);
+
+ pa_operation_unref(o);
+ pa_threaded_mainloop_unlock(p->mainloop);
+
+ return 0;
+
+unlock_and_fail:
+
+ if (o) {
+ pa_operation_cancel(o);
+ pa_operation_unref(o);
+ }
+
+ pa_threaded_mainloop_unlock(p->mainloop);
+ return -1;
+}
+
+int pa_simple_flush(pa_simple *p, int *rerror) {
+ pa_operation *o = NULL;
+
+ pa_assert(p);
+
+ CHECK_VALIDITY_RETURN_ANY(rerror, p->direction == PA_STREAM_PLAYBACK, PA_ERR_BADSTATE, -1);
+
+ pa_threaded_mainloop_lock(p->mainloop);
+ CHECK_DEAD_GOTO(p, rerror, unlock_and_fail);
+
+ o = pa_stream_flush(p->stream, success_cb, p);
+ CHECK_SUCCESS_GOTO(p, rerror, o, unlock_and_fail);
+
+ p->operation_success = 0;
+ while (pa_operation_get_state(o) != PA_OPERATION_DONE) {
+ pa_threaded_mainloop_wait(p->mainloop);
+ CHECK_DEAD_GOTO(p, rerror, unlock_and_fail);
+ }
+ CHECK_SUCCESS_GOTO(p, rerror, p->operation_success, unlock_and_fail);
+
+ pa_operation_unref(o);
+ pa_threaded_mainloop_unlock(p->mainloop);
+
+ return 0;
+
+unlock_and_fail:
+
+ if (o) {
+ pa_operation_cancel(o);
+ pa_operation_unref(o);
+ }
+
+ pa_threaded_mainloop_unlock(p->mainloop);
+ return -1;
+}
+
+pa_usec_t pa_simple_get_latency(pa_simple *p, int *rerror) {
+ pa_usec_t t;
+ int negative;
+
+ pa_assert(p);
+
+ pa_threaded_mainloop_lock(p->mainloop);
+
+ for (;;) {
+ CHECK_DEAD_GOTO(p, rerror, unlock_and_fail);
+
+ if (pa_stream_get_latency(p->stream, &t, &negative) >= 0)
+ break;
+
+ CHECK_SUCCESS_GOTO(p, rerror, pa_context_errno(p->context) == PA_ERR_NODATA, unlock_and_fail);
+
+ /* Wait until latency data is available again */
+ pa_threaded_mainloop_wait(p->mainloop);
+ }
+
+ pa_threaded_mainloop_unlock(p->mainloop);
+
+ return negative ? 0 : t;
+
+unlock_and_fail:
+
+ pa_threaded_mainloop_unlock(p->mainloop);
+ return (pa_usec_t) -1;
+}
+
diff --git a/src/pulse/simple.h b/src/pulse/simple.h
new file mode 100644
index 00000000..0ddd57e0
--- /dev/null
+++ b/src/pulse/simple.h
@@ -0,0 +1,149 @@
+#ifndef foosimplehfoo
+#define foosimplehfoo
+
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <sys/types.h>
+
+#include <pulse/sample.h>
+#include <pulse/channelmap.h>
+#include <pulse/def.h>
+#include <pulse/cdecl.h>
+
+/** \page simple Simple API
+ *
+ * \section overv_sec Overview
+ *
+ * The simple API is designed for applications with very basic sound
+ * playback or capture needs. It can only support a single stream per
+ * connection and has no handling of complex features like events, channel
+ * mappings and volume control. It is, however, very simple to use and
+ * quite sufficent for many programs.
+ *
+ * \section conn_sec Connecting
+ *
+ * The first step before using the sound system is to connect to the
+ * server. This is normally done this way:
+ *
+ * \code
+ * pa_simple *s;
+ * pa_sample_spec ss;
+ *
+ * ss.format = PA_SAMPLE_S16NE;
+ * ss.channels = 2;
+ * ss.rate = 44100;
+ *
+ * s = pa_simple_new(NULL, // Use the default server.
+ * "Fooapp", // Our application's name.
+ * PA_STREAM_PLAYBACK,
+ * NULL, // Use the default device.
+ * "Music", // Description of our stream.
+ * &ss, // Our sample format.
+ * NULL, // Use default channel map
+ * NULL, // Use default buffering attributes.
+ * NULL, // Ignore error code.
+ * );
+ * \endcode
+ *
+ * At this point a connected object is returned, or NULL if there was a
+ * problem connecting.
+ *
+ * \section transfer_sec Transferring data
+ *
+ * Once the connection is established to the server, data can start flowing.
+ * Using the connection is very similar to the normal read() and write()
+ * system calls. The main difference is that they're call pa_simple_read()
+ * and pa_simple_write(). Note that these operations always block.
+ *
+ * \section ctrl_sec Buffer control
+ *
+ * If a playback stream is used then a few other operations are available:
+ *
+ * \li pa_simple_drain() - Will wait for all sent data to finish playing.
+ * \li pa_simple_flush() - Will throw away all data currently in buffers.
+ * \li pa_simple_get_playback_latency() - Will return the total latency of
+ * the playback pipeline.
+ *
+ * \section cleanup_sec Cleanup
+ *
+ * Once playback or capture is complete, the connection should be closed
+ * and resources freed. This is done through:
+ *
+ * \code
+ * pa_simple_free(s);
+ * \endcode
+ */
+
+/** \file
+ * A simple but limited synchronous playback and recording
+ * API. This is a synchronous, simplified wrapper around the standard
+ * asynchronous API. */
+
+/** \example pacat-simple.c
+ * A simple playback tool using the simple API */
+
+/** \example parec-simple.c
+ * A simple recording tool using the simple API */
+
+PA_C_DECL_BEGIN
+
+/** \struct pa_simple
+ * An opaque simple connection object */
+typedef struct pa_simple pa_simple;
+
+/** Create a new connection to the server */
+pa_simple* pa_simple_new(
+ const char *server, /**< Server name, or NULL for default */
+ const char *name, /**< A descriptive name for this client (application name, ...) */
+ pa_stream_direction_t dir, /**< Open this stream for recording or playback? */
+ const char *dev, /**< Sink (resp. source) name, or NULL for default */
+ const char *stream_name, /**< A descriptive name for this client (application name, song title, ...) */
+ const pa_sample_spec *ss, /**< The sample type to use */
+ const pa_channel_map *map, /**< The channel map to use, or NULL for default */
+ const pa_buffer_attr *attr, /**< Buffering attributes, or NULL for default */
+ int *error /**< A pointer where the error code is stored when the routine returns NULL. It is OK to pass NULL here. */
+ );
+
+/** Close and free the connection to the server. The connection objects becomes invalid when this is called. */
+void pa_simple_free(pa_simple *s);
+
+/** Write some data to the server */
+int pa_simple_write(pa_simple *s, const void*data, size_t bytes, int *error);
+
+/** Wait until all data already written is played by the daemon */
+int pa_simple_drain(pa_simple *s, int *error);
+
+/** Read some data from the server */
+int pa_simple_read(pa_simple *s, void*data, size_t bytes, int *error);
+
+/** Return the playback latency. \since 0.5 */
+pa_usec_t pa_simple_get_latency(pa_simple *s, int *error);
+
+/** Flush the playback buffer. \since 0.5 */
+int pa_simple_flush(pa_simple *s, int *error);
+
+PA_C_DECL_END
+
+#endif
diff --git a/src/pulse/stream.c b/src/pulse/stream.c
new file mode 100644
index 00000000..c44323fc
--- /dev/null
+++ b/src/pulse/stream.c
@@ -0,0 +1,1839 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <pulse/def.h>
+#include <pulse/timeval.h>
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/pstream-util.h>
+#include <pulsecore/log.h>
+#include <pulsecore/hashmap.h>
+#include <pulsecore/macro.h>
+
+#include "internal.h"
+
+#define LATENCY_IPOL_INTERVAL_USEC (100000L)
+
+pa_stream *pa_stream_new(pa_context *c, const char *name, const pa_sample_spec *ss, const pa_channel_map *map) {
+ pa_stream *s;
+ int i;
+ pa_channel_map tmap;
+
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(c, ss && pa_sample_spec_valid(ss), PA_ERR_INVALID);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 12 || (ss->format != PA_SAMPLE_S32LE || ss->format != PA_SAMPLE_S32NE), PA_ERR_NOTSUPPORTED);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, !map || (pa_channel_map_valid(map) && map->channels == ss->channels), PA_ERR_INVALID);
+
+ if (!map)
+ PA_CHECK_VALIDITY_RETURN_NULL(c, map = pa_channel_map_init_auto(&tmap, ss->channels, PA_CHANNEL_MAP_DEFAULT), PA_ERR_INVALID);
+
+ s = pa_xnew(pa_stream, 1);
+ PA_REFCNT_INIT(s);
+ s->context = c;
+ s->mainloop = c->mainloop;
+
+ s->buffer_attr_not_ready = s->timing_info_not_ready = FALSE;
+
+ s->read_callback = NULL;
+ s->read_userdata = NULL;
+ s->write_callback = NULL;
+ s->write_userdata = NULL;
+ s->state_callback = NULL;
+ s->state_userdata = NULL;
+ s->overflow_callback = NULL;
+ s->overflow_userdata = NULL;
+ s->underflow_callback = NULL;
+ s->underflow_userdata = NULL;
+ s->latency_update_callback = NULL;
+ s->latency_update_userdata = NULL;
+ s->moved_callback = NULL;
+ s->moved_userdata = NULL;
+ s->suspended_callback = NULL;
+ s->suspended_userdata = NULL;
+
+ s->direction = PA_STREAM_NODIRECTION;
+ s->name = pa_xstrdup(name);
+ s->sample_spec = *ss;
+ s->channel_map = *map;
+ s->flags = 0;
+
+ s->channel = 0;
+ s->channel_valid = 0;
+ s->syncid = c->csyncid++;
+ s->stream_index = PA_INVALID_INDEX;
+ s->requested_bytes = 0;
+ s->state = PA_STREAM_UNCONNECTED;
+
+ s->manual_buffer_attr = FALSE;
+ memset(&s->buffer_attr, 0, sizeof(s->buffer_attr));
+
+ s->device_index = PA_INVALID_INDEX;
+ s->device_name = NULL;
+ s->suspended = FALSE;
+
+ s->peek_memchunk.index = 0;
+ s->peek_memchunk.length = 0;
+ s->peek_memchunk.memblock = NULL;
+ s->peek_data = NULL;
+
+ s->record_memblockq = NULL;
+
+ s->previous_time = 0;
+ s->timing_info_valid = 0;
+ s->read_index_not_before = 0;
+ s->write_index_not_before = 0;
+
+ for (i = 0; i < PA_MAX_WRITE_INDEX_CORRECTIONS; i++)
+ s->write_index_corrections[i].valid = 0;
+ s->current_write_index_correction = 0;
+
+ s->corked = 0;
+
+ s->cached_time_valid = 0;
+
+ s->auto_timing_update_event = NULL;
+ s->auto_timing_update_requested = 0;
+
+ /* Refcounting is strictly one-way: from the "bigger" to the "smaller" object. */
+ PA_LLIST_PREPEND(pa_stream, c->streams, s);
+ pa_stream_ref(s);
+
+ return s;
+}
+
+static void stream_free(pa_stream *s) {
+ pa_assert(s);
+ pa_assert(!s->context);
+ pa_assert(!s->channel_valid);
+
+ if (s->auto_timing_update_event) {
+ pa_assert(s->mainloop);
+ s->mainloop->time_free(s->auto_timing_update_event);
+ }
+
+ if (s->peek_memchunk.memblock) {
+ if (s->peek_data)
+ pa_memblock_release(s->peek_memchunk.memblock);
+ pa_memblock_unref(s->peek_memchunk.memblock);
+ }
+
+ if (s->record_memblockq)
+ pa_memblockq_free(s->record_memblockq);
+
+ pa_xfree(s->name);
+ pa_xfree(s->device_name);
+ pa_xfree(s);
+}
+
+void pa_stream_unref(pa_stream *s) {
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+ if (PA_REFCNT_DEC(s) <= 0)
+ stream_free(s);
+}
+
+pa_stream* pa_stream_ref(pa_stream *s) {
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+ PA_REFCNT_INC(s);
+ return s;
+}
+
+pa_stream_state_t pa_stream_get_state(pa_stream *s) {
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+ return s->state;
+}
+
+pa_context* pa_stream_get_context(pa_stream *s) {
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+ return s->context;
+}
+
+uint32_t pa_stream_get_index(pa_stream *s) {
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+ PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE, PA_INVALID_INDEX);
+
+ return s->stream_index;
+}
+
+void pa_stream_set_state(pa_stream *s, pa_stream_state_t st) {
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+ if (s->state == st)
+ return;
+
+ pa_stream_ref(s);
+
+ s->state = st;
+ if (s->state_callback)
+ s->state_callback(s, s->state_userdata);
+
+ if ((st == PA_STREAM_FAILED || st == PA_STREAM_TERMINATED) && s->context) {
+
+ /* Detach from context */
+ pa_operation *o, *n;
+
+ /* Unref all operatio object that point to us */
+ for (o = s->context->operations; o; o = n) {
+ n = o->next;
+
+ if (o->stream == s)
+ pa_operation_cancel(o);
+ }
+
+ /* Drop all outstanding replies for this stream */
+ if (s->context->pdispatch)
+ pa_pdispatch_unregister_reply(s->context->pdispatch, s);
+
+ if (s->channel_valid)
+ pa_dynarray_put((s->direction == PA_STREAM_PLAYBACK) ? s->context->playback_streams : s->context->record_streams, s->channel, NULL);
+
+ PA_LLIST_REMOVE(pa_stream, s->context->streams, s);
+ pa_stream_unref(s);
+
+ s->channel = 0;
+ s->channel_valid = 0;
+
+ s->context = NULL;
+
+ s->read_callback = NULL;
+ s->write_callback = NULL;
+ s->state_callback = NULL;
+ s->overflow_callback = NULL;
+ s->underflow_callback = NULL;
+ s->latency_update_callback = NULL;
+ }
+
+ pa_stream_unref(s);
+}
+
+void pa_command_stream_killed(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_context *c = userdata;
+ pa_stream *s;
+ uint32_t channel;
+
+ pa_assert(pd);
+ pa_assert(command == PA_COMMAND_PLAYBACK_STREAM_KILLED || command == PA_COMMAND_RECORD_STREAM_KILLED);
+ pa_assert(t);
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ pa_context_ref(c);
+
+ if (pa_tagstruct_getu32(t, &channel) < 0 ||
+ !pa_tagstruct_eof(t)) {
+ pa_context_fail(c, PA_ERR_PROTOCOL);
+ goto finish;
+ }
+
+ if (!(s = pa_dynarray_get(command == PA_COMMAND_PLAYBACK_STREAM_KILLED ? c->playback_streams : c->record_streams, channel)))
+ goto finish;
+
+ pa_context_set_error(c, PA_ERR_KILLED);
+ pa_stream_set_state(s, PA_STREAM_FAILED);
+
+finish:
+ pa_context_unref(c);
+}
+
+void pa_command_stream_moved(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_context *c = userdata;
+ pa_stream *s;
+ uint32_t channel;
+ const char *dn;
+ int suspended;
+ uint32_t di;
+
+ pa_assert(pd);
+ pa_assert(command == PA_COMMAND_PLAYBACK_STREAM_MOVED || command == PA_COMMAND_RECORD_STREAM_MOVED);
+ pa_assert(t);
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ pa_context_ref(c);
+
+ if (c->version < 12) {
+ pa_context_fail(c, PA_ERR_PROTOCOL);
+ goto finish;
+ }
+
+ if (pa_tagstruct_getu32(t, &channel) < 0 ||
+ pa_tagstruct_getu32(t, &di) < 0 ||
+ pa_tagstruct_gets(t, &dn) < 0 ||
+ pa_tagstruct_get_boolean(t, &suspended) < 0 ||
+ !pa_tagstruct_eof(t)) {
+ pa_context_fail(c, PA_ERR_PROTOCOL);
+ goto finish;
+ }
+
+ if (!dn || di == PA_INVALID_INDEX) {
+ pa_context_fail(c, PA_ERR_PROTOCOL);
+ goto finish;
+ }
+
+ if (!(s = pa_dynarray_get(command == PA_COMMAND_PLAYBACK_STREAM_MOVED ? c->playback_streams : c->record_streams, channel)))
+ goto finish;
+
+ pa_xfree(s->device_name);
+ s->device_name = pa_xstrdup(dn);
+ s->device_index = di;
+
+ s->suspended = suspended;
+
+ if (s->moved_callback)
+ s->moved_callback(s, s->moved_userdata);
+
+finish:
+ pa_context_unref(c);
+}
+
+void pa_command_stream_suspended(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_context *c = userdata;
+ pa_stream *s;
+ uint32_t channel;
+ int suspended;
+
+ pa_assert(pd);
+ pa_assert(command == PA_COMMAND_PLAYBACK_STREAM_SUSPENDED || command == PA_COMMAND_RECORD_STREAM_SUSPENDED);
+ pa_assert(t);
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ pa_context_ref(c);
+
+ if (c->version < 12) {
+ pa_context_fail(c, PA_ERR_PROTOCOL);
+ goto finish;
+ }
+
+ if (pa_tagstruct_getu32(t, &channel) < 0 ||
+ pa_tagstruct_get_boolean(t, &suspended) < 0 ||
+ !pa_tagstruct_eof(t)) {
+ pa_context_fail(c, PA_ERR_PROTOCOL);
+ goto finish;
+ }
+
+ if (!(s = pa_dynarray_get(command == PA_COMMAND_PLAYBACK_STREAM_SUSPENDED ? c->playback_streams : c->record_streams, channel)))
+ goto finish;
+
+ s->suspended = suspended;
+
+ if (s->suspended_callback)
+ s->suspended_callback(s, s->suspended_userdata);
+
+finish:
+ pa_context_unref(c);
+}
+
+void pa_command_request(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_stream *s;
+ pa_context *c = userdata;
+ uint32_t bytes, channel;
+
+ pa_assert(pd);
+ pa_assert(command == PA_COMMAND_REQUEST);
+ pa_assert(t);
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ pa_context_ref(c);
+
+ if (pa_tagstruct_getu32(t, &channel) < 0 ||
+ pa_tagstruct_getu32(t, &bytes) < 0 ||
+ !pa_tagstruct_eof(t)) {
+ pa_context_fail(c, PA_ERR_PROTOCOL);
+ goto finish;
+ }
+
+ if (!(s = pa_dynarray_get(c->playback_streams, channel)))
+ goto finish;
+
+ if (s->state == PA_STREAM_READY) {
+ s->requested_bytes += bytes;
+
+ if (s->requested_bytes > 0 && s->write_callback)
+ s->write_callback(s, s->requested_bytes, s->write_userdata);
+ }
+
+finish:
+ pa_context_unref(c);
+}
+
+void pa_command_overflow_or_underflow(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_stream *s;
+ pa_context *c = userdata;
+ uint32_t channel;
+
+ pa_assert(pd);
+ pa_assert(command == PA_COMMAND_OVERFLOW || command == PA_COMMAND_UNDERFLOW);
+ pa_assert(t);
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ pa_context_ref(c);
+
+ if (pa_tagstruct_getu32(t, &channel) < 0 ||
+ !pa_tagstruct_eof(t)) {
+ pa_context_fail(c, PA_ERR_PROTOCOL);
+ goto finish;
+ }
+
+ if (!(s = pa_dynarray_get(c->playback_streams, channel)))
+ goto finish;
+
+ if (s->state == PA_STREAM_READY) {
+
+ if (command == PA_COMMAND_OVERFLOW) {
+ if (s->overflow_callback)
+ s->overflow_callback(s, s->overflow_userdata);
+ } else if (command == PA_COMMAND_UNDERFLOW) {
+ if (s->underflow_callback)
+ s->underflow_callback(s, s->underflow_userdata);
+ }
+ }
+
+ finish:
+ pa_context_unref(c);
+}
+
+static void request_auto_timing_update(pa_stream *s, int force) {
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+ if (!(s->flags & PA_STREAM_AUTO_TIMING_UPDATE))
+ return;
+
+ if (s->state == PA_STREAM_READY &&
+ (force || !s->auto_timing_update_requested)) {
+ pa_operation *o;
+
+/* pa_log("automatically requesting new timing data"); */
+
+ if ((o = pa_stream_update_timing_info(s, NULL, NULL))) {
+ pa_operation_unref(o);
+ s->auto_timing_update_requested = 1;
+ }
+ }
+
+ if (s->auto_timing_update_event) {
+ struct timeval next;
+ pa_gettimeofday(&next);
+ pa_timeval_add(&next, LATENCY_IPOL_INTERVAL_USEC);
+ s->mainloop->time_restart(s->auto_timing_update_event, &next);
+ }
+}
+
+static void invalidate_indexes(pa_stream *s, int r, int w) {
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+/* pa_log("invalidate r:%u w:%u tag:%u", r, w, s->context->ctag); */
+
+ if (s->state != PA_STREAM_READY)
+ return;
+
+ if (w) {
+ s->write_index_not_before = s->context->ctag;
+
+ if (s->timing_info_valid)
+ s->timing_info.write_index_corrupt = 1;
+
+/* pa_log("write_index invalidated"); */
+ }
+
+ if (r) {
+ s->read_index_not_before = s->context->ctag;
+
+ if (s->timing_info_valid)
+ s->timing_info.read_index_corrupt = 1;
+
+/* pa_log("read_index invalidated"); */
+ }
+
+ if ((s->direction == PA_STREAM_PLAYBACK && r) ||
+ (s->direction == PA_STREAM_RECORD && w))
+ s->cached_time_valid = 0;
+
+ request_auto_timing_update(s, 1);
+}
+
+static void auto_timing_update_callback(PA_GCC_UNUSED pa_mainloop_api *m, PA_GCC_UNUSED pa_time_event *e, PA_GCC_UNUSED const struct timeval *tv, void *userdata) {
+ pa_stream *s = userdata;
+
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+/* pa_log("time event"); */
+
+ pa_stream_ref(s);
+ request_auto_timing_update(s, 0);
+ pa_stream_unref(s);
+}
+
+static void create_stream_complete(pa_stream *s) {
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+ pa_assert(s->state == PA_STREAM_CREATING);
+
+ if (s->buffer_attr_not_ready || s->timing_info_not_ready)
+ return;
+
+ pa_stream_set_state(s, PA_STREAM_READY);
+
+ if (s->requested_bytes > 0 && s->write_callback)
+ s->write_callback(s, s->requested_bytes, s->write_userdata);
+
+ if (s->flags & PA_STREAM_AUTO_TIMING_UPDATE) {
+ struct timeval tv;
+ pa_gettimeofday(&tv);
+ tv.tv_usec += LATENCY_IPOL_INTERVAL_USEC; /* every 100 ms */
+ pa_assert(!s->auto_timing_update_event);
+ s->auto_timing_update_event = s->mainloop->time_new(s->mainloop, &tv, &auto_timing_update_callback, s);
+ }
+}
+
+static void automatic_buffer_attr(pa_buffer_attr *attr, pa_sample_spec *ss) {
+ pa_assert(attr);
+ pa_assert(ss);
+
+ attr->tlength = pa_bytes_per_second(ss)/2;
+ attr->maxlength = (attr->tlength*3)/2;
+ attr->minreq = attr->tlength/50;
+ attr->prebuf = attr->tlength - attr->minreq;
+ attr->fragsize = attr->tlength/50;
+}
+
+void pa_create_stream_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_stream *s = userdata;
+
+ pa_assert(pd);
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+ pa_assert(s->state == PA_STREAM_CREATING);
+
+ pa_stream_ref(s);
+
+ if (command != PA_COMMAND_REPLY) {
+ if (pa_context_handle_error(s->context, command, t) < 0)
+ goto finish;
+
+ pa_stream_set_state(s, PA_STREAM_FAILED);
+ goto finish;
+ }
+
+ if (pa_tagstruct_getu32(t, &s->channel) < 0 ||
+ ((s->direction != PA_STREAM_UPLOAD) && pa_tagstruct_getu32(t, &s->stream_index) < 0) ||
+ ((s->direction != PA_STREAM_RECORD) && pa_tagstruct_getu32(t, &s->requested_bytes) < 0)) {
+ pa_context_fail(s->context, PA_ERR_PROTOCOL);
+ goto finish;
+ }
+
+ if (s->context->version >= 9) {
+ if (s->direction == PA_STREAM_PLAYBACK) {
+ if (pa_tagstruct_getu32(t, &s->buffer_attr.maxlength) < 0 ||
+ pa_tagstruct_getu32(t, &s->buffer_attr.tlength) < 0 ||
+ pa_tagstruct_getu32(t, &s->buffer_attr.prebuf) < 0 ||
+ pa_tagstruct_getu32(t, &s->buffer_attr.minreq) < 0) {
+ pa_context_fail(s->context, PA_ERR_PROTOCOL);
+ goto finish;
+ }
+ } else if (s->direction == PA_STREAM_RECORD) {
+ if (pa_tagstruct_getu32(t, &s->buffer_attr.maxlength) < 0 ||
+ pa_tagstruct_getu32(t, &s->buffer_attr.fragsize) < 0) {
+ pa_context_fail(s->context, PA_ERR_PROTOCOL);
+ goto finish;
+ }
+ }
+ }
+
+ if (s->context->version >= 12 && s->direction != PA_STREAM_UPLOAD) {
+ pa_sample_spec ss;
+ pa_channel_map cm;
+ const char *dn = NULL;
+ int suspended;
+
+ if (pa_tagstruct_get_sample_spec(t, &ss) < 0 ||
+ pa_tagstruct_get_channel_map(t, &cm) < 0 ||
+ pa_tagstruct_getu32(t, &s->device_index) < 0 ||
+ pa_tagstruct_gets(t, &dn) < 0 ||
+ pa_tagstruct_get_boolean(t, &suspended) < 0) {
+ pa_context_fail(s->context, PA_ERR_PROTOCOL);
+ goto finish;
+ }
+
+ if (!dn || s->device_index == PA_INVALID_INDEX ||
+ ss.channels != cm.channels ||
+ !pa_channel_map_valid(&cm) ||
+ !pa_sample_spec_valid(&ss) ||
+ (!(s->flags & PA_STREAM_FIX_FORMAT) && ss.format != s->sample_spec.format) ||
+ (!(s->flags & PA_STREAM_FIX_RATE) && ss.rate != s->sample_spec.rate) ||
+ (!(s->flags & PA_STREAM_FIX_CHANNELS) && !pa_channel_map_equal(&cm, &s->channel_map))) {
+ pa_context_fail(s->context, PA_ERR_PROTOCOL);
+ goto finish;
+ }
+
+ pa_xfree(s->device_name);
+ s->device_name = pa_xstrdup(dn);
+ s->suspended = suspended;
+
+ if (!s->manual_buffer_attr && pa_bytes_per_second(&ss) != pa_bytes_per_second(&s->sample_spec)) {
+ pa_buffer_attr attr;
+ pa_operation *o;
+
+ automatic_buffer_attr(&attr, &ss);
+
+ /* If we need to update the buffer metrics, we wait for
+ * the the OK for that call before we go to
+ * PA_STREAM_READY */
+
+ s->state = PA_STREAM_READY;
+ pa_assert_se(o = pa_stream_set_buffer_attr(s, &attr, NULL, NULL));
+ pa_operation_unref(o);
+ s->state = PA_STREAM_CREATING;
+
+ s->buffer_attr_not_ready = TRUE;
+ }
+
+ s->channel_map = cm;
+ s->sample_spec = ss;
+ }
+
+ if (!pa_tagstruct_eof(t)) {
+ pa_context_fail(s->context, PA_ERR_PROTOCOL);
+ goto finish;
+ }
+
+ if (s->direction == PA_STREAM_RECORD) {
+ pa_assert(!s->record_memblockq);
+
+ s->record_memblockq = pa_memblockq_new(
+ 0,
+ s->buffer_attr.maxlength,
+ 0,
+ pa_frame_size(&s->sample_spec),
+ 1,
+ 0,
+ NULL);
+ }
+
+ s->channel_valid = 1;
+ pa_dynarray_put((s->direction == PA_STREAM_RECORD) ? s->context->record_streams : s->context->playback_streams, s->channel, s);
+
+ if (s->direction != PA_STREAM_UPLOAD && s->flags & PA_STREAM_AUTO_TIMING_UPDATE) {
+
+ /* If automatic timing updates are active, we wait for the
+ * first timing update before going to PA_STREAM_READY
+ * state */
+
+ s->state = PA_STREAM_READY;
+ request_auto_timing_update(s, 1);
+ s->state = PA_STREAM_CREATING;
+
+ s->timing_info_not_ready = TRUE;
+ }
+
+ create_stream_complete(s);
+
+finish:
+ pa_stream_unref(s);
+}
+
+static int create_stream(
+ pa_stream_direction_t direction,
+ pa_stream *s,
+ const char *dev,
+ const pa_buffer_attr *attr,
+ pa_stream_flags_t flags,
+ const pa_cvolume *volume,
+ pa_stream *sync_stream) {
+
+ pa_tagstruct *t;
+ uint32_t tag;
+
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+ PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_UNCONNECTED, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY(s->context, !(flags & ~((direction != PA_STREAM_UPLOAD ?
+ PA_STREAM_START_CORKED|
+ PA_STREAM_INTERPOLATE_TIMING|
+ PA_STREAM_NOT_MONOTONOUS|
+ PA_STREAM_AUTO_TIMING_UPDATE|
+ PA_STREAM_NO_REMAP_CHANNELS|
+ PA_STREAM_NO_REMIX_CHANNELS|
+ PA_STREAM_FIX_FORMAT|
+ PA_STREAM_FIX_RATE|
+ PA_STREAM_FIX_CHANNELS|
+ PA_STREAM_DONT_MOVE : 0))), PA_ERR_INVALID);
+ PA_CHECK_VALIDITY(s->context, !volume || volume->channels == s->sample_spec.channels, PA_ERR_INVALID);
+ PA_CHECK_VALIDITY(s->context, !sync_stream || (direction == PA_STREAM_PLAYBACK && sync_stream->direction == PA_STREAM_PLAYBACK), PA_ERR_INVALID);
+
+ pa_stream_ref(s);
+
+ s->direction = direction;
+ s->flags = flags;
+
+ if (sync_stream)
+ s->syncid = sync_stream->syncid;
+
+ if (attr) {
+ s->buffer_attr = *attr;
+ s->manual_buffer_attr = TRUE;
+ } else {
+ /* half a second, with minimum request of 10 ms */
+ s->buffer_attr.tlength = pa_bytes_per_second(&s->sample_spec)/2;
+ s->buffer_attr.maxlength = (s->buffer_attr.tlength*3)/2;
+ s->buffer_attr.minreq = s->buffer_attr.tlength/50;
+ s->buffer_attr.prebuf = s->buffer_attr.tlength - s->buffer_attr.minreq;
+ s->buffer_attr.fragsize = s->buffer_attr.tlength/50;
+ s->manual_buffer_attr = FALSE;
+ }
+
+ if (!dev)
+ dev = s->direction == PA_STREAM_PLAYBACK ? s->context->conf->default_sink : s->context->conf->default_source;
+
+ t = pa_tagstruct_command(
+ s->context,
+ s->direction == PA_STREAM_PLAYBACK ? PA_COMMAND_CREATE_PLAYBACK_STREAM : PA_COMMAND_CREATE_RECORD_STREAM,
+ &tag);
+
+ pa_tagstruct_put(
+ t,
+ PA_TAG_STRING, s->name,
+ PA_TAG_SAMPLE_SPEC, &s->sample_spec,
+ PA_TAG_CHANNEL_MAP, &s->channel_map,
+ PA_TAG_U32, PA_INVALID_INDEX,
+ PA_TAG_STRING, dev,
+ PA_TAG_U32, s->buffer_attr.maxlength,
+ PA_TAG_BOOLEAN, !!(flags & PA_STREAM_START_CORKED),
+ PA_TAG_INVALID);
+
+ if (s->direction == PA_STREAM_PLAYBACK) {
+ pa_cvolume cv;
+
+ pa_tagstruct_put(
+ t,
+ PA_TAG_U32, s->buffer_attr.tlength,
+ PA_TAG_U32, s->buffer_attr.prebuf,
+ PA_TAG_U32, s->buffer_attr.minreq,
+ PA_TAG_U32, s->syncid,
+ PA_TAG_INVALID);
+
+ if (!volume)
+ volume = pa_cvolume_reset(&cv, s->sample_spec.channels);
+
+ pa_tagstruct_put_cvolume(t, volume);
+ } else
+ pa_tagstruct_putu32(t, s->buffer_attr.fragsize);
+
+ if (s->context->version >= 12 && s->direction != PA_STREAM_UPLOAD) {
+ pa_tagstruct_put(
+ t,
+ PA_TAG_BOOLEAN, flags & PA_STREAM_NO_REMAP_CHANNELS,
+ PA_TAG_BOOLEAN, flags & PA_STREAM_NO_REMIX_CHANNELS,
+ PA_TAG_BOOLEAN, flags & PA_STREAM_FIX_FORMAT,
+ PA_TAG_BOOLEAN, flags & PA_STREAM_FIX_RATE,
+ PA_TAG_BOOLEAN, flags & PA_STREAM_FIX_CHANNELS,
+ PA_TAG_BOOLEAN, flags & PA_STREAM_DONT_MOVE,
+ PA_TAG_BOOLEAN, flags & PA_STREAM_VARIABLE_RATE,
+ PA_TAG_INVALID);
+ }
+
+ pa_pstream_send_tagstruct(s->context->pstream, t);
+ pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_create_stream_callback, s, NULL);
+
+ pa_stream_set_state(s, PA_STREAM_CREATING);
+
+ pa_stream_unref(s);
+ return 0;
+}
+
+int pa_stream_connect_playback(
+ pa_stream *s,
+ const char *dev,
+ const pa_buffer_attr *attr,
+ pa_stream_flags_t flags,
+ pa_cvolume *volume,
+ pa_stream *sync_stream) {
+
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+ return create_stream(PA_STREAM_PLAYBACK, s, dev, attr, flags, volume, sync_stream);
+}
+
+int pa_stream_connect_record(
+ pa_stream *s,
+ const char *dev,
+ const pa_buffer_attr *attr,
+ pa_stream_flags_t flags) {
+
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+ return create_stream(PA_STREAM_RECORD, s, dev, attr, flags, NULL, NULL);
+}
+
+int pa_stream_write(
+ pa_stream *s,
+ const void *data,
+ size_t length,
+ void (*free_cb)(void *p),
+ int64_t offset,
+ pa_seek_mode_t seek) {
+
+ pa_memchunk chunk;
+
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+ pa_assert(data);
+
+ PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY(s->context, s->direction == PA_STREAM_PLAYBACK || s->direction == PA_STREAM_UPLOAD, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY(s->context, seek <= PA_SEEK_RELATIVE_END, PA_ERR_INVALID);
+ PA_CHECK_VALIDITY(s->context, s->direction == PA_STREAM_PLAYBACK || (seek == PA_SEEK_RELATIVE && offset == 0), PA_ERR_INVALID);
+
+ if (length <= 0)
+ return 0;
+
+ if (free_cb)
+ chunk.memblock = pa_memblock_new_user(s->context->mempool, (void*) data, length, free_cb, 1);
+ else {
+ void *tdata;
+ chunk.memblock = pa_memblock_new(s->context->mempool, length);
+ tdata = pa_memblock_acquire(chunk.memblock);
+ memcpy(tdata, data, length);
+ pa_memblock_release(chunk.memblock);
+ }
+
+ chunk.index = 0;
+ chunk.length = length;
+
+ pa_pstream_send_memblock(s->context->pstream, s->channel, offset, seek, &chunk);
+ pa_memblock_unref(chunk.memblock);
+
+ if (length < s->requested_bytes)
+ s->requested_bytes -= length;
+ else
+ s->requested_bytes = 0;
+
+ if (s->direction == PA_STREAM_PLAYBACK) {
+
+ /* Update latency request correction */
+ if (s->write_index_corrections[s->current_write_index_correction].valid) {
+
+ if (seek == PA_SEEK_ABSOLUTE) {
+ s->write_index_corrections[s->current_write_index_correction].corrupt = 0;
+ s->write_index_corrections[s->current_write_index_correction].absolute = 1;
+ s->write_index_corrections[s->current_write_index_correction].value = offset + length;
+ } else if (seek == PA_SEEK_RELATIVE) {
+ if (!s->write_index_corrections[s->current_write_index_correction].corrupt)
+ s->write_index_corrections[s->current_write_index_correction].value += offset + length;
+ } else
+ s->write_index_corrections[s->current_write_index_correction].corrupt = 1;
+ }
+
+ /* Update the write index in the already available latency data */
+ if (s->timing_info_valid) {
+
+ if (seek == PA_SEEK_ABSOLUTE) {
+ s->timing_info.write_index_corrupt = 0;
+ s->timing_info.write_index = offset + length;
+ } else if (seek == PA_SEEK_RELATIVE) {
+ if (!s->timing_info.write_index_corrupt)
+ s->timing_info.write_index += offset + length;
+ } else
+ s->timing_info.write_index_corrupt = 1;
+ }
+
+ if (!s->timing_info_valid || s->timing_info.write_index_corrupt)
+ request_auto_timing_update(s, 1);
+ }
+
+ return 0;
+}
+
+int pa_stream_peek(pa_stream *s, const void **data, size_t *length) {
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+ pa_assert(data);
+ pa_assert(length);
+
+ PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY(s->context, s->direction == PA_STREAM_RECORD, PA_ERR_BADSTATE);
+
+ if (!s->peek_memchunk.memblock) {
+
+ if (pa_memblockq_peek(s->record_memblockq, &s->peek_memchunk) < 0) {
+ *data = NULL;
+ *length = 0;
+ return 0;
+ }
+
+ s->peek_data = pa_memblock_acquire(s->peek_memchunk.memblock);
+ }
+
+ pa_assert(s->peek_data);
+ *data = (uint8_t*) s->peek_data + s->peek_memchunk.index;
+ *length = s->peek_memchunk.length;
+ return 0;
+}
+
+int pa_stream_drop(pa_stream *s) {
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+ PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY(s->context, s->direction == PA_STREAM_RECORD, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY(s->context, s->peek_memchunk.memblock, PA_ERR_BADSTATE);
+
+ pa_memblockq_drop(s->record_memblockq, s->peek_memchunk.length);
+
+ /* Fix the simulated local read index */
+ if (s->timing_info_valid && !s->timing_info.read_index_corrupt)
+ s->timing_info.read_index += s->peek_memchunk.length;
+
+ pa_assert(s->peek_data);
+ pa_memblock_release(s->peek_memchunk.memblock);
+ pa_memblock_unref(s->peek_memchunk.memblock);
+ s->peek_memchunk.length = 0;
+ s->peek_memchunk.index = 0;
+ s->peek_memchunk.memblock = NULL;
+
+ return 0;
+}
+
+size_t pa_stream_writable_size(pa_stream *s) {
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+ PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE, (size_t) -1);
+ PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->direction != PA_STREAM_RECORD, PA_ERR_BADSTATE, (size_t) -1);
+
+ return s->requested_bytes;
+}
+
+size_t pa_stream_readable_size(pa_stream *s) {
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+ PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE, (size_t) -1);
+ PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->direction == PA_STREAM_RECORD, PA_ERR_BADSTATE, (size_t) -1);
+
+ return pa_memblockq_get_length(s->record_memblockq);
+}
+
+pa_operation * pa_stream_drain(pa_stream *s, pa_stream_success_cb_t cb, void *userdata) {
+ pa_operation *o;
+ pa_tagstruct *t;
+ uint32_t tag;
+
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction == PA_STREAM_PLAYBACK, PA_ERR_BADSTATE);
+
+ o = pa_operation_new(s->context, s, (pa_operation_cb_t) cb, userdata);
+
+ t = pa_tagstruct_command(s->context, PA_COMMAND_DRAIN_PLAYBACK_STREAM, &tag);
+ pa_tagstruct_putu32(t, s->channel);
+ pa_pstream_send_tagstruct(s->context->pstream, t);
+ pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_stream_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+}
+
+static void stream_get_timing_info_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_operation *o = userdata;
+ struct timeval local, remote, now;
+ pa_timing_info *i;
+
+ pa_assert(pd);
+ pa_assert(o);
+ pa_assert(PA_REFCNT_VALUE(o) >= 1);
+
+ if (!o->context || !o->stream)
+ goto finish;
+
+ i = &o->stream->timing_info;
+
+/* pa_log("pre corrupt w:%u r:%u\n", !o->stream->timing_info_valid || i->write_index_corrupt,!o->stream->timing_info_valid || i->read_index_corrupt); */
+
+ o->stream->timing_info_valid = 0;
+ i->write_index_corrupt = 0;
+ i->read_index_corrupt = 0;
+
+/* pa_log("timing update %u\n", tag); */
+
+ if (command != PA_COMMAND_REPLY) {
+ if (pa_context_handle_error(o->context, command, t) < 0)
+ goto finish;
+
+ } else if (pa_tagstruct_get_usec(t, &i->sink_usec) < 0 ||
+ pa_tagstruct_get_usec(t, &i->source_usec) < 0 ||
+ pa_tagstruct_get_boolean(t, &i->playing) < 0 ||
+ pa_tagstruct_get_timeval(t, &local) < 0 ||
+ pa_tagstruct_get_timeval(t, &remote) < 0 ||
+ pa_tagstruct_gets64(t, &i->write_index) < 0 ||
+ pa_tagstruct_gets64(t, &i->read_index) < 0 ||
+ !pa_tagstruct_eof(t)) {
+ pa_context_fail(o->context, PA_ERR_PROTOCOL);
+ goto finish;
+
+ } else {
+ o->stream->timing_info_valid = 1;
+
+ pa_gettimeofday(&now);
+
+ /* Calculcate timestamps */
+ if (pa_timeval_cmp(&local, &remote) <= 0 && pa_timeval_cmp(&remote, &now) <= 0) {
+ /* local and remote seem to have synchronized clocks */
+
+ if (o->stream->direction == PA_STREAM_PLAYBACK)
+ i->transport_usec = pa_timeval_diff(&remote, &local);
+ else
+ i->transport_usec = pa_timeval_diff(&now, &remote);
+
+ i->synchronized_clocks = 1;
+ i->timestamp = remote;
+ } else {
+ /* clocks are not synchronized, let's estimate latency then */
+ i->transport_usec = pa_timeval_diff(&now, &local)/2;
+ i->synchronized_clocks = 0;
+ i->timestamp = local;
+ pa_timeval_add(&i->timestamp, i->transport_usec);
+ }
+
+ /* Invalidate read and write indexes if necessary */
+ if (tag < o->stream->read_index_not_before)
+ i->read_index_corrupt = 1;
+
+ if (tag < o->stream->write_index_not_before)
+ i->write_index_corrupt = 1;
+
+ if (o->stream->direction == PA_STREAM_PLAYBACK) {
+ /* Write index correction */
+
+ int n, j;
+ uint32_t ctag = tag;
+
+ /* Go through the saved correction values and add up the total correction.*/
+
+ for (n = 0, j = o->stream->current_write_index_correction+1;
+ n < PA_MAX_WRITE_INDEX_CORRECTIONS;
+ n++, j = (j + 1) % PA_MAX_WRITE_INDEX_CORRECTIONS) {
+
+ /* Step over invalid data or out-of-date data */
+ if (!o->stream->write_index_corrections[j].valid ||
+ o->stream->write_index_corrections[j].tag < ctag)
+ continue;
+
+ /* Make sure that everything is in order */
+ ctag = o->stream->write_index_corrections[j].tag+1;
+
+ /* Now fix the write index */
+ if (o->stream->write_index_corrections[j].corrupt) {
+ /* A corrupting seek was made */
+ i->write_index = 0;
+ i->write_index_corrupt = 1;
+ } else if (o->stream->write_index_corrections[j].absolute) {
+ /* An absolute seek was made */
+ i->write_index = o->stream->write_index_corrections[j].value;
+ i->write_index_corrupt = 0;
+ } else if (!i->write_index_corrupt) {
+ /* A relative seek was made */
+ i->write_index += o->stream->write_index_corrections[j].value;
+ }
+ }
+ }
+
+ if (o->stream->direction == PA_STREAM_RECORD) {
+ /* Read index correction */
+
+ if (!i->read_index_corrupt)
+ i->read_index -= pa_memblockq_get_length(o->stream->record_memblockq);
+ }
+
+ o->stream->cached_time_valid = 0;
+ }
+
+ o->stream->auto_timing_update_requested = 0;
+/* pa_log("post corrupt w:%u r:%u\n", i->write_index_corrupt || !o->stream->timing_info_valid, i->read_index_corrupt || !o->stream->timing_info_valid); */
+
+ /* Clear old correction entries */
+ if (o->stream->direction == PA_STREAM_PLAYBACK) {
+ int n;
+
+ for (n = 0; n < PA_MAX_WRITE_INDEX_CORRECTIONS; n++) {
+ if (!o->stream->write_index_corrections[n].valid)
+ continue;
+
+ if (o->stream->write_index_corrections[n].tag <= tag)
+ o->stream->write_index_corrections[n].valid = 0;
+ }
+ }
+
+ /* First, let's complete the initialization, if necessary. */
+ if (o->stream->state == PA_STREAM_CREATING) {
+ o->stream->timing_info_not_ready = FALSE;
+ create_stream_complete(o->stream);
+ }
+
+ if (o->stream->latency_update_callback)
+ o->stream->latency_update_callback(o->stream, o->stream->latency_update_userdata);
+
+ if (o->callback && o->stream && o->stream->state == PA_STREAM_READY) {
+ pa_stream_success_cb_t cb = (pa_stream_success_cb_t) o->callback;
+ cb(o->stream, o->stream->timing_info_valid, o->userdata);
+ }
+
+finish:
+
+ pa_operation_done(o);
+ pa_operation_unref(o);
+}
+
+pa_operation* pa_stream_update_timing_info(pa_stream *s, pa_stream_success_cb_t cb, void *userdata) {
+ uint32_t tag;
+ pa_operation *o;
+ pa_tagstruct *t;
+ struct timeval now;
+ int cidx = 0;
+
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE);
+
+ if (s->direction == PA_STREAM_PLAYBACK) {
+ /* Find a place to store the write_index correction data for this entry */
+ cidx = (s->current_write_index_correction + 1) % PA_MAX_WRITE_INDEX_CORRECTIONS;
+
+ /* Check if we could allocate a correction slot. If not, there are too many outstanding queries */
+ PA_CHECK_VALIDITY_RETURN_NULL(s->context, !s->write_index_corrections[cidx].valid, PA_ERR_INTERNAL);
+ }
+ o = pa_operation_new(s->context, s, (pa_operation_cb_t) cb, userdata);
+
+ t = pa_tagstruct_command(
+ s->context,
+ s->direction == PA_STREAM_PLAYBACK ? PA_COMMAND_GET_PLAYBACK_LATENCY : PA_COMMAND_GET_RECORD_LATENCY,
+ &tag);
+ pa_tagstruct_putu32(t, s->channel);
+ pa_tagstruct_put_timeval(t, pa_gettimeofday(&now));
+
+ pa_pstream_send_tagstruct(s->context->pstream, t);
+ pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, stream_get_timing_info_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ if (s->direction == PA_STREAM_PLAYBACK) {
+ /* Fill in initial correction data */
+ o->stream->current_write_index_correction = cidx;
+ o->stream->write_index_corrections[cidx].valid = 1;
+ o->stream->write_index_corrections[cidx].tag = tag;
+ o->stream->write_index_corrections[cidx].absolute = 0;
+ o->stream->write_index_corrections[cidx].value = 0;
+ o->stream->write_index_corrections[cidx].corrupt = 0;
+ }
+
+/* pa_log("requesting update %u\n", tag); */
+
+ return o;
+}
+
+void pa_stream_disconnect_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_stream *s = userdata;
+
+ pa_assert(pd);
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+ pa_stream_ref(s);
+
+ if (command != PA_COMMAND_REPLY) {
+ if (pa_context_handle_error(s->context, command, t) < 0)
+ goto finish;
+
+ pa_stream_set_state(s, PA_STREAM_FAILED);
+ goto finish;
+ } else if (!pa_tagstruct_eof(t)) {
+ pa_context_fail(s->context, PA_ERR_PROTOCOL);
+ goto finish;
+ }
+
+ pa_stream_set_state(s, PA_STREAM_TERMINATED);
+
+finish:
+ pa_stream_unref(s);
+}
+
+int pa_stream_disconnect(pa_stream *s) {
+ pa_tagstruct *t;
+ uint32_t tag;
+
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+ PA_CHECK_VALIDITY(s->context, s->channel_valid, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY(s->context, s->context->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+
+ pa_stream_ref(s);
+
+ t = pa_tagstruct_command(
+ s->context,
+ s->direction == PA_STREAM_PLAYBACK ? PA_COMMAND_DELETE_PLAYBACK_STREAM :
+ (s->direction == PA_STREAM_RECORD ? PA_COMMAND_DELETE_RECORD_STREAM : PA_COMMAND_DELETE_UPLOAD_STREAM),
+ &tag);
+ pa_tagstruct_putu32(t, s->channel);
+ pa_pstream_send_tagstruct(s->context->pstream, t);
+ pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_stream_disconnect_callback, s, NULL);
+
+ pa_stream_unref(s);
+ return 0;
+}
+
+void pa_stream_set_read_callback(pa_stream *s, pa_stream_request_cb_t cb, void *userdata) {
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+ s->read_callback = cb;
+ s->read_userdata = userdata;
+}
+
+void pa_stream_set_write_callback(pa_stream *s, pa_stream_request_cb_t cb, void *userdata) {
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+ s->write_callback = cb;
+ s->write_userdata = userdata;
+}
+
+void pa_stream_set_state_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata) {
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+ s->state_callback = cb;
+ s->state_userdata = userdata;
+}
+
+void pa_stream_set_overflow_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata) {
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+ s->overflow_callback = cb;
+ s->overflow_userdata = userdata;
+}
+
+void pa_stream_set_underflow_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata) {
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+ s->underflow_callback = cb;
+ s->underflow_userdata = userdata;
+}
+
+void pa_stream_set_latency_update_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata) {
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+ s->latency_update_callback = cb;
+ s->latency_update_userdata = userdata;
+}
+
+void pa_stream_set_moved_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata) {
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+ s->moved_callback = cb;
+ s->moved_userdata = userdata;
+}
+
+void pa_stream_set_suspended_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata) {
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+ s->suspended_callback = cb;
+ s->suspended_userdata = userdata;
+}
+
+void pa_stream_simple_ack_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_operation *o = userdata;
+ int success = 1;
+
+ pa_assert(pd);
+ pa_assert(o);
+ pa_assert(PA_REFCNT_VALUE(o) >= 1);
+
+ if (!o->context)
+ goto finish;
+
+ if (command != PA_COMMAND_REPLY) {
+ if (pa_context_handle_error(o->context, command, t) < 0)
+ goto finish;
+
+ success = 0;
+ } else if (!pa_tagstruct_eof(t)) {
+ pa_context_fail(o->context, PA_ERR_PROTOCOL);
+ goto finish;
+ }
+
+ if (o->callback) {
+ pa_stream_success_cb_t cb = (pa_stream_success_cb_t) o->callback;
+ cb(o->stream, success, o->userdata);
+ }
+
+finish:
+ pa_operation_done(o);
+ pa_operation_unref(o);
+}
+
+pa_operation* pa_stream_cork(pa_stream *s, int b, pa_stream_success_cb_t cb, void *userdata) {
+ pa_operation *o;
+ pa_tagstruct *t;
+ uint32_t tag;
+
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE);
+
+ s->corked = b;
+
+ o = pa_operation_new(s->context, s, (pa_operation_cb_t) cb, userdata);
+
+ t = pa_tagstruct_command(
+ s->context,
+ s->direction == PA_STREAM_PLAYBACK ? PA_COMMAND_CORK_PLAYBACK_STREAM : PA_COMMAND_CORK_RECORD_STREAM,
+ &tag);
+ pa_tagstruct_putu32(t, s->channel);
+ pa_tagstruct_put_boolean(t, !!b);
+ pa_pstream_send_tagstruct(s->context->pstream, t);
+ pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_stream_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ if (s->direction == PA_STREAM_PLAYBACK)
+ invalidate_indexes(s, 1, 0);
+
+ return o;
+}
+
+static pa_operation* stream_send_simple_command(pa_stream *s, uint32_t command, pa_stream_success_cb_t cb, void *userdata) {
+ pa_tagstruct *t;
+ pa_operation *o;
+ uint32_t tag;
+
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE);
+
+ o = pa_operation_new(s->context, s, (pa_operation_cb_t) cb, userdata);
+
+ t = pa_tagstruct_command(s->context, command, &tag);
+ pa_tagstruct_putu32(t, s->channel);
+ pa_pstream_send_tagstruct(s->context->pstream, t);
+ pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_stream_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+}
+
+pa_operation* pa_stream_flush(pa_stream *s, pa_stream_success_cb_t cb, void *userdata) {
+ pa_operation *o;
+
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE);
+
+ if ((o = stream_send_simple_command(s, s->direction == PA_STREAM_PLAYBACK ? PA_COMMAND_FLUSH_PLAYBACK_STREAM : PA_COMMAND_FLUSH_RECORD_STREAM, cb, userdata))) {
+
+ if (s->direction == PA_STREAM_PLAYBACK) {
+ if (s->write_index_corrections[s->current_write_index_correction].valid)
+ s->write_index_corrections[s->current_write_index_correction].corrupt = 1;
+
+ if (s->timing_info_valid)
+ s->timing_info.write_index_corrupt = 1;
+
+ if (s->buffer_attr.prebuf > 0)
+ invalidate_indexes(s, 1, 0);
+ else
+ request_auto_timing_update(s, 1);
+ } else
+ invalidate_indexes(s, 0, 1);
+ }
+
+ return o;
+}
+
+pa_operation* pa_stream_prebuf(pa_stream *s, pa_stream_success_cb_t cb, void *userdata) {
+ pa_operation *o;
+
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction == PA_STREAM_PLAYBACK, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->buffer_attr.prebuf > 0, PA_ERR_BADSTATE);
+
+ if ((o = stream_send_simple_command(s, PA_COMMAND_PREBUF_PLAYBACK_STREAM, cb, userdata)))
+ invalidate_indexes(s, 1, 0);
+
+ return o;
+}
+
+pa_operation* pa_stream_trigger(pa_stream *s, pa_stream_success_cb_t cb, void *userdata) {
+ pa_operation *o;
+
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction == PA_STREAM_PLAYBACK, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->buffer_attr.prebuf > 0, PA_ERR_BADSTATE);
+
+ if ((o = stream_send_simple_command(s, PA_COMMAND_TRIGGER_PLAYBACK_STREAM, cb, userdata)))
+ invalidate_indexes(s, 1, 0);
+
+ return o;
+}
+
+pa_operation* pa_stream_set_name(pa_stream *s, const char *name, pa_stream_success_cb_t cb, void *userdata) {
+ pa_operation *o;
+ pa_tagstruct *t;
+ uint32_t tag;
+
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+ pa_assert(name);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE);
+
+ o = pa_operation_new(s->context, s, (pa_operation_cb_t) cb, userdata);
+
+ t = pa_tagstruct_command(
+ s->context,
+ s->direction == PA_STREAM_RECORD ? PA_COMMAND_SET_RECORD_STREAM_NAME : PA_COMMAND_SET_PLAYBACK_STREAM_NAME,
+ &tag);
+ pa_tagstruct_putu32(t, s->channel);
+ pa_tagstruct_puts(t, name);
+ pa_pstream_send_tagstruct(s->context->pstream, t);
+ pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_stream_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+}
+
+int pa_stream_get_time(pa_stream *s, pa_usec_t *r_usec) {
+ pa_usec_t usec = 0;
+
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+ PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY(s->context, s->timing_info_valid, PA_ERR_NODATA);
+ PA_CHECK_VALIDITY(s->context, s->direction != PA_STREAM_PLAYBACK || !s->timing_info.read_index_corrupt, PA_ERR_NODATA);
+ PA_CHECK_VALIDITY(s->context, s->direction != PA_STREAM_RECORD || !s->timing_info.write_index_corrupt, PA_ERR_NODATA);
+
+ if (s->cached_time_valid)
+ /* We alredy calculated the time value for this timing info, so let's reuse it */
+ usec = s->cached_time;
+ else {
+ if (s->direction == PA_STREAM_PLAYBACK) {
+ /* The last byte that was written into the output device
+ * had this time value associated */
+ usec = pa_bytes_to_usec(s->timing_info.read_index < 0 ? 0 : (uint64_t) s->timing_info.read_index, &s->sample_spec);
+
+ if (!s->corked) {
+ /* Because the latency info took a little time to come
+ * to us, we assume that the real output time is actually
+ * a little ahead */
+ usec += s->timing_info.transport_usec;
+
+ /* However, the output device usually maintains a buffer
+ too, hence the real sample currently played is a little
+ back */
+ if (s->timing_info.sink_usec >= usec)
+ usec = 0;
+ else
+ usec -= s->timing_info.sink_usec;
+ }
+
+ } else if (s->direction == PA_STREAM_RECORD) {
+ /* The last byte written into the server side queue had
+ * this time value associated */
+ usec = pa_bytes_to_usec(s->timing_info.write_index < 0 ? 0 : (uint64_t) s->timing_info.write_index, &s->sample_spec);
+
+ if (!s->corked) {
+ /* Add transport latency */
+ usec += s->timing_info.transport_usec;
+
+ /* Add latency of data in device buffer */
+ usec += s->timing_info.source_usec;
+
+ /* If this is a monitor source, we need to correct the
+ * time by the playback device buffer */
+ if (s->timing_info.sink_usec >= usec)
+ usec = 0;
+ else
+ usec -= s->timing_info.sink_usec;
+ }
+ }
+
+ s->cached_time = usec;
+ s->cached_time_valid = 1;
+ }
+
+ /* Interpolate if requested */
+ if (s->flags & PA_STREAM_INTERPOLATE_TIMING) {
+
+ /* We just add the time that passed since the latency info was
+ * current */
+ if (!s->corked && s->timing_info.playing) {
+ struct timeval now;
+ usec += pa_timeval_diff(pa_gettimeofday(&now), &s->timing_info.timestamp);
+ }
+ }
+
+ /* Make sure the time runs monotonically */
+ if (!(s->flags & PA_STREAM_NOT_MONOTONOUS)) {
+ if (usec < s->previous_time)
+ usec = s->previous_time;
+ else
+ s->previous_time = usec;
+ }
+
+ if (r_usec)
+ *r_usec = usec;
+
+ return 0;
+}
+
+static pa_usec_t time_counter_diff(pa_stream *s, pa_usec_t a, pa_usec_t b, int *negative) {
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+ if (negative)
+ *negative = 0;
+
+ if (a >= b)
+ return a-b;
+ else {
+ if (negative && s->direction == PA_STREAM_RECORD) {
+ *negative = 1;
+ return b-a;
+ } else
+ return 0;
+ }
+}
+
+int pa_stream_get_latency(pa_stream *s, pa_usec_t *r_usec, int *negative) {
+ pa_usec_t t, c;
+ int r;
+ int64_t cindex;
+
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+ pa_assert(r_usec);
+
+ PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY(s->context, s->timing_info_valid, PA_ERR_NODATA);
+ PA_CHECK_VALIDITY(s->context, s->direction != PA_STREAM_PLAYBACK || !s->timing_info.write_index_corrupt, PA_ERR_NODATA);
+ PA_CHECK_VALIDITY(s->context, s->direction != PA_STREAM_RECORD || !s->timing_info.read_index_corrupt, PA_ERR_NODATA);
+
+ if ((r = pa_stream_get_time(s, &t)) < 0)
+ return r;
+
+ if (s->direction == PA_STREAM_PLAYBACK)
+ cindex = s->timing_info.write_index;
+ else
+ cindex = s->timing_info.read_index;
+
+ if (cindex < 0)
+ cindex = 0;
+
+ c = pa_bytes_to_usec(cindex, &s->sample_spec);
+
+ if (s->direction == PA_STREAM_PLAYBACK)
+ *r_usec = time_counter_diff(s, c, t, negative);
+ else
+ *r_usec = time_counter_diff(s, t, c, negative);
+
+ return 0;
+}
+
+const pa_timing_info* pa_stream_get_timing_info(pa_stream *s) {
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->timing_info_valid, PA_ERR_BADSTATE);
+
+ return &s->timing_info;
+}
+
+const pa_sample_spec* pa_stream_get_sample_spec(pa_stream *s) {
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+ return &s->sample_spec;
+}
+
+const pa_channel_map* pa_stream_get_channel_map(pa_stream *s) {
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+ return &s->channel_map;
+}
+
+const pa_buffer_attr* pa_stream_get_buffer_attr(pa_stream *s) {
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->context->version >= 9, PA_ERR_NODATA);
+
+ return &s->buffer_attr;
+}
+
+static void stream_set_buffer_attr_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_operation *o = userdata;
+ int success = 1;
+
+ pa_assert(pd);
+ pa_assert(o);
+ pa_assert(PA_REFCNT_VALUE(o) >= 1);
+
+ if (!o->context)
+ goto finish;
+
+ if (command != PA_COMMAND_REPLY) {
+ if (pa_context_handle_error(o->context, command, t) < 0)
+ goto finish;
+
+ success = 0;
+ } else {
+
+ if (o->stream->direction == PA_STREAM_PLAYBACK) {
+ if (pa_tagstruct_getu32(t, &o->stream->buffer_attr.maxlength) < 0 ||
+ pa_tagstruct_getu32(t, &o->stream->buffer_attr.tlength) < 0 ||
+ pa_tagstruct_getu32(t, &o->stream->buffer_attr.prebuf) < 0 ||
+ pa_tagstruct_getu32(t, &o->stream->buffer_attr.minreq) < 0) {
+ pa_context_fail(o->context, PA_ERR_PROTOCOL);
+ goto finish;
+ }
+ } else if (o->stream->direction == PA_STREAM_RECORD) {
+ if (pa_tagstruct_getu32(t, &o->stream->buffer_attr.maxlength) < 0 ||
+ pa_tagstruct_getu32(t, &o->stream->buffer_attr.fragsize) < 0) {
+ pa_context_fail(o->context, PA_ERR_PROTOCOL);
+ goto finish;
+ }
+ }
+
+ if (!pa_tagstruct_eof(t)) {
+ pa_context_fail(o->context, PA_ERR_PROTOCOL);
+ goto finish;
+ }
+
+ o->stream->manual_buffer_attr = TRUE;
+ }
+
+ if (o->stream->state == PA_STREAM_CREATING) {
+ o->stream->buffer_attr_not_ready = FALSE;
+ create_stream_complete(o->stream);
+ }
+
+ if (o->callback) {
+ pa_stream_success_cb_t cb = (pa_stream_success_cb_t) o->callback;
+ cb(o->stream, success, o->userdata);
+ }
+
+finish:
+ pa_operation_done(o);
+ pa_operation_unref(o);
+}
+
+
+pa_operation* pa_stream_set_buffer_attr(pa_stream *s, const pa_buffer_attr *attr, pa_stream_success_cb_t cb, void *userdata) {
+ pa_operation *o;
+ pa_tagstruct *t;
+ uint32_t tag;
+
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+ pa_assert(attr);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->context->version >= 12, PA_ERR_NOTSUPPORTED);
+
+ o = pa_operation_new(s->context, s, (pa_operation_cb_t) cb, userdata);
+
+ t = pa_tagstruct_command(
+ s->context,
+ s->direction == PA_STREAM_RECORD ? PA_COMMAND_SET_RECORD_STREAM_BUFFER_ATTR : PA_COMMAND_SET_PLAYBACK_STREAM_BUFFER_ATTR,
+ &tag);
+ pa_tagstruct_putu32(t, s->channel);
+
+ pa_tagstruct_putu32(t, attr->maxlength);
+
+ if (s->direction == PA_STREAM_PLAYBACK)
+ pa_tagstruct_put(
+ t,
+ PA_TAG_U32, attr->tlength,
+ PA_TAG_U32, attr->prebuf,
+ PA_TAG_U32, attr->minreq,
+ PA_TAG_INVALID);
+ else
+ pa_tagstruct_putu32(t, attr->fragsize);
+
+ pa_pstream_send_tagstruct(s->context->pstream, t);
+ pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, stream_set_buffer_attr_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+}
+
+uint32_t pa_stream_get_device_index(pa_stream *s) {
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+ PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE, PA_INVALID_INDEX);
+ PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE, PA_INVALID_INDEX);
+ PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->context->version >= 12, PA_ERR_NOTSUPPORTED, PA_INVALID_INDEX);
+ PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->device_index != PA_INVALID_INDEX, PA_ERR_BADSTATE, PA_INVALID_INDEX);
+
+ return s->device_index;
+}
+
+const char *pa_stream_get_device_name(pa_stream *s) {
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->context->version >= 12, PA_ERR_NOTSUPPORTED);
+ PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->device_name, PA_ERR_BADSTATE);
+
+ return s->device_name;
+}
+
+int pa_stream_is_suspended(pa_stream *s) {
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+ PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY(s->context, s->context->version >= 12, PA_ERR_NOTSUPPORTED);
+
+ return s->suspended;
+}
+
+static void stream_update_sample_rate_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_operation *o = userdata;
+ int success = 1;
+
+ pa_assert(pd);
+ pa_assert(o);
+ pa_assert(PA_REFCNT_VALUE(o) >= 1);
+
+ if (!o->context)
+ goto finish;
+
+ if (command != PA_COMMAND_REPLY) {
+ if (pa_context_handle_error(o->context, command, t) < 0)
+ goto finish;
+
+ success = 0;
+ } else {
+
+ if (!pa_tagstruct_eof(t)) {
+ pa_context_fail(o->context, PA_ERR_PROTOCOL);
+ goto finish;
+ }
+ }
+
+ o->stream->sample_spec.rate = PA_PTR_TO_UINT(o->private);
+ pa_assert(pa_sample_spec_valid(&o->stream->sample_spec));
+
+ if (o->callback) {
+ pa_stream_success_cb_t cb = (pa_stream_success_cb_t) o->callback;
+ cb(o->stream, success, o->userdata);
+ }
+
+finish:
+ pa_operation_done(o);
+ pa_operation_unref(o);
+}
+
+
+pa_operation *pa_stream_update_sample_rate(pa_stream *s, uint32_t rate, pa_stream_success_cb_t cb, void *userdata) {
+ pa_operation *o;
+ pa_tagstruct *t;
+ uint32_t tag;
+
+ pa_assert(s);
+ pa_assert(PA_REFCNT_VALUE(s) >= 1);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(s->context, rate > 0 && rate <= PA_RATE_MAX, PA_ERR_INVALID);
+ PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->flags & PA_STREAM_VARIABLE_RATE, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->context->version >= 12, PA_ERR_NOTSUPPORTED);
+
+ o = pa_operation_new(s->context, s, (pa_operation_cb_t) cb, userdata);
+ o->private = PA_UINT_TO_PTR(rate);
+
+ t = pa_tagstruct_command(
+ s->context,
+ s->direction == PA_STREAM_RECORD ? PA_COMMAND_UPDATE_RECORD_STREAM_SAMPLE_RATE : PA_COMMAND_UPDATE_PLAYBACK_STREAM_SAMPLE_RATE,
+ &tag);
+ pa_tagstruct_putu32(t, s->channel);
+ pa_tagstruct_putu32(t, rate);
+
+ pa_pstream_send_tagstruct(s->context->pstream, t);
+ pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, stream_update_sample_rate_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+
+}
diff --git a/src/pulse/stream.h b/src/pulse/stream.h
new file mode 100644
index 00000000..85473227
--- /dev/null
+++ b/src/pulse/stream.h
@@ -0,0 +1,515 @@
+#ifndef foostreamhfoo
+#define foostreamhfoo
+
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <sys/types.h>
+
+#include <pulse/sample.h>
+#include <pulse/channelmap.h>
+#include <pulse/volume.h>
+#include <pulse/def.h>
+#include <pulse/cdecl.h>
+#include <pulse/operation.h>
+
+/** \page streams Audio Streams
+ *
+ * \section overv_sec Overview
+ *
+ * Audio streams form the central functionality of the sound server. Data is
+ * routed, converted and mixed from several sources before it is passed along
+ * to a final output. Currently, there are three forms of audio streams:
+ *
+ * \li Playback streams - Data flows from the client to the server.
+ * \li Record streams - Data flows from the server to the client.
+ * \li Upload streams - Similar to playback streams, but the data is stored in
+ * the sample cache. See \ref scache for more information
+ * about controlling the sample cache.
+ *
+ * \section create_sec Creating
+ *
+ * To access a stream, a pa_stream object must be created using
+ * pa_stream_new(). At this point the audio sample format and mapping of
+ * channels must be specified. See \ref sample and \ref channelmap for more
+ * information about those structures.
+ *
+ * This first step will only create a client-side object, representing the
+ * stream. To use the stream, a server-side object must be created and
+ * associated with the local object. Depending on which type of stream is
+ * desired, a different function is needed:
+ *
+ * \li Playback stream - pa_stream_connect_playback()
+ * \li Record stream - pa_stream_connect_record()
+ * \li Upload stream - pa_stream_connect_upload() (see \ref scache)
+ *
+ * Similar to how connections are done in contexts, connecting a stream will
+ * not generate a pa_operation object. Also like contexts, the application
+ * should register a state change callback, using
+ * pa_stream_set_state_callback(), and wait for the stream to enter an active
+ * state.
+ *
+ * \subsection bufattr_subsec Buffer Attributes
+ *
+ * Playback and record streams always have a server side buffer as
+ * part of the data flow. The size of this buffer strikes a
+ * compromise between low latency and sensitivity for buffer
+ * overflows/underruns.
+ *
+ * The buffer metrics may be controlled by the application. They are
+ * described with a pa_buffer_attr structure which contains a number
+ * of fields:
+ *
+ * \li maxlength - The absolute maximum number of bytes that can be stored in
+ * the buffer. If this value is exceeded then data will be
+ * lost.
+ * \li tlength - The target length of a playback buffer. The server will only
+ * send requests for more data as long as the buffer has less
+ * than this number of bytes of data.
+ * \li prebuf - Number of bytes that need to be in the buffer before
+ * playback will commence. Start of playback can be forced using
+ * pa_stream_trigger() even though the prebuffer size hasn't been
+ * reached. If a buffer underrun occurs, this prebuffering will be
+ * again enabled. If the playback shall never stop in case of a buffer
+ * underrun, this value should be set to 0. In that case the read
+ * index of the output buffer overtakes the write index, and hence the
+ * fill level of the buffer is negative.
+ * \li minreq - Minimum free number of the bytes in the playback buffer before
+ * the server will request more data.
+ * \li fragsize - Maximum number of bytes that the server will push in one
+ * chunk for record streams.
+ *
+ * The server side playback buffers are indexed by a write and a read
+ * index. The application writes to the write index and the sound
+ * device reads from the read index. The read index is increased
+ * monotonically, while the write index may be freely controlled by
+ * the application. Substracting the read index from the write index
+ * will give you the current fill level of the buffer. The read/write
+ * indexes are 64bit values and measured in bytes, they will never
+ * wrap. The current read/write index may be queried using
+ * pa_stream_get_timing_info() (see below for more information). In
+ * case of a buffer underrun the read index is equal or larger than
+ * the write index. Unless the prebuf value is 0, PulseAudio will
+ * temporarily pause playback in such a case, and wait until the
+ * buffer is filled up to prebuf bytes again. If prebuf is 0, the
+ * read index may be larger than the write index, in which case
+ * silence is played. If the application writes data to indexes lower
+ * than the read index, the data is immediately lost.
+ *
+ * \section transfer_sec Transferring Data
+ *
+ * Once the stream is up, data can start flowing between the client and the
+ * server. Two different access models can be used to transfer the data:
+ *
+ * \li Asynchronous - The application register a callback using
+ * pa_stream_set_write_callback() and
+ * pa_stream_set_read_callback() to receive notifications
+ * that data can either be written or read.
+ * \li Polled - Query the library for available data/space using
+ * pa_stream_writable_size() and pa_stream_readable_size() and
+ * transfer data as needed. The sizes are stored locally, in the
+ * client end, so there is no delay when reading them.
+ *
+ * It is also possible to mix the two models freely.
+ *
+ * Once there is data/space available, it can be transferred using either
+ * pa_stream_write() for playback, or pa_stream_peek() / pa_stream_drop() for
+ * record. Make sure you do not overflow the playback buffers as data will be
+ * dropped.
+ *
+ * \section bufctl_sec Buffer Control
+ *
+ * The transfer buffers can be controlled through a number of operations:
+ *
+ * \li pa_stream_cork() - Start or stop the playback or recording.
+ * \li pa_stream_trigger() - Start playback immediatly and do not wait for
+ * the buffer to fill up to the set trigger level.
+ * \li pa_stream_prebuf() - Reenable the playback trigger level.
+ * \li pa_stream_drain() - Wait for the playback buffer to go empty. Will
+ * return a pa_operation object that will indicate when
+ * the buffer is completely drained.
+ * \li pa_stream_flush() - Drop all data from the playback buffer and do not
+ * wait for it to finish playing.
+ *
+ * \section seek_modes Seeking in the Playback Buffer
+ *
+ * A client application may freely seek in the playback buffer. To
+ * accomplish that the pa_stream_write() function takes a seek mode
+ * and an offset argument. The seek mode is one of:
+ *
+ * \li PA_SEEK_RELATIVE - seek relative to the current write index
+ * \li PA_SEEK_ABSOLUTE - seek relative to the beginning of the playback buffer, (i.e. the first that was ever played in the stream)
+ * \li PA_SEEK_RELATIVE_ON_READ - seek relative to the current read index. Use this to write data to the output buffer that should be played as soon as possible
+ * \li PA_SEEK_RELATIVE_END - seek relative to the last byte ever written.
+ *
+ * If an application just wants to append some data to the output
+ * buffer, PA_SEEK_RELATIVE and an offset of 0 should be used.
+ *
+ * After a call to pa_stream_write() the write index will be left at
+ * the position right after the last byte of the written data.
+ *
+ * \section latency_sec Latency
+ *
+ * A major problem with networked audio is the increased latency caused by
+ * the network. To remedy this, PulseAudio supports an advanced system of
+ * monitoring the current latency.
+ *
+ * To get the raw data needed to calculate latencies, call
+ * pa_stream_get_timing_info(). This will give you a pa_timing_info
+ * structure that contains everything that is known about the server
+ * side buffer transport delays and the backend active in the
+ * server. (Besides other things it contains the write and read index
+ * values mentioned above.)
+ *
+ * This structure is updated every time a
+ * pa_stream_update_timing_info() operation is executed. (i.e. before
+ * the first call to this function the timing information structure is
+ * not available!) Since it is a lot of work to keep this structure
+ * up-to-date manually, PulseAudio can do that automatically for you:
+ * if PA_STREAM_AUTO_TIMING_UPDATE is passed when connecting the
+ * stream PulseAudio will automatically update the structure every
+ * 100ms and every time a function is called that might invalidate the
+ * previously known timing data (such as pa_stream_write() or
+ * pa_stream_flush()). Please note however, that there always is a
+ * short time window when the data in the timing information structure
+ * is out-of-date. PulseAudio tries to mark these situations by
+ * setting the write_index_corrupt and read_index_corrupt fields
+ * accordingly.
+ *
+ * The raw timing data in the pa_timing_info structure is usually hard
+ * to deal with. Therefore a more simplistic interface is available:
+ * you can call pa_stream_get_time() or pa_stream_get_latency(). The
+ * former will return the current playback time of the hardware since
+ * the stream has been started. The latter returns the time a sample
+ * that you write now takes to be played by the hardware. These two
+ * functions base their calculations on the same data that is returned
+ * by pa_stream_get_timing_info(). Hence the same rules for keeping
+ * the timing data up-to-date apply here. In case the write or read
+ * index is corrupted, these two functions will fail with
+ * PA_ERR_NODATA set.
+ *
+ * Since updating the timing info structure usually requires a full
+ * network round trip and some applications monitor the timing very
+ * often PulseAudio offers a timing interpolation system. If
+ * PA_STREAM_INTERPOLATE_TIMING is passed when connecting the stream,
+ * pa_stream_get_time() and pa_stream_get_latency() will try to
+ * interpolate the current playback time/latency by estimating the
+ * number of samples that have been played back by the hardware since
+ * the last regular timing update. It is espcially useful to combine
+ * this option with PA_STREAM_AUTO_TIMING_UPDATE, which will enable
+ * you to monitor the current playback time/latency very precisely and
+ * very frequently without requiring a network round trip every time.
+ *
+ * \section flow_sec Overflow and underflow
+ *
+ * Even with the best precautions, buffers will sometime over - or
+ * underflow. To handle this gracefully, the application can be
+ * notified when this happens. Callbacks are registered using
+ * pa_stream_set_overflow_callback() and
+ * pa_stream_set_underflow_callback().
+ *
+ * \section sync_streams Sychronizing Multiple Playback Streams
+ *
+ * PulseAudio allows applications to fully synchronize multiple
+ * playback streams that are connected to the same output device. That
+ * means the streams will always be played back sample-by-sample
+ * synchronously. If stream operations like pa_stream_cork() are
+ * issued on one of the synchronized streams, they are simultaneously
+ * issued on the others.
+ *
+ * To synchronize a stream to another, just pass the "master" stream
+ * as last argument to pa_stream_connect_playack(). To make sure that
+ * the freshly created stream doesn't start playback right-away, make
+ * sure to pass PA_STREAM_START_CORKED and - after all streams have
+ * been created - uncork them all with a single call to
+ * pa_stream_cork() for the master stream.
+ *
+ * To make sure that a particular stream doesn't stop to play when a
+ * server side buffer underrun happens on it while the other
+ * synchronized streams continue playing and hence deviate you need to
+ * pass a "prebuf" pa_buffer_attr of 0 when connecting it.
+ *
+ * \section disc_sec Disconnecting
+ *
+ * When a stream has served is purpose it must be disconnected with
+ * pa_stream_disconnect(). If you only unreference it, then it will live on
+ * and eat resources both locally and on the server until you disconnect the
+ * context.
+ *
+ */
+
+/** \file
+ * Audio streams for input, output and sample upload */
+
+PA_C_DECL_BEGIN
+
+/** An opaque stream for playback or recording */
+typedef struct pa_stream pa_stream;
+
+/** A generic callback for operation completion */
+typedef void (*pa_stream_success_cb_t) (pa_stream*s, int success, void *userdata);
+
+/** A generic request callback */
+typedef void (*pa_stream_request_cb_t)(pa_stream *p, size_t bytes, void *userdata);
+
+/** A generic notification callback */
+typedef void (*pa_stream_notify_cb_t)(pa_stream *p, void *userdata);
+
+/** Create a new, unconnected stream with the specified name and sample type */
+pa_stream* pa_stream_new(
+ pa_context *c /**< The context to create this stream in */,
+ const char *name /**< A name for this stream */,
+ const pa_sample_spec *ss /**< The desired sample format */,
+ const pa_channel_map *map /**< The desired channel map, or NULL for default */);
+
+/** Decrease the reference counter by one */
+void pa_stream_unref(pa_stream *s);
+
+/** Increase the reference counter by one */
+pa_stream *pa_stream_ref(pa_stream *s);
+
+/** Return the current state of the stream */
+pa_stream_state_t pa_stream_get_state(pa_stream *p);
+
+/** Return the context this stream is attached to */
+pa_context* pa_stream_get_context(pa_stream *p);
+
+/** Return the sink input resp. source output index this stream is
+ * identified in the server with. This is useful for usage with the
+ * introspection functions, such as pa_context_get_sink_input_info()
+ * resp. pa_context_get_source_output_info(). */
+uint32_t pa_stream_get_index(pa_stream *s);
+
+/** Return the index of the sink or source this stream is connected to
+ * in the server. This is useful for usage with the introspection
+ * functions, such as pa_context_get_sink_info_by_index()
+ * resp. pa_context_get_source_info_by_index(). Please note that
+ * streams may be moved between sinks/sources and thus it is
+ * recommended to use pa_stream_set_moved_callback() to be notified
+ * about this. This function will return with PA_ERR_NOTSUPPORTED when the
+ * server is older than 0.9.8. \since 0.9.8 */
+uint32_t pa_stream_get_device_index(pa_stream *s);
+
+/** Return the name of the sink or source this stream is connected to
+ * in the server. This is useful for usage with the introspection
+ * functions, such as pa_context_get_sink_info_by_name()
+ * resp. pa_context_get_source_info_by_name(). Please note that
+ * streams may be moved between sinks/sources and thus it is
+ * recommended to use pa_stream_set_moved_callback() to be notified
+ * about this. This function will return with PA_ERR_NOTSUPPORTED when the
+ * server is older than 0.9.8. \since 0.9.8 */
+const char *pa_stream_get_device_name(pa_stream *s);
+
+/** Return 1 if the sink or source this stream is connected to has
+ * been suspended. This will return 0 if not, and negative on
+ * error. This function will return with PA_ERR_NOTSUPPORTED when the
+ * server is older than 0.9.8. \since 0.9.8 */
+int pa_stream_is_suspended(pa_stream *s);
+
+/** Connect the stream to a sink */
+int pa_stream_connect_playback(
+ pa_stream *s /**< The stream to connect to a sink */,
+ const char *dev /**< Name of the sink to connect to, or NULL for default */ ,
+ const pa_buffer_attr *attr /**< Buffering attributes, or NULL for default */,
+ pa_stream_flags_t flags /**< Additional flags, or 0 for default */,
+ pa_cvolume *volume /**< Initial volume, or NULL for default */,
+ pa_stream *sync_stream /**< Synchronize this stream with the specified one, or NULL for a standalone stream*/);
+
+/** Connect the stream to a source */
+int pa_stream_connect_record(
+ pa_stream *s /**< The stream to connect to a source */ ,
+ const char *dev /**< Name of the source to connect to, or NULL for default */,
+ const pa_buffer_attr *attr /**< Buffer attributes, or NULL for default */,
+ pa_stream_flags_t flags /**< Additional flags, or 0 for default */);
+
+/** Disconnect a stream from a source/sink */
+int pa_stream_disconnect(pa_stream *s);
+
+/** Write some data to the server (for playback sinks), if free_cb is
+ * non-NULL this routine is called when all data has been written out
+ * and an internal reference to the specified data is kept, the data
+ * is not copied. If NULL, the data is copied into an internal
+ * buffer. The client my freely seek around in the output buffer. For
+ * most applications passing 0 and PA_SEEK_RELATIVE as arguments for
+ * offset and seek should be useful.*/
+int pa_stream_write(
+ pa_stream *p /**< The stream to use */,
+ const void *data /**< The data to write */,
+ size_t bytes /**< The length of the data to write in bytes*/,
+ pa_free_cb_t free_cb /**< A cleanup routine for the data or NULL to request an internal copy */,
+ int64_t offset, /**< Offset for seeking, must be 0 for upload streams */
+ pa_seek_mode_t seek /**< Seek mode, must be PA_SEEK_RELATIVE for upload streams */);
+
+/** Read the next fragment from the buffer (for recording).
+ * data will point to the actual data and length will contain the size
+ * of the data in bytes (which can be less than a complete framgnet).
+ * Use pa_stream_drop() to actually remove the data from the
+ * buffer. If no data is available will return a NULL pointer \since 0.8 */
+int pa_stream_peek(
+ pa_stream *p /**< The stream to use */,
+ const void **data /**< Pointer to pointer that will point to data */,
+ size_t *bytes /**< The length of the data read in bytes */);
+
+/** Remove the current fragment on record streams. It is invalid to do this without first
+ * calling pa_stream_peek(). \since 0.8 */
+int pa_stream_drop(pa_stream *p);
+
+/** Return the number of bytes that may be written using pa_stream_write() */
+size_t pa_stream_writable_size(pa_stream *p);
+
+/** Return the number of bytes that may be read using pa_stream_read() \since 0.8 */
+size_t pa_stream_readable_size(pa_stream *p);
+
+/** Drain a playback stream. Use this for notification when the buffer is empty */
+pa_operation* pa_stream_drain(pa_stream *s, pa_stream_success_cb_t cb, void *userdata);
+
+/** Request a timing info structure update for a stream. Use
+ * pa_stream_get_timing_info() to get access to the raw timing data,
+ * or pa_stream_get_time() or pa_stream_get_latency() to get cleaned
+ * up values. */
+pa_operation* pa_stream_update_timing_info(pa_stream *p, pa_stream_success_cb_t cb, void *userdata);
+
+/** Set the callback function that is called whenever the state of the stream changes */
+void pa_stream_set_state_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata);
+
+/** Set the callback function that is called when new data may be
+ * written to the stream. */
+void pa_stream_set_write_callback(pa_stream *p, pa_stream_request_cb_t cb, void *userdata);
+
+/** Set the callback function that is called when new data is available from the stream.
+ * Return the number of bytes read. \since 0.8 */
+void pa_stream_set_read_callback(pa_stream *p, pa_stream_request_cb_t cb, void *userdata);
+
+/** Set the callback function that is called when a buffer overflow happens. (Only for playback streams) \since 0.8 */
+void pa_stream_set_overflow_callback(pa_stream *p, pa_stream_notify_cb_t cb, void *userdata);
+
+/** Set the callback function that is called when a buffer underflow happens. (Only for playback streams) \since 0.8 */
+void pa_stream_set_underflow_callback(pa_stream *p, pa_stream_notify_cb_t cb, void *userdata);
+
+/** Set the callback function that is called whenever a latency
+ * information update happens. Useful on PA_STREAM_AUTO_TIMING_UPDATE
+ * streams only. (Only for playback streams) \since 0.8.2 */
+void pa_stream_set_latency_update_callback(pa_stream *p, pa_stream_notify_cb_t cb, void *userdata);
+
+/** Set the callback function that is called whenever the stream is
+ * moved to a different sink/source. Use pa_stream_get_device_name()or
+ * pa_stream_get_device_index() to query the new sink/source. This
+ * notification is only generated when the server is at least
+ * 0.9.8. \since 0.9.8 */
+void pa_stream_set_moved_callback(pa_stream *p, pa_stream_notify_cb_t cb, void *userdata);
+
+/** Set the callback function that is called whenever the sink/source
+ * this stream is connected to is suspended or resumed. Use
+ * pa_stream_is_suspended() to query the new suspend status. Please
+ * note that the suspend status might also change when the stream is
+ * moved between devices. Thus if you call this function you very
+ * likely want to call pa_stream_set_moved_callback, too. This
+ * notification is only generated when the server is at least
+ * 0.9.8. \since 0.9.8 */
+void pa_stream_set_suspended_callback(pa_stream *p, pa_stream_notify_cb_t cb, void *userdata);
+
+/** Pause (or resume) playback of this stream temporarily. Available on both playback and recording streams. \since 0.3 */
+pa_operation* pa_stream_cork(pa_stream *s, int b, pa_stream_success_cb_t cb, void *userdata);
+
+/** Flush the playback buffer of this stream. Most of the time you're
+ * better off using the parameter delta of pa_stream_write() instead of this
+ * function. Available on both playback and recording streams. \since 0.3 */
+pa_operation* pa_stream_flush(pa_stream *s, pa_stream_success_cb_t cb, void *userdata);
+
+/** Reenable prebuffering as specified in the pa_buffer_attr
+ * structure. Available for playback streams only. \since 0.6 */
+pa_operation* pa_stream_prebuf(pa_stream *s, pa_stream_success_cb_t cb, void *userdata);
+
+/** Request immediate start of playback on this stream. This disables
+ * prebuffering as specified in the pa_buffer_attr
+ * structure, temporarily. Available for playback streams only. \since 0.3 */
+pa_operation* pa_stream_trigger(pa_stream *s, pa_stream_success_cb_t cb, void *userdata);
+
+/** Rename the stream. \since 0.5 */
+pa_operation* pa_stream_set_name(pa_stream *s, const char *name, pa_stream_success_cb_t cb, void *userdata);
+
+/** Return the current playback/recording time. This is based on the
+ * data in the timing info structure returned by
+ * pa_stream_get_timing_info(). This function will usually only return
+ * new data if a timing info update has been recieved. Only if timing
+ * interpolation has been requested (PA_STREAM_INTERPOLATE_TIMING)
+ * the data from the last timing update is used for an estimation of
+ * the current playback/recording time based on the local time that
+ * passed since the timing info structure has been acquired. The time
+ * value returned by this function is guaranteed to increase
+ * monotonically. (that means: the returned value is always greater or
+ * equal to the value returned on the last call) This behaviour can
+ * be disabled by using PA_STREAM_NOT_MONOTONOUS. This may be
+ * desirable to deal better with bad estimations of transport
+ * latencies, but may have strange effects if the application is not
+ * able to deal with time going 'backwards'. \since 0.6 */
+int pa_stream_get_time(pa_stream *s, pa_usec_t *r_usec);
+
+/** Return the total stream latency. This function is based on
+ * pa_stream_get_time(). In case the stream is a monitoring stream the
+ * result can be negative, i.e. the captured samples are not yet
+ * played. In this case *negative is set to 1. \since 0.6 */
+int pa_stream_get_latency(pa_stream *s, pa_usec_t *r_usec, int *negative);
+
+/** Return the latest raw timing data structure. The returned pointer
+ * points to an internal read-only instance of the timing
+ * structure. The user should make a copy of this structure if he
+ * wants to modify it. An in-place update to this data structure may
+ * be requested using pa_stream_update_timing_info(). If no
+ * pa_stream_update_timing_info() call was issued before, this
+ * function will fail with PA_ERR_NODATA. Please note that the
+ * write_index member field (and only this field) is updated on each
+ * pa_stream_write() call, not just when a timing update has been
+ * recieved. \since 0.8 */
+const pa_timing_info* pa_stream_get_timing_info(pa_stream *s);
+
+/** Return a pointer to the stream's sample specification. \since 0.6 */
+const pa_sample_spec* pa_stream_get_sample_spec(pa_stream *s);
+
+/** Return a pointer to the stream's channel map. \since 0.8 */
+const pa_channel_map* pa_stream_get_channel_map(pa_stream *s);
+
+/** Return the buffer metrics of the stream. Only valid after the
+ * stream has been connected successfuly and if the server is at least
+ * PulseAudio 0.9. \since 0.9.0 */
+const pa_buffer_attr* pa_stream_get_buffer_attr(pa_stream *s);
+
+/** Change the buffer metrics of the stream during playback. The
+ * server might have chosen different buffer metrics then
+ * requested. The selected metrics may be queried with
+ * pa_stream_get_buffer_attr() as soon as the callback is called. Only
+ * valid after the stream has been connected successfully and if the
+ * server is at least PulseAudio 0.9.8. \since 0.9.8 */
+pa_operation *pa_stream_set_buffer_attr(pa_stream *s, const pa_buffer_attr *attr, pa_stream_success_cb_t cb, void *userdata);
+
+/* Change the stream sampling rate during playback. You need to pass
+ * PA_STREAM_VARIABLE_RATE in the flags parameter of
+ * pa_stream_connect() if you plan to use this function. Only valid
+ * after the stream has been connected successfully and if the server
+ * is at least PulseAudio 0.9.8. \since 0.9.8 */
+pa_operation *pa_stream_update_sample_rate(pa_stream *s, uint32_t rate, pa_stream_success_cb_t cb, void *userdata);
+
+PA_C_DECL_END
+
+#endif
diff --git a/src/pulse/subscribe.c b/src/pulse/subscribe.c
new file mode 100644
index 00000000..580038cc
--- /dev/null
+++ b/src/pulse/subscribe.c
@@ -0,0 +1,92 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+
+#include <pulsecore/gccmacro.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/pstream-util.h>
+
+#include "internal.h"
+
+#include "subscribe.h"
+
+void pa_command_subscribe_event(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
+ pa_context *c = userdata;
+ pa_subscription_event_type_t e;
+ uint32_t idx;
+
+ pa_assert(pd);
+ pa_assert(command == PA_COMMAND_SUBSCRIBE_EVENT);
+ pa_assert(t);
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ pa_context_ref(c);
+
+ if (pa_tagstruct_getu32(t, &e) < 0 ||
+ pa_tagstruct_getu32(t, &idx) < 0 ||
+ !pa_tagstruct_eof(t)) {
+ pa_context_fail(c, PA_ERR_PROTOCOL);
+ goto finish;
+ }
+
+ if (c->subscribe_callback)
+ c->subscribe_callback(c, e, idx, c->subscribe_userdata);
+
+finish:
+ pa_context_unref(c);
+}
+
+
+pa_operation* pa_context_subscribe(pa_context *c, pa_subscription_mask_t m, pa_context_success_cb_t cb, void *userdata) {
+ pa_operation *o;
+ pa_tagstruct *t;
+ uint32_t tag;
+
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+
+ o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+
+ t = pa_tagstruct_command(c, PA_COMMAND_SUBSCRIBE, &tag);
+ pa_tagstruct_putu32(t, m);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+}
+
+void pa_context_set_subscribe_callback(pa_context *c, pa_context_subscribe_cb_t cb, void *userdata) {
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ c->subscribe_callback = cb;
+ c->subscribe_userdata = userdata;
+}
diff --git a/src/pulse/subscribe.h b/src/pulse/subscribe.h
new file mode 100644
index 00000000..c37ead57
--- /dev/null
+++ b/src/pulse/subscribe.h
@@ -0,0 +1,64 @@
+#ifndef foosubscribehfoo
+#define foosubscribehfoo
+
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <inttypes.h>
+
+#include <pulse/def.h>
+#include <pulse/context.h>
+#include <pulse/cdecl.h>
+
+/** \page subscribe Event Subscription
+ *
+ * \section overv_sec Overview
+ *
+ * The application can be notified, asynchronously, whenever the internal
+ * layout of the server changes. Possible notifications are desribed in the
+ * \ref pa_subscription_event_type and \ref pa_subscription_mask
+ * enumerations.
+ *
+ * The application sets the notification mask using pa_context_subscribe()
+ * and the function that will be called whenever a notification occurs using
+ * pa_context_set_subscribe_callback().
+ */
+
+/** \file
+ * Daemon introspection event subscription subsystem. */
+
+PA_C_DECL_BEGIN
+
+/** Subscription event callback prototype */
+typedef void (*pa_context_subscribe_cb_t)(pa_context *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata);
+
+/** Enable event notification */
+pa_operation* pa_context_subscribe(pa_context *c, pa_subscription_mask_t m, pa_context_success_cb_t cb, void *userdata);
+
+/** Set the context specific call back function that is called whenever the state of the daemon changes */
+void pa_context_set_subscribe_callback(pa_context *c, pa_context_subscribe_cb_t cb, void *userdata);
+
+PA_C_DECL_END
+
+#endif
diff --git a/src/pulse/thread-mainloop.c b/src/pulse/thread-mainloop.c
new file mode 100644
index 00000000..e8c956bb
--- /dev/null
+++ b/src/pulse/thread-mainloop.c
@@ -0,0 +1,231 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <signal.h>
+#include <stdio.h>
+
+#ifdef HAVE_POLL_H
+#include <poll.h>
+#else
+#include <pulsecore/poll.h>
+#endif
+
+#include <pulse/xmalloc.h>
+#include <pulse/mainloop.h>
+
+#include <pulsecore/log.h>
+#include <pulsecore/hashmap.h>
+#include <pulsecore/thread.h>
+#include <pulsecore/mutex.h>
+#include <pulsecore/macro.h>
+
+#include "thread-mainloop.h"
+
+struct pa_threaded_mainloop {
+ pa_mainloop *real_mainloop;
+ int n_waiting;
+
+ pa_thread* thread;
+ pa_mutex* mutex;
+ pa_cond* cond, *accept_cond;
+};
+
+static inline int in_worker(pa_threaded_mainloop *m) {
+ return pa_thread_self() == m->thread;
+}
+
+static int poll_func(struct pollfd *ufds, unsigned long nfds, int timeout, void *userdata) {
+ pa_mutex *mutex = userdata;
+ int r;
+
+ pa_assert(mutex);
+
+ /* Before entering poll() we unlock the mutex, so that
+ * avahi_simple_poll_quit() can succeed from another thread. */
+
+ pa_mutex_unlock(mutex);
+ r = poll(ufds, nfds, timeout);
+ pa_mutex_lock(mutex);
+
+ return r;
+}
+
+static void thread(void *userdata) {
+ pa_threaded_mainloop *m = userdata;
+
+#ifndef OS_IS_WIN32
+ sigset_t mask;
+
+ /* Make sure that signals are delivered to the main thread */
+ sigfillset(&mask);
+ pthread_sigmask(SIG_BLOCK, &mask, NULL);
+#endif
+
+ pa_mutex_lock(m->mutex);
+
+ pa_mainloop_run(m->real_mainloop, NULL);
+
+ pa_mutex_unlock(m->mutex);
+}
+
+pa_threaded_mainloop *pa_threaded_mainloop_new(void) {
+ pa_threaded_mainloop *m;
+
+ m = pa_xnew(pa_threaded_mainloop, 1);
+
+ if (!(m->real_mainloop = pa_mainloop_new())) {
+ pa_xfree(m);
+ return NULL;
+ }
+
+ m->mutex = pa_mutex_new(TRUE, TRUE);
+ m->cond = pa_cond_new();
+ m->accept_cond = pa_cond_new();
+ m->thread = NULL;
+
+ pa_mainloop_set_poll_func(m->real_mainloop, poll_func, m->mutex);
+
+ m->n_waiting = 0;
+
+ return m;
+}
+
+void pa_threaded_mainloop_free(pa_threaded_mainloop* m) {
+ pa_assert(m);
+
+ /* Make sure that this function is not called from the helper thread */
+ pa_assert((m->thread && !pa_thread_is_running(m->thread)) || !in_worker(m));
+
+ pa_threaded_mainloop_stop(m);
+
+ if (m->thread)
+ pa_thread_free(m->thread);
+
+ pa_mainloop_free(m->real_mainloop);
+
+ pa_mutex_free(m->mutex);
+ pa_cond_free(m->cond);
+ pa_cond_free(m->accept_cond);
+
+ pa_xfree(m);
+}
+
+int pa_threaded_mainloop_start(pa_threaded_mainloop *m) {
+ pa_assert(m);
+
+ pa_assert(!m->thread || !pa_thread_is_running(m->thread));
+
+ if (!(m->thread = pa_thread_new(thread, m)))
+ return -1;
+
+ return 0;
+}
+
+void pa_threaded_mainloop_stop(pa_threaded_mainloop *m) {
+ pa_assert(m);
+
+ if (!m->thread || !pa_thread_is_running(m->thread))
+ return;
+
+ /* Make sure that this function is not called from the helper thread */
+ pa_assert(!in_worker(m));
+
+ pa_mutex_lock(m->mutex);
+ pa_mainloop_quit(m->real_mainloop, 0);
+ pa_mutex_unlock(m->mutex);
+
+ pa_thread_join(m->thread);
+}
+
+void pa_threaded_mainloop_lock(pa_threaded_mainloop *m) {
+ pa_assert(m);
+
+ /* Make sure that this function is not called from the helper thread */
+ pa_assert(!m->thread || !pa_thread_is_running(m->thread) || !in_worker(m));
+
+ pa_mutex_lock(m->mutex);
+}
+
+void pa_threaded_mainloop_unlock(pa_threaded_mainloop *m) {
+ pa_assert(m);
+
+ /* Make sure that this function is not called from the helper thread */
+ pa_assert(!m->thread || !pa_thread_is_running(m->thread) || !in_worker(m));
+
+ pa_mutex_unlock(m->mutex);
+}
+
+void pa_threaded_mainloop_signal(pa_threaded_mainloop *m, int wait_for_accept) {
+ pa_assert(m);
+
+ pa_cond_signal(m->cond, 1);
+
+ if (wait_for_accept && m->n_waiting > 0)
+ pa_cond_wait(m->accept_cond, m->mutex);
+}
+
+void pa_threaded_mainloop_wait(pa_threaded_mainloop *m) {
+ pa_assert(m);
+
+ /* Make sure that this function is not called from the helper thread */
+ pa_assert(!m->thread || !pa_thread_is_running(m->thread) || !in_worker(m));
+
+ m->n_waiting ++;
+
+ pa_cond_wait(m->cond, m->mutex);
+
+ pa_assert(m->n_waiting > 0);
+ m->n_waiting --;
+}
+
+void pa_threaded_mainloop_accept(pa_threaded_mainloop *m) {
+ pa_assert(m);
+
+ /* Make sure that this function is not called from the helper thread */
+ pa_assert(!m->thread || !pa_thread_is_running(m->thread) || !in_worker(m));
+
+ pa_cond_signal(m->accept_cond, 0);
+}
+
+int pa_threaded_mainloop_get_retval(pa_threaded_mainloop *m) {
+ pa_assert(m);
+
+ return pa_mainloop_get_retval(m->real_mainloop);
+}
+
+pa_mainloop_api* pa_threaded_mainloop_get_api(pa_threaded_mainloop*m) {
+ pa_assert(m);
+
+ return pa_mainloop_get_api(m->real_mainloop);
+}
+
+int pa_threaded_mainloop_in_thread(pa_threaded_mainloop *m) {
+ pa_assert(m);
+
+ return m->thread && pa_thread_self() == m->thread;
+}
diff --git a/src/pulse/thread-mainloop.h b/src/pulse/thread-mainloop.h
new file mode 100644
index 00000000..ea08f72a
--- /dev/null
+++ b/src/pulse/thread-mainloop.h
@@ -0,0 +1,305 @@
+#ifndef foothreadmainloophfoo
+#define foothreadmainloophfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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/mainloop-api.h>
+#include <pulse/cdecl.h>
+
+PA_C_DECL_BEGIN
+
+/** \page threaded_mainloop Threaded Main Loop
+ *
+ * \section overv_sec Overview
+ *
+ * The threaded main loop implementation is a special version of the primary
+ * main loop implementation (see \ref mainloop). For the basic design, see
+ * its documentation.
+ *
+ * The added feature in the threaded main loop is that it spawns a new thread
+ * that runs the real main loop. This allows a synchronous application to use
+ * the asynchronous API without risking to stall the PulseAudio library.
+ *
+ * \section creat_sec Creation
+ *
+ * A pa_threaded_mainloop object is created using pa_threaded_mainloop_new().
+ * This will only allocate the required structures though, so to use it the
+ * thread must also be started. This is done through
+ * pa_threaded_mainloop_start(), after which you can start using the main loop.
+ *
+ * \section destr_sec Destruction
+ *
+ * When the PulseAudio connection has been terminated, the thread must be
+ * stopped and the resources freed. Stopping the thread is done using
+ * pa_threaded_mainloop_stop(), which must be called without the lock (see
+ * below) held. When that function returns, the thread is stopped and the
+ * pa_threaded_mainloop object can be freed using pa_threaded_mainloop_free().
+ *
+ * \section lock_sec Locking
+ *
+ * Since the PulseAudio API doesn't allow concurrent accesses to objects,
+ * a locking scheme must be used to guarantee safe usage. The threaded main
+ * loop API provides such a scheme through the functions
+ * pa_threaded_mainloop_lock() and pa_threaded_mainloop_unlock().
+ *
+ * The lock is recursive, so it's safe to use it multiple times from the same
+ * thread. Just make sure you call pa_threaded_mainloop_unlock() the same
+ * number of times you called pa_threaded_mainloop_lock().
+ *
+ * The lock needs to be held whenever you call any PulseAudio function that
+ * uses an object associated with this main loop. Make sure you do not hold
+ * on to the lock more than necessary though, as the threaded main loop stops
+ * while the lock is held.
+ *
+ * Example:
+ *
+ * \code
+ * void my_check_stream_func(pa_threaded_mainloop *m, pa_stream *s) {
+ * pa_stream_state_t state;
+ *
+ * pa_threaded_mainloop_lock(m);
+ *
+ * state = pa_stream_get_state(s);
+ *
+ * pa_threaded_mainloop_unlock(m);
+ *
+ * if (state == PA_STREAM_READY)
+ * printf("Stream is ready!");
+ * else
+ * printf("Stream is not ready!");
+ * }
+ * \endcode
+ *
+ * \section cb_sec Callbacks
+ *
+ * Callbacks in PulseAudio are asynchronous, so they require extra care when
+ * using them together with a threaded main loop.
+ *
+ * The easiest way to turn the callback based operations into synchronous
+ * ones, is to simply wait for the callback to be called and continue from
+ * there. This is the approach chosen in PulseAudio's threaded API.
+ *
+ * \subsection basic_subsec Basic callbacks
+ *
+ * For the basic case, where all that is required is to wait for the callback
+ * to be invoked, the code should look something like this:
+ *
+ * Example:
+ *
+ * \code
+ * static void my_drain_callback(pa_stream*s, int success, void *userdata) {
+ * pa_threaded_mainloop *m;
+ *
+ * m = (pa_threaded_mainloop*)userdata;
+ * assert(m);
+ *
+ * pa_threaded_mainloop_signal(m, 0);
+ * }
+ *
+ * void my_drain_stream_func(pa_threaded_mainloop *m, pa_stream *s) {
+ * pa_operation *o;
+ *
+ * pa_threaded_mainloop_lock(m);
+ *
+ * o = pa_stream_drain(s, my_drain_callback, m);
+ * assert(o);
+ *
+ * while (pa_operation_get_state(o) != OPERATION_DONE)
+ * pa_threaded_mainloop_wait(m);
+ *
+ * pa_operation_unref(o);
+ *
+ * pa_threaded_mainloop_unlock(m);
+ * }
+ * \endcode
+ *
+ * The main function, my_drain_stream_func(), will wait for the callback to
+ * be called using pa_threaded_mainloop_wait().
+ *
+ * If your application is multi-threaded, then this waiting must be done
+ * inside a while loop. The reason for this is that multiple threads might be
+ * using pa_threaded_mainloop_wait() at the same time. Each thread must
+ * therefore verify that it was its callback that was invoked.
+ *
+ * The callback, my_drain_callback(), indicates to the main function that it
+ * has been called using pa_threaded_mainloop_signal().
+ *
+ * As you can see, both pa_threaded_mainloop_wait() may only be called with
+ * the lock held. The same thing is true for pa_threaded_mainloop_signal(),
+ * but as the lock is held before the callback is invoked, you do not have to
+ * deal with that.
+ *
+ * The functions will not dead lock because the wait function will release
+ * the lock before waiting and then regrab it once it has been signaled.
+ * For those of you familiar with threads, the behaviour is that of a
+ * condition variable.
+ *
+ * \subsection data_subsec Data callbacks
+ *
+ * For many callbacks, simply knowing that they have been called is
+ * insufficient. The callback also receives some data that is desired. To
+ * access this data safely, we must extend our example a bit:
+ *
+ * \code
+ * static int *drain_result;
+ *
+ * static void my_drain_callback(pa_stream*s, int success, void *userdata) {
+ * pa_threaded_mainloop *m;
+ *
+ * m = (pa_threaded_mainloop*)userdata;
+ * assert(m);
+ *
+ * drain_result = &success;
+ *
+ * pa_threaded_mainloop_signal(m, 1);
+ * }
+ *
+ * void my_drain_stream_func(pa_threaded_mainloop *m, pa_stream *s) {
+ * pa_operation *o;
+ *
+ * pa_threaded_mainloop_lock(m);
+ *
+ * o = pa_stream_drain(s, my_drain_callback, m);
+ * assert(o);
+ *
+ * while (pa_operation_get_state(o) != OPERATION_DONE)
+ * pa_threaded_mainloop_wait(m);
+ *
+ * pa_operation_unref(o);
+ *
+ * if (*drain_result)
+ * printf("Success!");
+ * else
+ * printf("Bitter defeat...");
+ *
+ * pa_threaded_mainloop_accept(m);
+ *
+ * pa_threaded_mainloop_unlock(m);
+ * }
+ * \endcode
+ *
+ * The example is a bit silly as it would probably have been easier to just
+ * copy the contents of success, but for larger data structures this can be
+ * wasteful.
+ *
+ * The difference here compared to the basic callback is the 1 sent to
+ * pa_threaded_mainloop_signal() and the call to
+ * pa_threaded_mainloop_accept(). What will happen is that
+ * pa_threaded_mainloop_signal() will signal the main function and then stop.
+ * The main function is then free to use the data in the callback until
+ * pa_threaded_mainloop_accept() is called, which will allow the callback
+ * to continue.
+ *
+ * Note that pa_threaded_mainloop_accept() must be called some time between
+ * exiting the while loop and unlocking the main loop! Failure to do so will
+ * result in a race condition. I.e. it is not ok to release the lock and
+ * regrab it before calling pa_threaded_mainloop_accept().
+ *
+ * \subsection async_subsec Asynchronous callbacks
+ *
+ * PulseAudio also has callbacks that are completely asynchronous, meaning
+ * that they can be called at any time. The threading main loop API provides
+ * the locking mechanism to handle concurrent accesses, but nothing else.
+ * Applications will have to handle communication from the callback to the
+ * main program through some own system.
+ *
+ * The callbacks that are completely asynchronous are:
+ *
+ * \li State callbacks for contexts, streams, etc.
+ * \li Subscription notifications
+ */
+
+/** \file
+ *
+ * A thread based event loop implementation based on pa_mainloop. The
+ * event loop is run in a helper thread in the background. A few
+ * synchronization primitives are available to access the objects
+ * attached to the event loop safely. */
+
+/** An opaque threaded main loop object */
+typedef struct pa_threaded_mainloop pa_threaded_mainloop;
+
+/** Allocate a new threaded main loop object. You have to call
+ * pa_threaded_mainloop_start() before the event loop thread starts
+ * running. */
+pa_threaded_mainloop *pa_threaded_mainloop_new(void);
+
+/** Free a threaded main loop object. If the event loop thread is
+ * still running, it is terminated using pa_threaded_mainloop_stop()
+ * first. */
+void pa_threaded_mainloop_free(pa_threaded_mainloop* m);
+
+/** Start the event loop thread. */
+int pa_threaded_mainloop_start(pa_threaded_mainloop *m);
+
+/** Terminate the event loop thread cleanly. Make sure to unlock the
+ * mainloop object before calling this function. */
+void pa_threaded_mainloop_stop(pa_threaded_mainloop *m);
+
+/** Lock the event loop object, effectively blocking the event loop
+ * thread from processing events. You can use this to enforce
+ * exclusive access to all objects attached to the event loop. This
+ * lock is recursive. This function may not be called inside the event
+ * loop thread. Events that are dispatched from the event loop thread
+ * are executed with this lock held. */
+void pa_threaded_mainloop_lock(pa_threaded_mainloop *m);
+
+/** Unlock the event loop object, inverse of pa_threaded_mainloop_lock() */
+void pa_threaded_mainloop_unlock(pa_threaded_mainloop *m);
+
+/** Wait for an event to be signalled by the event loop thread. You
+ * can use this to pass data from the event loop thread to the main
+ * thread in synchronized fashion. This function may not be called
+ * inside the event loop thread. Prior to this call the event loop
+ * object needs to be locked using pa_threaded_mainloop_lock(). While
+ * waiting the lock will be released, immediately before returning it
+ * will be acquired again. */
+void pa_threaded_mainloop_wait(pa_threaded_mainloop *m);
+
+/** Signal all threads waiting for a signalling event in
+ * pa_threaded_mainloop_wait(). If wait_for_release is non-zero, do
+ * not return before the signal was accepted by a
+ * pa_threaded_mainloop_accept() call. While waiting for that condition
+ * the event loop object is unlocked. */
+void pa_threaded_mainloop_signal(pa_threaded_mainloop *m, int wait_for_accept);
+
+/** Accept a signal from the event thread issued with
+ * pa_threaded_mainloop_signal(). This call should only be used in
+ * conjunction with pa_threaded_mainloop_signal() with a non-zero
+ * wait_for_accept value. */
+void pa_threaded_mainloop_accept(pa_threaded_mainloop *m);
+
+/** Return the return value as specified with the main loop's quit() routine. */
+int pa_threaded_mainloop_get_retval(pa_threaded_mainloop *m);
+
+/** Return the abstract main loop abstraction layer vtable for this main loop. */
+pa_mainloop_api* pa_threaded_mainloop_get_api(pa_threaded_mainloop*m);
+
+/** Returns non-zero when called from withing the event loop thread. \since 0.9.7 */
+int pa_threaded_mainloop_in_thread(pa_threaded_mainloop *m);
+
+PA_C_DECL_END
+
+#endif
diff --git a/src/pulse/timeval.c b/src/pulse/timeval.c
new file mode 100644
index 00000000..70ceb71e
--- /dev/null
+++ b/src/pulse/timeval.c
@@ -0,0 +1,166 @@
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stddef.h>
+#include <sys/time.h>
+
+#ifdef HAVE_WINDOWS_H
+#include <windows.h>
+#endif
+
+#include <pulsecore/winsock.h>
+#include <pulsecore/macro.h>
+
+#include "timeval.h"
+
+struct timeval *pa_gettimeofday(struct timeval *tv) {
+#ifdef HAVE_GETTIMEOFDAY
+ pa_assert(tv);
+
+ pa_assert_se(gettimeofday(tv, NULL) == 0);
+ return tv;
+#elif defined(OS_IS_WIN32)
+ /*
+ * Copied from implementation by Steven Edwards (LGPL).
+ * Found on wine mailing list.
+ */
+
+#if defined(_MSC_VER) || defined(__BORLANDC__)
+#define EPOCHFILETIME (116444736000000000i64)
+#else
+#define EPOCHFILETIME (116444736000000000LL)
+#endif
+
+ FILETIME ft;
+ LARGE_INTEGER li;
+ __int64 t;
+
+ pa_assert(tv);
+
+ GetSystemTimeAsFileTime(&ft);
+ li.LowPart = ft.dwLowDateTime;
+ li.HighPart = ft.dwHighDateTime;
+ t = li.QuadPart; /* In 100-nanosecond intervals */
+ t -= EPOCHFILETIME; /* Offset to the Epoch time */
+ t /= 10; /* In microseconds */
+ tv->tv_sec = (time_t) (t / PA_USEC_PER_SEC);
+ tv->tv_usec = (suseconds_t) (t % PA_USEC_PER_SEC);
+
+ return tv;
+#else
+#error "Platform lacks gettimeofday() or equivalent function."
+#endif
+}
+
+pa_usec_t pa_timeval_diff(const struct timeval *a, const struct timeval *b) {
+ pa_usec_t r;
+
+ pa_assert(a);
+ pa_assert(b);
+
+ /* Check which whan is the earlier time and swap the two arguments if required. */
+ if (pa_timeval_cmp(a, b) < 0) {
+ const struct timeval *c;
+ c = a;
+ a = b;
+ b = c;
+ }
+
+ /* Calculate the second difference*/
+ r = ((pa_usec_t) a->tv_sec - b->tv_sec) * PA_USEC_PER_SEC;
+
+ /* Calculate the microsecond difference */
+ if (a->tv_usec > b->tv_usec)
+ r += ((pa_usec_t) a->tv_usec - b->tv_usec);
+ else if (a->tv_usec < b->tv_usec)
+ r -= ((pa_usec_t) b->tv_usec - a->tv_usec);
+
+ return r;
+}
+
+int pa_timeval_cmp(const struct timeval *a, const struct timeval *b) {
+ pa_assert(a);
+ pa_assert(b);
+
+ if (a->tv_sec < b->tv_sec)
+ return -1;
+
+ if (a->tv_sec > b->tv_sec)
+ return 1;
+
+ if (a->tv_usec < b->tv_usec)
+ return -1;
+
+ if (a->tv_usec > b->tv_usec)
+ return 1;
+
+ return 0;
+}
+
+pa_usec_t pa_timeval_age(const struct timeval *tv) {
+ struct timeval now;
+ pa_assert(tv);
+
+ return pa_timeval_diff(pa_gettimeofday(&now), tv);
+}
+
+struct timeval* pa_timeval_add(struct timeval *tv, pa_usec_t v) {
+ unsigned long secs;
+ pa_assert(tv);
+
+ secs = (unsigned long) (v/PA_USEC_PER_SEC);
+ tv->tv_sec += secs;
+ v -= ((pa_usec_t) secs) * PA_USEC_PER_SEC;
+
+ tv->tv_usec += (suseconds_t) v;
+
+ /* Normalize */
+ while (tv->tv_usec >= PA_USEC_PER_SEC) {
+ tv->tv_sec++;
+ tv->tv_usec -= PA_USEC_PER_SEC;
+ }
+
+ return tv;
+}
+
+struct timeval* pa_timeval_store(struct timeval *tv, pa_usec_t v) {
+ pa_assert(tv);
+
+ tv->tv_sec = v / PA_USEC_PER_SEC;
+ tv->tv_usec = v % PA_USEC_PER_SEC;
+
+ return tv;
+}
+
+pa_usec_t pa_timeval_load(const struct timeval *tv) {
+ pa_assert(tv);
+
+ return
+ (pa_usec_t) tv->tv_sec * PA_USEC_PER_SEC +
+ (pa_usec_t) tv->tv_usec;
+}
diff --git a/src/pulse/timeval.h b/src/pulse/timeval.h
new file mode 100644
index 00000000..65a0e513
--- /dev/null
+++ b/src/pulse/timeval.h
@@ -0,0 +1,67 @@
+#ifndef footimevalhfoo
+#define footimevalhfoo
+
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <pulse/cdecl.h>
+#include <pulse/sample.h>
+
+/** \file
+ * Utility functions for handling timeval calculations */
+
+PA_C_DECL_BEGIN
+
+#define PA_MSEC_PER_SEC 1000
+#define PA_USEC_PER_SEC 1000000
+#define PA_NSEC_PER_SEC 1000000000
+#define PA_USEC_PER_MSEC 1000
+
+struct timeval;
+
+/** Return the current timestamp, just like UNIX gettimeofday() */
+struct timeval *pa_gettimeofday(struct timeval *tv);
+
+/** Calculate the difference between the two specified timeval
+ * structs. */
+pa_usec_t pa_timeval_diff(const struct timeval *a, const struct timeval *b) PA_GCC_PURE;
+
+/** Compare the two timeval structs and return 0 when equal, negative when a < b, positive otherwse */
+int pa_timeval_cmp(const struct timeval *a, const struct timeval *b) PA_GCC_PURE;
+
+/** Return the time difference between now and the specified timestamp */
+pa_usec_t pa_timeval_age(const struct timeval *tv);
+
+/** Add the specified time inmicroseconds to the specified timeval structure */
+struct timeval* pa_timeval_add(struct timeval *tv, pa_usec_t v) PA_GCC_PURE;
+
+/** Store the specified uec value in the timeval struct. \since 0.9.7 */
+struct timeval* pa_timeval_store(struct timeval *tv, pa_usec_t v);
+
+/** Load the specified tv value and return it in usec. \since 0.9.7 */
+pa_usec_t pa_timeval_load(const struct timeval *tv);
+
+PA_C_DECL_END
+
+#endif
diff --git a/src/pulse/utf8.c b/src/pulse/utf8.c
new file mode 100644
index 00000000..b2f6c3bd
--- /dev/null
+++ b/src/pulse/utf8.c
@@ -0,0 +1,267 @@
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+/* This file is based on the GLIB utf8 validation functions. The
+ * original license text follows. */
+
+/* gutf8.c - Operations on UTF-8 strings.
+ *
+ * Copyright (C) 1999 Tom Tromey
+ * Copyright (C) 2000 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <string.h>
+
+#ifdef HAVE_ICONV
+#include <iconv.h>
+#endif
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/macro.h>
+
+#include "utf8.h"
+
+#define FILTER_CHAR '_'
+
+static inline int is_unicode_valid(uint32_t ch) {
+
+ if (ch >= 0x110000) /* End of unicode space */
+ return 0;
+ if ((ch & 0xFFFFF800) == 0xD800) /* Reserved area for UTF-16 */
+ return 0;
+ if ((ch >= 0xFDD0) && (ch <= 0xFDEF)) /* Reserved */
+ return 0;
+ if ((ch & 0xFFFE) == 0xFFFE) /* BOM (Byte Order Mark) */
+ return 0;
+
+ return 1;
+}
+
+static inline int is_continuation_char(uint8_t ch) {
+ if ((ch & 0xc0) != 0x80) /* 10xxxxxx */
+ return 0;
+ return 1;
+}
+
+static inline void merge_continuation_char(uint32_t *u_ch, uint8_t ch) {
+ *u_ch <<= 6;
+ *u_ch |= ch & 0x3f;
+}
+
+static char* utf8_validate(const char *str, char *output) {
+ uint32_t val = 0;
+ uint32_t min = 0;
+ const uint8_t *p, *last;
+ int size;
+ uint8_t *o;
+
+ pa_assert(str);
+
+ o = (uint8_t*) output;
+ for (p = (const uint8_t*) str; *p; p++) {
+ if (*p < 128) {
+ if (o)
+ *o = *p;
+ } else {
+ last = p;
+
+ if ((*p & 0xe0) == 0xc0) { /* 110xxxxx two-char seq. */
+ size = 2;
+ min = 128;
+ val = *p & 0x1e;
+ goto ONE_REMAINING;
+ } else if ((*p & 0xf0) == 0xe0) { /* 1110xxxx three-char seq.*/
+ size = 3;
+ min = (1 << 11);
+ val = *p & 0x0f;
+ goto TWO_REMAINING;
+ } else if ((*p & 0xf8) == 0xf0) { /* 11110xxx four-char seq */
+ size = 4;
+ min = (1 << 16);
+ val = *p & 0x07;
+ } else {
+ size = 1;
+ goto error;
+ }
+
+ p++;
+ if (!is_continuation_char(*p))
+ goto error;
+ merge_continuation_char(&val, *p);
+
+TWO_REMAINING:
+ p++;
+ if (!is_continuation_char(*p))
+ goto error;
+ merge_continuation_char(&val, *p);
+
+ONE_REMAINING:
+ p++;
+ if (!is_continuation_char(*p))
+ goto error;
+ merge_continuation_char(&val, *p);
+
+ if (val < min)
+ goto error;
+
+ if (!is_unicode_valid(val))
+ goto error;
+
+ if (o) {
+ memcpy(o, last, size);
+ o += size - 1;
+ }
+
+ if (o)
+ o++;
+
+ continue;
+
+error:
+ if (o) {
+ *o = FILTER_CHAR;
+ p = last; /* We retry at the next character */
+ } else
+ goto failure;
+ }
+
+ if (o)
+ o++;
+ }
+
+ if (o) {
+ *o = '\0';
+ return output;
+ }
+
+ return (char*) str;
+
+failure:
+ return NULL;
+}
+
+char* pa_utf8_valid (const char *str) {
+ return utf8_validate(str, NULL);
+}
+
+char* pa_utf8_filter (const char *str) {
+ char *new_str;
+
+ pa_assert(str);
+ new_str = pa_xnew(char, strlen(str) + 1);
+ return utf8_validate(str, new_str);
+}
+
+#ifdef HAVE_ICONV
+
+static char* iconv_simple(const char *str, const char *to, const char *from) {
+ char *new_str;
+ size_t len, inlen;
+ iconv_t cd;
+ ICONV_CONST char *inbuf;
+ char *outbuf;
+ size_t res, inbytes, outbytes;
+
+ pa_assert(str);
+ pa_assert(to);
+ pa_assert(from);
+
+ cd = iconv_open(to, from);
+ if (cd == (iconv_t)-1)
+ return NULL;
+
+ inlen = len = strlen(str) + 1;
+ new_str = pa_xnew(char, len);
+
+ for (;;) {
+ inbuf = (ICONV_CONST char*) str; /* Brain dead prototype for iconv() */
+ inbytes = inlen;
+ outbuf = new_str;
+ outbytes = len;
+
+ res = iconv(cd, &inbuf, &inbytes, &outbuf, &outbytes);
+
+ if (res != (size_t)-1)
+ break;
+
+ if (errno != E2BIG) {
+ pa_xfree(new_str);
+ new_str = NULL;
+ break;
+ }
+
+ pa_assert(inbytes != 0);
+
+ len += inbytes;
+ new_str = pa_xrealloc(new_str, len);
+ }
+
+ iconv_close(cd);
+
+ return new_str;
+}
+
+char* pa_utf8_to_locale (const char *str) {
+ return iconv_simple(str, "", "UTF-8");
+}
+
+char* pa_locale_to_utf8 (const char *str) {
+ return iconv_simple(str, "UTF-8", "");
+}
+
+#else
+
+char* pa_utf8_to_locale (const char *str) {
+ pa_assert(str);
+ return NULL;
+}
+
+char* pa_locale_to_utf8 (const char *str) {
+ pa_assert(str);
+ return NULL;
+}
+
+#endif
diff --git a/src/pulse/utf8.h b/src/pulse/utf8.h
new file mode 100644
index 00000000..1e08047c
--- /dev/null
+++ b/src/pulse/utf8.h
@@ -0,0 +1,50 @@
+#ifndef fooutf8hfoo
+#define fooutf8hfoo
+
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <pulse/cdecl.h>
+
+/** \file
+ * UTF8 Validation functions
+ */
+
+PA_C_DECL_BEGIN
+
+/** Test if the specified strings qualifies as valid UTF8. Return the string if so, otherwise NULL */
+char *pa_utf8_valid(const char *str) PA_GCC_PURE;
+
+/** Filter all invalid UTF8 characters from the specified string, returning a new fully UTF8 valid string. Don't forget to free the returned string with pa_xfree() */
+char *pa_utf8_filter(const char *str);
+
+/** Convert a UTF-8 string to the current locale. Free the string using pa_xfree(). */
+char* pa_utf8_to_locale (const char *str);
+
+/** Convert a string in the current locale to UTF-8. Free the string using pa_xfree(). */
+char* pa_locale_to_utf8 (const char *str);
+
+PA_C_DECL_END
+
+#endif
diff --git a/src/pulse/util.c b/src/pulse/util.c
new file mode 100644
index 00000000..d3ac9f66
--- /dev/null
+++ b/src/pulse/util.c
@@ -0,0 +1,262 @@
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+#ifdef HAVE_PWD_H
+#include <pwd.h>
+#endif
+
+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+
+#ifdef HAVE_NETDB_H
+#include <netdb.h>
+#endif
+
+#ifdef HAVE_WINDOWS_H
+#include <windows.h>
+#endif
+
+#ifdef HAVE_SYS_PRCTL_H
+#include <sys/prctl.h>
+#endif
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/winsock.h>
+#include <pulsecore/core-error.h>
+#include <pulsecore/log.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/macro.h>
+
+#include "util.h"
+
+char *pa_get_user_name(char *s, size_t l) {
+ char *p;
+ char buf[1024];
+
+#ifdef HAVE_PWD_H
+ struct passwd pw, *r;
+#endif
+
+ pa_assert(s);
+ pa_assert(l > 0);
+
+ if (!(p = getenv("USER")) && !(p = getenv("LOGNAME")) && !(p = getenv("USERNAME"))) {
+#ifdef HAVE_PWD_H
+
+#ifdef HAVE_GETPWUID_R
+ if (getpwuid_r(getuid(), &pw, buf, sizeof(buf), &r) != 0 || !r) {
+#else
+ /* XXX Not thread-safe, but needed on OSes (e.g. FreeBSD 4.X)
+ * that do not support getpwuid_r. */
+ if ((r = getpwuid(getuid())) == NULL) {
+#endif
+ pa_snprintf(s, l, "%lu", (unsigned long) getuid());
+ return s;
+ }
+
+ p = r->pw_name;
+
+#elif defined(OS_IS_WIN32) /* HAVE_PWD_H */
+ DWORD size = sizeof(buf);
+
+ if (!GetUserName(buf, &size))
+ return NULL;
+
+ p = buf;
+
+#else /* HAVE_PWD_H */
+ return NULL;
+#endif /* HAVE_PWD_H */
+ }
+
+ return pa_strlcpy(s, p, l);
+}
+
+char *pa_get_host_name(char *s, size_t l) {
+
+ pa_assert(s);
+ pa_assert(l > 0);
+
+ if (gethostname(s, l) < 0) {
+ pa_log("gethostname(): %s", pa_cstrerror(errno));
+ return NULL;
+ }
+
+ s[l-1] = 0;
+ return s;
+}
+
+char *pa_get_home_dir(char *s, size_t l) {
+ char *e;
+
+#ifdef HAVE_PWD_H
+ char buf[1024];
+ struct passwd pw, *r;
+#endif
+
+ pa_assert(s);
+ pa_assert(l > 0);
+
+ if ((e = getenv("HOME")))
+ return pa_strlcpy(s, e, l);
+
+ if ((e = getenv("USERPROFILE")))
+ return pa_strlcpy(s, e, l);
+
+#ifdef HAVE_PWD_H
+#ifdef HAVE_GETPWUID_R
+ if (getpwuid_r(getuid(), &pw, buf, sizeof(buf), &r) != 0 || !r) {
+ pa_log("getpwuid_r() failed");
+#else
+ /* XXX Not thread-safe, but needed on OSes (e.g. FreeBSD 4.X)
+ * that do not support getpwuid_r. */
+ if ((r = getpwuid(getuid())) == NULL) {
+ pa_log("getpwuid_r() failed");
+#endif
+ return NULL;
+ }
+
+ return pa_strlcpy(s, r->pw_dir, l);
+#else /* HAVE_PWD_H */
+ return NULL;
+#endif
+}
+
+char *pa_get_binary_name(char *s, size_t l) {
+
+ pa_assert(s);
+ pa_assert(l > 0);
+
+#if defined(OS_IS_WIN32)
+ {
+ char path[PATH_MAX];
+
+ if (GetModuleFileName(NULL, path, PATH_MAX))
+ return pa_strlcpy(s, pa_path_get_filename(path), l);
+ }
+#endif
+
+#ifdef __linux__
+ {
+ char *rp;
+ /* This works on Linux only */
+
+ if ((rp = pa_readlink("/proc/self/exe"))) {
+ pa_strlcpy(s, pa_path_get_filename(rp), l);
+ pa_xfree(rp);
+ return s;
+ }
+ }
+
+#endif
+
+#if defined(HAVE_SYS_PRCTL_H) && defined(PR_GET_NAME)
+ {
+
+ #ifndef TASK_COMM_LEN
+ /* Actually defined in linux/sched.h */
+ #define TASK_COMM_LEN 16
+ #endif
+
+ char tcomm[TASK_COMM_LEN+1];
+ memset(tcomm, 0, sizeof(tcomm));
+
+ /* This works on Linux only */
+ if (prctl(PR_GET_NAME, (unsigned long) tcomm, 0, 0, 0) == 0)
+ return pa_strlcpy(s, tcomm, l);
+
+ }
+#endif
+
+ return NULL;
+}
+
+char *pa_path_get_filename(const char *p) {
+ char *fn;
+
+ pa_assert(p);
+
+ if ((fn = strrchr(p, PA_PATH_SEP_CHAR)))
+ return fn+1;
+
+ return (char*) p;
+}
+
+char *pa_get_fqdn(char *s, size_t l) {
+ char hn[256];
+#ifdef HAVE_GETADDRINFO
+ struct addrinfo *a, hints;
+#endif
+
+ pa_assert(s);
+ pa_assert(l > 0);
+
+ if (!pa_get_host_name(hn, sizeof(hn)))
+ return NULL;
+
+#ifdef HAVE_GETADDRINFO
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_flags = AI_CANONNAME;
+
+ if (getaddrinfo(hn, NULL, &hints, &a) < 0 || !a || !a->ai_canonname || !*a->ai_canonname)
+ return pa_strlcpy(s, hn, l);
+
+ pa_strlcpy(s, a->ai_canonname, l);
+ freeaddrinfo(a);
+ return s;
+#else
+ return pa_strlcpy(s, hn, l);
+#endif
+}
+
+int pa_msleep(unsigned long t) {
+#ifdef OS_IS_WIN32
+ Sleep(t);
+ return 0;
+#elif defined(HAVE_NANOSLEEP)
+ struct timespec ts;
+
+ ts.tv_sec = t/1000;
+ ts.tv_nsec = (t % 1000) * 1000000;
+
+ return nanosleep(&ts, NULL);
+#else
+#error "Platform lacks a sleep function."
+#endif
+}
diff --git a/src/pulse/util.h b/src/pulse/util.h
new file mode 100644
index 00000000..764678e5
--- /dev/null
+++ b/src/pulse/util.h
@@ -0,0 +1,62 @@
+#ifndef fooutilhfoo
+#define fooutilhfoo
+
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <stddef.h>
+
+#include <pulse/cdecl.h>
+
+/** \file
+ * Assorted utility functions */
+
+PA_C_DECL_BEGIN
+
+/** Return the current username in the specified string buffer. */
+char *pa_get_user_name(char *s, size_t l);
+
+/** Return the current hostname in the specified buffer. */
+char *pa_get_host_name(char *s, size_t l);
+
+/** Return the fully qualified domain name in s */
+char *pa_get_fqdn(char *s, size_t l);
+
+/** Return the home directory of the current user */
+char *pa_get_home_dir(char *s, size_t l);
+
+/** Return the binary file name of the current process. This is not
+ * supported on all architectures, in which case NULL is returned. */
+char *pa_get_binary_name(char *s, size_t l);
+
+/** Return a pointer to the filename inside a path (which is the last
+ * component). */
+char *pa_path_get_filename(const char *p);
+
+/** Wait t milliseconds */
+int pa_msleep(unsigned long t);
+
+PA_C_DECL_END
+
+#endif
diff --git a/src/pulse/version.h.in b/src/pulse/version.h.in
new file mode 100644
index 00000000..20c7a9c0
--- /dev/null
+++ b/src/pulse/version.h.in
@@ -0,0 +1,56 @@
+#ifndef fooversionhfoo /*-*-C-*-*/
+#define fooversionhfoo
+
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+/* WARNING: Make sure to edit the real source file version.h.in! */
+
+#include <pulse/cdecl.h>
+
+/** \file
+ * Define header version */
+
+PA_C_DECL_BEGIN
+
+/** Return the version of the header files. Keep in mind that this is
+a macro and not a function, so it is impossible to get the pointer of
+it. */
+#define pa_get_headers_version() ("@PACKAGE_VERSION@")
+
+/** Return the version of the library the current application is linked to. */
+const char* pa_get_library_version(void);
+
+/** The current API version. Version 6 relates to Polypaudio
+ * 0.6. Prior versions (i.e. Polypaudio 0.5.1 and older) have
+ * PA_API_VERSION undefined. */
+#define PA_API_VERSION @PA_API_VERSION@
+
+/** The current protocol version. Version 8 relates to Polypaudio 0.8/PulseAudio 0.9.
+ * \since 0.8 */
+#define PA_PROTOCOL_VERSION @PA_PROTOCOL_VERSION@
+
+PA_C_DECL_END
+
+#endif
diff --git a/src/pulse/volume.c b/src/pulse/volume.c
new file mode 100644
index 00000000..3688b847
--- /dev/null
+++ b/src/pulse/volume.c
@@ -0,0 +1,180 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/macro.h>
+
+#include "volume.h"
+
+int pa_cvolume_equal(const pa_cvolume *a, const pa_cvolume *b) {
+ int i;
+ pa_assert(a);
+ pa_assert(b);
+
+ if (a->channels != b->channels)
+ return 0;
+
+ for (i = 0; i < a->channels; i++)
+ if (a->values[i] != b->values[i])
+ return 0;
+
+ return 1;
+}
+
+pa_cvolume* pa_cvolume_set(pa_cvolume *a, unsigned channels, pa_volume_t v) {
+ int i;
+
+ pa_assert(a);
+ pa_assert(channels > 0);
+ pa_assert(channels <= PA_CHANNELS_MAX);
+
+ a->channels = channels;
+
+ for (i = 0; i < a->channels; i++)
+ a->values[i] = v;
+
+ return a;
+}
+
+pa_volume_t pa_cvolume_avg(const pa_cvolume *a) {
+ uint64_t sum = 0;
+ int i;
+ pa_assert(a);
+
+ for (i = 0; i < a->channels; i++)
+ sum += a->values[i];
+
+ sum /= a->channels;
+
+ return (pa_volume_t) sum;
+}
+
+pa_volume_t pa_sw_volume_multiply(pa_volume_t a, pa_volume_t b) {
+ return pa_sw_volume_from_linear(pa_sw_volume_to_linear(a)* pa_sw_volume_to_linear(b));
+}
+
+#define USER_DECIBEL_RANGE 30
+
+pa_volume_t pa_sw_volume_from_dB(double dB) {
+ if (dB <= -USER_DECIBEL_RANGE)
+ return PA_VOLUME_MUTED;
+
+ return (pa_volume_t) ((dB/USER_DECIBEL_RANGE+1)*PA_VOLUME_NORM);
+}
+
+double pa_sw_volume_to_dB(pa_volume_t v) {
+ if (v == PA_VOLUME_MUTED)
+ return PA_DECIBEL_MININFTY;
+
+ return ((double) v/PA_VOLUME_NORM-1)*USER_DECIBEL_RANGE;
+}
+
+pa_volume_t pa_sw_volume_from_linear(double v) {
+
+ if (v <= 0)
+ return PA_VOLUME_MUTED;
+
+ if (v > .999 && v < 1.001)
+ return PA_VOLUME_NORM;
+
+ return pa_sw_volume_from_dB(20*log10(v));
+}
+
+double pa_sw_volume_to_linear(pa_volume_t v) {
+
+ if (v == PA_VOLUME_MUTED)
+ return 0;
+
+ return pow(10, pa_sw_volume_to_dB(v)/20);
+}
+
+char *pa_cvolume_snprint(char *s, size_t l, const pa_cvolume *c) {
+ unsigned channel;
+ int first = 1;
+ char *e;
+
+ pa_assert(s);
+ pa_assert(l > 0);
+ pa_assert(c);
+
+ *(e = s) = 0;
+
+ for (channel = 0; channel < c->channels && l > 1; channel++) {
+ l -= pa_snprintf(e, l, "%s%u: %3u%%",
+ first ? "" : " ",
+ channel,
+ (c->values[channel]*100)/PA_VOLUME_NORM);
+
+ e = strchr(e, 0);
+ first = 0;
+ }
+
+ return s;
+}
+
+/** Return non-zero if the volume of all channels is equal to the specified value */
+int pa_cvolume_channels_equal_to(const pa_cvolume *a, pa_volume_t v) {
+ unsigned c;
+ pa_assert(a);
+
+ for (c = 0; c < a->channels; c++)
+ if (a->values[c] != v)
+ return 0;
+
+ return 1;
+}
+
+pa_cvolume *pa_sw_cvolume_multiply(pa_cvolume *dest, const pa_cvolume *a, const pa_cvolume *b) {
+ unsigned i;
+
+ pa_assert(dest);
+ pa_assert(a);
+ pa_assert(b);
+
+ for (i = 0; i < a->channels && i < b->channels && i < PA_CHANNELS_MAX; i++) {
+
+ dest->values[i] = pa_sw_volume_multiply(
+ i < a->channels ? a->values[i] : PA_VOLUME_NORM,
+ i < b->channels ? b->values[i] : PA_VOLUME_NORM);
+ }
+
+ dest->channels = i;
+
+ return dest;
+}
+
+int pa_cvolume_valid(const pa_cvolume *v) {
+ pa_assert(v);
+
+ if (v->channels <= 0 || v->channels > PA_CHANNELS_MAX)
+ return 0;
+
+ return 1;
+}
diff --git a/src/pulse/volume.h b/src/pulse/volume.h
new file mode 100644
index 00000000..22e5b8a4
--- /dev/null
+++ b/src/pulse/volume.h
@@ -0,0 +1,175 @@
+#ifndef foovolumehfoo
+#define foovolumehfoo
+
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <inttypes.h>
+#include <pulse/cdecl.h>
+#include <pulse/sample.h>
+
+/** \page volume Volume Control
+ *
+ * \section overv_sec Overview
+ *
+ * Sinks, sources, sink inputs and samples can all have their own volumes.
+ * To deal with these, The PulseAudio libray contains a number of functions
+ * that ease handling.
+ *
+ * The basic volume type in PulseAudio is the \ref pa_volume_t type. Most of
+ * the time, applications will use the aggregated pa_cvolume structure that
+ * can store the volume of all channels at once.
+ *
+ * Volumes commonly span between muted (0%), and normal (100%). It is possible
+ * to set volumes to higher than 100%, but clipping might occur.
+ *
+ * \section calc_sec Calculations
+ *
+ * The volumes in PulseAudio are logarithmic in nature and applications
+ * shouldn't perform calculations with them directly. Instead, they should
+ * be converted to and from either dB or a linear scale:
+ *
+ * \li dB - pa_sw_volume_from_dB() / pa_sw_volume_to_dB()
+ * \li Linear - pa_sw_volume_from_linear() / pa_sw_volume_to_linear()
+ *
+ * For simple multiplication, pa_sw_volume_multiply() and
+ * pa_sw_cvolume_multiply() can be used.
+ *
+ * Calculations can only be reliably performed on software volumes
+ * as it is commonly unknown what scale hardware volumes relate to.
+ *
+ * The functions described above are only valid when used with
+ * software volumes. Hence it is usually a better idea to treat all
+ * volume values as opaque with a range from PA_VOLUME_MUTE (0%) to
+ * PA_VOLUME_NORM (100%) and to refrain from any calculations with
+ * them.
+ *
+ * \section conv_sec Convenience Functions
+ *
+ * To handle the pa_cvolume structure, the PulseAudio library provides a
+ * number of convenienc functions:
+ *
+ * \li pa_cvolume_valid() - Tests if a pa_cvolume structure is valid.
+ * \li pa_cvolume_equal() - Tests if two pa_cvolume structures are identical.
+ * \li pa_cvolume_channels_equal_to() - Tests if all channels of a pa_cvolume
+ * structure have a given volume.
+ * \li pa_cvolume_is_muted() - Tests if all channels of a pa_cvolume
+ * structure are muted.
+ * \li pa_cvolume_is_norm() - Tests if all channels of a pa_cvolume structure
+ * are at a normal volume.
+ * \li pa_cvolume_set() - Set all channels of a pa_cvolume structure to a
+ * certain volume.
+ * \li pa_cvolume_reset() - Set all channels of a pa_cvolume structure to a
+ * normal volume.
+ * \li pa_cvolume_mute() - Set all channels of a pa_cvolume structure to a
+ * muted volume.
+ * \li pa_cvolume_avg() - Return the average volume of all channels.
+ * \li pa_cvolume_snprint() - Pretty print a pa_cvolume structure.
+ */
+
+/** \file
+ * Constants and routines for volume handling */
+
+PA_C_DECL_BEGIN
+
+/** Volume specification:
+ * PA_VOLUME_MUTED: silence;
+ * < PA_VOLUME_NORM: decreased volume;
+ * PA_VOLUME_NORM: normal volume;
+ * > PA_VOLUME_NORM: increased volume */
+typedef uint32_t pa_volume_t;
+
+/** Normal volume (100%) */
+#define PA_VOLUME_NORM (0x10000)
+
+/** Muted volume (0%) */
+#define PA_VOLUME_MUTED (0)
+
+/** A structure encapsulating a per-channel volume */
+typedef struct pa_cvolume {
+ uint8_t channels; /**< Number of channels */
+ pa_volume_t values[PA_CHANNELS_MAX]; /**< Per-channel volume */
+} pa_cvolume;
+
+/** Return non-zero when *a == *b */
+int pa_cvolume_equal(const pa_cvolume *a, const pa_cvolume *b) PA_GCC_PURE;
+
+/** Set the volume of all channels to PA_VOLUME_NORM */
+#define pa_cvolume_reset(a, n) pa_cvolume_set((a), (n), PA_VOLUME_NORM)
+
+/** Set the volume of all channels to PA_VOLUME_MUTED */
+#define pa_cvolume_mute(a, n) pa_cvolume_set((a), (n), PA_VOLUME_MUTED)
+
+/** Set the volume of all channels to the specified parameter */
+pa_cvolume* pa_cvolume_set(pa_cvolume *a, unsigned channels, pa_volume_t v);
+
+/** Maximum length of the strings returned by pa_cvolume_snprint() */
+#define PA_CVOLUME_SNPRINT_MAX 64
+
+/** Pretty print a volume structure */
+char *pa_cvolume_snprint(char *s, size_t l, const pa_cvolume *c);
+
+/** Return the average volume of all channels */
+pa_volume_t pa_cvolume_avg(const pa_cvolume *a) PA_GCC_PURE;
+
+/** Return TRUE when the passed cvolume structure is valid, FALSE otherwise */
+int pa_cvolume_valid(const pa_cvolume *v) PA_GCC_PURE;
+
+/** Return non-zero if the volume of all channels is equal to the specified value */
+int pa_cvolume_channels_equal_to(const pa_cvolume *a, pa_volume_t v) PA_GCC_PURE;
+
+/** Return 1 if the specified volume has all channels muted */
+#define pa_cvolume_is_muted(a) pa_cvolume_channels_equal_to((a), PA_VOLUME_MUTED)
+
+/** Return 1 if the specified volume has all channels on normal level */
+#define pa_cvolume_is_norm(a) pa_cvolume_channels_equal_to((a), PA_VOLUME_NORM)
+
+/** Multiply two volumes specifications, return the result. This uses PA_VOLUME_NORM as neutral element of multiplication. This is only valid for software volumes! */
+pa_volume_t pa_sw_volume_multiply(pa_volume_t a, pa_volume_t b) PA_GCC_CONST;
+
+/** Multiply to per-channel volumes and return the result in *dest. This is only valid for software volumes! */
+pa_cvolume *pa_sw_cvolume_multiply(pa_cvolume *dest, const pa_cvolume *a, const pa_cvolume *b) PA_GCC_PURE;
+
+/** Convert a decibel value to a volume. This is only valid for software volumes! \since 0.4 */
+pa_volume_t pa_sw_volume_from_dB(double f) PA_GCC_CONST;
+
+/** Convert a volume to a decibel value. This is only valid for software volumes! \since 0.4 */
+double pa_sw_volume_to_dB(pa_volume_t v) PA_GCC_CONST;
+
+/** Convert a linear factor to a volume. This is only valid for software volumes! \since 0.8 */
+pa_volume_t pa_sw_volume_from_linear(double v) PA_GCC_CONST;
+
+/** Convert a volume to a linear factor. This is only valid for software volumes! \since 0.8 */
+double pa_sw_volume_to_linear(pa_volume_t v) PA_GCC_CONST;
+
+#ifdef INFINITY
+#define PA_DECIBEL_MININFTY (-INFINITY)
+#else
+/** This value is used as minus infinity when using pa_volume_{to,from}_dB(). \since 0.4 */
+#define PA_DECIBEL_MININFTY (-200)
+#endif
+
+PA_C_DECL_END
+
+#endif
diff --git a/src/pulse/xmalloc.c b/src/pulse/xmalloc.c
new file mode 100644
index 00000000..5348dda4
--- /dev/null
+++ b/src/pulse/xmalloc.c
@@ -0,0 +1,130 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <signal.h>
+#include <unistd.h>
+#include <string.h>
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/gccmacro.h>
+#include <pulsecore/macro.h>
+
+#include "xmalloc.h"
+
+/* Make sure not to allocate more than this much memory. */
+#define MAX_ALLOC_SIZE (1024*1024*20) /* 20MB */
+
+/* #undef malloc */
+/* #undef free */
+/* #undef realloc */
+/* #undef strndup */
+/* #undef strdup */
+
+static void oom(void) PA_GCC_NORETURN;
+
+/** called in case of an OOM situation. Prints an error message and
+ * exits */
+static void oom(void) {
+ static const char e[] = "Not enough memory\n";
+ pa_loop_write(STDERR_FILENO, e, sizeof(e)-1, NULL);
+#ifdef SIGQUIT
+ raise(SIGQUIT);
+#endif
+ _exit(1);
+}
+
+void* pa_xmalloc(size_t size) {
+ void *p;
+ pa_assert(size > 0);
+ pa_assert(size < MAX_ALLOC_SIZE);
+
+ if (!(p = malloc(size)))
+ oom();
+
+ return p;
+}
+
+void* pa_xmalloc0(size_t size) {
+ void *p;
+ pa_assert(size > 0);
+ pa_assert(size < MAX_ALLOC_SIZE);
+
+ if (!(p = calloc(1, size)))
+ oom();
+
+ return p;
+}
+
+void *pa_xrealloc(void *ptr, size_t size) {
+ void *p;
+ pa_assert(size > 0);
+ pa_assert(size < MAX_ALLOC_SIZE);
+
+ if (!(p = realloc(ptr, size)))
+ oom();
+ return p;
+}
+
+void* pa_xmemdup(const void *p, size_t l) {
+ if (!p)
+ return NULL;
+ else {
+ char *r = pa_xmalloc(l);
+ memcpy(r, p, l);
+ return r;
+ }
+}
+
+char *pa_xstrdup(const char *s) {
+ if (!s)
+ return NULL;
+
+ return pa_xmemdup(s, strlen(s)+1);
+}
+
+char *pa_xstrndup(const char *s, size_t l) {
+ char *e, *r;
+
+ if (!s)
+ return NULL;
+
+ if ((e = memchr(s, 0, l)))
+ return pa_xmemdup(s, e-s+1);
+
+ r = pa_xmalloc(l+1);
+ memcpy(r, s, l);
+ r[l] = 0;
+ return r;
+}
+
+void pa_xfree(void *p) {
+ if (!p)
+ return;
+
+ free(p);
+}
diff --git a/src/pulse/xmalloc.h b/src/pulse/xmalloc.h
new file mode 100644
index 00000000..62a450dc
--- /dev/null
+++ b/src/pulse/xmalloc.h
@@ -0,0 +1,89 @@
+#ifndef foomemoryhfoo
+#define foomemoryhfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <sys/types.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <assert.h>
+#include <pulse/cdecl.h>
+
+/** \file
+ * Memory allocation functions.
+ */
+
+PA_C_DECL_BEGIN
+
+/** Allocate the specified number of bytes, just like malloc() does. However, in case of OOM, terminate */
+void* pa_xmalloc(size_t l);
+
+/** Same as pa_xmalloc(), but initialize allocated memory to 0 */
+void *pa_xmalloc0(size_t l);
+
+/** The combination of pa_xmalloc() and realloc() */
+void *pa_xrealloc(void *ptr, size_t size);
+
+/** Free allocated memory */
+void pa_xfree(void *p);
+
+/** Duplicate the specified string, allocating memory with pa_xmalloc() */
+char *pa_xstrdup(const char *s);
+
+/** Duplicate the specified string, but truncate after l characters */
+char *pa_xstrndup(const char *s, size_t l);
+
+/** Duplicate the specified memory block */
+void* pa_xmemdup(const void *p, size_t l);
+
+/** Internal helper for pa_xnew() */
+static inline void* pa_xnew_internal(unsigned n, size_t k) {
+ assert(n < INT_MAX/k);
+ return pa_xmalloc(n*k);
+}
+
+/** Allocate n new structures of the specified type. */
+#define pa_xnew(type, n) ((type*) pa_xnew_internal((n), sizeof(type)))
+
+/** Internal helper for pa_xnew0() */
+static inline void* pa_xnew0_internal(unsigned n, size_t k) {
+ assert(n < INT_MAX/k);
+ return pa_xmalloc0(n*k);
+}
+
+/** Same as pa_xnew() but set the memory to zero */
+#define pa_xnew0(type, n) ((type*) pa_xnew0_internal((n), sizeof(type)))
+
+/** Internal helper for pa_xnew0() */
+static inline void* pa_xnewdup_internal(const void *p, unsigned n, size_t k) {
+ assert(n < INT_MAX/k);
+ return pa_xmemdup(p, n*k);
+}
+
+/** Same as pa_xnew() but set the memory to zero */
+#define pa_xnewdup(type, p, n) ((type*) pa_xnewdup_internal((p), (n), sizeof(type)))
+
+PA_C_DECL_END
+
+#endif
diff --git a/src/pulsecore/Makefile b/src/pulsecore/Makefile
new file mode 120000
index 00000000..c110232d
--- /dev/null
+++ b/src/pulsecore/Makefile
@@ -0,0 +1 @@
+../pulse/Makefile \ No newline at end of file
diff --git a/src/pulsecore/asyncmsgq.c b/src/pulsecore/asyncmsgq.c
new file mode 100644
index 00000000..96b43a71
--- /dev/null
+++ b/src/pulsecore/asyncmsgq.c
@@ -0,0 +1,303 @@
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <unistd.h>
+#include <errno.h>
+
+#include <pulsecore/atomic.h>
+#include <pulsecore/log.h>
+#include <pulsecore/thread.h>
+#include <pulsecore/semaphore.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/flist.h>
+#include <pulse/xmalloc.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_asyncmsgq *a;
+
+ a = pa_xnew(pa_asyncmsgq, 1);
+
+ PA_REFCNT_INIT(a);
+ pa_assert_se(a->asyncq = pa_asyncq_new(size));
+ 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, 0))) {
+
+ 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_assert_se(pa_asyncq_push(a->asyncq, i, 1) == 0);
+ 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);
+
+ pa_assert_se(i.semaphore);
+
+ /* Thus 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, 1) == 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, int wait) {
+ pa_assert(PA_REFCNT_VALUE(a) > 0);
+ pa_assert(!a->current);
+
+ if (!(a->current = pa_asyncq_pop(a->asyncq, wait))) {
+/* 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, 1) < 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, 0) < 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_get_fd(pa_asyncmsgq *a) {
+ pa_assert(PA_REFCNT_VALUE(a) > 0);
+
+ return pa_asyncq_get_fd(a->asyncq);
+}
+
+int pa_asyncmsgq_before_poll(pa_asyncmsgq *a) {
+ pa_assert(PA_REFCNT_VALUE(a) > 0);
+
+ return pa_asyncq_before_poll(a->asyncq);
+}
+
+void pa_asyncmsgq_after_poll(pa_asyncmsgq *a) {
+ pa_assert(PA_REFCNT_VALUE(a) > 0);
+
+ pa_asyncq_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, memchunk);
+
+ return 0;
+}
diff --git a/src/pulsecore/asyncmsgq.h b/src/pulsecore/asyncmsgq.h
new file mode 100644
index 00000000..5d3867ba
--- /dev/null
+++ b/src/pulsecore/asyncmsgq.h
@@ -0,0 +1,75 @@
+#ifndef foopulseasyncmsgqhfoo
+#define foopulseasyncmsgqhfoo
+
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#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, int 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);
+
+/* Just for the reading side */
+int pa_asyncmsgq_get_fd(pa_asyncmsgq *q);
+int pa_asyncmsgq_before_poll(pa_asyncmsgq *a);
+void pa_asyncmsgq_after_poll(pa_asyncmsgq *a);
+
+#endif
diff --git a/src/pulsecore/asyncq.c b/src/pulsecore/asyncq.c
new file mode 100644
index 00000000..75b15c0e
--- /dev/null
+++ b/src/pulsecore/asyncq.c
@@ -0,0 +1,213 @@
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <unistd.h>
+#include <errno.h>
+
+#include <pulsecore/atomic.h>
+#include <pulsecore/log.h>
+#include <pulsecore/thread.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/core-util.h>
+#include <pulse/xmalloc.h>
+
+#include "asyncq.h"
+#include "fdsem.h"
+
+#define ASYNCQ_SIZE 128
+
+/* For debugging purposes we can define _Y to put and extra thread
+ * yield between each operation. */
+
+/* #define PROFILE */
+
+#ifdef PROFILE
+#define _Y pa_thread_yield()
+#else
+#define _Y do { } while(0)
+#endif
+
+struct pa_asyncq {
+ unsigned size;
+ unsigned read_idx;
+ unsigned write_idx;
+ pa_fdsem *read_fdsem, *write_fdsem;
+};
+
+#define PA_ASYNCQ_CELLS(x) ((pa_atomic_ptr_t*) ((uint8_t*) (x) + PA_ALIGN(sizeof(struct pa_asyncq))))
+
+static int is_power_of_two(unsigned size) {
+ return !(size & (size - 1));
+}
+
+static int reduce(pa_asyncq *l, int value) {
+ return value & (unsigned) (l->size - 1);
+}
+
+pa_asyncq *pa_asyncq_new(unsigned size) {
+ pa_asyncq *l;
+
+ if (!size)
+ size = ASYNCQ_SIZE;
+
+ pa_assert(is_power_of_two(size));
+
+ l = pa_xmalloc0(PA_ALIGN(sizeof(pa_asyncq)) + (sizeof(pa_atomic_ptr_t) * size));
+
+ l->size = size;
+
+ 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) {
+ pa_assert(l);
+
+ if (free_cb) {
+ void *p;
+
+ while ((p = pa_asyncq_pop(l, 0)))
+ free_cb(p);
+ }
+
+ pa_fdsem_free(l->read_fdsem);
+ pa_fdsem_free(l->write_fdsem);
+ pa_xfree(l);
+}
+
+int pa_asyncq_push(pa_asyncq*l, void *p, int wait) {
+ int 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)
+ 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;
+}
+
+void* pa_asyncq_pop(pa_asyncq*l, int wait) {
+ int 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)
+ 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_get_fd(pa_asyncq *q) {
+ pa_assert(q);
+
+ return pa_fdsem_get(q->write_fdsem);
+}
+
+int pa_asyncq_before_poll(pa_asyncq *l) {
+ int 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;
+ }
+
+ return 0;
+}
+
+void pa_asyncq_after_poll(pa_asyncq *l) {
+ pa_assert(l);
+
+ pa_fdsem_after_poll(l->write_fdsem);
+}
diff --git a/src/pulsecore/asyncq.h b/src/pulsecore/asyncq.h
new file mode 100644
index 00000000..53d45866
--- /dev/null
+++ b/src/pulsecore/asyncq.h
@@ -0,0 +1,56 @@
+#ifndef foopulseasyncqhfoo
+#define foopulseasyncqhfoo
+
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <sys/types.h>
+#include <pulse/def.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, int wait);
+int pa_asyncq_push(pa_asyncq *q, void *p, int wait);
+
+int pa_asyncq_get_fd(pa_asyncq *q);
+int pa_asyncq_before_poll(pa_asyncq *a);
+void pa_asyncq_after_poll(pa_asyncq *a);
+
+#endif
diff --git a/src/pulsecore/atomic.h b/src/pulsecore/atomic.h
new file mode 100644
index 00000000..c2c99888
--- /dev/null
+++ b/src/pulsecore/atomic.h
@@ -0,0 +1,245 @@
+#ifndef foopulseatomichfoo
+#define foopulseatomichfoo
+
+/* $Id$ */
+
+/***
+ 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 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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.
+***/
+
+/*
+ * 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) }
+
+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();
+}
+
+/* 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 non-zero when the operation was successful. */
+static inline int 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) }
+
+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();
+}
+
+static inline int 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(__GNUC__) && (defined(__amd64__) || defined(__x86_64__))
+
+#error "The native atomic operations implementation for AMD64 has not been tested. 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: make the native atomic operations implementation for AMD64 work, fix libatomic_ops, or upgrade your GCC."
+
+/* Addapted 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 int 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 == oldval;
+}
+
+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 int 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;
+}
+
+#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 = (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 AO_fetch_and_add_full(&a->value, (AO_t) i);
+}
+
+static inline int pa_atomic_sub(pa_atomic_t *a, int i) {
+ return AO_fetch_and_add_full(&a->value, (AO_t) -i);
+}
+
+static inline int pa_atomic_inc(pa_atomic_t *a) {
+ return AO_fetch_and_add1_full(&a->value);
+}
+
+static inline int pa_atomic_dec(pa_atomic_t *a) {
+ return AO_fetch_and_sub1_full(&a->value);
+}
+
+static inline int pa_atomic_cmpxchg(pa_atomic_t *a, int old_i, int new_i) {
+ return AO_compare_and_swap_full(&a->value, old_i, 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 int 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/authkey-prop.c b/src/pulsecore/authkey-prop.c
new file mode 100644
index 00000000..54154500
--- /dev/null
+++ b/src/pulsecore/authkey-prop.c
@@ -0,0 +1,108 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/props.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/log.h>
+#include <pulsecore/refcnt.h>
+
+#include "authkey-prop.h"
+
+struct authkey_data {
+ PA_REFCNT_DECLARE;
+ size_t length;
+};
+
+int pa_authkey_prop_get(pa_core *c, const char *name, void *data, size_t len) {
+ struct authkey_data *a;
+
+ pa_assert(c);
+ pa_assert(name);
+ pa_assert(data);
+ pa_assert(len > 0);
+
+ if (!(a = pa_property_get(c, name)))
+ return -1;
+
+ pa_assert(a->length == len);
+ memcpy(data, (uint8_t*) a + PA_ALIGN(sizeof(struct authkey_data)), len);
+
+ return 0;
+}
+
+int pa_authkey_prop_put(pa_core *c, const char *name, const void *data, size_t len) {
+ struct authkey_data *a;
+
+ pa_assert(c);
+ pa_assert(name);
+
+ if (pa_property_get(c, name))
+ return -1;
+
+ a = pa_xmalloc(PA_ALIGN(sizeof(struct authkey_data)) + len);
+ PA_REFCNT_INIT(a);
+ a->length = len;
+ memcpy((uint8_t*) a + PA_ALIGN(sizeof(struct authkey_data)), data, len);
+
+ pa_property_set(c, name, a);
+
+ return 0;
+}
+
+void pa_authkey_prop_ref(pa_core *c, const char *name) {
+ struct authkey_data *a;
+
+ pa_assert(c);
+ pa_assert(name);
+
+ a = pa_property_get(c, name);
+ pa_assert(a);
+ pa_assert(PA_REFCNT_VALUE(a) >= 1);
+ PA_REFCNT_INC(a);
+}
+
+void pa_authkey_prop_unref(pa_core *c, const char *name) {
+ struct authkey_data *a;
+
+ pa_assert(c);
+ pa_assert(name);
+
+ a = pa_property_get(c, name);
+ pa_assert(a);
+ pa_assert(PA_REFCNT_VALUE(a) >= 1);
+
+ if (PA_REFCNT_DEC(a) <= 0) {
+ pa_property_remove(c, name);
+ pa_xfree(a);
+ }
+}
+
+
diff --git a/src/pulsecore/authkey-prop.h b/src/pulsecore/authkey-prop.h
new file mode 100644
index 00000000..247202f3
--- /dev/null
+++ b/src/pulsecore/authkey-prop.h
@@ -0,0 +1,45 @@
+#ifndef fooauthkeyprophfoo
+#define fooauthkeyprophfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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 <pulsecore/core.h>
+
+/* The authkey-prop uses a central property to store a previously
+ * loaded cookie in memory. Useful for sharing the same cookie between
+ * several modules. */
+
+/* Return the data of the specified authorization key property. Doesn't alter the refernce count of the key */
+int pa_authkey_prop_get(pa_core *c, const char *name, void *data, size_t len);
+
+/* Store data in the specified authorization key property. The initial reference count is set to 1 */
+int pa_authkey_prop_put(pa_core *c, const char *name, const void *data, size_t len);
+
+/* Increase the reference count of the specified authorization key */
+void pa_authkey_prop_ref(pa_core *c, const char *name);
+
+/* Decrease the reference count of the specified authorization key */
+void pa_authkey_prop_unref(pa_core *c, const char *name);
+
+#endif
diff --git a/src/pulsecore/authkey.c b/src/pulsecore/authkey.c
new file mode 100644
index 00000000..80bc8576
--- /dev/null
+++ b/src/pulsecore/authkey.c
@@ -0,0 +1,238 @@
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <errno.h>
+#include <stdio.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <time.h>
+#include <limits.h>
+#include <sys/stat.h>
+
+#include <pulse/util.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 authorization 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, 0, SEEK_SET);
+ (void) ftruncate(fd, 0);
+
+ 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
+
+#ifndef O_NOCTTY
+#define O_NOCTTY 0
+#endif
+
+/* Load an euthorization cookie from file fn and store it in data. If
+ * the cookie file doesn't exist, create it */
+static int load(const char *fn, 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 ((fd = open(fn, O_RDWR|O_CREAT|O_BINARY|O_NOCTTY, S_IRUSR|S_IWUSR)) < 0) {
+
+ if (errno != EACCES || (fd = open(fn, O_RDONLY|O_BINARY|O_NOCTTY)) < 0) {
+ pa_log("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("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;
+}
+
+/* Load a cookie from a cookie file. If the file doesn't exist, create it. */
+int pa_authkey_load(const char *path, void *data, size_t length) {
+ int ret;
+
+ pa_assert(path);
+ pa_assert(data);
+ pa_assert(length > 0);
+
+ if ((ret = load(path, data, length)) < 0)
+ pa_log("Failed to load authorization key '%s': %s", path, (ret < 0) ? pa_cstrerror(errno) : "File corrupt");
+
+ return ret;
+}
+
+/* If the specified file path starts with / return it, otherwise
+ * return path prepended with home directory */
+static const char *normalize_path(const char *fn, char *s, size_t l) {
+
+ pa_assert(fn);
+ pa_assert(s);
+ pa_assert(l > 0);
+
+#ifndef OS_IS_WIN32
+ if (fn[0] != '/') {
+#else
+ if (strlen(fn) < 3 || !isalpha(fn[0]) || fn[1] != ':' || fn[2] != '\\') {
+#endif
+ char homedir[PATH_MAX];
+
+ if (!pa_get_home_dir(homedir, sizeof(homedir)))
+ return NULL;
+
+#ifndef OS_IS_WIN32
+ pa_snprintf(s, l, "%s/%s", homedir, fn);
+#else
+ pa_snprintf(s, l, "%s\\%s", homedir, fn);
+#endif
+ return s;
+ }
+
+ return fn;
+}
+
+/* Load a cookie from a file in the home directory. If the specified
+ * path starts with /, use it as absolute path instead. */
+int pa_authkey_load_auto(const char *fn, void *data, size_t length) {
+ char path[PATH_MAX];
+ const char *p;
+
+ pa_assert(fn);
+ pa_assert(data);
+ pa_assert(length > 0);
+
+ if (!(p = normalize_path(fn, path, sizeof(path))))
+ return -2;
+
+ return pa_authkey_load(p, data, length);
+}
+
+/* Store the specified cookie in the speicified cookie file */
+int pa_authkey_save(const char *fn, const void *data, size_t length) {
+ int fd = -1;
+ int unlock = 0, ret = -1;
+ ssize_t r;
+ char path[PATH_MAX];
+ const char *p;
+
+ pa_assert(fn);
+ pa_assert(data);
+ pa_assert(length > 0);
+
+ if (!(p = normalize_path(fn, path, sizeof(path))))
+ return -2;
+
+ if ((fd = open(p, O_RDWR|O_CREAT|O_NOCTTY, S_IRUSR|S_IWUSR)) < 0) {
+ pa_log("Failed to open cookie file '%s': %s", fn, pa_cstrerror(errno));
+ 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));
+ 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;
+}
diff --git a/src/pulsecore/authkey.h b/src/pulsecore/authkey.h
new file mode 100644
index 00000000..18e5157d
--- /dev/null
+++ b/src/pulsecore/authkey.h
@@ -0,0 +1,34 @@
+#ifndef fooauthkeyhfoo
+#define fooauthkeyhfoo
+
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <sys/types.h>
+
+int pa_authkey_load(const char *path, void *data, size_t len);
+int pa_authkey_load_auto(const char *fn, void *data, size_t length);
+
+int pa_authkey_save(const char *path, const void *data, size_t length);
+
+#endif
diff --git a/src/pulsecore/autoload.c b/src/pulsecore/autoload.c
new file mode 100644
index 00000000..a1d3e02d
--- /dev/null
+++ b/src/pulsecore/autoload.c
@@ -0,0 +1,204 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/module.h>
+#include <pulsecore/memchunk.h>
+#include <pulsecore/sound-file.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/core-scache.h>
+#include <pulsecore/core-subscribe.h>
+
+#include "autoload.h"
+
+static void entry_free(pa_autoload_entry *e) {
+ pa_assert(e);
+ pa_subscription_post(e->core, PA_SUBSCRIPTION_EVENT_AUTOLOAD|PA_SUBSCRIPTION_EVENT_REMOVE, PA_INVALID_INDEX);
+ pa_xfree(e->name);
+ pa_xfree(e->module);
+ pa_xfree(e->argument);
+ pa_xfree(e);
+}
+
+static void entry_remove_and_free(pa_autoload_entry *e) {
+ pa_assert(e);
+ pa_assert(e->core);
+
+ pa_idxset_remove_by_data(e->core->autoload_idxset, e, NULL);
+ pa_hashmap_remove(e->core->autoload_hashmap, e->name);
+ entry_free(e);
+}
+
+static pa_autoload_entry* entry_new(pa_core *c, const char *name) {
+ pa_autoload_entry *e = NULL;
+
+ pa_core_assert_ref(c);
+ pa_assert(name);
+
+ if (c->autoload_hashmap && (e = pa_hashmap_get(c->autoload_hashmap, name)))
+ return NULL;
+
+ e = pa_xnew(pa_autoload_entry, 1);
+ e->core = c;
+ e->name = pa_xstrdup(name);
+ e->module = e->argument = NULL;
+ e->in_action = 0;
+
+ if (!c->autoload_hashmap)
+ c->autoload_hashmap = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+ pa_assert(c->autoload_hashmap);
+
+ pa_hashmap_put(c->autoload_hashmap, e->name, e);
+
+ if (!c->autoload_idxset)
+ c->autoload_idxset = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
+ pa_idxset_put(c->autoload_idxset, e, &e->index);
+
+ pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_AUTOLOAD|PA_SUBSCRIPTION_EVENT_NEW, e->index);
+
+ return e;
+}
+
+int pa_autoload_add(pa_core *c, const char*name, pa_namereg_type_t type, const char*module, const char *argument, uint32_t *idx) {
+ pa_autoload_entry *e = NULL;
+
+ pa_assert(c);
+ pa_assert(name);
+ pa_assert(module);
+ pa_assert(type == PA_NAMEREG_SINK || type == PA_NAMEREG_SOURCE);
+
+ if (!(e = entry_new(c, name)))
+ return -1;
+
+ e->module = pa_xstrdup(module);
+ e->argument = pa_xstrdup(argument);
+ e->type = type;
+
+ if (idx)
+ *idx = e->index;
+
+ return 0;
+}
+
+int pa_autoload_remove_by_name(pa_core *c, const char*name, pa_namereg_type_t type) {
+ pa_autoload_entry *e;
+
+ pa_assert(c);
+ pa_assert(name);
+ pa_assert(type == PA_NAMEREG_SINK || type == PA_NAMEREG_SOURCE);
+
+ if (!c->autoload_hashmap || !(e = pa_hashmap_get(c->autoload_hashmap, name)) || e->type != type)
+ return -1;
+
+ entry_remove_and_free(e);
+ return 0;
+}
+
+int pa_autoload_remove_by_index(pa_core *c, uint32_t idx) {
+ pa_autoload_entry *e;
+
+ pa_assert(c);
+ pa_assert(idx != PA_IDXSET_INVALID);
+
+ if (!c->autoload_idxset || !(e = pa_idxset_get_by_index(c->autoload_idxset, idx)))
+ return -1;
+
+ entry_remove_and_free(e);
+ return 0;
+}
+
+void pa_autoload_request(pa_core *c, const char *name, pa_namereg_type_t type) {
+ pa_autoload_entry *e;
+ pa_module *m;
+
+ pa_assert(c);
+ pa_assert(name);
+
+ if (!c->autoload_hashmap || !(e = pa_hashmap_get(c->autoload_hashmap, name)) || (e->type != type))
+ return;
+
+ if (e->in_action)
+ return;
+
+ e->in_action = 1;
+
+ if (type == PA_NAMEREG_SINK || type == PA_NAMEREG_SOURCE) {
+ if ((m = pa_module_load(c, e->module, e->argument)))
+ m->auto_unload = 1;
+ }
+
+ e->in_action = 0;
+}
+
+static void free_func(void *p, PA_GCC_UNUSED void *userdata) {
+ pa_autoload_entry *e = p;
+ pa_idxset_remove_by_data(e->core->autoload_idxset, e, NULL);
+ entry_free(e);
+}
+
+void pa_autoload_free(pa_core *c) {
+
+ if (c->autoload_hashmap) {
+ pa_hashmap_free(c->autoload_hashmap, free_func, NULL);
+ c->autoload_hashmap = NULL;
+ }
+
+ if (c->autoload_idxset) {
+ pa_idxset_free(c->autoload_idxset, NULL, NULL);
+ c->autoload_idxset = NULL;
+ }
+}
+
+const pa_autoload_entry* pa_autoload_get_by_name(pa_core *c, const char*name, pa_namereg_type_t type) {
+ pa_autoload_entry *e;
+
+ pa_core_assert_ref(c);
+ pa_assert(name);
+
+ if (!c->autoload_hashmap || !(e = pa_hashmap_get(c->autoload_hashmap, name)) || e->type != type)
+ return NULL;
+
+ return e;
+}
+
+const pa_autoload_entry* pa_autoload_get_by_index(pa_core *c, uint32_t idx) {
+ pa_autoload_entry *e;
+
+ pa_core_assert_ref(c);
+ pa_assert(idx != PA_IDXSET_INVALID);
+
+ if (!c->autoload_idxset || !(e = pa_idxset_get_by_index(c->autoload_idxset, idx)))
+ return NULL;
+
+ return e;
+}
diff --git a/src/pulsecore/autoload.h b/src/pulsecore/autoload.h
new file mode 100644
index 00000000..8a3522a7
--- /dev/null
+++ b/src/pulsecore/autoload.h
@@ -0,0 +1,60 @@
+#ifndef fooautoloadhfoo
+#define fooautoloadhfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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 <pulsecore/namereg.h>
+
+/* Using the autoloading facility, modules by be loaded on-demand and
+ * synchronously. The user may register a "ghost sink" or "ghost
+ * source". Whenever this sink/source is requested but not available a
+ * specified module is loaded. */
+
+/* An autoload entry, or "ghost" sink/source */
+typedef struct pa_autoload_entry {
+ pa_core *core;
+ uint32_t index;
+ char *name;
+ pa_namereg_type_t type; /* Type of the autoload entry */
+ int in_action; /* The module is currently being loaded */
+ char *module, *argument;
+} pa_autoload_entry;
+
+/* Add a new autoload entry of the given time, with the speicified
+ * sink/source name, module name and argument. Return the entry's
+ * index in *index */
+int pa_autoload_add(pa_core *c, const char*name, pa_namereg_type_t type, const char*module, const char *argument, uint32_t *idx);
+
+/* Free all autoload entries */
+void pa_autoload_free(pa_core *c);
+int pa_autoload_remove_by_name(pa_core *c, const char*name, pa_namereg_type_t type);
+int pa_autoload_remove_by_index(pa_core *c, uint32_t idx);
+
+/* Request an autoload entry by its name, effectively causing a module to be loaded */
+void pa_autoload_request(pa_core *c, const char *name, pa_namereg_type_t type);
+
+const pa_autoload_entry* pa_autoload_get_by_name(pa_core *c, const char*name, pa_namereg_type_t type);
+const pa_autoload_entry* pa_autoload_get_by_index(pa_core *c, uint32_t idx);
+
+#endif
diff --git a/src/pulsecore/avahi-wrap.c b/src/pulsecore/avahi-wrap.c
new file mode 100644
index 00000000..fae54810
--- /dev/null
+++ b/src/pulsecore/avahi-wrap.c
@@ -0,0 +1,197 @@
+/* $Id$ */
+
+/***
+ 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 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/log.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 *tv, void *userdata) {
+ AvahiTimeout *t = userdata;
+
+ pa_assert(a);
+ pa_assert(e);
+ pa_assert(t);
+
+ t->callback(t, t->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 00000000..1e20ec38
--- /dev/null
+++ b/src/pulsecore/avahi-wrap.h
@@ -0,0 +1,34 @@
+#ifndef fooavahiwrapperhfoo
+#define fooavahiwrapperhfoo
+
+/* $Id$ */
+
+/***
+ 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 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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 <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/cli-command.c b/src/pulsecore/cli-command.c
new file mode 100644
index 00000000..3110a271
--- /dev/null
+++ b/src/pulsecore/cli-command.c
@@ -0,0 +1,1423 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include <pulse/xmalloc.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/sample-util.h>
+#include <pulsecore/sound-file.h>
+#include <pulsecore/play-memchunk.h>
+#include <pulsecore/autoload.h>
+#include <pulsecore/sound-file-stream.h>
+#include <pulsecore/props.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/core-error.h>
+
+#include "cli-command.h"
+
+struct command {
+ const char *name;
+ int (*proc) (pa_core *c, pa_tokenizer*t, pa_strbuf *buf, pa_bool_t *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, pa_bool_t *fail);
+static int pa_cli_command_help(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail);
+static int pa_cli_command_modules(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail);
+static int pa_cli_command_clients(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail);
+static int pa_cli_command_sinks(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail);
+static int pa_cli_command_sources(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail);
+static int pa_cli_command_sink_inputs(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail);
+static int pa_cli_command_source_outputs(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail);
+static int pa_cli_command_stat(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail);
+static int pa_cli_command_info(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail);
+static int pa_cli_command_load(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail);
+static int pa_cli_command_unload(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail);
+static int pa_cli_command_sink_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail);
+static int pa_cli_command_sink_input_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail);
+static int pa_cli_command_source_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail);
+static int pa_cli_command_sink_mute(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail);
+static int pa_cli_command_source_mute(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail);
+static int pa_cli_command_sink_input_mute(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail);
+static int pa_cli_command_sink_default(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail);
+static int pa_cli_command_source_default(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail);
+static int pa_cli_command_kill_client(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail);
+static int pa_cli_command_kill_sink_input(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail);
+static int pa_cli_command_kill_source_output(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail);
+static int pa_cli_command_scache_play(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail);
+static int pa_cli_command_scache_remove(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail);
+static int pa_cli_command_scache_list(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail);
+static int pa_cli_command_scache_load(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail);
+static int pa_cli_command_scache_load_dir(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail);
+static int pa_cli_command_play_file(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail);
+static int pa_cli_command_autoload_list(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail);
+static int pa_cli_command_autoload_add(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail);
+static int pa_cli_command_autoload_remove(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail);
+static int pa_cli_command_dump(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail);
+static int pa_cli_command_list_props(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail);
+static int pa_cli_command_move_sink_input(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail);
+static int pa_cli_command_move_source_output(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail);
+static int pa_cli_command_vacuum(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail);
+static int pa_cli_command_suspend_sink(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail);
+static int pa_cli_command_suspend_source(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail);
+static int pa_cli_command_suspend(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail);
+
+/* A method table for all available commands */
+
+static const struct command commands[] = {
+ { "exit", pa_cli_command_exit, "Terminate the daemon", 1 },
+ { "help", pa_cli_command_help, "Show this help", 1 },
+ { "list-modules", pa_cli_command_modules, "List loaded modules", 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)", 2},
+ { "set-sink-volume", pa_cli_command_sink_volume, "Set the volume of a sink (args: index|name, volume)", 3},
+ { "set-sink-input-volume", pa_cli_command_sink_input_volume, "Set the volume of a sink input (args: index, 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-sink-input-mute", pa_cli_command_sink_input_mute, "Set the mute switch of a sink input (args: index, bool)", 3},
+ { "set-source-mute", pa_cli_command_source_mute, "Set the mute switch of a source (args: index|name, 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},
+ { "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},
+ { "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},
+ { "play-file", pa_cli_command_play_file, "Play a sound file (args: filename, sink|index)", 3},
+ { "list-autoload", pa_cli_command_autoload_list, "List autoload entries", 1},
+ { "add-autoload-sink", pa_cli_command_autoload_add, "Add autoload entry for a sink (args: sink, module name, arguments)", 4},
+ { "add-autoload-source", pa_cli_command_autoload_add, "Add autoload entry for a source (args: source, module name, arguments)", 4},
+ { "remove-autoload-sink", pa_cli_command_autoload_remove, "Remove autoload entry for a sink (args: name)", 2},
+ { "remove-autoload-source", pa_cli_command_autoload_remove, "Remove autoload entry for a source (args: name)", 2},
+ { "dump", pa_cli_command_dump, "Dump daemon configuration", 1},
+ { "list-props", pa_cli_command_list_props, NULL, 1},
+ { "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},
+ { "vacuum", pa_cli_command_vacuum, NULL, 1},
+ { "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},
+ { 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, pa_bool_t *fail) {
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ c->mainloop->quit(c->mainloop, 0);
+ return 0;
+}
+
+static int pa_cli_command_help(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *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, pa_bool_t *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, pa_bool_t *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_sinks(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *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, pa_bool_t *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, pa_bool_t *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, pa_bool_t *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, pa_bool_t *fail) {
+ char s[256];
+ const pa_mempool_stat *stat;
+ unsigned k;
+ const char *def_sink, *def_source;
+
+ 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);
+
+ stat = pa_mempool_get_stat(c->mempool);
+
+ pa_strbuf_printf(buf, "Memory blocks currently allocated: %u, size: %s.\n",
+ (unsigned) pa_atomic_load(&stat->n_allocated),
+ pa_bytes_snprint(s, sizeof(s), (size_t) pa_atomic_load(&stat->allocated_size)));
+
+ pa_strbuf_printf(buf, "Memory blocks allocated during the whole lifetime: %u, size: %s.\n",
+ (unsigned) pa_atomic_load(&stat->n_accumulated),
+ pa_bytes_snprint(s, sizeof(s), (size_t) pa_atomic_load(&stat->accumulated_size)));
+
+ pa_strbuf_printf(buf, "Memory blocks imported from other processes: %u, size: %s.\n",
+ (unsigned) pa_atomic_load(&stat->n_imported),
+ pa_bytes_snprint(s, sizeof(s), (size_t) pa_atomic_load(&stat->imported_size)));
+
+ pa_strbuf_printf(buf, "Memory blocks exported to other processes: %u, size: %s.\n",
+ (unsigned) pa_atomic_load(&stat->n_exported),
+ pa_bytes_snprint(s, sizeof(s), (size_t) pa_atomic_load(&stat->exported_size)));
+
+ pa_strbuf_printf(buf, "Total sample cache size: %s.\n",
+ pa_bytes_snprint(s, sizeof(s), pa_scache_total_size(c)));
+
+ pa_strbuf_printf(buf, "Default sample spec: %s\n",
+ pa_sample_spec_snprint(s, sizeof(s), &c->default_sample_spec));
+
+ def_sink = pa_namereg_get_default_sink_name(c);
+ def_source = pa_namereg_get_default_source_name(c);
+ pa_strbuf_printf(buf, "Default sink name: %s\n"
+ "Default source name: %s\n",
+ def_sink ? def_sink : "none",
+ def_source ? def_source : "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(&stat->n_allocated_by_type[k]),
+ (unsigned) pa_atomic_load(&stat->n_accumulated_by_type[k]));
+
+ return 0;
+}
+
+static int pa_cli_command_info(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *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_sink_inputs(c, t, buf, fail);
+ pa_cli_command_source_outputs(c, t, buf, fail);
+ pa_cli_command_scache_list(c, t, buf, fail);
+ pa_cli_command_autoload_list(c, t, buf, fail);
+ return 0;
+}
+
+static int pa_cli_command_load(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) {
+ pa_module *m;
+ const char *name;
+
+ 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 (!(m = pa_module_load(c, name, pa_tokenizer_get(t, 2)))) {
+ 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, pa_bool_t *fail) {
+ pa_module *m;
+ uint32_t idx;
+ const char *i;
+ char *e;
+
+ 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.\n");
+ return -1;
+ }
+
+ idx = (uint32_t) strtoul(i, &e, 10);
+ if (*e || !(m = pa_idxset_get_by_index(c->modules, idx))) {
+ pa_strbuf_puts(buf, "Invalid module index.\n");
+ return -1;
+ }
+
+ pa_module_unload_request(m);
+ return 0;
+}
+
+static int pa_cli_command_sink_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *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, 0x100 is normal volume)\n");
+ return -1;
+ }
+
+ if (pa_atou(v, &volume) < 0) {
+ pa_strbuf_puts(buf, "Failed to parse volume.\n");
+ return -1;
+ }
+
+ if (!(sink = pa_namereg_get(c, n, PA_NAMEREG_SINK, 1))) {
+ pa_strbuf_puts(buf, "No sink found by this name or index.\n");
+ return -1;
+ }
+
+ pa_cvolume_set(&cvolume, sink->sample_spec.channels, volume);
+ pa_sink_set_volume(sink, &cvolume);
+ return 0;
+}
+
+static int pa_cli_command_sink_input_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *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, 0x100 is normal volume)\n");
+ return -1;
+ }
+
+ if (pa_atou(v, &volume) < 0) {
+ pa_strbuf_puts(buf, "Failed to parse volume.\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_cvolume_set(&cvolume, si->sample_spec.channels, volume);
+ pa_sink_input_set_volume(si, &cvolume);
+ return 0;
+}
+
+static int pa_cli_command_source_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *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, 0x100 is normal volume)\n");
+ return -1;
+ }
+
+ if (pa_atou(v, &volume) < 0) {
+ pa_strbuf_puts(buf, "Failed to parse volume.\n");
+ return -1;
+ }
+
+ if (!(source = pa_namereg_get(c, n, PA_NAMEREG_SOURCE, 1))) {
+ pa_strbuf_puts(buf, "No source found by this name or index.\n");
+ return -1;
+ }
+
+ pa_cvolume_set(&cvolume, source->sample_spec.channels, volume);
+ pa_source_set_volume(source, &cvolume);
+ return 0;
+}
+
+static int pa_cli_command_sink_mute(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *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 (pa_atoi(m, &mute) < 0) {
+ pa_strbuf_puts(buf, "Failed to parse mute switch.\n");
+ return -1;
+ }
+
+ if (!(sink = pa_namereg_get(c, n, PA_NAMEREG_SINK, 1))) {
+ pa_strbuf_puts(buf, "No sink found by this name or index.\n");
+ return -1;
+ }
+
+ pa_sink_set_mute(sink, mute);
+ return 0;
+}
+
+static int pa_cli_command_source_mute(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *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 (pa_atoi(m, &mute) < 0) {
+ pa_strbuf_puts(buf, "Failed to parse mute switch.\n");
+ return -1;
+ }
+
+ if (!(source = pa_namereg_get(c, n, PA_NAMEREG_SOURCE, 1))) {
+ pa_strbuf_puts(buf, "No sink found by this name or index.\n");
+ return -1;
+ }
+
+ pa_source_set_mute(source, mute);
+ return 0;
+}
+
+static int pa_cli_command_sink_input_mute(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *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 volume >= 0. (0 is muted, 0x100 is normal volume)\n");
+ return -1;
+ }
+
+ if (pa_atoi(v, &mute) < 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);
+ return 0;
+}
+
+static int pa_cli_command_sink_default(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *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 sink either by its name or its index.\n");
+ return -1;
+ }
+
+ pa_namereg_set_default(c, n, PA_NAMEREG_SINK);
+ return 0;
+}
+
+static int pa_cli_command_source_default(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *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 source either by its name or its index.\n");
+ return -1;
+ }
+
+ pa_namereg_set_default(c, n, PA_NAMEREG_SOURCE);
+ return 0;
+}
+
+static int pa_cli_command_kill_client(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *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, pa_bool_t *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, pa_bool_t *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, pa_bool_t *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, pa_bool_t *fail) {
+ const char *n, *sink_name;
+ pa_sink *sink;
+
+ 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, 1))) {
+ pa_strbuf_puts(buf, "No sink by that name.\n");
+ return -1;
+ }
+
+ if (pa_scache_play_item(c, n, sink, PA_VOLUME_NORM) < 0) {
+ pa_strbuf_puts(buf, "Failed to play sample.\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int pa_cli_command_scache_remove(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *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, pa_bool_t *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, pa_bool_t *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, pa_bool_t *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, 1))) {
+ pa_strbuf_puts(buf, "No sink by that name.\n");
+ return -1;
+ }
+
+
+ return pa_play_file(sink, fname, NULL);
+}
+
+static int pa_cli_command_autoload_add(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) {
+ const char *a, *b;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ if (!(a = pa_tokenizer_get(t, 1)) || !(b = pa_tokenizer_get(t, 2))) {
+ pa_strbuf_puts(buf, "You need to specify a device name, a filename or a module name and optionally module arguments\n");
+ return -1;
+ }
+
+ pa_autoload_add(c, a, strstr(pa_tokenizer_get(t, 0), "sink") ? PA_NAMEREG_SINK : PA_NAMEREG_SOURCE, b, pa_tokenizer_get(t, 3), NULL);
+
+ return 0;
+}
+
+static int pa_cli_command_autoload_remove(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) {
+ const char *name;
+
+ 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 a device name\n");
+ return -1;
+ }
+
+ if (pa_autoload_remove_by_name(c, name, strstr(pa_tokenizer_get(t, 0), "sink") ? PA_NAMEREG_SINK : PA_NAMEREG_SOURCE) < 0) {
+ pa_strbuf_puts(buf, "Failed to remove autload entry\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int pa_cli_command_autoload_list(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) {
+ char *s;
+
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ pa_assert_se(s = pa_autoload_list_to_string(c));
+ pa_strbuf_puts(buf, s);
+ pa_xfree(s);
+
+ return 0;
+}
+
+static int pa_cli_command_list_props(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) {
+ pa_core_assert_ref(c);
+ pa_assert(t);
+ pa_assert(buf);
+ pa_assert(fail);
+
+ pa_property_dump(c, buf);
+ return 0;
+}
+
+static int pa_cli_command_vacuum(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *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, pa_bool_t *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, 1))) {
+ pa_strbuf_puts(buf, "No sink found by this name or index.\n");
+ return -1;
+ }
+
+ if (pa_sink_input_move_to(si, sink, 0) < 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, pa_bool_t *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, 1))) {
+ pa_strbuf_puts(buf, "No source found by this name or index.\n");
+ return -1;
+ }
+
+ if (pa_source_output_move_to(so, source) < 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, pa_bool_t *fail) {
+ const char *n, *m;
+ pa_sink *sink;
+ int suspend;
+
+ 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 (pa_atoi(m, &suspend) < 0) {
+ pa_strbuf_puts(buf, "Failed to parse suspend switch.\n");
+ return -1;
+ }
+
+ if (!(sink = pa_namereg_get(c, n, PA_NAMEREG_SINK, 1))) {
+ pa_strbuf_puts(buf, "No sink found by this name or index.\n");
+ return -1;
+ }
+
+ pa_sink_suspend(sink, suspend);
+ return 0;
+}
+
+static int pa_cli_command_suspend_source(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) {
+ const char *n, *m;
+ pa_source *source;
+ int suspend;
+
+ 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 (pa_atoi(m, &suspend) < 0) {
+ pa_strbuf_puts(buf, "Failed to parse suspend switch.\n");
+ return -1;
+ }
+
+ if (!(source = pa_namereg_get(c, n, PA_NAMEREG_SOURCE, 1))) {
+ pa_strbuf_puts(buf, "No source found by this name or index.\n");
+ return -1;
+ }
+
+ pa_source_suspend(source, suspend);
+ return 0;
+}
+
+static int pa_cli_command_suspend(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) {
+ const char *m;
+ int suspend;
+ int ret;
+
+ 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 (pa_atoi(m, &suspend) < 0) {
+ pa_strbuf_puts(buf, "Failed to parse suspend switch.\n");
+ return -1;
+ }
+
+ ret = - (pa_sink_suspend_all(c, suspend) < 0);
+ if (pa_source_suspend_all(c, suspend) < 0)
+ ret = -1;
+
+ if (ret < 0)
+ pa_strbuf_puts(buf, "Failed to resume/suspend all sinks/sources.\n");
+
+ return 0;
+}
+
+static int pa_cli_command_dump(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) {
+ pa_module *m;
+ pa_sink *sink;
+ pa_source *source;
+ int nl;
+ const char *p;
+ uint32_t idx;
+ char txt[256];
+ time_t now;
+ void *i;
+ pa_autoload_entry *a;
+
+ 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
+
+ for (m = pa_idxset_first(c->modules, &idx); m; m = pa_idxset_next(c->modules, &idx)) {
+ if (m->auto_unload)
+ continue;
+
+ 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 = 0;
+
+ for (sink = pa_idxset_first(c->sinks, &idx); sink; sink = pa_idxset_next(c->sinks, &idx)) {
+ if (sink->module && sink->module->auto_unload)
+ continue;
+
+ if (!nl) {
+ pa_strbuf_puts(buf, "\n");
+ nl = 1;
+ }
+
+ pa_strbuf_printf(buf, "set-sink-volume %s 0x%03x\n", sink->name, pa_cvolume_avg(pa_sink_get_volume(sink)));
+ pa_strbuf_printf(buf, "set-sink-mute %s %d\n", sink->name, pa_sink_get_mute(sink));
+ }
+
+ for (source = pa_idxset_first(c->sources, &idx); source; source = pa_idxset_next(c->sources, &idx)) {
+ if (source->module && source->module->auto_unload)
+ continue;
+
+ if (!nl) {
+ pa_strbuf_puts(buf, "\n");
+ nl = 1;
+ }
+
+ pa_strbuf_printf(buf, "set-source-volume %s 0x%03x\n", source->name, pa_cvolume_avg(pa_source_get_volume(source)));
+ pa_strbuf_printf(buf, "set-source-mute %s %d\n", source->name, pa_source_get_mute(source));
+ }
+
+
+ if (c->autoload_hashmap) {
+ nl = 0;
+
+ i = NULL;
+ while ((a = pa_hashmap_iterate(c->autoload_hashmap, &i, NULL))) {
+
+ if (!nl) {
+ pa_strbuf_puts(buf, "\n");
+ nl = 1;
+ }
+
+ pa_strbuf_printf(buf, "add-autoload-%s %s %s", a->type == PA_NAMEREG_SINK ? "sink" : "source", a->name, a->module);
+
+ if (a->argument)
+ pa_strbuf_printf(buf, " %s", a->argument);
+
+ pa_strbuf_puts(buf, "\n");
+ }
+ }
+
+ nl = 0;
+
+ if ((p = pa_namereg_get_default_sink_name(c))) {
+ if (!nl) {
+ pa_strbuf_puts(buf, "\n");
+ nl = 1;
+ }
+ pa_strbuf_printf(buf, "set-default-sink %s\n", p);
+ }
+
+ if ((p = pa_namereg_get_default_source_name(c))) {
+ if (!nl) {
+ pa_strbuf_puts(buf, "\n");
+ nl = 1;
+ }
+ pa_strbuf_printf(buf, "set-default-source %s\n", p);
+ }
+
+ pa_strbuf_puts(buf, "\n### EOF\n");
+
+ return 0;
+}
+
+int pa_cli_command_execute_line_stateful(pa_core *c, const char *s, pa_strbuf *buf, pa_bool_t *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)) {
+ const char *filename = cs+l+strspn(cs+l, whitespace);
+ if (pa_cli_command_execute_file(c, filename, buf, fail) < 0)
+ if (*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 = access(filename, F_OK) == 0 ? IFSTATE_TRUE : IFSTATE_FALSE;
+ pa_log_debug("Checking for existance of '%s': %s", filename, *ifstate == IFSTATE_TRUE ? "success" : "failure");
+ }
+ } 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, pa_bool_t *fail) {
+ return pa_cli_command_execute_line_stateful(c, s, buf, fail, NULL);
+}
+
+int pa_cli_command_execute_file(pa_core *c, const char *fn, pa_strbuf *buf, pa_bool_t *fail) {
+ char line[1024];
+ FILE *f = NULL;
+ int ifstate = IFSTATE_NONE;
+ int ret = -1;
+
+ pa_assert(c);
+ pa_assert(fn);
+ pa_assert(buf);
+
+ if (!(f = fopen(fn, "r"))) {
+ pa_strbuf_printf(buf, "open('%s') failed: %s\n", fn, pa_cstrerror(errno));
+ if (!*fail)
+ ret = 0;
+ goto fail;
+ }
+
+ while (fgets(line, sizeof(line), f)) {
+ char *e = line + strcspn(line, linebreak);
+ *e = 0;
+
+ if (pa_cli_command_execute_line_stateful(c, line, buf, fail, &ifstate) < 0 && *fail)
+ goto fail;
+ }
+
+ ret = 0;
+
+fail:
+ if (f)
+ fclose(f);
+
+ return ret;
+}
+
+int pa_cli_command_execute(pa_core *c, const char *s, pa_strbuf *buf, pa_bool_t *fail) {
+ const char *p;
+ int ifstate = IFSTATE_NONE;
+
+ pa_assert(c);
+ pa_assert(s);
+ pa_assert(buf);
+
+ 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 00000000..c90c8e08
--- /dev/null
+++ b/src/pulsecore/cli-command.h
@@ -0,0 +1,45 @@
+#ifndef fooclicommandhfoo
+#define fooclicommandhfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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 <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, pa_bool_t *fail);
+
+/* Execute a whole file of CLI commands */
+int pa_cli_command_execute_file(pa_core *c, const char *fn, pa_strbuf *buf, pa_bool_t *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, pa_bool_t *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, pa_bool_t *fail, int *ifstate);
+
+#endif
diff --git a/src/pulsecore/cli-text.c b/src/pulsecore/cli-text.c
new file mode 100644
index 00000000..b64cafe2
--- /dev/null
+++ b/src/pulsecore/cli-text.c
@@ -0,0 +1,450 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include <pulse/volume.h>
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/module.h>
+#include <pulsecore/client.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/sample-util.h>
+#include <pulsecore/core-scache.h>
+#include <pulsecore/autoload.h>
+#include <pulsecore/macro.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));
+
+ for (m = pa_idxset_first(c->modules, &idx); m; m = pa_idxset_next(c->modules, &idx)) {
+ pa_strbuf_printf(s, " index: %u\n"
+ "\tname: <%s>\n"
+ "\targument: <%s>\n"
+ "\tused: %i\n"
+ "\tauto unload: %s\n",
+ m->index, m->name, m->argument ? m->argument : "", m->n_used,
+ m->auto_unload ? "yes" : "no");
+ }
+
+ return pa_strbuf_tostring_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));
+
+ for (client = pa_idxset_first(c->clients, &idx); client; client = pa_idxset_next(c->clients, &idx)) {
+ pa_strbuf_printf(s, " index: %u\n\tname: <%s>\n\tdriver: <%s>\n", client->index, client->name, client->driver);
+
+ if (client->owner)
+ pa_strbuf_printf(s, "\towner module: <%u>\n", client->owner->index);
+ }
+
+ return pa_strbuf_tostring_free(s);
+}
+
+char *pa_sink_list_to_string(pa_core *c) {
+ pa_strbuf *s;
+ pa_sink *sink;
+ uint32_t idx = PA_IDXSET_INVALID;
+ static const char* const state_table[] = {
+ [PA_SINK_RUNNING] = "RUNNING",
+ [PA_SINK_SUSPENDED] = "SUSPENDED",
+ [PA_SINK_IDLE] = "IDLE",
+ [PA_SINK_UNLINKED] = "UNLINKED"
+ };
+ pa_assert(c);
+
+ s = pa_strbuf_new();
+
+ pa_strbuf_printf(s, "%u sink(s) available.\n", pa_idxset_size(c->sinks));
+
+ for (sink = pa_idxset_first(c->sinks, &idx); sink; sink = pa_idxset_next(c->sinks, &idx)) {
+ char ss[PA_SAMPLE_SPEC_SNPRINT_MAX], cv[PA_CVOLUME_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX];
+
+ pa_strbuf_printf(
+ s,
+ " %c index: %u\n"
+ "\tname: <%s>\n"
+ "\tdriver: <%s>\n"
+ "\tflags: %s%s%s%s\n"
+ "\tstate: %s\n"
+ "\tvolume: <%s>\n"
+ "\tmute: <%i>\n"
+ "\tlatency: <%0.0f usec>\n"
+ "\tmonitor source: <%u>\n"
+ "\tsample spec: <%s>\n"
+ "\tchannel map: <%s>\n"
+ "\tused by: <%u>\n"
+ "\tlinked by: <%u>\n",
+ c->default_sink_name && !strcmp(sink->name, c->default_sink_name) ? '*' : ' ',
+ sink->index,
+ sink->name,
+ sink->driver,
+ sink->flags & PA_SINK_HW_VOLUME_CTRL ? "HW_VOLUME_CTRL " : "",
+ sink->flags & PA_SINK_LATENCY ? "LATENCY " : "",
+ sink->flags & PA_SINK_HARDWARE ? "HARDWARE " : "",
+ sink->flags & PA_SINK_NETWORK ? "NETWORK " : "",
+ state_table[pa_sink_get_state(sink)],
+ pa_cvolume_snprint(cv, sizeof(cv), pa_sink_get_volume(sink)),
+ !!pa_sink_get_mute(sink),
+ (double) pa_sink_get_latency(sink),
+ 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),
+ pa_sink_used_by(sink),
+ pa_sink_linked_by(sink));
+
+ if (sink->module)
+ pa_strbuf_printf(s, "\tmodule: <%u>\n", sink->module->index);
+ if (sink->description)
+ pa_strbuf_printf(s, "\tdescription: <%s>\n", sink->description);
+ }
+
+ return pa_strbuf_tostring_free(s);
+}
+
+char *pa_source_list_to_string(pa_core *c) {
+ pa_strbuf *s;
+ pa_source *source;
+ uint32_t idx = PA_IDXSET_INVALID;
+ static const char* const state_table[] = {
+ [PA_SOURCE_RUNNING] = "RUNNING",
+ [PA_SOURCE_SUSPENDED] = "SUSPENDED",
+ [PA_SOURCE_IDLE] = "IDLE",
+ [PA_SOURCE_UNLINKED] = "UNLINKED"
+ };
+ pa_assert(c);
+
+ s = pa_strbuf_new();
+
+ pa_strbuf_printf(s, "%u source(s) available.\n", pa_idxset_size(c->sources));
+
+ for (source = pa_idxset_first(c->sources, &idx); source; source = pa_idxset_next(c->sources, &idx)) {
+ char ss[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX], cv[PA_CVOLUME_SNPRINT_MAX];
+
+
+ pa_strbuf_printf(
+ s,
+ " %c index: %u\n"
+ "\tname: <%s>\n"
+ "\tdriver: <%s>\n"
+ "\tflags: %s%s%s%s\n"
+ "\tstate: %s\n"
+ "\tvolume: <%s>\n"
+ "\tmute: <%u>\n"
+ "\tlatency: <%0.0f usec>\n"
+ "\tsample spec: <%s>\n"
+ "\tchannel map: <%s>\n"
+ "\tused by: <%u>\n"
+ "\tlinked by: <%u>\n",
+ c->default_source_name && !strcmp(source->name, c->default_source_name) ? '*' : ' ',
+ source->index,
+ source->name,
+ source->driver,
+ source->flags & PA_SOURCE_HW_VOLUME_CTRL ? "HW_VOLUME_CTRL " : "",
+ source->flags & PA_SOURCE_LATENCY ? "LATENCY " : "",
+ source->flags & PA_SOURCE_HARDWARE ? "HARDWARE " : "",
+ source->flags & PA_SOURCE_NETWORK ? "NETWORK " : "",
+ state_table[pa_source_get_state(source)],
+ pa_cvolume_snprint(cv, sizeof(cv), pa_source_get_volume(source)),
+ !!pa_source_get_mute(source),
+ (double) pa_source_get_latency(source),
+ pa_sample_spec_snprint(ss, sizeof(ss), &source->sample_spec),
+ pa_channel_map_snprint(cm, sizeof(cm), &source->channel_map),
+ pa_source_used_by(source),
+ pa_source_linked_by(source));
+
+ if (source->monitor_of)
+ pa_strbuf_printf(s, "\tmonitor_of: <%u>\n", source->monitor_of->index);
+ if (source->module)
+ pa_strbuf_printf(s, "\tmodule: <%u>\n", source->module->index);
+ if (source->description)
+ pa_strbuf_printf(s, "\tdescription: <%s>\n", source->description);
+ }
+
+ return pa_strbuf_tostring_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_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 outputs(s) available.\n", pa_idxset_size(c->source_outputs));
+
+ for (o = pa_idxset_first(c->source_outputs, &idx); o; o = pa_idxset_next(c->source_outputs, &idx)) {
+ char ss[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX];
+
+ pa_assert(o->source);
+
+ pa_strbuf_printf(
+ s,
+ " index: %u\n"
+ "\tname: '%s'\n"
+ "\tdriver: <%s>\n"
+ "\tflags: %s%s%s%s%s%s%s\n"
+ "\tstate: %s\n"
+ "\tsource: <%u> '%s'\n"
+ "\tlatency: <%0.0f usec>\n"
+ "\tsample spec: <%s>\n"
+ "\tchannel map: <%s>\n"
+ "\tresample method: %s\n",
+ o->index,
+ o->name,
+ 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_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 " : "",
+ state_table[pa_source_output_get_state(o)],
+ o->source->index, o->source->name,
+ (double) pa_source_output_get_latency(o),
+ pa_sample_spec_snprint(ss, sizeof(ss), &o->sample_spec),
+ pa_channel_map_snprint(cm, sizeof(cm), &o->channel_map),
+ pa_resample_method_to_string(pa_source_output_get_resample_method(o)));
+ 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, o->client->name);
+ }
+
+ return pa_strbuf_tostring_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_RUNNING] = "RUNNING",
+ [PA_SINK_INPUT_DRAINED] = "DRAINED",
+ [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));
+
+ for (i = pa_idxset_first(c->sink_inputs, &idx); i; i = pa_idxset_next(c->sink_inputs, &idx)) {
+ char ss[PA_SAMPLE_SPEC_SNPRINT_MAX], cv[PA_CVOLUME_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX];
+
+ pa_assert(i->sink);
+
+ pa_strbuf_printf(
+ s,
+ " index: %u\n"
+ "\tname: <%s>\n"
+ "\tdriver: <%s>\n"
+ "\tflags: %s%s%s%s%s%s%s\n"
+ "\tstate: %s\n"
+ "\tsink: <%u> '%s'\n"
+ "\tvolume: <%s>\n"
+ "\tmute: <%i>\n"
+ "\tlatency: <%0.0f usec>\n"
+ "\tsample spec: <%s>\n"
+ "\tchannel map: <%s>\n"
+ "\tresample method: %s\n",
+ i->index,
+ i->name,
+ 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_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 " : "",
+ state_table[pa_sink_input_get_state(i)],
+ i->sink->index, i->sink->name,
+ pa_cvolume_snprint(cv, sizeof(cv), pa_sink_input_get_volume(i)),
+ !!pa_sink_input_get_mute(i),
+ (double) pa_sink_input_get_latency(i),
+ pa_sample_spec_snprint(ss, sizeof(ss), &i->sample_spec),
+ pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map),
+ pa_resample_method_to_string(pa_sink_input_get_resample_method(i)));
+
+ 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, i->client->name);
+ }
+
+ return pa_strbuf_tostring_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 entries available.\n", c->scache ? pa_idxset_size(c->scache) : 0);
+
+ if (c->scache) {
+ pa_scache_entry *e;
+ uint32_t idx = PA_IDXSET_INVALID;
+
+ for (e = pa_idxset_first(c->scache, &idx); e; e = pa_idxset_next(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";
+
+ 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 / pa_bytes_per_second(&e->sample_spec);
+ }
+
+ pa_strbuf_printf(
+ s,
+ " name: <%s>\n"
+ "\tindex: <%u>\n"
+ "\tsample spec: <%s>\n"
+ "\tchannel map: <%s>\n"
+ "\tlength: <%lu>\n"
+ "\tduration: <%0.1fs>\n"
+ "\tvolume: <%s>\n"
+ "\tlazy: %s\n"
+ "\tfilename: %s\n",
+ e->name,
+ e->index,
+ ss,
+ cm,
+ (long unsigned)(e->memchunk.memblock ? e->memchunk.length : 0),
+ l,
+ pa_cvolume_snprint(cv, sizeof(cv), &e->volume),
+ e->lazy ? "yes" : "no",
+ e->filename ? e->filename : "n/a");
+ }
+ }
+
+ return pa_strbuf_tostring_free(s);
+}
+
+char *pa_autoload_list_to_string(pa_core *c) {
+ pa_strbuf *s;
+ pa_assert(c);
+
+ s = pa_strbuf_new();
+
+ pa_strbuf_printf(s, "%u autoload entries available.\n", c->autoload_hashmap ? pa_hashmap_size(c->autoload_hashmap) : 0);
+
+ if (c->autoload_hashmap) {
+ pa_autoload_entry *e;
+ void *state = NULL;
+
+ while ((e = pa_hashmap_iterate(c->autoload_hashmap, &state, NULL))) {
+ pa_strbuf_printf(
+ s, " name: <%s>\n\ttype: <%s>\n\tindex: <%u>\n\tmodule_name: <%s>\n\targuments: <%s>\n",
+ e->name,
+ e->type == PA_NAMEREG_SOURCE ? "source" : "sink",
+ e->index,
+ e->module,
+ e->argument ? e->argument : "");
+
+ }
+ }
+
+ return pa_strbuf_tostring_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_module_list_to_string(c);
+ break;
+ case 6:
+ t = pa_scache_list_to_string(c);
+ break;
+ case 7:
+ t = pa_autoload_list_to_string(c);
+ break;
+ }
+
+ pa_strbuf_puts(s, t);
+ pa_xfree(t);
+ }
+
+ return pa_strbuf_tostring_free(s);
+}
diff --git a/src/pulsecore/cli-text.h b/src/pulsecore/cli-text.h
new file mode 100644
index 00000000..9e5bf081
--- /dev/null
+++ b/src/pulsecore/cli-text.h
@@ -0,0 +1,44 @@
+#ifndef fooclitexthfoo
+#define fooclitexthfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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 <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_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_autoload_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 00000000..85e08634
--- /dev/null
+++ b/src/pulsecore/cli.c
@@ -0,0 +1,155 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/ioline.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/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;
+
+ void (*eof_callback)(pa_cli *c, void *userdata);
+ void *userdata;
+
+ pa_client *client;
+
+ pa_bool_t fail, kill_requested;
+ int defer_kill;
+};
+
+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_assert(io);
+
+ c = pa_xnew(pa_cli, 1);
+ c->core = core;
+ pa_assert_se(c->line = pa_ioline_new(io));
+
+ c->userdata = NULL;
+ c->eof_callback = NULL;
+
+ pa_iochannel_socket_peer_to_string(io, cname, sizeof(cname));
+ pa_assert_se(c->client = pa_client_new(core, __FILE__, cname));
+ c->client->kill = client_kill;
+ c->client->userdata = c;
+ c->client->owner = m;
+
+ pa_ioline_set_callback(c->line, line_callback, c);
+ pa_ioline_puts(c->line, "Welcome to PulseAudio! Use \"help\" for usage information.\n"PROMPT);
+
+ c->fail = c->kill_requested = FALSE;
+ c->defer_kill = 0;
+
+ 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);
+}
+
+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;
+ }
+
+ pa_assert_se(buf = pa_strbuf_new());
+ c->defer_kill++;
+ pa_cli_command_execute_line(c->core, s, buf, &c->fail);
+ c->defer_kill--;
+ pa_ioline_puts(line, p = pa_strbuf_tostring_free(buf));
+ pa_xfree(p);
+
+ if (c->kill_requested) {
+ if (c->eof_callback)
+ c->eof_callback(c, c->userdata);
+ } else
+ pa_ioline_puts(line, PROMPT);
+}
+
+void pa_cli_set_eof_callback(pa_cli *c, void (*cb)(pa_cli*c, void *userdata), void *userdata) {
+ pa_assert(c);
+
+ c->eof_callback = cb;
+ c->userdata = userdata;
+}
diff --git a/src/pulsecore/cli.h b/src/pulsecore/cli.h
new file mode 100644
index 00000000..2b58d458
--- /dev/null
+++ b/src/pulsecore/cli.h
@@ -0,0 +1,40 @@
+#ifndef fooclihfoo
+#define fooclihfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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 <pulsecore/iochannel.h>
+#include <pulsecore/core.h>
+#include <pulsecore/module.h>
+
+typedef struct pa_cli pa_cli;
+
+/* 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, void (*cb)(pa_cli*c, void *userdata), void *userdata);
+
+#endif
diff --git a/src/pulsecore/client.c b/src/pulsecore/client.c
new file mode 100644
index 00000000..319b8387
--- /dev/null
+++ b/src/pulsecore/client.c
@@ -0,0 +1,100 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/core-subscribe.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+
+#include "client.h"
+
+pa_client *pa_client_new(pa_core *core, const char *driver, const char *name) {
+ pa_client *c;
+
+ pa_core_assert_ref(core);
+
+ c = pa_xnew(pa_client, 1);
+ c->name = pa_xstrdup(name);
+ c->driver = pa_xstrdup(driver);
+ c->owner = NULL;
+ c->core = core;
+
+ c->kill = NULL;
+ c->userdata = NULL;
+
+ pa_assert_se(pa_idxset_put(core->clients, c, &c->index) >= 0);
+
+ pa_log_info("Created %u \"%s\"", c->index, c->name);
+ pa_subscription_post(core, PA_SUBSCRIPTION_EVENT_CLIENT|PA_SUBSCRIPTION_EVENT_NEW, c->index);
+
+ pa_core_check_quit(core);
+
+ return c;
+}
+
+void pa_client_free(pa_client *c) {
+ pa_assert(c);
+ pa_assert(c->core);
+
+ pa_idxset_remove_by_data(c->core->clients, c, NULL);
+
+ pa_core_check_quit(c->core);
+
+ pa_log_info("Freed %u \"%s\"", c->index, c->name);
+ pa_subscription_post(c->core, PA_SUBSCRIPTION_EVENT_CLIENT|PA_SUBSCRIPTION_EVENT_REMOVE, c->index);
+ pa_xfree(c->name);
+ pa_xfree(c->driver);
+ pa_xfree(c);
+}
+
+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_log_info("Client %u changed name from \"%s\" to \"%s\"", c->index, c->name, name);
+
+ pa_xfree(c->name);
+ c->name = pa_xstrdup(name);
+
+ pa_subscription_post(c->core, PA_SUBSCRIPTION_EVENT_CLIENT|PA_SUBSCRIPTION_EVENT_CHANGE, c->index);
+}
diff --git a/src/pulsecore/client.h b/src/pulsecore/client.h
new file mode 100644
index 00000000..6d09b999
--- /dev/null
+++ b/src/pulsecore/client.h
@@ -0,0 +1,61 @@
+#ifndef foopulseclienthfoo
+#define foopulseclienthfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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 <inttypes.h>
+
+typedef struct pa_client pa_client;
+
+#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 he wants.*/
+
+struct pa_client {
+ uint32_t index;
+
+ pa_module *owner;
+ char *name, *driver;
+ pa_core *core;
+
+ void (*kill)(pa_client *c);
+ void *userdata;
+};
+
+pa_client *pa_client_new(pa_core *c, const char *driver, const char *name);
+
+/* 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);
+
+#endif
diff --git a/src/pulsecore/conf-parser.c b/src/pulsecore/conf-parser.c
new file mode 100644
index 00000000..12ea49c2
--- /dev/null
+++ b/src/pulsecore/conf-parser.c
@@ -0,0 +1,201 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <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 next_assignment(const char *filename, unsigned line, const pa_config_item *t, const char *lvalue, const char *rvalue, void *userdata) {
+ pa_assert(filename);
+ pa_assert(t);
+ pa_assert(lvalue);
+ pa_assert(rvalue);
+
+ for (; t->parse; t++)
+ if (!strcmp(lvalue, t->lvalue))
+ return t->parse(filename, line, lvalue, rvalue, t->data, userdata);
+
+ pa_log("[%s:%u] Unknown lvalue '%s'.", filename, line, lvalue);
+
+ return -1;
+}
+
+/* Returns non-zero when c is contained in s */
+static int in_string(char c, const char *s) {
+ pa_assert(s);
+
+ for (; *s; s++)
+ if (*s == c)
+ return 1;
+
+ return 0;
+}
+
+/* Remove all whitepsapce from the beginning and the end of *s. *s may
+ * be modified. */
+static char *strip(char *s) {
+ char *b = s+strspn(s, WHITESPACE);
+ char *e, *l = NULL;
+
+ for (e = b; *e; e++)
+ if (!in_string(*e, WHITESPACE))
+ l = e;
+
+ if (l)
+ *(l+1) = 0;
+
+ return b;
+}
+
+/* Parse a variable assignment line */
+static int parse_line(const char *filename, unsigned line, const pa_config_item *t, char *l, void *userdata) {
+ char *e, *c, *b = l+strspn(l, WHITESPACE);
+
+ if ((c = strpbrk(b, COMMENTS)))
+ *c = 0;
+
+ if (!*b)
+ return 0;
+
+ if (!(e = strchr(b, '='))) {
+ pa_log("[%s:%u] Missing '='.", filename, line);
+ return -1;
+ }
+
+ *e = 0;
+ e++;
+
+ return next_assignment(filename, line, t, strip(b), strip(e), userdata);
+}
+
+/* Go through the file and parse each line */
+int pa_config_parse(const char *filename, FILE *f, const pa_config_item *t, void *userdata) {
+ int r = -1;
+ unsigned line = 0;
+ int do_close = !f;
+
+ pa_assert(filename);
+ pa_assert(t);
+
+ if (!f && !(f = fopen(filename, "r"))) {
+ if (errno == ENOENT) {
+ r = 0;
+ goto finish;
+ }
+
+ pa_log_warn("Failed to open configuration file '%s': %s",
+ filename, pa_cstrerror(errno));
+ goto finish;
+ }
+
+ while (!feof(f)) {
+ char l[256];
+ if (!fgets(l, sizeof(l), f)) {
+ if (feof(f))
+ break;
+
+ pa_log_warn("Failed to read configuration file '%s': %s",
+ filename, pa_cstrerror(errno));
+ goto finish;
+ }
+
+ if (parse_line(filename, ++line, t, l, userdata) < 0)
+ goto finish;
+ }
+
+ r = 0;
+
+finish:
+
+ if (do_close && f)
+ fclose(f);
+
+ return r;
+}
+
+int pa_config_parse_int(const char *filename, unsigned line, const char *lvalue, const char *rvalue, void *data, PA_GCC_UNUSED void *userdata) {
+ int *i = data;
+ int32_t k;
+
+ pa_assert(filename);
+ pa_assert(lvalue);
+ pa_assert(rvalue);
+ pa_assert(data);
+
+ if (pa_atoi(rvalue, &k) < 0) {
+ pa_log("[%s:%u] Failed to parse numeric value: %s", filename, line, rvalue);
+ return -1;
+ }
+
+ *i = (int) k;
+ return 0;
+}
+
+int pa_config_parse_bool(const char *filename, unsigned line, const char *lvalue, const char *rvalue, void *data, PA_GCC_UNUSED void *userdata) {
+ int k;
+ pa_bool_t *b = data;
+
+ pa_assert(filename);
+ pa_assert(lvalue);
+ pa_assert(rvalue);
+ pa_assert(data);
+
+ if ((k = pa_parse_boolean(rvalue)) < 0) {
+ pa_log("[%s:%u] Failed to parse boolean value: %s", filename, line, rvalue);
+ return -1;
+ }
+
+ *b = !!k;
+
+ return 0;
+}
+
+int pa_config_parse_string(const char *filename, PA_GCC_UNUSED unsigned line, const char *lvalue, const char *rvalue, void *data, PA_GCC_UNUSED void *userdata) {
+ char **s = data;
+
+ pa_assert(filename);
+ pa_assert(lvalue);
+ pa_assert(rvalue);
+ pa_assert(data);
+
+ pa_xfree(*s);
+ *s = *rvalue ? pa_xstrdup(rvalue) : NULL;
+ return 0;
+}
diff --git a/src/pulsecore/conf-parser.h b/src/pulsecore/conf-parser.h
new file mode 100644
index 00000000..b56d979e
--- /dev/null
+++ b/src/pulsecore/conf-parser.h
@@ -0,0 +1,49 @@
+#ifndef fooconfparserhfoo
+#define fooconfparserhfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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 <stdio.h>
+
+/* An abstract parser for simple, line based, shallow configuration
+ * files consisting of variable assignments only. */
+
+/* Wraps info for parsing a specific configuration variable */
+typedef struct pa_config_item {
+ const char *lvalue; /* name of the variable */
+ int (*parse)(const char *filename, unsigned line, const char *lvalue, const char *rvalue, void *data, void *userdata); /* Function that is called to parse the variable's value */
+ void *data; /* Where to store the variable's data */
+} pa_config_item;
+
+/* The configuration file parsing routine. Expects a table of
+ * pa_config_items in *t that is terminated by an item where lvalue is
+ * NULL */
+int pa_config_parse(const char *filename, FILE *f, const pa_config_item *t, void *userdata);
+
+/* Generic parsers for integers, booleans and strings */
+int pa_config_parse_int(const char *filename, unsigned line, const char *lvalue, const char *rvalue, void *data, void *userdata);
+int pa_config_parse_bool(const char *filename, unsigned line, const char *lvalue, const char *rvalue, void *data, void *userdata);
+int pa_config_parse_string(const char *filename, unsigned line, const char *lvalue, const char *rvalue, void *data, void *userdata);
+
+#endif
diff --git a/src/pulsecore/core-def.h b/src/pulsecore/core-def.h
new file mode 100644
index 00000000..4bc05137
--- /dev/null
+++ b/src/pulsecore/core-def.h
@@ -0,0 +1,29 @@
+#ifndef foocoredefhfoo
+#define foocoredefhfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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.
+***/
+
+/* FIXME: Remove this shit */
+
+#endif
diff --git a/src/pulsecore/core-error.c b/src/pulsecore/core-error.c
new file mode 100644
index 00000000..8a61e726
--- /dev/null
+++ b/src/pulsecore/core-error.c
@@ -0,0 +1,80 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <pulse/utf8.h>
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/native-common.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 ((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
+
+ if (!original) {
+ pa_snprintf(errbuf, sizeof(errbuf), "Unknown error %i", 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 00000000..443c4883
--- /dev/null
+++ b/src/pulsecore/core-error.h
@@ -0,0 +1,44 @@
+#ifndef foocoreerrorhfoo
+#define foocoreerrorhfoo
+
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <inttypes.h>
+#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-scache.c b/src/pulsecore/core-scache.c
new file mode 100644
index 00000000..46444a90
--- /dev/null
+++ b/src/pulsecore/core-scache.c
@@ -0,0 +1,474 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <limits.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 <pulsecore/sink-input.h>
+#include <pulsecore/sample-util.h>
+#include <pulsecore/play-memchunk.h>
+#include <pulsecore/core-subscribe.h>
+#include <pulsecore/namereg.h>
+#include <pulsecore/sound-file.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 2
+
+static void timeout_callback(pa_mainloop_api *m, pa_time_event*e, PA_GCC_UNUSED const struct timeval *tv, void *userdata) {
+ pa_core *c = userdata;
+ struct timeval ntv;
+
+ pa_assert(c);
+ pa_assert(c->mainloop == m);
+ pa_assert(c->scache_auto_unload_event == e);
+
+ pa_scache_unload_unused(c);
+
+ pa_gettimeofday(&ntv);
+ ntv.tv_sec += UNLOAD_POLL_TIME;
+ m->time_restart(e, &ntv);
+}
+
+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_xfree(e->name);
+ pa_xfree(e->filename);
+ if (e->memchunk.memblock)
+ pa_memblock_unref(e->memchunk.memblock);
+ pa_xfree(e);
+}
+
+static pa_scache_entry* scache_add_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, 0))) {
+ if (e->memchunk.memblock)
+ pa_memblock_unref(e->memchunk.memblock);
+
+ pa_xfree(e->filename);
+
+ pa_assert(e->core == c);
+
+ pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE|PA_SUBSCRIPTION_EVENT_CHANGE, e->index);
+ } else {
+ e = pa_xnew(pa_scache_entry, 1);
+
+ if (!pa_namereg_register(c, name, PA_NAMEREG_SAMPLE, e, 1)) {
+ pa_xfree(e);
+ return NULL;
+ }
+
+ e->name = pa_xstrdup(name);
+ e->core = c;
+
+ if (!c->scache) {
+ c->scache = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
+ pa_assert(c->scache);
+ }
+
+ pa_idxset_put(c->scache, e, &e->index);
+
+ pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE|PA_SUBSCRIPTION_EVENT_NEW, e->index);
+ }
+
+ e->last_used_time = 0;
+ e->memchunk.memblock = NULL;
+ e->memchunk.index = e->memchunk.length = 0;
+ e->filename = NULL;
+ e->lazy = 0;
+ e->last_used_time = 0;
+
+ memset(&e->sample_spec, 0, sizeof(e->sample_spec));
+ pa_channel_map_init(&e->channel_map);
+ pa_cvolume_reset(&e->volume, PA_CHANNELS_MAX);
+
+ 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, uint32_t *idx) {
+ pa_scache_entry *e;
+ char st[PA_SAMPLE_SPEC_SNPRINT_MAX];
+ pa_channel_map tmap;
+
+ pa_assert(c);
+ pa_assert(name);
+ pa_assert(!ss || pa_sample_spec_valid(ss));
+ pa_assert(!map || (pa_channel_map_valid(map) && ss && ss->channels == map->channels));
+
+ if (ss && !map)
+ if (!(map = pa_channel_map_init_auto(&tmap, ss->channels, PA_CHANNEL_MAP_DEFAULT)))
+ return -1;
+
+ if (chunk && chunk->length > PA_SCACHE_ENTRY_SIZE_MAX)
+ return -1;
+
+ if (!(e = scache_add_item(c, name)))
+ return -1;
+
+ memset(&e->sample_spec, 0, sizeof(e->sample_spec));
+ pa_channel_map_init(&e->channel_map);
+
+ if (ss) {
+ e->sample_spec = *ss;
+ e->volume.channels = e->sample_spec.channels;
+ }
+
+ if (map)
+ e->channel_map = *map;
+
+ if (chunk) {
+ e->memchunk = *chunk;
+ pa_memblock_ref(e->memchunk.memblock);
+ }
+
+ 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));
+
+ 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;
+
+#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 (pa_sound_file_load(c->mempool, filename, &ss, &map, &chunk) < 0)
+ return -1;
+
+ r = pa_scache_add_item(c, name, &ss, &map, &chunk, idx);
+ pa_memblock_unref(chunk.memblock);
+
+ return r;
+}
+
+int pa_scache_add_file_lazy(pa_core *c, const char *name, const char *filename, uint32_t *idx) {
+ pa_scache_entry *e;
+
+#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)))
+ return -1;
+
+ e->lazy = 1;
+ e->filename = pa_xstrdup(filename);
+
+ if (!c->scache_auto_unload_event) {
+ struct timeval ntv;
+ pa_gettimeofday(&ntv);
+ ntv.tv_sec += UNLOAD_POLL_TIME;
+ c->scache_auto_unload_event = c->mainloop->time_new(c->mainloop, &ntv, timeout_callback, c);
+ }
+
+ if (idx)
+ *idx = e->index;
+
+ 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, 0)))
+ return -1;
+
+ if (pa_idxset_remove_by_data(c->scache, e, NULL) != e)
+ pa_assert(0);
+
+ pa_log_debug("Removed sample \"%s\"", name);
+
+ free_entry(e);
+
+ return 0;
+}
+
+static void free_cb(void *p, PA_GCC_UNUSED void *userdata) {
+ pa_scache_entry *e = p;
+ pa_assert(e);
+
+ free_entry(e);
+}
+
+void pa_scache_free(pa_core *c) {
+ pa_assert(c);
+
+ if (c->scache) {
+ pa_idxset_free(c->scache, free_cb, NULL);
+ c->scache = NULL;
+ }
+
+ if (c->scache_auto_unload_event)
+ c->mainloop->time_free(c->scache_auto_unload_event);
+}
+
+int pa_scache_play_item(pa_core *c, const char *name, pa_sink *sink, pa_volume_t volume) {
+ pa_scache_entry *e;
+ char *t;
+ pa_cvolume r;
+
+ pa_assert(c);
+ pa_assert(name);
+ pa_assert(sink);
+
+ if (!(e = pa_namereg_get(c, name, PA_NAMEREG_SAMPLE, 1)))
+ return -1;
+
+ if (e->lazy && !e->memchunk.memblock) {
+ if (pa_sound_file_load(c->mempool, e->filename, &e->sample_spec, &e->channel_map, &e->memchunk) < 0)
+ return -1;
+
+ pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE|PA_SUBSCRIPTION_EVENT_CHANGE, e->index);
+
+ if (e->volume.channels > e->sample_spec.channels)
+ e->volume.channels = e->sample_spec.channels;
+ }
+
+ if (!e->memchunk.memblock)
+ return -1;
+
+ pa_log_debug("Playing sample \"%s\" on \"%s\"", name, sink->name);
+
+ t = pa_sprintf_malloc("sample:%s", name);
+
+ pa_cvolume_set(&r, e->volume.channels, volume);
+ pa_sw_cvolume_multiply(&r, &r, &e->volume);
+
+ if (pa_play_memchunk(sink, t, &e->sample_spec, &e->channel_map, &e->memchunk, &r) < 0) {
+ pa_xfree(t);
+ return -1;
+ }
+
+ pa_xfree(t);
+
+ if (e->lazy)
+ time(&e->last_used_time);
+
+ return 0;
+}
+
+int pa_scache_play_item_by_name(pa_core *c, const char *name, const char*sink_name, pa_volume_t volume, int autoload) {
+ pa_sink *sink;
+
+ pa_assert(c);
+ pa_assert(name);
+
+ if (!(sink = pa_namereg_get(c, sink_name, PA_NAMEREG_SINK, autoload)))
+ return -1;
+
+ return pa_scache_play_item(c, name, sink, volume);
+}
+
+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, 0)))
+ return PA_IDXSET_INVALID;
+
+ return e->index;
+}
+
+uint32_t pa_scache_total_size(pa_core *c) {
+ pa_scache_entry *e;
+ uint32_t idx, sum = 0;
+
+ pa_assert(c);
+
+ if (!c->scache || !pa_idxset_size(c->scache))
+ return 0;
+
+ for (e = pa_idxset_first(c->scache, &idx); e; e = pa_idxset_next(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);
+
+ for (e = pa_idxset_first(c->scache, &idx); e; e = pa_idxset_next(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);
+ e->memchunk.memblock = NULL;
+ e->memchunk.index = e->memchunk.length = 0;
+
+ 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[PATH_MAX];
+
+ if (e->d_name[0] == '.')
+ continue;
+
+ pa_snprintf(p, sizeof(p), "%s/%s", pathname, e->d_name);
+ add_file(c, p);
+ }
+ }
+
+ closedir(dir);
+ return 0;
+}
diff --git a/src/pulsecore/core-scache.h b/src/pulsecore/core-scache.h
new file mode 100644
index 00000000..ab7ec0ef
--- /dev/null
+++ b/src/pulsecore/core-scache.h
@@ -0,0 +1,68 @@
+#ifndef foocorescachehfoo
+#define foocorescachehfoo
+
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <pulsecore/core.h>
+#include <pulsecore/memchunk.h>
+#include <pulsecore/sink.h>
+
+#define PA_SCACHE_ENTRY_SIZE_MAX (1024*1024*2)
+
+typedef struct pa_scache_entry {
+ pa_core *core;
+ uint32_t index;
+ char *name;
+
+ pa_cvolume volume;
+ pa_sample_spec sample_spec;
+ pa_channel_map channel_map;
+ pa_memchunk memchunk;
+
+ char *filename;
+
+ int lazy;
+ time_t last_used_time;
+} 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, 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);
+int pa_scache_play_item_by_name(pa_core *c, const char *name, const char*sink_name, pa_volume_t volume, int autoload);
+void pa_scache_free(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);
+
+uint32_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 00000000..06c5a4ad
--- /dev/null
+++ b/src/pulsecore/core-subscribe.c
@@ -0,0 +1,266 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/queue.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;
+ int 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 = 0;
+ 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 = 1;
+ 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"
+ };
+
+ const char * const type_table[] = {
+ [PA_SUBSCRIPTION_EVENT_NEW] = "NEW",
+ [PA_SUBSCRIPTION_EVENT_CHANGE] = "CHANGE",
+ [PA_SUBSCRIPTION_EVENT_REMOVE] = "REMOVE"
+ };
+
+ pa_log("%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 subscirption 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 noone 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.");
+ 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.");
+ 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 00000000..2b6863f9
--- /dev/null
+++ b/src/pulsecore/core-subscribe.h
@@ -0,0 +1,41 @@
+#ifndef foocoresubscribehfoo
+#define foocoresubscribehfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+typedef struct pa_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 00000000..61d04c2d
--- /dev/null
+++ b/src/pulsecore/core-util.c
@@ -0,0 +1,1579 @@
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+#include <time.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#ifdef HAVE_STRTOF_L
+#include <locale.h>
+#endif
+
+#ifdef HAVE_SCHED_H
+#include <sched.h>
+#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
+
+#ifdef HAVE_PWD_H
+#include <pwd.h>
+#endif
+
+#ifdef HAVE_GRP_H
+#include <grp.h>
+#endif
+
+#ifdef HAVE_LIBSAMPLERATE
+#include <samplerate.h>
+#endif
+
+#include <pulse/xmalloc.h>
+#include <pulse/util.h>
+#include <pulse/utf8.h>
+
+#include <pulsecore/core-error.h>
+#include <pulsecore/winsock.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/thread.h>
+
+#include "core-util.h"
+
+/* Not all platforms have this */
+#ifndef MSG_NOSIGNAL
+#define MSG_NOSIGNAL 0
+#endif
+
+#ifndef OS_IS_WIN32
+#define PA_USER_RUNTIME_PATH_PREFIX "/tmp/pulse-"
+#else
+#define PA_USER_RUNTIME_PATH_PREFIX "%TEMP%\\pulse-"
+#endif
+
+#ifdef OS_IS_WIN32
+
+#define PULSE_ROOTENV "PULSE_ROOT"
+
+int pa_set_root(HANDLE handle) {
+ char library_path[MAX_PATH + sizeof(PULSE_ROOTENV) + 1], *sep;
+
+ strcpy(library_path, PULSE_ROOTENV "=");
+
+ if (!GetModuleFileName(handle, library_path + sizeof(PULSE_ROOTENV), MAX_PATH))
+ return 0;
+
+ sep = strrchr(library_path, PA_PATH_SEP_CHAR);
+ if (sep)
+ *sep = '\0';
+
+ if (_putenv(library_path) < 0)
+ return 0;
+
+ return 1;
+}
+
+#endif
+
+/** Make a file descriptor nonblock. Doesn't do any error checking */
+void pa_make_fd_nonblock(int fd) {
+
+#ifdef O_NONBLOCK
+ int v;
+ pa_assert(fd >= 0);
+
+ pa_assert_se((v = fcntl(fd, F_GETFL)) >= 0);
+
+ if (!(v & O_NONBLOCK))
+ pa_assert_se(fcntl(fd, F_SETFL, v|O_NONBLOCK) >= 0);
+
+#elif defined(OS_IS_WIN32)
+ u_long arg = 1;
+ if (ioctlsocket(fd, FIONBIO, &arg) < 0) {
+ pa_assert_se(WSAGetLastError() == WSAENOTSOCK);
+ pa_log_warn("Only sockets can be made non-blocking!");
+ }
+#else
+ pa_log_warn("Non-blocking I/O not supported.!");
+#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 */
+int pa_make_secure_dir(const char* dir, mode_t m, uid_t uid, gid_t gid) {
+ struct stat st;
+ int r;
+
+ pa_assert(dir);
+
+#ifdef OS_IS_WIN32
+ r = mkdir(dir);
+#else
+ {
+ mode_t u;
+ u = umask(~m);
+ r = mkdir(dir, m);
+ umask(u);
+ }
+#endif
+
+ if (r < 0 && errno != EEXIST)
+ return -1;
+
+#ifdef HAVE_CHOWN
+ if (uid == (uid_t)-1)
+ uid = getuid();
+ if (gid == (gid_t)-1)
+ gid = getgid();
+ (void) chown(dir, uid, gid);
+#endif
+
+#ifdef HAVE_CHMOD
+ chmod(dir, m);
+#endif
+
+#ifdef HAVE_LSTAT
+ if (lstat(dir, &st) < 0)
+#else
+ if (stat(dir, &st) < 0)
+#endif
+ goto fail;
+
+#ifndef OS_IS_WIN32
+ if (!S_ISDIR(st.st_mode) ||
+ (st.st_uid != uid) ||
+ (st.st_gid != gid) ||
+ ((st.st_mode & 0777) != m)) {
+ errno = EACCES;
+ goto fail;
+ }
+#else
+ pa_log_warn("secure directory creation not supported on Win32.");
+#endif
+
+ return 0;
+
+fail:
+ rmdir(dir);
+ 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);
+ 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) {
+ int ret = -1;
+ char *dir;
+
+ if (!(dir = pa_parent_dir(fn)))
+ goto finish;
+
+ if (pa_make_secure_dir(dir, m, uid, gid) < 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
+
+ return read(fd, buf, count);
+}
+
+/** 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;
+
+ if ((r = send(fd, buf, count, MSG_NOSIGNAL)) >= 0)
+ return r;
+
+#ifdef OS_IS_WIN32
+ if (WSAGetLastError() != WSAENOTSOCK) {
+ errno = WSAGetLastError();
+ return r;
+ }
+#else
+ if (errno != ENOTSOCK)
+ return r;
+#endif
+
+ if (type)
+ *type = 1;
+ }
+
+ return write(fd, buf, count);
+}
+
+/** Calls read() in a loop. Makes sure that as much as 'size' bytes,
+ * unless EOF is reached or an error occured */
+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 -= 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 -= r;
+ }
+
+ return ret;
+}
+
+/** Platform independent read function. Necessary since not all
+ * systems treat all file descriptors equal. */
+int pa_close(int fd) {
+
+#ifdef OS_IS_WIN32
+ int ret;
+
+ if ((ret = closesocket(fd)) == 0)
+ return 0;
+
+ if (WSAGetLastError() != WSAENOTSOCK) {
+ errno = WSAGetLastError();
+ return ret;
+ }
+#endif
+
+ return close(fd);
+}
+
+/* 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, ...) {
+ int 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 && r < size)
+ return c;
+
+ if (r > -1) /* glibc 2.1 */
+ size = 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) {
+ int 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 && r < size)
+ return c;
+
+ if (r > -1) /* glibc 2.1 */
+ size = 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) {
+ pa_assert(b);
+ pa_assert(s);
+ pa_assert(l > 0);
+
+ strncpy(b, s, l);
+ b[l-1] = 0;
+ return b;
+}
+
+/* Make the current thread a realtime thread, and acquire the highest
+ * rtprio we can get that is less or equal the specified parameter. If
+ * the thread is already realtime, don't do anything. */
+int pa_make_realtime(int rtprio) {
+
+#ifdef _POSIX_PRIORITY_SCHEDULING
+ struct sched_param sp;
+ int r, policy;
+
+ memset(&sp, 0, sizeof(sp));
+ policy = 0;
+
+ if ((r = pthread_getschedparam(pthread_self(), &policy, &sp)) != 0) {
+ pa_log("pthread_getschedgetparam(): %s", pa_cstrerror(r));
+ return -1;
+ }
+
+ if (policy == SCHED_FIFO && sp.sched_priority >= rtprio) {
+ pa_log_info("Thread already being scheduled with SCHED_FIFO with priority %i.", sp.sched_priority);
+ return 0;
+ }
+
+ sp.sched_priority = rtprio;
+ if ((r = pthread_setschedparam(pthread_self(), SCHED_FIFO, &sp)) != 0) {
+
+ while (sp.sched_priority > 1) {
+ sp.sched_priority --;
+
+ if ((r = pthread_setschedparam(pthread_self(), SCHED_FIFO, &sp)) == 0) {
+ pa_log_info("Successfully enabled SCHED_FIFO scheduling for thread, with priority %i, which is lower than the requested %i.", sp.sched_priority, rtprio);
+ return 0;
+ }
+ }
+
+ pa_log_warn("pthread_setschedparam(): %s", pa_cstrerror(r));
+ return -1;
+ }
+
+ pa_log_info("Successfully enabled SCHED_FIFO scheduling for thread, with priority %i.", sp.sched_priority);
+ return 0;
+#else
+ 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
+ if (setpriority(PRIO_PROCESS, 0, nice_level) < 0) {
+ int n;
+
+ for (n = nice_level+1; n < 0; n++) {
+
+ if (setpriority(PRIO_PROCESS, 0, n) == 0) {
+ pa_log_info("Successfully acquired nice level %i, which is lower than the requested %i.", n, nice_level);
+ return 0;
+ }
+ }
+
+ pa_log_warn("setpriority(): %s", pa_cstrerror(errno));
+ return -1;
+ }
+
+ pa_log_info("Successfully gained nice level %i.", nice_level);
+#endif
+
+#ifdef OS_IS_WIN32
+ if (nice_level < 0) {
+ if (!SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS)) {
+ pa_log_warn("SetPriorityClass() failed: 0x%08X", GetLastError());
+ return .-1;
+ } else
+ 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_make_realtime()*/
+void pa_reset_priority(void) {
+#ifdef HAVE_SYS_RESOURCE_H
+ struct sched_param sp;
+
+ setpriority(PRIO_PROCESS, 0, 0);
+
+ memset(&sp, 0, sizeof(sp));
+ pa_assert_se(pthread_setschedparam(pthread_self(), SCHED_OTHER, &sp) == 0);
+#endif
+
+#ifdef OS_IS_WIN32
+ SetPriorityClass(GetCurrentProcess(), NORMAL_PRIORITY_CLASS);
+#endif
+}
+
+/* Try to parse a boolean string value.*/
+int pa_parse_boolean(const char *v) {
+
+ if (!strcmp(v, "1") || v[0] == 'y' || v[0] == 'Y' || v[0] == 't' || v[0] == 'T' || !strcasecmp(v, "on"))
+ return 1;
+ else if (!strcmp(v, "0") || v[0] == 'n' || v[0] == 'N' || v[0] == 'f' || v[0] == 'F' || !strcasecmp(v, "off"))
+ return 0;
+
+ return -1;
+}
+
+/* Split the specified string wherever one of the strings in delimiter
+ * occurs. Each time it is called returns a newly allocated string
+ * with pa_xmalloc(). The variable state points to, should be
+ * initiallized 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);
+}
+
+/* What is interpreted as whitespace? */
+#define WHITESPACE " \t\n"
+
+/* 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);
+}
+
+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, *result = NULL;
+ long n;
+ void *data;
+ int r = -1;
+
+#ifdef HAVE_GETGRGID_R
+#ifdef _SC_GETGR_R_SIZE_MAX
+ n = sysconf(_SC_GETGR_R_SIZE_MAX);
+#else
+ n = -1;
+#endif
+ if (n < 0) n = 512;
+ data = pa_xmalloc(n);
+
+ if (getgrgid_r(gid, &group, data, n, &result) < 0 || !result) {
+ pa_log("getgrgid_r(%u): %s", (unsigned)gid, pa_cstrerror(errno));
+ goto finish;
+ }
+
+ r = strcmp(name, result->gr_name) == 0;
+
+finish:
+ pa_xfree(data);
+#else
+ /* XXX Not thread-safe, but needed on OSes (e.g. FreeBSD 4.X) that do not
+ * support getgrgid_r. */
+ if ((result = getgrgid(gid)) == NULL) {
+ pa_log("getgrgid(%u): %s", gid, pa_cstrerror(errno));
+ goto finish;
+ }
+
+ r = strcmp(name, result->gr_name) == 0;
+
+finish:
+#endif
+
+ 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;
+ int n = sysconf(_SC_NGROUPS_MAX);
+ int r = -1, i;
+
+ pa_assert(n > 0);
+
+ gids = pa_xmalloc(sizeof(GETGROUPS_T)*n);
+
+ if ((n = getgroups(n, gids)) < 0) {
+ pa_log("getgroups(): %s", pa_cstrerror(errno));
+ goto finish;
+ }
+
+ for (i = 0; i < n; i++) {
+ if (is_group(gids[i], name) > 0) {
+ *gid = gids[i];
+ r = 1;
+ goto finish;
+ }
+ }
+
+ if (is_group(tgid = getgid(), name) > 0) {
+ *gid = tgid;
+ r = 1;
+ goto finish;
+ }
+
+ r = 0;
+
+finish:
+
+ pa_xfree(gids);
+ return r;
+}
+
+/* Check whether the specifc user id is a member of the specified group */
+int pa_uid_in_group(uid_t uid, const char *name) {
+ char *g_buf, *p_buf;
+ long g_n, p_n;
+ struct group grbuf, *gr;
+ char **i;
+ int r = -1;
+
+ g_n = sysconf(_SC_GETGR_R_SIZE_MAX);
+ g_buf = pa_xmalloc(g_n);
+
+ p_n = sysconf(_SC_GETPW_R_SIZE_MAX);
+ p_buf = pa_xmalloc(p_n);
+
+ if (getgrnam_r(name, &grbuf, g_buf, (size_t) g_n, &gr) != 0 || !gr)
+ goto finish;
+
+ r = 0;
+ for (i = gr->gr_mem; *i; i++) {
+ struct passwd pwbuf, *pw;
+
+ if (getpwnam_r(*i, &pwbuf, p_buf, (size_t) p_n, &pw) != 0 || !pw)
+ continue;
+
+ if (pw->pw_uid == uid) {
+ r = 1;
+ break;
+ }
+ }
+
+finish:
+ pa_xfree(g_buf);
+ pa_xfree(p_buf);
+
+ return r;
+}
+
+/* Get the GID of a gfiven group, return (gid_t) -1 on failure. */
+gid_t pa_get_gid_of_group(const char *name) {
+ gid_t ret = (gid_t) -1;
+ char *g_buf;
+ long g_n;
+ struct group grbuf, *gr;
+
+ g_n = sysconf(_SC_GETGR_R_SIZE_MAX);
+ g_buf = pa_xmalloc(g_n);
+
+ if (getgrnam_r(name, &grbuf, g_buf, (size_t) g_n, &gr) != 0 || !gr)
+ goto finish;
+
+ ret = gr->gr_gid;
+
+finish:
+ pa_xfree(g_buf);
+ 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) {
+ return -1;
+
+}
+
+int pa_uid_in_group(uid_t uid, const char *name) {
+ return -1;
+}
+
+gid_t pa_get_gid_of_group(const char *name) {
+ return (gid_t) -1;
+}
+
+int pa_check_in_group(gid_t g) {
+ 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 flock;
+
+ /* Try a R/W lock first */
+
+ flock.l_type = b ? F_WRLCK : F_UNLCK;
+ flock.l_whence = SEEK_SET;
+ flock.l_start = 0;
+ flock.l_len = 0;
+
+ if (fcntl(fd, F_SETLKW, &flock) >= 0)
+ return 0;
+
+ /* Perhaps the file descriptor qas opened for read only, than try again with a read lock. */
+ if (b && errno == EBADF) {
+ flock.l_type = F_RDLCK;
+ if (fcntl(fd, F_SETLKW, &flock) >= 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());
+#endif
+
+ return -1;
+}
+
+/* Remove trailing newlines from a string */
+char* pa_strip_nl(char *s) {
+ pa_assert(s);
+
+ s[strcspn(s, "\r\n")] = 0;
+ return s;
+}
+
+/* Create a temporary lock file and lock it. */
+int pa_lock_lockfile(const char *fn) {
+ int fd = -1;
+ pa_assert(fn);
+
+ for (;;) {
+ struct stat st;
+
+ if ((fd = open(fn, O_CREAT|O_RDWR
+#ifdef O_NOCTTY
+ |O_NOCTTY
+#endif
+#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 wheter 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;
+ }
+
+ fd = -1;
+ }
+
+ return fd;
+
+fail:
+
+ if (fd >= 0)
+ pa_close(fd);
+
+ return -1;
+}
+
+/* Unlock a temporary lcok file */
+int pa_unlock_lockfile(const char *fn, int fd) {
+ int r = 0;
+ pa_assert(fn);
+ pa_assert(fd >= 0);
+
+ 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;
+}
+
+/* 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 *mode) {
+ const char *fn;
+ char h[PATH_MAX];
+
+#ifdef OS_IS_WIN32
+ char buf[PATH_MAX];
+
+ if (!getenv(PULSE_ROOTENV))
+ pa_set_root(NULL);
+#endif
+
+ if (env && (fn = getenv(env))) {
+#ifdef OS_IS_WIN32
+ if (!ExpandEnvironmentStrings(fn, buf, PATH_MAX))
+ return NULL;
+ fn = buf;
+#endif
+
+ if (result)
+ *result = pa_xstrdup(fn);
+
+ return fopen(fn, mode);
+ }
+
+ if (local) {
+ const char *e;
+ char *lfn = NULL;
+
+ if ((e = getenv("PULSE_CONFIG_PATH")))
+ fn = lfn = pa_sprintf_malloc("%s/%s", e, local);
+ else if (pa_get_home_dir(h, sizeof(h))) {
+ char *d;
+
+ d = pa_sprintf_malloc("%s/.pulse", h);
+ mkdir(d, 0755);
+ pa_xfree(d);
+
+ fn = lfn = pa_sprintf_malloc("%s/.pulse/%s", h, local);
+ }
+
+ if (lfn) {
+ FILE *f;
+
+#ifdef OS_IS_WIN32
+ if (!ExpandEnvironmentStrings(lfn, buf, PATH_MAX))
+ return NULL;
+ fn = buf;
+#endif
+
+ f = fopen(fn, mode);
+ if (f != NULL) {
+ if (result)
+ *result = pa_xstrdup(fn);
+ pa_xfree(lfn);
+ return f;
+ }
+
+ if (errno != ENOENT)
+ pa_log_warn("Failed to open configuration file '%s': %s", lfn, pa_cstrerror(errno));
+
+ pa_xfree(lfn);
+ }
+ }
+
+ if (!global) {
+ if (result)
+ *result = NULL;
+ errno = ENOENT;
+ return NULL;
+ }
+
+#ifdef OS_IS_WIN32
+ if (!ExpandEnvironmentStrings(global, buf, PATH_MAX))
+ return NULL;
+ global = buf;
+#endif
+
+ if (result)
+ *result = pa_xstrdup(global);
+
+ return fopen(global, mode);
+}
+
+/* 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 (i < dlength && j+3 <= slength) {
+ 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;
+
+ 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 */
+int 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 */
+int 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 && strcmp(s+l1-l2, sfx) == 0;
+}
+
+/* if fn is null return the PulseAudio run time path in s (/tmp/pulse)
+ * if fn is non-null and starts with / return fn in s
+ * otherwise append fn to the run time path and return it in s */
+char *pa_runtime_path(const char *fn, char *s, size_t l) {
+ const char *e;
+
+#ifndef OS_IS_WIN32
+ if (fn && *fn == '/')
+#else
+ if (fn && strlen(fn) >= 3 && isalpha(fn[0]) && fn[1] == ':' && fn[2] == '\\')
+#endif
+ return pa_strlcpy(s, fn, l);
+
+ if ((e = getenv("PULSE_RUNTIME_PATH"))) {
+
+ if (fn)
+ pa_snprintf(s, l, "%s%c%s", e, PA_PATH_SEP_CHAR, fn);
+ else
+ pa_snprintf(s, l, "%s", e);
+
+ } else {
+ char u[256];
+
+ if (fn)
+ pa_snprintf(s, l, "%s%s%c%s", PA_USER_RUNTIME_PATH_PREFIX, pa_get_user_name(u, sizeof(u)), PA_PATH_SEP_CHAR, fn);
+ else
+ pa_snprintf(s, l, "%s%s", PA_USER_RUNTIME_PATH_PREFIX, pa_get_user_name(u, sizeof(u)));
+ }
+
+
+#ifdef OS_IS_WIN32
+ {
+ char buf[l];
+ strcpy(buf, s);
+ ExpandEnvironmentStrings(buf, s, l);
+ }
+#endif
+
+ return s;
+}
+
+/* Convert the string s to a signed integer in *ret_i */
+int pa_atoi(const char *s, int32_t *ret_i) {
+ char *x = NULL;
+ long l;
+
+ pa_assert(s);
+ pa_assert(ret_i);
+
+ errno = 0;
+ l = strtol(s, &x, 0);
+
+ if (!x || *x || errno != 0)
+ return -1;
+
+ if ((int32_t) l != l)
+ 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);
+
+ errno = 0;
+ l = strtoul(s, &x, 0);
+
+ if (!x || *x || errno != 0)
+ return -1;
+
+ if ((uint32_t) l != l)
+ return -1;
+
+ *ret_u = (uint32_t) l;
+
+ return 0;
+}
+
+#ifdef HAVE_STRTOF_L
+static locale_t c_locale = NULL;
+
+static void c_locale_destroy(void) {
+ freelocale(c_locale);
+}
+#endif
+
+int pa_atof(const char *s, float *ret_f) {
+ char *x = NULL;
+ float f;
+ int r = 0;
+
+ pa_assert(s);
+ pa_assert(ret_f);
+
+ /* This should be locale independent */
+
+#ifdef HAVE_STRTOF_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 = strtof_l(s, &x, c_locale);
+ } else
+#endif
+ {
+ errno = 0;
+#ifdef HAVE_STRTOF
+ f = strtof(s, &x);
+#else
+ f = strtod(s, &x);
+#endif
+ }
+
+ if (!x || *x || errno != 0)
+ r = -1;
+ else
+ *ret_f = f;
+
+ return r;
+}
+
+/* Same as snprintf, but guarantees NUL-termination on every platform */
+int pa_snprintf(char *str, size_t size, const char *format, ...) {
+ int ret;
+ va_list ap;
+
+ pa_assert(str);
+ pa_assert(size > 0);
+ pa_assert(format);
+
+ va_start(ap, format);
+ ret = vsnprintf(str, size, format, ap);
+ va_end(ap);
+
+ str[size-1] = 0;
+
+ return 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_xnew(char, l);
+ if (getcwd(p, l))
+ return p;
+
+ if (errno != ERANGE)
+ return NULL;
+
+ pa_xfree(p);
+ l *= 2;
+ }
+}
+
+char *pa_make_path_absolute(const char *p) {
+ char *r;
+ char *cwd;
+
+ pa_assert(p);
+
+ if (p[0] == '/')
+ return pa_xstrdup(p);
+
+ if (!(cwd = pa_getcwd()))
+ return pa_xstrdup(p);
+
+ r = pa_sprintf_malloc("%s/%s", cwd, p);
+ pa_xfree(cwd);
+ return r;
+}
+
+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;
+ size_t bs;
+
+ pa_assert(p);
+ pa_assert(l > 0);
+
+ a = PA_PAGE_ALIGN_PTR(p);
+ size = (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 < PA_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));
+ return (void*) p;
+ }
+
+ bs = PA_PAGE_ALIGN(rlim.rlim_cur);
+#else
+ bs = PA_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) {
+ size_t l = 100;
+
+ for (;;) {
+ char *c;
+ ssize_t n;
+
+ c = pa_xnew(char, 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;
+ }
+}
diff --git a/src/pulsecore/core-util.h b/src/pulsecore/core-util.h
new file mode 100644
index 00000000..c8760a1f
--- /dev/null
+++ b/src/pulsecore/core-util.h
@@ -0,0 +1,132 @@
+#ifndef foocoreutilhfoo
+#define foocoreutilhfoo
+
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdarg.h>
+#include <stdio.h>
+
+#include <pulsecore/gccmacro.h>
+#include <pulsecore/macro.h>
+
+struct timeval;
+
+void pa_make_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);
+int pa_make_secure_parent_dir(const char *fn, mode_t, uid_t uid, gid_t gid);
+
+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_make_realtime(int rtprio);
+int pa_raise_priority(int nice_level);
+void pa_reset_priority(void);
+
+int pa_parse_boolean(const char *s) PA_GCC_PURE;
+
+static inline const char *pa_yes_no(pa_bool_t b) {
+ return b ? "yes" : "no";
+}
+
+char *pa_split(const char *c, const char*delimiters, const char **state);
+char *pa_split_spaces(const char *c, const char **state);
+
+char *pa_strip_nl(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);
+
+FILE *pa_open_config_file(const char *global, const char *local, const char *env, char **result, const char *mode);
+
+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);
+
+int pa_startswith(const char *s, const char *pfx) PA_GCC_PURE;
+int pa_endswith(const char *s, const char *sfx) PA_GCC_PURE;
+
+char *pa_runtime_path(const char *fn, char *s, size_t l);
+
+int pa_atoi(const char *s, int32_t *ret_i);
+int pa_atou(const char *s, uint32_t *ret_u);
+int pa_atof(const char *s, float *ret_f);
+
+int pa_snprintf(char *str, size_t size, const char *format, ...);
+
+char *pa_truncate_utf8(char *c, size_t l);
+
+char *pa_getcwd(void);
+char *pa_make_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_make_power_of_two(unsigned n) {
+ unsigned j = n;
+
+ if (pa_is_power_of_two(n))
+ return n;
+
+ while (j) {
+ j = j >> 1;
+ n = n | j;
+ }
+
+ return n + 1;
+}
+
+void pa_close_pipe(int fds[2]);
+
+char *pa_readlink(const char *p);
+
+#endif
diff --git a/src/pulsecore/core.c b/src/pulsecore/core.c
new file mode 100644
index 00000000..cf018509
--- /dev/null
+++ b/src/pulsecore/core.c
@@ -0,0 +1,220 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <signal.h>
+
+#include <pulse/timeval.h>
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/module.h>
+#include <pulsecore/sink.h>
+#include <pulsecore/source.h>
+#include <pulsecore/namereg.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/core-scache.h>
+#include <pulsecore/autoload.h>
+#include <pulsecore/core-subscribe.h>
+#include <pulsecore/props.h>
+#include <pulsecore/random.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+
+#include "core.h"
+
+static PA_DEFINE_CHECK_TYPE(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(c, userdata);
+ return 0;
+
+ default:
+ return -1;
+ }
+}
+
+static void core_free(pa_object *o);
+
+pa_core* pa_core_new(pa_mainloop_api *m, int shared) {
+ pa_core* c;
+ pa_mempool *pool;
+ int j;
+
+ pa_assert(m);
+
+ if (shared) {
+ if (!(pool = pa_mempool_new(shared))) {
+ pa_log_warn("failed to allocate shared memory pool. Falling back to a normal memory pool.");
+ shared = 0;
+ }
+ }
+
+ if (!shared) {
+ if (!(pool = pa_mempool_new(shared))) {
+ 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->mainloop = m;
+ c->clients = pa_idxset_new(NULL, NULL);
+ c->sinks = pa_idxset_new(NULL, NULL);
+ c->sources = pa_idxset_new(NULL, NULL);
+ c->source_outputs = pa_idxset_new(NULL, NULL);
+ c->sink_inputs = pa_idxset_new(NULL, NULL);
+
+ c->default_source_name = c->default_sink_name = NULL;
+
+ c->modules = NULL;
+ c->namereg = NULL;
+ c->scache = NULL;
+ c->autoload_idxset = NULL;
+ c->autoload_hashmap = NULL;
+ c->running_as_daemon = FALSE;
+
+ c->default_sample_spec.format = PA_SAMPLE_S16NE;
+ c->default_sample_spec.rate = 44100;
+ c->default_sample_spec.channels = 2;
+ c->default_n_fragments = 4;
+ c->default_fragment_size_msec = 25;
+
+ c->module_auto_unload_event = NULL;
+ c->module_defer_unload_event = NULL;
+ c->scache_auto_unload_event = 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->quit_event = NULL;
+
+ c->exit_idle_time = -1;
+ c->module_idle_time = 20;
+ c->scache_idle_time = 20;
+
+ c->resample_method = PA_RESAMPLER_SPEEX_FLOAT_BASE + 3;
+
+ c->is_system_instance = FALSE;
+ c->disallow_module_loading = FALSE;
+ c->realtime_scheduling = FALSE;
+ c->realtime_priority = 5;
+ c->disable_remixing = FALSE;
+
+ for (j = 0; j < PA_CORE_HOOK_MAX; j++)
+ pa_hook_init(&c->hooks[j], c);
+
+ pa_property_init(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);
+
+ pa_module_unload_all(c);
+ pa_assert(!c->modules);
+
+ pa_assert(pa_idxset_isempty(c->clients));
+ pa_idxset_free(c->clients, NULL, NULL);
+
+ pa_assert(pa_idxset_isempty(c->sinks));
+ pa_idxset_free(c->sinks, NULL, NULL);
+
+ pa_assert(pa_idxset_isempty(c->sources));
+ pa_idxset_free(c->sources, NULL, NULL);
+
+ pa_assert(pa_idxset_isempty(c->source_outputs));
+ pa_idxset_free(c->source_outputs, NULL, NULL);
+
+ pa_assert(pa_idxset_isempty(c->sink_inputs));
+ pa_idxset_free(c->sink_inputs, NULL, NULL);
+
+ pa_scache_free(c);
+ pa_namereg_free(c);
+ pa_autoload_free(c);
+ pa_subscription_free_all(c);
+
+ if (c->quit_event)
+ c->mainloop->time_free(c->quit_event);
+
+ pa_xfree(c->default_source_name);
+ pa_xfree(c->default_sink_name);
+
+ pa_mempool_free(c->mempool);
+
+ pa_property_cleanup(c);
+
+ for (j = 0; j < PA_CORE_HOOK_MAX; j++)
+ pa_hook_free(&c->hooks[j]);
+
+ pa_xfree(c);
+}
+
+static void quit_callback(pa_mainloop_api*m, pa_time_event *e, PA_GCC_UNUSED const struct timeval *tv, void *userdata) {
+ pa_core *c = userdata;
+ pa_assert(c->quit_event == e);
+
+ m->quit(m, 0);
+}
+
+void pa_core_check_quit(pa_core *c) {
+ pa_assert(c);
+
+ if (!c->quit_event && c->exit_idle_time >= 0 && pa_idxset_size(c->clients) == 0) {
+ struct timeval tv;
+ pa_gettimeofday(&tv);
+ tv.tv_sec+= c->exit_idle_time;
+ c->quit_event = c->mainloop->time_new(c->mainloop, &tv, quit_callback, c);
+ } else if (c->quit_event && pa_idxset_size(c->clients) > 0) {
+ c->mainloop->time_free(c->quit_event);
+ c->quit_event = NULL;
+ }
+}
diff --git a/src/pulsecore/core.h b/src/pulsecore/core.h
new file mode 100644
index 00000000..ce45e300
--- /dev/null
+++ b/src/pulsecore/core.h
@@ -0,0 +1,142 @@
+#ifndef foocorehfoo
+#define foocorehfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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/mainloop-api.h>
+#include <pulse/sample.h>
+
+#include <pulsecore/idxset.h>
+#include <pulsecore/hashmap.h>
+#include <pulsecore/memblock.h>
+#include <pulsecore/resampler.h>
+#include <pulsecore/queue.h>
+#include <pulsecore/llist.h>
+#include <pulsecore/hook-list.h>
+#include <pulsecore/asyncmsgq.h>
+
+typedef struct pa_core pa_core;
+
+#include <pulsecore/core-subscribe.h>
+#include <pulsecore/sink-input.h>
+#include <pulsecore/msgobject.h>
+
+typedef enum pa_core_hook {
+ PA_CORE_HOOK_SINK_NEW_POST,
+ PA_CORE_HOOK_SINK_UNLINK,
+ PA_CORE_HOOK_SINK_UNLINK_POST,
+ PA_CORE_HOOK_SINK_STATE_CHANGED,
+ PA_CORE_HOOK_SINK_DESCRIPTION_CHANGED,
+ PA_CORE_HOOK_SOURCE_NEW_POST,
+ PA_CORE_HOOK_SOURCE_UNLINK,
+ PA_CORE_HOOK_SOURCE_UNLINK_POST,
+ PA_CORE_HOOK_SOURCE_STATE_CHANGED,
+ PA_CORE_HOOK_SOURCE_DESCRIPTION_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,
+ PA_CORE_HOOK_SINK_INPUT_MOVE_POST,
+ PA_CORE_HOOK_SINK_INPUT_NAME_CHANGED,
+ PA_CORE_HOOK_SINK_INPUT_STATE_CHANGED,
+ 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,
+ PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_POST,
+ PA_CORE_HOOK_SOURCE_OUTPUT_NAME_CHANGED,
+ PA_CORE_HOOK_SOURCE_OUTPUT_STATE_CHANGED,
+ 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;
+
+ /* 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, *sinks, *sources, *sink_inputs, *source_outputs, *modules, *scache, *autoload_idxset;
+
+ /* Some hashmaps for all sorts of entities */
+ pa_hashmap *namereg, *autoload_hashmap, *properties;
+
+ /* The name of the default sink/source */
+ char *default_source_name, *default_sink_name;
+
+ pa_sample_spec default_sample_spec;
+ unsigned default_n_fragments, default_fragment_size_msec;
+
+ pa_time_event *module_auto_unload_event;
+ pa_defer_event *module_defer_unload_event;
+
+ 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;
+
+ pa_mempool *mempool;
+
+ int exit_idle_time, module_idle_time, scache_idle_time;
+
+ pa_time_event *quit_event;
+
+ pa_time_event *scache_auto_unload_event;
+
+ pa_bool_t disallow_module_loading, running_as_daemon;
+ pa_resample_method_t resample_method;
+ pa_bool_t is_system_instance;
+ pa_bool_t realtime_scheduling;
+ int realtime_priority;
+ pa_bool_t disable_remixing;
+
+ /* hooks */
+ pa_hook hooks[PA_CORE_HOOK_MAX];
+};
+
+PA_DECLARE_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, int shared);
+
+/* Check whether noone is connected to this core */
+void pa_core_check_quit(pa_core *c);
+
+#endif
diff --git a/src/pulsecore/creds.h b/src/pulsecore/creds.h
new file mode 100644
index 00000000..51dfc33d
--- /dev/null
+++ b/src/pulsecore/creds.h
@@ -0,0 +1,56 @@
+#ifndef foocredshfoo
+#define foocredshfoo
+
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <sys/types.h>
+
+#ifndef PACKAGE
+#error "Please include config.h before including this file!"
+#endif
+
+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+
+#ifdef HAVE_SYS_UN_H
+#include <sys/un.h>
+#endif
+
+typedef struct pa_creds pa_creds;
+
+#if defined(SCM_CREDENTIALS)
+
+#define HAVE_CREDS 1
+
+struct pa_creds {
+ gid_t gid;
+ uid_t uid;
+};
+
+#else
+#undef HAVE_CREDS
+#endif
+
+#endif
diff --git a/src/pulsecore/dllmain.c b/src/pulsecore/dllmain.c
new file mode 100644
index 00000000..52cbf9e2
--- /dev/null
+++ b/src/pulsecore/dllmain.c
@@ -0,0 +1,57 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef OS_IS_WIN32
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <windows.h>
+
+extern pa_set_root(HANDLE handle);
+
+BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
+ WSADATA data;
+
+ switch (fdwReason) {
+
+ case DLL_PROCESS_ATTACH:
+ if (!pa_set_root(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 00000000..8bdb46fa
--- /dev/null
+++ b/src/pulsecore/dynarray.c
@@ -0,0 +1,111 @@
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/macro.h>
+
+#include "dynarray.h"
+
+/* If the array becomes to small, increase its size by 100 entries */
+#define INCREASE_BY 100
+
+struct pa_dynarray {
+ void **data;
+ unsigned n_allocated, n_entries;
+};
+
+pa_dynarray* pa_dynarray_new(void) {
+ pa_dynarray *a;
+ a = pa_xnew(pa_dynarray, 1);
+ a->data = NULL;
+ a->n_entries = 0;
+ a->n_allocated = 0;
+ return a;
+}
+
+void pa_dynarray_free(pa_dynarray* a, void (*func)(void *p, void *userdata), void *userdata) {
+ unsigned i;
+ pa_assert(a);
+
+ if (func)
+ for (i = 0; i < a->n_entries; i++)
+ if (a->data[i])
+ func(a->data[i], userdata);
+
+ pa_xfree(a->data);
+ pa_xfree(a);
+}
+
+void pa_dynarray_put(pa_dynarray*a, unsigned i, void *p) {
+ pa_assert(a);
+
+ if (i >= a->n_allocated) {
+ unsigned n;
+
+ if (!p)
+ return;
+
+ n = i+INCREASE_BY;
+ a->data = pa_xrealloc(a->data, sizeof(void*)*n);
+ memset(a->data+a->n_allocated, 0, sizeof(void*)*(n-a->n_allocated));
+ a->n_allocated = n;
+ }
+
+ a->data[i] = p;
+
+ if (i >= a->n_entries)
+ a->n_entries = i+1;
+}
+
+unsigned pa_dynarray_append(pa_dynarray*a, void *p) {
+ unsigned i;
+
+ pa_assert(a);
+
+ i = a->n_entries;
+ pa_dynarray_put(a, i, p);
+ return i;
+}
+
+void *pa_dynarray_get(pa_dynarray*a, unsigned i) {
+ pa_assert(a);
+
+ if (i >= a->n_entries)
+ return NULL;
+
+ pa_assert(a->data);
+ return a->data[i];
+}
+
+unsigned pa_dynarray_size(pa_dynarray*a) {
+ pa_assert(a);
+
+ return a->n_entries;
+}
diff --git a/src/pulsecore/dynarray.h b/src/pulsecore/dynarray.h
new file mode 100644
index 00000000..0f222e10
--- /dev/null
+++ b/src/pulsecore/dynarray.h
@@ -0,0 +1,51 @@
+#ifndef foodynarrayhfoo
+#define foodynarrayhfoo
+
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+typedef struct pa_dynarray pa_dynarray;
+
+/* Implementation of a simple dynamically sized array. The array
+ * expands if required, but doesn't shrink if possible. Memory
+ * management of the array's entries is the user's job. */
+
+pa_dynarray* pa_dynarray_new(void);
+
+/* Free the array calling the specified function for every entry in
+ * the array. The function may be NULL. */
+void pa_dynarray_free(pa_dynarray* a, void (*func)(void *p, void *userdata), void *userdata);
+
+/* Store p at position i in the array */
+void pa_dynarray_put(pa_dynarray*a, unsigned i, void *p);
+
+/* Store p a the first free position in the array. Returns the index
+ * of that entry. If entries are removed from the array their position
+ * are not filled any more by this function. */
+unsigned pa_dynarray_append(pa_dynarray*a, void *p);
+
+void *pa_dynarray_get(pa_dynarray*a, unsigned i);
+
+unsigned pa_dynarray_size(pa_dynarray*a);
+
+#endif
diff --git a/src/pulsecore/endianmacros.h b/src/pulsecore/endianmacros.h
new file mode 100644
index 00000000..6b80246b
--- /dev/null
+++ b/src/pulsecore/endianmacros.h
@@ -0,0 +1,120 @@
+#ifndef fooendianmacroshfoo
+#define fooendianmacroshfoo
+
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#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 float PA_FLOAT32_SWAP(float x) {
+ uint32_t i = *(uint32_t*) &x;
+ i = PA_UINT32_SWAP(i);
+ return *(float*) &i;
+}
+
+#define PA_MAYBE_INT16_SWAP(c,x) ((c) ? PA_INT32_SWAP(x) : x)
+#define PA_MAYBE_UINT16_SWAP(c,x) ((c) ? PA_UINT32_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)
+
+#define PA_MAYBE_FLOAT32_SWAP(c,x) ((c) ? PA_FLOAT32_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_FLOAT32_TO_LE(x) PA_FLOAT32_SWAP(x)
+ #define PA_FLOAT32_TO_BE(x) ((float) (x))
+#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_FLOAT32_TO_LE(x) ((float) (x))
+ #define PA_FLOAT32_TO_BE(x) PA_FLOAT32_SWAP(x)
+#endif
+
+#endif
diff --git a/src/pulsecore/envelope.c b/src/pulsecore/envelope.c
new file mode 100644
index 00000000..571f8754
--- /dev/null
+++ b/src/pulsecore/envelope.c
@@ -0,0 +1,783 @@
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+
+#include <pulse/sample.h>
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/endianmacros.h>
+#include <pulsecore/memchunk.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/flist.h>
+#include <pulsecore/semaphore.h>
+#include <pulsecore/g711.h>
+
+#include "envelope.h"
+
+/*
+ Envelope subsystem for applying linear interpolated volume
+ envelopes on audio data. If multiple enevelopes shall be applied
+ at the same time, the "minimum" envelope is determined and
+ applied.
+
+ Envelopes are defined in a statically allocated constant structure
+ pa_envelope_def. It may be activated using pa_envelope_add(). And
+ already active envelope may be replaced with pa_envelope_replace()
+ and removed with pa_envelope_remove().The combined "minimum"
+ envelope can be applied to audio data with pa_envelope_apply().
+
+ _apply() on one hand and _add()/_replace()/_remove() on the other
+ can be executed in seperate threads, in which case no locking is
+ used.
+*/
+
+PA_STATIC_FLIST_DECLARE(items, 0, pa_xfree);
+
+struct pa_envelope_item {
+ PA_LLIST_FIELDS(pa_envelope_item);
+ const pa_envelope_def *def;
+ pa_usec_t start_x;
+ union {
+ int32_t i;
+ float f;
+ } start_y;
+ unsigned j;
+};
+
+enum envelope_state {
+ STATE_VALID0,
+ STATE_VALID1,
+ STATE_READ0,
+ STATE_READ1,
+ STATE_WAIT0,
+ STATE_WAIT1,
+ STATE_WRITE0,
+ STATE_WRITE1
+};
+
+struct pa_envelope {
+ pa_sample_spec sample_spec;
+
+ PA_LLIST_HEAD(pa_envelope_item, items);
+
+ pa_atomic_t state;
+
+ size_t x;
+
+ struct {
+ unsigned n_points, n_allocated, n_current;
+
+ size_t *x;
+ union {
+ int32_t *i;
+ float *f;
+ } y;
+
+ size_t cached_dx;
+ int32_t cached_dy_i;
+ float cached_dy_dx;
+ pa_bool_t cached_valid;
+ } points[2];
+
+ pa_bool_t is_float;
+
+ pa_semaphore *semaphore;
+};
+
+pa_envelope *pa_envelope_new(const pa_sample_spec *ss) {
+ pa_envelope *e;
+ pa_assert(ss);
+
+ e = pa_xnew(pa_envelope, 1);
+
+ e->sample_spec = *ss;
+ PA_LLIST_HEAD_INIT(pa_envelope_item, e->items);
+
+ e->x = 0;
+
+ e->points[0].n_points = e->points[1].n_points = 0;
+ e->points[0].n_allocated = e->points[1].n_allocated = 0;
+ e->points[0].n_current = e->points[1].n_current = 0;
+ e->points[0].x = e->points[1].x = NULL;
+ e->points[0].y.i = e->points[1].y.i = NULL;
+ e->points[0].cached_valid = e->points[1].cached_valid = FALSE;
+
+ pa_atomic_store(&e->state, STATE_VALID0);
+
+ e->is_float =
+ ss->format == PA_SAMPLE_FLOAT32LE ||
+ ss->format == PA_SAMPLE_FLOAT32BE;
+
+ e->semaphore = pa_semaphore_new(0);
+
+ return e;
+}
+
+void pa_envelope_free(pa_envelope *e) {
+ pa_assert(e);
+
+ while (e->items)
+ pa_envelope_remove(e, e->items);
+
+ pa_xfree(e->points[0].x);
+ pa_xfree(e->points[1].x);
+ pa_xfree(e->points[0].y.i);
+ pa_xfree(e->points[1].y.i);
+
+ pa_semaphore_free(e->semaphore);
+
+ pa_xfree(e);
+}
+
+static int32_t linear_interpolate_int(pa_usec_t x1, int32_t _y1, pa_usec_t x2, int32_t y2, pa_usec_t x3) {
+ return (int32_t) (_y1 + (x3 - x1) * (float) (y2 - _y1) / (float) (x2 - x1));
+}
+
+static float linear_interpolate_float(pa_usec_t x1, float _y1, pa_usec_t x2, float y2, pa_usec_t x3) {
+ return _y1 + (x3 - x1) * (y2 - _y1) / (x2 - x1);
+}
+
+static int32_t item_get_int(pa_envelope_item *i, pa_usec_t x) {
+ pa_assert(i);
+
+ if (x <= i->start_x)
+ return i->start_y.i;
+
+ x -= i->start_x;
+
+ if (x <= i->def->points_x[0])
+ return linear_interpolate_int(0, i->start_y.i,
+ i->def->points_x[0], i->def->points_y.i[0], x);
+
+ if (x >= i->def->points_x[i->def->n_points-1])
+ return i->def->points_y.i[i->def->n_points-1];
+
+ pa_assert(i->j > 0);
+ pa_assert(i->def->points_x[i->j-1] <= x);
+ pa_assert(x < i->def->points_x[i->j]);
+
+ return linear_interpolate_int(i->def->points_x[i->j-1], i->def->points_y.i[i->j-1],
+ i->def->points_x[i->j], i->def->points_y.i[i->j], x);
+}
+
+static float item_get_float(pa_envelope_item *i, pa_usec_t x) {
+ pa_assert(i);
+
+ if (x <= i->start_x)
+ return i->start_y.f;
+
+ x -= i->start_x;
+
+ if (x <= i->def->points_x[0])
+ return linear_interpolate_float(0, i->start_y.f,
+ i->def->points_x[0], i->def->points_y.f[0], x);
+
+ if (x >= i->def->points_x[i->def->n_points-1])
+ return i->def->points_y.f[i->def->n_points-1];
+
+ pa_assert(i->j > 0);
+ pa_assert(i->def->points_x[i->j-1] <= x);
+ pa_assert(x < i->def->points_x[i->j]);
+
+ return linear_interpolate_float(i->def->points_x[i->j-1], i->def->points_y.f[i->j-1],
+ i->def->points_x[i->j], i->def->points_y.f[i->j], x);
+}
+
+static void envelope_begin_write(pa_envelope *e, int *v) {
+ enum envelope_state new_state, old_state;
+ pa_bool_t wait_sem;
+
+ pa_assert(e);
+ pa_assert(v);
+
+ for (;;) {
+ do {
+ wait_sem = FALSE;
+ old_state = pa_atomic_load(&e->state);
+
+ switch (old_state) {
+ case STATE_VALID0:
+ *v = 1;
+ new_state = STATE_WRITE0;
+ break;
+ case STATE_VALID1:
+ *v = 0;
+ new_state = STATE_WRITE1;
+ break;
+ case STATE_READ0:
+ new_state = STATE_WAIT0;
+ wait_sem = TRUE;
+ break;
+ case STATE_READ1:
+ new_state = STATE_WAIT1;
+ wait_sem = TRUE;
+ break;
+ default:
+ pa_assert_not_reached();
+ }
+ } while (!pa_atomic_cmpxchg(&e->state, old_state, new_state));
+
+ if (!wait_sem)
+ break;
+
+ pa_semaphore_wait(e->semaphore);
+ }
+}
+
+static pa_bool_t envelope_commit_write(pa_envelope *e, int v) {
+ enum envelope_state new_state, old_state;
+
+ pa_assert(e);
+
+ do {
+ old_state = pa_atomic_load(&e->state);
+
+ switch (old_state) {
+ case STATE_WRITE0:
+ pa_assert(v == 1);
+ new_state = STATE_VALID1;
+ break;
+ case STATE_WRITE1:
+ pa_assert(v == 0);
+ new_state = STATE_VALID0;
+ break;
+ case STATE_VALID0:
+ case STATE_VALID1:
+ case STATE_READ0:
+ case STATE_READ1:
+ return FALSE;
+ default:
+ pa_assert_not_reached();
+ }
+ } while (!pa_atomic_cmpxchg(&e->state, old_state, new_state));
+
+ return TRUE;
+}
+
+static void envelope_begin_read(pa_envelope *e, int *v) {
+ enum envelope_state new_state, old_state;
+ pa_assert(e);
+ pa_assert(v);
+
+ do {
+ old_state = pa_atomic_load(&e->state);
+
+ switch (old_state) {
+ case STATE_VALID0:
+ case STATE_WRITE0:
+ *v = 0;
+ new_state = STATE_READ0;
+ break;
+ case STATE_VALID1:
+ case STATE_WRITE1:
+ *v = 1;
+ new_state = STATE_READ1;
+ break;
+ default:
+ pa_assert_not_reached();
+ }
+ } while (!pa_atomic_cmpxchg(&e->state, old_state, new_state));
+}
+
+static void envelope_commit_read(pa_envelope *e, int v) {
+ enum envelope_state new_state, old_state;
+ pa_bool_t post_sem;
+
+ pa_assert(e);
+
+ do {
+ post_sem = FALSE;
+ old_state = pa_atomic_load(&e->state);
+
+ switch (old_state) {
+ case STATE_READ0:
+ pa_assert(v == 0);
+ new_state = STATE_VALID0;
+ break;
+ case STATE_READ1:
+ pa_assert(v == 1);
+ new_state = STATE_VALID1;
+ break;
+ case STATE_WAIT0:
+ pa_assert(v == 0);
+ new_state = STATE_VALID0;
+ post_sem = TRUE;
+ break;
+ case STATE_WAIT1:
+ pa_assert(v == 1);
+ new_state = STATE_VALID1;
+ post_sem = TRUE;
+ break;
+ default:
+ pa_assert_not_reached();
+ }
+ } while (!pa_atomic_cmpxchg(&e->state, old_state, new_state));
+
+ if (post_sem)
+ pa_semaphore_post(e->semaphore);
+}
+
+static void envelope_merge(pa_envelope *e, int v) {
+
+ e->points[v].n_points = 0;
+
+ if (e->items) {
+ pa_envelope_item *i;
+ pa_usec_t x = (pa_usec_t) -1;
+
+ for (i = e->items; i; i = i->next)
+ i->j = 0;
+
+ for (;;) {
+ pa_bool_t min_is_set;
+ pa_envelope_item *s = NULL;
+
+ /* Let's find the next spot on the X axis to analyze */
+ for (i = e->items; i; i = i->next) {
+
+ for (;;) {
+
+ if (i->j >= i->def->n_points)
+ break;
+
+ if ((x != (pa_usec_t) -1) && i->start_x + i->def->points_x[i->j] <= x) {
+ i->j++;
+ continue;
+ }
+
+ if (!s || (i->start_x + i->def->points_x[i->j] < s->start_x + s->def->points_x[s->j]))
+ s = i;
+
+ break;
+ }
+ }
+
+ if (!s)
+ break;
+
+ if (e->points[v].n_points >= e->points[v].n_allocated) {
+ e->points[v].n_allocated = MAX(e->points[v].n_points*2, PA_ENVELOPE_POINTS_MAX);
+
+ e->points[v].x = pa_xrealloc(e->points[v].x, sizeof(size_t) * e->points[v].n_allocated);
+ e->points[v].y.i = pa_xrealloc(e->points[v].y.i, sizeof(int32_t) * e->points[v].n_allocated);
+ }
+
+ x = s->start_x + s->def->points_x[s->j];
+ e->points[v].x[e->points[v].n_points] = pa_usec_to_bytes(x, &e->sample_spec);
+
+ min_is_set = FALSE;
+
+ /* Now let's find the lowest value */
+ if (e->is_float) {
+ float min_f;
+
+ for (i = e->items; i; i = i->next) {
+ float f = item_get_float(i, x);
+ if (!min_is_set || f < min_f) {
+ min_f = f;
+ min_is_set = TRUE;
+ }
+ }
+
+ e->points[v].y.f[e->points[v].n_points] = min_f;
+ } else {
+ int32_t min_k;
+
+ for (i = e->items; i; i = i->next) {
+ int32_t k = item_get_int(i, x);
+ if (!min_is_set || k < min_k) {
+ min_k = k;
+ min_is_set = TRUE;
+ }
+ }
+
+ e->points[v].y.i[e->points[v].n_points] = min_k;
+ }
+
+ pa_assert_se(min_is_set);
+ e->points[v].n_points++;
+ }
+ }
+
+ e->points[v].n_current = 0;
+ e->points[v].cached_valid = FALSE;
+}
+
+pa_envelope_item *pa_envelope_add(pa_envelope *e, const pa_envelope_def *def) {
+ pa_envelope_item *i;
+ int v;
+
+ pa_assert(e);
+ pa_assert(def);
+ pa_assert(def->n_points > 0);
+
+ if (!(i = pa_flist_pop(PA_STATIC_FLIST_GET(items))))
+ i = pa_xnew(pa_envelope_item, 1);
+
+ i->def = def;
+
+ if (e->is_float)
+ i->start_y.f = def->points_y.f[0];
+ else
+ i->start_y.i = def->points_y.i[0];
+
+ PA_LLIST_PREPEND(pa_envelope_item, e->items, i);
+
+ envelope_begin_write(e, &v);
+
+ do {
+
+ i->start_x = pa_bytes_to_usec(e->x, &e->sample_spec);
+ envelope_merge(e, v);
+
+ } while (!envelope_commit_write(e, v));
+
+ return i;
+}
+
+pa_envelope_item *pa_envelope_replace(pa_envelope *e, pa_envelope_item *i, const pa_envelope_def *def) {
+ pa_usec_t x;
+ int v;
+
+ pa_assert(e);
+ pa_assert(i);
+ pa_assert(def->n_points > 0);
+
+ envelope_begin_write(e, &v);
+
+ for (;;) {
+ float saved_f;
+ int32_t saved_i;
+ uint64_t saved_start_x;
+ const pa_envelope_def *saved_def;
+
+ x = pa_bytes_to_usec(e->x, &e->sample_spec);
+
+ if (e->is_float) {
+ saved_f = i->start_y.f;
+ i->start_y.f = item_get_float(i, x);
+ } else {
+ saved_i = i->start_y.i;
+ i->start_y.i = item_get_int(i, x);
+ }
+
+ saved_start_x = i->start_x;
+ saved_def = i->def;
+
+ i->start_x = x;
+ i->def = def;
+
+ envelope_merge(e, v);
+
+ if (envelope_commit_write(e, v))
+ break;
+
+ i->start_x = saved_start_x;
+ i->def = saved_def;
+
+ if (e->is_float)
+ i->start_y.f = saved_f;
+ else
+ i->start_y.i = saved_i;
+ }
+
+ return i;
+}
+
+void pa_envelope_remove(pa_envelope *e, pa_envelope_item *i) {
+ int v;
+
+ pa_assert(e);
+ pa_assert(i);
+
+ PA_LLIST_REMOVE(pa_envelope_item, e->items, i);
+
+ if (pa_flist_push(PA_STATIC_FLIST_GET(items), i) < 0)
+ pa_xfree(i);
+
+ envelope_begin_write(e, &v);
+ do {
+ envelope_merge(e, v);
+ } while (!envelope_commit_write(e, v));
+}
+
+static int32_t linear_get_int(pa_envelope *e, int v) {
+ pa_assert(e);
+
+ /* The repeated division could be replaced by Bresenham, as an
+ * optimization */
+
+ if (e->x < e->points[v].x[0])
+ return e->points[v].y.i[0];
+
+ for (;;) {
+ if (e->points[v].n_current+1 >= e->points[v].n_points)
+ return e->points[v].y.i[e->points[v].n_points-1];
+
+ if (e->x < e->points[v].x[e->points[v].n_current+1])
+ break;
+
+ e->points[v].n_current++;
+ e->points[v].cached_valid = FALSE;
+ }
+
+ if (!e->points[v].cached_valid) {
+ e->points[v].cached_dx = e->points[v].x[e->points[v].n_current+1] - e->points[v].x[e->points[v].n_current];
+ e->points[v].cached_dy_i = e->points[v].y.i[e->points[v].n_current+1] - e->points[v].y.i[e->points[v].n_current];
+ e->points[v].cached_valid = TRUE;
+ }
+
+ return e->points[v].y.i[e->points[v].n_current] + (e->points[v].cached_dy_i * (int32_t) (e->x - e->points[v].x[e->points[v].n_current])) / (int32_t) e->points[v].cached_dx;
+}
+
+static float linear_get_float(pa_envelope *e, int v) {
+ pa_assert(e);
+
+ if (e->x < e->points[v].x[0])
+ return e->points[v].y.f[0];
+
+ for (;;) {
+ if (e->points[v].n_current+1 >= e->points[v].n_points)
+ return e->points[v].y.f[e->points[v].n_points-1];
+
+ if (e->x < e->points[v].x[e->points[v].n_current+1])
+ break;
+
+ e->points[v].n_current++;
+ e->points[v].cached_valid = FALSE;
+ }
+
+ if (!e->points[v].cached_valid) {
+ e->points[v].cached_dy_dx =
+ (e->points[v].y.f[e->points[v].n_current+1] - e->points[v].y.f[e->points[v].n_current]) /
+ (e->points[v].x[e->points[v].n_current+1] - e->points[v].x[e->points[v].n_current]);
+ e->points[v].cached_valid = TRUE;
+ }
+
+ return e->points[v].y.f[e->points[v].n_current] + (e->x - e->points[v].x[e->points[v].n_current]) * e->points[v].cached_dy_dx;
+}
+
+void pa_envelope_apply(pa_envelope *e, pa_memchunk *chunk) {
+ int v;
+
+ pa_assert(e);
+ pa_assert(chunk);
+
+ envelope_begin_read(e, &v);
+
+ if (e->points[v].n_points > 0) {
+ void *p;
+ size_t fs, n;
+
+ pa_memchunk_make_writable(chunk, 0);
+ p = (uint8_t*) pa_memblock_acquire(chunk->memblock) + chunk->index;
+ fs = pa_frame_size(&e->sample_spec);
+ n = chunk->length;
+
+ switch (e->sample_spec.format) {
+
+
+
+ case PA_SAMPLE_U8: {
+ uint8_t *t;
+
+ for (t = p; n > 0; n -= fs) {
+ int16_t factor = linear_get_int(e, v);
+ unsigned c;
+ e->x += fs;
+
+ for (c = 0; c < e->sample_spec.channels; c++, t++)
+ *t = (uint8_t) (((factor * ((int16_t) *t - 0x80)) / 0x10000) + 0x80);
+ }
+
+ break;
+ }
+
+ case PA_SAMPLE_ULAW: {
+ uint8_t *t;
+
+ for (t = p; n > 0; n -= fs) {
+ int16_t factor = linear_get_int(e, v);
+ unsigned c;
+ e->x += fs;
+
+ for (c = 0; c < e->sample_spec.channels; c++, t++) {
+ int16_t k = st_ulaw2linear16(*t);
+ *t = (uint8_t) st_14linear2ulaw(((factor * k) / 0x10000) >> 2);
+ }
+ }
+
+ break;
+ }
+
+ case PA_SAMPLE_ALAW: {
+ uint8_t *t;
+
+ for (t = p; n > 0; n -= fs) {
+ int16_t factor = linear_get_int(e, v);
+ unsigned c;
+ e->x += fs;
+
+ for (c = 0; c < e->sample_spec.channels; c++, t++) {
+ int16_t k = st_alaw2linear16(*t);
+ *t = (uint8_t) st_13linear2alaw(((factor * k) / 0x10000) >> 3);
+ }
+ }
+
+ break;
+ }
+
+ case PA_SAMPLE_S16NE: {
+ int16_t *t;
+
+ for (t = p; n > 0; n -= fs) {
+ int32_t factor = linear_get_int(e, v);
+ unsigned c;
+ e->x += fs;
+
+ for (c = 0; c < e->sample_spec.channels; c++, t++)
+ *t = (factor * *t) / 0x10000;
+ }
+
+ break;
+ }
+
+ case PA_SAMPLE_S16RE: {
+ int16_t *t;
+
+ for (t = p; n > 0; n -= fs) {
+ int32_t factor = linear_get_int(e, v);
+ unsigned c;
+ e->x += fs;
+
+ for (c = 0; c < e->sample_spec.channels; c++, t++) {
+ int16_t r = (factor * PA_INT16_SWAP(*t)) / 0x10000;
+ *t = PA_INT16_SWAP(r);
+ }
+ }
+
+ break;
+ }
+
+ case PA_SAMPLE_S32NE: {
+ int32_t *t;
+
+ for (t = p; n > 0; n -= fs) {
+ int32_t factor = linear_get_int(e, v);
+ unsigned c;
+ e->x += fs;
+
+ for (c = 0; c < e->sample_spec.channels; c++, t++)
+ *t = (int32_t) (((int64_t) factor * (int64_t) *t) / 0x10000);
+ }
+
+ break;
+ }
+
+ case PA_SAMPLE_S32RE: {
+ int32_t *t;
+
+ for (t = p; n > 0; n -= fs) {
+ int32_t factor = linear_get_int(e, v);
+ unsigned c;
+ e->x += fs;
+
+ for (c = 0; c < e->sample_spec.channels; c++, t++) {
+ int32_t r = (int32_t) (((int64_t) factor * (int64_t) PA_INT32_SWAP(*t)) / 0x10000);
+ *t = PA_INT32_SWAP(r);
+ }
+ }
+
+ break;
+ }
+
+ case PA_SAMPLE_FLOAT32NE: {
+ float *t;
+
+ for (t = p; n > 0; n -= fs) {
+ float factor = linear_get_float(e, v);
+ unsigned c;
+ e->x += fs;
+
+ for (c = 0; c < e->sample_spec.channels; c++, t++)
+ *t = *t * factor;
+ }
+
+ break;
+ }
+
+ case PA_SAMPLE_FLOAT32RE: {
+ float *t;
+
+ for (t = p; n > 0; n -= fs) {
+ float factor = linear_get_float(e, v);
+ unsigned c;
+ e->x += fs;
+
+ for (c = 0; c < e->sample_spec.channels; c++, t++) {
+ float r = PA_FLOAT32_SWAP(*t) * factor;
+ *t = PA_FLOAT32_SWAP(r);
+ }
+ }
+
+ break;
+ }
+
+ case PA_SAMPLE_MAX:
+ case PA_SAMPLE_INVALID:
+ pa_assert_not_reached();
+ }
+
+ pa_memblock_release(chunk->memblock);
+
+ e->x += chunk->length;
+ } else {
+ /* When we have no envelope to apply we reset our origin */
+ e->x = 0;
+ }
+
+ envelope_commit_read(e, v);
+}
+
+void pa_envelope_rewind(pa_envelope *e, size_t n_bytes) {
+ int v;
+
+ pa_assert(e);
+
+ envelope_begin_read(e, &v);
+
+ if (n_bytes < e->x)
+ e->x -= n_bytes;
+ else
+ e->x = 0;
+
+ e->points[v].n_current = 0;
+ e->points[v].cached_valid = FALSE;
+
+ envelope_commit_read(e, v);
+}
diff --git a/src/pulsecore/envelope.h b/src/pulsecore/envelope.h
new file mode 100644
index 00000000..23be8f6a
--- /dev/null
+++ b/src/pulsecore/envelope.h
@@ -0,0 +1,55 @@
+#ifndef foopulseenvelopehfoo
+#define foopulseenvelopehfoo
+
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <pulsecore/macro.h>
+#include <pulsecore/memchunk.h>
+
+#include <pulse/sample.h>
+
+#define PA_ENVELOPE_POINTS_MAX 4
+
+typedef struct pa_envelope pa_envelope;
+typedef struct pa_envelope_item pa_envelope_item;
+
+typedef struct pa_envelope_def {
+ unsigned n_points;
+
+ pa_usec_t points_x[PA_ENVELOPE_POINTS_MAX];
+ struct {
+ int32_t i[PA_ENVELOPE_POINTS_MAX];
+ float f[PA_ENVELOPE_POINTS_MAX];
+ } points_y;
+} pa_envelope_def;
+
+pa_envelope *pa_envelope_new(const pa_sample_spec *ss);
+void pa_envelope_free(pa_envelope *e);
+pa_envelope_item *pa_envelope_add(pa_envelope *e, const pa_envelope_def *def);
+pa_envelope_item *pa_envelope_replace(pa_envelope *e, pa_envelope_item *i, const pa_envelope_def *def);
+void pa_envelope_remove(pa_envelope *e, pa_envelope_item *i);
+void pa_envelope_apply(pa_envelope *e, pa_memchunk *chunk);
+void pa_envelope_rewind(pa_envelope *e, size_t n_bytes);
+
+#endif
diff --git a/src/pulsecore/esound.h b/src/pulsecore/esound.h
new file mode 100644
index 00000000..ea6a5665
--- /dev/null
+++ b/src/pulsecore/esound.h
@@ -0,0 +1,211 @@
+#ifndef fooesoundhfoo
+#define fooesoundhfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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.
+***/
+
+/* 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 authorization 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 inital 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 immed. */
+
+ /* 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 complere 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 00000000..59eec18e
--- /dev/null
+++ b/src/pulsecore/fdsem.c
@@ -0,0 +1,280 @@
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#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/thread.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/core-util.h>
+#include <pulse/xmalloc.h>
+
+#ifndef HAVE_PIPE
+#include <pulsecore/pipe.h>
+#endif
+
+#ifdef __linux__
+
+#if !defined(__NR_eventfd) && defined(__i386__)
+#define __NR_eventfd 323
+#endif
+
+#if !defined(__NR_eventfd) && defined(__x86_64__)
+#define __NR_eventfd 284
+#endif
+
+#if !defined(__NR_eventfd) && defined(__arm__)
+#define __NR_eventfd (__NR_SYSCALL_BASE+351)
+#endif
+
+#if !defined(SYS_eventfd) && defined(__NR_eventfd)
+#define SYS_eventfd __NR_eventfd
+#endif
+
+#ifdef SYS_eventfd
+#define HAVE_EVENTFD
+
+static inline long eventfd(unsigned count) {
+ return syscall(SYS_eventfd, count);
+}
+
+#endif
+#endif
+
+#include "fdsem.h"
+
+struct pa_fdsem {
+ int fds[2];
+#ifdef HAVE_EVENTFD
+ int efd;
+#endif
+ pa_atomic_t waiting;
+ pa_atomic_t signalled;
+ pa_atomic_t in_pipe;
+};
+
+pa_fdsem *pa_fdsem_new(void) {
+ pa_fdsem *f;
+
+ f = pa_xnew(pa_fdsem, 1);
+
+#ifdef HAVE_EVENTFD
+ if ((f->efd = eventfd(0)) >= 0) {
+ pa_make_fd_cloexec(f->efd);
+ f->fds[0] = f->fds[1] = -1;
+
+ } else
+#endif
+ {
+ if (pipe(f->fds) < 0) {
+ pa_xfree(f);
+ return NULL;
+ }
+
+ pa_make_fd_cloexec(f->fds[0]);
+ pa_make_fd_cloexec(f->fds[1]);
+ }
+
+ pa_atomic_store(&f->waiting, 0);
+ pa_atomic_store(&f->signalled, 0);
+ pa_atomic_store(&f->in_pipe, 0);
+
+ return f;
+}
+
+void pa_fdsem_free(pa_fdsem *f) {
+ pa_assert(f);
+
+#ifdef HAVE_EVENTFD
+ 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->in_pipe) <= 0)
+ return;
+
+ do {
+ char x[10];
+
+#ifdef HAVE_EVENTFD
+ if (f->efd >= 0) {
+ uint64_t u;
+
+ if ((r = read(f->efd, &u, sizeof(u))) != sizeof(u)) {
+ pa_assert(r < 0 && errno == EINTR);
+ continue;
+ }
+ r = (ssize_t) u;
+ } else
+#endif
+
+ if ((r = read(f->fds[0], &x, sizeof(x))) <= 0) {
+ pa_assert(r < 0 && errno == EINTR);
+ continue;
+ }
+
+ } while (pa_atomic_sub(&f->in_pipe, r) > r);
+}
+
+void pa_fdsem_post(pa_fdsem *f) {
+ pa_assert(f);
+
+ if (pa_atomic_cmpxchg(&f->signalled, 0, 1)) {
+
+ if (pa_atomic_load(&f->waiting)) {
+ ssize_t r;
+ char x = 'x';
+
+ pa_atomic_inc(&f->in_pipe);
+
+ for (;;) {
+
+#ifdef HAVE_EVENTFD
+ if (f->efd >= 0) {
+ uint64_t u = 1;
+
+ if ((r = write(f->efd, &u, sizeof(u))) != sizeof(u)) {
+ pa_assert(r < 0 && errno == EINTR);
+ continue;
+ }
+ } else
+#endif
+
+ if ((r = write(f->fds[1], &x, 1)) != 1) {
+ pa_assert(r < 0 && errno == EINTR);
+ continue;
+ }
+
+ break;
+ }
+ }
+ }
+}
+
+void pa_fdsem_wait(pa_fdsem *f) {
+ pa_assert(f);
+
+ flush(f);
+
+ if (pa_atomic_cmpxchg(&f->signalled, 1, 0))
+ return;
+
+ pa_atomic_inc(&f->waiting);
+
+ while (!pa_atomic_cmpxchg(&f->signalled, 1, 0)) {
+ char x[10];
+ ssize_t r;
+
+#ifdef HAVE_EVENTFD
+ if (f->efd >= 0) {
+ uint64_t u;
+
+ if ((r = read(f->efd, &u, sizeof(u))) != sizeof(u)) {
+ pa_assert(r < 0 && errno == EINTR);
+ continue;
+ }
+
+ r = (ssize_t) u;
+ } else
+#endif
+
+ if ((r = read(f->fds[0], &x, sizeof(x))) <= 0) {
+ pa_assert(r < 0 && errno == EINTR);
+ continue;
+ }
+
+ pa_atomic_sub(&f->in_pipe, r);
+ }
+
+ pa_assert_se(pa_atomic_dec(&f->waiting) >= 1);
+}
+
+int pa_fdsem_try(pa_fdsem *f) {
+ pa_assert(f);
+
+ flush(f);
+
+ if (pa_atomic_cmpxchg(&f->signalled, 1, 0))
+ return 1;
+
+ return 0;
+}
+
+int pa_fdsem_get(pa_fdsem *f) {
+ pa_assert(f);
+
+#ifdef HAVE_EVENTFD
+ 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->signalled, 1, 0))
+ return -1;
+
+ pa_atomic_inc(&f->waiting);
+
+ if (pa_atomic_cmpxchg(&f->signalled, 1, 0)) {
+ pa_assert_se(pa_atomic_dec(&f->waiting) >= 1);
+ return -1;
+ }
+ return 0;
+}
+
+int pa_fdsem_after_poll(pa_fdsem *f) {
+ pa_assert(f);
+
+ pa_assert_se(pa_atomic_dec(&f->waiting) >= 1);
+
+ flush(f);
+
+ if (pa_atomic_cmpxchg(&f->signalled, 1, 0))
+ return 1;
+
+ return 0;
+}
diff --git a/src/pulsecore/fdsem.h b/src/pulsecore/fdsem.h
new file mode 100644
index 00000000..f38ef205
--- /dev/null
+++ b/src/pulsecore/fdsem.h
@@ -0,0 +1,49 @@
+#ifndef foopulsefdsemhfoo
+#define foopulsefdsemhfoo
+
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <sys/types.h>
+#include <pulse/def.h>
+
+/* A simple, asynchronous semaphore which uses fds for sleeping. In
+ * the best case all functions are lock-free unless sleeping is
+ * required. */
+
+typedef struct pa_fdsem pa_fdsem;
+
+pa_fdsem *pa_fdsem_new(void);
+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/Makefile b/src/pulsecore/ffmpeg/Makefile
new file mode 100644
index 00000000..316beb72
--- /dev/null
+++ b/src/pulsecore/ffmpeg/Makefile
@@ -0,0 +1,13 @@
+# This is a dirty trick just to ease compilation with emacs
+#
+# This file is not intended to be distributed or anything
+#
+# So: don't touch it, even better ignore it!
+
+all:
+ $(MAKE) -C ../..
+
+clean:
+ $(MAKE) -C ../.. clean
+
+.PHONY: all clean
diff --git a/src/pulsecore/ffmpeg/avcodec.h b/src/pulsecore/ffmpeg/avcodec.h
new file mode 100644
index 00000000..696fc986
--- /dev/null
+++ b/src/pulsecore/ffmpeg/avcodec.h
@@ -0,0 +1,82 @@
+/*
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#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 00000000..8da742d0
--- /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 00000000..da1443d9
--- /dev/null
+++ b/src/pulsecore/ffmpeg/resample2.c
@@ -0,0 +1,324 @@
+/*
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file 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
+}
+
+/**
+ * Initializes an audio resampler.
+ * Note, if either rate is not an integer then simply scale both rates up so they are.
+ */
+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);
+}
+
+/**
+ * Compensates samplerate/timestamp drift. The compensation is done by changing
+ * the resampler parameters, so no audible clicks or similar distortions ocur
+ * @param compensation_distance distance in output samples over which the compensation should be performed
+ * @param sample_delta number of output samples which should be output less
+ *
+ * example: av_resample_compensate(c, 10, 500)
+ * here instead of 510 samples only 500 samples would be output
+ *
+ * note, due to rounding the actual compensation might be slightly different,
+ * especially if the compensation_distance is large and the in_rate used during init is small
+ */
+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;
+}
+
+/**
+ * resamples.
+ * @param src an array of unconsumed samples
+ * @param consumed the number of samples of src which have been consumed are returned here
+ * @param src_size the number of unconsumed samples available
+ * @param dst_size the amount of space in samples available in dst
+ * @param update_ctx if this is 0 then the context wont be modified, that way several channels can be resampled with the same context
+ * @return the number of samples written in dst or -1 if an error occured
+ */
+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/flist.c b/src/pulsecore/flist.c
new file mode 100644
index 00000000..d9740777
--- /dev/null
+++ b/src/pulsecore/flist.c
@@ -0,0 +1,234 @@
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/atomic.h>
+#include <pulsecore/log.h>
+#include <pulsecore/thread.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/macro.h>
+
+#include "flist.h"
+
+/* Algorithm is not perfect, in a few corner cases it will fail to pop
+ * from the flist although it isn't empty, and fail to push into the
+ * flist, although it isn't full.
+ *
+ * We keep a fixed size array of entries, each item is either marked
+ * UNUSED, USED or BUSY and contains a user data pointer. When pushing
+ * into the queue we look for an UNUSED cell and mark it BUSY with a
+ * CAS operation. If successful we use it and mark it USED, otherwise
+ * we go on and look for the next UNUSED cell. The algorithm for
+ * popping an item from the queue is practically inverse: look for a
+ * USED cell and and mark it BUSY with a CAS operation, after reading
+ * from it mark it UNUSED again.
+ *
+ * To accelerate finding of used/unused cells we maintain a read and a
+ * write index which is used like a ring buffer. After each push we
+ * increase the write index and after each pop we increase the read
+ * index.
+ *
+ * The indexes are incremented atomically and are never truncated to
+ * the buffer size. Instead we assume that the buffer size is a power
+ * of two and that the truncation can thus be done by applying a
+ * simple AND on read.
+ *
+ * To make sure that we do not look for empty cells indefinitely we
+ * maintain a length value which stores the number of used cells. From
+ * this value the number of unused cells is easily calculated. Please
+ * note that the length value is not updated atomically with the read
+ * and write index and might thus be a few cells off the real
+ * value. To deal with this we always look for N_EXTRA_SCAN extra
+ * cells when pushing/popping entries.
+ *
+ * It might make sense to replace this implementation with a link list
+ * stack or queue, which however requires DCAS to be simple. Patches
+ * welcome.
+ *
+ * Please note that this algorithm is home grown.*/
+
+#define FLIST_SIZE 128
+#define N_EXTRA_SCAN 2
+
+/* For debugging purposes we can define _Y to put and extra thread
+ * yield between each operation. */
+
+#ifdef PROFILE
+#define _Y pa_thread_yield()
+#else
+#define _Y do { } while(0)
+#endif
+
+enum {
+ STATE_UNUSED,
+ STATE_USED,
+ STATE_BUSY
+};
+
+struct cell {
+ pa_atomic_t state;
+ void *data;
+};
+
+struct pa_flist {
+ unsigned size;
+ pa_atomic_t length;
+ pa_atomic_t read_idx;
+ pa_atomic_t write_idx;
+};
+
+#define PA_FLIST_CELLS(x) ((struct cell*) ((uint8_t*) (x) + PA_ALIGN(sizeof(struct pa_flist))))
+
+pa_flist *pa_flist_new(unsigned size) {
+ pa_flist *l;
+
+ if (!size)
+ size = FLIST_SIZE;
+
+ pa_assert(pa_is_power_of_two(size));
+
+ l = pa_xmalloc0(PA_ALIGN(sizeof(pa_flist)) + (sizeof(struct cell) * size));
+
+ l->size = size;
+
+ pa_atomic_store(&l->read_idx, 0);
+ pa_atomic_store(&l->write_idx, 0);
+ pa_atomic_store(&l->length, 0);
+
+ return l;
+}
+
+static int reduce(pa_flist *l, int value) {
+ return value & (unsigned) (l->size - 1);
+}
+
+void pa_flist_free(pa_flist *l, pa_free_cb_t free_cb) {
+ pa_assert(l);
+
+ if (free_cb) {
+ struct cell *cells;
+ int len, idx;
+
+ cells = PA_FLIST_CELLS(l);
+
+ idx = reduce(l, pa_atomic_load(&l->read_idx));
+ len = pa_atomic_load(&l->length);
+
+ for (; len > 0; len--) {
+
+ if (pa_atomic_load(&cells[idx].state) == STATE_USED)
+ free_cb(cells[idx].data);
+
+ idx = reduce(l, idx + 1);
+ }
+ }
+
+ pa_xfree(l);
+}
+
+int pa_flist_push(pa_flist*l, void *p) {
+ int idx, len, n;
+ struct cell *cells;
+
+ pa_assert(l);
+ pa_assert(p);
+
+ cells = PA_FLIST_CELLS(l);
+
+ n = len = (int) l->size - pa_atomic_load(&l->length) + N_EXTRA_SCAN;
+ _Y;
+ idx = reduce(l, pa_atomic_load(&l->write_idx));
+
+ for (; n > 0 ; n--) {
+ _Y;
+
+ if (pa_atomic_cmpxchg(&cells[idx].state, STATE_UNUSED, STATE_BUSY)) {
+ _Y;
+ pa_atomic_inc(&l->write_idx);
+ _Y;
+ cells[idx].data = p;
+ _Y;
+ pa_atomic_store(&cells[idx].state, STATE_USED);
+ _Y;
+ pa_atomic_inc(&l->length);
+ return 0;
+ }
+
+ _Y;
+ idx = reduce(l, idx + 1);
+ }
+
+#ifdef PROFILE
+ if (len > N_EXTRA_SCAN)
+ pa_log_warn("Didn't find free cell after %u iterations.", len);
+#endif
+
+ return -1;
+}
+
+void* pa_flist_pop(pa_flist*l) {
+ int idx, len, n;
+ struct cell *cells;
+
+ pa_assert(l);
+
+ cells = PA_FLIST_CELLS(l);
+
+ n = len = pa_atomic_load(&l->length) + N_EXTRA_SCAN;
+ _Y;
+ idx = reduce(l, pa_atomic_load(&l->read_idx));
+
+ for (; n > 0 ; n--) {
+ _Y;
+
+ if (pa_atomic_cmpxchg(&cells[idx].state, STATE_USED, STATE_BUSY)) {
+ void *p;
+ _Y;
+ pa_atomic_inc(&l->read_idx);
+ _Y;
+ p = cells[idx].data;
+ _Y;
+ pa_atomic_store(&cells[idx].state, STATE_UNUSED);
+ _Y;
+
+ pa_atomic_dec(&l->length);
+ return p;
+ }
+
+ _Y;
+ idx = reduce(l, idx+1);
+ }
+
+#ifdef PROFILE
+ if (len > N_EXTRA_SCAN)
+ pa_log_warn("Didn't find used cell after %u iterations.", len);
+#endif
+
+ return NULL;
+}
diff --git a/src/pulsecore/flist.h b/src/pulsecore/flist.h
new file mode 100644
index 00000000..daf0fec4
--- /dev/null
+++ b/src/pulsecore/flist.h
@@ -0,0 +1,68 @@
+#ifndef foopulseflisthfoo
+#define foopulseflisthfoo
+
+/* $Id$ */
+
+/***
+ 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 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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/def.h>
+
+#include <pulsecore/once.h>
+#include <pulsecore/gccmacro.h>
+
+/* A multiple-reader multipler-write lock-free free list implementation */
+
+typedef struct pa_flist pa_flist;
+
+/* Size is required to be a power of two, or 0 for the default size */
+pa_flist * pa_flist_new(unsigned size);
+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 not that the destructor stuff is not really necesary, we do
+ * this just to make valgrind output more useful. */
+
+#define PA_STATIC_FLIST_DECLARE(name, size, free_cb) \
+ static struct { \
+ pa_flist *flist; \
+ pa_once once; \
+ } name##_flist = { NULL, PA_ONCE_INIT }; \
+ static void name##_flist_init(void) { \
+ name##_flist.flist = pa_flist_new(size); \
+ } \
+ 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 (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 00000000..aa2d703a
--- /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 00000000..37ebcf72
--- /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/gccmacro.h b/src/pulsecore/gccmacro.h
new file mode 100644
index 00000000..f94a8c45
--- /dev/null
+++ b/src/pulsecore/gccmacro.h
@@ -0,0 +1,90 @@
+#ifndef foopulsegccmacrohfoo
+#define foopulsegccmacrohfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef __GNUC__
+#define PA_GCC_PRINTF_ATTR(a,b) __attribute__ ((format (printf, a, b)))
+#else
+/** If we're in GNU C, use some magic for detecting invalid format strings */
+#define PA_GCC_PRINTF_ATTR(a,b)
+#endif
+
+#if defined(__GNUC__) && (__GNUC__ >= 4)
+#define PA_GCC_SENTINEL __attribute__ ((sentinel))
+#else
+/** Macro for usage of GCC's sentinel compilation warnings */
+#define PA_GCC_SENTINEL
+#endif
+
+#ifdef __GNUC__
+#define PA_GCC_NORETURN __attribute__((noreturn))
+#else
+/** Macro for no-return functions */
+#define PA_GCC_NORETURN
+#endif
+
+#ifdef __GNUC__
+#define PA_GCC_UNUSED __attribute__ ((unused))
+#else
+/** Macro for not used parameter */
+#define PA_GCC_UNUSED
+#endif
+
+#ifdef __GNUC__
+#define PA_GCC_DESTRUCTOR __attribute__ ((destructor))
+#else
+/** Call this function when process terminates */
+#define PA_GCC_DESTRUCTOR
+#endif
+
+#ifndef PA_GCC_PURE
+#ifdef __GNUC__
+#define PA_GCC_PURE __attribute__ ((pure))
+#else
+/** This function's return value depends only the arguments list and global state **/
+#define PA_GCC_PURE
+#endif
+#endif
+
+#ifndef PA_GCC_CONST
+#ifdef __GNUC__
+#define PA_GCC_CONST __attribute__ ((const))
+#else
+/** This function's return value depends only the arguments list (stricter version of PA_GCC_PURE) **/
+#define PA_GCC_CONST
+#endif
+#endif
+
+#ifndef PA_LIKELY
+#ifdef __GNUC__
+#define PA_LIKELY(x) (__builtin_expect(!!(x),1))
+#define PA_UNLIKELY(x) (__builtin_expect((x),0))
+#else
+#define PA_LIKELY(x) (x)
+#define PA_UNLIKELY(x) (x)
+#endif
+#endif
+
+#endif
diff --git a/src/pulsecore/hashmap.c b/src/pulsecore/hashmap.c
new file mode 100644
index 00000000..f5589664
--- /dev/null
+++ b/src/pulsecore/hashmap.c
@@ -0,0 +1,234 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/idxset.h>
+#include <pulsecore/log.h>
+#include <pulsecore/flist.h>
+#include <pulsecore/macro.h>
+
+#include "hashmap.h"
+
+#define BUCKETS 127
+
+struct hashmap_entry {
+ struct hashmap_entry *next, *previous, *bucket_next, *bucket_previous;
+ unsigned hash;
+ const void *key;
+ void *value;
+};
+
+struct pa_hashmap {
+ unsigned size;
+ struct hashmap_entry **data;
+ struct hashmap_entry *first_entry;
+
+ unsigned n_entries;
+ pa_hash_func_t hash_func;
+ pa_compare_func_t compare_func;
+};
+
+PA_STATIC_FLIST_DECLARE(entries, 0, pa_xfree);
+
+pa_hashmap *pa_hashmap_new(pa_hash_func_t hash_func, pa_compare_func_t compare_func) {
+ pa_hashmap *h;
+
+ h = pa_xnew(pa_hashmap, 1);
+ h->data = pa_xnew0(struct hashmap_entry*, h->size = BUCKETS);
+ h->first_entry = NULL;
+ h->n_entries = 0;
+ h->hash_func = hash_func ? hash_func : pa_idxset_trivial_hash_func;
+ h->compare_func = compare_func ? compare_func : pa_idxset_trivial_compare_func;
+
+ return h;
+}
+
+static void remove(pa_hashmap *h, struct hashmap_entry *e) {
+ pa_assert(h);
+ pa_assert(e);
+
+ if (e->next)
+ e->next->previous = e->previous;
+ if (e->previous)
+ e->previous->next = e->next;
+ else
+ h->first_entry = e->next;
+
+ 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 {
+ pa_assert(e->hash < h->size);
+ h->data[e->hash] = e->bucket_next;
+ }
+
+ if (pa_flist_push(PA_STATIC_FLIST_GET(entries), e) < 0)
+ pa_xfree(e);
+
+ h->n_entries--;
+}
+
+void pa_hashmap_free(pa_hashmap*h, void (*free_func)(void *p, void *userdata), void *userdata) {
+ pa_assert(h);
+
+ while (h->first_entry) {
+ if (free_func)
+ free_func(h->first_entry->value, userdata);
+ remove(h, h->first_entry);
+ }
+
+ pa_xfree(h->data);
+ pa_xfree(h);
+}
+
+static struct hashmap_entry *get(pa_hashmap *h, unsigned hash, const void *key) {
+ struct hashmap_entry *e;
+ pa_assert(h);
+ pa_assert(hash < h->size);
+
+ for (e = h->data[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, const void *key, void *value) {
+ struct hashmap_entry *e;
+ unsigned hash;
+ pa_assert(h);
+
+ hash = h->hash_func(key) % h->size;
+
+ if ((e = get(h, hash, key)))
+ return -1;
+
+ if (!(e = pa_flist_pop(PA_STATIC_FLIST_GET(entries))))
+ e = pa_xnew(struct hashmap_entry, 1);
+
+ e->hash = hash;
+ e->key = key;
+ e->value = value;
+
+ e->previous = NULL;
+ e->next = h->first_entry;
+ if (h->first_entry)
+ h->first_entry->previous = e;
+ h->first_entry = e;
+
+ e->bucket_previous = NULL;
+ e->bucket_next = h->data[hash];
+ if (h->data[hash])
+ h->data[hash]->bucket_previous = e;
+ h->data[hash] = e;
+
+ h->n_entries ++;
+ return 0;
+}
+
+void* pa_hashmap_get(pa_hashmap *h, const void *key) {
+ unsigned hash;
+ struct hashmap_entry *e;
+
+ pa_assert(h);
+
+ hash = h->hash_func(key) % h->size;
+
+ if (!(e = get(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) % h->size;
+
+ if (!(e = get(h, hash, key)))
+ return NULL;
+
+ data = e->value;
+ remove(h, e);
+ return data;
+}
+
+unsigned pa_hashmap_size(pa_hashmap *h) {
+ return h->n_entries;
+}
+
+void *pa_hashmap_iterate(pa_hashmap *h, void **state, const void **key) {
+ pa_assert(h);
+ pa_assert(state);
+
+ if (!*state)
+ *state = h->first_entry;
+ else
+ *state = ((struct hashmap_entry*) *state)->next;
+
+ if (!*state) {
+ if (key)
+ *key = NULL;
+ return NULL;
+ }
+
+ if (key)
+ *key = ((struct hashmap_entry*) *state)->key;
+
+ return ((struct hashmap_entry*) *state)->value;
+}
+
+void* pa_hashmap_steal_first(pa_hashmap *h) {
+ void *data;
+
+ pa_assert(h);
+
+ if (!h->first_entry)
+ return NULL;
+
+ data = h->first_entry->value;
+ remove(h, h->first_entry);
+ return data;
+}
+
+void *pa_hashmap_get_first(pa_hashmap *h) {
+ pa_assert(h);
+
+ if (!h->first_entry)
+ return NULL;
+
+ return h->first_entry->value;
+}
diff --git a/src/pulsecore/hashmap.h b/src/pulsecore/hashmap.h
new file mode 100644
index 00000000..98df4502
--- /dev/null
+++ b/src/pulsecore/hashmap.h
@@ -0,0 +1,63 @@
+#ifndef foohashmaphfoo
+#define foohashmaphfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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 <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. */
+
+typedef struct pa_hashmap pa_hashmap;
+
+typedef void (*pa_free2_cb_t)(void *p, void *userdata);
+
+/* 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);
+
+/* Free the hash table. Calls the specified function for every value in the table. The function may be NULL */
+void pa_hashmap_free(pa_hashmap*, pa_free2_cb_t free_cb, void *userdata);
+
+/* Returns non-zero when the entry already exists */
+int pa_hashmap_put(pa_hashmap *h, const void *key, void *value);
+void* pa_hashmap_get(pa_hashmap *h, const void *key);
+
+/* Returns the data of the entry while removing */
+void* pa_hashmap_remove(pa_hashmap *h, const void *key);
+
+unsigned pa_hashmap_size(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. 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(pa_hashmap *h, void **state, const void**key);
+
+void *pa_hashmap_steal_first(pa_hashmap *h);
+
+void *pa_hashmap_get_first(pa_hashmap *h);
+
+#endif
diff --git a/src/pulsecore/hook-list.c b/src/pulsecore/hook-list.c
new file mode 100644
index 00000000..3a6874c4
--- /dev/null
+++ b/src/pulsecore/hook-list.c
@@ -0,0 +1,120 @@
+/* $Id$ */
+
+/***
+ 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 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulsecore/macro.h>
+
+#include "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->last = NULL;
+ hook->n_dead = hook->firing = 0;
+ hook->data = data;
+}
+
+static void slot_free(pa_hook *hook, pa_hook_slot *slot) {
+ pa_assert(hook);
+ pa_assert(slot);
+
+ if (hook->last == slot)
+ hook->last = slot->prev;
+
+ PA_LLIST_REMOVE(pa_hook_slot, hook->slots, slot);
+
+ pa_xfree(slot);
+}
+
+void pa_hook_free(pa_hook *hook) {
+ pa_assert(hook);
+ pa_assert(!hook->firing);
+
+ while (hook->slots)
+ slot_free(hook, hook->slots);
+
+ pa_hook_init(hook, NULL);
+}
+
+pa_hook_slot* pa_hook_connect(pa_hook *hook, pa_hook_cb_t cb, void *data) {
+ pa_hook_slot *slot;
+
+ pa_assert(cb);
+
+ slot = pa_xnew(pa_hook_slot, 1);
+ slot->hook = hook;
+ slot->dead = 0;
+ slot->callback = cb;
+ slot->data = data;
+
+ PA_LLIST_INSERT_AFTER(pa_hook_slot, hook->slots, hook->last, slot);
+ hook->last = slot;
+
+ return slot;
+}
+
+void pa_hook_slot_free(pa_hook_slot *slot) {
+ pa_assert(slot);
+ pa_assert(!slot->dead);
+
+ if (slot->hook->firing > 0) {
+ slot->dead = 1;
+ 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->firing ++;
+
+ for (slot = hook->slots; slot; slot = slot->next) {
+ if (slot->dead)
+ continue;
+
+ if ((result = slot->callback(hook->data, data, slot->data)) != PA_HOOK_OK)
+ break;
+ }
+
+ hook->firing --;
+
+ for (slot = hook->slots; hook->n_dead > 0 && slot; slot = next) {
+ next = slot->next;
+
+ if (slot->dead) {
+ slot_free(hook, slot);
+ hook->n_dead--;
+ }
+ }
+
+ return result;
+}
+
diff --git a/src/pulsecore/hook-list.h b/src/pulsecore/hook-list.h
new file mode 100644
index 00000000..b3bd600a
--- /dev/null
+++ b/src/pulsecore/hook-list.h
@@ -0,0 +1,69 @@
+#ifndef foohooklistfoo
+#define foohooklistfoo
+
+/* $Id$ */
+
+/***
+ 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 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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 <pulsecore/llist.h>
+#include <pulse/xmalloc.h>
+#include <pulsecore/gccmacro.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 pa_hook_result_t (*pa_hook_cb_t)(
+ void *hook_data,
+ void *call_data,
+ void *slot_data);
+
+struct pa_hook_slot {
+ int dead;
+ pa_hook *hook;
+ pa_hook_cb_t callback;
+ void *data;
+ PA_LLIST_FIELDS(pa_hook_slot);
+};
+
+struct pa_hook {
+ PA_LLIST_HEAD(pa_hook_slot, slots);
+ pa_hook_slot *last;
+ int firing, n_dead;
+
+ void *data;
+};
+
+void pa_hook_init(pa_hook *hook, void *data);
+void pa_hook_free(pa_hook *hook);
+
+pa_hook_slot* pa_hook_connect(pa_hook *hook, pa_hook_cb_t, void *data);
+void pa_hook_slot_free(pa_hook_slot *slot);
+
+pa_hook_result_t pa_hook_fire(pa_hook *hook, void *data);
+
+#endif
diff --git a/src/pulsecore/idxset.c b/src/pulsecore/idxset.c
new file mode 100644
index 00000000..8a88471f
--- /dev/null
+++ b/src/pulsecore/idxset.c
@@ -0,0 +1,423 @@
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/flist.h>
+
+#include "idxset.h"
+
+struct idxset_entry {
+ void *data;
+ uint32_t index;
+ unsigned hash_value;
+
+ struct idxset_entry *hash_prev, *hash_next;
+ struct idxset_entry* iterate_prev, *iterate_next;
+};
+
+struct pa_idxset {
+ pa_hash_func_t hash_func;
+ pa_compare_func_t compare_func;
+
+ unsigned hash_table_size, n_entries;
+ struct idxset_entry **hash_table, **array, *iterate_list_head, *iterate_list_tail;
+ uint32_t index, start_index, array_size;
+};
+
+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 + *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;
+}
+
+pa_idxset* pa_idxset_new(pa_hash_func_t hash_func, pa_compare_func_t compare_func) {
+ pa_idxset *s;
+
+ s = pa_xnew(pa_idxset, 1);
+ 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->hash_table_size = 127;
+ s->hash_table = pa_xnew0(struct idxset_entry*, s->hash_table_size);
+ s->array = NULL;
+ s->array_size = 0;
+ s->index = 0;
+ s->start_index = 0;
+ s->n_entries = 0;
+
+ s->iterate_list_head = s->iterate_list_tail = NULL;
+
+ return s;
+}
+
+void pa_idxset_free(pa_idxset *s, void (*free_func) (void *p, void *userdata), void *userdata) {
+ pa_assert(s);
+
+ while (s->iterate_list_head) {
+ struct idxset_entry *e = s->iterate_list_head;
+ s->iterate_list_head = s->iterate_list_head->iterate_next;
+
+ if (free_func)
+ free_func(e->data, userdata);
+
+ if (pa_flist_push(PA_STATIC_FLIST_GET(entries), e) < 0)
+ pa_xfree(e);
+ }
+
+ pa_xfree(s->hash_table);
+ pa_xfree(s->array);
+ pa_xfree(s);
+}
+
+static struct idxset_entry* hash_scan(pa_idxset *s, struct idxset_entry* e, const void *p) {
+ pa_assert(p);
+
+ pa_assert(s->compare_func);
+ for (; e; e = e->hash_next)
+ if (s->compare_func(e->data, p) == 0)
+ return e;
+
+ return NULL;
+}
+
+static void extend_array(pa_idxset *s, uint32_t idx) {
+ uint32_t i, j, l;
+ struct idxset_entry** n;
+
+ pa_assert(s);
+ pa_assert(idx >= s->start_index);
+
+ if (idx < s->start_index + s->array_size)
+ return;
+
+ for (i = 0; i < s->array_size; i++)
+ if (s->array[i])
+ break;
+
+ l = idx - s->start_index - i + 100;
+ n = pa_xnew0(struct idxset_entry*, l);
+
+ for (j = 0; j < s->array_size-i; j++)
+ n[j] = s->array[i+j];
+
+ pa_xfree(s->array);
+
+ s->array = n;
+ s->array_size = l;
+ s->start_index += i;
+}
+
+static struct idxset_entry** array_index(pa_idxset*s, uint32_t idx) {
+ pa_assert(s);
+
+ if (idx >= s->start_index + s->array_size)
+ return NULL;
+
+ if (idx < s->start_index)
+ return NULL;
+
+ return s->array + idx - s->start_index;
+}
+
+int pa_idxset_put(pa_idxset*s, void *p, uint32_t *idx) {
+ unsigned h;
+ struct idxset_entry *e, **a;
+
+ pa_assert(s);
+ pa_assert(p);
+
+ pa_assert(s->hash_func);
+ h = s->hash_func(p) % s->hash_table_size;
+
+ pa_assert(s->hash_table);
+ if ((e = hash_scan(s, s->hash_table[h], p))) {
+ if (idx)
+ *idx = e->index;
+
+ return -1;
+ }
+
+ if (!(e = pa_flist_pop(PA_STATIC_FLIST_GET(entries))))
+ e = pa_xnew(struct idxset_entry, 1);
+ e->data = p;
+ e->index = s->index++;
+ e->hash_value = h;
+
+ /* Insert into hash table */
+ e->hash_next = s->hash_table[h];
+ e->hash_prev = NULL;
+ if (s->hash_table[h])
+ s->hash_table[h]->hash_prev = e;
+ s->hash_table[h] = e;
+
+ /* Insert into array */
+ extend_array(s, e->index);
+ a = array_index(s, e->index);
+ pa_assert(a && !*a);
+ *a = e;
+
+ /* Insert into linked list */
+ e->iterate_next = NULL;
+ e->iterate_prev = s->iterate_list_tail;
+ 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->index;
+
+ return 0;
+}
+
+void* pa_idxset_get_by_index(pa_idxset*s, uint32_t idx) {
+ struct idxset_entry **a;
+ pa_assert(s);
+
+ if (!(a = array_index(s, idx)))
+ return NULL;
+
+ if (!*a)
+ return NULL;
+
+ return (*a)->data;
+}
+
+void* pa_idxset_get_by_data(pa_idxset*s, const void *p, uint32_t *idx) {
+ unsigned h;
+ struct idxset_entry *e;
+
+ pa_assert(s);
+ pa_assert(p);
+
+ pa_assert(s->hash_func);
+ h = s->hash_func(p) % s->hash_table_size;
+
+ pa_assert(s->hash_table);
+ if (!(e = hash_scan(s, s->hash_table[h], p)))
+ return NULL;
+
+ if (idx)
+ *idx = e->index;
+
+ return e->data;
+}
+
+static void remove_entry(pa_idxset *s, struct idxset_entry *e) {
+ struct idxset_entry **a;
+
+ pa_assert(s);
+ pa_assert(e);
+
+ /* Remove from array */
+ a = array_index(s, e->index);
+ pa_assert(a && *a && *a == e);
+ *a = NULL;
+
+ /* Remove from linked list */
+ if (e->iterate_next)
+ e->iterate_next->iterate_prev = e->iterate_prev;
+ else
+ s->iterate_list_tail = e->iterate_prev;
+
+ if (e->iterate_prev)
+ e->iterate_prev->iterate_next = e->iterate_next;
+ else
+ s->iterate_list_head = e->iterate_next;
+
+ /* Remove from hash table */
+ if (e->hash_next)
+ e->hash_next->hash_prev = e->hash_prev;
+
+ if (e->hash_prev)
+ e->hash_prev->hash_next = e->hash_next;
+ else
+ s->hash_table[e->hash_value] = e->hash_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_remove_by_index(pa_idxset*s, uint32_t idx) {
+ struct idxset_entry **a;
+ void *data;
+
+ pa_assert(s);
+
+ if (!(a = array_index(s, idx)))
+ return NULL;
+
+ if (!*a)
+ return NULL;
+
+ data = (*a)->data;
+ remove_entry(s, *a);
+
+ return data;
+}
+
+void* pa_idxset_remove_by_data(pa_idxset*s, const void *data, uint32_t *idx) {
+ struct idxset_entry *e;
+ unsigned h;
+ void *r;
+
+ pa_assert(s);
+
+ pa_assert(s->hash_func);
+ h = s->hash_func(data) % s->hash_table_size;
+
+ pa_assert(s->hash_table);
+ if (!(e = hash_scan(s, s->hash_table[h], data)))
+ return NULL;
+
+ r = e->data;
+ if (idx)
+ *idx = e->index;
+
+ remove_entry(s, e);
+
+ return r;
+}
+
+void* pa_idxset_rrobin(pa_idxset *s, uint32_t *idx) {
+ struct idxset_entry **a, *e = NULL;
+
+ pa_assert(s);
+ pa_assert(idx);
+
+ if ((a = array_index(s, *idx)) && *a)
+ e = (*a)->iterate_next;
+
+ if (!e)
+ e = s->iterate_list_head;
+
+ if (!e)
+ return NULL;
+
+ *idx = e->index;
+ return e->data;
+}
+
+void* pa_idxset_first(pa_idxset *s, uint32_t *idx) {
+ pa_assert(s);
+
+ if (!s->iterate_list_head)
+ return NULL;
+
+ if (idx)
+ *idx = s->iterate_list_head->index;
+ return s->iterate_list_head->data;
+}
+
+void *pa_idxset_next(pa_idxset *s, uint32_t *idx) {
+ struct idxset_entry **a, *e = NULL;
+
+ pa_assert(s);
+ pa_assert(idx);
+
+ if ((a = array_index(s, *idx)) && *a)
+ e = (*a)->iterate_next;
+
+ if (e) {
+ *idx = e->index;
+ return e->data;
+ } else {
+ *idx = PA_IDXSET_INVALID;
+ return NULL;
+ }
+}
+
+int pa_idxset_foreach(pa_idxset*s, int (*func)(void *p, uint32_t idx, int *del, void*userdata), void *userdata) {
+ struct idxset_entry *e;
+
+ pa_assert(s);
+ pa_assert(func);
+
+ e = s->iterate_list_head;
+ while (e) {
+ int del = 0, r;
+ struct idxset_entry *n = e->iterate_next;
+
+ r = func(e->data, e->index, &del, userdata);
+
+ if (del)
+ remove_entry(s, e);
+
+ if (r < 0)
+ return r;
+
+ e = n;
+ }
+
+ return 0;
+}
+
+unsigned pa_idxset_size(pa_idxset*s) {
+ pa_assert(s);
+
+ return s->n_entries;
+}
+
+int pa_idxset_isempty(pa_idxset *s) {
+ pa_assert(s);
+
+ return s->n_entries == 0;
+}
+
diff --git a/src/pulsecore/idxset.h b/src/pulsecore/idxset.h
new file mode 100644
index 00000000..5b55cec2
--- /dev/null
+++ b/src/pulsecore/idxset.h
@@ -0,0 +1,100 @@
+#ifndef fooidxsethfoo
+#define fooidxsethfoo
+
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <inttypes.h>
+
+/* A combination of a set and a dynamic array. Entries are indexable
+ * both through a numeric automatically generated 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 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, void (*free_func) (void *p, void *userdata), void *userdata);
+
+/* 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 idx is returned in *index */
+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);
+
+/* 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);
+
+/* 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);
+
+/* Call a function for every item in the set. If the callback function
+ returns -1, the loop is terminated. If *del is set to non-zero that
+ specific item is removed. It is not safe to call any other
+ functions on the idxset while pa_idxset_foreach is executed. */
+int pa_idxset_foreach(pa_idxset*s, int (*func)(void *p, uint32_t idx, int *del, void*userdata), void *userdata);
+
+unsigned pa_idxset_size(pa_idxset*s);
+
+int pa_idxset_isempty(pa_idxset *s);
+
+#endif
diff --git a/src/pulsecore/inet_ntop.c b/src/pulsecore/inet_ntop.c
new file mode 100644
index 00000000..4a4f7aac
--- /dev/null
+++ b/src/pulsecore/inet_ntop.c
@@ -0,0 +1,81 @@
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+
+#ifndef HAVE_INET_NTOP
+
+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+
+#include "winsock.h"
+
+#include "inet_ntop.h"
+
+const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt) {
+ struct in_addr *in = (struct in_addr*)src;
+ struct in6_addr *in6 = (struct in6_addr*)src;
+
+ assert(src && 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;
+ 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;
+ default:
+ errno = EAFNOSUPPORT;
+ return NULL;
+ }
+
+ return dst;
+}
+
+#endif /* INET_NTOP */
diff --git a/src/pulsecore/inet_ntop.h b/src/pulsecore/inet_ntop.h
new file mode 100644
index 00000000..7fb67b44
--- /dev/null
+++ b/src/pulsecore/inet_ntop.h
@@ -0,0 +1,12 @@
+#ifndef fooinet_ntophfoo
+#define fooinet_ntophfoo
+
+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+
+#include "winsock.h"
+
+const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt);
+
+#endif
diff --git a/src/pulsecore/inet_pton.c b/src/pulsecore/inet_pton.c
new file mode 100644
index 00000000..84d0c0ea
--- /dev/null
+++ b/src/pulsecore/inet_pton.c
@@ -0,0 +1,63 @@
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+
+#ifndef HAVE_INET_PTON
+
+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+
+#include "winsock.h"
+
+#include "inet_pton.h"
+
+int inet_pton(int af, const char *src, void *dst) {
+ struct in_addr *in = (struct in_addr*)dst;
+ struct in6_addr *in6 = (struct in6_addr*)dst;
+
+ assert(src && dst);
+
+ switch (af) {
+ case AF_INET:
+ in->s_addr = inet_addr(src);
+ if (in->s_addr == INADDR_NONE)
+ return 0;
+ break;
+ case AF_INET6:
+ /* FIXME */
+ default:
+ errno = EAFNOSUPPORT;
+ return -1;
+ }
+
+ return 1;
+}
+
+#endif /* INET_PTON */
diff --git a/src/pulsecore/inet_pton.h b/src/pulsecore/inet_pton.h
new file mode 100644
index 00000000..111b4a07
--- /dev/null
+++ b/src/pulsecore/inet_pton.h
@@ -0,0 +1,12 @@
+#ifndef fooinet_ptonhfoo
+#define fooinet_ptonhfoo
+
+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+
+#include "winsock.h"
+
+int inet_pton(int af, const char *src, void *dst);
+
+#endif
diff --git a/src/pulsecore/iochannel.c b/src/pulsecore/iochannel.c
new file mode 100644
index 00000000..63ab2ad7
--- /dev/null
+++ b/src/pulsecore/iochannel.c
@@ -0,0 +1,426 @@
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+
+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+#ifdef HAVE_SYS_UN_H
+#include <sys/un.h>
+#endif
+
+#include "winsock.h"
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/core-error.h>
+#include <pulsecore/core-util.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;
+
+ pa_bool_t readable;
+ pa_bool_t writable;
+ pa_bool_t hungup;
+
+ pa_bool_t no_close;
+
+ pa_io_event* input_event, *output_event;
+};
+
+static void enable_mainloop_sources(pa_iochannel *io) {
+ pa_assert(io);
+
+ if (io->input_event == io->output_event && io->input_event) {
+ pa_io_event_flags_t f = PA_IO_EVENT_NULL;
+ pa_assert(io->input_event);
+
+ if (!io->readable)
+ f |= PA_IO_EVENT_INPUT;
+ if (!io->writable)
+ f |= PA_IO_EVENT_OUTPUT;
+
+ io->mainloop->io_enable(io->input_event, f);
+ } else {
+ if (io->input_event)
+ io->mainloop->io_enable(io->input_event, io->readable ? PA_IO_EVENT_NULL : PA_IO_EVENT_INPUT);
+ if (io->output_event)
+ io->mainloop->io_enable(io->output_event, io->writable ? PA_IO_EVENT_NULL : PA_IO_EVENT_OUTPUT);
+ }
+}
+
+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;
+ pa_bool_t 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_mainloop_sources(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_xnew(pa_iochannel, 1);
+ io->ifd = ifd;
+ io->ofd = ofd;
+ io->ifd_type = io->ofd_type = 0;
+ io->mainloop = m;
+
+ io->userdata = NULL;
+ io->callback = NULL;
+ io->readable = FALSE;
+ io->writable = FALSE;
+ io->hungup = FALSE;
+ io->no_close = FALSE;
+
+ io->input_event = io->output_event = NULL;
+
+ if (ifd == ofd) {
+ pa_assert(ifd >= 0);
+ pa_make_fd_nonblock(io->ifd);
+ io->input_event = io->output_event = m->io_new(m, ifd, PA_IO_EVENT_INPUT|PA_IO_EVENT_OUTPUT, callback, io);
+ } else {
+
+ if (ifd >= 0) {
+ pa_make_fd_nonblock(io->ifd);
+ io->input_event = m->io_new(m, ifd, PA_IO_EVENT_INPUT, callback, io);
+ }
+
+ if (ofd >= 0) {
+ pa_make_fd_nonblock(io->ofd);
+ io->output_event = m->io_new(m, ofd, PA_IO_EVENT_OUTPUT, callback, io);
+ }
+ }
+
+ return io;
+}
+
+void pa_iochannel_free(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);
+
+ 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);
+}
+
+pa_bool_t pa_iochannel_is_readable(pa_iochannel*io) {
+ pa_assert(io);
+
+ return io->readable || io->hungup;
+}
+
+pa_bool_t pa_iochannel_is_writable(pa_iochannel*io) {
+ pa_assert(io);
+
+ return io->writable && !io->hungup;
+}
+
+pa_bool_t 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);
+
+ if ((r = pa_write(io->ofd, data, l, &io->ofd_type)) >= 0) {
+ io->writable = FALSE;
+ enable_mainloop_sources(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) {
+ io->readable = FALSE;
+ enable_mainloop_sources(io);
+ }
+
+ return r;
+}
+
+#ifdef HAVE_CREDS
+
+pa_bool_t pa_iochannel_creds_supported(pa_iochannel *io) {
+ struct sockaddr_un 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, (struct sockaddr*) &sa, &l) < 0)
+ return 0;
+
+ return sa.sun_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);
+
+ memset(&iov, 0, sizeof(iov));
+ iov.iov_base = (void*) data;
+ iov.iov_len = l;
+
+ memset(&cmsg, 0, sizeof(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();
+ }
+
+ memset(&mh, 0, sizeof(mh));
+ mh.msg_name = NULL;
+ mh.msg_namelen = 0;
+ mh.msg_iov = &iov;
+ mh.msg_iovlen = 1;
+ mh.msg_control = &cmsg;
+ mh.msg_controllen = sizeof(cmsg);
+ mh.msg_flags = 0;
+
+ if ((r = sendmsg(io->ofd, &mh, MSG_NOSIGNAL)) >= 0) {
+ io->writable = FALSE;
+ enable_mainloop_sources(io);
+ }
+
+ return r;
+}
+
+ssize_t pa_iochannel_read_with_creds(pa_iochannel*io, void*data, size_t l, pa_creds *creds, pa_bool_t *creds_valid) {
+ ssize_t r;
+ struct msghdr mh;
+ struct iovec iov;
+ union {
+ struct cmsghdr hdr;
+ uint8_t data[CMSG_SPACE(sizeof(struct ucred))];
+ } cmsg;
+
+ pa_assert(io);
+ pa_assert(data);
+ pa_assert(l);
+ pa_assert(io->ifd >= 0);
+ pa_assert(creds);
+ pa_assert(creds_valid);
+
+ memset(&iov, 0, sizeof(iov));
+ iov.iov_base = data;
+ iov.iov_len = l;
+
+ memset(&cmsg, 0, sizeof(cmsg));
+
+ memset(&mh, 0, sizeof(mh));
+ mh.msg_name = NULL;
+ mh.msg_namelen = 0;
+ mh.msg_iov = &iov;
+ mh.msg_iovlen = 1;
+ mh.msg_control = &cmsg;
+ mh.msg_controllen = sizeof(cmsg);
+ mh.msg_flags = 0;
+
+ if ((r = recvmsg(io->ifd, &mh, 0)) >= 0) {
+ struct cmsghdr *cmh;
+
+ *creds_valid = 0;
+
+ for (cmh = CMSG_FIRSTHDR(&mh); cmh; cmh = CMSG_NXTHDR(&mh, cmh)) {
+
+ if (cmh->cmsg_level == SOL_SOCKET && 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));
+
+ creds->gid = u.gid;
+ creds->uid = u.uid;
+ *creds_valid = TRUE;
+ break;
+ }
+ }
+
+ io->readable = FALSE;
+ enable_mainloop_sources(io);
+ }
+
+ 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, pa_bool_t 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;
+}
diff --git a/src/pulsecore/iochannel.h b/src/pulsecore/iochannel.h
new file mode 100644
index 00000000..c9794d99
--- /dev/null
+++ b/src/pulsecore/iochannel.h
@@ -0,0 +1,93 @@
+#ifndef fooiochannelhfoo
+#define fooiochannelhfoo
+
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#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. Everytime 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 */
+
+/* When pa_iochannel_is_readable() returns non-zero, the user has to
+ * call this function in a loop until it is no longer set or EOF
+ * reached. Otherwise strange things may happen when an EOF is
+ * reached. */
+
+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);
+
+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
+pa_bool_t pa_iochannel_creds_supported(pa_iochannel *io);
+int pa_iochannel_creds_enable(pa_iochannel *io);
+
+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_creds(pa_iochannel*io, void*data, size_t l, pa_creds *ucred, pa_bool_t *creds_valid);
+#endif
+
+pa_bool_t pa_iochannel_is_readable(pa_iochannel*io);
+pa_bool_t pa_iochannel_is_writable(pa_iochannel*io);
+pa_bool_t pa_iochannel_is_hungup(pa_iochannel*io);
+
+/* Don't close the file descirptors when the io channel is freed. By
+ * default the file descriptors are closed. */
+void pa_iochannel_set_noclose(pa_iochannel*io, pa_bool_t 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 recieve 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);
+
+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 00000000..5fd2189b
--- /dev/null
+++ b/src/pulsecore/ioline.c
@@ -0,0 +1,415 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/winsock.h>
+#include <pulsecore/core-error.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;
+ int dead;
+
+ char *wbuf;
+ size_t wbuf_length, wbuf_index, wbuf_valid_length;
+
+ char *rbuf;
+ size_t rbuf_length, rbuf_index, rbuf_valid_length;
+
+ void (*callback)(pa_ioline*io, const char *s, void *userdata);
+ void *userdata;
+
+ int defer_close;
+};
+
+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->dead = 0;
+
+ 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->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->defer_close = 0;
+
+ 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 = 1;
+
+ 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_xmalloc(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, void (*callback)(pa_ioline*io, const char *s, void *userdata), void *userdata) {
+ pa_assert(l);
+ pa_assert(PA_REFCNT_VALUE(l) >= 1);
+
+ l->callback = callback;
+ l->userdata = userdata;
+}
+
+static void failure(pa_ioline *l, int 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, 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->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_xmalloc(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 != ECONNRESET) {
+ pa_log("read(): %s", pa_cstrerror(errno));
+ failure(l, 0);
+ } else
+ failure(l, 1);
+
+ return -1;
+ }
+
+ l->rbuf_valid_length += r;
+
+ /* Look if a line has been terminated in the newly read data */
+ scan_for_lines(l, l->rbuf_valid_length - 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->dead && pa_iochannel_is_writable(l->io) && l->wbuf_valid_length) {
+
+ if ((r = pa_iochannel_write(l->io, l->wbuf+l->wbuf_index, l->wbuf_valid_length)) <= 0) {
+
+ if (r < 0 && errno != EPIPE)
+ pa_log("write(): %s", pa_cstrerror(errno));
+
+ failure(l, 0);
+
+ return -1;
+ }
+
+ l->wbuf_index += r;
+ l->wbuf_valid_length -= r;
+
+ /* A shortcut for the next time */
+ if (l->wbuf_valid_length == 0)
+ l->wbuf_index = 0;
+ }
+
+ 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, 1);
+
+ 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 = 1;
+
+ 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);
+}
diff --git a/src/pulsecore/ioline.h b/src/pulsecore/ioline.h
new file mode 100644
index 00000000..8475b798
--- /dev/null
+++ b/src/pulsecore/ioline.h
@@ -0,0 +1,53 @@
+#ifndef fooiolinehfoo
+#define fooiolinehfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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 <pulsecore/iochannel.h>
+#include <pulsecore/core-util.h>
+
+/* An ioline wraps an iochannel for line based communication. A
+ * callback function is called whenever a new line has been recieved
+ * from the client */
+
+typedef struct pa_ioline pa_ioline;
+
+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 recieved line */
+void pa_ioline_set_callback(pa_ioline*io, void (*callback)(pa_ioline*io, const char *s, void *userdata), 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);
+
+#endif
diff --git a/src/pulsecore/ipacl.c b/src/pulsecore/ipacl.c
new file mode 100644
index 00000000..9b22e8f5
--- /dev/null
+++ b/src/pulsecore/ipacl.c
@@ -0,0 +1,239 @@
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sys/types.h>
+#include <sys/types.h>
+#include <string.h>
+
+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.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_ARPA_INET_H
+#include <arpa/inet.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/winsock.h>
+
+#ifndef HAVE_INET_PTON
+#include "inet_pton.h"
+#endif
+
+#include "ipacl.h"
+
+struct acl_entry {
+ PA_LLIST_FIELDS(struct acl_entry);
+ int family;
+ struct in_addr address_ipv4;
+ struct in6_addr address_ipv6;
+ 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);
+
+ } 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 = 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);
+ }
+
+ } 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;
+
+ if (sa.ss_family != AF_INET && sa.ss_family != AF_INET6)
+ return -1;
+
+ if (sa.ss_family == AF_INET && salen != sizeof(struct sockaddr_in))
+ return -1;
+
+ if (sa.ss_family == AF_INET6 && salen != sizeof(struct sockaddr_in6))
+ return -1;
+
+ 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;
+ } 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;
+ }
+ }
+ }
+
+ return 0;
+}
diff --git a/src/pulsecore/ipacl.h b/src/pulsecore/ipacl.h
new file mode 100644
index 00000000..175f54e0
--- /dev/null
+++ b/src/pulsecore/ipacl.h
@@ -0,0 +1,34 @@
+#ifndef fooparseaddrhfoo
+#define fooparseaddrhfoo
+
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+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 00000000..e62f15b4
--- /dev/null
+++ b/src/pulsecore/llist.h
@@ -0,0 +1,109 @@
+#ifndef foollistfoo
+#define foollistfoo
+
+/* $Id$ */
+
+/***
+ 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 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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 <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)
+
+#endif
diff --git a/src/pulsecore/log.c b/src/pulsecore/log.c
new file mode 100644
index 00000000..c824e84d
--- /dev/null
+++ b/src/pulsecore/log.c
@@ -0,0 +1,234 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+
+#ifdef HAVE_SYSLOG_H
+#include <syslog.h>
+#endif
+
+#include <pulse/utf8.h>
+#include <pulse/xmalloc.h>
+#include <pulse/util.h>
+
+#include <pulsecore/macro.h>
+#include <pulsecore/core-util.h>
+
+#include "log.h"
+
+#define ENV_LOGLEVEL "PULSE_LOG"
+#define ENV_LOGMETA "PULSE_LOG_META"
+
+static char *log_ident = NULL, *log_ident_local = NULL;
+static pa_log_target_t log_target = PA_LOG_STDERR;
+static void (*user_log_func)(pa_log_level_t l, const char *s) = NULL;
+static pa_log_level_t maximal_level = PA_LOG_NOTICE;
+
+#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
+
+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(log_ident);
+ pa_xfree(log_ident_local);
+
+ log_ident = pa_xstrdup(p);
+ if (!(log_ident_local = pa_utf8_to_locale(log_ident)))
+ log_ident_local = pa_xstrdup(log_ident);
+}
+
+/* To make valgrind shut up. */
+static void ident_destructor(void) PA_GCC_DESTRUCTOR;
+static void ident_destructor(void) {
+ pa_xfree(log_ident);
+ pa_xfree(log_ident_local);
+}
+
+void pa_log_set_maximal_level(pa_log_level_t l) {
+ pa_assert(l < PA_LOG_LEVEL_MAX);
+
+ maximal_level = l;
+}
+
+void pa_log_set_target(pa_log_target_t t, void (*func)(pa_log_level_t l, const char*s)) {
+ pa_assert(t == PA_LOG_USER || !func);
+
+ log_target = t;
+ user_log_func = func;
+}
+
+void pa_log_levelv_meta(
+ pa_log_level_t level,
+ const char*file,
+ int line,
+ const char *func,
+ const char *format,
+ va_list ap) {
+
+ const char *e;
+ char *text, *t, *n, *location;
+
+ pa_assert(level < PA_LOG_LEVEL_MAX);
+ pa_assert(format);
+
+ if ((e = getenv(ENV_LOGLEVEL)))
+ maximal_level = atoi(e);
+
+ if (level > maximal_level)
+ return;
+
+ text = pa_vsprintf_malloc(format, ap);
+
+ if (getenv(ENV_LOGMETA) && file && line > 0 && func)
+ location = pa_sprintf_malloc("[%s:%i %s()] ", file, line, func);
+ else if (file)
+ location = pa_sprintf_malloc("%s: ", pa_path_get_filename(file));
+ else
+ location = pa_xstrdup("");
+
+ if (!pa_utf8_valid(text))
+ pa_log_level(level, __FILE__": invalid UTF-8 string following below:");
+
+ for (t = text; t; t = n) {
+ if ((n = strchr(t, '\n'))) {
+ *n = 0;
+ n++;
+ }
+
+ if (!*t)
+ continue;
+
+ switch (log_target) {
+ case PA_LOG_STDERR: {
+ const char *prefix = "", *suffix = "";
+ char *local_t;
+
+#ifndef OS_IS_WIN32
+ /* Yes indeed. Useless, but fun! */
+ if (isatty(STDERR_FILENO)) {
+ if (level <= PA_LOG_ERROR) {
+ prefix = "\x1B[1;31m";
+ suffix = "\x1B[0m";
+ } else if (level <= PA_LOG_WARN) {
+ prefix = "\x1B[1m";
+ suffix = "\x1B[0m";
+ }
+ }
+#endif
+
+ local_t = pa_utf8_to_locale(t);
+ if (!local_t)
+ fprintf(stderr, "%c: %s%s%s%s\n", level_to_char[level], location, prefix, t, suffix);
+ else {
+ fprintf(stderr, "%c: %s%s%s%s\n", level_to_char[level], location, prefix, local_t, suffix);
+ pa_xfree(local_t);
+ }
+
+ break;
+ }
+
+#ifdef HAVE_SYSLOG_H
+ case PA_LOG_SYSLOG: {
+ char *local_t;
+
+ openlog(log_ident_local ? log_ident_local : "???", LOG_PID, LOG_USER);
+
+ local_t = pa_utf8_to_locale(t);
+ if (!local_t)
+ syslog(level_to_syslog[level], "%s%s", location, t);
+ else {
+ syslog(level_to_syslog[level], "%s%s", location, local_t);
+ pa_xfree(local_t);
+ }
+
+ closelog();
+ break;
+ }
+#endif
+
+ case PA_LOG_USER: {
+ char *x;
+
+ x = pa_sprintf_malloc("%s%s", location, t);
+ user_log_func(level, x);
+ pa_xfree(x);
+
+ break;
+ }
+
+ case PA_LOG_NULL:
+ default:
+ break;
+ }
+ }
+
+ pa_xfree(text);
+ pa_xfree(location);
+}
+
+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);
+}
diff --git a/src/pulsecore/log.h b/src/pulsecore/log.h
new file mode 100644
index 00000000..b0711dca
--- /dev/null
+++ b/src/pulsecore/log.h
@@ -0,0 +1,107 @@
+#ifndef foologhfoo
+#define foologhfoo
+
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <pulsecore/gccmacro.h>
+
+/* A simple logging subsystem */
+
+/* Where to log to */
+typedef enum pa_log_target {
+ PA_LOG_STDERR, /* default */
+ PA_LOG_SYSLOG,
+ PA_LOG_USER, /* to user specified function */
+ PA_LOG_NULL /* to /dev/null */
+} pa_log_target_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 message */
+ PA_LOG_LEVEL_MAX
+} pa_log_level_t;
+
+/* Set an identification for the current daemon. Used when logging to syslog. */
+void pa_log_set_ident(const char *p);
+
+/* Set another log target. If t is PA_LOG_USER you may specify a function that is called every log string */
+void pa_log_set_target(pa_log_target_t t, void (*func)(pa_log_level_t t, const char*s));
+
+/* Minimal log level */
+void pa_log_set_maximal_level(pa_log_level_t l);
+
+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);
+
+#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__)
+
+#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
+
+#endif
diff --git a/src/pulsecore/ltdl-helper.c b/src/pulsecore/ltdl-helper.c
new file mode 100644
index 00000000..711396d8
--- /dev/null
+++ b/src/pulsecore/ltdl-helper.c
@@ -0,0 +1,64 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <ctype.h>
+
+#include <pulse/xmalloc.h>
+#include <pulse/util.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(module);
+ pa_assert(symbol);
+
+ if ((f = ((pa_void_func_t) (long) lt_dlsym(handle, symbol))))
+ return f;
+
+ /* 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(*c))
+ *c = '_';
+
+ f = (pa_void_func_t) (long) 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 00000000..5c7388a1
--- /dev/null
+++ b/src/pulsecore/ltdl-helper.h
@@ -0,0 +1,34 @@
+#ifndef foopulsecoreltdlhelperhfoo
+#define foopulsecoreltdlhelperhfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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 <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 00000000..41af19c9
--- /dev/null
+++ b/src/pulsecore/macro.h
@@ -0,0 +1,159 @@
+#ifndef foopulsemacrohfoo
+#define foopulsemacrohfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <assert.h>
+#include <limits.h>
+#include <unistd.h>
+
+#include <pulsecore/log.h>
+#include <pulsecore/gccmacro.h>
+
+#ifndef PACKAGE
+#error "Please include config.h before including this file!"
+#endif
+
+#if defined(PAGE_SIZE)
+#define PA_PAGE_SIZE ((size_t) PAGE_SIZE)
+#elif defined(PAGESIZE)
+#define PA_PAGE_SIZE ((size_t) PAGESIZE)
+#elif defined(HAVE_SYSCONF)
+#define PA_PAGE_SIZE ((size_t) (sysconf(_SC_PAGE_SIZE)))
+#else
+/* Let's hope it's like x86. */
+#define PA_PAGE_SIZE ((size_t) 4096)
+#endif
+
+static inline size_t pa_align(size_t l) {
+ return (((l + sizeof(void*) - 1) / sizeof(void*)) * sizeof(void*));
+}
+#define PA_ALIGN(x) (pa_align(x))
+
+static inline void* pa_page_align_ptr(const void *p) {
+ return (void*) (((size_t) p) & ~(PA_PAGE_SIZE-1));
+}
+#define PA_PAGE_ALIGN_PTR(x) (pa_page_align_ptr(x))
+
+static inline size_t pa_page_align(size_t l) {
+ return l & ~(PA_PAGE_SIZE-1);
+}
+#define PA_PAGE_ALIGN(x) (pa_page_align(x))
+
+#define PA_ELEMENTSOF(x) (sizeof(x)/sizeof((x)[0]))
+
+#ifndef MAX
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+#endif
+
+#ifndef MIN
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+#endif
+
+#ifndef CLAMP
+#define CLAMP(x, low, high) (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x)))
+#endif
+
+#define PA_CLAMP_UNLIKELY(x, low, high) (PA_UNLIKELY((x) > (high)) ? (high) : (PA_UNLIKELY((x) < (low)) ? (low) : (x)))
+/* We don't define a PA_CLAMP_LIKELY here, because it doesn't really
+ * make sense: we cannot know if it is more likely that the values is
+ * lower or greater than the boundaries.*/
+
+/* This type is not intended to be used in exported APIs! Use classic "int" there! */
+#ifdef HAVE_STD_BOOL
+typedef _Bool pa_bool_t;
+#else
+typedef int pa_bool_t;
+#endif
+
+#ifndef FALSE
+#define FALSE ((pa_bool_t) 0)
+#endif
+
+#ifndef TRUE
+#define TRUE (!FALSE)
+#endif
+
+#ifdef __GNUC__
+#define PA_PRETTY_FUNCTION __PRETTY_FUNCTION__
+#else
+#define PA_PRETTY_FUNCTION ""
+#endif
+
+#define pa_return_if_fail(expr) \
+ do { \
+ if (!(expr)) { \
+ pa_log_debug("%s: Assertion <%s> failed.\n", PA_PRETTY_FUNCTION, #expr ); \
+ return; \
+ } \
+ } while(0)
+
+#define pa_return_val_if_fail(expr, val) \
+ do { \
+ if (!(expr)) { \
+ pa_log_debug("%s: Assertion <%s> failed.\n", PA_PRETTY_FUNCTION, #expr ); \
+ return (val); \
+ } \
+ } while(0)
+
+#define pa_return_null_if_fail(expr) pa_return_val_if_fail(expr, NULL)
+
+#define pa_assert assert
+
+#define pa_assert_not_reached() pa_assert(!"Should not be reached.")
+
+/* An assert which guarantees side effects of x */
+#ifdef NDEBUG
+#define pa_assert_se(x) x
+#else
+#define pa_assert_se(x) pa_assert(x)
+#endif
+
+#define PA_PTR_TO_UINT(p) ((unsigned int) (unsigned long) (p))
+#define PA_UINT_TO_PTR(u) ((void*) (unsigned long) (u))
+
+#define PA_PTR_TO_UINT32(p) ((uint32_t) PA_PTR_TO_UINT(p))
+#define PA_UINT32_TO_PTR(u) PA_UINT_TO_PTR((uint32_t) u)
+
+#define PA_PTR_TO_INT(p) ((int) PA_PTR_TO_UINT(p))
+#define PA_INT_TO_PTR(u) PA_UINT_TO_PTR((int) u)
+
+#define PA_PTR_TO_INT32(p) ((int32_t) PA_PTR_TO_UINT(p))
+#define PA_INT32_TO_PTR(u) PA_UINT_TO_PTR((int32_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
+
+static inline const char *pa_strnull(const char *x) {
+ return x ? x : "(null)";
+}
+
+#endif
diff --git a/src/pulsecore/mcalign.c b/src/pulsecore/mcalign.c
new file mode 100644
index 00000000..8ca7c962
--- /dev/null
+++ b/src/pulsecore/mcalign.c
@@ -0,0 +1,213 @@
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <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 raimainder 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;
+}
diff --git a/src/pulsecore/mcalign.h b/src/pulsecore/mcalign.h
new file mode 100644
index 00000000..6ff8f94e
--- /dev/null
+++ b/src/pulsecore/mcalign.h
@@ -0,0 +1,82 @@
+#ifndef foomcalignhfoo
+#define foomcalignhfoo
+
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#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 he 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 sucessful,
+ * 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);
+
+#endif
diff --git a/src/pulsecore/memblock.c b/src/pulsecore/memblock.c
new file mode 100644
index 00000000..99b5a13f
--- /dev/null
+++ b/src/pulsecore/memblock.c
@@ -0,0 +1,1117 @@
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <signal.h>
+#include <errno.h>
+
+#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/macro.h>
+#include <pulsecore/flist.h>
+#include <pulsecore/core-util.h>
+
+#include "memblock.h"
+
+#define PA_MEMPOOL_SLOTS_MAX 128
+#define PA_MEMPOOL_SLOT_SIZE (16*1024)
+
+#define PA_MEMEXPORT_SLOTS_MAX 128
+
+#define PA_MEMIMPORT_SLOTS_MAX 128
+#define PA_MEMIMPORT_SEGMENTS_MAX 16
+
+struct pa_memblock {
+ PA_REFCNT_DECLARE; /* the reference counter */
+ pa_mempool *pool;
+
+ pa_memblock_type_t type;
+ int read_only; /* boolean */
+
+ 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;
+ } user;
+
+ struct {
+ uint32_t id;
+ pa_memimport_segment *segment;
+ } imported;
+ } per_type;
+};
+
+struct pa_memimport_segment {
+ pa_memimport *import;
+ pa_shm memory;
+ unsigned n_blocks;
+};
+
+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;
+
+ /* 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 mempool_slot {
+ PA_LLIST_FIELDS(struct mempool_slot);
+ /* the actual data follows immediately hereafter */
+};
+
+struct pa_mempool {
+ pa_semaphore *semaphore;
+ pa_mutex *mutex;
+
+ pa_shm memory;
+ size_t block_size;
+ unsigned n_blocks;
+
+ 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, b->length);
+
+ pa_atomic_inc(&b->pool->stat.n_accumulated);
+ pa_atomic_add(&b->pool->stat.accumulated_size, b->length);
+
+ if (b->type == PA_MEMBLOCK_IMPORTED) {
+ pa_atomic_inc(&b->pool->stat.n_imported);
+ pa_atomic_add(&b->pool->stat.imported_size, 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, 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, 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 > 0);
+
+ 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 > 0);
+
+ /* If -1 is passed as length we choose the size for the caller. */
+
+ if (length == (size_t) -1)
+ length = p->block_size - PA_ALIGN(sizeof(struct mempool_slot)) - PA_ALIGN(sizeof(pa_memblock));
+
+ b = pa_xmalloc(PA_ALIGN(sizeof(pa_memblock)) + length);
+ PA_REFCNT_INIT(b);
+ b->pool = p;
+ b->type = PA_MEMBLOCK_APPENDED;
+ b->read_only = 0;
+ 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 * idx));
+
+ if (!slot) {
+ pa_log_debug("Pool full");
+ pa_atomic_inc(&p->stat.n_pool_full);
+ return NULL;
+ }
+ }
+
+ return slot;
+}
+
+/* No lock necessary */
+static void* mempool_slot_data(struct mempool_slot *slot) {
+ pa_assert(slot);
+
+ return (uint8_t*) slot + PA_ALIGN(sizeof(struct mempool_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 ((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 */
+pa_memblock *pa_memblock_new_pool(pa_mempool *p, size_t length) {
+ pa_memblock *b = NULL;
+ struct mempool_slot *slot;
+
+ pa_assert(p);
+ pa_assert(length > 0);
+
+ /* 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(struct mempool_slot)) >= 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 - PA_ALIGN(sizeof(struct mempool_slot)) >= 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_ALIGN(sizeof(struct mempool_slot))));
+ pa_atomic_inc(&p->stat.n_too_large_for_pool);
+ return NULL;
+ }
+
+ PA_REFCNT_INIT(b);
+ b->pool = p;
+ b->read_only = 0;
+ 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, int read_only) {
+ pa_memblock *b;
+
+ pa_assert(p);
+ pa_assert(d);
+ pa_assert(length != (size_t) -1);
+ pa_assert(length > 0);
+
+ if (!(b = pa_flist_pop(PA_STATIC_FLIST_GET(unused_memblocks))))
+ b = pa_xnew(pa_memblock, 1);
+ PA_REFCNT_INIT(b);
+ b->pool = p;
+ b->type = PA_MEMBLOCK_FIXED;
+ b->read_only = read_only;
+ 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, void (*free_cb)(void *p), int read_only) {
+ pa_memblock *b;
+
+ pa_assert(p);
+ pa_assert(d);
+ pa_assert(length > 0);
+ 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;
+ b->type = PA_MEMBLOCK_USER;
+ b->read_only = read_only;
+ 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;
+
+ stat_add(b);
+ return b;
+}
+
+/* No lock necessary */
+int 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 */
+int pa_memblock_ref_is_one(pa_memblock *b) {
+ int r;
+
+ pa_assert(b);
+
+ r = PA_REFCNT_VALUE(b);
+ pa_assert(r > 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, 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;
+}
+
+pa_mempool* pa_memblock_get_pool(pa_memblock *b) {
+ pa_assert(b);
+ pa_assert(PA_REFCNT_VALUE(b) > 0);
+
+ 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_assert(b);
+
+ pa_assert(pa_atomic_load(&b->n_acquired) == 0);
+
+ stat_remove(b);
+
+ switch (b->type) {
+ case PA_MEMBLOCK_USER :
+ pa_assert(b->per_type.user.free_cb);
+ b->per_type.user.free_cb(pa_atomic_ptr_load(&b->data));
+
+ /* Fall through */
+
+ case PA_MEMBLOCK_FIXED:
+ case PA_MEMBLOCK_APPENDED :
+ if (pa_flist_push(PA_STATIC_FLIST_GET(unused_memblocks), b) < 0)
+ pa_xfree(b);
+
+ break;
+
+ case PA_MEMBLOCK_IMPORTED : {
+ pa_memimport_segment *segment;
+ pa_memimport *import;
+
+ /* FIXME! This should be implemented lock-free */
+
+ segment = b->per_type.imported.segment;
+ pa_assert(segment);
+ import = segment->import;
+ pa_assert(import);
+
+ pa_mutex_lock(import->mutex);
+ pa_hashmap_remove(import->blocks, PA_UINT32_TO_PTR(b->per_type.imported.id));
+ 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;
+ int call_free;
+
+ slot = mempool_slot_by_ptr(b->pool, pa_atomic_ptr_load(&b->data));
+ pa_assert(slot);
+
+ call_free = b->type == PA_MEMBLOCK_POOL_EXTERNAL;
+
+ /* 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();
+ }
+}
+
+/* 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 - PA_ALIGN(sizeof(struct mempool_slot))) {
+ 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 = 0;
+
+ 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->type = PA_MEMBLOCK_USER;
+ b->read_only = 0;
+
+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 *seg;
+
+ 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, b->length);
+
+ seg = b->per_type.imported.segment;
+ pa_assert(seg);
+ pa_assert(seg->import);
+
+ pa_mutex_lock(seg->import->mutex);
+
+ pa_hashmap_remove(
+ seg->import->blocks,
+ PA_UINT32_TO_PTR(b->per_type.imported.id));
+
+ memblock_make_local(b);
+
+ if (-- seg->n_blocks <= 0) {
+ pa_mutex_unlock(seg->import->mutex);
+ segment_detach(seg);
+ } else
+ pa_mutex_unlock(seg->import->mutex);
+}
+
+pa_mempool* pa_mempool_new(int shared) {
+ pa_mempool *p;
+
+ p = pa_xnew(pa_mempool, 1);
+
+ p->mutex = pa_mutex_new(TRUE, TRUE);
+ p->semaphore = pa_semaphore_new(0);
+
+ p->block_size = PA_PAGE_ALIGN(PA_MEMPOOL_SLOT_SIZE);
+ if (p->block_size < PA_PAGE_SIZE)
+ p->block_size = PA_PAGE_SIZE;
+
+ p->n_blocks = PA_MEMPOOL_SLOTS_MAX;
+
+ pa_assert(p->block_size > PA_ALIGN(sizeof(struct mempool_slot)));
+
+ if (pa_shm_create_rw(&p->memory, p->n_blocks * p->block_size, shared, 0700) < 0) {
+ pa_xfree(p);
+ return NULL;
+ }
+
+ memset(&p->stat, 0, sizeof(p->stat));
+ pa_atomic_store(&p->n_init, 0);
+
+ PA_LLIST_HEAD_INIT(pa_memimport, p->imports);
+ PA_LLIST_HEAD_INIT(pa_memexport, p->exports);
+
+ p->free_slots = pa_flist_new(p->n_blocks*2);
+
+ return p;
+}
+
+void pa_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) {
+/* raise(SIGTRAP); */
+ pa_log_warn("Memory pool destroyed but not all memory blocks freed! %u remain.", pa_atomic_load(&p->stat.n_allocated));
+ }
+
+ 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(struct mempool_slot)) - 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*2);
+
+ 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,
+ (uint8_t*) slot - (uint8_t*) p->memory.ptr + PA_ALIGN(sizeof(struct mempool_slot)),
+ p->block_size - PA_ALIGN(sizeof(struct mempool_slot)));
+
+ while (pa_flist_push(p->free_slots, slot))
+ ;
+ }
+
+ pa_flist_free(list, NULL);
+}
+
+/* No lock necessary */
+int pa_mempool_get_shm_id(pa_mempool *p, uint32_t *id) {
+ pa_assert(p);
+
+ if (!p->memory.shared)
+ return -1;
+
+ *id = p->memory.id;
+
+ return 0;
+}
+
+/* No lock necessary */
+int pa_mempool_is_shared(pa_mempool *p) {
+ pa_assert(p);
+
+ return !!p->memory.shared;
+}
+
+/* For recieving 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;
+ 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 */
+static pa_memimport_segment* segment_attach(pa_memimport *i, uint32_t shm_id) {
+ pa_memimport_segment* seg;
+
+ if (pa_hashmap_size(i->segments) >= PA_MEMIMPORT_SEGMENTS_MAX)
+ return NULL;
+
+ seg = pa_xnew(pa_memimport_segment, 1);
+
+ if (pa_shm_attach_ro(&seg->memory, shm_id) < 0) {
+ pa_xfree(seg);
+ return NULL;
+ }
+
+ seg->import = i;
+ seg->n_blocks = 0;
+
+ pa_hashmap_put(i->segments, PA_UINT32_TO_PTR(shm_id), seg);
+ return seg;
+}
+
+/* Should be called locked */
+static void segment_detach(pa_memimport_segment *seg) {
+ pa_assert(seg);
+
+ pa_hashmap_remove(seg->import->segments, PA_UINT32_TO_PTR(seg->memory.id));
+ pa_shm_free(&seg->memory);
+ pa_xfree(seg);
+}
+
+/* Self-locked. Not multiple-caller safe */
+void pa_memimport_free(pa_memimport *i) {
+ pa_memexport *e;
+ pa_memblock *b;
+
+ pa_assert(i);
+
+ pa_mutex_lock(i->mutex);
+
+ while ((b = pa_hashmap_get_first(i->blocks)))
+ memblock_replace_import(b);
+
+ 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_hashmap_free(i->blocks, NULL, NULL);
+ pa_hashmap_free(i->segments, NULL, NULL);
+
+ pa_mutex_free(i->mutex);
+
+ pa_xfree(i);
+}
+
+/* Self-locked */
+pa_memblock* pa_memimport_get(pa_memimport *i, uint32_t block_id, uint32_t shm_id, size_t offset, size_t size) {
+ pa_memblock *b = NULL;
+ pa_memimport_segment *seg;
+
+ pa_assert(i);
+
+ pa_mutex_lock(i->mutex);
+
+ 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 (!(seg = segment_attach(i, shm_id)))
+ 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;
+ b->type = PA_MEMBLOCK_IMPORTED;
+ b->read_only = 1;
+ 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++;
+
+finish:
+ pa_mutex_unlock(i->mutex);
+
+ if (b)
+ stat_add(b);
+
+ 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;
+
+ pa_assert(p);
+ pa_assert(cb);
+
+ if (!p->memory.shared)
+ return NULL;
+
+ e = pa_xnew(pa_memexport, 1);
+ e->mutex = pa_mutex_new(TRUE, TRUE);
+ e->pool = p;
+ 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);
+ 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, e->used_slots - e->slots);
+ 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_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->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, 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 = slot - e->slots;
+ 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, 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(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 = slot - e->slots;
+
+ 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);
+ memory = &b->pool->memory;
+ }
+
+ pa_assert(data >= memory->ptr);
+ pa_assert((uint8_t*) data + b->length <= (uint8_t*) memory->ptr + memory->size);
+
+ *shm_id = memory->id;
+ *offset = (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, b->length);
+
+ return 0;
+}
diff --git a/src/pulsecore/memblock.h b/src/pulsecore/memblock.h
new file mode 100644
index 00000000..c704014a
--- /dev/null
+++ b/src/pulsecore/memblock.h
@@ -0,0 +1,139 @@
+#ifndef foopulsememblockhfoo
+#define foopulsememblockhfoo
+
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <sys/types.h>
+#include <inttypes.h>
+
+#include <pulse/def.h>
+#include <pulsecore/llist.h>
+#include <pulsecore/refcnt.h>
+#include <pulsecore/atomic.h>
+
+/* A pa_memblock is a reference counted memory block. PulseAudio
+ * passed 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 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_memblock pa_memblock;
+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, void (*free_cb)(void *p), int read_only);
+
+/* A special case of pa_memblock_new_user: take a memory buffer previously allocated with pa_xmalloc() */
+#define pa_memblock_new_malloced(p,data,length) pa_memblock_new_user(p, data, length, pa_xfree, 0)
+
+/* Allocate a new memory block of type PA_MEMBLOCK_FIXED */
+pa_memblock *pa_memblock_new_fixed(pa_mempool *, void *data, size_t length, int 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 he wants to release all
+references to the memory. This causes the memory to be copied and
+converted into a pool or 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);
+
+int pa_memblock_is_read_only(pa_memblock *b);
+int pa_memblock_ref_is_one(pa_memblock *b);
+void* pa_memblock_acquire(pa_memblock *b);
+void pa_memblock_release(pa_memblock *b);
+size_t pa_memblock_get_length(pa_memblock *b);
+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(int shared);
+void pa_mempool_free(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);
+int pa_mempool_is_shared(pa_mempool *p);
+size_t pa_mempool_block_size_max(pa_mempool *p);
+
+/* For recieving 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);
+pa_memblock* pa_memimport_get(pa_memimport *i, uint32_t block_id, uint32_t shm_id, size_t offset, size_t size);
+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, 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 00000000..8247feab
--- /dev/null
+++ b/src/pulsecore/memblockq.c
@@ -0,0 +1,739 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sys/time.h>
+#include <time.h>
+#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"
+
+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;
+ unsigned n_blocks;
+ size_t maxlength, tlength, base, prebuf, minreq;
+ int64_t read_index, write_index;
+ pa_bool_t in_prebuf;
+ pa_memblock *silence;
+ pa_mcalign *mcalign;
+ int64_t missing;
+ size_t requested;
+};
+
+pa_memblockq* pa_memblockq_new(
+ int64_t idx,
+ size_t maxlength,
+ size_t tlength,
+ size_t base,
+ size_t prebuf,
+ size_t minreq,
+ pa_memblock *silence) {
+
+ pa_memblockq* bq;
+
+ pa_assert(base > 0);
+
+ bq = pa_xnew(pa_memblockq, 1);
+ bq->blocks = bq->blocks_tail = NULL;
+ bq->n_blocks = 0;
+
+ bq->base = base;
+ bq->read_index = bq->write_index = idx;
+
+ pa_log_debug("memblockq requested: maxlength=%lu, tlength=%lu, base=%lu, prebuf=%lu, minreq=%lu",
+ (unsigned long) maxlength, (unsigned long) tlength, (unsigned long) base, (unsigned long) prebuf, (unsigned long) minreq);
+
+ bq->missing = bq->requested = bq->maxlength = bq->tlength = bq->prebuf = bq->minreq = 0;
+ bq->in_prebuf = TRUE;
+
+ pa_memblockq_set_maxlength(bq, maxlength);
+ pa_memblockq_set_tlength(bq, tlength);
+ pa_memblockq_set_prebuf(bq, prebuf);
+ pa_memblockq_set_minreq(bq, minreq);
+
+ pa_log_debug("memblockq sanitized: maxlength=%lu, tlength=%lu, base=%lu, prebuf=%lu, minreq=%lu",
+ (unsigned long)bq->maxlength, (unsigned long)bq->tlength, (unsigned long)bq->base, (unsigned long)bq->prebuf, (unsigned long)bq->minreq);
+
+ bq->silence = silence ? pa_memblock_ref(silence) : NULL;
+ bq->mcalign = NULL;
+
+ return bq;
+}
+
+void pa_memblockq_free(pa_memblockq* bq) {
+ pa_assert(bq);
+
+ pa_memblockq_flush(bq);
+
+ if (bq->silence)
+ pa_memblock_unref(bq->silence);
+
+ if (bq->mcalign)
+ pa_mcalign_free(bq->mcalign);
+
+ pa_xfree(bq);
+}
+
+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
+ bq->blocks = q->next;
+
+ if (q->next)
+ q->next->prev = q->prev;
+ else
+ bq->blocks_tail = q->prev;
+
+ 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 pa_bool_t can_push(pa_memblockq *bq, size_t l) {
+ int64_t end;
+
+ pa_assert(bq);
+
+ if (bq->read_index > bq->write_index) {
+ size_t d = bq->read_index - bq->write_index;
+
+ if (l > d)
+ l -= d;
+ else
+ return TRUE;
+ }
+
+ end = bq->blocks_tail ? bq->blocks_tail->index + bq->blocks_tail->chunk.length : 0;
+
+ /* Make sure that the list doesn't get too long */
+ if (bq->write_index + (int64_t)l > end)
+ if (bq->write_index + l - bq->read_index > bq->maxlength)
+ return FALSE;
+
+ return TRUE;
+}
+
+int pa_memblockq_push(pa_memblockq* bq, const pa_memchunk *uchunk) {
+ struct list_item *q, *n;
+ pa_memchunk chunk;
+ int64_t old, delta;
+
+ 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));
+
+ if (uchunk->length % bq->base)
+ return -1;
+
+ if (!can_push(bq, uchunk->length))
+ return -1;
+
+ old = bq->write_index;
+ chunk = *uchunk;
+
+ if (bq->read_index > bq->write_index) {
+
+ /* We currently have a buffer underflow, we need to drop some
+ * incoming data */
+
+ size_t d = bq->read_index - bq->write_index;
+
+ if (chunk.length > d) {
+ chunk.index += d;
+ chunk.length -= d;
+ bq->write_index += d;
+ } else {
+ /* We drop the incoming data completely */
+ bq->write_index += chunk.length;
+ goto finish;
+ }
+ }
+
+ /* 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 */
+
+ q = bq->blocks_tail;
+ 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 + chunk.length >= q->index + 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 + chunk.length < q->index + 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 thie 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 = bq->write_index + chunk.length - q->index;
+ pa_assert(d > 0);
+
+ /* Drop it from the new entry */
+ p->index = q->index + 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 = 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 = bq->write_index + chunk.length - q->index;
+ q->index += 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 + (int64_t)q->chunk.length == chunk.index &&
+ bq->write_index == q->index + (int64_t)q->chunk.length) {
+
+ q->chunk.length += chunk.length;
+ bq->write_index += 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 += 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:
+
+ delta = bq->write_index - old;
+
+ if (delta >= bq->requested) {
+ delta -= bq->requested;
+ bq->requested = 0;
+ } else {
+ bq->requested -= delta;
+ delta = 0;
+ }
+
+ bq->missing -= delta;
+
+ return 0;
+}
+
+static pa_bool_t memblockq_check_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) {
+ pa_assert(bq);
+ pa_assert(chunk);
+
+ /* We need to pre-buffer */
+ if (memblockq_check_prebuf(bq))
+ return -1;
+
+ /* Do we need to spit out silence? */
+ if (!bq->blocks || bq->blocks->index > bq->read_index) {
+
+ size_t length;
+
+ /* How much silence shall we return? */
+ length = bq->blocks ? bq->blocks->index - bq->read_index : 0;
+
+ /* We need to return silence, since no data is yet available */
+ if (bq->silence) {
+ chunk->memblock = pa_memblock_ref(bq->silence);
+
+ if (!length || length > pa_memblock_get_length(chunk->memblock))
+ length = pa_memblock_get_length(chunk->memblock);
+
+ chunk->length = length;
+ } else {
+
+ /* If the memblockq is empty, return -1, otherwise return
+ * the time to sleep */
+ if (!bq->blocks)
+ return -1;
+
+ chunk->memblock = NULL;
+ chunk->length = length;
+ }
+
+ chunk->index = 0;
+ return 0;
+ }
+
+ /* Ok, let's pass real data to the caller */
+ pa_assert(bq->blocks->index == bq->read_index);
+
+ *chunk = bq->blocks->chunk;
+ pa_memblock_ref(chunk->memblock);
+
+ return 0;
+}
+
+void pa_memblockq_drop(pa_memblockq *bq, size_t length) {
+ int64_t old, delta;
+ 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 (memblockq_check_prebuf(bq))
+ break;
+
+ if (bq->blocks) {
+ size_t d;
+
+ pa_assert(bq->blocks->index >= bq->read_index);
+
+ d = (size_t) (bq->blocks->index - bq->read_index);
+
+ if (d >= length) {
+ /* The first block is too far in the future */
+
+ bq->read_index += length;
+ break;
+ } else {
+
+ length -= d;
+ bq->read_index += d;
+ }
+
+ pa_assert(bq->blocks->index == bq->read_index);
+
+ if (bq->blocks->chunk.length <= length) {
+ /* We need to drop the full block */
+
+ length -= bq->blocks->chunk.length;
+ bq->read_index += bq->blocks->chunk.length;
+
+ drop_block(bq, bq->blocks);
+ } else {
+ /* Only the start of this block needs to be dropped */
+
+ bq->blocks->chunk.index += length;
+ bq->blocks->chunk.length -= length;
+ bq->blocks->index += length;
+ bq->read_index += length;
+ break;
+ }
+
+ } else {
+
+ /* The list is empty, there's nothing we could drop */
+ bq->read_index += length;
+ break;
+ }
+ }
+
+ delta = bq->read_index - old;
+ bq->missing += delta;
+}
+
+int pa_memblockq_is_readable(pa_memblockq *bq) {
+ pa_assert(bq);
+
+ if (memblockq_check_prebuf(bq))
+ return 0;
+
+ if (pa_memblockq_get_length(bq) <= 0)
+ return 0;
+
+ return 1;
+}
+
+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);
+}
+
+size_t pa_memblockq_missing(pa_memblockq *bq) {
+ size_t l;
+ pa_assert(bq);
+
+ if ((l = pa_memblockq_get_length(bq)) >= bq->tlength)
+ return 0;
+
+ l = bq->tlength - l;
+
+ return l >= bq->minreq ? l : 0;
+}
+
+size_t pa_memblockq_get_minreq(pa_memblockq *bq) {
+ pa_assert(bq);
+
+ return bq->minreq;
+}
+
+void pa_memblockq_seek(pa_memblockq *bq, int64_t offset, pa_seek_mode_t seek) {
+ int64_t old, delta;
+ 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();
+ }
+
+ delta = bq->write_index - old;
+
+ if (delta >= bq->requested) {
+ delta -= bq->requested;
+ bq->requested = 0;
+ } else if (delta >= 0) {
+ bq->requested -= delta;
+ delta = 0;
+ }
+
+ bq->missing -= delta;
+}
+
+void pa_memblockq_flush(pa_memblockq *bq) {
+ int64_t old, delta;
+ pa_assert(bq);
+
+ while (bq->blocks)
+ drop_block(bq, bq->blocks);
+
+ pa_assert(bq->n_blocks == 0);
+
+ old = bq->write_index;
+ bq->write_index = bq->read_index;
+
+ pa_memblockq_prebuf_force(bq);
+
+ delta = bq->write_index - old;
+
+ if (delta > bq->requested) {
+ delta -= bq->requested;
+ bq->requested = 0;
+ } else if (delta >= 0) {
+ bq->requested -= delta;
+ delta = 0;
+ }
+
+ bq->missing -= delta;
+}
+
+size_t pa_memblockq_get_tlength(pa_memblockq *bq) {
+ pa_assert(bq);
+
+ return bq->tlength;
+}
+
+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 (!bq->mcalign)
+ bq->mcalign = pa_mcalign_new(bq->base);
+
+ 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)
+ return -1;
+ }
+
+ return 0;
+}
+
+void pa_memblockq_shorten(pa_memblockq *bq, size_t length) {
+ size_t l;
+ pa_assert(bq);
+
+ l = pa_memblockq_get_length(bq);
+
+ if (l > length)
+ pa_memblockq_drop(bq, l - length);
+}
+
+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->in_prebuf && 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);
+
+/* pa_log("pop: %lli", bq->missing); */
+
+ if (bq->missing <= 0)
+ return 0;
+
+ l = (size_t) bq->missing;
+ bq->missing = 0;
+ bq->requested += l;
+
+ 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);
+
+ if (bq->prebuf > bq->maxlength)
+ pa_memblockq_set_prebuf(bq, bq->maxlength);
+}
+
+void pa_memblockq_set_tlength(pa_memblockq *bq, size_t tlength) {
+ size_t old_tlength;
+ pa_assert(bq);
+
+ old_tlength = bq->tlength;
+
+ if (tlength <= 0)
+ tlength = bq->maxlength;
+
+ bq->tlength = ((tlength+bq->base-1)/bq->base)*bq->base;
+
+ if (bq->tlength > bq->maxlength)
+ bq->tlength = bq->maxlength;
+
+ if (bq->minreq > bq->tlength - bq->prebuf)
+ pa_memblockq_set_minreq(bq, bq->tlength - bq->prebuf);
+
+ bq->missing += (int64_t) bq->tlength - (int64_t) old_tlength;
+}
+
+void pa_memblockq_set_prebuf(pa_memblockq *bq, size_t prebuf) {
+ pa_assert(bq);
+
+ bq->prebuf = (prebuf == (size_t) -1) ? bq->tlength/2 : prebuf;
+ bq->prebuf = ((bq->prebuf+bq->base-1)/bq->base)*bq->base;
+
+ if (prebuf > 0 && bq->prebuf < bq->base)
+ bq->prebuf = bq->base;
+
+ if (bq->prebuf > bq->maxlength)
+ bq->prebuf = bq->maxlength;
+
+ if (bq->prebuf <= 0 || pa_memblockq_get_length(bq) >= bq->prebuf)
+ bq->in_prebuf = FALSE;
+
+ if (bq->minreq > bq->tlength - bq->prebuf)
+ pa_memblockq_set_minreq(bq, bq->tlength - bq->prebuf);
+}
+
+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->prebuf)
+ bq->minreq = bq->tlength - bq->prebuf;
+
+ if (bq->minreq < bq->base)
+ bq->minreq = bq->base;
+}
diff --git a/src/pulsecore/memblockq.h b/src/pulsecore/memblockq.h
new file mode 100644
index 00000000..46637f10
--- /dev/null
+++ b/src/pulsecore/memblockq.h
@@ -0,0 +1,151 @@
+#ifndef foomemblockqhfoo
+#define foomemblockqhfoo
+
+/* $Id$ */
+
+/***
+ 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 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <sys/types.h>
+#include <inttypes.h>
+
+#include <pulsecore/memblock.h>
+#include <pulsecore/memchunk.h>
+#include <pulse/def.h>
+
+/* A memblockq is a queue of pa_memchunks (yepp, 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:
+
+ - 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.
+
+ - base: a base value for all metrics. Only multiples of this value
+ are popped from the queue or should be pushed into
+ it. Must not be 0.
+
+ - 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_missing() will only return values greater
+ than this value. Pass 0 for the default.
+
+ - silence: return this memblock when reading unitialized data
+*/
+pa_memblockq* pa_memblockq_new(
+ int64_t idx,
+ size_t maxlength,
+ size_t tlength,
+ size_t base,
+ size_t prebuf,
+ size_t minreq,
+ pa_memblock *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);
+
+/* 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);
+
+/* Drop the specified bytes from the queue. */
+void pa_memblockq_drop(pa_memblockq *bq, size_t length);
+
+/* Test if the pa_memblockq is currently readable, that is, more data than base */
+int 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 how many bytes are missing in queue to the specified fill amount */
+size_t pa_memblockq_missing(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);
+
+/* Returns the minimal request value */
+size_t pa_memblockq_get_minreq(pa_memblockq *bq);
+
+/* Manipulate the write pointer */
+void pa_memblockq_seek(pa_memblockq *bq, int64_t offset, pa_seek_mode_t seek);
+
+/* Set the queue to silence, set write index to read index */
+void pa_memblockq_flush(pa_memblockq *bq);
+
+/* Get Target length */
+size_t pa_memblockq_get_tlength(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);
+
+/* Shorten the pa_memblockq to the specified length by dropping data
+ * at the read end of the queue. The read index is increased until the
+ * queue has the specified length */
+void pa_memblockq_shorten(pa_memblockq *bq, size_t length);
+
+/* 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);
+
+/* Return the prebuffer length in bytes */
+size_t pa_memblockq_get_prebuf(pa_memblockq *bq);
+
+/* Change metrics. */
+void pa_memblockq_set_maxlength(pa_memblockq *memblockq, size_t maxlength);
+void pa_memblockq_set_tlength(pa_memblockq *memblockq, size_t tlength);
+void pa_memblockq_set_prebuf(pa_memblockq *memblockq, size_t prebuf);
+void pa_memblockq_set_minreq(pa_memblockq *memblockq, size_t minreq);
+
+#endif
diff --git a/src/pulsecore/memchunk.c b/src/pulsecore/memchunk.c
new file mode 100644
index 00000000..4e73b636
--- /dev/null
+++ b/src/pulsecore/memchunk.c
@@ -0,0 +1,92 @@
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <pulse/xmalloc.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_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 = c->length;
+ if (l < min)
+ l = min;
+
+ n = pa_memblock_new(pa_memblock_get_pool(c->memblock), l);
+ tdata = pa_memblock_acquire(n);
+ sdata = pa_memblock_acquire(c->memblock);
+ memcpy(tdata, (uint8_t*) sdata + c->index, c->length);
+ pa_memblock_release(n);
+ pa_memblock_release(c->memblock);
+ pa_memblock_unref(c->memblock);
+ c->memblock = n;
+ c->index = 0;
+
+ return c;
+}
+
+pa_memchunk* pa_memchunk_reset(pa_memchunk *c) {
+ pa_assert(c);
+
+ c->memblock = NULL;
+ c->length = c->index = 0;
+
+ 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 = (uint8_t*) pa_memblock_acquire(c->memblock) + c->index;
+ pa_will_need(p, c->length);
+ pa_memblock_release(c->memblock);
+
+ return (pa_memchunk*) c;
+}
diff --git a/src/pulsecore/memchunk.h b/src/pulsecore/memchunk.h
new file mode 100644
index 00000000..e6105ace
--- /dev/null
+++ b/src/pulsecore/memchunk.h
@@ -0,0 +1,52 @@
+#ifndef foomemchunkhfoo
+#define foomemchunkhfoo
+
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#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 */
+
+typedef struct pa_memchunk {
+ pa_memblock *memblock;
+ size_t index, length;
+} pa_memchunk;
+
+/* 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 cotaining 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);
+
+#endif
diff --git a/src/pulsecore/modargs.c b/src/pulsecore/modargs.c
new file mode 100644
index 00000000..0dab254b
--- /dev/null
+++ b/src/pulsecore/modargs.c
@@ -0,0 +1,324 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/hashmap.h>
+#include <pulsecore/idxset.h>
+#include <pulsecore/sample-util.h>
+#include <pulsecore/namereg.h>
+#include <pulsecore/sink.h>
+#include <pulsecore/source.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/macro.h>
+
+#include "modargs.h"
+
+struct entry {
+ char *key, *value;
+};
+
+static int add_key_value(pa_hashmap *map, char *key, char *value, const char* const valid_keys[]) {
+ struct entry *e;
+
+ pa_assert(map);
+ pa_assert(key);
+ pa_assert(value);
+
+ if (valid_keys) {
+ const char*const* v;
+ for (v = valid_keys; *v; v++)
+ if (strcmp(*v, key) == 0)
+ break;
+
+ if (!*v) {
+ pa_xfree(key);
+ pa_xfree(value);
+ return -1;
+ }
+ }
+
+ e = pa_xnew(struct entry, 1);
+ e->key = key;
+ e->value = value;
+ pa_hashmap_put(map, key, e);
+
+ return 0;
+}
+
+pa_modargs *pa_modargs_new(const char *args, const char* const* valid_keys) {
+ pa_hashmap *map = NULL;
+
+ map = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+
+ if (args) {
+ enum { WHITESPACE, KEY, VALUE_START, VALUE_SIMPLE, VALUE_DOUBLE_QUOTES, VALUE_TICKS } state;
+ const char *p, *key, *value;
+ size_t key_len = 0, value_len = 0;
+
+ key = value = NULL;
+ state = WHITESPACE;
+ for (p = args; *p; p++) {
+ switch (state) {
+ case WHITESPACE:
+ if (*p == '=')
+ goto fail;
+ else if (!isspace(*p)) {
+ key = p;
+ state = KEY;
+ key_len = 1;
+ }
+ break;
+ case KEY:
+ if (*p == '=')
+ state = VALUE_START;
+ 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(*p)) {
+ if (add_key_value(map, pa_xstrndup(key, key_len), pa_xstrdup(""), valid_keys) < 0)
+ goto fail;
+ state = WHITESPACE;
+ } else {
+ state = VALUE_SIMPLE;
+ value = p;
+ value_len = 1;
+ }
+ break;
+ case VALUE_SIMPLE:
+ if (isspace(*p)) {
+ if (add_key_value(map, pa_xstrndup(key, key_len), pa_xstrndup(value, value_len), valid_keys) < 0)
+ goto fail;
+ state = WHITESPACE;
+ } else
+ value_len++;
+ break;
+ case VALUE_DOUBLE_QUOTES:
+ if (*p == '"') {
+ if (add_key_value(map, pa_xstrndup(key, key_len), pa_xstrndup(value, value_len), valid_keys) < 0)
+ goto fail;
+ state = WHITESPACE;
+ } else
+ value_len++;
+ break;
+ case VALUE_TICKS:
+ if (*p == '\'') {
+ if (add_key_value(map, pa_xstrndup(key, key_len), pa_xstrndup(value, value_len), valid_keys) < 0)
+ goto fail;
+ state = WHITESPACE;
+ } else
+ value_len++;
+ break;
+ }
+ }
+
+ if (state == VALUE_START) {
+ if (add_key_value(map, pa_xstrndup(key, key_len), pa_xstrdup(""), valid_keys) < 0)
+ goto fail;
+ } else if (state == VALUE_SIMPLE) {
+ if (add_key_value(map, pa_xstrndup(key, key_len), pa_xstrdup(value), valid_keys) < 0)
+ goto fail;
+ } else if (state != WHITESPACE)
+ goto fail;
+ }
+
+ return (pa_modargs*) map;
+
+fail:
+
+ if (map)
+ pa_modargs_free((pa_modargs*) map);
+
+ return NULL;
+}
+
+static void free_func(void *p, PA_GCC_UNUSED void*userdata) {
+ struct entry *e = p;
+ pa_assert(e);
+
+ pa_xfree(e->key);
+ pa_xfree(e->value);
+ pa_xfree(e);
+}
+
+void pa_modargs_free(pa_modargs*ma) {
+ pa_hashmap *map = (pa_hashmap*) ma;
+ pa_hashmap_free(map, free_func, NULL);
+}
+
+const char *pa_modargs_get_value(pa_modargs *ma, const char *key, const char *def) {
+ pa_hashmap *map = (pa_hashmap*) ma;
+ struct entry*e;
+
+ if (!(e = pa_hashmap_get(map, 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(ma);
+ pa_assert(key);
+ 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(ma);
+ pa_assert(key);
+ 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, pa_bool_t *value) {
+ const char *v;
+ int r;
+
+ pa_assert(ma);
+ pa_assert(key);
+ 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_sample_spec(pa_modargs *ma, pa_sample_spec *rss) {
+ const char *format;
+ uint32_t channels;
+ pa_sample_spec ss;
+
+ pa_assert(ma);
+ pa_assert(rss);
+
+ ss = *rss;
+ if ((pa_modargs_get_value_u32(ma, "rate", &ss.rate)) < 0)
+ return -1;
+
+ channels = ss.channels;
+ if ((pa_modargs_get_value_u32(ma, "channels", &channels)) < 0)
+ 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_channel_map(pa_modargs *ma, const char *name, pa_channel_map *rmap) {
+ pa_channel_map map;
+ const char *cm;
+
+ pa_assert(ma);
+ 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_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(ma);
+ pa_assert(rss);
+ pa_assert(rmap);
+
+ ss = *rss;
+
+ if (pa_modargs_get_sample_spec(ma, &ss) < 0)
+ return -1;
+
+ if (!pa_channel_map_init_auto(&map, ss.channels, def))
+ map.channels = 0;
+
+ if (pa_modargs_get_channel_map(ma, NULL, &map) < 0)
+ return -1;
+
+ if (map.channels != ss.channels)
+ return -1;
+
+ *rmap = map;
+ *rss = ss;
+
+ return 0;
+}
diff --git a/src/pulsecore/modargs.h b/src/pulsecore/modargs.h
new file mode 100644
index 00000000..504b9cd7
--- /dev/null
+++ b/src/pulsecore/modargs.h
@@ -0,0 +1,63 @@
+#ifndef foomodargshfoo
+#define foomodargshfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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 <inttypes.h>
+#include <pulse/sample.h>
+#include <pulse/channelmap.h>
+#include <pulsecore/core.h>
+#include <pulsecore/macro.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[]);
+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 */
+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, pa_bool_t *value);
+
+/* Return sample spec data from the three arguments "rate", "format" and "channels" */
+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 */
+int pa_modargs_get_channel_map(pa_modargs *ma, const char *name, pa_channel_map *map);
+
+/* 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);
+
+#endif
diff --git a/src/pulsecore/modinfo.c b/src/pulsecore/modinfo.c
new file mode 100644
index 00000000..d1a78fbb
--- /dev/null
+++ b/src/pulsecore/modinfo.c
@@ -0,0 +1,97 @@
+/* $Id$ */
+
+/***
+ 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 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <ltdl.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/core-util.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_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);
+ pa_bool_t (*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 ((func2 = (pa_bool_t (*)(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);
+}
diff --git a/src/pulsecore/modinfo.h b/src/pulsecore/modinfo.h
new file mode 100644
index 00000000..da6d5428
--- /dev/null
+++ b/src/pulsecore/modinfo.h
@@ -0,0 +1,47 @@
+#ifndef foomodinfohfoo
+#define foomodinfohfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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.
+***/
+
+/* 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;
+ pa_bool_t 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 00000000..ae140ff4
--- /dev/null
+++ b/src/pulsecore/module.c
@@ -0,0 +1,308 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+
+#include <pulse/timeval.h>
+#include <pulse/xmalloc.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 "module.h"
+
+#define PA_SYMBOL_INIT "pa__init"
+#define PA_SYMBOL_DONE "pa__done"
+#define PA_SYMBOL_LOAD_ONCE "pa__load_once"
+
+#define UNLOAD_POLL_TIME 2
+
+static void timeout_callback(pa_mainloop_api *m, pa_time_event*e, PA_GCC_UNUSED const struct timeval *tv, void *userdata) {
+ pa_core *c = PA_CORE(userdata);
+ struct timeval ntv;
+
+ pa_core_assert_ref(c);
+ pa_assert(c->mainloop == m);
+ pa_assert(c->module_auto_unload_event == e);
+
+ pa_module_unload_unused(c);
+
+ pa_gettimeofday(&ntv);
+ pa_timeval_add(&ntv, UNLOAD_POLL_TIME*1000000);
+ m->time_restart(e, &ntv);
+}
+
+pa_module* pa_module_load(pa_core *c, const char *name, const char *argument) {
+ pa_module *m = NULL;
+ pa_bool_t (*load_once)(void);
+
+ pa_assert(c);
+ pa_assert(name);
+
+ if (c->disallow_module_loading)
+ goto fail;
+
+ m = pa_xnew(pa_module, 1);
+ m->name = pa_xstrdup(name);
+ m->argument = pa_xstrdup(argument);
+
+ if (!(m->dl = lt_dlopenext(name))) {
+ pa_log("Failed to open module \"%s\": %s", name, lt_dlerror());
+ goto fail;
+ }
+
+ if ((load_once = (pa_bool_t (*)(void)) pa_load_sym(m->dl, name, PA_SYMBOL_LOAD_ONCE))) {
+
+ if (load_once() && c->modules) {
+ pa_module *i;
+ uint32_t idx;
+ /* OK, the module only wants to be loaded once, let's make sure it is */
+
+ for (i = pa_idxset_first(c->modules, &idx); i; i = pa_idxset_next(c->modules, &idx)) {
+ if (strcmp(name, i->name) == 0) {
+ pa_log("Module \"%s\" should be loaded once at most. Refusing to load.", name);
+ goto fail;
+ }
+ }
+ }
+ }
+
+ if (!(m->init = (int (*)(pa_module*_m)) pa_load_sym(m->dl, name, PA_SYMBOL_INIT))) {
+ pa_log("Failed to load module \"%s\": symbol \""PA_SYMBOL_INIT"\" not found.", name);
+ goto fail;
+ }
+
+ m->done = (void (*)(pa_module*_m)) pa_load_sym(m->dl, name, PA_SYMBOL_DONE);
+ m->userdata = NULL;
+ m->core = c;
+ m->n_used = -1;
+ m->auto_unload = 0;
+ m->unload_requested = 0;
+
+ if (m->init(m) < 0) {
+ pa_log_error("Failed to load module \"%s\" (argument: \"%s\"): initialization failed.", name, argument ? argument : "");
+ goto fail;
+ }
+
+ if (!c->modules)
+ c->modules = pa_idxset_new(NULL, NULL);
+
+ if (!c->module_auto_unload_event) {
+ struct timeval ntv;
+ pa_gettimeofday(&ntv);
+ pa_timeval_add(&ntv, UNLOAD_POLL_TIME*1000000);
+ c->module_auto_unload_event = c->mainloop->time_new(c->mainloop, &ntv, timeout_callback, c);
+ }
+
+ pa_assert_se(pa_idxset_put(c->modules, m, &m->index) >= 0);
+ pa_assert(m->index != PA_IDXSET_INVALID);
+
+ 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);
+
+ return m;
+
+fail:
+
+ if (m) {
+ pa_xfree(m->argument);
+ pa_xfree(m->name);
+
+ if (m->dl)
+ lt_dlclose(m->dl);
+
+ pa_xfree(m);
+ }
+
+ return NULL;
+}
+
+static void pa_module_free(pa_module *m) {
+ pa_assert(m);
+ pa_assert(m->core);
+
+ if (m->core->disallow_module_loading)
+ return;
+
+ pa_log_info("Unloading \"%s\" (index: #%u).", m->name, m->index);
+
+ if (m->done)
+ m->done(m);
+
+ lt_dlclose(m->dl);
+
+ 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_core *c, pa_module *m) {
+ pa_assert(c);
+ pa_assert(m);
+
+ pa_assert(c->modules);
+ if (!(m = pa_idxset_remove_by_data(c->modules, m, NULL)))
+ return;
+
+ pa_module_free(m);
+}
+
+void pa_module_unload_by_index(pa_core *c, uint32_t idx) {
+ pa_module *m;
+ pa_assert(c);
+ pa_assert(idx != PA_IDXSET_INVALID);
+
+ if (!(m = pa_idxset_remove_by_index(c->modules, idx)))
+ return;
+
+ pa_module_free(m);
+}
+
+static void free_callback(void *p, PA_GCC_UNUSED void *userdata) {
+ pa_module *m = p;
+ pa_assert(m);
+ pa_module_free(m);
+}
+
+void pa_module_unload_all(pa_core *c) {
+ pa_module *m;
+
+ pa_assert(c);
+
+ if (!c->modules)
+ return;
+
+ while ((m = pa_idxset_first(c->modules, NULL)))
+ pa_module_unload(c, m);
+
+ pa_idxset_free(c->modules, free_callback, NULL);
+ c->modules = NULL;
+
+ if (c->module_auto_unload_event) {
+ c->mainloop->time_free(c->module_auto_unload_event);
+ c->module_auto_unload_event = NULL;
+ }
+
+ if (c->module_defer_unload_event) {
+ c->mainloop->defer_free(c->module_defer_unload_event);
+ c->module_defer_unload_event = NULL;
+ }
+}
+
+static int unused_callback(void *p, PA_GCC_UNUSED uint32_t idx, int *del, void *userdata) {
+ pa_module *m = p;
+ time_t *now = userdata;
+
+ pa_assert(m);
+ pa_assert(del);
+ pa_assert(now);
+
+ if (m->n_used == 0 && m->auto_unload && m->last_used_time+m->core->module_idle_time <= *now) {
+ pa_module_free(m);
+ *del = 1;
+ }
+
+ return 0;
+}
+
+void pa_module_unload_unused(pa_core *c) {
+ time_t now;
+ pa_assert(c);
+
+ if (!c->modules)
+ return;
+
+ time(&now);
+ pa_idxset_foreach(c->modules, unused_callback, &now);
+}
+
+static int unload_callback(void *p, PA_GCC_UNUSED uint32_t idx, int *del, PA_GCC_UNUSED void *userdata) {
+ pa_module *m = p;
+ pa_assert(m);
+
+ if (m->unload_requested) {
+ pa_module_free(m);
+ *del = 1;
+ }
+
+ return 0;
+}
+
+static void defer_cb(pa_mainloop_api*api, pa_defer_event *e, void *userdata) {
+ pa_core *core = PA_CORE(userdata);
+
+ pa_core_assert_ref(core);
+ api->defer_enable(e, 0);
+
+ if (!core->modules)
+ return;
+
+ pa_idxset_foreach(core->modules, unload_callback, NULL);
+}
+
+void pa_module_unload_request(pa_module *m) {
+ pa_assert(m);
+
+ m->unload_requested = 1;
+
+ 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_set_used(pa_module*m, int used) {
+ pa_assert(m);
+
+ if (m->n_used != used)
+ pa_subscription_post(m->core, PA_SUBSCRIPTION_EVENT_MODULE|PA_SUBSCRIPTION_EVENT_CHANGE, m->index);
+
+ if (used == 0 && m->n_used > 0)
+ time(&m->last_used_time);
+
+ m->n_used = used;
+}
+
+pa_modinfo *pa_module_get_info(pa_module *m) {
+ pa_assert(m);
+
+ return pa_modinfo_get_by_handle(m->dl, m->name);
+}
diff --git a/src/pulsecore/module.h b/src/pulsecore/module.h
new file mode 100644
index 00000000..25f122d1
--- /dev/null
+++ b/src/pulsecore/module.h
@@ -0,0 +1,87 @@
+#ifndef foomodulehfoo
+#define foomodulehfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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 <inttypes.h>
+#include <ltdl.h>
+
+typedef struct pa_module pa_module;
+
+#include <pulsecore/core.h>
+#include <pulsecore/modinfo.h>
+
+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);
+
+ void *userdata;
+
+ int n_used;
+ int auto_unload;
+ time_t last_used_time;
+
+ int unload_requested;
+};
+
+pa_module* pa_module_load(pa_core *c, const char *name, const char*argument);
+void pa_module_unload(pa_core *c, pa_module *m);
+void pa_module_unload_by_index(pa_core *c, uint32_t idx);
+
+void pa_module_unload_all(pa_core *c);
+void pa_module_unload_unused(pa_core *c);
+
+void pa_module_unload_request(pa_module *m);
+
+void pa_module_set_used(pa_module*m, int used);
+
+#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_LOAD_ONCE(b) \
+ pa_bool_t pa__load_once(void) { return b; } \
+ struct __stupid_useless_struct_to_allow_trailing_semicolon
+
+pa_modinfo *pa_module_get_info(pa_module *m);
+
+#endif
diff --git a/src/pulsecore/msgobject.c b/src/pulsecore/msgobject.c
new file mode 100644
index 00000000..f54e69f2
--- /dev/null
+++ b/src/pulsecore/msgobject.c
@@ -0,0 +1,49 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "msgobject.h"
+
+PA_DEFINE_CHECK_TYPE(pa_msgobject, pa_object);
+
+pa_msgobject *pa_msgobject_new_internal(size_t size, const char *type_name, int (*check_type)(const char *type_name)) {
+ pa_msgobject *o;
+
+ pa_assert(size > sizeof(pa_msgobject));
+ pa_assert(type_name);
+
+ if (!check_type)
+ check_type = pa_msgobject_check_type;
+
+ pa_assert(check_type(type_name));
+ pa_assert(check_type("pa_object"));
+ pa_assert(check_type("pa_msgobject"));
+
+ o = PA_MSGOBJECT(pa_object_new_internal(size, type_name, 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 00000000..8221cc33
--- /dev/null
+++ b/src/pulsecore/msgobject.h
@@ -0,0 +1,54 @@
+#ifndef foopulsemsgobjecthfoo
+#define foopulsemsgobjecthfoo
+
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <sys/types.h>
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/refcnt.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_name, int (*check_type)(const char *type_name));
+
+int pa_msgobject_check_type(const char *type);
+
+#define pa_msgobject_new(type) ((type*) pa_msgobject_new_internal(sizeof(type), #type, type##_check_type))
+#define pa_msgobject_free ((void (*) (pa_msgobject* o)) pa_object_free)
+
+#define PA_MSGOBJECT(o) pa_msgobject_cast(o)
+
+PA_DECLARE_CLASS(pa_msgobject);
+
+#endif
diff --git a/src/pulsecore/mutex-posix.c b/src/pulsecore/mutex-posix.c
new file mode 100644
index 00000000..6ac98484
--- /dev/null
+++ b/src/pulsecore/mutex-posix.c
@@ -0,0 +1,142 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pthread.h>
+#include <errno.h>
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/log.h>
+#include <pulsecore/core-error.h>
+
+#include "mutex.h"
+
+struct pa_mutex {
+ pthread_mutex_t mutex;
+};
+
+struct pa_cond {
+ pthread_cond_t cond;
+};
+
+pa_mutex* pa_mutex_new(pa_bool_t recursive, pa_bool_t inherit_priority) {
+ pa_mutex *m;
+ pthread_mutexattr_t attr;
+ int r;
+
+ 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)
+ pa_assert_se(pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT) == 0);
+#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);
+}
+
+pa_bool_t 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);
+}
diff --git a/src/pulsecore/mutex-win32.c b/src/pulsecore/mutex-win32.c
new file mode 100644
index 00000000..77d63d15
--- /dev/null
+++ b/src/pulsecore/mutex-win32.c
@@ -0,0 +1,135 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <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(pa_bool_t recursive, pa_bool_t 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, NULL, NULL);
+ 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_get_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;
+}
diff --git a/src/pulsecore/mutex.h b/src/pulsecore/mutex.h
new file mode 100644
index 00000000..9ca8fae5
--- /dev/null
+++ b/src/pulsecore/mutex.h
@@ -0,0 +1,50 @@
+#ifndef foopulsemutexhfoo
+#define foopulsemutexhfoo
+
+/* $Id$ */
+
+/***
+ 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 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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 <pulsecore/macro.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 priorized 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(pa_bool_t recursive, pa_bool_t inherit_priority);
+
+void pa_mutex_free(pa_mutex *m);
+void pa_mutex_lock(pa_mutex *m);
+pa_bool_t 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);
+
+#endif
diff --git a/src/pulsecore/namereg.c b/src/pulsecore/namereg.c
new file mode 100644
index 00000000..fe520384
--- /dev/null
+++ b/src/pulsecore/namereg.c
@@ -0,0 +1,300 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/autoload.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 int is_valid_char(char c) {
+ return
+ (c >= 'a' && c <= 'z') ||
+ (c >= 'A' && c <= 'Z') ||
+ (c >= '0' && c <= '9') ||
+ c == '.' ||
+ c == '_';
+}
+
+pa_bool_t pa_namereg_is_valid_name(const char *name) {
+ const char *c;
+
+ 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;
+}
+
+static char* cleanup_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 = is_valid_char(*a) ? *a : '_';
+
+ *b = 0;
+
+ return n;
+}
+
+void pa_namereg_free(pa_core *c) {
+ pa_assert(c);
+
+ if (!c->namereg)
+ return;
+
+ pa_assert(pa_hashmap_size(c->namereg) == 0);
+ pa_hashmap_free(c->namereg, NULL, NULL);
+}
+
+const char *pa_namereg_register(pa_core *c, const char *name, pa_namereg_type_t type, void *data, int 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) &&
+ !pa_namereg_is_valid_name(name) ) {
+
+ if (fail)
+ return NULL;
+
+ if (!(name = n = cleanup_name(name)))
+ return NULL;
+ }
+
+ if (!c->namereg)
+ c->namereg = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+
+ 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_xnew(char, 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, int autoload) {
+ struct namereg_entry *e;
+ uint32_t idx;
+ pa_assert(c);
+
+ if (!name) {
+
+ if (type == PA_NAMEREG_SOURCE)
+ name = pa_namereg_get_default_source_name(c);
+ else if (type == PA_NAMEREG_SINK)
+ name = pa_namereg_get_default_sink_name(c);
+
+ } else if (strcmp(name, "@DEFAULT_SINK@") == 0) {
+ if (type == PA_NAMEREG_SINK)
+ name = pa_namereg_get_default_sink_name(c);
+
+ } else if (strcmp(name, "@DEFAULT_SOURCE@") == 0) {
+ if (type == PA_NAMEREG_SOURCE)
+ name = pa_namereg_get_default_source_name(c);
+
+ } else if (strcmp(name, "@DEFAULT_MONITOR@") == 0) {
+ if (type == PA_NAMEREG_SOURCE) {
+ pa_sink *k;
+
+ if ((k = pa_namereg_get(c, NULL, PA_NAMEREG_SINK, autoload)))
+ return k->monitor_source;
+ }
+ } else if (*name == '@')
+ name = NULL;
+
+ if (!name)
+ return NULL;
+
+ if (c->namereg && (e = pa_hashmap_get(c->namereg, name)))
+ if (e->type == type)
+ return e->data;
+
+ if (pa_atou(name, &idx) < 0) {
+
+ if (autoload) {
+ pa_autoload_request(c, name, type);
+
+ if (c->namereg && (e = pa_hashmap_get(c->namereg, name)))
+ if (e->type == type)
+ return e->data;
+ }
+
+ 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);
+
+ return NULL;
+}
+
+int pa_namereg_set_default(pa_core*c, const char *name, pa_namereg_type_t type) {
+ char **s;
+
+ pa_assert(c);
+ pa_assert(type == PA_NAMEREG_SINK || type == PA_NAMEREG_SOURCE);
+
+ s = type == PA_NAMEREG_SINK ? &c->default_sink_name : &c->default_source_name;
+
+ if (!name && !*s)
+ return 0;
+
+ if (name && *s && !strcmp(name, *s))
+ return 0;
+
+ if (!pa_namereg_is_valid_name(name))
+ return -1;
+
+ pa_xfree(*s);
+ *s = pa_xstrdup(name);
+ pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_SERVER|PA_SUBSCRIPTION_EVENT_CHANGE, PA_INVALID_INDEX);
+
+ return 0;
+}
+
+const char *pa_namereg_get_default_sink_name(pa_core *c) {
+ pa_sink *s;
+
+ pa_assert(c);
+
+ if (c->default_sink_name)
+ return c->default_sink_name;
+
+ if ((s = pa_idxset_first(c->sinks, NULL)))
+ pa_namereg_set_default(c, s->name, PA_NAMEREG_SINK);
+
+ return c->default_sink_name;
+}
+
+const char *pa_namereg_get_default_source_name(pa_core *c) {
+ pa_source *s;
+ uint32_t idx;
+
+ pa_assert(c);
+
+ if (c->default_source_name)
+ return c->default_source_name;
+
+ for (s = pa_idxset_first(c->sources, &idx); s; s = pa_idxset_next(c->sources, &idx))
+ if (!s->monitor_of) {
+ pa_namereg_set_default(c, s->name, PA_NAMEREG_SOURCE);
+ break;
+ }
+
+ if (!c->default_source_name)
+ if ((s = pa_idxset_first(c->sources, NULL)))
+ pa_namereg_set_default(c, s->name, PA_NAMEREG_SOURCE);
+
+ return c->default_source_name;
+}
diff --git a/src/pulsecore/namereg.h b/src/pulsecore/namereg.h
new file mode 100644
index 00000000..d0db9e81
--- /dev/null
+++ b/src/pulsecore/namereg.h
@@ -0,0 +1,50 @@
+#ifndef foonamereghfoo
+#define foonamereghfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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 <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_type_t;
+
+void pa_namereg_free(pa_core *c);
+
+const char *pa_namereg_register(pa_core *c, const char *name, pa_namereg_type_t type, void *data, int 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, int autoload);
+int pa_namereg_set_default(pa_core*c, const char *name, pa_namereg_type_t type);
+
+const char *pa_namereg_get_default_sink_name(pa_core *c);
+const char *pa_namereg_get_default_source_name(pa_core *c);
+
+pa_bool_t pa_namereg_is_valid_name(const char *name);
+
+#endif
diff --git a/src/pulsecore/native-common.h b/src/pulsecore/native-common.h
new file mode 100644
index 00000000..3ab2361b
--- /dev/null
+++ b/src/pulsecore/native-common.h
@@ -0,0 +1,158 @@
+#ifndef foonativecommonhfoo
+#define foonativecommonhfoo
+
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <pulse/cdecl.h>
+#include <pulse/def.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,
+
+ PA_COMMAND_ADD_AUTOLOAD,
+ PA_COMMAND_REMOVE_AUTOLOAD,
+ PA_COMMAND_GET_AUTOLOAD_INFO,
+ PA_COMMAND_GET_AUTOLOAD_INFO_LIST,
+
+ 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 v13 (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,
+
+ PA_COMMAND_MAX
+};
+
+#define PA_NATIVE_COOKIE_LENGTH 256
+#define PA_NATIVE_COOKIE_FILE ".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"
+
+
+PA_C_DECL_END
+
+#endif
diff --git a/src/pulsecore/object.c b/src/pulsecore/object.c
new file mode 100644
index 00000000..6c36242b
--- /dev/null
+++ b/src/pulsecore/object.c
@@ -0,0 +1,72 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "object.h"
+
+pa_object *pa_object_new_internal(size_t size, const char *type_name, int (*check_type)(const char *type_name)) {
+ pa_object *o;
+
+ pa_assert(size > sizeof(pa_object));
+ pa_assert(type_name);
+
+ if (!check_type)
+ check_type = pa_object_check_type;
+
+ pa_assert(check_type(type_name));
+ pa_assert(check_type("pa_object"));
+
+ o = pa_xmalloc(size);
+ PA_REFCNT_INIT(o);
+ o->type_name = type_name;
+ 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);
+ }
+}
+
+int pa_object_check_type(const char *type_name) {
+ pa_assert(type_name);
+
+ return strcmp(type_name, "pa_object") == 0;
+}
diff --git a/src/pulsecore/object.h b/src/pulsecore/object.h
new file mode 100644
index 00000000..562fd113
--- /dev/null
+++ b/src/pulsecore/object.h
@@ -0,0 +1,106 @@
+#ifndef foopulseobjecthfoo
+#define foopulseobjecthfoo
+
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <string.h>
+#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_name;
+ void (*free)(pa_object *o);
+ int (*check_type)(const char *type_name);
+};
+
+pa_object *pa_object_new_internal(size_t size, const char *type_name, int (*check_type)(const char *type_name));
+#define pa_object_new(type) ((type*) pa_object_new_internal(sizeof(type), #type, type##_check_type)
+
+#define pa_object_free ((void (*) (pa_object* o)) pa_xfree)
+
+int pa_object_check_type(const char *type);
+
+static inline int pa_object_isinstance(void *o) {
+ pa_object *obj = (pa_object*) o;
+ return obj ? obj->check_type("pa_object") : 0;
+}
+
+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"));
+ 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(c) \
+ static inline int c##_isinstance(void *o) { \
+ pa_object *obj = (pa_object*) o; \
+ return obj ? obj->check_type(#c) : 1; \
+ } \
+ static inline c* c##_cast(void *o) { \
+ pa_assert(c##_isinstance(o)); \
+ return (c*) o; \
+ } \
+ static inline c* c##_ref(c *o) { \
+ return (c*) 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_DEFINE_CHECK_TYPE(c, parent) \
+ int c##_check_type(const char *type) { \
+ pa_assert(type); \
+ if (strcmp(type, #c) == 0) \
+ return 1; \
+ return parent##_check_type(type); \
+ } \
+ 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 00000000..a358cf65
--- /dev/null
+++ b/src/pulsecore/once.c
@@ -0,0 +1,96 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulsecore/macro.h>
+#include <pulsecore/mutex.h>
+
+#include "once.h"
+
+int pa_once_begin(pa_once *control) {
+ pa_mutex *m;
+
+ pa_assert(control);
+
+ if (pa_atomic_load(&control->done))
+ return 0;
+
+ pa_atomic_inc(&control->ref);
+
+ /* 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. */
+
+ for (;;) {
+
+ if ((m = pa_atomic_ptr_load(&control->mutex))) {
+
+ /* The mutex is stored in locked state, hence let's just
+ * wait until it is unlocked */
+ pa_mutex_lock(m);
+
+ pa_once_end(control);
+ return 0;
+ }
+
+ pa_assert_se(m = pa_mutex_new(FALSE, FALSE));
+ pa_mutex_lock(m);
+
+ if (pa_atomic_ptr_cmpxchg(&control->mutex, NULL, m))
+ return 1;
+
+ pa_mutex_unlock(m);
+ pa_mutex_free(m);
+ }
+}
+
+void pa_once_end(pa_once *control) {
+ pa_mutex *m;
+
+ pa_assert(control);
+
+ pa_atomic_store(&control->done, 1);
+
+ pa_assert_se(m = pa_atomic_ptr_load(&control->mutex));
+ pa_mutex_unlock(m);
+
+ if (pa_atomic_dec(&control->ref) <= 1) {
+ pa_assert_se(pa_atomic_ptr_cmpxchg(&control->mutex, m, NULL));
+ pa_mutex_free(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 00000000..c9fe6d0a
--- /dev/null
+++ b/src/pulsecore/once.h
@@ -0,0 +1,76 @@
+#ifndef foopulseoncehfoo
+#define foopulseoncehfoo
+
+/* $Id$ */
+
+/***
+ 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 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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 <pulsecore/mutex.h>
+#include <pulsecore/atomic.h>
+
+typedef struct pa_once {
+ pa_atomic_ptr_t mutex;
+ pa_atomic_t ref, done;
+} pa_once;
+
+#define PA_ONCE_INIT \
+ { \
+ .mutex = PA_ATOMIC_PTR_INIT(NULL), \
+ .ref = PA_ATOMIC_INIT(0), \
+ .done = PA_ATOMIC_INIT(0) \
+ }
+
+/* Not to be called directly, use the macros defined below instead */
+int 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 00000000..2706efea
--- /dev/null
+++ b/src/pulsecore/packet.c
@@ -0,0 +1,81 @@
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/macro.h>
+
+#include "packet.h"
+
+pa_packet* pa_packet_new(size_t length) {
+ pa_packet *p;
+
+ pa_assert(length > 0);
+
+ p = pa_xmalloc(PA_ALIGN(sizeof(pa_packet)) + length);
+ PA_REFCNT_INIT(p);
+ p->length = length;
+ p->data = (uint8_t*) p + PA_ALIGN(sizeof(pa_packet));
+ p->type = PA_PACKET_APPENDED;
+
+ return p;
+}
+
+pa_packet* pa_packet_new_dynamic(void* data, size_t length) {
+ pa_packet *p;
+
+ pa_assert(data);
+ pa_assert(length > 0);
+
+ p = pa_xnew(pa_packet, 1);
+ PA_REFCNT_INIT(p);
+ p->length = length;
+ p->data = data;
+ p->type = PA_PACKET_DYNAMIC;
+
+ return p;
+}
+
+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);
+ pa_xfree(p);
+ }
+}
diff --git a/src/pulsecore/packet.h b/src/pulsecore/packet.h
new file mode 100644
index 00000000..bcac4a7f
--- /dev/null
+++ b/src/pulsecore/packet.h
@@ -0,0 +1,45 @@
+#ifndef foopackethfoo
+#define foopackethfoo
+
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <sys/types.h>
+#include <inttypes.h>
+
+#include <pulsecore/refcnt.h>
+
+typedef struct pa_packet {
+ PA_REFCNT_DECLARE;
+ enum { PA_PACKET_APPENDED, PA_PACKET_DYNAMIC } type;
+ size_t length;
+ uint8_t *data;
+} pa_packet;
+
+pa_packet* pa_packet_new(size_t length);
+pa_packet* pa_packet_new_dynamic(void* data, size_t length);
+
+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 00000000..149c9e00
--- /dev/null
+++ b/src/pulsecore/parseaddr.c
@@ -0,0 +1,124 @@
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <pulse/xmalloc.h>
+#include <pulse/util.h>
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/macro.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] == ':')
+ *ret_port = atoi(e+2);
+ else if (e[1] != 0)
+ return NULL;
+
+ return pa_xstrndup(s+1, e-s-1);
+ } else {
+ char *e;
+
+ if (!(e = strrchr(s, ':')))
+ return pa_xstrdup(s);
+
+ *ret_port = atoi(e+1);
+ return pa_xstrndup(s, 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 hn[256], *pfx;
+ /* The URL starts with a host specification for detecting local connections */
+
+ if (!pa_get_host_name(hn, sizeof(hn)))
+ return -1;
+
+ pfx = pa_sprintf_malloc("{%s}", hn);
+ 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;
+}
diff --git a/src/pulsecore/parseaddr.h b/src/pulsecore/parseaddr.h
new file mode 100644
index 00000000..fd7cad3b
--- /dev/null
+++ b/src/pulsecore/parseaddr.h
@@ -0,0 +1,44 @@
+#ifndef fooparseaddrhfoo
+#define fooparseaddrhfoo
+
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <inttypes.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);
+
+#endif
diff --git a/src/pulsecore/pdispatch.c b/src/pulsecore/pdispatch.c
new file mode 100644
index 00000000..bdd7cde1
--- /dev/null
+++ b/src/pulsecore/pdispatch.c
@@ -0,0 +1,348 @@
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.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 "pdispatch.h"
+
+/*#define DEBUG_OPCODES */
+
+#ifdef DEBUG_OPCODES
+
+static const char *command_names[PA_COMMAND_MAX] = {
+ [PA_COMMAND_ERROR] = "ERROR",
+ [PA_COMMAND_TIMEOUT] = "TIMEOUT",
+ [PA_COMMAND_REPLY] = "REPLY",
+ [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_REQUEST] = "REQUEST",
+ [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_PLAYBACK_STREAM_KILLED] = "PLAYBACK_STREAM_KILLED",
+ [PA_COMMAND_RECORD_STREAM_KILLED] = "RECORD_STREAM_KILLED",
+ [PA_COMMAND_STAT] = "STAT",
+ [PA_COMMAND_GET_PLAYBACK_LATENCY] = "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_SUBSCRIBE_EVENT] = "SUBSCRIBE_EVENT",
+ [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_VOLME",
+ [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_GET_AUTOLOAD_INFO] = "GET_AUTOLOAD_INFO",
+ [PA_COMMAND_GET_AUTOLOAD_INFO_LIST] = "GET_AUTOLOAD_INFO_LIST",
+};
+
+#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_callback drain_callback;
+ void *drain_userdata;
+ const pa_creds *creds;
+};
+
+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, const pa_pdispatch_cb_t*table, unsigned entries) {
+ pa_pdispatch *pd;
+ pa_assert(mainloop);
+
+ pa_assert((entries && table) || (!entries && !table));
+
+ pd = pa_xnew(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->drain_callback = NULL;
+ pd->drain_userdata = NULL;
+ pd->creds = NULL;
+
+ 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, const pa_creds *creds, void *userdata) {
+ uint32_t tag, command;
+ pa_tagstruct *ts = NULL;
+ int ret = -1;
+
+ pa_assert(pd);
+ pa_assert(PA_REFCNT_VALUE(pd) >= 1);
+ pa_assert(packet);
+ pa_assert(PA_REFCNT_VALUE(packet) >= 1);
+ pa_assert(packet->data);
+
+ pa_pdispatch_ref(pd);
+
+ if (packet->length <= 8)
+ goto finish;
+
+ ts = pa_tagstruct_new(packet->data, packet->length);
+
+ if (pa_tagstruct_getu32(ts, &command) < 0 ||
+ pa_tagstruct_getu32(ts, &tag) < 0)
+ goto finish;
+
+#ifdef DEBUG_OPCODES
+{
+ char t[256];
+ char const *p;
+ if (!(p = command_names[command]))
+ pa_snprintf((char*) (p = t), sizeof(t), "%u", command);
+
+ pa_log("Recieved opcode <%s>", p);
+}
+#endif
+
+ pd->creds = creds;
+
+ if (command == PA_COMMAND_ERROR || command == PA_COMMAND_REPLY) {
+ struct reply_info *r;
+
+ for (r = pd->replies; r; r = r->next)
+ 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 *c = pd->callback_table+command;
+
+ (*c)(pd, command, tag, ts, userdata);
+ } else {
+ pa_log("Recieved unsupported command %u", command);
+ goto finish;
+ }
+
+ ret = 0;
+
+finish:
+ pd->creds = 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, PA_GCC_UNUSED const struct timeval *tv, 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_gettimeofday(&tv);
+ tv.tv_sec += timeout;
+
+ pa_assert_se(r->time_event = pd->mainloop->time_new(pd->mainloop, &tv, 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, void (*cb)(pa_pdispatch *pd, void *userdata), 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);
+
+ for (r = pd->replies; r; r = n) {
+ n = r->next;
+
+ 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;
+}
+
+const pa_creds * pa_pdispatch_creds(pa_pdispatch *pd) {
+ pa_assert(pd);
+ pa_assert(PA_REFCNT_VALUE(pd) >= 1);
+
+ return pd->creds;
+}
diff --git a/src/pulsecore/pdispatch.h b/src/pulsecore/pdispatch.h
new file mode 100644
index 00000000..de0aa3ec
--- /dev/null
+++ b/src/pulsecore/pdispatch.h
@@ -0,0 +1,59 @@
+#ifndef foopdispatchhfoo
+#define foopdispatchhfoo
+
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#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_callback)(pa_pdispatch *pd, void *userdata);
+
+pa_pdispatch* pa_pdispatch_new(pa_mainloop_api *m, 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, const pa_creds *creds, 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_callback 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);
+
+#endif
diff --git a/src/pulsecore/pid.c b/src/pulsecore/pid.c
new file mode 100644
index 00000000..f3c9faaa
--- /dev/null
+++ b/src/pulsecore/pid.c
@@ -0,0 +1,332 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#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 <pulse/util.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);
+ return (pid_t) -1;
+ }
+
+ return (pid_t) pid;
+}
+
+static int open_pid_file(const char *fn, int mode) {
+ int fd = -1;
+
+ pa_assert(fn);
+
+ for (;;) {
+ struct stat st;
+
+ if ((fd = open(fn, mode
+#ifdef O_NOCTTY
+ |O_NOCTTY
+#endif
+#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 ye, w'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;
+ }
+
+ fd = -1;
+ }
+
+ return fd;
+
+fail:
+
+ if (fd >= 0) {
+ pa_lock_fd(fd, 0);
+ pa_close(fd);
+ }
+
+ return -1;
+}
+
+/* Create a new PID file for the current process. */
+int pa_pid_file_create(void) {
+ int fd = -1;
+ int ret = -1;
+ char fn[PATH_MAX];
+ char t[20];
+ pid_t pid;
+ size_t l;
+
+#ifdef OS_IS_WIN32
+ HANDLE process;
+#endif
+
+ pa_runtime_path("pid", fn, sizeof(fn));
+
+ 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) {
+#ifdef OS_IS_WIN32
+ if ((process = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid)) != NULL) {
+ CloseHandle(process);
+#else
+ if (kill(pid, 0) >= 0 || errno != ESRCH) {
+#endif
+ pa_log("Daemon already running.");
+ goto fail;
+ }
+
+ pa_log_warn("Stale PID file, overwriting.");
+ }
+
+ /* Overwrite the current PID file */
+ if (lseek(fd, 0, SEEK_SET) == (off_t) -1 || ftruncate(fd, 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;
+ }
+ }
+
+ return ret;
+}
+
+/* Remove the PID file, if it is ours */
+int pa_pid_file_remove(void) {
+ int fd = -1;
+ char fn[PATH_MAX];
+ int ret = -1;
+ pid_t pid;
+
+ pa_runtime_path("pid", fn, sizeof(fn));
+
+ 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, 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);
+ 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;
+ }
+ }
+
+ return ret;
+}
+
+/* Check whether the daemon is currently running, i.e. if a PID file
+ * exists and the PID therein too. Returns 0 on succcess, -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 *binary_name) {
+ return pa_pid_file_kill(0, pid, binary_name);
+}
+
+#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 *binary_name) {
+ int fd = -1;
+ char fn[PATH_MAX];
+ int ret = -1;
+ pid_t _pid;
+#ifdef __linux__
+ char *e = NULL;
+#endif
+ if (!pid)
+ pid = &_pid;
+
+ pa_runtime_path("pid", fn, sizeof(fn));
+
+ if ((fd = open_pid_file(fn, O_RDONLY)) < 0)
+ goto fail;
+
+ if ((*pid = read_pid(fn, fd)) == (pid_t) -1)
+ goto fail;
+
+#ifdef __linux__
+ if (binary_name) {
+ pa_snprintf(fn, sizeof(fn), "/proc/%lu/exe", (unsigned long) pid);
+
+ if ((e = pa_readlink(fn))) {
+ char *f = pa_path_get_filename(e);
+ if (strcmp(f, binary_name)
+#if defined(__OPTIMIZE__)
+ /* libtool likes to rename our binary names ... */
+ && !(pa_startswith(f, "lt-") && strcmp(f+3, binary_name) == 0)
+#endif
+ )
+ goto fail;
+ }
+ }
+#endif
+
+ ret = kill(*pid, sig);
+
+fail:
+
+ if (fd >= 0) {
+ pa_lock_fd(fd, 0);
+ pa_close(fd);
+ }
+
+#ifdef __linux__
+ pa_xfree(e);
+#endif
+
+ 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 00000000..1d6de7b5
--- /dev/null
+++ b/src/pulsecore/pid.h
@@ -0,0 +1,32 @@
+#ifndef foopidhfoo
+#define foopidhfoo
+
+/* $Id$ */
+
+/***
+ 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 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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.
+***/
+
+int pa_pid_file_create(void);
+int pa_pid_file_remove(void);
+int pa_pid_file_check_running(pid_t *pid, const char *binary_name);
+int pa_pid_file_kill(int sig, pid_t *pid, const char *binary_name);
+
+#endif
diff --git a/src/pulsecore/pipe.c b/src/pulsecore/pipe.c
new file mode 100644
index 00000000..e614c9c6
--- /dev/null
+++ b/src/pulsecore/pipe.c
@@ -0,0 +1,162 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <sys/types.h>
+
+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+
+#include "winsock.h"
+
+#include "pipe.h"
+
+#ifndef HAVE_PIPE
+
+static int set_block(int fd, int blocking) {
+#ifdef O_NONBLOCK
+
+ int v;
+
+ 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[0]);
+
+ return -1;
+}
+
+#endif /* HAVE_PIPE */
diff --git a/src/pulsecore/pipe.h b/src/pulsecore/pipe.h
new file mode 100644
index 00000000..e013a2e7
--- /dev/null
+++ b/src/pulsecore/pipe.h
@@ -0,0 +1,33 @@
+#ifndef foopipehfoo
+#define foopipehfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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.
+***/
+
+#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 00000000..5d3c2d39
--- /dev/null
+++ b/src/pulsecore/play-memblockq.c
@@ -0,0 +1,236 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/sink-input.h>
+#include <pulsecore/gccmacro.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_DECLARE_CLASS(memblockq_stream);
+#define MEMBLOCKQ_STREAM(o) (memblockq_stream_cast(o))
+static PA_DEFINE_CHECK_TYPE(memblockq_stream, pa_msgobject);
+
+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);
+
+ memblockq_stream_unlink(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) {
+ pa_sink_input_assert_ref(i);
+
+ memblockq_stream_unlink(MEMBLOCKQ_STREAM(i->userdata));
+}
+
+static int sink_input_peek_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) {
+ memblockq_stream *u;
+
+ pa_assert(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) {
+ 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;
+ }
+
+ return 0;
+}
+
+static void sink_input_drop_cb(pa_sink_input *i, size_t length) {
+ memblockq_stream *u;
+
+ pa_assert(i);
+ pa_assert(length > 0);
+ u = MEMBLOCKQ_STREAM(i->userdata);
+ memblockq_stream_assert_ref(u);
+
+ if (!u->memblockq)
+ return;
+
+ pa_memblockq_drop(u->memblockq, length);
+}
+
+pa_sink_input* pa_memblockq_sink_input_new(
+ pa_sink *sink,
+ const char *name,
+ const pa_sample_spec *ss,
+ const pa_channel_map *map,
+ pa_memblockq *q,
+ pa_cvolume *volume) {
+
+ 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 */
+
+ if (q && pa_memblockq_get_length(q) <= 0) {
+ pa_memblockq_free(q);
+ return NULL;
+ }
+
+ if (volume && pa_cvolume_is_muted(volume)) {
+ pa_memblockq_free(q);
+ return NULL;
+ }
+
+ 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 = q;
+
+ pa_sink_input_new_data_init(&data);
+ data.sink = sink;
+ data.name = name;
+ 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);
+
+ if (!(u->sink_input = pa_sink_input_new(sink->core, &data, 0)))
+ goto fail;
+
+ u->sink_input->peek = sink_input_peek_cb;
+ u->sink_input->drop = sink_input_drop_cb;
+ u->sink_input->kill = sink_input_kill_cb;
+ u->sink_input->userdata = u;
+
+ if (q)
+ pa_memblockq_prebuf_disable(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 char *name,
+ const pa_sample_spec *ss,
+ const pa_channel_map *map,
+ pa_memblockq *q,
+ pa_cvolume *volume) {
+
+ pa_sink_input *i;
+
+ pa_assert(sink);
+ pa_assert(ss);
+ pa_assert(q);
+
+ if (!(i = pa_memblockq_sink_input_new(sink, name, ss, map, q, volume)))
+ return -1;
+
+ pa_sink_input_put(i);
+ 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);
+ u->memblockq = q;
+}
diff --git a/src/pulsecore/play-memblockq.h b/src/pulsecore/play-memblockq.h
new file mode 100644
index 00000000..d8790316
--- /dev/null
+++ b/src/pulsecore/play-memblockq.h
@@ -0,0 +1,48 @@
+#ifndef fooplaymemblockqhfoo
+#define fooplaymemblockqhfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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 <pulsecore/sink.h>
+#include <pulsecore/memblockq.h>
+
+pa_sink_input* pa_memblockq_sink_input_new(
+ pa_sink *sink,
+ const char *name,
+ const pa_sample_spec *ss,
+ const pa_channel_map *map,
+ pa_memblockq *q,
+ pa_cvolume *volume);
+
+void pa_memblockq_sink_input_set_queue(pa_sink_input *i, pa_memblockq *q);
+
+int pa_play_memblockq(
+ pa_sink *sink,
+ const char *name,
+ const pa_sample_spec *ss,
+ const pa_channel_map *map,
+ pa_memblockq *q,
+ pa_cvolume *cvolume);
+
+#endif
diff --git a/src/pulsecore/play-memchunk.c b/src/pulsecore/play-memchunk.c
new file mode 100644
index 00000000..6aaec567
--- /dev/null
+++ b/src/pulsecore/play-memchunk.c
@@ -0,0 +1,196 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/sink-input.h>
+#include <pulsecore/gccmacro.h>
+#include <pulsecore/thread-mq.h>
+
+#include "play-memchunk.h"
+
+typedef struct memchunk_stream {
+ pa_msgobject parent;
+ pa_core *core;
+ pa_sink_input *sink_input;
+ pa_memchunk memchunk;
+} memchunk_stream;
+
+enum {
+ MEMCHUNK_STREAM_MESSAGE_UNLINK,
+};
+
+PA_DECLARE_CLASS(memchunk_stream);
+#define MEMCHUNK_STREAM(o) (memchunk_stream_cast(o))
+static PA_DEFINE_CHECK_TYPE(memchunk_stream, pa_msgobject);
+
+static void memchunk_stream_unlink(memchunk_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;
+
+ memchunk_stream_unref(u);
+}
+
+static void memchunk_stream_free(pa_object *o) {
+ memchunk_stream *u = MEMCHUNK_STREAM(o);
+ pa_assert(u);
+
+ memchunk_stream_unlink(u);
+
+ if (u->memchunk.memblock)
+ pa_memblock_unref(u->memchunk.memblock);
+
+ pa_xfree(u);
+}
+
+static int memchunk_stream_process_msg(pa_msgobject *o, int code, void*userdata, int64_t offset, pa_memchunk *chunk) {
+ memchunk_stream *u = MEMCHUNK_STREAM(o);
+ memchunk_stream_assert_ref(u);
+
+ switch (code) {
+ case MEMCHUNK_STREAM_MESSAGE_UNLINK:
+ memchunk_stream_unlink(u);
+ break;
+ }
+
+ return 0;
+}
+
+static void sink_input_kill_cb(pa_sink_input *i) {
+ pa_sink_input_assert_ref(i);
+
+ memchunk_stream_unlink(MEMCHUNK_STREAM(i->userdata));
+}
+
+static int sink_input_peek_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) {
+ memchunk_stream *u;
+
+ pa_assert(i);
+ pa_assert(chunk);
+ u = MEMCHUNK_STREAM(i->userdata);
+ memchunk_stream_assert_ref(u);
+
+ if (!u->memchunk.memblock)
+ return -1;
+
+ if (u->memchunk.length <= 0) {
+ pa_memblock_unref(u->memchunk.memblock);
+ u->memchunk.memblock = NULL;
+ pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(u), MEMCHUNK_STREAM_MESSAGE_UNLINK, NULL, 0, NULL, NULL);
+ return -1;
+ }
+
+ pa_assert(u->memchunk.memblock);
+ *chunk = u->memchunk;
+ pa_memblock_ref(chunk->memblock);
+
+ return 0;
+}
+
+static void sink_input_drop_cb(pa_sink_input *i, size_t length) {
+ memchunk_stream *u;
+
+ pa_assert(i);
+ pa_assert(length > 0);
+ u = MEMCHUNK_STREAM(i->userdata);
+ memchunk_stream_assert_ref(u);
+
+ if (length < u->memchunk.length) {
+ u->memchunk.length -= length;
+ u->memchunk.index += length;
+ } else
+ u->memchunk.length = 0;
+}
+
+int pa_play_memchunk(
+ pa_sink *sink,
+ const char *name,
+ const pa_sample_spec *ss,
+ const pa_channel_map *map,
+ const pa_memchunk *chunk,
+ pa_cvolume *volume) {
+
+ memchunk_stream *u = NULL;
+ pa_sink_input_new_data data;
+
+ pa_assert(sink);
+ pa_assert(ss);
+ pa_assert(chunk);
+
+ if (volume && pa_cvolume_is_muted(volume))
+ return 0;
+
+ pa_memchunk_will_need(chunk);
+
+ u = pa_msgobject_new(memchunk_stream);
+ u->parent.parent.free = memchunk_stream_free;
+ u->parent.process_msg = memchunk_stream_process_msg;
+ u->core = sink->core;
+ u->memchunk = *chunk;
+ pa_memblock_ref(u->memchunk.memblock);
+
+ pa_sink_input_new_data_init(&data);
+ data.sink = sink;
+ data.driver = __FILE__;
+ data.name = name;
+ 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);
+
+ if (!(u->sink_input = pa_sink_input_new(sink->core, &data, 0)))
+ goto fail;
+
+ u->sink_input->peek = sink_input_peek_cb;
+ u->sink_input->drop = sink_input_drop_cb;
+ u->sink_input->kill = sink_input_kill_cb;
+ u->sink_input->userdata = u;
+
+ 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:
+ if (u)
+ memchunk_stream_unref(u);
+
+ return -1;
+}
+
diff --git a/src/pulsecore/play-memchunk.h b/src/pulsecore/play-memchunk.h
new file mode 100644
index 00000000..5afb094c
--- /dev/null
+++ b/src/pulsecore/play-memchunk.h
@@ -0,0 +1,38 @@
+#ifndef fooplaychunkhfoo
+#define fooplaychunkhfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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 <pulsecore/sink.h>
+#include <pulsecore/memchunk.h>
+
+int pa_play_memchunk(
+ pa_sink *sink,
+ const char *name,
+ const pa_sample_spec *ss,
+ const pa_channel_map *map,
+ const pa_memchunk *chunk,
+ pa_cvolume *cvolume);
+
+#endif
diff --git a/src/pulsecore/poll.c b/src/pulsecore/poll.c
new file mode 100644
index 00000000..288f7dfb
--- /dev/null
+++ b/src/pulsecore/poll.c
@@ -0,0 +1,196 @@
+/* $Id$ */
+
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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.
+***/
+
+/***
+ 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
+
+#include <errno.h>
+
+#ifdef HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#endif
+
+#include "winsock.h"
+
+#ifndef HAVE_POLL_H
+
+#include <pulsecore/core-util.h>
+
+#include "poll.h"
+
+int 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;
+ char data[64];
+
+ 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 ((SELECT_TYPE_ARG1) maxfd + 1, SELECT_TYPE_ARG234 &rset,
+ SELECT_TYPE_ARG234 &wset, SELECT_TYPE_ARG234 &xset,
+ SELECT_TYPE_ARG5 (timeout == -1 ? NULL : &tv));
+ if ((ready == -1) && (errno == EBADF)) {
+ ready = 0;
+
+ FD_ZERO (&rset);
+ FD_ZERO (&wset);
+ FD_ZERO (&xset);
+
+ maxfd = -1;
+
+ 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((SELECT_TYPE_ARG1) f->fd, SELECT_TYPE_ARG234 &rset,
+ SELECT_TYPE_ARG234 &wset, SELECT_TYPE_ARG234 &xset,
+ SELECT_TYPE_ARG5 &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;
+ }
+ }
+ }
+
+ 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 ((SELECT_TYPE_ARG1) maxfd + 1, SELECT_TYPE_ARG234 &rset,
+ SELECT_TYPE_ARG234 &wset, SELECT_TYPE_ARG234 &xset,
+ SELECT_TYPE_ARG5 (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! */
+ 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;
+ }
+ }
+
+ 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.h b/src/pulsecore/poll.h
new file mode 100644
index 00000000..6be6069b
--- /dev/null
+++ b/src/pulsecore/poll.h
@@ -0,0 +1,60 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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.
+***/
+
+/***
+ Based on work for the GNU C Library.
+ Copyright (C) 1994,96,97,98,99,2000,2001,2004 Free Software Foundation, Inc.
+***/
+
+/* 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. */
+
+
+/* Type used for the number of file descriptors. */
+typedef unsigned long int nfds_t;
+
+/* 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. */
+extern int poll (struct pollfd *__fds, nfds_t __nfds, int __timeout);
diff --git a/src/pulsecore/props.c b/src/pulsecore/props.c
new file mode 100644
index 00000000..cbf748df
--- /dev/null
+++ b/src/pulsecore/props.c
@@ -0,0 +1,140 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+
+#include "props.h"
+
+typedef struct pa_property {
+ char *name; /* Points to memory allocated by the property subsystem */
+ void *data; /* Points to memory maintained by the caller */
+} pa_property;
+
+/* Allocate a new property object */
+static pa_property* property_new(const char *name, void *data) {
+ pa_property* p;
+
+ pa_assert(name);
+ pa_assert(data);
+
+ p = pa_xnew(pa_property, 1);
+ p->name = pa_xstrdup(name);
+ p->data = data;
+
+ return p;
+}
+
+/* Free a property object */
+static void property_free(pa_property *p) {
+ pa_assert(p);
+
+ pa_xfree(p->name);
+ pa_xfree(p);
+}
+
+void* pa_property_get(pa_core *c, const char *name) {
+ pa_property *p;
+
+ pa_assert(c);
+ pa_assert(name);
+ pa_assert(c->properties);
+
+ if (!(p = pa_hashmap_get(c->properties, name)))
+ return NULL;
+
+ return p->data;
+}
+
+int pa_property_set(pa_core *c, const char *name, void *data) {
+ pa_property *p;
+
+ pa_assert(c);
+ pa_assert(name);
+ pa_assert(data);
+ pa_assert(c->properties);
+
+ if (pa_hashmap_get(c->properties, name))
+ return -1;
+
+ p = property_new(name, data);
+ pa_hashmap_put(c->properties, p->name, p);
+ return 0;
+}
+
+int pa_property_remove(pa_core *c, const char *name) {
+ pa_property *p;
+
+ pa_assert(c);
+ pa_assert(name);
+ pa_assert(c->properties);
+
+ if (!(p = pa_hashmap_remove(c->properties, name)))
+ return -1;
+
+ property_free(p);
+ return 0;
+}
+
+void pa_property_init(pa_core *c) {
+ pa_assert(c);
+
+ c->properties = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+}
+
+void pa_property_cleanup(pa_core *c) {
+ pa_assert(c);
+
+ if (!c->properties)
+ return;
+
+ pa_assert(!pa_hashmap_size(c->properties));
+
+ pa_hashmap_free(c->properties, NULL, NULL);
+ c->properties = NULL;
+
+}
+
+void pa_property_dump(pa_core *c, pa_strbuf *s) {
+ void *state = NULL;
+ pa_property *p;
+
+ pa_assert(c);
+ pa_assert(s);
+
+ while ((p = pa_hashmap_iterate(c->properties, &state, NULL)))
+ pa_strbuf_printf(s, "[%s] -> [%p]\n", p->name, p->data);
+}
+
+int pa_property_replace(pa_core *c, const char *name, void *data) {
+ pa_assert(c);
+ pa_assert(name);
+
+ pa_property_remove(c, name);
+ return pa_property_set(c, name, data);
+}
diff --git a/src/pulsecore/props.h b/src/pulsecore/props.h
new file mode 100644
index 00000000..880325f6
--- /dev/null
+++ b/src/pulsecore/props.h
@@ -0,0 +1,60 @@
+#ifndef foopropshfoo
+#define foopropshfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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 <pulsecore/core.h>
+#include <pulsecore/strbuf.h>
+
+/* The 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. */
+
+/* Return a pointer to the value of the specified property. */
+void* pa_property_get(pa_core *c, const char *name);
+
+/* Set the 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_property_set(pa_core *c, const char *name, void *data);
+
+/* Remove the specified property. Return non-zero on failure */
+int pa_property_remove(pa_core *c, const char *name);
+
+/* A combination of pa_property_remove() and pa_property_set() */
+int pa_property_replace(pa_core *c, const char *name, void *data);
+
+/* Free all memory used by the property system */
+void pa_property_cleanup(pa_core *c);
+
+/* Initialize the properties subsystem */
+void pa_property_init(pa_core *c);
+
+/* Dump the current set of properties */
+void pa_property_dump(pa_core *c, pa_strbuf *s);
+
+#endif
diff --git a/src/pulsecore/protocol-cli.c b/src/pulsecore/protocol-cli.c
new file mode 100644
index 00000000..ceb6ae4d
--- /dev/null
+++ b/src/pulsecore/protocol-cli.c
@@ -0,0 +1,105 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/cli.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+
+#include "protocol-cli.h"
+
+/* Don't allow more than this many concurrent connections */
+#define MAX_CONNECTIONS 25
+
+struct pa_protocol_cli {
+ pa_module *module;
+ pa_core *core;
+ pa_socket_server*server;
+ pa_idxset *connections;
+};
+
+static void cli_eof_cb(pa_cli*c, void*userdata) {
+ pa_protocol_cli *p = userdata;
+ pa_assert(p);
+
+ pa_idxset_remove_by_data(p->connections, c, NULL);
+ pa_cli_free(c);
+}
+
+static void on_connection(pa_socket_server*s, pa_iochannel *io, void *userdata) {
+ pa_protocol_cli *p = userdata;
+ pa_cli *c;
+
+ pa_assert(s);
+ pa_assert(io);
+ pa_assert(p);
+
+ 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, p->module);
+ pa_cli_set_eof_callback(c, cli_eof_cb, p);
+
+ pa_idxset_put(p->connections, c, NULL);
+}
+
+pa_protocol_cli* pa_protocol_cli_new(pa_core *core, pa_socket_server *server, pa_module *m, PA_GCC_UNUSED pa_modargs *ma) {
+ pa_protocol_cli* p;
+
+ pa_core_assert_ref(core);
+ pa_assert(server);
+
+ p = pa_xnew(pa_protocol_cli, 1);
+ p->module = m;
+ p->core = core;
+ p->server = server;
+ p->connections = pa_idxset_new(NULL, NULL);
+
+ pa_socket_server_set_callback(p->server, on_connection, p);
+
+ return p;
+}
+
+static void free_connection(void *p, PA_GCC_UNUSED void *userdata) {
+ pa_assert(p);
+
+ pa_cli_free(p);
+}
+
+void pa_protocol_cli_free(pa_protocol_cli *p) {
+ pa_assert(p);
+
+ pa_idxset_free(p->connections, free_connection, NULL);
+ pa_socket_server_unref(p->server);
+ pa_xfree(p);
+}
diff --git a/src/pulsecore/protocol-cli.h b/src/pulsecore/protocol-cli.h
new file mode 100644
index 00000000..3870def3
--- /dev/null
+++ b/src/pulsecore/protocol-cli.h
@@ -0,0 +1,37 @@
+#ifndef fooprotocolclihfoo
+#define fooprotocolclihfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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 <pulsecore/core.h>
+#include <pulsecore/socket-server.h>
+#include <pulsecore/module.h>
+#include <pulsecore/modargs.h>
+
+typedef struct pa_protocol_cli pa_protocol_cli;
+
+pa_protocol_cli* pa_protocol_cli_new(pa_core *core, pa_socket_server *server, pa_module *m, pa_modargs *ma);
+void pa_protocol_cli_free(pa_protocol_cli *n);
+
+#endif
diff --git a/src/pulsecore/protocol-esound.c b/src/pulsecore/protocol-esound.c
new file mode 100644
index 00000000..004e535e
--- /dev/null
+++ b/src/pulsecore/protocol-esound.c
@@ -0,0 +1,1471 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+
+#include <pulse/sample.h>
+#include <pulse/timeval.h>
+#include <pulse/utf8.h>
+#include <pulse/xmalloc.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/authkey.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 "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
+
+#define DEFAULT_COOKIE_FILE ".esd_auth"
+
+#define PLAYBACK_BUFFER_SECONDS (.25)
+#define PLAYBACK_BUFFER_FRAGMENTS (10)
+#define RECORD_BUFFER_SECONDS (5)
+#define RECORD_BUFFER_FRAGMENTS (100)
+
+#define MAX_CACHE_SAMPLE_SIZE (1024000)
+
+#define SCACHE_PREFIX "esound."
+
+/* This is heavily based on esound's code */
+
+typedef struct connection {
+ pa_msgobject parent;
+
+ uint32_t index;
+ pa_bool_t dead;
+ pa_protocol_esound *protocol;
+ pa_iochannel *io;
+ pa_client *client;
+ pa_bool_t 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, fragment_size;
+ pa_atomic_t missing;
+ } playback;
+
+ struct {
+ pa_memchunk memchunk;
+ char *name;
+ pa_sample_spec sample_spec;
+ } scache;
+
+ pa_time_event *auth_timeout_event;
+} connection;
+
+PA_DECLARE_CLASS(connection);
+#define CONNECTION(o) (connection_cast(o))
+static PA_DEFINE_CHECK_TYPE(connection, pa_msgobject);
+
+struct pa_protocol_esound {
+ pa_module *module;
+ pa_core *core;
+ int public;
+ pa_socket_server *server;
+ pa_idxset *connections;
+
+ char *sink_name, *source_name;
+ unsigned n_player;
+ uint8_t esd_key[ESD_KEY_LEN];
+ pa_ip_acl *auth_ip_acl;
+};
+
+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 void sink_input_drop_cb(pa_sink_input *i, size_t length);
+static int sink_input_peek_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk);
+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_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);
+
+/* 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" },
+ { -1, NULL, "TODO: sample kill" },
+
+ { ESD_KEY_LEN + sizeof(int), esd_proto_standby_or_resume, "standby" }, /* NOOP! */
+ { ESD_KEY_LEN + sizeof(int), esd_proto_standby_or_resume, "resume" }, /* NOOP! */ /* 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" },
+ { -1, NULL, "TODO: subscribe" },
+ { -1, NULL, "TODO: unsubscribe" },
+
+ { 3 * sizeof(int), esd_proto_stream_pan, "stream pan"},
+ { 3 * sizeof(int), NULL, "sample pan" },
+
+ { sizeof(int), NULL, "standby mode" },
+ { 0, esd_proto_get_latency, "get latency" }
+};
+
+static void connection_unlink(connection *c) {
+ pa_assert(c);
+
+ if (!c->protocol)
+ return;
+
+ 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, pa_bool_t swap_bytes, pa_sample_spec *ss) {
+ pa_assert(ss);
+
+ ss->channels = ((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 (!(expression)) { \
+ pa_log_warn(__FILE__ ": " __VA_ARGS__); \
+ return -1; \
+ } \
+} while(0);
+
+/*** esound commands ***/
+
+static int esd_proto_connect(connection *c, PA_GCC_UNUSED 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) {
+ if (memcmp(data, c->protocol->esd_key, ESD_KEY_LEN) != 0) {
+ pa_log("kicked client with invalid authorization key.");
+ return -1;
+ }
+
+ c->authorized = TRUE;
+ 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;
+ }
+
+ ok = 1;
+ connection_write(c, &ok, sizeof(int));
+ return 0;
+}
+
+static int esd_proto_stream_play(connection *c, PA_GCC_UNUSED 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;
+
+ 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 = rate;
+ format_esd2native(format, c->swap_byte_order, &ss);
+
+ CHECK_VALIDITY(pa_sample_spec_valid(&ss), "Invalid sample specification");
+
+ if (c->protocol->sink_name) {
+ sink = pa_namereg_get(c->protocol->core, c->protocol->sink_name, PA_NAMEREG_SINK, 1);
+ CHECK_VALIDITY(sink, "No such sink: %s", c->protocol->sink_name);
+ }
+
+ strncpy(name, data, sizeof(name));
+ name[sizeof(name)-1] = 0;
+
+ 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.sink = sink;
+ sdata.driver = __FILE__;
+ sdata.name = c->client->name;
+ pa_sink_input_new_data_set_sample_spec(&sdata, &ss);
+ sdata.module = c->protocol->module;
+ sdata.client = c->client;
+
+ c->sink_input = pa_sink_input_new(c->protocol->core, &sdata, 0);
+ CHECK_VALIDITY(c->sink_input, "Failed to create sink input.");
+
+ l = (size_t) (pa_bytes_per_second(&ss)*PLAYBACK_BUFFER_SECONDS);
+ c->input_memblockq = pa_memblockq_new(
+ 0,
+ l,
+ 0,
+ pa_frame_size(&ss),
+ (size_t) -1,
+ l/PLAYBACK_BUFFER_FRAGMENTS,
+ NULL);
+ pa_iochannel_socket_set_rcvbuf(c->io, l/PLAYBACK_BUFFER_FRAGMENTS*2);
+ c->playback.fragment_size = l/PLAYBACK_BUFFER_FRAGMENTS;
+
+ c->sink_input->parent.process_msg = sink_input_process_msg;
+ c->sink_input->peek = sink_input_peek_cb;
+ c->sink_input->drop = sink_input_drop_cb;
+ c->sink_input->kill = sink_input_kill_cb;
+ c->sink_input->userdata = c;
+
+ c->state = ESD_STREAMING_DATA;
+
+ c->protocol->n_player++;
+
+ pa_atomic_store(&c->playback.missing, pa_memblockq_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 = 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;
+
+ if (!(sink = pa_namereg_get(c->protocol->core, c->protocol->sink_name, PA_NAMEREG_SINK, 1))) {
+ pa_log("no such sink.");
+ return -1;
+ }
+
+ if (!(source = sink->monitor_source)) {
+ pa_log("no such monitor source.");
+ return -1;
+ }
+ } else {
+ pa_assert(request == ESD_PROTO_STREAM_REC);
+
+ if (c->protocol->source_name) {
+ if (!(source = pa_namereg_get(c->protocol->core, c->protocol->source_name, PA_NAMEREG_SOURCE, 1))) {
+ pa_log("no such source.");
+ return -1;
+ }
+ }
+ }
+
+ strncpy(name, data, sizeof(name));
+ name[sizeof(name)-1] = 0;
+
+ 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.source = source;
+ sdata.driver = __FILE__;
+ sdata.name = c->client->name;
+ pa_source_output_new_data_set_sample_spec(&sdata, &ss);
+ sdata.module = c->protocol->module;
+ sdata.client = c->client;
+
+ c->source_output = pa_source_output_new(c->protocol->core, &sdata, 9);
+ 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(
+ 0,
+ l,
+ 0,
+ pa_frame_size(&ss),
+ 1,
+ 0,
+ NULL);
+ pa_iochannel_socket_set_sndbuf(c->io, l/RECORD_BUFFER_FRAGMENTS*2);
+
+ 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;
+
+ 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, PA_GCC_UNUSED esd_proto_t request, const void *data, size_t length) {
+ pa_sink *sink;
+ int32_t latency;
+
+ connection_ref(c);
+ pa_assert(!data);
+ pa_assert(length == 0);
+
+ if (!(sink = pa_namereg_get(c->protocol->core, c->protocol->sink_name, PA_NAMEREG_SINK, 1)))
+ latency = 0;
+ else {
+ double usec = pa_sink_get_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, PA_GCC_UNUSED 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_ref(c);
+ pa_assert(data);
+ pa_assert(length == sizeof(int32_t));
+
+ if ((sink = pa_namereg_get(c->protocol->core, c->protocol->sink_name, PA_NAMEREG_SINK, 1))) {
+ rate = 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_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 = c->protocol->core->scache ? pa_idxset_size(c->protocol->core->scache) : 0;
+ t = s*(nsamples+1) + k*(c->protocol->n_player+1);
+
+ connection_write_prepare(c, t);
+
+ memset(terminator, 0, sizeof(terminator));
+
+ for (conn = pa_idxset_first(c->protocol->connections, &idx); conn; conn = pa_idxset_next(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);
+ rate = conn->sink_input->sample_spec.rate;
+ lvolume = (volume.values[0]*ESD_VOLUME_BASE)/PA_VOLUME_NORM;
+ rvolume = (volume.values[1]*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 && conn->client->name)
+ strncpy(name, conn->client->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;
+ for (ce = pa_idxset_first(c->protocol->core->scache, &idx); ce; ce = pa_idxset_next(c->protocol->core->scache, &idx)) {
+ int32_t id, rate, lvolume, rvolume, format, len;
+ char name[ESD_NAME_MAX];
+
+ pa_assert(t >= s*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_UINT32_SWAP(c->swap_byte_order, ce->sample_spec.rate);
+ connection_write(c, &rate, sizeof(int32_t));
+
+ /* left */
+ lvolume = PA_MAYBE_UINT32_SWAP(c->swap_byte_order, (ce->volume.values[0]*ESD_VOLUME_BASE)/PA_VOLUME_NORM);
+ connection_write(c, &lvolume, sizeof(int32_t));
+
+ /*right*/
+ rvolume = PA_MAYBE_UINT32_SWAP(c->swap_byte_order, (ce->volume.values[0]*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(&ce->sample_spec));
+ 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, PA_GCC_UNUSED 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);
+ data = (const char*)data + sizeof(uint32_t);
+
+ 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 = 2;
+ pa_sink_input_set_volume(conn->sink_input, &volume);
+ ok = 1;
+ } else
+ ok = 0;
+
+ connection_write(c, &ok, sizeof(int32_t));
+
+ return 0;
+}
+
+static int esd_proto_sample_cache(connection *c, PA_GCC_UNUSED 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 = 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);
+ strncpy(name+sizeof(SCACHE_PREFIX)-1, data, ESD_NAME_MAX);
+ name[sizeof(name)-1] = 0;
+
+ 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, sc_length);
+ c->scache.memchunk.index = 0;
+ c->scache.memchunk.length = 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, &idx);
+
+ idx += 1;
+ connection_write(c, &idx, sizeof(uint32_t));
+
+ return 0;
+}
+
+static int esd_proto_sample_get_id(connection *c, PA_GCC_UNUSED 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);
+ strncpy(name+sizeof(SCACHE_PREFIX)-1, data, ESD_NAME_MAX);
+ name[sizeof(name)-1] = 0;
+
+ 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 = 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->protocol->sink_name, PA_NAMEREG_SINK, 1)))
+ if (pa_scache_play_item(c->protocol->core, name, sink, PA_VOLUME_NORM) >= 0)
+ ok = idx + 1;
+ } else {
+ pa_assert(request == ESD_PROTO_SAMPLE_FREE);
+
+ if (pa_scache_remove_item(c->protocol->core, name) >= 0)
+ ok = idx + 1;
+ }
+ }
+
+ connection_write(c, &ok, sizeof(int32_t));
+
+ return 0;
+}
+
+static int esd_proto_standby_or_resume(connection *c, PA_GCC_UNUSED esd_proto_t request, PA_GCC_UNUSED const void *data, PA_GCC_UNUSED size_t length) {
+ int32_t ok;
+
+ connection_assert_ref(c);
+
+ connection_write_prepare(c, sizeof(int32_t) * 2);
+
+ ok = 1;
+ connection_write(c, &ok, sizeof(int32_t));
+ connection_write(c, &ok, sizeof(int32_t));
+
+ 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) {
+ pa_log_debug("read(): %s", r < 0 ? pa_cstrerror(errno) : "EOF");
+ return -1;
+ }
+
+ if ((c->read_data_length+= r) >= 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("recieved invalid request.");
+ return -1;
+ }
+
+ handler = proto_map+c->request;
+
+/* pa_log("executing request #%u", c->request); */
+
+ if (!handler->proc) {
+ pa_log("recieved 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;
+ }
+
+ if ((c->read_data_length += r) >= 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 += 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, &idx);
+
+ pa_memblock_unref(c->scache.memchunk.memblock);
+ c->scache.memchunk.memblock = NULL;
+ c->scache.memchunk.index = c->scache.memchunk.length = 0;
+
+ 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;
+
+ pa_assert(c->input_memblockq);
+
+/* pa_log("STREAMING_DATA"); */
+
+ if (!(l = pa_atomic_load(&c->playback.missing)))
+ return 0;
+
+ if (l > c->playback.fragment_size)
+ l = c->playback.fragment_size;
+
+ if (c->playback.current_memblock)
+ if (pa_memblock_get_length(c->playback.current_memblock) - c->playback.memblock_index < l) {
+ pa_memblock_unref(c->playback.current_memblock);
+ c->playback.current_memblock = NULL;
+ c->playback.memblock_index = 0;
+ }
+
+ if (!c->playback.current_memblock) {
+ pa_assert_se(c->playback.current_memblock = pa_memblock_new(c->protocol->core->mempool, c->playback.fragment_size*2));
+ c->playback.memblock_index = 0;
+ }
+
+ 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 = r;
+
+ c->playback.memblock_index += 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, r);
+ }
+
+ 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) {
+
+ if (r < 0 && (errno == EINTR || errno == EAGAIN))
+ return 0;
+
+ pa_log("write(): %s", pa_cstrerror(errno));
+ return -1;
+ }
+
+ if ((c->write_data_index +=r) >= c->write_data_length)
+ c->write_data_length = c->write_data_index = 0;
+
+ } 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) {
+
+ if (r < 0 && (errno == EINTR || errno == EAGAIN))
+ return 0;
+
+ pa_log("write(): %s", pa_cstrerror(errno));
+ return -1;
+ }
+
+ pa_memblockq_drop(c->output_memblockq, r);
+ }
+
+ 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->source_output && 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;
+
+ if (pa_iochannel_is_writable(c->io))
+ if (do_write(c) < 0)
+ goto fail;
+
+ 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);
+
+ 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);
+
+/* 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;
+
+ *r = pa_bytes_to_usec(pa_memblockq_get_length(c->input_memblockq), &c->sink_input->sample_spec);
+
+ /* Fall through, the default handler will add in the extra
+ * latency added by the resampler */
+ }
+
+ default:
+ return pa_sink_input_process_msg(o, code, userdata, offset, chunk);
+ }
+}
+
+/* Called from thread context */
+static int sink_input_peek_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) {
+ connection*c;
+ int r;
+
+ pa_assert(i);
+ c = CONNECTION(i->userdata);
+ connection_assert_ref(c);
+ pa_assert(chunk);
+
+ if ((r = pa_memblockq_peek(c->input_memblockq, chunk)) < 0 && c->dead)
+ pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(c), CONNECTION_MESSAGE_UNLINK_CONNECTION, NULL, 0, NULL, NULL);
+
+ return r;
+}
+
+/* Called from thread context */
+static void sink_input_drop_cb(pa_sink_input *i, size_t length) {
+ connection*c;
+ size_t old, new;
+
+ pa_assert(i);
+ c = CONNECTION(i->userdata);
+ connection_assert_ref(c);
+ pa_assert(length);
+
+ /* pa_log("DROP"); */
+
+ old = pa_memblockq_missing(c->input_memblockq);
+ pa_memblockq_drop(c->input_memblockq, length);
+ new = pa_memblockq_missing(c->input_memblockq);
+
+ if (new > old) {
+ if (pa_atomic_add(&c->playback.missing, new - old) <= 0)
+ pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(c), CONNECTION_MESSAGE_REQUEST_DATA, NULL, 0, NULL, NULL);
+ }
+}
+
+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_assert(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_assert(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);
+}
+
+/*** socket server callback ***/
+
+static void auth_timeout(pa_mainloop_api*m, pa_time_event *e, const struct timeval *tv, void *userdata) {
+ connection *c = CONNECTION(userdata);
+
+ pa_assert(m);
+ pa_assert(tv);
+ connection_assert_ref(c);
+ pa_assert(c->auth_timeout_event == e);
+
+ if (!c->authorized)
+ connection_unlink(c);
+}
+
+static void on_connection(pa_socket_server*s, pa_iochannel *io, void *userdata) {
+ connection *c;
+ pa_protocol_esound *p = userdata;
+ char cname[256], pname[128];
+
+ pa_assert(s);
+ pa_assert(io);
+ pa_assert(p);
+
+ 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->protocol = p;
+ c->io = io;
+ pa_iochannel_set_callback(c->io, io_callback, c);
+
+ pa_iochannel_socket_peer_to_string(io, pname, sizeof(pname));
+ pa_snprintf(cname, sizeof(cname), "EsounD client (%s)", pname);
+ c->client = pa_client_new(p->core, __FILE__, cname);
+ c->client->owner = p->module;
+ c->client->kill = client_kill_cb;
+ c->client->userdata = c;
+
+ c->authorized = !!p->public;
+ 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.fragment_size = 0;
+ pa_atomic_store(&c->playback.missing, 0);
+
+ c->scache.memchunk.length = c->scache.memchunk.index = 0;
+ c->scache.memchunk.memblock = NULL;
+ c->scache.name = NULL;
+
+ c->original_name = NULL;
+
+ if (!c->authorized && p->auth_ip_acl && pa_ip_acl_check(p->auth_ip_acl, pa_iochannel_get_recv_fd(io)) > 0) {
+ pa_log_info("Client authenticated by IP ACL.");
+ c->authorized = TRUE;
+ }
+
+ if (!c->authorized) {
+ struct timeval tv;
+ pa_gettimeofday(&tv);
+ tv.tv_sec += AUTH_TIMEOUT;
+ c->auth_timeout_event = p->core->mainloop->time_new(p->core->mainloop, &tv, 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);
+}
+
+/*** entry points ***/
+
+pa_protocol_esound* pa_protocol_esound_new(pa_core*core, pa_socket_server *server, pa_module *m, pa_modargs *ma) {
+ pa_protocol_esound *p = NULL;
+ pa_bool_t public = FALSE;
+ const char *acl;
+
+ pa_assert(core);
+ pa_assert(server);
+ pa_assert(m);
+ pa_assert(ma);
+
+ if (pa_modargs_get_value_boolean(ma, "auth-anonymous", &public) < 0) {
+ pa_log("auth-anonymous= expects a boolean argument.");
+ goto fail;
+ }
+
+ p = pa_xnew(pa_protocol_esound, 1);
+
+ if (pa_authkey_load_auto(pa_modargs_get_value(ma, "cookie", DEFAULT_COOKIE_FILE), p->esd_key, sizeof(p->esd_key)) < 0)
+ goto fail;
+
+ if ((acl = pa_modargs_get_value(ma, "auth-ip-acl", NULL))) {
+
+ if (!(p->auth_ip_acl = pa_ip_acl_new(acl))) {
+ pa_log("Failed to parse IP ACL '%s'", acl);
+ goto fail;
+ }
+ } else
+ p->auth_ip_acl = NULL;
+
+ p->core = core;
+ p->module = m;
+ p->public = public;
+ p->server = server;
+ pa_socket_server_set_callback(p->server, on_connection, p);
+ p->connections = pa_idxset_new(NULL, NULL);
+
+ p->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL));
+ p->source_name = pa_xstrdup(pa_modargs_get_value(ma, "source", NULL));
+ p->n_player = 0;
+
+ return p;
+
+fail:
+ pa_xfree(p);
+ return NULL;
+}
+
+void pa_protocol_esound_free(pa_protocol_esound *p) {
+ connection *c;
+ pa_assert(p);
+
+ while ((c = pa_idxset_first(p->connections, NULL)))
+ connection_unlink(c);
+ pa_idxset_free(p->connections, NULL, NULL);
+
+ pa_socket_server_unref(p->server);
+
+ if (p->auth_ip_acl)
+ pa_ip_acl_free(p->auth_ip_acl);
+
+ pa_xfree(p->sink_name);
+ pa_xfree(p->source_name);
+
+ pa_xfree(p);
+}
diff --git a/src/pulsecore/protocol-esound.h b/src/pulsecore/protocol-esound.h
new file mode 100644
index 00000000..868ef5d2
--- /dev/null
+++ b/src/pulsecore/protocol-esound.h
@@ -0,0 +1,38 @@
+#ifndef fooprotocolesoundhfoo
+#define fooprotocolesoundhfoo
+
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <pulsecore/core.h>
+#include <pulsecore/socket-server.h>
+#include <pulsecore/module.h>
+#include <pulsecore/modargs.h>
+
+typedef struct pa_protocol_esound pa_protocol_esound;
+
+pa_protocol_esound* pa_protocol_esound_new(pa_core*core, pa_socket_server *server, pa_module *m, pa_modargs *ma);
+void pa_protocol_esound_free(pa_protocol_esound *p);
+
+#endif
diff --git a/src/pulsecore/protocol-http.c b/src/pulsecore/protocol-http.c
new file mode 100644
index 00000000..d91ae142
--- /dev/null
+++ b/src/pulsecore/protocol-http.c
@@ -0,0 +1,277 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <pulse/util.h>
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/ioline.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/log.h>
+#include <pulsecore/namereg.h>
+#include <pulsecore/cli-text.h>
+
+#include "protocol-http.h"
+
+/* Don't allow more than this many concurrent connections */
+#define MAX_CONNECTIONS 10
+
+#define internal_server_error(c) http_message((c), 500, "Internal Server Error", NULL)
+
+#define URL_ROOT "/"
+#define URL_CSS "/style"
+#define URL_STATUS "/status"
+
+struct connection {
+ pa_protocol_http *protocol;
+ pa_ioline *line;
+ enum { REQUEST_LINE, MIME_HEADER, DATA } state;
+ char *url;
+};
+
+struct pa_protocol_http {
+ pa_module *module;
+ pa_core *core;
+ pa_socket_server*server;
+ pa_idxset *connections;
+};
+
+static void http_response(struct connection *c, int code, const char *msg, const char *mime) {
+ char s[256];
+
+ pa_assert(c);
+ pa_assert(msg);
+ pa_assert(mime);
+
+ pa_snprintf(s, sizeof(s),
+ "HTTP/1.0 %i %s\n"
+ "Connection: close\n"
+ "Content-Type: %s\n"
+ "Cache-Control: no-cache\n"
+ "Expires: 0\n"
+ "Server: "PACKAGE_NAME"/"PACKAGE_VERSION"\n"
+ "\n", code, msg, mime);
+
+ pa_ioline_puts(c->line, s);
+}
+
+static void http_message(struct connection *c, int code, const char *msg, const char *text) {
+ char s[256];
+ pa_assert(c);
+
+ http_response(c, code, msg, "text/html");
+
+ if (!text)
+ text = msg;
+
+ pa_snprintf(s, sizeof(s),
+ "<?xml version=\"1.0\"?>\n"
+ "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n"
+ "<html xmlns=\"http://www.w3.org/1999/xhtml\"><head><title>%s</title></head>\n"
+ "<body>%s</body></html>\n",
+ text, text);
+
+ pa_ioline_puts(c->line, s);
+ pa_ioline_defer_close(c->line);
+}
+
+
+static void connection_free(struct connection *c, int del) {
+ pa_assert(c);
+
+ if (c->url)
+ pa_xfree(c->url);
+
+ if (del)
+ pa_idxset_remove_by_data(c->protocol->connections, c, NULL);
+
+ pa_ioline_unref(c->line);
+ pa_xfree(c);
+}
+
+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_free(c, 1);
+ return;
+ }
+
+ switch (c->state) {
+ case REQUEST_LINE: {
+ if (memcmp(s, "GET ", 4))
+ goto fail;
+
+ s +=4;
+
+ c->url = pa_xstrndup(s, strcspn(s, " \r\n\t?"));
+ c->state = MIME_HEADER;
+ break;
+
+ }
+
+ case MIME_HEADER: {
+
+ /* Ignore MIME headers */
+ if (strcspn(s, " \r\n") != 0)
+ break;
+
+ /* We're done */
+ c->state = DATA;
+
+ pa_log_info("request for %s", c->url);
+
+ if (!strcmp(c->url, URL_ROOT)) {
+ char txt[256];
+ http_response(c, 200, "OK", "text/html");
+
+ pa_ioline_puts(c->line,
+ "<?xml version=\"1.0\"?>\n"
+ "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n"
+ "<html xmlns=\"http://www.w3.org/1999/xhtml\"><title>"PACKAGE_NAME" "PACKAGE_VERSION"</title>\n"
+ "<link rel=\"stylesheet\" type=\"text/css\" href=\"style\"/></head><body>\n");
+
+ pa_ioline_puts(c->line,
+ "<h1>"PACKAGE_NAME" "PACKAGE_VERSION"</h1>\n"
+ "<table>");
+
+#define PRINTF_FIELD(a,b) pa_ioline_printf(c->line, "<tr><td><b>%s</b></td><td>%s</td></tr>\n",(a),(b))
+
+ PRINTF_FIELD("User Name:", pa_get_user_name(txt, sizeof(txt)));
+ PRINTF_FIELD("Fully Qualified Domain Name:", pa_get_fqdn(txt, sizeof(txt)));
+ PRINTF_FIELD("Default Sample Specification:", pa_sample_spec_snprint(txt, sizeof(txt), &c->protocol->core->default_sample_spec));
+ PRINTF_FIELD("Default Sink:", pa_namereg_get_default_sink_name(c->protocol->core));
+ PRINTF_FIELD("Default Source:", pa_namereg_get_default_source_name(c->protocol->core));
+
+ pa_ioline_puts(c->line, "</table>");
+
+ pa_ioline_puts(c->line, "<p><a href=\"/status\">Click here</a> for an extensive server status report.</p>");
+
+ pa_ioline_puts(c->line, "</body></html>\n");
+
+ pa_ioline_defer_close(c->line);
+ } else if (!strcmp(c->url, URL_CSS)) {
+ http_response(c, 200, "OK", "text/css");
+
+ pa_ioline_puts(c->line,
+ "body { color: black; background-color: white; margin: 0.5cm; }\n"
+ "a:link, a:visited { color: #900000; }\n"
+ "p { margin-left: 0.5cm; margin-right: 0.5cm; }\n"
+ "h1 { color: #00009F; }\n"
+ "h2 { color: #00009F; }\n"
+ "ul { margin-left: .5cm; }\n"
+ "ol { margin-left: .5cm; }\n"
+ "pre { margin-left: .5cm; background-color: #f0f0f0; padding: 0.4cm;}\n"
+ ".grey { color: #afafaf; }\n"
+ "table { margin-left: 1cm; border:1px solid lightgrey; padding: 0.2cm; }\n"
+ "td { padding-left:10px; padding-right:10px; }\n");
+
+ pa_ioline_defer_close(c->line);
+ } else if (!strcmp(c->url, URL_STATUS)) {
+ char *r;
+
+ http_response(c, 200, "OK", "text/plain");
+ r = pa_full_status_string(c->protocol->core);
+ pa_ioline_puts(c->line, r);
+ pa_xfree(r);
+
+ pa_ioline_defer_close(c->line);
+ } else
+ http_message(c, 404, "Not Found", NULL);
+
+ break;
+ }
+
+ default:
+ ;
+ }
+
+ return;
+
+fail:
+ internal_server_error(c);
+}
+
+static void on_connection(pa_socket_server*s, pa_iochannel *io, void *userdata) {
+ pa_protocol_http *p = userdata;
+ struct connection *c;
+
+ pa_assert(s);
+ pa_assert(io);
+ pa_assert(p);
+
+ 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;
+ }
+
+ c = pa_xnew(struct connection, 1);
+ c->protocol = p;
+ c->line = pa_ioline_new(io);
+ c->state = REQUEST_LINE;
+ c->url = NULL;
+
+ pa_ioline_set_callback(c->line, line_callback, c);
+ pa_idxset_put(p->connections, c, NULL);
+}
+
+pa_protocol_http* pa_protocol_http_new(pa_core *core, pa_socket_server *server, pa_module *m, PA_GCC_UNUSED pa_modargs *ma) {
+ pa_protocol_http* p;
+
+ pa_core_assert_ref(core);
+ pa_assert(server);
+
+ p = pa_xnew(pa_protocol_http, 1);
+ p->module = m;
+ p->core = core;
+ p->server = server;
+ p->connections = pa_idxset_new(NULL, NULL);
+
+ pa_socket_server_set_callback(p->server, on_connection, p);
+
+ return p;
+}
+
+static void free_connection(void *p, PA_GCC_UNUSED void *userdata) {
+ pa_assert(p);
+ connection_free(p, 0);
+}
+
+void pa_protocol_http_free(pa_protocol_http *p) {
+ pa_assert(p);
+
+ pa_idxset_free(p->connections, free_connection, NULL);
+ pa_socket_server_unref(p->server);
+ pa_xfree(p);
+}
diff --git a/src/pulsecore/protocol-http.h b/src/pulsecore/protocol-http.h
new file mode 100644
index 00000000..cf952476
--- /dev/null
+++ b/src/pulsecore/protocol-http.h
@@ -0,0 +1,37 @@
+#ifndef fooprotocolhttphfoo
+#define fooprotocolhttphfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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 <pulsecore/core.h>
+#include <pulsecore/socket-server.h>
+#include <pulsecore/module.h>
+#include <pulsecore/modargs.h>
+
+typedef struct pa_protocol_http pa_protocol_http;
+
+pa_protocol_http* pa_protocol_http_new(pa_core *core, pa_socket_server *server, pa_module *m, pa_modargs *ma);
+void pa_protocol_http_free(pa_protocol_http *n);
+
+#endif
diff --git a/src/pulsecore/protocol-native.c b/src/pulsecore/protocol-native.c
new file mode 100644
index 00000000..4f582798
--- /dev/null
+++ b/src/pulsecore/protocol-native.c
@@ -0,0 +1,3458 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <pulse/timeval.h>
+#include <pulse/version.h>
+#include <pulse/utf8.h>
+#include <pulse/util.h>
+#include <pulse/xmalloc.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/authkey.h>
+#include <pulsecore/namereg.h>
+#include <pulsecore/core-scache.h>
+#include <pulsecore/core-subscribe.h>
+#include <pulsecore/log.h>
+#include <pulsecore/autoload.h>
+#include <pulsecore/authkey-prop.h>
+#include <pulsecore/strlist.h>
+#include <pulsecore/props.h>
+#include <pulsecore/sample-util.h>
+#include <pulsecore/llist.h>
+#include <pulsecore/creds.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/ipacl.h>
+#include <pulsecore/thread-mq.h>
+
+#include "protocol-native.h"
+
+/* Kick a client if it doesn't authenticate within this time */
+#define AUTH_TIMEOUT 60
+
+/* Don't accept more connection than this */
+#define MAX_CONNECTIONS 64
+
+#define MAX_MEMBLOCKQ_LENGTH (4*1024*1024) /* 4MB */
+
+typedef struct connection connection;
+struct pa_protocol_native;
+
+typedef struct record_stream {
+ pa_msgobject parent;
+
+ connection *connection;
+ uint32_t index;
+
+ pa_source_output *source_output;
+ pa_memblockq *memblockq;
+ size_t fragment_size;
+} record_stream;
+
+typedef struct output_stream {
+ pa_msgobject parent;
+} output_stream;
+
+typedef struct playback_stream {
+ output_stream parent;
+
+ connection *connection;
+ uint32_t index;
+
+ pa_sink_input *sink_input;
+ pa_memblockq *memblockq;
+ int drain_request;
+ uint32_t drain_tag;
+ uint32_t syncid;
+ int underrun;
+
+ pa_atomic_t missing;
+ size_t minreq;
+
+ /* Only updated after SINK_INPUT_MESSAGE_UPDATE_LATENCY */
+ int64_t read_index, write_index;
+ size_t resampled_chunk_length;
+} playback_stream;
+
+typedef struct upload_stream {
+ output_stream parent;
+
+ connection *connection;
+ uint32_t index;
+
+ pa_memchunk memchunk;
+ size_t length;
+ char *name;
+ pa_sample_spec sample_spec;
+ pa_channel_map channel_map;
+} upload_stream;
+
+struct connection {
+ pa_msgobject parent;
+
+ int authorized;
+ uint32_t version;
+ pa_protocol_native *protocol;
+ pa_client *client;
+ 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_DECLARE_CLASS(record_stream);
+#define RECORD_STREAM(o) (record_stream_cast(o))
+static PA_DEFINE_CHECK_TYPE(record_stream, pa_msgobject);
+
+PA_DECLARE_CLASS(output_stream);
+#define OUTPUT_STREAM(o) (output_stream_cast(o))
+static PA_DEFINE_CHECK_TYPE(output_stream, pa_msgobject);
+
+PA_DECLARE_CLASS(playback_stream);
+#define PLAYBACK_STREAM(o) (playback_stream_cast(o))
+static PA_DEFINE_CHECK_TYPE(playback_stream, output_stream);
+
+PA_DECLARE_CLASS(upload_stream);
+#define UPLOAD_STREAM(o) (upload_stream_cast(o))
+static PA_DEFINE_CHECK_TYPE(upload_stream, output_stream);
+
+PA_DECLARE_CLASS(connection);
+#define CONNECTION(o) (connection_cast(o))
+static PA_DEFINE_CHECK_TYPE(connection, pa_msgobject);
+
+struct pa_protocol_native {
+ pa_module *module;
+ pa_core *core;
+ int public;
+ pa_socket_server *server;
+ pa_idxset *connections;
+ uint8_t auth_cookie[PA_NATIVE_COOKIE_LENGTH];
+ int auth_cookie_in_property;
+#ifdef HAVE_CREDS
+ char *auth_group;
+#endif
+ pa_ip_acl *auth_ip_acl;
+};
+
+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
+};
+
+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
+};
+
+enum {
+ RECORD_STREAM_MESSAGE_POST_DATA /* data from source output to main loop */
+};
+
+enum {
+ CONNECTION_MESSAGE_RELEASE,
+ CONNECTION_MESSAGE_REVOKE
+};
+
+static int sink_input_peek_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk);
+static void sink_input_drop_cb(pa_sink_input *i, size_t length);
+static void sink_input_kill_cb(pa_sink_input *i);
+static void sink_input_suspend_cb(pa_sink_input *i, pa_bool_t suspend);
+static void sink_input_moved_cb(pa_sink_input *i);
+
+static void send_memblock(connection *c);
+static void request_bytes(struct playback_stream*s);
+
+static void source_output_kill_cb(pa_source_output *o);
+static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk);
+static void source_output_suspend_cb(pa_source_output *o, pa_bool_t suspend);
+static void source_output_moved_cb(pa_source_output *o);
+static pa_usec_t source_output_get_latency_cb(pa_source_output *o);
+
+static int sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk);
+
+static void command_exit(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+static void command_create_playback_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+static void command_drain_playback_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+static void command_create_record_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+static void command_delete_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+static void command_auth(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+static void command_set_client_name(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+static void command_lookup(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+static void command_stat(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+static void command_get_playback_latency(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+static void command_get_record_latency(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+static void command_create_upload_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+static void command_finish_upload_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+static void command_play_sample(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+static void command_remove_sample(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+static void command_get_info(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+static void command_get_info_list(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+static void command_get_server_info(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+static void command_subscribe(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+static void command_set_volume(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+static void command_set_mute(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+static void command_cork_playback_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+static void command_trigger_or_flush_or_prebuf_playback_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+static void command_set_default_sink_or_source(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+static void command_set_stream_name(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+static void command_kill(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+static void command_load_module(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+static void command_unload_module(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+static void command_add_autoload(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+static void command_remove_autoload(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+static void command_get_autoload_info(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+static void command_get_autoload_info_list(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+static void command_cork_record_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+static void command_flush_record_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+static void command_move_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+static void command_suspend(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+static void command_set_stream_buffer_attr(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+static void command_update_stream_sample_rate(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+
+static const pa_pdispatch_cb_t command_table[PA_COMMAND_MAX] = {
+ [PA_COMMAND_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_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_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_SINK_MUTE] = command_set_mute,
+ [PA_COMMAND_SET_SINK_INPUT_MUTE] = command_set_mute,
+ [PA_COMMAND_SET_SOURCE_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] = command_get_autoload_info,
+ [PA_COMMAND_GET_AUTOLOAD_INFO_LIST] = command_get_autoload_info_list,
+ [PA_COMMAND_ADD_AUTOLOAD] = command_add_autoload,
+ [PA_COMMAND_REMOVE_AUTOLOAD] = command_remove_autoload,
+
+ [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
+};
+
+/* structure management */
+
+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);
+}
+
+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->memchunk.memblock)
+ pa_memblock_unref(s->memchunk.memblock);
+
+ pa_xfree(s);
+}
+
+static upload_stream* upload_stream_new(
+ connection *c,
+ const pa_sample_spec *ss,
+ const pa_channel_map *map,
+ const char *name, size_t length) {
+
+ upload_stream *s;
+
+ pa_assert(c);
+ pa_assert(ss);
+ pa_assert(name);
+ pa_assert(length > 0);
+
+ 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;
+
+ pa_idxset_put(c->output_streams, s, &s->index);
+
+ return s;
+}
+
+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);
+}
+
+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);
+}
+
+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:
+
+ 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))
+ send_memblock(s->connection);
+
+ break;
+ }
+
+ return 0;
+}
+
+static record_stream* record_stream_new(
+ connection *c,
+ pa_source *source,
+ pa_sample_spec *ss,
+ pa_channel_map *map,
+ const char *name,
+ uint32_t *maxlength,
+ uint32_t fragment_size,
+ pa_source_output_flags_t flags) {
+
+ record_stream *s;
+ pa_source_output *source_output;
+ size_t base;
+ pa_source_output_new_data data;
+
+ pa_assert(c);
+ pa_assert(ss);
+ pa_assert(name);
+ pa_assert(maxlength);
+ pa_assert(*maxlength > 0);
+
+ pa_source_output_new_data_init(&data);
+ data.module = c->protocol->module;
+ data.client = c->client;
+ data.source = source;
+ data.driver = __FILE__;
+ data.name = name;
+ pa_source_output_new_data_set_sample_spec(&data, ss);
+ pa_source_output_new_data_set_channel_map(&data, map);
+
+ if (!(source_output = pa_source_output_new(c->protocol->core, &data, flags)))
+ 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->source_output->push = source_output_push_cb;
+ s->source_output->kill = source_output_kill_cb;
+ s->source_output->get_latency = source_output_get_latency_cb;
+ s->source_output->moved = source_output_moved_cb;
+ s->source_output->suspend = source_output_suspend_cb;
+ s->source_output->userdata = s;
+
+ s->memblockq = pa_memblockq_new(
+ 0,
+ *maxlength,
+ 0,
+ base = pa_frame_size(&s->source_output->sample_spec),
+ 1,
+ 0,
+ NULL);
+
+ *maxlength = pa_memblockq_get_maxlength(s->memblockq);
+
+ s->fragment_size = (fragment_size/base)*base;
+ if (s->fragment_size <= 0)
+ s->fragment_size = base;
+
+ if (s->fragment_size > *maxlength)
+ s->fragment_size = *maxlength;
+
+ *ss = s->source_output->sample_spec;
+ *map = s->source_output->channel_map;
+
+ pa_idxset_put(c->record_streams, s, &s->index);
+
+ pa_source_output_put(s->source_output);
+ return s;
+}
+
+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);
+}
+
+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);
+}
+
+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;
+ uint32_t l = 0;
+
+ for (;;) {
+ int32_t k;
+
+ if ((k = pa_atomic_load(&s->missing)) <= 0)
+ break;
+
+ l += k;
+
+ if (l < s->minreq)
+ break;
+
+ if (pa_atomic_sub(&s->missing, k) <= k)
+ break;
+ }
+
+ if (l < s->minreq)
+ break;
+
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, PA_COMMAND_REQUEST);
+ pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */
+ pa_tagstruct_putu32(t, s->index);
+ pa_tagstruct_putu32(t, l);
+ pa_pstream_send_tagstruct(s->connection->pstream, t);
+
+/* pa_log("Requesting %u bytes", l); */
+ break;
+ }
+
+ case PLAYBACK_STREAM_MESSAGE_UNDERFLOW: {
+ pa_tagstruct *t;
+
+ /* Report that we're empty */
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, PA_COMMAND_UNDERFLOW);
+ 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_OVERFLOW: {
+ pa_tagstruct *t;
+
+ /* Notify the user we're overflowed*/
+ t = pa_tagstruct_new(NULL, 0);
+ 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_DRAIN_ACK:
+ pa_pstream_send_simple_ack(s->connection->pstream, PA_PTR_TO_UINT(userdata));
+ break;
+
+ }
+
+ return 0;
+}
+
+static playback_stream* playback_stream_new(
+ connection *c,
+ pa_sink *sink,
+ pa_sample_spec *ss,
+ pa_channel_map *map,
+ const char *name,
+ uint32_t *maxlength,
+ uint32_t *tlength,
+ uint32_t *prebuf,
+ uint32_t *minreq,
+ pa_cvolume *volume,
+ uint32_t syncid,
+ uint32_t *missing,
+ pa_sink_input_flags_t flags) {
+
+ playback_stream *s, *ssync;
+ pa_sink_input *sink_input;
+ pa_memblock *silence;
+ uint32_t idx;
+ int64_t start_index;
+ pa_sink_input_new_data data;
+
+ pa_assert(c);
+ pa_assert(ss);
+ pa_assert(name);
+ pa_assert(maxlength);
+
+ /* Find syncid group */
+ for (ssync = pa_idxset_first(c->output_streams, &idx); ssync; ssync = pa_idxset_next(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)
+ return NULL;
+ }
+
+ pa_sink_input_new_data_init(&data);
+ data.sink = sink;
+ data.driver = __FILE__;
+ data.name = name;
+ 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);
+ data.module = c->protocol->module;
+ data.client = c->client;
+ data.sync_base = ssync ? ssync->sink_input : NULL;
+
+ if (!(sink_input = pa_sink_input_new(c->protocol->core, &data, flags)))
+ return NULL;
+
+ 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->underrun = 1;
+
+ s->sink_input->parent.process_msg = sink_input_process_msg;
+ s->sink_input->peek = sink_input_peek_cb;
+ s->sink_input->drop = sink_input_drop_cb;
+ s->sink_input->kill = sink_input_kill_cb;
+ s->sink_input->moved = sink_input_moved_cb;
+ s->sink_input->suspend = sink_input_suspend_cb;
+ s->sink_input->userdata = s;
+
+ start_index = ssync ? pa_memblockq_get_read_index(ssync->memblockq) : 0;
+
+ silence = pa_silence_memblock_new(c->protocol->core->mempool, &s->sink_input->sample_spec, 0);
+
+ s->memblockq = pa_memblockq_new(
+ start_index,
+ *maxlength,
+ *tlength,
+ pa_frame_size(&s->sink_input->sample_spec),
+ *prebuf,
+ *minreq,
+ silence);
+
+ pa_memblock_unref(silence);
+
+ *maxlength = (uint32_t) pa_memblockq_get_maxlength(s->memblockq);
+ *tlength = (uint32_t) pa_memblockq_get_tlength(s->memblockq);
+ *prebuf = (uint32_t) pa_memblockq_get_prebuf(s->memblockq);
+ *minreq = (uint32_t) pa_memblockq_get_minreq(s->memblockq);
+ *missing = (uint32_t) pa_memblockq_pop_missing(s->memblockq);
+
+ *ss = s->sink_input->sample_spec;
+ *map = s->sink_input->channel_map;
+
+ s->minreq = pa_memblockq_get_minreq(s->memblockq);
+ pa_atomic_store(&s->missing, 0);
+ s->drain_request = 0;
+
+ pa_idxset_put(c->output_streams, s, &s->index);
+
+ pa_sink_input_put(s->sink_input);
+
+ return s;
+}
+
+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_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;
+}
+
+static void connection_unlink(connection *c) {
+ record_stream *r;
+ output_stream *o;
+
+ pa_assert(c);
+
+ if (!c->protocol)
+ return;
+
+ 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;
+ connection_unref(c);
+}
+
+static void connection_free(pa_object *o) {
+ connection *c = CONNECTION(o);
+
+ pa_assert(c);
+
+ connection_unlink(c);
+
+ pa_idxset_free(c->record_streams, NULL, NULL);
+ pa_idxset_free(c->output_streams, NULL, NULL);
+
+ pa_pdispatch_unref(c->pdispatch);
+ pa_pstream_unref(c->pstream);
+ pa_client_free(c->client);
+
+ pa_xfree(c);
+}
+
+/* Called from thread context */
+static void request_bytes(playback_stream *s) {
+ size_t m, previous_missing;
+
+ playback_stream_assert_ref(s);
+
+ m = pa_memblockq_pop_missing(s->memblockq);
+
+ if (m <= 0)
+ return;
+
+/* pa_log("request_bytes(%u)", m); */
+
+ previous_missing = pa_atomic_add(&s->missing, m);
+ if (previous_missing < s->minreq && previous_missing+m >= s->minreq) {
+ pa_assert(pa_thread_mq_get());
+ pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PLAYBACK_STREAM_MESSAGE_REQUEST_DATA, NULL, 0, NULL, NULL);
+ }
+}
+
+static void send_memblock(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->fragment_size)
+ schunk.length = r->fragment_size;
+
+ 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;
+ }
+ }
+}
+
+static void send_playback_stream_killed(playback_stream *p) {
+ pa_tagstruct *t;
+ playback_stream_assert_ref(p);
+
+ t = pa_tagstruct_new(NULL, 0);
+ 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);
+}
+
+static void send_record_stream_killed(record_stream *r) {
+ pa_tagstruct *t;
+ record_stream_assert_ref(r);
+
+ t = pa_tagstruct_new(NULL, 0);
+ 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);
+}
+
+/*** 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);
+ 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:
+ pa_memblockq_seek(s->memblockq, offset, PA_PTR_TO_UINT(userdata));
+ request_bytes(s);
+ return 0;
+
+ case SINK_INPUT_MESSAGE_POST_DATA: {
+ pa_assert(chunk);
+
+/* pa_log("sink input post: %u", chunk->length); */
+
+ if (pa_memblockq_push_align(s->memblockq, chunk) < 0) {
+
+ pa_log_warn("Failed to push data into queue");
+ pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PLAYBACK_STREAM_MESSAGE_OVERFLOW, NULL, 0, NULL, NULL);
+ pa_memblockq_seek(s->memblockq, chunk->length, PA_SEEK_RELATIVE);
+ }
+
+ request_bytes(s);
+
+ s->underrun = 0;
+ return 0;
+ }
+
+ case SINK_INPUT_MESSAGE_DRAIN: {
+
+ pa_memblockq_prebuf_disable(s->memblockq);
+
+ 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 = 1;
+ }
+ request_bytes(s);
+
+ return 0;
+ }
+
+ case SINK_INPUT_MESSAGE_FLUSH:
+ case SINK_INPUT_MESSAGE_PREBUF_FORCE:
+ case SINK_INPUT_MESSAGE_TRIGGER: {
+
+ pa_sink_input *isync;
+ void (*func)(pa_memblockq *bq);
+
+ switch (code) {
+ case SINK_INPUT_MESSAGE_FLUSH:
+ func = pa_memblockq_flush;
+ break;
+
+ case SINK_INPUT_MESSAGE_PREBUF_FORCE:
+ func = pa_memblockq_prebuf_force;
+ break;
+
+ case SINK_INPUT_MESSAGE_TRIGGER:
+ func = pa_memblockq_prebuf_disable;
+ break;
+
+ default:
+ pa_assert_not_reached();
+ }
+
+ func(s->memblockq);
+ s->underrun = 0;
+ request_bytes(s);
+
+ /* 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);
+ func(ssync->memblockq);
+ ssync->underrun = 0;
+ request_bytes(ssync);
+ }
+
+ for (isync = i->sync_next; isync; isync = isync->sync_next) {
+ playback_stream *ssync = PLAYBACK_STREAM(isync->userdata);
+ func(ssync->memblockq);
+ ssync->underrun = 0;
+ request_bytes(ssync);
+ }
+
+ return 0;
+ }
+
+ case SINK_INPUT_MESSAGE_UPDATE_LATENCY:
+
+ s->read_index = pa_memblockq_get_read_index(s->memblockq);
+ s->write_index = pa_memblockq_get_write_index(s->memblockq);
+ s->resampled_chunk_length = s->sink_input->thread_info.resampled_chunk.memblock ? s->sink_input->thread_info.resampled_chunk.length : 0;
+ return 0;
+
+ case PA_SINK_INPUT_MESSAGE_SET_STATE:
+
+ pa_memblockq_prebuf_force(s->memblockq);
+ request_bytes(s);
+ 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;
+ }
+ }
+
+ return pa_sink_input_process_msg(o, code, userdata, offset, chunk);
+}
+
+/* Called from thread context */
+static int sink_input_peek_cb(pa_sink_input *i, size_t length, 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);
+
+ if (pa_memblockq_get_length(s->memblockq) <= 0 && !s->underrun) {
+ pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PLAYBACK_STREAM_MESSAGE_UNDERFLOW, NULL, 0, NULL, NULL);
+ s->underrun = 1;
+ }
+
+ if (pa_memblockq_peek(s->memblockq, chunk) < 0) {
+/* pa_log("peek: failure"); */
+ return -1;
+ }
+
+/* pa_log("peek: %u", chunk->length); */
+
+ request_bytes(s);
+
+ return 0;
+}
+
+/* Called from thread context */
+static void sink_input_drop_cb(pa_sink_input *i, size_t length) {
+ playback_stream *s;
+
+ pa_sink_input_assert_ref(i);
+ s = PLAYBACK_STREAM(i->userdata);
+ playback_stream_assert_ref(s);
+ pa_assert(length > 0);
+
+ pa_memblockq_drop(s->memblockq, length);
+
+ if (s->drain_request && !pa_memblockq_is_readable(s->memblockq)) {
+ s->drain_request = 0;
+ 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);
+ }
+
+ request_bytes(s);
+
+/* pa_log("after_drop: %u %u", pa_memblockq_get_length(s->memblockq), pa_memblockq_is_readable(s->memblockq)); */
+}
+
+/* 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);
+
+ send_playback_stream_killed(s);
+ playback_stream_unlink(s);
+}
+
+/* Called from main context */
+static void sink_input_suspend_cb(pa_sink_input *i, pa_bool_t suspend) {
+ 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 < 12)
+ return;
+
+ t = pa_tagstruct_new(NULL, 0);
+ 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_moved_cb(pa_sink_input *i) {
+ 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 < 12)
+ return;
+
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, PA_COMMAND_PLAYBACK_STREAM_MOVED);
+ pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */
+ pa_tagstruct_putu32(t, s->index);
+ pa_tagstruct_putu32(t, i->sink->index);
+ pa_tagstruct_puts(t, i->sink->name);
+ pa_tagstruct_put_boolean(t, pa_sink_get_state(i->sink) == PA_SINK_SUSPENDED);
+ pa_pstream_send_tagstruct(s->connection->pstream, t);
+}
+
+/*** source_output callbacks ***/
+
+/* 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_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);
+
+ send_record_stream_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_suspend_cb(pa_source_output *o, pa_bool_t suspend) {
+ 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 < 12)
+ return;
+
+ t = pa_tagstruct_new(NULL, 0);
+ 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_moved_cb(pa_source_output *o) {
+ 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 < 12)
+ return;
+
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, PA_COMMAND_RECORD_STREAM_MOVED);
+ pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */
+ pa_tagstruct_putu32(t, s->index);
+ pa_tagstruct_putu32(t, o->source->index);
+ pa_tagstruct_puts(t, o->source->name);
+ pa_pstream_send_tagstruct(s->connection->pstream, t);
+}
+
+/*** pdispatch callbacks ***/
+
+static void protocol_error(connection *c) {
+ pa_log("protocol error, kicking client");
+ connection_unlink(c);
+}
+
+#define CHECK_VALIDITY(pstream, expression, tag, error) do { \
+if (!(expression)) { \
+ pa_pstream_send_error((pstream), (tag), (error)); \
+ return; \
+} \
+} while(0);
+
+static pa_tagstruct *reply_new(uint32_t tag) {
+ pa_tagstruct *reply;
+
+ reply = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(reply, PA_COMMAND_REPLY);
+ pa_tagstruct_putu32(reply, tag);
+ return reply;
+}
+
+static void command_create_playback_stream(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ connection *c = CONNECTION(userdata);
+ playback_stream *s;
+ uint32_t maxlength, tlength, prebuf, minreq, sink_index, syncid, missing;
+ const char *name, *sink_name;
+ pa_sample_spec ss;
+ pa_channel_map map;
+ pa_tagstruct *reply;
+ pa_sink *sink = NULL;
+ pa_cvolume volume;
+ int corked;
+ int no_remap = 0, no_remix = 0, fix_format = 0, fix_rate = 0, fix_channels = 0, no_move = 0, variable_rate = 0;
+ pa_sink_input_flags_t flags = 0;
+
+ connection_assert_ref(c);
+ pa_assert(t);
+
+ if (pa_tagstruct_get(
+ t,
+ PA_TAG_STRING, &name,
+ PA_TAG_SAMPLE_SPEC, &ss,
+ PA_TAG_CHANNEL_MAP, &map,
+ PA_TAG_U32, &sink_index,
+ PA_TAG_STRING, &sink_name,
+ PA_TAG_U32, &maxlength,
+ PA_TAG_BOOLEAN, &corked,
+ PA_TAG_U32, &tlength,
+ PA_TAG_U32, &prebuf,
+ PA_TAG_U32, &minreq,
+ PA_TAG_U32, &syncid,
+ PA_TAG_CVOLUME, &volume,
+ PA_TAG_INVALID) < 0 || !name) {
+ protocol_error(c);
+ return;
+ }
+
+ 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);
+ return;
+ }
+ }
+
+ if (!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);
+ CHECK_VALIDITY(c->pstream, sink_index != PA_INVALID_INDEX || !sink_name || (*sink_name && pa_utf8_valid(name)), tag, PA_ERR_INVALID);
+ CHECK_VALIDITY(c->pstream, pa_channel_map_valid(&map), tag, PA_ERR_INVALID);
+ CHECK_VALIDITY(c->pstream, pa_sample_spec_valid(&ss), tag, PA_ERR_INVALID);
+ CHECK_VALIDITY(c->pstream, pa_cvolume_valid(&volume), tag, PA_ERR_INVALID);
+ CHECK_VALIDITY(c->pstream, map.channels == ss.channels && volume.channels == ss.channels, tag, PA_ERR_INVALID);
+ CHECK_VALIDITY(c->pstream, maxlength > 0, tag, PA_ERR_INVALID);
+ CHECK_VALIDITY(c->pstream, maxlength <= MAX_MEMBLOCKQ_LENGTH, tag, PA_ERR_INVALID);
+
+ if (sink_index != PA_INVALID_INDEX) {
+ sink = pa_idxset_get_by_index(c->protocol->core->sinks, sink_index);
+ CHECK_VALIDITY(c->pstream, sink, tag, PA_ERR_NOENTITY);
+ } else if (sink_name) {
+ sink = pa_namereg_get(c->protocol->core, sink_name, PA_NAMEREG_SINK, 1);
+ CHECK_VALIDITY(c->pstream, sink, tag, PA_ERR_NOENTITY);
+ }
+
+ 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);
+
+ s = playback_stream_new(c, sink, &ss, &map, name, &maxlength, &tlength, &prebuf, &minreq, &volume, syncid, &missing, flags);
+ CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_INVALID);
+
+ 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);
+
+/* pa_log("initial request is %u", missing); */
+
+ if (c->version >= 9) {
+ /* Since 0.9.0 we support sending the buffer metrics back to the client */
+
+ pa_tagstruct_putu32(reply, (uint32_t) maxlength);
+ pa_tagstruct_putu32(reply, (uint32_t) tlength);
+ pa_tagstruct_putu32(reply, (uint32_t) prebuf);
+ pa_tagstruct_putu32(reply, (uint32_t) minreq);
+ }
+
+ 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, pa_sink_get_state(s->sink_input->sink) == PA_SINK_SUSPENDED);
+ }
+
+ pa_pstream_send_tagstruct(c->pstream, reply);
+}
+
+static void command_delete_stream(PA_GCC_UNUSED pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ connection *c = CONNECTION(userdata);
+ uint32_t channel;
+
+ 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_EXIST);
+ 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_EXIST);
+ 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_EXIST);
+ 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_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ connection *c = CONNECTION(userdata);
+ record_stream *s;
+ uint32_t maxlength, fragment_size;
+ uint32_t source_index;
+ const char *name, *source_name;
+ pa_sample_spec ss;
+ pa_channel_map map;
+ pa_tagstruct *reply;
+ pa_source *source = NULL;
+ int corked;
+ int no_remap = 0, no_remix = 0, fix_format = 0, fix_rate = 0, fix_channels = 0, no_move = 0, variable_rate = 0;
+ pa_source_output_flags_t flags = 0;
+
+ 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, &source_index) < 0 ||
+ pa_tagstruct_gets(t, &source_name) < 0 ||
+ pa_tagstruct_getu32(t, &maxlength) < 0 ||
+ pa_tagstruct_get_boolean(t, &corked) < 0 ||
+ pa_tagstruct_getu32(t, &fragment_size) < 0) {
+ protocol_error(c);
+ return;
+ }
+
+ 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);
+ return;
+ }
+ }
+
+ if (!pa_tagstruct_eof(t)) {
+ protocol_error(c);
+ return;
+ }
+
+ 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);
+
+ 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, 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, source_index != PA_INVALID_INDEX || !source_name || (*source_name && pa_utf8_valid(source_name)), tag, PA_ERR_INVALID);
+ CHECK_VALIDITY(c->pstream, map.channels == ss.channels, tag, PA_ERR_INVALID);
+ CHECK_VALIDITY(c->pstream, maxlength > 0, tag, PA_ERR_INVALID);
+ CHECK_VALIDITY(c->pstream, maxlength <= MAX_MEMBLOCKQ_LENGTH, tag, PA_ERR_INVALID);
+
+ if (source_index != PA_INVALID_INDEX) {
+ source = pa_idxset_get_by_index(c->protocol->core->sources, source_index);
+ CHECK_VALIDITY(c->pstream, source, tag, PA_ERR_NOENTITY);
+ } else if (source_name) {
+ source = pa_namereg_get(c->protocol->core, source_name, PA_NAMEREG_SOURCE, 1);
+ CHECK_VALIDITY(c->pstream, source, tag, PA_ERR_NOENTITY);
+ }
+
+ s = record_stream_new(c, source, &ss, &map, name, &maxlength, fragment_size, flags);
+ CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_INVALID);
+
+ 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) maxlength);
+ pa_tagstruct_putu32(reply, (uint32_t) s->fragment_size);
+ }
+
+ 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, pa_source_get_state(s->source_output->source) == PA_SOURCE_SUSPENDED);
+ }
+
+ pa_pstream_send_tagstruct(c->pstream, reply);
+}
+
+static void command_exit(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ connection *c = CONNECTION(userdata);
+
+ 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);
+
+ c->protocol->core->mainloop->quit(c->protocol->core->mainloop, 0);
+ pa_pstream_send_simple_ack(c->pstream, tag); /* nonsense */
+}
+
+static void command_auth(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ connection *c = CONNECTION(userdata);
+ const void*cookie;
+ pa_tagstruct *reply;
+
+ 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;
+ }
+
+ if (!c->authorized) {
+ int success = 0;
+
+#ifdef HAVE_CREDS
+ const pa_creds *creds;
+
+ if ((creds = pa_pdispatch_creds(pd))) {
+ if (creds->uid == getuid())
+ success = 1;
+ else if (c->protocol->auth_group) {
+ int r;
+ gid_t gid;
+
+ if ((gid = pa_get_gid_of_group(c->protocol->auth_group)) == (gid_t) -1)
+ pa_log_warn("failed to get GID of group '%s'", c->protocol->auth_group);
+ else if (gid == creds->gid)
+ success = 1;
+
+ if (!success) {
+ if ((r = pa_uid_in_group(creds->uid, c->protocol->auth_group)) < 0)
+ pa_log_warn("failed to check group membership.");
+ else if (r > 0)
+ success = 1;
+ }
+ }
+
+ pa_log_info("Got credentials: uid=%lu gid=%lu success=%i",
+ (unsigned long) creds->uid,
+ (unsigned long) creds->gid,
+ success);
+
+ if (c->version >= 10 &&
+ pa_mempool_is_shared(c->protocol->core->mempool) &&
+ creds->uid == getuid()) {
+
+ pa_pstream_use_shm(c->pstream, 1);
+ pa_log_info("Enabled SHM for new connection");
+ }
+
+ }
+#endif
+
+ if (!success && memcmp(c->protocol->auth_cookie, cookie, PA_NATIVE_COOKIE_LENGTH) == 0)
+ success = 1;
+
+ if (!success) {
+ pa_log_warn("Denied access to client with invalid authorization data.");
+ pa_pstream_send_error(c->pstream, tag, PA_ERR_ACCESS);
+ return;
+ }
+
+ c->authorized = 1;
+ if (c->auth_timeout_event) {
+ c->protocol->core->mainloop->time_free(c->auth_timeout_event);
+ c->auth_timeout_event = NULL;
+ }
+ }
+
+ reply = reply_new(tag);
+ pa_tagstruct_putu32(reply, PA_PROTOCOL_VERSION);
+
+#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
+}
+
+static void command_set_client_name(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ connection *c = CONNECTION(userdata);
+ const char *name;
+
+ 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, name && pa_utf8_valid(name), tag, PA_ERR_INVALID);
+
+ pa_client_set_name(c->client, name);
+ pa_pstream_send_simple_ack(c->pstream, tag);
+}
+
+static void command_lookup(PA_GCC_UNUSED pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ connection *c = CONNECTION(userdata);
+ const char *name;
+ uint32_t idx = PA_IDXSET_INVALID;
+
+ 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 && *name && pa_utf8_valid(name), 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, 1)))
+ 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, 1)))
+ 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_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ connection *c = CONNECTION(userdata);
+ uint32_t idx;
+ playback_stream *s;
+
+ 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_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ connection *c = CONNECTION(userdata);
+ pa_tagstruct *reply;
+ const pa_mempool_stat *stat;
+
+ 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, pa_scache_total_size(c->protocol->core));
+ pa_pstream_send_tagstruct(c->pstream, reply);
+}
+
+static void command_get_playback_latency(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ connection *c = CONNECTION(userdata);
+ pa_tagstruct *reply;
+ playback_stream *s;
+ struct timeval tv, now;
+ uint32_t idx;
+ pa_usec_t latency;
+
+ 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);
+ CHECK_VALIDITY(c->pstream, pa_asyncmsgq_send(s->sink_input->sink->asyncmsgq, PA_MSGOBJECT(s->sink_input), SINK_INPUT_MESSAGE_UPDATE_LATENCY, s, 0, NULL) == 0, tag, PA_ERR_NOENTITY)
+
+ reply = reply_new(tag);
+
+ latency = pa_sink_get_latency(s->sink_input->sink);
+ latency += pa_bytes_to_usec(s->resampled_chunk_length, &s->sink_input->sample_spec);
+
+ pa_tagstruct_put_usec(reply, latency);
+
+ pa_tagstruct_put_usec(reply, 0);
+ pa_tagstruct_put_boolean(reply, pa_sink_input_get_state(s->sink_input) == PA_SINK_INPUT_RUNNING);
+ pa_tagstruct_put_timeval(reply, &tv);
+ pa_tagstruct_put_timeval(reply, pa_gettimeofday(&now));
+ pa_tagstruct_puts64(reply, s->write_index);
+ pa_tagstruct_puts64(reply, s->read_index);
+ pa_pstream_send_tagstruct(c->pstream, reply);
+}
+
+static void command_get_record_latency(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ connection *c = CONNECTION(userdata);
+ pa_tagstruct *reply;
+ record_stream *s;
+ struct timeval tv, now;
+ uint32_t idx;
+
+ 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);
+
+ reply = reply_new(tag);
+ pa_tagstruct_put_usec(reply, s->source_output->source->monitor_of ? pa_sink_get_latency(s->source_output->source->monitor_of) : 0);
+ pa_tagstruct_put_usec(reply, pa_source_get_latency(s->source_output->source));
+ pa_tagstruct_put_boolean(reply, 0);
+ 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_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ connection *c = CONNECTION(userdata);
+ upload_stream *s;
+ uint32_t length;
+ const char *name;
+ pa_sample_spec ss;
+ pa_channel_map map;
+ pa_tagstruct *reply;
+
+ 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 ||
+ !pa_tagstruct_eof(t)) {
+ 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);
+ CHECK_VALIDITY(c->pstream, name && *name && pa_utf8_valid(name), tag, PA_ERR_INVALID);
+
+ s = upload_stream_new(c, &ss, &map, name, length);
+ 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_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ connection *c = CONNECTION(userdata);
+ uint32_t channel;
+ upload_stream *s;
+ uint32_t idx;
+
+ 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 (pa_scache_add_item(c->protocol->core, s->name, &s->sample_spec, &s->channel_map, &s->memchunk, &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_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ connection *c = CONNECTION(userdata);
+ uint32_t sink_index;
+ pa_volume_t volume;
+ pa_sink *sink;
+ const char *name, *sink_name;
+
+ connection_assert_ref(c);
+ pa_assert(t);
+
+ 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 ||
+ !pa_tagstruct_eof(t)) {
+ protocol_error(c);
+ return;
+ }
+
+ CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
+ CHECK_VALIDITY(c->pstream, sink_index != PA_INVALID_INDEX || !sink_name || (*sink_name && pa_utf8_valid(name)), tag, PA_ERR_INVALID);
+ CHECK_VALIDITY(c->pstream, name && *name && pa_utf8_valid(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, 1);
+
+ CHECK_VALIDITY(c->pstream, sink, tag, PA_ERR_NOENTITY);
+
+ if (pa_scache_play_item(c->protocol->core, name, sink, volume) < 0) {
+ pa_pstream_send_error(c->pstream, tag, PA_ERR_NOENTITY);
+ return;
+ }
+
+ pa_pstream_send_simple_ack(c->pstream, tag);
+}
+
+static void command_remove_sample(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ connection *c = CONNECTION(userdata);
+ const char *name;
+
+ 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 && *name && pa_utf8_valid(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(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;
+ }
+}
+
+static void sink_fill_tagstruct(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, sink->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),
+ PA_TAG_BOOLEAN, pa_sink_get_mute(sink),
+ 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_TAG_INVALID);
+}
+
+static void source_fill_tagstruct(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, source->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),
+ PA_TAG_BOOLEAN, pa_source_get_mute(source),
+ 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_TAG_INVALID);
+}
+
+static void client_fill_tagstruct(pa_tagstruct *t, pa_client *client) {
+ pa_assert(t);
+ pa_assert(client);
+
+ pa_tagstruct_putu32(t, client->index);
+ pa_tagstruct_puts(t, client->name);
+ pa_tagstruct_putu32(t, client->owner ? client->owner->index : PA_INVALID_INDEX);
+ pa_tagstruct_puts(t, client->driver);
+}
+
+static void module_fill_tagstruct(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, module->n_used);
+ pa_tagstruct_put_boolean(t, module->auto_unload);
+}
+
+static void sink_input_fill_tagstruct(connection *c, pa_tagstruct *t, pa_sink_input *s) {
+ pa_sample_spec fixed_ss;
+
+ pa_assert(t);
+ pa_sink_input_assert_ref(s);
+
+ fixup_sample_spec(c, &fixed_ss, &s->sample_spec);
+
+ pa_tagstruct_putu32(t, s->index);
+ pa_tagstruct_puts(t, s->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, &s->volume);
+ pa_tagstruct_put_usec(t, pa_sink_input_get_latency(s));
+ pa_tagstruct_put_usec(t, pa_sink_get_latency(s->sink));
+ 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, pa_sink_input_get_mute(s));
+}
+
+static void source_output_fill_tagstruct(connection *c, pa_tagstruct *t, pa_source_output *s) {
+ pa_sample_spec fixed_ss;
+
+ pa_assert(t);
+ pa_source_output_assert_ref(s);
+
+ fixup_sample_spec(c, &fixed_ss, &s->sample_spec);
+
+ pa_tagstruct_putu32(t, s->index);
+ pa_tagstruct_puts(t, s->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));
+ pa_tagstruct_put_usec(t, pa_source_get_latency(s->source));
+ pa_tagstruct_puts(t, pa_resample_method_to_string(pa_source_output_get_resample_method(s)));
+ pa_tagstruct_puts(t, s->driver);
+}
+
+static void scache_fill_tagstruct(connection *c, pa_tagstruct *t, pa_scache_entry *e) {
+ pa_sample_spec fixed_ss;
+
+ pa_assert(t);
+ pa_assert(e);
+
+ fixup_sample_spec(c, &fixed_ss, &e->sample_spec);
+
+ pa_tagstruct_putu32(t, e->index);
+ pa_tagstruct_puts(t, e->name);
+ pa_tagstruct_put_cvolume(t, &e->volume);
+ pa_tagstruct_put_usec(t, pa_bytes_to_usec(e->memchunk.length, &e->sample_spec));
+ pa_tagstruct_put_sample_spec(t, &fixed_ss);
+ pa_tagstruct_put_channel_map(t, &e->channel_map);
+ pa_tagstruct_putu32(t, e->memchunk.length);
+ pa_tagstruct_put_boolean(t, e->lazy);
+ pa_tagstruct_puts(t, e->filename);
+}
+
+static void command_get_info(PA_GCC_UNUSED pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ connection *c = CONNECTION(userdata);
+ uint32_t idx;
+ pa_sink *sink = NULL;
+ pa_source *source = NULL;
+ pa_client *client = NULL;
+ pa_module *module = NULL;
+ pa_sink_input *si = NULL;
+ pa_source_output *so = NULL;
+ pa_scache_entry *sce = NULL;
+ const char *name;
+ pa_tagstruct *reply;
+
+ 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, idx != PA_INVALID_INDEX || !name || (*name && pa_utf8_valid(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, 1);
+ } 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, 1);
+ } 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, 0);
+ }
+
+ if (!sink && !source && !client && !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(reply, client);
+ else if (module)
+ module_fill_tagstruct(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_GCC_UNUSED pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ connection *c = CONNECTION(userdata);
+ pa_idxset *i;
+ uint32_t idx;
+ void *p;
+ pa_tagstruct *reply;
+
+ 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_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) {
+ for (p = pa_idxset_first(i, &idx); p; p = pa_idxset_next(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(reply, p);
+ else if (command == PA_COMMAND_GET_MODULE_INFO_LIST)
+ module_fill_tagstruct(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_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ connection *c = CONNECTION(userdata);
+ pa_tagstruct *reply;
+ char txt[256];
+ const char *n;
+ pa_sample_spec fixed_ss;
+
+ 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);
+ pa_tagstruct_puts(reply, pa_get_user_name(txt, sizeof(txt)));
+ pa_tagstruct_puts(reply, pa_get_fqdn(txt, sizeof(txt)));
+
+ fixup_sample_spec(c, &fixed_ss, &c->protocol->core->default_sample_spec);
+ pa_tagstruct_put_sample_spec(reply, &fixed_ss);
+
+ n = pa_namereg_get_default_sink_name(c->protocol->core);
+ pa_tagstruct_puts(reply, n);
+ n = pa_namereg_get_default_source_name(c->protocol->core);
+ pa_tagstruct_puts(reply, n);
+
+ pa_tagstruct_putu32(reply, c->protocol->core->cookie);
+
+ 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;
+ connection *c = CONNECTION(userdata);
+
+ connection_assert_ref(c);
+
+ t = pa_tagstruct_new(NULL, 0);
+ 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_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ connection *c = CONNECTION(userdata);
+ pa_subscription_mask_t m;
+
+ 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_GCC_UNUSED pa_pdispatch *pd,
+ uint32_t command,
+ uint32_t tag,
+ pa_tagstruct *t,
+ void *userdata) {
+
+ connection *c = CONNECTION(userdata);
+ uint32_t idx;
+ pa_cvolume volume;
+ pa_sink *sink = NULL;
+ pa_source *source = NULL;
+ pa_sink_input *si = NULL;
+ const char *name = NULL;
+
+ 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, idx != PA_INVALID_INDEX || !name || (*name && pa_utf8_valid(name)), 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, 1);
+ 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, 1);
+ break;
+
+ case PA_COMMAND_SET_SINK_INPUT_VOLUME:
+ si = pa_idxset_get_by_index(c->protocol->core->sink_inputs, idx);
+ break;
+
+ default:
+ pa_assert_not_reached();
+ }
+
+ CHECK_VALIDITY(c->pstream, si || sink || source, tag, PA_ERR_NOENTITY);
+
+ if (sink)
+ pa_sink_set_volume(sink, &volume);
+ else if (source)
+ pa_source_set_volume(source, &volume);
+ else if (si)
+ pa_sink_input_set_volume(si, &volume);
+
+ pa_pstream_send_simple_ack(c->pstream, tag);
+}
+
+static void command_set_mute(
+ PA_GCC_UNUSED pa_pdispatch *pd,
+ uint32_t command,
+ uint32_t tag,
+ pa_tagstruct *t,
+ void *userdata) {
+
+ connection *c = CONNECTION(userdata);
+ uint32_t idx;
+ int mute;
+ pa_sink *sink = NULL;
+ pa_source *source = NULL;
+ pa_sink_input *si = NULL;
+ const char *name = NULL;
+
+ 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, idx != PA_INVALID_INDEX || !name || (*name && pa_utf8_valid(name)), 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, 1);
+
+ 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, 1);
+
+ break;
+
+ case PA_COMMAND_SET_SINK_INPUT_MUTE:
+ si = pa_idxset_get_by_index(c->protocol->core->sink_inputs, idx);
+ break;
+
+ default:
+ pa_assert_not_reached();
+ }
+
+ CHECK_VALIDITY(c->pstream, si || sink || source, tag, PA_ERR_NOENTITY);
+
+ if (sink)
+ pa_sink_set_mute(sink, mute);
+ else if (source)
+ pa_source_set_mute(source, mute);
+ else if (si)
+ pa_sink_input_set_mute(si, mute);
+
+ pa_pstream_send_simple_ack(c->pstream, tag);
+}
+
+static void command_cork_playback_stream(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ connection *c = CONNECTION(userdata);
+ uint32_t idx;
+ int b;
+ playback_stream *s;
+
+ 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);
+ pa_pstream_send_simple_ack(c->pstream, tag);
+}
+
+static void command_trigger_or_flush_or_prebuf_playback_stream(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ connection *c = CONNECTION(userdata);
+ uint32_t idx;
+ playback_stream *s;
+
+ 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_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ connection *c = CONNECTION(userdata);
+ uint32_t idx;
+ record_stream *s;
+ int b;
+
+ 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_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ connection *c = CONNECTION(userdata);
+ uint32_t idx;
+ record_stream *s;
+
+ 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(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) {
+ connection *c = CONNECTION(userdata);
+ uint32_t idx;
+ uint32_t maxlength, tlength, prebuf, minreq, fragsize;
+ pa_tagstruct *reply;
+
+ connection_assert_ref(c);
+ pa_assert(t);
+
+ 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;
+
+ 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, &maxlength,
+ PA_TAG_U32, &tlength,
+ PA_TAG_U32, &prebuf,
+ PA_TAG_U32, &minreq,
+ PA_TAG_INVALID) < 0 ||
+ !pa_tagstruct_eof(t)) {
+ protocol_error(c);
+ return;
+ }
+
+ CHECK_VALIDITY(c->pstream, maxlength > 0, tag, PA_ERR_INVALID);
+ CHECK_VALIDITY(c->pstream, maxlength <= MAX_MEMBLOCKQ_LENGTH, tag, PA_ERR_INVALID);
+
+ pa_memblockq_set_maxlength(s->memblockq, maxlength);
+ pa_memblockq_set_tlength(s->memblockq, tlength);
+ pa_memblockq_set_prebuf(s->memblockq, prebuf);
+ pa_memblockq_set_minreq(s->memblockq, minreq);
+
+ reply = reply_new(tag);
+ pa_tagstruct_putu32(reply, (uint32_t) pa_memblockq_get_maxlength(s->memblockq));
+ pa_tagstruct_putu32(reply, (uint32_t) pa_memblockq_get_tlength(s->memblockq));
+ pa_tagstruct_putu32(reply, (uint32_t) pa_memblockq_get_prebuf(s->memblockq));
+ pa_tagstruct_putu32(reply, (uint32_t) pa_memblockq_get_minreq(s->memblockq));
+
+ } else {
+ record_stream *s;
+ size_t base;
+ 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, &maxlength,
+ PA_TAG_U32, &fragsize,
+ PA_TAG_INVALID) < 0 ||
+ !pa_tagstruct_eof(t)) {
+ protocol_error(c);
+ return;
+ }
+
+ CHECK_VALIDITY(c->pstream, maxlength > 0, tag, PA_ERR_INVALID);
+ CHECK_VALIDITY(c->pstream, maxlength <= MAX_MEMBLOCKQ_LENGTH, tag, PA_ERR_INVALID);
+
+ pa_memblockq_set_maxlength(s->memblockq, maxlength);
+
+ base = pa_frame_size(&s->source_output->sample_spec);
+ s->fragment_size = (fragsize/base)*base;
+ if (s->fragment_size <= 0)
+ s->fragment_size = base;
+
+ if (s->fragment_size > pa_memblockq_get_maxlength(s->memblockq))
+ s->fragment_size = pa_memblockq_get_maxlength(s->memblockq);
+
+ reply = reply_new(tag);
+ pa_tagstruct_putu32(reply, (uint32_t) pa_memblockq_get_maxlength(s->memblockq));
+ pa_tagstruct_putu32(reply, s->fragment_size);
+ }
+
+ 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) {
+ connection *c = CONNECTION(userdata);
+ uint32_t idx;
+ uint32_t rate;
+
+ 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, rate > 0 && rate <= PA_RATE_MAX, 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_set_default_sink_or_source(PA_GCC_UNUSED pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ connection *c = CONNECTION(userdata);
+ const char *s;
+
+ 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 || (*s && pa_utf8_valid(s)), tag, PA_ERR_INVALID);
+
+ pa_namereg_set_default(c->protocol->core, s, command == PA_COMMAND_SET_DEFAULT_SOURCE ? PA_NAMEREG_SOURCE : PA_NAMEREG_SINK);
+ pa_pstream_send_simple_ack(c->pstream, tag);
+}
+
+static void command_set_stream_name(PA_GCC_UNUSED pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ connection *c = CONNECTION(userdata);
+ uint32_t idx;
+ const char *name;
+
+ 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_name(s->sink_input, 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_name(s->source_output, name);
+ }
+
+ pa_pstream_send_simple_ack(c->pstream, tag);
+}
+
+static void command_kill(PA_GCC_UNUSED pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ connection *c = CONNECTION(userdata);
+ uint32_t idx;
+
+ 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);
+
+ 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);
+
+ 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);
+
+ connection_ref(c);
+ pa_source_output_kill(s);
+ }
+
+ pa_pstream_send_simple_ack(c->pstream, tag);
+ connection_unref(c);
+}
+
+static void command_load_module(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ connection *c = CONNECTION(userdata);
+ pa_module *m;
+ const char *name, *argument;
+ pa_tagstruct *reply;
+
+ 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 (!(m = pa_module_load(c->protocol->core, name, argument))) {
+ 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_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ connection *c = CONNECTION(userdata);
+ uint32_t idx;
+ pa_module *m;
+
+ 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);
+ pa_pstream_send_simple_ack(c->pstream, tag);
+}
+
+static void command_add_autoload(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ connection *c = CONNECTION(userdata);
+ const char *name, *module, *argument;
+ uint32_t type;
+ uint32_t idx;
+ pa_tagstruct *reply;
+
+ connection_assert_ref(c);
+ pa_assert(t);
+
+ if (pa_tagstruct_gets(t, &name) < 0 ||
+ pa_tagstruct_getu32(t, &type) < 0 ||
+ pa_tagstruct_gets(t, &module) < 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), tag, PA_ERR_INVALID);
+ CHECK_VALIDITY(c->pstream, type == 0 || type == 1, tag, PA_ERR_INVALID);
+ CHECK_VALIDITY(c->pstream, module && *module && pa_utf8_valid(module), tag, PA_ERR_INVALID);
+ CHECK_VALIDITY(c->pstream, !argument || pa_utf8_valid(argument), tag, PA_ERR_INVALID);
+
+ if (pa_autoload_add(c->protocol->core, name, type == 0 ? PA_NAMEREG_SINK : PA_NAMEREG_SOURCE, module, argument, &idx) < 0) {
+ pa_pstream_send_error(c->pstream, tag, PA_ERR_EXIST);
+ return;
+ }
+
+ reply = reply_new(tag);
+ pa_tagstruct_putu32(reply, idx);
+ pa_pstream_send_tagstruct(c->pstream, reply);
+}
+
+static void command_remove_autoload(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ connection *c = CONNECTION(userdata);
+ const char *name = NULL;
+ uint32_t type, idx = PA_IDXSET_INVALID;
+ int r;
+
+ connection_assert_ref(c);
+ pa_assert(t);
+
+ if ((pa_tagstruct_getu32(t, &idx) < 0 &&
+ (pa_tagstruct_gets(t, &name) < 0 ||
+ pa_tagstruct_getu32(t, &type) < 0)) ||
+ !pa_tagstruct_eof(t)) {
+ protocol_error(c);
+ return;
+ }
+
+ CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
+ CHECK_VALIDITY(c->pstream, name || idx != PA_IDXSET_INVALID, tag, PA_ERR_INVALID);
+ CHECK_VALIDITY(c->pstream, !name || (*name && pa_utf8_valid(name) && (type == 0 || type == 1)), tag, PA_ERR_INVALID);
+
+ if (name)
+ r = pa_autoload_remove_by_name(c->protocol->core, name, type == 0 ? PA_NAMEREG_SINK : PA_NAMEREG_SOURCE);
+ else
+ r = pa_autoload_remove_by_index(c->protocol->core, idx);
+
+ CHECK_VALIDITY(c->pstream, r >= 0, tag, PA_ERR_NOENTITY);
+
+ pa_pstream_send_simple_ack(c->pstream, tag);
+}
+
+static void autoload_fill_tagstruct(pa_tagstruct *t, const pa_autoload_entry *e) {
+ pa_assert(t && e);
+
+ pa_tagstruct_putu32(t, e->index);
+ pa_tagstruct_puts(t, e->name);
+ pa_tagstruct_putu32(t, e->type == PA_NAMEREG_SINK ? 0 : 1);
+ pa_tagstruct_puts(t, e->module);
+ pa_tagstruct_puts(t, e->argument);
+}
+
+static void command_get_autoload_info(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ connection *c = CONNECTION(userdata);
+ const pa_autoload_entry *a = NULL;
+ uint32_t type, idx;
+ const char *name;
+ pa_tagstruct *reply;
+
+ connection_assert_ref(c);
+ pa_assert(t);
+
+ if ((pa_tagstruct_getu32(t, &idx) < 0 &&
+ (pa_tagstruct_gets(t, &name) < 0 ||
+ pa_tagstruct_getu32(t, &type) < 0)) ||
+ !pa_tagstruct_eof(t)) {
+ protocol_error(c);
+ return;
+ }
+
+ CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
+ CHECK_VALIDITY(c->pstream, name || idx != PA_IDXSET_INVALID, tag, PA_ERR_INVALID);
+ CHECK_VALIDITY(c->pstream, !name || (*name && (type == 0 || type == 1) && pa_utf8_valid(name)), tag, PA_ERR_INVALID);
+
+ if (name)
+ a = pa_autoload_get_by_name(c->protocol->core, name, type == 0 ? PA_NAMEREG_SINK : PA_NAMEREG_SOURCE);
+ else
+ a = pa_autoload_get_by_index(c->protocol->core, idx);
+
+ CHECK_VALIDITY(c->pstream, a, tag, PA_ERR_NOENTITY);
+
+ reply = reply_new(tag);
+ autoload_fill_tagstruct(reply, a);
+ pa_pstream_send_tagstruct(c->pstream, reply);
+}
+
+static void command_get_autoload_info_list(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ connection *c = CONNECTION(userdata);
+ pa_tagstruct *reply;
+
+ 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 (c->protocol->core->autoload_hashmap) {
+ pa_autoload_entry *a;
+ void *state = NULL;
+
+ while ((a = pa_hashmap_iterate(c->protocol->core->autoload_hashmap, &state, NULL)))
+ autoload_fill_tagstruct(reply, a);
+ }
+
+ pa_pstream_send_tagstruct(c->pstream, reply);
+}
+
+static void command_move_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
+ connection *c = CONNECTION(userdata);
+ uint32_t idx = PA_INVALID_INDEX, idx_device = PA_INVALID_INDEX;
+ const char *name = NULL;
+
+ 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) < 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, idx_device != PA_INVALID_INDEX || !name || (*name && pa_utf8_valid(name)), 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, PA_NAMEREG_SINK, 1);
+
+ CHECK_VALIDITY(c->pstream, si && sink, tag, PA_ERR_NOENTITY);
+
+ if (pa_sink_input_move_to(si, sink, 0) < 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, PA_NAMEREG_SOURCE, 1);
+
+ CHECK_VALIDITY(c->pstream, so && source, tag, PA_ERR_NOENTITY);
+
+ if (pa_source_output_move_to(so, source) < 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) {
+ connection *c = CONNECTION(userdata);
+ uint32_t idx = PA_INVALID_INDEX;
+ const char *name = NULL;
+ int b;
+
+ 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, idx != PA_INVALID_INDEX || !name || !*name || pa_utf8_valid(name), tag, PA_ERR_INVALID);
+
+ if (command == PA_COMMAND_SUSPEND_SINK) {
+
+ if (idx == PA_INVALID_INDEX && name && !*name) {
+
+ if (pa_sink_suspend_all(c->protocol->core, b) < 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, 1);
+
+ CHECK_VALIDITY(c->pstream, sink, tag, PA_ERR_NOENTITY);
+
+ if (pa_sink_suspend(sink, b) < 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) {
+
+ if (pa_source_suspend_all(c->protocol->core, b) < 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, 1);
+
+ CHECK_VALIDITY(c->pstream, source, tag, PA_ERR_NOENTITY);
+
+ if (pa_source_suspend(source, b) < 0) {
+ pa_pstream_send_error(c->pstream, tag, PA_ERR_INVALID);
+ return;
+ }
+ }
+ }
+
+ pa_pstream_send_simple_ack(c->pstream, tag);
+}
+
+/*** pstream callbacks ***/
+
+static void pstream_packet_callback(pa_pstream *p, pa_packet *packet, const pa_creds *creds, void *userdata) {
+ connection *c = CONNECTION(userdata);
+
+ pa_assert(p);
+ pa_assert(packet);
+ connection_assert_ref(c);
+
+ if (pa_pdispatch_run(c->pdispatch, packet, creds, c) < 0) {
+ pa_log("invalid packet.");
+ 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) {
+ connection *c = CONNECTION(userdata);
+ output_stream *stream;
+
+ pa_assert(p);
+ pa_assert(chunk);
+ connection_assert_ref(c);
+
+ if (!(stream = OUTPUT_STREAM(pa_idxset_get_by_index(c->output_streams, channel)))) {
+ pa_log("client sent block for invalid stream.");
+ /* Ignoring */
+ return;
+ }
+
+ if (playback_stream_isinstance(stream)) {
+ playback_stream *ps = PLAYBACK_STREAM(stream);
+
+ 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, NULL, NULL);
+
+ pa_asyncmsgq_post(ps->sink_input->sink->asyncmsgq, PA_MSGOBJECT(ps->sink_input), SINK_INPUT_MESSAGE_POST_DATA, NULL, 0, chunk, NULL);
+
+ } else {
+ upload_stream *u = UPLOAD_STREAM(stream);
+ size_t l;
+
+ if (!u->memchunk.memblock) {
+ if (u->length == chunk->length) {
+ 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 *src, *dst;
+ dst = pa_memblock_acquire(u->memchunk.memblock);
+ 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(u->memchunk.memblock);
+ pa_memblock_release(chunk->memblock);
+
+ u->memchunk.length += l;
+ u->length -= l;
+ }
+ }
+}
+
+static void pstream_die_callback(pa_pstream *p, void *userdata) {
+ connection *c = CONNECTION(userdata);
+
+ pa_assert(p);
+ connection_assert_ref(c);
+
+ connection_unlink(c);
+ pa_log_info("connection died.");
+}
+
+static void pstream_drain_callback(pa_pstream *p, void *userdata) {
+ connection *c = CONNECTION(userdata);
+
+ pa_assert(p);
+ connection_assert_ref(c);
+
+ 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);
+
+ connection_unlink(CONNECTION(c->userdata));
+}
+
+/*** socket server callbacks ***/
+
+static void auth_timeout(pa_mainloop_api*m, pa_time_event *e, const struct timeval *tv, void *userdata) {
+ connection *c = CONNECTION(userdata);
+
+ pa_assert(m);
+ pa_assert(tv);
+ connection_assert_ref(c);
+ pa_assert(c->auth_timeout_event == e);
+
+ if (!c->authorized)
+ connection_unlink(c);
+}
+
+static void on_connection(PA_GCC_UNUSED pa_socket_server*s, pa_iochannel *io, void *userdata) {
+ pa_protocol_native *p = userdata;
+ connection *c;
+ char cname[256], pname[128];
+
+ pa_assert(s);
+ pa_assert(io);
+ pa_assert(p);
+
+ 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;
+ }
+
+ c = pa_msgobject_new(connection);
+ c->parent.parent.free = connection_free;
+ c->parent.process_msg = connection_process_msg;
+
+ c->authorized = !!p->public;
+
+ if (!c->authorized && p->auth_ip_acl && pa_ip_acl_check(p->auth_ip_acl, pa_iochannel_get_recv_fd(io)) > 0) {
+ pa_log_info("Client authenticated by IP ACL.");
+ c->authorized = 1;
+ }
+
+ if (!c->authorized) {
+ struct timeval tv;
+ pa_gettimeofday(&tv);
+ tv.tv_sec += AUTH_TIMEOUT;
+ c->auth_timeout_event = p->core->mainloop->time_new(p->core->mainloop, &tv, auth_timeout, c);
+ } else
+ c->auth_timeout_event = NULL;
+
+ c->version = 8;
+ c->protocol = p;
+ pa_iochannel_socket_peer_to_string(io, pname, sizeof(pname));
+ pa_snprintf(cname, sizeof(cname), "Native client (%s)", pname);
+ c->client = pa_client_new(p->core, __FILE__, cname);
+ c->client->kill = client_kill_cb;
+ c->client->userdata = c;
+ c->client->owner = p->module;
+
+ c->pstream = pa_pstream_new(p->core->mainloop, io, p->core->mempool);
+
+ pa_pstream_set_recieve_packet_callback(c->pstream, pstream_packet_callback, c);
+ pa_pstream_set_recieve_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, 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
+}
+
+/*** module entry points ***/
+
+static int load_key(pa_protocol_native*p, const char*fn) {
+ pa_assert(p);
+
+ p->auth_cookie_in_property = 0;
+
+ if (!fn && pa_authkey_prop_get(p->core, PA_NATIVE_COOKIE_PROPERTY_NAME, p->auth_cookie, sizeof(p->auth_cookie)) >= 0) {
+ pa_log_info("using already loaded auth cookie.");
+ pa_authkey_prop_ref(p->core, PA_NATIVE_COOKIE_PROPERTY_NAME);
+ p->auth_cookie_in_property = 1;
+ return 0;
+ }
+
+ if (!fn)
+ fn = PA_NATIVE_COOKIE_FILE;
+
+ if (pa_authkey_load_auto(fn, p->auth_cookie, sizeof(p->auth_cookie)) < 0)
+ return -1;
+
+ pa_log_info("loading cookie from disk.");
+
+ if (pa_authkey_prop_put(p->core, PA_NATIVE_COOKIE_PROPERTY_NAME, p->auth_cookie, sizeof(p->auth_cookie)) >= 0)
+ p->auth_cookie_in_property = 1;
+
+ return 0;
+}
+
+static pa_protocol_native* protocol_new_internal(pa_core *c, pa_module *m, pa_modargs *ma) {
+ pa_protocol_native *p;
+ pa_bool_t public = FALSE;
+ const char *acl;
+
+ pa_assert(c);
+ pa_assert(ma);
+
+ if (pa_modargs_get_value_boolean(ma, "auth-anonymous", &public) < 0) {
+ pa_log("auth-anonymous= expects a boolean argument.");
+ return NULL;
+ }
+
+ p = pa_xnew(pa_protocol_native, 1);
+ p->core = c;
+ p->module = m;
+ p->public = public;
+ p->server = NULL;
+ p->auth_ip_acl = NULL;
+
+#ifdef HAVE_CREDS
+ {
+ pa_bool_t a = 1;
+ if (pa_modargs_get_value_boolean(ma, "auth-group-enabled", &a) < 0) {
+ pa_log("auth-group-enabled= expects a boolean argument.");
+ return NULL;
+ }
+ p->auth_group = a ? pa_xstrdup(pa_modargs_get_value(ma, "auth-group", c->is_system_instance ? PA_ACCESS_GROUP : NULL)) : NULL;
+
+ if (p->auth_group)
+ pa_log_info("Allowing access to group '%s'.", p->auth_group);
+ }
+#endif
+
+
+ if ((acl = pa_modargs_get_value(ma, "auth-ip-acl", NULL))) {
+
+ if (!(p->auth_ip_acl = pa_ip_acl_new(acl))) {
+ pa_log("Failed to parse IP ACL '%s'", acl);
+ goto fail;
+ }
+ }
+
+ if (load_key(p, pa_modargs_get_value(ma, "cookie", NULL)) < 0)
+ goto fail;
+
+ p->connections = pa_idxset_new(NULL, NULL);
+
+ return p;
+
+fail:
+#ifdef HAVE_CREDS
+ pa_xfree(p->auth_group);
+#endif
+ if (p->auth_ip_acl)
+ pa_ip_acl_free(p->auth_ip_acl);
+ pa_xfree(p);
+ return NULL;
+}
+
+pa_protocol_native* pa_protocol_native_new(pa_core *core, pa_socket_server *server, pa_module *m, pa_modargs *ma) {
+ char t[256];
+ pa_protocol_native *p;
+
+ if (!(p = protocol_new_internal(core, m, ma)))
+ return NULL;
+
+ p->server = server;
+ pa_socket_server_set_callback(p->server, on_connection, p);
+
+ if (pa_socket_server_get_address(p->server, t, sizeof(t))) {
+ pa_strlist *l;
+ l = pa_property_get(core, PA_NATIVE_SERVER_PROPERTY_NAME);
+ l = pa_strlist_prepend(l, t);
+ pa_property_replace(core, PA_NATIVE_SERVER_PROPERTY_NAME, l);
+ }
+
+ return p;
+}
+
+void pa_protocol_native_free(pa_protocol_native *p) {
+ connection *c;
+ pa_assert(p);
+
+ while ((c = pa_idxset_first(p->connections, NULL)))
+ connection_unlink(c);
+ pa_idxset_free(p->connections, NULL, NULL);
+
+ if (p->server) {
+ char t[256];
+
+ if (pa_socket_server_get_address(p->server, t, sizeof(t))) {
+ pa_strlist *l;
+ l = pa_property_get(p->core, PA_NATIVE_SERVER_PROPERTY_NAME);
+ l = pa_strlist_remove(l, t);
+
+ if (l)
+ pa_property_replace(p->core, PA_NATIVE_SERVER_PROPERTY_NAME, l);
+ else
+ pa_property_remove(p->core, PA_NATIVE_SERVER_PROPERTY_NAME);
+ }
+
+ pa_socket_server_unref(p->server);
+ }
+
+ if (p->auth_cookie_in_property)
+ pa_authkey_prop_unref(p->core, PA_NATIVE_COOKIE_PROPERTY_NAME);
+
+ if (p->auth_ip_acl)
+ pa_ip_acl_free(p->auth_ip_acl);
+
+#ifdef HAVE_CREDS
+ pa_xfree(p->auth_group);
+#endif
+ pa_xfree(p);
+}
+
+pa_protocol_native* pa_protocol_native_new_iochannel(
+ pa_core*core,
+ pa_iochannel *io,
+ pa_module *m,
+ pa_modargs *ma) {
+
+ pa_protocol_native *p;
+
+ if (!(p = protocol_new_internal(core, m, ma)))
+ return NULL;
+
+ on_connection(NULL, io, p);
+
+ return p;
+}
diff --git a/src/pulsecore/protocol-native.h b/src/pulsecore/protocol-native.h
new file mode 100644
index 00000000..bf05f937
--- /dev/null
+++ b/src/pulsecore/protocol-native.h
@@ -0,0 +1,40 @@
+#ifndef fooprotocolnativehfoo
+#define fooprotocolnativehfoo
+
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <pulsecore/core.h>
+#include <pulsecore/socket-server.h>
+#include <pulsecore/module.h>
+#include <pulsecore/modargs.h>
+
+typedef struct pa_protocol_native pa_protocol_native;
+
+pa_protocol_native* pa_protocol_native_new(pa_core*core, pa_socket_server *server, pa_module *m, pa_modargs *ma);
+void pa_protocol_native_free(pa_protocol_native *n);
+
+pa_protocol_native* pa_protocol_native_new_iochannel(pa_core*core, pa_iochannel *io, pa_module *m, pa_modargs *ma);
+
+#endif
diff --git a/src/pulsecore/protocol-simple.c b/src/pulsecore/protocol-simple.c
new file mode 100644
index 00000000..777def30
--- /dev/null
+++ b/src/pulsecore/protocol-simple.c
@@ -0,0 +1,636 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <limits.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+
+#include <pulse/xmalloc.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 "protocol-simple.h"
+
+/* Don't allow more than this many concurrent connections */
+#define MAX_CONNECTIONS 10
+
+typedef struct connection {
+ pa_msgobject parent;
+ pa_protocol_simple *protocol;
+ pa_iochannel *io;
+ pa_sink_input *sink_input;
+ pa_source_output *source_output;
+ pa_client *client;
+ pa_memblockq *input_memblockq, *output_memblockq;
+
+ int dead;
+
+ struct {
+ pa_memblock *current_memblock;
+ size_t memblock_index, fragment_size;
+ pa_atomic_t missing;
+ } playback;
+} connection;
+
+PA_DECLARE_CLASS(connection);
+#define CONNECTION(o) (connection_cast(o))
+static PA_DEFINE_CHECK_TYPE(connection, pa_msgobject);
+
+struct pa_protocol_simple {
+ pa_module *module;
+ pa_core *core;
+ pa_socket_server*server;
+ pa_idxset *connections;
+
+ enum {
+ RECORD = 1,
+ PLAYBACK = 2,
+ DUPLEX = 3
+ } mode;
+
+ pa_sample_spec sample_spec;
+ char *source_name, *sink_name;
+};
+
+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 a aconnection now */
+};
+
+
+#define PLAYBACK_BUFFER_SECONDS (.5)
+#define PLAYBACK_BUFFER_FRAGMENTS (10)
+#define RECORD_BUFFER_SECONDS (5)
+#define RECORD_BUFFER_FRAGMENTS (100)
+
+static void connection_unlink(connection *c) {
+ pa_assert(c);
+
+ if (!c->protocol)
+ return;
+
+ 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_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 *o) {
+ connection *c = CONNECTION(o);
+ pa_assert(c);
+
+ connection_unref(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;
+
+ connection_assert_ref(c);
+
+ if (!c->sink_input || (l = pa_atomic_load(&c->playback.missing)) <= 0)
+ return 0;
+
+ if (l > c->playback.fragment_size)
+ l = c->playback.fragment_size;
+
+ if (c->playback.current_memblock)
+ if (pa_memblock_get_length(c->playback.current_memblock) - c->playback.memblock_index < l) {
+ pa_memblock_unref(c->playback.current_memblock);
+ c->playback.current_memblock = NULL;
+ c->playback.memblock_index = 0;
+ }
+
+ if (!c->playback.current_memblock) {
+ pa_assert_se(c->playback.current_memblock = pa_memblock_new(c->protocol->core->mempool, l));
+ c->playback.memblock_index = 0;
+ }
+
+ 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 = r;
+
+ c->playback.memblock_index += 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, 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) {
+
+ if (errno == EINTR || errno == EAGAIN)
+ return 0;
+
+ pa_log("write(): %s", pa_cstrerror(errno));
+ return -1;
+ }
+
+ pa_memblockq_drop(c->output_memblockq, r);
+
+ return 0;
+}
+
+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;
+ } else if (pa_iochannel_is_hungup(c->io))
+ goto fail;
+
+ if (pa_iochannel_is_writable(c->io)) {
+ if (do_write(c) < 0)
+ goto fail;
+ }
+
+ return;
+
+fail:
+
+ 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 = 1;
+
+ 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);
+
+ 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);
+
+/* 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;
+
+ *r = pa_bytes_to_usec(pa_memblockq_get_length(c->input_memblockq), &c->sink_input->sample_spec);
+
+ /* Fall through, the default handler will add in the extra
+ * latency added by the resampler */
+ }
+
+ default:
+ return pa_sink_input_process_msg(o, code, userdata, offset, chunk);
+ }
+}
+
+/* Called from thread context */
+static int sink_input_peek_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) {
+ connection *c;
+ int r;
+
+ pa_assert(i);
+ c = CONNECTION(i->userdata);
+ connection_assert_ref(c);
+ pa_assert(chunk);
+
+ r = pa_memblockq_peek(c->input_memblockq, chunk);
+
+/* pa_log("peeked %u %i", r >= 0 ? chunk->length: 0, r); */
+
+ if (c->dead && r < 0)
+ pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(c), CONNECTION_MESSAGE_UNLINK_CONNECTION, NULL, 0, NULL, NULL);
+
+ return r;
+}
+
+/* Called from thread context */
+static void sink_input_drop_cb(pa_sink_input *i, size_t length) {
+ connection *c;
+ size_t old, new;
+
+ pa_assert(i);
+ c = CONNECTION(i->userdata);
+ connection_assert_ref(c);
+ pa_assert(length);
+
+ old = pa_memblockq_missing(c->input_memblockq);
+ pa_memblockq_drop(c->input_memblockq, length);
+ new = pa_memblockq_missing(c->input_memblockq);
+
+ if (new > old) {
+ if (pa_atomic_add(&c->playback.missing, new - old) <= 0)
+ pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(c), CONNECTION_MESSAGE_REQUEST_DATA, NULL, 0, NULL, NULL);
+ }
+}
+
+/* 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_assert(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_assert(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 ***/
+
+static void on_connection(pa_socket_server*s, pa_iochannel *io, void *userdata) {
+ pa_protocol_simple *p = userdata;
+ connection *c = NULL;
+ char cname[256];
+
+ pa_assert(s);
+ pa_assert(io);
+ pa_assert(p);
+
+ 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;
+ c->sink_input = NULL;
+ c->source_output = NULL;
+ c->input_memblockq = c->output_memblockq = NULL;
+ c->protocol = p;
+ c->playback.current_memblock = NULL;
+ c->playback.memblock_index = 0;
+ c->playback.fragment_size = 0;
+ c->dead = 0;
+ pa_atomic_store(&c->playback.missing, 0);
+
+ pa_iochannel_socket_peer_to_string(io, cname, sizeof(cname));
+ pa_assert_se(c->client = pa_client_new(p->core, __FILE__, cname));
+ c->client->owner = p->module;
+ c->client->kill = client_kill_cb;
+ c->client->userdata = c;
+
+ if (p->mode & PLAYBACK) {
+ pa_sink_input_new_data data;
+ size_t l;
+
+ pa_sink_input_new_data_init(&data);
+ data.driver = __FILE__;
+ data.name = c->client->name;
+ pa_sink_input_new_data_set_sample_spec(&data, &p->sample_spec);
+ data.module = p->module;
+ data.client = c->client;
+
+ if (!(c->sink_input = pa_sink_input_new(p->core, &data, 0))) {
+ pa_log("Failed to create sink input.");
+ goto fail;
+ }
+
+ c->sink_input->parent.process_msg = sink_input_process_msg;
+ c->sink_input->peek = sink_input_peek_cb;
+ c->sink_input->drop = sink_input_drop_cb;
+ c->sink_input->kill = sink_input_kill_cb;
+ c->sink_input->userdata = c;
+
+ l = (size_t) (pa_bytes_per_second(&p->sample_spec)*PLAYBACK_BUFFER_SECONDS);
+ c->input_memblockq = pa_memblockq_new(
+ 0,
+ l,
+ 0,
+ pa_frame_size(&p->sample_spec),
+ (size_t) -1,
+ l/PLAYBACK_BUFFER_FRAGMENTS,
+ NULL);
+ pa_iochannel_socket_set_rcvbuf(io, l/PLAYBACK_BUFFER_FRAGMENTS*5);
+ c->playback.fragment_size = l/PLAYBACK_BUFFER_FRAGMENTS;
+
+ pa_atomic_store(&c->playback.missing, pa_memblockq_missing(c->input_memblockq));
+
+ pa_sink_input_put(c->sink_input);
+ }
+
+ if (p->mode & RECORD) {
+ pa_source_output_new_data data;
+ size_t l;
+
+ pa_source_output_new_data_init(&data);
+ data.driver = __FILE__;
+ data.name = c->client->name;
+ pa_source_output_new_data_set_sample_spec(&data, &p->sample_spec);
+ data.module = p->module;
+ data.client = c->client;
+
+ if (!(c->source_output = pa_source_output_new(p->core, &data, 0))) {
+ 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;
+
+ l = (size_t) (pa_bytes_per_second(&p->sample_spec)*RECORD_BUFFER_SECONDS);
+ c->output_memblockq = pa_memblockq_new(
+ 0,
+ l,
+ 0,
+ pa_frame_size(&p->sample_spec),
+ 1,
+ 0,
+ NULL);
+ pa_iochannel_socket_set_sndbuf(io, l/RECORD_BUFFER_FRAGMENTS*2);
+
+ pa_source_output_put(c->source_output);
+ }
+
+ pa_iochannel_set_callback(c->io, io_callback, c);
+ pa_idxset_put(p->connections, c, NULL);
+
+ return;
+
+fail:
+ if (c)
+ connection_unlink(c);
+}
+
+pa_protocol_simple* pa_protocol_simple_new(pa_core *core, pa_socket_server *server, pa_module *m, pa_modargs *ma) {
+ pa_protocol_simple* p = NULL;
+ pa_bool_t enable;
+
+ pa_assert(core);
+ pa_assert(server);
+ pa_assert(ma);
+
+ p = pa_xnew0(pa_protocol_simple, 1);
+ p->module = m;
+ p->core = core;
+ p->server = server;
+ p->connections = pa_idxset_new(NULL, NULL);
+
+ p->sample_spec = core->default_sample_spec;
+ if (pa_modargs_get_sample_spec(ma, &p->sample_spec) < 0) {
+ pa_log("Failed to parse sample type specification.");
+ goto fail;
+ }
+
+ p->source_name = pa_xstrdup(pa_modargs_get_value(ma, "source", NULL));
+ p->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL));
+
+ enable = FALSE;
+ if (pa_modargs_get_value_boolean(ma, "record", &enable) < 0) {
+ pa_log("record= expects a numeric argument.");
+ goto fail;
+ }
+ p->mode = enable ? RECORD : 0;
+
+ enable = 1;
+ if (pa_modargs_get_value_boolean(ma, "playback", &enable) < 0) {
+ pa_log("playback= expects a numeric argument.");
+ goto fail;
+ }
+ p->mode |= enable ? PLAYBACK : 0;
+
+ if ((p->mode & (RECORD|PLAYBACK)) == 0) {
+ pa_log("neither playback nor recording enabled for protocol.");
+ goto fail;
+ }
+
+ pa_socket_server_set_callback(p->server, on_connection, p);
+
+ return p;
+
+fail:
+ if (p)
+ pa_protocol_simple_free(p);
+
+ return NULL;
+}
+
+
+void pa_protocol_simple_free(pa_protocol_simple *p) {
+ connection *c;
+ pa_assert(p);
+
+ if (p->connections) {
+ while((c = pa_idxset_first(p->connections, NULL)))
+ connection_unlink(c);
+
+ pa_idxset_free(p->connections, NULL, NULL);
+ }
+
+ if (p->server)
+ pa_socket_server_unref(p->server);
+
+ pa_xfree(p);
+}
diff --git a/src/pulsecore/protocol-simple.h b/src/pulsecore/protocol-simple.h
new file mode 100644
index 00000000..3b02c88e
--- /dev/null
+++ b/src/pulsecore/protocol-simple.h
@@ -0,0 +1,37 @@
+#ifndef fooprotocolsimplehfoo
+#define fooprotocolsimplehfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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 <pulsecore/socket-server.h>
+#include <pulsecore/module.h>
+#include <pulsecore/core.h>
+#include <pulsecore/modargs.h>
+
+typedef struct pa_protocol_simple pa_protocol_simple;
+
+pa_protocol_simple* pa_protocol_simple_new(pa_core *core, pa_socket_server *server, pa_module *m, pa_modargs *ma);
+void pa_protocol_simple_free(pa_protocol_simple *n);
+
+#endif
diff --git a/src/pulsecore/pstream-util.c b/src/pulsecore/pstream-util.c
new file mode 100644
index 00000000..a6932158
--- /dev/null
+++ b/src/pulsecore/pstream-util.c
@@ -0,0 +1,64 @@
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulsecore/native-common.h>
+#include <pulsecore/macro.h>
+
+#include "pstream-util.h"
+
+void pa_pstream_send_tagstruct_with_creds(pa_pstream *p, pa_tagstruct *t, const pa_creds *creds) {
+ size_t length;
+ uint8_t *data;
+ pa_packet *packet;
+
+ pa_assert(p);
+ pa_assert(t);
+
+ pa_assert_se(data = pa_tagstruct_free_data(t, &length));
+ pa_assert_se(packet = pa_packet_new_dynamic(data, length));
+ pa_pstream_send_packet(p, packet, creds);
+ pa_packet_unref(packet);
+}
+
+void pa_pstream_send_error(pa_pstream *p, uint32_t tag, uint32_t error) {
+ pa_tagstruct *t;
+
+ pa_assert_se(t = pa_tagstruct_new(NULL, 0));
+ 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(NULL, 0));
+ pa_tagstruct_putu32(t, PA_COMMAND_REPLY);
+ pa_tagstruct_putu32(t, tag);
+ pa_pstream_send_tagstruct(p, t);
+}
diff --git a/src/pulsecore/pstream-util.h b/src/pulsecore/pstream-util.h
new file mode 100644
index 00000000..67759f2a
--- /dev/null
+++ b/src/pulsecore/pstream-util.h
@@ -0,0 +1,40 @@
+#ifndef foopstreamutilhfoo
+#define foopstreamutilhfoo
+
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#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);
+
+#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);
+
+#endif
diff --git a/src/pulsecore/pstream.c b/src/pulsecore/pstream.c
new file mode 100644
index 00000000..9d32a363
--- /dev/null
+++ b/src/pulsecore/pstream.c
@@ -0,0 +1,1020 @@
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+#ifdef HAVE_SYS_UN_H
+#include <sys/un.h>
+#endif
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/winsock.h>
+#include <pulsecore/queue.h>
+#include <pulsecore/log.h>
+#include <pulsecore/core-scache.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_SHMRELEASE 0x40000000LU
+#define PA_FLAG_SHMREVOKE 0xC0000000LU
+#define PA_FLAG_SHMMASK 0xFF000000LU
+#define PA_FLAG_SEEKMASK 0x000000FFLU
+
+/* 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 FRAME_SIZE_MAX_ALLOW PA_SCACHE_ENTRY_SIZE_MAX /* allow uploading a single sample in one frame at max */
+
+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
+ int with_creds;
+ pa_creds creds;
+#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 pa_pstream {
+ PA_REFCNT_DECLARE;
+
+ pa_mainloop_api *mainloop;
+ pa_defer_event *defer_event;
+ pa_iochannel *io;
+
+ pa_queue *send_queue;
+
+ int dead;
+
+ struct {
+ pa_pstream_descriptor descriptor;
+ struct item_info* current;
+ uint32_t shm_info[PA_PSTREAM_SHM_MAX];
+ void *data;
+ size_t index;
+ pa_memchunk memchunk;
+ } write;
+
+ struct {
+ pa_pstream_descriptor descriptor;
+ pa_memblock *memblock;
+ pa_packet *packet;
+ uint32_t shm_info[PA_PSTREAM_SHM_MAX];
+ void *data;
+ size_t index;
+ } read;
+
+ int use_shm;
+ pa_memimport *import;
+ pa_memexport *export;
+
+ pa_pstream_packet_cb_t recieve_packet_callback;
+ void *recieve_packet_callback_userdata;
+
+ pa_pstream_memblock_cb_t recieve_memblock_callback;
+ void *recieve_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_creds read_creds, write_creds;
+ int read_creds_valid, send_creds_now;
+#endif
+};
+
+static int do_write(pa_pstream *p);
+static int do_read(pa_pstream *p);
+
+static void do_something(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 && pa_iochannel_is_readable(p->io)) {
+ if (do_read(p) < 0)
+ goto fail;
+ } else if (!p->dead && pa_iochannel_is_hungup(p->io))
+ goto fail;
+
+ if (!p->dead && pa_iochannel_is_writable(p->io)) {
+ if (do_write(p) < 0)
+ goto fail;
+ }
+
+ 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 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_something(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_something(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_xnew(pa_pstream, 1);
+ PA_REFCNT_INIT(p);
+ p->io = io;
+ pa_iochannel_set_callback(io, io_callback, p);
+ p->dead = 0;
+
+ 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->write.current = NULL;
+ p->write.index = 0;
+ pa_memchunk_reset(&p->write.memchunk);
+ p->read.memblock = NULL;
+ p->read.packet = NULL;
+ p->read.index = 0;
+
+ p->recieve_packet_callback = NULL;
+ p->recieve_packet_callback_userdata = NULL;
+ p->recieve_memblock_callback = NULL;
+ p->recieve_memblock_callback_userdata = NULL;
+ p->drain_callback = NULL;
+ p->drain_callback_userdata = NULL;
+ p->die_callback = NULL;
+ p->die_callback_userdata = NULL;
+ p->revoke_callback = NULL;
+ p->revoke_callback_userdata = NULL;
+ p->release_callback = NULL;
+ p->release_callback_userdata = NULL;
+
+ p->mempool = pool;
+
+ p->use_shm = 0;
+ p->export = NULL;
+
+ /* We do importing unconditionally */
+ p->import = pa_memimport_new(p->mempool, memimport_release_cb, p);
+
+ pa_iochannel_socket_set_rcvbuf(io, 1024*8);
+ pa_iochannel_socket_set_sndbuf(io, 1024*8);
+
+#ifdef HAVE_CREDS
+ p->send_creds_now = 0;
+ p->read_creds_valid = 0;
+#endif
+ return p;
+}
+
+static void item_free(void *item, PA_GCC_UNUSED void *q) {
+ 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);
+ }
+
+ 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, NULL);
+
+ if (p->write.current)
+ item_free(p->write.current, NULL);
+
+ if (p->write.memchunk.memblock)
+ pa_memblock_unref(p->write.memchunk.memblock);
+
+ if (p->read.memblock)
+ pa_memblock_unref(p->read.memblock);
+
+ if (p->read.packet)
+ pa_packet_unref(p->read.packet);
+
+ pa_xfree(p);
+}
+
+void pa_pstream_send_packet(pa_pstream*p, pa_packet *packet, const pa_creds *creds) {
+ struct item_info *i;
+
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) > 0);
+ pa_assert(packet);
+
+ if (p->dead)
+ 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_creds = !!creds))
+ i->creds = *creds;
+#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 = 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_creds = 0;
+#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_creds = 0;
+#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_creds = 0;
+#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;
+ 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) {
+
+ pa_assert(p->write.current->packet);
+ p->write.data = p->write.current->packet->data;
+ p->write.descriptor[PA_PSTREAM_DESCRIPTOR_LENGTH] = htonl(p->write.current->packet->length);
+
+ } 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;
+ int send_payload = 1;
+
+ 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 = p->write.current->seek_mode & PA_FLAG_SEEKMASK;
+
+ if (p->use_shm) {
+ uint32_t block_id, shm_id;
+ size_t offset, length;
+
+ pa_assert(p->export);
+
+ if (pa_memexport_put(p->export,
+ p->write.current->chunk.memblock,
+ &block_id,
+ &shm_id,
+ &offset,
+ &length) >= 0) {
+
+ flags |= PA_FLAG_SHMDATA;
+ send_payload = 0;
+
+ p->write.shm_info[PA_PSTREAM_SHM_BLOCKID] = htonl(block_id);
+ p->write.shm_info[PA_PSTREAM_SHM_SHMID] = htonl(shm_id);
+ p->write.shm_info[PA_PSTREAM_SHM_INDEX] = htonl((uint32_t) (offset + p->write.current->chunk.index));
+ p->write.shm_info[PA_PSTREAM_SHM_LENGTH] = htonl((uint32_t) p->write.current->chunk.length);
+
+ p->write.descriptor[PA_PSTREAM_DESCRIPTOR_LENGTH] = htonl(sizeof(p->write.shm_info));
+ p->write.data = p->write.shm_info;
+ }
+/* else */
+/* pa_log_warn("Failed to export memory block."); */
+ }
+
+ if (send_payload) {
+ p->write.descriptor[PA_PSTREAM_DESCRIPTOR_LENGTH] = htonl(p->write.current->chunk.length);
+ p->write.memchunk = p->write.current->chunk;
+ pa_memblock_ref(p->write.memchunk.memblock);
+ p->write.data = NULL;
+ }
+
+ p->write.descriptor[PA_PSTREAM_DESCRIPTOR_FLAGS] = htonl(flags);
+ }
+
+#ifdef HAVE_CREDS
+ if ((p->send_creds_now = p->write.current->with_creds))
+ p->write_creds = p->write.current->creds;
+#endif
+}
+
+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)
+ return 0;
+
+ 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 = (uint8_t*) pa_memblock_acquire(p->write.memchunk.memblock) + p->write.memchunk.index;
+ 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_creds_now) {
+
+ if ((r = pa_iochannel_write_with_creds(p->io, d, l, &p->write_creds)) < 0)
+ goto fail;
+
+ p->send_creds_now = 0;
+ } else
+#endif
+
+ if ((r = pa_iochannel_write(p->io, d, l)) < 0)
+ goto fail;
+
+ if (release_memblock)
+ pa_memblock_release(release_memblock);
+
+ p->write.index += 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, NULL);
+ 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 0;
+
+fail:
+
+ if (release_memblock)
+ pa_memblock_release(release_memblock);
+
+ return -1;
+}
+
+static int do_read(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->read.index < PA_PSTREAM_DESCRIPTOR_SIZE) {
+ d = (uint8_t*) p->read.descriptor + p->read.index;
+ l = PA_PSTREAM_DESCRIPTOR_SIZE - p->read.index;
+ } else {
+ pa_assert(p->read.data || p->read.memblock);
+
+ if (p->read.data)
+ d = p->read.data;
+ else {
+ d = pa_memblock_acquire(p->read.memblock);
+ release_memblock = p->read.memblock;
+ }
+
+ d = (uint8_t*) d + p->read.index - PA_PSTREAM_DESCRIPTOR_SIZE;
+ l = ntohl(p->read.descriptor[PA_PSTREAM_DESCRIPTOR_LENGTH]) - (p->read.index - PA_PSTREAM_DESCRIPTOR_SIZE);
+ }
+
+#ifdef HAVE_CREDS
+ {
+ pa_bool_t b = 0;
+
+ if ((r = pa_iochannel_read_with_creds(p->io, d, l, &p->read_creds, &b)) <= 0)
+ goto fail;
+
+ p->read_creds_valid = p->read_creds_valid || b;
+ }
+#else
+ if ((r = pa_iochannel_read(p->io, d, l)) <= 0)
+ goto fail;
+#endif
+
+ if (release_memblock)
+ pa_memblock_release(release_memblock);
+
+ p->read.index += r;
+
+ if (p->read.index == PA_PSTREAM_DESCRIPTOR_SIZE) {
+ uint32_t flags, length, channel;
+ /* Reading of frame descriptor complete */
+
+ flags = ntohl(p->read.descriptor[PA_PSTREAM_DESCRIPTOR_FLAGS]);
+
+ if (!p->use_shm && (flags & PA_FLAG_SHMMASK) != 0) {
+ pa_log_warn("Recieved 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(p->read.descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_HI])); */
+
+ pa_assert(p->export);
+ pa_memexport_process_release(p->export, ntohl(p->read.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(p->read.descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_HI])); */
+
+ pa_assert(p->import);
+ pa_memimport_process_revoke(p->import, ntohl(p->read.descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_HI]));
+
+ goto frame_done;
+ }
+
+ length = ntohl(p->read.descriptor[PA_PSTREAM_DESCRIPTOR_LENGTH]);
+
+ if (length > FRAME_SIZE_MAX_ALLOW || length <= 0) {
+ pa_log_warn("Recieved invalid frame size: %lu", (unsigned long) length);
+ return -1;
+ }
+
+ pa_assert(!p->read.packet && !p->read.memblock);
+
+ channel = ntohl(p->read.descriptor[PA_PSTREAM_DESCRIPTOR_CHANNEL]);
+
+ if (channel == (uint32_t) -1) {
+
+ if (flags != 0) {
+ pa_log_warn("Received packet frame with invalid flags value.");
+ return -1;
+ }
+
+ /* Frame is a packet frame */
+ p->read.packet = pa_packet_new(length);
+ p->read.data = p->read.packet->data;
+
+ } 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) {
+
+ if (length != sizeof(p->read.shm_info)) {
+ pa_log_warn("Recieved SHM memblock frame with Invalid frame length.");
+ return -1;
+ }
+
+ /* Frame is a memblock frame referencing an SHM memblock */
+ p->read.data = p->read.shm_info;
+
+ } else if ((flags & PA_FLAG_SHMMASK) == 0) {
+
+ /* Frame is a memblock frame */
+
+ p->read.memblock = pa_memblock_new(p->mempool, length);
+ p->read.data = NULL;
+ } else {
+
+ pa_log_warn("Recieved memblock frame with invalid flags value.");
+ return -1;
+ }
+ }
+
+ } else if (p->read.index > PA_PSTREAM_DESCRIPTOR_SIZE) {
+ /* Frame payload available */
+
+ if (p->read.memblock && p->recieve_memblock_callback) {
+
+ /* Is this memblock data? Than pass it to the user */
+ l = (p->read.index - r) < PA_PSTREAM_DESCRIPTOR_SIZE ? p->read.index - PA_PSTREAM_DESCRIPTOR_SIZE : (size_t) r;
+
+ if (l > 0) {
+ pa_memchunk chunk;
+
+ chunk.memblock = p->read.memblock;
+ chunk.index = p->read.index - PA_PSTREAM_DESCRIPTOR_SIZE - l;
+ chunk.length = l;
+
+ if (p->recieve_memblock_callback) {
+ int64_t offset;
+
+ offset = (int64_t) (
+ (((uint64_t) ntohl(p->read.descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_HI])) << 32) |
+ (((uint64_t) ntohl(p->read.descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_LO]))));
+
+ p->recieve_memblock_callback(
+ p,
+ ntohl(p->read.descriptor[PA_PSTREAM_DESCRIPTOR_CHANNEL]),
+ offset,
+ ntohl(p->read.descriptor[PA_PSTREAM_DESCRIPTOR_FLAGS]) & PA_FLAG_SEEKMASK,
+ &chunk,
+ p->recieve_memblock_callback_userdata);
+ }
+
+ /* Drop seek info for following callbacks */
+ p->read.descriptor[PA_PSTREAM_DESCRIPTOR_FLAGS] =
+ p->read.descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_HI] =
+ p->read.descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_LO] = 0;
+ }
+ }
+
+ /* Frame complete */
+ if (p->read.index >= ntohl(p->read.descriptor[PA_PSTREAM_DESCRIPTOR_LENGTH]) + PA_PSTREAM_DESCRIPTOR_SIZE) {
+
+ if (p->read.memblock) {
+
+ /* This was a memblock frame. We can unref the memblock now */
+ pa_memblock_unref(p->read.memblock);
+
+ } else if (p->read.packet) {
+
+ if (p->recieve_packet_callback)
+#ifdef HAVE_CREDS
+ p->recieve_packet_callback(p, p->read.packet, p->read_creds_valid ? &p->read_creds : NULL, p->recieve_packet_callback_userdata);
+#else
+ p->recieve_packet_callback(p, p->read.packet, NULL, p->recieve_packet_callback_userdata);
+#endif
+
+ pa_packet_unref(p->read.packet);
+ } else {
+ pa_memblock *b;
+
+ pa_assert((ntohl(p->read.descriptor[PA_PSTREAM_DESCRIPTOR_FLAGS]) & PA_FLAG_SHMMASK) == PA_FLAG_SHMDATA);
+
+ pa_assert(p->import);
+
+ if (!(b = pa_memimport_get(p->import,
+ ntohl(p->read.shm_info[PA_PSTREAM_SHM_BLOCKID]),
+ ntohl(p->read.shm_info[PA_PSTREAM_SHM_SHMID]),
+ ntohl(p->read.shm_info[PA_PSTREAM_SHM_INDEX]),
+ ntohl(p->read.shm_info[PA_PSTREAM_SHM_LENGTH])))) {
+
+ pa_log_warn("Failed to import memory block.");
+ return -1;
+ }
+
+ if (p->recieve_memblock_callback) {
+ int64_t offset;
+ pa_memchunk chunk;
+
+ chunk.memblock = b;
+ chunk.index = 0;
+ chunk.length = pa_memblock_get_length(b);
+
+ offset = (int64_t) (
+ (((uint64_t) ntohl(p->read.descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_HI])) << 32) |
+ (((uint64_t) ntohl(p->read.descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_LO]))));
+
+ p->recieve_memblock_callback(
+ p,
+ ntohl(p->read.descriptor[PA_PSTREAM_DESCRIPTOR_CHANNEL]),
+ offset,
+ ntohl(p->read.descriptor[PA_PSTREAM_DESCRIPTOR_FLAGS]) & PA_FLAG_SEEKMASK,
+ &chunk,
+ p->recieve_memblock_callback_userdata);
+ }
+
+ pa_memblock_unref(b);
+ }
+
+ goto frame_done;
+ }
+ }
+
+ return 0;
+
+frame_done:
+ p->read.memblock = NULL;
+ p->read.packet = NULL;
+ p->read.index = 0;
+ p->read.data = NULL;
+
+#ifdef HAVE_CREDS
+ p->read_creds_valid = 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_recieve_packet_callback(pa_pstream *p, pa_pstream_packet_cb_t cb, void *userdata) {
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) > 0);
+
+ p->recieve_packet_callback = cb;
+ p->recieve_packet_callback_userdata = userdata;
+}
+
+void pa_pstream_set_recieve_memblock_callback(pa_pstream *p, pa_pstream_memblock_cb_t cb, void *userdata) {
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) > 0);
+
+ p->recieve_memblock_callback = cb;
+ p->recieve_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->release_callback = cb;
+ p->release_callback_userdata = userdata;
+}
+
+int pa_pstream_is_pending(pa_pstream *p) {
+ int b;
+
+ pa_assert(p);
+ pa_assert(PA_REFCNT_VALUE(p) > 0);
+
+ if (p->dead)
+ b = 0;
+ else
+ b = p->write.current || !pa_queue_is_empty(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 = 1;
+
+ 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->recieve_packet_callback = NULL;
+ p->recieve_memblock_callback = NULL;
+}
+
+void pa_pstream_use_shm(pa_pstream *p, int 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;
+ }
+ }
+}
diff --git a/src/pulsecore/pstream.h b/src/pulsecore/pstream.h
new file mode 100644
index 00000000..72babea9
--- /dev/null
+++ b/src/pulsecore/pstream.h
@@ -0,0 +1,68 @@
+#ifndef foopstreamhfoo
+#define foopstreamhfoo
+
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#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/memchunk.h>
+#include <pulsecore/creds.h>
+
+typedef struct pa_pstream pa_pstream;
+
+typedef void (*pa_pstream_packet_cb_t)(pa_pstream *p, pa_packet *packet, const pa_creds *creds, 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);
+void pa_pstream_unref(pa_pstream*p);
+pa_pstream* pa_pstream_ref(pa_pstream*p);
+
+void pa_pstream_send_packet(pa_pstream*p, pa_packet *packet, const pa_creds *creds);
+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_recieve_packet_callback(pa_pstream *p, pa_pstream_packet_cb_t cb, void *userdata);
+void pa_pstream_set_recieve_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);
+
+int pa_pstream_is_pending(pa_pstream *p);
+
+void pa_pstream_use_shm(pa_pstream *p, int enable);
+
+void pa_pstream_unlink(pa_pstream *p);
+
+#endif
diff --git a/src/pulsecore/queue.c b/src/pulsecore/queue.c
new file mode 100644
index 00000000..9b6a37f0
--- /dev/null
+++ b/src/pulsecore/queue.c
@@ -0,0 +1,125 @@
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <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, void (*destroy)(void *p, void *userdata), void *userdata) {
+ void *data;
+ pa_assert(q);
+
+ while ((data = pa_queue_pop(q)))
+ if (destroy)
+ destroy(data, userdata);
+
+ 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_is_empty(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 00000000..cd767364
--- /dev/null
+++ b/src/pulsecore/queue.h
@@ -0,0 +1,42 @@
+#ifndef fooqueuehfoo
+#define fooqueuehfoo
+
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+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, void (*destroy)(void *p, void *userdata), void *userdata);
+
+void pa_queue_push(pa_queue *q, void *p);
+void* pa_queue_pop(pa_queue *q);
+
+int pa_queue_is_empty(pa_queue *q);
+
+#endif
diff --git a/src/pulsecore/random.c b/src/pulsecore/random.c
new file mode 100644
index 00000000..87afebfa
--- /dev/null
+++ b/src/pulsecore/random.c
@@ -0,0 +1,114 @@
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <time.h>
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+
+#include "random.h"
+
+static int has_whined = 0;
+
+static const char * const devices[] = { "/dev/urandom", "/dev/random", NULL };
+
+static int random_proper(void *ret_data, size_t length) {
+#ifdef OS_IS_WIN32
+ pa_assert(ret_data);
+ pa_assert(length > 0);
+
+ return -1;
+
+#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 = open(*device, O_RDONLY)) >= 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;
+ }
+
+ 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 = 1;
+
+ 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 = 1;
+
+ 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 00000000..01b7d746
--- /dev/null
+++ b/src/pulsecore/random.h
@@ -0,0 +1,33 @@
+#ifndef foorandomhfoo
+#define foorandomhfoo
+
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <sys/types.h>
+
+void pa_random_seed(void);
+void pa_random(void *ret_data, size_t length);
+
+#endif
diff --git a/src/pulsecore/refcnt.h b/src/pulsecore/refcnt.h
new file mode 100644
index 00000000..64271ab2
--- /dev/null
+++ b/src/pulsecore/refcnt.h
@@ -0,0 +1,44 @@
+#ifndef foopulserefcnthfoo
+#define foopulserefcnthfoo
+
+/* $Id$ */
+
+/***
+ 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 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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 <pulsecore/atomic.h>
+
+#define PA_REFCNT_DECLARE \
+ pa_atomic_t _ref
+
+#define PA_REFCNT_INIT(p) \
+ pa_atomic_store(&(p)->_ref, 1)
+
+#define PA_REFCNT_INC(p) \
+ pa_atomic_inc(&(p)->_ref)
+
+#define PA_REFCNT_DEC(p) \
+ (pa_atomic_dec(&(p)->_ref)-1)
+
+#define PA_REFCNT_VALUE(p) \
+ pa_atomic_load(&(p)->_ref)
+
+#endif
diff --git a/src/pulsecore/resampler.c b/src/pulsecore/resampler.c
new file mode 100644
index 00000000..fe7f1ad2
--- /dev/null
+++ b/src/pulsecore/resampler.c
@@ -0,0 +1,1527 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#if HAVE_LIBSAMPLERATE
+#include <samplerate.h>
+#endif
+
+#include <liboil/liboilfuncs.h>
+#include <liboil/liboil.h>
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/sconv.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/strbuf.h>
+
+#include "speexwrap.h"
+
+#include "ffmpeg/avcodec.h"
+
+#include "resampler.h"
+
+/* Number of samples of extra space we allow the resamplers to return */
+#define EXTRA_SAMPLES 128
+
+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_sz;
+ pa_mempool *mempool;
+
+ pa_memchunk buf1, buf2, buf3, buf4;
+ unsigned buf1_samples, buf2_samples, buf3_samples, buf4_samples;
+
+ pa_sample_format_t work_format;
+
+ pa_convert_func_t to_work_format_func;
+ pa_convert_func_t from_work_format_func;
+
+ float map_table[PA_CHANNELS_MAX][PA_CHANNELS_MAX];
+ pa_bool_t map_required;
+
+ void (*impl_free)(pa_resampler *r);
+ void (*impl_update_rates)(pa_resampler *r);
+ void (*impl_resample)(pa_resampler *r, const pa_memchunk *in, unsigned in_samples, pa_memchunk *out, unsigned *out_samples);
+ void (*impl_reset)(pa_resampler *r);
+
+ struct { /* data specific to the trivial resampler */
+ unsigned o_counter;
+ unsigned i_counter;
+ } trivial;
+
+#ifdef HAVE_LIBSAMPLERATE
+ struct { /* data specific to libsamplerate */
+ SRC_STATE *state;
+ } src;
+#endif
+
+ struct { /* data specific to speex */
+ SpeexResamplerState* state;
+ } speex;
+
+ struct { /* data specific to ffmpeg */
+ struct AVResampleContext *state;
+ pa_memchunk buf[PA_CHANNELS_MAX];
+ } ffmpeg;
+};
+
+static int copy_init(pa_resampler *r);
+static int trivial_init(pa_resampler*r);
+static int speex_init(pa_resampler*r);
+static int ffmpeg_init(pa_resampler*r);
+#ifdef HAVE_LIBSAMPLERATE
+static int libsamplerate_init(pa_resampler*r);
+#endif
+
+static void calc_map_table(pa_resampler *r);
+
+static int (* const init_table[])(pa_resampler*r) = {
+#ifdef HAVE_LIBSAMPLERATE
+ [PA_RESAMPLER_SRC_SINC_BEST_QUALITY] = libsamplerate_init,
+ [PA_RESAMPLER_SRC_SINC_MEDIUM_QUALITY] = libsamplerate_init,
+ [PA_RESAMPLER_SRC_SINC_FASTEST] = libsamplerate_init,
+ [PA_RESAMPLER_SRC_ZERO_ORDER_HOLD] = libsamplerate_init,
+ [PA_RESAMPLER_SRC_LINEAR] = 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] = trivial_init,
+ [PA_RESAMPLER_SPEEX_FLOAT_BASE+0] = speex_init,
+ [PA_RESAMPLER_SPEEX_FLOAT_BASE+1] = speex_init,
+ [PA_RESAMPLER_SPEEX_FLOAT_BASE+2] = speex_init,
+ [PA_RESAMPLER_SPEEX_FLOAT_BASE+3] = speex_init,
+ [PA_RESAMPLER_SPEEX_FLOAT_BASE+4] = speex_init,
+ [PA_RESAMPLER_SPEEX_FLOAT_BASE+5] = speex_init,
+ [PA_RESAMPLER_SPEEX_FLOAT_BASE+6] = speex_init,
+ [PA_RESAMPLER_SPEEX_FLOAT_BASE+7] = speex_init,
+ [PA_RESAMPLER_SPEEX_FLOAT_BASE+8] = speex_init,
+ [PA_RESAMPLER_SPEEX_FLOAT_BASE+9] = speex_init,
+ [PA_RESAMPLER_SPEEX_FLOAT_BASE+10] = speex_init,
+ [PA_RESAMPLER_SPEEX_FIXED_BASE+0] = speex_init,
+ [PA_RESAMPLER_SPEEX_FIXED_BASE+1] = speex_init,
+ [PA_RESAMPLER_SPEEX_FIXED_BASE+2] = speex_init,
+ [PA_RESAMPLER_SPEEX_FIXED_BASE+3] = speex_init,
+ [PA_RESAMPLER_SPEEX_FIXED_BASE+4] = speex_init,
+ [PA_RESAMPLER_SPEEX_FIXED_BASE+5] = speex_init,
+ [PA_RESAMPLER_SPEEX_FIXED_BASE+6] = speex_init,
+ [PA_RESAMPLER_SPEEX_FIXED_BASE+7] = speex_init,
+ [PA_RESAMPLER_SPEEX_FIXED_BASE+8] = speex_init,
+ [PA_RESAMPLER_SPEEX_FIXED_BASE+9] = speex_init,
+ [PA_RESAMPLER_SPEEX_FIXED_BASE+10] = speex_init,
+ [PA_RESAMPLER_FFMPEG] = ffmpeg_init,
+ [PA_RESAMPLER_AUTO] = NULL,
+ [PA_RESAMPLER_COPY] = copy_init
+};
+
+static inline size_t sample_size(pa_sample_format_t f) {
+ pa_sample_spec ss = {
+ .format = f,
+ .rate = 0,
+ .channels = 1
+ };
+
+ return pa_sample_size(&ss);
+}
+
+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,
+ pa_resample_method_t method,
+ pa_resample_flags_t flags) {
+
+ pa_resampler *r = NULL;
+
+ 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);
+
+ /* Fix method */
+
+ if (!(flags & PA_RESAMPLER_VARIABLE_RATE) && a->rate == b->rate) {
+ 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;
+ }
+
+ if (method == PA_RESAMPLER_FFMPEG && (flags & PA_RESAMPLER_VARIABLE_RATE)) {
+ pa_log_info("Resampler 'ffmpeg' cannot do variable rate, reverting to resampler 'auto'.");
+ method = PA_RESAMPLER_AUTO;
+ }
+
+ if (method == PA_RESAMPLER_COPY && ((flags & PA_RESAMPLER_VARIABLE_RATE) || a->rate != b->rate)) {
+ pa_log_info("Resampler 'copy' cannot change sampling rate, reverting to resampler 'auto'.");
+ method = PA_RESAMPLER_AUTO;
+ }
+
+ if (method == PA_RESAMPLER_AUTO)
+ method = PA_RESAMPLER_SPEEX_FLOAT_BASE + 3;
+
+ r = pa_xnew(pa_resampler, 1);
+ r->mempool = pool;
+ r->method = method;
+ r->flags = flags;
+
+ r->impl_free = NULL;
+ r->impl_update_rates = NULL;
+ r->impl_resample = NULL;
+ r->impl_reset = NULL;
+
+ /* 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);
+
+ pa_memchunk_reset(&r->buf1);
+ pa_memchunk_reset(&r->buf2);
+ pa_memchunk_reset(&r->buf3);
+ pa_memchunk_reset(&r->buf4);
+
+ r->buf1_samples = r->buf2_samples = r->buf3_samples = r->buf4_samples = 0;
+
+ calc_map_table(r);
+
+ pa_log_info("Using resampler '%s'", pa_resample_method_to_string(method));
+
+ if ((method >= PA_RESAMPLER_SPEEX_FIXED_BASE && method <= PA_RESAMPLER_SPEEX_FIXED_MAX) ||
+ (method == PA_RESAMPLER_FFMPEG))
+ r->work_format = PA_SAMPLE_S16NE;
+ else if (method == PA_RESAMPLER_TRIVIAL || method == PA_RESAMPLER_COPY) {
+
+ if (r->map_required || a->format != b->format) {
+
+ if (a->format == PA_SAMPLE_S32NE || a->format == PA_SAMPLE_S32RE ||
+ a->format == PA_SAMPLE_FLOAT32NE || a->format == PA_SAMPLE_FLOAT32RE ||
+ b->format == PA_SAMPLE_S32NE || b->format == PA_SAMPLE_S32RE ||
+ b->format == PA_SAMPLE_FLOAT32NE || b->format == PA_SAMPLE_FLOAT32RE)
+ r->work_format = PA_SAMPLE_FLOAT32NE;
+ else
+ r->work_format = PA_SAMPLE_S16NE;
+
+ } else
+ r->work_format = a->format;
+
+ } else
+ r->work_format = PA_SAMPLE_FLOAT32NE;
+
+ pa_log_info("Using %s as working format.", pa_sample_format_to_string(r->work_format));
+
+ r->w_sz = sample_size(r->work_format);
+
+ if (r->i_ss.format == r->work_format)
+ r->to_work_format_func = NULL;
+ else 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)
+ r->from_work_format_func = NULL;
+ else 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;
+ }
+
+ /* initialize implementation */
+ if (init_table[method](r) < 0)
+ goto fail;
+
+ return r;
+
+fail:
+ if (r)
+ pa_xfree(r);
+
+ return NULL;
+}
+
+void pa_resampler_free(pa_resampler *r) {
+ pa_assert(r);
+
+ if (r->impl_free)
+ r->impl_free(r);
+
+ if (r->buf1.memblock)
+ pa_memblock_unref(r->buf1.memblock);
+ if (r->buf2.memblock)
+ pa_memblock_unref(r->buf2.memblock);
+ if (r->buf3.memblock)
+ pa_memblock_unref(r->buf3.memblock);
+ if (r->buf4.memblock)
+ pa_memblock_unref(r->buf4.memblock);
+
+ pa_xfree(r);
+}
+
+void pa_resampler_set_input_rate(pa_resampler *r, uint32_t rate) {
+ pa_assert(r);
+ pa_assert(rate > 0);
+
+ 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);
+
+ if (r->o_ss.rate == rate)
+ return;
+
+ r->o_ss.rate = rate;
+
+ r->impl_update_rates(r);
+}
+
+size_t pa_resampler_request(pa_resampler *r, size_t out_length) {
+ pa_assert(r);
+
+ return (((out_length / r->o_fz)*r->i_ss.rate)/r->o_ss.rate) * r->i_fz;
+}
+
+size_t pa_resampler_max_block_size(pa_resampler *r) {
+ size_t block_size_max;
+ pa_sample_spec ss;
+ size_t fs;
+
+ 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 */
+ ss = r->i_ss;
+ if (r->o_ss.channels > ss.channels)
+ ss.channels = r->o_ss.channels;
+
+ /* We silently assume that the format enum is ordered by size */
+ if (r->o_ss.format > ss.format)
+ ss.format = r->o_ss.format;
+ if (r->work_format > ss.format)
+ ss.format = r->work_format;
+
+ if (r->o_ss.rate > ss.rate)
+ ss.rate = r->o_ss.rate;
+
+ fs = pa_frame_size(&ss);
+
+ return (((block_size_max/fs + EXTRA_SAMPLES)*r->i_ss.rate)/ss.rate)*r->i_fz;
+}
+
+void pa_resampler_reset(pa_resampler *r) {
+ pa_assert(r);
+
+ if (r->impl_reset)
+ r->impl_reset(r);
+}
+
+pa_resample_method_t pa_resampler_get_method(pa_resampler *r) {
+ pa_assert(r);
+
+ return r->method;
+}
+
+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"
+};
+
+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
+
+ 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 (!strcmp(string, resample_methods[m]))
+ return m;
+
+ if (!strcmp(string, "speex-fixed"))
+ return PA_RESAMPLER_SPEEX_FIXED_BASE + 3;
+
+ if (!strcmp(string, "speex-float"))
+ return PA_RESAMPLER_SPEEX_FLOAT_BASE + 3;
+
+ return PA_RESAMPLER_INVALID;
+}
+
+static pa_bool_t 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 pa_bool_t 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 pa_bool_t 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 pa_bool_t on_lfe(pa_channel_position_t p) {
+ return
+ p == PA_CHANNEL_POSITION_LFE;
+}
+
+static void calc_map_table(pa_resampler *r) {
+ unsigned oc, ic;
+ pa_bool_t ic_connected[PA_CHANNELS_MAX];
+ pa_bool_t remix;
+ pa_strbuf *s;
+ char *t;
+
+ pa_assert(r);
+
+ if (!(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)))))
+ return;
+
+ memset(r->map_table, 0, sizeof(r->map_table));
+ memset(ic_connected, 0, sizeof(ic_connected));
+ remix = (r->flags & (PA_RESAMPLER_NO_REMAP|PA_RESAMPLER_NO_REMIX)) == 0;
+
+ for (oc = 0; oc < r->o_ss.channels; oc++) {
+ pa_bool_t oc_connected = FALSE;
+ pa_channel_position_t b = r->o_cm.map[oc];
+
+ for (ic = 0; ic < r->i_ss.channels; ic++) {
+ pa_channel_position_t a = r->i_cm.map[ic];
+
+ if (r->flags & PA_RESAMPLER_NO_REMAP) {
+ /* We shall not do any remapping. Hence, just check by index */
+
+ if (ic == oc)
+ r->map_table[oc][ic] = 1.0;
+
+ continue;
+ }
+
+ if (r->flags & PA_RESAMPLER_NO_REMIX) {
+ /* We shall not do any remixing. Hence, just check by name */
+
+ if (a == b)
+ r->map_table[oc][ic] = 1.0;
+
+ continue;
+ }
+
+ pa_assert(remix);
+
+ /* OK, we shall do the full monty: upmixing and
+ * downmixing. Our algorithm is relatively simple, does
+ * not do spacialization, delay elements or apply lowpass
+ * filters for LFE. 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 losely 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.
+ *
+ * 2) Mono Handling:
+ * S:Mono: Copy into all D:channels
+ * D:Mono: Copy in all S:channels
+ *
+ * 3) Mix D:Left, D:Right:
+ * D:Left: If not connected, avg all S:Left
+ * D:Right: If not connected, avg all S:Right
+ *
+ * 4) Mix D:Center
+ * 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 0.1, the current left and right
+ * should be multiplied by 0.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.375. The current (as result of 1..6) factors
+ * should be multiplied by 0.75. (Alt. suggestion: 0.25
+ * vs. 0.5)
+ *
+ * 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.
+ */
+
+ if (a == b || a == PA_CHANNEL_POSITION_MONO || b == PA_CHANNEL_POSITION_MONO) {
+ r->map_table[oc][ic] = 1.0;
+
+ oc_connected = TRUE;
+ ic_connected[ic] = TRUE;
+ }
+ }
+
+ if (!oc_connected && remix) {
+ /* OK, we shall remix */
+
+ if (on_left(b)) {
+ unsigned n = 0;
+
+ /* We are not connected and on the left side, let's
+ * average all left side input channels. */
+
+ for (ic = 0; ic < r->i_ss.channels; ic++)
+ if (on_left(r->i_cm.map[ic]))
+ n++;
+
+ if (n > 0)
+ for (ic = 0; ic < r->i_ss.channels; ic++)
+ if (on_left(r->i_cm.map[ic])) {
+ r->map_table[oc][ic] = 1.0 / n;
+ 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)) {
+ unsigned n = 0;
+
+ /* We are not connected and on the right side, let's
+ * average all right side input channels. */
+
+ for (ic = 0; ic < r->i_ss.channels; ic++)
+ if (on_right(r->i_cm.map[ic]))
+ n++;
+
+ if (n > 0)
+ for (ic = 0; ic < r->i_ss.channels; ic++)
+ if (on_right(r->i_cm.map[ic])) {
+ r->map_table[oc][ic] = 1.0 / n;
+ 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)) {
+ unsigned n = 0;
+
+ /* We are not connected and at the center. Let's
+ * average all center input channels. */
+
+ for (ic = 0; ic < r->i_ss.channels; ic++)
+ if (on_center(r->i_cm.map[ic]))
+ n++;
+
+ if (n > 0) {
+ for (ic = 0; ic < r->i_ss.channels; ic++)
+ if (on_center(r->i_cm.map[ic])) {
+ r->map_table[oc][ic] = 1.0 / n;
+ ic_connected[ic] = TRUE;
+ }
+ } else {
+
+ /* Hmm, no center channel around, let's synthesize
+ * it by mixing L and R.*/
+
+ n = 0;
+
+ for (ic = 0; ic < r->i_ss.channels; ic++)
+ if (on_left(r->i_cm.map[ic]) || on_right(r->i_cm.map[ic]))
+ n++;
+
+ if (n > 0)
+ for (ic = 0; ic < r->i_ss.channels; ic++)
+ if (on_left(r->i_cm.map[ic]) || on_right(r->i_cm.map[ic])) {
+ r->map_table[oc][ic] = 1.0 / n;
+ 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)) {
+
+ /* We are not connected and an LFE. Let's average all
+ * channels for LFE. */
+
+ for (ic = 0; ic < r->i_ss.channels; ic++) {
+ r->map_table[oc][ic] = 1.0 / r->i_ss.channels;
+
+ /* Please note that a channel connected to LFE
+ * doesn't really count as connected. */
+ }
+ }
+ }
+ }
+
+ if (remix) {
+ unsigned
+ ic_unconnected_left = 0,
+ ic_unconnected_right = 0,
+ ic_unconnected_center = 0,
+ ic_unconnected_lfe = 0;
+
+ for (ic = 0; ic < r->i_ss.channels; 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++;
+ }
+
+ if (ic_unconnected_left > 0) {
+
+ /* OK, so there are unconnected input channels on the
+ * left. Let's multiply all already connected channels on
+ * the left side by .9 and add in our averaged unconnected
+ * channels multplied by .1 */
+
+ for (oc = 0; oc < r->o_ss.channels; oc++) {
+
+ if (!on_left(r->o_cm.map[oc]))
+ continue;
+
+ for (ic = 0; ic < r->i_ss.channels; ic++) {
+
+ if (ic_connected[ic]) {
+ r->map_table[oc][ic] *= .9;
+ continue;
+ }
+
+ if (on_left(r->i_cm.map[ic]))
+ r->map_table[oc][ic] = .1 / ic_unconnected_left;
+ }
+ }
+ }
+
+ if (ic_unconnected_right > 0) {
+
+ /* OK, so there are unconnected input channels on the
+ * right. Let's multiply all already connected channels on
+ * the right side by .9 and add in our averaged unconnected
+ * channels multplied by .1 */
+
+ for (oc = 0; oc < r->o_ss.channels; oc++) {
+
+ if (!on_right(r->o_cm.map[oc]))
+ continue;
+
+ for (ic = 0; ic < r->i_ss.channels; ic++) {
+
+ if (ic_connected[ic]) {
+ r->map_table[oc][ic] *= .9;
+ continue;
+ }
+
+ if (on_right(r->i_cm.map[ic]))
+ r->map_table[oc][ic] = .1 / ic_unconnected_right;
+ }
+ }
+ }
+
+ if (ic_unconnected_center > 0) {
+ pa_bool_t mixed_in = FALSE;
+
+ /* OK, so there are unconnected input channels on the
+ * center. Let's multiply all already connected channels on
+ * the center side by .9 and add in our averaged unconnected
+ * channels multplied by .1 */
+
+ for (oc = 0; oc < r->o_ss.channels; oc++) {
+
+ if (!on_center(r->o_cm.map[oc]))
+ continue;
+
+ for (ic = 0; ic < r->i_ss.channels; ic++) {
+
+ if (ic_connected[ic]) {
+ r->map_table[oc][ic] *= .9;
+ continue;
+ }
+
+ if (on_center(r->i_cm.map[ic])) {
+ r->map_table[oc][ic] = .1 / ic_unconnected_center;
+ mixed_in = TRUE;
+ }
+ }
+ }
+
+ if (!mixed_in) {
+
+ /* 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 .375 and 0.75 as
+ factors. */
+
+ for (oc = 0; oc < r->o_ss.channels; oc++) {
+
+ if (!on_left(r->o_cm.map[oc]) && !on_right(r->o_cm.map[oc]))
+ continue;
+
+ for (ic = 0; ic < r->i_ss.channels; ic++) {
+
+ if (ic_connected[ic]) {
+ r->map_table[oc][ic] *= .75;
+ continue;
+ }
+
+ if (on_center(r->i_cm.map[ic]))
+ r->map_table[oc][ic] = .375 / ic_unconnected_center;
+ }
+ }
+ }
+ }
+
+ if (ic_unconnected_lfe > 0) {
+
+ /* OK, so there is an unconnected LFE channel. Let's mix
+ * it into all channels, with factor 0.375 */
+
+ for (ic = 0; ic < r->i_ss.channels; ic++) {
+
+ if (!on_lfe(r->i_cm.map[ic]))
+ continue;
+
+ for (oc = 0; oc < r->o_ss.channels; oc++)
+ r->map_table[oc][ic] = 0.375 / ic_unconnected_lfe;
+ }
+ }
+ }
+
+
+ s = pa_strbuf_new();
+
+ pa_strbuf_printf(s, " ");
+ for (ic = 0; ic < r->i_ss.channels; ic++)
+ pa_strbuf_printf(s, " I%02u ", ic);
+ pa_strbuf_puts(s, "\n +");
+
+ for (ic = 0; ic < r->i_ss.channels; ic++)
+ pa_strbuf_printf(s, "------");
+ pa_strbuf_puts(s, "\n");
+
+ for (oc = 0; oc < r->o_ss.channels; oc++) {
+ pa_strbuf_printf(s, "O%02u |", oc);
+
+ for (ic = 0; ic < r->i_ss.channels; ic++)
+ pa_strbuf_printf(s, " %1.3f", r->map_table[oc][ic]);
+
+ pa_strbuf_puts(s, "\n");
+ }
+
+ pa_log_debug("Channel matrix:\n%s", t = pa_strbuf_tostring_free(s));
+ pa_xfree(t);
+}
+
+static pa_memchunk* convert_to_work_format(pa_resampler *r, pa_memchunk *input) {
+ unsigned n_samples;
+ void *src, *dst;
+
+ pa_assert(r);
+ pa_assert(input);
+ pa_assert(input->memblock);
+
+ /* Convert the incoming sample into the work sample format and place them in buf1 */
+
+ if (!r->to_work_format_func || !input->length)
+ return input;
+
+ n_samples = (input->length / r->i_fz) * r->i_ss.channels;
+
+ r->buf1.index = 0;
+ r->buf1.length = r->w_sz * n_samples;
+
+ if (!r->buf1.memblock || r->buf1_samples < n_samples) {
+ if (r->buf1.memblock)
+ pa_memblock_unref(r->buf1.memblock);
+
+ r->buf1_samples = n_samples;
+ r->buf1.memblock = pa_memblock_new(r->mempool, r->buf1.length);
+ }
+
+ src = (uint8_t*) pa_memblock_acquire(input->memblock) + input->index;
+ dst = (uint8_t*) pa_memblock_acquire(r->buf1.memblock);
+
+ r->to_work_format_func(n_samples, src, dst);
+
+ pa_memblock_release(input->memblock);
+ pa_memblock_release(r->buf1.memblock);
+
+ return &r->buf1;
+}
+
+static void vectoradd_s16_with_fraction(
+ int16_t *d, int dstr,
+ const int16_t *s1, int sstr1,
+ const int16_t *s2, int sstr2,
+ int n,
+ float s3, float s4) {
+
+ int32_t i3, i4;
+
+ i3 = (int32_t) (s3 * 0x10000);
+ i4 = (int32_t) (s4 * 0x10000);
+
+ for (; n > 0; n--) {
+ int32_t a, b;
+
+ a = *s1;
+ b = *s2;
+
+ a = (a * i3) / 0x10000;
+ b = (b * i4) / 0x10000;
+
+ *d = (int16_t) (a + b);
+
+ s1 = (const int16_t*) ((const uint8_t*) s1 + sstr1);
+ s2 = (const int16_t*) ((const uint8_t*) s2 + sstr2);
+ d = (int16_t*) ((uint8_t*) d + dstr);
+
+ }
+}
+
+static pa_memchunk *remap_channels(pa_resampler *r, pa_memchunk *input) {
+ unsigned in_n_samples, out_n_samples, n_frames;
+ int i_skip, o_skip;
+ unsigned oc;
+ void *src, *dst;
+
+ pa_assert(r);
+ pa_assert(input);
+ pa_assert(input->memblock);
+
+ /* Remap channels and place the result int buf2 */
+
+ if (!r->map_required || !input->length)
+ return input;
+
+ in_n_samples = input->length / r->w_sz;
+ n_frames = in_n_samples / r->i_ss.channels;
+ out_n_samples = n_frames * r->o_ss.channels;
+
+ r->buf2.index = 0;
+ r->buf2.length = r->w_sz * out_n_samples;
+
+ if (!r->buf2.memblock || r->buf2_samples < out_n_samples) {
+ if (r->buf2.memblock)
+ pa_memblock_unref(r->buf2.memblock);
+
+ r->buf2_samples = out_n_samples;
+ r->buf2.memblock = pa_memblock_new(r->mempool, r->buf2.length);
+ }
+
+ src = ((uint8_t*) pa_memblock_acquire(input->memblock) + input->index);
+ dst = pa_memblock_acquire(r->buf2.memblock);
+
+ memset(dst, 0, r->buf2.length);
+
+ o_skip = r->w_sz * r->o_ss.channels;
+ i_skip = r->w_sz * r->i_ss.channels;
+
+ switch (r->work_format) {
+ case PA_SAMPLE_FLOAT32NE:
+
+ for (oc = 0; oc < r->o_ss.channels; oc++) {
+ unsigned ic;
+ static const float one = 1.0;
+
+ for (ic = 0; ic < r->i_ss.channels; ic++) {
+
+ if (r->map_table[oc][ic] <= 0.0)
+ continue;
+
+ oil_vectoradd_f32(
+ (float*) dst + oc, o_skip,
+ (float*) dst + oc, o_skip,
+ (float*) src + ic, i_skip,
+ n_frames,
+ &one, &r->map_table[oc][ic]);
+ }
+ }
+
+ break;
+
+ case PA_SAMPLE_S16NE:
+
+ for (oc = 0; oc < r->o_ss.channels; oc++) {
+ unsigned ic;
+
+ for (ic = 0; ic < r->i_ss.channels; ic++) {
+
+ if (r->map_table[oc][ic] <= 0.0)
+ continue;
+
+ if (r->map_table[oc][ic] >= 1.0) {
+ static const int16_t one = 1;
+
+ oil_vectoradd_s16(
+ (int16_t*) dst + oc, o_skip,
+ (int16_t*) dst + oc, o_skip,
+ (int16_t*) src + ic, i_skip,
+ n_frames,
+ &one, &one);
+
+ } else
+
+ vectoradd_s16_with_fraction(
+ (int16_t*) dst + oc, o_skip,
+ (int16_t*) dst + oc, o_skip,
+ (int16_t*) src + ic, i_skip,
+ n_frames,
+ 1.0, r->map_table[oc][ic]);
+ }
+ }
+
+ break;
+
+ default:
+ pa_assert_not_reached();
+ }
+
+ pa_memblock_release(input->memblock);
+ pa_memblock_release(r->buf2.memblock);
+
+ r->buf2.length = out_n_samples * r->w_sz;
+
+ return &r->buf2;
+}
+
+static pa_memchunk *resample(pa_resampler *r, pa_memchunk *input) {
+ unsigned in_n_frames, in_n_samples;
+ unsigned out_n_frames, out_n_samples;
+
+ pa_assert(r);
+ pa_assert(input);
+
+ /* Resample the data and place the result in buf3 */
+
+ if (!r->impl_resample || !input->length)
+ return input;
+
+ in_n_samples = input->length / r->w_sz;
+ in_n_frames = in_n_samples / r->o_ss.channels;
+
+ out_n_frames = ((in_n_frames*r->o_ss.rate)/r->i_ss.rate)+EXTRA_SAMPLES;
+ out_n_samples = out_n_frames * r->o_ss.channels;
+
+ r->buf3.index = 0;
+ r->buf3.length = r->w_sz * out_n_samples;
+
+ if (!r->buf3.memblock || r->buf3_samples < out_n_samples) {
+ if (r->buf3.memblock)
+ pa_memblock_unref(r->buf3.memblock);
+
+ r->buf3_samples = out_n_samples;
+ r->buf3.memblock = pa_memblock_new(r->mempool, r->buf3.length);
+ }
+
+ r->impl_resample(r, input, in_n_frames, &r->buf3, &out_n_frames);
+ r->buf3.length = out_n_frames * r->w_sz * r->o_ss.channels;
+
+ return &r->buf3;
+}
+
+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 buf4 */
+
+ if (!r->from_work_format_func || !input->length)
+ return input;
+
+ n_samples = input->length / r->w_sz;
+ n_frames = n_samples / r->o_ss.channels;
+
+ r->buf4.index = 0;
+ r->buf4.length = r->o_fz * n_frames;
+
+ if (!r->buf4.memblock || r->buf4_samples < n_samples) {
+ if (r->buf4.memblock)
+ pa_memblock_unref(r->buf4.memblock);
+
+ r->buf4_samples = n_samples;
+ r->buf4.memblock = pa_memblock_new(r->mempool, r->buf4.length);
+ }
+
+ src = (uint8_t*) pa_memblock_acquire(input->memblock) + input->index;
+ dst = pa_memblock_acquire(r->buf4.memblock);
+ r->from_work_format_func(n_samples, src, dst);
+ pa_memblock_release(input->memblock);
+ pa_memblock_release(r->buf4.memblock);
+
+ r->buf4.length = r->o_fz * n_frames;
+
+ return &r->buf4;
+}
+
+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);
+ buf = remap_channels(r, buf);
+ buf = resample(r, 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);
+}
+
+/*** libsamplerate based implementation ***/
+
+#ifdef HAVE_LIBSAMPLERATE
+static void libsamplerate_resample(pa_resampler *r, const pa_memchunk *input, unsigned in_n_frames, pa_memchunk *output, unsigned *out_n_frames) {
+ SRC_DATA data;
+
+ pa_assert(r);
+ pa_assert(input);
+ pa_assert(output);
+ pa_assert(out_n_frames);
+
+ memset(&data, 0, sizeof(data));
+
+ data.data_in = (float*) ((uint8_t*) pa_memblock_acquire(input->memblock) + input->index);
+ data.input_frames = in_n_frames;
+
+ data.data_out = (float*) ((uint8_t*) pa_memblock_acquire(output->memblock) + output->index);
+ data.output_frames = *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(r->src.state, &data) == 0);
+ pa_assert((unsigned) data.input_frames_used == in_n_frames);
+
+ pa_memblock_release(input->memblock);
+ pa_memblock_release(output->memblock);
+
+ *out_n_frames = data.output_frames_gen;
+}
+
+static void libsamplerate_update_rates(pa_resampler *r) {
+ pa_assert(r);
+
+ pa_assert_se(src_set_ratio(r->src.state, (double) r->o_ss.rate / r->i_ss.rate) == 0);
+}
+
+static void libsamplerate_reset(pa_resampler *r) {
+ pa_assert(r);
+
+ pa_assert_se(src_reset(r->src.state) == 0);
+}
+
+static void libsamplerate_free(pa_resampler *r) {
+ pa_assert(r);
+
+ if (r->src.state)
+ src_delete(r->src.state);
+}
+
+static int libsamplerate_init(pa_resampler *r) {
+ int err;
+
+ pa_assert(r);
+
+ if (!(r->src.state = src_new(r->method, r->o_ss.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;
+
+ return 0;
+}
+#endif
+
+/*** speex based implementation ***/
+
+static void 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;
+
+ pa_assert(r);
+ pa_assert(input);
+ pa_assert(output);
+ pa_assert(out_n_frames);
+
+ in = (float*) ((uint8_t*) pa_memblock_acquire(input->memblock) + input->index);
+ out = (float*) ((uint8_t*) pa_memblock_acquire(output->memblock) + output->index);
+
+ pa_assert_se(paspfl_resampler_process_interleaved_float(r->speex.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;
+}
+
+static void 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;
+
+ pa_assert(r);
+ pa_assert(input);
+ pa_assert(output);
+ pa_assert(out_n_frames);
+
+ in = (int16_t*) ((uint8_t*) pa_memblock_acquire(input->memblock) + input->index);
+ out = (int16_t*) ((uint8_t*) pa_memblock_acquire(output->memblock) + output->index);
+
+ pa_assert_se(paspfx_resampler_process_interleaved_int(r->speex.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;
+}
+
+static void speex_update_rates(pa_resampler *r) {
+ pa_assert(r);
+
+ if (r->method >= PA_RESAMPLER_SPEEX_FIXED_BASE && r->method <= PA_RESAMPLER_SPEEX_FIXED_MAX)
+ pa_assert_se(paspfx_resampler_set_rate(r->speex.state, r->i_ss.rate, r->o_ss.rate) == 0);
+ else {
+ pa_assert(r->method >= PA_RESAMPLER_SPEEX_FLOAT_BASE && r->method <= PA_RESAMPLER_SPEEX_FLOAT_MAX);
+ pa_assert_se(paspfl_resampler_set_rate(r->speex.state, r->i_ss.rate, r->o_ss.rate) == 0);
+ }
+}
+
+static void speex_reset(pa_resampler *r) {
+ pa_assert(r);
+
+ if (r->method >= PA_RESAMPLER_SPEEX_FIXED_BASE && r->method <= PA_RESAMPLER_SPEEX_FIXED_MAX)
+ pa_assert_se(paspfx_resampler_reset_mem(r->speex.state) == 0);
+ else {
+ pa_assert(r->method >= PA_RESAMPLER_SPEEX_FLOAT_BASE && r->method <= PA_RESAMPLER_SPEEX_FLOAT_MAX);
+ pa_assert_se(paspfl_resampler_reset_mem(r->speex.state) == 0);
+ }
+}
+
+static void speex_free(pa_resampler *r) {
+ pa_assert(r);
+
+ if (!r->speex.state)
+ return;
+
+ if (r->method >= PA_RESAMPLER_SPEEX_FIXED_BASE && r->method <= PA_RESAMPLER_SPEEX_FIXED_MAX)
+ paspfx_resampler_destroy(r->speex.state);
+ else {
+ pa_assert(r->method >= PA_RESAMPLER_SPEEX_FLOAT_BASE && r->method <= PA_RESAMPLER_SPEEX_FLOAT_MAX);
+ paspfl_resampler_destroy(r->speex.state);
+ }
+}
+
+static int speex_init(pa_resampler *r) {
+ int q, err;
+
+ 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;
+
+ pa_log_info("Choosing speex quality setting %i.", q);
+
+ if (!(r->speex.state = paspfx_resampler_init(r->o_ss.channels, r->i_ss.rate, r->o_ss.rate, q, &err)))
+ return -1;
+
+ 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;
+
+ pa_log_info("Choosing speex quality setting %i.", q);
+
+ if (!(r->speex.state = paspfl_resampler_init(r->o_ss.channels, r->i_ss.rate, r->o_ss.rate, q, &err)))
+ return -1;
+
+ r->impl_resample = speex_resample_float;
+ }
+
+ return 0;
+}
+
+/* Trivial implementation */
+
+static void trivial_resample(pa_resampler *r, const pa_memchunk *input, unsigned in_n_frames, pa_memchunk *output, unsigned *out_n_frames) {
+ size_t fz;
+ unsigned o_index;
+ void *src, *dst;
+
+ pa_assert(r);
+ pa_assert(input);
+ pa_assert(output);
+ pa_assert(out_n_frames);
+
+ fz = r->w_sz * r->o_ss.channels;
+
+ src = (uint8_t*) pa_memblock_acquire(input->memblock) + input->index;
+ dst = (uint8_t*) pa_memblock_acquire(output->memblock) + output->index;
+
+ for (o_index = 0;; o_index++, r->trivial.o_counter++) {
+ unsigned j;
+
+ j = ((r->trivial.o_counter * r->i_ss.rate) / r->o_ss.rate);
+ j = j > r->trivial.i_counter ? j - r->trivial.i_counter : 0;
+
+ if (j >= in_n_frames)
+ break;
+
+ pa_assert(o_index * fz < pa_memblock_get_length(output->memblock));
+
+ oil_memcpy((uint8_t*) dst + fz * o_index,
+ (uint8_t*) src + fz * j, fz);
+ }
+
+ pa_memblock_release(input->memblock);
+ pa_memblock_release(output->memblock);
+
+ *out_n_frames = o_index;
+
+ r->trivial.i_counter += in_n_frames;
+
+ /* Normalize counters */
+ while (r->trivial.i_counter >= r->i_ss.rate) {
+ pa_assert(r->trivial.o_counter >= r->o_ss.rate);
+
+ r->trivial.i_counter -= r->i_ss.rate;
+ r->trivial.o_counter -= r->o_ss.rate;
+ }
+}
+
+static void trivial_update_rates_or_reset(pa_resampler *r) {
+ pa_assert(r);
+
+ r->trivial.i_counter = 0;
+ r->trivial.o_counter = 0;
+}
+
+static int trivial_init(pa_resampler*r) {
+ pa_assert(r);
+
+ r->trivial.o_counter = r->trivial.i_counter = 0;
+
+ r->impl_resample = trivial_resample;
+ r->impl_update_rates = trivial_update_rates_or_reset;
+ r->impl_reset = trivial_update_rates_or_reset;
+
+ return 0;
+}
+
+/*** ffmpeg based implementation ***/
+
+static void 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;
+
+ pa_assert(r);
+ pa_assert(input);
+ pa_assert(output);
+ pa_assert(out_n_frames);
+
+ for (c = 0; c < r->o_ss.channels; c++) {
+ unsigned u;
+ pa_memblock *b, *w;
+ int16_t *p, *t, *k, *q, *s;
+ int consumed_frames;
+ unsigned in, l;
+
+ /* Allocate a new block */
+ b = pa_memblock_new(r->mempool, r->ffmpeg.buf[c].length + in_n_frames * sizeof(int16_t));
+ p = pa_memblock_acquire(b);
+
+ /* Copy the remaining data into it */
+ l = r->ffmpeg.buf[c].length;
+ if (r->ffmpeg.buf[c].memblock) {
+ t = (int16_t*) ((uint8_t*) pa_memblock_acquire(r->ffmpeg.buf[c].memblock) + r->ffmpeg.buf[c].index);
+ memcpy(p, t, l);
+ pa_memblock_release(r->ffmpeg.buf[c].memblock);
+ pa_memblock_unref(r->ffmpeg.buf[c].memblock);
+ pa_memchunk_reset(&r->ffmpeg.buf[c]);
+ }
+
+ /* Now append the new data, splitting up channels */
+ t = ((int16_t*) ((uint8_t*) pa_memblock_acquire(input->memblock) + input->index)) + c;
+ k = (int16_t*) ((uint8_t*) p + l);
+ for (u = 0; u < in_n_frames; u++) {
+ *k = *t;
+ t += r->o_ss.channels;
+ k ++;
+ }
+ pa_memblock_release(input->memblock);
+
+ /* Calculate the resulting number of frames */
+ in = in_n_frames + l / sizeof(int16_t);
+
+ /* 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 = av_resample(r->ffmpeg.state,
+ q, p,
+ &consumed_frames,
+ in, *out_n_frames,
+ c >= (unsigned) r->o_ss.channels-1);
+
+ pa_memblock_release(b);
+
+ /* Now store the remaining samples away */
+ pa_assert(consumed_frames <= (int) in);
+ if (consumed_frames < (int) in) {
+ r->ffmpeg.buf[c].memblock = b;
+ r->ffmpeg.buf[c].index = consumed_frames * sizeof(int16_t);
+ r->ffmpeg.buf[c].length = (in - consumed_frames) * sizeof(int16_t);
+ } else
+ pa_memblock_unref(b);
+
+ /* And place the results in the output buffer */
+ s = (short*) ((uint8_t*) pa_memblock_acquire(output->memblock) + output->index) + c;
+ for (u = 0; u < used_frames; u++) {
+ *s = *q;
+ q++;
+ s += r->o_ss.channels;
+ }
+ pa_memblock_release(output->memblock);
+ pa_memblock_release(w);
+ pa_memblock_unref(w);
+ }
+
+ *out_n_frames = used_frames;
+}
+
+static void ffmpeg_free(pa_resampler *r) {
+ unsigned c;
+
+ pa_assert(r);
+
+ if (r->ffmpeg.state)
+ av_resample_close(r->ffmpeg.state);
+
+ for (c = 0; c < PA_ELEMENTSOF(r->ffmpeg.buf); c++)
+ if (r->ffmpeg.buf[c].memblock)
+ pa_memblock_unref(r->ffmpeg.buf[c].memblock);
+}
+
+static int ffmpeg_init(pa_resampler *r) {
+ unsigned c;
+
+ pa_assert(r);
+
+ /* 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 (!(r->ffmpeg.state = av_resample_init(r->o_ss.rate, r->i_ss.rate, 16, 10, 0, 0.8)))
+ return -1;
+
+ r->impl_free = ffmpeg_free;
+ r->impl_resample = ffmpeg_resample;
+
+ for (c = 0; c < PA_ELEMENTSOF(r->ffmpeg.buf); c++)
+ pa_memchunk_reset(&r->ffmpeg.buf[c]);
+
+ return 0;
+}
+
+/*** 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 00000000..82d01082
--- /dev/null
+++ b/src/pulsecore/resampler.h
@@ -0,0 +1,100 @@
+#ifndef fooresamplerhfoo
+#define fooresamplerhfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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/memblock.h>
+#include <pulsecore/memchunk.h>
+
+typedef struct pa_resampler pa_resampler;
+
+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_MAX
+} pa_resample_method_t;
+
+typedef enum pa_resample_flags {
+ PA_RESAMPLER_VARIABLE_RATE = 1,
+ PA_RESAMPLER_NO_REMAP = 2, /* implies NO_REMIX */
+ PA_RESAMPLER_NO_REMIX = 4
+} pa_resample_flags_t;
+
+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,
+ 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);
+
+/* 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);
+
+/* 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);
+
+
+#endif
diff --git a/src/pulsecore/rtclock.c b/src/pulsecore/rtclock.c
new file mode 100644
index 00000000..07d776e4
--- /dev/null
+++ b/src/pulsecore/rtclock.c
@@ -0,0 +1,98 @@
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stddef.h>
+#include <time.h>
+#include <sys/time.h>
+
+#include <pulse/timeval.h>
+#include <pulsecore/macro.h>
+
+#include "rtclock.h"
+
+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) {
+#ifdef HAVE_CLOCK_GETTIME
+ struct timespec ts;
+
+#ifdef CLOCK_MONOTONIC
+ /* No locking or atomic ops for no_monotonic here */
+ static pa_bool_t no_monotonic = FALSE;
+
+ if (!no_monotonic)
+ if (clock_gettime(CLOCK_MONOTONIC, &ts) < 0)
+ no_monotonic = TRUE;
+
+ if (no_monotonic)
+#endif
+ pa_assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
+
+ pa_assert(tv);
+
+ tv->tv_sec = ts.tv_sec;
+ tv->tv_usec = ts.tv_nsec / 1000;
+
+ return tv;
+
+#else /* HAVE_CLOCK_GETTIME */
+
+ return pa_gettimeofday(tv);
+
+#endif
+}
+
+pa_bool_t pa_rtclock_hrtimer(void) {
+#ifdef HAVE_CLOCK_GETTIME
+ struct timespec ts;
+
+#ifdef CLOCK_MONOTONIC
+ if (clock_getres(CLOCK_MONOTONIC, &ts) >= 0)
+ return ts.tv_sec == 0 && ts.tv_nsec <= PA_HRTIMER_THRESHOLD_USEC*1000;
+#endif
+
+ pa_assert_se(clock_getres(CLOCK_REALTIME, &ts) == 0);
+ return ts.tv_sec == 0 && ts.tv_nsec <= PA_HRTIMER_THRESHOLD_USEC*1000;
+
+#else /* HAVE_CLOCK_GETTIME */
+
+ return FALSE;
+
+#endif
+}
+
+pa_usec_t pa_rtclock_usec(void) {
+ struct timeval tv;
+
+ return pa_timeval_load(pa_rtclock_get(&tv));
+}
diff --git a/src/pulsecore/rtclock.h b/src/pulsecore/rtclock.h
new file mode 100644
index 00000000..f0360af3
--- /dev/null
+++ b/src/pulsecore/rtclock.h
@@ -0,0 +1,43 @@
+#ifndef foopulsertclockhfoo
+#define foopulsertclockhfoo
+
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <pulsecore/macro.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_usec(void);
+
+pa_usec_t pa_rtclock_age(const struct timeval *tv);
+pa_bool_t pa_rtclock_hrtimer(void);
+
+/* timer with a resolution better than this are considered high-resolution */
+#define PA_HRTIMER_THRESHOLD_USEC 10
+
+#endif
diff --git a/src/pulsecore/rtpoll.c b/src/pulsecore/rtpoll.c
new file mode 100644
index 00000000..83008266
--- /dev/null
+++ b/src/pulsecore/rtpoll.c
@@ -0,0 +1,753 @@
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <signal.h>
+#include <string.h>
+#include <errno.h>
+
+#ifdef __linux__
+#include <sys/utsname.h>
+#endif
+
+#ifdef HAVE_POLL_H
+#include <poll.h>
+#else
+#include <pulsecore/poll.h>
+#endif
+
+#include <pulse/xmalloc.h>
+#include <pulse/timeval.h>
+
+#include <pulsecore/core-error.h>
+#include <pulsecore/rtclock.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/llist.h>
+#include <pulsecore/rtsig.h>
+#include <pulsecore/flist.h>
+#include <pulsecore/core-util.h>
+
+#include <pulsecore/winsock.h>
+
+#include "rtpoll.h"
+
+struct pa_rtpoll {
+ struct pollfd *pollfd, *pollfd2;
+ unsigned n_pollfd_alloc, n_pollfd_used;
+
+ pa_bool_t timer_enabled;
+ struct timeval next_elapse;
+ pa_usec_t period;
+
+ pa_bool_t scan_for_dead;
+ pa_bool_t running, installed, rebuild_needed, quit;
+
+#ifdef HAVE_PPOLL
+ int rtsig;
+ sigset_t sigset_unblocked;
+ timer_t timer;
+#ifdef __linux__
+ pa_bool_t dont_use_ppoll;
+#endif
+#endif
+
+ PA_LLIST_HEAD(pa_rtpoll_item, items);
+};
+
+struct pa_rtpoll_item {
+ pa_rtpoll *rtpoll;
+ pa_bool_t 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 *userdata;
+
+ PA_LLIST_FIELDS(pa_rtpoll_item);
+};
+
+PA_STATIC_FLIST_DECLARE(items, 0, pa_xfree);
+
+static void signal_handler_noop(int s) { }
+
+pa_rtpoll *pa_rtpoll_new(void) {
+ pa_rtpoll *p;
+
+ p = pa_xnew(pa_rtpoll, 1);
+
+#ifdef HAVE_PPOLL
+
+#ifdef __linux__
+ /* ppoll is broken on Linux < 2.6.16 */
+ p->dont_use_ppoll = FALSE;
+
+ {
+ struct utsname u;
+ unsigned major, minor, micro;
+
+ pa_assert_se(uname(&u) == 0);
+
+ if (sscanf(u.release, "%u.%u.%u", &major, &minor, &micro) != 3 ||
+ (major < 2) ||
+ (major == 2 && minor < 6) ||
+ (major == 2 && minor == 6 && micro < 16))
+
+ p->dont_use_ppoll = TRUE;
+ }
+
+#endif
+
+ p->rtsig = -1;
+ sigemptyset(&p->sigset_unblocked);
+ p->timer = (timer_t) -1;
+
+#endif
+
+ 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);
+ p->n_pollfd_used = 0;
+
+ p->period = 0;
+ memset(&p->next_elapse, 0, sizeof(p->next_elapse));
+ p->timer_enabled = FALSE;
+
+ p->running = FALSE;
+ p->installed = FALSE;
+ p->scan_for_dead = FALSE;
+ p->rebuild_needed = FALSE;
+ p->quit = FALSE;
+
+ PA_LLIST_HEAD_INIT(pa_rtpoll_item, p->items);
+
+ return p;
+}
+
+void pa_rtpoll_install(pa_rtpoll *p) {
+ pa_assert(p);
+ pa_assert(!p->installed);
+
+ p->installed = 1;
+
+#ifdef HAVE_PPOLL
+# ifdef __linux__
+ if (p->dont_use_ppoll)
+ return;
+# endif
+
+ if ((p->rtsig = pa_rtsig_get_for_thread()) < 0) {
+ pa_log_warn("Failed to reserve POSIX realtime signal.");
+ return;
+ }
+
+ pa_log_debug("Acquired POSIX realtime signal %s", pa_sig2str(p->rtsig));
+
+ {
+ sigset_t ss;
+ struct sigaction sa;
+
+ pa_assert_se(sigemptyset(&ss) == 0);
+ pa_assert_se(sigaddset(&ss, p->rtsig) == 0);
+ pa_assert_se(pthread_sigmask(SIG_BLOCK, &ss, &p->sigset_unblocked) == 0);
+ pa_assert_se(sigdelset(&p->sigset_unblocked, p->rtsig) == 0);
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = signal_handler_noop;
+ pa_assert_se(sigemptyset(&sa.sa_mask) == 0);
+
+ pa_assert_se(sigaction(p->rtsig, &sa, NULL) == 0);
+
+ /* We never reset the signal handler. Why should we? */
+ }
+
+#endif
+}
+
+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);
+
+#ifdef HAVE_PPOLL
+ if (p->timer != (timer_t) -1)
+ timer_delete(p->timer);
+#endif
+
+ 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_bool_t wait) {
+ pa_rtpoll_item *i;
+ int r = 0;
+ struct timeval timeout;
+
+ pa_assert(p);
+ pa_assert(!p->running);
+ pa_assert(p->installed);
+
+ p->running = TRUE;
+
+ /* 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)
+ goto finish;
+
+ if ((k = i->work_cb(i)) != 0) {
+ if (k < 0)
+ r = k;
+
+ 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;
+
+ goto finish;
+ }
+ }
+
+ if (p->rebuild_needed)
+ rtpoll_rebuild(p);
+
+ /* Calculate timeout */
+ if (!wait || p->quit) {
+ timeout.tv_sec = 0;
+ timeout.tv_usec = 0;
+ } else if (p->timer_enabled) {
+ struct timeval now;
+ pa_rtclock_get(&now);
+
+ memset(&timeout, 0, sizeof(timeout));
+ if (pa_timeval_cmp(&p->next_elapse, &now) > 0)
+ pa_timeval_add(&timeout, pa_timeval_diff(&p->next_elapse, &now));
+ }
+
+ /* OK, now let's sleep */
+#ifdef HAVE_PPOLL
+
+#ifdef __linux__
+ if (!p->dont_use_ppoll)
+#endif
+ {
+ 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->timer_enabled ? &ts : NULL, p->rtsig < 0 ? NULL : &p->sigset_unblocked);
+ }
+#ifdef __linux__
+ else
+#endif
+
+#endif
+ r = poll(p->pollfd, p->n_pollfd_used, p->timer_enabled ? (timeout.tv_sec*1000) + (timeout.tv_usec / 1000) : -1);
+
+ if (r < 0) {
+ if (errno == EAGAIN || errno == EINTR)
+ r = 0;
+ else
+ pa_log_error("poll(): %s", pa_cstrerror(errno));
+
+ reset_all_revents(p);
+ }
+
+ if (p->timer_enabled) {
+ if (p->period > 0) {
+ struct timeval now;
+ pa_rtclock_get(&now);
+
+ pa_timeval_add(&p->next_elapse, p->period);
+
+ /* Guarantee that the next timeout will happen in the future */
+ if (pa_timeval_cmp(&p->next_elapse, &now) < 0)
+ pa_timeval_add(&p->next_elapse, (pa_timeval_diff(&now, &p->next_elapse) / p->period + 1) * p->period);
+
+ } else
+ p->timer_enabled = FALSE;
+ }
+
+ /* 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;
+}
+
+static void update_timer(pa_rtpoll *p) {
+ pa_assert(p);
+
+#ifdef HAVE_PPOLL
+
+#ifdef __linux__
+ if (!p->dont_use_ppoll) {
+#endif
+
+ if (p->timer == (timer_t) -1) {
+ struct sigevent se;
+
+ memset(&se, 0, sizeof(se));
+ se.sigev_notify = SIGEV_SIGNAL;
+ se.sigev_signo = p->rtsig;
+
+ if (timer_create(CLOCK_MONOTONIC, &se, &p->timer) < 0)
+ if (timer_create(CLOCK_REALTIME, &se, &p->timer) < 0) {
+ pa_log_warn("Failed to allocate POSIX timer: %s", pa_cstrerror(errno));
+ p->timer = (timer_t) -1;
+ }
+ }
+
+ if (p->timer != (timer_t) -1) {
+ struct itimerspec its;
+ memset(&its, 0, sizeof(its));
+
+ if (p->timer_enabled) {
+ its.it_value.tv_sec = p->next_elapse.tv_sec;
+ its.it_value.tv_nsec = p->next_elapse.tv_usec*1000;
+
+ /* Make sure that 0,0 is not understood as
+ * "disarming" */
+ if (its.it_value.tv_sec == 0)
+ its.it_value.tv_nsec = 1;
+
+ if (p->period > 0) {
+ struct timeval tv;
+ pa_timeval_store(&tv, p->period);
+ its.it_interval.tv_sec = tv.tv_sec;
+ its.it_interval.tv_nsec = tv.tv_usec*1000;
+ }
+ }
+
+ pa_assert_se(timer_settime(p->timer, TIMER_ABSTIME, &its, NULL) == 0);
+ }
+
+#ifdef __linux__
+ }
+#endif
+
+#endif
+}
+
+void pa_rtpoll_set_timer_absolute(pa_rtpoll *p, const struct timeval *ts) {
+ pa_assert(p);
+ pa_assert(ts);
+
+ p->next_elapse = *ts;
+ p->period = 0;
+ p->timer_enabled = TRUE;
+
+ update_timer(p);
+}
+
+void pa_rtpoll_set_timer_periodic(pa_rtpoll *p, pa_usec_t usec) {
+ pa_assert(p);
+
+ p->period = usec;
+ pa_rtclock_get(&p->next_elapse);
+ pa_timeval_add(&p->next_elapse, usec);
+ p->timer_enabled = TRUE;
+
+ update_timer(p);
+}
+
+void pa_rtpoll_set_timer_relative(pa_rtpoll *p, pa_usec_t usec) {
+ pa_assert(p);
+
+ p->period = 0;
+ pa_rtclock_get(&p->next_elapse);
+ pa_timeval_add(&p->next_elapse, usec);
+ p->timer_enabled = TRUE;
+
+ update_timer(p);
+}
+
+void pa_rtpoll_set_timer_disabled(pa_rtpoll *p) {
+ pa_assert(p);
+
+ p->period = 0;
+ memset(&p->next_elapse, 0, sizeof(p->next_elapse));
+ p->timer_enabled = FALSE;
+
+ update_timer(p);
+}
+
+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->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)) {
+ pa_assert(i);
+ pa_assert(i->priority < PA_RTPOLL_NEVER);
+
+ i->before_cb = before_cb;
+}
+
+void pa_rtpoll_item_set_after_callback(pa_rtpoll_item *i, void (*after_cb)(pa_rtpoll_item *i)) {
+ pa_assert(i);
+ pa_assert(i->priority < PA_RTPOLL_NEVER);
+
+ i->after_cb = after_cb;
+}
+
+void pa_rtpoll_item_set_work_callback(pa_rtpoll_item *i, int (*work_cb)(pa_rtpoll_item *i)) {
+ pa_assert(i);
+ pa_assert(i->priority < PA_RTPOLL_NEVER);
+
+ i->work_cb = work_cb;
+}
+
+void pa_rtpoll_item_set_userdata(pa_rtpoll_item *i, void *userdata) {
+ pa_assert(i);
+
+ i->userdata = userdata;
+}
+
+void* pa_rtpoll_item_get_userdata(pa_rtpoll_item *i) {
+ pa_assert(i);
+
+ return i->userdata;
+}
+
+static int fdsem_before(pa_rtpoll_item *i) {
+
+ if (pa_fdsem_before_poll(i->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->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;
+
+ i->before_cb = fdsem_before;
+ i->after_cb = fdsem_after;
+ i->userdata = f;
+
+ return i;
+}
+
+static int asyncmsgq_before(pa_rtpoll_item *i) {
+ pa_assert(i);
+
+ if (pa_asyncmsgq_before_poll(i->userdata) < 0)
+ return 1; /* 1 means immediate restart of the loop */
+
+ return 0;
+}
+
+static void asyncmsgq_after(pa_rtpoll_item *i) {
+ pa_assert(i);
+
+ pa_assert((i->pollfd[0].revents & ~POLLIN) == 0);
+ pa_asyncmsgq_after_poll(i->userdata);
+}
+
+static int asyncmsgq_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->userdata, &object, &code, &data, &offset, &chunk, 0) == 0) {
+ int ret;
+
+ if (!object && code == PA_MESSAGE_SHUTDOWN) {
+ pa_asyncmsgq_done(i->userdata, 0);
+ pa_rtpoll_quit(i->rtpoll);
+ return 1;
+ }
+
+ ret = pa_asyncmsgq_dispatch(object, code, data, offset, &chunk);
+ pa_asyncmsgq_done(i->userdata, ret);
+ return 1;
+ }
+
+ return 0;
+}
+
+pa_rtpoll_item *pa_rtpoll_item_new_asyncmsgq(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_get_fd(q);
+ pollfd->events = POLLIN;
+
+ i->before_cb = asyncmsgq_before;
+ i->after_cb = asyncmsgq_after;
+ i->work_cb = asyncmsgq_work;
+ i->userdata = q;
+
+ return i;
+}
+
+void pa_rtpoll_quit(pa_rtpoll *p) {
+ pa_assert(p);
+
+ p->quit = TRUE;
+}
diff --git a/src/pulsecore/rtpoll.h b/src/pulsecore/rtpoll.h
new file mode 100644
index 00000000..02f5c7c2
--- /dev/null
+++ b/src/pulsecore/rtpoll.h
@@ -0,0 +1,116 @@
+#ifndef foopulsertpollhfoo
+#define foopulsertpollhfoo
+
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#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) It uses timer_create() and POSIX real time signals to guarantee
+ * optimal high-resolution timing. Starting with Linux 2.6.21 hrtimers
+ * are available, and since right now only nanosleep() and the POSIX
+ * clock and timer interfaces have been ported to hrtimers (and not
+ * ppoll/pselect!) we have to combine ppoll() with timer_create(). The
+ * fact that POSIX timers and POSIX rt signals are used internally is
+ * completely hidden.
+ *
+ * 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 veeery 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);
+
+/* Install the rtpoll in the current thread */
+void pa_rtpoll_install(pa_rtpoll *p);
+
+/* Sleep on the rtpoll until the time event, or any of the fd events
+ * is triggered. If "wait" is 0 we don't sleep but only update the
+ * struct pollfd. 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, pa_bool_t wait);
+
+void pa_rtpoll_set_timer_absolute(pa_rtpoll *p, const struct timeval *ts);
+void pa_rtpoll_set_timer_periodic(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);
+
+/* 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
+ * iteraton of the loop will start immediately. */
+void pa_rtpoll_item_set_work_callback(pa_rtpoll_item *i, int (*work_cb)(pa_rtpoll_item *i));
+
+/* 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 iteraton of the loop will start
+ * immediately.. */
+void pa_rtpoll_item_set_before_callback(pa_rtpoll_item *i, int (*before_cb)(pa_rtpoll_item *i));
+
+/* 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 pa_rtpoll_item_set_userdata(pa_rtpoll_item *i, void *userdata);
+void* pa_rtpoll_item_get_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(pa_rtpoll *p, pa_rtpoll_priority_t prio, pa_asyncmsgq *q);
+
+/* Requests the loop to exit. Will cause the next iteration of
+ * pa_rtpoll_run() to return 0 */
+void pa_rtpoll_quit(pa_rtpoll *p);
+
+#endif
diff --git a/src/pulsecore/rtsig.c b/src/pulsecore/rtsig.c
new file mode 100644
index 00000000..bfc49c88
--- /dev/null
+++ b/src/pulsecore/rtsig.c
@@ -0,0 +1,133 @@
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <signal.h>
+
+#include <pulsecore/macro.h>
+#include <pulsecore/flist.h>
+#include <pulsecore/once.h>
+#include <pulsecore/thread.h>
+#include <pulsecore/core-util.h>
+
+#include "rtsig.h"
+
+#ifdef SIGRTMIN
+
+static void _free_rtsig(void *p) {
+ pa_rtsig_put(PA_PTR_TO_INT(p));
+}
+
+PA_STATIC_FLIST_DECLARE(rtsig_flist, pa_make_power_of_two(SIGRTMAX-SIGRTMIN+1), NULL);
+PA_STATIC_TLS_DECLARE(rtsig_tls, _free_rtsig);
+
+static pa_atomic_t rtsig_current = PA_ATOMIC_INIT(-1);
+
+static int rtsig_start = -1, rtsig_end = -1;
+
+int pa_rtsig_get(void) {
+ void *p;
+ int sig;
+
+ if ((p = pa_flist_pop(PA_STATIC_FLIST_GET(rtsig_flist))))
+ return PA_PTR_TO_INT(p);
+
+ sig = pa_atomic_dec(&rtsig_current);
+
+ pa_assert(sig <= SIGRTMAX);
+ pa_assert(sig <= rtsig_end);
+
+ if (sig < rtsig_start) {
+ pa_atomic_inc(&rtsig_current);
+ return -1;
+ }
+
+ return sig;
+}
+
+int pa_rtsig_get_for_thread(void) {
+ int sig;
+ void *p;
+
+ if ((p = PA_STATIC_TLS_GET(rtsig_tls)))
+ return PA_PTR_TO_INT(p);
+
+ if ((sig = pa_rtsig_get()) < 0)
+ return -1;
+
+ PA_STATIC_TLS_SET(rtsig_tls, PA_INT_TO_PTR(sig));
+ return sig;
+}
+
+void pa_rtsig_put(int sig) {
+ pa_assert(sig >= rtsig_start);
+ pa_assert(sig <= rtsig_end);
+
+ pa_assert_se(pa_flist_push(PA_STATIC_FLIST_GET(rtsig_flist), PA_INT_TO_PTR(sig)) >= 0);
+}
+
+void pa_rtsig_configure(int start, int end) {
+ int s;
+ sigset_t ss;
+
+ pa_assert(pa_atomic_load(&rtsig_current) == -1);
+
+ pa_assert(SIGRTMIN <= start);
+ pa_assert(start <= end);
+ pa_assert(end <= SIGRTMAX);
+
+ rtsig_start = start;
+ rtsig_end = end;
+
+ sigemptyset(&ss);
+
+ for (s = rtsig_start; s <= rtsig_end; s++)
+ pa_assert_se(sigaddset(&ss, s) == 0);
+
+ pa_assert(pthread_sigmask(SIG_BLOCK, &ss, NULL) == 0);
+
+ /* We allocate starting from the end */
+ pa_atomic_store(&rtsig_current, rtsig_end);
+}
+
+#else /* SIGRTMIN */
+
+int pa_rtsig_get(void) {
+ return -1;
+}
+
+int pa_rtsig_get_for_thread(void) {
+ return -1;
+}
+
+void pa_rtsig_put(int sig) {
+}
+
+void pa_rtsig_configure(int start, int end) {
+}
+
+#endif /* SIGRTMIN */
diff --git a/src/pulsecore/rtsig.h b/src/pulsecore/rtsig.h
new file mode 100644
index 00000000..7830d272
--- /dev/null
+++ b/src/pulsecore/rtsig.h
@@ -0,0 +1,41 @@
+#ifndef foopulsertsighfoo
+#define foopulsertsighfoo
+
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+/* Return the next unused POSIX Realtime signals */
+int pa_rtsig_get(void);
+
+/* If not called before in the current thread, return the next unused
+ * rtsig, and install it in a TLS region and give it up automatically
+ * when the thread shuts down */
+int pa_rtsig_get_for_thread(void);
+
+/* Give an rtsig back. */
+void pa_rtsig_put(int sig);
+
+/* Block all RT signals */
+void pa_rtsig_configure(int start, int end);
+
+#endif
diff --git a/src/pulsecore/sample-util.c b/src/pulsecore/sample-util.c
new file mode 100644
index 00000000..4ea5d446
--- /dev/null
+++ b/src/pulsecore/sample-util.c
@@ -0,0 +1,933 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <liboil/liboilfuncs.h>
+#include <liboil/liboil.h>
+
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/g711.h>
+
+#include "sample-util.h"
+#include "endianmacros.h"
+
+#define PA_SILENCE_MAX (PA_PAGE_SIZE*16)
+
+pa_memblock *pa_silence_memblock_new(pa_mempool *pool, const pa_sample_spec *spec, size_t length) {
+ size_t fs;
+ pa_assert(pool);
+ pa_assert(spec);
+
+ if (length <= 0)
+ length = pa_bytes_per_second(spec)/20; /* 50 ms */
+
+ if (length > PA_SILENCE_MAX)
+ length = PA_SILENCE_MAX;
+
+ fs = pa_frame_size(spec);
+
+ length = (length+fs-1)/fs;
+
+ if (length <= 0)
+ length = 1;
+
+ length *= fs;
+
+ return pa_silence_memblock(pa_memblock_new(pool, length), spec);
+}
+
+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;
+}
+
+void 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);
+}
+
+void pa_silence_memory(void *p, size_t length, const pa_sample_spec *spec) {
+ uint8_t c = 0;
+ pa_assert(p);
+ pa_assert(length > 0);
+ pa_assert(spec);
+
+ switch (spec->format) {
+ case PA_SAMPLE_U8:
+ c = 0x80;
+ break;
+ case PA_SAMPLE_S16LE:
+ case PA_SAMPLE_S16BE:
+ case PA_SAMPLE_S32LE:
+ case PA_SAMPLE_S32BE:
+ case PA_SAMPLE_FLOAT32:
+ case PA_SAMPLE_FLOAT32RE:
+ c = 0;
+ break;
+ case PA_SAMPLE_ALAW:
+ c = 0xd5;
+ break;
+ case PA_SAMPLE_ULAW:
+ c = 0xff;
+ break;
+ default:
+ pa_assert_not_reached();
+ }
+
+ memset(p, c, length);
+}
+
+static void calc_linear_integer_stream_volumes(pa_mix_info streams[], unsigned nstreams, const pa_sample_spec *spec) {
+ unsigned k;
+
+ pa_assert(streams);
+ pa_assert(spec);
+
+ for (k = 0; k < nstreams; k++) {
+ unsigned channel;
+
+ for (channel = 0; channel < spec->channels; channel++) {
+ pa_mix_info *m = streams + k;
+ m->linear[channel].i = (int32_t) (pa_sw_volume_to_linear(m->volume.values[channel]) * 0x10000);
+ }
+ }
+}
+
+static void calc_linear_integer_volume(int32_t linear[], const pa_cvolume *volume) {
+ unsigned channel;
+
+ pa_assert(linear);
+ pa_assert(volume);
+
+ for (channel = 0; channel < volume->channels; channel++)
+ linear[channel] = (int32_t) (pa_sw_volume_to_linear(volume->values[channel]) * 0x10000);
+}
+
+static void calc_linear_float_stream_volumes(pa_mix_info streams[], unsigned nstreams, const pa_sample_spec *spec) {
+ unsigned k;
+
+ pa_assert(streams);
+ pa_assert(spec);
+
+ for (k = 0; k < nstreams; k++) {
+ unsigned channel;
+
+ for (channel = 0; channel < spec->channels; channel++) {
+ pa_mix_info *m = streams + k;
+ m->linear[channel].f = pa_sw_volume_to_linear(m->volume.values[channel]);
+ }
+ }
+}
+
+static void calc_linear_float_volume(float linear[], const pa_cvolume *volume) {
+ unsigned channel;
+
+ pa_assert(linear);
+ pa_assert(volume);
+
+ for (channel = 0; channel < volume->channels; channel++)
+ linear[channel] = pa_sw_volume_to_linear(volume->values[channel]);
+}
+
+size_t pa_mix(
+ pa_mix_info streams[],
+ unsigned nstreams,
+ void *data,
+ size_t length,
+ const pa_sample_spec *spec,
+ const pa_cvolume *volume,
+ pa_bool_t mute) {
+
+ pa_cvolume full_volume;
+ unsigned k;
+ size_t d = 0;
+
+ pa_assert(streams);
+ pa_assert(data);
+ pa_assert(length);
+ pa_assert(spec);
+
+ if (!volume)
+ volume = pa_cvolume_reset(&full_volume, spec->channels);
+
+ for (k = 0; k < nstreams; k++)
+ streams[k].ptr = (uint8_t*) pa_memblock_acquire(streams[k].chunk.memblock) + streams[k].chunk.index;
+
+ switch (spec->format) {
+
+ case PA_SAMPLE_S16NE:{
+ unsigned channel = 0;
+ int32_t linear[PA_CHANNELS_MAX];
+
+ calc_linear_integer_stream_volumes(streams, nstreams, spec);
+ calc_linear_integer_volume(linear, volume);
+
+ for (d = 0;; d += sizeof(int16_t)) {
+ int32_t sum = 0;
+ unsigned i;
+
+ if (PA_UNLIKELY(d >= length))
+ goto finish;
+
+ for (i = 0; i < nstreams; i++) {
+ pa_mix_info *m = streams + i;
+ int32_t v, cv = m->linear[channel].i;
+
+ if (PA_UNLIKELY(d >= m->chunk.length))
+ goto finish;
+
+ if (PA_UNLIKELY(cv <= 0) || PA_UNLIKELY(!!mute) || PA_UNLIKELY(linear[channel] <= 0))
+ v = 0;
+ else {
+ v = *((int16_t*) m->ptr);
+ v = (v * cv) / 0x10000;
+ }
+
+ sum += v;
+ m->ptr = (uint8_t*) m->ptr + sizeof(int16_t);
+ }
+
+ sum = PA_CLAMP_UNLIKELY(sum, -0x8000, 0x7FFF);
+ sum = (sum * linear[channel]) / 0x10000;
+ *((int16_t*) data) = (int16_t) sum;
+
+ data = (uint8_t*) data + sizeof(int16_t);
+
+ if (PA_UNLIKELY(++channel >= spec->channels))
+ channel = 0;
+ }
+
+ break;
+ }
+
+ case PA_SAMPLE_S16RE:{
+ unsigned channel = 0;
+ int32_t linear[PA_CHANNELS_MAX];
+
+ calc_linear_integer_stream_volumes(streams, nstreams, spec);
+ calc_linear_integer_volume(linear, volume);
+
+ for (d = 0;; d += sizeof(int16_t)) {
+ int32_t sum = 0;
+ unsigned i;
+
+ if (PA_UNLIKELY(d >= length))
+ goto finish;
+
+ for (i = 0; i < nstreams; i++) {
+ pa_mix_info *m = streams + i;
+ int32_t v, cv = m->linear[channel].i;
+
+ if (PA_UNLIKELY(d >= m->chunk.length))
+ goto finish;
+
+ if (PA_UNLIKELY(cv <= 0) || PA_UNLIKELY(!!mute) || PA_UNLIKELY(linear[channel] <= 0))
+ v = 0;
+ else {
+ v = PA_INT16_SWAP(*((int16_t*) m->ptr));
+ v = (v * cv) / 0x10000;
+ }
+
+ sum += v;
+ m->ptr = (uint8_t*) m->ptr + sizeof(int16_t);
+ }
+
+ sum = PA_CLAMP_UNLIKELY(sum, -0x8000, 0x7FFF);
+ sum = (sum * linear[channel]) / 0x10000;
+ *((int16_t*) data) = PA_INT16_SWAP((int16_t) sum);
+
+ data = (uint8_t*) data + sizeof(int16_t);
+
+ if (PA_UNLIKELY(++channel >= spec->channels))
+ channel = 0;
+ }
+
+ break;
+ }
+
+ case PA_SAMPLE_S32NE:{
+ unsigned channel = 0;
+ int32_t linear[PA_CHANNELS_MAX];
+
+ calc_linear_integer_stream_volumes(streams, nstreams, spec);
+ calc_linear_integer_volume(linear, volume);
+
+ for (d = 0;; d += sizeof(int32_t)) {
+ int64_t sum = 0;
+ unsigned i;
+
+ if (PA_UNLIKELY(d >= length))
+ goto finish;
+
+ for (i = 0; i < nstreams; i++) {
+ pa_mix_info *m = streams + i;
+ int64_t v;
+ int32_t cv = m->linear[channel].i;
+
+ if (PA_UNLIKELY(d >= m->chunk.length))
+ goto finish;
+
+ if (PA_UNLIKELY(cv <= 0) || PA_UNLIKELY(!!mute) || PA_UNLIKELY(linear[channel] <= 0))
+ v = 0;
+ else {
+ v = *((int32_t*) m->ptr);
+ v = (v * cv) / 0x10000;
+ }
+
+ sum += v;
+ m->ptr = (uint8_t*) m->ptr + sizeof(int32_t);
+ }
+
+ sum = PA_CLAMP_UNLIKELY(sum, -0x80000000LL, 0x7FFFFFFFLL);
+ sum = (sum * linear[channel]) / 0x10000;
+ *((int32_t*) data) = (int32_t) sum;
+
+ data = (uint8_t*) data + sizeof(int32_t);
+
+ if (PA_UNLIKELY(++channel >= spec->channels))
+ channel = 0;
+ }
+
+ break;
+ }
+
+ case PA_SAMPLE_S32RE:{
+ unsigned channel = 0;
+ int32_t linear[PA_CHANNELS_MAX];
+
+ calc_linear_integer_stream_volumes(streams, nstreams, spec);
+ calc_linear_integer_volume(linear, volume);
+
+ for (d = 0;; d += sizeof(int32_t)) {
+ int64_t sum = 0;
+ unsigned i;
+
+ if (PA_UNLIKELY(d >= length))
+ goto finish;
+
+ for (i = 0; i < nstreams; i++) {
+ pa_mix_info *m = streams + i;
+ int64_t v;
+ int32_t cv = m->linear[channel].i;
+
+ if (PA_UNLIKELY(d >= m->chunk.length))
+ goto finish;
+
+ if (PA_UNLIKELY(cv <= 0) || PA_UNLIKELY(!!mute) || PA_UNLIKELY(linear[channel] <= 0))
+ v = 0;
+ else {
+ v = PA_INT32_SWAP(*((int32_t*) m->ptr));
+ v = (v * cv) / 0x10000;
+ }
+
+ sum += v;
+ m->ptr = (uint8_t*) m->ptr + sizeof(int32_t);
+ }
+
+ sum = PA_CLAMP_UNLIKELY(sum, -0x80000000LL, 0x7FFFFFFFLL);
+ sum = (sum * linear[channel]) / 0x10000;
+ *((int32_t*) data) = PA_INT32_SWAP((int32_t) sum);
+
+ data = (uint8_t*) data + sizeof(int32_t);
+
+ if (PA_UNLIKELY(++channel >= spec->channels))
+ channel = 0;
+ }
+
+ break;
+ }
+
+ case PA_SAMPLE_U8: {
+ unsigned channel = 0;
+ int32_t linear[PA_CHANNELS_MAX];
+
+ calc_linear_integer_stream_volumes(streams, nstreams, spec);
+ calc_linear_integer_volume(linear, volume);
+
+ for (d = 0;; d ++) {
+ int32_t sum = 0;
+ unsigned i;
+
+ if (PA_UNLIKELY(d >= length))
+ goto finish;
+
+ for (i = 0; i < nstreams; i++) {
+ pa_mix_info *m = streams + i;
+ int32_t v, cv = m->linear[channel].i;
+
+ if (PA_UNLIKELY(d >= m->chunk.length))
+ goto finish;
+
+ if (PA_UNLIKELY(cv <= 0) || PA_UNLIKELY(!!mute) || PA_UNLIKELY(linear[channel] <= 0))
+ v = 0;
+ else {
+ v = (int32_t) *((uint8_t*) m->ptr) - 0x80;
+ v = (v * cv) / 0x10000;
+ }
+
+ sum += v;
+ m->ptr = (uint8_t*) m->ptr + 1;
+ }
+
+ sum = (sum * linear[channel]) / 0x10000;
+ sum = PA_CLAMP_UNLIKELY(sum, -0x80, 0x7F);
+ *((uint8_t*) data) = (uint8_t) (sum + 0x80);
+
+ data = (uint8_t*) data + 1;
+
+ if (PA_UNLIKELY(++channel >= spec->channels))
+ channel = 0;
+ }
+
+ break;
+ }
+
+ case PA_SAMPLE_ULAW: {
+ unsigned channel = 0;
+ int32_t linear[PA_CHANNELS_MAX];
+
+ calc_linear_integer_stream_volumes(streams, nstreams, spec);
+ calc_linear_integer_volume(linear, volume);
+
+ for (d = 0;; d ++) {
+ int32_t sum = 0;
+ unsigned i;
+
+ if (PA_UNLIKELY(d >= length))
+ goto finish;
+
+ for (i = 0; i < nstreams; i++) {
+ pa_mix_info *m = streams + i;
+ int32_t v, cv = m->linear[channel].i;
+
+ if (PA_UNLIKELY(d >= m->chunk.length))
+ goto finish;
+
+ if (PA_UNLIKELY(cv <= 0) || PA_UNLIKELY(!!mute) || PA_UNLIKELY(linear[channel] <= 0))
+ v = 0;
+ else {
+ v = (int32_t) st_ulaw2linear16(*((uint8_t*) m->ptr));
+ v = (v * cv) / 0x10000;
+ }
+
+ sum += v;
+ m->ptr = (uint8_t*) m->ptr + 1;
+ }
+
+ sum = PA_CLAMP_UNLIKELY(sum, -0x8000, 0x7FFF);
+ sum = (sum * linear[channel]) / 0x10000;
+ *((uint8_t*) data) = (uint8_t) st_14linear2ulaw(sum >> 2);
+
+ data = (uint8_t*) data + 1;
+
+ if (PA_UNLIKELY(++channel >= spec->channels))
+ channel = 0;
+ }
+
+ break;
+ }
+
+ case PA_SAMPLE_ALAW: {
+ unsigned channel = 0;
+ int32_t linear[PA_CHANNELS_MAX];
+
+ calc_linear_integer_stream_volumes(streams, nstreams, spec);
+ calc_linear_integer_volume(linear, volume);
+
+ for (d = 0;; d ++) {
+ int32_t sum = 0;
+ unsigned i;
+
+ if (PA_UNLIKELY(d >= length))
+ goto finish;
+
+ for (i = 0; i < nstreams; i++) {
+ pa_mix_info *m = streams + i;
+ int32_t v, cv = m->linear[channel].i;
+
+ if (PA_UNLIKELY(d >= m->chunk.length))
+ goto finish;
+
+ if (PA_UNLIKELY(cv <= 0) || PA_UNLIKELY(!!mute) || PA_UNLIKELY(linear[channel] <= 0))
+ v = 0;
+ else {
+ v = (int32_t) st_alaw2linear16(*((uint8_t*) m->ptr));
+ v = (v * cv) / 0x10000;
+ }
+
+ sum += v;
+ m->ptr = (uint8_t*) m->ptr + 1;
+ }
+
+ sum = PA_CLAMP_UNLIKELY(sum, -0x8000, 0x7FFF);
+ sum = (sum * linear[channel]) / 0x10000;
+ *((uint8_t*) data) = (uint8_t) st_13linear2alaw(sum >> 3);
+
+ data = (uint8_t*) data + 1;
+
+ if (PA_UNLIKELY(++channel >= spec->channels))
+ channel = 0;
+ }
+
+ break;
+ }
+
+ case PA_SAMPLE_FLOAT32NE: {
+ unsigned channel = 0;
+ float linear[PA_CHANNELS_MAX];
+
+ calc_linear_float_stream_volumes(streams, nstreams, spec);
+ calc_linear_float_volume(linear, volume);
+
+ for (d = 0;; d += sizeof(float)) {
+ float sum = 0;
+ unsigned i;
+
+ if (PA_UNLIKELY(d >= length))
+ goto finish;
+
+ for (i = 0; i < nstreams; i++) {
+ pa_mix_info *m = streams + i;
+ float v, cv = m->linear[channel].f;
+
+ if (PA_UNLIKELY(d >= m->chunk.length))
+ goto finish;
+
+ if (PA_UNLIKELY(cv <= 0) || PA_UNLIKELY(!!mute) || PA_UNLIKELY(linear[channel] <= 0))
+ v = 0;
+ else {
+ v = *((float*) m->ptr);
+ v *= cv;
+ }
+
+ sum += v;
+ m->ptr = (uint8_t*) m->ptr + sizeof(float);
+ }
+
+ sum *= linear[channel];
+ *((float*) data) = sum;
+
+ data = (uint8_t*) data + sizeof(float);
+
+ if (PA_UNLIKELY(++channel >= spec->channels))
+ channel = 0;
+ }
+
+ break;
+ }
+
+ case PA_SAMPLE_FLOAT32RE: {
+ unsigned channel = 0;
+ float linear[PA_CHANNELS_MAX];
+
+ calc_linear_float_stream_volumes(streams, nstreams, spec);
+ calc_linear_float_volume(linear, volume);
+
+ for (d = 0;; d += sizeof(float)) {
+ float sum = 0;
+ unsigned i;
+
+ if (PA_UNLIKELY(d >= length))
+ goto finish;
+
+ for (i = 0; i < nstreams; i++) {
+ pa_mix_info *m = streams + i;
+ float v, cv = m->linear[channel].f;
+
+ if (PA_UNLIKELY(d >= m->chunk.length))
+ goto finish;
+
+ if (PA_UNLIKELY(cv <= 0) || PA_UNLIKELY(!!mute) || PA_UNLIKELY(linear[channel] <= 0))
+ v = 0;
+ else {
+ uint32_t z = *(uint32_t*) m->ptr;
+ z = PA_UINT32_SWAP(z);
+ v = *((float*) &z);
+ v *= cv;
+ }
+
+ sum += v;
+ m->ptr = (uint8_t*) m->ptr + sizeof(float);
+ }
+
+ sum *= linear[channel];
+ *((uint32_t*) data) = PA_UINT32_SWAP(*(uint32_t*) &sum);
+
+ data = (uint8_t*) data + sizeof(float);
+
+ if (PA_UNLIKELY(++channel >= spec->channels))
+ channel = 0;
+ }
+
+ break;
+ }
+
+ default:
+ pa_log_error("ERROR: Unable to mix audio data of format %s.", pa_sample_format_to_string(spec->format));
+ pa_assert_not_reached();
+ }
+
+finish:
+
+ for (k = 0; k < nstreams; k++)
+ pa_memblock_release(streams[k].chunk.memblock);
+
+ return d;
+}
+
+
+void pa_volume_memchunk(
+ pa_memchunk*c,
+ const pa_sample_spec *spec,
+ const pa_cvolume *volume) {
+
+ void *ptr;
+
+ pa_assert(c);
+ pa_assert(spec);
+ pa_assert(c->length % pa_frame_size(spec) == 0);
+ pa_assert(volume);
+
+ if (pa_cvolume_channels_equal_to(volume, PA_VOLUME_NORM))
+ return;
+
+ if (pa_cvolume_channels_equal_to(volume, PA_VOLUME_MUTED)) {
+ pa_silence_memchunk(c, spec);
+ return;
+ }
+
+ ptr = (uint8_t*) pa_memblock_acquire(c->memblock) + c->index;
+
+ switch (spec->format) {
+
+ case PA_SAMPLE_S16NE: {
+ int16_t *d;
+ size_t n;
+ unsigned channel;
+ int32_t linear[PA_CHANNELS_MAX];
+
+ calc_linear_integer_volume(linear, volume);
+
+ for (channel = 0, d = ptr, n = c->length/sizeof(int16_t); n > 0; d++, n--) {
+ int32_t t;
+
+ t = (int32_t)(*d);
+ t = (t * linear[channel]) / 0x10000;
+ t = PA_CLAMP_UNLIKELY(t, -0x8000, 0x7FFF);
+ *d = (int16_t) t;
+
+ if (PA_UNLIKELY(++channel >= spec->channels))
+ channel = 0;
+ }
+ break;
+ }
+
+ case PA_SAMPLE_S16RE: {
+ int16_t *d;
+ size_t n;
+ unsigned channel;
+ int32_t linear[PA_CHANNELS_MAX];
+
+ calc_linear_integer_volume(linear, volume);
+
+ for (channel = 0, d = ptr, n = c->length/sizeof(int16_t); n > 0; d++, n--) {
+ int32_t t;
+
+ t = (int32_t)(PA_INT16_SWAP(*d));
+ t = (t * linear[channel]) / 0x10000;
+ t = PA_CLAMP_UNLIKELY(t, -0x8000, 0x7FFF);
+ *d = PA_INT16_SWAP((int16_t) t);
+
+ if (PA_UNLIKELY(++channel >= spec->channels))
+ channel = 0;
+ }
+
+ break;
+ }
+
+ case PA_SAMPLE_S32NE: {
+ int32_t *d;
+ size_t n;
+ unsigned channel;
+ int32_t linear[PA_CHANNELS_MAX];
+
+ calc_linear_integer_volume(linear, volume);
+
+ for (channel = 0, d = ptr, n = c->length/sizeof(int32_t); n > 0; d++, n--) {
+ int64_t t;
+
+ t = (int64_t)(*d);
+ t = (t * linear[channel]) / 0x10000;
+ t = PA_CLAMP_UNLIKELY(t, -0x80000000LL, 0x7FFFFFFFLL);
+ *d = (int32_t) t;
+
+ if (PA_UNLIKELY(++channel >= spec->channels))
+ channel = 0;
+ }
+ break;
+ }
+
+ case PA_SAMPLE_S32RE: {
+ int32_t *d;
+ size_t n;
+ unsigned channel;
+ int32_t linear[PA_CHANNELS_MAX];
+
+ calc_linear_integer_volume(linear, volume);
+
+ for (channel = 0, d = ptr, n = c->length/sizeof(int32_t); n > 0; d++, n--) {
+ int64_t t;
+
+ t = (int64_t)(PA_INT32_SWAP(*d));
+ t = (t * linear[channel]) / 0x10000;
+ t = PA_CLAMP_UNLIKELY(t, -0x80000000LL, 0x7FFFFFFFLL);
+ *d = PA_INT32_SWAP((int32_t) t);
+
+ if (PA_UNLIKELY(++channel >= spec->channels))
+ channel = 0;
+ }
+
+ break;
+ }
+
+ case PA_SAMPLE_U8: {
+ uint8_t *d;
+ size_t n;
+ unsigned channel;
+ int32_t linear[PA_CHANNELS_MAX];
+
+ calc_linear_integer_volume(linear, volume);
+
+ for (channel = 0, d = ptr, n = c->length; n > 0; d++, n--) {
+ int32_t t;
+
+ t = (int32_t) *d - 0x80;
+ t = (t * linear[channel]) / 0x10000;
+ t = PA_CLAMP_UNLIKELY(t, -0x80, 0x7F);
+ *d = (uint8_t) (t + 0x80);
+
+ if (PA_UNLIKELY(++channel >= spec->channels))
+ channel = 0;
+ }
+ break;
+ }
+
+ case PA_SAMPLE_ULAW: {
+ uint8_t *d;
+ size_t n;
+ unsigned channel;
+ int32_t linear[PA_CHANNELS_MAX];
+
+ calc_linear_integer_volume(linear, volume);
+
+ for (channel = 0, d = ptr, n = c->length; n > 0; d++, n--) {
+ int32_t t;
+
+ t = (int32_t) st_ulaw2linear16(*d);
+ t = (t * linear[channel]) / 0x10000;
+ t = PA_CLAMP_UNLIKELY(t, -0x8000, 0x7FFF);
+ *d = (uint8_t) st_14linear2ulaw(t >> 2);
+
+ if (PA_UNLIKELY(++channel >= spec->channels))
+ channel = 0;
+ }
+ break;
+ }
+
+ case PA_SAMPLE_ALAW: {
+ uint8_t *d;
+ size_t n;
+ unsigned channel;
+ int32_t linear[PA_CHANNELS_MAX];
+
+ calc_linear_integer_volume(linear, volume);
+
+ for (channel = 0, d = ptr, n = c->length; n > 0; d++, n--) {
+ int32_t t;
+
+ t = (int32_t) st_alaw2linear16(*d);
+ t = (t * linear[channel]) / 0x10000;
+ t = PA_CLAMP_UNLIKELY(t, -0x8000, 0x7FFF);
+ *d = (uint8_t) st_13linear2alaw(t >> 3);
+
+ if (PA_UNLIKELY(++channel >= spec->channels))
+ channel = 0;
+ }
+ break;
+ }
+
+ case PA_SAMPLE_FLOAT32NE: {
+ float *d;
+ int skip;
+ unsigned n;
+ unsigned channel;
+
+ d = ptr;
+ skip = spec->channels * sizeof(float);
+ n = c->length/sizeof(float)/spec->channels;
+
+ for (channel = 0; channel < spec->channels; channel ++) {
+ float v, *t;
+
+ if (PA_UNLIKELY(volume->values[channel] == PA_VOLUME_NORM))
+ continue;
+
+ v = (float) pa_sw_volume_to_linear(volume->values[channel]);
+ t = d + channel;
+ oil_scalarmult_f32(t, skip, t, skip, &v, n);
+ }
+ break;
+ }
+
+ case PA_SAMPLE_FLOAT32RE: {
+ uint32_t *d;
+ size_t n;
+ unsigned channel;
+ float linear[PA_CHANNELS_MAX];
+
+ calc_linear_float_volume(linear, volume);
+
+ for (channel = 0, d = ptr, n = c->length/sizeof(float); n > 0; d++, n--) {
+ float t;
+ uint32_t z;
+
+ z = PA_UINT32_SWAP(*d);
+ t = *(float*) &z;
+ t *= linear[channel];
+ z = *(uint32_t*) &t;
+ *d = PA_UINT32_SWAP(z);
+
+ if (PA_UNLIKELY(++channel >= spec->channels))
+ channel = 0;
+ }
+
+ break;
+ }
+
+
+ default:
+ pa_log_warn(" Unable to change volume of format %s.", pa_sample_format_to_string(spec->format));
+ /* If we cannot change the volume, we just don't do it */
+ }
+
+ pa_memblock_release(c->memblock);
+}
+
+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;
+}
+
+int 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 ++) {
+ oil_memcpy(d, s, 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 ++) {
+ oil_memcpy(d, s, ss);
+ s = (uint8_t*) s + fs;
+ d = (uint8_t*) d + ss;
+ }
+ }
+}
diff --git a/src/pulsecore/sample-util.h b/src/pulsecore/sample-util.h
new file mode 100644
index 00000000..2ef8f924
--- /dev/null
+++ b/src/pulsecore/sample-util.h
@@ -0,0 +1,73 @@
+#ifndef foosampleutilhfoo
+#define foosampleutilhfoo
+
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <pulse/sample.h>
+#include <pulse/volume.h>
+#include <pulsecore/memblock.h>
+#include <pulsecore/memchunk.h>
+
+pa_memblock *pa_silence_memblock(pa_memblock* b, const pa_sample_spec *spec);
+pa_memblock *pa_silence_memblock_new(pa_mempool *pool, const pa_sample_spec *spec, size_t length);
+void pa_silence_memchunk(pa_memchunk *c, const pa_sample_spec *spec);
+void pa_silence_memory(void *p, size_t length, const pa_sample_spec *spec);
+
+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,
+ pa_bool_t mute);
+
+void pa_volume_memchunk(
+ pa_memchunk*c,
+ const pa_sample_spec *spec,
+ const pa_cvolume *volume);
+
+size_t pa_frame_align(size_t l, const pa_sample_spec *ss) PA_GCC_PURE;
+
+int 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);
+
+#endif
diff --git a/src/pulsecore/sconv-s16be.c b/src/pulsecore/sconv-s16be.c
new file mode 100644
index 00000000..638beb2e
--- /dev/null
+++ b/src/pulsecore/sconv-s16be.c
@@ -0,0 +1,61 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "endianmacros.h"
+
+#define INT16_FROM PA_INT16_FROM_BE
+#define INT16_TO PA_INT16_TO_BE
+
+#define INT32_FROM PA_INT32_FROM_BE
+#define INT32_TO PA_INT32_TO_BE
+
+#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_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
+
+#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 00000000..454c9508
--- /dev/null
+++ b/src/pulsecore/sconv-s16be.h
@@ -0,0 +1,61 @@
+#ifndef foosconv_s16befoo
+#define foosconv_s16befoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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 <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_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);
+
+#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
+
+#define pa_sconv_float32be_to_s32ne pa_sconv_s32be_from_float32ne
+#define pa_sconv_float32be_from_s32ne pa_sconv_s32be_to_float32ne
+#define pa_sconv_float32le_to_s32ne pa_sconv_s32be_from_float32re
+#define pa_sconv_float32le_from_s32ne pa_sconv_s32be_to_float32re
+
+#define pa_sconv_s16be_to_s32ne pa_sconv_s32be_from_s16ne
+#define pa_sconv_s16be_from_s32ne pa_sconv_s32be_to_s16ne
+#define pa_sconv_s16le_to_s32ne pa_sconv_s32be_from_s16re
+#define pa_sconv_s16le_from_s32ne pa_sconv_s32be_to_s16re
+#endif
+
+#endif
diff --git a/src/pulsecore/sconv-s16le.c b/src/pulsecore/sconv-s16le.c
new file mode 100644
index 00000000..90e9b6d2
--- /dev/null
+++ b/src/pulsecore/sconv-s16le.c
@@ -0,0 +1,251 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+/* Despite the name of this file we implement S32 handling here, too. */
+
+#include <inttypes.h>
+#include <stdio.h>
+
+#include <liboil/liboilfuncs.h>
+
+#include <pulsecore/sconv.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/log.h>
+
+#include "endianmacros.h"
+
+#include "sconv-s16le.h"
+
+#ifndef INT16_FROM
+#define INT16_FROM PA_INT16_FROM_LE
+#endif
+
+#ifndef INT16_TO
+#define INT16_TO PA_INT16_TO_LE
+#endif
+
+#ifndef INT32_FROM
+#define INT32_FROM PA_INT32_FROM_LE
+#endif
+
+#ifndef INT32_TO
+#define INT32_TO PA_INT32_TO_LE
+#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++) = ((float) INT16_FROM(s))/0x7FFF;
+ }
+
+#else
+{
+ static const double add = 0, factor = 1.0/0x7FFF;
+ oil_scaleconv_f32_s16(b, a, n, &add, &factor);
+}
+#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++) = (float) (((double) INT32_FROM(s))/0x7FFFFFFF);
+ }
+
+#else
+{
+ static const double add = 0, factor = 1.0/0x7FFFFFFF;
+ oil_scaleconv_f32_s32(b, a, n, &add, &factor);
+}
+#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++);
+
+ v = PA_CLAMP_UNLIKELY(v, -1, 1);
+ s = (int16_t) (v * 0x7FFF);
+ *(b++) = INT16_TO(s);
+ }
+
+#else
+{
+ static const double add = 0, factor = 0x7FFF;
+ oil_scaleconv_s16_f32(b, a, n, &add, &factor);
+}
+#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++);
+
+ v = PA_CLAMP_UNLIKELY(v, -1, 1);
+ s = (int32_t) ((double) v * (double) 0x7FFFFFFF);
+ *(b++) = INT32_TO(s);
+ }
+
+#else
+{
+ static const double add = 0, factor = 0x7FFFFFFF;
+ oil_scaleconv_s32_f32(b, a, n, &add, &factor);
+}
+#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 = ((float) INT16_FROM(s))/0x7FFF;
+ uint32_t *j = (uint32_t*) &k;
+ *j = PA_UINT32_SWAP(*j);
+ *(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 = (float) (((double) INT32_FROM(s))/0x7FFFFFFF);
+ uint32_t *j = (uint32_t*) &k;
+ *j = PA_UINT32_SWAP(*j);
+ *(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 = *(a++);
+ uint32_t *j = (uint32_t*) &v;
+ *j = PA_UINT32_SWAP(*j);
+ v = PA_CLAMP_UNLIKELY(v, -1, 1);
+ s = (int16_t) (v * 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 = *(a++);
+ uint32_t *j = (uint32_t*) &v;
+ *j = PA_UINT32_SWAP(*j);
+ v = PA_CLAMP_UNLIKELY(v, -1, 1);
+ s = (int32_t) ((double) v * 0x7FFFFFFF);
+ *(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_UINT32_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_UINT16_SWAP(*a)) << 16;
+ *b = INT32_TO(s);
+ a++;
+ b++;
+ }
+}
diff --git a/src/pulsecore/sconv-s16le.h b/src/pulsecore/sconv-s16le.h
new file mode 100644
index 00000000..4165f8a2
--- /dev/null
+++ b/src/pulsecore/sconv-s16le.h
@@ -0,0 +1,61 @@
+#ifndef foosconv_s16lefoo
+#define foosconv_s16lefoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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 <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_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);
+
+#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
+
+#define pa_sconv_float32be_to_s32ne pa_sconv_s32le_from_float32re
+#define pa_sconv_float32be_from_s32ne pa_sconv_s32le_to_float32re
+#define pa_sconv_float32le_to_s32ne pa_sconv_s32le_from_float32ne
+#define pa_sconv_float32le_from_s32ne pa_sconv_s32le_to_float32ne
+
+#define pa_sconv_s16be_to_s32ne pa_sconv_s32le_from_s16re
+#define pa_sconv_s16be_from_s32ne pa_sconv_s32le_to_s16re
+#define pa_sconv_s16le_to_s32ne pa_sconv_s32le_from_s16ne
+#define pa_sconv_s16le_from_s32ne pa_sconv_s32le_to_s16ne
+#endif
+
+#endif
diff --git a/src/pulsecore/sconv.c b/src/pulsecore/sconv.c
new file mode 100644
index 00000000..ebd74586
--- /dev/null
+++ b/src/pulsecore/sconv.c
@@ -0,0 +1,271 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <liboil/liboilfuncs.h>
+#include <liboil/liboil.h>
+
+#include <pulsecore/g711.h>
+#include <pulsecore/macro.h>
+
+#include "endianmacros.h"
+#include "sconv-s16le.h"
+#include "sconv-s16be.h"
+
+#include "sconv.h"
+
+/* u8 */
+static void u8_to_float32ne(unsigned n, const uint8_t *a, float *b) {
+ static const double add = -1, factor = 1.0/128.0;
+
+ pa_assert(a);
+ pa_assert(b);
+
+ oil_scaleconv_f32_u8(b, a, n, &add, &factor);
+}
+
+static void u8_from_float32ne(unsigned n, const float *a, uint8_t *b) {
+ static const double add = 128, factor = 127.0;
+
+ pa_assert(a);
+ pa_assert(b);
+
+ oil_scaleconv_u8_f32(b, a, n, &add, &factor);
+}
+
+static void u8_to_s16ne(unsigned n, const uint8_t *a, int16_t *b) {
+ static const int16_t add = -0x80, factor = 0x100;
+
+ pa_assert(a);
+ pa_assert(b);
+
+ oil_conv_s16_u8(b, 2, a, 1, n);
+ oil_scalaradd_s16(b, 2, b, 2, &add, n);
+ oil_scalarmult_s16(b, 2, b, 2, &factor, n);
+}
+
+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) (*a / 0x100 + 0x80);
+}
+
+/* float32 */
+
+static void float32ne_to_float32ne(unsigned n, const float *a, float *b) {
+ pa_assert(a);
+ pa_assert(b);
+
+ oil_memcpy(b, a, 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);
+
+ oil_memcpy(b, a, 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_UINT16_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, 1);
+ v *= 0x1FFF;
+ *(b++) = st_14linear2ulaw((int16_t) 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, 1);
+ v *= 0xFFF;
+ *b = st_13linear2alaw((int16_t) 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(*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);
+}
+
+pa_convert_func_t pa_get_convert_to_float32ne_function(pa_sample_format_t f) {
+
+ static const pa_convert_func_t 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_FLOAT32NE] = (pa_convert_func_t) float32ne_to_float32ne,
+ [PA_SAMPLE_FLOAT32RE] = (pa_convert_func_t) float32re_to_float32ne,
+ };
+
+ pa_assert(f >= 0);
+ pa_assert(f < PA_SAMPLE_MAX);
+
+ return table[f];
+}
+
+pa_convert_func_t pa_get_convert_from_float32ne_function(pa_sample_format_t f) {
+
+ static const pa_convert_func_t 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_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_assert(f >= 0);
+ pa_assert(f < PA_SAMPLE_MAX);
+
+ return table[f];
+}
+
+pa_convert_func_t pa_get_convert_to_s16ne_function(pa_sample_format_t f) {
+
+ static const pa_convert_func_t 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_ALAW] = (pa_convert_func_t) alaw_to_s16ne,
+ [PA_SAMPLE_ULAW] = (pa_convert_func_t) ulaw_to_s16ne
+ };
+
+ pa_assert(f >= 0);
+ pa_assert(f < PA_SAMPLE_MAX);
+
+ return table[f];
+}
+
+pa_convert_func_t pa_get_convert_from_s16ne_function(pa_sample_format_t f) {
+
+ static const pa_convert_func_t 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_ALAW] = (pa_convert_func_t) alaw_from_s16ne,
+ [PA_SAMPLE_ULAW] = (pa_convert_func_t) ulaw_from_s16ne,
+ };
+
+ pa_assert(f >= 0);
+ pa_assert(f < PA_SAMPLE_MAX);
+
+ return table[f];
+}
diff --git a/src/pulsecore/sconv.h b/src/pulsecore/sconv.h
new file mode 100644
index 00000000..901f50a3
--- /dev/null
+++ b/src/pulsecore/sconv.h
@@ -0,0 +1,38 @@
+#ifndef foosconvhfoo
+#define foosconvhfoo
+
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#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;
+
+#endif
diff --git a/src/pulsecore/semaphore-posix.c b/src/pulsecore/semaphore-posix.c
new file mode 100644
index 00000000..750c2afc
--- /dev/null
+++ b/src/pulsecore/semaphore-posix.c
@@ -0,0 +1,69 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <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);
+}
diff --git a/src/pulsecore/semaphore-win32.c b/src/pulsecore/semaphore-win32.c
new file mode 100644
index 00000000..f6576348
--- /dev/null
+++ b/src/pulsecore/semaphore-win32.c
@@ -0,0 +1,65 @@
+/* $Id: mutex-win32.c 1426 2007-02-13 15:35:19Z ossman $ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <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 00000000..c394e0f2
--- /dev/null
+++ b/src/pulsecore/semaphore.h
@@ -0,0 +1,35 @@
+#ifndef foopulsesemaphorehfoo
+#define foopulsesemaphorehfoo
+
+/* $Id$ */
+
+/***
+ 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 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+typedef struct pa_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);
+
+#endif
diff --git a/src/pulsecore/shm.c b/src/pulsecore/shm.c
new file mode 100644
index 00000000..7c764e3a
--- /dev/null
+++ b/src/pulsecore/shm.c
@@ -0,0 +1,383 @@
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <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
+
+#include <pulse/xmalloc.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 "shm.h"
+
+#if defined(__linux__) && !defined(MADV_REMOVE)
+#define MADV_REMOVE 9
+#endif
+
+#define MAX_SHM_SIZE (PA_ALIGN(1024*1024*20))
+
+#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/"
+#else
+#undef SHM_PATH
+#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 */
+struct shm_marker {
+ pa_atomic_t marker; /* 0xbeefcafe */
+ pa_atomic_t pid;
+ void *_reserverd1;
+ void *_reserverd2;
+ void *_reserverd3;
+ void *_reserverd4;
+};
+
+static char *segment_name(char *fn, size_t l, unsigned id) {
+ pa_snprintf(fn, l, "/pulse-shm-%u", id);
+ return fn;
+}
+
+int pa_shm_create_rw(pa_shm *m, size_t size, int shared, mode_t mode) {
+ char fn[32];
+ int fd = -1;
+
+ pa_assert(m);
+ pa_assert(size > 0);
+ pa_assert(size < MAX_SHM_SIZE);
+ pa_assert(mode >= 0600);
+
+ /* Each time we create a new SHM area, let's first drop all stale
+ * ones */
+ pa_shm_cleanup();
+
+ /* Round up to make it aligned */
+ size = PA_ALIGN(size);
+
+ if (!shared) {
+ m->id = 0;
+ m->size = size;
+
+#ifdef MAP_ANONYMOUS
+ if ((m->ptr = mmap(NULL, m->size, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0)) == MAP_FAILED) {
+ pa_log("mmap() failed: %s", pa_cstrerror(errno));
+ goto fail;
+ }
+#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));
+ goto fail;
+ }
+ }
+#else
+ m->ptr = pa_xmalloc(m->size);
+#endif
+
+ m->do_unlink = 0;
+
+ } else {
+#ifdef HAVE_SHM_OPEN
+ struct shm_marker *marker;
+
+ pa_random(&m->id, sizeof(m->id));
+ segment_name(fn, sizeof(fn), m->id);
+
+ if ((fd = shm_open(fn, O_RDWR|O_CREAT|O_EXCL, mode & 0444)) < 0) {
+ pa_log("shm_open() failed: %s", pa_cstrerror(errno));
+ goto fail;
+ }
+
+ m->size = size + PA_ALIGN(sizeof(struct shm_marker));
+
+ if (ftruncate(fd, m->size) < 0) {
+ pa_log("ftruncate() failed: %s", pa_cstrerror(errno));
+ goto fail;
+ }
+
+ if ((m->ptr = mmap(NULL, m->size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) {
+ pa_log("mmap() failed: %s", pa_cstrerror(errno));
+ goto fail;
+ }
+
+ /* We store our PID at the end of the shm block, so that we
+ * can check for dead shm segments later */
+ marker = (struct shm_marker*) ((uint8_t*) m->ptr + m->size - PA_ALIGN(sizeof(struct shm_marker)));
+ pa_atomic_store(&marker->pid, (int) getpid());
+ pa_atomic_store(&marker->marker, SHM_MARKER);
+
+ pa_assert_se(close(fd) == 0);
+ m->do_unlink = 1;
+#else
+ return -1;
+#endif
+ }
+
+ m->shared = shared;
+
+ return 0;
+
+fail:
+
+#ifdef HAVE_SHM_OPEN
+ if (fd >= 0) {
+ shm_unlink(fn);
+ pa_close(fd);
+ }
+#endif
+
+ return -1;
+}
+
+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->shared) {
+#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
+ } else {
+#ifdef HAVE_SHM_OPEN
+ if (munmap(m->ptr, m->size) < 0)
+ pa_log("munmap() failed: %s", pa_cstrerror(errno));
+
+ if (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));
+ }
+#else
+ /* We shouldn't be here without shm support */
+ pa_assert_not_reached();
+#endif
+ }
+
+ memset(m, 0, sizeof(*m));
+}
+
+void pa_shm_punch(pa_shm *m, size_t offset, size_t size) {
+ void *ptr;
+ size_t o, ps;
+
+ 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 this to multiples of the page size */
+ ptr = (uint8_t*) m->ptr + offset;
+ o = (uint8_t*) ptr - (uint8_t*) PA_PAGE_ALIGN_PTR(ptr);
+
+ if (o > 0) {
+ ps = PA_PAGE_SIZE;
+ ptr = (uint8_t*) ptr + (ps - o);
+ size -= ps - o;
+ }
+
+#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
+ pa_assert_se(madvise(ptr, size, MADV_DONTNEED) == 0);
+#elif defined(POSIX_MADV_DONTNEED)
+ pa_assert_se(posix_madvise(ptr, size, POSIX_MADV_DONTNEED) == 0);
+#endif
+}
+
+#ifdef HAVE_SHM_OPEN
+
+int pa_shm_attach_ro(pa_shm *m, unsigned id) {
+ char fn[32];
+ int fd = -1;
+ struct stat st;
+
+ pa_assert(m);
+
+ segment_name(fn, sizeof(fn), m->id = id);
+
+ if ((fd = shm_open(fn, O_RDONLY, 0)) < 0) {
+ if (errno != EACCES)
+ pa_log("shm_open() failed: %s", pa_cstrerror(errno));
+ 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 > MAX_SHM_SIZE+PA_ALIGN(sizeof(struct shm_marker)) || PA_ALIGN(st.st_size) != st.st_size) {
+ pa_log("Invalid shared memory segment size");
+ goto fail;
+ }
+
+ m->size = st.st_size;
+
+ if ((m->ptr = mmap(NULL, m->size, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) {
+ pa_log("mmap() failed: %s", pa_cstrerror(errno));
+ goto fail;
+ }
+
+ m->do_unlink = 0;
+ m->shared = 1;
+
+ pa_assert_se(pa_close(fd) == 0);
+
+ return 0;
+
+fail:
+ if (fd >= 0)
+ pa_close(fd);
+
+ return -1;
+}
+
+#else /* HAVE_SHM_OPEN */
+
+int pa_shm_attach_ro(pa_shm *m, unsigned id) {
+ return -1;
+}
+
+#endif /* HAVE_SHM_OPEN */
+
+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 (strncmp(de->d_name, "pulse-shm-", 10))
+ continue;
+
+ if (pa_atou(de->d_name + 10, &id) < 0)
+ continue;
+
+ if (pa_shm_attach_ro(&seg, id) < 0)
+ continue;
+
+ if (seg.size < PA_ALIGN(sizeof(struct shm_marker))) {
+ pa_shm_free(&seg);
+ continue;
+ }
+
+ m = (struct shm_marker*) ((uint8_t*) seg.ptr + seg.size - PA_ALIGN(sizeof(struct shm_marker)));
+
+ 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)
+ pa_log_warn("Failed to remove SHM segment %s: %s\n", 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 00000000..270591de
--- /dev/null
+++ b/src/pulsecore/shm.h
@@ -0,0 +1,46 @@
+#ifndef foopulseshmhfoo
+#define foopulseshmhfoo
+
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <sys/types.h>
+
+typedef struct pa_shm {
+ unsigned id;
+ void *ptr;
+ size_t size;
+ int do_unlink;
+ int shared;
+} pa_shm;
+
+int pa_shm_create_rw(pa_shm *m, size_t size, int shared, mode_t mode);
+int pa_shm_attach_ro(pa_shm *m, unsigned id);
+
+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 00000000..07ddb83a
--- /dev/null
+++ b/src/pulsecore/sink-input.c
@@ -0,0 +1,989 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <pulse/utf8.h>
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/sample-util.h>
+#include <pulsecore/core-subscribe.h>
+#include <pulsecore/log.h>
+#include <pulsecore/play-memblockq.h>
+#include <pulsecore/namereg.h>
+
+#include "sink-input.h"
+
+#define CONVERT_BUFFER_LENGTH (PA_PAGE_SIZE)
+#define SILENCE_BUFFER_LENGTH (PA_PAGE_SIZE*12)
+#define MOVE_BUFFER_LENGTH (PA_PAGE_SIZE*256)
+
+static PA_DEFINE_CHECK_TYPE(pa_sink_input, pa_msgobject);
+
+static void sink_input_free(pa_object *o);
+
+pa_sink_input_new_data* pa_sink_input_new_data_init(pa_sink_input_new_data *data) {
+ pa_assert(data);
+
+ memset(data, 0, sizeof(*data));
+ data->resample_method = PA_RESAMPLER_INVALID;
+
+ return data;
+}
+
+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;
+}
+
+void pa_sink_input_new_data_set_volume(pa_sink_input_new_data *data, const pa_cvolume *volume) {
+ pa_assert(data);
+
+ if ((data->volume_is_set = !!volume))
+ data->volume = *volume;
+}
+
+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_muted(pa_sink_input_new_data *data, pa_bool_t mute) {
+ pa_assert(data);
+
+ data->muted_is_set = TRUE;
+ data->muted = !!mute;
+}
+
+pa_sink_input* pa_sink_input_new(
+ pa_core *core,
+ pa_sink_input_new_data *data,
+ pa_sink_input_flags_t flags) {
+
+ pa_sink_input *i;
+ pa_resampler *resampler = NULL;
+ char st[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX];
+
+ pa_assert(core);
+ pa_assert(data);
+
+ if (pa_hook_fire(&core->hooks[PA_CORE_HOOK_SINK_INPUT_NEW], data) < 0)
+ return NULL;
+
+ pa_return_null_if_fail(!data->driver || pa_utf8_valid(data->driver));
+ pa_return_null_if_fail(!data->name || pa_utf8_valid(data->name));
+
+ if (!data->sink)
+ data->sink = pa_namereg_get(core, NULL, PA_NAMEREG_SINK, 1);
+
+ pa_return_null_if_fail(data->sink);
+ pa_return_null_if_fail(pa_sink_get_state(data->sink) != PA_SINK_UNLINKED);
+ pa_return_null_if_fail(!data->sync_base || (data->sync_base->sink == data->sink && pa_sink_input_get_state(data->sync_base) == PA_SINK_INPUT_CORKED));
+
+ if (!data->sample_spec_is_set)
+ data->sample_spec = data->sink->sample_spec;
+
+ pa_return_null_if_fail(pa_sample_spec_valid(&data->sample_spec));
+
+ if (!data->channel_map_is_set) {
+ if (data->sink->channel_map.channels == data->sample_spec.channels)
+ data->channel_map = data->sink->channel_map;
+ else
+ 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);
+
+ if (!data->volume_is_set)
+ pa_cvolume_reset(&data->volume, data->sample_spec.channels);
+
+ pa_return_null_if_fail(pa_cvolume_valid(&data->volume));
+ pa_return_null_if_fail(data->volume.channels == data->sample_spec.channels);
+
+ if (flags & PA_SINK_INPUT_FIX_FORMAT)
+ data->sample_spec.format = data->sink->sample_spec.format;
+
+ if (flags & PA_SINK_INPUT_FIX_RATE)
+ data->sample_spec.rate = data->sink->sample_spec.rate;
+
+ if (flags & PA_SINK_INPUT_FIX_CHANNELS) {
+ data->sample_spec.channels = data->sink->sample_spec.channels;
+ data->channel_map = data->sink->channel_map;
+ }
+
+ pa_assert(pa_sample_spec_valid(&data->sample_spec));
+ pa_assert(pa_channel_map_valid(&data->channel_map));
+
+ /* Due to the fixing of the sample spec the volume might not match anymore */
+ if (data->volume.channels != data->sample_spec.channels)
+ pa_cvolume_set(&data->volume, data->sample_spec.channels, pa_cvolume_avg(&data->volume));
+
+ if (!data->muted_is_set)
+ data->muted = 0;
+
+ if (data->resample_method == PA_RESAMPLER_INVALID)
+ data->resample_method = core->resample_method;
+
+ pa_return_null_if_fail(data->resample_method < PA_RESAMPLER_MAX);
+
+ if (pa_hook_fire(&core->hooks[PA_CORE_HOOK_SINK_INPUT_FIXATE], data) < 0)
+ return NULL;
+
+ 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 NULL;
+ }
+
+ if ((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)) {
+
+ if (!(resampler = pa_resampler_new(
+ core->mempool,
+ &data->sample_spec, &data->channel_map,
+ &data->sink->sample_spec, &data->sink->channel_map,
+ data->resample_method,
+ ((flags & PA_SINK_INPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0) |
+ ((flags & PA_SINK_INPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) |
+ (core->disable_remixing || (flags & PA_SINK_INPUT_NO_REMIX) ? PA_RESAMPLER_NO_REMIX : 0)))) {
+ pa_log_warn("Unsupported resampling operation.");
+ return NULL;
+ }
+
+ data->resample_method = pa_resampler_get_method(resampler);
+ }
+
+ 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 = flags;
+ i->name = pa_xstrdup(data->name);
+ i->driver = pa_xstrdup(data->driver);
+ i->module = data->module;
+ i->sink = data->sink;
+ i->client = data->client;
+
+ i->resample_method = data->resample_method;
+ i->sample_spec = data->sample_spec;
+ i->channel_map = data->channel_map;
+
+ i->volume = data->volume;
+ 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->peek = NULL;
+ i->drop = NULL;
+ i->kill = NULL;
+ i->get_latency = NULL;
+ i->attach = NULL;
+ i->detach = NULL;
+ i->suspend = NULL;
+ i->moved = NULL;
+ i->userdata = NULL;
+
+ i->thread_info.state = i->state;
+ pa_atomic_store(&i->thread_info.drained, 1);
+ i->thread_info.sample_spec = i->sample_spec;
+ i->thread_info.silence_memblock = NULL;
+ i->thread_info.move_silence = 0;
+ pa_memchunk_reset(&i->thread_info.resampled_chunk);
+ i->thread_info.resampler = resampler;
+ i->thread_info.volume = i->volume;
+ i->thread_info.muted = i->muted;
+ i->thread_info.attached = FALSE;
+
+ pa_assert_se(pa_idxset_put(core->sink_inputs, pa_sink_input_ref(i), &i->index) == 0);
+ pa_assert_se(pa_idxset_put(i->sink->inputs, i, NULL) == 0);
+
+ pa_log_info("Created input %u \"%s\" on %s with sample spec %s and channel map %s",
+ i->index,
+ i->name,
+ i->sink->name,
+ pa_sample_spec_snprint(st, sizeof(st), &i->sample_spec),
+ pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map));
+
+ /* Don't forget to call pa_sink_input_put! */
+
+ return i;
+}
+
+static void update_n_corked(pa_sink_input *i, pa_sink_input_state_t state) {
+ pa_assert(i);
+
+ 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++;
+
+ pa_sink_update_status(i->sink);
+}
+
+static int sink_input_set_state(pa_sink_input *i, pa_sink_input_state_t state) {
+ pa_sink_input *ssync;
+ pa_assert(i);
+
+ if (state == PA_SINK_INPUT_DRAINED)
+ state = PA_SINK_INPUT_RUNNING;
+
+ if (i->state == state)
+ return 0;
+
+ if (pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_STATE, PA_UINT_TO_PTR(state), 0, NULL) < 0)
+ return -1;
+
+ 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->sink->core->hooks[PA_CORE_HOOK_SINK_INPUT_STATE_CHANGED], i);
+
+ return 0;
+}
+
+void pa_sink_input_unlink(pa_sink_input *i) {
+ pa_bool_t linked;
+ pa_assert(i);
+
+ /* See pa_sink_unlink() for a couple of comments how this function
+ * works */
+
+ pa_sink_input_ref(i);
+
+ linked = PA_SINK_INPUT_LINKED(i->state);
+
+ if (linked)
+ pa_hook_fire(&i->sink->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->sink->core->sink_inputs, i, NULL);
+ if (pa_idxset_remove_by_data(i->sink->inputs, i, NULL))
+ pa_sink_input_unref(i);
+
+ if (linked) {
+ pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_REMOVE_INPUT, i, 0, NULL);
+ sink_input_set_state(i, PA_SINK_INPUT_UNLINKED);
+ pa_sink_update_status(i->sink);
+ } else
+ i->state = PA_SINK_INPUT_UNLINKED;
+
+ i->peek = NULL;
+ i->drop = NULL;
+ i->kill = NULL;
+ i->get_latency = NULL;
+ i->attach = NULL;
+ i->detach = NULL;
+ i->suspend = NULL;
+ i->moved = NULL;
+
+ if (linked) {
+ pa_subscription_post(i->sink->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_REMOVE, i->index);
+ pa_hook_fire(&i->sink->core->hooks[PA_CORE_HOOK_SINK_INPUT_UNLINK_POST], i);
+ }
+
+ i->sink = NULL;
+ pa_sink_input_unref(i);
+}
+
+static void sink_input_free(pa_object *o) {
+ pa_sink_input* i = PA_SINK_INPUT(o);
+
+ pa_assert(i);
+ pa_assert(pa_sink_input_refcnt(i) == 0);
+
+ if (PA_SINK_INPUT_LINKED(i->state))
+ pa_sink_input_unlink(i);
+
+ pa_log_info("Freeing output %u \"%s\"", i->index, i->name);
+
+ pa_assert(!i->thread_info.attached);
+
+ if (i->thread_info.resampled_chunk.memblock)
+ pa_memblock_unref(i->thread_info.resampled_chunk.memblock);
+
+ if (i->thread_info.resampler)
+ pa_resampler_free(i->thread_info.resampler);
+
+ if (i->thread_info.silence_memblock)
+ pa_memblock_unref(i->thread_info.silence_memblock);
+
+ pa_xfree(i->name);
+ pa_xfree(i->driver);
+ pa_xfree(i);
+}
+
+void pa_sink_input_put(pa_sink_input *i) {
+ pa_sink_input_assert_ref(i);
+
+ pa_assert(i->state == PA_SINK_INPUT_INIT);
+ pa_assert(i->peek);
+ pa_assert(i->drop);
+
+ i->thread_info.state = i->state = i->flags & PA_SINK_INPUT_START_CORKED ? PA_SINK_INPUT_CORKED : PA_SINK_INPUT_RUNNING;
+ i->thread_info.volume = i->volume;
+ i->thread_info.muted = i->muted;
+
+ if (i->state == PA_SINK_INPUT_CORKED)
+ i->sink->n_corked++;
+
+ pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_ADD_INPUT, i, 0, NULL);
+ pa_sink_update_status(i->sink);
+
+ pa_subscription_post(i->sink->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW, i->index);
+ pa_hook_fire(&i->sink->core->hooks[PA_CORE_HOOK_SINK_INPUT_PUT], i);
+
+ /* Please note that if you change something here, you have to
+ change something in pa_sink_input_move() with the ghost stream
+ registration too. */
+}
+
+void pa_sink_input_kill(pa_sink_input*i) {
+ pa_sink_input_assert_ref(i);
+ pa_assert(PA_SINK_INPUT_LINKED(i->state));
+
+ if (i->kill)
+ i->kill(i);
+}
+
+pa_usec_t pa_sink_input_get_latency(pa_sink_input *i) {
+ pa_usec_t r = 0;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert(PA_SINK_INPUT_LINKED(i->state));
+
+ if (pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_GET_LATENCY, &r, 0, NULL) < 0)
+ r = 0;
+
+ if (i->get_latency)
+ r += i->get_latency(i);
+
+ return r;
+}
+
+/* Called from thread context */
+int pa_sink_input_peek(pa_sink_input *i, size_t length, pa_memchunk *chunk, pa_cvolume *volume) {
+ int ret = -1;
+ int do_volume_adj_here;
+ int volume_is_norm;
+ size_t block_size_max;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert(PA_SINK_INPUT_LINKED(i->thread_info.state));
+ pa_assert(pa_frame_aligned(length, &i->sink->sample_spec));
+ pa_assert(chunk);
+ pa_assert(volume);
+
+ if (!i->peek || !i->drop || i->thread_info.state == PA_SINK_INPUT_CORKED)
+ goto finish;
+
+ pa_assert(i->thread_info.state == PA_SINK_INPUT_RUNNING || i->thread_info.state == PA_SINK_INPUT_DRAINED);
+
+ /* Default buffer size */
+ if (length <= 0)
+ length = pa_frame_align(CONVERT_BUFFER_LENGTH, &i->sink->sample_spec);
+
+ /* Make sure the buffer fits in the mempool tile */
+ block_size_max = pa_mempool_block_size_max(i->sink->core->mempool);
+ if (length > block_size_max)
+ length = pa_frame_align(block_size_max, &i->sink->sample_spec);
+
+ if (i->thread_info.move_silence > 0) {
+ size_t l;
+
+ /* We have just been moved and shall play some silence for a
+ * while until the old sink has drained its playback buffer */
+
+ if (!i->thread_info.silence_memblock)
+ i->thread_info.silence_memblock = pa_silence_memblock_new(
+ i->sink->core->mempool,
+ &i->sink->sample_spec,
+ pa_frame_align(SILENCE_BUFFER_LENGTH, &i->sink->sample_spec));
+
+ chunk->memblock = pa_memblock_ref(i->thread_info.silence_memblock);
+ chunk->index = 0;
+ l = pa_memblock_get_length(chunk->memblock);
+ chunk->length = i->thread_info.move_silence < l ? i->thread_info.move_silence : l;
+
+ ret = 0;
+ do_volume_adj_here = 1;
+ goto finish;
+ }
+
+ if (!i->thread_info.resampler) {
+ do_volume_adj_here = 0; /* FIXME??? */
+ ret = i->peek(i, length, chunk);
+ goto finish;
+ }
+
+ 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.volume) && !i->thread_info.muted;
+
+ while (!i->thread_info.resampled_chunk.memblock) {
+ pa_memchunk tchunk;
+ size_t l, rmbs;
+
+ l = pa_resampler_request(i->thread_info.resampler, length);
+
+ if (l <= 0)
+ l = pa_frame_align(CONVERT_BUFFER_LENGTH, &i->sample_spec);
+
+ rmbs = pa_resampler_max_block_size(i->thread_info.resampler);
+ if (l > rmbs)
+ l = rmbs;
+
+ if ((ret = i->peek(i, l, &tchunk)) < 0)
+ goto finish;
+
+ pa_assert(tchunk.length > 0);
+
+ if (tchunk.length > l)
+ tchunk.length = l;
+
+ i->drop(i, tchunk.length);
+
+ /* It might be necessary to adjust the volume here */
+ if (do_volume_adj_here && !volume_is_norm) {
+ pa_memchunk_make_writable(&tchunk, 0);
+
+ if (i->thread_info.muted)
+ pa_silence_memchunk(&tchunk, &i->thread_info.sample_spec);
+ else
+ pa_volume_memchunk(&tchunk, &i->thread_info.sample_spec, &i->thread_info.volume);
+ }
+
+ pa_resampler_run(i->thread_info.resampler, &tchunk, &i->thread_info.resampled_chunk);
+ pa_memblock_unref(tchunk.memblock);
+ }
+
+ pa_assert(i->thread_info.resampled_chunk.memblock);
+ pa_assert(i->thread_info.resampled_chunk.length > 0);
+
+ *chunk = i->thread_info.resampled_chunk;
+ pa_memblock_ref(i->thread_info.resampled_chunk.memblock);
+
+ ret = 0;
+
+finish:
+
+ if (ret >= 0)
+ pa_atomic_store(&i->thread_info.drained, 0);
+ else if (ret < 0)
+ pa_atomic_store(&i->thread_info.drained, 1);
+
+ if (ret >= 0) {
+ /* 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.volume;
+ }
+
+ return ret;
+}
+
+/* Called from thread context */
+void pa_sink_input_drop(pa_sink_input *i, size_t length) {
+ pa_sink_input_assert_ref(i);
+ pa_assert(PA_SINK_INPUT_LINKED(i->thread_info.state));
+ pa_assert(pa_frame_aligned(length, &i->sink->sample_spec));
+ pa_assert(length > 0);
+
+ if (!i->peek || !i->drop || i->thread_info.state == PA_SINK_INPUT_CORKED)
+ return;
+
+ if (i->thread_info.move_silence > 0) {
+
+ if (i->thread_info.move_silence >= length) {
+ i->thread_info.move_silence -= length;
+ length = 0;
+ } else {
+ length -= i->thread_info.move_silence;
+ i->thread_info.move_silence = 0;
+ }
+
+ if (i->thread_info.move_silence <= 0) {
+ if (i->thread_info.silence_memblock) {
+ pa_memblock_unref(i->thread_info.silence_memblock);
+ i->thread_info.silence_memblock = NULL;
+ }
+ }
+
+ if (length <= 0)
+ return;
+ }
+
+ if (i->thread_info.resampled_chunk.memblock) {
+ size_t l = length;
+
+ if (l > i->thread_info.resampled_chunk.length)
+ l = i->thread_info.resampled_chunk.length;
+
+ i->thread_info.resampled_chunk.index += l;
+ i->thread_info.resampled_chunk.length -= l;
+
+ if (i->thread_info.resampled_chunk.length <= 0) {
+ pa_memblock_unref(i->thread_info.resampled_chunk.memblock);
+ pa_memchunk_reset(&i->thread_info.resampled_chunk);
+ }
+
+ length -= l;
+ }
+
+ if (length > 0) {
+
+ if (i->thread_info.resampler) {
+ /* So, we have a resampler. To avoid discontinuities we
+ * have to actually read all data that could be read and
+ * pass it through the resampler. */
+
+ while (length > 0) {
+ pa_memchunk chunk;
+ pa_cvolume volume;
+
+ if (pa_sink_input_peek(i, length, &chunk, &volume) >= 0) {
+ size_t l;
+
+ pa_memblock_unref(chunk.memblock);
+
+ l = chunk.length;
+ if (l > length)
+ l = length;
+
+ pa_sink_input_drop(i, l);
+ length -= l;
+
+ } else {
+ size_t l;
+
+ l = pa_resampler_request(i->thread_info.resampler, length);
+
+ /* Hmmm, peeking failed, so let's at least drop
+ * the right amount of data */
+
+ if (l > 0)
+ if (i->drop)
+ i->drop(i, l);
+
+ break;
+ }
+ }
+
+ } else {
+
+ /* We have no resampler, hence let's just drop the data */
+
+ if (i->drop)
+ i->drop(i, length);
+ }
+ }
+}
+
+void pa_sink_input_set_volume(pa_sink_input *i, const pa_cvolume *volume) {
+ pa_sink_input_assert_ref(i);
+ pa_assert(PA_SINK_INPUT_LINKED(i->state));
+
+ if (pa_cvolume_equal(&i->volume, volume))
+ return;
+
+ i->volume = *volume;
+
+ pa_asyncmsgq_post(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_VOLUME, pa_xnewdup(struct pa_cvolume, volume, 1), 0, NULL, pa_xfree);
+ pa_subscription_post(i->sink->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
+}
+
+const pa_cvolume *pa_sink_input_get_volume(pa_sink_input *i) {
+ pa_sink_input_assert_ref(i);
+ pa_assert(PA_SINK_INPUT_LINKED(i->state));
+
+ return &i->volume;
+}
+
+void pa_sink_input_set_mute(pa_sink_input *i, pa_bool_t mute) {
+ pa_assert(i);
+ pa_sink_input_assert_ref(i);
+ pa_assert(PA_SINK_INPUT_LINKED(i->state));
+
+ if (!i->muted == !mute)
+ return;
+
+ i->muted = mute;
+
+ pa_asyncmsgq_post(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_MUTE, PA_UINT_TO_PTR(mute), 0, NULL, NULL);
+ pa_subscription_post(i->sink->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
+}
+
+int pa_sink_input_get_mute(pa_sink_input *i) {
+ pa_sink_input_assert_ref(i);
+ pa_assert(PA_SINK_INPUT_LINKED(i->state));
+
+ return !!i->muted;
+}
+
+void pa_sink_input_cork(pa_sink_input *i, pa_bool_t b) {
+ pa_sink_input_assert_ref(i);
+ pa_assert(PA_SINK_INPUT_LINKED(i->state));
+
+ sink_input_set_state(i, b ? PA_SINK_INPUT_CORKED : PA_SINK_INPUT_RUNNING);
+}
+
+int pa_sink_input_set_rate(pa_sink_input *i, uint32_t rate) {
+ pa_sink_input_assert_ref(i);
+ pa_assert(PA_SINK_INPUT_LINKED(i->state));
+ pa_return_val_if_fail(i->thread_info.resampler, -1);
+
+ if (i->sample_spec.rate == rate)
+ return 0;
+
+ i->sample_spec.rate = rate;
+
+ pa_asyncmsgq_post(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_RATE, PA_UINT_TO_PTR(rate), 0, NULL, NULL);
+
+ pa_subscription_post(i->sink->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
+ return 0;
+}
+
+void pa_sink_input_set_name(pa_sink_input *i, const char *name) {
+ pa_sink_input_assert_ref(i);
+
+ if (!i->name && !name)
+ return;
+
+ if (i->name && name && !strcmp(i->name, name))
+ return;
+
+ pa_xfree(i->name);
+ i->name = pa_xstrdup(name);
+
+ if (PA_SINK_INPUT_LINKED(i->state)) {
+ pa_hook_fire(&i->sink->core->hooks[PA_CORE_HOOK_SINK_INPUT_NAME_CHANGED], i);
+ pa_subscription_post(i->sink->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
+ }
+}
+
+pa_resample_method_t pa_sink_input_get_resample_method(pa_sink_input *i) {
+ pa_sink_input_assert_ref(i);
+
+ return i->resample_method;
+}
+
+int pa_sink_input_move_to(pa_sink_input *i, pa_sink *dest, int immediately) {
+ pa_resampler *new_resampler;
+ pa_sink *origin;
+ pa_usec_t silence_usec = 0;
+ pa_sink_input_move_info info;
+ pa_sink_input_move_hook_data hook_data;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert(PA_SINK_INPUT_LINKED(i->state));
+ pa_sink_assert_ref(dest);
+
+ origin = i->sink;
+
+ if (dest == origin)
+ return 0;
+
+ if (i->flags & PA_SINK_INPUT_DONT_MOVE)
+ return -1;
+
+ if (i->sync_next || i->sync_prev) {
+ pa_log_warn("Moving synchronised streams not supported.");
+ return -1;
+ }
+
+ 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 -1;
+ }
+
+ if (i->thread_info.resampler &&
+ pa_sample_spec_equal(&origin->sample_spec, &dest->sample_spec) &&
+ pa_channel_map_equal(&origin->channel_map, &dest->channel_map))
+
+ /* Try to reuse the old resampler if possible */
+ new_resampler = i->thread_info.resampler;
+
+ else if ((i->flags & PA_SINK_INPUT_VARIABLE_RATE) ||
+ !pa_sample_spec_equal(&i->sample_spec, &dest->sample_spec) ||
+ !pa_channel_map_equal(&i->channel_map, &dest->channel_map)) {
+
+ /* Okey, we need a new resampler for the new sink */
+
+ if (!(new_resampler = pa_resampler_new(
+ dest->core->mempool,
+ &i->sample_spec, &i->channel_map,
+ &dest->sample_spec, &dest->channel_map,
+ i->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)))) {
+ pa_log_warn("Unsupported resampling operation.");
+ return -1;
+ }
+ } else
+ new_resampler = NULL;
+
+ hook_data.sink_input = i;
+ hook_data.destination = dest;
+ pa_hook_fire(&i->sink->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE], &hook_data);
+
+ memset(&info, 0, sizeof(info));
+ info.sink_input = i;
+
+ if (!immediately) {
+ pa_usec_t old_latency, new_latency;
+
+ /* Let's do a little bit of Voodoo for compensating latency
+ * differences. We assume that the accuracy for our
+ * estimations is still good enough, even though we do these
+ * operations non-atomic. */
+
+ old_latency = pa_sink_get_latency(origin);
+ new_latency = pa_sink_get_latency(dest);
+
+ /* The already resampled data should go to the old sink */
+
+ if (old_latency >= new_latency) {
+
+ /* The latency of the old sink is larger than the latency
+ * of the new sink. Therefore to compensate for the
+ * difference we to play silence on the new one for a
+ * while */
+
+ silence_usec = old_latency - new_latency;
+
+ } else {
+
+ /* The latency of new sink is larger than the latency of
+ * the old sink. Therefore we have to precompute a little
+ * and make sure that this is still played on the old
+ * sink, until we can play the first sample on the new
+ * sink.*/
+
+ info.buffer_bytes = pa_usec_to_bytes(new_latency - old_latency, &origin->sample_spec);
+ }
+
+ /* Okey, let's move it */
+
+ if (info.buffer_bytes > 0) {
+
+ info.ghost_sink_input = pa_memblockq_sink_input_new(
+ origin,
+ "Ghost Stream",
+ &origin->sample_spec,
+ &origin->channel_map,
+ NULL,
+ NULL);
+
+ info.ghost_sink_input->thread_info.state = info.ghost_sink_input->state = PA_SINK_INPUT_RUNNING;
+ info.ghost_sink_input->thread_info.volume = info.ghost_sink_input->volume;
+ info.ghost_sink_input->thread_info.muted = info.ghost_sink_input->muted;
+
+ info.buffer = pa_memblockq_new(0, MOVE_BUFFER_LENGTH, 0, pa_frame_size(&origin->sample_spec), 0, 0, NULL);
+ }
+ }
+
+ pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_REMOVE_INPUT_AND_BUFFER, &info, 0, NULL);
+
+ if (info.ghost_sink_input) {
+ /* Basically, do what pa_sink_input_put() does ...*/
+
+ pa_subscription_post(i->sink->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW, info.ghost_sink_input->index);
+ pa_hook_fire(&i->sink->core->hooks[PA_CORE_HOOK_SINK_INPUT_PUT], info.ghost_sink_input);
+ pa_sink_input_unref(info.ghost_sink_input);
+ }
+
+ pa_idxset_remove_by_data(origin->inputs, i, NULL);
+ pa_idxset_put(dest->inputs, i, NULL);
+ i->sink = dest;
+
+ if (pa_sink_input_get_state(i) == PA_SINK_INPUT_CORKED) {
+ pa_assert_se(origin->n_corked-- >= 1);
+ dest->n_corked++;
+ }
+
+ /* Replace resampler */
+ if (new_resampler != i->thread_info.resampler) {
+ if (i->thread_info.resampler)
+ pa_resampler_free(i->thread_info.resampler);
+ i->thread_info.resampler = new_resampler;
+
+ /* if the resampler changed, the silence memblock is
+ * probably invalid now, too */
+ if (i->thread_info.silence_memblock) {
+ pa_memblock_unref(i->thread_info.silence_memblock);
+ i->thread_info.silence_memblock = NULL;
+ }
+ }
+
+ /* Dump already resampled data */
+ if (i->thread_info.resampled_chunk.memblock) {
+ /* Hmm, this data has already been added to the ghost queue, presumably, hence let's sleep a little bit longer */
+ silence_usec += pa_bytes_to_usec(i->thread_info.resampled_chunk.length, &origin->sample_spec);
+ pa_memblock_unref(i->thread_info.resampled_chunk.memblock);
+ pa_memchunk_reset(&i->thread_info.resampled_chunk);
+ }
+
+ /* Calculate the new sleeping time */
+ if (immediately)
+ i->thread_info.move_silence = 0;
+ else
+ i->thread_info.move_silence = pa_usec_to_bytes(
+ pa_bytes_to_usec(i->thread_info.move_silence, &origin->sample_spec) +
+ silence_usec,
+ &dest->sample_spec);
+
+ pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_ADD_INPUT, i, 0, NULL);
+
+ pa_sink_update_status(origin);
+ pa_sink_update_status(dest);
+
+ if (i->moved)
+ i->moved(i);
+
+ pa_hook_fire(&i->sink->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_POST], i);
+
+ pa_log_debug("Successfully moved sink input %i from %s to %s.", i->index, origin->name, dest->name);
+
+ /* Notify everyone */
+ pa_subscription_post(i->sink->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
+
+ return 0;
+}
+
+/* Called from thread context */
+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);
+ pa_assert(PA_SINK_INPUT_LINKED(i->thread_info.state));
+
+ switch (code) {
+ case PA_SINK_INPUT_MESSAGE_SET_VOLUME:
+ i->thread_info.volume = *((pa_cvolume*) userdata);
+ return 0;
+
+ case PA_SINK_INPUT_MESSAGE_SET_MUTE:
+ i->thread_info.muted = PA_PTR_TO_UINT(userdata);
+ return 0;
+
+ case PA_SINK_INPUT_MESSAGE_GET_LATENCY: {
+ pa_usec_t *r = userdata;
+
+ if (i->thread_info.resampled_chunk.memblock)
+ *r += pa_bytes_to_usec(i->thread_info.resampled_chunk.length, &i->sink->sample_spec);
+
+ if (i->thread_info.move_silence)
+ *r += pa_bytes_to_usec(i->thread_info.move_silence, &i->sink->sample_spec);
+
+ 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;
+
+ if ((PA_PTR_TO_UINT(userdata) == PA_SINK_INPUT_DRAINED || PA_PTR_TO_UINT(userdata) == PA_SINK_INPUT_RUNNING) &&
+ (i->thread_info.state != PA_SINK_INPUT_DRAINED) && (i->thread_info.state != PA_SINK_INPUT_RUNNING))
+ pa_atomic_store(&i->thread_info.drained, 1);
+
+ i->thread_info.state = PA_PTR_TO_UINT(userdata);
+
+ for (ssync = i->thread_info.sync_prev; ssync; ssync = ssync->thread_info.sync_prev) {
+ if ((PA_PTR_TO_UINT(userdata) == PA_SINK_INPUT_DRAINED || PA_PTR_TO_UINT(userdata) == PA_SINK_INPUT_RUNNING) &&
+ (ssync->thread_info.state != PA_SINK_INPUT_DRAINED) && (ssync->thread_info.state != PA_SINK_INPUT_RUNNING))
+ pa_atomic_store(&ssync->thread_info.drained, 1);
+ ssync->thread_info.state = PA_PTR_TO_UINT(userdata);
+ }
+
+ for (ssync = i->thread_info.sync_next; ssync; ssync = ssync->thread_info.sync_next) {
+ if ((PA_PTR_TO_UINT(userdata) == PA_SINK_INPUT_DRAINED || PA_PTR_TO_UINT(userdata) == PA_SINK_INPUT_RUNNING) &&
+ (ssync->thread_info.state != PA_SINK_INPUT_DRAINED) && (ssync->thread_info.state != PA_SINK_INPUT_RUNNING))
+ pa_atomic_store(&ssync->thread_info.drained, 1);
+ ssync->thread_info.state = PA_PTR_TO_UINT(userdata);
+ }
+
+ return 0;
+ }
+ }
+
+ return -1;
+}
+
+pa_sink_input_state_t pa_sink_input_get_state(pa_sink_input *i) {
+ pa_sink_input_assert_ref(i);
+
+ if (i->state == PA_SINK_INPUT_RUNNING || i->state == PA_SINK_INPUT_DRAINED)
+ return pa_atomic_load(&i->thread_info.drained) ? PA_SINK_INPUT_DRAINED : PA_SINK_INPUT_RUNNING;
+
+ return i->state;
+}
diff --git a/src/pulsecore/sink-input.h b/src/pulsecore/sink-input.h
new file mode 100644
index 00000000..8975db9e
--- /dev/null
+++ b/src/pulsecore/sink-input.h
@@ -0,0 +1,251 @@
+#ifndef foopulsesinkinputhfoo
+#define foopulsesinkinputhfoo
+
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <inttypes.h>
+
+typedef struct pa_sink_input pa_sink_input;
+
+#include <pulse/sample.h>
+#include <pulsecore/hook-list.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_put() has not been called yet */
+ PA_SINK_INPUT_DRAINED, /*< The stream stopped playing because there was no data to play */
+ 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 */
+} pa_sink_input_state_t;
+
+static inline pa_bool_t PA_SINK_INPUT_LINKED(pa_sink_input_state_t x) {
+ return x == PA_SINK_INPUT_DRAINED || 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_flags_t;
+
+struct pa_sink_input {
+ pa_msgobject parent;
+
+ uint32_t index;
+ pa_core *core;
+
+ /* Please note that this state should only be read with
+ * pa_sink_input_get_state(). That function will transparently
+ * merge the thread_info.drained value in. */
+ pa_sink_input_state_t state;
+ pa_sink_input_flags_t flags;
+
+ char *name, *driver; /* may be NULL */
+ pa_module *module; /* may be NULL */
+ pa_client *client; /* may be NULL */
+
+ pa_sink *sink;
+
+ pa_sample_spec sample_spec;
+ pa_channel_map channel_map;
+
+ pa_sink_input *sync_prev, *sync_next;
+
+ pa_cvolume volume;
+ pa_bool_t muted;
+
+ /* Returns the chunk of audio data (but doesn't drop it
+ * yet!). Returns -1 on failure. Called from IO thread context. If
+ * data needs to be generated from scratch then please in the
+ * specified length. 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 (*peek) (pa_sink_input *i, size_t length, pa_memchunk *chunk);
+
+ /* Drops the specified number of bytes, usually called right after
+ * peek(), but not necessarily. Called from IO thread context. */
+ void (*drop) (pa_sink_input *i, size_t length);
+
+ /* 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 the sink this input is attached
+ * to suspends or resumes. Called from main context */
+ void (*suspend) (pa_sink_input *i, pa_bool_t b); /* may be NULL */
+
+ /* If non-NULL called whenever the the sink this input is attached
+ * to changes. Called from main context */
+ void (*moved) (pa_sink_input *i); /* may be NULL */
+
+ /* Supposed to unlink and destroy this stream. Called from main
+ * context. */
+ void (*kill) (pa_sink_input *i); /* may be NULL */
+
+ /* Return the current latency (i.e. length of bufferd audio) of
+ this stream. Called from main context. If NULL a
+ PA_SINK_INPUT_MESSAGE_GET_LATENCY message is sent to the IO thread
+ instead. */
+ pa_usec_t (*get_latency) (pa_sink_input *i); /* may be NULL */
+
+ pa_resample_method_t resample_method;
+
+ struct {
+ pa_sink_input_state_t state;
+ pa_atomic_t drained;
+
+ pa_bool_t attached; /* True only between ->attach() and ->detach() calls */
+
+ pa_sample_spec sample_spec;
+
+ pa_memchunk resampled_chunk;
+ pa_resampler *resampler; /* may be NULL */
+
+ /* Some silence to play before the actual data. This is used to
+ * compensate for latency differences when moving a sink input
+ * "hot" between sinks. */
+ size_t move_silence;
+ pa_memblock *silence_memblock; /* may be NULL */
+
+ pa_sink_input *sync_prev, *sync_next;
+
+ pa_cvolume volume;
+ pa_bool_t muted;
+ } thread_info;
+
+ void *userdata;
+};
+
+PA_DECLARE_CLASS(pa_sink_input);
+#define PA_SINK_INPUT(o) pa_sink_input_cast(o)
+
+enum {
+ PA_SINK_INPUT_MESSAGE_SET_VOLUME,
+ PA_SINK_INPUT_MESSAGE_SET_MUTE,
+ PA_SINK_INPUT_MESSAGE_GET_LATENCY,
+ PA_SINK_INPUT_MESSAGE_SET_RATE,
+ PA_SINK_INPUT_MESSAGE_SET_STATE,
+ PA_SINK_INPUT_MESSAGE_MAX
+};
+
+typedef struct pa_sink_input_new_data {
+ const char *name, *driver;
+ pa_module *module;
+ pa_client *client;
+
+ pa_sink *sink;
+
+ pa_sample_spec sample_spec;
+ pa_bool_t sample_spec_is_set;
+ pa_channel_map channel_map;
+ pa_bool_t channel_map_is_set;
+
+ pa_cvolume volume;
+ pa_bool_t volume_is_set;
+ pa_bool_t muted;
+ pa_bool_t muted_is_set;
+
+ pa_resample_method_t resample_method;
+
+ pa_sink_input *sync_base;
+} pa_sink_input_new_data;
+
+typedef struct pa_sink_input_move_hook_data {
+ pa_sink_input *sink_input;
+ pa_sink *destination;
+} pa_sink_input_move_hook_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);
+void pa_sink_input_new_data_set_volume(pa_sink_input_new_data *data, const pa_cvolume *volume);
+void pa_sink_input_new_data_set_muted(pa_sink_input_new_data *data, pa_bool_t mute);
+
+/* To be called by the implementing module only */
+
+pa_sink_input* pa_sink_input_new(
+ pa_core *core,
+ pa_sink_input_new_data *data,
+ pa_sink_input_flags_t flags);
+
+void pa_sink_input_put(pa_sink_input *i);
+void pa_sink_input_unlink(pa_sink_input* i);
+
+void pa_sink_input_set_name(pa_sink_input *i, const char *name);
+
+/* Callable by everyone */
+
+/* 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);
+
+void pa_sink_input_set_volume(pa_sink_input *i, const pa_cvolume *volume);
+const pa_cvolume *pa_sink_input_get_volume(pa_sink_input *i);
+void pa_sink_input_set_mute(pa_sink_input *i, pa_bool_t mute);
+int pa_sink_input_get_mute(pa_sink_input *i);
+
+void pa_sink_input_cork(pa_sink_input *i, pa_bool_t b);
+
+int pa_sink_input_set_rate(pa_sink_input *i, uint32_t rate);
+
+pa_resample_method_t pa_sink_input_get_resample_method(pa_sink_input *i);
+
+int pa_sink_input_move_to(pa_sink_input *i, pa_sink *dest, int immediately);
+
+pa_sink_input_state_t pa_sink_input_get_state(pa_sink_input *i);
+
+/* To be used exclusively by the sink driver thread */
+
+int 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);
+int pa_sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk);
+
+typedef struct pa_sink_input_move_info {
+ pa_sink_input *sink_input;
+ pa_sink_input *ghost_sink_input;
+ pa_memblockq *buffer;
+ size_t buffer_bytes;
+} pa_sink_input_move_info;
+
+#endif
diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c
new file mode 100644
index 00000000..9adb6097
--- /dev/null
+++ b/src/pulsecore/sink.c
@@ -0,0 +1,1066 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <pulse/introspect.h>
+#include <pulse/utf8.h>
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/sink-input.h>
+#include <pulsecore/namereg.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/sample-util.h>
+#include <pulsecore/core-subscribe.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/play-memblockq.h>
+
+#include "sink.h"
+
+#define MAX_MIX_CHANNELS 32
+#define MIX_BUFFER_LENGTH (PA_PAGE_SIZE)
+#define SILENCE_BUFFER_LENGTH (PA_PAGE_SIZE*12)
+
+static PA_DEFINE_CHECK_TYPE(pa_sink, pa_msgobject);
+
+static void sink_free(pa_object *s);
+
+pa_sink* pa_sink_new(
+ pa_core *core,
+ const char *driver,
+ const char *name,
+ int fail,
+ const pa_sample_spec *spec,
+ const pa_channel_map *map) {
+
+ pa_sink *s;
+ char *n = NULL;
+ char st[256];
+ pa_channel_map tmap;
+
+ pa_assert(core);
+ pa_assert(name);
+ pa_assert(spec);
+
+ pa_return_null_if_fail(pa_sample_spec_valid(spec));
+
+ if (!map)
+ pa_return_null_if_fail((map = pa_channel_map_init_auto(&tmap, spec->channels, PA_CHANNEL_MAP_DEFAULT)));
+
+ pa_return_null_if_fail(map && pa_channel_map_valid(map));
+ pa_return_null_if_fail(map->channels == spec->channels);
+ pa_return_null_if_fail(!driver || pa_utf8_valid(driver));
+ pa_return_null_if_fail(name && pa_utf8_valid(name) && *name);
+
+ s = pa_msgobject_new(pa_sink);
+
+ if (!(name = pa_namereg_register(core, name, PA_NAMEREG_SINK, s, fail))) {
+ pa_xfree(s);
+ 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 = 0;
+ s->name = pa_xstrdup(name);
+ s->description = NULL;
+ s->driver = pa_xstrdup(driver);
+ s->module = NULL;
+
+ s->sample_spec = *spec;
+ s->channel_map = *map;
+
+ s->inputs = pa_idxset_new(NULL, NULL);
+ s->n_corked = 0;
+
+ pa_cvolume_reset(&s->volume, spec->channels);
+ s->muted = FALSE;
+ s->refresh_volume = s->refresh_mute = FALSE;
+
+ s->get_latency = NULL;
+ s->set_volume = NULL;
+ s->get_volume = NULL;
+ s->set_mute = NULL;
+ s->get_mute = NULL;
+ s->set_state = NULL;
+ s->userdata = NULL;
+
+ s->asyncmsgq = NULL;
+ s->rtpoll = NULL;
+ s->silence = NULL;
+
+ pa_assert_se(pa_idxset_put(core->sinks, s, &s->index) >= 0);
+
+ pa_sample_spec_snprint(st, sizeof(st), spec);
+ pa_log_info("Created sink %u \"%s\" with sample spec \"%s\"", s->index, s->name, st);
+
+ n = pa_sprintf_malloc("%s.monitor", name);
+
+ if (!(s->monitor_source = pa_source_new(core, driver, n, 0, spec, map)))
+ pa_log_warn("Failed to create monitor source.");
+ else {
+ char *d;
+ s->monitor_source->monitor_of = s;
+ d = pa_sprintf_malloc("Monitor Source of %s", s->name);
+ pa_source_set_description(s->monitor_source, d);
+ pa_xfree(d);
+ }
+
+ pa_xfree(n);
+
+ s->thread_info.inputs = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
+ s->thread_info.soft_volume = s->volume;
+ s->thread_info.soft_muted = s->muted;
+ s->thread_info.state = s->state;
+
+ return s;
+}
+
+static int sink_set_state(pa_sink *s, pa_sink_state_t state) {
+ int ret;
+ pa_bool_t suspend_change;
+
+ pa_assert(s);
+
+ if (s->state == state)
+ return 0;
+
+ suspend_change =
+ (s->state == PA_SINK_SUSPENDED && PA_SINK_OPENED(state)) ||
+ (PA_SINK_OPENED(s->state) && state == PA_SINK_SUSPENDED);
+
+ if (s->set_state)
+ if ((ret = s->set_state(s, state)) < 0)
+ return -1;
+
+ if (pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_STATE, PA_UINT_TO_PTR(state), 0, NULL) < 0)
+ return -1;
+
+ s->state = state;
+
+ if (suspend_change) {
+ pa_sink_input *i;
+ uint32_t idx;
+
+ /* We're suspending or resuming, tell everyone about it */
+
+ for (i = PA_SINK_INPUT(pa_idxset_first(s->inputs, &idx)); i; i = PA_SINK_INPUT(pa_idxset_next(s->inputs, &idx)))
+ if (i->suspend)
+ i->suspend(i, state == PA_SINK_SUSPENDED);
+ }
+
+ if (state != PA_SINK_UNLINKED) /* if we enter UNLINKED state pa_sink_unlink() will fire the apropriate events */
+ pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_STATE_CHANGED], s);
+
+ return 0;
+}
+
+void pa_sink_put(pa_sink* s) {
+ pa_sink_assert_ref(s);
+
+ pa_assert(s->state == PA_SINK_INIT);
+ pa_assert(s->asyncmsgq);
+ pa_assert(s->rtpoll);
+
+ pa_assert_se(sink_set_state(s, PA_SINK_IDLE) == 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_NEW_POST], s);
+}
+
+void pa_sink_unlink(pa_sink* s) {
+ pa_bool_t linked;
+ pa_sink_input *i, *j = NULL;
+
+ pa_assert(s);
+
+ /* 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()! */
+
+ /* All operations here shall be idempotent, i.e. pa_sink_unlink()
+ * may be called multiple times on the same sink without bad
+ * effects. */
+
+ linked = PA_SINK_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);
+
+ while ((i = pa_idxset_first(s->inputs, NULL))) {
+ pa_assert(i != j);
+ pa_sink_input_kill(i);
+ j = i;
+ }
+
+ if (linked)
+ sink_set_state(s, PA_SINK_UNLINKED);
+ else
+ s->state = PA_SINK_UNLINKED;
+
+ s->get_latency = NULL;
+ s->get_volume = NULL;
+ s->set_volume = NULL;
+ s->set_mute = NULL;
+ s->get_mute = NULL;
+ s->set_state = NULL;
+
+ 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);
+ }
+}
+
+static void sink_free(pa_object *o) {
+ pa_sink *s = PA_SINK(o);
+ pa_sink_input *i;
+
+ pa_assert(s);
+ pa_assert(pa_sink_refcnt(s) == 0);
+
+ if (PA_SINK_LINKED(s->state))
+ pa_sink_unlink(s);
+
+ pa_log_info("Freeing sink %u \"%s\"", s->index, s->name);
+
+ if (s->monitor_source) {
+ pa_source_unref(s->monitor_source);
+ s->monitor_source = NULL;
+ }
+
+ pa_idxset_free(s->inputs, NULL, NULL);
+
+ while ((i = pa_hashmap_steal_first(s->thread_info.inputs)))
+ pa_sink_input_unref(i);
+
+ pa_hashmap_free(s->thread_info.inputs, NULL, NULL);
+
+ if (s->silence)
+ pa_memblock_unref(s->silence);
+
+ pa_xfree(s->name);
+ pa_xfree(s->description);
+ pa_xfree(s->driver);
+ pa_xfree(s);
+}
+
+void pa_sink_set_asyncmsgq(pa_sink *s, pa_asyncmsgq *q) {
+ pa_sink_assert_ref(s);
+ pa_assert(q);
+
+ s->asyncmsgq = q;
+
+ if (s->monitor_source)
+ pa_source_set_asyncmsgq(s->monitor_source, q);
+}
+
+void pa_sink_set_rtpoll(pa_sink *s, pa_rtpoll *p) {
+ pa_sink_assert_ref(s);
+ pa_assert(p);
+
+ s->rtpoll = p;
+ if (s->monitor_source)
+ pa_source_set_rtpoll(s->monitor_source, p);
+}
+
+int pa_sink_update_status(pa_sink*s) {
+ pa_sink_assert_ref(s);
+ pa_assert(PA_SINK_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);
+}
+
+int pa_sink_suspend(pa_sink *s, pa_bool_t suspend) {
+ pa_sink_assert_ref(s);
+ pa_assert(PA_SINK_LINKED(s->state));
+
+ if (suspend)
+ return sink_set_state(s, PA_SINK_SUSPENDED);
+ else
+ return sink_set_state(s, pa_sink_used_by(s) ? PA_SINK_RUNNING : PA_SINK_IDLE);
+}
+
+void pa_sink_ping(pa_sink *s) {
+ pa_sink_assert_ref(s);
+ pa_assert(PA_SINK_LINKED(s->state));
+
+ pa_asyncmsgq_post(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_PING, NULL, 0, NULL, NULL);
+}
+
+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;
+
+ pa_sink_assert_ref(s);
+ pa_assert(info);
+
+ while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL)) && maxinfo > 0) {
+ pa_sink_input_assert_ref(i);
+
+ if (pa_sink_input_peek(i, length, &info->chunk, &info->volume) < 0)
+ continue;
+
+ info->userdata = pa_sink_input_ref(i);
+
+ pa_assert(info->chunk.memblock);
+ pa_assert(info->chunk.length > 0);
+
+ info++;
+ n++;
+ maxinfo--;
+ }
+
+ return n;
+}
+
+static void inputs_drop(pa_sink *s, pa_mix_info *info, unsigned n, size_t length) {
+ pa_sink_input *i;
+ void *state = NULL;
+ unsigned p = 0;
+ unsigned n_unreffed = 0;
+
+ pa_sink_assert_ref(s);
+
+ /* We optimize for the case where the order of the inputs has not changed */
+
+ while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL))) {
+ unsigned j;
+ pa_mix_info* m;
+
+ pa_sink_input_assert_ref(i);
+
+ m = NULL;
+
+ /* 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, length);
+
+ if (m) {
+ pa_sink_input_unref(m->userdata);
+ m->userdata = NULL;
+ if (m->chunk.memblock)
+ pa_memblock_unref(m->chunk.memblock);
+ pa_memchunk_reset(&m->chunk);
+
+ 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);
+ }
+ }
+}
+
+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_assert(PA_SINK_OPENED(s->thread_info.state));
+ pa_assert(pa_frame_aligned(length, &s->sample_spec));
+ pa_assert(result);
+
+ 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 = s->thread_info.state == PA_SINK_RUNNING ? fill_mix_info(s, length, info, MAX_MIX_CHANNELS) : 0;
+
+ if (n == 0) {
+
+ if (length > SILENCE_BUFFER_LENGTH)
+ length = pa_frame_align(SILENCE_BUFFER_LENGTH, &s->sample_spec);
+
+ pa_assert(length > 0);
+
+ if (!s->silence || pa_memblock_get_length(s->silence) < length) {
+ if (s->silence)
+ pa_memblock_unref(s->silence);
+ s->silence = pa_silence_memblock_new(s->core->mempool, &s->sample_spec, length);
+ }
+
+ result->memblock = pa_memblock_ref(s->silence);
+ result->length = length;
+ result->index = 0;
+
+ } else if (n == 1) {
+ pa_cvolume volume;
+
+ *result = info[0].chunk;
+ pa_memblock_ref(result->memblock);
+
+ if (result->length > length)
+ result->length = length;
+
+ pa_sw_cvolume_multiply(&volume, &s->thread_info.soft_volume, &info[0].volume);
+
+ if (s->thread_info.soft_muted || !pa_cvolume_is_norm(&volume)) {
+ pa_memchunk_make_writable(result, 0);
+ if (s->thread_info.soft_muted || pa_cvolume_is_muted(&volume))
+ pa_silence_memchunk(result, &s->sample_spec);
+ else
+ 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;
+ }
+
+ if (s->thread_info.state == PA_SINK_RUNNING)
+ inputs_drop(s, info, n, result->length);
+
+ if (s->monitor_source && PA_SOURCE_OPENED(pa_source_get_state(s->monitor_source)))
+ pa_source_post(s->monitor_source, result);
+
+ pa_sink_unref(s);
+}
+
+void pa_sink_render_into(pa_sink*s, pa_memchunk *target) {
+ pa_mix_info info[MAX_MIX_CHANNELS];
+ unsigned n;
+
+ pa_sink_assert_ref(s);
+ pa_assert(PA_SINK_OPENED(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_sink_ref(s);
+
+ n = s->thread_info.state == PA_SINK_RUNNING ? fill_mix_info(s, target->length, info, MAX_MIX_CHANNELS) : 0;
+
+ if (n == 0) {
+ pa_silence_memchunk(target, &s->sample_spec);
+ } else if (n == 1) {
+ if (target->length > info[0].chunk.length)
+ target->length = info[0].chunk.length;
+
+ if (s->thread_info.soft_muted)
+ pa_silence_memchunk(target, &s->sample_spec);
+ else {
+ void *src, *ptr;
+ pa_cvolume volume;
+
+ ptr = pa_memblock_acquire(target->memblock);
+ src = pa_memblock_acquire(info[0].chunk.memblock);
+
+ memcpy((uint8_t*) ptr + target->index,
+ (uint8_t*) src + info[0].chunk.index,
+ target->length);
+
+ pa_memblock_release(target->memblock);
+ pa_memblock_release(info[0].chunk.memblock);
+
+ pa_sw_cvolume_multiply(&volume, &s->thread_info.soft_volume, &info[0].volume);
+
+ if (!pa_cvolume_is_norm(&volume))
+ pa_volume_memchunk(target, &s->sample_spec, &volume);
+ }
+
+ } else {
+ void *ptr;
+
+ ptr = pa_memblock_acquire(target->memblock);
+
+ target->length = pa_mix(info, n,
+ (uint8_t*) ptr + target->index,
+ target->length,
+ &s->sample_spec,
+ &s->thread_info.soft_volume,
+ s->thread_info.soft_muted);
+
+ pa_memblock_release(target->memblock);
+ }
+
+ if (s->thread_info.state == PA_SINK_RUNNING)
+ inputs_drop(s, info, n, target->length);
+
+ if (s->monitor_source && PA_SOURCE_OPENED(pa_source_get_state(s->monitor_source)))
+ pa_source_post(s->monitor_source, target);
+
+ pa_sink_unref(s);
+}
+
+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_assert(PA_SINK_OPENED(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_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);
+}
+
+void pa_sink_render_full(pa_sink *s, size_t length, pa_memchunk *result) {
+ pa_sink_assert_ref(s);
+ pa_assert(PA_SINK_OPENED(s->thread_info.state));
+ pa_assert(length > 0);
+ pa_assert(pa_frame_aligned(length, &s->sample_spec));
+ pa_assert(result);
+
+ /*** This needs optimization ***/
+
+ result->index = 0;
+ result->length = length;
+ result->memblock = pa_memblock_new(s->core->mempool, length);
+
+ pa_sink_render_into_full(s, result);
+}
+
+void pa_sink_skip(pa_sink *s, size_t length) {
+ pa_sink_input *i;
+ void *state = NULL;
+
+ pa_sink_assert_ref(s);
+ pa_assert(PA_SINK_OPENED(s->thread_info.state));
+ pa_assert(length > 0);
+ pa_assert(pa_frame_aligned(length, &s->sample_spec));
+
+ if (pa_source_used_by(s->monitor_source)) {
+ pa_memchunk chunk;
+
+ /* If something is connected to our monitor source, we have to
+ * pass valid data to it */
+
+ while (length > 0) {
+ pa_sink_render(s, length, &chunk);
+ pa_memblock_unref(chunk.memblock);
+
+ pa_assert(chunk.length <= length);
+ length -= chunk.length;
+ }
+
+ } else {
+ /* Ok, noone cares about the rendered data, so let's not even render it */
+
+ while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL))) {
+ pa_sink_input_assert_ref(i);
+ pa_sink_input_drop(i, length);
+ }
+ }
+}
+
+pa_usec_t pa_sink_get_latency(pa_sink *s) {
+ pa_usec_t usec = 0;
+
+ pa_sink_assert_ref(s);
+ pa_assert(PA_SINK_LINKED(s->state));
+
+ if (!PA_SINK_OPENED(s->state))
+ return 0;
+
+ if (s->get_latency)
+ return s->get_latency(s);
+
+ if (pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0)
+ return 0;
+
+ return usec;
+}
+
+void pa_sink_set_volume(pa_sink *s, const pa_cvolume *volume) {
+ int changed;
+
+ pa_sink_assert_ref(s);
+ pa_assert(PA_SINK_LINKED(s->state));
+ pa_assert(volume);
+
+ changed = !pa_cvolume_equal(volume, &s->volume);
+ s->volume = *volume;
+
+ if (s->set_volume && s->set_volume(s) < 0)
+ s->set_volume = NULL;
+
+ if (!s->set_volume)
+ pa_asyncmsgq_post(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_VOLUME, pa_xnewdup(struct pa_cvolume, volume, 1), 0, NULL, pa_xfree);
+
+ if (changed)
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+}
+
+const pa_cvolume *pa_sink_get_volume(pa_sink *s) {
+ struct pa_cvolume old_volume;
+
+ pa_sink_assert_ref(s);
+ pa_assert(PA_SINK_LINKED(s->state));
+
+ old_volume = s->volume;
+
+ if (s->get_volume && s->get_volume(s) < 0)
+ s->get_volume = NULL;
+
+ if (!s->get_volume && s->refresh_volume)
+ pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_VOLUME, &s->volume, 0, NULL);
+
+ if (!pa_cvolume_equal(&old_volume, &s->volume))
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+
+ return &s->volume;
+}
+
+void pa_sink_set_mute(pa_sink *s, pa_bool_t mute) {
+ int changed;
+
+ pa_sink_assert_ref(s);
+ pa_assert(PA_SINK_LINKED(s->state));
+
+ changed = s->muted != mute;
+ s->muted = mute;
+
+ if (s->set_mute && s->set_mute(s) < 0)
+ s->set_mute = NULL;
+
+ if (!s->set_mute)
+ pa_asyncmsgq_post(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_MUTE, PA_UINT_TO_PTR(mute), 0, NULL, NULL);
+
+ if (changed)
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+}
+
+pa_bool_t pa_sink_get_mute(pa_sink *s) {
+ pa_bool_t old_muted;
+
+ pa_sink_assert_ref(s);
+ pa_assert(PA_SINK_LINKED(s->state));
+
+ old_muted = s->muted;
+
+ if (s->get_mute && s->get_mute(s) < 0)
+ s->get_mute = NULL;
+
+ if (!s->get_mute && s->refresh_mute)
+ pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_MUTE, &s->muted, 0, NULL);
+
+ if (old_muted != s->muted)
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+
+ return s->muted;
+}
+
+void pa_sink_set_module(pa_sink *s, pa_module *m) {
+ pa_sink_assert_ref(s);
+
+ if (s->module == m)
+ return;
+
+ s->module = m;
+
+ if (s->monitor_source)
+ pa_source_set_module(s->monitor_source, m);
+
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+}
+
+void pa_sink_set_description(pa_sink *s, const char *description) {
+ pa_sink_assert_ref(s);
+
+ if (!description && !s->description)
+ return;
+
+ if (description && s->description && !strcmp(description, s->description))
+ return;
+
+ pa_xfree(s->description);
+ s->description = pa_xstrdup(description);
+
+ if (s->monitor_source) {
+ char *n;
+
+ n = pa_sprintf_malloc("Monitor Source of %s", s->description? s->description : s->name);
+ pa_source_set_description(s->monitor_source, n);
+ pa_xfree(n);
+ }
+
+ if (PA_SINK_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_DESCRIPTION_CHANGED], s);
+ }
+}
+
+unsigned pa_sink_linked_by(pa_sink *s) {
+ unsigned ret;
+
+ pa_sink_assert_ref(s);
+ pa_assert(PA_SINK_LINKED(s->state));
+
+ ret = pa_idxset_size(s->inputs);
+
+ /* We add in the number of streams connected to us here. Please
+ * not the asymmmetry to pa_sink_used_by()! */
+
+ if (s->monitor_source)
+ ret += pa_source_linked_by(s->monitor_source);
+
+ return ret;
+}
+
+unsigned pa_sink_used_by(pa_sink *s) {
+ unsigned ret;
+
+ pa_sink_assert_ref(s);
+ pa_assert(PA_SINK_LINKED(s->state));
+
+ ret = pa_idxset_size(s->inputs);
+ pa_assert(ret >= s->n_corked);
+ ret -= s->n_corked;
+
+ /* Streams connected to our monitor source do not matter for
+ * pa_sink_used_by()!.*/
+
+ return ret;
+}
+
+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);
+ pa_assert(s->thread_info.state != PA_SINK_UNLINKED);
+
+ switch ((pa_sink_message_t) code) {
+
+ case PA_SINK_MESSAGE_ADD_INPUT: {
+ pa_sink_input *i = PA_SINK_INPUT(userdata);
+ 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_assert(!i->thread_info.attached);
+ i->thread_info.attached = TRUE;
+
+ if (i->attach)
+ i->attach(i);
+
+ /* If you change anything here, make sure to change the
+ * ghost sink input handling a few lines down at
+ * PA_SINK_MESSAGE_REMOVE_INPUT_AND_BUFFER, too. */
+
+ return 0;
+ }
+
+ 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_REMOVE_INPUT_AND_BUFFER, too. */
+
+ if (i->detach)
+ i->detach(i);
+
+ pa_assert(i->thread_info.attached);
+ i->thread_info.attached = FALSE;
+
+ /* 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->thread_info.sync_prev);
+ pa_assert(!i->thread_info.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;
+ }
+
+ if (pa_hashmap_remove(s->thread_info.inputs, PA_UINT32_TO_PTR(i->index)))
+ pa_sink_input_unref(i);
+
+ return 0;
+ }
+
+ case PA_SINK_MESSAGE_REMOVE_INPUT_AND_BUFFER: {
+ pa_sink_input_move_info *info = userdata;
+ int volume_is_norm;
+
+ /* We don't support moving synchronized streams. */
+ pa_assert(!info->sink_input->sync_prev);
+ pa_assert(!info->sink_input->sync_next);
+ pa_assert(!info->sink_input->thread_info.sync_next);
+ pa_assert(!info->sink_input->thread_info.sync_prev);
+
+ if (info->sink_input->detach)
+ info->sink_input->detach(info->sink_input);
+
+ pa_assert(info->sink_input->thread_info.attached);
+ info->sink_input->thread_info.attached = FALSE;
+
+ if (info->ghost_sink_input) {
+ pa_assert(info->buffer_bytes > 0);
+ pa_assert(info->buffer);
+
+ volume_is_norm = pa_cvolume_is_norm(&info->sink_input->thread_info.volume);
+
+ pa_log_debug("Buffering %lu bytes ...", (unsigned long) info->buffer_bytes);
+
+ while (info->buffer_bytes > 0) {
+ pa_memchunk memchunk;
+ pa_cvolume volume;
+ size_t n;
+
+ if (pa_sink_input_peek(info->sink_input, info->buffer_bytes, &memchunk, &volume) < 0)
+ break;
+
+ n = memchunk.length > info->buffer_bytes ? info->buffer_bytes : memchunk.length;
+ pa_sink_input_drop(info->sink_input, n);
+ memchunk.length = n;
+
+ if (!volume_is_norm) {
+ pa_memchunk_make_writable(&memchunk, 0);
+ pa_volume_memchunk(&memchunk, &s->sample_spec, &volume);
+ }
+
+ if (pa_memblockq_push(info->buffer, &memchunk) < 0) {
+ pa_memblock_unref(memchunk.memblock);
+ break;
+ }
+
+ pa_memblock_unref(memchunk.memblock);
+ info->buffer_bytes -= n;
+ }
+
+ /* Add the remaining already resampled chunk to the buffer */
+ if (info->sink_input->thread_info.resampled_chunk.memblock)
+ pa_memblockq_push(info->buffer, &info->sink_input->thread_info.resampled_chunk);
+
+ pa_memblockq_sink_input_set_queue(info->ghost_sink_input, info->buffer);
+
+ pa_log_debug("Buffered %lu bytes ...", (unsigned long) pa_memblockq_get_length(info->buffer));
+ }
+
+ /* Let's remove the sink input ...*/
+ if (pa_hashmap_remove(s->thread_info.inputs, PA_UINT32_TO_PTR(info->sink_input->index)))
+ pa_sink_input_unref(info->sink_input);
+
+ /* .. and add the ghost sink input instead */
+ if (info->ghost_sink_input) {
+ pa_hashmap_put(s->thread_info.inputs, PA_UINT32_TO_PTR(info->ghost_sink_input->index), pa_sink_input_ref(info->ghost_sink_input));
+ info->ghost_sink_input->thread_info.sync_prev = info->ghost_sink_input->thread_info.sync_next = NULL;
+
+ pa_assert(!info->ghost_sink_input->thread_info.attached);
+ info->ghost_sink_input->thread_info.attached = TRUE;
+
+ if (info->ghost_sink_input->attach)
+ info->ghost_sink_input->attach(info->ghost_sink_input);
+ }
+
+ return 0;
+ }
+
+ case PA_SINK_MESSAGE_SET_VOLUME:
+ s->thread_info.soft_volume = *((pa_cvolume*) userdata);
+ return 0;
+
+ case PA_SINK_MESSAGE_SET_MUTE:
+ s->thread_info.soft_muted = PA_PTR_TO_UINT(userdata);
+ return 0;
+
+ case PA_SINK_MESSAGE_GET_VOLUME:
+ *((pa_cvolume*) userdata) = s->thread_info.soft_volume;
+ return 0;
+
+ case PA_SINK_MESSAGE_GET_MUTE:
+ *((pa_bool_t*) userdata) = s->thread_info.soft_muted;
+ return 0;
+
+ case PA_SINK_MESSAGE_PING:
+ return 0;
+
+ case PA_SINK_MESSAGE_SET_STATE:
+
+ s->thread_info.state = PA_PTR_TO_UINT(userdata);
+ return 0;
+
+ case PA_SINK_MESSAGE_DETACH:
+
+ /* We're detaching all our input streams so that the
+ * asyncmsgq and rtpoll fields can be changed without
+ * problems */
+ pa_sink_detach_within_thread(s);
+ break;
+
+ case PA_SINK_MESSAGE_ATTACH:
+
+ /* Reattach all streams */
+ pa_sink_attach_within_thread(s);
+ break;
+
+ case PA_SINK_MESSAGE_GET_LATENCY:
+ case PA_SINK_MESSAGE_MAX:
+ ;
+ }
+
+ return -1;
+}
+
+int pa_sink_suspend_all(pa_core *c, pa_bool_t suspend) {
+ pa_sink *sink;
+ uint32_t idx;
+ int ret = 0;
+
+ pa_core_assert_ref(c);
+
+ for (sink = PA_SINK(pa_idxset_first(c->sinks, &idx)); sink; sink = PA_SINK(pa_idxset_next(c->sinks, &idx)))
+ ret -= pa_sink_suspend(sink, suspend) < 0;
+
+ return ret;
+}
+
+void pa_sink_detach(pa_sink *s) {
+ pa_sink_assert_ref(s);
+ pa_assert(PA_SINK_LINKED(s->state));
+
+ pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_DETACH, NULL, 0, NULL);
+}
+
+void pa_sink_attach(pa_sink *s) {
+ pa_sink_assert_ref(s);
+ pa_assert(PA_SINK_LINKED(s->state));
+
+ pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_ATTACH, NULL, 0, NULL);
+}
+
+void pa_sink_detach_within_thread(pa_sink *s) {
+ pa_sink_input *i;
+ void *state = NULL;
+
+ pa_sink_assert_ref(s);
+ pa_assert(PA_SINK_LINKED(s->thread_info.state));
+
+ while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL)))
+ if (i->detach)
+ i->detach(i);
+
+ if (s->monitor_source)
+ pa_source_detach_within_thread(s->monitor_source);
+}
+
+void pa_sink_attach_within_thread(pa_sink *s) {
+ pa_sink_input *i;
+ void *state = NULL;
+
+ pa_sink_assert_ref(s);
+ pa_assert(PA_SINK_LINKED(s->thread_info.state));
+
+ while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL)))
+ if (i->attach)
+ i->attach(i);
+
+ if (s->monitor_source)
+ pa_source_attach_within_thread(s->monitor_source);
+}
diff --git a/src/pulsecore/sink.h b/src/pulsecore/sink.h
new file mode 100644
index 00000000..e9969309
--- /dev/null
+++ b/src/pulsecore/sink.h
@@ -0,0 +1,188 @@
+#ifndef foopulsesinkhfoo
+#define foopulsesinkhfoo
+
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+typedef struct pa_sink pa_sink;
+
+#include <inttypes.h>
+
+#include <pulse/sample.h>
+#include <pulse/channelmap.h>
+#include <pulse/volume.h>
+
+#include <pulsecore/core-def.h>
+#include <pulsecore/core.h>
+#include <pulsecore/idxset.h>
+#include <pulsecore/source.h>
+#include <pulsecore/module.h>
+#include <pulsecore/refcnt.h>
+#include <pulsecore/msgobject.h>
+#include <pulsecore/rtpoll.h>
+
+#define PA_MAX_INPUTS_PER_SINK 32
+
+typedef enum pa_sink_state {
+ PA_SINK_INIT,
+ PA_SINK_RUNNING,
+ PA_SINK_SUSPENDED,
+ PA_SINK_IDLE,
+ PA_SINK_UNLINKED
+} pa_sink_state_t;
+
+static inline pa_bool_t PA_SINK_OPENED(pa_sink_state_t x) {
+ return x == PA_SINK_RUNNING || x == PA_SINK_IDLE;
+}
+
+static inline pa_bool_t PA_SINK_LINKED(pa_sink_state_t x) {
+ return x == PA_SINK_RUNNING || x == PA_SINK_IDLE || x == PA_SINK_SUSPENDED;
+}
+
+struct pa_sink {
+ pa_msgobject parent;
+
+ uint32_t index;
+ pa_core *core;
+ pa_sink_state_t state;
+ pa_sink_flags_t flags;
+
+ char *name;
+ char *description, *driver; /* may be NULL */
+
+ pa_module *module; /* may be NULL */
+
+ pa_sample_spec sample_spec;
+ pa_channel_map channel_map;
+
+ pa_idxset *inputs;
+ unsigned n_corked;
+ pa_source *monitor_source;
+
+ pa_cvolume volume;
+ pa_bool_t muted;
+ pa_bool_t refresh_volume;
+ pa_bool_t refresh_mute;
+
+ int (*set_state)(pa_sink *s, pa_sink_state_t state); /* may be NULL */
+ int (*set_volume)(pa_sink *s); /* dito */
+ int (*get_volume)(pa_sink *s); /* dito */
+ int (*get_mute)(pa_sink *s); /* dito */
+ int (*set_mute)(pa_sink *s); /* dito */
+ pa_usec_t (*get_latency)(pa_sink *s); /* dito */
+
+ pa_asyncmsgq *asyncmsgq;
+ pa_rtpoll *rtpoll;
+
+ /* 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_cvolume soft_volume;
+ pa_bool_t soft_muted;
+ } thread_info;
+
+ pa_memblock *silence;
+
+ void *userdata;
+};
+
+PA_DECLARE_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_VOLUME,
+ PA_SINK_MESSAGE_GET_MUTE,
+ PA_SINK_MESSAGE_SET_MUTE,
+ PA_SINK_MESSAGE_GET_LATENCY,
+ PA_SINK_MESSAGE_SET_STATE,
+ PA_SINK_MESSAGE_PING,
+ PA_SINK_MESSAGE_REMOVE_INPUT_AND_BUFFER,
+ PA_SINK_MESSAGE_ATTACH,
+ PA_SINK_MESSAGE_DETACH,
+ PA_SINK_MESSAGE_MAX
+} pa_sink_message_t;
+
+/* To be called exclusively by the sink driver, from main context */
+
+pa_sink* pa_sink_new(
+ pa_core *core,
+ const char *driver,
+ const char *name,
+ int namereg_fail,
+ const pa_sample_spec *spec,
+ const pa_channel_map *map);
+
+void pa_sink_put(pa_sink *s);
+void pa_sink_unlink(pa_sink* s);
+
+void pa_sink_set_module(pa_sink *sink, pa_module *m);
+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_detach(pa_sink *s);
+void pa_sink_attach(pa_sink *s);
+
+/* May be called by everyone, from main context */
+
+pa_usec_t pa_sink_get_latency(pa_sink *s);
+
+int pa_sink_update_status(pa_sink*s);
+int pa_sink_suspend(pa_sink *s, pa_bool_t suspend);
+int pa_sink_suspend_all(pa_core *c, pa_bool_t suspend);
+
+/* Sends a ping message to the sink thread, to make it wake up and
+ * check for data to process even if there is no real message is
+ * sent */
+void pa_sink_ping(pa_sink *s);
+
+void pa_sink_set_volume(pa_sink *sink, const pa_cvolume *volume);
+const pa_cvolume *pa_sink_get_volume(pa_sink *sink);
+void pa_sink_set_mute(pa_sink *sink, pa_bool_t mute);
+pa_bool_t pa_sink_get_mute(pa_sink *sink);
+
+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 */
+#define pa_sink_get_state(s) ((s)->state)
+
+/* 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_skip(pa_sink *s, size_t length);
+
+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);
+
+#endif
diff --git a/src/pulsecore/sioman.c b/src/pulsecore/sioman.c
new file mode 100644
index 00000000..8d4c6fa7
--- /dev/null
+++ b/src/pulsecore/sioman.c
@@ -0,0 +1,41 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulsecore/macro.h>
+#include <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 00000000..49fffb34
--- /dev/null
+++ b/src/pulsecore/sioman.h
@@ -0,0 +1,30 @@
+#ifndef foosiomanhfoo
+#define foosiomanhfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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.
+***/
+
+int pa_stdio_acquire(void);
+void pa_stdio_release(void);
+
+#endif
diff --git a/src/pulsecore/socket-client.c b/src/pulsecore/socket-client.c
new file mode 100644
index 00000000..5b5bc5ca
--- /dev/null
+++ b/src/pulsecore/socket-client.c
@@ -0,0 +1,569 @@
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+/* #undef HAVE_LIBASYNCNS */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+
+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+#ifdef HAVE_SYS_UN_H
+#include <sys/un.h>
+#endif
+#ifdef HAVE_ARPA_INET_H
+#include <arpa/inet.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/timeval.h>
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/winsock.h>
+#include <pulsecore/core-error.h>
+#include <pulsecore/socket-util.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/log.h>
+#include <pulsecore/parseaddr.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/refcnt.h>
+
+#include "socket-client.h"
+
+#define CONNECT_TIMEOUT 5
+
+struct pa_socket_client {
+ PA_REFCNT_DECLARE;
+ pa_mainloop_api *mainloop;
+ int fd;
+ pa_io_event *io_event;
+ pa_time_event *timeout_event;
+ pa_defer_event *defer_event;
+ void (*callback)(pa_socket_client*c, pa_iochannel *io, void *userdata);
+ void *userdata;
+ int local;
+#ifdef HAVE_LIBASYNCNS
+ asyncns_t *asyncns;
+ asyncns_query_t * asyncns_query;
+ pa_io_event *asyncns_io_event;
+#endif
+};
+
+static pa_socket_client*pa_socket_client_new(pa_mainloop_api *m) {
+ pa_socket_client *c;
+ pa_assert(m);
+
+ c = pa_xnew(pa_socket_client, 1);
+ PA_REFCNT_INIT(c);
+ c->mainloop = m;
+ c->fd = -1;
+ c->io_event = NULL;
+ c->defer_event = NULL;
+ c->timeout_event = NULL;
+ c->callback = NULL;
+ c->userdata = NULL;
+ c->local = 0;
+
+#ifdef HAVE_LIBASYNCNS
+ c->asyncns = NULL;
+ c->asyncns_io_event = NULL;
+ c->asyncns_query = NULL;
+#endif
+
+ 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->defer_event) {
+ c->mainloop->defer_free(c->defer_event);
+ c->defer_event = NULL;
+ }
+
+ if (c->timeout_event) {
+ c->mainloop->time_free(c->timeout_event);
+ c->timeout_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);
+ pa_assert(io);
+
+finish:
+ if (!io && c->fd >= 0)
+ pa_close(c->fd);
+ c->fd = -1;
+
+ free_events(c);
+
+ pa_assert(c->callback);
+ c->callback(c, io, c->userdata);
+
+ pa_socket_client_unref(c);
+}
+
+static void connect_fixed_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_GCC_UNUSED 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) {
+ int r;
+
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+ pa_assert(sa);
+ pa_assert(len > 0);
+
+ pa_make_fd_nonblock(c->fd);
+
+ if ((r = 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;
+ }
+
+ pa_assert_se(c->io_event = c->mainloop->io_new(c->mainloop, c->fd, PA_IO_EVENT_OUTPUT, connect_io_cb, c));
+ } else
+ pa_assert_se(c->defer_event = c->mainloop->defer_new(c->mainloop, connect_fixed_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);
+
+ memset(&sa, 0, sizeof(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));
+}
+
+#ifdef HAVE_SYS_UN_H
+
+pa_socket_client* pa_socket_client_new_unix(pa_mainloop_api *m, const char *filename) {
+ struct sockaddr_un sa;
+
+ pa_assert(m);
+ pa_assert(filename);
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sun_family = AF_UNIX;
+ strncpy(sa.sun_path, filename, sizeof(sa.sun_path)-1);
+ sa.sun_path[sizeof(sa.sun_path) - 1] = 0;
+
+ return pa_socket_client_new_sockaddr(m, (struct sockaddr*) &sa, sizeof(sa));
+}
+
+#else /* HAVE_SYS_UN_H */
+
+pa_socket_client* pa_socket_client_new_unix(pa_mainloop_api *m, const char *filename) {
+ 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);
+
+ switch (sa->sa_family) {
+ case AF_UNIX:
+ c->local = 1;
+ break;
+
+ case AF_INET:
+ c->local = ((const struct sockaddr_in*) sa)->sin_addr.s_addr == INADDR_LOOPBACK;
+ break;
+
+ case AF_INET6:
+ c->local = memcmp(&((const struct sockaddr_in6*) sa)->sin6_addr, &in6addr_loopback, sizeof(struct in6_addr)) == 0;
+ break;
+
+ default:
+ c->local = 0;
+ }
+
+ if ((c->fd = socket(sa->sa_family, SOCK_STREAM, 0)) < 0) {
+ pa_log("socket(): %s", pa_cstrerror(errno));
+ return -1;
+ }
+
+ pa_make_fd_cloexec(c->fd);
+ if (sa->sa_family == AF_INET || sa->sa_family == AF_INET6)
+ pa_make_tcp_socket_low_delay(c->fd);
+ else
+ pa_make_socket_low_delay(c->fd);
+
+ if (do_connect(c, sa, 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);
+
+ pa_assert_se(c = pa_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, void (*on_connection)(pa_socket_client *c, pa_iochannel*io, void *userdata), 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) {
+ struct sockaddr_in6 sa;
+
+ pa_assert(m);
+ pa_assert(address);
+ pa_assert(port > 0);
+
+ memset(&sa, 0, sizeof(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));
+}
+
+#ifdef HAVE_LIBASYNCNS
+
+static void asyncns_cb(pa_mainloop_api*m, pa_io_event *e, int fd, PA_GCC_UNUSED 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)
+ sockaddr_prepare(c, res->ai_addr, res->ai_addrlen);
+
+ 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 *tv, void *userdata) {
+ pa_socket_client *c = userdata;
+
+ pa_assert(m);
+ pa_assert(e);
+ pa_assert(tv);
+ 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) {
+ struct timeval tv;
+ pa_assert(c);
+ pa_assert(!c->timeout_event);
+
+ pa_gettimeofday(&tv);
+ pa_timeval_add(&tv, CONNECT_TIMEOUT * 1000000);
+ c->timeout_event = c->mainloop->time_new(c->mainloop, &tv, timeout_cb, c);
+}
+
+pa_socket_client* pa_socket_client_new_string(pa_mainloop_api *m, const char*name, uint16_t default_port) {
+ pa_socket_client *c = NULL;
+ pa_parsed_address a;
+
+ pa_assert(m);
+ pa_assert(name);
+
+ if (pa_parse_address(name, &a) < 0)
+ return NULL;
+
+ 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);
+ 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);
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = a.type == PA_PARSED_ADDRESS_TCP4 ? PF_INET : (a.type == PA_PARSED_ADDRESS_TCP6 ? PF_INET6 : PF_UNSPEC);
+ hints.ai_socktype = SOCK_STREAM;
+
+#ifdef HAVE_LIBASYNCNS
+ {
+ asyncns_t *asyncns;
+
+ if (!(asyncns = asyncns_new(1)))
+ goto finish;
+
+ c = pa_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);
+ c->asyncns_query = asyncns_getaddrinfo(c->asyncns, a.path_or_host, port, &hints);
+ pa_assert(c->asyncns_query);
+ start_timeout(c);
+ }
+#else /* HAVE_LIBASYNCNS */
+ {
+#ifdef 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);
+ }
+
+ freeaddrinfo(res);
+#else /* HAVE_GETADDRINFO */
+ struct hostent *host = NULL;
+ struct sockaddr_in s;
+
+ /* FIXME: PF_INET6 support */
+ if (hints.ai_family == PF_INET6) {
+ pa_log_error("IPv6 is not supported on Windows");
+ goto finish;
+ }
+
+ 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;
+
+ 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);
+#endif /* HAVE_GETADDRINFO */
+ }
+#endif /* HAVE_LIBASYNCNS */
+ }
+ }
+
+finish:
+ 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. */
+int 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 00000000..b1d58eff
--- /dev/null
+++ b/src/pulsecore/socket-client.h
@@ -0,0 +1,50 @@
+#ifndef foosocketclienthfoo
+#define foosocketclienthfoo
+
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <inttypes.h>
+
+#include <pulse/mainloop-api.h>
+#include <pulsecore/iochannel.h>
+
+struct sockaddr;
+
+typedef struct pa_socket_client pa_socket_client;
+
+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, const char *a, uint16_t default_port);
+
+void pa_socket_client_unref(pa_socket_client *c);
+pa_socket_client* pa_socket_client_ref(pa_socket_client *c);
+
+void pa_socket_client_set_callback(pa_socket_client *c, void (*on_connection)(pa_socket_client *c, pa_iochannel*io, void *userdata), void *userdata);
+
+int 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 00000000..162a1aac
--- /dev/null
+++ b/src/pulsecore/socket-server.c
@@ -0,0 +1,532 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <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_SOCKET_H
+#include <sys/socket.h>
+#endif
+#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_ARPA_INET_H
+#include <arpa/inet.h>
+#endif
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+
+#ifdef HAVE_LIBWRAP
+#include <tcpd.h>
+#endif
+
+#ifndef HAVE_INET_NTOP
+#include "inet_ntop.h"
+#endif
+
+#ifndef HAVE_INET_PTON
+#include "inet_pton.h"
+#endif
+
+#include "winsock.h"
+
+#include <pulse/xmalloc.h>
+#include <pulse/util.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 "socket-server.h"
+
+struct pa_socket_server {
+ PA_REFCNT_DECLARE;
+ int fd;
+ char *filename;
+ char *tcpwrap_service;
+
+ void (*on_connection)(pa_socket_server*s, pa_iochannel *io, void *userdata);
+ void *userdata;
+
+ pa_io_event *io_event;
+ pa_mainloop_api *mainloop;
+ enum { SOCKET_SERVER_GENERIC, SOCKET_SERVER_IPV4, SOCKET_SERVER_UNIX, SOCKET_SERVER_IPV6 } type;
+};
+
+static void callback(pa_mainloop_api *mainloop, pa_io_event *e, int fd, PA_GCC_UNUSED 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 = accept(fd, NULL, NULL)) < 0) {
+ pa_log("accept(): %s", pa_cstrerror(errno));
+ goto finish;
+ }
+
+ pa_make_fd_cloexec(nfd);
+
+ 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(fd);
+ else
+ pa_make_socket_low_delay(fd);
+
+ pa_assert_se(io = pa_iochannel_new(s->mainloop, nfd, nfd));
+ s->on_connection(s, io, s->userdata);
+
+finish:
+ pa_socket_server_unref(s);
+}
+
+pa_socket_server* pa_socket_server_new(pa_mainloop_api *m, int fd) {
+ pa_socket_server *s;
+
+ pa_assert(m);
+ pa_assert(fd >= 0);
+
+ s = pa_xnew(pa_socket_server, 1);
+ PA_REFCNT_INIT(s);
+ s->fd = fd;
+ s->filename = NULL;
+ s->on_connection = NULL;
+ s->userdata = NULL;
+ s->tcpwrap_service = NULL;
+
+ s->mainloop = m;
+ pa_assert_se(s->io_event = m->io_new(m, fd, PA_IO_EVENT_INPUT, callback, s));
+
+ s->type = SOCKET_SERVER_GENERIC;
+
+ 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;
+ struct sockaddr_un sa;
+ pa_socket_server *s;
+
+ pa_assert(m);
+ pa_assert(filename);
+
+ if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) {
+ pa_log("socket(): %s", pa_cstrerror(errno));
+ goto fail;
+ }
+
+ pa_make_fd_cloexec(fd);
+
+ sa.sun_family = AF_UNIX;
+ strncpy(sa.sun_path, filename, sizeof(sa.sun_path)-1);
+ sa.sun_path[sizeof(sa.sun_path) - 1] = 0;
+
+ pa_make_socket_low_delay(fd);
+
+ if (bind(fd, (struct sockaddr*) &sa, 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 = pa_socket_server_new(m, fd));
+
+ s->filename = pa_xstrdup(filename);
+ s->type = SOCKET_SERVER_UNIX;
+
+ 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, const char *tcpwrap_service) {
+ pa_socket_server *ss;
+ int fd = -1;
+ struct sockaddr_in sa;
+ int on = 1;
+
+ pa_assert(m);
+ pa_assert(port);
+
+ if ((fd = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
+ pa_log("socket(PF_INET): %s", pa_cstrerror(errno));
+ goto fail;
+ }
+
+ pa_make_fd_cloexec(fd);
+
+#ifdef SO_REUSEADDR
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &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) {
+ pa_log("bind(): %s", pa_cstrerror(errno));
+ goto fail;
+ }
+
+ if (listen(fd, 5) < 0) {
+ pa_log("listen(): %s", pa_cstrerror(errno));
+ goto fail;
+ }
+
+ if ((ss = pa_socket_server_new(m, fd))) {
+ ss->type = SOCKET_SERVER_IPV4;
+ ss->tcpwrap_service = pa_xstrdup(tcpwrap_service);
+ }
+
+ return ss;
+
+fail:
+ if (fd >= 0)
+ pa_close(fd);
+
+ return NULL;
+}
+
+pa_socket_server* pa_socket_server_new_ipv6(pa_mainloop_api *m, const uint8_t address[16], uint16_t port, const char *tcpwrap_service) {
+ pa_socket_server *ss;
+ int fd = -1;
+ struct sockaddr_in6 sa;
+ int on = 1;
+
+ pa_assert(m);
+ pa_assert(port > 0);
+
+ if ((fd = socket(PF_INET6, SOCK_STREAM, 0)) < 0) {
+ pa_log("socket(PF_INET6): %s", pa_cstrerror(errno));
+ goto fail;
+ }
+
+ pa_make_fd_cloexec(fd);
+
+#ifdef IPV6_V6ONLY
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)) < 0)
+ pa_log("setsockopt(IPPROTO_IPV6, IPV6_V6ONLY): %s", pa_cstrerror(errno));
+#endif
+
+#ifdef SO_REUSEADDR
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &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) {
+ pa_log("bind(): %s", pa_cstrerror(errno));
+ goto fail;
+ }
+
+ if (listen(fd, 5) < 0) {
+ pa_log("listen(): %s", pa_cstrerror(errno));
+ goto fail;
+ }
+
+ if ((ss = pa_socket_server_new(m, fd))) {
+ ss->type = SOCKET_SERVER_IPV6;
+ ss->tcpwrap_service = pa_xstrdup(tcpwrap_service);
+ }
+
+ return ss;
+
+fail:
+ if (fd >= 0)
+ pa_close(fd);
+
+ return NULL;
+}
+
+pa_socket_server* pa_socket_server_new_ipv4_loopback(pa_mainloop_api *m, uint16_t port, const char *tcpwrap_service) {
+ pa_assert(m);
+ pa_assert(port > 0);
+
+ return pa_socket_server_new_ipv4(m, INADDR_LOOPBACK, port, tcpwrap_service);
+}
+
+pa_socket_server* pa_socket_server_new_ipv6_loopback(pa_mainloop_api *m, uint16_t port, const char *tcpwrap_service) {
+ pa_assert(m);
+ pa_assert(port > 0);
+
+ return pa_socket_server_new_ipv6(m, in6addr_loopback.s6_addr, port, tcpwrap_service);
+}
+
+pa_socket_server* pa_socket_server_new_ipv4_any(pa_mainloop_api *m, uint16_t port, const char *tcpwrap_service) {
+ pa_assert(m);
+ pa_assert(port > 0);
+
+ return pa_socket_server_new_ipv4(m, INADDR_ANY, port, tcpwrap_service);
+}
+
+pa_socket_server* pa_socket_server_new_ipv6_any(pa_mainloop_api *m, uint16_t port, const char *tcpwrap_service) {
+ pa_assert(m);
+ pa_assert(port > 0);
+
+ return pa_socket_server_new_ipv6(m, in6addr_any.s6_addr, port, tcpwrap_service);
+}
+
+pa_socket_server* pa_socket_server_new_ipv4_string(pa_mainloop_api *m, const char *name, uint16_t port, 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, tcpwrap_service);
+
+ return NULL;
+}
+
+pa_socket_server* pa_socket_server_new_ipv6_string(pa_mainloop_api *m, const char *name, uint16_t port, 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, tcpwrap_service);
+
+ return NULL;
+}
+
+static void socket_server_free(pa_socket_server*s) {
+ pa_assert(s);
+
+ if (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, void (*on_connection)(pa_socket_server*s, pa_iochannel *io, void *userdata), 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) {
+ 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 hn[256];
+ if (!pa_get_host_name(hn, sizeof(hn)))
+ return NULL;
+
+ pa_snprintf(c, l, "{%s}tcp6:localhost:%u", hn, (unsigned) ntohs(sa.sin6_port));
+ } 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;
+ }
+
+ 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 hn[256];
+ if (!pa_get_host_name(hn, sizeof(hn)))
+ return NULL;
+
+ pa_snprintf(c, l, "{%s}tcp:localhost:%u", hn, (unsigned) ntohs(sa.sin_port));
+ } 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 hn[256];
+
+ if (!s->filename)
+ return NULL;
+
+ if (!pa_get_host_name(hn, sizeof(hn)))
+ return NULL;
+
+ pa_snprintf(c, l, "{%s}unix:%s", hn, s->filename);
+ return c;
+ }
+
+ default:
+ return NULL;
+ }
+}
diff --git a/src/pulsecore/socket-server.h b/src/pulsecore/socket-server.h
new file mode 100644
index 00000000..777599e5
--- /dev/null
+++ b/src/pulsecore/socket-server.h
@@ -0,0 +1,54 @@
+#ifndef foosocketserverhfoo
+#define foosocketserverhfoo
+
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#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(pa_mainloop_api *m, int fd);
+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, const char *tcpwrap_service);
+pa_socket_server* pa_socket_server_new_ipv6(pa_mainloop_api *m, const uint8_t address[16], uint16_t port, const char *tcpwrap_service);
+pa_socket_server* pa_socket_server_new_ipv4_loopback(pa_mainloop_api *m, uint16_t port, const char *tcpwrap_service);
+pa_socket_server* pa_socket_server_new_ipv6_loopback(pa_mainloop_api *m, uint16_t port, const char *tcpwrap_service);
+pa_socket_server* pa_socket_server_new_ipv4_any(pa_mainloop_api *m, uint16_t port, const char *tcpwrap_service);
+pa_socket_server* pa_socket_server_new_ipv6_any(pa_mainloop_api *m, uint16_t port, const char *tcpwrap_service);
+pa_socket_server* pa_socket_server_new_ipv4_string(pa_mainloop_api *m, const char *name, uint16_t port, const char *tcpwrap_service);
+pa_socket_server* pa_socket_server_new_ipv6_string(pa_mainloop_api *m, const char *name, uint16_t port, const char *tcpwrap_service);
+
+void pa_socket_server_unref(pa_socket_server*s);
+pa_socket_server* pa_socket_server_ref(pa_socket_server *s);
+
+void pa_socket_server_set_callback(pa_socket_server*s, void (*on_connection)(pa_socket_server*s, pa_iochannel *io, void *userdata), 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 00000000..456accb8
--- /dev/null
+++ b/src/pulsecore/socket-util.c
@@ -0,0 +1,286 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+#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_ARPA_INET_H
+#include <arpa/inet.h>
+#endif
+
+#ifndef HAVE_INET_NTOP
+#include "inet_ntop.h"
+#endif
+
+#include "winsock.h"
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/core-error.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+
+#include "socket-util.h"
+
+void pa_socket_peer_to_string(int fd, char *c, size_t l) {
+ struct stat st;
+
+ pa_assert(fd >= 0);
+ pa_assert(c);
+ pa_assert(l > 0);
+
+#ifndef OS_IS_WIN32
+ pa_assert_se(fstat(fd, &st) == 0);
+#endif
+
+#ifndef OS_IS_WIN32
+ if (S_ISSOCK(st.st_mode)) {
+#endif
+ union {
+ struct sockaddr sa;
+ struct sockaddr_in in;
+ struct sockaddr_in6 in6;
+#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;
+ } 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;
+ }
+#ifdef HAVE_SYS_UN_H
+ } else if (sa.sa.sa_family == AF_UNIX) {
+ pa_snprintf(c, l, "UNIX socket client");
+ return;
+#endif
+ }
+
+ }
+#ifndef OS_IS_WIN32
+ pa_snprintf(c, l, "Unknown network client");
+ return;
+ } 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, (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, (void*)&on, sizeof(on)) < 0)
+#else
+ if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (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, (void*)&tos, sizeof(tos)) < 0)
+#else
+ if (setsockopt(fd, IPPROTO_IP, IP_TOS, (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, (void*)&tos, sizeof(tos)) < 0)
+#else
+ if (setsockopt(fd, IPPROTO_IP, IP_TOS, (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) {
+ pa_assert(fd >= 0);
+
+ if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (void*)&l, sizeof(l)) < 0) {
+ pa_log_warn("SO_RCVBUF: %s", pa_cstrerror(errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+int pa_socket_set_sndbuf(int fd, size_t l) {
+ pa_assert(fd >= 0);
+
+ if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (void*)&l, sizeof(l)) < 0) {
+ pa_log("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 = socket(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);
+
+ 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 */
diff --git a/src/pulsecore/socket-util.h b/src/pulsecore/socket-util.h
new file mode 100644
index 00000000..a0344c68
--- /dev/null
+++ b/src/pulsecore/socket-util.h
@@ -0,0 +1,42 @@
+#ifndef foosocketutilhfoo
+#define foosocketutilhfoo
+
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <sys/types.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);
+
+#endif
diff --git a/src/pulsecore/sound-file-stream.c b/src/pulsecore/sound-file-stream.c
new file mode 100644
index 00000000..bb1f3e9a
--- /dev/null
+++ b/src/pulsecore/sound-file-stream.c
@@ -0,0 +1,339 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include <sndfile.h>
+
+#include <pulse/xmalloc.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 "sound-file-stream.h"
+
+typedef struct file_stream {
+ pa_msgobject parent;
+ pa_core *core;
+ SNDFILE *sndfile;
+ pa_sink_input *sink_input;
+ pa_memchunk memchunk;
+ sf_count_t (*readf_function)(SNDFILE *sndfile, void *ptr, sf_count_t frames);
+ size_t drop;
+} file_stream;
+
+enum {
+ FILE_STREAM_MESSAGE_UNLINK
+};
+
+PA_DECLARE_CLASS(file_stream);
+#define FILE_STREAM(o) (file_stream_cast(o))
+static PA_DEFINE_CHECK_TYPE(file_stream, pa_msgobject);
+
+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);
+}
+
+static void file_stream_free(pa_object *o) {
+ file_stream *u = FILE_STREAM(o);
+ pa_assert(u);
+
+ file_stream_unlink(u);
+
+ if (u->memchunk.memblock)
+ pa_memblock_unref(u->memchunk.memblock);
+
+ if (u->sndfile)
+ sf_close(u->sndfile);
+
+ pa_xfree(u);
+}
+
+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;
+}
+
+static void sink_input_kill_cb(pa_sink_input *i) {
+ pa_sink_input_assert_ref(i);
+
+ file_stream_unlink(FILE_STREAM(i->userdata));
+}
+
+static int sink_input_peek_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) {
+ file_stream *u;
+
+ pa_assert(i);
+ pa_assert(chunk);
+ u = FILE_STREAM(i->userdata);
+ file_stream_assert_ref(u);
+
+ if (!u->sndfile)
+ return -1;
+
+ for (;;) {
+
+ if (!u->memchunk.memblock) {
+
+ u->memchunk.memblock = pa_memblock_new(i->sink->core->mempool, length);
+ u->memchunk.index = 0;
+
+ if (u->readf_function) {
+ sf_count_t n;
+ void *p;
+ size_t fs = pa_frame_size(&i->sample_spec);
+
+ p = pa_memblock_acquire(u->memchunk.memblock);
+ n = u->readf_function(u->sndfile, p, length/fs);
+ pa_memblock_release(u->memchunk.memblock);
+
+ if (n <= 0)
+ n = 0;
+
+ u->memchunk.length = n * fs;
+ } else {
+ sf_count_t n;
+ void *p;
+
+ p = pa_memblock_acquire(u->memchunk.memblock);
+ n = sf_read_raw(u->sndfile, p, length);
+ pa_memblock_release(u->memchunk.memblock);
+
+ if (n <= 0)
+ n = 0;
+
+ u->memchunk.length = n;
+ }
+
+ if (u->memchunk.length <= 0) {
+
+ pa_memblock_unref(u->memchunk.memblock);
+ pa_memchunk_reset(&u->memchunk);
+
+ pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(u), FILE_STREAM_MESSAGE_UNLINK, NULL, 0, NULL, NULL);
+
+ sf_close(u->sndfile);
+ u->sndfile = NULL;
+
+ return -1;
+ }
+ }
+
+ pa_assert(u->memchunk.memblock);
+ pa_assert(u->memchunk.length > 0);
+
+ if (u->drop < u->memchunk.length) {
+ u->memchunk.index += u->drop;
+ u->memchunk.length -= u->drop;
+ u->drop = 0;
+ break;
+ }
+
+ u->drop -= u->memchunk.length;
+ pa_memblock_unref(u->memchunk.memblock);
+ pa_memchunk_reset(&u->memchunk);
+ }
+
+ *chunk = u->memchunk;
+ pa_memblock_ref(chunk->memblock);
+
+ pa_assert(chunk->length > 0);
+ pa_assert(u->drop <= 0);
+
+ return 0;
+}
+
+static void sink_input_drop_cb(pa_sink_input *i, size_t length) {
+ file_stream *u;
+
+ pa_assert(i);
+ pa_assert(length > 0);
+ u = FILE_STREAM(i->userdata);
+ file_stream_assert_ref(u);
+
+ if (u->memchunk.memblock) {
+
+ if (length < u->memchunk.length) {
+ u->memchunk.index += length;
+ u->memchunk.length -= length;
+ return;
+ }
+
+ length -= u->memchunk.length;
+ pa_memblock_unref(u->memchunk.memblock);
+ pa_memchunk_reset(&u->memchunk);
+ }
+
+ u->drop += length;
+}
+
+int pa_play_file(
+ pa_sink *sink,
+ const char *fname,
+ const pa_cvolume *volume) {
+
+ file_stream *u = NULL;
+ SF_INFO sfinfo;
+ pa_sample_spec ss;
+ pa_sink_input_new_data data;
+ int fd;
+
+ 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;
+ pa_memchunk_reset(&u->memchunk);
+ u->sndfile = NULL;
+ u->readf_function = NULL;
+ u->drop = 0;
+
+ memset(&sfinfo, 0, sizeof(sfinfo));
+
+ if ((fd = open(fname, O_RDONLY
+#ifdef O_NOCTTY
+ |O_NOCTTY
+#endif
+ )) < 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
+
+ if (!(u->sndfile = sf_open_fd(fd, SFM_READ, &sfinfo, 1))) {
+ pa_log("Failed to open file %s", fname);
+ pa_close(fd);
+ goto fail;
+ }
+
+ switch (sfinfo.format & 0xFF) {
+ case SF_FORMAT_PCM_16:
+ case SF_FORMAT_PCM_U8:
+ case SF_FORMAT_PCM_S8:
+ ss.format = PA_SAMPLE_S16NE;
+ u->readf_function = (sf_count_t (*)(SNDFILE *sndfile, void *ptr, sf_count_t frames)) sf_readf_short;
+ break;
+
+ case SF_FORMAT_ULAW:
+ ss.format = PA_SAMPLE_ULAW;
+ break;
+
+ case SF_FORMAT_ALAW:
+ ss.format = PA_SAMPLE_ALAW;
+ break;
+
+ case SF_FORMAT_FLOAT:
+ default:
+ ss.format = PA_SAMPLE_FLOAT32NE;
+ u->readf_function = (sf_count_t (*)(SNDFILE *sndfile, void *ptr, sf_count_t frames)) sf_readf_float;
+ break;
+ }
+
+ ss.rate = sfinfo.samplerate;
+ ss.channels = sfinfo.channels;
+
+ if (!pa_sample_spec_valid(&ss)) {
+ pa_log("Unsupported sample format in file %s", fname);
+ goto fail;
+ }
+
+ pa_sink_input_new_data_init(&data);
+ data.sink = sink;
+ data.driver = __FILE__;
+ data.name = fname;
+ pa_sink_input_new_data_set_sample_spec(&data, &ss);
+ pa_sink_input_new_data_set_volume(&data, volume);
+
+ if (!(u->sink_input = pa_sink_input_new(sink->core, &data, 0)))
+ goto fail;
+
+ u->sink_input->peek = sink_input_peek_cb;
+ u->sink_input->drop = sink_input_drop_cb;
+ u->sink_input->kill = sink_input_kill_cb;
+ u->sink_input->userdata = u;
+
+ 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:
+ if (u)
+ file_stream_unref(u);
+
+ return -1;
+}
diff --git a/src/pulsecore/sound-file-stream.h b/src/pulsecore/sound-file-stream.h
new file mode 100644
index 00000000..189e242d
--- /dev/null
+++ b/src/pulsecore/sound-file-stream.h
@@ -0,0 +1,31 @@
+#ifndef foosoundfilestreamhfoo
+#define foosoundfilestreamhfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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 <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 00000000..3e6f683d
--- /dev/null
+++ b/src/pulsecore/sound-file.c
@@ -0,0 +1,211 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#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 "sound-file.h"
+#include "core-scache.h"
+
+int pa_sound_file_load(
+ pa_mempool *pool,
+ const char *fname,
+ pa_sample_spec *ss,
+ pa_channel_map *map,
+ pa_memchunk *chunk) {
+
+ SNDFILE *sf = NULL;
+ SF_INFO sfinfo;
+ 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);
+ memset(&sfinfo, 0, sizeof(sfinfo));
+
+ if ((fd = open(fname, O_RDONLY
+#ifdef O_NOCTTY
+ |O_NOCTTY
+#endif
+ )) < 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
+
+ if (!(sf = sf_open_fd(fd, SFM_READ, &sfinfo, 1))) {
+ pa_log("Failed to open file %s", fname);
+ pa_close(fd);
+ goto finish;
+ }
+
+ switch (sfinfo.format & SF_FORMAT_SUBMASK) {
+ case SF_FORMAT_PCM_16:
+ case SF_FORMAT_PCM_U8:
+ case SF_FORMAT_PCM_S8:
+ ss->format = PA_SAMPLE_S16NE;
+ readf_function = (sf_count_t (*)(SNDFILE *sndfile, void *ptr, sf_count_t frames)) sf_readf_short;
+ break;
+
+ case SF_FORMAT_ULAW:
+ ss->format = PA_SAMPLE_ULAW;
+ break;
+
+ case SF_FORMAT_ALAW:
+ ss->format = PA_SAMPLE_ALAW;
+ break;
+
+ case SF_FORMAT_FLOAT:
+ case SF_FORMAT_DOUBLE:
+ default:
+ ss->format = PA_SAMPLE_FLOAT32NE;
+ readf_function = (sf_count_t (*)(SNDFILE *sndfile, void *ptr, sf_count_t frames)) sf_readf_float;
+ break;
+ }
+
+ ss->rate = sfinfo.samplerate;
+ ss->channels = sfinfo.channels;
+
+ if (!pa_sample_spec_valid(ss)) {
+ pa_log("Unsupported sample format in file %s", fname);
+ goto finish;
+ }
+
+ if (map)
+ if (!pa_channel_map_init_auto(map, ss->channels, PA_CHANNEL_MAP_DEFAULT)) {
+ pa_log("Unsupported channel map in file %s", fname);
+ goto finish;
+ }
+
+ if ((l = pa_frame_size(ss) * sfinfo.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;
+
+ ptr = pa_memblock_acquire(chunk->memblock);
+
+ if ((readf_function && readf_function(sf, ptr, sfinfo.frames) != sfinfo.frames) ||
+ (!readf_function && sf_read_raw(sf, ptr, 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);
+
+ return ret;
+}
+
+int pa_sound_file_too_big_to_cache(const char *fname) {
+
+ SNDFILE*sf = NULL;
+ SF_INFO sfinfo;
+ pa_sample_spec ss;
+
+ pa_assert(fname);
+
+ if (!(sf = sf_open(fname, SFM_READ, &sfinfo))) {
+ pa_log("Failed to open file %s", fname);
+ return -1;
+ }
+
+ sf_close(sf);
+
+ switch (sfinfo.format & SF_FORMAT_SUBMASK) {
+ case SF_FORMAT_PCM_16:
+ case SF_FORMAT_PCM_U8:
+ case SF_FORMAT_PCM_S8:
+ ss.format = PA_SAMPLE_S16NE;
+ break;
+
+ case SF_FORMAT_ULAW:
+ ss.format = PA_SAMPLE_ULAW;
+ break;
+
+ case SF_FORMAT_ALAW:
+ ss.format = PA_SAMPLE_ALAW;
+ break;
+
+ case SF_FORMAT_DOUBLE:
+ case SF_FORMAT_FLOAT:
+ default:
+ ss.format = PA_SAMPLE_FLOAT32NE;
+ break;
+ }
+
+ ss.rate = sfinfo.samplerate;
+ ss.channels = sfinfo.channels;
+
+ if (!pa_sample_spec_valid(&ss)) {
+ pa_log("Unsupported sample format in file %s", fname);
+ return -1;
+ }
+
+ if ((pa_frame_size(&ss) * sfinfo.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 00000000..46763bd8
--- /dev/null
+++ b/src/pulsecore/sound-file.h
@@ -0,0 +1,35 @@
+#ifndef soundfilehfoo
+#define soundfilehfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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>
+
+int pa_sound_file_load(pa_mempool *pool, const char *fname, pa_sample_spec *ss, pa_channel_map *map, pa_memchunk *chunk);
+
+int pa_sound_file_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 00000000..88c11469
--- /dev/null
+++ b/src/pulsecore/source-output.c
@@ -0,0 +1,515 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <pulse/utf8.h>
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/core-subscribe.h>
+#include <pulsecore/log.h>
+#include <pulsecore/namereg.h>
+
+#include "source-output.h"
+
+static PA_DEFINE_CHECK_TYPE(pa_source_output, pa_msgobject);
+
+static void source_output_free(pa_object* mo);
+
+pa_source_output_new_data* pa_source_output_new_data_init(pa_source_output_new_data *data) {
+ pa_assert(data);
+
+ memset(data, 0, sizeof(*data));
+ data->resample_method = PA_RESAMPLER_INVALID;
+ return data;
+}
+
+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;
+}
+
+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;
+}
+
+pa_source_output* pa_source_output_new(
+ pa_core *core,
+ pa_source_output_new_data *data,
+ pa_source_output_flags_t flags) {
+
+ pa_source_output *o;
+ pa_resampler *resampler = NULL;
+ char st[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX];
+
+ pa_assert(core);
+ pa_assert(data);
+
+ if (pa_hook_fire(&core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_NEW], data) < 0)
+ return NULL;
+
+ pa_return_null_if_fail(!data->driver || pa_utf8_valid(data->driver));
+ pa_return_null_if_fail(!data->name || pa_utf8_valid(data->name));
+
+ if (!data->source)
+ data->source = pa_namereg_get(core, NULL, PA_NAMEREG_SOURCE, 1);
+
+ pa_return_null_if_fail(data->source);
+ pa_return_null_if_fail(pa_source_get_state(data->source) != PA_SOURCE_UNLINKED);
+
+ if (!data->sample_spec_is_set)
+ data->sample_spec = data->source->sample_spec;
+
+ pa_return_null_if_fail(pa_sample_spec_valid(&data->sample_spec));
+
+ if (!data->channel_map_is_set) {
+ if (data->source->channel_map.channels == data->sample_spec.channels)
+ data->channel_map = data->source->channel_map;
+ else
+ 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);
+
+ if (flags & PA_SOURCE_OUTPUT_FIX_FORMAT)
+ data->sample_spec.format = data->source->sample_spec.format;
+
+ if (flags & PA_SOURCE_OUTPUT_FIX_RATE)
+ data->sample_spec.rate = data->source->sample_spec.rate;
+
+ if (flags & PA_SOURCE_OUTPUT_FIX_CHANNELS) {
+ data->sample_spec.channels = data->source->sample_spec.channels;
+ data->channel_map = data->source->channel_map;
+ }
+
+ pa_assert(pa_sample_spec_valid(&data->sample_spec));
+ pa_assert(pa_channel_map_valid(&data->channel_map));
+
+ if (data->resample_method == PA_RESAMPLER_INVALID)
+ data->resample_method = core->resample_method;
+
+ pa_return_null_if_fail(data->resample_method < PA_RESAMPLER_MAX);
+
+ if (pa_hook_fire(&core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_FIXATE], data) < 0)
+ return NULL;
+
+ 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 NULL;
+ }
+
+ if ((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 (!(resampler = pa_resampler_new(
+ core->mempool,
+ &data->source->sample_spec, &data->source->channel_map,
+ &data->sample_spec, &data->channel_map,
+ data->resample_method,
+ ((flags & PA_SOURCE_OUTPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0) |
+ ((flags & PA_SOURCE_OUTPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) |
+ (core->disable_remixing || (flags & PA_SOURCE_OUTPUT_NO_REMIX) ? PA_RESAMPLER_NO_REMIX : 0)))) {
+ pa_log_warn("Unsupported resampling operation.");
+ return NULL;
+ }
+
+ data->resample_method = pa_resampler_get_method(resampler);
+ }
+
+ 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 = flags;
+ o->name = pa_xstrdup(data->name);
+ o->driver = pa_xstrdup(data->driver);
+ o->module = data->module;
+ o->source = data->source;
+ o->client = data->client;
+
+ o->resample_method = data->resample_method;
+ o->sample_spec = data->sample_spec;
+ o->channel_map = data->channel_map;
+
+ o->push = NULL;
+ o->kill = NULL;
+ o->get_latency = NULL;
+ o->detach = NULL;
+ o->attach = NULL;
+ o->suspend = NULL;
+ o->moved = NULL;
+ 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;
+
+ 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);
+
+ pa_log_info("Created output %u \"%s\" on %s with sample spec %s and channel map %s",
+ o->index,
+ o->name,
+ o->source->name,
+ pa_sample_spec_snprint(st, sizeof(st), &o->sample_spec),
+ pa_channel_map_snprint(cm, sizeof(cm), &o->channel_map));
+
+ /* Don't forget to call pa_source_output_put! */
+
+ return o;
+}
+
+static int source_output_set_state(pa_source_output *o, pa_source_output_state_t state) {
+ pa_assert(o);
+
+ if (o->state == state)
+ return 0;
+
+ if (pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_SET_STATE, PA_UINT_TO_PTR(state), 0, NULL) < 0)
+ return -1;
+
+ 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++;
+
+ pa_source_update_status(o->source);
+
+ o->state = state;
+
+ if (state != PA_SOURCE_OUTPUT_UNLINKED)
+ pa_hook_fire(&o->source->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_STATE_CHANGED], o);
+
+ return 0;
+}
+
+void pa_source_output_unlink(pa_source_output*o) {
+ pa_bool_t linked;
+ pa_assert(o);
+
+ /* See pa_sink_unlink() for a couple of comments how this function
+ * works */
+
+ pa_source_output_ref(o);
+
+ linked = PA_SOURCE_OUTPUT_LINKED(o->state);
+
+ if (linked)
+ pa_hook_fire(&o->source->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK], o);
+
+ pa_idxset_remove_by_data(o->source->core->source_outputs, o, NULL);
+ if (pa_idxset_remove_by_data(o->source->outputs, o, NULL))
+ pa_source_output_unref(o);
+
+ if (linked) {
+ pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_REMOVE_OUTPUT, o, 0, NULL);
+ source_output_set_state(o, PA_SOURCE_OUTPUT_UNLINKED);
+ pa_source_update_status(o->source);
+ } else
+ o->state = PA_SOURCE_OUTPUT_UNLINKED;
+
+ o->push = NULL;
+ o->kill = NULL;
+ o->get_latency = NULL;
+ o->attach = NULL;
+ o->detach = NULL;
+ o->suspend = NULL;
+ o->moved = NULL;
+
+ if (linked) {
+ pa_subscription_post(o->source->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_REMOVE, o->index);
+ pa_hook_fire(&o->source->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK_POST], o);
+ }
+
+ o->source = NULL;
+ pa_source_output_unref(o);
+}
+
+static void source_output_free(pa_object* mo) {
+ pa_source_output *o = PA_SOURCE_OUTPUT(mo);
+
+ pa_assert(pa_source_output_refcnt(o) == 0);
+
+ if (PA_SOURCE_OUTPUT_LINKED(o->state))
+ pa_source_output_unlink(o);
+
+ pa_log_info("Freeing output %u \"%s\"", o->index, o->name);
+
+ pa_assert(!o->thread_info.attached);
+
+ if (o->thread_info.resampler)
+ pa_resampler_free(o->thread_info.resampler);
+
+ pa_xfree(o->name);
+ pa_xfree(o->driver);
+ pa_xfree(o);
+}
+
+void pa_source_output_put(pa_source_output *o) {
+ pa_source_output_assert_ref(o);
+
+ pa_assert(o->state == PA_SOURCE_OUTPUT_INIT);
+ pa_assert(o->push);
+
+ o->thread_info.state = o->state = o->flags & PA_SOURCE_OUTPUT_START_CORKED ? PA_SOURCE_OUTPUT_CORKED : PA_SOURCE_OUTPUT_RUNNING;
+
+ if (o->state == PA_SOURCE_OUTPUT_CORKED)
+ o->source->n_corked++;
+
+ pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_ADD_OUTPUT, o, 0, NULL);
+ pa_source_update_status(o->source);
+
+ pa_subscription_post(o->source->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_NEW, o->index);
+
+ pa_hook_fire(&o->source->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PUT], o);
+}
+
+void pa_source_output_kill(pa_source_output*o) {
+ pa_source_output_assert_ref(o);
+ pa_assert(PA_SOURCE_OUTPUT_LINKED(o->state));
+
+ if (o->kill)
+ o->kill(o);
+}
+
+pa_usec_t pa_source_output_get_latency(pa_source_output *o) {
+ pa_usec_t r = 0;
+
+ pa_source_output_assert_ref(o);
+ pa_assert(PA_SOURCE_OUTPUT_LINKED(o->state));
+
+ if (pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_GET_LATENCY, &r, 0, NULL) < 0)
+ r = 0;
+
+ if (o->get_latency)
+ r += o->get_latency(o);
+
+ return r;
+}
+
+/* Called from thread context */
+void pa_source_output_push(pa_source_output *o, const pa_memchunk *chunk) {
+ pa_memchunk rchunk;
+
+ pa_source_output_assert_ref(o);
+ pa_assert(PA_SOURCE_OUTPUT_LINKED(o->thread_info.state));
+ pa_assert(chunk);
+ pa_assert(chunk->length);
+
+ if (!o->push || o->state == PA_SOURCE_OUTPUT_CORKED)
+ return;
+
+ pa_assert(o->state == PA_SOURCE_OUTPUT_RUNNING);
+
+ if (!o->thread_info.resampler) {
+ o->push(o, chunk);
+ return;
+ }
+
+ pa_resampler_run(o->thread_info.resampler, chunk, &rchunk);
+ if (!rchunk.length)
+ return;
+
+ pa_assert(rchunk.memblock);
+ o->push(o, &rchunk);
+ pa_memblock_unref(rchunk.memblock);
+}
+
+void pa_source_output_cork(pa_source_output *o, pa_bool_t b) {
+ pa_source_output_assert_ref(o);
+ pa_assert(PA_SOURCE_OUTPUT_LINKED(o->state));
+
+ source_output_set_state(o, b ? PA_SOURCE_OUTPUT_CORKED : PA_SOURCE_OUTPUT_RUNNING);
+}
+
+int pa_source_output_set_rate(pa_source_output *o, uint32_t rate) {
+ pa_source_output_assert_ref(o);
+ pa_assert(PA_SOURCE_OUTPUT_LINKED(o->state));
+ pa_return_val_if_fail(o->thread_info.resampler, -1);
+
+ 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->source->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index);
+ return 0;
+}
+
+void pa_source_output_set_name(pa_source_output *o, const char *name) {
+ pa_source_output_assert_ref(o);
+
+ if (!o->name && !name)
+ return;
+
+ if (o->name && name && !strcmp(o->name, name))
+ return;
+
+ pa_xfree(o->name);
+ o->name = pa_xstrdup(name);
+
+ if (PA_SOURCE_OUTPUT_LINKED(o->state)) {
+ pa_hook_fire(&o->source->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_NAME_CHANGED], o);
+ pa_subscription_post(o->source->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index);
+ }
+}
+
+pa_resample_method_t pa_source_output_get_resample_method(pa_source_output *o) {
+ pa_source_output_assert_ref(o);
+
+ return o->resample_method;
+}
+
+int pa_source_output_move_to(pa_source_output *o, pa_source *dest) {
+ pa_source *origin;
+ pa_resampler *new_resampler = NULL;
+ pa_source_output_move_hook_data hook_data;
+
+ pa_source_output_assert_ref(o);
+ pa_assert(PA_SOURCE_OUTPUT_LINKED(o->state));
+ pa_source_assert_ref(dest);
+
+ origin = o->source;
+
+ if (dest == origin)
+ return 0;
+
+ if (o->flags & PA_SOURCE_OUTPUT_DONT_MOVE)
+ return -1;
+
+ 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 -1;
+ }
+
+ if (o->thread_info.resampler &&
+ pa_sample_spec_equal(&origin->sample_spec, &dest->sample_spec) &&
+ pa_channel_map_equal(&origin->channel_map, &dest->channel_map))
+
+ /* Try to reuse the old resampler if possible */
+ new_resampler = o->thread_info.resampler;
+
+ else if ((o->flags & PA_SOURCE_OUTPUT_VARIABLE_RATE) ||
+ !pa_sample_spec_equal(&o->sample_spec, &dest->sample_spec) ||
+ !pa_channel_map_equal(&o->channel_map, &dest->channel_map)) {
+
+ /* Okey, we need a new resampler for the new source */
+
+ if (!(new_resampler = pa_resampler_new(
+ dest->core->mempool,
+ &dest->sample_spec, &dest->channel_map,
+ &o->sample_spec, &o->channel_map,
+ o->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)))) {
+ pa_log_warn("Unsupported resampling operation.");
+ return -1;
+ }
+ }
+
+ hook_data.source_output = o;
+ hook_data.destination = dest;
+ pa_hook_fire(&o->source->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE], &hook_data);
+
+ /* Okey, let's move it */
+ pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_REMOVE_OUTPUT, o, 0, NULL);
+
+ pa_idxset_remove_by_data(origin->outputs, o, NULL);
+ pa_idxset_put(dest->outputs, o, NULL);
+ o->source = dest;
+
+ if (pa_source_output_get_state(o) == PA_SOURCE_OUTPUT_CORKED) {
+ pa_assert_se(origin->n_corked-- >= 1);
+ dest->n_corked++;
+ }
+
+ /* Replace resampler */
+ if (new_resampler != o->thread_info.resampler) {
+ if (o->thread_info.resampler)
+ pa_resampler_free(o->thread_info.resampler);
+ o->thread_info.resampler = new_resampler;
+ }
+
+ pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_ADD_OUTPUT, o, 0, NULL);
+
+ pa_source_update_status(origin);
+ pa_source_update_status(dest);
+
+ if (o->moved)
+ o->moved(o);
+
+ pa_hook_fire(&o->source->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_POST], o);
+
+ pa_log_debug("Successfully moved source output %i from %s to %s.", o->index, origin->name, dest->name);
+
+ /* Notify everyone */
+ pa_subscription_post(o->source->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index);
+
+ return 0;
+}
+
+/* Called from thread context */
+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);
+ pa_assert(PA_SOURCE_OUTPUT_LINKED(o->thread_info.state));
+
+ switch (code) {
+
+ 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: {
+ o->thread_info.state = PA_PTR_TO_UINT(userdata);
+
+ return 0;
+ }
+ }
+
+ return -1;
+}
diff --git a/src/pulsecore/source-output.h b/src/pulsecore/source-output.h
new file mode 100644
index 00000000..d6da8d00
--- /dev/null
+++ b/src/pulsecore/source-output.h
@@ -0,0 +1,191 @@
+#ifndef foopulsesourceoutputhfoo
+#define foopulsesourceoutputhfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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 <inttypes.h>
+
+typedef struct pa_source_output pa_source_output;
+
+#include <pulse/sample.h>
+#include <pulsecore/source.h>
+#include <pulsecore/memblockq.h>
+#include <pulsecore/resampler.h>
+#include <pulsecore/module.h>
+#include <pulsecore/client.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 pa_bool_t PA_SOURCE_OUTPUT_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_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 *name, *driver; /* may be NULL */
+ pa_module *module; /* may be NULL */
+ pa_client *client; /* may be NULL */
+
+ pa_source *source;
+
+ pa_sample_spec sample_spec;
+ pa_channel_map channel_map;
+
+ /* Pushes a new memchunk into the output. Called from IO thread
+ * context. */
+ void (*push)(pa_source_output *o, const pa_memchunk *chunk);
+
+ /* If non-NULL this function is called when the output is first
+ * connected to a source. 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 the source this output is attached
+ * to changes. Called from main context */
+ void (*moved) (pa_source_output *o); /* may be NULL */
+
+ /* If non-NULL called whenever the the source this output is attached
+ * to suspends or resumes. Called from main context */
+ void (*suspend) (pa_source_output *o, pa_bool_t b); /* may be NULL */
+
+ /* Supposed to unlink and destroy this stream. Called from main
+ * context. */
+ void (*kill)(pa_source_output* o); /* may be NULL */
+
+ /* Return the current latency (i.e. length of bufferd audio) of
+ this stream. Called from main context. If NULL a
+ PA_SOURCE_OUTPUT_MESSAGE_GET_LATENCY message is sent to the IO
+ thread instead. */
+ pa_usec_t (*get_latency) (pa_source_output *o); /* may be NULL */
+
+ pa_resample_method_t resample_method;
+
+ struct {
+ pa_source_output_state_t state;
+
+ pa_bool_t attached; /* True only between ->attach() and ->detach() calls */
+
+ pa_sample_spec sample_spec;
+
+ pa_resampler* resampler; /* may be NULL */
+ } thread_info;
+
+ void *userdata;
+};
+
+PA_DECLARE_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_MAX
+};
+
+typedef struct pa_source_output_new_data {
+ const char *name, *driver;
+ pa_module *module;
+ pa_client *client;
+
+ pa_source *source;
+
+ pa_sample_spec sample_spec;
+ pa_bool_t sample_spec_is_set;
+ pa_channel_map channel_map;
+ pa_bool_t channel_map_is_set;
+
+ pa_resample_method_t resample_method;
+} pa_source_output_new_data;
+
+typedef struct pa_source_output_move_hook_data {
+ pa_source_output *source_output;
+ pa_source *destination;
+} pa_source_output_move_hook_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);
+void pa_source_output_new_data_set_volume(pa_source_output_new_data *data, const pa_cvolume *volume);
+
+/* To be called by the implementing module only */
+
+pa_source_output* pa_source_output_new(
+ pa_core *core,
+ pa_source_output_new_data *data,
+ pa_source_output_flags_t flags);
+
+void pa_source_output_put(pa_source_output *o);
+void pa_source_output_unlink(pa_source_output*o);
+
+void pa_source_output_set_name(pa_source_output *i, const char *name);
+
+/* Callable by everyone */
+
+/* External code may request disconnection with this funcion */
+void pa_source_output_kill(pa_source_output*o);
+
+pa_usec_t pa_source_output_get_latency(pa_source_output *i);
+
+void pa_source_output_cork(pa_source_output *i, pa_bool_t b);
+
+int pa_source_output_set_rate(pa_source_output *o, uint32_t rate);
+
+pa_resample_method_t pa_source_output_get_resample_method(pa_source_output *o);
+
+int pa_source_output_move_to(pa_source_output *o, pa_source *dest);
+
+#define pa_source_output_get_state(o) ((o)->state)
+
+/* To be used exclusively by the source driver thread */
+
+void pa_source_output_push(pa_source_output *o, const pa_memchunk *chunk);
+int pa_source_output_process_msg(pa_msgobject *mo, int code, void *userdata, int64_t offset, pa_memchunk *chunk);
+
+#endif
diff --git a/src/pulsecore/source.c b/src/pulsecore/source.c
new file mode 100644
index 00000000..d707ad86
--- /dev/null
+++ b/src/pulsecore/source.c
@@ -0,0 +1,594 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <pulse/utf8.h>
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/source-output.h>
+#include <pulsecore/namereg.h>
+#include <pulsecore/core-subscribe.h>
+#include <pulsecore/log.h>
+#include <pulsecore/sample-util.h>
+
+#include "source.h"
+
+static PA_DEFINE_CHECK_TYPE(pa_source, pa_msgobject);
+
+static void source_free(pa_object *o);
+
+pa_source* pa_source_new(
+ pa_core *core,
+ const char *driver,
+ const char *name,
+ int fail,
+ const pa_sample_spec *spec,
+ const pa_channel_map *map) {
+
+ pa_source *s;
+ char st[256];
+ pa_channel_map tmap;
+
+ pa_assert(core);
+ pa_assert(name);
+ pa_assert(spec);
+
+ pa_return_null_if_fail(pa_sample_spec_valid(spec));
+
+ if (!map)
+ pa_return_null_if_fail(map = pa_channel_map_init_auto(&tmap, spec->channels, PA_CHANNEL_MAP_DEFAULT));
+
+ pa_return_null_if_fail(map && pa_channel_map_valid(map));
+ pa_return_null_if_fail(map->channels == spec->channels);
+ pa_return_null_if_fail(!driver || pa_utf8_valid(driver));
+ pa_return_null_if_fail(pa_utf8_valid(name) && *name);
+
+ s = pa_msgobject_new(pa_source);
+
+ if (!(name = pa_namereg_register(core, name, PA_NAMEREG_SOURCE, s, fail))) {
+ pa_xfree(s);
+ 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 = 0;
+ s->name = pa_xstrdup(name);
+ s->description = NULL;
+ s->driver = pa_xstrdup(driver);
+ s->module = NULL;
+
+ s->sample_spec = *spec;
+ s->channel_map = *map;
+
+ s->outputs = pa_idxset_new(NULL, NULL);
+ s->n_corked = 0;
+ s->monitor_of = NULL;
+
+ pa_cvolume_reset(&s->volume, spec->channels);
+ s->muted = FALSE;
+ s->refresh_volume = s->refresh_muted = FALSE;
+
+ s->get_latency = NULL;
+ s->set_volume = NULL;
+ s->get_volume = NULL;
+ s->set_mute = NULL;
+ s->get_mute = NULL;
+ s->set_state = NULL;
+ s->userdata = NULL;
+
+ s->asyncmsgq = NULL;
+ s->rtpoll = NULL;
+
+ pa_assert_se(pa_idxset_put(core->sources, s, &s->index) >= 0);
+
+ pa_sample_spec_snprint(st, sizeof(st), spec);
+ pa_log_info("Created source %u \"%s\" with sample spec \"%s\"", s->index, s->name, st);
+
+ s->thread_info.outputs = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
+ s->thread_info.soft_volume = s->volume;
+ s->thread_info.soft_muted = s->muted;
+ s->thread_info.state = s->state;
+
+ return s;
+}
+
+static int source_set_state(pa_source *s, pa_source_state_t state) {
+ int ret;
+ pa_bool_t suspend_change;
+
+ pa_assert(s);
+
+ if (s->state == state)
+ return 0;
+
+ suspend_change =
+ (s->state == PA_SOURCE_SUSPENDED && PA_SOURCE_OPENED(state)) ||
+ (PA_SOURCE_OPENED(s->state) && state == PA_SOURCE_SUSPENDED);
+
+ if (s->set_state)
+ if ((ret = s->set_state(s, state)) < 0)
+ return -1;
+
+ if (pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_STATE, PA_UINT_TO_PTR(state), 0, NULL) < 0)
+ return -1;
+
+ s->state = state;
+
+ if (suspend_change) {
+ pa_source_output *o;
+ uint32_t idx;
+
+ /* We're suspending or resuming, tell everyone about it */
+
+ for (o = PA_SOURCE_OUTPUT(pa_idxset_first(s->outputs, &idx)); o; o = PA_SOURCE_OUTPUT(pa_idxset_next(s->outputs, &idx)))
+ if (o->suspend)
+ o->suspend(o, state == PA_SINK_SUSPENDED);
+ }
+
+ if (state != PA_SOURCE_UNLINKED) /* if we enter UNLINKED state pa_source_unlink() will fire the apropriate events */
+ pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_STATE_CHANGED], s);
+
+ return 0;
+}
+
+void pa_source_put(pa_source *s) {
+ pa_source_assert_ref(s);
+
+ pa_assert(s->state == PA_SINK_INIT);
+ pa_assert(s->rtpoll);
+ pa_assert(s->asyncmsgq);
+
+ pa_assert_se(source_set_state(s, PA_SOURCE_IDLE) == 0);
+
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE | PA_SUBSCRIPTION_EVENT_NEW, s->index);
+ pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_NEW_POST], s);
+}
+
+void pa_source_unlink(pa_source *s) {
+ pa_bool_t linked;
+ pa_source_output *o, *j = NULL;
+
+ pa_assert(s);
+
+ /* See pa_sink_unlink() for a couple of comments how this function
+ * works. */
+
+ linked = PA_SOURCE_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);
+
+ while ((o = pa_idxset_first(s->outputs, NULL))) {
+ pa_assert(o != j);
+ pa_source_output_kill(o);
+ j = o;
+ }
+
+ if (linked)
+ source_set_state(s, PA_SOURCE_UNLINKED);
+ else
+ s->state = PA_SOURCE_UNLINKED;
+
+ s->get_latency = NULL;
+ s->get_volume = NULL;
+ s->set_volume = NULL;
+ s->set_mute = NULL;
+ s->get_mute = NULL;
+ s->set_state = NULL;
+
+ 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);
+ }
+}
+
+static void source_free(pa_object *o) {
+ pa_source_output *so;
+ pa_source *s = PA_SOURCE(o);
+
+ pa_assert(s);
+ pa_assert(pa_source_refcnt(s) == 0);
+
+ if (PA_SOURCE_LINKED(s->state))
+ pa_source_unlink(s);
+
+ pa_log_info("Freeing source %u \"%s\"", s->index, s->name);
+
+ pa_idxset_free(s->outputs, NULL, NULL);
+
+ while ((so = pa_hashmap_steal_first(s->thread_info.outputs)))
+ pa_source_output_unref(so);
+
+ pa_hashmap_free(s->thread_info.outputs, NULL, NULL);
+
+ pa_xfree(s->name);
+ pa_xfree(s->description);
+ pa_xfree(s->driver);
+ pa_xfree(s);
+}
+
+int pa_source_update_status(pa_source*s) {
+ pa_source_assert_ref(s);
+ pa_assert(PA_SOURCE_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);
+}
+
+int pa_source_suspend(pa_source *s, pa_bool_t suspend) {
+ pa_source_assert_ref(s);
+ pa_assert(PA_SOURCE_LINKED(s->state));
+
+ if (suspend)
+ return source_set_state(s, PA_SOURCE_SUSPENDED);
+ else
+ return source_set_state(s, pa_source_used_by(s) ? PA_SOURCE_RUNNING : PA_SOURCE_IDLE);
+}
+
+void pa_source_ping(pa_source *s) {
+ pa_source_assert_ref(s);
+ pa_assert(PA_SOURCE_LINKED(s->state));
+
+ pa_asyncmsgq_post(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_PING, NULL, 0, NULL, NULL);
+}
+
+void pa_source_post(pa_source*s, const pa_memchunk *chunk) {
+ pa_source_output *o;
+ void *state = NULL;
+
+ pa_source_assert_ref(s);
+ pa_assert(PA_SOURCE_OPENED(s->thread_info.state));
+ pa_assert(chunk);
+
+ if (s->thread_info.state != PA_SOURCE_RUNNING)
+ 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_push(o, &vchunk);
+
+ pa_memblock_unref(vchunk.memblock);
+ } else {
+
+ while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL)))
+ pa_source_output_push(o, chunk);
+ }
+}
+
+pa_usec_t pa_source_get_latency(pa_source *s) {
+ pa_usec_t usec;
+
+ pa_source_assert_ref(s);
+ pa_assert(PA_SOURCE_LINKED(s->state));
+
+ if (!PA_SOURCE_OPENED(s->state))
+ return 0;
+
+ if (s->get_latency)
+ return s->get_latency(s);
+
+ if (pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0)
+ return 0;
+
+ return usec;
+}
+
+void pa_source_set_volume(pa_source *s, const pa_cvolume *volume) {
+ int changed;
+
+ pa_source_assert_ref(s);
+ pa_assert(PA_SOURCE_LINKED(s->state));
+ pa_assert(volume);
+
+ changed = !pa_cvolume_equal(volume, &s->volume);
+ s->volume = *volume;
+
+ if (s->set_volume && s->set_volume(s) < 0)
+ s->set_volume = NULL;
+
+ if (!s->set_volume)
+ pa_asyncmsgq_post(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_VOLUME, pa_xnewdup(struct pa_cvolume, volume, 1), 0, NULL, pa_xfree);
+
+ if (changed)
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+}
+
+const pa_cvolume *pa_source_get_volume(pa_source *s) {
+ pa_cvolume old_volume;
+
+ pa_source_assert_ref(s);
+ pa_assert(PA_SOURCE_LINKED(s->state));
+
+ old_volume = s->volume;
+
+ if (s->get_volume && s->get_volume(s) < 0)
+ s->get_volume = NULL;
+
+ if (!s->get_volume && s->refresh_volume)
+ pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_GET_VOLUME, &s->volume, 0, NULL);
+
+ if (!pa_cvolume_equal(&old_volume, &s->volume))
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+
+ return &s->volume;
+}
+
+void pa_source_set_mute(pa_source *s, pa_bool_t mute) {
+ int changed;
+
+ pa_source_assert_ref(s);
+ pa_assert(PA_SOURCE_LINKED(s->state));
+
+ changed = s->muted != mute;
+ s->muted = mute;
+
+ if (s->set_mute && s->set_mute(s) < 0)
+ s->set_mute = NULL;
+
+ if (!s->set_mute)
+ pa_asyncmsgq_post(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_MUTE, PA_UINT_TO_PTR(mute), 0, NULL, NULL);
+
+ if (changed)
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+}
+
+pa_bool_t pa_source_get_mute(pa_source *s) {
+ pa_bool_t old_muted;
+
+ pa_source_assert_ref(s);
+ pa_assert(PA_SOURCE_LINKED(s->state));
+
+ old_muted = s->muted;
+
+ if (s->get_mute && s->get_mute(s) < 0)
+ s->get_mute = NULL;
+
+ if (!s->get_mute && s->refresh_muted)
+ pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_GET_MUTE, &s->muted, 0, NULL);
+
+ if (old_muted != s->muted)
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+
+ return s->muted;
+}
+
+void pa_source_set_module(pa_source *s, pa_module *m) {
+ pa_source_assert_ref(s);
+
+ if (m == s->module)
+ return;
+
+ s->module = m;
+
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+}
+
+void pa_source_set_description(pa_source *s, const char *description) {
+ pa_source_assert_ref(s);
+
+ if (!description && !s->description)
+ return;
+
+ if (description && s->description && !strcmp(description, s->description))
+ return;
+
+ pa_xfree(s->description);
+ s->description = pa_xstrdup(description);
+
+ if (PA_SOURCE_LINKED(s->state)) {
+ pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_DESCRIPTION_CHANGED], s);
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+ }
+}
+
+void pa_source_set_asyncmsgq(pa_source *s, pa_asyncmsgq *q) {
+ pa_source_assert_ref(s);
+ pa_assert(q);
+
+ s->asyncmsgq = q;
+}
+
+void pa_source_set_rtpoll(pa_source *s, pa_rtpoll *p) {
+ pa_source_assert_ref(s);
+ pa_assert(p);
+
+ s->rtpoll = p;
+}
+
+unsigned pa_source_linked_by(pa_source *s) {
+ pa_source_assert_ref(s);
+ pa_assert(PA_SOURCE_LINKED(s->state));
+
+ return pa_idxset_size(s->outputs);
+}
+
+unsigned pa_source_used_by(pa_source *s) {
+ unsigned ret;
+
+ pa_source_assert_ref(s);
+ pa_assert(PA_SOURCE_LINKED(s->state));
+
+ ret = pa_idxset_size(s->outputs);
+ pa_assert(ret >= s->n_corked);
+
+ return ret - s->n_corked;
+}
+
+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);
+ pa_assert(s->thread_info.state != PA_SOURCE_UNLINKED);
+
+ 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));
+
+ pa_assert(!o->thread_info.attached);
+ o->thread_info.attached = TRUE;
+
+ if (o->attach)
+ o->attach(o);
+
+ return 0;
+ }
+
+ case PA_SOURCE_MESSAGE_REMOVE_OUTPUT: {
+ pa_source_output *o = PA_SOURCE_OUTPUT(userdata);
+
+ if (o->detach)
+ o->detach(o);
+
+ pa_assert(o->thread_info.attached);
+ o->thread_info.attached = FALSE;
+
+ if (pa_hashmap_remove(s->thread_info.outputs, PA_UINT32_TO_PTR(o->index)))
+ pa_source_output_unref(o);
+
+ return 0;
+ }
+
+ case PA_SOURCE_MESSAGE_SET_VOLUME:
+ s->thread_info.soft_volume = *((pa_cvolume*) userdata);
+ return 0;
+
+ case PA_SOURCE_MESSAGE_SET_MUTE:
+ s->thread_info.soft_muted = PA_PTR_TO_UINT(userdata);
+ return 0;
+
+ case PA_SOURCE_MESSAGE_GET_VOLUME:
+ *((pa_cvolume*) userdata) = s->thread_info.soft_volume;
+ return 0;
+
+ case PA_SOURCE_MESSAGE_GET_MUTE:
+ *((pa_bool_t*) userdata) = s->thread_info.soft_muted;
+ return 0;
+
+ case PA_SOURCE_MESSAGE_PING:
+ return 0;
+
+ case PA_SOURCE_MESSAGE_SET_STATE:
+ s->thread_info.state = PA_PTR_TO_UINT(userdata);
+ return 0;
+
+ case PA_SOURCE_MESSAGE_DETACH:
+
+ /* We're detaching all our output streams so that the
+ * asyncmsgq and rtpoll fields can be changed without
+ * problems */
+ pa_source_detach_within_thread(s);
+ break;
+
+ case PA_SOURCE_MESSAGE_ATTACH:
+
+ /* Reattach all streams */
+ pa_source_attach_within_thread(s);
+ break;
+
+ case PA_SOURCE_MESSAGE_GET_LATENCY:
+ case PA_SOURCE_MESSAGE_MAX:
+ ;
+ }
+
+ return -1;
+}
+
+int pa_source_suspend_all(pa_core *c, pa_bool_t suspend) {
+ uint32_t idx;
+ pa_source *source;
+ int ret = 0;
+
+ pa_core_assert_ref(c);
+
+ for (source = PA_SOURCE(pa_idxset_first(c->sources, &idx)); source; source = PA_SOURCE(pa_idxset_next(c->sources, &idx)))
+ ret -= pa_source_suspend(source, suspend) < 0;
+
+ return ret;
+}
+
+void pa_source_detach(pa_source *s) {
+ pa_source_assert_ref(s);
+ pa_assert(PA_SOURCE_LINKED(s->state));
+
+ pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_DETACH, NULL, 0, NULL);
+}
+
+void pa_source_attach(pa_source *s) {
+ pa_source_assert_ref(s);
+ pa_assert(PA_SOURCE_LINKED(s->state));
+
+ pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_ATTACH, NULL, 0, NULL);
+}
+
+void pa_source_detach_within_thread(pa_source *s) {
+ pa_source_output *o;
+ void *state = NULL;
+
+ pa_source_assert_ref(s);
+ pa_assert(PA_SOURCE_LINKED(s->thread_info.state));
+
+ while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL)))
+ if (o->detach)
+ o->detach(o);
+}
+
+void pa_source_attach_within_thread(pa_source *s) {
+ pa_source_output *o;
+ void *state = NULL;
+
+ pa_source_assert_ref(s);
+ pa_assert(PA_SOURCE_LINKED(s->thread_info.state));
+
+ while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL)))
+ if (o->attach)
+ o->attach(o);
+
+}
diff --git a/src/pulsecore/source.h b/src/pulsecore/source.h
new file mode 100644
index 00000000..bd0a9122
--- /dev/null
+++ b/src/pulsecore/source.h
@@ -0,0 +1,179 @@
+#ifndef foopulsesourcehfoo
+#define foopulsesourcehfoo
+
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+typedef struct pa_source pa_source;
+
+#include <inttypes.h>
+
+#include <pulse/sample.h>
+#include <pulse/channelmap.h>
+#include <pulse/volume.h>
+
+#include <pulsecore/core-def.h>
+#include <pulsecore/core.h>
+#include <pulsecore/idxset.h>
+#include <pulsecore/memblock.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>
+
+#define PA_MAX_OUTPUTS_PER_SOURCE 32
+
+typedef enum pa_source_state {
+ PA_SOURCE_INIT,
+ PA_SOURCE_RUNNING,
+ PA_SOURCE_SUSPENDED,
+ PA_SOURCE_IDLE,
+ PA_SOURCE_UNLINKED
+} pa_source_state_t;
+
+static inline pa_bool_t PA_SOURCE_OPENED(pa_source_state_t x) {
+ return x == PA_SOURCE_RUNNING || x == PA_SOURCE_IDLE;
+}
+
+static inline pa_bool_t PA_SOURCE_LINKED(pa_source_state_t x) {
+ return x == PA_SOURCE_RUNNING || x == PA_SOURCE_IDLE || x == PA_SOURCE_SUSPENDED;
+}
+
+struct pa_source {
+ pa_msgobject parent;
+
+ uint32_t index;
+ pa_core *core;
+ pa_source_state_t state;
+ pa_source_flags_t flags;
+
+ char *name;
+ char *description, *driver; /* may be NULL */
+
+ pa_module *module; /* may be NULL */
+
+ pa_sample_spec sample_spec;
+ pa_channel_map channel_map;
+
+ pa_idxset *outputs;
+ unsigned n_corked;
+ pa_sink *monitor_of; /* may be NULL */
+
+ pa_cvolume volume;
+ pa_bool_t muted;
+ pa_bool_t refresh_volume;
+ pa_bool_t refresh_muted;
+
+ int (*set_state)(pa_source*source, pa_source_state_t state); /* may be NULL */
+ int (*set_volume)(pa_source *s); /* dito */
+ int (*get_volume)(pa_source *s); /* dito */
+ int (*set_mute)(pa_source *s); /* dito */
+ int (*get_mute)(pa_source *s); /* dito */
+ pa_usec_t (*get_latency)(pa_source *s); /* dito */
+
+ pa_asyncmsgq *asyncmsgq;
+ pa_rtpoll *rtpoll;
+
+ /* 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_cvolume soft_volume;
+ pa_bool_t soft_muted;
+ } thread_info;
+
+ void *userdata;
+};
+
+PA_DECLARE_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_VOLUME,
+ PA_SOURCE_MESSAGE_GET_MUTE,
+ PA_SOURCE_MESSAGE_SET_MUTE,
+ PA_SOURCE_MESSAGE_GET_LATENCY,
+ PA_SOURCE_MESSAGE_SET_STATE,
+ PA_SOURCE_MESSAGE_PING,
+ PA_SOURCE_MESSAGE_ATTACH,
+ PA_SOURCE_MESSAGE_DETACH,
+ PA_SOURCE_MESSAGE_MAX
+} pa_source_message_t;
+
+/* To be called exclusively by the source driver, from main context */
+
+pa_source* pa_source_new(
+ pa_core *core,
+ const char *driver,
+ const char *name,
+ int namereg_fail,
+ const pa_sample_spec *spec,
+ const pa_channel_map *map);
+
+void pa_source_put(pa_source *s);
+void pa_source_unlink(pa_source *s);
+
+void pa_source_set_module(pa_source *s, pa_module *m);
+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_detach(pa_source *s);
+void pa_source_attach(pa_source *s);
+
+/* May be called by everyone, from main context */
+
+pa_usec_t pa_source_get_latency(pa_source *s);
+
+int pa_source_update_status(pa_source*s);
+int pa_source_suspend(pa_source *s, pa_bool_t suspend);
+int pa_source_suspend_all(pa_core *c, pa_bool_t suspend);
+
+void pa_source_ping(pa_source *s);
+
+void pa_source_set_volume(pa_source *source, const pa_cvolume *volume);
+const pa_cvolume *pa_source_get_volume(pa_source *source);
+void pa_source_set_mute(pa_source *source, pa_bool_t mute);
+pa_bool_t pa_source_get_mute(pa_source *source);
+
+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 */
+#define pa_source_get_state(s) ((pa_source_state_t) (s)->state)
+
+/* To be called exclusively by the source driver, from IO context */
+
+void pa_source_post(pa_source*s, const pa_memchunk *b);
+
+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);
+
+#endif
diff --git a/src/pulsecore/speex/Makefile b/src/pulsecore/speex/Makefile
new file mode 100644
index 00000000..316beb72
--- /dev/null
+++ b/src/pulsecore/speex/Makefile
@@ -0,0 +1,13 @@
+# This is a dirty trick just to ease compilation with emacs
+#
+# This file is not intended to be distributed or anything
+#
+# So: don't touch it, even better ignore it!
+
+all:
+ $(MAKE) -C ../..
+
+clean:
+ $(MAKE) -C ../.. clean
+
+.PHONY: all clean
diff --git a/src/pulsecore/speex/arch.h b/src/pulsecore/speex/arch.h
new file mode 100644
index 00000000..9987c8fb
--- /dev/null
+++ b/src/pulsecore/speex/arch.h
@@ -0,0 +1,241 @@
+/* Copyright (C) 2003 Jean-Marc Valin */
+/**
+ @file arch.h
+ @brief Various architecture definitions Speex
+*/
+/*
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ - Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ - 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.
+
+ - Neither the name of the Xiph.org Foundation 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 THE COPYRIGHT HOLDERS AND 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 THE FOUNDATION OR
+ 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.
+*/
+
+#ifndef ARCH_H
+#define ARCH_H
+
+#ifndef SPEEX_VERSION
+#define SPEEX_MAJOR_VERSION 1 /**< Major Speex version. */
+#define SPEEX_MINOR_VERSION 1 /**< Minor Speex version. */
+#define SPEEX_MICRO_VERSION 15 /**< Micro Speex version. */
+#define SPEEX_EXTRA_VERSION "" /**< Extra Speex version. */
+#define SPEEX_VERSION "speex-1.2beta3" /**< Speex version string. */
+#endif
+
+/* A couple test to catch stupid option combinations */
+#ifdef FIXED_POINT
+
+#ifdef FLOATING_POINT
+#error You cannot compile as floating point and fixed point at the same time
+#endif
+#ifdef _USE_SSE
+#error SSE is only for floating-point
+#endif
+#if ((defined (ARM4_ASM)||defined (ARM4_ASM)) && defined(BFIN_ASM)) || (defined (ARM4_ASM)&&defined(ARM5E_ASM))
+#error Make up your mind. What CPU do you have?
+#endif
+#ifdef VORBIS_PSYCHO
+#error Vorbis-psy model currently not implemented in fixed-point
+#endif
+
+#else
+
+#ifndef FLOATING_POINT
+#error You now need to define either FIXED_POINT or FLOATING_POINT
+#endif
+#if defined (ARM4_ASM) || defined(ARM5E_ASM) || defined(BFIN_ASM)
+#error I suppose you can have a [ARM4/ARM5E/Blackfin] that has float instructions?
+#endif
+#ifdef FIXED_POINT_DEBUG
+#error "Don't you think enabling fixed-point is a good thing to do if you want to debug that?"
+#endif
+
+
+#endif
+
+#ifndef OUTSIDE_SPEEX
+#include "speex/speex_types.h"
+#endif
+
+#define ABS(x) ((x) < 0 ? (-(x)) : (x)) /**< Absolute integer value. */
+#define ABS16(x) ((x) < 0 ? (-(x)) : (x)) /**< Absolute 16-bit value. */
+#define MIN16(a,b) ((a) < (b) ? (a) : (b)) /**< Maximum 16-bit value. */
+#define MAX16(a,b) ((a) > (b) ? (a) : (b)) /**< Maximum 16-bit value. */
+#define ABS32(x) ((x) < 0 ? (-(x)) : (x)) /**< Absolute 32-bit value. */
+#define MIN32(a,b) ((a) < (b) ? (a) : (b)) /**< Maximum 32-bit value. */
+#define MAX32(a,b) ((a) > (b) ? (a) : (b)) /**< Maximum 32-bit value. */
+
+#ifdef FIXED_POINT
+
+typedef spx_int16_t spx_word16_t;
+typedef spx_int32_t spx_word32_t;
+typedef spx_word32_t spx_mem_t;
+typedef spx_word16_t spx_coef_t;
+typedef spx_word16_t spx_lsp_t;
+typedef spx_word32_t spx_sig_t;
+
+#define Q15ONE 32767
+
+#define LPC_SCALING 8192
+#define SIG_SCALING 16384
+#define LSP_SCALING 8192.
+#define GAMMA_SCALING 32768.
+#define GAIN_SCALING 64
+#define GAIN_SCALING_1 0.015625
+
+#define LPC_SHIFT 13
+#define LSP_SHIFT 13
+#define SIG_SHIFT 14
+#define GAIN_SHIFT 6
+
+#define VERY_SMALL 0
+#define VERY_LARGE32 ((spx_word32_t)2147483647)
+#define VERY_LARGE16 ((spx_word16_t)32767)
+#define Q15_ONE ((spx_word16_t)32767)
+
+
+#ifdef FIXED_DEBUG
+#include "fixed_debug.h"
+#else
+
+#include "fixed_generic.h"
+
+#ifdef ARM5E_ASM
+#include "fixed_arm5e.h"
+#elif defined (ARM4_ASM)
+#include "fixed_arm4.h"
+#elif defined (ARM5E_ASM)
+#include "fixed_arm5e.h"
+#elif defined (BFIN_ASM)
+#include "fixed_bfin.h"
+#endif
+
+#endif
+
+
+#else
+
+typedef float spx_mem_t;
+typedef float spx_coef_t;
+typedef float spx_lsp_t;
+typedef float spx_sig_t;
+typedef float spx_word16_t;
+typedef float spx_word32_t;
+
+#define Q15ONE 1.0f
+#define LPC_SCALING 1.f
+#define SIG_SCALING 1.f
+#define LSP_SCALING 1.f
+#define GAMMA_SCALING 1.f
+#define GAIN_SCALING 1.f
+#define GAIN_SCALING_1 1.f
+
+
+#define VERY_SMALL 1e-15f
+#define VERY_LARGE32 1e15f
+#define VERY_LARGE16 1e15f
+#define Q15_ONE ((spx_word16_t)1.f)
+
+#define QCONST16(x,bits) (x)
+#define QCONST32(x,bits) (x)
+
+#define NEG16(x) (-(x))
+#define NEG32(x) (-(x))
+#define EXTRACT16(x) (x)
+#define EXTEND32(x) (x)
+#define SHR16(a,shift) (a)
+#define SHL16(a,shift) (a)
+#define SHR32(a,shift) (a)
+#define SHL32(a,shift) (a)
+#define PSHR16(a,shift) (a)
+#define PSHR32(a,shift) (a)
+#define VSHR32(a,shift) (a)
+#define SATURATE16(x,a) (x)
+#define SATURATE32(x,a) (x)
+
+#define PSHR(a,shift) (a)
+#define SHR(a,shift) (a)
+#define SHL(a,shift) (a)
+#define SATURATE(x,a) (x)
+
+#define ADD16(a,b) ((a)+(b))
+#define SUB16(a,b) ((a)-(b))
+#define ADD32(a,b) ((a)+(b))
+#define SUB32(a,b) ((a)-(b))
+#define MULT16_16_16(a,b) ((a)*(b))
+#define MULT16_16(a,b) ((spx_word32_t)(a)*(spx_word32_t)(b))
+#define MAC16_16(c,a,b) ((c)+(spx_word32_t)(a)*(spx_word32_t)(b))
+
+#define MULT16_32_Q11(a,b) ((a)*(b))
+#define MULT16_32_Q13(a,b) ((a)*(b))
+#define MULT16_32_Q14(a,b) ((a)*(b))
+#define MULT16_32_Q15(a,b) ((a)*(b))
+#define MULT16_32_P15(a,b) ((a)*(b))
+
+#define MAC16_32_Q11(c,a,b) ((c)+(a)*(b))
+#define MAC16_32_Q15(c,a,b) ((c)+(a)*(b))
+
+#define MAC16_16_Q11(c,a,b) ((c)+(a)*(b))
+#define MAC16_16_Q13(c,a,b) ((c)+(a)*(b))
+#define MAC16_16_P13(c,a,b) ((c)+(a)*(b))
+#define MULT16_16_Q11_32(a,b) ((a)*(b))
+#define MULT16_16_Q13(a,b) ((a)*(b))
+#define MULT16_16_Q14(a,b) ((a)*(b))
+#define MULT16_16_Q15(a,b) ((a)*(b))
+#define MULT16_16_P15(a,b) ((a)*(b))
+#define MULT16_16_P13(a,b) ((a)*(b))
+#define MULT16_16_P14(a,b) ((a)*(b))
+
+#define DIV32_16(a,b) (((spx_word32_t)(a))/(spx_word16_t)(b))
+#define PDIV32_16(a,b) (((spx_word32_t)(a))/(spx_word16_t)(b))
+#define DIV32(a,b) (((spx_word32_t)(a))/(spx_word32_t)(b))
+#define PDIV32(a,b) (((spx_word32_t)(a))/(spx_word32_t)(b))
+
+
+#endif
+
+
+#if defined (CONFIG_TI_C54X) || defined (CONFIG_TI_C55X)
+
+/* 2 on TI C5x DSP */
+#define BYTES_PER_CHAR 2
+#define BITS_PER_CHAR 16
+#define LOG2_BITS_PER_CHAR 4
+
+#else
+
+#define BYTES_PER_CHAR 1
+#define BITS_PER_CHAR 8
+#define LOG2_BITS_PER_CHAR 3
+
+#endif
+
+
+
+#ifdef FIXED_DEBUG
+long long spx_mips=0;
+#endif
+
+
+#endif
diff --git a/src/pulsecore/speex/fixed_generic.h b/src/pulsecore/speex/fixed_generic.h
new file mode 100644
index 00000000..547e22c7
--- /dev/null
+++ b/src/pulsecore/speex/fixed_generic.h
@@ -0,0 +1,106 @@
+/* Copyright (C) 2003 Jean-Marc Valin */
+/**
+ @file fixed_generic.h
+ @brief Generic fixed-point operations
+*/
+/*
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ - Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ - 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.
+
+ - Neither the name of the Xiph.org Foundation 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 THE COPYRIGHT HOLDERS AND 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 THE FOUNDATION OR
+ 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.
+*/
+
+#ifndef FIXED_GENERIC_H
+#define FIXED_GENERIC_H
+
+#define QCONST16(x,bits) ((spx_word16_t)(.5+(x)*(((spx_word32_t)1)<<(bits))))
+#define QCONST32(x,bits) ((spx_word32_t)(.5+(x)*(((spx_word32_t)1)<<(bits))))
+
+#define NEG16(x) (-(x))
+#define NEG32(x) (-(x))
+#define EXTRACT16(x) ((spx_word16_t)(x))
+#define EXTEND32(x) ((spx_word32_t)(x))
+#define SHR16(a,shift) ((a) >> (shift))
+#define SHL16(a,shift) ((a) << (shift))
+#define SHR32(a,shift) ((a) >> (shift))
+#define SHL32(a,shift) ((a) << (shift))
+#define PSHR16(a,shift) (SHR16((a)+((1<<((shift))>>1)),shift))
+#define PSHR32(a,shift) (SHR32((a)+((1<<((shift))>>1)),shift))
+#define VSHR32(a, shift) (((shift)>0) ? SHR32(a, shift) : SHL32(a, -(shift)))
+#define SATURATE16(x,a) (((x)>(a) ? (a) : (x)<-(a) ? -(a) : (x)))
+#define SATURATE32(x,a) (((x)>(a) ? (a) : (x)<-(a) ? -(a) : (x)))
+
+#define SHR(a,shift) ((a) >> (shift))
+#define SHL(a,shift) ((spx_word32_t)(a) << (shift))
+#define PSHR(a,shift) (SHR((a)+((1<<((shift))>>1)),shift))
+#define SATURATE(x,a) (((x)>(a) ? (a) : (x)<-(a) ? -(a) : (x)))
+
+
+#define ADD16(a,b) ((spx_word16_t)((spx_word16_t)(a)+(spx_word16_t)(b)))
+#define SUB16(a,b) ((spx_word16_t)(a)-(spx_word16_t)(b))
+#define ADD32(a,b) ((spx_word32_t)(a)+(spx_word32_t)(b))
+#define SUB32(a,b) ((spx_word32_t)(a)-(spx_word32_t)(b))
+
+
+/* result fits in 16 bits */
+#define MULT16_16_16(a,b) ((((spx_word16_t)(a))*((spx_word16_t)(b))))
+
+/* (spx_word32_t)(spx_word16_t) gives TI compiler a hint that it's 16x16->32 multiply */
+#define MULT16_16(a,b) (((spx_word32_t)(spx_word16_t)(a))*((spx_word32_t)(spx_word16_t)(b)))
+
+#define MAC16_16(c,a,b) (ADD32((c),MULT16_16((a),(b))))
+#define MULT16_32_Q12(a,b) ADD32(MULT16_16((a),SHR((b),12)), SHR(MULT16_16((a),((b)&0x00000fff)),12))
+#define MULT16_32_Q13(a,b) ADD32(MULT16_16((a),SHR((b),13)), SHR(MULT16_16((a),((b)&0x00001fff)),13))
+#define MULT16_32_Q14(a,b) ADD32(MULT16_16((a),SHR((b),14)), SHR(MULT16_16((a),((b)&0x00003fff)),14))
+
+#define MULT16_32_Q11(a,b) ADD32(MULT16_16((a),SHR((b),11)), SHR(MULT16_16((a),((b)&0x000007ff)),11))
+#define MAC16_32_Q11(c,a,b) ADD32(c,ADD32(MULT16_16((a),SHR((b),11)), SHR(MULT16_16((a),((b)&0x000007ff)),11)))
+
+#define MULT16_32_P15(a,b) ADD32(MULT16_16((a),SHR((b),15)), PSHR(MULT16_16((a),((b)&0x00007fff)),15))
+#define MULT16_32_Q15(a,b) ADD32(MULT16_16((a),SHR((b),15)), SHR(MULT16_16((a),((b)&0x00007fff)),15))
+#define MAC16_32_Q15(c,a,b) ADD32(c,ADD32(MULT16_16((a),SHR((b),15)), SHR(MULT16_16((a),((b)&0x00007fff)),15)))
+
+
+#define MAC16_16_Q11(c,a,b) (ADD32((c),SHR(MULT16_16((a),(b)),11)))
+#define MAC16_16_Q13(c,a,b) (ADD32((c),SHR(MULT16_16((a),(b)),13)))
+#define MAC16_16_P13(c,a,b) (ADD32((c),SHR(ADD32(4096,MULT16_16((a),(b))),13)))
+
+#define MULT16_16_Q11_32(a,b) (SHR(MULT16_16((a),(b)),11))
+#define MULT16_16_Q13(a,b) (SHR(MULT16_16((a),(b)),13))
+#define MULT16_16_Q14(a,b) (SHR(MULT16_16((a),(b)),14))
+#define MULT16_16_Q15(a,b) (SHR(MULT16_16((a),(b)),15))
+
+#define MULT16_16_P13(a,b) (SHR(ADD32(4096,MULT16_16((a),(b))),13))
+#define MULT16_16_P14(a,b) (SHR(ADD32(8192,MULT16_16((a),(b))),14))
+#define MULT16_16_P15(a,b) (SHR(ADD32(16384,MULT16_16((a),(b))),15))
+
+#define MUL_16_32_R15(a,bh,bl) ADD32(MULT16_16((a),(bh)), SHR(MULT16_16((a),(bl)),15))
+
+#define DIV32_16(a,b) ((spx_word16_t)(((spx_word32_t)(a))/((spx_word16_t)(b))))
+#define PDIV32_16(a,b) ((spx_word16_t)(((spx_word32_t)(a)+((spx_word16_t)(b)>>1))/((spx_word16_t)(b))))
+#define DIV32(a,b) (((spx_word32_t)(a))/((spx_word32_t)(b)))
+#define PDIV32(a,b) (((spx_word32_t)(a)+((spx_word16_t)(b)>>1))/((spx_word32_t)(b)))
+
+#endif
diff --git a/src/pulsecore/speex/resample.c b/src/pulsecore/speex/resample.c
new file mode 100644
index 00000000..1e592002
--- /dev/null
+++ b/src/pulsecore/speex/resample.c
@@ -0,0 +1,1121 @@
+/* Copyright (C) 2007 Jean-Marc Valin
+
+ File: resample.c
+ Arbitrary resampling code
+
+ 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. The name of the author may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 THE AUTHOR 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.
+*/
+
+/*
+ The design goals of this code are:
+ - Very fast algorithm
+ - SIMD-friendly algorithm
+ - Low memory requirement
+ - Good *perceptual* quality (and not best SNR)
+
+ Warning: This resampler is relatively new. Although I think I got rid of
+ all the major bugs and I don't expect the API to change anymore, there
+ may be something I've missed. So use with caution.
+
+ This algorithm is based on this original resampling algorithm:
+ Smith, Julius O. Digital Audio Resampling Home Page
+ Center for Computer Research in Music and Acoustics (CCRMA),
+ Stanford University, 2007.
+ Web published at http://www-ccrma.stanford.edu/~jos/resample/.
+
+ There is one main difference, though. This resampler uses cubic
+ interpolation instead of linear interpolation in the above paper. This
+ makes the table much smaller and makes it possible to compute that table
+ on a per-stream basis. In turn, being able to tweak the table for each
+ stream makes it possible to both reduce complexity on simple ratios
+ (e.g. 2/3), and get rid of the rounding operations in the inner loop.
+ The latter both reduces CPU time and makes the algorithm more SIMD-friendly.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef OUTSIDE_SPEEX
+#include <stdlib.h>
+static void *speex_alloc (int size) {return calloc(size,1);}
+static void *speex_realloc (void *ptr, int size) {return realloc(ptr, size);}
+static void speex_free (void *ptr) {free(ptr);}
+#include "speex_resampler.h"
+#include "arch.h"
+#else /* OUTSIDE_SPEEX */
+
+#include "speex/speex_resampler.h"
+#include "arch.h"
+#include "os_support.h"
+#endif /* OUTSIDE_SPEEX */
+
+#include <math.h>
+
+#ifndef M_PI
+#define M_PI 3.14159263
+#endif
+
+#ifdef FIXED_POINT
+#define WORD2INT(x) ((x) < -32767 ? -32768 : ((x) > 32766 ? 32767 : (x)))
+#else
+#define WORD2INT(x) ((x) < -32767.5f ? -32768 : ((x) > 32766.5f ? 32767 : floor(.5+(x))))
+#endif
+
+/*#define float double*/
+#define FILTER_SIZE 64
+#define OVERSAMPLE 8
+
+#define IMAX(a,b) ((a) > (b) ? (a) : (b))
+#define IMIN(a,b) ((a) < (b) ? (a) : (b))
+
+#ifndef NULL
+#define NULL 0
+#endif
+
+typedef int (*resampler_basic_func)(SpeexResamplerState *, spx_uint32_t , const spx_word16_t *, spx_uint32_t *, spx_word16_t *, spx_uint32_t *);
+
+struct SpeexResamplerState_ {
+ spx_uint32_t in_rate;
+ spx_uint32_t out_rate;
+ spx_uint32_t num_rate;
+ spx_uint32_t den_rate;
+
+ int quality;
+ spx_uint32_t nb_channels;
+ spx_uint32_t filt_len;
+ spx_uint32_t mem_alloc_size;
+ int int_advance;
+ int frac_advance;
+ float cutoff;
+ spx_uint32_t oversample;
+ int initialised;
+ int started;
+
+ /* These are per-channel */
+ spx_int32_t *last_sample;
+ spx_uint32_t *samp_frac_num;
+ spx_uint32_t *magic_samples;
+
+ spx_word16_t *mem;
+ spx_word16_t *sinc_table;
+ spx_uint32_t sinc_table_length;
+ resampler_basic_func resampler_ptr;
+
+ int in_stride;
+ int out_stride;
+} ;
+
+static double kaiser12_table[68] = {
+ 0.99859849, 1.00000000, 0.99859849, 0.99440475, 0.98745105, 0.97779076,
+ 0.96549770, 0.95066529, 0.93340547, 0.91384741, 0.89213598, 0.86843014,
+ 0.84290116, 0.81573067, 0.78710866, 0.75723148, 0.72629970, 0.69451601,
+ 0.66208321, 0.62920216, 0.59606986, 0.56287762, 0.52980938, 0.49704014,
+ 0.46473455, 0.43304576, 0.40211431, 0.37206735, 0.34301800, 0.31506490,
+ 0.28829195, 0.26276832, 0.23854851, 0.21567274, 0.19416736, 0.17404546,
+ 0.15530766, 0.13794294, 0.12192957, 0.10723616, 0.09382272, 0.08164178,
+ 0.07063950, 0.06075685, 0.05193064, 0.04409466, 0.03718069, 0.03111947,
+ 0.02584161, 0.02127838, 0.01736250, 0.01402878, 0.01121463, 0.00886058,
+ 0.00691064, 0.00531256, 0.00401805, 0.00298291, 0.00216702, 0.00153438,
+ 0.00105297, 0.00069463, 0.00043489, 0.00025272, 0.00013031, 0.0000527734,
+ 0.00001000, 0.00000000};
+/*
+static double kaiser12_table[36] = {
+ 0.99440475, 1.00000000, 0.99440475, 0.97779076, 0.95066529, 0.91384741,
+ 0.86843014, 0.81573067, 0.75723148, 0.69451601, 0.62920216, 0.56287762,
+ 0.49704014, 0.43304576, 0.37206735, 0.31506490, 0.26276832, 0.21567274,
+ 0.17404546, 0.13794294, 0.10723616, 0.08164178, 0.06075685, 0.04409466,
+ 0.03111947, 0.02127838, 0.01402878, 0.00886058, 0.00531256, 0.00298291,
+ 0.00153438, 0.00069463, 0.00025272, 0.0000527734, 0.00000500, 0.00000000};
+*/
+static double kaiser10_table[36] = {
+ 0.99537781, 1.00000000, 0.99537781, 0.98162644, 0.95908712, 0.92831446,
+ 0.89005583, 0.84522401, 0.79486424, 0.74011713, 0.68217934, 0.62226347,
+ 0.56155915, 0.50119680, 0.44221549, 0.38553619, 0.33194107, 0.28205962,
+ 0.23636152, 0.19515633, 0.15859932, 0.12670280, 0.09935205, 0.07632451,
+ 0.05731132, 0.04193980, 0.02979584, 0.02044510, 0.01345224, 0.00839739,
+ 0.00488951, 0.00257636, 0.00115101, 0.00035515, 0.00000000, 0.00000000};
+
+static double kaiser8_table[36] = {
+ 0.99635258, 1.00000000, 0.99635258, 0.98548012, 0.96759014, 0.94302200,
+ 0.91223751, 0.87580811, 0.83439927, 0.78875245, 0.73966538, 0.68797126,
+ 0.63451750, 0.58014482, 0.52566725, 0.47185369, 0.41941150, 0.36897272,
+ 0.32108304, 0.27619388, 0.23465776, 0.19672670, 0.16255380, 0.13219758,
+ 0.10562887, 0.08273982, 0.06335451, 0.04724088, 0.03412321, 0.02369490,
+ 0.01563093, 0.00959968, 0.00527363, 0.00233883, 0.00050000, 0.00000000};
+
+static double kaiser6_table[36] = {
+ 0.99733006, 1.00000000, 0.99733006, 0.98935595, 0.97618418, 0.95799003,
+ 0.93501423, 0.90755855, 0.87598009, 0.84068475, 0.80211977, 0.76076565,
+ 0.71712752, 0.67172623, 0.62508937, 0.57774224, 0.53019925, 0.48295561,
+ 0.43647969, 0.39120616, 0.34752997, 0.30580127, 0.26632152, 0.22934058,
+ 0.19505503, 0.16360756, 0.13508755, 0.10953262, 0.08693120, 0.06722600,
+ 0.05031820, 0.03607231, 0.02432151, 0.01487334, 0.00752000, 0.00000000};
+
+struct FuncDef {
+ double *table;
+ int oversample;
+};
+
+static struct FuncDef _KAISER12 = {kaiser12_table, 64};
+#define KAISER12 (&_KAISER12)
+/*static struct FuncDef _KAISER12 = {kaiser12_table, 32};
+#define KAISER12 (&_KAISER12)*/
+static struct FuncDef _KAISER10 = {kaiser10_table, 32};
+#define KAISER10 (&_KAISER10)
+static struct FuncDef _KAISER8 = {kaiser8_table, 32};
+#define KAISER8 (&_KAISER8)
+static struct FuncDef _KAISER6 = {kaiser6_table, 32};
+#define KAISER6 (&_KAISER6)
+
+struct QualityMapping {
+ int base_length;
+ int oversample;
+ float downsample_bandwidth;
+ float upsample_bandwidth;
+ struct FuncDef *window_func;
+};
+
+
+/* This table maps conversion quality to internal parameters. There are two
+ reasons that explain why the up-sampling bandwidth is larger than the
+ down-sampling bandwidth:
+ 1) When up-sampling, we can assume that the spectrum is already attenuated
+ close to the Nyquist rate (from an A/D or a previous resampling filter)
+ 2) Any aliasing that occurs very close to the Nyquist rate will be masked
+ by the sinusoids/noise just below the Nyquist rate (guaranteed only for
+ up-sampling).
+*/
+static const struct QualityMapping quality_map[11] = {
+ { 8, 4, 0.830f, 0.860f, KAISER6 }, /* Q0 */
+ { 16, 4, 0.850f, 0.880f, KAISER6 }, /* Q1 */
+ { 32, 4, 0.882f, 0.910f, KAISER6 }, /* Q2 */ /* 82.3% cutoff ( ~60 dB stop) 6 */
+ { 48, 8, 0.895f, 0.917f, KAISER8 }, /* Q3 */ /* 84.9% cutoff ( ~80 dB stop) 8 */
+ { 64, 8, 0.921f, 0.940f, KAISER8 }, /* Q4 */ /* 88.7% cutoff ( ~80 dB stop) 8 */
+ { 80, 16, 0.922f, 0.940f, KAISER10}, /* Q5 */ /* 89.1% cutoff (~100 dB stop) 10 */
+ { 96, 16, 0.940f, 0.945f, KAISER10}, /* Q6 */ /* 91.5% cutoff (~100 dB stop) 10 */
+ {128, 16, 0.950f, 0.950f, KAISER10}, /* Q7 */ /* 93.1% cutoff (~100 dB stop) 10 */
+ {160, 16, 0.960f, 0.960f, KAISER10}, /* Q8 */ /* 94.5% cutoff (~100 dB stop) 10 */
+ {192, 32, 0.968f, 0.968f, KAISER12}, /* Q9 */ /* 95.5% cutoff (~100 dB stop) 10 */
+ {256, 32, 0.975f, 0.975f, KAISER12}, /* Q10 */ /* 96.6% cutoff (~100 dB stop) 10 */
+};
+/*8,24,40,56,80,104,128,160,200,256,320*/
+static double compute_func(float x, struct FuncDef *func)
+{
+ float y, frac;
+ double interp[4];
+ int ind;
+ y = x*func->oversample;
+ ind = (int)floor(y);
+ frac = (y-ind);
+ /* CSE with handle the repeated powers */
+ interp[3] = -0.1666666667*frac + 0.1666666667*(frac*frac*frac);
+ interp[2] = frac + 0.5*(frac*frac) - 0.5*(frac*frac*frac);
+ /*interp[2] = 1.f - 0.5f*frac - frac*frac + 0.5f*frac*frac*frac;*/
+ interp[0] = -0.3333333333*frac + 0.5*(frac*frac) - 0.1666666667*(frac*frac*frac);
+ /* Just to make sure we don't have rounding problems */
+ interp[1] = 1.f-interp[3]-interp[2]-interp[0];
+
+ /*sum = frac*accum[1] + (1-frac)*accum[2];*/
+ return interp[0]*func->table[ind] + interp[1]*func->table[ind+1] + interp[2]*func->table[ind+2] + interp[3]*func->table[ind+3];
+}
+
+#if 0
+#include <stdio.h>
+int main(int argc, char **argv)
+{
+ int i;
+ for (i=0;i<256;i++)
+ {
+ printf ("%f\n", compute_func(i/256., KAISER12));
+ }
+ return 0;
+}
+#endif
+
+#ifdef FIXED_POINT
+/* The slow way of computing a sinc for the table. Should improve that some day */
+static spx_word16_t sinc(float cutoff, float x, int N, struct FuncDef *window_func)
+{
+ /*fprintf (stderr, "%f ", x);*/
+ float xx = x * cutoff;
+ if (fabs(x)<1e-6f)
+ return WORD2INT(32768.*cutoff);
+ else if (fabs(x) > .5f*N)
+ return 0;
+ /*FIXME: Can it really be any slower than this? */
+ return WORD2INT(32768.*cutoff*sin(M_PI*xx)/(M_PI*xx) * compute_func(fabs(2.*x/N), window_func));
+}
+#else
+/* The slow way of computing a sinc for the table. Should improve that some day */
+static spx_word16_t sinc(float cutoff, float x, int N, struct FuncDef *window_func)
+{
+ /*fprintf (stderr, "%f ", x);*/
+ float xx = x * cutoff;
+ if (fabs(x)<1e-6)
+ return cutoff;
+ else if (fabs(x) > .5*N)
+ return 0;
+ /*FIXME: Can it really be any slower than this? */
+ return cutoff*sin(M_PI*xx)/(M_PI*xx) * compute_func(fabs(2.*x/N), window_func);
+}
+#endif
+
+#ifdef FIXED_POINT
+static void cubic_coef(spx_word16_t x, spx_word16_t interp[4])
+{
+ /* Compute interpolation coefficients. I'm not sure whether this corresponds to cubic interpolation
+ but I know it's MMSE-optimal on a sinc */
+ spx_word16_t x2, x3;
+ x2 = MULT16_16_P15(x, x);
+ x3 = MULT16_16_P15(x, x2);
+ interp[0] = PSHR32(MULT16_16(QCONST16(-0.16667f, 15),x) + MULT16_16(QCONST16(0.16667f, 15),x3),15);
+ interp[1] = EXTRACT16(EXTEND32(x) + SHR32(SUB32(EXTEND32(x2),EXTEND32(x3)),1));
+ interp[3] = PSHR32(MULT16_16(QCONST16(-0.33333f, 15),x) + MULT16_16(QCONST16(.5f,15),x2) - MULT16_16(QCONST16(0.16667f, 15),x3),15);
+ /* Just to make sure we don't have rounding problems */
+ interp[2] = Q15_ONE-interp[0]-interp[1]-interp[3];
+ if (interp[2]<32767)
+ interp[2]+=1;
+}
+#else
+static void cubic_coef(spx_word16_t frac, spx_word16_t interp[4])
+{
+ /* Compute interpolation coefficients. I'm not sure whether this corresponds to cubic interpolation
+ but I know it's MMSE-optimal on a sinc */
+ interp[0] = -0.16667f*frac + 0.16667f*frac*frac*frac;
+ interp[1] = frac + 0.5f*frac*frac - 0.5f*frac*frac*frac;
+ /*interp[2] = 1.f - 0.5f*frac - frac*frac + 0.5f*frac*frac*frac;*/
+ interp[3] = -0.33333f*frac + 0.5f*frac*frac - 0.16667f*frac*frac*frac;
+ /* Just to make sure we don't have rounding problems */
+ interp[2] = 1.-interp[0]-interp[1]-interp[3];
+}
+#endif
+
+static int resampler_basic_direct_single(SpeexResamplerState *st, spx_uint32_t channel_index, const spx_word16_t *in, spx_uint32_t *in_len, spx_word16_t *out, spx_uint32_t *out_len)
+{
+ int N = st->filt_len;
+ int out_sample = 0;
+ spx_word16_t *mem;
+ int last_sample = st->last_sample[channel_index];
+ spx_uint32_t samp_frac_num = st->samp_frac_num[channel_index];
+ mem = st->mem + channel_index * st->mem_alloc_size;
+ while (!(last_sample >= (spx_int32_t)*in_len || out_sample >= (spx_int32_t)*out_len))
+ {
+ int j;
+ spx_word32_t sum=0;
+
+ /* We already have all the filter coefficients pre-computed in the table */
+ const spx_word16_t *ptr;
+ /* Do the memory part */
+ for (j=0;last_sample-N+1+j < 0;j++)
+ {
+ sum += MULT16_16(mem[last_sample+j],st->sinc_table[samp_frac_num*st->filt_len+j]);
+ }
+
+ /* Do the new part */
+ ptr = in+st->in_stride*(last_sample-N+1+j);
+ for (;j<N;j++)
+ {
+ sum += MULT16_16(*ptr,st->sinc_table[samp_frac_num*st->filt_len+j]);
+ ptr += st->in_stride;
+ }
+
+ *out = PSHR32(sum,15);
+ out += st->out_stride;
+ out_sample++;
+ last_sample += st->int_advance;
+ samp_frac_num += st->frac_advance;
+ if (samp_frac_num >= st->den_rate)
+ {
+ samp_frac_num -= st->den_rate;
+ last_sample++;
+ }
+ }
+ st->last_sample[channel_index] = last_sample;
+ st->samp_frac_num[channel_index] = samp_frac_num;
+ return out_sample;
+}
+
+#ifdef FIXED_POINT
+#else
+/* This is the same as the previous function, except with a double-precision accumulator */
+static int resampler_basic_direct_double(SpeexResamplerState *st, spx_uint32_t channel_index, const spx_word16_t *in, spx_uint32_t *in_len, spx_word16_t *out, spx_uint32_t *out_len)
+{
+ int N = st->filt_len;
+ int out_sample = 0;
+ spx_word16_t *mem;
+ int last_sample = st->last_sample[channel_index];
+ spx_uint32_t samp_frac_num = st->samp_frac_num[channel_index];
+ mem = st->mem + channel_index * st->mem_alloc_size;
+ while (!(last_sample >= (spx_int32_t)*in_len || out_sample >= (spx_int32_t)*out_len))
+ {
+ int j;
+ double sum=0;
+
+ /* We already have all the filter coefficients pre-computed in the table */
+ const spx_word16_t *ptr;
+ /* Do the memory part */
+ for (j=0;last_sample-N+1+j < 0;j++)
+ {
+ sum += MULT16_16(mem[last_sample+j],(double)st->sinc_table[samp_frac_num*st->filt_len+j]);
+ }
+
+ /* Do the new part */
+ ptr = in+st->in_stride*(last_sample-N+1+j);
+ for (;j<N;j++)
+ {
+ sum += MULT16_16(*ptr,(double)st->sinc_table[samp_frac_num*st->filt_len+j]);
+ ptr += st->in_stride;
+ }
+
+ *out = sum;
+ out += st->out_stride;
+ out_sample++;
+ last_sample += st->int_advance;
+ samp_frac_num += st->frac_advance;
+ if (samp_frac_num >= st->den_rate)
+ {
+ samp_frac_num -= st->den_rate;
+ last_sample++;
+ }
+ }
+ st->last_sample[channel_index] = last_sample;
+ st->samp_frac_num[channel_index] = samp_frac_num;
+ return out_sample;
+}
+#endif
+
+static int resampler_basic_interpolate_single(SpeexResamplerState *st, spx_uint32_t channel_index, const spx_word16_t *in, spx_uint32_t *in_len, spx_word16_t *out, spx_uint32_t *out_len)
+{
+ int N = st->filt_len;
+ int out_sample = 0;
+ spx_word16_t *mem;
+ int last_sample = st->last_sample[channel_index];
+ spx_uint32_t samp_frac_num = st->samp_frac_num[channel_index];
+ mem = st->mem + channel_index * st->mem_alloc_size;
+ while (!(last_sample >= (spx_int32_t)*in_len || out_sample >= (spx_int32_t)*out_len))
+ {
+ int j;
+ spx_word32_t sum=0;
+
+ /* We need to interpolate the sinc filter */
+ spx_word32_t accum[4] = {0.f,0.f, 0.f, 0.f};
+ spx_word16_t interp[4];
+ const spx_word16_t *ptr;
+ int offset;
+ spx_word16_t frac;
+ offset = samp_frac_num*st->oversample/st->den_rate;
+#ifdef FIXED_POINT
+ frac = PDIV32(SHL32((samp_frac_num*st->oversample) % st->den_rate,15),st->den_rate);
+#else
+ frac = ((float)((samp_frac_num*st->oversample) % st->den_rate))/st->den_rate;
+#endif
+ /* This code is written like this to make it easy to optimise with SIMD.
+ For most DSPs, it would be best to split the loops in two because most DSPs
+ have only two accumulators */
+ for (j=0;last_sample-N+1+j < 0;j++)
+ {
+ spx_word16_t curr_mem = mem[last_sample+j];
+ accum[0] += MULT16_16(curr_mem,st->sinc_table[4+(j+1)*st->oversample-offset-2]);
+ accum[1] += MULT16_16(curr_mem,st->sinc_table[4+(j+1)*st->oversample-offset-1]);
+ accum[2] += MULT16_16(curr_mem,st->sinc_table[4+(j+1)*st->oversample-offset]);
+ accum[3] += MULT16_16(curr_mem,st->sinc_table[4+(j+1)*st->oversample-offset+1]);
+ }
+ ptr = in+st->in_stride*(last_sample-N+1+j);
+ /* Do the new part */
+ for (;j<N;j++)
+ {
+ spx_word16_t curr_in = *ptr;
+ ptr += st->in_stride;
+ accum[0] += MULT16_16(curr_in,st->sinc_table[4+(j+1)*st->oversample-offset-2]);
+ accum[1] += MULT16_16(curr_in,st->sinc_table[4+(j+1)*st->oversample-offset-1]);
+ accum[2] += MULT16_16(curr_in,st->sinc_table[4+(j+1)*st->oversample-offset]);
+ accum[3] += MULT16_16(curr_in,st->sinc_table[4+(j+1)*st->oversample-offset+1]);
+ }
+ cubic_coef(frac, interp);
+ sum = MULT16_32_Q15(interp[0],accum[0]) + MULT16_32_Q15(interp[1],accum[1]) + MULT16_32_Q15(interp[2],accum[2]) + MULT16_32_Q15(interp[3],accum[3]);
+
+ *out = PSHR32(sum,15);
+ out += st->out_stride;
+ out_sample++;
+ last_sample += st->int_advance;
+ samp_frac_num += st->frac_advance;
+ if (samp_frac_num >= st->den_rate)
+ {
+ samp_frac_num -= st->den_rate;
+ last_sample++;
+ }
+ }
+ st->last_sample[channel_index] = last_sample;
+ st->samp_frac_num[channel_index] = samp_frac_num;
+ return out_sample;
+}
+
+#ifdef FIXED_POINT
+#else
+/* This is the same as the previous function, except with a double-precision accumulator */
+static int resampler_basic_interpolate_double(SpeexResamplerState *st, spx_uint32_t channel_index, const spx_word16_t *in, spx_uint32_t *in_len, spx_word16_t *out, spx_uint32_t *out_len)
+{
+ int N = st->filt_len;
+ int out_sample = 0;
+ spx_word16_t *mem;
+ int last_sample = st->last_sample[channel_index];
+ spx_uint32_t samp_frac_num = st->samp_frac_num[channel_index];
+ mem = st->mem + channel_index * st->mem_alloc_size;
+ while (!(last_sample >= (spx_int32_t)*in_len || out_sample >= (spx_int32_t)*out_len))
+ {
+ int j;
+ spx_word32_t sum=0;
+
+ /* We need to interpolate the sinc filter */
+ double accum[4] = {0.f,0.f, 0.f, 0.f};
+ float interp[4];
+ const spx_word16_t *ptr;
+ float alpha = ((float)samp_frac_num)/st->den_rate;
+ int offset = samp_frac_num*st->oversample/st->den_rate;
+ float frac = alpha*st->oversample - offset;
+ /* This code is written like this to make it easy to optimise with SIMD.
+ For most DSPs, it would be best to split the loops in two because most DSPs
+ have only two accumulators */
+ for (j=0;last_sample-N+1+j < 0;j++)
+ {
+ double curr_mem = mem[last_sample+j];
+ accum[0] += MULT16_16(curr_mem,st->sinc_table[4+(j+1)*st->oversample-offset-2]);
+ accum[1] += MULT16_16(curr_mem,st->sinc_table[4+(j+1)*st->oversample-offset-1]);
+ accum[2] += MULT16_16(curr_mem,st->sinc_table[4+(j+1)*st->oversample-offset]);
+ accum[3] += MULT16_16(curr_mem,st->sinc_table[4+(j+1)*st->oversample-offset+1]);
+ }
+ ptr = in+st->in_stride*(last_sample-N+1+j);
+ /* Do the new part */
+ for (;j<N;j++)
+ {
+ double curr_in = *ptr;
+ ptr += st->in_stride;
+ accum[0] += MULT16_16(curr_in,st->sinc_table[4+(j+1)*st->oversample-offset-2]);
+ accum[1] += MULT16_16(curr_in,st->sinc_table[4+(j+1)*st->oversample-offset-1]);
+ accum[2] += MULT16_16(curr_in,st->sinc_table[4+(j+1)*st->oversample-offset]);
+ accum[3] += MULT16_16(curr_in,st->sinc_table[4+(j+1)*st->oversample-offset+1]);
+ }
+ cubic_coef(frac, interp);
+ sum = interp[0]*accum[0] + interp[1]*accum[1] + interp[2]*accum[2] + interp[3]*accum[3];
+
+ *out = PSHR32(sum,15);
+ out += st->out_stride;
+ out_sample++;
+ last_sample += st->int_advance;
+ samp_frac_num += st->frac_advance;
+ if (samp_frac_num >= st->den_rate)
+ {
+ samp_frac_num -= st->den_rate;
+ last_sample++;
+ }
+ }
+ st->last_sample[channel_index] = last_sample;
+ st->samp_frac_num[channel_index] = samp_frac_num;
+ return out_sample;
+}
+#endif
+
+static void update_filter(SpeexResamplerState *st)
+{
+ spx_uint32_t old_length;
+
+ old_length = st->filt_len;
+ st->oversample = quality_map[st->quality].oversample;
+ st->filt_len = quality_map[st->quality].base_length;
+
+ if (st->num_rate > st->den_rate)
+ {
+ /* down-sampling */
+ st->cutoff = quality_map[st->quality].downsample_bandwidth * st->den_rate / st->num_rate;
+ /* FIXME: divide the numerator and denominator by a certain amount if they're too large */
+ st->filt_len = st->filt_len*st->num_rate / st->den_rate;
+ /* Round down to make sure we have a multiple of 4 */
+ st->filt_len &= (~0x3);
+ if (2*st->den_rate < st->num_rate)
+ st->oversample >>= 1;
+ if (4*st->den_rate < st->num_rate)
+ st->oversample >>= 1;
+ if (8*st->den_rate < st->num_rate)
+ st->oversample >>= 1;
+ if (16*st->den_rate < st->num_rate)
+ st->oversample >>= 1;
+ if (st->oversample < 1)
+ st->oversample = 1;
+ } else {
+ /* up-sampling */
+ st->cutoff = quality_map[st->quality].upsample_bandwidth;
+ }
+
+ /* Choose the resampling type that requires the least amount of memory */
+ if (st->den_rate <= st->oversample)
+ {
+ spx_uint32_t i;
+ if (!st->sinc_table)
+ st->sinc_table = (spx_word16_t *)speex_alloc(st->filt_len*st->den_rate*sizeof(spx_word16_t));
+ else if (st->sinc_table_length < st->filt_len*st->den_rate)
+ {
+ st->sinc_table = (spx_word16_t *)speex_realloc(st->sinc_table,st->filt_len*st->den_rate*sizeof(spx_word16_t));
+ st->sinc_table_length = st->filt_len*st->den_rate;
+ }
+ for (i=0;i<st->den_rate;i++)
+ {
+ spx_int32_t j;
+ for (j=0;j<st->filt_len;j++)
+ {
+ st->sinc_table[i*st->filt_len+j] = sinc(st->cutoff,((j-(spx_int32_t)st->filt_len/2+1)-((float)i)/st->den_rate), st->filt_len, quality_map[st->quality].window_func);
+ }
+ }
+#ifdef FIXED_POINT
+ st->resampler_ptr = resampler_basic_direct_single;
+#else
+ if (st->quality>8)
+ st->resampler_ptr = resampler_basic_direct_double;
+ else
+ st->resampler_ptr = resampler_basic_direct_single;
+#endif
+ /*fprintf (stderr, "resampler uses direct sinc table and normalised cutoff %f\n", cutoff);*/
+ } else {
+ spx_int32_t i;
+ if (!st->sinc_table)
+ st->sinc_table = (spx_word16_t *)speex_alloc((st->filt_len*st->oversample+8)*sizeof(spx_word16_t));
+ else if (st->sinc_table_length < st->filt_len*st->oversample+8)
+ {
+ st->sinc_table = (spx_word16_t *)speex_realloc(st->sinc_table,(st->filt_len*st->oversample+8)*sizeof(spx_word16_t));
+ st->sinc_table_length = st->filt_len*st->oversample+8;
+ }
+ for (i=-4;i<(spx_int32_t)(st->oversample*st->filt_len+4);i++)
+ st->sinc_table[i+4] = sinc(st->cutoff,(i/(float)st->oversample - st->filt_len/2), st->filt_len, quality_map[st->quality].window_func);
+#ifdef FIXED_POINT
+ st->resampler_ptr = resampler_basic_interpolate_single;
+#else
+ if (st->quality>8)
+ st->resampler_ptr = resampler_basic_interpolate_double;
+ else
+ st->resampler_ptr = resampler_basic_interpolate_single;
+#endif
+ /*fprintf (stderr, "resampler uses interpolated sinc table and normalised cutoff %f\n", cutoff);*/
+ }
+ st->int_advance = st->num_rate/st->den_rate;
+ st->frac_advance = st->num_rate%st->den_rate;
+
+
+ /* Here's the place where we update the filter memory to take into account
+ the change in filter length. It's probably the messiest part of the code
+ due to handling of lots of corner cases. */
+ if (!st->mem)
+ {
+ spx_uint32_t i;
+ st->mem = (spx_word16_t*)speex_alloc(st->nb_channels*(st->filt_len-1) * sizeof(spx_word16_t));
+ for (i=0;i<st->nb_channels*(st->filt_len-1);i++)
+ st->mem[i] = 0;
+ st->mem_alloc_size = st->filt_len-1;
+ /*speex_warning("init filter");*/
+ } else if (!st->started)
+ {
+ spx_uint32_t i;
+ st->mem = (spx_word16_t*)speex_realloc(st->mem, st->nb_channels*(st->filt_len-1) * sizeof(spx_word16_t));
+ for (i=0;i<st->nb_channels*(st->filt_len-1);i++)
+ st->mem[i] = 0;
+ st->mem_alloc_size = st->filt_len-1;
+ /*speex_warning("reinit filter");*/
+ } else if (st->filt_len > old_length)
+ {
+ spx_int32_t i;
+ /* Increase the filter length */
+ /*speex_warning("increase filter size");*/
+ int old_alloc_size = st->mem_alloc_size;
+ if (st->filt_len-1 > st->mem_alloc_size)
+ {
+ st->mem = (spx_word16_t*)speex_realloc(st->mem, st->nb_channels*(st->filt_len-1) * sizeof(spx_word16_t));
+ st->mem_alloc_size = st->filt_len-1;
+ }
+ for (i=st->nb_channels-1;i>=0;i--)
+ {
+ spx_int32_t j;
+ spx_uint32_t olen = old_length;
+ /*if (st->magic_samples[i])*/
+ {
+ /* Try and remove the magic samples as if nothing had happened */
+
+ /* FIXME: This is wrong but for now we need it to avoid going over the array bounds */
+ olen = old_length + 2*st->magic_samples[i];
+ for (j=old_length-2+st->magic_samples[i];j>=0;j--)
+ st->mem[i*st->mem_alloc_size+j+st->magic_samples[i]] = st->mem[i*old_alloc_size+j];
+ for (j=0;j<st->magic_samples[i];j++)
+ st->mem[i*st->mem_alloc_size+j] = 0;
+ st->magic_samples[i] = 0;
+ }
+ if (st->filt_len > olen)
+ {
+ /* If the new filter length is still bigger than the "augmented" length */
+ /* Copy data going backward */
+ for (j=0;j<olen-1;j++)
+ st->mem[i*st->mem_alloc_size+(st->filt_len-2-j)] = st->mem[i*st->mem_alloc_size+(olen-2-j)];
+ /* Then put zeros for lack of anything better */
+ for (;j<st->filt_len-1;j++)
+ st->mem[i*st->mem_alloc_size+(st->filt_len-2-j)] = 0;
+ /* Adjust last_sample */
+ st->last_sample[i] += (st->filt_len - olen)/2;
+ } else {
+ /* Put back some of the magic! */
+ st->magic_samples[i] = (olen - st->filt_len)/2;
+ for (j=0;j<st->filt_len-1+st->magic_samples[i];j++)
+ st->mem[i*st->mem_alloc_size+j] = st->mem[i*st->mem_alloc_size+j+st->magic_samples[i]];
+ }
+ }
+ } else if (st->filt_len < old_length)
+ {
+ spx_uint32_t i;
+ /* Reduce filter length, this a bit tricky. We need to store some of the memory as "magic"
+ samples so they can be used directly as input the next time(s) */
+ for (i=0;i<st->nb_channels;i++)
+ {
+ spx_uint32_t j;
+ spx_uint32_t old_magic = st->magic_samples[i];
+ st->magic_samples[i] = (old_length - st->filt_len)/2;
+ /* We must copy some of the memory that's no longer used */
+ /* Copy data going backward */
+ for (j=0;j<st->filt_len-1+st->magic_samples[i]+old_magic;j++)
+ st->mem[i*st->mem_alloc_size+j] = st->mem[i*st->mem_alloc_size+j+st->magic_samples[i]];
+ st->magic_samples[i] += old_magic;
+ }
+ }
+
+}
+
+SpeexResamplerState *speex_resampler_init(spx_uint32_t nb_channels, spx_uint32_t in_rate, spx_uint32_t out_rate, int quality, int *err)
+{
+ return speex_resampler_init_frac(nb_channels, in_rate, out_rate, in_rate, out_rate, quality, err);
+}
+
+SpeexResamplerState *speex_resampler_init_frac(spx_uint32_t nb_channels, spx_uint32_t ratio_num, spx_uint32_t ratio_den, spx_uint32_t in_rate, spx_uint32_t out_rate, int quality, int *err)
+{
+ spx_uint32_t i;
+ SpeexResamplerState *st;
+ if (quality > 10 || quality < 0)
+ {
+ if (err)
+ *err = RESAMPLER_ERR_INVALID_ARG;
+ return NULL;
+ }
+ st = (SpeexResamplerState *)speex_alloc(sizeof(SpeexResamplerState));
+ st->initialised = 0;
+ st->started = 0;
+ st->in_rate = 0;
+ st->out_rate = 0;
+ st->num_rate = 0;
+ st->den_rate = 0;
+ st->quality = -1;
+ st->sinc_table_length = 0;
+ st->mem_alloc_size = 0;
+ st->filt_len = 0;
+ st->mem = 0;
+ st->resampler_ptr = 0;
+
+ st->cutoff = 1.f;
+ st->nb_channels = nb_channels;
+ st->in_stride = 1;
+ st->out_stride = 1;
+
+ /* Per channel data */
+ st->last_sample = (spx_int32_t*)speex_alloc(nb_channels*sizeof(int));
+ st->magic_samples = (spx_uint32_t*)speex_alloc(nb_channels*sizeof(int));
+ st->samp_frac_num = (spx_uint32_t*)speex_alloc(nb_channels*sizeof(int));
+ for (i=0;i<nb_channels;i++)
+ {
+ st->last_sample[i] = 0;
+ st->magic_samples[i] = 0;
+ st->samp_frac_num[i] = 0;
+ }
+
+ speex_resampler_set_quality(st, quality);
+ speex_resampler_set_rate_frac(st, ratio_num, ratio_den, in_rate, out_rate);
+
+
+ update_filter(st);
+
+ st->initialised = 1;
+ if (err)
+ *err = RESAMPLER_ERR_SUCCESS;
+
+ return st;
+}
+
+void speex_resampler_destroy(SpeexResamplerState *st)
+{
+ speex_free(st->mem);
+ speex_free(st->sinc_table);
+ speex_free(st->last_sample);
+ speex_free(st->magic_samples);
+ speex_free(st->samp_frac_num);
+ speex_free(st);
+}
+
+
+
+static int speex_resampler_process_native(SpeexResamplerState *st, spx_uint32_t channel_index, const spx_word16_t *in, spx_uint32_t *in_len, spx_word16_t *out, spx_uint32_t *out_len)
+{
+ int j=0;
+ int N = st->filt_len;
+ int out_sample = 0;
+ spx_word16_t *mem;
+ spx_uint32_t tmp_out_len = 0;
+ mem = st->mem + channel_index * st->mem_alloc_size;
+ st->started = 1;
+
+ /* Handle the case where we have samples left from a reduction in filter length */
+ if (st->magic_samples[channel_index])
+ {
+ int istride_save;
+ spx_uint32_t tmp_in_len;
+ spx_uint32_t tmp_magic;
+
+ istride_save = st->in_stride;
+ tmp_in_len = st->magic_samples[channel_index];
+ tmp_out_len = *out_len;
+ /* magic_samples needs to be set to zero to avoid infinite recursion */
+ tmp_magic = st->magic_samples[channel_index];
+ st->magic_samples[channel_index] = 0;
+ st->in_stride = 1;
+ speex_resampler_process_native(st, channel_index, mem+N-1, &tmp_in_len, out, &tmp_out_len);
+ st->in_stride = istride_save;
+ /*speex_warning_int("extra samples:", tmp_out_len);*/
+ /* If we couldn't process all "magic" input samples, save the rest for next time */
+ if (tmp_in_len < tmp_magic)
+ {
+ spx_uint32_t i;
+ st->magic_samples[channel_index] = tmp_magic-tmp_in_len;
+ for (i=0;i<st->magic_samples[channel_index];i++)
+ mem[N-1+i]=mem[N-1+i+tmp_in_len];
+ }
+ out += tmp_out_len*st->out_stride;
+ *out_len -= tmp_out_len;
+ }
+
+ /* Call the right resampler through the function ptr */
+ out_sample = st->resampler_ptr(st, channel_index, in, in_len, out, out_len);
+
+ if (st->last_sample[channel_index] < (spx_int32_t)*in_len)
+ *in_len = st->last_sample[channel_index];
+ *out_len = out_sample+tmp_out_len;
+ st->last_sample[channel_index] -= *in_len;
+
+ for (j=0;j<N-1-(spx_int32_t)*in_len;j++)
+ mem[j] = mem[j+*in_len];
+ for (;j<N-1;j++)
+ mem[j] = in[st->in_stride*(j+*in_len-N+1)];
+
+ return RESAMPLER_ERR_SUCCESS;
+}
+
+#define FIXED_STACK_ALLOC 1024
+
+#ifdef FIXED_POINT
+int speex_resampler_process_float(SpeexResamplerState *st, spx_uint32_t channel_index, const float *in, spx_uint32_t *in_len, float *out, spx_uint32_t *out_len)
+{
+ spx_uint32_t i;
+ int istride_save, ostride_save;
+#ifdef VAR_ARRAYS
+ spx_word16_t x[*in_len];
+ spx_word16_t y[*out_len];
+ /*VARDECL(spx_word16_t *x);
+ VARDECL(spx_word16_t *y);
+ ALLOC(x, *in_len, spx_word16_t);
+ ALLOC(y, *out_len, spx_word16_t);*/
+ istride_save = st->in_stride;
+ ostride_save = st->out_stride;
+ for (i=0;i<*in_len;i++)
+ x[i] = WORD2INT(in[i*st->in_stride]);
+ st->in_stride = st->out_stride = 1;
+ speex_resampler_process_native(st, channel_index, x, in_len, y, out_len);
+ st->in_stride = istride_save;
+ st->out_stride = ostride_save;
+ for (i=0;i<*out_len;i++)
+ out[i*st->out_stride] = y[i];
+#else
+ spx_word16_t x[FIXED_STACK_ALLOC];
+ spx_word16_t y[FIXED_STACK_ALLOC];
+ spx_uint32_t ilen=*in_len, olen=*out_len;
+ istride_save = st->in_stride;
+ ostride_save = st->out_stride;
+ while (ilen && olen)
+ {
+ spx_uint32_t ichunk, ochunk;
+ ichunk = ilen;
+ ochunk = olen;
+ if (ichunk>FIXED_STACK_ALLOC)
+ ichunk=FIXED_STACK_ALLOC;
+ if (ochunk>FIXED_STACK_ALLOC)
+ ochunk=FIXED_STACK_ALLOC;
+ for (i=0;i<ichunk;i++)
+ x[i] = WORD2INT(in[i*st->in_stride]);
+ st->in_stride = st->out_stride = 1;
+ speex_resampler_process_native(st, channel_index, x, &ichunk, y, &ochunk);
+ st->in_stride = istride_save;
+ st->out_stride = ostride_save;
+ for (i=0;i<ochunk;i++)
+ out[i*st->out_stride] = y[i];
+ out += ochunk;
+ in += ichunk;
+ ilen -= ichunk;
+ olen -= ochunk;
+ }
+ *in_len -= ilen;
+ *out_len -= olen;
+#endif
+ return RESAMPLER_ERR_SUCCESS;
+}
+int speex_resampler_process_int(SpeexResamplerState *st, spx_uint32_t channel_index, const spx_int16_t *in, spx_uint32_t *in_len, spx_int16_t *out, spx_uint32_t *out_len)
+{
+ return speex_resampler_process_native(st, channel_index, in, in_len, out, out_len);
+}
+#else
+int speex_resampler_process_float(SpeexResamplerState *st, spx_uint32_t channel_index, const float *in, spx_uint32_t *in_len, float *out, spx_uint32_t *out_len)
+{
+ return speex_resampler_process_native(st, channel_index, in, in_len, out, out_len);
+}
+int speex_resampler_process_int(SpeexResamplerState *st, spx_uint32_t channel_index, const spx_int16_t *in, spx_uint32_t *in_len, spx_int16_t *out, spx_uint32_t *out_len)
+{
+ spx_uint32_t i;
+ int istride_save, ostride_save;
+#ifdef VAR_ARRAYS
+ spx_word16_t x[*in_len];
+ spx_word16_t y[*out_len];
+ /*VARDECL(spx_word16_t *x);
+ VARDECL(spx_word16_t *y);
+ ALLOC(x, *in_len, spx_word16_t);
+ ALLOC(y, *out_len, spx_word16_t);*/
+ istride_save = st->in_stride;
+ ostride_save = st->out_stride;
+ for (i=0;i<*in_len;i++)
+ x[i] = in[i*st->in_stride];
+ st->in_stride = st->out_stride = 1;
+ speex_resampler_process_native(st, channel_index, x, in_len, y, out_len);
+ st->in_stride = istride_save;
+ st->out_stride = ostride_save;
+ for (i=0;i<*out_len;i++)
+ out[i*st->out_stride] = WORD2INT(y[i]);
+#else
+ spx_word16_t x[FIXED_STACK_ALLOC];
+ spx_word16_t y[FIXED_STACK_ALLOC];
+ spx_uint32_t ilen=*in_len, olen=*out_len;
+ istride_save = st->in_stride;
+ ostride_save = st->out_stride;
+ while (ilen && olen)
+ {
+ spx_uint32_t ichunk, ochunk;
+ ichunk = ilen;
+ ochunk = olen;
+ if (ichunk>FIXED_STACK_ALLOC)
+ ichunk=FIXED_STACK_ALLOC;
+ if (ochunk>FIXED_STACK_ALLOC)
+ ochunk=FIXED_STACK_ALLOC;
+ for (i=0;i<ichunk;i++)
+ x[i] = in[i*st->in_stride];
+ st->in_stride = st->out_stride = 1;
+ speex_resampler_process_native(st, channel_index, x, &ichunk, y, &ochunk);
+ st->in_stride = istride_save;
+ st->out_stride = ostride_save;
+ for (i=0;i<ochunk;i++)
+ out[i*st->out_stride] = WORD2INT(y[i]);
+ out += ochunk;
+ in += ichunk;
+ ilen -= ichunk;
+ olen -= ochunk;
+ }
+ *in_len -= ilen;
+ *out_len -= olen;
+#endif
+ return RESAMPLER_ERR_SUCCESS;
+}
+#endif
+
+int speex_resampler_process_interleaved_float(SpeexResamplerState *st, const float *in, spx_uint32_t *in_len, float *out, spx_uint32_t *out_len)
+{
+ spx_uint32_t i;
+ int istride_save, ostride_save;
+ spx_uint32_t bak_len = *out_len;
+ istride_save = st->in_stride;
+ ostride_save = st->out_stride;
+ st->in_stride = st->out_stride = st->nb_channels;
+ for (i=0;i<st->nb_channels;i++)
+ {
+ *out_len = bak_len;
+ speex_resampler_process_float(st, i, in+i, in_len, out+i, out_len);
+ }
+ st->in_stride = istride_save;
+ st->out_stride = ostride_save;
+ return RESAMPLER_ERR_SUCCESS;
+}
+
+
+int speex_resampler_process_interleaved_int(SpeexResamplerState *st, const spx_int16_t *in, spx_uint32_t *in_len, spx_int16_t *out, spx_uint32_t *out_len)
+{
+ spx_uint32_t i;
+ int istride_save, ostride_save;
+ spx_uint32_t bak_len = *out_len;
+ istride_save = st->in_stride;
+ ostride_save = st->out_stride;
+ st->in_stride = st->out_stride = st->nb_channels;
+ for (i=0;i<st->nb_channels;i++)
+ {
+ *out_len = bak_len;
+ speex_resampler_process_int(st, i, in+i, in_len, out+i, out_len);
+ }
+ st->in_stride = istride_save;
+ st->out_stride = ostride_save;
+ return RESAMPLER_ERR_SUCCESS;
+}
+
+int speex_resampler_set_rate(SpeexResamplerState *st, spx_uint32_t in_rate, spx_uint32_t out_rate)
+{
+ return speex_resampler_set_rate_frac(st, in_rate, out_rate, in_rate, out_rate);
+}
+
+void speex_resampler_get_rate(SpeexResamplerState *st, spx_uint32_t *in_rate, spx_uint32_t *out_rate)
+{
+ *in_rate = st->in_rate;
+ *out_rate = st->out_rate;
+}
+
+int speex_resampler_set_rate_frac(SpeexResamplerState *st, spx_uint32_t ratio_num, spx_uint32_t ratio_den, spx_uint32_t in_rate, spx_uint32_t out_rate)
+{
+ spx_uint32_t fact;
+ spx_uint32_t old_den;
+ spx_uint32_t i;
+ if (st->in_rate == in_rate && st->out_rate == out_rate && st->num_rate == ratio_num && st->den_rate == ratio_den)
+ return RESAMPLER_ERR_SUCCESS;
+
+ old_den = st->den_rate;
+ st->in_rate = in_rate;
+ st->out_rate = out_rate;
+ st->num_rate = ratio_num;
+ st->den_rate = ratio_den;
+ /* FIXME: This is terribly inefficient, but who cares (at least for now)? */
+ for (fact=2;fact<=IMIN(st->num_rate, st->den_rate);fact++)
+ {
+ while ((st->num_rate % fact == 0) && (st->den_rate % fact == 0))
+ {
+ st->num_rate /= fact;
+ st->den_rate /= fact;
+ }
+ }
+
+ if (old_den > 0)
+ {
+ for (i=0;i<st->nb_channels;i++)
+ {
+ st->samp_frac_num[i]=st->samp_frac_num[i]*st->den_rate/old_den;
+ /* Safety net */
+ if (st->samp_frac_num[i] >= st->den_rate)
+ st->samp_frac_num[i] = st->den_rate-1;
+ }
+ }
+
+ if (st->initialised)
+ update_filter(st);
+ return RESAMPLER_ERR_SUCCESS;
+}
+
+void speex_resampler_get_ratio(SpeexResamplerState *st, spx_uint32_t *ratio_num, spx_uint32_t *ratio_den)
+{
+ *ratio_num = st->num_rate;
+ *ratio_den = st->den_rate;
+}
+
+int speex_resampler_set_quality(SpeexResamplerState *st, int quality)
+{
+ if (quality > 10 || quality < 0)
+ return RESAMPLER_ERR_INVALID_ARG;
+ if (st->quality == quality)
+ return RESAMPLER_ERR_SUCCESS;
+ st->quality = quality;
+ if (st->initialised)
+ update_filter(st);
+ return RESAMPLER_ERR_SUCCESS;
+}
+
+void speex_resampler_get_quality(SpeexResamplerState *st, int *quality)
+{
+ *quality = st->quality;
+}
+
+void speex_resampler_set_input_stride(SpeexResamplerState *st, spx_uint32_t stride)
+{
+ st->in_stride = stride;
+}
+
+void speex_resampler_get_input_stride(SpeexResamplerState *st, spx_uint32_t *stride)
+{
+ *stride = st->in_stride;
+}
+
+void speex_resampler_set_output_stride(SpeexResamplerState *st, spx_uint32_t stride)
+{
+ st->out_stride = stride;
+}
+
+void speex_resampler_get_output_stride(SpeexResamplerState *st, spx_uint32_t *stride)
+{
+ *stride = st->out_stride;
+}
+
+int speex_resampler_skip_zeros(SpeexResamplerState *st)
+{
+ spx_uint32_t i;
+ for (i=0;i<st->nb_channels;i++)
+ st->last_sample[i] = st->filt_len/2;
+ return RESAMPLER_ERR_SUCCESS;
+}
+
+int speex_resampler_reset_mem(SpeexResamplerState *st)
+{
+ spx_uint32_t i;
+ for (i=0;i<st->nb_channels*(st->filt_len-1);i++)
+ st->mem[i] = 0;
+ return RESAMPLER_ERR_SUCCESS;
+}
+
+const char *speex_resampler_strerror(int err)
+{
+ switch (err)
+ {
+ case RESAMPLER_ERR_SUCCESS:
+ return "Success.";
+ case RESAMPLER_ERR_ALLOC_FAILED:
+ return "Memory allocation failed.";
+ case RESAMPLER_ERR_BAD_STATE:
+ return "Bad resampler state.";
+ case RESAMPLER_ERR_INVALID_ARG:
+ return "Invalid argument.";
+ case RESAMPLER_ERR_PTR_OVERLAP:
+ return "Input and output buffers overlap.";
+ default:
+ return "Unknown error. Bad error code or strange version mismatch.";
+ }
+}
diff --git a/src/pulsecore/speex/speex_resampler.h b/src/pulsecore/speex/speex_resampler.h
new file mode 100644
index 00000000..8629eeb3
--- /dev/null
+++ b/src/pulsecore/speex/speex_resampler.h
@@ -0,0 +1,328 @@
+/* Copyright (C) 2007 Jean-Marc Valin
+
+ File: speex_resampler.h
+ Resampling code
+
+ The design goals of this code are:
+ - Very fast algorithm
+ - Low memory requirement
+ - Good *perceptual* quality (and not best SNR)
+
+ 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. The name of the author may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 THE AUTHOR 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.
+*/
+
+
+#ifndef SPEEX_RESAMPLER_H
+#define SPEEX_RESAMPLER_H
+
+#ifdef OUTSIDE_SPEEX
+
+/********* WARNING: MENTAL SANITY ENDS HERE *************/
+
+/* If the resampler is defined outside of Speex, we change the symbol names so that
+ there won't be any clash if linking with Speex later on. */
+
+/* #define RANDOM_PREFIX your software name here */
+#ifndef RANDOM_PREFIX
+#error "Please define RANDOM_PREFIX (above) to something specific to your project to prevent symbol name clashes"
+#endif
+
+#define CAT_PREFIX2(a,b) a ## b
+#define CAT_PREFIX(a,b) CAT_PREFIX2(a, b)
+
+#define speex_resampler_init CAT_PREFIX(RANDOM_PREFIX,_resampler_init)
+#define speex_resampler_init_frac CAT_PREFIX(RANDOM_PREFIX,_resampler_init_frac)
+#define speex_resampler_destroy CAT_PREFIX(RANDOM_PREFIX,_resampler_destroy)
+#define speex_resampler_process_float CAT_PREFIX(RANDOM_PREFIX,_resampler_process_float)
+#define speex_resampler_process_int CAT_PREFIX(RANDOM_PREFIX,_resampler_process_int)
+#define speex_resampler_process_interleaved_float CAT_PREFIX(RANDOM_PREFIX,_resampler_process_interleaved_float)
+#define speex_resampler_process_interleaved_int CAT_PREFIX(RANDOM_PREFIX,_resampler_process_interleaved_int)
+#define speex_resampler_set_rate CAT_PREFIX(RANDOM_PREFIX,_resampler_set_rate)
+#define speex_resampler_get_rate CAT_PREFIX(RANDOM_PREFIX,_resampler_get_rate)
+#define speex_resampler_set_rate_frac CAT_PREFIX(RANDOM_PREFIX,_resampler_set_rate_frac)
+#define speex_resampler_get_ratio CAT_PREFIX(RANDOM_PREFIX,_resampler_get_ratio)
+#define speex_resampler_set_quality CAT_PREFIX(RANDOM_PREFIX,_resampler_set_quality)
+#define speex_resampler_get_quality CAT_PREFIX(RANDOM_PREFIX,_resampler_get_quality)
+#define speex_resampler_set_input_stride CAT_PREFIX(RANDOM_PREFIX,_resampler_set_input_stride)
+#define speex_resampler_get_input_stride CAT_PREFIX(RANDOM_PREFIX,_resampler_get_input_stride)
+#define speex_resampler_set_output_stride CAT_PREFIX(RANDOM_PREFIX,_resampler_set_output_stride)
+#define speex_resampler_get_output_stride CAT_PREFIX(RANDOM_PREFIX,_resampler_get_output_stride)
+#define speex_resampler_skip_zeros CAT_PREFIX(RANDOM_PREFIX,_resampler_skip_zeros)
+#define speex_resampler_reset_mem CAT_PREFIX(RANDOM_PREFIX,_resampler_reset_mem)
+#define speex_resampler_strerror CAT_PREFIX(RANDOM_PREFIX,_resampler_strerror)
+
+#define spx_int16_t short
+#define spx_int32_t int
+#define spx_uint16_t unsigned short
+#define spx_uint32_t unsigned int
+
+#else /* OUTSIDE_SPEEX */
+
+#include "speex/speex_types.h"
+
+#endif /* OUTSIDE_SPEEX */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define SPEEX_RESAMPLER_QUALITY_MAX 10
+#define SPEEX_RESAMPLER_QUALITY_MIN 0
+#define SPEEX_RESAMPLER_QUALITY_DEFAULT 4
+#define SPEEX_RESAMPLER_QUALITY_VOIP 3
+#define SPEEX_RESAMPLER_QUALITY_DESKTOP 5
+
+enum {
+ RESAMPLER_ERR_SUCCESS = 0,
+ RESAMPLER_ERR_ALLOC_FAILED = 1,
+ RESAMPLER_ERR_BAD_STATE = 2,
+ RESAMPLER_ERR_INVALID_ARG = 3,
+ RESAMPLER_ERR_PTR_OVERLAP = 4,
+
+ RESAMPLER_ERR_MAX_ERROR
+};
+
+struct SpeexResamplerState_;
+typedef struct SpeexResamplerState_ SpeexResamplerState;
+
+/** Create a new resampler with integer input and output rates.
+ * @param nb_channels Number of channels to be processed
+ * @param in_rate Input sampling rate (integer number of Hz).
+ * @param out_rate Output sampling rate (integer number of Hz).
+ * @param quality Resampling quality between 0 and 10, where 0 has poor quality
+ * and 10 has very high quality.
+ * @return Newly created resampler state
+ * @retval NULL Error: not enough memory
+ */
+SpeexResamplerState *speex_resampler_init(spx_uint32_t nb_channels,
+ spx_uint32_t in_rate,
+ spx_uint32_t out_rate,
+ int quality,
+ int *err);
+
+/** Create a new resampler with fractional input/output rates. The sampling
+ * rate ratio is an arbitrary rational number with both the numerator and
+ * denominator being 32-bit integers.
+ * @param nb_channels Number of channels to be processed
+ * @param ratio_num Numerator of the sampling rate ratio
+ * @param ratio_den Denominator of the sampling rate ratio
+ * @param in_rate Input sampling rate rounded to the nearest integer (in Hz).
+ * @param out_rate Output sampling rate rounded to the nearest integer (in Hz).
+ * @param quality Resampling quality between 0 and 10, where 0 has poor quality
+ * and 10 has very high quality.
+ * @return Newly created resampler state
+ * @retval NULL Error: not enough memory
+ */
+SpeexResamplerState *speex_resampler_init_frac(spx_uint32_t nb_channels,
+ spx_uint32_t ratio_num,
+ spx_uint32_t ratio_den,
+ spx_uint32_t in_rate,
+ spx_uint32_t out_rate,
+ int quality,
+ int *err);
+
+/** Destroy a resampler state.
+ * @param st Resampler state
+ */
+void speex_resampler_destroy(SpeexResamplerState *st);
+
+/** Resample a float array. The input and output buffers must *not* overlap.
+ * @param st Resampler state
+ * @param channel_index Index of the channel to process for the multi-channel
+ * base (0 otherwise)
+ * @param in Input buffer
+ * @param in_len Number of input samples in the input buffer. Returns the
+ * number of samples processed
+ * @param out Output buffer
+ * @param out_len Size of the output buffer. Returns the number of samples written
+ */
+int speex_resampler_process_float(SpeexResamplerState *st,
+ spx_uint32_t channel_index,
+ const float *in,
+ spx_uint32_t *in_len,
+ float *out,
+ spx_uint32_t *out_len);
+
+/** Resample an int array. The input and output buffers must *not* overlap.
+ * @param st Resampler state
+ * @param channel_index Index of the channel to process for the multi-channel
+ * base (0 otherwise)
+ * @param in Input buffer
+ * @param in_len Number of input samples in the input buffer. Returns the number
+ * of samples processed
+ * @param out Output buffer
+ * @param out_len Size of the output buffer. Returns the number of samples written
+ */
+int speex_resampler_process_int(SpeexResamplerState *st,
+ spx_uint32_t channel_index,
+ const spx_int16_t *in,
+ spx_uint32_t *in_len,
+ spx_int16_t *out,
+ spx_uint32_t *out_len);
+
+/** Resample an interleaved float array. The input and output buffers must *not* overlap.
+ * @param st Resampler state
+ * @param in Input buffer
+ * @param in_len Number of input samples in the input buffer. Returns the number
+ * of samples processed. This is all per-channel.
+ * @param out Output buffer
+ * @param out_len Size of the output buffer. Returns the number of samples written.
+ * This is all per-channel.
+ */
+int speex_resampler_process_interleaved_float(SpeexResamplerState *st,
+ const float *in,
+ spx_uint32_t *in_len,
+ float *out,
+ spx_uint32_t *out_len);
+
+/** Resample an interleaved int array. The input and output buffers must *not* overlap.
+ * @param st Resampler state
+ * @param in Input buffer
+ * @param in_len Number of input samples in the input buffer. Returns the number
+ * of samples processed. This is all per-channel.
+ * @param out Output buffer
+ * @param out_len Size of the output buffer. Returns the number of samples written.
+ * This is all per-channel.
+ */
+int speex_resampler_process_interleaved_int(SpeexResamplerState *st,
+ const spx_int16_t *in,
+ spx_uint32_t *in_len,
+ spx_int16_t *out,
+ spx_uint32_t *out_len);
+
+/** Set (change) the input/output sampling rates (integer value).
+ * @param st Resampler state
+ * @param in_rate Input sampling rate (integer number of Hz).
+ * @param out_rate Output sampling rate (integer number of Hz).
+ */
+int speex_resampler_set_rate(SpeexResamplerState *st,
+ spx_uint32_t in_rate,
+ spx_uint32_t out_rate);
+
+/** Get the current input/output sampling rates (integer value).
+ * @param st Resampler state
+ * @param in_rate Input sampling rate (integer number of Hz) copied.
+ * @param out_rate Output sampling rate (integer number of Hz) copied.
+ */
+void speex_resampler_get_rate(SpeexResamplerState *st,
+ spx_uint32_t *in_rate,
+ spx_uint32_t *out_rate);
+
+/** Set (change) the input/output sampling rates and resampling ratio
+ * (fractional values in Hz supported).
+ * @param st Resampler state
+ * @param ratio_num Numerator of the sampling rate ratio
+ * @param ratio_den Denominator of the sampling rate ratio
+ * @param in_rate Input sampling rate rounded to the nearest integer (in Hz).
+ * @param out_rate Output sampling rate rounded to the nearest integer (in Hz).
+ */
+int speex_resampler_set_rate_frac(SpeexResamplerState *st,
+ spx_uint32_t ratio_num,
+ spx_uint32_t ratio_den,
+ spx_uint32_t in_rate,
+ spx_uint32_t out_rate);
+
+/** Get the current resampling ratio. This will be reduced to the least
+ * common denominator.
+ * @param st Resampler state
+ * @param ratio_num Numerator of the sampling rate ratio copied
+ * @param ratio_den Denominator of the sampling rate ratio copied
+ */
+void speex_resampler_get_ratio(SpeexResamplerState *st,
+ spx_uint32_t *ratio_num,
+ spx_uint32_t *ratio_den);
+
+/** Set (change) the conversion quality.
+ * @param st Resampler state
+ * @param quality Resampling quality between 0 and 10, where 0 has poor
+ * quality and 10 has very high quality.
+ */
+int speex_resampler_set_quality(SpeexResamplerState *st,
+ int quality);
+
+/** Get the conversion quality.
+ * @param st Resampler state
+ * @param quality Resampling quality between 0 and 10, where 0 has poor
+ * quality and 10 has very high quality.
+ */
+void speex_resampler_get_quality(SpeexResamplerState *st,
+ int *quality);
+
+/** Set (change) the input stride.
+ * @param st Resampler state
+ * @param stride Input stride
+ */
+void speex_resampler_set_input_stride(SpeexResamplerState *st,
+ spx_uint32_t stride);
+
+/** Get the input stride.
+ * @param st Resampler state
+ * @param stride Input stride copied
+ */
+void speex_resampler_get_input_stride(SpeexResamplerState *st,
+ spx_uint32_t *stride);
+
+/** Set (change) the output stride.
+ * @param st Resampler state
+ * @param stride Output stride
+ */
+void speex_resampler_set_output_stride(SpeexResamplerState *st,
+ spx_uint32_t stride);
+
+/** Get the output stride.
+ * @param st Resampler state copied
+ * @param stride Output stride
+ */
+void speex_resampler_get_output_stride(SpeexResamplerState *st,
+ spx_uint32_t *stride);
+
+/** Make sure that the first samples to go out of the resamplers don't have
+ * leading zeros. This is only useful before starting to use a newly created
+ * resampler. It is recommended to use that when resampling an audio file, as
+ * it will generate a file with the same length. For real-time processing,
+ * it is probably easier not to use this call (so that the output duration
+ * is the same for the first frame).
+ * @param st Resampler state
+ */
+int speex_resampler_skip_zeros(SpeexResamplerState *st);
+
+/** Reset a resampler so a new (unrelated) stream can be processed.
+ * @param st Resampler state
+ */
+int speex_resampler_reset_mem(SpeexResamplerState *st);
+
+/** Returns the English meaning for an error code
+ * @param err Error code
+ * @return English string
+ */
+const char *speex_resampler_strerror(int err);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/pulsecore/speexwrap.h b/src/pulsecore/speexwrap.h
new file mode 100644
index 00000000..df73edf0
--- /dev/null
+++ b/src/pulsecore/speexwrap.h
@@ -0,0 +1,50 @@
+#ifndef foopulsespeexwraphfoo
+#define foopulsespeexwraphfoo
+
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+/* We define a minimal version of speex_resampler.h however define one
+ * version for fixed and another one for float. Yes, somewhat ugly */
+
+#define spx_int16_t short
+#define spx_int32_t int
+#define spx_uint16_t unsigned short
+#define spx_uint32_t unsigned int
+
+typedef struct SpeexResamplerState_ SpeexResamplerState;
+
+SpeexResamplerState *paspfx_resampler_init(spx_uint32_t nb_channels, spx_uint32_t in_rate, spx_uint32_t out_rate, int quality, int *err);
+void paspfx_resampler_destroy(SpeexResamplerState *st);
+int paspfx_resampler_process_interleaved_int(SpeexResamplerState *st, const spx_int16_t *in, spx_uint32_t *in_len, spx_int16_t *out, spx_uint32_t *out_len);
+int paspfx_resampler_set_rate(SpeexResamplerState *st, spx_uint32_t in_rate, spx_uint32_t out_rate);
+int paspfx_resampler_reset_mem(SpeexResamplerState *st);
+
+SpeexResamplerState *paspfl_resampler_init(spx_uint32_t nb_channels, spx_uint32_t in_rate, spx_uint32_t out_rate, int quality, int *err);
+void paspfl_resampler_destroy(SpeexResamplerState *st);
+int paspfl_resampler_process_interleaved_float(SpeexResamplerState *st, const float *in, spx_uint32_t *in_len, float *out, spx_uint32_t *out_len);
+int paspfl_resampler_set_rate(SpeexResamplerState *st, spx_uint32_t in_rate, spx_uint32_t out_rate);
+int paspfl_resampler_reset_mem(SpeexResamplerState *st);
+
+#endif
diff --git a/src/pulsecore/start-child.c b/src/pulsecore/start-child.c
new file mode 100644
index 00000000..e01011d6
--- /dev/null
+++ b/src/pulsecore/start-child.c
@@ -0,0 +1,162 @@
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <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 "start-child.h"
+
+int pa_start_child_for_read(const char *name, const char *argv1, pid_t *pid) {
+ 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 {
+#ifdef __linux__
+ DIR* d;
+#endif
+ int max_fd, i;
+
+ /* child */
+
+ pa_reset_priority();
+
+ pa_assert_se(pa_close(pipe_fds[0]) == 0);
+ pa_assert_se(dup2(pipe_fds[1], 1) == 1);
+
+ if (pipe_fds[1] != 1)
+ pa_assert_se(pa_close(pipe_fds[1]) == 0);
+
+ pa_close(0);
+ pa_assert_se(open("/dev/null", O_RDONLY) == 0);
+
+ pa_close(2);
+ pa_assert_se(open("/dev/null", O_WRONLY) == 2);
+
+#ifdef __linux__
+
+ if ((d = opendir("/proc/self/fd/"))) {
+
+ struct dirent *de;
+
+ while ((de = readdir(d))) {
+ char *e = NULL;
+ int fd;
+
+ if (de->d_name[0] == '.')
+ continue;
+
+ errno = 0;
+ fd = strtol(de->d_name, &e, 10);
+ pa_assert(errno == 0 && e && *e == 0);
+
+ if (fd >= 3 && dirfd(d) != fd)
+ pa_close(fd);
+ }
+
+ closedir(d);
+ } else {
+
+#endif
+
+ max_fd = 1024;
+
+#ifdef HAVE_SYS_RESOURCE_H
+ {
+ struct rlimit r;
+ if (getrlimit(RLIMIT_NOFILE, &r) == 0)
+ max_fd = r.rlim_max;
+ }
+#endif
+
+ for (i = 3; i < max_fd; i++)
+ pa_close(i);
+
+#ifdef __linux__
+ }
+#endif
+
+#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
+
+#ifdef SIGPIPE
+ /* Make sure that SIGPIPE kills the child process */
+ signal(SIGPIPE, SIG_DFL);
+#endif
+
+#ifdef SIGTERM
+ /* Make sure that SIGTERM kills the child process */
+ signal(SIGTERM, SIG_DFL);
+#endif
+
+ execl(name, name, argv1, NULL);
+ _exit(1);
+ }
+
+fail:
+ pa_close_pipe(pipe_fds);
+
+ return -1;
+}
diff --git a/src/pulsecore/start-child.h b/src/pulsecore/start-child.h
new file mode 100644
index 00000000..359b5044
--- /dev/null
+++ b/src/pulsecore/start-child.h
@@ -0,0 +1,32 @@
+#ifndef foopulsestartchildhfoo
+#define foopulsestartchildhfoo
+
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#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 00000000..7c576c67
--- /dev/null
+++ b/src/pulsecore/strbuf.c
@@ -0,0 +1,184 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <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_tostring(pa_strbuf *sb) {
+ char *t, *e;
+ struct chunk *c;
+
+ pa_assert(sb);
+
+ e = t = pa_xnew(char, 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_tostring() */
+char *pa_strbuf_tostring_free(pa_strbuf *sb) {
+ char *t;
+
+ pa_assert(sb);
+ t = pa_strbuf_tostring(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 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 */
+int pa_strbuf_printf(pa_strbuf *sb, const char *format, ...) {
+ int 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 && r < size) {
+ c->length = r;
+ append(sb, c);
+ return r;
+ }
+
+ if (r > -1) /* glibc 2.1 */
+ size = r+1;
+ else /* glibc 2.0 */
+ size *= 2;
+ }
+}
diff --git a/src/pulsecore/strbuf.h b/src/pulsecore/strbuf.h
new file mode 100644
index 00000000..1c0850b1
--- /dev/null
+++ b/src/pulsecore/strbuf.h
@@ -0,0 +1,40 @@
+#ifndef foostrbufhfoo
+#define foostrbufhfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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 <pulsecore/gccmacro.h>
+
+typedef struct pa_strbuf pa_strbuf;
+
+pa_strbuf *pa_strbuf_new(void);
+void pa_strbuf_free(pa_strbuf *sb);
+char *pa_strbuf_tostring(pa_strbuf *sb);
+char *pa_strbuf_tostring_free(pa_strbuf *sb);
+
+int 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);
+
+#endif
diff --git a/src/pulsecore/strlist.c b/src/pulsecore/strlist.c
new file mode 100644
index 00000000..ac83f6b1
--- /dev/null
+++ b/src/pulsecore/strlist.c
@@ -0,0 +1,163 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <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_tostring(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_tostring_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 (!strcmp(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;
+}
diff --git a/src/pulsecore/strlist.h b/src/pulsecore/strlist.h
new file mode 100644
index 00000000..6e6e2d4a
--- /dev/null
+++ b/src/pulsecore/strlist.h
@@ -0,0 +1,52 @@
+#ifndef foostrlisthfoo
+#define foostrlisthfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+typedef struct pa_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 stringes. Returned memory has to be freed with pa_xfree() */
+char *pa_strlist_tostring(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);
+
+#endif
diff --git a/src/pulsecore/tagstruct.c b/src/pulsecore/tagstruct.c
new file mode 100644
index 00000000..556fe806
--- /dev/null
+++ b/src/pulsecore/tagstruct.c
@@ -0,0 +1,673 @@
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <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/winsock.h>
+#include <pulsecore/macro.h>
+
+#include "tagstruct.h"
+
+struct pa_tagstruct {
+ uint8_t *data;
+ size_t length, allocated;
+ size_t rindex;
+
+ int dynamic;
+};
+
+pa_tagstruct *pa_tagstruct_new(const uint8_t* data, size_t length) {
+ pa_tagstruct*t;
+
+ pa_assert(!data || (data && length));
+
+ t = pa_xnew(pa_tagstruct, 1);
+ t->data = (uint8_t*) data;
+ t->allocated = t->length = data ? length : 0;
+ t->rindex = 0;
+ t->dynamic = !data;
+
+ return t;
+}
+
+void pa_tagstruct_free(pa_tagstruct*t) {
+ pa_assert(t);
+
+ if (t->dynamic)
+ pa_xfree(t->data);
+ pa_xfree(t);
+}
+
+uint8_t* pa_tagstruct_free_data(pa_tagstruct*t, size_t *l) {
+ uint8_t *p;
+
+ pa_assert(t);
+ pa_assert(t->dynamic);
+ pa_assert(l);
+
+ p = t->data;
+ *l = t->length;
+ pa_xfree(t);
+ return p;
+}
+
+static void extend(pa_tagstruct*t, size_t l) {
+ pa_assert(t);
+ pa_assert(t->dynamic);
+
+ if (t->length+l <= t->allocated)
+ return;
+
+ t->data = pa_xrealloc(t->data, t->allocated = t->length+l+100);
+}
+
+void pa_tagstruct_puts(pa_tagstruct*t, const char *s) {
+ size_t l;
+ pa_assert(t);
+
+ if (s) {
+ l = strlen(s)+2;
+ extend(t, l);
+ t->data[t->length] = PA_TAG_STRING;
+ strcpy((char*) (t->data+t->length+1), s);
+ t->length += l;
+ } else {
+ extend(t, 1);
+ t->data[t->length] = PA_TAG_STRING_NULL;
+ t->length += 1;
+ }
+}
+
+void pa_tagstruct_putu32(pa_tagstruct*t, uint32_t i) {
+ pa_assert(t);
+
+ extend(t, 5);
+ t->data[t->length] = PA_TAG_U32;
+ i = htonl(i);
+ memcpy(t->data+t->length+1, &i, 4);
+ t->length += 5;
+}
+
+void pa_tagstruct_putu8(pa_tagstruct*t, uint8_t c) {
+ pa_assert(t);
+
+ extend(t, 2);
+ t->data[t->length] = PA_TAG_U8;
+ *(t->data+t->length+1) = c;
+ t->length += 2;
+}
+
+void pa_tagstruct_put_sample_spec(pa_tagstruct *t, const pa_sample_spec *ss) {
+ uint32_t rate;
+
+ pa_assert(t);
+ pa_assert(ss);
+
+ extend(t, 7);
+ t->data[t->length] = PA_TAG_SAMPLE_SPEC;
+ t->data[t->length+1] = (uint8_t) ss->format;
+ t->data[t->length+2] = ss->channels;
+ rate = htonl(ss->rate);
+ memcpy(t->data+t->length+3, &rate, 4);
+ t->length += 7;
+}
+
+void pa_tagstruct_put_arbitrary(pa_tagstruct *t, const void *p, size_t length) {
+ uint32_t tmp;
+
+ pa_assert(t);
+ pa_assert(p);
+
+ extend(t, 5+length);
+ t->data[t->length] = PA_TAG_ARBITRARY;
+ tmp = htonl(length);
+ memcpy(t->data+t->length+1, &tmp, 4);
+ if (length)
+ memcpy(t->data+t->length+5, p, length);
+ t->length += 5+length;
+}
+
+void pa_tagstruct_put_boolean(pa_tagstruct*t, int b) {
+ pa_assert(t);
+
+ extend(t, 1);
+ t->data[t->length] = b ? PA_TAG_BOOLEAN_TRUE : PA_TAG_BOOLEAN_FALSE;
+ t->length += 1;
+}
+
+void pa_tagstruct_put_timeval(pa_tagstruct*t, const struct timeval *tv) {
+ uint32_t tmp;
+ pa_assert(t);
+
+ extend(t, 9);
+ t->data[t->length] = PA_TAG_TIMEVAL;
+ tmp = htonl(tv->tv_sec);
+ memcpy(t->data+t->length+1, &tmp, 4);
+ tmp = htonl(tv->tv_usec);
+ memcpy(t->data+t->length+5, &tmp, 4);
+ t->length += 9;
+}
+
+void pa_tagstruct_put_usec(pa_tagstruct*t, pa_usec_t u) {
+ uint32_t tmp;
+
+ pa_assert(t);
+
+ extend(t, 9);
+ t->data[t->length] = PA_TAG_USEC;
+ tmp = htonl((uint32_t) (u >> 32));
+ memcpy(t->data+t->length+1, &tmp, 4);
+ tmp = htonl((uint32_t) u);
+ memcpy(t->data+t->length+5, &tmp, 4);
+ t->length += 9;
+}
+
+void pa_tagstruct_putu64(pa_tagstruct*t, uint64_t u) {
+ uint32_t tmp;
+
+ pa_assert(t);
+
+ extend(t, 9);
+ t->data[t->length] = PA_TAG_U64;
+ tmp = htonl((uint32_t) (u >> 32));
+ memcpy(t->data+t->length+1, &tmp, 4);
+ tmp = htonl((uint32_t) u);
+ memcpy(t->data+t->length+5, &tmp, 4);
+ t->length += 9;
+}
+
+void pa_tagstruct_puts64(pa_tagstruct*t, int64_t u) {
+ uint32_t tmp;
+
+ pa_assert(t);
+
+ extend(t, 9);
+ t->data[t->length] = PA_TAG_S64;
+ tmp = htonl((uint32_t) ((uint64_t) u >> 32));
+ memcpy(t->data+t->length+1, &tmp, 4);
+ tmp = htonl((uint32_t) ((uint64_t) u));
+ memcpy(t->data+t->length+5, &tmp, 4);
+ t->length += 9;
+}
+
+void pa_tagstruct_put_channel_map(pa_tagstruct *t, const pa_channel_map *map) {
+ unsigned i;
+
+ pa_assert(t);
+ extend(t, 2 + map->channels);
+
+ t->data[t->length++] = PA_TAG_CHANNEL_MAP;
+ t->data[t->length++] = map->channels;
+
+ for (i = 0; i < map->channels; i ++)
+ t->data[t->length++] = (uint8_t) map->map[i];
+}
+
+void pa_tagstruct_put_cvolume(pa_tagstruct *t, const pa_cvolume *cvolume) {
+ unsigned i;
+ pa_volume_t vol;
+
+ pa_assert(t);
+ extend(t, 2 + cvolume->channels * sizeof(pa_volume_t));
+
+ t->data[t->length++] = PA_TAG_CVOLUME;
+ t->data[t->length++] = cvolume->channels;
+
+ for (i = 0; i < cvolume->channels; i ++) {
+ vol = htonl(cvolume->values[i]);
+ memcpy(t->data + t->length, &vol, sizeof(pa_volume_t));
+ t->length += sizeof(pa_volume_t);
+ }
+}
+
+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 (t->rindex+2 > t->length)
+ return -1;
+
+ if (t->data[t->rindex] != PA_TAG_STRING)
+ return -1;
+
+ error = 1;
+ for (n = 0, c = (char*) (t->data+t->rindex+1); t->rindex+1+n < t->length; n++, c++)
+ if (!*c) {
+ error = 0;
+ break;
+ }
+
+ if (error)
+ return -1;
+
+ *s = (char*) (t->data+t->rindex+1);
+
+ t->rindex += n+2;
+ return 0;
+}
+
+int pa_tagstruct_getu32(pa_tagstruct*t, uint32_t *i) {
+ pa_assert(t);
+ pa_assert(i);
+
+ if (t->rindex+5 > t->length)
+ return -1;
+
+ if (t->data[t->rindex] != PA_TAG_U32)
+ return -1;
+
+ memcpy(i, t->data+t->rindex+1, 4);
+ *i = ntohl(*i);
+ t->rindex += 5;
+ return 0;
+}
+
+int pa_tagstruct_getu8(pa_tagstruct*t, uint8_t *c) {
+ pa_assert(t);
+ pa_assert(c);
+
+ if (t->rindex+2 > t->length)
+ return -1;
+
+ if (t->data[t->rindex] != PA_TAG_U8)
+ return -1;
+
+ *c = t->data[t->rindex+1];
+ t->rindex +=2;
+ return 0;
+}
+
+int pa_tagstruct_get_sample_spec(pa_tagstruct *t, pa_sample_spec *ss) {
+ pa_assert(t);
+ pa_assert(ss);
+
+ if (t->rindex+7 > t->length)
+ return -1;
+
+ if (t->data[t->rindex] != PA_TAG_SAMPLE_SPEC)
+ return -1;
+
+ ss->format = t->data[t->rindex+1];
+ ss->channels = t->data[t->rindex+2];
+ memcpy(&ss->rate, t->data+t->rindex+3, 4);
+ ss->rate = ntohl(ss->rate);
+
+ t->rindex += 7;
+ return 0;
+}
+
+int pa_tagstruct_get_arbitrary(pa_tagstruct *t, const void **p, size_t length) {
+ uint32_t len;
+
+ pa_assert(t);
+ pa_assert(p);
+
+ if (t->rindex+5+length > t->length)
+ return -1;
+
+ if (t->data[t->rindex] != PA_TAG_ARBITRARY)
+ return -1;
+
+ memcpy(&len, t->data+t->rindex+1, 4);
+ if (ntohl(len) != length)
+ return -1;
+
+ *p = t->data+t->rindex+5;
+ t->rindex += 5+length;
+ return 0;
+}
+
+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(t->dynamic);
+ pa_assert(l);
+
+ *l = t->length;
+ return t->data;
+}
+
+int pa_tagstruct_get_boolean(pa_tagstruct*t, int *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 = 1;
+ else if (t->data[t->rindex] == PA_TAG_BOOLEAN_FALSE)
+ *b = 0;
+ else
+ return -1;
+
+ t->rindex +=1;
+ return 0;
+}
+
+int pa_tagstruct_get_timeval(pa_tagstruct*t, struct timeval *tv) {
+
+ pa_assert(t);
+ pa_assert(tv);
+
+ if (t->rindex+9 > t->length)
+ return -1;
+
+ if (t->data[t->rindex] != PA_TAG_TIMEVAL)
+ return -1;
+
+ memcpy(&tv->tv_sec, t->data+t->rindex+1, 4);
+ tv->tv_sec = ntohl(tv->tv_sec);
+ memcpy(&tv->tv_usec, t->data+t->rindex+5, 4);
+ tv->tv_usec = ntohl(tv->tv_usec);
+ t->rindex += 9;
+ return 0;
+}
+
+int pa_tagstruct_get_usec(pa_tagstruct*t, pa_usec_t *u) {
+ uint32_t tmp;
+
+ pa_assert(t);
+ pa_assert(u);
+
+ if (t->rindex+9 > t->length)
+ return -1;
+
+ if (t->data[t->rindex] != PA_TAG_USEC)
+ return -1;
+
+ memcpy(&tmp, t->data+t->rindex+1, 4);
+ *u = (pa_usec_t) ntohl(tmp) << 32;
+ memcpy(&tmp, t->data+t->rindex+5, 4);
+ *u |= (pa_usec_t) ntohl(tmp);
+ t->rindex +=9;
+ return 0;
+}
+
+int pa_tagstruct_getu64(pa_tagstruct*t, uint64_t *u) {
+ uint32_t tmp;
+
+ pa_assert(t);
+ pa_assert(u);
+
+ if (t->rindex+9 > t->length)
+ return -1;
+
+ if (t->data[t->rindex] != PA_TAG_U64)
+ return -1;
+
+ memcpy(&tmp, t->data+t->rindex+1, 4);
+ *u = (uint64_t) ntohl(tmp) << 32;
+ memcpy(&tmp, t->data+t->rindex+5, 4);
+ *u |= (uint64_t) ntohl(tmp);
+ t->rindex +=9;
+ return 0;
+}
+
+int pa_tagstruct_gets64(pa_tagstruct*t, int64_t *u) {
+ uint32_t tmp;
+
+ pa_assert(t);
+ pa_assert(u);
+
+ if (t->rindex+9 > t->length)
+ return -1;
+
+ if (t->data[t->rindex] != PA_TAG_S64)
+ return -1;
+
+ memcpy(&tmp, t->data+t->rindex+1, 4);
+ *u = (int64_t) ((uint64_t) ntohl(tmp) << 32);
+ memcpy(&tmp, t->data+t->rindex+5, 4);
+ *u |= (int64_t) ntohl(tmp);
+ t->rindex +=9;
+ return 0;
+}
+
+int pa_tagstruct_get_channel_map(pa_tagstruct *t, pa_channel_map *map) {
+ unsigned i;
+
+ pa_assert(t);
+ pa_assert(map);
+
+ if (t->rindex+2 > t->length)
+ return -1;
+
+ if (t->data[t->rindex] != PA_TAG_CHANNEL_MAP)
+ return -1;
+
+ if ((map->channels = t->data[t->rindex+1]) > PA_CHANNELS_MAX)
+ return -1;
+
+ if (t->rindex+2+map->channels > t->length)
+ return -1;
+
+ for (i = 0; i < map->channels; i ++)
+ map->map[i] = (int8_t) t->data[t->rindex + 2 + i];
+
+ t->rindex += 2 + map->channels;
+ return 0;
+}
+
+int pa_tagstruct_get_cvolume(pa_tagstruct *t, pa_cvolume *cvolume) {
+ unsigned i;
+ pa_volume_t vol;
+
+ pa_assert(t);
+ pa_assert(cvolume);
+
+ if (t->rindex+2 > t->length)
+ return -1;
+
+ if (t->data[t->rindex] != PA_TAG_CVOLUME)
+ return -1;
+
+ if ((cvolume->channels = t->data[t->rindex+1]) > PA_CHANNELS_MAX)
+ return -1;
+
+ if (t->rindex+2+cvolume->channels*sizeof(pa_volume_t) > t->length)
+ return -1;
+
+ for (i = 0; i < cvolume->channels; i ++) {
+ memcpy(&vol, t->data + t->rindex + 2 + i * sizeof(pa_volume_t), sizeof(pa_volume_t));
+ cvolume->values[i] = (pa_volume_t) ntohl(vol);
+ }
+
+ t->rindex += 2 + cvolume->channels * sizeof(pa_volume_t);
+ return 0;
+}
+
+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;
+
+ 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, int*));
+ 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;
+
+ 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 00000000..e9bb9ac8
--- /dev/null
+++ b/src/pulsecore/tagstruct.h
@@ -0,0 +1,95 @@
+#ifndef footagstructhfoo
+#define footagstructhfoo
+
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <inttypes.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <time.h>
+
+#include <pulse/sample.h>
+#include <pulse/channelmap.h>
+#include <pulse/volume.h>
+
+typedef struct pa_tagstruct pa_tagstruct;
+
+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_tagstruct *pa_tagstruct_new(const uint8_t* data, size_t length);
+void pa_tagstruct_free(pa_tagstruct*t);
+uint8_t* pa_tagstruct_free_data(pa_tagstruct*t, size_t *l);
+
+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, int 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);
+
+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, int *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);
+
+
+#endif
diff --git a/src/pulsecore/thread-mq.c b/src/pulsecore/thread-mq.c
new file mode 100644
index 00000000..9b879425
--- /dev/null
+++ b/src/pulsecore/thread-mq.c
@@ -0,0 +1,112 @@
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <unistd.h>
+#include <errno.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/atomic.h>
+#include <pulsecore/once.h>
+#include <pulsecore/log.h>
+#include <pulsecore/thread.h>
+#include <pulsecore/semaphore.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/flist.h>
+
+#include "thread-mq.h"
+
+PA_STATIC_TLS_DECLARE_NO_FREE(thread_mq);
+
+static void asyncmsgq_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(pa_asyncmsgq_get_fd(q->outq) == fd);
+ pa_assert(events == PA_IO_EVENT_INPUT);
+
+ pa_asyncmsgq_ref(aq = q->outq);
+ pa_asyncmsgq_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;
+
+ ret = pa_asyncmsgq_dispatch(object, code, data, offset, &chunk);
+ pa_asyncmsgq_done(aq, ret);
+ }
+
+ if (pa_asyncmsgq_before_poll(aq) == 0)
+ break;
+ }
+
+ pa_asyncmsgq_unref(aq);
+}
+
+void pa_thread_mq_init(pa_thread_mq *q, pa_mainloop_api *mainloop) {
+ pa_assert(q);
+ pa_assert(mainloop);
+
+ q->mainloop = mainloop;
+ pa_assert_se(q->inq = pa_asyncmsgq_new(0));
+ pa_assert_se(q->outq = pa_asyncmsgq_new(0));
+
+ pa_assert_se(pa_asyncmsgq_before_poll(q->outq) == 0);
+ pa_assert_se(q->io_event = mainloop->io_new(mainloop, pa_asyncmsgq_get_fd(q->outq), PA_IO_EVENT_INPUT, asyncmsgq_cb, q));
+}
+
+void pa_thread_mq_done(pa_thread_mq *q) {
+ pa_assert(q);
+
+ q->mainloop->io_free(q->io_event);
+ q->io_event = NULL;
+
+ pa_asyncmsgq_unref(q->inq);
+ pa_asyncmsgq_unref(q->outq);
+ q->inq = q->outq = NULL;
+
+ q->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 00000000..13b6e01f
--- /dev/null
+++ b/src/pulsecore/thread-mq.h
@@ -0,0 +1,49 @@
+#ifndef foopulsethreadmqhfoo
+#define foopulsethreadmqhfoo
+
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <pulse/mainloop-api.h>
+#include <pulsecore/asyncmsgq.h>
+
+/* Two way communication between a thread and a mainloop. Before the
+ * thread is started a pa_pthread_mq should be initialized and than
+ * attached to the thread using pa_thread_mq_install(). */
+
+typedef struct pa_thread_mq {
+ pa_mainloop_api *mainloop;
+ pa_asyncmsgq *inq, *outq;
+ pa_io_event *io_event;
+} pa_thread_mq;
+
+void pa_thread_mq_init(pa_thread_mq *q, pa_mainloop_api *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);
+
+#endif
diff --git a/src/pulsecore/thread-posix.c b/src/pulsecore/thread-posix.c
new file mode 100644
index 00000000..7f43f43e
--- /dev/null
+++ b/src/pulsecore/thread-posix.c
@@ -0,0 +1,197 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pthread.h>
+#include <sched.h>
+#include <errno.h>
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/mutex.h>
+#include <pulsecore/once.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;
+};
+
+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);
+}
+
+PA_STATIC_TLS_DECLARE(current_thread, thread_free_cb);
+
+static void* internal_thread_func(void *userdata) {
+ pa_thread *t = userdata;
+ pa_assert(t);
+
+ 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(pa_thread_func_t thread_func, void *userdata) {
+ pa_thread *t;
+
+ pa_assert(thread_func);
+
+ t = pa_xnew(pa_thread, 1);
+ t->thread_func = thread_func;
+ t->userdata = userdata;
+ pa_atomic_store(&t->running, 0);
+
+ 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);
+}
+
+int pa_thread_join(pa_thread *t) {
+ pa_assert(t);
+
+ 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_xnew(pa_thread, 1);
+ t->id = pthread_self();
+ t->thread_func = NULL;
+ t->userdata = NULL;
+ 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_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 00000000..cad1420a
--- /dev/null
+++ b/src/pulsecore/thread-win32.c
@@ -0,0 +1,216 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+
+#include <windows.h>
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/log.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(pa_thread_func_t thread_func, void *userdata) {
+ pa_thread *t;
+
+ 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, NULL);
+
+ 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);
+}
+
+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_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 00000000..54ef320e
--- /dev/null
+++ b/src/pulsecore/thread.h
@@ -0,0 +1,112 @@
+#ifndef foopulsethreadhfoo
+#define foopulsethreadhfoo
+
+/* $Id$ */
+
+/***
+ 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 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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/def.h>
+#include <pulsecore/once.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(pa_thread_func_t thread_func, void *userdata);
+void pa_thread_free(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);
+
+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 *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 (!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
+
+#ifdef HAVE_TLS_BUILTIN
+/* 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 00000000..4cebded4
--- /dev/null
+++ b/src/pulsecore/time-smoother.c
@@ -0,0 +1,382 @@
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+
+#include <pulse/sample.h>
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/macro.h>
+
+#include "time-smoother.h"
+
+#define HISTORY_MAX 50
+
+/*
+ * 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 recieved 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 surpress
+ * 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_bool_t monotonic;
+
+ 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 */
+
+ /* 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;
+
+ /* Cached parameters for our interpolation polynomial y=ax^3+b^2+cx */
+ double a, b, c;
+ pa_bool_t abc_valid;
+
+ pa_bool_t paused;
+ pa_usec_t pause_time;
+};
+
+pa_smoother* pa_smoother_new(pa_usec_t adjust_time, pa_usec_t history_time, pa_bool_t monotonic) {
+ pa_smoother *s;
+
+ pa_assert(adjust_time > 0);
+ pa_assert(history_time > 0);
+
+ s = pa_xnew(pa_smoother, 1);
+ s->adjust_time = adjust_time;
+ s->history_time = history_time;
+ s->time_offset = 0;
+ s->monotonic = monotonic;
+
+ s->px = s->py = 0;
+ s->dp = 1;
+
+ s->ex = s->ey = 0;
+ s->de = 1;
+
+ s->history_idx = 0;
+ s->n_history = 0;
+
+ s->last_y = 0;
+
+ s->abc_valid = FALSE;
+
+ s->paused = FALSE;
+
+ return s;
+}
+
+void pa_smoother_free(pa_smoother* s) {
+ pa_assert(s);
+
+ pa_xfree(s);
+}
+
+static void drop_old(pa_smoother *s, pa_usec_t x) {
+ unsigned j;
+
+ /* First drop items from history which are too old, but make sure
+ * to always keep two entries in the history */
+
+ for (j = s->n_history; j > 2; j--) {
+
+ 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 */
+ s->history_idx ++;
+ while (s->history_idx >= HISTORY_MAX)
+ s->history_idx -= HISTORY_MAX;
+
+ s->n_history --;
+ }
+}
+
+static void add_to_history(pa_smoother *s, pa_usec_t x, pa_usec_t y) {
+ unsigned j;
+ pa_assert(s);
+
+ drop_old(s, x);
+
+ /* Calculate position for new entry */
+ j = s->history_idx + s->n_history;
+ while (j >= HISTORY_MAX)
+ j -= HISTORY_MAX;
+
+ /* 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;
+ 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;
+
+ drop_old(s, x);
+
+ /* First, calculate average of all measurements */
+ i = s->history_idx;
+ for (j = s->n_history; j > 0; j--) {
+
+ ax += s->history_x[i];
+ ay += s->history_y[i];
+ c++;
+
+ i++;
+ while (i >= HISTORY_MAX)
+ i -= HISTORY_MAX;
+ }
+
+ /* Too few measurements, assume gradient of 1 */
+ if (c < 2)
+ return 1;
+
+ 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;
+
+ i++;
+ while (i >= HISTORY_MAX)
+ i -= HISTORY_MAX;
+ }
+
+ r = (double) k / t;
+
+ return s->monotonic && r < 0 ? 0 : r;
+}
+
+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) {
+ 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) (s->dp * (x - s->px));
+
+ if (t < 0)
+ t = 0;
+
+ *y = (pa_usec_t) t;
+
+ if (deriv)
+ *deriv = s->dp;
+
+ } else {
+
+ if (!s->abc_valid) {
+ pa_usec_t ex, ey, px, py;
+ int64_t kx, ky;
+ double de, dp;
+
+ /* Ok, we're not yet on track, thus let's interpolate, and
+ * make sure that the first derivative is smooth */
+
+ /* 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 symplify 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+b^2+cx */
+ s->c = de;
+ s->b = (((double) (3*ky)/kx - dp - 2*de)) / kx;
+ s->a = (dp/kx - 2*s->b - de/kx) / (3*kx);
+
+ s->abc_valid = TRUE;
+ }
+
+ /* Move to origin */
+ x -= s->ex;
+
+ /* Horner scheme */
+ *y = (pa_usec_t) ((double) x * (s->c + (double) x * (s->b + (double) x * s->a)));
+
+ /* Move back from origin */
+ *y += s->ey;
+
+ /* Horner scheme */
+ if (deriv)
+ *deriv = s->c + ((double) x * (s->b*2 + (double) x * s->a*3));
+ }
+
+ /* Guarantee monotonicity */
+ if (s->monotonic) {
+
+ if (*y < s->last_y)
+ *y = s->last_y;
+ else
+ s->last_y = *y;
+
+ 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;
+
+ pa_assert(s);
+ pa_assert(x >= s->time_offset);
+
+ /* Fix up x value */
+ if (s->paused)
+ x = s->pause_time;
+
+ pa_assert(x >= s->time_offset);
+ x -= s->time_offset;
+
+ pa_assert(x >= s->ex);
+
+ /* 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;
+
+ /* 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 */
+ s->px = x + s->adjust_time;
+ s->py = y + s->dp *s->adjust_time;
+
+ s->abc_valid = FALSE;
+}
+
+pa_usec_t pa_smoother_get(pa_smoother *s, pa_usec_t x) {
+ pa_usec_t y;
+
+ pa_assert(s);
+ pa_assert(x >= s->time_offset);
+
+ /* Fix up x value */
+ if (s->paused)
+ x = s->pause_time;
+
+ pa_assert(x >= s->time_offset);
+ x -= s->time_offset;
+
+ pa_assert(x >= s->ex);
+
+ estimate(s, x, &y, NULL);
+ return y;
+}
+
+void pa_smoother_set_time_offset(pa_smoother *s, pa_usec_t offset) {
+ pa_assert(s);
+
+ s->time_offset = offset;
+}
+
+void pa_smoother_pause(pa_smoother *s, pa_usec_t x) {
+ pa_assert(s);
+
+ if (s->paused)
+ return;
+
+ s->paused = TRUE;
+ s->pause_time = x;
+}
+
+void pa_smoother_resume(pa_smoother *s, pa_usec_t x) {
+ pa_assert(s);
+
+ if (!s->paused)
+ return;
+
+ pa_assert(x >= s->pause_time);
+
+ s->paused = FALSE;
+ s->time_offset += x - s->pause_time;
+}
diff --git a/src/pulsecore/time-smoother.h b/src/pulsecore/time-smoother.h
new file mode 100644
index 00000000..8b8512e2
--- /dev/null
+++ b/src/pulsecore/time-smoother.h
@@ -0,0 +1,43 @@
+#ifndef foopulsetimesmootherhfoo
+#define foopulsetimesmootherhfoo
+
+/* $Id$ */
+
+/***
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <pulsecore/macro.h>
+#include <pulse/sample.h>
+
+typedef struct pa_smoother pa_smoother;
+
+pa_smoother* pa_smoother_new(pa_usec_t adjust_time, pa_usec_t history_time, pa_bool_t monotonic);
+void pa_smoother_free(pa_smoother* s);
+
+void pa_smoother_put(pa_smoother *s, pa_usec_t x, pa_usec_t y);
+pa_usec_t pa_smoother_get(pa_smoother *s, pa_usec_t x);
+
+void pa_smoother_set_time_offset(pa_smoother *s, pa_usec_t offset);
+
+void pa_smoother_pause(pa_smoother *s, pa_usec_t x);
+void pa_smoother_resume(pa_smoother *s, pa_usec_t x);
+
+#endif
diff --git a/src/pulsecore/tokenizer.c b/src/pulsecore/tokenizer.c
new file mode 100644
index 00000000..f79c19c5
--- /dev/null
+++ b/src/pulsecore/tokenizer.c
@@ -0,0 +1,90 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/dynarray.h>
+#include <pulsecore/gccmacro.h>
+#include <pulsecore/macro.h>
+
+#include "tokenizer.h"
+
+static void token_free(void *p, PA_GCC_UNUSED void *userdata) {
+ pa_xfree(p);
+}
+
+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();
+ 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, token_free, NULL);
+}
+
+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 00000000..68a8db49
--- /dev/null
+++ b/src/pulsecore/tokenizer.h
@@ -0,0 +1,34 @@
+#ifndef footokenizerhfoo
+#define footokenizerhfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+typedef struct pa_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/winsock.h b/src/pulsecore/winsock.h
new file mode 100644
index 00000000..0352bf4d
--- /dev/null
+++ b/src/pulsecore/winsock.h
@@ -0,0 +1,26 @@
+#ifndef foowinsockhfoo
+#define foowinsockhfoo
+
+#ifdef HAVE_WINSOCK2_H
+#include <winsock2.h>
+
+#define ESHUTDOWN WSAESHUTDOWN
+#define ECONNRESET WSAECONNRESET
+#define ECONNABORTED WSAECONNABORTED
+#define ENETRESET WSAENETRESET
+#define EINPROGRESS WSAEINPROGRESS
+#define EAFNOSUPPORT WSAEAFNOSUPPORT
+#define ETIMEDOUT WSAETIMEDOUT
+#define ECONNREFUSED WSAECONNREFUSED
+#define EHOSTUNREACH WSAEHOSTUNREACH
+#define EWOULDBLOCK WSAEWOULDBLOCK
+
+typedef long suseconds_t;
+
+#endif
+
+#ifdef HAVE_WS2TCPIP_H
+#include <ws2tcpip.h>
+#endif
+
+#endif
diff --git a/src/pulsecore/x11prop.c b/src/pulsecore/x11prop.c
new file mode 100644
index 00000000..a740e39b
--- /dev/null
+++ b/src/pulsecore/x11prop.c
@@ -0,0 +1,71 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+
+#include "x11prop.h"
+
+void pa_x11_set_prop(Display *d, const char *name, const char *data) {
+ Atom a = XInternAtom(d, name, False);
+ XChangeProperty(d, RootWindow(d, 0), a, XA_STRING, 8, PropModeReplace, (const unsigned char*) data, strlen(data)+1);
+}
+
+void pa_x11_del_prop(Display *d, const char *name) {
+ Atom a = XInternAtom(d, name, False);
+ XDeleteProperty(d, RootWindow(d, 0), a);
+}
+
+char* pa_x11_get_prop(Display *d, const char *name, char *p, size_t l) {
+ Atom actual_type;
+ int actual_format;
+ unsigned long nitems;
+ unsigned long nbytes_after;
+ unsigned char *prop = NULL;
+ char *ret = NULL;
+
+ Atom a = XInternAtom(d, name, False);
+ if (XGetWindowProperty(d, RootWindow(d, 0), a, 0, (l+2)/4, False, XA_STRING, &actual_type, &actual_format, &nitems, &nbytes_after, &prop) != Success)
+ goto finish;
+
+ if (actual_type != XA_STRING)
+ goto finish;
+
+ memcpy(p, prop, nitems);
+ p[nitems] = 0;
+
+ ret = p;
+
+finish:
+
+ if (prop)
+ XFree(prop);
+
+ return ret;
+}
diff --git a/src/pulsecore/x11prop.h b/src/pulsecore/x11prop.h
new file mode 100644
index 00000000..388c5a34
--- /dev/null
+++ b/src/pulsecore/x11prop.h
@@ -0,0 +1,35 @@
+#ifndef foox11prophfoo
+#define foox11prophfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <sys/types.h>
+
+#include <X11/Xlib.h>
+
+void pa_x11_set_prop(Display *d, const char *name, const char *data);
+void pa_x11_del_prop(Display *d, const char *name);
+char* pa_x11_get_prop(Display *d, 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 00000000..800a9458
--- /dev/null
+++ b/src/pulsecore/x11wrap.c
@@ -0,0 +1,277 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/llist.h>
+#include <pulsecore/log.h>
+#include <pulsecore/props.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;
+ int (*callback)(pa_x11_wrapper *w, XEvent *e, void *userdata);
+ void *userdata;
+};
+
+/* Dispatch all pending X11 events */
+static void work(pa_x11_wrapper *w) {
+ pa_assert(w);
+ pa_assert(PA_REFCNT_VALUE(w) >= 1);
+
+ while (XPending(w->display)) {
+ pa_x11_client *c;
+ XEvent e;
+ XNextEvent(w->display, &e);
+
+ for (c = w->clients; c; c = c->next) {
+ pa_assert(c->callback);
+ if (c->callback(w, &e, c->userdata) != 0)
+ break;
+ }
+ }
+}
+
+/* IO notification event for the X11 display connection */
+static void display_io_event(pa_mainloop_api *m, pa_io_event *e, int fd, PA_GCC_UNUSED 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_GCC_UNUSED 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_property_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_property_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_property_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)
+ 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;
+}
+
+pa_x11_client* pa_x11_client_new(pa_x11_wrapper *w, int (*cb)(pa_x11_wrapper *w, XEvent *e, void *userdata), 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->callback = 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 00000000..9bed2fce
--- /dev/null
+++ b/src/pulsecore/x11wrap.h
@@ -0,0 +1,54 @@
+#ifndef foox11wraphfoo
+#define foox11wraphfoo
+
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ 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 <X11/Xlib.h>
+
+#include <pulsecore/core.h>
+
+typedef struct pa_x11_wrapper pa_x11_wrapper;
+
+/* Return the X11 wrapper for this core. In case no wrapper was
+ existant 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);
+
+typedef struct pa_x11_client pa_x11_client;
+
+/* Register an X11 client, that is called for each X11 event */
+pa_x11_client* pa_x11_client_new(pa_x11_wrapper *w, int (*cb)(pa_x11_wrapper *w, XEvent *e, void *userdata), void *userdata);
+
+/* Free an X11 client object */
+void pa_x11_client_free(pa_x11_client *c);
+
+#endif
diff --git a/src/tests/Makefile b/src/tests/Makefile
new file mode 120000
index 00000000..c110232d
--- /dev/null
+++ b/src/tests/Makefile
@@ -0,0 +1 @@
+../pulse/Makefile \ No newline at end of file
diff --git a/src/tests/asyncmsgq-test.c b/src/tests/asyncmsgq-test.c
new file mode 100644
index 00000000..380c5e7f
--- /dev/null
+++ b/src/tests/asyncmsgq-test.c
@@ -0,0 +1,110 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <assert.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <pulse/util.h>
+#include <pulse/xmalloc.h>
+#include <pulsecore/asyncmsgq.h>
+#include <pulsecore/thread.h>
+#include <pulsecore/log.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/macro.h>
+
+enum {
+ OPERATION_A,
+ OPERATION_B,
+ OPERATION_C,
+ QUIT
+};
+
+static void the_thread(void *_q) {
+ pa_asyncmsgq *q = _q;
+ int quit = 0;
+
+ do {
+ int code = 0;
+
+ pa_assert_se(pa_asyncmsgq_get(q, NULL, &code, NULL, NULL, NULL, 1) == 0);
+
+ switch (code) {
+
+ case OPERATION_A:
+ printf("Operation A\n");
+ break;
+
+ case OPERATION_B:
+ printf("Operation B\n");
+ break;
+
+ case OPERATION_C:
+ printf("Operation C\n");
+ break;
+
+ case QUIT:
+ printf("quit\n");
+ quit = 1;
+ break;
+ }
+
+ pa_asyncmsgq_done(q, 0);
+
+ } while (!quit);
+}
+
+int main(int argc, char *argv[]) {
+ pa_asyncmsgq *q;
+ pa_thread *t;
+
+ pa_assert_se(q = pa_asyncmsgq_new(0));
+
+ pa_assert_se(t = pa_thread_new(the_thread, q));
+
+ printf("Operation A post\n");
+ pa_asyncmsgq_post(q, NULL, OPERATION_A, NULL, 0, NULL, NULL);
+
+ pa_thread_yield();
+
+ printf("Operation B post\n");
+ pa_asyncmsgq_post(q, NULL, OPERATION_B, NULL, 0, NULL, NULL);
+
+ pa_thread_yield();
+
+ printf("Operation C send\n");
+ pa_asyncmsgq_send(q, NULL, OPERATION_C, NULL, 0, NULL);
+
+ pa_thread_yield();
+
+ printf("Quit post\n");
+ pa_asyncmsgq_post(q, NULL, QUIT, NULL, 0, NULL, NULL);
+
+ pa_thread_free(t);
+
+ pa_asyncmsgq_unref(q);
+
+ return 0;
+}
diff --git a/src/tests/asyncq-test.c b/src/tests/asyncq-test.c
new file mode 100644
index 00000000..09b20047
--- /dev/null
+++ b/src/tests/asyncq-test.c
@@ -0,0 +1,87 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <assert.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <pulse/util.h>
+#include <pulse/xmalloc.h>
+#include <pulsecore/asyncq.h>
+#include <pulsecore/thread.h>
+#include <pulsecore/log.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/macro.h>
+
+static void producer(void *_q) {
+ pa_asyncq *q = _q;
+ int i;
+
+ for (i = 0; i < 1000; i++) {
+ printf("pushing %i\n", i);
+ pa_asyncq_push(q, PA_UINT_TO_PTR(i+1), 1);
+ }
+
+ pa_asyncq_push(q, PA_UINT_TO_PTR(-1), 1);
+ printf("pushed end\n");
+}
+
+static void consumer(void *_q) {
+ pa_asyncq *q = _q;
+ void *p;
+ int i;
+
+ sleep(1);
+
+ for (i = 0;; i++) {
+ p = pa_asyncq_pop(q, 1);
+
+ if (p == PA_UINT_TO_PTR(-1))
+ break;
+
+ pa_assert(p == PA_UINT_TO_PTR(i+1));
+
+ printf("popped %i\n", i);
+ }
+
+ printf("popped end\n");
+}
+
+int main(int argc, char *argv[]) {
+ pa_asyncq *q;
+ pa_thread *t1, *t2;
+
+ pa_assert_se(q = pa_asyncq_new(0));
+
+ pa_assert_se(t1 = pa_thread_new(producer, q));
+ pa_assert_se(t2 = pa_thread_new(consumer, q));
+
+ pa_thread_free(t1);
+ pa_thread_free(t2);
+
+ pa_asyncq_free(q, NULL);
+
+ return 0;
+}
diff --git a/src/tests/channelmap-test.c b/src/tests/channelmap-test.c
new file mode 100644
index 00000000..98f36b61
--- /dev/null
+++ b/src/tests/channelmap-test.c
@@ -0,0 +1,33 @@
+/* $Id$ */
+
+#include <stdio.h>
+#include <assert.h>
+
+#include <pulse/channelmap.h>
+#include <pulsecore/gccmacro.h>
+
+int main(PA_GCC_UNUSED int argc, PA_GCC_UNUSED char *argv[]) {
+ char cm[PA_CHANNEL_MAP_SNPRINT_MAX];
+ pa_channel_map map, map2;
+
+ pa_channel_map_init_auto(&map, 6, PA_CHANNEL_MAP_AIFF);
+
+ fprintf(stderr, "map: <%s>\n", pa_channel_map_snprint(cm, sizeof(cm), &map));
+
+ pa_channel_map_init_auto(&map, 6, PA_CHANNEL_MAP_AUX);
+
+ fprintf(stderr, "map: <%s>\n", pa_channel_map_snprint(cm, sizeof(cm), &map));
+
+ pa_channel_map_init_auto(&map, 6, PA_CHANNEL_MAP_ALSA);
+
+ fprintf(stderr, "map: <%s>\n", pa_channel_map_snprint(cm, sizeof(cm), &map));
+
+ pa_channel_map_parse(&map2, cm);
+
+ assert(pa_channel_map_equal(&map, &map2));
+
+ pa_channel_map_parse(&map2, "left,test");
+
+
+ return 0;
+}
diff --git a/src/tests/cpulimit-test.c b/src/tests/cpulimit-test.c
new file mode 100644
index 00000000..d582e9c5
--- /dev/null
+++ b/src/tests/cpulimit-test.c
@@ -0,0 +1,93 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <assert.h>
+#include <sys/time.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <signal.h>
+
+#include <pulse/mainloop.h>
+#include <pulsecore/gccmacro.h>
+
+#ifdef TEST2
+#include <pulse/mainloop-signal.h>
+#endif
+
+#include "../daemon/cpulimit.h"
+
+/* A simple example for testing the cpulimit subsystem */
+
+static time_t start;
+
+#ifdef TEST2
+
+static void func(pa_mainloop_api *m, PA_GCC_UNUSED pa_signal_event *e, PA_GCC_UNUSED int sig, PA_GCC_UNUSED void *userdata) {
+ time_t now;
+ time(&now);
+
+ if ((now - start) >= 30) {
+ m->quit(m, 1);
+ fprintf(stderr, "Test failed\n");
+ } else
+ raise(SIGUSR1);
+}
+
+#endif
+
+int main(PA_GCC_UNUSED int argc, PA_GCC_UNUSED char *argv[]) {
+ pa_mainloop *m;
+
+ m = pa_mainloop_new();
+ assert(m);
+
+ pa_cpu_limit_init(pa_mainloop_get_api(m));
+
+ time(&start);
+
+#ifdef TEST2
+ pa_signal_init(pa_mainloop_get_api(m));
+ pa_signal_new(SIGUSR1, func, NULL);
+ raise(SIGUSR1);
+ pa_mainloop_run(m, NULL);
+ pa_signal_done();
+#else
+ for (;;) {
+ time_t now;
+ time(&now);
+
+ if ((now - start) >= 30) {
+ fprintf(stderr, "Test failed\n");
+ break;
+ }
+ }
+#endif
+
+ pa_cpu_limit_done();
+
+ pa_mainloop_free(m);
+
+ return 0;
+}
diff --git a/src/tests/envelope-test.c b/src/tests/envelope-test.c
new file mode 100644
index 00000000..240747d7
--- /dev/null
+++ b/src/tests/envelope-test.c
@@ -0,0 +1,248 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <pulse/sample.h>
+#include <pulse/volume.h>
+#include <pulse/timeval.h>
+
+#include <pulsecore/envelope.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/endianmacros.h>
+#include <pulsecore/memblock.h>
+#include <pulsecore/sample-util.h>
+
+#include <liboil/liboil.h>
+
+const pa_envelope_def ramp_down = {
+ .n_points = 2,
+ .points_x = { 100*PA_USEC_PER_MSEC, 300*PA_USEC_PER_MSEC },
+ .points_y = {
+ .f = { 1.0, 0.2 },
+ .i = { 0x10000, 0x10000/5 }
+ }
+};
+
+const pa_envelope_def ramp_up = {
+ .n_points = 2,
+ .points_x = { 100*PA_USEC_PER_MSEC, 300*PA_USEC_PER_MSEC },
+ .points_y = {
+ .f = { 0.2, 1.0 },
+ .i = { 0x10000/5, 0x10000 }
+ }
+};
+
+const pa_envelope_def ramp_down2 = {
+ .n_points = 2,
+ .points_x = { 50*PA_USEC_PER_MSEC, 900*PA_USEC_PER_MSEC },
+ .points_y = {
+ .f = { 0.8, 0.7 },
+ .i = { 0x10000*4/5, 0x10000*7/10 }
+ }
+};
+
+const pa_envelope_def ramp_up2 = {
+ .n_points = 2,
+ .points_x = { 50*PA_USEC_PER_MSEC, 900*PA_USEC_PER_MSEC },
+ .points_y = {
+ .f = { 0.7, 0.9 },
+ .i = { 0x10000*7/10, 0x10000*9/10 }
+ }
+};
+
+static void dump_block(const pa_sample_spec *ss, const pa_memchunk *chunk) {
+ void *d;
+ unsigned i;
+
+ static unsigned j = 0;
+
+ d = pa_memblock_acquire(chunk->memblock);
+
+ switch (ss->format) {
+
+ case PA_SAMPLE_U8:
+ case PA_SAMPLE_ULAW:
+ case PA_SAMPLE_ALAW: {
+ uint8_t *u = d;
+
+ for (i = 0; i < chunk->length / pa_frame_size(ss); i++)
+ printf("0x%02x ", *(u++));
+
+ break;
+ }
+
+ case PA_SAMPLE_S16NE:
+ case PA_SAMPLE_S16RE: {
+ int16_t *u = d;
+
+ for (i = 0; i < chunk->length / pa_frame_size(ss); i++)
+ printf("%i\t%i\n", j++, *(u++));
+
+ break;
+ }
+
+ case PA_SAMPLE_S32NE:
+ case PA_SAMPLE_S32RE: {
+ int32_t *u = d;
+
+ for (i = 0; i < chunk->length / pa_frame_size(ss); i++)
+ printf("%i\t%i\n", j++, *(u++));
+
+ break;
+ }
+
+ case PA_SAMPLE_FLOAT32NE:
+ case PA_SAMPLE_FLOAT32RE: {
+ float *u = d;
+
+ for (i = 0; i < chunk->length / pa_frame_size(ss); i++) {
+ printf("%i\t%1.3g\n", j++, PA_MAYBE_FLOAT32_SWAP(ss->format == PA_SAMPLE_FLOAT32RE, *u));
+ u++;
+ }
+
+ break;
+ }
+
+ default:
+ pa_assert_not_reached();
+ }
+
+ printf("\n");
+
+ pa_memblock_release(chunk->memblock);
+}
+
+static pa_memblock * generate_block(pa_mempool *pool, const pa_sample_spec *ss) {
+ pa_memblock *block;
+ void *d;
+ unsigned n_samples;
+
+ block = pa_memblock_new(pool, pa_bytes_per_second(ss));
+ n_samples = pa_memblock_get_length(block) / pa_sample_size(ss);
+
+ d = pa_memblock_acquire(block);
+
+ switch (ss->format) {
+
+ case PA_SAMPLE_S16NE:
+ case PA_SAMPLE_S16RE: {
+ int16_t *i;
+
+ for (i = d; n_samples > 0; n_samples--, i++)
+ *i = 0x7FFF;
+
+ break;
+ }
+
+ case PA_SAMPLE_S32NE:
+ case PA_SAMPLE_S32RE: {
+ int32_t *i;
+
+ for (i = d; n_samples > 0; n_samples--, i++)
+ *i = 0x7FFFFFFF;
+
+ break;
+ }
+
+ case PA_SAMPLE_FLOAT32RE:
+ case PA_SAMPLE_FLOAT32NE: {
+ float *f;
+
+ for (f = d; n_samples > 0; n_samples--, f++)
+ *f = PA_MAYBE_FLOAT32_SWAP(ss->format == PA_SAMPLE_FLOAT32RE, 1.0);
+
+ break;
+ }
+
+ default:
+ pa_assert_not_reached();
+ }
+
+ pa_memblock_release(block);
+ return block;
+}
+
+int main(int argc, char *argv[]) {
+ pa_mempool *pool;
+ pa_memblock *block;
+ pa_memchunk chunk;
+ pa_envelope *envelope;
+ pa_envelope_item *item1, *item2;
+
+ const pa_sample_spec ss = {
+ .format = PA_SAMPLE_S16NE,
+ .channels = 1,
+ .rate = 200
+ };
+
+ const pa_cvolume v = {
+ .channels = 1,
+ .values = { PA_VOLUME_NORM, PA_VOLUME_NORM/2 }
+ };
+
+ oil_init();
+ pa_log_set_maximal_level(PA_LOG_DEBUG);
+
+ pa_assert_se(pool = pa_mempool_new(FALSE));
+ pa_assert_se(envelope = pa_envelope_new(&ss));
+
+ block = generate_block(pool, &ss);
+
+ chunk.memblock = pa_memblock_ref(block);
+ chunk.length = pa_memblock_get_length(block);
+ chunk.index = 0;
+
+ pa_volume_memchunk(&chunk, &ss, &v);
+
+ item1 = pa_envelope_add(envelope, &ramp_down);
+ item2 = pa_envelope_add(envelope, &ramp_down2);
+ pa_envelope_apply(envelope, &chunk);
+ dump_block(&ss, &chunk);
+
+ pa_memblock_unref(chunk.memblock);
+
+ chunk.memblock = pa_memblock_ref(block);
+ chunk.length = pa_memblock_get_length(block);
+ chunk.index = 0;
+
+ item1 = pa_envelope_replace(envelope, item1, &ramp_up);
+ item2 = pa_envelope_replace(envelope, item2, &ramp_up2);
+ pa_envelope_apply(envelope, &chunk);
+ dump_block(&ss, &chunk);
+
+ pa_memblock_unref(chunk.memblock);
+
+ pa_envelope_remove(envelope, item1);
+ pa_envelope_remove(envelope, item2);
+ pa_envelope_free(envelope);
+
+ pa_memblock_unref(block);
+
+ pa_mempool_free(pool);
+
+ return 0;
+}
diff --git a/src/tests/flist-test.c b/src/tests/flist-test.c
new file mode 100644
index 00000000..7e54454e
--- /dev/null
+++ b/src/tests/flist-test.c
@@ -0,0 +1,105 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <assert.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <pulse/util.h>
+#include <pulse/xmalloc.h>
+#include <pulsecore/flist.h>
+#include <pulsecore/thread.h>
+#include <pulsecore/log.h>
+#include <pulsecore/core-util.h>
+
+#define THREADS_MAX 20
+
+static pa_flist *flist;
+static int quit = 0;
+
+static void spin(void) {
+ int k;
+
+ /* Spin a little */
+ k = rand() % 10000;
+ for (; k > 0; k--)
+ pa_thread_yield();
+}
+
+static void thread_func(void *data) {
+ char *s = data;
+ int n = 0;
+ int b = 1;
+
+ while (!quit) {
+ char *text;
+
+ /* Allocate some memory, if possible take it from the flist */
+ if (b && (text = pa_flist_pop(flist)))
+ pa_log("%s: popped '%s'", s, text);
+ else {
+ text = pa_sprintf_malloc("Block %i, allocated by %s", n++, s);
+ pa_log("%s: allocated '%s'", s, text);
+ }
+
+ b = !b;
+
+ spin();
+
+ /* Give it back to the flist if possible */
+ if (pa_flist_push(flist, text) < 0) {
+ pa_log("%s: failed to push back '%s'", s, text);
+ pa_xfree(text);
+ } else
+ pa_log("%s: pushed", s);
+
+ spin();
+ }
+
+ if (pa_flist_push(flist, s) < 0)
+ pa_xfree(s);
+}
+
+int main(int argc, char* argv[]) {
+ pa_thread *threads[THREADS_MAX];
+ int i;
+
+ flist = pa_flist_new(0);
+
+ for (i = 0; i < THREADS_MAX; i++) {
+ threads[i] = pa_thread_new(thread_func, pa_sprintf_malloc("Thread #%i", i+1));
+ assert(threads[i]);
+ }
+
+ pa_msleep(60000);
+ quit = 1;
+
+ for (i = 0; i < THREADS_MAX; i++)
+ pa_thread_free(threads[i]);
+
+ pa_flist_free(flist, pa_xfree);
+
+ return 0;
+}
diff --git a/src/tests/get-binary-name-test.c b/src/tests/get-binary-name-test.c
new file mode 100644
index 00000000..29ebbe22
--- /dev/null
+++ b/src/tests/get-binary-name-test.c
@@ -0,0 +1,36 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <limits.h>
+#include <stdio.h>
+
+#include <pulse/util.h>
+
+int main(int argc, char *argv[]) {
+ char exename[PATH_MAX];
+
+ printf("%s\n", pa_get_binary_name(exename, sizeof(exename)));
+ return 0;
+}
diff --git a/src/tests/hook-list-test.c b/src/tests/hook-list-test.c
new file mode 100644
index 00000000..8628f521
--- /dev/null
+++ b/src/tests/hook-list-test.c
@@ -0,0 +1,39 @@
+/* $Id$ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulsecore/hook-list.h>
+#include <pulsecore/log.h>
+
+static pa_hook_result_t func1(const char*hook_data, const char*call_data, const char*slot_data) {
+ pa_log("(func1) hook=%s call=%s slot=%s", hook_data, call_data, slot_data);
+ return PA_HOOK_OK;
+}
+
+static pa_hook_result_t func2(const char*hook_data, const char*call_data, const char*slot_data) {
+ pa_log("(func2) hook=%s call=%s slot=%s", hook_data, call_data, slot_data);
+ return PA_HOOK_OK;
+}
+
+int main(int argc, char *argv[]) {
+ pa_hook hook;
+ pa_hook_slot *slot;
+
+ pa_hook_init(&hook, (void*) "hook");
+
+ pa_hook_connect(&hook, (pa_hook_cb_t) func1, (void*) "slot1");
+ slot = pa_hook_connect(&hook, (pa_hook_cb_t) func2, (void*) "slot2");
+ pa_hook_connect(&hook, (pa_hook_cb_t) func1, (void*) "slot3");
+
+ pa_hook_fire(&hook, (void*) "call1");
+
+ pa_hook_slot_free(slot);
+
+ pa_hook_fire(&hook, (void*) "call2");
+
+ pa_hook_free(&hook);
+
+ return 0;
+}
diff --git a/src/tests/interpol-test.c b/src/tests/interpol-test.c
new file mode 100644
index 00000000..85a509d4
--- /dev/null
+++ b/src/tests/interpol-test.c
@@ -0,0 +1,169 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <signal.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <math.h>
+
+#include <pulse/pulseaudio.h>
+#include <pulse/mainloop.h>
+
+#include <pulsecore/thread.h>
+
+static pa_context *context = NULL;
+static pa_stream *stream = NULL;
+static pa_mainloop_api *mainloop_api = NULL;
+
+static void stream_write_cb(pa_stream *p, size_t length, void *userdata) {
+
+ /* Just some silence */
+ pa_stream_write(p, pa_xmalloc0(length), length, pa_xfree, 0, PA_SEEK_RELATIVE);
+}
+
+/* This is called whenever the context status changes */
+static void context_state_callback(pa_context *c, void *userdata) {
+ assert(c);
+
+ switch (pa_context_get_state(c)) {
+ case PA_CONTEXT_CONNECTING:
+ case PA_CONTEXT_AUTHORIZING:
+ case PA_CONTEXT_SETTING_NAME:
+ break;
+
+ case PA_CONTEXT_READY: {
+
+ static const pa_sample_spec ss = {
+ .format = PA_SAMPLE_S16LE,
+ .rate = 44100,
+ .channels = 1
+ };
+
+ fprintf(stderr, "Connection established.\n");
+
+ stream = pa_stream_new(c, "interpol-test", &ss, NULL);
+ assert(stream);
+
+ pa_stream_connect_playback(stream, NULL, NULL, PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_AUTO_TIMING_UPDATE, NULL, NULL);
+ pa_stream_set_write_callback(stream, stream_write_cb, NULL);
+
+ break;
+ }
+
+ case PA_CONTEXT_TERMINATED:
+ break;
+
+ case PA_CONTEXT_FAILED:
+ default:
+ fprintf(stderr, "Context error: %s\n", pa_strerror(pa_context_errno(c)));
+ abort();
+ }
+}
+
+int main(int argc, char *argv[]) {
+ pa_threaded_mainloop* m = NULL;
+ int k, r;
+ struct timeval start, last_info = { 0, 0 };
+ pa_usec_t old_t = 0, old_rtc = 0;
+
+ /* Set up a new main loop */
+ m = pa_threaded_mainloop_new();
+ assert(m);
+
+ mainloop_api = pa_threaded_mainloop_get_api(m);
+
+ context = pa_context_new(mainloop_api, argv[0]);
+ assert(context);
+
+ pa_context_set_state_callback(context, context_state_callback, NULL);
+
+ r = pa_context_connect(context, NULL, 0, NULL);
+ assert(r >= 0);
+
+ pa_gettimeofday(&start);
+
+ pa_threaded_mainloop_start(m);
+
+ for (k = 0; k < 5000; k++) {
+ int success = 0, changed = 0;
+ pa_usec_t t, rtc;
+ struct timeval now, tv;
+
+ pa_threaded_mainloop_lock(m);
+
+ if (stream) {
+ const pa_timing_info *info;
+
+ if (pa_stream_get_time(stream, &t) >= 0)
+ success = 1;
+
+ if ((info = pa_stream_get_timing_info(stream)))
+ if (last_info.tv_usec != info->timestamp.tv_usec || last_info.tv_sec != info->timestamp.tv_sec) {
+ changed = 1;
+ last_info = info->timestamp;
+ }
+ }
+
+ pa_threaded_mainloop_unlock(m);
+
+ if (success) {
+ pa_gettimeofday(&now);
+
+ rtc = pa_timeval_diff(&now, &start);
+ printf("%i\t%llu\t%llu\t%llu\t%llu\t%u\n", k, (unsigned long long) rtc, (unsigned long long) t, (unsigned long long) (rtc-old_rtc), (unsigned long long) (t-old_t), changed);
+ old_t = t;
+ old_rtc = rtc;
+ }
+
+ /* Spin loop, ugly but normal usleep() is just too badly grained */
+
+ tv = now;
+ while (pa_timeval_diff(pa_gettimeofday(&now), &tv) < 1000)
+ pa_thread_yield();
+ }
+
+ if (m)
+ pa_threaded_mainloop_stop(m);
+
+ if (stream) {
+ pa_stream_disconnect(stream);
+ pa_stream_unref(stream);
+ }
+
+ if (context) {
+ pa_context_disconnect(context);
+ pa_context_unref(context);
+ }
+
+ if (m)
+ pa_threaded_mainloop_free(m);
+
+ return 0;
+}
diff --git a/src/tests/ipacl-test.c b/src/tests/ipacl-test.c
new file mode 100644
index 00000000..d1bcb3e3
--- /dev/null
+++ b/src/tests/ipacl-test.c
@@ -0,0 +1,136 @@
+/* $Id$ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <assert.h>
+#include <string.h>
+
+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.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_ARPA_INET_H
+#include <arpa/inet.h>
+#endif
+
+#include "../pulsecore/winsock.h"
+
+#include <pulsecore/ipacl.h>
+
+int main(int argc, char *argv[]) {
+ struct sockaddr_in sa;
+ struct sockaddr_in6 sa6;
+ int fd;
+ int r;
+ pa_ip_acl *acl;
+
+ fd = socket(PF_INET, SOCK_STREAM, 0);
+ assert(fd >= 0);
+
+ sa.sin_family = AF_INET;
+ sa.sin_port = htons(22);
+ sa.sin_addr.s_addr = inet_addr("127.0.0.1");
+
+ r = connect(fd, (struct sockaddr*) &sa, sizeof(sa));
+ assert(r >= 0);
+
+ acl = pa_ip_acl_new("127.0.0.1");
+ assert(acl);
+ printf("result=%u (should be 1)\n", pa_ip_acl_check(acl, fd));
+ pa_ip_acl_free(acl);
+
+ acl = pa_ip_acl_new("127.0.0.2/0");
+ assert(acl);
+ printf("result=%u (should be 1)\n", pa_ip_acl_check(acl, fd));
+ pa_ip_acl_free(acl);
+
+ acl = pa_ip_acl_new("127.0.0.1/32");
+ assert(acl);
+ printf("result=%u (should be 1)\n", pa_ip_acl_check(acl, fd));
+ pa_ip_acl_free(acl);
+
+ acl = pa_ip_acl_new("127.0.0.1/7");
+ assert(acl);
+ printf("result=%u (should be 1)\n", pa_ip_acl_check(acl, fd));
+ pa_ip_acl_free(acl);
+
+ acl = pa_ip_acl_new("127.0.0.2");
+ assert(acl);
+ printf("result=%u (should be 0)\n", pa_ip_acl_check(acl, fd));
+ pa_ip_acl_free(acl);
+
+ acl = pa_ip_acl_new("127.0.0.0/8;0.0.0.0/32");
+ assert(acl);
+ printf("result=%u (should be 1)\n", pa_ip_acl_check(acl, fd));
+ pa_ip_acl_free(acl);
+
+ acl = pa_ip_acl_new("128.0.0.2/9");
+ assert(acl);
+ printf("result=%u (should be 0)\n", pa_ip_acl_check(acl, fd));
+ pa_ip_acl_free(acl);
+
+ acl = pa_ip_acl_new("::1/9");
+ assert(acl);
+ printf("result=%u (should be 0)\n", pa_ip_acl_check(acl, fd));
+ pa_ip_acl_free(acl);
+
+ close(fd);
+
+ fd = socket(PF_INET6, SOCK_STREAM, 0);
+ assert(fd >= 0);
+
+ memset(&sa6, 0, sizeof(sa6));
+ sa6.sin6_family = AF_INET6;
+ sa6.sin6_port = htons(22);
+ inet_pton(AF_INET6, "::1", &sa6.sin6_addr);
+
+ r = connect(fd, (struct sockaddr*) &sa6, sizeof(sa6));
+ assert(r >= 0);
+
+ acl = pa_ip_acl_new("::1");
+ assert(acl);
+ printf("result=%u (should be 1)\n", pa_ip_acl_check(acl, fd));
+ pa_ip_acl_free(acl);
+
+ acl = pa_ip_acl_new("::1/9");
+ assert(acl);
+ printf("result=%u (should be 1)\n", pa_ip_acl_check(acl, fd));
+ pa_ip_acl_free(acl);
+
+ acl = pa_ip_acl_new("::/0");
+ assert(acl);
+ printf("result=%u (should be 1)\n", pa_ip_acl_check(acl, fd));
+ pa_ip_acl_free(acl);
+
+ acl = pa_ip_acl_new("::2/128");
+ assert(acl);
+ printf("result=%u (should be 0)\n", pa_ip_acl_check(acl, fd));
+ pa_ip_acl_free(acl);
+
+ acl = pa_ip_acl_new("::2/127");
+ assert(acl);
+ printf("result=%u (should be 0)\n", pa_ip_acl_check(acl, fd));
+ pa_ip_acl_free(acl);
+
+ acl = pa_ip_acl_new("::2/126");
+ assert(acl);
+ printf("result=%u (should be 1)\n", pa_ip_acl_check(acl, fd));
+ pa_ip_acl_free(acl);
+
+ close(fd);
+
+ return 0;
+}
diff --git a/src/tests/mainloop-test.c b/src/tests/mainloop-test.c
new file mode 100644
index 00000000..c386251c
--- /dev/null
+++ b/src/tests/mainloop-test.c
@@ -0,0 +1,126 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <assert.h>
+
+#include <pulse/timeval.h>
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/gccmacro.h>
+
+#ifdef GLIB_MAIN_LOOP
+
+#include <glib.h>
+#include <pulse/glib-mainloop.h>
+
+static GMainLoop* glib_main_loop = NULL;
+
+#else /* GLIB_MAIN_LOOP */
+#include <pulse/mainloop.h>
+#endif /* GLIB_MAIN_LOOP */
+
+static pa_defer_event *de;
+
+static void iocb(pa_mainloop_api*a, pa_io_event *e, int fd, pa_io_event_flags_t f, void *userdata) {
+ unsigned char c;
+ read(fd, &c, sizeof(c));
+ fprintf(stderr, "IO EVENT: %c\n", c < 32 ? '.' : c);
+ a->defer_enable(de, 1);
+}
+
+static void dcb(pa_mainloop_api*a, pa_defer_event *e, void *userdata) {
+ fprintf(stderr, "DEFER EVENT\n");
+ a->defer_enable(e, 0);
+}
+
+static void tcb(pa_mainloop_api*a, pa_time_event *e, const struct timeval *tv, void *userdata) {
+ fprintf(stderr, "TIME EVENT\n");
+
+#if defined(GLIB_MAIN_LOOP)
+ g_main_loop_quit(glib_main_loop);
+#else
+ a->quit(a, 0);
+#endif
+}
+
+int main(PA_GCC_UNUSED int argc, PA_GCC_UNUSED char *argv[]) {
+ pa_mainloop_api *a;
+ pa_io_event *ioe;
+ pa_time_event *te;
+ struct timeval tv;
+
+#ifdef GLIB_MAIN_LOOP
+ pa_glib_mainloop *g;
+
+ glib_main_loop = g_main_loop_new(NULL, FALSE);
+ assert(glib_main_loop);
+
+ g = pa_glib_mainloop_new(NULL);
+ assert(g);
+
+ a = pa_glib_mainloop_get_api(g);
+ assert(a);
+#else /* GLIB_MAIN_LOOP */
+ pa_mainloop *m;
+
+ m = pa_mainloop_new();
+ assert(m);
+
+ a = pa_mainloop_get_api(m);
+ assert(a);
+#endif /* GLIB_MAIN_LOOP */
+
+ ioe = a->io_new(a, 0, PA_IO_EVENT_INPUT, iocb, NULL);
+ assert(ioe);
+
+ de = a->defer_new(a, dcb, NULL);
+ assert(de);
+
+ pa_gettimeofday(&tv);
+ tv.tv_sec += 10;
+ te = a->time_new(a, &tv, tcb, NULL);
+
+#if defined(GLIB_MAIN_LOOP)
+ g_main_loop_run(glib_main_loop);
+#else
+ pa_mainloop_run(m, NULL);
+#endif
+
+ a->time_free(te);
+ a->defer_free(de);
+ a->io_free(ioe);
+
+#ifdef GLIB_MAIN_LOOP
+ pa_glib_mainloop_free(g);
+ g_main_loop_unref(glib_main_loop);
+#else
+ pa_mainloop_free(m);
+#endif
+
+ return 0;
+}
diff --git a/src/tests/mcalign-test.c b/src/tests/mcalign-test.c
new file mode 100644
index 00000000..d1013118
--- /dev/null
+++ b/src/tests/mcalign-test.c
@@ -0,0 +1,112 @@
+/* $Id$ */
+
+/***
+ 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <assert.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <time.h>
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/mcalign.h>
+#include <pulsecore/gccmacro.h>
+
+/* A simple program for testing pa_mcalign */
+
+int main(PA_GCC_UNUSED int argc, PA_GCC_UNUSED char *argv[]) {
+ pa_mempool *p;
+ pa_mcalign *a;
+ pa_memchunk c;
+
+ p = pa_mempool_new(0);
+
+ a = pa_mcalign_new(11);
+
+ pa_memchunk_reset(&c);
+
+ srand(time(NULL));
+
+ for (;;) {
+ ssize_t r;
+ size_t l;
+
+ if (!c.memblock) {
+ c.memblock = pa_memblock_new(p, 2048);
+ c.index = c.length = 0;
+ }
+
+ assert(c.index < pa_memblock_get_length(c.memblock));
+
+ l = pa_memblock_get_length(c.memblock) - c.index;
+
+ l = l <= 1 ? l : rand() % (l-1) +1 ;
+
+ p = pa_memblock_acquire(c.memblock);
+
+ if ((r = read(STDIN_FILENO, (uint8_t*) p + c.index, l)) <= 0) {
+ pa_memblock_release(c.memblock);
+ fprintf(stderr, "read() failed: %s\n", r < 0 ? strerror(errno) : "EOF");
+ break;
+ }
+
+ pa_memblock_release(c.memblock);
+
+ c.length = r;
+ pa_mcalign_push(a, &c);
+ fprintf(stderr, "Read %ld bytes\n", (long)r);
+
+ c.index += r;
+
+ if (c.index >= pa_memblock_get_length(c.memblock)) {
+ pa_memblock_unref(c.memblock);
+ pa_memchunk_reset(&c);
+ }
+
+ for (;;) {
+ pa_memchunk t;
+
+ if (pa_mcalign_pop(a, &t) < 0)
+ break;
+
+ p = pa_memblock_acquire(t.memblock);
+ pa_loop_write(STDOUT_FILENO, (uint8_t*) p + t.index, t.length, NULL);
+ pa_memblock_release(t.memblock);
+ fprintf(stderr, "Wrote %lu bytes.\n", (unsigned long) t.length);
+
+ pa_memblock_unref(t.memblock);
+ }
+ }
+
+ pa_mcalign_free(a);
+
+ if (c.memblock)
+ pa_memblock_unref(c.memblock);
+
+ pa_mempool_free(p);
+
+ return 0;
+}
diff --git a/src/tests/memblock-test.c b/src/tests/memblock-test.c
new file mode 100644
index 00000000..2b9d3401
--- /dev/null
+++ b/src/tests/memblock-test.c
@@ -0,0 +1,176 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <unistd.h>
+
+#include <pulsecore/memblock.h>
+#include <pulsecore/macro.h>
+#include <pulse/xmalloc.h>
+
+static void release_cb(pa_memimport *i, uint32_t block_id, void *userdata) {
+ printf("%s: Imported block %u is released.\n", (char*) userdata, block_id);
+}
+
+static void revoke_cb(pa_memexport *e, uint32_t block_id, void *userdata) {
+ printf("%s: Exported block %u is revoked.\n", (char*) userdata, block_id);
+}
+
+static void print_stats(pa_mempool *p, const char *text) {
+ const pa_mempool_stat*s = pa_mempool_get_stat(p);
+
+ printf("%s = {\n"
+ "n_allocated = %u\n"
+ "n_accumulated = %u\n"
+ "n_imported = %u\n"
+ "n_exported = %u\n"
+ "allocated_size = %u\n"
+ "accumulated_size = %u\n"
+ "imported_size = %u\n"
+ "exported_size = %u\n"
+ "n_too_large_for_pool = %u\n"
+ "n_pool_full = %u\n"
+ "}\n",
+ text,
+ (unsigned) pa_atomic_load(&s->n_allocated),
+ (unsigned) pa_atomic_load(&s->n_accumulated),
+ (unsigned) pa_atomic_load(&s->n_imported),
+ (unsigned) pa_atomic_load(&s->n_exported),
+ (unsigned) pa_atomic_load(&s->allocated_size),
+ (unsigned) pa_atomic_load(&s->accumulated_size),
+ (unsigned) pa_atomic_load(&s->imported_size),
+ (unsigned) pa_atomic_load(&s->exported_size),
+ (unsigned) pa_atomic_load(&s->n_too_large_for_pool),
+ (unsigned) pa_atomic_load(&s->n_pool_full));
+}
+
+int main(int argc, char *argv[]) {
+ pa_mempool *pool_a, *pool_b, *pool_c;
+ unsigned id_a, id_b, id_c;
+ pa_memexport *export_a, *export_b;
+ pa_memimport *import_b, *import_c;
+ pa_memblock *mb_a, *mb_b, *mb_c;
+ int r, i;
+ pa_memblock* blocks[5];
+ uint32_t id, shm_id;
+ size_t offset, size;
+ char *x;
+
+ const char txt[] = "This is a test!";
+
+ pool_a = pa_mempool_new(1);
+ pool_b = pa_mempool_new(1);
+ pool_c = pa_mempool_new(1);
+
+ pa_mempool_get_shm_id(pool_a, &id_a);
+ pa_mempool_get_shm_id(pool_b, &id_b);
+ pa_mempool_get_shm_id(pool_c, &id_c);
+
+ pa_assert(pool_a && pool_b && pool_c);
+
+ blocks[0] = pa_memblock_new_fixed(pool_a, (void*) txt, sizeof(txt), 1);
+
+ blocks[1] = pa_memblock_new(pool_a, sizeof(txt));
+ x = pa_memblock_acquire(blocks[1]);
+ snprintf(x, pa_memblock_get_length(blocks[1]), "%s", txt);
+ pa_memblock_release(blocks[1]);
+
+ blocks[2] = pa_memblock_new_pool(pool_a, sizeof(txt));
+ x = pa_memblock_acquire(blocks[2]);
+ snprintf(x, pa_memblock_get_length(blocks[2]), "%s", txt);
+ pa_memblock_release(blocks[2]);
+
+ blocks[3] = pa_memblock_new_malloced(pool_a, pa_xstrdup(txt), sizeof(txt));
+ blocks[4] = NULL;
+
+ for (i = 0; blocks[i]; i++) {
+ printf("Memory block %u\n", i);
+
+ mb_a = blocks[i];
+ pa_assert(mb_a);
+
+ export_a = pa_memexport_new(pool_a, revoke_cb, (void*) "A");
+ export_b = pa_memexport_new(pool_b, revoke_cb, (void*) "B");
+
+ pa_assert(export_a && export_b);
+
+ import_b = pa_memimport_new(pool_b, release_cb, (void*) "B");
+ import_c = pa_memimport_new(pool_c, release_cb, (void*) "C");
+
+ pa_assert(import_b && import_c);
+
+ r = pa_memexport_put(export_a, mb_a, &id, &shm_id, &offset, &size);
+ pa_assert(r >= 0);
+ pa_assert(shm_id == id_a);
+
+ printf("A: Memory block exported as %u\n", id);
+
+ mb_b = pa_memimport_get(import_b, id, shm_id, offset, size);
+ pa_assert(mb_b);
+ r = pa_memexport_put(export_b, mb_b, &id, &shm_id, &offset, &size);
+ pa_assert(r >= 0);
+ pa_assert(shm_id == id_a || shm_id == id_b);
+ pa_memblock_unref(mb_b);
+
+ printf("B: Memory block exported as %u\n", id);
+
+ mb_c = pa_memimport_get(import_c, id, shm_id, offset, size);
+ pa_assert(mb_c);
+ x = pa_memblock_acquire(mb_c);
+ printf("1 data=%s\n", x);
+ pa_memblock_release(mb_c);
+
+ print_stats(pool_a, "A");
+ print_stats(pool_b, "B");
+ print_stats(pool_c, "C");
+
+ pa_memexport_free(export_b);
+ x = pa_memblock_acquire(mb_c);
+ printf("2 data=%s\n", x);
+ pa_memblock_release(mb_c);
+ pa_memblock_unref(mb_c);
+
+ pa_memimport_free(import_b);
+
+ pa_memblock_unref(mb_a);
+
+ pa_memimport_free(import_c);
+ pa_memexport_free(export_a);
+ }
+
+ printf("vaccuuming...\n");
+
+ pa_mempool_vacuum(pool_a);
+ pa_mempool_vacuum(pool_b);
+ pa_mempool_vacuum(pool_c);
+
+ printf("vaccuuming done...\n");
+
+ pa_mempool_free(pool_a);
+ pa_mempool_free(pool_b);
+ pa_mempool_free(pool_c);
+
+ return 0;
+}
diff --git a/src/tests/memblockq-test.c b/src/tests/memblockq-test.c
new file mode 100644
index 00000000..25ea399b
--- /dev/null
+++ b/src/tests/memblockq-test.c
@@ -0,0 +1,153 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <assert.h>
+#include <stdio.h>
+#include <signal.h>
+
+#include <pulsecore/memblockq.h>
+#include <pulsecore/log.h>
+
+int main(int argc, char *argv[]) {
+ int ret;
+
+ pa_mempool *p;
+ pa_memblockq *bq;
+ pa_memchunk chunk1, chunk2, chunk3, chunk4;
+ pa_memblock *silence;
+
+ pa_log_set_maximal_level(PA_LOG_DEBUG);
+
+ p = pa_mempool_new(0);
+
+ silence = pa_memblock_new_fixed(p, (char*) "__", 2, 1);
+ assert(silence);
+
+ bq = pa_memblockq_new(0, 40, 10, 2, 4, 4, silence);
+ assert(bq);
+
+ chunk1.memblock = pa_memblock_new_fixed(p, (char*) "11", 2, 1);
+ chunk1.index = 0;
+ chunk1.length = 2;
+ assert(chunk1.memblock);
+
+ chunk2.memblock = pa_memblock_new_fixed(p, (char*) "XX22", 4, 1);
+ chunk2.index = 2;
+ chunk2.length = 2;
+ assert(chunk2.memblock);
+
+ chunk3.memblock = pa_memblock_new_fixed(p, (char*) "3333", 4, 1);
+ chunk3.index = 0;
+ chunk3.length = 4;
+ assert(chunk3.memblock);
+
+ chunk4.memblock = pa_memblock_new_fixed(p, (char*) "44444444", 8, 1);
+ chunk4.index = 0;
+ chunk4.length = 8;
+ assert(chunk4.memblock);
+
+ ret = pa_memblockq_push(bq, &chunk1);
+ assert(ret == 0);
+
+ ret = pa_memblockq_push(bq, &chunk1);
+ assert(ret == 0);
+
+ ret = pa_memblockq_push(bq, &chunk2);
+ assert(ret == 0);
+
+ ret = pa_memblockq_push(bq, &chunk2);
+ assert(ret == 0);
+
+ pa_memblockq_seek(bq, -6, 0);
+ ret = pa_memblockq_push(bq, &chunk3);
+ assert(ret == 0);
+
+ pa_memblockq_seek(bq, -2, 0);
+ ret = pa_memblockq_push(bq, &chunk3);
+ assert(ret == 0);
+
+ pa_memblockq_seek(bq, -10, 0);
+ ret = pa_memblockq_push(bq, &chunk4);
+ assert(ret == 0);
+
+ pa_memblockq_seek(bq, 10, 0);
+
+ ret = pa_memblockq_push(bq, &chunk1);
+ assert(ret == 0);
+
+ pa_memblockq_seek(bq, -6, 0);
+ ret = pa_memblockq_push(bq, &chunk2);
+ assert(ret == 0);
+
+ /* Test splitting */
+ pa_memblockq_seek(bq, -12, 0);
+ ret = pa_memblockq_push(bq, &chunk1);
+ assert(ret == 0);
+
+ pa_memblockq_seek(bq, 20, 0);
+
+ /* Test merging */
+ ret = pa_memblockq_push(bq, &chunk3);
+ assert(ret == 0);
+ pa_memblockq_seek(bq, -2, 0);
+
+ chunk3.index += 2;
+ chunk3.length -= 2;
+ ret = pa_memblockq_push(bq, &chunk3);
+ assert(ret == 0);
+
+ pa_memblockq_shorten(bq, pa_memblockq_get_length(bq)-2);
+
+ printf(">");
+
+ for (;;) {
+ pa_memchunk out;
+ char *e;
+ size_t n;
+
+ if (pa_memblockq_peek(bq, &out) < 0)
+ break;
+
+ p = pa_memblock_acquire(out.memblock);
+ for (e = (char*) p + out.index, n = 0; n < out.length; n++)
+ printf("%c", *e);
+ pa_memblock_release(out.memblock);
+
+ pa_memblock_unref(out.memblock);
+ pa_memblockq_drop(bq, out.length);
+ }
+
+ printf("<\n");
+
+ pa_memblockq_free(bq);
+ pa_memblock_unref(silence);
+ pa_memblock_unref(chunk1.memblock);
+ pa_memblock_unref(chunk2.memblock);
+ pa_memblock_unref(chunk3.memblock);
+ pa_memblock_unref(chunk4.memblock);
+
+ return 0;
+}
diff --git a/src/tests/mix-test.c b/src/tests/mix-test.c
new file mode 100644
index 00000000..d07b1b0c
--- /dev/null
+++ b/src/tests/mix-test.c
@@ -0,0 +1,261 @@
+/* $Id: resampler-test.c 2037 2007-11-09 02:45:07Z lennart $ */
+
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+
+#include <pulse/sample.h>
+#include <pulse/volume.h>
+
+#include <pulsecore/resampler.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/endianmacros.h>
+#include <pulsecore/memblock.h>
+#include <pulsecore/sample-util.h>
+
+#include <liboil/liboil.h>
+
+static float swap_float(float a) {
+ uint32_t *b = (uint32_t*) &a;
+ *b = PA_UINT32_SWAP(*b);
+ return a;
+}
+
+static void dump_block(const pa_sample_spec *ss, const pa_memchunk *chunk) {
+ void *d;
+ unsigned i;
+
+ d = pa_memblock_acquire(chunk->memblock);
+
+ switch (ss->format) {
+
+ case PA_SAMPLE_U8:
+ case PA_SAMPLE_ULAW:
+ case PA_SAMPLE_ALAW: {
+ uint8_t *u = d;
+
+ for (i = 0; i < chunk->length / pa_frame_size(ss); i++)
+ printf("0x%02x ", *(u++));
+
+ break;
+ }
+
+ case PA_SAMPLE_S16NE:
+ case PA_SAMPLE_S16RE: {
+ uint16_t *u = d;
+
+ for (i = 0; i < chunk->length / pa_frame_size(ss); i++)
+ printf("0x%04x ", *(u++));
+
+ break;
+ }
+
+ case PA_SAMPLE_S32NE:
+ case PA_SAMPLE_S32RE: {
+ uint32_t *u = d;
+
+ for (i = 0; i < chunk->length / pa_frame_size(ss); i++)
+ printf("0x%08x ", *(u++));
+
+ break;
+ }
+
+ case PA_SAMPLE_FLOAT32NE:
+ case PA_SAMPLE_FLOAT32RE: {
+ float *u = d;
+
+ for (i = 0; i < chunk->length / pa_frame_size(ss); i++) {
+ printf("%1.5f ", ss->format == PA_SAMPLE_FLOAT32NE ? *u : swap_float(*u));
+ u++;
+ }
+
+ break;
+ }
+
+ default:
+ pa_assert_not_reached();
+ }
+
+ printf("\n");
+
+ pa_memblock_release(chunk->memblock);
+}
+
+static pa_memblock* generate_block(pa_mempool *pool, const pa_sample_spec *ss) {
+ pa_memblock *r;
+ void *d;
+ unsigned i;
+
+ pa_assert_se(r = pa_memblock_new(pool, pa_frame_size(ss) * 10));
+ d = pa_memblock_acquire(r);
+
+ switch (ss->format) {
+
+ case PA_SAMPLE_U8:
+ case PA_SAMPLE_ULAW:
+ case PA_SAMPLE_ALAW: {
+ uint8_t *u = d;
+
+ u[0] = 0x00;
+ u[1] = 0xFF;
+ u[2] = 0x7F;
+ u[3] = 0x80;
+ u[4] = 0x9f;
+ u[5] = 0x3f;
+ u[6] = 0x1;
+ u[7] = 0xF0;
+ u[8] = 0x20;
+ u[9] = 0x21;
+ break;
+ }
+
+ case PA_SAMPLE_S16NE:
+ case PA_SAMPLE_S16RE: {
+ uint16_t *u = d;
+
+ u[0] = 0x0000;
+ u[1] = 0xFFFF;
+ u[2] = 0x7FFF;
+ u[3] = 0x8000;
+ u[4] = 0x9fff;
+ u[5] = 0x3fff;
+ u[6] = 0x1;
+ u[7] = 0xF000;
+ u[8] = 0x20;
+ u[9] = 0x21;
+ break;
+ }
+
+ case PA_SAMPLE_S32NE:
+ case PA_SAMPLE_S32RE: {
+ uint32_t *u = d;
+
+ u[0] = 0x00000001;
+ u[1] = 0xFFFF0002;
+ u[2] = 0x7FFF0003;
+ u[3] = 0x80000004;
+ u[4] = 0x9fff0005;
+ u[5] = 0x3fff0006;
+ u[6] = 0x10007;
+ u[7] = 0xF0000008;
+ u[8] = 0x200009;
+ u[9] = 0x21000A;
+ break;
+ }
+
+ case PA_SAMPLE_FLOAT32NE:
+ case PA_SAMPLE_FLOAT32RE: {
+ float *u = d;
+
+ u[0] = 0.0;
+ u[1] = -1.0;
+ u[2] = 1.0;
+ u[3] = 4711;
+ u[4] = 0.222;
+ u[5] = 0.33;
+ u[6] = -.3;
+ u[7] = 99;
+ u[8] = -0.555;
+ u[9] = -.123;
+
+ if (ss->format == PA_SAMPLE_FLOAT32RE)
+ for (i = 0; i < 10; i++)
+ u[i] = swap_float(u[i]);
+
+ break;
+ }
+
+ default:
+ pa_assert_not_reached();
+ }
+
+ pa_memblock_release(r);
+
+ return r;
+}
+
+int main(int argc, char *argv[]) {
+ pa_mempool *pool;
+ pa_sample_spec a;
+ pa_cvolume v;
+
+ oil_init();
+ pa_log_set_maximal_level(PA_LOG_DEBUG);
+
+ pa_assert_se(pool = pa_mempool_new(FALSE));
+
+ a.channels = 1;
+ a.rate = 44100;
+
+ v.channels = a.channels;
+ v.values[0] = pa_sw_volume_from_linear(0.9);
+
+ for (a.format = 0; a.format < PA_SAMPLE_MAX; a.format ++) {
+ pa_memchunk i, j, k;
+ pa_mix_info m[2];
+ void *ptr;
+
+ printf("=== mixing: %s\n", pa_sample_format_to_string(a.format));
+
+ /* Generate block */
+ i.memblock = generate_block(pool, &a);
+ i.length = pa_memblock_get_length(i.memblock);
+ i.index = 0;
+
+ /* Make a copy */
+ j = i;
+ pa_memblock_ref(j.memblock);
+ pa_memchunk_make_writable(&j, 0);
+
+ /* Adjust volume of the copy */
+ pa_volume_memchunk(&j, &a, &v);
+
+ m[0].chunk = i;
+ m[0].volume.values[0] = PA_VOLUME_NORM;
+ m[0].volume.channels = a.channels;
+ m[1].chunk = j;
+ m[1].volume.values[0] = PA_VOLUME_NORM;
+ m[1].volume.channels = a.channels;
+
+ k.memblock = pa_memblock_new(pool, i.length);
+ k.length = i.length;
+ k.index = 0;
+
+ ptr = (uint8_t*) pa_memblock_acquire(k.memblock) + k.index;
+ pa_mix(m, 2, ptr, k.length, &a, NULL, FALSE);
+ pa_memblock_release(k.memblock);
+
+ dump_block(&a, &i);
+ dump_block(&a, &j);
+ dump_block(&a, &k);
+
+ pa_memblock_unref(i.memblock);
+ pa_memblock_unref(j.memblock);
+ pa_memblock_unref(k.memblock);
+ }
+
+ pa_mempool_free(pool);
+
+ return 0;
+}
diff --git a/src/tests/pacat-simple.c b/src/tests/pacat-simple.c
new file mode 100644
index 00000000..2da67c1a
--- /dev/null
+++ b/src/tests/pacat-simple.c
@@ -0,0 +1,119 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include <pulse/simple.h>
+#include <pulse/error.h>
+#include <pulsecore/gccmacro.h>
+
+#define BUFSIZE 1024
+
+int main(PA_GCC_UNUSED int argc, char*argv[]) {
+
+ /* The Sample format to use */
+ static const pa_sample_spec ss = {
+ .format = PA_SAMPLE_S16LE,
+ .rate = 44100,
+ .channels = 2
+ };
+
+ pa_simple *s = NULL;
+ int ret = 1;
+ int error;
+
+ /* replace STDIN with the specified file if needed */
+ if (argc > 1) {
+ int fd;
+
+ if ((fd = open(argv[1], O_RDONLY)) < 0) {
+ fprintf(stderr, __FILE__": open() failed: %s\n", strerror(errno));
+ goto finish;
+ }
+
+ if (dup2(fd, STDIN_FILENO) < 0) {
+ fprintf(stderr, __FILE__": dup2() failed: %s\n", strerror(errno));
+ goto finish;
+ }
+
+ close(fd);
+ }
+
+ /* Create a new playback stream */
+ if (!(s = pa_simple_new(NULL, argv[0], PA_STREAM_PLAYBACK, NULL, "playback", &ss, NULL, NULL, &error))) {
+ fprintf(stderr, __FILE__": pa_simple_new() failed: %s\n", pa_strerror(error));
+ goto finish;
+ }
+
+ for (;;) {
+ uint8_t buf[BUFSIZE];
+ ssize_t r;
+
+#if 0
+ pa_usec_t latency;
+
+ if ((latency = pa_simple_get_latency(s, &error)) == (pa_usec_t) -1) {
+ fprintf(stderr, __FILE__": pa_simple_get_latency() failed: %s\n", pa_strerror(error));
+ goto finish;
+ }
+
+ fprintf(stderr, "%0.0f usec \r", (float)latency);
+#endif
+
+ /* Read some data ... */
+ if ((r = read(STDIN_FILENO, buf, sizeof(buf))) <= 0) {
+ if (r == 0) /* EOF */
+ break;
+
+ fprintf(stderr, __FILE__": read() failed: %s\n", strerror(errno));
+ goto finish;
+ }
+
+ /* ... and play it */
+ if (pa_simple_write(s, buf, r, &error) < 0) {
+ fprintf(stderr, __FILE__": pa_simple_write() failed: %s\n", pa_strerror(error));
+ goto finish;
+ }
+ }
+
+ /* Make sure that every single sample was played */
+ if (pa_simple_drain(s, &error) < 0) {
+ fprintf(stderr, __FILE__": pa_simple_drain() failed: %s\n", pa_strerror(error));
+ goto finish;
+ }
+
+ ret = 0;
+
+finish:
+
+ if (s)
+ pa_simple_free(s);
+
+ return ret;
+}
diff --git a/src/tests/parec-simple.c b/src/tests/parec-simple.c
new file mode 100644
index 00000000..d7d88360
--- /dev/null
+++ b/src/tests/parec-simple.c
@@ -0,0 +1,100 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#include <pulse/simple.h>
+#include <pulse/error.h>
+#include <pulsecore/gccmacro.h>
+
+#define BUFSIZE 1024
+
+/* A simple routine calling UNIX write() in a loop */
+static ssize_t loop_write(int fd, const void*data, size_t size) {
+ ssize_t ret = 0;
+
+ while (size > 0) {
+ ssize_t r;
+
+ if ((r = write(fd, data, size)) < 0)
+ return r;
+
+ if (r == 0)
+ break;
+
+ ret += r;
+ data = (const uint8_t*) data + r;
+ size -= r;
+ }
+
+ return ret;
+}
+
+int main(PA_GCC_UNUSED int argc, char*argv[]) {
+ /* The sample type to use */
+ static const pa_sample_spec ss = {
+ .format = PA_SAMPLE_S16LE,
+ .rate = 44100,
+ .channels = 2
+ };
+ pa_simple *s = NULL;
+ int ret = 1;
+ int error;
+
+ /* Create the recording stream */
+ if (!(s = pa_simple_new(NULL, argv[0], PA_STREAM_RECORD, NULL, "record", &ss, NULL, NULL, &error))) {
+ fprintf(stderr, __FILE__": pa_simple_new() failed: %s\n", pa_strerror(error));
+ goto finish;
+ }
+
+ for (;;) {
+ uint8_t buf[BUFSIZE];
+ ssize_t r;
+
+ /* Record some data ... */
+ if (pa_simple_read(s, buf, sizeof(buf), &error) < 0) {
+ fprintf(stderr, __FILE__": pa_simple_read() failed: %s\n", pa_strerror(error));
+ goto finish;
+ }
+
+ /* And write it to STDOUT */
+ if ((r = loop_write(STDOUT_FILENO, buf, sizeof(buf))) <= 0) {
+ fprintf(stderr, __FILE__": write() failed: %s\n", strerror(errno));
+ goto finish;
+ }
+ }
+
+ ret = 0;
+
+finish:
+
+ if (s)
+ pa_simple_free(s);
+
+ return ret;
+}
diff --git a/src/tests/proplist-test.c b/src/tests/proplist-test.c
new file mode 100644
index 00000000..b88f4e5e
--- /dev/null
+++ b/src/tests/proplist-test.c
@@ -0,0 +1,61 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+
+#include <pulse/proplist.h>
+#include <pulse/xmalloc.h>
+#include <pulsecore/macro.h>
+
+int main(int argc, char*argv[]) {
+ pa_proplist *a, *b;
+ char *s, *t;
+
+ a = pa_proplist_new();
+ pa_assert_se(pa_proplist_puts(a, PA_PROP_MEDIA_TITLE, "Brandenburgische Konzerte") == 0);
+ pa_assert_se(pa_proplist_puts(a, PA_PROP_MEDIA_ARTIST, "Johann Sebastian Bach") == 0);
+
+ b = pa_proplist_new();
+ pa_assert_se(pa_proplist_puts(b, PA_PROP_MEDIA_TITLE, "Goldbergvariationen") == 0);
+ pa_assert_se(pa_proplist_put(b, PA_PROP_MEDIA_ICON, "\0\1\2\3\4\5\6\7", 8) == 0);
+
+ pa_proplist_merge(a, b);
+
+ pa_assert_se(!pa_proplist_gets(a, PA_PROP_MEDIA_ICON));
+
+ printf("%s\n", pa_strnull(pa_proplist_gets(a, PA_PROP_MEDIA_TITLE)));
+ pa_assert_se(pa_proplist_remove(b, PA_PROP_MEDIA_TITLE) == 0);
+
+ s = pa_proplist_to_string(a);
+ t = pa_proplist_to_string(b);
+ printf("---\n%s---\n%s", s, t);
+ pa_xfree(s);
+ pa_xfree(t);
+
+ pa_proplist_free(a);
+ pa_proplist_free(b);
+
+ return 0;
+}
diff --git a/src/tests/queue-test.c b/src/tests/queue-test.c
new file mode 100644
index 00000000..b357ab10
--- /dev/null
+++ b/src/tests/queue-test.c
@@ -0,0 +1,69 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <assert.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <pulse/util.h>
+#include <pulse/xmalloc.h>
+#include <pulsecore/queue.h>
+#include <pulsecore/log.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/macro.h>
+
+int main(int argc, char *argv[]) {
+ pa_queue *q;
+
+ pa_assert_se(q = pa_queue_new());
+
+ pa_assert(pa_queue_is_empty(q));
+
+ pa_queue_push(q, (void*) "eins");
+ pa_log("%s\n", (char*) pa_queue_pop(q));
+
+ pa_assert(pa_queue_is_empty(q));
+
+ pa_queue_push(q, (void*) "zwei");
+ pa_queue_push(q, (void*) "drei");
+ pa_queue_push(q, (void*) "vier");
+
+ pa_log("%s\n", (char*) pa_queue_pop(q));
+ pa_log("%s\n", (char*) pa_queue_pop(q));
+
+ pa_queue_push(q, (void*) "fuenf");
+
+ pa_log("%s\n", (char*) pa_queue_pop(q));
+ pa_log("%s\n", (char*) pa_queue_pop(q));
+
+ pa_assert(pa_queue_is_empty(q));
+
+ pa_queue_push(q, (void*) "sechs");
+ pa_queue_push(q, (void*) "sieben");
+
+ pa_queue_free(q, NULL, NULL);
+
+ return 0;
+}
diff --git a/src/tests/remix-test.c b/src/tests/remix-test.c
new file mode 100644
index 00000000..d2fa6943
--- /dev/null
+++ b/src/tests/remix-test.c
@@ -0,0 +1,91 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+
+#include <pulse/sample.h>
+#include <pulse/volume.h>
+
+#include <pulsecore/resampler.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/endianmacros.h>
+#include <pulsecore/memblock.h>
+#include <pulsecore/sample-util.h>
+
+#include <liboil/liboil.h>
+
+int main(int argc, char *argv[]) {
+
+ static const pa_channel_map maps[] = {
+ { 1, { PA_CHANNEL_POSITION_MONO } },
+ { 2, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT } },
+ { 3, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT, PA_CHANNEL_POSITION_CENTER } },
+ { 3, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT, PA_CHANNEL_POSITION_LFE } },
+ { 3, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT, PA_CHANNEL_POSITION_REAR_CENTER } },
+ { 4, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT, PA_CHANNEL_POSITION_CENTER, PA_CHANNEL_POSITION_LFE } },
+ { 4, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT, PA_CHANNEL_POSITION_CENTER, PA_CHANNEL_POSITION_REAR_CENTER } },
+ { 4, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT, PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT } },
+ { 5, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT, PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, PA_CHANNEL_POSITION_CENTER } },
+ { 5, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT, PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, PA_CHANNEL_POSITION_LFE } },
+ { 6, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT, PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, PA_CHANNEL_POSITION_LFE, PA_CHANNEL_POSITION_CENTER } },
+ { 8, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT, PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, PA_CHANNEL_POSITION_LFE, PA_CHANNEL_POSITION_CENTER, PA_CHANNEL_POSITION_SIDE_LEFT, PA_CHANNEL_POSITION_SIDE_RIGHT } },
+ { 0, { 0 } }
+ };
+
+ unsigned i, j;
+ pa_mempool *pool;
+
+ oil_init();
+ pa_log_set_maximal_level(PA_LOG_DEBUG);
+
+ pa_assert_se(pool = pa_mempool_new(FALSE));
+
+ for (i = 0; maps[i].channels > 0; i++)
+ for (j = 0; maps[j].channels > 0; j++) {
+ char a[PA_CHANNEL_MAP_SNPRINT_MAX], b[PA_CHANNEL_MAP_SNPRINT_MAX];
+ pa_resampler *r;
+ pa_sample_spec ss1, ss2;
+
+ pa_log_info("Converting from '%s' to '%s'.\n", pa_channel_map_snprint(a, sizeof(a), &maps[i]), pa_channel_map_snprint(b, sizeof(b), &maps[j]));
+
+ ss1.channels = maps[i].channels;
+ ss2.channels = maps[j].channels;
+
+ ss1.rate = ss2.rate = 44100;
+ ss1.format = ss2.format = PA_SAMPLE_S16NE;
+
+ r = pa_resampler_new(pool, &ss1, &maps[i], &ss2, &maps[j], PA_RESAMPLER_AUTO, 0);
+
+ /* We don't really care for the resampler. We just want to
+ * see the remixing debug output. */
+
+ pa_resampler_free(r);
+ }
+
+
+ pa_mempool_free(pool);
+
+ return 0;
+}
diff --git a/src/tests/resampler-test.c b/src/tests/resampler-test.c
new file mode 100644
index 00000000..820a0c1e
--- /dev/null
+++ b/src/tests/resampler-test.c
@@ -0,0 +1,254 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+
+#include <pulse/sample.h>
+#include <pulse/volume.h>
+
+#include <pulsecore/resampler.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/endianmacros.h>
+#include <pulsecore/memblock.h>
+#include <pulsecore/sample-util.h>
+
+#include <liboil/liboil.h>
+
+static float swap_float(float a) {
+ uint32_t *b = (uint32_t*) &a;
+ *b = PA_UINT32_SWAP(*b);
+ return a;
+}
+
+static void dump_block(const pa_sample_spec *ss, const pa_memchunk *chunk) {
+ void *d;
+ unsigned i;
+
+ d = pa_memblock_acquire(chunk->memblock);
+
+ switch (ss->format) {
+
+ case PA_SAMPLE_U8:
+ case PA_SAMPLE_ULAW:
+ case PA_SAMPLE_ALAW: {
+ uint8_t *u = d;
+
+ for (i = 0; i < chunk->length / pa_frame_size(ss); i++)
+ printf("0x%02x ", *(u++));
+
+ break;
+ }
+
+ case PA_SAMPLE_S16NE:
+ case PA_SAMPLE_S16RE: {
+ uint16_t *u = d;
+
+ for (i = 0; i < chunk->length / pa_frame_size(ss); i++)
+ printf("0x%04x ", *(u++));
+
+ break;
+ }
+
+ case PA_SAMPLE_S32NE:
+ case PA_SAMPLE_S32RE: {
+ uint32_t *u = d;
+
+ for (i = 0; i < chunk->length / pa_frame_size(ss); i++)
+ printf("0x%08x ", *(u++));
+
+ break;
+ }
+
+ case PA_SAMPLE_FLOAT32NE:
+ case PA_SAMPLE_FLOAT32RE: {
+ float *u = d;
+
+ for (i = 0; i < chunk->length / pa_frame_size(ss); i++) {
+ printf("%1.3g ", ss->format == PA_SAMPLE_FLOAT32NE ? *u : swap_float(*u));
+ u++;
+ }
+
+ break;
+ }
+
+ default:
+ pa_assert_not_reached();
+ }
+
+ printf("\n");
+
+ pa_memblock_release(chunk->memblock);
+}
+
+static pa_memblock* generate_block(pa_mempool *pool, const pa_sample_spec *ss) {
+ pa_memblock *r;
+ void *d;
+ unsigned i;
+
+ pa_assert_se(r = pa_memblock_new(pool, pa_frame_size(ss) * 10));
+ d = pa_memblock_acquire(r);
+
+ switch (ss->format) {
+
+ case PA_SAMPLE_U8:
+ case PA_SAMPLE_ULAW:
+ case PA_SAMPLE_ALAW: {
+ uint8_t *u = d;
+
+ u[0] = 0x00;
+ u[1] = 0xFF;
+ u[2] = 0x7F;
+ u[3] = 0x80;
+ u[4] = 0x9f;
+ u[5] = 0x3f;
+ u[6] = 0x1;
+ u[7] = 0xF0;
+ u[8] = 0x20;
+ u[9] = 0x21;
+ break;
+ }
+
+ case PA_SAMPLE_S16NE:
+ case PA_SAMPLE_S16RE: {
+ uint16_t *u = d;
+
+ u[0] = 0x0000;
+ u[1] = 0xFFFF;
+ u[2] = 0x7FFF;
+ u[3] = 0x8000;
+ u[4] = 0x9fff;
+ u[5] = 0x3fff;
+ u[6] = 0x1;
+ u[7] = 0xF000;
+ u[8] = 0x20;
+ u[9] = 0x21;
+ break;
+ }
+
+ case PA_SAMPLE_S32NE:
+ case PA_SAMPLE_S32RE: {
+ uint32_t *u = d;
+
+ u[0] = 0x00000001;
+ u[1] = 0xFFFF0002;
+ u[2] = 0x7FFF0003;
+ u[3] = 0x80000004;
+ u[4] = 0x9fff0005;
+ u[5] = 0x3fff0006;
+ u[6] = 0x10007;
+ u[7] = 0xF0000008;
+ u[8] = 0x200009;
+ u[9] = 0x21000A;
+ break;
+ }
+
+ case PA_SAMPLE_FLOAT32NE:
+ case PA_SAMPLE_FLOAT32RE: {
+ float *u = d;
+
+ u[0] = 0.0;
+ u[1] = -1.0;
+ u[2] = 1.0;
+ u[3] = 4711;
+ u[4] = 0.222;
+ u[5] = 0.33;
+ u[6] = -.3;
+ u[7] = 99;
+ u[8] = -0.555;
+ u[9] = -.123;
+
+ if (ss->format == PA_SAMPLE_FLOAT32RE)
+ for (i = 0; i < 10; i++)
+ u[i] = swap_float(u[i]);
+
+ break;
+ }
+
+ default:
+ pa_assert_not_reached();
+ }
+
+ pa_memblock_release(r);
+
+ return r;
+}
+
+int main(int argc, char *argv[]) {
+ pa_mempool *pool;
+ pa_sample_spec a, b;
+ pa_cvolume v;
+
+ oil_init();
+ pa_log_set_maximal_level(PA_LOG_DEBUG);
+
+ pa_assert_se(pool = pa_mempool_new(FALSE));
+
+ a.channels = b.channels = 1;
+ a.rate = b.rate = 44100;
+
+ v.channels = a.channels;
+ v.values[0] = pa_sw_volume_from_linear(0.5);
+
+ for (a.format = 0; a.format < PA_SAMPLE_MAX; a.format ++) {
+ for (b.format = 0; b.format < PA_SAMPLE_MAX; b.format ++) {
+
+ pa_resampler *forth, *back;
+ pa_memchunk i, j, k;
+
+ printf("=== %s -> %s -> %s -> /2\n",
+ pa_sample_format_to_string(a.format),
+ pa_sample_format_to_string(b.format),
+ pa_sample_format_to_string(a.format));
+
+ pa_assert_se(forth = pa_resampler_new(pool, &a, NULL, &b, NULL, PA_RESAMPLER_AUTO, 0));
+ pa_assert_se(back = pa_resampler_new(pool, &b, NULL, &a, NULL, PA_RESAMPLER_AUTO, 0));
+
+ i.memblock = generate_block(pool, &a);
+ i.length = pa_memblock_get_length(i.memblock);
+ i.index = 0;
+ pa_resampler_run(forth, &i, &j);
+ pa_resampler_run(back, &j, &k);
+
+ dump_block(&a, &i);
+ dump_block(&b, &j);
+ dump_block(&a, &k);
+
+ pa_memblock_unref(j.memblock);
+ pa_memblock_unref(k.memblock);
+
+ pa_volume_memchunk(&i, &a, &v);
+ dump_block(&a, &i);
+
+ pa_memblock_unref(i.memblock);
+
+ pa_resampler_free(forth);
+ pa_resampler_free(back);
+ }
+ }
+
+ pa_mempool_free(pool);
+
+ return 0;
+}
diff --git a/src/tests/rtpoll-test.c b/src/tests/rtpoll-test.c
new file mode 100644
index 00000000..e6493771
--- /dev/null
+++ b/src/tests/rtpoll-test.c
@@ -0,0 +1,93 @@
+/* $Id: thread-test.c 1621 2007-08-10 22:00:22Z lennart $ */
+
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <signal.h>
+#include <poll.h>
+
+#include <pulsecore/log.h>
+#include <pulsecore/rtpoll.h>
+#include <pulsecore/rtsig.h>
+
+static int before(pa_rtpoll_item *i) {
+ pa_log("before");
+ return 0;
+}
+
+static void after(pa_rtpoll_item *i) {
+ pa_log("after");
+}
+
+static int worker(pa_rtpoll_item *w) {
+ pa_log("worker");
+ return 0;
+}
+
+int main(int argc, char *argv[]) {
+ pa_rtpoll *p;
+ pa_rtpoll_item *i, *w;
+ struct pollfd *pollfd;
+
+#ifdef SIGRTMIN
+ pa_rtsig_configure(SIGRTMIN+10, SIGRTMAX);
+#endif
+
+ p = pa_rtpoll_new();
+
+ i = pa_rtpoll_item_new(p, PA_RTPOLL_EARLY, 1);
+ pa_rtpoll_item_set_before_callback(i, before);
+ pa_rtpoll_item_set_after_callback(i, after);
+
+ pollfd = pa_rtpoll_item_get_pollfd(i, NULL);
+ pollfd->fd = 0;
+ pollfd->events = POLLIN;
+
+ w = pa_rtpoll_item_new(p, PA_RTPOLL_NORMAL, 0);
+ pa_rtpoll_item_set_before_callback(w, worker);
+
+ pa_rtpoll_install(p);
+ pa_rtpoll_set_timer_periodic(p, 10000000); /* 10 s */
+
+ pa_rtpoll_run(p, 1);
+
+ pa_rtpoll_item_free(i);
+
+ i = pa_rtpoll_item_new(p, PA_RTPOLL_EARLY, 1);
+ pa_rtpoll_item_set_before_callback(i, before);
+ pa_rtpoll_item_set_after_callback(i, after);
+
+ pollfd = pa_rtpoll_item_get_pollfd(i, NULL);
+ pollfd->fd = 0;
+ pollfd->events = POLLIN;
+
+ pa_rtpoll_run(p, 1);
+
+ pa_rtpoll_item_free(i);
+
+ pa_rtpoll_item_free(w);
+
+ pa_rtpoll_free(p);
+
+ return 0;
+}
diff --git a/src/tests/sig2str-test.c b/src/tests/sig2str-test.c
new file mode 100644
index 00000000..52cb9db4
--- /dev/null
+++ b/src/tests/sig2str-test.c
@@ -0,0 +1,39 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <signal.h>
+#include <stdio.h>
+
+#include <pulsecore/macro.h>
+#include <pulsecore/core-util.h>
+
+int main(int argc, char *argv[]) {
+ int sig;
+
+ for (sig = -1; sig <= NSIG; sig++)
+ printf("%i = %s\n", sig, pa_sig2str(sig));
+
+ return 0;
+}
diff --git a/src/tests/smoother-test.c b/src/tests/smoother-test.c
new file mode 100644
index 00000000..caa7df70
--- /dev/null
+++ b/src/tests/smoother-test.c
@@ -0,0 +1,80 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <pulsecore/time-smoother.h>
+#include <pulse/timeval.h>
+
+int main(int argc, char*argv[]) {
+ pa_usec_t x;
+ unsigned u = 0;
+ pa_smoother *s;
+ int m;
+
+/* unsigned msec[] = { */
+/* 200, 200, */
+/* 300, 320, */
+/* 400, 400, */
+/* 500, 480, */
+/* 0, 0 */
+/* }; */
+
+ int msec[200];
+
+ srand(0);
+
+ for (m = 0, u = 0; u < PA_ELEMENTSOF(msec)-2; u+= 2) {
+
+ msec[u] = m+1;
+ msec[u+1] = m + rand() % 2000 - 1000;
+
+ m += rand() % 100;
+
+ if (msec[u+1] < 0)
+ msec[u+1] = 0;
+ }
+
+ msec[PA_ELEMENTSOF(msec)-2] = 0;
+ msec[PA_ELEMENTSOF(msec)-1] = 0;
+
+ s = pa_smoother_new(1000*PA_USEC_PER_MSEC, 2000*PA_USEC_PER_MSEC, TRUE);
+
+ for (x = 0, u = 0; x < PA_USEC_PER_SEC * 10; x += PA_USEC_PER_MSEC) {
+
+ while (msec[u] > 0 && (pa_usec_t) msec[u]*PA_USEC_PER_MSEC < x) {
+ pa_smoother_put(s, msec[u]*PA_USEC_PER_MSEC, msec[u+1]*PA_USEC_PER_MSEC);
+ printf("%i\t\t%i\n", msec[u], msec[u+1]);
+ u += 2;
+ }
+
+ printf("%llu\t%llu\n", x/PA_USEC_PER_MSEC, pa_smoother_get(s, x)/PA_USEC_PER_MSEC);
+ }
+
+ pa_smoother_free(s);
+
+ return 0;
+}
diff --git a/src/tests/strlist-test.c b/src/tests/strlist-test.c
new file mode 100644
index 00000000..47770b5d
--- /dev/null
+++ b/src/tests/strlist-test.c
@@ -0,0 +1,42 @@
+#include <stdio.h>
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/strlist.h>
+#include <pulsecore/gccmacro.h>
+
+int main(PA_GCC_UNUSED int argc, PA_GCC_UNUSED char* argv[]) {
+ char *t, *u;
+ pa_strlist *l = NULL;
+
+ l = pa_strlist_prepend(l, "e");
+ l = pa_strlist_prepend(l, "d");
+ l = pa_strlist_prepend(l, "c");
+ l = pa_strlist_prepend(l, "b");
+ l = pa_strlist_prepend(l, "a");
+
+ t = pa_strlist_tostring(l);
+ pa_strlist_free(l);
+
+ fprintf(stderr, "1: %s\n", t);
+
+ l = pa_strlist_parse(t);
+ pa_xfree(t);
+
+ t = pa_strlist_tostring(l);
+ fprintf(stderr, "2: %s\n", t);
+ pa_xfree(t);
+
+ l = pa_strlist_pop(l, &u);
+ fprintf(stderr, "3: %s\n", u);
+ pa_xfree(u);
+
+ l = pa_strlist_remove(l, "c");
+
+ t = pa_strlist_tostring(l);
+ fprintf(stderr, "4: %s\n", t);
+ pa_xfree(t);
+
+ pa_strlist_free(l);
+
+ return 0;
+}
diff --git a/src/tests/sync-playback.c b/src/tests/sync-playback.c
new file mode 100644
index 00000000..63510eb6
--- /dev/null
+++ b/src/tests/sync-playback.c
@@ -0,0 +1,192 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <signal.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <math.h>
+
+#include <pulse/pulseaudio.h>
+#include <pulse/mainloop.h>
+
+#define NSTREAMS 4
+#define SINE_HZ 440
+#define SAMPLE_HZ 8000
+
+static pa_context *context = NULL;
+static pa_stream *streams[NSTREAMS];
+static pa_mainloop_api *mainloop_api = NULL;
+
+static float data[SAMPLE_HZ]; /* one second space */
+
+static int n_streams_ready = 0;
+
+static const pa_sample_spec sample_spec = {
+ .format = PA_SAMPLE_FLOAT32,
+ .rate = SAMPLE_HZ,
+ .channels = 1
+};
+
+static const pa_buffer_attr buffer_attr = {
+ .maxlength = SAMPLE_HZ*sizeof(float)*NSTREAMS, /* exactly space for the entire play time */
+ .tlength = 0,
+ .prebuf = 0, /* Setting prebuf to 0 guarantees us the the streams will run synchronously, no matter what */
+ .minreq = 0
+};
+
+static void nop_free_cb(void *p) {}
+
+static void underflow_cb(struct pa_stream *s, void *userdata) {
+ int i = (int) (long) userdata;
+
+ fprintf(stderr, "Stream %i finished\n", i);
+
+ if (++n_streams_ready >= 2*NSTREAMS) {
+ fprintf(stderr, "We're done\n");
+ mainloop_api->quit(mainloop_api, 0);
+ }
+}
+
+/* This routine is called whenever the stream state changes */
+static void stream_state_callback(pa_stream *s, void *userdata) {
+ assert(s);
+
+ switch (pa_stream_get_state(s)) {
+ case PA_STREAM_UNCONNECTED:
+ case PA_STREAM_CREATING:
+ case PA_STREAM_TERMINATED:
+ break;
+
+ case PA_STREAM_READY: {
+
+ int r, i = (int) (long) userdata;
+
+ fprintf(stderr, "Writing data to stream %i.\n", i);
+
+ r = pa_stream_write(s, data, sizeof(data), nop_free_cb, sizeof(data) * i, PA_SEEK_ABSOLUTE);
+ assert(r == 0);
+
+ /* Be notified when this stream is drained */
+ pa_stream_set_underflow_callback(s, underflow_cb, userdata);
+
+ /* All streams have been set up, let's go! */
+ if (++n_streams_ready >= NSTREAMS) {
+ fprintf(stderr, "Uncorking\n");
+ pa_operation_unref(pa_stream_cork(s, 0, NULL, NULL));
+ }
+
+ break;
+ }
+
+ default:
+ case PA_STREAM_FAILED:
+ fprintf(stderr, "Stream error: %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
+ abort();
+ }
+}
+
+/* This is called whenever the context status changes */
+static void context_state_callback(pa_context *c, void *userdata) {
+ assert(c);
+
+ switch (pa_context_get_state(c)) {
+ case PA_CONTEXT_CONNECTING:
+ case PA_CONTEXT_AUTHORIZING:
+ case PA_CONTEXT_SETTING_NAME:
+ break;
+
+ case PA_CONTEXT_READY: {
+
+ int i;
+ fprintf(stderr, "Connection established.\n");
+
+ for (i = 0; i < NSTREAMS; i++) {
+ char name[64];
+
+ fprintf(stderr, "Creating stream %i\n", i);
+
+ snprintf(name, sizeof(name), "stream #%i", i);
+
+ streams[i] = pa_stream_new(c, name, &sample_spec, NULL);
+ assert(streams[i]);
+ pa_stream_set_state_callback(streams[i], stream_state_callback, (void*) (long) i);
+ pa_stream_connect_playback(streams[i], NULL, &buffer_attr, PA_STREAM_START_CORKED, NULL, i == 0 ? NULL : streams[0]);
+ }
+
+ break;
+ }
+
+ case PA_CONTEXT_TERMINATED:
+ mainloop_api->quit(mainloop_api, 0);
+ break;
+
+ case PA_CONTEXT_FAILED:
+ default:
+ fprintf(stderr, "Context error: %s\n", pa_strerror(pa_context_errno(c)));
+ abort();
+ }
+}
+
+int main(int argc, char *argv[]) {
+ pa_mainloop* m = NULL;
+ int i, ret = 0;
+
+ for (i = 0; i < SAMPLE_HZ; i++)
+ data[i] = (float) sin(((double) i/SAMPLE_HZ)*2*M_PI*SINE_HZ)/2;
+
+ for (i = 0; i < NSTREAMS; i++)
+ streams[i] = NULL;
+
+ /* Set up a new main loop */
+ m = pa_mainloop_new();
+ assert(m);
+
+ mainloop_api = pa_mainloop_get_api(m);
+
+ context = pa_context_new(mainloop_api, argv[0]);
+ assert(context);
+
+ pa_context_set_state_callback(context, context_state_callback, NULL);
+
+ pa_context_connect(context, NULL, 0, NULL);
+
+ if (pa_mainloop_run(m, &ret) < 0)
+ fprintf(stderr, "pa_mainloop_run() failed.\n");
+
+ pa_context_unref(context);
+
+ for (i = 0; i < NSTREAMS; i++)
+ if (streams[i])
+ pa_stream_unref(streams[i]);
+
+ pa_mainloop_free(m);
+
+ return ret;
+}
diff --git a/src/tests/thread-mainloop-test.c b/src/tests/thread-mainloop-test.c
new file mode 100644
index 00000000..558e53a5
--- /dev/null
+++ b/src/tests/thread-mainloop-test.c
@@ -0,0 +1,79 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+
+#include <pulse/timeval.h>
+#include <pulse/util.h>
+#include <pulse/thread-mainloop.h>
+
+#include <pulsecore/gccmacro.h>
+#include <pulsecore/macro.h>
+
+static void tcb(pa_mainloop_api*a, pa_time_event *e, const struct timeval *tv, void *userdata) {
+ pa_assert_se(pa_threaded_mainloop_in_thread(userdata));
+ fprintf(stderr, "TIME EVENT START\n");
+ pa_threaded_mainloop_signal(userdata, 1);
+ fprintf(stderr, "TIME EVENT END\n");
+}
+
+int main(PA_GCC_UNUSED int argc, PA_GCC_UNUSED char *argv[]) {
+ pa_mainloop_api *a;
+ pa_threaded_mainloop *m;
+ struct timeval tv;
+
+ pa_assert_se(m = pa_threaded_mainloop_new());
+ pa_assert_se(a = pa_threaded_mainloop_get_api(m));
+
+ pa_threaded_mainloop_start(m);
+
+ pa_threaded_mainloop_lock(m);
+
+ pa_assert_se(!pa_threaded_mainloop_in_thread(m));
+
+ pa_gettimeofday(&tv);
+ tv.tv_sec += 5;
+ a->time_new(a, &tv, tcb, m);
+
+ fprintf(stderr, "waiting 5s (signal)\n");
+ pa_threaded_mainloop_wait(m);
+ fprintf(stderr, "wait completed\n");
+ pa_threaded_mainloop_accept(m);
+ fprintf(stderr, "signal accepted\n");
+
+ pa_threaded_mainloop_unlock(m);
+
+ fprintf(stderr, "waiting 5s (sleep)\n");
+ pa_msleep(5000);
+
+ fprintf(stderr, "shutting down\n");
+
+ pa_threaded_mainloop_stop(m);
+
+ pa_threaded_mainloop_free(m);
+ return 0;
+}
diff --git a/src/tests/thread-test.c b/src/tests/thread-test.c
new file mode 100644
index 00000000..72dde6cb
--- /dev/null
+++ b/src/tests/thread-test.c
@@ -0,0 +1,144 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulsecore/thread.h>
+#include <pulsecore/mutex.h>
+#include <pulsecore/once.h>
+#include <pulsecore/log.h>
+#include <pulsecore/core-util.h>
+#include <pulse/xmalloc.h>
+
+static pa_mutex *mutex = NULL;
+static pa_cond *cond1 = NULL, *cond2 = NULL;
+static pa_tls *tls = NULL;
+
+static int magic_number = 0;
+
+#define THREADS_MAX 20
+
+static void once_func(void) {
+ pa_log("once!");
+}
+
+static pa_once once = PA_ONCE_INIT;
+
+static void thread_func(void *data) {
+ pa_tls_set(tls, data);
+
+ pa_log("thread_func() for %s starting...", (char*) pa_tls_get(tls));
+
+ pa_mutex_lock(mutex);
+
+ for (;;) {
+ int k, n;
+
+ pa_log("%s waiting ...", (char*) pa_tls_get(tls));
+
+ for (;;) {
+
+ if (magic_number < 0)
+ goto quit;
+
+ if (magic_number != 0)
+ break;
+
+ pa_cond_wait(cond1, mutex);
+ }
+
+ k = magic_number;
+ magic_number = 0;
+
+ pa_mutex_unlock(mutex);
+
+ pa_run_once(&once, once_func);
+
+ pa_cond_signal(cond2, 0);
+
+ pa_log("%s got number %i", (char*) pa_tls_get(tls), k);
+
+ /* Spin! */
+ for (n = 0; n < k; n++)
+ pa_thread_yield();
+
+ pa_mutex_lock(mutex);
+ }
+
+quit:
+
+ pa_mutex_unlock(mutex);
+
+ pa_log("thread_func() for %s done...", (char*) pa_tls_get(tls));
+}
+
+int main(int argc, char *argv[]) {
+ int i, k;
+ pa_thread* t[THREADS_MAX];
+
+ assert(pa_thread_is_running(pa_thread_self()));
+
+ mutex = pa_mutex_new(FALSE, FALSE);
+ cond1 = pa_cond_new();
+ cond2 = pa_cond_new();
+ tls = pa_tls_new(pa_xfree);
+
+ for (i = 0; i < THREADS_MAX; i++) {
+ t[i] = pa_thread_new(thread_func, pa_sprintf_malloc("Thread #%i", i+1));
+ assert(t[i]);
+ }
+
+ pa_mutex_lock(mutex);
+
+ pa_log("loop-init");
+
+ for (k = 0; k < 100; k++) {
+ assert(magic_number == 0);
+
+
+ magic_number = (int) rand() % 0x10000;
+
+ pa_log("iteration %i (%i)", k, magic_number);
+
+ pa_cond_signal(cond1, 0);
+
+ pa_cond_wait(cond2, mutex);
+ }
+
+ pa_log("loop-exit");
+
+ magic_number = -1;
+ pa_cond_signal(cond1, 1);
+
+ pa_mutex_unlock(mutex);
+
+ for (i = 0; i < THREADS_MAX; i++)
+ pa_thread_free(t[i]);
+
+ pa_mutex_free(mutex);
+ pa_cond_free(cond1);
+ pa_cond_free(cond2);
+ pa_tls_free(tls);
+
+ return 0;
+}
diff --git a/src/tests/utf8-test.c b/src/tests/utf8-test.c
new file mode 100644
index 00000000..b9594dcc
--- /dev/null
+++ b/src/tests/utf8-test.c
@@ -0,0 +1,26 @@
+/* $Id$ */
+
+#include <stdio.h>
+#include <assert.h>
+
+#include <pulse/utf8.h>
+#include <pulse/xmalloc.h>
+
+int main(int argc, char *argv[]) {
+ char *c;
+
+ assert(pa_utf8_valid("hallo"));
+ assert(pa_utf8_valid("hallo\n"));
+ assert(!pa_utf8_valid("hüpfburg\n"));
+ assert(pa_utf8_valid("hallo\n"));
+ assert(pa_utf8_valid("hüpfburg\n"));
+
+ printf("LATIN1: %s\n", c = pa_utf8_filter("hüpfburg"));
+ pa_xfree(c);
+ printf("UTF8: %sx\n", c = pa_utf8_filter("hüpfburg"));
+ pa_xfree(c);
+ printf("LATIN1: %sx\n", c = pa_utf8_filter("üxknärzmörzeltörszß³§dsjkfh"));
+ pa_xfree(c);
+
+ return 0;
+}
diff --git a/src/tests/voltest.c b/src/tests/voltest.c
new file mode 100644
index 00000000..dcc1ec51
--- /dev/null
+++ b/src/tests/voltest.c
@@ -0,0 +1,22 @@
+/* $Id$ */
+
+#include <stdio.h>
+
+#include <pulse/volume.h>
+#include <pulsecore/gccmacro.h>
+
+int main(PA_GCC_UNUSED int argc, PA_GCC_UNUSED char *argv[]) {
+ pa_volume_t v;
+
+ for (v = PA_VOLUME_MUTED; v <= PA_VOLUME_NORM*2; v += 256) {
+
+ double dB = pa_sw_volume_to_dB(v);
+ double f = pa_sw_volume_to_linear(v);
+
+ printf("Volume: %3i; percent: %i%%; decibel %0.2f; linear = %0.2f; volume(decibel): %3i; volume(linear): %3i\n",
+ v, (v*100)/PA_VOLUME_NORM, dB, f, pa_sw_volume_from_dB(dB), pa_sw_volume_from_linear(f));
+
+ }
+
+ return 0;
+}
diff --git a/src/utils/Makefile b/src/utils/Makefile
new file mode 120000
index 00000000..c110232d
--- /dev/null
+++ b/src/utils/Makefile
@@ -0,0 +1 @@
+../pulse/Makefile \ No newline at end of file
diff --git a/src/utils/pabrowse.c b/src/utils/pabrowse.c
new file mode 100644
index 00000000..d88001ef
--- /dev/null
+++ b/src/utils/pabrowse.c
@@ -0,0 +1,156 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <signal.h>
+
+#include <pulse/pulseaudio.h>
+#include <pulse/browser.h>
+
+static void exit_signal_callback(pa_mainloop_api*m, pa_signal_event *e, int sig, void *userdata) {
+ fprintf(stderr, "Got signal, exiting\n");
+ m->quit(m, 0);
+}
+
+static void dump_server(const pa_browse_info *i) {
+ char t[16];
+
+ if (i->cookie)
+ snprintf(t, sizeof(t), "0x%08x", *i->cookie);
+
+ printf("server: %s\n"
+ "server-version: %s\n"
+ "user-name: %s\n"
+ "fqdn: %s\n"
+ "cookie: %s\n",
+ i->server,
+ i->server_version ? i->server_version : "n/a",
+ i->user_name ? i->user_name : "n/a",
+ i->fqdn ? i->fqdn : "n/a",
+ i->cookie ? t : "n/a");
+}
+
+static void dump_device(const pa_browse_info *i) {
+ char ss[PA_SAMPLE_SPEC_SNPRINT_MAX];
+
+ if (i->sample_spec)
+ pa_sample_spec_snprint(ss, sizeof(ss), i->sample_spec);
+
+ printf("device: %s\n"
+ "description: %s\n"
+ "sample spec: %s\n",
+ i->device,
+ i->description ? i->description : "n/a",
+ i->sample_spec ? ss : "n/a");
+
+}
+
+static void browser_callback(pa_browser *b, pa_browse_opcode_t c, const pa_browse_info *i, void *userdata) {
+ assert(b && i);
+
+ switch (c) {
+
+ case PA_BROWSE_NEW_SERVER:
+ printf("\n=> new server <%s>\n", i->name);
+ dump_server(i);
+ break;
+
+ case PA_BROWSE_NEW_SINK:
+ printf("\n=> new sink <%s>\n", i->name);
+ dump_server(i);
+ dump_device(i);
+ break;
+
+ case PA_BROWSE_NEW_SOURCE:
+ printf("\n=> new source <%s>\n", i->name);
+ dump_server(i);
+ dump_device(i);
+ break;
+
+ case PA_BROWSE_REMOVE_SERVER:
+ printf("\n=> removed server <%s>\n", i->name);
+ break;
+
+ case PA_BROWSE_REMOVE_SINK:
+ printf("\n=> removed sink <%s>\n", i->name);
+ break;
+
+ case PA_BROWSE_REMOVE_SOURCE:
+ printf("\n=> removed source <%s>\n", i->name);
+ break;
+
+ default:
+ ;
+ }
+}
+
+static void error_callback(pa_browser *b, const char *s, void *userdata) {
+ pa_mainloop_api*m = userdata;
+
+ fprintf(stderr, "Failure: %s\n", s);
+ m->quit(m, 1);
+}
+
+int main(int argc, char *argv[]) {
+ pa_mainloop *mainloop = NULL;
+ pa_browser *browser = NULL;
+ int ret = 1, r;
+ const char *s;
+
+ if (!(mainloop = pa_mainloop_new()))
+ goto finish;
+
+ r = pa_signal_init(pa_mainloop_get_api(mainloop));
+ assert(r == 0);
+ pa_signal_new(SIGINT, exit_signal_callback, NULL);
+ pa_signal_new(SIGTERM, exit_signal_callback, NULL);
+ signal(SIGPIPE, SIG_IGN);
+
+ if (!(browser = pa_browser_new_full(pa_mainloop_get_api(mainloop), PA_BROWSE_FOR_SERVERS|PA_BROWSE_FOR_SINKS|PA_BROWSE_FOR_SOURCES, &s))) {
+ fprintf(stderr, "pa_browse_new_full(): %s\n", s);
+ goto finish;
+ }
+
+ pa_browser_set_callback(browser, browser_callback, NULL);
+ pa_browser_set_error_callback(browser, error_callback, pa_mainloop_get_api(mainloop));
+
+ ret = 0;
+ pa_mainloop_run(mainloop, &ret);
+
+finish:
+
+ if (browser)
+ pa_browser_unref(browser);
+
+ if (mainloop) {
+ pa_signal_done();
+ pa_mainloop_free(mainloop);
+ }
+
+ return ret;
+}
diff --git a/src/utils/pacat.c b/src/utils/pacat.c
new file mode 100644
index 00000000..68e308d8
--- /dev/null
+++ b/src/utils/pacat.c
@@ -0,0 +1,769 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <signal.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <fcntl.h>
+
+#include <pulse/pulseaudio.h>
+
+#define TIME_EVENT_USEC 50000
+
+#if PA_API_VERSION < 10
+#error Invalid PulseAudio API version
+#endif
+
+static enum { RECORD, PLAYBACK } mode = PLAYBACK;
+
+static pa_context *context = NULL;
+static pa_stream *stream = NULL;
+static pa_mainloop_api *mainloop_api = NULL;
+
+static void *buffer = NULL;
+static size_t buffer_length = 0, buffer_index = 0;
+
+static pa_io_event* stdio_event = NULL;
+
+static char *stream_name = NULL, *client_name = NULL, *device = NULL;
+
+static int verbose = 0;
+static pa_volume_t volume = PA_VOLUME_NORM;
+
+static pa_sample_spec sample_spec = {
+ .format = PA_SAMPLE_S16LE,
+ .rate = 44100,
+ .channels = 2
+};
+
+static pa_channel_map channel_map;
+static int channel_map_set = 0;
+
+static pa_stream_flags_t flags = 0;
+
+/* A shortcut for terminating the application */
+static void quit(int ret) {
+ assert(mainloop_api);
+ mainloop_api->quit(mainloop_api, ret);
+}
+
+/* Write some data to the stream */
+static void do_stream_write(size_t length) {
+ size_t l;
+ assert(length);
+
+ if (!buffer || !buffer_length)
+ return;
+
+ l = length;
+ if (l > buffer_length)
+ l = buffer_length;
+
+ if (pa_stream_write(stream, (uint8_t*) buffer + buffer_index, l, NULL, 0, PA_SEEK_RELATIVE) < 0) {
+ fprintf(stderr, "pa_stream_write() failed: %s\n", pa_strerror(pa_context_errno(context)));
+ quit(1);
+ return;
+ }
+
+ buffer_length -= l;
+ buffer_index += l;
+
+ if (!buffer_length) {
+ pa_xfree(buffer);
+ buffer = NULL;
+ buffer_index = buffer_length = 0;
+ }
+}
+
+/* This is called whenever new data may be written to the stream */
+static void stream_write_callback(pa_stream *s, size_t length, void *userdata) {
+ assert(s);
+ assert(length > 0);
+
+ if (stdio_event)
+ mainloop_api->io_enable(stdio_event, PA_IO_EVENT_INPUT);
+
+ if (!buffer)
+ return;
+
+ do_stream_write(length);
+}
+
+/* This is called whenever new data may is available */
+static void stream_read_callback(pa_stream *s, size_t length, void *userdata) {
+ const void *data;
+ assert(s);
+ assert(length > 0);
+
+ if (stdio_event)
+ mainloop_api->io_enable(stdio_event, PA_IO_EVENT_OUTPUT);
+
+ if (pa_stream_peek(s, &data, &length) < 0) {
+ fprintf(stderr, "pa_stream_peek() failed: %s\n", pa_strerror(pa_context_errno(context)));
+ quit(1);
+ return;
+ }
+
+ assert(data);
+ assert(length > 0);
+
+ if (buffer) {
+ fprintf(stderr, "Buffer overrun, dropping incoming data\n");
+ if (pa_stream_drop(s) < 0) {
+ fprintf(stderr, "pa_stream_drop() failed: %s\n", pa_strerror(pa_context_errno(context)));
+ quit(1);
+ }
+ return;
+ }
+
+ buffer = pa_xmalloc(buffer_length = length);
+ memcpy(buffer, data, length);
+ buffer_index = 0;
+ pa_stream_drop(s);
+}
+
+/* This routine is called whenever the stream state changes */
+static void stream_state_callback(pa_stream *s, void *userdata) {
+ assert(s);
+
+ switch (pa_stream_get_state(s)) {
+ case PA_STREAM_CREATING:
+ case PA_STREAM_TERMINATED:
+ break;
+
+ case PA_STREAM_READY:
+ if (verbose) {
+ const pa_buffer_attr *a;
+ char cmt[PA_CHANNEL_MAP_SNPRINT_MAX], sst[PA_SAMPLE_SPEC_SNPRINT_MAX];
+
+ fprintf(stderr, "Stream successfully created.\n");
+
+ if (!(a = pa_stream_get_buffer_attr(s)))
+ fprintf(stderr, "pa_stream_get_buffer_attr() failed: %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
+ else {
+
+ if (mode == PLAYBACK)
+ fprintf(stderr, "Buffer metrics: maxlength=%u, tlength=%u, prebuf=%u, minreq=%u\n", a->maxlength, a->tlength, a->prebuf, a->minreq);
+ else {
+ assert(mode == RECORD);
+ fprintf(stderr, "Buffer metrics: maxlength=%u, fragsize=%u\n", a->maxlength, a->fragsize);
+ }
+ }
+
+ fprintf(stderr, "Using sample spec '%s', channel map '%s'.\n",
+ pa_sample_spec_snprint(sst, sizeof(sst), pa_stream_get_sample_spec(s)),
+ pa_channel_map_snprint(cmt, sizeof(cmt), pa_stream_get_channel_map(s)));
+
+ fprintf(stderr, "Connected to device %s (%u, %ssuspended).\n",
+ pa_stream_get_device_name(s),
+ pa_stream_get_device_index(s),
+ pa_stream_is_suspended(s) ? "" : "not ");
+ }
+
+ break;
+
+ case PA_STREAM_FAILED:
+ default:
+ fprintf(stderr, "Stream error: %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
+ quit(1);
+ }
+}
+
+static void stream_suspended_callback(pa_stream *s, void *userdata) {
+ assert(s);
+
+ if (verbose) {
+ if (pa_stream_is_suspended(s))
+ fprintf(stderr, "Stream device suspended.\n");
+ else
+ fprintf(stderr, "Stream device resumed.\n");
+ }
+}
+
+static void stream_moved_callback(pa_stream *s, void *userdata) {
+ assert(s);
+
+ if (verbose)
+ fprintf(stderr, "Stream moved to device %s (%u, %ssuspended).\n", pa_stream_get_device_name(s), pa_stream_get_device_index(s), pa_stream_is_suspended(s) ? "" : "not ");
+}
+
+/* This is called whenever the context status changes */
+static void context_state_callback(pa_context *c, void *userdata) {
+ assert(c);
+
+ switch (pa_context_get_state(c)) {
+ case PA_CONTEXT_CONNECTING:
+ case PA_CONTEXT_AUTHORIZING:
+ case PA_CONTEXT_SETTING_NAME:
+ break;
+
+ case PA_CONTEXT_READY: {
+ int r;
+
+ assert(c);
+ assert(!stream);
+
+ if (verbose)
+ fprintf(stderr, "Connection established.\n");
+
+ if (!(stream = pa_stream_new(c, stream_name, &sample_spec, channel_map_set ? &channel_map : NULL))) {
+ fprintf(stderr, "pa_stream_new() failed: %s\n", pa_strerror(pa_context_errno(c)));
+ goto fail;
+ }
+
+ pa_stream_set_state_callback(stream, stream_state_callback, NULL);
+ pa_stream_set_write_callback(stream, stream_write_callback, NULL);
+ pa_stream_set_read_callback(stream, stream_read_callback, NULL);
+ pa_stream_set_suspended_callback(stream, stream_suspended_callback, NULL);
+ pa_stream_set_moved_callback(stream, stream_moved_callback, NULL);
+
+ if (mode == PLAYBACK) {
+ pa_cvolume cv;
+ if ((r = pa_stream_connect_playback(stream, device, NULL, flags, pa_cvolume_set(&cv, sample_spec.channels, volume), NULL)) < 0) {
+ fprintf(stderr, "pa_stream_connect_playback() failed: %s\n", pa_strerror(pa_context_errno(c)));
+ goto fail;
+ }
+
+ } else {
+ if ((r = pa_stream_connect_record(stream, device, NULL, flags)) < 0) {
+ fprintf(stderr, "pa_stream_connect_record() failed: %s\n", pa_strerror(pa_context_errno(c)));
+ goto fail;
+ }
+ }
+
+ break;
+ }
+
+ case PA_CONTEXT_TERMINATED:
+ quit(0);
+ break;
+
+ case PA_CONTEXT_FAILED:
+ default:
+ fprintf(stderr, "Connection failure: %s\n", pa_strerror(pa_context_errno(c)));
+ goto fail;
+ }
+
+ return;
+
+fail:
+ quit(1);
+
+}
+
+/* Connection draining complete */
+static void context_drain_complete(pa_context*c, void *userdata) {
+ pa_context_disconnect(c);
+}
+
+/* Stream draining complete */
+static void stream_drain_complete(pa_stream*s, int success, void *userdata) {
+ pa_operation *o;
+
+ if (!success) {
+ fprintf(stderr, "Failed to drain stream: %s\n", pa_strerror(pa_context_errno(context)));
+ quit(1);
+ }
+
+ if (verbose)
+ fprintf(stderr, "Playback stream drained.\n");
+
+ pa_stream_disconnect(stream);
+ pa_stream_unref(stream);
+ stream = NULL;
+
+ if (!(o = pa_context_drain(context, context_drain_complete, NULL)))
+ pa_context_disconnect(context);
+ else {
+ if (verbose)
+ fprintf(stderr, "Draining connection to server.\n");
+ }
+}
+
+/* New data on STDIN **/
+static void stdin_callback(pa_mainloop_api*a, pa_io_event *e, int fd, pa_io_event_flags_t f, void *userdata) {
+ size_t l, w = 0;
+ ssize_t r;
+
+ assert(a == mainloop_api);
+ assert(e);
+ assert(stdio_event == e);
+
+ if (buffer) {
+ mainloop_api->io_enable(stdio_event, PA_IO_EVENT_NULL);
+ return;
+ }
+
+ if (!stream || pa_stream_get_state(stream) != PA_STREAM_READY || !(l = w = pa_stream_writable_size(stream)))
+ l = 4096;
+
+ buffer = pa_xmalloc(l);
+
+ if ((r = read(fd, buffer, l)) <= 0) {
+ if (r == 0) {
+ if (verbose)
+ fprintf(stderr, "Got EOF.\n");
+
+ if (stream) {
+ pa_operation *o;
+
+ if (!(o = pa_stream_drain(stream, stream_drain_complete, NULL))) {
+ fprintf(stderr, "pa_stream_drain(): %s\n", pa_strerror(pa_context_errno(context)));
+ quit(1);
+ return;
+ }
+
+ pa_operation_unref(o);
+ } else
+ quit(0);
+
+ } else {
+ fprintf(stderr, "read() failed: %s\n", strerror(errno));
+ quit(1);
+ }
+
+ mainloop_api->io_free(stdio_event);
+ stdio_event = NULL;
+ return;
+ }
+
+ buffer_length = r;
+ buffer_index = 0;
+
+ if (w)
+ do_stream_write(w);
+}
+
+/* Some data may be written to STDOUT */
+static void stdout_callback(pa_mainloop_api*a, pa_io_event *e, int fd, pa_io_event_flags_t f, void *userdata) {
+ ssize_t r;
+
+ assert(a == mainloop_api);
+ assert(e);
+ assert(stdio_event == e);
+
+ if (!buffer) {
+ mainloop_api->io_enable(stdio_event, PA_IO_EVENT_NULL);
+ return;
+ }
+
+ assert(buffer_length);
+
+ if ((r = write(fd, (uint8_t*) buffer+buffer_index, buffer_length)) <= 0) {
+ fprintf(stderr, "write() failed: %s\n", strerror(errno));
+ quit(1);
+
+ mainloop_api->io_free(stdio_event);
+ stdio_event = NULL;
+ return;
+ }
+
+ buffer_length -= r;
+ buffer_index += r;
+
+ if (!buffer_length) {
+ pa_xfree(buffer);
+ buffer = NULL;
+ buffer_length = buffer_index = 0;
+ }
+}
+
+/* UNIX signal to quit recieved */
+static void exit_signal_callback(pa_mainloop_api*m, pa_signal_event *e, int sig, void *userdata) {
+ if (verbose)
+ fprintf(stderr, "Got signal, exiting.\n");
+ quit(0);
+}
+
+/* Show the current latency */
+static void stream_update_timing_callback(pa_stream *s, int success, void *userdata) {
+ pa_usec_t latency, usec;
+ int negative = 0;
+
+ assert(s);
+
+ if (!success ||
+ pa_stream_get_time(s, &usec) < 0 ||
+ pa_stream_get_latency(s, &latency, &negative) < 0) {
+ fprintf(stderr, "Failed to get latency: %s\n", pa_strerror(pa_context_errno(context)));
+ quit(1);
+ return;
+ }
+
+ fprintf(stderr, "Time: %0.3f sec; Latency: %0.0f usec. \r",
+ (float) usec / 1000000,
+ (float) latency * (negative?-1:1));
+}
+
+/* Someone requested that the latency is shown */
+static void sigusr1_signal_callback(pa_mainloop_api*m, pa_signal_event *e, int sig, void *userdata) {
+
+ if (!stream)
+ return;
+
+ pa_operation_unref(pa_stream_update_timing_info(stream, stream_update_timing_callback, NULL));
+}
+
+static void time_event_callback(pa_mainloop_api*m, pa_time_event *e, const struct timeval *tv, void *userdata) {
+ struct timeval next;
+
+ if (stream && pa_stream_get_state(stream) == PA_STREAM_READY) {
+ pa_operation *o;
+ if (!(o = pa_stream_update_timing_info(stream, stream_update_timing_callback, NULL)))
+ fprintf(stderr, "pa_stream_update_timing_info() failed: %s\n", pa_strerror(pa_context_errno(context)));
+ else
+ pa_operation_unref(o);
+ }
+
+ pa_gettimeofday(&next);
+ pa_timeval_add(&next, TIME_EVENT_USEC);
+
+ m->time_restart(e, &next);
+}
+
+static void help(const char *argv0) {
+
+ printf("%s [options]\n\n"
+ " -h, --help Show this help\n"
+ " --version Show version\n\n"
+ " -r, --record Create a connection for recording\n"
+ " -p, --playback Create a connection for playback\n\n"
+ " -v, --verbose Enable verbose operations\n\n"
+ " -s, --server=SERVER The name of the server to connect to\n"
+ " -d, --device=DEVICE The name of the sink/source to connect to\n"
+ " -n, --client-name=NAME How to call this client on the server\n"
+ " --stream-name=NAME How to call this stream on the server\n"
+ " --volume=VOLUME Specify the initial (linear) volume in range 0...65536\n"
+ " --rate=SAMPLERATE The sample rate in Hz (defaults to 44100)\n"
+ " --format=SAMPLEFORMAT The sample type, one of s16le, s16be, u8, float32le,\n"
+ " float32be, ulaw, alaw (defaults to s16ne)\n"
+ " --channels=CHANNELS The number of channels, 1 for mono, 2 for stereo\n"
+ " (defaults to 2)\n"
+ " --channel-map=CHANNELMAP Channel map to use instead of the default\n"
+ " --fix-format Take the sample format from the sink the stream is\n"
+ " being connected to.\n"
+ " --fix-rate Take the sampling rate from the sink the stream is\n"
+ " being connected to.\n"
+ " --fix-channels Take the number of channels and the channel map\n"
+ " from the sink the stream is being connected to.\n"
+ " --no-remix Don't upmix or downmix channels.\n"
+ " --no-remap Map channels by index instead of name.\n"
+ ,
+ argv0);
+}
+
+enum {
+ ARG_VERSION = 256,
+ ARG_STREAM_NAME,
+ ARG_VOLUME,
+ ARG_SAMPLERATE,
+ ARG_SAMPLEFORMAT,
+ ARG_CHANNELS,
+ ARG_CHANNELMAP,
+ ARG_FIX_FORMAT,
+ ARG_FIX_RATE,
+ ARG_FIX_CHANNELS,
+ ARG_NO_REMAP,
+ ARG_NO_REMIX
+};
+
+int main(int argc, char *argv[]) {
+ pa_mainloop* m = NULL;
+ int ret = 1, r, c;
+ char *bn, *server = NULL;
+ pa_time_event *time_event = NULL;
+
+ static const struct option long_options[] = {
+ {"record", 0, NULL, 'r'},
+ {"playback", 0, NULL, 'p'},
+ {"device", 1, NULL, 'd'},
+ {"server", 1, NULL, 's'},
+ {"client-name", 1, NULL, 'n'},
+ {"stream-name", 1, NULL, ARG_STREAM_NAME},
+ {"version", 0, NULL, ARG_VERSION},
+ {"help", 0, NULL, 'h'},
+ {"verbose", 0, NULL, 'v'},
+ {"volume", 1, NULL, ARG_VOLUME},
+ {"rate", 1, NULL, ARG_SAMPLERATE},
+ {"format", 1, NULL, ARG_SAMPLEFORMAT},
+ {"channels", 1, NULL, ARG_CHANNELS},
+ {"channel-map", 1, NULL, ARG_CHANNELMAP},
+ {"fix-format", 0, NULL, ARG_FIX_FORMAT},
+ {"fix-rate", 0, NULL, ARG_FIX_RATE},
+ {"fix-channels",0, NULL, ARG_FIX_CHANNELS},
+ {"no-remap", 0, NULL, ARG_NO_REMAP},
+ {"no-remix", 0, NULL, ARG_NO_REMIX},
+ {NULL, 0, NULL, 0}
+ };
+
+ if (!(bn = strrchr(argv[0], '/')))
+ bn = argv[0];
+ else
+ bn++;
+
+ if (strstr(bn, "rec") || strstr(bn, "mon"))
+ mode = RECORD;
+ else if (strstr(bn, "cat") || strstr(bn, "play"))
+ mode = PLAYBACK;
+
+ while ((c = getopt_long(argc, argv, "rpd:s:n:hv", long_options, NULL)) != -1) {
+
+ switch (c) {
+ case 'h' :
+ help(bn);
+ ret = 0;
+ goto quit;
+
+ case ARG_VERSION:
+ printf("pacat "PACKAGE_VERSION"\nCompiled with libpulse %s\nLinked with libpulse %s\n", pa_get_headers_version(), pa_get_library_version());
+ ret = 0;
+ goto quit;
+
+ case 'r':
+ mode = RECORD;
+ break;
+
+ case 'p':
+ mode = PLAYBACK;
+ break;
+
+ case 'd':
+ pa_xfree(device);
+ device = pa_xstrdup(optarg);
+ break;
+
+ case 's':
+ pa_xfree(server);
+ server = pa_xstrdup(optarg);
+ break;
+
+ case 'n':
+ pa_xfree(client_name);
+ client_name = pa_xstrdup(optarg);
+ break;
+
+ case ARG_STREAM_NAME:
+ pa_xfree(stream_name);
+ stream_name = pa_xstrdup(optarg);
+ break;
+
+ case 'v':
+ verbose = 1;
+ break;
+
+ case ARG_VOLUME: {
+ int v = atoi(optarg);
+ volume = v < 0 ? 0 : v;
+ break;
+ }
+
+ case ARG_CHANNELS:
+ sample_spec.channels = atoi(optarg);
+ break;
+
+ case ARG_SAMPLEFORMAT:
+ sample_spec.format = pa_parse_sample_format(optarg);
+ break;
+
+ case ARG_SAMPLERATE:
+ sample_spec.rate = atoi(optarg);
+ break;
+
+ case ARG_CHANNELMAP:
+ if (!pa_channel_map_parse(&channel_map, optarg)) {
+ fprintf(stderr, "Invalid channel map\n");
+ goto quit;
+ }
+
+ channel_map_set = 1;
+ break;
+
+ case ARG_FIX_CHANNELS:
+ flags |= PA_STREAM_FIX_CHANNELS;
+ break;
+
+ case ARG_FIX_RATE:
+ flags |= PA_STREAM_FIX_RATE;
+ break;
+
+ case ARG_FIX_FORMAT:
+ flags |= PA_STREAM_FIX_FORMAT;
+ break;
+
+ case ARG_NO_REMIX:
+ flags |= PA_STREAM_NO_REMIX_CHANNELS;
+ break;
+
+ case ARG_NO_REMAP:
+ flags |= PA_STREAM_NO_REMAP_CHANNELS;
+ break;
+
+ default:
+ goto quit;
+ }
+ }
+
+ if (!pa_sample_spec_valid(&sample_spec)) {
+ fprintf(stderr, "Invalid sample specification\n");
+ goto quit;
+ }
+
+ if (channel_map_set && channel_map.channels != sample_spec.channels) {
+ fprintf(stderr, "Channel map doesn't match sample specification\n");
+ goto quit;
+ }
+
+ if (verbose) {
+ char t[PA_SAMPLE_SPEC_SNPRINT_MAX];
+ pa_sample_spec_snprint(t, sizeof(t), &sample_spec);
+ fprintf(stderr, "Opening a %s stream with sample specification '%s'.\n", mode == RECORD ? "recording" : "playback", t);
+ }
+
+ if (!(optind >= argc)) {
+ if (optind+1 == argc) {
+ int fd;
+
+ if ((fd = open(argv[optind], mode == PLAYBACK ? O_RDONLY : O_WRONLY|O_TRUNC|O_CREAT, 0666)) < 0) {
+ fprintf(stderr, "open(): %s\n", strerror(errno));
+ goto quit;
+ }
+
+ if (dup2(fd, mode == PLAYBACK ? 0 : 1) < 0) {
+ fprintf(stderr, "dup2(): %s\n", strerror(errno));
+ goto quit;
+ }
+
+ close(fd);
+
+ if (!stream_name)
+ stream_name = pa_xstrdup(argv[optind]);
+
+ } else {
+ fprintf(stderr, "Too many arguments.\n");
+ goto quit;
+ }
+ }
+
+ if (!client_name)
+ client_name = pa_xstrdup(bn);
+
+ if (!stream_name)
+ stream_name = pa_xstrdup(client_name);
+
+ /* Set up a new main loop */
+ if (!(m = pa_mainloop_new())) {
+ fprintf(stderr, "pa_mainloop_new() failed.\n");
+ goto quit;
+ }
+
+ mainloop_api = pa_mainloop_get_api(m);
+
+ r = pa_signal_init(mainloop_api);
+ assert(r == 0);
+ pa_signal_new(SIGINT, exit_signal_callback, NULL);
+ pa_signal_new(SIGTERM, exit_signal_callback, NULL);
+#ifdef SIGUSR1
+ pa_signal_new(SIGUSR1, sigusr1_signal_callback, NULL);
+#endif
+#ifdef SIGPIPE
+ signal(SIGPIPE, SIG_IGN);
+#endif
+
+ if (!(stdio_event = mainloop_api->io_new(mainloop_api,
+ mode == PLAYBACK ? STDIN_FILENO : STDOUT_FILENO,
+ mode == PLAYBACK ? PA_IO_EVENT_INPUT : PA_IO_EVENT_OUTPUT,
+ mode == PLAYBACK ? stdin_callback : stdout_callback, NULL))) {
+ fprintf(stderr, "io_new() failed.\n");
+ goto quit;
+ }
+
+ /* Create a new connection context */
+ if (!(context = pa_context_new(mainloop_api, client_name))) {
+ fprintf(stderr, "pa_context_new() failed.\n");
+ goto quit;
+ }
+
+ pa_context_set_state_callback(context, context_state_callback, NULL);
+
+ /* Connect the context */
+ pa_context_connect(context, server, 0, NULL);
+
+ if (verbose) {
+ struct timeval tv;
+
+ pa_gettimeofday(&tv);
+ pa_timeval_add(&tv, TIME_EVENT_USEC);
+
+ if (!(time_event = mainloop_api->time_new(mainloop_api, &tv, time_event_callback, NULL))) {
+ fprintf(stderr, "time_new() failed.\n");
+ goto quit;
+ }
+ }
+
+ /* Run the main loop */
+ if (pa_mainloop_run(m, &ret) < 0) {
+ fprintf(stderr, "pa_mainloop_run() failed.\n");
+ goto quit;
+ }
+
+quit:
+ if (stream)
+ pa_stream_unref(stream);
+
+ if (context)
+ pa_context_unref(context);
+
+ if (stdio_event) {
+ assert(mainloop_api);
+ mainloop_api->io_free(stdio_event);
+ }
+
+ if (time_event) {
+ assert(mainloop_api);
+ mainloop_api->time_free(time_event);
+ }
+
+ if (m) {
+ pa_signal_done();
+ pa_mainloop_free(m);
+ }
+
+ pa_xfree(buffer);
+
+ pa_xfree(server);
+ pa_xfree(device);
+ pa_xfree(client_name);
+ pa_xfree(stream_name);
+
+ return ret;
+}
diff --git a/src/utils/pacmd.c b/src/utils/pacmd.c
new file mode 100644
index 00000000..daa6a96e
--- /dev/null
+++ b/src/utils/pacmd.c
@@ -0,0 +1,188 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <assert.h>
+#include <signal.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/un.h>
+
+#include <pulse/error.h>
+#include <pulse/util.h>
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/log.h>
+#include <pulsecore/pid.h>
+
+int main(PA_GCC_UNUSED int argc, PA_GCC_UNUSED char*argv[]) {
+ pid_t pid ;
+ int fd = -1;
+ int ret = 1, i;
+ struct sockaddr_un sa;
+ char ibuf[256], obuf[256];
+ size_t ibuf_index, ibuf_length, obuf_index, obuf_length;
+ fd_set ifds, ofds;
+
+ if (pa_pid_file_check_running(&pid, "pulseaudio") < 0) {
+ pa_log("no PulseAudio daemon running");
+ goto fail;
+ }
+
+ if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) {
+ pa_log("socket(PF_UNIX, SOCK_STREAM, 0): %s", strerror(errno));
+ goto fail;
+ }
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sun_family = AF_UNIX;
+ pa_runtime_path("cli", sa.sun_path, sizeof(sa.sun_path));
+
+ for (i = 0; i < 5; i++) {
+ int r;
+
+ if ((r = connect(fd, (struct sockaddr*) &sa, sizeof(sa))) < 0 && (errno != ECONNREFUSED && errno != ENOENT)) {
+ pa_log("connect(): %s", strerror(errno));
+ goto fail;
+ }
+
+ if (r >= 0)
+ break;
+
+ if (pa_pid_file_kill(SIGUSR2, NULL, "pulseaudio") < 0) {
+ pa_log("failed to kill PulseAudio daemon.");
+ goto fail;
+ }
+
+ pa_msleep(300);
+ }
+
+ if (i >= 5) {
+ pa_log("daemon not responding.");
+ goto fail;
+ }
+
+ ibuf_index = ibuf_length = obuf_index = obuf_length = 0;
+
+
+ FD_ZERO(&ifds);
+ FD_SET(0, &ifds);
+ FD_SET(fd, &ifds);
+
+ FD_ZERO(&ofds);
+
+ for (;;) {
+ if (select(FD_SETSIZE, &ifds, &ofds, NULL, NULL) < 0) {
+ pa_log("select(): %s", strerror(errno));
+ goto fail;
+ }
+
+ if (FD_ISSET(0, &ifds)) {
+ ssize_t r;
+ assert(!ibuf_length);
+
+ if ((r = read(0, ibuf, sizeof(ibuf))) <= 0) {
+ if (r == 0)
+ break;
+
+ pa_log("read(): %s", strerror(errno));
+ goto fail;
+ }
+
+ ibuf_length = (size_t) r;
+ ibuf_index = 0;
+ }
+
+ if (FD_ISSET(fd, &ifds)) {
+ ssize_t r;
+ assert(!obuf_length);
+
+ if ((r = read(fd, obuf, sizeof(obuf))) <= 0) {
+ if (r == 0)
+ break;
+
+ pa_log("read(): %s", strerror(errno));
+ goto fail;
+ }
+
+ obuf_length = (size_t) r;
+ obuf_index = 0;
+ }
+
+ if (FD_ISSET(1, &ofds)) {
+ ssize_t r;
+ assert(obuf_length);
+
+ if ((r = write(1, obuf + obuf_index, obuf_length)) < 0) {
+ pa_log("write(): %s", strerror(errno));
+ goto fail;
+ }
+
+ obuf_length -= (size_t) r;
+ obuf_index += obuf_index;
+
+ }
+
+ if (FD_ISSET(fd, &ofds)) {
+ ssize_t r;
+ assert(ibuf_length);
+
+ if ((r = write(fd, ibuf + ibuf_index, ibuf_length)) < 0) {
+ pa_log("write(): %s", strerror(errno));
+ goto fail;
+ }
+
+ ibuf_length -= (size_t) r;
+ ibuf_index += obuf_index;
+
+ }
+
+ FD_ZERO(&ifds);
+ FD_ZERO(&ofds);
+
+ if (obuf_length <= 0)
+ FD_SET(fd, &ifds);
+ else
+ FD_SET(1, &ofds);
+
+ if (ibuf_length <= 0)
+ FD_SET(0, &ifds);
+ else
+ FD_SET(fd, &ofds);
+ }
+
+
+ ret = 0;
+
+fail:
+ if (fd >= 0)
+ close(fd);
+
+ return ret;
+}
diff --git a/src/utils/pactl.c b/src/utils/pactl.c
new file mode 100644
index 00000000..4381d9d2
--- /dev/null
+++ b/src/utils/pactl.c
@@ -0,0 +1,930 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <signal.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <getopt.h>
+
+#include <sndfile.h>
+
+#include <pulse/pulseaudio.h>
+#include <pulsecore/core-util.h>
+
+#if PA_API_VERSION < 10
+#error Invalid PulseAudio API version
+#endif
+
+#define BUFSIZE 1024
+
+static pa_context *context = NULL;
+static pa_mainloop_api *mainloop_api = NULL;
+
+static char *device = NULL, *sample_name = NULL, *sink_name = NULL, *source_name = NULL, *module_name = NULL, *module_args = NULL;
+static uint32_t sink_input_idx = PA_INVALID_INDEX, source_output_idx = PA_INVALID_INDEX;
+static uint32_t module_index;
+static int suspend;
+
+static SNDFILE *sndfile = NULL;
+static pa_stream *sample_stream = NULL;
+static pa_sample_spec sample_spec;
+static size_t sample_length = 0;
+
+static int actions = 1;
+
+static int nl = 0;
+
+static enum {
+ NONE,
+ EXIT,
+ STAT,
+ UPLOAD_SAMPLE,
+ PLAY_SAMPLE,
+ REMOVE_SAMPLE,
+ LIST,
+ MOVE_SINK_INPUT,
+ MOVE_SOURCE_OUTPUT,
+ LOAD_MODULE,
+ UNLOAD_MODULE,
+ SUSPEND_SINK,
+ SUSPEND_SOURCE,
+} action = NONE;
+
+static void quit(int ret) {
+ assert(mainloop_api);
+ mainloop_api->quit(mainloop_api, ret);
+}
+
+
+static void context_drain_complete(pa_context *c, void *userdata) {
+ pa_context_disconnect(c);
+}
+
+static void drain(void) {
+ pa_operation *o;
+ if (!(o = pa_context_drain(context, context_drain_complete, NULL)))
+ pa_context_disconnect(context);
+ else
+ pa_operation_unref(o);
+}
+
+
+static void complete_action(void) {
+ assert(actions > 0);
+
+ if (!(--actions))
+ drain();
+}
+
+static void stat_callback(pa_context *c, const pa_stat_info *i, void *userdata) {
+ char s[128];
+ if (!i) {
+ fprintf(stderr, "Failed to get statistics: %s\n", pa_strerror(pa_context_errno(c)));
+ quit(1);
+ return;
+ }
+
+ pa_bytes_snprint(s, sizeof(s), i->memblock_total_size);
+ printf("Currently in use: %u blocks containing %s bytes total.\n", i->memblock_total, s);
+
+ pa_bytes_snprint(s, sizeof(s), i->memblock_allocated_size);
+ printf("Allocated during whole lifetime: %u blocks containing %s bytes total.\n", i->memblock_allocated, s);
+
+ pa_bytes_snprint(s, sizeof(s), i->scache_size);
+ printf("Sample cache size: %s\n", s);
+
+ complete_action();
+}
+
+static void get_server_info_callback(pa_context *c, const pa_server_info *i, void *useerdata) {
+ char s[PA_SAMPLE_SPEC_SNPRINT_MAX];
+
+ if (!i) {
+ fprintf(stderr, "Failed to get server information: %s\n", pa_strerror(pa_context_errno(c)));
+ quit(1);
+ return;
+ }
+
+ pa_sample_spec_snprint(s, sizeof(s), &i->sample_spec);
+
+ printf("User name: %s\n"
+ "Host Name: %s\n"
+ "Server Name: %s\n"
+ "Server Version: %s\n"
+ "Default Sample Specification: %s\n"
+ "Default Sink: %s\n"
+ "Default Source: %s\n"
+ "Cookie: %08x\n",
+ i->user_name,
+ i->host_name,
+ i->server_name,
+ i->server_version,
+ s,
+ i->default_sink_name,
+ i->default_source_name,
+ i->cookie);
+
+ complete_action();
+}
+
+static void get_sink_info_callback(pa_context *c, const pa_sink_info *i, int is_last, void *userdata) {
+ char s[PA_SAMPLE_SPEC_SNPRINT_MAX], cv[PA_CVOLUME_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX];
+
+ if (is_last < 0) {
+ fprintf(stderr, "Failed to get sink information: %s\n", pa_strerror(pa_context_errno(c)));
+ quit(1);
+ return;
+ }
+
+ if (is_last) {
+ complete_action();
+ return;
+ }
+
+ assert(i);
+
+ if (nl)
+ printf("\n");
+ nl = 1;
+
+ printf("*** Sink #%u ***\n"
+ "Name: %s\n"
+ "Driver: %s\n"
+ "Description: %s\n"
+ "Sample Specification: %s\n"
+ "Channel Map: %s\n"
+ "Owner Module: %u\n"
+ "Volume: %s\n"
+ "Monitor Source: %u\n"
+ "Latency: %0.0f usec\n"
+ "Flags: %s%s%s\n",
+ i->index,
+ i->name,
+ i->driver,
+ i->description,
+ pa_sample_spec_snprint(s, sizeof(s), &i->sample_spec),
+ pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map),
+ i->owner_module,
+ i->mute ? "muted" : pa_cvolume_snprint(cv, sizeof(cv), &i->volume),
+ i->monitor_source,
+ (double) i->latency,
+ i->flags & PA_SINK_HW_VOLUME_CTRL ? "HW_VOLUME_CTRL " : "",
+ i->flags & PA_SINK_LATENCY ? "LATENCY " : "",
+ i->flags & PA_SINK_HARDWARE ? "HARDWARE" : "");
+
+}
+
+static void get_source_info_callback(pa_context *c, const pa_source_info *i, int is_last, void *userdata) {
+ char s[PA_SAMPLE_SPEC_SNPRINT_MAX], t[32], cv[PA_CVOLUME_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX];
+
+ if (is_last < 0) {
+ fprintf(stderr, "Failed to get source information: %s\n", pa_strerror(pa_context_errno(c)));
+ quit(1);
+ return;
+ }
+
+ if (is_last) {
+ complete_action();
+ return;
+ }
+
+ assert(i);
+
+ if (nl)
+ printf("\n");
+ nl = 1;
+
+ snprintf(t, sizeof(t), "%u", i->monitor_of_sink);
+
+ printf("*** Source #%u ***\n"
+ "Name: %s\n"
+ "Driver: %s\n"
+ "Description: %s\n"
+ "Sample Specification: %s\n"
+ "Channel Map: %s\n"
+ "Owner Module: %u\n"
+ "Volume: %s\n"
+ "Monitor of Sink: %s\n"
+ "Latency: %0.0f usec\n"
+ "Flags: %s%s%s\n",
+ i->index,
+ i->name,
+ i->driver,
+ i->description,
+ pa_sample_spec_snprint(s, sizeof(s), &i->sample_spec),
+ pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map),
+ i->owner_module,
+ i->mute ? "muted" : pa_cvolume_snprint(cv, sizeof(cv), &i->volume),
+ i->monitor_of_sink != PA_INVALID_INDEX ? t : "no",
+ (double) i->latency,
+ i->flags & PA_SOURCE_HW_VOLUME_CTRL ? "HW_VOLUME_CTRL " : "",
+ i->flags & PA_SOURCE_LATENCY ? "LATENCY " : "",
+ i->flags & PA_SOURCE_HARDWARE ? "HARDWARE" : "");
+
+}
+
+static void get_module_info_callback(pa_context *c, const pa_module_info *i, int is_last, void *userdata) {
+ char t[32];
+
+ if (is_last < 0) {
+ fprintf(stderr, "Failed to get module information: %s\n", pa_strerror(pa_context_errno(c)));
+ quit(1);
+ return;
+ }
+
+ if (is_last) {
+ complete_action();
+ return;
+ }
+
+ assert(i);
+
+ if (nl)
+ printf("\n");
+ nl = 1;
+
+ snprintf(t, sizeof(t), "%u", i->n_used);
+
+ printf("*** Module #%u ***\n"
+ "Name: %s\n"
+ "Argument: %s\n"
+ "Usage counter: %s\n"
+ "Auto unload: %s\n",
+ i->index,
+ i->name,
+ i->argument ? i->argument : "",
+ i->n_used != PA_INVALID_INDEX ? t : "n/a",
+ i->auto_unload ? "yes" : "no");
+}
+
+static void get_client_info_callback(pa_context *c, const pa_client_info *i, int is_last, void *userdata) {
+ char t[32];
+
+ if (is_last < 0) {
+ fprintf(stderr, "Failed to get client information: %s\n", pa_strerror(pa_context_errno(c)));
+ quit(1);
+ return;
+ }
+
+ if (is_last) {
+ complete_action();
+ return;
+ }
+
+ assert(i);
+
+ if (nl)
+ printf("\n");
+ nl = 1;
+
+ snprintf(t, sizeof(t), "%u", i->owner_module);
+
+ printf("*** Client #%u ***\n"
+ "Name: %s\n"
+ "Driver: %s\n"
+ "Owner Module: %s\n",
+ i->index,
+ i->name,
+ i->driver,
+ i->owner_module != PA_INVALID_INDEX ? t : "n/a");
+}
+
+static void get_sink_input_info_callback(pa_context *c, const pa_sink_input_info *i, int is_last, void *userdata) {
+ char t[32], k[32], s[PA_SAMPLE_SPEC_SNPRINT_MAX], cv[PA_CVOLUME_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX];
+
+ if (is_last < 0) {
+ fprintf(stderr, "Failed to get sink input information: %s\n", pa_strerror(pa_context_errno(c)));
+ quit(1);
+ return;
+ }
+
+ if (is_last) {
+ complete_action();
+ return;
+ }
+
+ assert(i);
+
+ if (nl)
+ printf("\n");
+ nl = 1;
+
+ snprintf(t, sizeof(t), "%u", i->owner_module);
+ snprintf(k, sizeof(k), "%u", i->client);
+
+ printf("*** Sink Input #%u ***\n"
+ "Name: %s\n"
+ "Driver: %s\n"
+ "Owner Module: %s\n"
+ "Client: %s\n"
+ "Sink: %u\n"
+ "Sample Specification: %s\n"
+ "Channel Map: %s\n"
+ "Volume: %s\n"
+ "Buffer Latency: %0.0f usec\n"
+ "Sink Latency: %0.0f usec\n"
+ "Resample method: %s\n",
+ i->index,
+ i->name,
+ i->driver,
+ i->owner_module != PA_INVALID_INDEX ? t : "n/a",
+ i->client != PA_INVALID_INDEX ? k : "n/a",
+ i->sink,
+ pa_sample_spec_snprint(s, sizeof(s), &i->sample_spec),
+ pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map),
+ i->mute ? "muted" : pa_cvolume_snprint(cv, sizeof(cv), &i->volume),
+ (double) i->buffer_usec,
+ (double) i->sink_usec,
+ i->resample_method ? i->resample_method : "n/a");
+}
+
+
+static void get_source_output_info_callback(pa_context *c, const pa_source_output_info *i, int is_last, void *userdata) {
+ char t[32], k[32], s[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX];
+
+ if (is_last < 0) {
+ fprintf(stderr, "Failed to get source output information: %s\n", pa_strerror(pa_context_errno(c)));
+ quit(1);
+ return;
+ }
+
+ if (is_last) {
+ complete_action();
+ return;
+ }
+
+ assert(i);
+
+ if (nl)
+ printf("\n");
+ nl = 1;
+
+
+ snprintf(t, sizeof(t), "%u", i->owner_module);
+ snprintf(k, sizeof(k), "%u", i->client);
+
+ printf("*** Source Output #%u ***\n"
+ "Name: %s\n"
+ "Driver: %s\n"
+ "Owner Module: %s\n"
+ "Client: %s\n"
+ "Source: %u\n"
+ "Sample Specification: %s\n"
+ "Channel Map: %s\n"
+ "Buffer Latency: %0.0f usec\n"
+ "Source Latency: %0.0f usec\n"
+ "Resample method: %s\n",
+ i->index,
+ i->name,
+ i->driver,
+ i->owner_module != PA_INVALID_INDEX ? t : "n/a",
+ i->client != PA_INVALID_INDEX ? k : "n/a",
+ i->source,
+ pa_sample_spec_snprint(s, sizeof(s), &i->sample_spec),
+ pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map),
+ (double) i->buffer_usec,
+ (double) i->source_usec,
+ i->resample_method ? i->resample_method : "n/a");
+}
+
+static void get_sample_info_callback(pa_context *c, const pa_sample_info *i, int is_last, void *userdata) {
+ char t[32], s[PA_SAMPLE_SPEC_SNPRINT_MAX], cv[PA_CVOLUME_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX];
+
+ if (is_last < 0) {
+ fprintf(stderr, "Failed to get sample information: %s\n", pa_strerror(pa_context_errno(c)));
+ quit(1);
+ return;
+ }
+
+ if (is_last) {
+ complete_action();
+ return;
+ }
+
+ assert(i);
+
+ if (nl)
+ printf("\n");
+ nl = 1;
+
+
+ pa_bytes_snprint(t, sizeof(t), i->bytes);
+
+ printf("*** Sample #%u ***\n"
+ "Name: %s\n"
+ "Volume: %s\n"
+ "Sample Specification: %s\n"
+ "Channel Map: %s\n"
+ "Duration: %0.1fs\n"
+ "Size: %s\n"
+ "Lazy: %s\n"
+ "Filename: %s\n",
+ i->index,
+ i->name,
+ pa_cvolume_snprint(cv, sizeof(cv), &i->volume),
+ pa_sample_spec_valid(&i->sample_spec) ? pa_sample_spec_snprint(s, sizeof(s), &i->sample_spec) : "n/a",
+ pa_sample_spec_valid(&i->sample_spec) ? pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map) : "n/a",
+ (double) i->duration/1000000,
+ t,
+ i->lazy ? "yes" : "no",
+ i->filename ? i->filename : "n/a");
+}
+
+static void get_autoload_info_callback(pa_context *c, const pa_autoload_info *i, int is_last, void *userdata) {
+ if (is_last < 0) {
+ fprintf(stderr, "Failed to get autoload information: %s\n", pa_strerror(pa_context_errno(c)));
+ quit(1);
+ return;
+ }
+
+ if (is_last) {
+ complete_action();
+ return;
+ }
+
+ assert(i);
+
+ if (nl)
+ printf("\n");
+ nl = 1;
+
+ printf("*** Autoload Entry #%u ***\n"
+ "Name: %s\n"
+ "Type: %s\n"
+ "Module: %s\n"
+ "Argument: %s\n",
+ i->index,
+ i->name,
+ i->type == PA_AUTOLOAD_SINK ? "sink" : "source",
+ i->module,
+ i->argument);
+}
+
+static void simple_callback(pa_context *c, int success, void *userdata) {
+ if (!success) {
+ fprintf(stderr, "Failure: %s\n", pa_strerror(pa_context_errno(c)));
+ quit(1);
+ return;
+ }
+
+ complete_action();
+}
+
+static void index_callback(pa_context *c, uint32_t idx, void *userdata) {
+ if (idx == PA_INVALID_INDEX) {
+ fprintf(stderr, "Failure: %s\n", pa_strerror(pa_context_errno(c)));
+ quit(1);
+ return;
+ }
+
+ printf("%u\n", idx);
+
+ complete_action();
+}
+
+static void stream_state_callback(pa_stream *s, void *userdata) {
+ assert(s);
+
+ switch (pa_stream_get_state(s)) {
+ case PA_STREAM_CREATING:
+ case PA_STREAM_READY:
+ break;
+
+ case PA_STREAM_TERMINATED:
+ drain();
+ break;
+
+ case PA_STREAM_FAILED:
+ default:
+ fprintf(stderr, "Failed to upload sample: %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
+ quit(1);
+ }
+}
+
+static void stream_write_callback(pa_stream *s, size_t length, void *userdata) {
+ sf_count_t l;
+ float *d;
+ assert(s && length && sndfile);
+
+ d = pa_xmalloc(length);
+
+ assert(sample_length >= length);
+ l = length/pa_frame_size(&sample_spec);
+
+ if ((sf_readf_float(sndfile, d, l)) != l) {
+ pa_xfree(d);
+ fprintf(stderr, "Premature end of file\n");
+ quit(1);
+ }
+
+ pa_stream_write(s, d, length, pa_xfree, 0, PA_SEEK_RELATIVE);
+
+ sample_length -= length;
+
+ if (sample_length <= 0) {
+ pa_stream_set_write_callback(sample_stream, NULL, NULL);
+ pa_stream_finish_upload(sample_stream);
+ }
+}
+
+static void context_state_callback(pa_context *c, void *userdata) {
+ assert(c);
+ switch (pa_context_get_state(c)) {
+ case PA_CONTEXT_CONNECTING:
+ case PA_CONTEXT_AUTHORIZING:
+ case PA_CONTEXT_SETTING_NAME:
+ break;
+
+ case PA_CONTEXT_READY:
+ switch (action) {
+ case STAT:
+ actions = 2;
+ pa_operation_unref(pa_context_stat(c, stat_callback, NULL));
+ pa_operation_unref(pa_context_get_server_info(c, get_server_info_callback, NULL));
+ break;
+
+ case PLAY_SAMPLE:
+ pa_operation_unref(pa_context_play_sample(c, sample_name, device, PA_VOLUME_NORM, simple_callback, NULL));
+ break;
+
+ case REMOVE_SAMPLE:
+ pa_operation_unref(pa_context_remove_sample(c, sample_name, simple_callback, NULL));
+ break;
+
+ case UPLOAD_SAMPLE:
+ sample_stream = pa_stream_new(c, sample_name, &sample_spec, NULL);
+ assert(sample_stream);
+
+ pa_stream_set_state_callback(sample_stream, stream_state_callback, NULL);
+ pa_stream_set_write_callback(sample_stream, stream_write_callback, NULL);
+ pa_stream_connect_upload(sample_stream, sample_length);
+ break;
+
+ case EXIT:
+ pa_operation_unref(pa_context_exit_daemon(c, NULL, NULL));
+ drain();
+
+ case LIST:
+ actions = 8;
+ pa_operation_unref(pa_context_get_module_info_list(c, get_module_info_callback, NULL));
+ pa_operation_unref(pa_context_get_sink_info_list(c, get_sink_info_callback, NULL));
+ pa_operation_unref(pa_context_get_source_info_list(c, get_source_info_callback, NULL));
+ pa_operation_unref(pa_context_get_sink_input_info_list(c, get_sink_input_info_callback, NULL));
+ pa_operation_unref(pa_context_get_source_output_info_list(c, get_source_output_info_callback, NULL));
+ pa_operation_unref(pa_context_get_client_info_list(c, get_client_info_callback, NULL));
+ pa_operation_unref(pa_context_get_sample_info_list(c, get_sample_info_callback, NULL));
+ pa_operation_unref(pa_context_get_autoload_info_list(c, get_autoload_info_callback, NULL));
+ break;
+
+ case MOVE_SINK_INPUT:
+ pa_operation_unref(pa_context_move_sink_input_by_name(c, sink_input_idx, sink_name, simple_callback, NULL));
+ break;
+
+ case MOVE_SOURCE_OUTPUT:
+ pa_operation_unref(pa_context_move_source_output_by_name(c, source_output_idx, source_name, simple_callback, NULL));
+ break;
+
+ case LOAD_MODULE:
+ pa_operation_unref(pa_context_load_module(c, module_name, module_args, index_callback, NULL));
+ break;
+
+ case UNLOAD_MODULE:
+ pa_operation_unref(pa_context_unload_module(c, module_index, simple_callback, NULL));
+ break;
+
+ case SUSPEND_SINK:
+ if (sink_name)
+ pa_operation_unref(pa_context_suspend_sink_by_name(c, sink_name, suspend, simple_callback, NULL));
+ else
+ pa_operation_unref(pa_context_suspend_sink_by_index(c, PA_INVALID_INDEX, suspend, simple_callback, NULL));
+ break;
+
+ case SUSPEND_SOURCE:
+ if (source_name)
+ pa_operation_unref(pa_context_suspend_source_by_name(c, source_name, suspend, simple_callback, NULL));
+ else
+ pa_operation_unref(pa_context_suspend_source_by_index(c, PA_INVALID_INDEX, suspend, simple_callback, NULL));
+ break;
+
+ default:
+ assert(0);
+ }
+ break;
+
+ case PA_CONTEXT_TERMINATED:
+ quit(0);
+ break;
+
+ case PA_CONTEXT_FAILED:
+ default:
+ fprintf(stderr, "Connection failure: %s\n", pa_strerror(pa_context_errno(c)));
+ quit(1);
+ }
+}
+
+static void exit_signal_callback(pa_mainloop_api *m, pa_signal_event *e, int sig, void *userdata) {
+ fprintf(stderr, "Got SIGINT, exiting.\n");
+ quit(0);
+}
+
+static void help(const char *argv0) {
+
+ printf("%s [options] stat\n"
+ "%s [options] list\n"
+ "%s [options] exit\n"
+ "%s [options] upload-sample FILENAME [NAME]\n"
+ "%s [options] play-sample NAME [SINK]\n"
+ "%s [options] remove-sample NAME\n"
+ "%s [options] move-sink-input ID SINK\n"
+ "%s [options] move-source-output ID SOURCE\n"
+ "%s [options] load-module NAME [ARGS ...]\n"
+ "%s [options] unload-module ID\n"
+ "%s [options] suspend-sink [SINK] 1|0\n"
+ "%s [options] suspend-source [SOURCE] 1|0\n\n"
+ " -h, --help Show this help\n"
+ " --version Show version\n\n"
+ " -s, --server=SERVER The name of the server to connect to\n"
+ " -n, --client-name=NAME How to call this client on the server\n",
+ argv0, argv0, argv0, argv0, argv0, argv0, argv0, argv0, argv0, argv0, argv0, argv0);
+}
+
+enum { ARG_VERSION = 256 };
+
+int main(int argc, char *argv[]) {
+ pa_mainloop* m = NULL;
+ char tmp[PATH_MAX];
+ int ret = 1, r, c;
+ char *server = NULL, *client_name = NULL, *bn;
+
+ static const struct option long_options[] = {
+ {"server", 1, NULL, 's'},
+ {"client-name", 1, NULL, 'n'},
+ {"version", 0, NULL, ARG_VERSION},
+ {"help", 0, NULL, 'h'},
+ {NULL, 0, NULL, 0}
+ };
+
+ if (!(bn = strrchr(argv[0], '/')))
+ bn = argv[0];
+ else
+ bn++;
+
+ while ((c = getopt_long(argc, argv, "s:n:h", long_options, NULL)) != -1) {
+ switch (c) {
+ case 'h' :
+ help(bn);
+ ret = 0;
+ goto quit;
+
+ case ARG_VERSION:
+ printf("pactl "PACKAGE_VERSION"\nCompiled with libpulse %s\nLinked with libpulse %s\n", pa_get_headers_version(), pa_get_library_version());
+ ret = 0;
+ goto quit;
+
+ case 's':
+ pa_xfree(server);
+ server = pa_xstrdup(optarg);
+ break;
+
+ case 'n':
+ pa_xfree(client_name);
+ client_name = pa_xstrdup(optarg);
+ break;
+
+ default:
+ goto quit;
+ }
+ }
+
+ if (!client_name)
+ client_name = pa_xstrdup(bn);
+
+ if (optind < argc) {
+ if (!strcmp(argv[optind], "stat"))
+ action = STAT;
+ else if (!strcmp(argv[optind], "exit"))
+ action = EXIT;
+ else if (!strcmp(argv[optind], "list"))
+ action = LIST;
+ else if (!strcmp(argv[optind], "upload-sample")) {
+ struct SF_INFO sfinfo;
+ action = UPLOAD_SAMPLE;
+
+ if (optind+1 >= argc) {
+ fprintf(stderr, "Please specify a sample file to load\n");
+ goto quit;
+ }
+
+ if (optind+2 < argc)
+ sample_name = pa_xstrdup(argv[optind+2]);
+ else {
+ char *f = strrchr(argv[optind+1], '/');
+ size_t n;
+ if (f)
+ f++;
+ else
+ f = argv[optind];
+
+ n = strcspn(f, ".");
+ strncpy(tmp, f, n);
+ tmp[n] = 0;
+ sample_name = pa_xstrdup(tmp);
+ }
+
+ memset(&sfinfo, 0, sizeof(sfinfo));
+ if (!(sndfile = sf_open(argv[optind+1], SFM_READ, &sfinfo))) {
+ fprintf(stderr, "Failed to open sound file.\n");
+ goto quit;
+ }
+
+ sample_spec.format = PA_SAMPLE_FLOAT32;
+ sample_spec.rate = sfinfo.samplerate;
+ sample_spec.channels = sfinfo.channels;
+
+ sample_length = sfinfo.frames*pa_frame_size(&sample_spec);
+ } else if (!strcmp(argv[optind], "play-sample")) {
+ action = PLAY_SAMPLE;
+ if (argc != optind+2 && argc != optind+3) {
+ fprintf(stderr, "You have to specify a sample name to play\n");
+ goto quit;
+ }
+
+ sample_name = pa_xstrdup(argv[optind+1]);
+
+ if (optind+2 < argc)
+ device = pa_xstrdup(argv[optind+2]);
+
+ } else if (!strcmp(argv[optind], "remove-sample")) {
+ action = REMOVE_SAMPLE;
+ if (argc != optind+2) {
+ fprintf(stderr, "You have to specify a sample name to remove\n");
+ goto quit;
+ }
+
+ sample_name = pa_xstrdup(argv[optind+1]);
+ } else if (!strcmp(argv[optind], "move-sink-input")) {
+ action = MOVE_SINK_INPUT;
+ if (argc != optind+3) {
+ fprintf(stderr, "You have to specify a sink input index and a sink\n");
+ goto quit;
+ }
+
+ sink_input_idx = atoi(argv[optind+1]);
+ sink_name = pa_xstrdup(argv[optind+2]);
+ } else if (!strcmp(argv[optind], "move-source-output")) {
+ action = MOVE_SOURCE_OUTPUT;
+ if (argc != optind+3) {
+ fprintf(stderr, "You have to specify a source output index and a source\n");
+ goto quit;
+ }
+
+ source_output_idx = atoi(argv[optind+1]);
+ source_name = pa_xstrdup(argv[optind+2]);
+ } else if (!strcmp(argv[optind], "load-module")) {
+ int i;
+ size_t n = 0;
+ char *p;
+
+ action = LOAD_MODULE;
+
+ if (argc <= optind+1) {
+ fprintf(stderr, "You have to specify a module name and arguments.\n");
+ goto quit;
+ }
+
+ module_name = argv[optind+1];
+
+ for (i = optind+2; i < argc; i++)
+ n += strlen(argv[i])+1;
+
+ if (n > 0) {
+ p = module_args = pa_xnew0(char, n);
+
+ for (i = optind+2; i < argc; i++)
+ p += sprintf(p, "%s%s", p == module_args ? "" : " ", argv[i]);
+ }
+
+ } else if (!strcmp(argv[optind], "unload-module")) {
+ action = UNLOAD_MODULE;
+
+ if (argc != optind+2) {
+ fprintf(stderr, "You have to specify a module index\n");
+ goto quit;
+ }
+
+ module_index = atoi(argv[optind+1]);
+
+ } else if (!strcmp(argv[optind], "suspend-sink")) {
+ action = SUSPEND_SINK;
+
+ if (argc > optind+3 || optind+1 >= argc) {
+ fprintf(stderr, "You may not specify more than one sink. You have to specify at least one boolean value.\n");
+ goto quit;
+ }
+
+ suspend = pa_parse_boolean(argv[argc-1]);
+
+ if (argc > optind+2)
+ sink_name = pa_xstrdup(argv[optind+1]);
+
+ } else if (!strcmp(argv[optind], "suspend-source")) {
+ action = SUSPEND_SOURCE;
+
+ if (argc > optind+3 || optind+1 >= argc) {
+ fprintf(stderr, "You may not specify more than one source. You have to specify at least one boolean value.\n");
+ goto quit;
+ }
+
+ suspend = pa_parse_boolean(argv[argc-1]);
+
+ if (argc > optind+2)
+ source_name = pa_xstrdup(argv[optind+1]);
+ }
+ }
+
+ if (action == NONE) {
+ fprintf(stderr, "No valid command specified.\n");
+ goto quit;
+ }
+
+ if (!(m = pa_mainloop_new())) {
+ fprintf(stderr, "pa_mainloop_new() failed.\n");
+ goto quit;
+ }
+
+ mainloop_api = pa_mainloop_get_api(m);
+
+ r = pa_signal_init(mainloop_api);
+ assert(r == 0);
+ pa_signal_new(SIGINT, exit_signal_callback, NULL);
+#ifdef SIGPIPE
+ signal(SIGPIPE, SIG_IGN);
+#endif
+
+ if (!(context = pa_context_new(mainloop_api, client_name))) {
+ fprintf(stderr, "pa_context_new() failed.\n");
+ goto quit;
+ }
+
+ pa_context_set_state_callback(context, context_state_callback, NULL);
+ pa_context_connect(context, server, 0, NULL);
+
+ if (pa_mainloop_run(m, &ret) < 0) {
+ fprintf(stderr, "pa_mainloop_run() failed.\n");
+ goto quit;
+ }
+
+quit:
+ if (sample_stream)
+ pa_stream_unref(sample_stream);
+
+ if (context)
+ pa_context_unref(context);
+
+ if (m) {
+ pa_signal_done();
+ pa_mainloop_free(m);
+ }
+
+ if (sndfile)
+ sf_close(sndfile);
+
+ pa_xfree(server);
+ pa_xfree(device);
+ pa_xfree(sample_name);
+ pa_xfree(sink_name);
+ pa_xfree(source_name);
+ pa_xfree(module_args);
+ pa_xfree(client_name);
+
+ return ret;
+}
diff --git a/src/utils/padsp b/src/utils/padsp
new file mode 100755
index 00000000..c70c3af7
--- /dev/null
+++ b/src/utils/padsp
@@ -0,0 +1,88 @@
+#!/bin/sh
+
+# $Id$
+#
+# 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 of the License, or
+# (at your option) any later version.
+#
+# PulseAudio is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# 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.
+
+while getopts 'hs:n:m:MSDd' param ; do
+ case $param in
+ s)
+ PULSE_SERVER="$OPTARG"
+ export PULSE_SERVER
+ ;;
+ n)
+ PADSP_CLIENT_NAME="$OPTARG"
+ export PADSP_CLIENT_NAME
+ ;;
+ m)
+ PADSP_STREAM_NAME="$OPTARG"
+ export PADSP_STREAM_NAME
+ ;;
+ M)
+ PADSP_NO_MIXER=1
+ export PADSP_NO_MIXER
+ ;;
+ S)
+ PADSP_NO_SNDSTAT=1
+ export PADSP_NO_SNDSTAT
+ ;;
+ D)
+ PADSP_NO_DSP=1
+ export PADSP_NO_DSP
+ ;;
+ d)
+ if [ x"$PADSP_DEBUG" = x ]; then
+ PADSP_DEBUG=1
+ else
+ PADSP_DEBUG=$(( $PADSP_DEBUG + 1 ))
+ fi
+ export PADSP_DEBUG
+ ;;
+ *)
+ echo "$0 - redirect OSS audio devices to PulseAudio"
+ echo " "
+ echo "$0 [options] application [arguments]"
+ echo " "
+ echo "options:"
+ echo " -h show brief help"
+ echo " -s <host>[:<port>] contact a specific PulseAudio server"
+ echo " -n <name> client name to report to the server"
+ echo " -m <name> stream name to report to the server"
+ echo " -M disable /dev/mixer emulation"
+ echo " -S disable /dev/sndstat emulation"
+ echo " -D disable /dev/dsp emulation"
+ echo " -d enable debug output"
+ exit 0
+ ;;
+ esac
+done
+
+shift $(( $OPTIND - 1 ))
+
+if [ x"$LD_PRELOAD" = x ] ; then
+ LD_PRELOAD="libpulsedsp.so"
+else
+ LD_PRELOAD="$LD_PRELOAD libpulsedsp.so"
+fi
+
+export LD_PRELOAD
+
+exec "$@"
diff --git a/src/utils/padsp.c b/src/utils/padsp.c
new file mode 100644
index 00000000..cb57ff8a
--- /dev/null
+++ b/src/utils/padsp.c
@@ -0,0 +1,2664 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef _FILE_OFFSET_BITS
+#undef _FILE_OFFSET_BITS
+#endif
+
+#ifndef _LARGEFILE64_SOURCE
+#define _LARGEFILE64_SOURCE 1
+#endif
+
+#include <sys/soundcard.h>
+#include <sys/ioctl.h>
+#include <pthread.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <dlfcn.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <signal.h>
+
+#ifdef __linux__
+#include <linux/sockios.h>
+#endif
+
+#include <pulse/pulseaudio.h>
+#include <pulsecore/llist.h>
+#include <pulsecore/gccmacro.h>
+
+/* On some systems SIOCINQ isn't defined, but FIONREAD is just an alias */
+#if !defined(SIOCINQ) && defined(FIONREAD)
+# define SIOCINQ FIONREAD
+#endif
+
+/* make sure gcc doesn't redefine open and friends as macros */
+#undef open
+#undef open64
+
+typedef enum {
+ FD_INFO_MIXER,
+ FD_INFO_STREAM,
+} fd_info_type_t;
+
+typedef struct fd_info fd_info;
+
+struct fd_info {
+ pthread_mutex_t mutex;
+ int ref;
+ int unusable;
+
+ fd_info_type_t type;
+ int app_fd, thread_fd;
+
+ pa_sample_spec sample_spec;
+ size_t fragment_size;
+ unsigned n_fragments;
+
+ pa_threaded_mainloop *mainloop;
+ pa_context *context;
+ pa_stream *play_stream;
+ pa_stream *rec_stream;
+ int play_precork;
+ int rec_precork;
+
+ pa_io_event *io_event;
+ pa_io_event_flags_t io_flags;
+
+ void *buf;
+ size_t rec_offset;
+
+ int operation_success;
+
+ pa_cvolume sink_volume, source_volume;
+ uint32_t sink_index, source_index;
+ int volume_modify_count;
+
+ int optr_n_blocks;
+
+ PA_LLIST_FIELDS(fd_info);
+};
+
+static int dsp_drain(fd_info *i);
+static void fd_info_remove_from_list(fd_info *i);
+
+static pthread_mutex_t fd_infos_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_mutex_t func_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+static PA_LLIST_HEAD(fd_info, fd_infos) = NULL;
+
+static int (*_ioctl)(int, int, void*) = NULL;
+static int (*_close)(int) = NULL;
+static int (*_open)(const char *, int, mode_t) = NULL;
+static FILE* (*_fopen)(const char *path, const char *mode) = NULL;
+static int (*_stat)(const char *, struct stat *) = NULL;
+#ifdef _STAT_VER
+static int (*___xstat)(int, const char *, struct stat *) = NULL;
+#endif
+#ifdef HAVE_OPEN64
+static int (*_open64)(const char *, int, mode_t) = NULL;
+static FILE* (*_fopen64)(const char *path, const char *mode) = NULL;
+static int (*_stat64)(const char *, struct stat64 *) = NULL;
+#ifdef _STAT_VER
+static int (*___xstat64)(int, const char *, struct stat64 *) = NULL;
+#endif
+#endif
+static int (*_fclose)(FILE *f) = NULL;
+static int (*_access)(const char *, int) = NULL;
+
+/* dlsym() violates ISO C, so confide the breakage into this function to
+ * avoid warnings. */
+typedef void (*fnptr)(void);
+static inline fnptr dlsym_fn(void *handle, const char *symbol) {
+ return (fnptr) (long) dlsym(handle, symbol);
+}
+
+#define LOAD_IOCTL_FUNC() \
+do { \
+ pthread_mutex_lock(&func_mutex); \
+ if (!_ioctl) \
+ _ioctl = (int (*)(int, int, void*)) dlsym_fn(RTLD_NEXT, "ioctl"); \
+ pthread_mutex_unlock(&func_mutex); \
+} while(0)
+
+#define LOAD_OPEN_FUNC() \
+do { \
+ pthread_mutex_lock(&func_mutex); \
+ if (!_open) \
+ _open = (int (*)(const char *, int, mode_t)) dlsym_fn(RTLD_NEXT, "open"); \
+ pthread_mutex_unlock(&func_mutex); \
+} while(0)
+
+#define LOAD_OPEN64_FUNC() \
+do { \
+ pthread_mutex_lock(&func_mutex); \
+ if (!_open64) \
+ _open64 = (int (*)(const char *, int, mode_t)) dlsym_fn(RTLD_NEXT, "open64"); \
+ pthread_mutex_unlock(&func_mutex); \
+} while(0)
+
+#define LOAD_CLOSE_FUNC() \
+do { \
+ pthread_mutex_lock(&func_mutex); \
+ if (!_close) \
+ _close = (int (*)(int)) dlsym_fn(RTLD_NEXT, "close"); \
+ pthread_mutex_unlock(&func_mutex); \
+} while(0)
+
+#define LOAD_ACCESS_FUNC() \
+do { \
+ pthread_mutex_lock(&func_mutex); \
+ if (!_access) \
+ _access = (int (*)(const char*, int)) dlsym_fn(RTLD_NEXT, "access"); \
+ pthread_mutex_unlock(&func_mutex); \
+} while(0)
+
+#define LOAD_STAT_FUNC() \
+do { \
+ pthread_mutex_lock(&func_mutex); \
+ if (!_stat) \
+ _stat = (int (*)(const char *, struct stat *)) dlsym_fn(RTLD_NEXT, "stat"); \
+ pthread_mutex_unlock(&func_mutex); \
+} while(0)
+
+#define LOAD_STAT64_FUNC() \
+do { \
+ pthread_mutex_lock(&func_mutex); \
+ if (!_stat64) \
+ _stat64 = (int (*)(const char *, struct stat64 *)) dlsym_fn(RTLD_NEXT, "stat64"); \
+ pthread_mutex_unlock(&func_mutex); \
+} while(0)
+
+#define LOAD_XSTAT_FUNC() \
+do { \
+ pthread_mutex_lock(&func_mutex); \
+ if (!___xstat) \
+ ___xstat = (int (*)(int, const char *, struct stat *)) dlsym_fn(RTLD_NEXT, "__xstat"); \
+ pthread_mutex_unlock(&func_mutex); \
+} while(0)
+
+#define LOAD_XSTAT64_FUNC() \
+do { \
+ pthread_mutex_lock(&func_mutex); \
+ if (!___xstat64) \
+ ___xstat64 = (int (*)(int, const char *, struct stat64 *)) dlsym_fn(RTLD_NEXT, "__xstat64"); \
+ pthread_mutex_unlock(&func_mutex); \
+} while(0)
+
+#define LOAD_FOPEN_FUNC() \
+do { \
+ pthread_mutex_lock(&func_mutex); \
+ if (!_fopen) \
+ _fopen = (FILE* (*)(const char *, const char*)) dlsym_fn(RTLD_NEXT, "fopen"); \
+ pthread_mutex_unlock(&func_mutex); \
+} while(0)
+
+#define LOAD_FOPEN64_FUNC() \
+do { \
+ pthread_mutex_lock(&func_mutex); \
+ if (!_fopen64) \
+ _fopen64 = (FILE* (*)(const char *, const char*)) dlsym_fn(RTLD_NEXT, "fopen64"); \
+ pthread_mutex_unlock(&func_mutex); \
+} while(0)
+
+#define LOAD_FCLOSE_FUNC() \
+do { \
+ pthread_mutex_lock(&func_mutex); \
+ if (!_fclose) \
+ _fclose = (int (*)(FILE *)) dlsym_fn(RTLD_NEXT, "fclose"); \
+ pthread_mutex_unlock(&func_mutex); \
+} while(0)
+
+#define CONTEXT_CHECK_DEAD_GOTO(i, label) do { \
+if (!(i)->context || pa_context_get_state((i)->context) != PA_CONTEXT_READY) { \
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": Not connected: %s\n", (i)->context ? pa_strerror(pa_context_errno((i)->context)) : "NULL"); \
+ goto label; \
+} \
+} while(0)
+
+#define PLAYBACK_STREAM_CHECK_DEAD_GOTO(i, label) do { \
+if (!(i)->context || pa_context_get_state((i)->context) != PA_CONTEXT_READY || \
+ !(i)->play_stream || pa_stream_get_state((i)->play_stream) != PA_STREAM_READY) { \
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": Not connected: %s\n", (i)->context ? pa_strerror(pa_context_errno((i)->context)) : "NULL"); \
+ goto label; \
+} \
+} while(0)
+
+#define RECORD_STREAM_CHECK_DEAD_GOTO(i, label) do { \
+if (!(i)->context || pa_context_get_state((i)->context) != PA_CONTEXT_READY || \
+ !(i)->rec_stream || pa_stream_get_state((i)->rec_stream) != PA_STREAM_READY) { \
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": Not connected: %s\n", (i)->context ? pa_strerror(pa_context_errno((i)->context)) : "NULL"); \
+ goto label; \
+} \
+} while(0)
+
+static void debug(int level, const char *format, ...) PA_GCC_PRINTF_ATTR(2,3);
+
+#define DEBUG_LEVEL_ALWAYS 0
+#define DEBUG_LEVEL_NORMAL 1
+#define DEBUG_LEVEL_VERBOSE 2
+
+static void debug(int level, const char *format, ...) {
+ va_list ap;
+ const char *dlevel_s;
+ int dlevel;
+
+ dlevel_s = getenv("PADSP_DEBUG");
+ if (!dlevel_s)
+ return;
+
+ dlevel = atoi(dlevel_s);
+
+ if (dlevel < level)
+ return;
+
+ va_start(ap, format);
+ vfprintf(stderr, format, ap);
+ va_end(ap);
+}
+
+static int padsp_disabled(void) {
+ static int *sym;
+ static int sym_resolved = 0;
+
+ /* If the current process has a symbol __padsp_disabled__ we use
+ * it to detect whether we should enable our stuff or not. A
+ * program needs to be compiled with -rdynamic for this to work!
+ * The symbol must be an int containing a three bit bitmask: bit 1
+ * -> disable /dev/dsp emulation, bit 2 -> disable /dev/sndstat
+ * emulation, bit 3 -> disable /dev/mixer emulation. Hence a value
+ * of 7 disables padsp entirely. */
+
+ pthread_mutex_lock(&func_mutex);
+ if (!sym_resolved) {
+ sym = (int*) dlsym(RTLD_DEFAULT, "__padsp_disabled__");
+ sym_resolved = 1;
+
+ }
+ pthread_mutex_unlock(&func_mutex);
+
+ if (!sym)
+ return 0;
+
+ return *sym;
+}
+
+static int dsp_cloak_enable(void) {
+ if (padsp_disabled() & 1)
+ return 0;
+
+ if (getenv("PADSP_NO_DSP"))
+ return 0;
+
+ return 1;
+}
+
+static int sndstat_cloak_enable(void) {
+ if (padsp_disabled() & 2)
+ return 0;
+
+ if (getenv("PADSP_NO_SNDSTAT"))
+ return 0;
+
+ return 1;
+}
+
+static int mixer_cloak_enable(void) {
+ if (padsp_disabled() & 4)
+ return 0;
+
+ if (getenv("PADSP_NO_MIXER"))
+ return 0;
+
+ return 1;
+}
+static pthread_key_t recursion_key;
+
+static void recursion_key_alloc(void) {
+ pthread_key_create(&recursion_key, NULL);
+}
+
+static int function_enter(void) {
+ /* Avoid recursive calls */
+ static pthread_once_t recursion_key_once = PTHREAD_ONCE_INIT;
+ pthread_once(&recursion_key_once, recursion_key_alloc);
+
+ if (pthread_getspecific(recursion_key))
+ return 0;
+
+ pthread_setspecific(recursion_key, (void*) 1);
+ return 1;
+}
+
+static void function_exit(void) {
+ pthread_setspecific(recursion_key, NULL);
+}
+
+static void fd_info_free(fd_info *i) {
+ assert(i);
+
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": freeing fd info (fd=%i)\n", i->app_fd);
+
+ dsp_drain(i);
+
+ if (i->mainloop)
+ pa_threaded_mainloop_stop(i->mainloop);
+
+ if (i->play_stream) {
+ pa_stream_disconnect(i->play_stream);
+ pa_stream_unref(i->play_stream);
+ }
+
+ if (i->rec_stream) {
+ pa_stream_disconnect(i->rec_stream);
+ pa_stream_unref(i->rec_stream);
+ }
+
+ if (i->context) {
+ pa_context_disconnect(i->context);
+ pa_context_unref(i->context);
+ }
+
+ if (i->mainloop)
+ pa_threaded_mainloop_free(i->mainloop);
+
+ if (i->app_fd >= 0) {
+ LOAD_CLOSE_FUNC();
+ _close(i->app_fd);
+ }
+
+ if (i->thread_fd >= 0) {
+ LOAD_CLOSE_FUNC();
+ _close(i->thread_fd);
+ }
+
+ free(i->buf);
+
+ pthread_mutex_destroy(&i->mutex);
+ free(i);
+}
+
+static fd_info *fd_info_ref(fd_info *i) {
+ assert(i);
+
+ pthread_mutex_lock(&i->mutex);
+ assert(i->ref >= 1);
+ i->ref++;
+
+ debug(DEBUG_LEVEL_VERBOSE, __FILE__": ref++, now %i\n", i->ref);
+ pthread_mutex_unlock(&i->mutex);
+
+ return i;
+}
+
+static void fd_info_unref(fd_info *i) {
+ int r;
+ pthread_mutex_lock(&i->mutex);
+ assert(i->ref >= 1);
+ r = --i->ref;
+ debug(DEBUG_LEVEL_VERBOSE, __FILE__": ref--, now %i\n", i->ref);
+ pthread_mutex_unlock(&i->mutex);
+
+ if (r <= 0)
+ fd_info_free(i);
+}
+
+static void context_state_cb(pa_context *c, void *userdata) {
+ fd_info *i = userdata;
+ assert(c);
+
+ switch (pa_context_get_state(c)) {
+ case PA_CONTEXT_READY:
+ case PA_CONTEXT_TERMINATED:
+ case PA_CONTEXT_FAILED:
+ pa_threaded_mainloop_signal(i->mainloop, 0);
+ break;
+
+ case PA_CONTEXT_UNCONNECTED:
+ case PA_CONTEXT_CONNECTING:
+ case PA_CONTEXT_AUTHORIZING:
+ case PA_CONTEXT_SETTING_NAME:
+ break;
+ }
+}
+
+static void reset_params(fd_info *i) {
+ assert(i);
+
+ i->sample_spec.format = PA_SAMPLE_U8;
+ i->sample_spec.channels = 1;
+ i->sample_spec.rate = 8000;
+ i->fragment_size = 0;
+ i->n_fragments = 0;
+}
+
+static const char *client_name(char *buf, size_t n) {
+ char p[PATH_MAX];
+ const char *e;
+
+ if ((e = getenv("PADSP_CLIENT_NAME")))
+ return e;
+
+ if (pa_get_binary_name(p, sizeof(p)))
+ snprintf(buf, n, "OSS Emulation[%s]", p);
+ else
+ snprintf(buf, n, "OSS");
+
+ return buf;
+}
+
+static const char *stream_name(void) {
+ const char *e;
+
+ if ((e = getenv("PADSP_STREAM_NAME")))
+ return e;
+
+ return "Audio Stream";
+}
+
+static void atfork_prepare(void) {
+ fd_info *i;
+
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": atfork_prepare() enter\n");
+
+ function_enter();
+
+ pthread_mutex_lock(&fd_infos_mutex);
+
+ for (i = fd_infos; i; i = i->next) {
+ pthread_mutex_lock(&i->mutex);
+ pa_threaded_mainloop_lock(i->mainloop);
+ }
+
+ pthread_mutex_lock(&func_mutex);
+
+
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": atfork_prepare() exit\n");
+}
+
+static void atfork_parent(void) {
+ fd_info *i;
+
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": atfork_parent() enter\n");
+
+ pthread_mutex_unlock(&func_mutex);
+
+ for (i = fd_infos; i; i = i->next) {
+ pa_threaded_mainloop_unlock(i->mainloop);
+ pthread_mutex_unlock(&i->mutex);
+ }
+
+ pthread_mutex_unlock(&fd_infos_mutex);
+
+ function_exit();
+
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": atfork_parent() exit\n");
+}
+
+static void atfork_child(void) {
+ fd_info *i;
+
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": atfork_child() enter\n");
+
+ /* We do only the bare minimum to get all fds closed */
+ pthread_mutex_init(&func_mutex, NULL);
+ pthread_mutex_init(&fd_infos_mutex, NULL);
+
+ for (i = fd_infos; i; i = i->next) {
+ pthread_mutex_init(&i->mutex, NULL);
+
+ if (i->context) {
+ pa_context_disconnect(i->context);
+ pa_context_unref(i->context);
+ i->context = NULL;
+ }
+
+ if (i->play_stream) {
+ pa_stream_unref(i->play_stream);
+ i->play_stream = NULL;
+ }
+
+ if (i->rec_stream) {
+ pa_stream_unref(i->rec_stream);
+ i->rec_stream = NULL;
+ }
+
+ if (i->app_fd >= 0) {
+ close(i->app_fd);
+ i->app_fd = -1;
+ }
+
+ if (i->thread_fd >= 0) {
+ close(i->thread_fd);
+ i->thread_fd = -1;
+ }
+
+ i->unusable = 1;
+ }
+
+ function_exit();
+
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": atfork_child() exit\n");
+}
+
+static void install_atfork(void) {
+ pthread_atfork(atfork_prepare, atfork_parent, atfork_child);
+}
+
+static void stream_success_cb(pa_stream *s, int success, void *userdata) {
+ fd_info *i = userdata;
+
+ assert(s);
+ assert(i);
+
+ i->operation_success = success;
+ pa_threaded_mainloop_signal(i->mainloop, 0);
+}
+
+static void context_success_cb(pa_context *c, int success, void *userdata) {
+ fd_info *i = userdata;
+
+ assert(c);
+ assert(i);
+
+ i->operation_success = success;
+ pa_threaded_mainloop_signal(i->mainloop, 0);
+}
+
+static fd_info* fd_info_new(fd_info_type_t type, int *_errno) {
+ fd_info *i;
+ int sfds[2] = { -1, -1 };
+ char name[64];
+ static pthread_once_t install_atfork_once = PTHREAD_ONCE_INIT;
+
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": fd_info_new()\n");
+
+ signal(SIGPIPE, SIG_IGN); /* Yes, ugly as hell */
+
+ pthread_once(&install_atfork_once, install_atfork);
+
+ if (!(i = malloc(sizeof(fd_info)))) {
+ *_errno = ENOMEM;
+ goto fail;
+ }
+
+ i->app_fd = i->thread_fd = -1;
+ i->type = type;
+
+ i->mainloop = NULL;
+ i->context = NULL;
+ i->play_stream = NULL;
+ i->rec_stream = NULL;
+ i->play_precork = 0;
+ i->rec_precork = 0;
+ i->io_event = NULL;
+ i->io_flags = 0;
+ pthread_mutex_init(&i->mutex, NULL);
+ i->ref = 1;
+ i->buf = NULL;
+ i->rec_offset = 0;
+ i->unusable = 0;
+ pa_cvolume_reset(&i->sink_volume, 2);
+ pa_cvolume_reset(&i->source_volume, 2);
+ i->volume_modify_count = 0;
+ i->sink_index = (uint32_t) -1;
+ i->source_index = (uint32_t) -1;
+ i->optr_n_blocks = 0;
+ PA_LLIST_INIT(fd_info, i);
+
+ reset_params(i);
+
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, sfds) < 0) {
+ *_errno = errno;
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": socket() failed: %s\n", strerror(errno));
+ goto fail;
+ }
+
+ i->app_fd = sfds[0];
+ i->thread_fd = sfds[1];
+
+ if (!(i->mainloop = pa_threaded_mainloop_new())) {
+ *_errno = EIO;
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_threaded_mainloop_new() failed\n");
+ goto fail;
+ }
+
+ if (!(i->context = pa_context_new(pa_threaded_mainloop_get_api(i->mainloop), client_name(name, sizeof(name))))) {
+ *_errno = EIO;
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_context_new() failed\n");
+ goto fail;
+ }
+
+ pa_context_set_state_callback(i->context, context_state_cb, i);
+
+ if (pa_context_connect(i->context, NULL, 0, NULL) < 0) {
+ *_errno = ECONNREFUSED;
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_context_connect() failed: %s\n", pa_strerror(pa_context_errno(i->context)));
+ goto fail;
+ }
+
+ pa_threaded_mainloop_lock(i->mainloop);
+
+ if (pa_threaded_mainloop_start(i->mainloop) < 0) {
+ *_errno = EIO;
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_threaded_mainloop_start() failed\n");
+ goto unlock_and_fail;
+ }
+
+ /* Wait until the context is ready */
+ pa_threaded_mainloop_wait(i->mainloop);
+
+ if (pa_context_get_state(i->context) != PA_CONTEXT_READY) {
+ *_errno = ECONNREFUSED;
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_context_connect() failed: %s\n", pa_strerror(pa_context_errno(i->context)));
+ goto unlock_and_fail;
+ }
+
+ pa_threaded_mainloop_unlock(i->mainloop);
+ return i;
+
+unlock_and_fail:
+
+ pa_threaded_mainloop_unlock(i->mainloop);
+
+fail:
+
+ if (i)
+ fd_info_unref(i);
+
+ return NULL;
+}
+
+static void fd_info_add_to_list(fd_info *i) {
+ assert(i);
+
+ pthread_mutex_lock(&fd_infos_mutex);
+ PA_LLIST_PREPEND(fd_info, fd_infos, i);
+ pthread_mutex_unlock(&fd_infos_mutex);
+
+ fd_info_ref(i);
+}
+
+static void fd_info_remove_from_list(fd_info *i) {
+ assert(i);
+
+ pthread_mutex_lock(&fd_infos_mutex);
+ PA_LLIST_REMOVE(fd_info, fd_infos, i);
+ pthread_mutex_unlock(&fd_infos_mutex);
+
+ fd_info_unref(i);
+}
+
+static fd_info* fd_info_find(int fd) {
+ fd_info *i;
+
+ pthread_mutex_lock(&fd_infos_mutex);
+
+ for (i = fd_infos; i; i = i->next)
+ if (i->app_fd == fd && !i->unusable) {
+ fd_info_ref(i);
+ break;
+ }
+
+ pthread_mutex_unlock(&fd_infos_mutex);
+
+ return i;
+}
+
+static void fix_metrics(fd_info *i) {
+ size_t fs;
+ char t[PA_SAMPLE_SPEC_SNPRINT_MAX];
+
+ fs = pa_frame_size(&i->sample_spec);
+
+ /* Don't fix things more than necessary */
+ if ((i->fragment_size % fs) == 0 &&
+ i->n_fragments >= 2 &&
+ i->fragment_size > 0)
+ return;
+
+ i->fragment_size = (i->fragment_size/fs)*fs;
+
+ /* Number of fragments set? */
+ if (i->n_fragments < 2) {
+ if (i->fragment_size > 0) {
+ i->n_fragments = pa_bytes_per_second(&i->sample_spec) / 2 / i->fragment_size;
+ if (i->n_fragments < 2)
+ i->n_fragments = 2;
+ } else
+ i->n_fragments = 12;
+ }
+
+ /* Fragment size set? */
+ if (i->fragment_size <= 0) {
+ i->fragment_size = pa_bytes_per_second(&i->sample_spec) / 2 / i->n_fragments;
+ if (i->fragment_size < 1024)
+ i->fragment_size = 1024;
+ }
+
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": sample spec: %s\n", pa_sample_spec_snprint(t, sizeof(t), &i->sample_spec));
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": fixated metrics to %i fragments, %li bytes each.\n", i->n_fragments, (long)i->fragment_size);
+}
+
+static void stream_request_cb(pa_stream *s, size_t length, void *userdata) {
+ fd_info *i = userdata;
+ assert(s);
+
+ if (i->io_event) {
+ pa_mainloop_api *api;
+ size_t n;
+
+ api = pa_threaded_mainloop_get_api(i->mainloop);
+
+ if (s == i->play_stream) {
+ n = pa_stream_writable_size(i->play_stream);
+ if (n == (size_t)-1) {
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_writable_size(): %s\n",
+ pa_strerror(pa_context_errno(i->context)));
+ }
+
+ if (n >= i->fragment_size)
+ i->io_flags |= PA_IO_EVENT_INPUT;
+ else
+ i->io_flags &= ~PA_IO_EVENT_INPUT;
+ }
+
+ if (s == i->rec_stream) {
+ n = pa_stream_readable_size(i->rec_stream);
+ if (n == (size_t)-1) {
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_readable_size(): %s\n",
+ pa_strerror(pa_context_errno(i->context)));
+ }
+
+ if (n >= i->fragment_size)
+ i->io_flags |= PA_IO_EVENT_OUTPUT;
+ else
+ i->io_flags &= ~PA_IO_EVENT_OUTPUT;
+ }
+
+ api->io_enable(i->io_event, i->io_flags);
+ }
+}
+
+static void stream_latency_update_cb(pa_stream *s, void *userdata) {
+ fd_info *i = userdata;
+ assert(s);
+
+ pa_threaded_mainloop_signal(i->mainloop, 0);
+}
+
+static void fd_info_shutdown(fd_info *i) {
+ assert(i);
+
+ if (i->io_event) {
+ pa_mainloop_api *api;
+ api = pa_threaded_mainloop_get_api(i->mainloop);
+ api->io_free(i->io_event);
+ i->io_event = NULL;
+ i->io_flags = 0;
+ }
+
+ if (i->thread_fd >= 0) {
+ close(i->thread_fd);
+ i->thread_fd = -1;
+ }
+}
+
+static int fd_info_copy_data(fd_info *i, int force) {
+ size_t n;
+
+ if (!i->play_stream && !i->rec_stream)
+ return -1;
+
+ if ((i->play_stream) && (pa_stream_get_state(i->play_stream) == PA_STREAM_READY)) {
+ n = pa_stream_writable_size(i->play_stream);
+
+ if (n == (size_t)-1) {
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_writable_size(): %s\n",
+ pa_strerror(pa_context_errno(i->context)));
+ return -1;
+ }
+
+ while (n >= i->fragment_size || force) {
+ ssize_t r;
+
+ if (!i->buf) {
+ if (!(i->buf = malloc(i->fragment_size))) {
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": malloc() failed.\n");
+ return -1;
+ }
+ }
+
+ if ((r = read(i->thread_fd, i->buf, i->fragment_size)) <= 0) {
+
+ if (errno == EAGAIN)
+ break;
+
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": read(): %s\n", r == 0 ? "EOF" : strerror(errno));
+ return -1;
+ }
+
+ if (pa_stream_write(i->play_stream, i->buf, r, free, 0, PA_SEEK_RELATIVE) < 0) {
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_write(): %s\n", pa_strerror(pa_context_errno(i->context)));
+ return -1;
+ }
+
+ i->buf = NULL;
+
+ assert(n >= (size_t) r);
+ n -= r;
+ }
+
+ if (n >= i->fragment_size)
+ i->io_flags |= PA_IO_EVENT_INPUT;
+ else
+ i->io_flags &= ~PA_IO_EVENT_INPUT;
+ }
+
+ if ((i->rec_stream) && (pa_stream_get_state(i->rec_stream) == PA_STREAM_READY)) {
+ n = pa_stream_readable_size(i->rec_stream);
+
+ if (n == (size_t)-1) {
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_readable_size(): %s\n",
+ pa_strerror(pa_context_errno(i->context)));
+ return -1;
+ }
+
+ while (n >= i->fragment_size || force) {
+ ssize_t r;
+ const void *data;
+ const char *buf;
+ size_t len;
+
+ if (pa_stream_peek(i->rec_stream, &data, &len) < 0) {
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_peek(): %s\n", pa_strerror(pa_context_errno(i->context)));
+ return -1;
+ }
+
+ if (!data)
+ break;
+
+ buf = (const char*)data + i->rec_offset;
+
+ if ((r = write(i->thread_fd, buf, len - i->rec_offset)) <= 0) {
+
+ if (errno == EAGAIN)
+ break;
+
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": write(): %s\n", strerror(errno));
+ return -1;
+ }
+
+ assert((size_t)r <= len - i->rec_offset);
+ i->rec_offset += r;
+
+ if (i->rec_offset == len) {
+ if (pa_stream_drop(i->rec_stream) < 0) {
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_drop(): %s\n", pa_strerror(pa_context_errno(i->context)));
+ return -1;
+ }
+ i->rec_offset = 0;
+ }
+
+ assert(n >= (size_t) r);
+ n -= r;
+ }
+
+ if (n >= i->fragment_size)
+ i->io_flags |= PA_IO_EVENT_OUTPUT;
+ else
+ i->io_flags &= ~PA_IO_EVENT_OUTPUT;
+ }
+
+ if (i->io_event) {
+ pa_mainloop_api *api;
+
+ api = pa_threaded_mainloop_get_api(i->mainloop);
+ api->io_enable(i->io_event, i->io_flags);
+ }
+
+ return 0;
+}
+
+static void stream_state_cb(pa_stream *s, void * userdata) {
+ fd_info *i = userdata;
+ assert(s);
+
+ switch (pa_stream_get_state(s)) {
+
+ case PA_STREAM_READY:
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": stream established.\n");
+ break;
+
+ case PA_STREAM_FAILED:
+ if (s == i->play_stream) {
+ debug(DEBUG_LEVEL_NORMAL,
+ __FILE__": pa_stream_connect_playback() failed: %s\n",
+ pa_strerror(pa_context_errno(i->context)));
+ pa_stream_unref(i->play_stream);
+ i->play_stream = NULL;
+ } else if (s == i->rec_stream) {
+ debug(DEBUG_LEVEL_NORMAL,
+ __FILE__": pa_stream_connect_record() failed: %s\n",
+ pa_strerror(pa_context_errno(i->context)));
+ pa_stream_unref(i->rec_stream);
+ i->rec_stream = NULL;
+ }
+ fd_info_shutdown(i);
+ break;
+
+ case PA_STREAM_TERMINATED:
+ case PA_STREAM_UNCONNECTED:
+ case PA_STREAM_CREATING:
+ break;
+ }
+}
+
+static int create_playback_stream(fd_info *i) {
+ pa_buffer_attr attr;
+ int n, flags;
+
+ assert(i);
+
+ fix_metrics(i);
+
+ if (!(i->play_stream = pa_stream_new(i->context, stream_name(), &i->sample_spec, NULL))) {
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_new() failed: %s\n", pa_strerror(pa_context_errno(i->context)));
+ goto fail;
+ }
+
+ pa_stream_set_state_callback(i->play_stream, stream_state_cb, i);
+ pa_stream_set_write_callback(i->play_stream, stream_request_cb, i);
+ pa_stream_set_latency_update_callback(i->play_stream, stream_latency_update_cb, i);
+
+ memset(&attr, 0, sizeof(attr));
+ attr.maxlength = i->fragment_size * (i->n_fragments+1);
+ attr.tlength = i->fragment_size * i->n_fragments;
+ attr.prebuf = i->fragment_size;
+ attr.minreq = i->fragment_size;
+
+ flags = PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_AUTO_TIMING_UPDATE;
+ if (i->play_precork) {
+ flags |= PA_STREAM_START_CORKED;
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": creating stream corked\n");
+ }
+ if (pa_stream_connect_playback(i->play_stream, NULL, &attr, flags, NULL, NULL) < 0) {
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_connect_playback() failed: %s\n", pa_strerror(pa_context_errno(i->context)));
+ goto fail;
+ }
+
+ n = i->fragment_size;
+ setsockopt(i->app_fd, SOL_SOCKET, SO_SNDBUF, &n, sizeof(n));
+ n = i->fragment_size;
+ setsockopt(i->thread_fd, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n));
+
+ return 0;
+
+fail:
+ return -1;
+}
+
+static int create_record_stream(fd_info *i) {
+ pa_buffer_attr attr;
+ int n, flags;
+
+ assert(i);
+
+ fix_metrics(i);
+
+ if (!(i->rec_stream = pa_stream_new(i->context, stream_name(), &i->sample_spec, NULL))) {
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_new() failed: %s\n", pa_strerror(pa_context_errno(i->context)));
+ goto fail;
+ }
+
+ pa_stream_set_state_callback(i->rec_stream, stream_state_cb, i);
+ pa_stream_set_read_callback(i->rec_stream, stream_request_cb, i);
+ pa_stream_set_latency_update_callback(i->rec_stream, stream_latency_update_cb, i);
+
+ memset(&attr, 0, sizeof(attr));
+ attr.maxlength = i->fragment_size * (i->n_fragments+1);
+ attr.fragsize = i->fragment_size;
+
+ flags = PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_AUTO_TIMING_UPDATE;
+ if (i->rec_precork) {
+ flags |= PA_STREAM_START_CORKED;
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": creating stream corked\n");
+ }
+ if (pa_stream_connect_record(i->rec_stream, NULL, &attr, flags) < 0) {
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_connect_record() failed: %s\n", pa_strerror(pa_context_errno(i->context)));
+ goto fail;
+ }
+
+ n = i->fragment_size;
+ setsockopt(i->app_fd, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n));
+ n = i->fragment_size;
+ setsockopt(i->thread_fd, SOL_SOCKET, SO_SNDBUF, &n, sizeof(n));
+
+ return 0;
+
+fail:
+ return -1;
+}
+
+static void free_streams(fd_info *i) {
+ assert(i);
+
+ if (i->play_stream) {
+ pa_stream_disconnect(i->play_stream);
+ pa_stream_unref(i->play_stream);
+ i->play_stream = NULL;
+ i->io_flags |= PA_IO_EVENT_INPUT;
+ }
+
+ if (i->rec_stream) {
+ pa_stream_disconnect(i->rec_stream);
+ pa_stream_unref(i->rec_stream);
+ i->rec_stream = NULL;
+ i->io_flags |= PA_IO_EVENT_OUTPUT;
+ }
+
+ if (i->io_event) {
+ pa_mainloop_api *api;
+
+ api = pa_threaded_mainloop_get_api(i->mainloop);
+ api->io_enable(i->io_event, i->io_flags);
+ }
+}
+
+static void io_event_cb(pa_mainloop_api *api, pa_io_event *e, int fd, pa_io_event_flags_t flags, void *userdata) {
+ fd_info *i = userdata;
+
+ pa_threaded_mainloop_signal(i->mainloop, 0);
+
+ if (flags & PA_IO_EVENT_INPUT) {
+
+ if (!i->play_stream) {
+ if (create_playback_stream(i) < 0)
+ goto fail;
+ } else {
+ if (fd_info_copy_data(i, 0) < 0)
+ goto fail;
+ }
+
+ } else if (flags & PA_IO_EVENT_OUTPUT) {
+
+ if (!i->rec_stream) {
+ if (create_record_stream(i) < 0)
+ goto fail;
+ } else {
+ if (fd_info_copy_data(i, 0) < 0)
+ goto fail;
+ }
+
+ } else if (flags & (PA_IO_EVENT_HANGUP|PA_IO_EVENT_ERROR))
+ goto fail;
+
+ return;
+
+fail:
+ /* We can't do anything better than removing the event source */
+ fd_info_shutdown(i);
+}
+
+static int dsp_open(int flags, int *_errno) {
+ fd_info *i;
+ pa_mainloop_api *api;
+ int ret;
+ int f;
+
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": dsp_open()\n");
+
+ if (!(i = fd_info_new(FD_INFO_STREAM, _errno)))
+ return -1;
+
+ if ((flags & O_NONBLOCK) == O_NONBLOCK) {
+ if ((f = fcntl(i->app_fd, F_GETFL)) >= 0)
+ fcntl(i->app_fd, F_SETFL, f|O_NONBLOCK);
+ }
+ if ((f = fcntl(i->thread_fd, F_GETFL)) >= 0)
+ fcntl(i->thread_fd, F_SETFL, f|O_NONBLOCK);
+
+ fcntl(i->app_fd, F_SETFD, FD_CLOEXEC);
+ fcntl(i->thread_fd, F_SETFD, FD_CLOEXEC);
+
+ pa_threaded_mainloop_lock(i->mainloop);
+ api = pa_threaded_mainloop_get_api(i->mainloop);
+
+ switch (flags & O_ACCMODE) {
+ case O_RDONLY:
+ i->io_flags = PA_IO_EVENT_OUTPUT;
+ shutdown(i->thread_fd, SHUT_RD);
+ shutdown(i->app_fd, SHUT_WR);
+ break;
+ case O_WRONLY:
+ i->io_flags = PA_IO_EVENT_INPUT;
+ shutdown(i->thread_fd, SHUT_WR);
+ shutdown(i->app_fd, SHUT_RD);
+ break;
+ case O_RDWR:
+ i->io_flags = PA_IO_EVENT_INPUT | PA_IO_EVENT_OUTPUT;
+ break;
+ default:
+ return -1;
+ }
+
+ if (!(i->io_event = api->io_new(api, i->thread_fd, i->io_flags, io_event_cb, i)))
+ goto fail;
+
+ pa_threaded_mainloop_unlock(i->mainloop);
+
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": dsp_open() succeeded, fd=%i\n", i->app_fd);
+
+ fd_info_add_to_list(i);
+ ret = i->app_fd;
+ fd_info_unref(i);
+
+ return ret;
+
+fail:
+ pa_threaded_mainloop_unlock(i->mainloop);
+
+ if (i)
+ fd_info_unref(i);
+
+ *_errno = EIO;
+
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": dsp_open() failed\n");
+
+ return -1;
+}
+
+static void sink_info_cb(pa_context *context, const pa_sink_info *si, int eol, void *userdata) {
+ fd_info *i = userdata;
+
+ if (!si && eol < 0) {
+ i->operation_success = 0;
+ pa_threaded_mainloop_signal(i->mainloop, 0);
+ return;
+ }
+
+ if (eol)
+ return;
+
+ if (!pa_cvolume_equal(&i->sink_volume, &si->volume))
+ i->volume_modify_count++;
+
+ i->sink_volume = si->volume;
+ i->sink_index = si->index;
+
+ i->operation_success = 1;
+ pa_threaded_mainloop_signal(i->mainloop, 0);
+}
+
+static void source_info_cb(pa_context *context, const pa_source_info *si, int eol, void *userdata) {
+ fd_info *i = userdata;
+
+ if (!si && eol < 0) {
+ i->operation_success = 0;
+ pa_threaded_mainloop_signal(i->mainloop, 0);
+ return;
+ }
+
+ if (eol)
+ return;
+
+ if (!pa_cvolume_equal(&i->source_volume, &si->volume))
+ i->volume_modify_count++;
+
+ i->source_volume = si->volume;
+ i->source_index = si->index;
+
+ i->operation_success = 1;
+ pa_threaded_mainloop_signal(i->mainloop, 0);
+}
+
+static void subscribe_cb(pa_context *context, pa_subscription_event_type_t t, uint32_t idx, void *userdata) {
+ fd_info *i = userdata;
+ pa_operation *o = NULL;
+
+ if (i->sink_index != idx)
+ return;
+
+ if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_CHANGE)
+ return;
+
+ if (!(o = pa_context_get_sink_info_by_index(i->context, i->sink_index, sink_info_cb, i))) {
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": Failed to get sink info: %s", pa_strerror(pa_context_errno(i->context)));
+ return;
+ }
+
+ pa_operation_unref(o);
+}
+
+static int mixer_open(int flags, int *_errno) {
+ fd_info *i;
+ pa_operation *o = NULL;
+ int ret;
+
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": mixer_open()\n");
+
+ if (!(i = fd_info_new(FD_INFO_MIXER, _errno)))
+ return -1;
+
+ pa_threaded_mainloop_lock(i->mainloop);
+
+ pa_context_set_subscribe_callback(i->context, subscribe_cb, i);
+
+ if (!(o = pa_context_subscribe(i->context, PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE, context_success_cb, i))) {
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": Failed to subscribe to events: %s", pa_strerror(pa_context_errno(i->context)));
+ *_errno = EIO;
+ goto fail;
+ }
+
+ i->operation_success = 0;
+ while (pa_operation_get_state(o) != PA_OPERATION_DONE) {
+ pa_threaded_mainloop_wait(i->mainloop);
+ CONTEXT_CHECK_DEAD_GOTO(i, fail);
+ }
+
+ pa_operation_unref(o);
+ o = NULL;
+
+ if (!i->operation_success) {
+ debug(DEBUG_LEVEL_NORMAL, __FILE__":Failed to subscribe to events: %s", pa_strerror(pa_context_errno(i->context)));
+ *_errno = EIO;
+ goto fail;
+ }
+
+ /* Get sink info */
+
+ if (!(o = pa_context_get_sink_info_by_name(i->context, NULL, sink_info_cb, i))) {
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": Failed to get sink info: %s", pa_strerror(pa_context_errno(i->context)));
+ *_errno = EIO;
+ goto fail;
+ }
+
+ i->operation_success = 0;
+ while (pa_operation_get_state(o) != PA_OPERATION_DONE) {
+ pa_threaded_mainloop_wait(i->mainloop);
+ CONTEXT_CHECK_DEAD_GOTO(i, fail);
+ }
+
+ pa_operation_unref(o);
+ o = NULL;
+
+ if (!i->operation_success) {
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": Failed to get sink info: %s", pa_strerror(pa_context_errno(i->context)));
+ *_errno = EIO;
+ goto fail;
+ }
+
+ /* Get source info */
+
+ if (!(o = pa_context_get_source_info_by_name(i->context, NULL, source_info_cb, i))) {
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": Failed to get source info: %s", pa_strerror(pa_context_errno(i->context)));
+ *_errno = EIO;
+ goto fail;
+ }
+
+ i->operation_success = 0;
+ while (pa_operation_get_state(o) != PA_OPERATION_DONE) {
+ pa_threaded_mainloop_wait(i->mainloop);
+ CONTEXT_CHECK_DEAD_GOTO(i, fail);
+ }
+
+ pa_operation_unref(o);
+ o = NULL;
+
+ if (!i->operation_success) {
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": Failed to get source info: %s", pa_strerror(pa_context_errno(i->context)));
+ *_errno = EIO;
+ goto fail;
+ }
+
+ pa_threaded_mainloop_unlock(i->mainloop);
+
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": mixer_open() succeeded, fd=%i\n", i->app_fd);
+
+ fd_info_add_to_list(i);
+ ret = i->app_fd;
+ fd_info_unref(i);
+
+ return ret;
+
+fail:
+ if (o)
+ pa_operation_unref(o);
+
+ pa_threaded_mainloop_unlock(i->mainloop);
+
+ if (i)
+ fd_info_unref(i);
+
+ *_errno = EIO;
+
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": mixer_open() failed\n");
+
+ return -1;
+}
+
+static int sndstat_open(int flags, int *_errno) {
+ static const char sndstat[] =
+ "Sound Driver:3.8.1a-980706 (PulseAudio Virtual OSS)\n"
+ "Kernel: POSIX\n"
+ "Config options: 0\n"
+ "\n"
+ "Installed drivers:\n"
+ "Type 255: PulseAudio Virtual OSS\n"
+ "\n"
+ "Card config:\n"
+ "PulseAudio Virtual OSS\n"
+ "\n"
+ "Audio devices:\n"
+ "0: PulseAudio Virtual OSS\n"
+ "\n"
+ "Synth devices: NOT ENABLED IN CONFIG\n"
+ "\n"
+ "Midi devices:\n"
+ "\n"
+ "Timers:\n"
+ "\n"
+ "Mixers:\n"
+ "0: PulseAudio Virtual OSS\n";
+
+ char fn[] = "/tmp/padsp-sndstat-XXXXXX";
+ mode_t u;
+ int fd = -1;
+ int e;
+
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": sndstat_open()\n");
+
+ if (flags != O_RDONLY
+#ifdef O_LARGEFILE
+ && flags != (O_RDONLY|O_LARGEFILE)
+#endif
+ ) {
+ *_errno = EACCES;
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": bad access!\n");
+ goto fail;
+ }
+
+ u = umask(0077);
+ fd = mkstemp(fn);
+ e = errno;
+ umask(u);
+
+ if (fd < 0) {
+ *_errno = e;
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": mkstemp() failed: %s\n", strerror(errno));
+ goto fail;
+ }
+
+ unlink(fn);
+
+ if (write(fd, sndstat, sizeof(sndstat) -1) != sizeof(sndstat)-1) {
+ *_errno = errno;
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": write() failed: %s\n", strerror(errno));
+ goto fail;
+ }
+
+ if (lseek(fd, SEEK_SET, 0) < 0) {
+ *_errno = errno;
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": lseek() failed: %s\n", strerror(errno));
+ goto fail;
+ }
+
+ return fd;
+
+fail:
+ if (fd >= 0)
+ close(fd);
+ return -1;
+}
+
+static int real_open(const char *filename, int flags, mode_t mode) {
+ int r, _errno = 0;
+
+ debug(DEBUG_LEVEL_VERBOSE, __FILE__": open(%s)\n", filename?filename:"NULL");
+
+ if (!function_enter()) {
+ LOAD_OPEN_FUNC();
+ return _open(filename, flags, mode);
+ }
+
+ if (filename && dsp_cloak_enable() && (strcmp(filename, "/dev/dsp") == 0 || strcmp(filename, "/dev/adsp") == 0))
+ r = dsp_open(flags, &_errno);
+ else if (filename && mixer_cloak_enable() && strcmp(filename, "/dev/mixer") == 0)
+ r = mixer_open(flags, &_errno);
+ else if (filename && sndstat_cloak_enable() && strcmp(filename, "/dev/sndstat") == 0)
+ r = sndstat_open(flags, &_errno);
+ else {
+ function_exit();
+ LOAD_OPEN_FUNC();
+ return _open(filename, flags, mode);
+ }
+
+ function_exit();
+
+ if (_errno)
+ errno = _errno;
+
+ return r;
+}
+
+int open(const char *filename, int flags, ...) {
+ va_list args;
+ mode_t mode = 0;
+
+ if (flags & O_CREAT) {
+ va_start(args, flags);
+ if (sizeof(mode_t) < sizeof(int))
+ mode = va_arg(args, int);
+ else
+ mode = va_arg(args, mode_t);
+ va_end(args);
+ }
+
+ return real_open(filename, flags, mode);
+}
+
+static int mixer_ioctl(fd_info *i, unsigned long request, void*argp, int *_errno) {
+ int ret = -1;
+
+ switch (request) {
+ case SOUND_MIXER_READ_DEVMASK :
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": SOUND_MIXER_READ_DEVMASK\n");
+
+ *(int*) argp = SOUND_MASK_PCM | SOUND_MASK_IGAIN;
+ break;
+
+ case SOUND_MIXER_READ_RECMASK :
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": SOUND_MIXER_READ_RECMASK\n");
+
+ *(int*) argp = SOUND_MASK_IGAIN;
+ break;
+
+ case SOUND_MIXER_READ_STEREODEVS:
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": SOUND_MIXER_READ_STEREODEVS\n");
+
+ pa_threaded_mainloop_lock(i->mainloop);
+ *(int*) argp = 0;
+ if (i->sink_volume.channels > 1)
+ *(int*) argp |= SOUND_MASK_PCM;
+ if (i->source_volume.channels > 1)
+ *(int*) argp |= SOUND_MASK_IGAIN;
+ pa_threaded_mainloop_unlock(i->mainloop);
+
+ break;
+
+ case SOUND_MIXER_READ_RECSRC:
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": SOUND_MIXER_READ_RECSRC\n");
+
+ *(int*) argp = SOUND_MASK_IGAIN;
+ break;
+
+ case SOUND_MIXER_WRITE_RECSRC:
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": SOUND_MIXER_WRITE_RECSRC\n");
+ break;
+
+ case SOUND_MIXER_READ_CAPS:
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": SOUND_MIXER_READ_CAPS\n");
+
+ *(int*) argp = 0;
+ break;
+
+ case SOUND_MIXER_READ_PCM:
+ case SOUND_MIXER_READ_IGAIN: {
+ pa_cvolume *v;
+
+ if (request == SOUND_MIXER_READ_PCM)
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": SOUND_MIXER_READ_PCM\n");
+ else
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": SOUND_MIXER_READ_IGAIN\n");
+
+ pa_threaded_mainloop_lock(i->mainloop);
+
+ if (request == SOUND_MIXER_READ_PCM)
+ v = &i->sink_volume;
+ else
+ v = &i->source_volume;
+
+ *(int*) argp =
+ ((v->values[0]*100/PA_VOLUME_NORM)) |
+ ((v->values[v->channels > 1 ? 1 : 0]*100/PA_VOLUME_NORM) << 8);
+
+ pa_threaded_mainloop_unlock(i->mainloop);
+
+ break;
+ }
+
+ case SOUND_MIXER_WRITE_PCM:
+ case SOUND_MIXER_WRITE_IGAIN: {
+ pa_cvolume v, *pv;
+
+ if (request == SOUND_MIXER_WRITE_PCM)
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": SOUND_MIXER_WRITE_PCM\n");
+ else
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": SOUND_MIXER_WRITE_IGAIN\n");
+
+ pa_threaded_mainloop_lock(i->mainloop);
+
+ if (request == SOUND_MIXER_WRITE_PCM) {
+ v = i->sink_volume;
+ pv = &i->sink_volume;
+ } else {
+ v = i->source_volume;
+ pv = &i->source_volume;
+ }
+
+ pv->values[0] = ((*(int*) argp & 0xFF)*PA_VOLUME_NORM)/100;
+ pv->values[1] = ((*(int*) argp >> 8)*PA_VOLUME_NORM)/100;
+
+ if (!pa_cvolume_equal(pv, &v)) {
+ pa_operation *o;
+
+ if (request == SOUND_MIXER_WRITE_PCM)
+ o = pa_context_set_sink_volume_by_index(i->context, i->sink_index, pv, context_success_cb, i);
+ else
+ o = pa_context_set_source_volume_by_index(i->context, i->source_index, pv, context_success_cb, i);
+
+ if (!o)
+ debug(DEBUG_LEVEL_NORMAL, __FILE__":Failed set volume: %s", pa_strerror(pa_context_errno(i->context)));
+ else {
+
+ i->operation_success = 0;
+ while (pa_operation_get_state(o) != PA_OPERATION_DONE) {
+ CONTEXT_CHECK_DEAD_GOTO(i, exit_loop);
+
+ pa_threaded_mainloop_wait(i->mainloop);
+ }
+ exit_loop:
+
+ if (!i->operation_success)
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": Failed to set volume: %s\n", pa_strerror(pa_context_errno(i->context)));
+
+ pa_operation_unref(o);
+ }
+
+ /* We don't wait for completion here */
+ i->volume_modify_count++;
+ }
+
+ pa_threaded_mainloop_unlock(i->mainloop);
+
+ break;
+ }
+
+ case SOUND_MIXER_INFO: {
+ mixer_info *mi = argp;
+
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": SOUND_MIXER_INFO\n");
+
+ memset(mi, 0, sizeof(mixer_info));
+ strncpy(mi->id, "PULSEAUDIO", sizeof(mi->id));
+ strncpy(mi->name, "PulseAudio Virtual OSS", sizeof(mi->name));
+ pa_threaded_mainloop_lock(i->mainloop);
+ mi->modify_counter = i->volume_modify_count;
+ pa_threaded_mainloop_unlock(i->mainloop);
+ break;
+ }
+
+ default:
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": unknown ioctl 0x%08lx\n", request);
+
+ *_errno = EINVAL;
+ goto fail;
+ }
+
+ ret = 0;
+
+fail:
+
+ return ret;
+}
+
+static int map_format(int *fmt, pa_sample_spec *ss) {
+
+ switch (*fmt) {
+ case AFMT_MU_LAW:
+ ss->format = PA_SAMPLE_ULAW;
+ break;
+
+ case AFMT_A_LAW:
+ ss->format = PA_SAMPLE_ALAW;
+ break;
+
+ case AFMT_S8:
+ *fmt = AFMT_U8;
+ /* fall through */
+ case AFMT_U8:
+ ss->format = PA_SAMPLE_U8;
+ break;
+
+ case AFMT_U16_BE:
+ *fmt = AFMT_S16_BE;
+ /* fall through */
+ case AFMT_S16_BE:
+ ss->format = PA_SAMPLE_S16BE;
+ break;
+
+ case AFMT_U16_LE:
+ *fmt = AFMT_S16_LE;
+ /* fall through */
+ case AFMT_S16_LE:
+ ss->format = PA_SAMPLE_S16LE;
+ break;
+
+ default:
+ ss->format = PA_SAMPLE_S16NE;
+ *fmt = AFMT_S16_NE;
+ break;
+ }
+
+ return 0;
+}
+
+static int map_format_back(pa_sample_format_t format) {
+ switch (format) {
+ case PA_SAMPLE_S16LE: return AFMT_S16_LE;
+ case PA_SAMPLE_S16BE: return AFMT_S16_BE;
+ case PA_SAMPLE_ULAW: return AFMT_MU_LAW;
+ case PA_SAMPLE_ALAW: return AFMT_A_LAW;
+ case PA_SAMPLE_U8: return AFMT_U8;
+ default:
+ abort();
+ }
+}
+
+static int dsp_flush_fd(int fd) {
+#ifdef SIOCINQ
+ int l;
+
+ if (ioctl(fd, SIOCINQ, &l) < 0) {
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": SIOCINQ: %s\n", strerror(errno));
+ return -1;
+ }
+
+ while (l > 0) {
+ char buf[1024];
+ size_t k;
+
+ k = (size_t) l > sizeof(buf) ? sizeof(buf) : (size_t) l;
+ if (read(fd, buf, k) < 0)
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": read(): %s\n", strerror(errno));
+ l -= k;
+ }
+
+ return 0;
+#else
+# warning "Your platform does not support SIOCINQ, something might not work as intended."
+ return 0;
+#endif
+}
+
+static int dsp_flush_socket(fd_info *i) {
+ int res = 0;
+
+ if ((i->thread_fd < 0) && (i->app_fd < 0))
+ return -1;
+
+ if (i->thread_fd >= 0)
+ res = dsp_flush_fd(i->thread_fd);
+
+ if (res < 0)
+ return res;
+
+ if (i->app_fd >= 0)
+ res = dsp_flush_fd(i->app_fd);
+
+ if (res < 0)
+ return res;
+
+ return 0;
+}
+
+static int dsp_empty_socket(fd_info *i) {
+#ifdef SIOCINQ
+ int ret = -1;
+
+ /* Empty the socket */
+ for (;;) {
+ int l;
+
+ if (i->thread_fd < 0)
+ break;
+
+ if (ioctl(i->thread_fd, SIOCINQ, &l) < 0) {
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": SIOCINQ: %s\n", strerror(errno));
+ break;
+ }
+
+ if (!l) {
+ ret = 0;
+ break;
+ }
+
+ pa_threaded_mainloop_wait(i->mainloop);
+ }
+
+ return ret;
+#else
+# warning "Your platform does not support SIOCINQ, something might not work as intended."
+ return 0;
+#endif
+}
+
+static int dsp_drain(fd_info *i) {
+ pa_operation *o = NULL;
+ int r = -1;
+
+ if (!i->mainloop)
+ return 0;
+
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": Draining.\n");
+
+ pa_threaded_mainloop_lock(i->mainloop);
+
+ if (dsp_empty_socket(i) < 0)
+ goto fail;
+
+ if (!i->play_stream)
+ goto fail;
+
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": Really draining.\n");
+
+ if (!(o = pa_stream_drain(i->play_stream, stream_success_cb, i))) {
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_drain(): %s\n", pa_strerror(pa_context_errno(i->context)));
+ goto fail;
+ }
+
+ i->operation_success = 0;
+ while (pa_operation_get_state(o) != PA_OPERATION_DONE) {
+ PLAYBACK_STREAM_CHECK_DEAD_GOTO(i, fail);
+
+ pa_threaded_mainloop_wait(i->mainloop);
+ }
+
+ if (!i->operation_success) {
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_drain() 2: %s\n", pa_strerror(pa_context_errno(i->context)));
+ goto fail;
+ }
+
+ r = 0;
+
+fail:
+
+ if (o)
+ pa_operation_unref(o);
+
+ pa_threaded_mainloop_unlock(i->mainloop);
+
+ return 0;
+}
+
+static int dsp_trigger(fd_info *i) {
+ pa_operation *o = NULL;
+ int r = -1;
+
+ if (!i->play_stream)
+ return 0;
+
+ pa_threaded_mainloop_lock(i->mainloop);
+
+ if (dsp_empty_socket(i) < 0)
+ goto fail;
+
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": Triggering.\n");
+
+ if (!(o = pa_stream_trigger(i->play_stream, stream_success_cb, i))) {
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_trigger(): %s\n", pa_strerror(pa_context_errno(i->context)));
+ goto fail;
+ }
+
+ i->operation_success = 0;
+ while (!pa_operation_get_state(o) != PA_OPERATION_DONE) {
+ PLAYBACK_STREAM_CHECK_DEAD_GOTO(i, fail);
+
+ pa_threaded_mainloop_wait(i->mainloop);
+ }
+
+ if (!i->operation_success) {
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_trigger(): %s\n", pa_strerror(pa_context_errno(i->context)));
+ goto fail;
+ }
+
+ r = 0;
+
+fail:
+
+ if (o)
+ pa_operation_unref(o);
+
+ pa_threaded_mainloop_unlock(i->mainloop);
+
+ return 0;
+}
+
+static int dsp_cork(fd_info *i, pa_stream *s, int b) {
+ pa_operation *o = NULL;
+ int r = -1;
+
+ pa_threaded_mainloop_lock(i->mainloop);
+
+ if (!(o = pa_stream_cork(s, b, stream_success_cb, i))) {
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_cork(): %s\n", pa_strerror(pa_context_errno(i->context)));
+ goto fail;
+ }
+
+ i->operation_success = 0;
+ while (!pa_operation_get_state(o) != PA_OPERATION_DONE) {
+ if (s == i->play_stream)
+ PLAYBACK_STREAM_CHECK_DEAD_GOTO(i, fail);
+ else if (s == i->rec_stream)
+ RECORD_STREAM_CHECK_DEAD_GOTO(i, fail);
+
+ pa_threaded_mainloop_wait(i->mainloop);
+ }
+
+ if (!i->operation_success) {
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_cork(): %s\n", pa_strerror(pa_context_errno(i->context)));
+ goto fail;
+ }
+
+ r = 0;
+
+fail:
+
+ if (o)
+ pa_operation_unref(o);
+
+ pa_threaded_mainloop_unlock(i->mainloop);
+
+ return 0;
+}
+
+static int dsp_ioctl(fd_info *i, unsigned long request, void*argp, int *_errno) {
+ int ret = -1;
+
+ if (i->thread_fd == -1) {
+ /*
+ * We've encountered some fatal error and are just waiting
+ * for a close.
+ */
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": got ioctl 0x%08lx in fatal error state\n", request);
+ *_errno = EIO;
+ return -1;
+ }
+
+ switch (request) {
+ case SNDCTL_DSP_SETFMT: {
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_SETFMT: %i\n", *(int*) argp);
+
+ pa_threaded_mainloop_lock(i->mainloop);
+
+ if (*(int*) argp == AFMT_QUERY)
+ *(int*) argp = map_format_back(i->sample_spec.format);
+ else {
+ map_format((int*) argp, &i->sample_spec);
+ free_streams(i);
+ }
+
+ pa_threaded_mainloop_unlock(i->mainloop);
+ break;
+ }
+
+ case SNDCTL_DSP_SPEED: {
+ pa_sample_spec ss;
+ int valid;
+ char t[256];
+
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_SPEED: %i\n", *(int*) argp);
+
+ pa_threaded_mainloop_lock(i->mainloop);
+
+ ss = i->sample_spec;
+ ss.rate = *(int*) argp;
+
+ if ((valid = pa_sample_spec_valid(&ss))) {
+ i->sample_spec = ss;
+ free_streams(i);
+ }
+
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": ss: %s\n", pa_sample_spec_snprint(t, sizeof(t), &i->sample_spec));
+
+ pa_threaded_mainloop_unlock(i->mainloop);
+
+ if (!valid) {
+ *_errno = EINVAL;
+ goto fail;
+ }
+
+ break;
+ }
+
+ case SNDCTL_DSP_STEREO:
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_STEREO: %i\n", *(int*) argp);
+
+ pa_threaded_mainloop_lock(i->mainloop);
+
+ i->sample_spec.channels = *(int*) argp ? 2 : 1;
+ free_streams(i);
+
+ pa_threaded_mainloop_unlock(i->mainloop);
+ return 0;
+
+ case SNDCTL_DSP_CHANNELS: {
+ pa_sample_spec ss;
+ int valid;
+
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_CHANNELS: %i\n", *(int*) argp);
+
+ pa_threaded_mainloop_lock(i->mainloop);
+
+ ss = i->sample_spec;
+ ss.channels = *(int*) argp;
+
+ if ((valid = pa_sample_spec_valid(&ss))) {
+ i->sample_spec = ss;
+ free_streams(i);
+ }
+
+ pa_threaded_mainloop_unlock(i->mainloop);
+
+ if (!valid) {
+ *_errno = EINVAL;
+ goto fail;
+ }
+
+ break;
+ }
+
+ case SNDCTL_DSP_GETBLKSIZE:
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_GETBLKSIZE\n");
+
+ pa_threaded_mainloop_lock(i->mainloop);
+
+ fix_metrics(i);
+ *(int*) argp = i->fragment_size;
+
+ pa_threaded_mainloop_unlock(i->mainloop);
+
+ break;
+
+ case SNDCTL_DSP_SETFRAGMENT:
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_SETFRAGMENT: 0x%08x\n", *(int*) argp);
+
+ pa_threaded_mainloop_lock(i->mainloop);
+
+ i->fragment_size = 1 << ((*(int*) argp) & 31);
+ i->n_fragments = (*(int*) argp) >> 16;
+
+ /* 0x7FFF means that we can set whatever we like */
+ if (i->n_fragments == 0x7FFF)
+ i->n_fragments = 12;
+
+ free_streams(i);
+
+ pa_threaded_mainloop_unlock(i->mainloop);
+
+ break;
+
+ case SNDCTL_DSP_GETCAPS:
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_CAPS\n");
+
+ *(int*) argp = DSP_CAP_DUPLEX | DSP_CAP_TRIGGER
+#ifdef DSP_CAP_MULTI
+ | DSP_CAP_MULTI
+#endif
+ ;
+ break;
+
+ case SNDCTL_DSP_GETODELAY: {
+ int l;
+
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_GETODELAY\n");
+
+ pa_threaded_mainloop_lock(i->mainloop);
+
+ *(int*) argp = 0;
+
+ for (;;) {
+ pa_usec_t usec;
+
+ PLAYBACK_STREAM_CHECK_DEAD_GOTO(i, exit_loop);
+
+ if (pa_stream_get_latency(i->play_stream, &usec, NULL) >= 0) {
+ *(int*) argp = pa_usec_to_bytes(usec, &i->sample_spec);
+ break;
+ }
+
+ if (pa_context_errno(i->context) != PA_ERR_NODATA) {
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_get_latency(): %s\n", pa_strerror(pa_context_errno(i->context)));
+ break;
+ }
+
+ pa_threaded_mainloop_wait(i->mainloop);
+ }
+
+ exit_loop:
+
+#ifdef SIOCINQ
+ if (ioctl(i->thread_fd, SIOCINQ, &l) < 0)
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": SIOCINQ failed: %s\n", strerror(errno));
+ else
+ *(int*) argp += l;
+#else
+# warning "Your platform does not support SIOCINQ, something might not work as intended."
+#endif
+
+ pa_threaded_mainloop_unlock(i->mainloop);
+
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": ODELAY: %i\n", *(int*) argp);
+
+ break;
+ }
+
+ case SNDCTL_DSP_RESET: {
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_RESET\n");
+
+ pa_threaded_mainloop_lock(i->mainloop);
+
+ free_streams(i);
+ dsp_flush_socket(i);
+
+ i->optr_n_blocks = 0;
+
+ pa_threaded_mainloop_unlock(i->mainloop);
+ break;
+ }
+
+ case SNDCTL_DSP_GETFMTS: {
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_GETFMTS\n");
+
+ *(int*) argp = AFMT_MU_LAW|AFMT_A_LAW|AFMT_U8|AFMT_S16_LE|AFMT_S16_BE;
+ break;
+ }
+
+ case SNDCTL_DSP_POST:
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_POST\n");
+
+ if (dsp_trigger(i) < 0)
+ *_errno = EIO;
+ break;
+
+ case SNDCTL_DSP_GETTRIGGER:
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_GETTRIGGER\n");
+
+ *(int*) argp = 0;
+ if (!i->play_precork)
+ *(int*) argp |= PCM_ENABLE_OUTPUT;
+ if (!i->rec_precork)
+ *(int*) argp |= PCM_ENABLE_INPUT;
+
+ break;
+
+ case SNDCTL_DSP_SETTRIGGER:
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_SETTRIGGER: 0x%08x\n", *(int*) argp);
+
+ if (!i->io_event) {
+ *_errno = EIO;
+ break;
+ }
+
+ i->play_precork = !((*(int*) argp) & PCM_ENABLE_OUTPUT);
+
+ if (i->play_stream) {
+ if (dsp_cork(i, i->play_stream, !((*(int*) argp) & PCM_ENABLE_OUTPUT)) < 0)
+ *_errno = EIO;
+ if (dsp_trigger(i) < 0)
+ *_errno = EIO;
+ }
+
+ i->rec_precork = !((*(int*) argp) & PCM_ENABLE_INPUT);
+
+ if (i->rec_stream) {
+ if (dsp_cork(i, i->rec_stream, !((*(int*) argp) & PCM_ENABLE_INPUT)) < 0)
+ *_errno = EIO;
+ }
+
+ break;
+
+ case SNDCTL_DSP_SYNC:
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_SYNC\n");
+
+ if (dsp_drain(i) < 0)
+ *_errno = EIO;
+
+ break;
+
+ case SNDCTL_DSP_GETOSPACE:
+ case SNDCTL_DSP_GETISPACE: {
+ audio_buf_info *bi = (audio_buf_info*) argp;
+ int l = 0;
+ size_t k = 0;
+
+ if (request == SNDCTL_DSP_GETOSPACE)
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_GETOSPACE\n");
+ else
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_GETISPACE\n");
+
+ pa_threaded_mainloop_lock(i->mainloop);
+
+ fix_metrics(i);
+
+ if (request == SNDCTL_DSP_GETOSPACE) {
+ if (i->play_stream) {
+ if ((k = pa_stream_writable_size(i->play_stream)) == (size_t) -1)
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_writable_size(): %s\n", pa_strerror(pa_context_errno(i->context)));
+ } else
+ k = i->fragment_size * i->n_fragments;
+
+#ifdef SIOCINQ
+ if (ioctl(i->thread_fd, SIOCINQ, &l) < 0) {
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": SIOCINQ failed: %s\n", strerror(errno));
+ l = 0;
+ }
+#else
+# warning "Your platform does not dsp_flush_fd, something might not work as intended."
+#endif
+
+ bi->bytes = k > (size_t) l ? k - l : 0;
+ } else {
+ if (i->rec_stream) {
+ if ((k = pa_stream_readable_size(i->rec_stream)) == (size_t) -1)
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_readable_size(): %s\n", pa_strerror(pa_context_errno(i->context)));
+ } else
+ k = 0;
+
+#ifdef SIOCINQ
+ if (ioctl(i->app_fd, SIOCINQ, &l) < 0) {
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": SIOCINQ failed: %s\n", strerror(errno));
+ l = 0;
+ }
+#else
+# warning "Your platform does not dsp_flush_fd, something might not work as intended."
+#endif
+ bi->bytes = k + l;
+ }
+
+ bi->fragsize = i->fragment_size;
+ bi->fragstotal = i->n_fragments;
+ bi->fragments = bi->bytes / bi->fragsize;
+
+ pa_threaded_mainloop_unlock(i->mainloop);
+
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": fragsize=%i, fragstotal=%i, bytes=%i, fragments=%i\n", bi->fragsize, bi->fragstotal, bi->bytes, bi->fragments);
+
+ break;
+ }
+
+ case SOUND_PCM_READ_RATE:
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": SOUND_PCM_READ_RATE\n");
+
+ pa_threaded_mainloop_lock(i->mainloop);
+ *(int*) argp = i->sample_spec.rate;
+ pa_threaded_mainloop_unlock(i->mainloop);
+ break;
+
+ case SOUND_PCM_READ_CHANNELS:
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": SOUND_PCM_READ_CHANNELS\n");
+
+ pa_threaded_mainloop_lock(i->mainloop);
+ *(int*) argp = i->sample_spec.channels;
+ pa_threaded_mainloop_unlock(i->mainloop);
+ break;
+
+ case SOUND_PCM_READ_BITS:
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": SOUND_PCM_READ_BITS\n");
+
+ pa_threaded_mainloop_lock(i->mainloop);
+ *(int*) argp = pa_sample_size(&i->sample_spec)*8;
+ pa_threaded_mainloop_unlock(i->mainloop);
+ break;
+
+ case SNDCTL_DSP_GETOPTR: {
+ count_info *info;
+
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_GETOPTR\n");
+
+ info = (count_info*) argp;
+ memset(info, 0, sizeof(*info));
+
+ pa_threaded_mainloop_lock(i->mainloop);
+
+ for (;;) {
+ pa_usec_t usec;
+
+ PLAYBACK_STREAM_CHECK_DEAD_GOTO(i, exit_loop);
+
+ if (pa_stream_get_time(i->play_stream, &usec) >= 0) {
+ size_t k = pa_usec_to_bytes(usec, &i->sample_spec);
+ int m;
+
+ info->bytes = (int) k;
+ m = k / i->fragment_size;
+ info->blocks = m - i->optr_n_blocks;
+ i->optr_n_blocks = m;
+
+ break;
+ }
+
+ if (pa_context_errno(i->context) != PA_ERR_NODATA) {
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_get_latency(): %s\n", pa_strerror(pa_context_errno(i->context)));
+ break;
+ }
+
+ pa_threaded_mainloop_wait(i->mainloop);
+ }
+
+ pa_threaded_mainloop_unlock(i->mainloop);
+
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": GETOPTR bytes=%i, blocks=%i, ptr=%i\n", info->bytes, info->blocks, info->ptr);
+
+ break;
+ }
+
+ case SNDCTL_DSP_GETIPTR:
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": invalid ioctl SNDCTL_DSP_GETIPTR\n");
+ goto inval;
+
+ case SNDCTL_DSP_SETDUPLEX:
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_SETDUPLEX\n");
+ /* this is a no-op */
+ break;
+
+ default:
+ /* Mixer ioctls are valid on /dev/dsp aswell */
+ return mixer_ioctl(i, request, argp, _errno);
+
+inval:
+ *_errno = EINVAL;
+ goto fail;
+ }
+
+ ret = 0;
+
+fail:
+
+ return ret;
+}
+
+int ioctl(int fd, unsigned long request, ...) {
+ fd_info *i;
+ va_list args;
+ void *argp;
+ int r, _errno = 0;
+
+ debug(DEBUG_LEVEL_VERBOSE, __FILE__": ioctl()\n");
+
+ va_start(args, request);
+ argp = va_arg(args, void *);
+ va_end(args);
+
+ if (!function_enter()) {
+ LOAD_IOCTL_FUNC();
+ return _ioctl(fd, request, argp);
+ }
+
+ if (!(i = fd_info_find(fd))) {
+ function_exit();
+ LOAD_IOCTL_FUNC();
+ return _ioctl(fd, request, argp);
+ }
+
+ if (i->type == FD_INFO_MIXER)
+ r = mixer_ioctl(i, request, argp, &_errno);
+ else
+ r = dsp_ioctl(i, request, argp, &_errno);
+
+ fd_info_unref(i);
+
+ if (_errno)
+ errno = _errno;
+
+ function_exit();
+
+ return r;
+}
+
+int close(int fd) {
+ fd_info *i;
+
+ debug(DEBUG_LEVEL_VERBOSE, __FILE__": close()\n");
+
+ if (!function_enter()) {
+ LOAD_CLOSE_FUNC();
+ return _close(fd);
+ }
+
+ if (!(i = fd_info_find(fd))) {
+ function_exit();
+ LOAD_CLOSE_FUNC();
+ return _close(fd);
+ }
+
+ fd_info_remove_from_list(i);
+ fd_info_unref(i);
+
+ function_exit();
+
+ return 0;
+}
+
+int access(const char *pathname, int mode) {
+
+ debug(DEBUG_LEVEL_VERBOSE, __FILE__": access(%s)\n", pathname?pathname:"NULL");
+
+ if (!pathname ||
+ ( strcmp(pathname, "/dev/dsp") != 0 &&
+ strcmp(pathname, "/dev/adsp") != 0 &&
+ strcmp(pathname, "/dev/sndstat") != 0 &&
+ strcmp(pathname, "/dev/mixer") != 0 )) {
+ LOAD_ACCESS_FUNC();
+ return _access(pathname, mode);
+ }
+
+ if (mode & (W_OK | X_OK)) {
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": access(%s, %x) = EACCESS\n", pathname, mode);
+ errno = EACCES;
+ return -1;
+ }
+
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": access(%s, %x) = OK\n", pathname, mode);
+
+ return 0;
+}
+
+int stat(const char *pathname, struct stat *buf) {
+#ifdef HAVE_OPEN64
+ struct stat64 parent;
+#else
+ struct stat parent;
+#endif
+ int ret;
+
+ if (!pathname ||
+ !buf ||
+ ( strcmp(pathname, "/dev/dsp") != 0 &&
+ strcmp(pathname, "/dev/adsp") != 0 &&
+ strcmp(pathname, "/dev/sndstat") != 0 &&
+ strcmp(pathname, "/dev/mixer") != 0 )) {
+ debug(DEBUG_LEVEL_VERBOSE, __FILE__": stat(%s)\n", pathname?pathname:"NULL");
+ LOAD_STAT_FUNC();
+ return _stat(pathname, buf);
+ }
+
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": stat(%s)\n", pathname);
+
+#ifdef _STAT_VER
+#ifdef HAVE_OPEN64
+ ret = __xstat64(_STAT_VER, "/dev", &parent);
+#else
+ ret = __xstat(_STAT_VER, "/dev", &parent);
+#endif
+#else
+#ifdef HAVE_OPEN64
+ ret = stat64("/dev", &parent);
+#else
+ ret = stat("/dev", &parent);
+#endif
+#endif
+
+ if (ret) {
+ debug(DEBUG_LEVEL_NORMAL, __FILE__": unable to stat \"/dev\"\n");
+ return -1;
+ }
+
+ buf->st_dev = parent.st_dev;
+ buf->st_ino = 0xDEADBEEF; /* FIXME: Can we do this in a safe way? */
+ buf->st_mode = S_IFCHR | S_IRUSR | S_IWUSR;
+ buf->st_nlink = 1;
+ buf->st_uid = getuid();
+ buf->st_gid = getgid();
+ buf->st_rdev = 0x0E03; /* FIXME: Linux specific */
+ buf->st_size = 0;
+ buf->st_atime = 1181557705;
+ buf->st_mtime = 1181557705;
+ buf->st_ctime = 1181557705;
+ buf->st_blksize = 1;
+ buf->st_blocks = 0;
+
+ return 0;
+}
+
+#ifdef HAVE_OPEN64
+
+int stat64(const char *pathname, struct stat64 *buf) {
+ struct stat oldbuf;
+ int ret;
+
+ debug(DEBUG_LEVEL_VERBOSE, __FILE__": stat64(%s)\n", pathname?pathname:"NULL");
+
+ if (!pathname ||
+ !buf ||
+ ( strcmp(pathname, "/dev/dsp") != 0 &&
+ strcmp(pathname, "/dev/adsp") != 0 &&
+ strcmp(pathname, "/dev/sndstat") != 0 &&
+ strcmp(pathname, "/dev/mixer") != 0 )) {
+ LOAD_STAT64_FUNC();
+ return _stat64(pathname, buf);
+ }
+
+ ret = stat(pathname, &oldbuf);
+ if (ret)
+ return ret;
+
+ buf->st_dev = oldbuf.st_dev;
+ buf->st_ino = oldbuf.st_ino;
+ buf->st_mode = oldbuf.st_mode;
+ buf->st_nlink = oldbuf.st_nlink;
+ buf->st_uid = oldbuf.st_uid;
+ buf->st_gid = oldbuf.st_gid;
+ buf->st_rdev = oldbuf.st_rdev;
+ buf->st_size = oldbuf.st_size;
+ buf->st_atime = oldbuf.st_atime;
+ buf->st_mtime = oldbuf.st_mtime;
+ buf->st_ctime = oldbuf.st_ctime;
+ buf->st_blksize = oldbuf.st_blksize;
+ buf->st_blocks = oldbuf.st_blocks;
+
+ return 0;
+}
+
+int open64(const char *filename, int flags, ...) {
+ va_list args;
+ mode_t mode = 0;
+
+ debug(DEBUG_LEVEL_VERBOSE, __FILE__": open64(%s)\n", filename?filename:"NULL");
+
+ if (flags & O_CREAT) {
+ va_start(args, flags);
+ if (sizeof(mode_t) < sizeof(int))
+ mode = va_arg(args, int);
+ else
+ mode = va_arg(args, mode_t);
+ va_end(args);
+ }
+
+ if (!filename ||
+ ( strcmp(filename, "/dev/dsp") != 0 &&
+ strcmp(filename, "/dev/adsp") != 0 &&
+ strcmp(filename, "/dev/sndstat") != 0 &&
+ strcmp(filename, "/dev/mixer") != 0 )) {
+ LOAD_OPEN64_FUNC();
+ return _open64(filename, flags, mode);
+ }
+
+ return real_open(filename, flags, mode);
+}
+
+#endif
+
+#ifdef _STAT_VER
+
+int __xstat(int ver, const char *pathname, struct stat *buf) {
+ debug(DEBUG_LEVEL_VERBOSE, __FILE__": __xstat(%s)\n", pathname?pathname:"NULL");
+
+ if (!pathname ||
+ !buf ||
+ ( strcmp(pathname, "/dev/dsp") != 0 &&
+ strcmp(pathname, "/dev/adsp") != 0 &&
+ strcmp(pathname, "/dev/sndstat") != 0 &&
+ strcmp(pathname, "/dev/mixer") != 0 )) {
+ LOAD_XSTAT_FUNC();
+ return ___xstat(ver, pathname, buf);
+ }
+
+ if (ver != _STAT_VER) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ return stat(pathname, buf);
+}
+
+#ifdef HAVE_OPEN64
+
+int __xstat64(int ver, const char *pathname, struct stat64 *buf) {
+ debug(DEBUG_LEVEL_VERBOSE, __FILE__": __xstat64(%s)\n", pathname?pathname:"NULL");
+
+ if (!pathname ||
+ !buf ||
+ ( strcmp(pathname, "/dev/dsp") != 0 &&
+ strcmp(pathname, "/dev/adsp") != 0 &&
+ strcmp(pathname, "/dev/sndstat") != 0 &&
+ strcmp(pathname, "/dev/mixer") != 0 )) {
+ LOAD_XSTAT64_FUNC();
+ return ___xstat64(ver, pathname, buf);
+ }
+
+ if (ver != _STAT_VER) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ return stat64(pathname, buf);
+}
+
+#endif
+
+#endif
+
+FILE* fopen(const char *filename, const char *mode) {
+ FILE *f = NULL;
+ int fd;
+ mode_t m;
+
+ debug(DEBUG_LEVEL_VERBOSE, __FILE__": fopen(%s)\n", filename?filename:"NULL");
+
+ if (!filename ||
+ !mode ||
+ ( strcmp(filename, "/dev/dsp") != 0 &&
+ strcmp(filename, "/dev/adsp") != 0 &&
+ strcmp(filename, "/dev/sndstat") != 0 &&
+ strcmp(filename, "/dev/mixer") != 0 )) {
+ LOAD_FOPEN_FUNC();
+ return _fopen(filename, mode);
+ }
+
+ switch (mode[0]) {
+ case 'r':
+ m = O_RDONLY;
+ break;
+ case 'w':
+ case 'a':
+ m = O_WRONLY;
+ break;
+ default:
+ errno = EINVAL;
+ return NULL;
+ }
+
+ if ((((mode[1] == 'b') || (mode[1] == 't')) && (mode[2] == '+')) || (mode[1] == '+'))
+ m = O_RDWR;
+
+ if ((fd = real_open(filename, m, 0)) < 0)
+ return NULL;
+
+ if (!(f = fdopen(fd, mode))) {
+ close(fd);
+ return NULL;
+ }
+
+ return f;
+}
+
+#ifdef HAVE_OPEN64
+
+FILE *fopen64(const char *filename, const char *mode) {
+
+ debug(DEBUG_LEVEL_VERBOSE, __FILE__": fopen64(%s)\n", filename?filename:"NULL");
+
+ if (!filename ||
+ !mode ||
+ ( strcmp(filename, "/dev/dsp") != 0 &&
+ strcmp(filename, "/dev/adsp") != 0 &&
+ strcmp(filename, "/dev/sndstat") != 0 &&
+ strcmp(filename, "/dev/mixer") != 0 )) {
+ LOAD_FOPEN64_FUNC();
+ return _fopen64(filename, mode);
+ }
+
+ return fopen(filename, mode);
+}
+
+#endif
+
+int fclose(FILE *f) {
+ fd_info *i;
+
+ debug(DEBUG_LEVEL_VERBOSE, __FILE__": fclose()\n");
+
+ if (!function_enter()) {
+ LOAD_FCLOSE_FUNC();
+ return _fclose(f);
+ }
+
+ if (!(i = fd_info_find(fileno(f)))) {
+ function_exit();
+ LOAD_FCLOSE_FUNC();
+ return _fclose(f);
+ }
+
+ fd_info_remove_from_list(i);
+
+ /* Dirty trick to avoid that the fd is not freed twice, once by us
+ * and once by the real fclose() */
+ i->app_fd = -1;
+
+ fd_info_unref(i);
+
+ function_exit();
+
+ LOAD_FCLOSE_FUNC();
+ return _fclose(f);
+}
diff --git a/src/utils/paplay.c b/src/utils/paplay.c
new file mode 100644
index 00000000..fddbb18c
--- /dev/null
+++ b/src/utils/paplay.c
@@ -0,0 +1,435 @@
+/* $Id$ */
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2004-2006 Lennart Poettering
+ Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <signal.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <locale.h>
+
+#include <sndfile.h>
+
+#include <pulse/pulseaudio.h>
+
+#if PA_API_VERSION < 9
+#error Invalid PulseAudio API version
+#endif
+
+static pa_context *context = NULL;
+static pa_stream *stream = NULL;
+static pa_mainloop_api *mainloop_api = NULL;
+
+static char *stream_name = NULL, *client_name = NULL, *device = NULL;
+
+static int verbose = 0;
+static pa_volume_t volume = PA_VOLUME_NORM;
+
+static SNDFILE* sndfile = NULL;
+
+static pa_sample_spec sample_spec = { 0, 0, 0 };
+static pa_channel_map channel_map;
+static int channel_map_set = 0;
+
+static sf_count_t (*readf_function)(SNDFILE *_sndfile, void *ptr, sf_count_t frames) = NULL;
+
+/* A shortcut for terminating the application */
+static void quit(int ret) {
+ assert(mainloop_api);
+ mainloop_api->quit(mainloop_api, ret);
+}
+
+/* Connection draining complete */
+static void context_drain_complete(pa_context*c, void *userdata) {
+ pa_context_disconnect(c);
+}
+
+/* Stream draining complete */
+static void stream_drain_complete(pa_stream*s, int success, void *userdata) {
+ pa_operation *o;
+
+ if (!success) {
+ fprintf(stderr, "Failed to drain stream: %s\n", pa_strerror(pa_context_errno(context)));
+ quit(1);
+ }
+
+ if (verbose)
+ fprintf(stderr, "Playback stream drained.\n");
+
+ pa_stream_disconnect(stream);
+ pa_stream_unref(stream);
+ stream = NULL;
+
+ if (!(o = pa_context_drain(context, context_drain_complete, NULL)))
+ pa_context_disconnect(context);
+ else {
+ pa_operation_unref(o);
+
+ if (verbose)
+ fprintf(stderr, "Draining connection to server.\n");
+ }
+}
+
+/* This is called whenever new data may be written to the stream */
+static void stream_write_callback(pa_stream *s, size_t length, void *userdata) {
+ sf_count_t bytes;
+ void *data;
+ assert(s && length);
+
+ if (!sndfile)
+ return;
+
+ data = pa_xmalloc(length);
+
+ if (readf_function) {
+ size_t k = pa_frame_size(&sample_spec);
+
+ if ((bytes = readf_function(sndfile, data, length/k)) > 0)
+ bytes *= k;
+
+ } else
+ bytes = sf_read_raw(sndfile, data, length);
+
+ if (bytes > 0)
+ pa_stream_write(s, data, bytes, pa_xfree, 0, PA_SEEK_RELATIVE);
+ else
+ pa_xfree(data);
+
+ if (bytes < (sf_count_t) length) {
+ sf_close(sndfile);
+ sndfile = NULL;
+ pa_operation_unref(pa_stream_drain(s, stream_drain_complete, NULL));
+ }
+}
+
+/* This routine is called whenever the stream state changes */
+static void stream_state_callback(pa_stream *s, void *userdata) {
+ assert(s);
+
+ switch (pa_stream_get_state(s)) {
+ case PA_STREAM_CREATING:
+ case PA_STREAM_TERMINATED:
+ break;
+
+ case PA_STREAM_READY:
+ if (verbose)
+ fprintf(stderr, "Stream successfully created\n");
+ break;
+
+ case PA_STREAM_FAILED:
+ default:
+ fprintf(stderr, "Stream errror: %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
+ quit(1);
+ }
+}
+
+/* This is called whenever the context status changes */
+static void context_state_callback(pa_context *c, void *userdata) {
+ assert(c);
+
+ switch (pa_context_get_state(c)) {
+ case PA_CONTEXT_CONNECTING:
+ case PA_CONTEXT_AUTHORIZING:
+ case PA_CONTEXT_SETTING_NAME:
+ break;
+
+ case PA_CONTEXT_READY: {
+ pa_cvolume cv;
+
+ assert(c && !stream);
+
+ if (verbose)
+ fprintf(stderr, "Connection established.\n");
+
+ stream = pa_stream_new(c, stream_name, &sample_spec, channel_map_set ? &channel_map : NULL);
+ assert(stream);
+
+ pa_stream_set_state_callback(stream, stream_state_callback, NULL);
+ pa_stream_set_write_callback(stream, stream_write_callback, NULL);
+ pa_stream_connect_playback(stream, device, NULL, 0, pa_cvolume_set(&cv, sample_spec.channels, volume), NULL);
+
+ break;
+ }
+
+ case PA_CONTEXT_TERMINATED:
+ quit(0);
+ break;
+
+ case PA_CONTEXT_FAILED:
+ default:
+ fprintf(stderr, "Connection failure: %s\n", pa_strerror(pa_context_errno(c)));
+ quit(1);
+ }
+}
+
+/* UNIX signal to quit recieved */
+static void exit_signal_callback(pa_mainloop_api*m, pa_signal_event *e, int sig, void *userdata) {
+ if (verbose)
+ fprintf(stderr, "Got SIGINT, exiting.\n");
+ quit(0);
+
+}
+
+static void help(const char *argv0) {
+
+ printf("%s [options] [FILE]\n\n"
+ " -h, --help Show this help\n"
+ " --version Show version\n\n"
+ " -v, --verbose Enable verbose operation\n\n"
+ " -s, --server=SERVER The name of the server to connect to\n"
+ " -d, --device=DEVICE The name of the sink to connect to\n"
+ " -n, --client-name=NAME How to call this client on the server\n"
+ " --stream-name=NAME How to call this stream on the server\n"
+ " --volume=VOLUME Specify the initial (linear) volume in range 0...65536\n"
+ " --channel-map=CHANNELMAP Set the channel map to the use\n",
+ argv0);
+}
+
+enum {
+ ARG_VERSION = 256,
+ ARG_STREAM_NAME,
+ ARG_VOLUME,
+ ARG_CHANNELMAP
+};
+
+int main(int argc, char *argv[]) {
+ pa_mainloop* m = NULL;
+ int ret = 1, r, c;
+ char *bn, *server = NULL;
+ const char *filename;
+ SF_INFO sfinfo;
+
+ static const struct option long_options[] = {
+ {"device", 1, NULL, 'd'},
+ {"server", 1, NULL, 's'},
+ {"client-name", 1, NULL, 'n'},
+ {"stream-name", 1, NULL, ARG_STREAM_NAME},
+ {"version", 0, NULL, ARG_VERSION},
+ {"help", 0, NULL, 'h'},
+ {"verbose", 0, NULL, 'v'},
+ {"volume", 1, NULL, ARG_VOLUME},
+ {"channel-map", 1, NULL, ARG_CHANNELMAP},
+ {NULL, 0, NULL, 0}
+ };
+
+ setlocale(LC_ALL, "");
+
+ if (!(bn = strrchr(argv[0], '/')))
+ bn = argv[0];
+ else
+ bn++;
+
+ while ((c = getopt_long(argc, argv, "d:s:n:hv", long_options, NULL)) != -1) {
+
+ switch (c) {
+ case 'h' :
+ help(bn);
+ ret = 0;
+ goto quit;
+
+ case ARG_VERSION:
+ printf("paplay "PACKAGE_VERSION"\nCompiled with libpulse %s\nLinked with libpulse %s\n", pa_get_headers_version(), pa_get_library_version());
+ ret = 0;
+ goto quit;
+
+ case 'd':
+ pa_xfree(device);
+ device = pa_xstrdup(optarg);
+ break;
+
+ case 's':
+ pa_xfree(server);
+ server = pa_xstrdup(optarg);
+ break;
+
+ case 'n':
+ pa_xfree(client_name);
+ client_name = pa_xstrdup(optarg);
+ break;
+
+ case ARG_STREAM_NAME:
+ pa_xfree(stream_name);
+ stream_name = pa_xstrdup(optarg);
+ break;
+
+ case 'v':
+ verbose = 1;
+ break;
+
+ case ARG_VOLUME: {
+ int v = atoi(optarg);
+ volume = v < 0 ? 0 : v;
+ break;
+ }
+
+ case ARG_CHANNELMAP:
+ if (!pa_channel_map_parse(&channel_map, optarg)) {
+ fprintf(stderr, "Invalid channel map\n");
+ goto quit;
+ }
+
+ channel_map_set = 1;
+ break;
+
+ default:
+ goto quit;
+ }
+ }
+
+ filename = optind < argc ? argv[optind] : "STDIN";
+
+ memset(&sfinfo, 0, sizeof(sfinfo));
+
+ if (optind < argc)
+ sndfile = sf_open(filename, SFM_READ, &sfinfo);
+ else
+ sndfile = sf_open_fd(STDIN_FILENO, SFM_READ, &sfinfo, 0);
+
+ if (!sndfile) {
+ fprintf(stderr, "Failed to open file '%s'\n", filename);
+ goto quit;
+ }
+
+ sample_spec.rate = sfinfo.samplerate;
+ sample_spec.channels = sfinfo.channels;
+
+ readf_function = NULL;
+
+ switch (sfinfo.format & 0xFF) {
+ case SF_FORMAT_PCM_16:
+ case SF_FORMAT_PCM_U8:
+ case SF_FORMAT_PCM_S8:
+ sample_spec.format = PA_SAMPLE_S16NE;
+ readf_function = (sf_count_t (*)(SNDFILE *_sndfile, void *ptr, sf_count_t frames)) sf_readf_short;
+ break;
+
+ case SF_FORMAT_ULAW:
+ sample_spec.format = PA_SAMPLE_ULAW;
+ break;
+
+ case SF_FORMAT_ALAW:
+ sample_spec.format = PA_SAMPLE_ALAW;
+ break;
+
+ case SF_FORMAT_FLOAT:
+ case SF_FORMAT_DOUBLE:
+ default:
+ sample_spec.format = PA_SAMPLE_FLOAT32NE;
+ readf_function = (sf_count_t (*)(SNDFILE *_sndfile, void *ptr, sf_count_t frames)) sf_readf_float;
+ break;
+ }
+
+ assert(pa_sample_spec_valid(&sample_spec));
+
+ if (channel_map_set && channel_map.channels != sample_spec.channels) {
+ fprintf(stderr, "Channel map doesn't match file.\n");
+ goto quit;
+ }
+
+ if (!client_name) {
+ client_name = pa_locale_to_utf8(bn);
+ if (!client_name)
+ client_name = pa_utf8_filter(bn);
+ }
+
+ if (!stream_name) {
+ const char *n;
+
+ n = sf_get_string(sndfile, SF_STR_TITLE);
+
+ if (!n)
+ n = filename;
+
+ stream_name = pa_locale_to_utf8(n);
+ if (!stream_name)
+ stream_name = pa_utf8_filter(n);
+ }
+
+ if (verbose) {
+ char t[PA_SAMPLE_SPEC_SNPRINT_MAX];
+ pa_sample_spec_snprint(t, sizeof(t), &sample_spec);
+ fprintf(stderr, "Using sample spec '%s'\n", t);
+ }
+
+ /* Set up a new main loop */
+ if (!(m = pa_mainloop_new())) {
+ fprintf(stderr, "pa_mainloop_new() failed.\n");
+ goto quit;
+ }
+
+ mainloop_api = pa_mainloop_get_api(m);
+
+ r = pa_signal_init(mainloop_api);
+ assert(r == 0);
+ pa_signal_new(SIGINT, exit_signal_callback, NULL);
+#ifdef SIGPIPE
+ signal(SIGPIPE, SIG_IGN);
+#endif
+
+ /* Create a new connection context */
+ if (!(context = pa_context_new(mainloop_api, client_name))) {
+ fprintf(stderr, "pa_context_new() failed.\n");
+ goto quit;
+ }
+
+ pa_context_set_state_callback(context, context_state_callback, NULL);
+
+ /* Connect the context */
+ pa_context_connect(context, server, 0, NULL);
+
+ /* Run the main loop */
+ if (pa_mainloop_run(m, &ret) < 0) {
+ fprintf(stderr, "pa_mainloop_run() failed.\n");
+ goto quit;
+ }
+
+quit:
+ if (stream)
+ pa_stream_unref(stream);
+
+ if (context)
+ pa_context_unref(context);
+
+ if (m) {
+ pa_signal_done();
+ pa_mainloop_free(m);
+ }
+
+ pa_xfree(server);
+ pa_xfree(device);
+ pa_xfree(client_name);
+ pa_xfree(stream_name);
+
+ if (sndfile)
+ sf_close(sndfile);
+
+ return ret;
+}
diff --git a/src/utils/pasuspender.c b/src/utils/pasuspender.c
new file mode 100644
index 00000000..05d96a68
--- /dev/null
+++ b/src/utils/pasuspender.c
@@ -0,0 +1,318 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <signal.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <getopt.h>
+
+#include <sndfile.h>
+
+#ifdef __linux__
+#include <sys/prctl.h>
+#endif
+
+#include <pulse/pulseaudio.h>
+#include <pulsecore/macro.h>
+
+#if PA_API_VERSION < 10
+#error Invalid PulseAudio API version
+#endif
+
+#define BUFSIZE 1024
+
+static pa_context *context = NULL;
+static pa_mainloop_api *mainloop_api = NULL;
+static char **child_argv = NULL;
+static int child_argc = 0;
+static pid_t child_pid = (pid_t) -1;
+static int child_ret = 0;
+static int dead = 1;
+
+static void quit(int ret) {
+ pa_assert(mainloop_api);
+ mainloop_api->quit(mainloop_api, ret);
+}
+
+
+static void context_drain_complete(pa_context *c, void *userdata) {
+ pa_context_disconnect(c);
+}
+
+static void drain(void) {
+ pa_operation *o;
+
+ if (!(o = pa_context_drain(context, context_drain_complete, NULL)))
+ pa_context_disconnect(context);
+ else
+ pa_operation_unref(o);
+}
+
+static void start_child(void) {
+
+ if ((child_pid = fork()) < 0) {
+
+ fprintf(stderr, "fork(): %s\n", strerror(errno));
+ quit(1);
+
+ } else if (child_pid == 0) {
+ /* Child */
+
+#ifdef __linux__
+ prctl(PR_SET_PDEATHSIG, SIGTERM, 0, 0, 0);
+#endif
+
+ if (execvp(child_argv[0], child_argv) < 0)
+ fprintf(stderr, "execvp(): %s\n", strerror(errno));
+
+ _exit(1);
+
+ } else {
+
+ /* parent */
+ dead = 0;
+ }
+}
+
+static void suspend_complete(pa_context *c, int success, void *userdata) {
+ static int n = 0;
+
+ n++;
+
+ if (!success) {
+ fprintf(stderr, "Failure to suspend: %s\n", pa_strerror(pa_context_errno(c)));
+ quit(1);
+ return;
+ }
+
+ if (n >= 2)
+ start_child();
+}
+
+static void resume_complete(pa_context *c, int success, void *userdata) {
+ static int n = 0;
+
+ n++;
+
+ if (!success) {
+ fprintf(stderr, "Failure to resume: %s\n", pa_strerror(pa_context_errno(c)));
+ quit(1);
+ return;
+ }
+
+ if (n >= 2)
+ drain(); /* drain and quit */
+}
+
+static void context_state_callback(pa_context *c, void *userdata) {
+ pa_assert(c);
+
+ switch (pa_context_get_state(c)) {
+ case PA_CONTEXT_CONNECTING:
+ case PA_CONTEXT_AUTHORIZING:
+ case PA_CONTEXT_SETTING_NAME:
+ break;
+
+ case PA_CONTEXT_READY:
+ if (pa_context_is_local(c)) {
+ pa_operation_unref(pa_context_suspend_sink_by_index(c, PA_INVALID_INDEX, 1, suspend_complete, NULL));
+ pa_operation_unref(pa_context_suspend_source_by_index(c, PA_INVALID_INDEX, 1, suspend_complete, NULL));
+ } else {
+ fprintf(stderr, "WARNING: Sound server is not local, not suspending.\n");
+ start_child();
+ }
+
+ break;
+
+ case PA_CONTEXT_TERMINATED:
+ quit(0);
+ break;
+
+ case PA_CONTEXT_FAILED:
+ default:
+ fprintf(stderr, "Connection failure: %s\n", pa_strerror(pa_context_errno(c)));
+
+ pa_context_unref(context);
+ context = NULL;
+
+ if (child_pid == (pid_t) -1)
+ /* not started yet, then we do it now */
+ start_child();
+ else if (dead)
+ /* already started, and dead, so let's quit */
+ quit(1);
+
+ break;
+ }
+}
+
+static void sigint_callback(pa_mainloop_api *m, pa_signal_event *e, int sig, void *userdata) {
+ fprintf(stderr, "Got SIGINT, exiting.\n");
+ quit(0);
+}
+
+static void sigchld_callback(pa_mainloop_api *m, pa_signal_event *e, int sig, void *userdata) {
+ int status = 0;
+ pid_t p;
+
+ p = waitpid(-1, &status, WNOHANG);
+
+ if (p != child_pid)
+ return;
+
+ dead = 1;
+
+ if (WIFEXITED(status))
+ child_ret = WEXITSTATUS(status);
+ else if (WIFSIGNALED(status)) {
+ fprintf(stderr, "WARNING: Child process terminated by signal %u\n", WTERMSIG(status));
+ child_ret = 1;
+ }
+
+ if (context) {
+ if (pa_context_is_local(context)) {
+ /* A context is around, so let's resume */
+ pa_operation_unref(pa_context_suspend_sink_by_index(context, PA_INVALID_INDEX, 0, resume_complete, NULL));
+ pa_operation_unref(pa_context_suspend_source_by_index(context, PA_INVALID_INDEX, 0, resume_complete, NULL));
+ } else
+ drain();
+ } else
+ /* Hmm, no context here, so let's terminate right away */
+ quit(0);
+}
+
+static void help(const char *argv0) {
+
+ printf("%s [options] ... \n\n"
+ " -h, --help Show this help\n"
+ " --version Show version\n"
+ " -s, --server=SERVER The name of the server to connect to\n\n",
+ argv0);
+}
+
+enum {
+ ARG_VERSION = 256
+};
+
+int main(int argc, char *argv[]) {
+ pa_mainloop* m = NULL;
+ int c, ret = 1;
+ char *server = NULL, *bn;
+
+ static const struct option long_options[] = {
+ {"server", 1, NULL, 's'},
+ {"version", 0, NULL, ARG_VERSION},
+ {"help", 0, NULL, 'h'},
+ {NULL, 0, NULL, 0}
+ };
+
+ if (!(bn = strrchr(argv[0], '/')))
+ bn = argv[0];
+ else
+ bn++;
+
+ while ((c = getopt_long(argc, argv, "s:h", long_options, NULL)) != -1) {
+ switch (c) {
+ case 'h' :
+ help(bn);
+ ret = 0;
+ goto quit;
+
+ case ARG_VERSION:
+ printf("pasuspender "PACKAGE_VERSION"\nCompiled with libpulse %s\nLinked with libpulse %s\n", pa_get_headers_version(), pa_get_library_version());
+ ret = 0;
+ goto quit;
+
+ case 's':
+ pa_xfree(server);
+ server = pa_xstrdup(optarg);
+ break;
+
+ default:
+ goto quit;
+ }
+ }
+
+ child_argv = argv + optind;
+ child_argc = argc - optind;
+
+ if (child_argc <= 0) {
+ help(bn);
+ ret = 0;
+ goto quit;
+ }
+
+ if (!(m = pa_mainloop_new())) {
+ fprintf(stderr, "pa_mainloop_new() failed.\n");
+ goto quit;
+ }
+
+ pa_assert_se(mainloop_api = pa_mainloop_get_api(m));
+ pa_assert_se(pa_signal_init(mainloop_api) == 0);
+ pa_signal_new(SIGINT, sigint_callback, NULL);
+ pa_signal_new(SIGCHLD, sigchld_callback, NULL);
+#ifdef SIGPIPE
+ signal(SIGPIPE, SIG_IGN);
+#endif
+
+ if (!(context = pa_context_new(mainloop_api, bn))) {
+ fprintf(stderr, "pa_context_new() failed.\n");
+ goto quit;
+ }
+
+ pa_context_set_state_callback(context, context_state_callback, NULL);
+ pa_context_connect(context, server, PA_CONTEXT_NOAUTOSPAWN, NULL);
+
+ if (pa_mainloop_run(m, &ret) < 0) {
+ fprintf(stderr, "pa_mainloop_run() failed.\n");
+ goto quit;
+ }
+
+quit:
+ if (context)
+ pa_context_unref(context);
+
+ if (m) {
+ pa_signal_done();
+ pa_mainloop_free(m);
+ }
+
+ pa_xfree(server);
+
+ if (!dead)
+ kill(child_pid, SIGTERM);
+
+ return ret == 0 ? child_ret : ret;
+}
diff --git a/src/utils/pax11publish.c b/src/utils/pax11publish.c
new file mode 100644
index 00000000..9a50f8ef
--- /dev/null
+++ b/src/utils/pax11publish.c
@@ -0,0 +1,222 @@
+/* $Id$ */
+
+/***
+ 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 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <getopt.h>
+#include <string.h>
+#include <assert.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+
+#include <pulse/util.h>
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/log.h>
+#include <pulsecore/authkey.h>
+#include <pulsecore/native-common.h>
+#include <pulsecore/x11prop.h>
+
+#include "../pulse/client-conf.h"
+
+int main(int argc, char *argv[]) {
+ const char *dname = NULL, *sink = NULL, *source = NULL, *server = NULL, *cookie_file = PA_NATIVE_COOKIE_FILE;
+ int c, ret = 1;
+ Display *d = NULL;
+ enum { DUMP, EXPORT, IMPORT, REMOVE } mode = DUMP;
+
+ while ((c = getopt(argc, argv, "deiD:S:O:I:c:hr")) != -1) {
+ switch (c) {
+ case 'D' :
+ dname = optarg;
+ break;
+ case 'h':
+ printf("%s [-D display] [-S server] [-O sink] [-I source] [-c file] [-d|-e|-i|-r]\n\n"
+ " -d Show current PulseAudio data attached to X11 display (default)\n"
+ " -e Export local PulseAudio data to X11 display\n"
+ " -i Import PulseAudio data from X11 display to local environment variables and cookie file.\n"
+ " -r Remove PulseAudio data from X11 display\n",
+ pa_path_get_filename(argv[0]));
+ ret = 0;
+ goto finish;
+ case 'd':
+ mode = DUMP;
+ break;
+ case 'e':
+ mode = EXPORT;
+ break;
+ case 'i':
+ mode = IMPORT;
+ break;
+ case 'r':
+ mode = REMOVE;
+ break;
+ case 'c':
+ cookie_file = optarg;
+ break;
+ case 'I':
+ source = optarg;
+ break;
+ case 'O':
+ sink = optarg;
+ break;
+ case 'S':
+ server = optarg;
+ break;
+ default:
+ fprintf(stderr, "Failed to parse command line.\n");
+ goto finish;
+ }
+ }
+
+ if (!(d = XOpenDisplay(dname))) {
+ pa_log("XOpenDisplay() failed");
+ goto finish;
+ }
+
+ switch (mode) {
+ case DUMP: {
+ char t[1024];
+ if (pa_x11_get_prop(d, "PULSE_SERVER", t, sizeof(t)))
+ printf("Server: %s\n", t);
+ if (pa_x11_get_prop(d, "PULSE_SOURCE", t, sizeof(t)))
+ printf("Source: %s\n", t);
+ if (pa_x11_get_prop(d, "PULSE_SINK", t, sizeof(t)))
+ printf("Sink: %s\n", t);
+ if (pa_x11_get_prop(d, "PULSE_COOKIE", t, sizeof(t)))
+ printf("Cookie: %s\n", t);
+
+ break;
+ }
+
+ case IMPORT: {
+ char t[1024];
+ if (pa_x11_get_prop(d, "PULSE_SERVER", t, sizeof(t)))
+ printf("PULSE_SERVER='%s'\nexport PULSE_SERVER\n", t);
+ if (pa_x11_get_prop(d, "PULSE_SOURCE", t, sizeof(t)))
+ printf("PULSE_SOURCE='%s'\nexport PULSE_SOURCE\n", t);
+ if (pa_x11_get_prop(d, "PULSE_SINK", t, sizeof(t)))
+ printf("PULSE_SINK='%s'\nexport PULSE_SINK\n", t);
+
+ if (pa_x11_get_prop(d, "PULSE_COOKIE", t, sizeof(t))) {
+ uint8_t cookie[PA_NATIVE_COOKIE_LENGTH];
+ size_t l;
+ if ((l = pa_parsehex(t, cookie, sizeof(cookie))) != sizeof(cookie)) {
+ fprintf(stderr, "Failed to parse cookie data\n");
+ goto finish;
+ }
+
+ if (pa_authkey_save(cookie_file, cookie, l) < 0) {
+ fprintf(stderr, "Failed to save cookie data\n");
+ goto finish;
+ }
+ }
+
+ break;
+ }
+
+ case EXPORT: {
+ pa_client_conf *conf = pa_client_conf_new();
+ uint8_t cookie[PA_NATIVE_COOKIE_LENGTH];
+ char hx[PA_NATIVE_COOKIE_LENGTH*2+1];
+ assert(conf);
+
+ if (pa_client_conf_load(conf, NULL) < 0) {
+ fprintf(stderr, "Failed to load client configuration file.\n");
+ goto finish;
+ }
+
+ if (pa_client_conf_env(conf) < 0) {
+ fprintf(stderr, "Failed to read environment configuration data.\n");
+ goto finish;
+ }
+
+ pa_x11_del_prop(d, "PULSE_SERVER");
+ pa_x11_del_prop(d, "PULSE_SINK");
+ pa_x11_del_prop(d, "PULSE_SOURCE");
+ pa_x11_del_prop(d, "PULSE_ID");
+ pa_x11_del_prop(d, "PULSE_COOKIE");
+
+ if (server)
+ pa_x11_set_prop(d, "PULSE_SERVER", server);
+ else if (conf->default_server)
+ pa_x11_set_prop(d, "PULSE_SERVER", conf->default_server);
+ else {
+ char hn[256];
+ if (!pa_get_fqdn(hn, sizeof(hn))) {
+ fprintf(stderr, "Failed to get FQDN.\n");
+ goto finish;
+ }
+
+ pa_x11_set_prop(d, "PULSE_SERVER", hn);
+ }
+
+ if (sink)
+ pa_x11_set_prop(d, "PULSE_SINK", sink);
+ else if (conf->default_sink)
+ pa_x11_set_prop(d, "PULSE_SINK", conf->default_sink);
+
+ if (source)
+ pa_x11_set_prop(d, "PULSE_SOURCE", source);
+ if (conf->default_source)
+ pa_x11_set_prop(d, "PULSE_SOURCE", conf->default_source);
+
+ pa_client_conf_free(conf);
+
+ if (pa_authkey_load_auto(cookie_file, cookie, sizeof(cookie)) < 0) {
+ fprintf(stderr, "Failed to load cookie data\n");
+ goto finish;
+ }
+
+ pa_x11_set_prop(d, "PULSE_COOKIE", pa_hexstr(cookie, sizeof(cookie), hx, sizeof(hx)));
+ break;
+ }
+
+ case REMOVE:
+ pa_x11_del_prop(d, "PULSE_SERVER");
+ pa_x11_del_prop(d, "PULSE_SINK");
+ pa_x11_del_prop(d, "PULSE_SOURCE");
+ pa_x11_del_prop(d, "PULSE_ID");
+ pa_x11_del_prop(d, "PULSE_COOKIE");
+ break;
+
+ default:
+ fprintf(stderr, "No yet implemented.\n");
+ goto finish;
+ }
+
+ ret = 0;
+
+finish:
+
+ if (d) {
+ XSync(d, False);
+ XCloseDisplay(d);
+ }
+
+ return ret;
+}