summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLennart Poettering <lennart@poettering.net>2008-03-15 15:15:36 +0000
committerLennart Poettering <lennart@poettering.net>2008-03-15 15:15:36 +0000
commit8d9bdaca5a4092cd078d79c58c4bfa0da277bff4 (patch)
treecbb37fd94b84fb8547c50f07f50c81b26098be99
parentdd81a907a7a596c20284baf44977dcb6a9d8cf91 (diff)
parent6ad7621b61d40dba9b877379ef5f15f73a2ed268 (diff)
really create glitch-free branch
git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/glitch-free@2120 fefdeb5f-60dc-0310-8127-8f9354f1896f
-rw-r--r--GPL340
-rw-r--r--LGPL510
-rw-r--r--LICENSE13
-rw-r--r--Makefile.am64
-rw-r--r--PROTOCOL80
-rw-r--r--README1
-rw-r--r--acinclude.m4388
-rwxr-xr-xautogen.sh24
-rwxr-xr-xbootstrap.sh64
-rw-r--r--configure.ac1157
-rw-r--r--doxygen/Makefile.am26
-rw-r--r--doxygen/doxygen.conf.in1156
-rw-r--r--libpulse-browse.pc.in11
-rw-r--r--libpulse-mainloop-glib.pc.in11
-rw-r--r--libpulse-simple.pc.in11
-rw-r--r--libpulse.pc.in10
-rw-r--r--man/Makefile.am192
-rw-r--r--man/default.pa.5.xml.in58
-rw-r--r--man/esdcompat.1.xml.in91
-rw-r--r--man/pabrowse.1.xml.in49
-rw-r--r--man/pacat.1.xml.in185
-rw-r--r--man/pacmd.1.xml.in52
-rw-r--r--man/pactl.1.xml.in191
-rw-r--r--man/padsp.1.xml.in112
-rw-r--r--man/paplay.1.xml.in129
-rw-r--r--man/pasuspender.1.xml.in82
-rw-r--r--man/pax11publish.1.xml.in153
-rw-r--r--man/pulse-client.conf.5.xml.in115
-rw-r--r--man/pulse-daemon.conf.5.xml.in372
-rw-r--r--man/pulseaudio.1.xml.in446
-rw-r--r--man/xmltoman.css30
-rw-r--r--man/xmltoman.dtd39
-rw-r--r--man/xmltoman.xsl129
-rw-r--r--pulseaudio-text.svg388
-rw-r--r--pulseaudio.svg287
-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
-rw-r--r--todo59
427 files changed, 116827 insertions, 0 deletions
diff --git a/GPL b/GPL
new file mode 100644
index 00000000..b7b5f53d
--- /dev/null
+++ b/GPL
@@ -0,0 +1,340 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/LGPL b/LGPL
new file mode 100644
index 00000000..2d2d780e
--- /dev/null
+++ b/LGPL
@@ -0,0 +1,510 @@
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations
+below.
+
+ When we speak of free software, we are referring to freedom of use,
+not price. Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it
+becomes a de-facto standard. To achieve this, non-free programs must
+be allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control
+compilation and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at least
+ three years, to give the same user the materials specified in
+ Subsection 6a, above, for a charge no more than the cost of
+ performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply, and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License
+may add an explicit geographical distribution limitation excluding those
+countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms
+of the ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library.
+It is safest to attach them to the start of each source file to most
+effectively convey the exclusion of warranty; and each file should
+have at least the "copyright" line and a pointer to where the full
+notice is found.
+
+
+ <one line to give the library's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or
+your school, if any, to sign a "copyright disclaimer" for the library,
+if necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James
+ Random Hacker.
+
+ <signature of Ty Coon>, 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
+
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..612c2341
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,13 @@
+All PulseAudio source files are licensed under the GNU Lesser General Public
+License. (see file LGPL for details)
+
+However, the server side links to the GPL-only library 'libsamplerate' which
+practically downgrades the license of the server part to GPL (see file GPL for
+details), exercising section 3 of the LGPL.
+
+Hence you should treat the client library ('libpulse') of PulseAudio as being
+LGPL licensed and the server part ('libpulsecore') as being GPL licensed. Since
+the PulseAudio daemon and the modules link to 'libpulsecore' they are of course
+also GPL licensed.
+
+-- Lennart Poettering, April 20th, 2006.
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 00000000..a41dc6c8
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,64 @@
+# $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.
+
+EXTRA_DIST = bootstrap.sh LICENSE GPL LGPL doxygen/Makefile.am doxygen/Makefile.in doxygen/doxygen.conf.in README todo
+SUBDIRS=src doxygen man
+
+MAINTAINERCLEANFILES =
+noinst_DATA =
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = libpulse.pc libpulse-simple.pc
+
+if HAVE_AVAHI
+pkgconfig_DATA += \
+ libpulse-browse.pc
+endif
+
+if HAVE_GLIB20
+pkgconfig_DATA += \
+ libpulse-mainloop-glib.pc
+endif
+
+homepage: all dist doxygen
+ test -d $$HOME/homepage/private
+ mkdir -p $$HOME/homepage/private/projects/pulseaudio $$HOME/homepage/private/projects/pulseaudio/doxygen
+ cp pulseaudio-@PACKAGE_VERSION@.tar.gz $$HOME/homepage/private/projects/pulseaudio
+ cp -a doxygen/html/* $$HOME/homepage/private/projects/pulseaudio/doxygen
+
+doxygen:
+ $(MAKE) -C doxygen doxygen
+
+eolspace:
+ find \( -name '*.c' -o -name '*.h' -o -name 'Makefile.am' \) -exec perl -i -pe 's/\s+\n$$/\1\n/;' \{\} \;
+
+untabify:
+ find \( -name '*.c' -o -name '*.h' \) -exec perl -i -pe 's/\t/ /g;' \{\} \;
+
+fedora-snapshot: dist
+ cp $(distdir).tar.gz $$HOME/cvs.fedora/pulseaudio/devel/$(distdir).svn`date +%Y%m%d`.tar.gz
+
+dist-hook:
+ if test -d .svn ; then \
+ svn update ; \
+ chmod u+w ${distdir}/ChangeLog || true ; \
+ svn2cl -o ${distdir}/ChangeLog ; \
+ fi
+
+.PHONY: homepage distcleancheck doxygen
diff --git a/PROTOCOL b/PROTOCOL
new file mode 100644
index 00000000..497fa47b
--- /dev/null
+++ b/PROTOCOL
@@ -0,0 +1,80 @@
+### v8, implemented by >= 0.8
+
+First version supported.
+
+### v9, implemented by >= 0.9.0
+
+Reply for PA_COMMAND_CREATE_PLAYBACK_STREAM,
+PA_COMMAND_CREATE_RECORD_STREAM now returns buffer_attrs that are used:
+
+Four new fields in reply of PA_COMMAND_CREATE_PLAYBACK_STREAM:
+
+ maxlength
+ tlength
+ prebuf
+ minreq
+
+Two new fields in reply of PA_COMMAND_CREATE_RECORD_STREAM:
+
+ maxlength
+ fragsize
+
+### v10, implemented by >= 0.9.5
+
+New opcodes:
+
+ PA_COMMAND_MOVE_SINK_INPUT
+ PA_COMMAND_MOVE_SOURCE_OUTPUT
+
+SHM data transfer support
+
+### v11, implemented by >= 0.9.7
+
+Reply to to PA_COMMAND_GET_SINK_INPUT_INFO, PA_COMMAND_GET_SINK_INPUT_INFO_LIST gets new field at the end:
+
+ mute
+
+New opcodes:
+
+ PA_COMMAND_SET_SINK_INPUT_MUTE
+ PA_COMMAND_SUSPEND_SINK
+ PA_COMMAND_SUSPEND_SOURCE
+
+### v12, implemented by >= 0.9.8
+
+S32LE, S32BE is now known as sample spec.
+
+Gained six new bool fields for PA_COMMAND_CREATE_PLAYBACK_STREAM, PA_COMMAND_CREATE_RECORD_STREAM request at the end:
+
+ no_remap_channels
+ no_remix_channels
+ fix_format
+ fix_rate
+ fix_channels
+ no_move
+ variable_rate
+
+Reply to these opcodes now includes:
+
+ sample_spec
+ channel_map
+ device_index
+ device_name
+ suspended
+
+New opcodes for changing buffer attrs:
+
+ PA_COMMAND_SET_PLAYBACK_STREAM_BUFFER_ATTR
+ PA_COMMAND_SET_RECORD_STREAM_BUFFER_ATTR
+
+New opcodes for changing sampling rate:
+
+ PA_COMMAND_UPDATE_PLAYBACK_STREAM_SAMPLE_RATE
+ PA_COMMAND_UPDATE_RECORD_STREAM_SAMPLE_RATE
+
+New opcodes for notifications:
+
+ PA_COMMAND_PLAYBACK_STREAM_SUSPENDED
+ PA_COMMAND_CAPTURE_STREAM_SUSPENDED
+ PA_COMMAND_PLAYBACK_STREAM_MOVED
+ PA_COMMAND_CAPTURE_STREAM_MOVED
diff --git a/README b/README
new file mode 100644
index 00000000..005ddf4a
--- /dev/null
+++ b/README
@@ -0,0 +1 @@
+For more information see http://pulseaudio.org/
diff --git a/acinclude.m4 b/acinclude.m4
new file mode 100644
index 00000000..723eb87e
--- /dev/null
+++ b/acinclude.m4
@@ -0,0 +1,388 @@
+dnl @synopsis ACX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]])
+dnl
+dnl @summary figure out how to build C programs using POSIX threads
+dnl
+dnl This macro figures out how to build C programs using POSIX threads.
+dnl It sets the PTHREAD_LIBS output variable to the threads library and
+dnl linker flags, and the PTHREAD_CFLAGS output variable to any special
+dnl C compiler flags that are needed. (The user can also force certain
+dnl compiler flags/libs to be tested by setting these environment
+dnl variables.)
+dnl
+dnl Also sets PTHREAD_CC to any special C compiler that is needed for
+dnl multi-threaded programs (defaults to the value of CC otherwise).
+dnl (This is necessary on AIX to use the special cc_r compiler alias.)
+dnl
+dnl NOTE: You are assumed to not only compile your program with these
+dnl flags, but also link it with them as well. e.g. you should link
+dnl with $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS
+dnl $LIBS
+dnl
+dnl If you are only building threads programs, you may wish to use
+dnl these variables in your default LIBS, CFLAGS, and CC:
+dnl
+dnl LIBS="$PTHREAD_LIBS $LIBS"
+dnl CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
+dnl CC="$PTHREAD_CC"
+dnl
+dnl In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute
+dnl constant has a nonstandard name, defines PTHREAD_CREATE_JOINABLE to
+dnl that name (e.g. PTHREAD_CREATE_UNDETACHED on AIX).
+dnl
+dnl ACTION-IF-FOUND is a list of shell commands to run if a threads
+dnl library is found, and ACTION-IF-NOT-FOUND is a list of commands to
+dnl run it if it is not found. If ACTION-IF-FOUND is not specified, the
+dnl default action will define HAVE_PTHREAD.
+dnl
+dnl Please let the authors know if this macro fails on any platform, or
+dnl if you have any other suggestions or comments. This macro was based
+dnl on work by SGJ on autoconf scripts for FFTW (www.fftw.org) (with
+dnl help from M. Frigo), as well as ac_pthread and hb_pthread macros
+dnl posted by Alejandro Forero Cuervo to the autoconf macro repository.
+dnl We are also grateful for the helpful feedback of numerous users.
+dnl
+dnl @category InstalledPackages
+dnl @author Steven G. Johnson <stevenj@alum.mit.edu>
+dnl @version 2006-05-29
+dnl @license GPLWithACException
+dnl
+dnl Checks for GCC shared/pthread inconsistency based on work by
+dnl Marcin Owsiany <marcin@owsiany.pl>
+
+
+AC_DEFUN([ACX_PTHREAD], [
+AC_REQUIRE([AC_CANONICAL_HOST])
+AC_LANG_SAVE
+AC_LANG_C
+acx_pthread_ok=no
+
+# We used to check for pthread.h first, but this fails if pthread.h
+# requires special compiler flags (e.g. on True64 or Sequent).
+# It gets checked for in the link test anyway.
+
+# First of all, check if the user has set any of the PTHREAD_LIBS,
+# etcetera environment variables, and if threads linking works using
+# them:
+if test x"$PTHREAD_LIBS$PTHREAD_CFLAGS" != x; then
+ save_CFLAGS="$CFLAGS"
+ CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
+ save_LIBS="$LIBS"
+ LIBS="$PTHREAD_LIBS $LIBS"
+ AC_MSG_CHECKING([for pthread_join in LIBS=$PTHREAD_LIBS with CFLAGS=$PTHREAD_CFLAGS])
+ AC_TRY_LINK_FUNC(pthread_join, acx_pthread_ok=yes)
+ AC_MSG_RESULT($acx_pthread_ok)
+ if test x"$acx_pthread_ok" = xno; then
+ PTHREAD_LIBS=""
+ PTHREAD_CFLAGS=""
+ fi
+ LIBS="$save_LIBS"
+ CFLAGS="$save_CFLAGS"
+fi
+
+# We must check for the threads library under a number of different
+# names; the ordering is very important because some systems
+# (e.g. DEC) have both -lpthread and -lpthreads, where one of the
+# libraries is broken (non-POSIX).
+
+# Create a list of thread flags to try. Items starting with a "-" are
+# C compiler flags, and other items are library names, except for "none"
+# which indicates that we try without any flags at all, and "pthread-config"
+# which is a program returning the flags for the Pth emulation library.
+
+acx_pthread_flags="pthreads none -Kthread -kthread lthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config"
+
+# The ordering *is* (sometimes) important. Some notes on the
+# individual items follow:
+
+# pthreads: AIX (must check this before -lpthread)
+# none: in case threads are in libc; should be tried before -Kthread and
+# other compiler flags to prevent continual compiler warnings
+# -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h)
+# -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able)
+# lthread: LinuxThreads port on FreeBSD (also preferred to -pthread)
+# -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads)
+# -pthreads: Solaris/gcc
+# -mthreads: Mingw32/gcc, Lynx/gcc
+# -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it
+# doesn't hurt to check since this sometimes defines pthreads too;
+# also defines -D_REENTRANT)
+# ... -mt is also the pthreads flag for HP/aCC
+# pthread: Linux, etcetera
+# --thread-safe: KAI C++
+# pthread-config: use pthread-config program (for GNU Pth library)
+
+case "${host_cpu}-${host_os}" in
+ *solaris*)
+
+ # On Solaris (at least, for some versions), libc contains stubbed
+ # (non-functional) versions of the pthreads routines, so link-based
+ # tests will erroneously succeed. (We need to link with -pthreads/-mt/
+ # -lpthread.) (The stubs are missing pthread_cleanup_push, or rather
+ # a function called by this macro, so we could check for that, but
+ # who knows whether they'll stub that too in a future libc.) So,
+ # we'll just look for -pthreads and -lpthread first:
+
+ acx_pthread_flags="-pthreads pthread -mt -pthread $acx_pthread_flags"
+ ;;
+esac
+
+if test x"$acx_pthread_ok" = xno; then
+for flag in $acx_pthread_flags; do
+
+ case $flag in
+ none)
+ AC_MSG_CHECKING([whether pthreads work without any flags])
+ ;;
+
+ -*)
+ AC_MSG_CHECKING([whether pthreads work with $flag])
+ PTHREAD_CFLAGS="$flag"
+ ;;
+
+ pthread-config)
+ AC_CHECK_PROG(acx_pthread_config, pthread-config, yes, no)
+ if test x"$acx_pthread_config" = xno; then continue; fi
+ PTHREAD_CFLAGS="`pthread-config --cflags`"
+ PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`"
+ ;;
+
+ *)
+ AC_MSG_CHECKING([for the pthreads library -l$flag])
+ PTHREAD_LIBS="-l$flag"
+ ;;
+ esac
+
+ save_LIBS="$LIBS"
+ save_CFLAGS="$CFLAGS"
+ LIBS="$PTHREAD_LIBS $LIBS"
+ CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
+
+ # Check for various functions. We must include pthread.h,
+ # since some functions may be macros. (On the Sequent, we
+ # need a special flag -Kthread to make this header compile.)
+ # We check for pthread_join because it is in -lpthread on IRIX
+ # while pthread_create is in libc. We check for pthread_attr_init
+ # due to DEC craziness with -lpthreads. We check for
+ # pthread_cleanup_push because it is one of the few pthread
+ # functions on Solaris that doesn't have a non-functional libc stub.
+ # We try pthread_create on general principles.
+ AC_TRY_LINK([#include <pthread.h>],
+ [pthread_t th; pthread_join(th, 0);
+ pthread_attr_init(0); pthread_cleanup_push(0, 0);
+ pthread_create(0,0,0,0); pthread_cleanup_pop(0); ],
+ [acx_pthread_ok=yes])
+
+ LIBS="$save_LIBS"
+ CFLAGS="$save_CFLAGS"
+
+ AC_MSG_RESULT($acx_pthread_ok)
+ if test "x$acx_pthread_ok" = xyes; then
+ break;
+ fi
+
+ PTHREAD_LIBS=""
+ PTHREAD_CFLAGS=""
+done
+fi
+
+# Various other checks:
+if test "x$acx_pthread_ok" = xyes; then
+ save_LIBS="$LIBS"
+ LIBS="$PTHREAD_LIBS $LIBS"
+ save_CFLAGS="$CFLAGS"
+ CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
+
+ # Detect AIX lossage: JOINABLE attribute is called UNDETACHED.
+ AC_MSG_CHECKING([for joinable pthread attribute])
+ attr_name=unknown
+ for attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do
+ AC_TRY_LINK([#include <pthread.h>], [int attr=$attr; return attr;],
+ [attr_name=$attr; break])
+ done
+ AC_MSG_RESULT($attr_name)
+ if test "$attr_name" != PTHREAD_CREATE_JOINABLE; then
+ AC_DEFINE_UNQUOTED(PTHREAD_CREATE_JOINABLE, $attr_name,
+ [Define to necessary symbol if this constant
+ uses a non-standard name on your system.])
+ fi
+
+ AC_MSG_CHECKING([if more special flags are required for pthreads])
+ flag=no
+ case "${host_cpu}-${host_os}" in
+ *-aix* | *-freebsd* | *-darwin*) flag="-D_THREAD_SAFE";;
+ *solaris* | *-osf* | *-hpux*) flag="-D_REENTRANT";;
+ esac
+ AC_MSG_RESULT(${flag})
+ if test "x$flag" != xno; then
+ PTHREAD_CFLAGS="$flag $PTHREAD_CFLAGS"
+ fi
+
+ LIBS="$save_LIBS"
+ CFLAGS="$save_CFLAGS"
+ # More AIX lossage: must compile with xlc_r or cc_r
+ if test x"$GCC" != xyes; then
+ AC_CHECK_PROGS(PTHREAD_CC, xlc_r cc_r, ${CC})
+ else
+ PTHREAD_CC=$CC
+ fi
+
+ # The next part tries to detect GCC inconsistency with -shared on some
+ # architectures and systems. The problem is that in certain
+ # configurations, when -shared is specified, GCC "forgets" to
+ # internally use various flags which are still necessary.
+
+ AC_MSG_CHECKING([whether to check for GCC pthread/shared inconsistencies])
+ check_inconsistencies=yes
+ case "${host_cpu}-${host_os}" in
+ *-darwin*) check_inconsistencies=no ;;
+ esac
+ if test x"$GCC" != xyes -o "x$check_inconsistencies" != xyes ; then
+ AC_MSG_RESULT([no])
+ else
+ AC_MSG_RESULT([yes])
+
+ # In order not to create several levels of indentation, we test
+ # the value of "$ok" until we find out the cure or run out of
+ # ideas.
+ ok="no"
+
+ #
+ # Prepare the flags
+ #
+ save_CFLAGS="$CFLAGS"
+ save_LIBS="$LIBS"
+ save_CC="$CC"
+ # Try with the flags determined by the earlier checks.
+ #
+ # -Wl,-z,defs forces link-time symbol resolution, so that the
+ # linking checks with -shared actually have any value
+ #
+ # FIXME: -fPIC is required for -shared on many architectures,
+ # so we specify it here, but the right way would probably be to
+ # properly detect whether it is actually required.
+ CFLAGS="-shared -fPIC -Wl,-z,defs $CFLAGS $PTHREAD_CFLAGS"
+ LIBS="$PTHREAD_LIBS $LIBS"
+ CC="$PTHREAD_CC"
+
+ AC_MSG_CHECKING([whether -pthread is sufficient with -shared])
+ AC_TRY_LINK([#include <pthread.h>],
+ [pthread_t th; pthread_join(th, 0);
+ pthread_attr_init(0); pthread_cleanup_push(0, 0);
+ pthread_create(0,0,0,0); pthread_cleanup_pop(0); ],
+ [ok=yes])
+
+ if test "x$ok" = xyes; then
+ AC_MSG_RESULT([yes])
+ else
+ AC_MSG_RESULT([no])
+ fi
+
+ #
+ # Linux gcc on some architectures such as mips/mipsel forgets
+ # about -lpthread
+ #
+ if test x"$ok" = xno; then
+ AC_MSG_CHECKING([whether -lpthread fixes that])
+ LIBS="-lpthread $PTHREAD_LIBS $save_LIBS"
+ AC_TRY_LINK([#include <pthread.h>],
+ [pthread_t th; pthread_join(th, 0);
+ pthread_attr_init(0); pthread_cleanup_push(0, 0);
+ pthread_create(0,0,0,0); pthread_cleanup_pop(0); ],
+ [ok=yes])
+
+ if test "x$ok" = xyes; then
+ AC_MSG_RESULT([yes])
+ PTHREAD_LIBS="-lpthread $PTHREAD_LIBS"
+ else
+ AC_MSG_RESULT([no])
+ fi
+ fi
+ #
+ # FreeBSD 4.10 gcc forgets to use -lc_r instead of -lc
+ #
+ if test x"$ok" = xno; then
+ AC_MSG_CHECKING([whether -lc_r fixes that])
+ LIBS="-lc_r $PTHREAD_LIBS $save_LIBS"
+ AC_TRY_LINK([#include <pthread.h>],
+ [pthread_t th; pthread_join(th, 0);
+ pthread_attr_init(0); pthread_cleanup_push(0, 0);
+ pthread_create(0,0,0,0); pthread_cleanup_pop(0); ],
+ [ok=yes])
+
+ if test "x$ok" = xyes; then
+ AC_MSG_RESULT([yes])
+ PTHREAD_LIBS="-lc_r $PTHREAD_LIBS"
+ else
+ AC_MSG_RESULT([no])
+ fi
+ fi
+ if test x"$ok" = xno; then
+ # OK, we have run out of ideas
+ AC_MSG_WARN([Impossible to determine how to use pthreads with shared libraries])
+
+ # so it's not safe to assume that we may use pthreads
+ acx_pthread_ok=no
+ fi
+
+ CFLAGS="$save_CFLAGS"
+ LIBS="$save_LIBS"
+ CC="$save_CC"
+ fi
+else
+ PTHREAD_CC="$CC"
+fi
+
+AC_SUBST(PTHREAD_LIBS)
+AC_SUBST(PTHREAD_CFLAGS)
+AC_SUBST(PTHREAD_CC)
+
+# Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND:
+if test x"$acx_pthread_ok" = xyes; then
+ ifelse([$1],,AC_DEFINE(HAVE_PTHREAD,1,[Define if you have POSIX threads libraries and header files.]),[$1])
+ :
+else
+ acx_pthread_ok=no
+ $2
+fi
+AC_LANG_RESTORE
+])dnl ACX_PTHREAD
+AC_DEFUN([AC_CHECK_DEFINE],[
+AS_VAR_PUSHDEF([ac_var],[ac_cv_defined_$1_$2])dnl
+AC_CACHE_CHECK([for $1 in $2], ac_var,
+AC_TRY_COMPILE([#include <$2>],[
+ #ifdef $1
+ int ok;
+ #else
+ choke me
+ #endif
+],AS_VAR_SET(ac_var, yes),AS_VAR_SET(ac_var, no)))
+AS_IF([test AS_VAR_GET(ac_var) != "no"], [$3], [$4])dnl
+AS_VAR_POPDEF([ac_var])dnl
+])
+
+AC_DEFUN([ACX_LIBWRAP], [
+LIBWRAP_LIBS=
+saved_LIBS="$LIBS"
+LIBS="$LIBS -lwrap"
+AC_MSG_CHECKING([for tcpwrap library and headers])
+AC_LINK_IFELSE(
+AC_LANG_PROGRAM(
+[#include <tcpd.h>
+#include <syslog.h>
+int allow_severity = LOG_INFO;
+int deny_severity = LOG_WARNING;],
+[struct request_info *req;
+return hosts_access (req);]),
+[AC_DEFINE(HAVE_LIBWRAP, [], [Have tcpwrap?])
+LIBWRAP_LIBS="-lwrap"
+AC_MSG_RESULT(yes)],
+[AC_MSG_RESULT(no)])
+LIBS="$saved_LIBS"
+])
+
+AC_DEFUN([ACX_LIRC], [
+LIRC_CFLAGS=
+LIRC_LIBS=
+AC_CHECK_HEADER(lirc/lirc_client.h,[AC_CHECK_LIB(lirc_client,lirc_init,[HAVE_LIRC=1
+LIRC_LIBS=-llirc_client],HAVE_LIRC=0)],HAVE_LIRC=0)
+])
diff --git a/autogen.sh b/autogen.sh
new file mode 100755
index 00000000..16c57386
--- /dev/null
+++ b/autogen.sh
@@ -0,0 +1,24 @@
+#!/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.
+
+# Only there to make jhbuild happy
+
+NOCONFIGURE=1 ./bootstrap.sh
+
+exec ./configure "$@"
diff --git a/bootstrap.sh b/bootstrap.sh
new file mode 100755
index 00000000..f23acbfe
--- /dev/null
+++ b/bootstrap.sh
@@ -0,0 +1,64 @@
+#!/bin/bash
+# $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=1.9
+
+run_versioned() {
+ local P
+ local V
+
+ V=$(echo "$2" | sed -e 's,\.,,g')
+
+ if [ -e "`which $1$V 2> /dev/null`" ] ; then
+ P="$1$V"
+ else
+ if [ -e "`which $1-$2 2> /dev/null`" ] ; then
+ P="$1-$2"
+ else
+ P="$1"
+ fi
+ fi
+
+ shift 2
+ "$P" "$@"
+}
+
+set -ex
+
+if [ "x$1" = "xam" ] ; then
+ run_versioned automake "$VERSION" -a -c --foreign
+ ./config.status
+else
+ rm -rf autom4te.cache
+ rm -f config.cache
+
+ touch config.rpath
+ test "x$LIBTOOLIZE" = "x" && LIBTOOLIZE=libtoolize
+
+ "$LIBTOOLIZE" -c --force --ltdl
+ run_versioned aclocal "$VERSION"
+ run_versioned autoconf 2.59 -Wall
+ run_versioned autoheader 2.59
+ run_versioned automake "$VERSION" --copy --foreign --add-missing
+
+ if test "x$NOCONFIGURE" = "x"; then
+ CFLAGS="-g -O0" ./configure --sysconfdir=/etc --localstatedir=/var --enable-force-preopen "$@"
+ make clean
+ fi
+fi
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 00000000..ba8f2c96
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,1157 @@
+# -*- Autoconf -*-
+# Process this file with autoconf to produce a configure script.
+
+# $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.
+
+AC_PREREQ(2.57)
+
+m4_define(PA_MAJOR, [0])
+m4_define(PA_MINOR, [9])
+m4_define(PA_MICRO, [8])
+
+AC_INIT([pulseaudio], PA_MAJOR.PA_MINOR.PA_MICRO,[mzchyfrnhqvb (at) 0pointer (dot) net])
+AC_CONFIG_SRCDIR([src/daemon/main.c])
+AC_CONFIG_HEADERS([config.h])
+AM_INIT_AUTOMAKE([foreign -Wall])
+
+AC_SUBST(PA_MAJORMINOR, "PA_MAJOR.PA_MINOR")
+AC_SUBST(PACKAGE_URL, [http://pulseaudio.org/])
+
+AC_SUBST(PA_API_VERSION, 11)
+AC_SUBST(PA_PROTOCOL_VERSION, 12)
+
+# The stable ABI for client applications, for the version info x:y:z
+# always will hold y=z
+AC_SUBST(LIBPULSE_VERSION_INFO, [4:0:4])
+
+# A simplified, synchronous, ABI-stable interface for client
+# applications, for the version info x:y:z always will hold y=z
+AC_SUBST(LIBPULSE_SIMPLE_VERSION_INFO, [0:1:0])
+
+# The ABI-stable network browsing interface for client applications,
+# for the version info x:y:z always will hold y=z
+AC_SUBST(LIBPULSE_BROWSE_VERSION_INFO, [1:1:1])
+
+# The ABI-stable GLib adapter for client applications, for the version
+# info x:y:z always will hold y=z
+AC_SUBST(LIBPULSE_MAINLOOP_GLIB_VERSION_INFO, [0:3:0])
+
+# An internally used, ABI-unstable library that contains the
+# PulseAudio core, SONAMEs are bumped on every release, version info
+# suffix will always be 0:0
+AC_SUBST(LIBPULSECORE_VERSION_INFO, [5:0:0])
+
+AC_CANONICAL_HOST
+
+if type -p stow > /dev/null && test -d /usr/local/stow ; then
+ AC_MSG_NOTICE([*** Found /usr/local/stow: default install prefix set to /usr/local/stow/${PACKAGE_NAME}-${PACKAGE_VERSION} ***])
+ ac_default_prefix="/usr/local/stow/${PACKAGE_NAME}-${PACKAGE_VERSION}"
+fi
+
+#### Platform hacks ####
+
+case $host in
+ *-*-solaris* )
+ AC_DEFINE(_XOPEN_SOURCE_EXTENDED, 1, Needed to get declarations for msg_control and msg_controllen on Solaris)
+ AC_DEFINE(_XOPEN_SOURCE, 2, Needed to get declarations for msg_control and msg_controllen on Solaris)
+ AC_DEFINE(__EXTENSIONS__, 1, Needed to get declarations for msg_control and msg_controllen on Solaris)
+ ;;
+esac
+
+#### Checks for programs. ####
+
+# mkdir -p
+
+AC_PROG_MKDIR_P
+
+# CC
+
+AC_PROG_CC
+AM_PROG_CC_C_O
+AC_PROG_GCC_TRADITIONAL
+AC_GNU_SOURCE
+
+# M4
+
+AC_PATH_PROG([M4], [m4 gm4], [no])
+if test "x$M4" = xno ; then
+ AC_MSG_ERROR([m4 missing])
+fi
+
+# GCC flags
+
+test_gcc_flag() {
+ AC_LANG_CONFTEST([int main(int argc, char*argv[]) {}])
+ $CC -c conftest.c $CFLAGS -o conftest.o > /dev/null 2> /dev/null
+ ret=$?
+ rm -f conftest.o
+ return $ret
+}
+
+# If using GCC specify some additional parameters
+if test "x$GCC" = "xyes" ; then
+
+ # We use gnu99 instead of c99 because many have interpreted the standard
+ # in a way that int64_t isn't defined on non-64 bit platforms.
+ DESIRED_FLAGS="-std=gnu99 -Wall -W -Wextra -pedantic -pipe -Wformat -Wold-style-definition -Wdeclaration-after-statement -Wfloat-equal -Wmissing-declarations -Wmissing-prototypes -Wstrict-prototypes -Wredundant-decls -Wmissing-noreturn -Wshadow -Wendif-labels -Wpointer-arith -Wcast-align -Wwrite-strings -Winline -Wno-unused-parameter -ffast-math"
+
+ for flag in $DESIRED_FLAGS ; do
+ AC_MSG_CHECKING([whether $CC accepts $flag])
+ if test_gcc_flag $flag ; then
+ CFLAGS="$CFLAGS $flag"
+ AC_MSG_RESULT([yes])
+ else
+ AC_MSG_RESULT([no])
+ fi
+ done
+fi
+
+AC_MSG_CHECKING([whether $CC knows __sync_bool_compare_and_swap()])
+AC_LANG_CONFTEST([int main() { int a = 4; __sync_bool_compare_and_swap(&a, 4, 5); }])
+$CC conftest.c $CFLAGS -o conftest > /dev/null 2> /dev/null
+ret=$?
+rm -f conftest.o conftest
+if test $ret -eq 0 ; then
+ AC_DEFINE([HAVE_ATOMIC_BUILTINS], 1, [Have __sync_bool_compare_and_swap() and friends.])
+ AC_MSG_RESULT([yes])
+else
+ AC_MSG_RESULT([no])
+fi
+
+AC_MSG_CHECKING([whether $CC knows __thread])
+AC_LANG_CONFTEST([static __thread int a = 6; int main() { a = 5; }])
+$CC conftest.c $CFLAGS -o conftest > /dev/null 2> /dev/null
+ret=$?
+rm -f conftest.o conftest
+if test $ret -eq 0 ; then
+ AC_DEFINE([HAVE_TLS_BUILTIN], 1, [Have __thread().])
+ AC_MSG_RESULT([yes])
+else
+ AC_MSG_RESULT([no])
+fi
+
+AC_MSG_CHECKING([whether $CC knows _Bool])
+AC_LANG_CONFTEST([int main() { _Bool b; }])
+$CC conftest.c $CFLAGS -o conftest > /dev/null 2> /dev/null
+ret=$?
+rm -f conftest.o conftest
+if test $ret -eq 0 ; then
+ AC_DEFINE([HAVE_STD_BOOL], 1, [Have _Bool.])
+ AC_MSG_RESULT([yes])
+else
+ AC_MSG_RESULT([no])
+fi
+
+#### libtool stuff ####
+
+AC_LTDL_ENABLE_INSTALL
+AC_LIBLTDL_INSTALLABLE
+AC_LIBTOOL_DLOPEN
+AC_LIBTOOL_WIN32_DLL
+AC_PROG_LIBTOOL
+AC_SUBST(LTDLINCL)
+AC_SUBST(LIBLTDL)
+AC_CONFIG_SUBDIRS(libltdl)
+
+old_LIBS=$LIBS
+LIBS="$LIBS $LIBLTDL"
+AC_CHECK_FUNCS([lt_dlmutex_register])
+LIBS=$old_LIBS
+AC_CHECK_TYPES([struct lt_user_dlloader, lt_dladvise], , , [#include <ltdl.h>])
+
+if test "x$enable_ltdl_install" = "xno" && test "x$ac_cv_lib_ltdl_lt_dlinit" = "xno" ; then
+ AC_MSG_ERROR([[
+
+ *** Cannot find the libltdl development files.
+ *** Maybe you need to install the libltdl-dev package.
+ ]])
+fi
+
+#### Determine build environment ####
+
+os_is_win32=0
+
+case "$host_os" in
+ mingw*)
+ AC_DEFINE([OS_IS_WIN32], 1, [Build target is Windows.])
+ os_is_win32=1
+ ;;
+ esac
+
+AM_CONDITIONAL(OS_IS_WIN32, test "x$os_is_win32" = "x1")
+
+###################################
+# Basic environment checks #
+###################################
+
+#### Checks for header files. ####
+
+# ISO
+AC_HEADER_STDC
+
+# POSIX
+AC_CHECK_HEADERS([arpa/inet.h glob.h grp.h netdb.h netinet/in.h \
+ netinet/in_systm.h netinet/tcp.h poll.h pwd.h sched.h \
+ sys/mman.h sys/resource.h sys/select.h sys/socket.h sys/wait.h \
+ syslog.h sys/dl.h dlfcn.h linux/sockios.h])
+AC_CHECK_HEADERS([netinet/ip.h], [], [],
+ [#include <sys/types.h>
+ #if HAVE_NETINET_IN_H
+ # include <netinet/in.h>
+ #endif
+ #if HAVE_NETINET_IN_SYSTM_H
+ # include <netinet/in_systm.h>
+ #endif
+ ])
+AC_CHECK_HEADERS([regex.h], [HAVE_REGEX=1], [HAVE_REGEX=0])
+AC_CHECK_HEADERS([sys/un.h], [HAVE_AF_UNIX=1], [HAVE_AF_UNIX=0])
+
+AM_CONDITIONAL(HAVE_REGEX, test "x$HAVE_REGEX" = "x1")
+AM_CONDITIONAL(HAVE_AF_UNIX, test "x$HAVE_AF_UNIX" = "x1")
+
+# Linux
+AC_CHECK_HEADERS([linux/input.h], [HAVE_EVDEV=1], [HAVE_EVDEV=0])
+
+AM_CONDITIONAL([HAVE_EVDEV], [test "x$HAVE_EVDEV" = "x1"])
+
+AC_CHECK_HEADERS([sys/prctl.h])
+
+# Solaris
+AC_CHECK_HEADERS([sys/filio.h])
+
+# Windows
+AC_CHECK_HEADERS([windows.h winsock2.h ws2tcpip.h])
+
+# Other
+AC_CHECK_HEADERS([sys/ioctl.h])
+AC_CHECK_HEADERS([byteswap.h])
+AC_CHECK_HEADERS([sys/syscall.h])
+
+#### Typdefs, structures, etc. ####
+
+AC_C_CONST
+AC_C_BIGENDIAN
+AC_TYPE_PID_T
+AC_TYPE_SIZE_T
+AC_CHECK_TYPES(ssize_t, , [AC_DEFINE([ssize_t], [signed long],
+ [Define ssize_t if it is not done by the standard libs.])])
+AC_TYPE_OFF_T
+AC_TYPE_SIGNAL
+AC_TYPE_UID_T
+
+AC_CHECK_DEFINE([SIGXCPU], [signal.h], [
+HAVE_SIGXCPU=1
+AC_DEFINE([HAVE_SIGXCPU], 1, [Have SIGXCPU?])
+], [HAVE_SIGXCPU=0])
+AM_CONDITIONAL(HAVE_SIGXCPU, test "x$HAVE_SIGXCPU" = "x1")
+
+# Solaris lacks this
+AC_CHECK_DEFINE([INADDR_NONE], [netinet/in.h], [],
+ [AC_CHECK_DEFINE([INADDR_NONE], [winsock2.h], [],
+ [AC_DEFINE([INADDR_NONE], [0xffffffff], [Define INADDR_NONE if not found in <netinet/in.h>])])])
+
+#### POSIX threads ####
+
+ACX_PTHREAD
+
+#### Check for libs ####
+
+# ISO
+AC_SEARCH_LIBS([pow], [m])
+
+# POSIX
+AC_SEARCH_LIBS([sched_setscheduler], [rt])
+AC_SEARCH_LIBS([dlopen], [dl])
+AC_SEARCH_LIBS([shm_open], [rt])
+AC_SEARCH_LIBS([inet_ntop], [nsl])
+AC_SEARCH_LIBS([timer_create], [rt])
+
+# BSD
+AC_SEARCH_LIBS([connect], [socket])
+
+# Non-standard
+
+# This magic is needed so we do not needlessly add static libs to the win32
+# build, disabling its ability to make dlls.
+AC_CHECK_FUNCS([getopt_long], [], [AC_CHECK_LIB([iberty], [getopt_long])])
+
+#### Check for functions ####
+
+# ISO
+AC_CHECK_FUNCS([lrintf strtof])
+
+# POSIX
+AC_FUNC_FORK
+AC_FUNC_GETGROUPS
+AC_FUNC_SELECT_ARGTYPES
+AC_CHECK_FUNCS([chmod chown clock_gettime getaddrinfo getgrgid_r \
+ getpwuid_r gettimeofday getuid inet_ntop inet_pton mlock nanosleep \
+ pipe posix_fadvise posix_madvise posix_memalign setpgid setsid shm_open \
+ sigaction sleep sysconf])
+AC_CHECK_FUNCS([mkfifo], [HAVE_MKFIFO=1], [HAVE_MKFIFO=0])
+
+AM_CONDITIONAL(HAVE_MKFIFO, test "x$HAVE_MKFIFO" = "x1")
+
+# X/OPEN
+AC_CHECK_FUNCS([readlink])
+
+# SUSv2
+AC_CHECK_FUNCS([ctime_r usleep])
+
+# SUSv3
+AC_CHECK_FUNCS([strerror_r])
+
+# BSD
+AC_CHECK_FUNCS([lstat])
+
+# Non-standard
+
+AC_CHECK_FUNCS([setresuid setresgid setreuid setregid seteuid setegid ppoll strsignal sig2str strtof_l])
+
+AC_MSG_CHECKING([for PTHREAD_PRIO_INHERIT])
+AC_LANG_CONFTEST([AC_LANG_SOURCE([[
+#include <pthread.h>
+int main() { int i = PTHREAD_PRIO_INHERIT; }]])])
+$PTHREAD_CC conftest.c $PTHREAD_CFLAGS $CFLAGS $PTHREAD_LIBS -o conftest > /dev/null 2> /dev/null
+ret=$?
+rm -f conftest.o conftest
+
+if test $ret -eq 0 ; then
+ AC_DEFINE([HAVE_PTHREAD_PRIO_INHERIT], 1, [Have PTHREAD_PRIO_INHERIT.])
+ AC_MSG_RESULT([yes])
+else
+ AC_MSG_RESULT([no])
+fi
+
+#### Large File-Support (LFS) ####
+
+AC_SYS_LARGEFILE
+
+# Check for open64 to know if the current system does have open64() and similar functions
+AC_CHECK_FUNCS([open64])
+
+#### [lib]iconv ####
+
+AM_ICONV
+
+###################################
+# External libraries #
+###################################
+
+#### X11 (optional) ####
+
+HAVE_X11=0
+
+# The macro tests the host, not the build target
+if test "x$os_is_win32" != "x1" ; then
+ AC_PATH_XTRA
+ test "x$no_x" != "xyes" && HAVE_X11=1
+fi
+
+AC_SUBST(HAVE_X11)
+AM_CONDITIONAL(HAVE_X11, test "x$HAVE_X11" = "x1")
+if test "x$HAVE_X11" = "x1" ; then
+ AC_DEFINE([HAVE_X11], 1, [Have X11])
+fi
+
+#### Capabilities (optional) ####
+
+CAP_LIBS=''
+
+AC_ARG_WITH(
+ [caps],
+ AC_HELP_STRING([--without-caps],[Omit support for POSIX capabilities.]))
+
+if test "x${with_caps}" != "xno"; then
+ AC_SEARCH_LIBS([cap_init], [cap], [], [
+ if test "x${with_caps}" = "xyes" ; then
+ AC_MSG_ERROR([*** POSIX caps libraries not found])
+ fi])
+ AC_CHECK_HEADERS([sys/capability.h], [], [
+ if test "x${with_caps}" = "xyes" ; then
+ AC_MSG_ERROR([*** POSIX caps headers not found])
+ fi])
+fi
+
+#### pkg-config ####
+
+# Check for pkg-config manually first, as if its not installed the
+# PKG_PROG_PKG_CONFIG macro won't be defined.
+AC_CHECK_PROG(have_pkg_config, pkg-config, yes, no)
+
+if test x"$have_pkg_config" = "xno"; then
+ AC_MSG_ERROR(pkg-config is required to install this program)
+fi
+
+PKG_PROG_PKG_CONFIG
+
+#### Sound file ####
+
+PKG_CHECK_MODULES(LIBSNDFILE, [ sndfile >= 1.0.10 ])
+AC_SUBST(LIBSNDFILE_CFLAGS)
+AC_SUBST(LIBSNDFILE_LIBS)
+
+#### atomic-ops ###
+
+AC_CHECK_HEADERS([atomic_ops.h], [], [
+AC_MSG_ERROR([*** libatomic-ops headers not found])
+])
+
+# Win32 does not need the lib and breaks horribly if we try to include it
+if test "x$os_is_win32" != "x1" ; then
+ LIBS="$LIBS -latomic_ops"
+fi
+
+#### Libsamplerate support (optional) ####
+
+AC_ARG_ENABLE([samplerate],
+ AC_HELP_STRING([--disable-samplerate], [Disable optional libsamplerate support]),
+ [
+ case "${enableval}" in
+ yes) samplerate=yes ;;
+ no) samplerate=no ;;
+ *) AC_MSG_ERROR(bad value ${enableval} for --disable-samplerate) ;;
+ esac
+ ],
+ [samplerate=auto])
+
+if test "x${samplerate}" != xno ; then
+ PKG_CHECK_MODULES(LIBSAMPLERATE, [ samplerate >= 0.1.0 ],
+ HAVE_LIBSAMPLERATE=1,
+ [
+ HAVE_LIBSAMPLERATE=0
+ if test "x$samplerate" = xyes ; then
+ AC_MSG_ERROR([*** Libsamplerate not found])
+ fi
+ ])
+else
+ HAVE_LIBSAMPLERATE=0
+fi
+
+if test "x${HAVE_LIBSAMPLERATE}" = x1 ; then
+ AC_DEFINE([HAVE_LIBSAMPLERATE], 1, [Have libsamplerate?])
+fi
+
+AC_SUBST(LIBSAMPLERATE_CFLAGS)
+AC_SUBST(LIBSAMPLERATE_LIBS)
+AC_SUBST(HAVE_LIBSAMPLERATE)
+AM_CONDITIONAL([HAVE_LIBSAMPLERATE], [test "x$HAVE_LIBSAMPLERATE" = x1])
+
+#### OSS support (optional) ####
+
+AC_ARG_ENABLE([oss],
+ AC_HELP_STRING([--disable-oss], [Disable optional OSS support]),
+ [
+ case "${enableval}" in
+ yes) oss=yes ;;
+ no) oss=no ;;
+ *) AC_MSG_ERROR(bad value ${enableval} for --disable-oss) ;;
+ esac
+ ],
+ [oss=auto])
+
+if test "x${oss}" != xno ; then
+ AC_CHECK_HEADERS([sys/soundcard.h],
+ [
+ HAVE_OSS=1
+ AC_DEFINE([HAVE_OSS], 1, [Have OSS?])
+ ],
+ [
+ HAVE_OSS=0
+ if test "x$oss" = xyes ; then
+ AC_MSG_ERROR([*** OSS support not found])
+ fi
+ ])
+else
+ HAVE_OSS=0
+fi
+
+AC_SUBST(HAVE_OSS)
+AM_CONDITIONAL([HAVE_OSS], [test "x$HAVE_OSS" = x1])
+
+
+#### ALSA support (optional) ####
+
+AC_ARG_ENABLE([alsa],
+ AC_HELP_STRING([--disable-alsa], [Disable optional ALSA support]),
+ [
+ case "${enableval}" in
+ yes) alsa=yes ;;
+ no) alsa=no ;;
+ *) AC_MSG_ERROR(bad value ${enableval} for --disable-alsa) ;;
+ esac
+ ],
+ [alsa=auto])
+
+if test "x${alsa}" != xno ; then
+ PKG_CHECK_MODULES(ASOUNDLIB, [ alsa >= 1.0.0 ],
+ [
+ HAVE_ALSA=1
+ AC_DEFINE([HAVE_ALSA], 1, [Have ALSA?])
+ ],
+ [
+ HAVE_ALSA=0
+ if test "x$alsa" = xyes ; then
+ AC_MSG_ERROR([*** ALSA support not found])
+ fi
+ ])
+else
+ HAVE_ALSA=0
+fi
+
+AC_SUBST(ASOUNDLIB_CFLAGS)
+AC_SUBST(ASOUNDLIB_LIBS)
+AC_SUBST(HAVE_ALSA)
+AM_CONDITIONAL([HAVE_ALSA], [test "x$HAVE_ALSA" = x1])
+
+#### Solaris audio support (optional) ####
+
+AC_ARG_ENABLE([solaris],
+ AC_HELP_STRING([--disable-solaris], [Disable optional Solaris audio support]),
+ [
+ case "${enableval}" in
+ yes) solaris=yes ;;
+ no) solaris=no ;;
+ *) AC_MSG_ERROR(bad value ${enableval} for --disable-solaris) ;;
+ esac
+ ],
+ [solaris=auto])
+
+if test "x${solaris}" != xno ; then
+ AC_CHECK_HEADERS([sys/audio.h],
+ [
+ HAVE_SOLARIS=1
+ AC_DEFINE([HAVE_SOLARIS], 1, [Have Solaris audio?])
+ ],
+ [
+ HAVE_SOLARIS=0
+ if test "x$solaris" = xyes ; then
+ AC_MSG_ERROR([*** Solaris audio support not found])
+ fi
+ ])
+else
+ HAVE_SOLARIS=0
+fi
+
+AC_SUBST(HAVE_SOLARIS)
+AM_CONDITIONAL([HAVE_SOLARIS], [test "x$HAVE_SOLARIS" = x1])
+
+#### GLib 2 support (optional) ####
+
+AC_ARG_ENABLE([glib2],
+ AC_HELP_STRING([--disable-glib2], [Disable optional GLib 2 support]),
+ [
+ case "${enableval}" in
+ yes) glib2=yes ;;
+ no) glib2=no ;;
+ *) AC_MSG_ERROR(bad value ${enableval} for --disable-glib2) ;;
+ esac
+ ],
+ [glib2=auto])
+
+if test "x${glib2}" != xno ; then
+ PKG_CHECK_MODULES(GLIB20, [ glib-2.0 >= 2.4.0 ],
+ HAVE_GLIB20=1,
+ [
+ HAVE_GLIB20=0
+ if test "x$glib2" = xyes ; then
+ AC_MSG_ERROR([*** GLib 2 support not found])
+ fi
+ ])
+else
+ HAVE_GLIB20=0
+fi
+
+AC_SUBST(GLIB20_CFLAGS)
+AC_SUBST(GLIB20_LIBS)
+AC_SUBST(HAVE_GLIB20)
+AM_CONDITIONAL([HAVE_GLIB20], [test "x$HAVE_GLIB20" = x1])
+
+#### GConf support (optional) ####
+
+AC_ARG_ENABLE([gconf],
+ AC_HELP_STRING([--disable-gconf], [Disable optional GConf support]),
+ [
+ case "${enableval}" in
+ yes) gconf=yes ;;
+ no) gconf=no ;;
+ *) AC_MSG_ERROR(bad value ${enableval} for --disable-gconf) ;;
+ esac
+ ],
+ [glib=auto])
+
+if test "x${gconf}" != xno ; then
+ PKG_CHECK_MODULES(GCONF, [ gconf-2.0 >= 2.4.0 ],
+ HAVE_GCONF=1,
+ [
+ HAVE_GCONF=0
+ if test "x$gconf" = xyes ; then
+ AC_MSG_ERROR([*** GConf support not found])
+ fi
+ ])
+else
+ HAVE_GCONF=0
+fi
+
+AC_SUBST(GCONF_CFLAGS)
+AC_SUBST(GCONF_LIBS)
+AC_SUBST(HAVE_GCONF)
+AM_CONDITIONAL([HAVE_GCONF], [test "x$HAVE_GCONF" = x1])
+
+#### Avahi support (optional) ####
+
+AC_ARG_ENABLE([avahi],
+ AC_HELP_STRING([--disable-avahi], [Disable optional Avahi support]),
+ [
+ case "${enableval}" in
+ yes) avahi=yes ;;
+ no) avahi=no ;;
+ *) AC_MSG_ERROR(bad value ${enableval} for --disable-avahi) ;;
+ esac
+ ],
+ [avahi=auto])
+
+if test "x${avahi}" != xno ; then
+ PKG_CHECK_MODULES(AVAHI, [ avahi-client >= 0.6.0 ],
+ HAVE_AVAHI=1,
+ [
+ HAVE_AVAHI=0
+ if test "x$avahi" = xyes ; then
+ AC_MSG_ERROR([*** Avahi support not found])
+ fi
+ ])
+else
+ HAVE_AVAHI=0
+fi
+
+AC_SUBST(AVAHI_CFLAGS)
+AC_SUBST(AVAHI_LIBS)
+AC_SUBST(HAVE_AVAHI)
+AM_CONDITIONAL([HAVE_AVAHI], [test "x$HAVE_AVAHI" = x1])
+
+### LIBOIL ####
+
+PKG_CHECK_MODULES(LIBOIL, [ liboil-0.3 >= 0.3.0 ])
+AC_SUBST(LIBOIL_CFLAGS)
+AC_SUBST(LIBOIL_LIBS)
+
+### JACK (optional) ####
+
+AC_ARG_ENABLE([jack],
+ AC_HELP_STRING([--disable-jack], [Disable optional JACK support]),
+ [
+ case "${enableval}" in
+ yes) jack=yes ;;
+ no) jack=no ;;
+ *) AC_MSG_ERROR(bad value ${enableval} for --disable-jack) ;;
+ esac
+ ],
+ [jack=auto])
+
+if test "x${jack}" != xno ; then
+ PKG_CHECK_MODULES(JACK, [ jack >= 0.100 ],
+ HAVE_JACK=1,
+ [
+ HAVE_JACK=0
+ if test "x$jack" = xyes ; then
+ AC_MSG_ERROR([*** JACK support not found])
+ fi
+ ])
+else
+ HAVE_JACK=0
+fi
+
+AC_SUBST(JACK_CFLAGS)
+AC_SUBST(JACK_LIBS)
+AC_SUBST(HAVE_JACK)
+AM_CONDITIONAL([HAVE_JACK], [test "x$HAVE_JACK" = x1])
+
+#### Async DNS support (optional) ####
+
+AC_ARG_ENABLE([asyncns],
+ AC_HELP_STRING([--disable-asyncns], [Disable optional Async DNS support]),
+ [
+ case "${enableval}" in
+ yes) asyncns=yes ;;
+ no) asyncns=no ;;
+ *) AC_MSG_ERROR(bad value ${enableval} for --disable-asyncns) ;;
+ esac
+ ],
+ [asyncns=auto])
+
+if test "x${asyncns}" != xno ; then
+ PKG_CHECK_MODULES(LIBASYNCNS, [ libasyncns >= 0.1 ],
+ HAVE_LIBASYNCNS=1,
+ [
+ HAVE_LIBASYNCNS=0
+ if test "x$asyncns" = xyes ; then
+ AC_MSG_ERROR([*** Async DNS support not found])
+ fi
+ ])
+else
+ HAVE_LIBASYNCNS=0
+fi
+
+AC_SUBST(LIBASYNCNS_CFLAGS)
+AC_SUBST(LIBASYNCNS_LIBS)
+AC_SUBST(HAVE_LIBASYNCNS)
+AM_CONDITIONAL([HAVE_LIBASYNCNS], [test "x$HAVE_LIBASYNCNS" = x1])
+
+if test "x$HAVE_LIBASYNCNS" != "x0" ; then
+ AC_DEFINE([HAVE_LIBASYNCNS], 1, [Have libasyncns?])
+fi
+
+#### TCP wrappers (optional) ####
+
+AC_ARG_ENABLE([tcpwrap],
+ AC_HELP_STRING([--disable-tcpwrap], [Disable optional TCP wrappers support]),
+ [
+ case "${enableval}" in
+ yes) tcpwrap=yes ;;
+ no) tcpwrap=no ;;
+ *) AC_MSG_ERROR(bad value ${enableval} for --disable-tcpwrap) ;;
+ esac
+ ],
+ [tcpwrap=auto])
+
+if test "x${tcpwrap}" != xno ; then
+ ACX_LIBWRAP
+ if test "x${LIBWRAP_LIBS}" = x && test "x$tcpwrap" = xyes ; then
+ AC_MSG_ERROR([*** TCP wrappers support not found])
+ fi
+else
+ LIBWRAP_LIBS=
+fi
+
+AC_SUBST(LIBWRAP_LIBS)
+
+#### LIRC support (optional) ####
+
+AC_ARG_ENABLE([lirc],
+ AC_HELP_STRING([--disable-lirc], [Disable optional LIRC support]),
+ [
+ case "${enableval}" in
+ yes) lirc=yes ;;
+ no) lirc=no ;;
+ *) AC_MSG_ERROR(bad value ${enableval} for --disable-lirc) ;;
+ esac
+ ],
+ [lirc=auto])
+
+if test "x${lirc}" != xno ; then
+ ACX_LIRC
+ if test "x${HAVE_LIRC}" = x0 && test "x$lirc" = xyes ; then
+ AC_MSG_ERROR([*** LIRC support not found])
+ fi
+else
+ HAVE_LIRC=0
+fi
+
+AC_SUBST(LIRC_CFLAGS)
+AC_SUBST(LIRC_LIBS)
+AM_CONDITIONAL([HAVE_LIRC], [test "x$HAVE_LIRC" = x1])
+
+#### HAL support (optional) ####
+
+AC_ARG_ENABLE([hal],
+ AC_HELP_STRING([--disable-hal], [Disable optional HAL support]),
+ [
+ case "${enableval}" in
+ yes) hal=yes ;;
+ no) hal=no ;;
+ *) AC_MSG_ERROR(bad value ${enableval} for --disable-hal) ;;
+ esac
+ ],
+ [hal=auto])
+if test "x${hal}" != xno -a \( "x$HAVE_OSS" = "x1" -o "x$HAVE_ALSA" = "x1" \) ; then
+ PKG_CHECK_MODULES(HAL, [ hal >= 0.5.7 ],
+ HAVE_HAL=1,
+ [
+ HAVE_HAL=0
+ if test "x$hal" = xyes ; then
+ AC_MSG_ERROR([*** HAL support not found])
+ fi
+ ])
+else
+ HAVE_HAL=0
+fi
+
+AC_SUBST(HAL_CFLAGS)
+AC_SUBST(HAL_LIBS)
+AC_SUBST(HAVE_HAL)
+AM_CONDITIONAL([HAVE_HAL], [test "x$HAVE_HAL" = x1])
+
+#### BlueZ support (optional) ####
+
+AC_ARG_ENABLE([bluez],
+ AC_HELP_STRING([--disable-bluez], [Disable optional BlueZ support]),
+ [
+ case "${enableval}" in
+ yes) bluez=yes ;;
+ no) bluez=no ;;
+ *) AC_MSG_ERROR(bad value ${enableval} for --disable-bluez) ;;
+ esac
+ ],
+ [bluez=auto])
+if test "x${bluez}" != xno ; then
+ PKG_CHECK_MODULES(BLUEZ, [ bluez >= 3.0 ],
+ HAVE_BLUEZ=1,
+ [
+ HAVE_BLUEZ=0
+ if test "x$bluez" = xyes ; then
+ AC_MSG_ERROR([*** BLUEZ support not found])
+ fi
+ ])
+else
+ HAVE_BLUEZ=0
+fi
+
+AC_SUBST(BLUEZ_CFLAGS)
+AC_SUBST(BLUEZ_LIBS)
+AC_SUBST(HAVE_BLUEZ)
+AM_CONDITIONAL([HAVE_BLUEZ], [test "x$HAVE_BLUEZ" = x1])
+
+#### D-Bus support (optional) ####
+
+AC_ARG_ENABLE([dbus],
+ AC_HELP_STRING([--disable-dbus], [Disable optional D-Bus support]),
+ [
+ case "${enableval}" in
+ yes) dbus=yes ;;
+ no) dbus=no ;;
+ *) AC_MSG_ERROR(bad value ${enableval} for --disable-dbus) ;;
+ esac
+ ],
+ [dbus=auto])
+
+if test "x$HAVE_HAL" = x1 ; then
+ dbus=yes
+fi
+
+if test "x${dbus}" != xno || test "x${bluez}" != xno || "x${hal}" != xno ; then
+
+ PKG_CHECK_MODULES(DBUS, [ dbus-1 >= 1.0.0 ],
+ [
+ HAVE_DBUS=1
+ saved_LIBS="$LIBS"
+ LIBS="$LIBS $DBUS_LIBS"
+ AC_CHECK_FUNCS(dbus_watch_get_unix_fd)
+ LIBS="$saved_LIBS"
+ AC_DEFINE([HAVE_DBUS], 1, [Have D-Bus.])
+ ],
+ [
+ HAVE_DBUS=0
+ if test "x$dbus" = xyes ; then
+ AC_MSG_ERROR([*** D-Bus support not found])
+ fi
+ ])
+else
+ HAVE_DBUS=0
+fi
+
+AC_SUBST(DBUS_CFLAGS)
+AC_SUBST(DBUS_LIBS)
+AC_SUBST(HAVE_DBUS)
+AM_CONDITIONAL([HAVE_DBUS], [test "x$HAVE_DBUS" = x1])
+
+#### PolicyKit support (optional) ####
+
+AC_ARG_ENABLE([polkit],
+ AC_HELP_STRING([--disable-polkit], [Disable optional PolicyKit support]),
+ [
+ case "${enableval}" in
+ yes) polkit=yes ;;
+ no) polkit=no ;;
+ *) AC_MSG_ERROR(bad value ${enableval} for --disable-polkit) ;;
+ esac
+ ],
+ [polkit=auto])
+
+if test "x${polkit}" != xno ; then
+
+ PKG_CHECK_MODULES(POLKIT, [ polkit-dbus ],
+ [
+ HAVE_POLKIT=1
+ saved_LIBS="$LIBS"
+ LIBS="$LIBS $POLKIT_LIBS"
+ AC_CHECK_FUNCS(polkit_context_is_caller_authorized)
+ LIBS="$saved_LIBS"
+ AC_DEFINE([HAVE_POLKIT], 1, [Have PolicyKit])
+ policydir=`pkg-config polkit-dbus --variable prefix`/share/PolicyKit/policy/
+ AC_SUBST(policydir)
+ ],
+ [
+ HAVE_POLKIT=0
+ if test "x$polkit" = xyes ; then
+ AC_MSG_ERROR([*** PolicyKit support not found])
+ fi
+ ])
+else
+ HAVE_POLKIT=0
+fi
+
+AC_SUBST(POLKIT_CFLAGS)
+AC_SUBST(POLKIT_LIBS)
+AC_SUBST(HAVE_POLKIT)
+AM_CONDITIONAL([HAVE_POLKIT], [test "x$HAVE_POLKIT" = x1])
+
+### Build and Install man pages ###
+AC_ARG_ENABLE(manpages,
+ AS_HELP_STRING([--disable-manpages],[Disable building and installation of man pages]),
+[case "${enableval}" in
+ yes) manpages=yes ;;
+ no) manpages=no ;;
+ *) AC_MSG_ERROR([bad value ${enableval} for --disable-manpages]) ;;
+esac],[manpages=yes])
+
+if test x$manpages = xyes ; then
+ #
+ # XMLTOMAN manpage generation
+ #
+ AC_ARG_ENABLE(xmltoman,
+ AS_HELP_STRING([--disable-xmltoman],[Enable rebuilding of man pages with xmltoman]),
+ [case "${enableval}" in
+ yes) xmltoman=yes ;;
+ no) xmltoman=no ;;
+ *) AC_MSG_ERROR([bad value ${enableval} for --disable-xmltoman]) ;;
+ esac],[xmltoman=yes])
+
+ if test x$xmltoman = xyes ; then
+ AC_CHECK_PROG(have_xmltoman, xmltoman, yes, no)
+ fi
+
+ if test x$have_xmltoman = xno -o x$xmltoman = xno; then
+ if ! test -e man/pulseaudio.1 ; then
+ AC_MSG_ERROR([*** xmltoman was not found or was disabled, it is required to build the manpages as they have not been pre-built, install xmltoman, pass --disable-manpages or dont pass --disable-xmltoman])
+ exit 1
+ fi
+ AC_MSG_WARN([*** Not rebuilding man pages as xmltoman is not found ***])
+ xmltoman=no
+ fi
+fi
+AM_CONDITIONAL([USE_XMLTOMAN], [test "x$xmltoman" = xyes])
+AM_CONDITIONAL([BUILD_MANPAGES], [test "x$manpages" = xyes])
+
+#### PulseAudio system group & user #####
+
+AC_ARG_WITH(system_user, AS_HELP_STRING([--with-system-user=<user>],[User for running the PulseAudio daemon as a system-wide instance (pulse)]))
+if test -z "$with_system_user" ; then
+ PA_SYSTEM_USER=pulse
+else
+ PA_SYSTEM_USER=$with_system_user
+fi
+AC_SUBST(PA_SYSTEM_USER)
+AC_DEFINE_UNQUOTED(PA_SYSTEM_USER,"$PA_SYSTEM_USER", [User for running the PulseAudio system daemon])
+
+AC_ARG_WITH(system_group,AS_HELP_STRING([--with-system-group=<group>],[Group for running the PulseAudio daemon as a system-wide instance (pulse)]))
+if test -z "$with_system_group" ; then
+ PA_SYSTEM_GROUP=pulse
+else
+ PA_SYSTEM_GROUP=$with_system_group
+fi
+AC_SUBST(PA_SYSTEM_GROUP)
+AC_DEFINE_UNQUOTED(PA_SYSTEM_GROUP,"$PA_SYSTEM_GROUP", [Group for the PulseAudio system daemon])
+
+AC_ARG_WITH(realtime_group,AS_HELP_STRING([--with-realtime-group=<group>],[Group for users that are allowed to start the PulseAudio daemon with realtime scheduling (realtime)]))
+if test -z "$with_realtime_group" ; then
+ PA_REALTIME_GROUP=pulse-rt
+else
+ PA_REALTIME_GROUP=$with_realtime_group
+fi
+AC_SUBST(PA_REALTIME_GROUP)
+AC_DEFINE_UNQUOTED(PA_REALTIME_GROUP,"$PA_REALTIME_GROUP", [Realtime group])
+
+AC_ARG_WITH(access_group,AS_HELP_STRING([--with-access-group=<group>],[Group which is allowed access to a system-wide PulseAudio daemon (pulse-access)]))
+if test -z "$with_access_group" ; then
+ PA_ACCESS_GROUP=pulse-access
+else
+ PA_ACCESS_GROUP=$with_access_group
+fi
+AC_SUBST(PA_ACCESS_GROUP)
+AC_DEFINE_UNQUOTED(PA_ACCESS_GROUP,"$PA_ACCESS_GROUP", [Access group])
+
+AC_ARG_WITH(peruser_esound, AS_HELP_STRING([--with-peruser-esound-socket], [Use per-user esound socket directory, like /tmp/.esd-UID/socket.]))
+
+if test "x$with_peruser_esound" = "xyes"; then
+ AC_DEFINE([USE_PERUSER_ESOUND_SOCKET], [1], [Define this if you want per-user esound socket directories])
+fi
+
+#### PulseAudio system runtime dir ####
+PA_SYSTEM_RUNTIME_PATH="${localstatedir}/run/pulse"
+AC_SUBST(PA_SYSTEM_RUNTIME_PATH)
+
+###################################
+# Output #
+###################################
+
+AC_ARG_ENABLE(
+ [static-bins],
+ AC_HELP_STRING([--enable-static-bins],[Statically link executables.]),
+ [STATIC_BINS=1], [STATIC_BINS=0])
+AM_CONDITIONAL([STATIC_BINS], [test "x$STATIC_BINS" = "x1"])
+
+AC_ARG_WITH(
+ [preopen-mods],
+ AC_HELP_STRING([--with-preopen-mods],[Modules to preopen in daemon (default: all).]),
+ [PREOPEN_MODS=$withval], [PREOPEN_MODS="all"])
+AM_CONDITIONAL([PREOPEN_MODS], [test "x$PREOPEN_MODS" != "xall"])
+if test "x$PREOPEN_MODS" != "xall" ; then
+ tmpLIBS=""
+ for mod in $PREOPEN_MODS; do
+ tmpLIBS="$tmpLIBS module-$mod.la"
+ done
+ PREOPEN_MODS="$tmpLIBS"
+ AC_SUBST(PREOPEN_MODS)
+fi
+
+AC_ARG_WITH(
+ [module-dir],
+ AC_HELP_STRING([--with-module-dir],[Directory where to install the modules to (defaults to ${libdir}/pulse-${PA_MAJORMINOR}/modules/]),
+ [modlibexecdir=$withval], [modlibexecdir="${libdir}/pulse-${PA_MAJORMINOR}/modules/"])
+
+AC_SUBST(modlibexecdir)
+
+AC_ARG_ENABLE(
+ [force-preopen],
+ AC_HELP_STRING([--enable-force-preopen],[Preopen modules, even when dlopen() is supported.]),
+ [FORCE_PREOPEN=1], [FORCE_PREOPEN=0])
+AM_CONDITIONAL([FORCE_PREOPEN], [test "x$FORCE_PREOPEN" = "x1"])
+
+AC_CONFIG_FILES([
+Makefile
+src/Makefile
+man/Makefile
+libpulse.pc
+libpulse-simple.pc
+libpulse-browse.pc
+libpulse-mainloop-glib.pc
+doxygen/Makefile
+doxygen/doxygen.conf
+src/pulse/version.h
+])
+AC_OUTPUT
+
+# ==========================================================================
+ENABLE_X11=no
+if test "x$HAVE_X11" = "x1" ; then
+ ENABLE_X11=yes
+fi
+
+ENABLE_OSS=no
+if test "x$HAVE_OSS" = "x1" ; then
+ ENABLE_OSS=yes
+fi
+
+ENABLE_ALSA=no
+if test "x$HAVE_ALSA" = "x1" ; then
+ ENABLE_ALSA=yes
+fi
+
+ENABLE_SOLARIS=no
+if test "x$HAVE_SOLARIS" = "x1" ; then
+ ENABLE_SOLARIS=yes
+fi
+
+ENABLE_GLIB20=no
+if test "x$HAVE_GLIB20" = "x1" ; then
+ ENABLE_GLIB20=yes
+fi
+
+ENABLE_GCONF=no
+if test "x$HAVE_GCONF" = "x1" ; then
+ ENABLE_GCONF=yes
+fi
+
+ENABLE_AVAHI=no
+if test "x$HAVE_AVAHI" = "x1" ; then
+ ENABLE_AVAHI=yes
+fi
+
+ENABLE_JACK=no
+if test "x$HAVE_JACK" = "x1" ; then
+ ENABLE_JACK=yes
+fi
+
+ENABLE_LIBASYNCNS=no
+if test "x$HAVE_LIBASYNCNS" = "x1" ; then
+ ENABLE_LIBASYNCNS=yes
+fi
+
+ENABLE_LIRC=no
+if test "x$HAVE_LIRC" = "x1" ; then
+ ENABLE_LIRC=yes
+fi
+
+ENABLE_HAL=no
+if test "x$HAVE_HAL" = "x1" ; then
+ ENABLE_HAL=yes
+fi
+
+ENABLE_TCPWRAP=no
+if test "x${LIBWRAP_LIBS}" != x ; then
+ ENABLE_TCPWRAP=yes
+fi
+
+ENABLE_LIBSAMPLERATE=no
+if test "x${HAVE_LIBSAMPLERATE}" = "x1" ; then
+ ENABLE_LIBSAMPLERATE=yes
+fi
+
+ENABLE_BLUEZ=no
+if test "x${HAVE_BLUEZ}" = "x1" ; then
+ ENABLE_BLUEZ=yes
+fi
+
+ENABLE_POLKIT=no
+if test "x${HAVE_POLKIT}" = "x1" ; then
+ ENABLE_POLKIT=yes
+fi
+
+echo "
+ ---{ $PACKAGE_NAME $VERSION }---
+
+ prefix: ${prefix}
+ sysconfdir: ${sysconfdir}
+ localstatedir: ${localstatedir}
+ System Runtime Path: ${PA_SYSTEM_RUNTIME_PATH}
+ Compiler: ${CC}
+ CFLAGS: ${CFLAGS}
+ Have X11: ${ENABLE_X11}
+ Enable OSS: ${ENABLE_OSS}
+ Enable Alsa: ${ENABLE_ALSA}
+ Enable Solaris: ${ENABLE_SOLARIS}
+ Enable GLib 2.0: ${ENABLE_GLIB20}
+ Enable GConf: ${ENABLE_GCONF}
+ Enable Avahi: ${ENABLE_AVAHI}
+ Enable Jack: ${ENABLE_JACK}
+ Enable Async DNS: ${ENABLE_LIBASYNCNS}
+ Enable LIRC: ${ENABLE_LIRC}
+ Enable HAL: ${ENABLE_HAL}
+ Enable BlueZ: ${ENABLE_BLUEZ}
+ Enable TCP Wrappers: ${ENABLE_TCPWRAP}
+ Enable libsamplerate: ${ENABLE_LIBSAMPLERATE}
+ Enable PolicyKit: ${ENABLE_POLKIT}
+ System User: ${PA_SYSTEM_USER}
+ System Group: ${PA_SYSTEM_GROUP}
+ Realtime Group: ${PA_REALTIME_GROUP}
+ Access Group: ${PA_ACCESS_GROUP}
+"
diff --git a/doxygen/Makefile.am b/doxygen/Makefile.am
new file mode 100644
index 00000000..c4f66d83
--- /dev/null
+++ b/doxygen/Makefile.am
@@ -0,0 +1,26 @@
+# $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.
+
+doxygen: doxygen.conf
+ doxygen $<
+
+clean-local:
+ -rm -rf html
+
+.PHONY: all doxygen
diff --git a/doxygen/doxygen.conf.in b/doxygen/doxygen.conf.in
new file mode 100644
index 00000000..81923a9f
--- /dev/null
+++ b/doxygen/doxygen.conf.in
@@ -0,0 +1,1156 @@
+# Doxyfile 1.3.8
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project
+#
+# All text after a hash (#) is considered a comment and will be ignored
+# The format is:
+# TAG = value [value, ...]
+# For lists items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ")
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded
+# by quotes) that should identify the project.
+
+PROJECT_NAME = PulseAudio
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number.
+# This could be handy for archiving the generated documentation or
+# if some version control system is used.
+
+PROJECT_NUMBER = @PACKAGE_VERSION@
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
+# base path where the generated documentation will be put.
+# If a relative path is entered, it will be relative to the location
+# where doxygen was started. If left blank the current directory will be used.
+
+OUTPUT_DIRECTORY =
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create
+# 4096 sub-directories (in 2 levels) under the output directory of each output
+# format and will distribute the generated files over these directories.
+# Enabling this option can be useful when feeding doxygen a huge amount of source
+# files, where putting all generated files in the same directory would otherwise
+# cause performance problems for the file system.
+
+CREATE_SUBDIRS = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# The default language is English, other supported languages are:
+# Brazilian, Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish,
+# Dutch, Finnish, French, German, Greek, Hungarian, Italian, Japanese,
+# Japanese-en (Japanese with English messages), Korean, Korean-en, Norwegian,
+# Polish, Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish,
+# Swedish, and Ukrainian.
+
+OUTPUT_LANGUAGE = English
+
+# This tag can be used to specify the encoding used in the generated output.
+# The encoding is not always determined by the language that is chosen,
+# but also whether or not the output is meant for Windows or non-Windows users.
+# In case there is a difference, setting the USE_WINDOWS_ENCODING tag to YES
+# forces the Windows encoding (this is the default for the Windows binary),
+# whereas setting the tag to NO uses a Unix-style encoding (the default for
+# all platforms other than Windows).
+
+USE_WINDOWS_ENCODING = NO
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
+# include brief member descriptions after the members that are listed in
+# the file and class documentation (similar to JavaDoc).
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
+# the brief description of a member or function before the detailed description.
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator
+# that is used to form the text in various listings. Each string
+# in this list, if found as the leading text of the brief description, will be
+# stripped from the text and the result after processing the whole list, is used
+# as the annotated text. Otherwise, the brief description is used as-is. If left
+# blank, the following values are used ("$name" is automatically replaced with the
+# name of the entity): "The $name class" "The $name widget" "The $name file"
+# "is" "provides" "specifies" "contains" "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF =
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# Doxygen will generate a detailed section even if there is only a brief
+# description.
+
+ALWAYS_DETAILED_SEC = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all inherited
+# members of a class in the documentation of that class as if those members were
+# ordinary class members. Constructors, destructors and assignment operators of
+# the base classes will not be shown.
+
+INLINE_INHERITED_MEMB = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
+# path before files name in the file list and in the header files. If set
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
+# can be used to strip a user-defined part of the path. Stripping is
+# only done if one of the specified strings matches the left-hand part of
+# the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the
+# path to strip.
+
+STRIP_FROM_PATH =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of
+# the path mentioned in the documentation of a class, which tells
+# the reader which header file to include in order to use a class.
+# If left blank only the name of the header file containing the class
+# definition is used. Otherwise one should specify the include paths that
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
+# (but less readable) file names. This can be useful is your file systems
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen
+# will interpret the first line (until the first dot) of a JavaDoc-style
+# comment as the brief description. If set to NO, the JavaDoc
+# comments will behave just like the Qt-style comments (thus requiring an
+# explicit @brief command for a brief description.
+
+JAVADOC_AUTOBRIEF = YES
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen
+# treat a multi-line C++ special comment block (i.e. a block of //! or ///
+# comments) as a brief description. This used to be the default behaviour.
+# The new default is to treat a multi-line C++ comment block as a detailed
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the DETAILS_AT_TOP tag is set to YES then Doxygen
+# will output the detailed description near the top, like JavaDoc.
+# If set to NO, the detailed description appears after the member
+# documentation.
+
+DETAILS_AT_TOP = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
+# member inherits the documentation from any documented member that it
+# re-implements.
+
+INHERIT_DOCS = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES, then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab.
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE = 4
+
+# This tag can be used to specify a number of aliases that acts
+# as commands in the documentation. An alias has the form "name=value".
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to
+# put the command \sideeffect (or @sideeffect) in the documentation, which
+# will result in a user-defined paragraph with heading "Side Effects:".
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
+# only. Doxygen will then generate output that is more tailored for C.
+# For instance, some of the names that are used will be different. The list
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C = YES
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java sources
+# only. Doxygen will then generate output that is more tailored for Java.
+# For instance, namespaces will be presented as packages, qualified scopes
+# will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of
+# the same type (for instance a group of public functions) to be put as a
+# subgroup of that type (e.g. under the Public Functions section). Set it to
+# NO to prevent subgrouping. Alternatively, this can be done per class using
+# the \nosubgrouping command.
+
+SUBGROUPING = YES
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
+# documentation are documented, even if no documentation was available.
+# Private class members and static file members will be hidden unless
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL = YES
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
+# will be included in the documentation.
+
+EXTRACT_PRIVATE = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file
+# will be included in the documentation.
+
+EXTRACT_STATIC = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
+# defined locally in source files will be included in the documentation.
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES = YES
+
+# This flag is only useful for Objective-C code. When set to YES local
+# methods, which are defined in the implementation section but not in
+# the interface are included in the documentation.
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
+# undocumented members of documented classes, files or namespaces.
+# If set to NO (the default) these members will be included in the
+# various overviews, but no documentation section is generated.
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy.
+# If set to NO (the default) these classes will be included in the various
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all
+# friend (class|struct|union) declarations.
+# If set to NO (the default) these declarations will be included in the
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any
+# documentation blocks found inside the body of a function.
+# If set to NO (the default) these blocks will be appended to the
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS = NO
+
+# The INTERNAL_DOCS tag determines if documentation
+# that is typed after a \internal command is included. If the tag is set
+# to NO (the default) then the documentation will be excluded.
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate
+# file names in lower-case letters. If set to YES upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
+# will show members with their full class and namespace scopes in the
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
+# will put a list of the files that are included by a file in the documentation
+# of that file.
+
+SHOW_INCLUDE_FILES = NO
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
+# is inserted in the documentation for inline members.
+
+INLINE_INFO = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
+# will sort the (detailed) documentation of file and class members
+# alphabetically by member name. If set to NO the members will appear in
+# declaration order.
+
+SORT_MEMBER_DOCS = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the
+# brief documentation of file, namespace and class members alphabetically
+# by member name. If set to NO (the default) the members will appear in
+# declaration order.
+
+SORT_BRIEF_DOCS = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be
+# sorted by fully-qualified names, including namespaces. If set to
+# NO (the default), the class list will be sorted only by class name,
+# not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or
+# disable (NO) the todo list. This list is created by putting \todo
+# commands in the documentation.
+
+GENERATE_TODOLIST = NO
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or
+# disable (NO) the test list. This list is created by putting \test
+# commands in the documentation.
+
+GENERATE_TESTLIST = NO
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or
+# disable (NO) the bug list. This list is created by putting \bug
+# commands in the documentation.
+
+GENERATE_BUGLIST = NO
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or
+# disable (NO) the deprecated list. This list is created by putting
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= NO
+
+# The ENABLED_SECTIONS tag can be used to enable conditional
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
+# the initial value of a variable or define consists of for it to appear in
+# the documentation. If the initializer consists of more lines than specified
+# here it will be hidden. Use a value of 0 to hide initializers completely.
+# The appearance of the initializer of individual variables and defines in the
+# documentation can be controlled using \showinitializer or \hideinitializer
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated
+# at the bottom of the documentation of classes and structs. If set to YES the
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated by doxygen. Possible values are YES and NO. If left blank
+# NO is used.
+
+WARNINGS = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED = YES
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some
+# parameters in a documented function, or documenting parameters that
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR = YES
+
+# The WARN_FORMAT tag determines the format of the warning messages that
+# doxygen can produce. The string should contain the $file, $line, and $text
+# tags, which will be replaced by the file and line number from which the
+# warning originated and the warning text.
+
+WARN_FORMAT = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning
+# and error messages should be written. If left blank the output is written
+# to stderr.
+
+WARN_LOGFILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain
+# documented source files. You may enter file names like "myfile.cpp" or
+# directories like "/usr/src/myproject". Separate the files or directories
+# with spaces.
+
+INPUT = ../src/pulse/context.h ../src/pulse/stream.h ../src/pulse/pulseaudio.h ../src/pulse/sample.h ../src/pulse/def.h ../src/pulse/subscribe.h ../src/pulse/introspect.h ../src/pulse/scache.h ../src/pulse/mainloop-api.h ../src/pulse/glib-mainloop.h ../src/pulse/mainloop.h ../src/pulse/mainloop-signal.h ../src/pulse/error.h ../src/pulse/operation.h ../src/pulse/simple.h ../src/pulse/version.h ../src/pulse/volume.h ../src/pulse/channelmap.h ../src/pulse/thread-mainloop.h ../src/pulse/xmalloc.h ../src/pulse/utf8.h ../src/pulse/util.h ../src/pulse/timeval.h ../src/pulse/browser.h
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank the following patterns are tested:
+# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx *.hpp
+# *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm
+
+FILE_PATTERNS =
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories
+# should be searched for input files as well. Possible values are YES and NO.
+# If left blank NO is used.
+
+RECURSIVE = NO
+
+# The EXCLUDE tag can be used to specify files and/or directories that should
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+
+EXCLUDE =
+
+# The EXCLUDE_SYMLINKS tag can be used select whether or not files or directories
+# that are symbolic links (a Unix filesystem feature) are excluded from the input.
+
+EXCLUDE_SYMLINKS = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories.
+
+EXCLUDE_PATTERNS =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or
+# directories that contain example code fragments that are included (see
+# the \include command).
+
+EXAMPLE_PATH = ../src/utils ../src/tests
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank all files are included.
+
+EXAMPLE_PATTERNS =
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude
+# commands irrespective of the value of the RECURSIVE tag.
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or
+# directories that contain image that are included in the documentation (see
+# the \image command).
+
+IMAGE_PATH =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command <filter> <input-file>, where <filter>
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an
+# input file. Doxygen will then use the output that the filter program writes
+# to standard output. If FILTER_PATTERNS is specified, this tag will be
+# ignored.
+
+INPUT_FILTER =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis. Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match. The filters are a list of the form:
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further
+# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER
+# is applied to all files.
+
+FILTER_PATTERNS =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will be used to filter the input files when producing source
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will
+# be generated. Documented entities will be cross-referenced with these sources.
+# Note: To get rid of all source code in the generated output, make sure also
+# VERBATIM_HEADERS is set to NO.
+
+SOURCE_BROWSER = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
+# doxygen to hide any special comment blocks from generated source code
+# fragments. Normal C and C++ comments will always remain visible.
+
+STRIP_CODE_COMMENTS = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES (the default)
+# then for each documented function all documented
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = YES
+
+# If the REFERENCES_RELATION tag is set to YES (the default)
+# then for each documented function all documented entities
+# called/used by that function will be listed.
+
+REFERENCES_RELATION = YES
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen
+# will generate a verbatim copy of the header file for each class for
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index
+# of all compounds will be generated. Enable this if the project
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX = YES
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX = 5
+
+# In case all classes in a project start with a common prefix, all
+# classes will be put under the same header in the alphabetical index.
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX = pa_ PA_
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
+# generate HTML output.
+
+GENERATE_HTML = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard header.
+
+HTML_HEADER =
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard footer.
+
+HTML_FOOTER =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading
+# style sheet that is used by each HTML page. It can be used to
+# fine-tune the look of the HTML output. If the tag is left blank doxygen
+# will generate a default style sheet. Note that doxygen will try to copy
+# the style sheet file to the HTML output directory, so don't put your own
+# stylesheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET =
+
+# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes,
+# files or namespaces will be aligned in HTML using tables. If set to
+# NO a bullet list will be used.
+
+HTML_ALIGN_MEMBERS = YES
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files
+# will be generated that can be used as input for tools like the
+# Microsoft HTML help workshop to generate a compressed HTML help file (.chm)
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can
+# be used to specify the file name of the resulting .chm file. You
+# can add a path in front of the file if the result should not be
+# written to the html output directory.
+
+CHM_FILE =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can
+# be used to specify the location (absolute path including file name) of
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag
+# controls if a separate .chi index file is generated (YES) or that
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag
+# controls whether a binary table of contents is generated (YES) or a
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND = NO
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index at
+# top of each HTML page. The value NO (the default) enables the index and
+# the value YES disables it.
+
+DISABLE_INDEX = NO
+
+# This tag can be used to set the number of enum values (range [1..20])
+# that doxygen will group on one line in the generated HTML documentation.
+
+ENUM_VALUES_PER_LINE = 1
+
+# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be
+# generated containing a tree-like index structure (just like the one that
+# is generated for HTML Help). For this to work a browser that supports
+# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+,
+# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are
+# probably better off using the HTML help feature.
+
+GENERATE_TREEVIEW = YES
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
+# used to set the initial width (in pixels) of the frame in which the tree
+# is shown.
+
+TREEVIEW_WIDTH = 250
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
+# generate Latex output.
+
+GENERATE_LATEX = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked. If left blank `latex' will be used as the default command name.
+
+LATEX_CMD_NAME = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to
+# generate index for LaTeX. If left blank `makeindex' will be used as the
+# default command name.
+
+MAKEINDEX_CMD_NAME = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact
+# LaTeX documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_LATEX = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used
+# by the printer. Possible values are: a4, a4wide, letter, legal and
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE = a4wide
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for
+# the generated latex document. The header should contain everything until
+# the first chapter. If it is left blank doxygen will generate a
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will
+# contain links (just like the HTML output) instead of page references
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS = NO
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of
+# plain latex in the generated Makefile. Set this option to YES to get a
+# higher quality PDF documentation.
+
+USE_PDFLATEX = NO
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode.
+# command to the generated LaTeX files. This will instruct LaTeX to keep
+# running if errors occur, instead of asking the user for help.
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE = NO
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not
+# include the index chapters (such as File Index, Compound Index, etc.)
+# in the output.
+
+LATEX_HIDE_INDICES = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output
+# The RTF output is optimized for Word 97 and may not look very pretty with
+# other RTF readers or editors.
+
+GENERATE_RTF = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact
+# RTF documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_RTF = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated
+# will contain hyperlink fields. The RTF file will
+# contain links (just like the HTML output) instead of page references.
+# This makes the output suitable for online browsing using WORD or other
+# programs which support those fields.
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's
+# config file, i.e. a series of assignments. You only have to provide
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE =
+
+# Set optional variables used in the generation of an rtf document.
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
+# generate man pages
+
+GENERATE_MAN = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT = man
+
+# The MAN_EXTENSION tag determines the extension that is added to
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output,
+# then it will generate one additional man file for each entity
+# documented in the real man page(s). These additional files
+# only source the real man page, but without them the man command
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will
+# generate an XML file that captures the structure of
+# the code including all documentation.
+
+GENERATE_XML = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_SCHEMA =
+
+# The XML_DTD tag can be used to specify an XML DTD,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_DTD =
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will
+# dump the program listings (including syntax highlighting
+# and cross-referencing information) to the XML output. Note that
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will
+# generate an AutoGen Definitions (see autogen.sf.net) file
+# that captures the structure of the code including all
+# documentation. Note that this feature is still experimental
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will
+# generate a Perl module file that captures the structure of
+# the code including all documentation. Note that this
+# feature is still experimental and incomplete at the
+# moment.
+
+GENERATE_PERLMOD = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be
+# nicely formatted so it can be parsed by a human reader. This is useful
+# if you want to understand what is going on. On the other hand, if this
+# tag is set to NO the size of the Perl module output will be much smaller
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY = YES
+
+# The names of the make variables in the generated doxyrules.make file
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX.
+# This is useful so different doxyrules.make files included by the same
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
+# evaluate all C-preprocessor directives found in the sources and include
+# files.
+
+ENABLE_PREPROCESSING = YES
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro
+# names in the source code. If set to NO (the default) only conditional
+# compilation will be performed. Macro expansion can be done in a controlled
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION = YES
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
+# then the macro expansion is limited to the macros specified with the
+# PREDEFINED and EXPAND_AS_PREDEFINED tags.
+
+EXPAND_ONLY_PREDEF = YES
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
+# in the INCLUDE_PATH (see below) will be search if a #include is found.
+
+SEARCH_INCLUDES = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by
+# the preprocessor.
+
+INCLUDE_PATH =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will
+# be used.
+
+INCLUDE_FILE_PATTERNS =
+
+# The PREDEFINED tag can be used to specify one or more macro names that
+# are defined before the preprocessor is started (similar to the -D option of
+# gcc). The argument of the tag is a list of macros of the form: name
+# or name=definition (no spaces). If the definition and the = are
+# omitted =1 is assumed.
+
+PREDEFINED = PA_C_DECL_BEGIN= PA_C_DECL_END=
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
+# this tag can be used to specify a list of macro names that should be expanded.
+# The macro definition that is found in the sources will be used.
+# Use the PREDEFINED tag if you want to use a different macro definition.
+
+#EXPAND_AS_DEFINED = PA_C_DECL_BEGIN, PA_C_DECL_END
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
+# doxygen's preprocessor will remove all function-like macros that are alone
+# on a line, have an all uppercase name, and do not end with a semicolon. Such
+# function macros are typically used for boiler-plate code, and will confuse the
+# parser if not removed.
+
+SKIP_FUNCTION_MACROS = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles.
+# Optionally an initial location of the external documentation
+# can be added for each tagfile. The format of a tag file without
+# this location is as follows:
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where "loc1" and "loc2" can be relative or absolute paths or
+# URLs. If a location is present for each tag, the installdox tool
+# does not have to be run to correct the links.
+# Note that each tag file must have a unique name
+# (where the name does NOT include the path)
+# If a tag file is not located in the directory in which doxygen
+# is run, you must also specify the path to the tagfile here.
+
+TAGFILES =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE =
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed
+# in the class index. If set to NO only the inherited external classes
+# will be listed.
+
+ALLEXTERNALS = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will
+# be listed.
+
+EXTERNAL_GROUPS = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base or
+# super classes. Setting the tag to NO turns the diagrams off. Note that this
+# option is superseded by the HAVE_DOT option below. This is only a fallback. It is
+# recommended to install and use dot, since it yields more powerful graphs.
+
+CLASS_DIAGRAMS = YES
+
+# If set to YES, the inheritance and collaboration graphs will hide
+# inheritance and usage relations if the target is undocumented
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz, a graph visualization
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT = NO
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect inheritance relations. Setting this tag to YES will force the
+# the CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect implementation dependencies (inheritance, containment, and
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+
+UML_LOOK = NO
+
+# If set to YES, the inheritance and collaboration graphs will show the
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT
+# tags are set to YES then doxygen will generate a graph for each documented
+# file showing the direct and indirect include dependencies of the file with
+# other documented files.
+
+INCLUDE_GRAPH = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each
+# documented header file showing the documented files that directly or
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH = YES
+
+# If the CALL_GRAPH and HAVE_DOT tags are set to YES then doxygen will
+# generate a call dependency graph for every global function or class method.
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable call graphs for selected
+# functions only using the \callgraph command.
+
+CALL_GRAPH = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
+# will graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. Possible values are png, jpg, or gif
+# If left blank png will be used.
+
+DOT_IMAGE_FORMAT = png
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found on the path.
+
+DOT_PATH =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the
+# \dotfile command).
+
+DOTFILE_DIRS =
+
+# The MAX_DOT_GRAPH_WIDTH tag can be used to set the maximum allowed width
+# (in pixels) of the graphs generated by dot. If a graph becomes larger than
+# this value, doxygen will try to truncate the graph, so that it fits within
+# the specified constraint. Beware that most browsers cannot cope with very
+# large images.
+
+MAX_DOT_GRAPH_WIDTH = 1024
+
+# The MAX_DOT_GRAPH_HEIGHT tag can be used to set the maximum allows height
+# (in pixels) of the graphs generated by dot. If a graph becomes larger than
+# this value, doxygen will try to truncate the graph, so that it fits within
+# the specified constraint. Beware that most browsers cannot cope with very
+# large images.
+
+MAX_DOT_GRAPH_HEIGHT = 1024
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the
+# graphs generated by dot. A depth value of 3 means that only nodes reachable
+# from the root by following a path via at most 3 edges will be shown. Nodes that
+# lay further from the root node will be omitted. Note that setting this option to
+# 1 or 2 may greatly reduce the computation time needed for large code bases. Also
+# note that a graph may be further truncated if the graph's image dimensions are
+# not sufficient to fit the graph (see MAX_DOT_GRAPH_WIDTH and MAX_DOT_GRAPH_HEIGHT).
+# If 0 is used for the depth value (the default), the graph is not depth-constrained.
+
+MAX_DOT_GRAPH_DEPTH = 0
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will
+# generate a legend page explaining the meaning of the various boxes and
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will
+# remove the intermediate dot files that are used to generate
+# the various graphs.
+
+DOT_CLEANUP = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to the search engine
+#---------------------------------------------------------------------------
+
+# The SEARCHENGINE tag specifies whether or not a search engine should be
+# used. If set to NO the values of all tags below this one will be ignored.
+
+SEARCHENGINE = NO
+
+SHOW_DIRECTORIES=NO
+
diff --git a/libpulse-browse.pc.in b/libpulse-browse.pc.in
new file mode 100644
index 00000000..66438b2a
--- /dev/null
+++ b/libpulse-browse.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=${prefix}
+libdir=@libdir@
+includedir=${prefix}/include
+
+Name: libpulse-browse
+Description: PulseAudio Network Browsing API
+Version: @PACKAGE_VERSION@
+Libs: -L${libdir} -lpulse-browse @PTHREAD_LIBS@
+Cflags: -D_REENTRANT -I${includedir}
+Requires: libpulse
diff --git a/libpulse-mainloop-glib.pc.in b/libpulse-mainloop-glib.pc.in
new file mode 100644
index 00000000..1a8bfa40
--- /dev/null
+++ b/libpulse-mainloop-glib.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=${prefix}
+libdir=@libdir@
+includedir=${prefix}/include
+
+Name: libpulse-mainloop-glib
+Description: GLIB 2.0 Main Loop Wrapper for PulseAudio
+Version: @PACKAGE_VERSION@
+Libs: -L${libdir} -lpulse-mainloop-glib @PTHREAD_LIBS@
+Cflags: -D_REENTRANT -I${includedir}
+Requires: libpulse glib-2.0
diff --git a/libpulse-simple.pc.in b/libpulse-simple.pc.in
new file mode 100644
index 00000000..00d1245a
--- /dev/null
+++ b/libpulse-simple.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=${prefix}
+libdir=@libdir@
+includedir=${prefix}/include
+
+Name: libpulse-simple
+Description: Simplified Synchronous Client Interface to PulseAudio
+Version: @PACKAGE_VERSION@
+Libs: -L${libdir} -lpulse-simple @PTHREAD_LIBS@
+Cflags: -D_REENTRANT -I${includedir}
+Requires: libpulse
diff --git a/libpulse.pc.in b/libpulse.pc.in
new file mode 100644
index 00000000..2193b2b9
--- /dev/null
+++ b/libpulse.pc.in
@@ -0,0 +1,10 @@
+prefix=@prefix@
+exec_prefix=${prefix}
+libdir=@libdir@
+includedir=${prefix}/include
+
+Name: libpulse
+Description: Client Interface to PulseAudio
+Version: @PACKAGE_VERSION@
+Libs: -L${libdir} -lpulse @PTHREAD_LIBS@
+Cflags: -D_REENTRANT -I${includedir}
diff --git a/man/Makefile.am b/man/Makefile.am
new file mode 100644
index 00000000..0a355f95
--- /dev/null
+++ b/man/Makefile.am
@@ -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.
+
+pulseconfdir=$(sysconfdir)/pulse
+
+if BUILD_MANPAGES
+
+man_MANS = \
+ pulseaudio.1 \
+ esdcompat.1 \
+ pax11publish.1 \
+ paplay.1 \
+ pacat.1 \
+ pacmd.1 \
+ pactl.1 \
+ pasuspender.1 \
+ padsp.1 \
+ pabrowse.1 \
+ pulse-daemon.conf.5 \
+ pulse-client.conf.5 \
+ default.pa.5
+
+noinst_DATA = \
+ pulseaudio.1.xml \
+ esdcompat.1.xml \
+ pax11publish.1.xml \
+ paplay.1.xml \
+ pacat.1.xml \
+ pacmd.1.xml \
+ pactl.1.xml \
+ pasuspender.1.xml \
+ padsp.1.xml \
+ pabrowse.1.xml \
+ pulse-daemon.conf.5.xml \
+ pulse-client.conf.5.xml \
+ default.pa.5.xml
+
+CLEANFILES = \
+ $(noinst_DATA)
+
+pulseaudio.1.xml: pulseaudio.1.xml.in Makefile
+ sed -e 's,@pulseconfdir\@,$(pulseconfdir),g' \
+ -e 's,@PACKAGE_BUGREPORT\@,$(PACKAGE_BUGREPORT),g' \
+ -e 's,@PACKAGE_URL\@,$(PACKAGE_URL),g' $< > $@
+
+esdcompat.1.xml: esdcompat.1.xml.in Makefile
+ sed -e 's,@pulseconfdir\@,$(pulseconfdir),g' \
+ -e 's,@PACKAGE_BUGREPORT\@,$(PACKAGE_BUGREPORT),g' \
+ -e 's,@PACKAGE_URL\@,$(PACKAGE_URL),g' $< > $@
+
+pax11publish.1.xml: pax11publish.1.xml.in Makefile
+ sed -e 's,@pulseconfdir\@,$(pulseconfdir),g' \
+ -e 's,@PACKAGE_BUGREPORT\@,$(PACKAGE_BUGREPORT),g' \
+ -e 's,@PACKAGE_URL\@,$(PACKAGE_URL),g' $< > $@
+
+paplay.1.xml: paplay.1.xml.in Makefile
+ sed -e 's,@pulseconfdir\@,$(pulseconfdir),g' \
+ -e 's,@PACKAGE_BUGREPORT\@,$(PACKAGE_BUGREPORT),g' \
+ -e 's,@PACKAGE_URL\@,$(PACKAGE_URL),g' $< > $@
+
+pacat.1.xml: pacat.1.xml.in Makefile
+ sed -e 's,@pulseconfdir\@,$(pulseconfdir),g' \
+ -e 's,@PACKAGE_BUGREPORT\@,$(PACKAGE_BUGREPORT),g' \
+ -e 's,@PACKAGE_URL\@,$(PACKAGE_URL),g' $< > $@
+
+pacmd.1.xml: pacmd.1.xml.in Makefile
+ sed -e 's,@pulseconfdir\@,$(pulseconfdir),g' \
+ -e 's,@PACKAGE_BUGREPORT\@,$(PACKAGE_BUGREPORT),g' \
+ -e 's,@PACKAGE_URL\@,$(PACKAGE_URL),g' $< > $@
+
+pactl.1.xml: pactl.1.xml.in Makefile
+ sed -e 's,@pulseconfdir\@,$(pulseconfdir),g' \
+ -e 's,@PACKAGE_BUGREPORT\@,$(PACKAGE_BUGREPORT),g' \
+ -e 's,@PACKAGE_URL\@,$(PACKAGE_URL),g' $< > $@
+
+pasuspender.1.xml: pasuspender.1.xml.in Makefile
+ sed -e 's,@pulseconfdir\@,$(pulseconfdir),g' \
+ -e 's,@PACKAGE_BUGREPORT\@,$(PACKAGE_BUGREPORT),g' \
+ -e 's,@PACKAGE_URL\@,$(PACKAGE_URL),g' $< > $@
+
+padsp.1.xml: padsp.1.xml.in Makefile
+ sed -e 's,@pulseconfdir\@,$(pulseconfdir),g' \
+ -e 's,@PACKAGE_BUGREPORT\@,$(PACKAGE_BUGREPORT),g' \
+ -e 's,@PACKAGE_URL\@,$(PACKAGE_URL),g' $< > $@
+
+pabrowse.1.xml: pabrowse.1.xml.in Makefile
+ sed -e 's,@pulseconfdir\@,$(pulseconfdir),g' \
+ -e 's,@PACKAGE_BUGREPORT\@,$(PACKAGE_BUGREPORT),g' \
+ -e 's,@PACKAGE_URL\@,$(PACKAGE_URL),g' $< > $@
+
+pulse-daemon.conf.5.xml: pulse-daemon.conf.5.xml.in Makefile
+ sed -e 's,@pulseconfdir\@,$(pulseconfdir),g' \
+ -e 's,@PACKAGE_BUGREPORT\@,$(PACKAGE_BUGREPORT),g' \
+ -e 's,@PACKAGE_URL\@,$(PACKAGE_URL),g' $< > $@
+
+pulse-client.conf.5.xml: pulse-client.conf.5.xml.in Makefile
+ sed -e 's,@pulseconfdir\@,$(pulseconfdir),g' \
+ -e 's,@PACKAGE_BUGREPORT\@,$(PACKAGE_BUGREPORT),g' \
+ -e 's,@PACKAGE_URL\@,$(PACKAGE_URL),g' $< > $@
+
+default.pa.5.xml: default.pa.5.xml.in Makefile
+ sed -e 's,@pulseconfdir\@,$(pulseconfdir),g' \
+ -e 's,@PACKAGE_BUGREPORT\@,$(PACKAGE_BUGREPORT),g' \
+ -e 's,@PACKAGE_URL\@,$(PACKAGE_URL),g' $< > $@
+
+if USE_XMLTOMAN
+
+CLEANFILES += \
+ $(man_MANS)
+
+pulseaudio.1: pulseaudio.1.xml Makefile
+ xmltoman $< > $@
+
+esdcompat.1: esdcompat.1.xml Makefile
+ xmltoman $< > $@
+
+pax11publish.1: pax11publish.1.xml Makefile
+ xmltoman $< > $@
+
+paplay.1: paplay.1.xml Makefile
+ xmltoman $< > $@
+
+pacat.1: pacat.1.xml Makefile
+ xmltoman $< > $@
+
+pacmd.1: pacmd.1.xml Makefile
+ xmltoman $< > $@
+
+pactl.1: pactl.1.xml Makefile
+ xmltoman $< > $@
+
+pasuspender.1: pasuspender.1.xml Makefile
+ xmltoman $< > $@
+
+padsp.1: padsp.1.xml Makefile
+ xmltoman $< > $@
+
+pabrowse.1: pabrowse.1.xml Makefile
+ xmltoman $< > $@
+
+pulse-daemon.conf.5: pulse-daemon.conf.5.xml Makefile
+ xmltoman $< > $@
+
+pulse-client.conf.5: pulse-client.conf.5.xml Makefile
+ xmltoman $< > $@
+
+default.pa.5: default.pa.5.xml Makefile
+ xmltoman $< > $@
+
+xmllint: $(noinst_DATA)
+ for f in $(noinst_DATA) ; do \
+ xmllint --noout --valid "$$f" || exit 1 ; \
+ done
+
+endif
+
+endif
+
+EXTRA_DIST = \
+ $(man_MANS) \
+ pulseaudio.1.xml.in \
+ esdcompat.1.xml.in \
+ pax11publish.1.xml.in \
+ paplay.1.xml.in \
+ pacat.1.xml.in \
+ pacmd.1.xml.in \
+ pactl.1.xml.in \
+ pasuspender.1.xml.in \
+ padsp.1.xml.in \
+ pabrowse.1.xml.in \
+ pulse-daemon.conf.5.xml.in \
+ pulse-client.conf.5.xml.in \
+ default.pa.5.xml.in \
+ xmltoman.css \
+ xmltoman.xsl \
+ xmltoman.dtd
diff --git a/man/default.pa.5.xml.in b/man/default.pa.5.xml.in
new file mode 100644
index 00000000..0f826db6
--- /dev/null
+++ b/man/default.pa.5.xml.in
@@ -0,0 +1,58 @@
+<?xml version="1.0"?><!--*-nxml-*-->
+<!DOCTYPE manpage SYSTEM "xmltoman.dtd">
+<?xml-stylesheet type="text/xsl" href="xmltoman.xsl" ?>
+
+<!-- $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.
+-->
+
+<manpage name="default.pa" section="5" desc="PulseAudio Sound Server Startup Script">
+
+ <synopsis>
+ <p><file>~/.pulse/default.pa</file></p>
+
+ <p><file>@pulseconfdir@/default.pa</file></p>
+ </synopsis>
+
+ <description>
+ <p>The PulseAudio sound server interprets the file
+ <file>~/.pulse/default.pa</file> on startup, and when that file
+ doesn't exist <file>@pulseconfdir@/default.pa</file>. It
+ should contain directives in the PulseAudio CLI languages, as
+ documented on <url href="http://pulseaudio.org/wiki/CLI"/>.</p>
+
+ <p>The same commands can also be entered during runtime in the <manref name="pacmd"
+ section="1"/> tool, allowing flexible runtime reconfiguration.</p>
+ </description>
+
+ <section name="Authors">
+ <p>The PulseAudio Developers &lt;@PACKAGE_BUGREPORT@&gt;;
+ PulseAudio is available from <url href="@PACKAGE_URL@"/></p>
+ </section>
+
+ <section name="See also">
+ <p>
+ <manref name="pulse-daemon.conf" section="5"/>, <manref
+ name="pulseaudio" section="1"/>, <manref name="pacmd"
+ section="1"/>
+ </p>
+ </section>
+
+</manpage>
diff --git a/man/esdcompat.1.xml.in b/man/esdcompat.1.xml.in
new file mode 100644
index 00000000..864dc5db
--- /dev/null
+++ b/man/esdcompat.1.xml.in
@@ -0,0 +1,91 @@
+<?xml version="1.0"?><!--*-nxml-*-->
+<!DOCTYPE manpage SYSTEM "xmltoman.dtd">
+<?xml-stylesheet type="text/xsl" href="xmltoman.xsl" ?>
+
+<!-- $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.
+-->
+
+<manpage name="esdcompat" section="1" desc="PulseAudio ESD wrapper script">
+
+ <synopsis>
+ <cmd>esdcompat [<arg>options</arg>]</cmd>
+ <cmd>esdcompat <opt>--help</opt></cmd>
+ <cmd>esdcompat <opt>--version</opt></cmd>
+ </synopsis>
+
+ <description>
+ <p><file>esdcompat</file> is a compatiblity script that takes the
+ same arguments as the ESD sound daemon <manref name="esd"
+ section="1"/>, but uses them to start a the PulseAudio sound server with the appropriate parameters. It is
+ required to make PulseAudio a drop-in replacement for esd, i.e. it
+ can be used to make <manref name="gnome-session" section="1"/>
+ start up PulseAudio instead of esd.</p>
+
+ <p>It is recommended to make <file>esd</file> a symbolic link to this script.</p>
+ </description>
+
+ <options>
+
+ <option>
+ <p><opt>-h | --help</opt></p>
+
+ <optdesc><p>Show help.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>--version</opt></p>
+
+ <optdesc><p>Show version information.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>-tcp | -promiscuous | -d | -b | -r | -as | -unix | -public | -terminate | -nobeeps | -trust | -port | -bind</opt></p>
+
+ <optdesc><p>These options understood by the original
+ <file>esd</file> are ignored by
+ <file>esdcompat</file>.</p></optdesc>
+
+ </option>
+
+
+ <option>
+ <p><opt>-spawnpid | -spawnfd</opt></p>
+
+ <optdesc><p>These internally used options understood by the
+ original <file>esd</file> are properly handled by
+ <file>esdcompat</file>, however are not to be used
+ manually.</p></optdesc>
+
+ </option>
+
+ </options>
+
+ <section name="Authors">
+ <p>The PulseAudio Developers &lt;@PACKAGE_BUGREPORT@&gt;; PulseAudio is available from <url href="@PACKAGE_URL@"/></p>
+ </section>
+
+ <section name="See also">
+ <p>
+ <manref name="pulseaudio" section="1"/>, <manref name="esd" section="1"/>
+ </p>
+ </section>
+
+</manpage>
diff --git a/man/pabrowse.1.xml.in b/man/pabrowse.1.xml.in
new file mode 100644
index 00000000..b539fb21
--- /dev/null
+++ b/man/pabrowse.1.xml.in
@@ -0,0 +1,49 @@
+<?xml version="1.0"?><!--*-nxml-*-->
+<!DOCTYPE manpage SYSTEM "xmltoman.dtd">
+<?xml-stylesheet type="text/xsl" href="xmltoman.xsl" ?>
+
+<!-- $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.
+-->
+
+<manpage name="pabrowse" section="1" desc="List PulseAudio sound servers on the network">
+
+ <synopsis>
+ <cmd>pabrowse</cmd>
+ </synopsis>
+
+ <description>
+ <p><file>pabrowse</file> lists all PulseAudio sound servers on the
+ local network that are being announced with Zeroconf/Avahi.</p>
+
+ <p>This program takes no command line arguments.</p>
+ </description>
+
+ <section name="Authors">
+ <p>The PulseAudio Developers &lt;@PACKAGE_BUGREPORT@&gt;; PulseAudio is available from <url href="@PACKAGE_URL@"/></p>
+ </section>
+
+ <section name="See also">
+ <p>
+ <manref name="pulseaudio" section="1"/>, <manref name="avahi-daemon" section="8"/>
+ </p>
+ </section>
+
+</manpage>
diff --git a/man/pacat.1.xml.in b/man/pacat.1.xml.in
new file mode 100644
index 00000000..748d136d
--- /dev/null
+++ b/man/pacat.1.xml.in
@@ -0,0 +1,185 @@
+<?xml version="1.0"?><!--*-nxml-*-->
+<!DOCTYPE manpage SYSTEM "xmltoman.dtd">
+<?xml-stylesheet type="text/xsl" href="xmltoman.xsl" ?>
+
+<!-- $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.
+-->
+
+<manpage name="pacat" section="1" desc="Play back or record raw audio streams on a PulseAudio sound server">
+
+ <synopsis>
+ <cmd>pacat [<arg>options</arg>] [<arg>FILE</arg>]</cmd>
+ <cmd>parec [<arg>options</arg>] [<arg>FILE</arg>]</cmd>
+ <cmd>paplay <opt>--help</opt></cmd>
+ <cmd>paplay <opt>--version</opt></cmd>
+ </synopsis>
+
+ <description>
+ <p><file>pacat</file> is a simple tool for playing back or
+ capturing raw audio files on a PulseAudio sound server.</p>
+ </description>
+
+ <options>
+
+ <option>
+ <p><opt>-h | --help</opt></p>
+
+ <optdesc><p>Show help.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>--version</opt></p>
+
+ <optdesc><p>Show version information.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>-r | --record</opt></p>
+
+ <optdesc><p>Capture raw audio data and write it to the specified file or to STDOUT if none is specified. If the tool is called under the name <file>parec</file> this is the default.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>-p | --playback</opt></p>
+
+ <optdesc><p>Read raw audio data from the specified file or STDIN if none is specified, and play it back. If the tool is called under the name <file>pacat</file> this is the default.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>-v | --verbose</opt></p>
+
+ <optdesc><p>Enable verbose operation. Dumps the current playback time to STDERR during playback/capturing.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>-s | --server</opt><arg>=SERVER</arg></p>
+
+ <optdesc><p>Choose the server to connect to.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>-d | --device</opt><arg>=SINKORSOURCE</arg></p>
+
+ <optdesc><p>Specify the symbolic name of the sink/source to play/record this stream on/from.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>-n | --client-name</opt><arg>=NAME</arg></p>
+
+ <optdesc><p>Specify the client name <file>paplay</file> shall pass to the server when connecting.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>--stream-name</opt><arg>=NAME</arg></p>
+
+ <optdesc><p>Specify the stream name <file>paplay</file> shall pass to the server when creating the stream.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>--volume</opt><arg>=VOLUME</arg></p>
+
+ <optdesc><p>Specify the initial playback volume to use. Choose a value between 0 (silent) and 65536 (100% volume).</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>--rate</opt><arg>=SAMPLERATE</arg></p>
+
+ <optdesc><p>Capture or play back audio with the specified sample rate. Defaults to 44100 Hz.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>--format</opt><arg>=FORMAT</arg></p>
+
+ <optdesc><p>Capture or play back audio with the specified sample
+ format. Specify one of <opt>u8</opt>, <opt>s16le</opt>,
+ <opt>s16be</opt>, <opt>float32le</opt>, <opt>float32be</opt>,
+ <opt>ulaw</opt>, <opt>alaw</opt>. Depending on the endianess of
+ the CPU the formats <opt>s16ne</opt>, <opt>s16re</opt>,
+ <opt>float32ne</opt>, <opt>float32re</opt> (for native,
+ resp. reverse endian) are available as aliases. Defaults to
+ s16ne.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>--channels</opt><arg>=CHANNELS</arg></p>
+
+ <optdesc><p>Capture or play back audio with the specified number
+ of channels. If more than two channels are used it is
+ recommended to use the <opt>--channel-map</opt> option
+ below. Defaults to 2.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>--channel-map</opt><arg>=CHANNELMAP</arg></p>
+
+ <optdesc><p>Explicitly choose a channel map when playing back
+ this stream. The argument should be a comma separated list of
+ channel names: <opt>front-left</opt>, <opt>front-right</opt>,
+ <opt>mono</opt>, <opt>front-center</opt>, <opt>rear-left</opt>,
+ <opt>rear-right</opt>, <opt>rear-center</opt>, <opt>lfe</opt>,
+ <opt>front-left-of-center</opt>,
+ <opt>front-right-of-center</opt>, <opt>side-left</opt>,
+ <opt>side-right</opt>, <opt>top-center</opt>,
+ <opt>top-front-center</opt>, <opt>top-front-left</opt>,
+ <opt>top-front-right</opt>, <opt>top-rear-left</opt>,
+ <opt>top-rear-right</opt>, <opt>top-rear-center</opt>, or any of
+ the 32 auxiliary channel names <opt>aux0</opt> to
+ <opt>aux31</opt>.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>--fix-format</opt></p>
+ <optdesc><p>If passed, the sample format of the stream is changed to the native format of the sink the stream is connected to.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>--fix-rate</opt></p>
+ <optdesc><p>If passed, the sampling rate of the stream is changed to the native rate of the sink the stream is connected to.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>--fix-channels</opt></p>
+ <optdesc><p>If passed, the number of channels and the channel map of the stream is changed to the native number of channels and the native channel map of the sink the stream is connected to.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>--no-remix</opt></p>
+ <optdesc><p>Never upmix or downmix channels.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>--no-remap</opt></p>
+ <optdesc><p>Never remap channels. Instead of mapping channels by their name this will match them solely by their index/order.</p></optdesc>
+ </option>
+
+ </options>
+
+ <section name="Authors">
+ <p>The PulseAudio Developers &lt;@PACKAGE_BUGREPORT@&gt;; PulseAudio is available from <url href="@PACKAGE_URL@"/></p>
+ </section>
+
+ <section name="See also">
+ <p>
+ <manref name="pulseaudio" section="1"/>, <manref name="paplay" section="1"/>
+ </p>
+ </section>
+
+</manpage>
diff --git a/man/pacmd.1.xml.in b/man/pacmd.1.xml.in
new file mode 100644
index 00000000..b760ba0e
--- /dev/null
+++ b/man/pacmd.1.xml.in
@@ -0,0 +1,52 @@
+<?xml version="1.0"?><!--*-nxml-*-->
+<!DOCTYPE manpage SYSTEM "xmltoman.dtd">
+<?xml-stylesheet type="text/xsl" href="xmltoman.xsl" ?>
+
+<!-- $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.
+-->
+
+<manpage name="pacmd" section="1" desc="Reconfigure a PulseAudio sound server during runtime">
+
+ <synopsis>
+ <cmd>pacmd</cmd>
+ </synopsis>
+
+ <description>
+ <p>This tool can be used to introspect or reconfigure a running
+ PulseAudio sound server during runtime. It connects to the sound
+ server and offers a simple live shell that can be used to enter
+ the commands also understood in the <file>default.pa</file>
+ configuration scripts.</p>
+
+ <p>This program takes no command line options.</p>
+ </description>
+
+ <section name="Authors">
+ <p>The PulseAudio Developers &lt;@PACKAGE_BUGREPORT@&gt;; PulseAudio is available from <url href="@PACKAGE_URL@"/></p>
+ </section>
+
+ <section name="See also">
+ <p>
+ <manref name="pulseaudio" section="1"/>, <manref name="pactl" section="1"/>, <manref name="default.pa" section="5"/>
+ </p>
+ </section>
+
+</manpage>
diff --git a/man/pactl.1.xml.in b/man/pactl.1.xml.in
new file mode 100644
index 00000000..d30d5f7c
--- /dev/null
+++ b/man/pactl.1.xml.in
@@ -0,0 +1,191 @@
+<?xml version="1.0"?><!--*-nxml-*-->
+<!DOCTYPE manpage SYSTEM "xmltoman.dtd">
+<?xml-stylesheet type="text/xsl" href="xmltoman.xsl" ?>
+
+<!-- $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.
+-->
+
+<manpage name="pactl" section="1" desc="Control a running PulseAudio sound server">
+
+ <synopsis>
+ <cmd>pactl [<arg>options</arg>] stat</cmd>
+ <cmd>pactl [<arg>options</arg>] list</cmd>
+ <cmd>pactl [<arg>options</arg>] exit</cmd>
+ <cmd>pactl [<arg>options</arg>] upload-sample <arg>FILENAME</arg> [<arg>NAME</arg>]</cmd>
+ <cmd>pactl [<arg>options</arg>] play-sample <arg>NAME</arg> [<arg>SINK</arg>]</cmd>
+ <cmd>pactl [<arg>options</arg>] remove-sample <arg>NAME</arg></cmd>
+ <cmd>pactl [<arg>options</arg>] move-sink-input <arg>ID</arg> <arg>SINK</arg></cmd>
+ <cmd>pactl [<arg>options</arg>] move-source-input <arg>ID</arg> <arg>SOURCE</arg></cmd>
+ <cmd>pactl [<arg>options</arg>] load-module <arg>NAME</arg> [<arg>ARGUMENTS ...</arg>]</cmd>
+ <cmd>pactl [<arg>options</arg>] unload-module <arg>ID</arg></cmd>
+ <cmd>pactl [<arg>options</arg>] suspend-sink [<arg>SINK</arg>] <arg>1|0</arg></cmd>
+ <cmd>pactl [<arg>options</arg>] suspend-source [<arg>SOURCE</arg>] <arg>1|0</arg></cmd>
+ <cmd>pactl <opt>--help</opt></cmd>
+ <cmd>pactl <opt>--version</opt></cmd>
+ </synopsis>
+
+ <description>
+ <p><file>pactl</file> can be used to issue control commands to the PulseAudio sound server.</p>
+
+ <p><file>pactl</file> only exposes a subset of the available operations. For the full set use the <manref name="pacmd" section="1"/>.</p>
+ </description>
+
+ <options>
+
+ <option>
+ <p><opt>-h | --help</opt></p>
+
+ <optdesc><p>Show help.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>--version</opt></p>
+
+ <optdesc><p>Show version information.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>-s | --server</opt><arg>=SERVER</arg></p>
+
+ <optdesc><p>Choose the server to connect to.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>-n | --client-name</opt><arg>=NAME</arg></p>
+
+ <optdesc><p>Specify the client name <file>pactl</file> shall pass to the server when connecting.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>stat</opt></p>
+
+ <optdesc><p>Dump a few statistics about the PulseAudio daemon.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>list</opt></p>
+
+ <optdesc><p>Dump all currently loaded modules, available sinks, sources, streams and clients.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>exit</opt></p>
+
+ <optdesc><p>Asks the PulseAudio server to terminate.</p></optdesc>
+ </option>
+
+
+ <option>
+ <p><opt>upload-sample</opt> <arg>FILENAME</arg> [<arg>NAME</arg>]</p>
+
+ <optdesc><p>Upload a sound from the specified audio file into
+ the sample cache. The file types supported are those understood
+ by <file>libsndfile</file>. The sample in the cache is named
+ after the audio file, unless the name is explicitly
+ specified.</p></optdesc>
+
+ </option>
+
+ <option>
+ <p><opt>play-sample</opt> <arg>NAME</arg> [<arg>SINK</arg>]</p>
+
+ <optdesc><p>Play the specified sample from the sample cache. It
+ is played on the default sink, unless the symbolic name or the
+ numerical index of the sink to play it on is
+ specified.</p></optdesc>
+
+ </option>
+
+ <option>
+ <p><opt>remove-sample</opt> <arg>NAME</arg></p>
+
+ <optdesc><p>Remove the specified sample from the sample cache.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>move-sink-input</opt> <arg>ID</arg> <arg>SINK</arg></p>
+
+ <optdesc><p>Move the specified playback stream (identified by its numerical index) to the specified sink (identified by its symbolic name or numerical index).</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>move-source-output</opt> <arg>ID</arg> <arg>SOURCE</arg></p>
+
+ <optdesc><p>Move the specified recording stream (identified by its numerical index) to the specified source (identified by its symbolic name or numerical index).</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>load-module</opt> <arg>NAME</arg> [<arg>ARGUMENTS ...</arg>]</p>
+
+ <optdesc><p>Load the specified module with the specified arguments into the running sound server. Prints the numeric index of the module just loaded to STDOUT. You can use it to unload the module later.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>unload-module</opt> <arg>ID</arg></p>
+
+ <optdesc><p>Unload the module instance identified by the specified numeric index.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>unload-module</opt> <arg>ID</arg></p>
+
+ <optdesc><p>Unload the module instance identified by the specified numeric index.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>suspend-sink</opt> <arg>SINK</arg> <arg>1|0</arg></p>
+
+ <optdesc><p>Suspend or resume the specified sink (which my be
+ specified either by its symbolic name, or by its numeric index),
+ depending whether 1 (suspend) or 0 (resume) is passed as last
+ argument. Suspending a sink will pause all playback. Depending
+ on the module implementing the sink this might have the effect
+ that the underlying device is closed, making it available for
+ other applications to use. The exact behaviour depends on the
+ module.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>suspend-source</opt> <arg>SOURCE</arg> <arg>1|0</arg></p>
+
+ <optdesc><p>Suspend or resume the specified source (which my be
+ specified either by its symbolic name, or by its numeric index),
+ depending whether 1 (suspend) or 0 (resume) is passed as last
+ argument. Suspending a source will pause all
+ capturing. Depending on the module implementing the source this
+ might have the effect that the underlying device is closed,
+ making it available for other applications to use. The exact
+ behaviour depends on the module.</p></optdesc>
+ </option>
+
+ </options>
+
+ <section name="Authors">
+ <p>The PulseAudio Developers &lt;@PACKAGE_BUGREPORT@&gt;; PulseAudio is available from <url href="@PACKAGE_URL@"/></p>
+ </section>
+
+ <section name="See also">
+ <p>
+ <manref name="pulseaudio" section="1"/>, <manref name="pacmd" section="1"/>
+ </p>
+ </section>
+
+</manpage>
diff --git a/man/padsp.1.xml.in b/man/padsp.1.xml.in
new file mode 100644
index 00000000..610a9602
--- /dev/null
+++ b/man/padsp.1.xml.in
@@ -0,0 +1,112 @@
+<?xml version="1.0"?><!--*-nxml-*-->
+<!DOCTYPE manpage SYSTEM "xmltoman.dtd">
+<?xml-stylesheet type="text/xsl" href="xmltoman.xsl" ?>
+
+<!-- $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.
+-->
+
+<manpage name="padsp" section="1" desc="PulseAudio OSS Wrapper">
+
+ <synopsis>
+ <cmd>padsp [<arg>options</arg>] <arg>PROGRAM</arg> [<arg>ARGUMENTS ...</arg>]</cmd>
+ <cmd>padsp <opt>-h</opt></cmd>
+ </synopsis>
+
+ <description>
+ <p><file>padsp</file> starts the specified program and
+ redirects its access to OSS compatible audio devices
+ (<file>/dev/dsp</file> and auxiliary devices) to a PulseAudio
+ sound server.</p>
+
+ <p><file>padsp</file> uses the $LD_PRELOAD environment variable
+ that is interpreted by <manref name="ld.so" section="8"/> and thus
+ does not work for SUID binaries and statically built
+ executables.</p>
+
+ <p>Equivalent to using <file>padsp</file> is starting an
+ application with $LD_PRELOAD set to
+ <file>libpulsedsp.so</file></p>
+ </description>
+
+ <options>
+
+ <option>
+ <p><opt>-h | --help</opt></p>
+
+ <optdesc><p>Show help.</p></optdesc>
+ </option>
+
+
+ <option>
+ <p><opt>-s</opt> <arg>SERVER</arg></p>
+
+ <optdesc><p>Set the PulseAudio server to connect to.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>-n</opt> <arg>NAME</arg></p>
+
+ <optdesc><p>The client application name that shall be passed to the server when connecting.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>-m</opt> <arg>NAME</arg></p>
+
+ <optdesc><p>The stream name that shall be passed to the server when creating a stream.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>-M</opt></p>
+
+ <optdesc><p>Disable <file>/dev/mixer</file> emulation.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>-S</opt></p>
+
+ <optdesc><p>Disable <file>/dev/sndstat</file> emulation.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>-D</opt></p>
+
+ <optdesc><p>Disable <file>/dev/dsp</file> emulation.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>-d</opt></p>
+
+ <optdesc><p>Enable debug output.</p></optdesc>
+ </option>
+
+ </options>
+
+ <section name="Authors">
+ <p>The PulseAudio Developers &lt;@PACKAGE_BUGREPORT@&gt;; PulseAudio is available from <url href="@PACKAGE_URL@"/></p>
+ </section>
+
+ <section name="See also">
+ <p>
+ <manref name="pulseaudio" section="1"/>, <manref name="pasuspender" section="1"/>, <manref name="ld.so" section="8"/>
+ </p>
+ </section>
+
+</manpage>
diff --git a/man/paplay.1.xml.in b/man/paplay.1.xml.in
new file mode 100644
index 00000000..bab45dc3
--- /dev/null
+++ b/man/paplay.1.xml.in
@@ -0,0 +1,129 @@
+<?xml version="1.0"?><!--*-nxml-*-->
+<!DOCTYPE manpage SYSTEM "xmltoman.dtd">
+<?xml-stylesheet type="text/xsl" href="xmltoman.xsl" ?>
+
+<!-- $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.
+-->
+
+<manpage name="paplay" section="1" desc="Play back audio files on a PulseAudio sound server">
+
+ <synopsis>
+ <cmd>paplay [<arg>options</arg>] <arg>FILE</arg></cmd>
+ <cmd>paplay <opt>--help</opt></cmd>
+ <cmd>paplay <opt>--version</opt></cmd>
+ </synopsis>
+
+ <description>
+ <p><file>paplay</file> is a simple tool for playing back audio
+ files on a PulseAudio sound server. It understands all audio file
+ formats supported by <file>libsndfile</file>.</p>
+ </description>
+
+ <options>
+
+ <option>
+ <p><opt>-h | --help</opt></p>
+
+ <optdesc><p>Show help.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>--version</opt></p>
+
+ <optdesc><p>Show version information.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>-v | --verbose</opt></p>
+
+ <optdesc><p>Enable verbose operation. Dumps the current playback time to STDERR during playback.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>-s | --server</opt><arg>=SERVER</arg></p>
+
+ <optdesc><p>Choose the server to connect to.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>-d | --device</opt><arg>=SINK</arg></p>
+
+ <optdesc><p>Specify the symbolic name of the sink to play this file on.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>-n | --client-name</opt><arg>=NAME</arg></p>
+
+ <optdesc><p>Specify the client name <file>paplay</file> shall pass to the server when connecting.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>--stream-name</opt><arg>=NAME</arg></p>
+
+ <optdesc><p>Specify the stream name <file>paplay</file> shall pass to the server when creating the stream.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>--volume</opt><arg>=VOLUME</arg></p>
+
+ <optdesc><p>Specify the initial playback volume to use. Choose a value between 0 (silent) and 65536 (100% volume).</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>--channel-map</opt><arg>=CHANNELMAP</arg></p>
+
+ <optdesc><p>Explicitly choose a channel map when playing back
+ this stream. The argument should be a comma separated list of
+ channel names: <arg>front-left</arg>, <arg>front-right</arg>,
+ <arg>mono</arg>, <arg>front-center</arg>, <arg>rear-left</arg>,
+ <arg>rear-right</arg>, <arg>rear-center</arg>, <arg>lfe</arg>,
+ <arg>front-left-of-center</arg>,
+ <arg>front-right-of-center</arg>, <arg>side-left</arg>,
+ <arg>side-right</arg>, <arg>top-center</arg>,
+ <arg>top-front-center</arg>, <arg>top-front-left</arg>,
+ <arg>top-front-right</arg>, <arg>top-rear-left</arg>,
+ <arg>top-rear-right</arg>, <arg>top-rear-center</arg>, or any of
+ the 32 auxiliary channel names <arg>aux0</arg> to
+ <arg>aux31</arg>.</p></optdesc>
+ </option>
+
+ </options>
+
+ <section name="Limitations">
+
+ <p>Due to a limitation in <file>libsndfile</file>
+ <file>paplay</file> currently does not always set the correct channel
+ mapping for playback of multichannel (i.e. surround) audio files, even if the channel mapping information is
+ available in the audio file.</p>
+
+ </section>
+
+ <section name="Authors">
+ <p>The PulseAudio Developers &lt;@PACKAGE_BUGREPORT@&gt;; PulseAudio is available from <url href="@PACKAGE_URL@"/></p>
+ </section>
+
+ <section name="See also">
+ <p>
+ <manref name="pulseaudio" section="1"/>, <manref name="pacat" section="1"/>
+ </p>
+ </section>
+
+</manpage>
diff --git a/man/pasuspender.1.xml.in b/man/pasuspender.1.xml.in
new file mode 100644
index 00000000..406dfe60
--- /dev/null
+++ b/man/pasuspender.1.xml.in
@@ -0,0 +1,82 @@
+<?xml version="1.0"?><!--*-nxml-*-->
+<!DOCTYPE manpage SYSTEM "xmltoman.dtd">
+<?xml-stylesheet type="text/xsl" href="xmltoman.xsl" ?>
+
+<!-- $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.
+-->
+
+<manpage name="pasuspender" section="1" desc="Temporarily suspend PulseAudio">
+
+ <synopsis>
+ <cmd>pasuspender [<arg>options</arg>] -- <arg>PROGRAM</arg> [<arg>ARGUMENTS ...</arg>]</cmd>
+ <cmd>pasuspender <opt>--help</opt></cmd>
+ <cmd>pasuspender <opt>--version</opt></cmd>
+ </synopsis>
+
+ <description>
+ <p><file>pasuspender</file> is a tool that can be used to tell a
+ local PulseAudio sound server to temporarily suspend access to the
+ audio devices, to allow other
+ applications access them directly. <file>pasuspender</file> will
+ suspend access to the audio devices, fork a child process, and
+ when the child process terminates, resume access again.</p>
+
+ <p>Make sure to include <opt>--</opt> in
+ your <file>pasuspender</file> command line before passing the
+ subprocess command line (as shown
+ above). Otherwise <file>pasuspender</file> itself might end up
+ interpreting the command line switches and options you intended to
+ pass to the subprocess.</p>
+ </description>
+
+ <options>
+
+ <option>
+ <p><opt>-h | --help</opt></p>
+
+ <optdesc><p>Show help.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>--version</opt></p>
+
+ <optdesc><p>Show version information.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>-s | --server=</opt><arg>SERVER</arg></p>
+
+ <optdesc><p>Specify the sound server to connect to.</p></optdesc>
+ </option>
+
+ </options>
+
+ <section name="Authors">
+ <p>The PulseAudio Developers &lt;@PACKAGE_BUGREPORT@&gt;; PulseAudio is available from <url href="@PACKAGE_URL@"/></p>
+ </section>
+
+ <section name="See also">
+ <p>
+ <manref name="pulseaudio" section="1"/>, <manref name="padsp" section="1"/>, <manref name="pacmd" section="1"/>, <manref name="pactl" section="1"/>
+ </p>
+ </section>
+
+</manpage>
diff --git a/man/pax11publish.1.xml.in b/man/pax11publish.1.xml.in
new file mode 100644
index 00000000..1e3a1f01
--- /dev/null
+++ b/man/pax11publish.1.xml.in
@@ -0,0 +1,153 @@
+<?xml version="1.0"?><!--*-nxml-*-->
+<!DOCTYPE manpage SYSTEM "xmltoman.dtd">
+<?xml-stylesheet type="text/xsl" href="xmltoman.xsl" ?>
+
+<!-- $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.
+-->
+
+<manpage name="pax11publish" section="1" desc="PulseAudio X11 Credential Utility">
+
+ <synopsis>
+ <cmd>pax11publish <opt>-h</opt></cmd>
+ <cmd>pax11publish [<arg>options</arg>] [<opt>-d</opt>]</cmd>
+ <cmd>pax11publish [<arg>options</arg>] <opt>-e</opt></cmd>
+ <cmd>pax11publish [<arg>options</arg>] <opt>-i</opt></cmd>
+ <cmd>pax11publish [<arg>options</arg>] <opt>-r</opt></cmd>
+ </synopsis>
+
+ <description>
+ <p>The <file>pax11publish</file> utility can be used to dump or
+ manipulate the PulseAudio server credentials that can be stored as
+ properties on the X11 root window.</p>
+
+ <p>Please note that the loadable module
+ <file>module-x11-publish</file> exports the same information
+ directly from the PulseAudio sound server, and should in most
+ cases be used in preference over this tool.</p>
+
+ <p>Use the following command to dump the raw
+ PulseAudio-specific data that is stored in your X11 root
+ window:</p>
+
+ <p>xprop -root | grep ^PULSE_</p>
+
+ </description>
+
+ <options>
+
+ <option>
+ <p><opt>-h</opt></p>
+
+ <optdesc><p>Show help.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>-d</opt></p>
+
+ <optdesc><p>Read the PulseAudio server credentials currently set
+ on the X11 root window and dump them in a human readable form. This reads the
+ PULSE_SERVER, PULSE_SINK, PULSE_SOURCE and PULSE_COOKIE
+ properties.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>-i</opt></p>
+ <optdesc><p>Similar to <opt>-d</opt>, however dumps them in a
+ Bourne shell compatible format so they may be used together with
+ the <file>eval</file> shell command to set the $PULSE_SERVER,
+ $PULSE_SINK, $PULSE_SOURCE environment variables. Also reads the
+ authentication cookie from the root window and stores it in
+ <file>~/.pulse-cookie</file>. </p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>-e</opt></p>
+
+ <optdesc><p>Export the currently locally used sound server,
+ sink, source configuration to the X11 root window. This takes
+ the data from the $PULSE_SERVER, $PULSE_SINK, $PULSE_SOURCE
+ environment variables and combines them with the data from
+ <file>~/.pulse/client.conf</file> (or
+ <file>@pulseconfdir@/client.conf</file> if that file does not
+ exist). If specific options are passed on the command line
+ (<opt>-S</opt>, <opt>-O</opt>, <opt>-I</opt>, <opt>-c</opt>, see
+ below), they take precedence. Also uploads the local
+ authentication cookie <file>~/.pulse-cookie</file> to the X11
+ server.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>-r</opt></p>
+
+ <optdesc><p>Removes the configured PulseAudio configuration from the X11 root window.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>-D</opt> <arg>DISPLAY</arg></p>
+
+ <optdesc><p>Connect to the specified X11 display, instead of the default one configured in $DISPLAY.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>-S</opt> <arg>SERVER</arg></p>
+
+ <optdesc><p>Only valid for <opt>-e</opt>: export the specified
+ PulseAudio server as default to the X11 display instead of the
+ one configured via local configuration.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>-O</opt> <arg>SINK</arg></p>
+
+ <optdesc><p>Only valid for <opt>-e</opt>: export the specified
+ sink as default sink to the X11 display instead of the one
+ configured via local configuration.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>-I</opt> <arg>SOURCE</arg></p>
+
+ <optdesc><p>Only valid for <opt>-e</opt>: export the specified
+ source as default to the X11 display instead of the one
+ configured via local configuration.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>-c</opt> <arg>FILE</arg></p>
+
+ <optdesc><p>Only valid for <opt>-e</opt>: export the PulseAudio
+ authentication cookie stored in the specified file to the X11
+ display instead of the one stored in <file>~/.pulse-cookie</file>.</p></optdesc>
+ </option>
+
+ </options>
+
+ <section name="Authors">
+ <p>The PulseAudio Developers &lt;@PACKAGE_BUGREPORT@&gt;; PulseAudio is available from <url href="@PACKAGE_URL@"/></p>
+ </section>
+
+ <section name="See also">
+ <p>
+ <manref name="pulseaudio" section="1"/>, <manref name="xprop" section="1"/>
+ </p>
+ </section>
+
+</manpage>
diff --git a/man/pulse-client.conf.5.xml.in b/man/pulse-client.conf.5.xml.in
new file mode 100644
index 00000000..dbf8dc0b
--- /dev/null
+++ b/man/pulse-client.conf.5.xml.in
@@ -0,0 +1,115 @@
+<?xml version="1.0"?><!--*-nxml-*-->
+<!DOCTYPE manpage SYSTEM "xmltoman.dtd">
+<?xml-stylesheet type="text/xsl" href="xmltoman.xsl" ?>
+
+<!-- $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.
+-->
+
+<manpage name="pulse-client.conf" section="5" desc="PulseAudio client configuration file">
+
+ <synopsis>
+ <p><file>~/.pulse/client.conf</file></p>
+
+ <p><file>@pulseconfdir@/client.conf</file></p>
+ </synopsis>
+
+ <description>
+ <p>The PulseAudio client library reads configuration directives from
+ a file <file>~/.pulse/client.conf</file> on startup, and when that
+ file doesn't exist from
+ <file>@pulseconfdir@/client.conf</file>.</p>
+
+ <p>The configuration file is a simple collection of variable
+ declarations. If the configuration file parser encounters either ;
+ or # for it ignores the rest of the line until its end.</p>
+
+ <p>For the settings that take a boolean argument, the values
+ <opt>true</opt>, <opt>yes</opt>, <opt>on</opt> and <opt>1</opt>
+ are equivalent, resp. <opt>false</opt>, <opt>no</opt>,
+ <opt>off</opt>, <opt>0</opt>.</p>
+
+ </description>
+
+ <section name="Directives">
+
+ <option>
+ <p><opt>default-sink=</opt> The default sink to connect to. If
+ specified overwrites the setting in the daemon. The environment
+ variable <opt>$PULSE_SINK</opt> however takes precedence.</p>
+ </option>
+
+ <option>
+ <p><opt>default-source=</opt> The default source to connect
+ to. If specified overwrites the setting in the daemon. The
+ environment variable <opt>$PULSE_SOURCE</opt> however takes
+ precedence.</p>
+ </option>
+
+ <option>
+ <p><opt>default-server=</opt> The default sever to connect
+ to. The environment variable <opt>$PULSE_SERVER</opt> takes
+ precedence.</p>
+ </option>
+
+ <option>
+ <p><opt>autospawn=</opt> Autospawn a PulseAudio daemon when
+ needed. Takes a boolean value, defaults to "no".</p>
+ </option>
+
+ <option>
+ <p><opt>daemon-binary=</opt> Path to the PulseAudio daemon to
+ run when autospawning. Defaults to a path configured at compile
+ time.</p>
+ </option>
+
+ <option>
+ <p><opt>extra-arguments=</opt> Extra arguments to pass to the
+ PulseAudio daemon when autospawning. Defaults to
+ <opt>--log-target=syslog --exit-idle-time=5</opt>
+ </p>
+ </option>
+
+ <option>
+ <p><opt>cookie-file=</opt> Specify the path to the PulseAudio
+ authentication cookie. Defaults to
+ <file>~/.pulse-cookie</file>.</p>
+ </option>
+
+ <option>
+ <p><opt>disable-shm=</opt> Disable data transfer via POSIX
+ shared memory. Takes a boolean argument, defaults to
+ <opt>no</opt>.</p>
+ </option>
+
+ </section>
+
+ <section name="Authors">
+ <p>The PulseAudio Developers &lt;@PACKAGE_BUGREPORT@&gt;;
+ PulseAudio is available from <url href="@PACKAGE_URL@"/></p>
+ </section>
+
+ <section name="See also">
+ <p>
+ <manref name="pulse-daemon.conf" section="5"/>, <manref name="pulseaudio" section="1"/>
+ </p>
+ </section>
+
+</manpage>
diff --git a/man/pulse-daemon.conf.5.xml.in b/man/pulse-daemon.conf.5.xml.in
new file mode 100644
index 00000000..2768e24e
--- /dev/null
+++ b/man/pulse-daemon.conf.5.xml.in
@@ -0,0 +1,372 @@
+<?xml version="1.0"?><!--*-nxml-*-->
+<!DOCTYPE manpage SYSTEM "xmltoman.dtd">
+<?xml-stylesheet type="text/xsl" href="xmltoman.xsl" ?>
+
+<!-- $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.
+-->
+
+<manpage name="pulse-daemon.conf" section="5" desc="PulseAudio daemon configuration file">
+
+ <synopsis>
+ <p><file>~/.pulse/daemon.conf</file></p>
+
+ <p><file>@pulseconfdir@/daemon.conf</file></p>
+ </synopsis>
+
+ <description>
+ <p>The PulseAudio sound server reads configuration directives from
+ a file <file>~/.pulse/daemon.conf</file> on startup, and when that
+ file doesn't exist from
+ <file>@pulseconfdir@/daemon.conf</file>. Please note that the
+ server also reads a configuration script on startup
+ <file>default.pa</file> which also contains runtime configuration
+ directives.</p>
+
+ <p>The configuration file is a simple collection of variable
+ declarations. If the configuration file parser encounters either ;
+ or # for it ignores the rest of the line until its end.</p>
+
+ <p>For the settings that take a boolean argument, the values
+ <opt>true</opt>, <opt>yes</opt>, <opt>on</opt> and <opt>1</opt>
+ are equivalent, resp. <opt>false</opt>, <opt>no</opt>,
+ <opt>off</opt>, <opt>0</opt>.</p>
+
+ </description>
+
+ <section name="General Directives">
+
+ <option>
+ <p><opt>daemonize= </opt> Daemonize after startup. Takes a
+ boolean value, defaults to "no". The <opt>--daemonize</opt>
+ command line option takes precedence.</p>
+ </option>
+
+ <option>
+ <p><opt>fail=</opt> Fail to start up if any of the directives
+ in the configuration script <file>default.pa</file>
+ fail. Takes a boolean argument, defaults to "yes". The <opt>--fail</opt> command line
+ option takes precedence.</p>
+ </option>
+
+ <option>
+ <p><opt>disallow-module-loading=</opt> Disallow module loading
+ after startup. This is a security feature that makes sure that
+ no further modules may be loaded into the PulseAudio server
+ after startup completed. It is recommended to enable this when
+ <opt>system-instance</opt> is enabled. Please note that certain
+ features like automatic hot-plug support will not work if this
+ option is enabled. Takes a boolean argument, defaults to
+ <opt>no</opt>. The <opt>--disallow-module-loading</opt> command line
+ option takes precedence.</p>
+ </option>
+
+ <option>
+ <p><opt>resample-method=</opt> The resampling algorithm to
+ use. Use one of <opt>src-sinc-best-quality</opt>,
+ <opt>src-sinc-medium-quality</opt>, <opt>src-sinc-fastest</opt>,
+ <opt>src-zero-order-hold</opt>, <opt>src-linear</opt>,
+ <opt>trivial</opt>, <opt>speex-float-N</opt>,
+ <opt>speex-fixed-N</opt>, <opt>ffmpeg</opt>. See the
+ documentation of libsamplerate for an explanation for the
+ different src- methods. The method <opt>trivial</opt> is the most basic
+ algorithm implemented. If you're tight on CPU consider using
+ this. On the other hand it has the worst quality of them
+ all. The Speex resamplers take an integer quality setting in the
+ range 0..9 (bad...good). They exist in two flavours: <opt>fixed</opt> and
+ <opt>float</opt>. The former uses fixed point numbers, the latter relies on
+ floating point numbers. On most desktop CPUs the float point
+ resmampler is a lot faster, and it also offers slightly better
+ quality. See the output of <opt>dump-resample-methods</opt> for
+ a complete list of all available resamplers. Defaults to
+ <opt>speex-float-3</opt>. The <opt>--resample-method</opt>
+ command line option takes precedence. Note that some modules
+ overwrite or allow overwriting of the resampler to use.</p>
+ </option>
+
+ <option>
+ <p><opt>disable-remixing=</opt> Never upmix or downmix channels
+ to different channel maps. Instead, do a simple name-based
+ matching only.</p>
+ </option>
+
+ <option>
+ <p><opt>use-pid-file=</opt> Create a PID file in
+ <file>/tmp/pulse-$USER/pid</file>. Of this is enabled you may
+ use commands like <opt>--kill</opt> or <opt>--check</opt>. If
+ you are planning to start more than one PulseAudio process per
+ user, you better disable this option since it effectively
+ disables multiple instances. Takes a boolean argument, defaults
+ to <opt>yes</opt>. The <opt>--no-cpu-limit</opt> command line
+ option takes precedence.</p>
+ </option>
+
+ <option>
+ <p><opt>no-cpu-limit=</opt> Do not install the CPU load limiter,
+ even on platforms where it is supported. This option is useful
+ when debugging/profiling PulseAudio to disable disturbing
+ SIGXCPU signals. Takes a boolean argument, defaults to <opt>no</opt>. The
+ <opt>--no-cpu-limit</opt> command line argument takes
+ precedence.</p>
+ </option>
+
+
+ <option>
+ <p><opt>system-instance=</opt> Run the daemon as system-wide
+ instance, requires root priviliges. Takes a boolean argument,
+ defaults to <opt>no</opt>. The <opt>--system</opt> command line
+ argument takes precedence.</p>
+ </option>
+
+
+ <option>
+ <p><opt>disable-shm=</opt> Disable data transfer via POSIX
+ shared memory. Takes a boolean argument, defaults to
+ <opt>no</opt>. The <opt>--disable-shm</opt> command line
+ argument takes precedence.</p>
+ </option>
+
+ </section>
+
+ <section name="Scheduling">
+
+ <option>
+ <p><opt>high-priority=</opt> Renice the daemon after startup to
+ become a high-priority process. This a good idea if you
+ experience drop-outs during playback. However, this is a certain
+ security issue, since it works when called SUID root only, or
+ RLIMIT_NICE is used. root is dropped immediately after gaining
+ the nice level on startup, thus it is presumably safe. See
+ <manref section="1" name="pulseaudio"/> for more
+ information. Takes a boolean argument, defaults to "yes". The <opt>--high-priority</opt>
+ command line option takes precedence.</p>
+ </option>
+
+ <option>
+ <p><opt>realtime-scheduling=</opt> Try to acquire SCHED_FIFO
+ scheduling for the IO threads. The same security concerns as
+ mentioned above apply. However, if PA enters an endless loop,
+ realtime scheduling causes a system lockup. Thus, realtime
+ scheduling should only be enabled on trusted machines for
+ now. Please not that only the IO threads of PulseAudio are made
+ real-time. The controlling thread is left a normally scheduled
+ thread. Thus enabling the high-priority option is orthogonal.
+ See <manref section="1" name="pulseaudio"/> for more
+ information. Takes a boolean argument, defaults to "no". The
+ <opt>--realtime</opt> command line option takes precedence.</p>
+ </option>
+
+ <option>
+ <p><opt>realtime-priority=</opt> The realtime priority to
+ acquire, if <opt>realtime-scheduling</opt> is enabled. Note: JACK uses 10
+ by default, 9 for clients. Thus it is recommended to choose the
+ PulseAudio real-time priorities lower. Some PulseAudio threads
+ might choose a priority a little lower or higher than the
+ specified value. Defaults to "5".</p>
+ </option>
+
+ <option>
+ <p><opt>nice-level=</opt> The nice level to acquire for the
+ daemon, if <opt>high-priority</opt> is enabled. Note: on some
+ distributions X11 uses -10 by default. Defaults to -11.</p>
+ </option>
+
+ </section>
+
+ <section name="Idle Times">
+
+ <option>
+ <p><opt>exit-idle-time=</opt> Terminate the daemon after the
+ last client quit and this time in seconds passed. Use a negative value to
+ disable this feature. Defaults to -1. The
+ <opt>--exit-idle-time</opt> command line option takes
+ precedence.</p>
+ </option>
+
+ <option>
+ <p><opt>module-idle-time=</opt> Unload autoloaded modules after
+ being idle for this time in seconds. Defaults to 20. The
+ <opt>--module-idle-time</opt> command line option takes
+ precedence.</p>
+ </option>
+
+ <option>
+ <p><opt>scache-idle-time=</opt> Unload autoloaded sample cache
+ entries after being idle for this time in seconds. Defaults to
+ 20. The <opt>--scache-idle-time</opt> command line option takes
+ precedence.</p>
+ </option>
+
+ </section>
+
+ <section name="Paths">
+
+ <option>
+ <p><opt>dl-search-path=</opt> The path were to look for dynamic
+ shared objects (DSOs/plugins). You may specify more than one
+ path seperated by colons. The default path depends on compile
+ time settings. The <opt>--dl-search-path</opt> command line
+ option takes precedence. </p>
+ </option>
+
+ <option>
+ <p><opt>default-script-file=</opt> The default configuration
+ script file to load. Specify an empty string for not loading a
+ default script file. The default behaviour is to load
+ <file>~/.pulse/default.pa</file>, and if that file does not
+ exist fall back to the system wide installed version
+ <file>@pulseconfdir@/default.pa</file>. If <opt>-n</opt> is
+ passed on the command line the default configuration script is
+ ignored.</p>
+ </option>
+
+ </section>
+
+ <section name="Logging">
+
+ <option>
+ <p><opt>log-target=</opt> The default log target. Use either
+ <opt>stderr</opt>, <opt>syslog</opt> or <opt>auto</opt>. The
+ latter is equivalent to <opt>sylog</opt> in case
+ <opt>daemonize</opt> is enabled, otherwise to
+ <opt>stderr</opt>. Defaults to <opt>auto</opt>. The
+ <opt>--log-target</opt> command line option takes
+ precedence.</p>
+ </option>
+
+ <option>
+ <p><opt>log-level=</opt> Log level, one of <opt>debug</opt>,
+ <opt>info</opt>, <opt>notice</opt>, <opt>warning</opt>,
+ <opt>error</opt>. Log messages with a lower log level than
+ specified here are not logged. Defaults to
+ <opt>notice</opt>. The <opt>--log-level</opt> command line
+ option takes precedence. The <opt>-v</opt> command line option
+ might alter this setting.</p>
+ </option>
+
+ </section>
+
+ <section name="Resource Limits">
+
+ <p>See <manref name="getrlimit" section="2"/> for
+ more information. Set to -1 if PulseAudio shall not touch the resource
+ limit. Not all resource limits are available on all operating
+ systems.</p>
+
+ <option>
+ <p><opt>rlimit-as</opt> Defaults to -1.</p>
+ </option>
+ <option>
+ <p><opt>rlimit-core</opt> Defaults to -1.</p>
+ </option>
+ <option>
+ <p><opt>rlimit-data</opt> Defaults to -1.</p>
+ </option>
+ <option>
+ <p><opt>rlimit-fsize</opt> Defaults to -1.</p>
+ </option>
+ <option>
+ <p><opt>rlimit-nofile</opt> Defaults to 256.</p>
+ </option>
+ <option>
+ <p><opt>rlimit-stack</opt> Defaults to -1.</p>
+ </option>
+ <option>
+ <p><opt>rlimit-nproc</opt> Defaults to -1.</p>
+ </option>
+ <option>
+ <p><opt>rlimit-memlock</opt> Defaults to 16 KiB. Please note
+ that the JACK client libraries may require more locked
+ memory.</p>
+ </option>
+ <option>
+ <p><opt>rlimit-nice</opt> Defaults to 31. Please make sure that
+ the default nice level as configured with <opt>nice-level</opt>
+ fits in this resource limit, if <opt>high-priority</opt> is
+ enabled.</p>
+ </option>
+ <option>
+ <p><opt>rlimit-rtprio</opt> Defaults to 9. Please make sure that
+ the default real-time priority level as configured with
+ <opt>realtime-priority=</opt> fits in this resource limit, if
+ <opt>realtime-scheduling</opt> is enabled. The JACK client
+ libraries require a real-time prority of 9 by default. </p>
+ </option>
+
+ </section>
+
+ <section name="Default Device Settings">
+
+ <p>Most drivers try to open the audio device with these settings
+ and then fall back to lower settings. The default settings are CD
+ quality: 16bit native endian, 2 channels, 44100 Hz sampling.</p>
+
+ <option>
+ <p><opt>default-sample-format=</opt> The default sampling
+ format. Specify one of <opt>u8</opt>, <opt>s16le</opt>,
+ <opt>s16be</opt>, <opt>float32le</opt>, <opt>float32be</opt>,
+ <opt>ulaw</opt>, <opt>alaw</opt>. Depending on the endianess of
+ the CPU the formats <opt>s16ne</opt>, <opt>s16re</opt>,
+ <opt>float32ne</opt>, <opt>float32re</opt> (for native,
+ resp. reverse endian) are available as aliases.</p>
+ </option>
+
+ <option>
+ <p><opt>default-sample-rate=</opt> The default sample frequency.</p>
+ </option>
+
+ <option>
+ <p><opt>default-sample-channels</opt> The default number of channels.</p>
+ </option>
+
+ </section>
+
+ <section name="Default Fragment Settings">
+
+ <p>Some hardware drivers require the hardware playback buffer to
+ be subdivided into several fragments. It is possible to change
+ these buffer metrics for machines with high scheduling
+ latencies. Not all possible values that may be configured here are
+ available in all hardware. The driver will to find the nearest
+ setting supported.</p>
+
+ <option>
+ <p><opt>default-fragments=</opt> The default number of
+ fragments. Defaults to 4.</p>
+ </option>
+ <option>
+ <p><opt>default-fragment-size-msec=</opt>The duration of a
+ single fragment. Defaults to 25ms (i.e. the total buffer is thus
+ 100ms long).</p>
+ </option>
+
+ </section>
+
+ <section name="Authors">
+ <p>The PulseAudio Developers &lt;@PACKAGE_BUGREPORT@&gt;; PulseAudio is available from <url href="@PACKAGE_URL@"/></p>
+ </section>
+
+ <section name="See also">
+ <p>
+ <manref name="pulse-client.conf" section="5"/>, <manref name="default.pa" section="5"/>, <manref name="pulseaudio" section="1"/>, <manref name="pacmd" section="1"/>
+ </p>
+ </section>
+
+</manpage>
diff --git a/man/pulseaudio.1.xml.in b/man/pulseaudio.1.xml.in
new file mode 100644
index 00000000..1f53a60b
--- /dev/null
+++ b/man/pulseaudio.1.xml.in
@@ -0,0 +1,446 @@
+<?xml version="1.0"?><!--*-nxml-*-->
+<!DOCTYPE manpage SYSTEM "xmltoman.dtd">
+<?xml-stylesheet type="text/xsl" href="xmltoman.xsl" ?>
+
+<!-- $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.
+-->
+
+<manpage name="pulseaudio" section="1" desc="The PulseAudio Sound System">
+
+ <synopsis>
+ <cmd>pulseaudio [<arg>options</arg>]</cmd>
+ <cmd>pulseaudio <opt>--help</opt></cmd>
+ <cmd>pulseaudio <opt>--version</opt></cmd>
+ <cmd>pulseaudio <opt>--dump-conf</opt></cmd>
+ <cmd>pulseaudio <opt>--dump-modules</opt></cmd>
+ <cmd>pulseaudio <opt>--dump-resample-methods</opt></cmd>
+ <cmd>pulseaudio <opt>--cleanup-shm</opt></cmd>
+ <cmd>pulseaudio <opt>--kill</opt></cmd>
+ <cmd>pulseaudio <opt>--check</opt></cmd>
+ </synopsis>
+
+ <description>
+ <p>PulseAudio is a networked low-latency sound server for Linux, POSIX and Windows systems.</p>
+ </description>
+
+ <options>
+
+ <option>
+ <p><opt>-h | --help</opt></p>
+
+ <optdesc><p>Show help.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>--version</opt></p>
+
+ <optdesc><p>Show version information.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>--dump-conf</opt></p>
+
+ <optdesc><p>Load the daemon configuration file
+ <file>daemon.conf</file> (see below), parse remaining
+ configuration options on the command line and dump the resulting
+ daemon configuration, in a format that is compatible with
+ <file>daemon.conf</file>.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>--dump-modules</opt></p>
+
+ <optdesc><p>List available loadable modules. Combine with
+ <opt>-v</opt> for a more elaborate listing.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>--dump-resampe-methods</opt></p>
+ <optdesc><p>List available audio resamplers.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>--cleanup-shm</opt></p>
+
+ <optdesc><p>Identify stale PulseAudio POSIX shared memory
+ segments in <file>/dev/shm</file> and remove them if
+ possible. This is done implicitly whenever a new daemon starts
+ up or a client tries to connect to a daemon. It should normally
+ not be necessary to issue this command by hand. Only available
+ on systems with POSIX shared memory segments implemented via a
+ virtual file system mounted to <file>/dev/shm</file>
+ (e.g. Linux).</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>-k | --kill</opt></p>
+
+ <optdesc><p>Kill an already running PulseAudio daemon of the
+ calling user (Equivalent to sending a SIGTERM).</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>--check</opt></p>
+
+ <optdesc><p>Return 0 as return code when the PulseAudio daemon
+ is already running for the calling user.</p></optdesc>
+ </option>
+
+
+ <option>
+ <p><opt>--system</opt><arg>[=BOOL]</arg></p>
+
+ <optdesc><p>Run as system-wide instance instead of
+ per-user. Please not that this disables certain features of
+ PulseAudio and is generally not recommended unless the system
+ knows no local users (e.g. is a thin client). This feature needs
+ special configuration and a dedicated UNIX user set up. It is
+ highly recommended to combine this with
+ <opt>--disallow-module-loading</opt> (see below).</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>-D | --daemon</opt><arg>[=BOOL]</arg></p>
+
+ <optdesc><p>Daemonize after startup, i.e. detach from the
+ terminal.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>--fail</opt><arg>[=BOOL]</arg></p>
+
+ <optdesc><p>Fail startup when any of the commands specified in
+ the startup script <file>default.pa</file> (see below)
+ fails.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>--high-priority</opt><arg>[=BOOL]</arg></p>
+
+ <optdesc><p>Try to acquire a high Unix nice level. This will
+ only succeed if the calling user has a non-zero RLIMIT_NICE
+ resource limit set (on systems that support this), or we're
+ called SUID root (see below), or we are configure to be run as
+ system daemon (see <arg>--system</arg> above). It is recommended
+ to enable this, since it is only a negligible security risk (see
+ below).</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>--realtime</opt><arg>[=BOOL]</arg></p>
+
+ <optdesc><p>Try to acquire a real-time scheduling for
+ PulseAudio's I/O threads. This will only succeed if the calling
+ user has a non-zero RLIMIT_RTPRIO resource limit set (on systems
+ that support this), or we're called SUID root (see below), or we
+ are configure to be run as system daemon (see
+ <arg>--system</arg> above). It is recommended to enable this
+ only for trusted users, since it is a major security risk (see
+ below).</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>--disallow-module-loading</opt><arg>[=BOOL]</arg></p>
+
+ <optdesc><p>Disallow module loading after startup. This is a
+ security feature since it disallows additional module loading
+ during runtime and on user request. It is highly recommended
+ when <arg>--system</arg> is used (see above). Note however, that
+ this breaks certain features like automatic module loading on hot
+ plug.</p></optdesc>
+
+ </option>
+
+ <option>
+ <p><opt>--exit-idle-time</opt><arg>=SECS</arg></p>
+
+ <optdesc><p>Terminate the daemon when idle and the specified
+ number of seconds passed.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>--module-idle-time</opt><arg>=SECS</arg></p>
+
+ <optdesc><p>Unload autoloaded modules when idle and the
+ specified number of seconds passed.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>--scache-idle-time</opt><arg>=SECS</arg></p>
+
+ <optdesc><p>Unload autoloaded samples from the cache when the
+ haven't been used for the specified number of
+ seconds.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>--log-level</opt><arg>[=LEVEL]</arg></p>
+
+ <optdesc><p>If an argument is passed, set the log level to the
+ specified value, otherwise increase the configured verbosity
+ level by one. The log levels are numerical from 0 to 4,
+ corresponding to <arg>error</arg>, <arg>warn</arg>,
+ <arg>notice</arg>, <arg>info</arg>, <arg>debug</arg>. Default
+ log level is <arg>notice</arg>, i.e. all log messages with lower
+ log levels are printed: <arg>error</arg>, <arg>warn</arg>,
+ <arg>notice</arg>.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>-v</opt></p>
+
+ <optdesc><p>Increase the configured verbosity level by one (see
+ <opt>--log-level</opt> above). Specify multiple times to
+ increase log level multiple times.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>--log-target</opt><arg>={auto,syslog,stderr}</arg></p>
+
+ <optdesc><p>Specify the log target. If set to <arg>auto</arg>
+ (which is the default), then logging is directed to syslog when
+ <opt>--daemonize</opt> is passed, otherwise to
+ STDERR.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>--p | --dl-search-path</opt><arg>=PATH</arg></p>
+
+ <optdesc><p>Set the search path for dynamic shared objects
+ (plugins).</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>--resample-method</opt><arg>=METHOD</arg></p>
+
+ <optdesc><p>Use the specified resampler by default (See
+ <opt>--dump-resample-methods</opt> above for possible
+ values).</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>--use-pid-file</opt><arg>[=BOOL]</arg></p>
+
+ <optdesc><p>Create a PID file. If this options is disabled it is possible to run multiple sound servers per user.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>--no-cpu-limit</opt><arg>[=BOOL]</arg></p>
+
+ <optdesc><p>Do not install CPU load limiter on platforms that
+ support it. By default, PulseAudio will terminate itself when it
+ notices that it takes up too much CPU time. This is useful as a
+ protection against system lockups when real-time scheduling is
+ used (see below). Disabling this meachnism is useful when
+ debugging PulseAudio with tools like <manref name="valgrind"
+ section="1"/> which slow down execution.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>--disable-shm</opt><arg>[=BOOL]</arg></p>
+
+ <optdesc><p>PulseAudio clients and the server can exchange audio
+ data via POSIX shared memory segments (on systems that support
+ this). If disabled PulseAudio will communicate exclusively over
+ sockets. Please note that data transfer via shared memory
+ segments is always disabled when PulseAudio is running with
+ <opt>--system</opt> enabled (see above).</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>-L | --load</opt><arg>="MODULE ARGUMENTS"</arg></p>
+
+ <optdesc><p>Load the specified plugin module with the specified
+ arguments.</p></optdesc>
+ </option>
+
+ <option>
+ <p><opt>-F | --file</opt><arg>=FILENAME</arg></p>
+
+ <optdesc><p>Run the specified script on startup. May be
+ specified multiple times to specify multiple scripts to be run
+ in order. Combine with <opt>-n</opt> to disable loading of the
+ default script <file>default.pa</file> (see below).</p></optdesc>
+ </option>
+ <option>
+ <p><opt>-C</opt></p>
+
+ <optdesc><p>Open a command interpreter on STDIN/STDOUT after
+ startup. This may be used to configure PulseAudio dynamically
+ during runtime. Equivalent to
+ <opt>--load</opt><arg>=module-cli</arg>.</p></optdesc>
+ </option>
+ <option>
+ <p><opt>-n</opt></p>
+
+ <optdesc><p>Don't load default script file
+ <file>default.pa</file> (see below) on startup. Useful in
+ conjunction with <opt>-C</opt> or
+ <opt>--file</opt>.</p></optdesc>
+ </option>
+
+
+ </options>
+
+ <section name="Files">
+
+ <p><file>~/.pulse/daemon.conf</file>,
+ <file>@pulseconfdir@/daemon.conf</file>: configuration settings
+ for the PulseAudio daemon. If the version in the user's home
+ directory does not exist the global configuration file is
+ loaded. See <manref name="pulse-daemon.conf" section="5"/> for
+ more information.</p>
+
+ <p><file>~/.pulse/default.pa</file>,
+ <file>@pulseconfdir@/default.pa</file>: the default configuration
+ script to execute when the PulseAudio daemon is started. If the
+ version in the user's home directory does not exist the global
+ configuration script is loaded. See <manref name="default.pa"
+ section="5"/> for more information.</p>
+
+ <p><file>~/.pulse/client.conf</file>,
+ <file>@pulseconfdir@/client.conf</file>: configuration settings
+ for PulseAudio client applications. If the version in the user's
+ home directory does not exist the global configuration file is
+ loaded. See <manref name="pulse-client.conf" section="5"/> for
+ more information.</p>
+
+ </section>
+
+ <section name="Signals">
+
+ <p><arg>SIGINT, SIGTERM</arg>: the PulseAudio daemon will shut
+ down (Same as <opt>--kill</opt>).</p>
+
+ <p><arg>SIGHUP</arg>: dump a long status report to STDOUT or
+ syslog, depending on the configuration.</p>
+
+ <p><arg>SIGUSR1</arg>: load module-cli, allowing runtime
+ reconfiguration via STDIN/STDOUT.</p>
+
+ <p><arg>SIGUSR2</arg>: load module-cli-protocol-unix, allowing
+ runtime reconfiguration via a AF_UNIX socket. See <manref
+ name="pacmd" section="1"/> for more information.</p>
+
+ </section>
+
+ <section name="UNIX Groups and users">
+
+ <p>Group <arg>pulse-rt</arg>: if the PulseAudio binary is marked
+ SUID root, then membership of the calling user in this group
+ decides whether real-time and/or high-priority scheduling is
+ enabled. Please note that enabling real-time scheduling is a
+ security risk (see below).</p>
+
+ <p>Group <arg>pulse-access</arg>: if PulseAudio is running as a system
+ daemon (see <opt>--system</opt> above) access is granted to
+ members of this group when they connect via AF_UNIX sockets. If
+ PulseAudio is running as a user daemon this group has no
+ meaning.</p>
+
+ <p>User <arg>pulse</arg>, group <arg>pulse</arg>: if PulseAudio is running as a system
+ daemon (see <opt>--system</opt> above) and is started as root the
+ daemon will drop priviliges and become a normal user process using
+ this user and group. If PulseAudio is running as a user daemon
+ this user and group has no meaning.</p>
+ </section>
+
+ <section name="Real-time and high-priority scheduling">
+ <p>To minimize the risk of drop-outs during playback it is
+ recommended to run PulseAudio with real-time scheduling if the
+ underlying platform supports it. This decouples the scheduling
+ latency of the PulseAudio daemon from the system load and is thus
+ the best way to make sure that PulseAudio always gets CPU time
+ when it needs it to refill the hardware playback
+ buffers. Unfortunately this is a security risk on most systems,
+ since PulseAudio runs as user process, and giving realtime
+ scheduling priviliges to a user process always comes with the risk
+ that the user misuses it to lock up the system -- which is
+ possible since making a process real-time effectively disables
+ preemption.</p>
+
+ <p>To minimize the risk PulseAudio by default does not enable
+ real-time scheduling. It is however recommended to enable it
+ on trusted systems. To do that start PulseAudio with
+ <opt>--realtime</opt> (see above) or enabled the appropriate option in
+ <file>daemon.conf</file>. Since acquiring realtime scheduling is a
+ priviliged operation on most systems, some special changes to the
+ system configuration need to be made to allow them to the calling
+ user. Two options are available:</p>
+
+ <p>On newer Linux systems the system resource limit RLIMIT_RTPRIO
+ (see <manref name="setrlimit" section="2"/> for more information)
+ can be used to allow specific users to acquire real-time
+ scheduling. This can be configured in
+ <file>/etc/security/limits.conf</file>, a resource limit of 9 is recommended.</p>
+
+ <p>Alternatively, the SUID root bit can be set for the PulseAudio
+ binary. Then, the daemon will drop root priviliges immediately on
+ startup, however retain the CAP_NICE capability (on systems that
+ support it), but only if the calling user is a member of the
+ <arg>pulse-rt</arg> group (see above). For all other users all
+ capababilities are dropped immediately. The advantage of this
+ solution is that the real-time priviliges are only granted to the
+ PulseAudio daemon -- not to all the user's processes.</p>
+
+ <p>Alternatively, if the risk of locking up the machine is
+ considered too big to enable real-time scheduling, high-priority
+ scheduling can be enabled instead (i.e. negative nice level). This
+ can be enabled by passing <opt>--high-priority</opt> (see above)
+ when starting PulseAudio and may also be enabled with the
+ approriate option in <file>daemon.conf</file>. Negative nice
+ levels can only be enabled when the appropriate resource limit
+ RLIMIT_NICE is set (see <manref name="setrlimit" section="2"/> for
+ more information), possibly configured in
+ <file>/etc/security/limits.conf</file>. A resource limit of 31
+ (corresponding with nice level -11) is recommended.</p>
+ </section>
+
+ <section name="Environment variables">
+
+ <p>The PulseAudio client libraries check for the existance of the
+ following environment variables and change their local configuration accordingly:</p>
+
+ <p><arg>$PULSE_SERVER</arg>: the server string specifying the server to connect to when a client asks for a sound server connection and doesn't explicitly ask for a specific server.</p>
+
+ <p><arg>$PULSE_SINK</arg>: the symbolic name of the sink to connect to when a client creates a playback stream and doesn't explicitly ask for a specific sink.</p>
+
+ <p><arg>$PULSE_SOURCE</arg>: the symbolic name of the source to connect to when a client creates a record stream and doesn't explicitly ask for a specific source.</p>
+
+ <p><arg>$PULSE_BINARY</arg>: path of PulseAudio executable to run when server auto-spawning is used.</p>
+
+ <p><arg>$PULSE_CLIENTCONFIG</arg>: path of file that shall be read instead of <file>client.conf</file> (see above) for client configuration.</p>
+
+ <p>These environment settings take precedence -- if set -- over the configuration settings from <file>client.conf</file> (see above).</p>
+
+ </section>
+
+ <section name="Authors">
+ <p>The PulseAudio Developers &lt;@PACKAGE_BUGREPORT@&gt;; PulseAudio is available from <url href="@PACKAGE_URL@"/></p>
+ </section>
+
+ <section name="See also">
+ <p>
+ <manref name="pulse-daemon.conf" section="5"/>, <manref name="default.pa" section="5"/>, <manref name="pulse-client.conf" section="5"/>, <manref name="pacmd" section="1"/>
+ </p>
+ </section>
+
+</manpage>
diff --git a/man/xmltoman.css b/man/xmltoman.css
new file mode 100644
index 00000000..579a4fdc
--- /dev/null
+++ b/man/xmltoman.css
@@ -0,0 +1,30 @@
+/* $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 General Public License as published by the Free
+ Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with PulseAudio; if not, write to the Free Software Foundation,
+ Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+***/
+
+body { color: black; background-color: white; }
+a:link, a:visited { color: #900000; }
+h1 { text-transform:uppercase; font-size: 18pt; }
+p { margin-left:1cm; margin-right:1cm; }
+.cmd { font-family:monospace; }
+.file { font-family:monospace; }
+.arg { text-transform:uppercase; font-family:monospace; font-style: italic; }
+.opt { font-family:monospace; font-weight: bold; }
+.manref { font-family:monospace; }
+.option .optdesc { margin-left:2cm; }
diff --git a/man/xmltoman.dtd b/man/xmltoman.dtd
new file mode 100644
index 00000000..121e62c8
--- /dev/null
+++ b/man/xmltoman.dtd
@@ -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 General Public License as published by the Free
+ Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with PulseAudio; if not, write to the Free Software Foundation,
+ Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+-->
+
+<!ELEMENT manpage (synopsis | description | section | options | seealso)*>
+<!ATTLIST manpage name CDATA #REQUIRED section CDATA #REQUIRED desc CDATA #IMPLIED>
+<!ELEMENT arg (#PCDATA)>
+<!ELEMENT p (#PCDATA | arg | url | manref | opt | file )*>
+<!ELEMENT synopsis (cmd | p)+>
+<!ELEMENT description (p)+>
+<!ELEMENT section (p | option)*>
+<!ATTLIST section name CDATA #REQUIRED>
+<!ELEMENT option (#PCDATA | p | optdesc)*>
+<!ELEMENT optdesc (#PCDATA | p )*>
+<!ELEMENT cmd (#PCDATA | arg | opt)*>
+<!ELEMENT options (p | option)*>
+<!ELEMENT seealso (p)*>
+<!ELEMENT opt (#PCDATA)>
+<!ELEMENT file (#PCDATA)>
+<!ELEMENT manref EMPTY>
+<!ATTLIST manref name CDATA #REQUIRED section CDATA #REQUIRED href CDATA #IMPLIED>
+<!ELEMENT url EMPTY>
+<!ATTLIST url href CDATA #REQUIRED>
diff --git a/man/xmltoman.xsl b/man/xmltoman.xsl
new file mode 100644
index 00000000..8d4ca212
--- /dev/null
+++ b/man/xmltoman.xsl
@@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="iso-8859-15"?>
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/1999/xhtml">
+
+<!--
+ This file is part of PulseAudio.
+
+ PulseAudio is free software; you can redistribute it and/or modify it under
+ the terms of the GNU General Public License as published by the Free
+ Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with PulseAudio; if not, write to the Free Software Foundation,
+ Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+-->
+
+<!-- $Id$ -->
+
+<xsl:output method="xml" version="1.0" encoding="iso-8859-15" doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN" doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" indent="yes"/>
+
+<xsl:template match="/manpage">
+
+ <html>
+
+ <head>
+ <title><xsl:value-of select="@name"/>(<xsl:value-of select="@section"/>)</title>
+ <style type="text/css">
+ body { color: black; background-color: white; }
+ a:link, a:visited { color: #900000; }
+ h1 { text-transform:uppercase; font-size: 18pt; }
+ p { margin-left:1cm; margin-right:1cm; }
+ .cmd { font-family:monospace; }
+ .file { font-family:monospace; }
+ .arg { text-transform:uppercase; font-family:monospace; font-style: italic; }
+ .opt { font-family:monospace; font-weight: bold; }
+ .manref { font-family:monospace; }
+ .option .optdesc { margin-left:2cm; }
+ </style>
+ </head>
+ <body>
+ <h1>Name</h1>
+ <p><xsl:value-of select="@name"/>
+ <xsl:if test="string-length(@desc) &gt; 0"> - <xsl:value-of select="@desc"/></xsl:if>
+ </p>
+ <xsl:apply-templates />
+ </body>
+ </html>
+</xsl:template>
+
+<xsl:template match="p">
+ <p>
+ <xsl:apply-templates/>
+ </p>
+</xsl:template>
+
+<xsl:template match="cmd">
+ <p class="cmd">
+ <xsl:apply-templates/>
+ </p>
+</xsl:template>
+
+<xsl:template match="arg">
+ <span class="arg"><xsl:apply-templates/></span>
+</xsl:template>
+
+<xsl:template match="opt">
+ <span class="opt"><xsl:apply-templates/></span>
+</xsl:template>
+
+<xsl:template match="file">
+ <span class="file"><xsl:apply-templates/></span>
+</xsl:template>
+
+<xsl:template match="optdesc">
+ <div class="optdesc">
+ <xsl:apply-templates/>
+ </div>
+</xsl:template>
+
+<xsl:template match="synopsis">
+ <h1>Synopsis</h1>
+ <xsl:apply-templates/>
+</xsl:template>
+
+<xsl:template match="seealso">
+ <h1>Synopsis</h1>
+ <xsl:apply-templates/>
+</xsl:template>
+
+<xsl:template match="description">
+ <h1>Description</h1>
+ <xsl:apply-templates/>
+</xsl:template>
+
+<xsl:template match="options">
+ <h1>Options</h1>
+ <xsl:apply-templates/>
+</xsl:template>
+
+<xsl:template match="section">
+ <h1><xsl:value-of select="@name"/></h1>
+ <xsl:apply-templates/>
+</xsl:template>
+
+<xsl:template match="option">
+ <div class="option"><xsl:apply-templates/></div>
+</xsl:template>
+
+<xsl:template match="manref">
+ <xsl:choose>
+ <xsl:when test="string-length(@href) &gt; 0">
+ <a class="manref"><xsl:attribute name="href"><xsl:value-of select="@href"/></xsl:attribute><xsl:value-of select="@name"/>(<xsl:value-of select="@section"/>)</a>
+ </xsl:when>
+ <xsl:otherwise>
+ <span class="manref"><xsl:value-of select="@name"/>(<xsl:value-of select="@section"/>)</span>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<xsl:template match="url">
+ <a class="url"><xsl:attribute name="href"><xsl:value-of select="@href"/></xsl:attribute><xsl:value-of select="@href"/></a>
+</xsl:template>
+
+</xsl:stylesheet>
diff --git a/pulseaudio-text.svg b/pulseaudio-text.svg
new file mode 100644
index 00000000..0e126130
--- /dev/null
+++ b/pulseaudio-text.svg
@@ -0,0 +1,388 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://web.resource.org/cc/"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="48px"
+ height="48px"
+ id="svg2161"
+ sodipodi:version="0.32"
+ inkscape:version="0.45"
+ sodipodi:docbase="/home/lennart/projects/pulseaudio"
+ sodipodi:docname="pulseaudio.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ sodipodi:modified="TRUE">
+ <defs
+ id="defs2163">
+ <linearGradient
+ id="linearGradient3093">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop3095" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop3097" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3093"
+ id="radialGradient2472"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,0.266476,0,283.9565)"
+ cx="224.5"
+ cy="387.11252"
+ fx="224.5"
+ fy="387.11252"
+ r="174.5" />
+ <linearGradient
+ id="linearGradient2503">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop2505" />
+ <stop
+ style="stop-color:#141413;stop-opacity:1;"
+ offset="1"
+ id="stop2507" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2503"
+ id="linearGradient1476"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.124741,0,0,0.124741,-49.78411,-8.952609)"
+ x1="585"
+ y1="390.61252"
+ x2="585"
+ y2="85.376541" />
+ <linearGradient
+ id="linearGradient2495">
+ <stop
+ style="stop-color:#0a0a09;stop-opacity:1;"
+ offset="0"
+ id="stop2497" />
+ <stop
+ style="stop-color:#282927;stop-opacity:1;"
+ offset="1"
+ id="stop2499" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2495"
+ id="linearGradient1474"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.124741,0,0,0.124741,-49.78411,-8.952609)"
+ x1="674"
+ y1="276.11252"
+ x2="505"
+ y2="199.11252" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2535"
+ id="linearGradient2399"
+ gradientUnits="userSpaceOnUse"
+ x1="585"
+ y1="390.61252"
+ x2="585"
+ y2="85.376541" />
+ <linearGradient
+ id="linearGradient2535">
+ <stop
+ id="stop2537"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:0.36078432;" />
+ <stop
+ id="stop2539"
+ offset="1"
+ style="stop-color:#ffffff;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2535"
+ id="linearGradient2397"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(-291.933,627.3998)"
+ x1="532"
+ y1="131.40625"
+ x2="667.5"
+ y2="357.40625" />
+ <linearGradient
+ id="linearGradient3072">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3074" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop3076" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3072"
+ id="linearGradient2395"
+ gradientUnits="userSpaceOnUse"
+ x1="585"
+ y1="76.360481"
+ x2="585"
+ y2="170.3912" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3093"
+ id="radialGradient2234"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,0.266476,0,283.9565)"
+ cx="224.5"
+ cy="387.11252"
+ fx="224.5"
+ fy="387.11252"
+ r="174.5" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2495"
+ id="linearGradient2236"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.124741,0,0,0.124741,-49.78411,-8.952609)"
+ x1="674"
+ y1="276.11252"
+ x2="505"
+ y2="199.11252" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2503"
+ id="linearGradient2238"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.124741,0,0,0.124741,-49.78411,-8.952609)"
+ x1="585"
+ y1="390.61252"
+ x2="585"
+ y2="85.376541" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3072"
+ id="linearGradient2240"
+ gradientUnits="userSpaceOnUse"
+ x1="585"
+ y1="76.360481"
+ x2="585"
+ y2="170.3912" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2535"
+ id="linearGradient2242"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(-291.933,627.3998)"
+ x1="532"
+ y1="131.40625"
+ x2="667.5"
+ y2="357.40625" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2535"
+ id="linearGradient2244"
+ gradientUnits="userSpaceOnUse"
+ x1="585"
+ y1="390.61252"
+ x2="585"
+ y2="85.376541" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2495"
+ id="linearGradient2255"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.124741,0,0,0.124741,-49.78411,-8.952609)"
+ x1="674"
+ y1="276.11252"
+ x2="505"
+ y2="199.11252" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2503"
+ id="linearGradient2257"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.124741,0,0,0.124741,-49.78411,-8.952609)"
+ x1="585"
+ y1="390.61252"
+ x2="585"
+ y2="85.376541" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3093"
+ id="radialGradient2260"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,0.266476,-145.39702,-74.948037)"
+ cx="224.5"
+ cy="387.11252"
+ fx="224.5"
+ fy="387.11252"
+ r="174.5" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="4.9497475"
+ inkscape:cx="16.230436"
+ inkscape:cy="-2.4336194"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="2043"
+ inkscape:window-height="794"
+ inkscape:window-x="180"
+ inkscape:window-y="140"
+ showguides="true"
+ inkscape:guide-bbox="true" />
+ <metadata
+ id="metadata2166">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <cc:license
+ rdf:resource="http://www.gnu.org/copyleft/gpl.html" />
+ <dc:title>PulseAudio logotype</dc:title>
+ <dc:date>2006-08-28</dc:date>
+ <dc:creator>
+ <cc:Agent>
+ <dc:title>Pierre Ossman &lt;ossman@cendio.se&gt; for Cendio AB</dc:title>
+ </cc:Agent>
+ </dc:creator>
+ <dc:rights>
+ <cc:Agent>
+ <dc:title />
+ </cc:Agent>
+ </dc:rights>
+ <dc:contributor>
+ <cc:Agent>
+ <dc:title>Rafael Jannone (basic idea)</dc:title>
+ </cc:Agent>
+ </dc:contributor>
+ </cc:Work>
+ <cc:License
+ rdf:about="http://creativecommons.org/licenses/GPL/2.0/">
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/Reproduction" />
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/Distribution" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/Notice" />
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/DerivativeWorks" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/ShareAlike" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/SourceCode" />
+ </cc:License>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <rect
+ ry="6.5049205"
+ y="2.2257283"
+ x="5.4760308"
+ height="37.047943"
+ width="37.047943"
+ id="rect2371"
+ style="fill:url(#linearGradient2255);fill-opacity:1;stroke:url(#linearGradient2257);stroke-width:0.99792439;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ inkscape:export-filename="/home/lennart/test.png"
+ inkscape:export-xdpi="165.5896"
+ inkscape:export-ydpi="165.5896" />
+ <g
+ transform="matrix(0.124741,0,0,0.124741,-61.69688,-99.94425)"
+ id="g2415"
+ inkscape:export-filename="/home/lennart/test.png"
+ inkscape:export-xdpi="165.5896"
+ inkscape:export-ydpi="165.5896">
+ <path
+ sodipodi:type="arc"
+ style="opacity:1;fill:#729fcf;fill-opacity:1;stroke:none;stroke-width:8;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="path2417"
+ sodipodi:cx="-1"
+ sodipodi:cy="863.61249"
+ sodipodi:rx="23"
+ sodipodi:ry="23"
+ d="M 22 863.61249 A 23 23 0 1 1 -24,863.61249 A 23 23 0 1 1 22 863.61249 z"
+ transform="matrix(1.676363,0,0,1.676363,688.6772,-480.168)" />
+ <path
+ style="opacity:1;fill:#729fcf;fill-opacity:1;stroke:none;stroke-width:8;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 666.92273,892.01313 C 633.50485,900.88553 608.86021,931.34683 608.86023,967.54442 C 608.86023,1003.7419 633.50486,1034.2345 666.92273,1043.1069 C 642.81497,1032.2877 625.48523,1002.5195 625.48523,967.54442 C 625.48522,932.56943 642.81496,902.83233 666.92273,892.01313 z M 707.07898,892.01313 C 731.18675,902.83233 748.51648,932.56933 748.51648,967.54442 C 748.51648,1002.5195 731.18674,1032.2877 707.07898,1043.1069 C 740.49686,1034.2345 765.1415,1003.7419 765.14148,967.54442 C 765.14148,931.34693 740.49687,900.88553 707.07898,892.01313 z "
+ id="path2419" />
+ <path
+ id="path2421"
+ d="M 655.64705,849.58672 C 603.46201,863.44178 564.97718,911.00985 564.97721,967.53562 C 564.97721,1024.0613 603.46203,1071.6783 655.64705,1085.5333 C 618.0006,1068.6381 590.93865,1022.1524 590.93865,967.53562 C 590.93863,912.91905 618.00059,866.48188 655.64705,849.58672 z M 718.35466,849.58672 C 756.00112,866.48188 783.06306,912.91889 783.06306,967.53562 C 783.06306,1022.1524 756.00111,1068.6381 718.35466,1085.5333 C 770.5397,1071.6783 809.02453,1024.0613 809.0245,967.53562 C 809.0245,911.01001 770.53972,863.44178 718.35466,849.58672 z "
+ style="opacity:1;fill:#729fcf;fill-opacity:1;stroke:none;stroke-width:8;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </g>
+ <g
+ id="g1494"
+ transform="matrix(0.124741,0,0,0.124741,-13.36814,-87.21636)"
+ inkscape:export-filename="/home/lennart/test.png"
+ inkscape:export-xdpi="165.5896"
+ inkscape:export-ydpi="165.5896">
+ <path
+ inkscape:export-ydpi="44.099998"
+ inkscape:export-xdpi="44.099998"
+ inkscape:export-filename="/home/ossman/Desktop/pa4.png"
+ d="M 495.15625,93.84375 C 468.52243,93.84375 447.21875,115.11921 447.21875,141.75 L 447.21875,334.46875 C 447.21875,361.09954 468.52545,382.40625 495.15625,382.40625 L 687.84375,382.40625 C 714.47454,382.40625 735.78125,361.09955 735.78125,334.46875 L 735.78125,141.75 C 735.78125,115.11921 714.47755,93.84375 687.84375,93.84375 L 495.15625,93.84375 z "
+ id="path2373"
+ style="fill:url(#linearGradient2240);fill-opacity:1;stroke:none;stroke-width:8;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ inkscape:original="M 495.15625 89.625 C 466.26648 89.625 443 112.86023 443 141.75 L 443 334.46875 C 443 363.35852 466.26647 386.625 495.15625 386.625 L 687.84375 386.625 C 716.73352 386.625 740 363.35853 740 334.46875 L 740 141.75 C 740 112.86023 716.7335 89.625 687.84375 89.625 L 495.15625 89.625 z "
+ inkscape:radius="-4.2074337"
+ sodipodi:type="inkscape:offset"
+ transform="translate(-291.933,627.3998)" />
+ <path
+ inkscape:export-ydpi="44.099998"
+ inkscape:export-xdpi="44.099998"
+ inkscape:export-filename="/home/ossman/Desktop/pa4.png"
+ style="fill:url(#linearGradient2242);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 436.05138,821.4365 C 397.62524,862.62866 358.12861,865.874 299.93097,865.874 C 242.63828,865.874 199.11564,893.22114 163.06701,927.96775 L 163.06701,961.86851 C 163.06701,985.0884 181.12504,1001.9935 203.22326,1001.9935 L 395.91076,1006.0248 C 420.50531,1006.0248 436.03576,986.46307 436.03576,961.86851 L 436.05138,821.4365 z "
+ id="path2375"
+ sodipodi:nodetypes="cscccccc" />
+ <path
+ inkscape:export-ydpi="44.099998"
+ inkscape:export-xdpi="44.099998"
+ inkscape:export-filename="/home/ossman/Desktop/pa4.png"
+ sodipodi:type="inkscape:offset"
+ inkscape:radius="-8"
+ inkscape:original="M 495.15625 89.625 C 466.26648 89.625 443 112.86023 443 141.75 L 443 334.46875 C 443 363.35852 466.26647 386.625 495.15625 386.625 L 687.84375 386.625 C 716.73352 386.625 740 363.35853 740 334.46875 L 740 141.75 C 740 112.86023 716.7335 89.625 687.84375 89.625 L 495.15625 89.625 z "
+ style="fill:none;fill-opacity:1;stroke:url(#linearGradient2244);stroke-width:8;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="path2377"
+ d="M 495.15625,97.625 C 470.55593,97.625 451,117.15545 451,141.75 L 451,334.46875 C 451,359.0633 470.56169,378.625 495.15625,378.625 L 687.84375,378.625 C 712.4383,378.625 732,359.06331 732,334.46875 L 732,141.75 C 732,117.15545 712.44405,97.625 687.84375,97.625 L 495.15625,97.625 z "
+ transform="translate(-291.933,627.3998)" />
+ </g>
+ <text
+ xml:space="preserve"
+ style="font-size:30.33161926px;font-style:normal;font-weight:normal;line-height:125%;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1pt;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="49.689053"
+ y="37.570499"
+ id="text2188"
+ sodipodi:linespacing="125%"
+ inkscape:export-filename="/home/lennart/test.png"
+ inkscape:export-xdpi="165.5896"
+ inkscape:export-ydpi="165.5896"><tspan
+ sodipodi:role="line"
+ id="tspan2190"
+ x="49.689053"
+ y="37.570499"
+ style="font-size:30.33161926px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;font-family:Tuffy"><tspan
+ style="font-size:30.33161926px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;font-family:Tuffy"
+ id="tspan2196">Pulse</tspan>Audio</tspan></text>
+ </g>
+</svg>
diff --git a/pulseaudio.svg b/pulseaudio.svg
new file mode 100644
index 00000000..a79e03da
--- /dev/null
+++ b/pulseaudio.svg
@@ -0,0 +1,287 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://web.resource.org/cc/"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="48px"
+ height="48px"
+ id="svg2161"
+ sodipodi:version="0.32"
+ inkscape:version="0.44"
+ sodipodi:docbase="/home/ossman/devel/pulseaudio"
+ sodipodi:docname="pulseaudio.svg">
+ <defs
+ id="defs2163">
+ <linearGradient
+ id="linearGradient3093">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop3095" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop3097" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3093"
+ id="radialGradient2472"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,0.266476,0,283.9565)"
+ cx="224.5"
+ cy="387.11252"
+ fx="224.5"
+ fy="387.11252"
+ r="174.5" />
+ <linearGradient
+ id="linearGradient2503">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop2505" />
+ <stop
+ style="stop-color:#141413;stop-opacity:1;"
+ offset="1"
+ id="stop2507" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2503"
+ id="linearGradient1476"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.124741,0,0,0.124741,-49.78411,-8.952609)"
+ x1="585"
+ y1="390.61252"
+ x2="585"
+ y2="85.376541" />
+ <linearGradient
+ id="linearGradient2495">
+ <stop
+ style="stop-color:#0a0a09;stop-opacity:1;"
+ offset="0"
+ id="stop2497" />
+ <stop
+ style="stop-color:#282927;stop-opacity:1;"
+ offset="1"
+ id="stop2499" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2495"
+ id="linearGradient1474"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.124741,0,0,0.124741,-49.78411,-8.952609)"
+ x1="674"
+ y1="276.11252"
+ x2="505"
+ y2="199.11252" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2535"
+ id="linearGradient2399"
+ gradientUnits="userSpaceOnUse"
+ x1="585"
+ y1="390.61252"
+ x2="585"
+ y2="85.376541" />
+ <linearGradient
+ id="linearGradient2535">
+ <stop
+ id="stop2537"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:0.36078432;" />
+ <stop
+ id="stop2539"
+ offset="1"
+ style="stop-color:#ffffff;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2535"
+ id="linearGradient2397"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(-291.933,627.3998)"
+ x1="532"
+ y1="131.40625"
+ x2="667.5"
+ y2="357.40625" />
+ <linearGradient
+ id="linearGradient3072">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3074" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop3076" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3072"
+ id="linearGradient2395"
+ gradientUnits="userSpaceOnUse"
+ x1="585"
+ y1="76.360481"
+ x2="585"
+ y2="170.3912" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="4.9497475"
+ inkscape:cx="20.060638"
+ inkscape:cy="18.992734"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="872"
+ inkscape:window-height="624"
+ inkscape:window-x="325"
+ inkscape:window-y="224" />
+ <metadata
+ id="metadata2166">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <cc:license
+ rdf:resource="http://www.gnu.org/copyleft/gpl.html" />
+ <dc:title>PulseAudio logotype</dc:title>
+ <dc:date>2006-08-28</dc:date>
+ <dc:creator>
+ <cc:Agent>
+ <dc:title>Pierre Ossman &lt;ossman@cendio.se&gt; for Cendio AB</dc:title>
+ </cc:Agent>
+ </dc:creator>
+ <dc:rights>
+ <cc:Agent>
+ <dc:title></dc:title>
+ </cc:Agent>
+ </dc:rights>
+ <dc:contributor>
+ <cc:Agent>
+ <dc:title>Rafael Jannone (basic idea)</dc:title>
+ </cc:Agent>
+ </dc:contributor>
+ </cc:Work>
+ <cc:License
+ rdf:about="http://creativecommons.org/licenses/GPL/2.0/">
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/Reproduction" />
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/Distribution" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/Notice" />
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/DerivativeWorks" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/ShareAlike" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/SourceCode" />
+ </cc:License>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <path
+ sodipodi:type="arc"
+ style="opacity:0.36679538;color:black;fill:url(#radialGradient2472);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:38.81499863;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ id="path2470"
+ sodipodi:cx="224.5"
+ sodipodi:cy="387.11252"
+ sodipodi:rx="174.5"
+ sodipodi:ry="46.5"
+ d="M 399 387.11252 A 174.5 46.5 0 1 1 50,387.11252 A 174.5 46.5 0 1 1 399 387.11252 z"
+ transform="matrix(0.137443,0,0,0.154237,-6.855952,-20.43595)"
+ inkscape:export-filename="/home/ossman/Desktop/pa4.png"
+ inkscape:export-xdpi="44.099998"
+ inkscape:export-ydpi="44.099998" />
+ <rect
+ ry="6.5049205"
+ y="2.2257283"
+ x="5.4760308"
+ height="37.047943"
+ width="37.047943"
+ id="rect2371"
+ style="fill:url(#linearGradient1474);fill-opacity:1;stroke:url(#linearGradient1476);stroke-width:0.99792439;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ inkscape:export-filename="/home/ossman/Desktop/pa4.png"
+ inkscape:export-xdpi="44.099998"
+ inkscape:export-ydpi="44.099998" />
+ <g
+ transform="matrix(0.124741,0,0,0.124741,-61.69688,-99.94425)"
+ id="g2415"
+ inkscape:export-filename="/home/ossman/Desktop/pa4.png"
+ inkscape:export-xdpi="44.099998"
+ inkscape:export-ydpi="44.099998">
+ <path
+ sodipodi:type="arc"
+ style="opacity:1;fill:#729fcf;fill-opacity:1;stroke:none;stroke-width:8;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="path2417"
+ sodipodi:cx="-1"
+ sodipodi:cy="863.61249"
+ sodipodi:rx="23"
+ sodipodi:ry="23"
+ d="M 22 863.61249 A 23 23 0 1 1 -24,863.61249 A 23 23 0 1 1 22 863.61249 z"
+ transform="matrix(1.676363,0,0,1.676363,688.6772,-480.168)" />
+ <path
+ style="opacity:1;fill:#729fcf;fill-opacity:1;stroke:none;stroke-width:8;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 666.92273,892.01313 C 633.50485,900.88553 608.86021,931.34683 608.86023,967.54442 C 608.86023,1003.7419 633.50486,1034.2345 666.92273,1043.1069 C 642.81497,1032.2877 625.48523,1002.5195 625.48523,967.54442 C 625.48522,932.56943 642.81496,902.83233 666.92273,892.01313 z M 707.07898,892.01313 C 731.18675,902.83233 748.51648,932.56933 748.51648,967.54442 C 748.51648,1002.5195 731.18674,1032.2877 707.07898,1043.1069 C 740.49686,1034.2345 765.1415,1003.7419 765.14148,967.54442 C 765.14148,931.34693 740.49687,900.88553 707.07898,892.01313 z "
+ id="path2419" />
+ <path
+ id="path2421"
+ d="M 655.64705,849.58672 C 603.46201,863.44178 564.97718,911.00985 564.97721,967.53562 C 564.97721,1024.0613 603.46203,1071.6783 655.64705,1085.5333 C 618.0006,1068.6381 590.93865,1022.1524 590.93865,967.53562 C 590.93863,912.91905 618.00059,866.48188 655.64705,849.58672 z M 718.35466,849.58672 C 756.00112,866.48188 783.06306,912.91889 783.06306,967.53562 C 783.06306,1022.1524 756.00111,1068.6381 718.35466,1085.5333 C 770.5397,1071.6783 809.02453,1024.0613 809.0245,967.53562 C 809.0245,911.01001 770.53972,863.44178 718.35466,849.58672 z "
+ style="opacity:1;fill:#729fcf;fill-opacity:1;stroke:none;stroke-width:8;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </g>
+ <g
+ id="g1494"
+ transform="matrix(0.124741,0,0,0.124741,-13.36814,-87.21636)">
+ <path
+ inkscape:export-ydpi="44.099998"
+ inkscape:export-xdpi="44.099998"
+ inkscape:export-filename="/home/ossman/Desktop/pa4.png"
+ d="M 495.15625,93.84375 C 468.52243,93.84375 447.21875,115.11921 447.21875,141.75 L 447.21875,334.46875 C 447.21875,361.09954 468.52545,382.40625 495.15625,382.40625 L 687.84375,382.40625 C 714.47454,382.40625 735.78125,361.09955 735.78125,334.46875 L 735.78125,141.75 C 735.78125,115.11921 714.47755,93.84375 687.84375,93.84375 L 495.15625,93.84375 z "
+ id="path2373"
+ style="fill:url(#linearGradient2395);fill-opacity:1;stroke:none;stroke-width:8;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ inkscape:original="M 495.15625 89.625 C 466.26648 89.625 443 112.86023 443 141.75 L 443 334.46875 C 443 363.35852 466.26647 386.625 495.15625 386.625 L 687.84375 386.625 C 716.73352 386.625 740 363.35853 740 334.46875 L 740 141.75 C 740 112.86023 716.7335 89.625 687.84375 89.625 L 495.15625 89.625 z "
+ inkscape:radius="-4.2074337"
+ sodipodi:type="inkscape:offset"
+ transform="translate(-291.933,627.3998)" />
+ <path
+ inkscape:export-ydpi="44.099998"
+ inkscape:export-xdpi="44.099998"
+ inkscape:export-filename="/home/ossman/Desktop/pa4.png"
+ style="fill:url(#linearGradient2397);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 436.05138,821.4365 C 397.62524,862.62866 358.12861,865.874 299.93097,865.874 C 242.63828,865.874 199.11564,893.22114 163.06701,927.96775 L 163.06701,961.86851 C 163.06701,985.0884 181.12504,1001.9935 203.22326,1001.9935 L 395.91076,1006.0248 C 420.50531,1006.0248 436.03576,986.46307 436.03576,961.86851 L 436.05138,821.4365 z "
+ id="path2375"
+ sodipodi:nodetypes="cscccccc" />
+ <path
+ inkscape:export-ydpi="44.099998"
+ inkscape:export-xdpi="44.099998"
+ inkscape:export-filename="/home/ossman/Desktop/pa4.png"
+ sodipodi:type="inkscape:offset"
+ inkscape:radius="-8"
+ inkscape:original="M 495.15625 89.625 C 466.26648 89.625 443 112.86023 443 141.75 L 443 334.46875 C 443 363.35852 466.26647 386.625 495.15625 386.625 L 687.84375 386.625 C 716.73352 386.625 740 363.35853 740 334.46875 L 740 141.75 C 740 112.86023 716.7335 89.625 687.84375 89.625 L 495.15625 89.625 z "
+ style="fill:none;fill-opacity:1;stroke:url(#linearGradient2399);stroke-width:8;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="path2377"
+ d="M 495.15625,97.625 C 470.55593,97.625 451,117.15545 451,141.75 L 451,334.46875 C 451,359.0633 470.56169,378.625 495.15625,378.625 L 687.84375,378.625 C 712.4383,378.625 732,359.06331 732,334.46875 L 732,141.75 C 732,117.15545 712.44405,97.625 687.84375,97.625 L 495.15625,97.625 z "
+ transform="translate(-291.933,627.3998)" />
+ </g>
+ </g>
+</svg>
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;
+}
diff --git a/todo b/todo
new file mode 100644
index 00000000..f579bebe
--- /dev/null
+++ b/todo
@@ -0,0 +1,59 @@
+*** $Id$ ***
+
+Build System:
+- Remove symdef files and use macros (like most other projects)
+- Use own name mangling scheme instead of ltdl's, which will eliminate the
+ need for .la files or extra trickery.
+
+Porting:
+- rtp module ported to Win32 (sendmsg/recvmsg emulation)
+
+I18N:
+- iconv stuff sent from utils to server (UTF-8)
+- iconv sample loading in server
+- Document utf8.h, timeval.h and util.h
+- gettextify pulseaudio
+
+Cleanups:
+- drop dependency of libpolyp on libX11, instead use an external mini binary
+- module-tunnel: improve latency calculation
+- use software volume when hardware doesn't support all channels (alsa done)
+- using POSIX monotonous clocks wherever possible instead of gettimeofday()
+
+Test:
+- autoload
+
+Auth/Crypto:
+- ssl
+- key rings for auth
+- challenge response auth
+- sasl auth
+
+Features:
+- alsa driver with hw mixing
+- "window manager for sound"
+- chroot()
+- use scatter/gather io for sockets
+- CODECs to reduce bandwidth usage (plug-in based)
+- multiline configuration statements
+- paplay needs to set a channel map. our default is only correct for AIFF.
+ (we need help from libsndfile for this)
+- Fix a way for the threading API to handle state and subscription callbacks
+ in a nice way.
+- examine if it is possible to mimic esd's handling of half duplex cards
+ (switch to capture when a recording client connects and drop playback during
+ that time)
+- Support for device selection in waveout driver
+- add an API to libpulse for allocating memory from the pa_context memory pool
+- allow buffer metric changes during runtime
+- better ".include" command in configuration files. should have glob support.
+- recursive .if
+
+Long term:
+- pass meta info for hearing impaired
+- X11: support for the X11 synchronization extension
+
+Backends for:
+- portaudio (semi-done)
+- sdl
+- xine (needs update)